@ -1,5 +1,10 @@
/*
* WARNING : be careful changing this code , it is very timing dependent
*
* 2018 - 10 - 28 checked
* avr - gcc 4.9 .2
* avr - gcc 5.4 .0
* avr - gcc 7.3 .0
*/
# ifndef F_CPU
@ -9,17 +14,19 @@
# include <avr/io.h>
# include <avr/interrupt.h>
# include <util/delay.h>
# include <stddef.h>
# include <stdbool.h>
# include "serial.h"
//#include <pro_micro.h>
# ifdef SOFT_SERIAL_PIN
# ifdef __AVR_ATmega32U4__
// if using ATmega32U4 I2C, can not use PD0 and PD1 in soft serial.
# ifdef USE_AVR_I2C
# if SOFT_SERIAL_PIN == D0 || SOFT_SERIAL_PIN == D1
# error Using ATmega32U4 I2C, so can not use PD0, PD1
# endif
# if SOFT_SERIAL_PIN == D0 || SOFT_SERIAL_PIN == D1
# error Using ATmega32U4 I2C, so can not use PD0, PD1
# endif
# endif
# if SOFT_SERIAL_PIN >= D0 && SOFT_SERIAL_PIN <= D3
@ -63,215 +70,477 @@
# error serial.c now support ATmega32U4 only
# endif
// Serial pulse period in microseconds. Its probably a bad idea to lower this
// value.
# define SERIAL_DELAY 24
# define ALWAYS_INLINE __attribute__((always_inline))
# define NO_INLINE __attribute__((noinline))
# define _delay_sub_us(x) __builtin_avr_delay_cycles(x)
// parity check
# define ODD_PARITY 1
# define EVEN_PARITY 0
# define PARITY EVEN_PARITY
# ifdef SERIAL_DELAY
// custom setup in config.h
// #define TID_SEND_ADJUST 2
// #define SERIAL_DELAY 6 // micro sec
// #define READ_WRITE_START_ADJUST 30 // cycles
// #define READ_WRITE_WIDTH_ADJUST 8 // cycles
# else
// ============ Standard setups ============
# ifndef SELECT_SOFT_SERIAL_SPEED
# define SELECT_SOFT_SERIAL_SPEED 1
// 0: about 189kbps (Experiment only)
// 1: about 137kbps (default)
// 2: about 75kbps
// 3: about 39kbps
// 4: about 26kbps
// 5: about 20kbps
# endif
uint8_t volatile serial_slave_buffer [ SERIAL_SLAVE_BUFFER_LENGTH ] = { 0 } ;
uint8_t volatile serial_master_buffer [ SERIAL_MASTER_BUFFER_LENGTH ] = { 0 } ;
# if __GNUC__ < 6
# define TID_SEND_ADJUST 14
# else
# define TID_SEND_ADJUST 2
# endif
# define SLAVE_DATA_CORRUPT (1<<0)
volatile uint8_t status = 0 ;
# if SELECT_SOFT_SERIAL_SPEED == 0
// Very High speed
# define SERIAL_DELAY 4 // micro sec
# if __GNUC__ < 6
# define READ_WRITE_START_ADJUST 33 // cycles
# define READ_WRITE_WIDTH_ADJUST 3 // cycles
# else
# define READ_WRITE_START_ADJUST 34 // cycles
# define READ_WRITE_WIDTH_ADJUST 7 // cycles
# endif
# elif SELECT_SOFT_SERIAL_SPEED == 1
// High speed
# define SERIAL_DELAY 6 // micro sec
# if __GNUC__ < 6
# define READ_WRITE_START_ADJUST 30 // cycles
# define READ_WRITE_WIDTH_ADJUST 3 // cycles
# else
# define READ_WRITE_START_ADJUST 33 // cycles
# define READ_WRITE_WIDTH_ADJUST 7 // cycles
# endif
# elif SELECT_SOFT_SERIAL_SPEED == 2
// Middle speed
# define SERIAL_DELAY 12 // micro sec
# define READ_WRITE_START_ADJUST 30 // cycles
# if __GNUC__ < 6
# define READ_WRITE_WIDTH_ADJUST 3 // cycles
# else
# define READ_WRITE_WIDTH_ADJUST 7 // cycles
# endif
# elif SELECT_SOFT_SERIAL_SPEED == 3
// Low speed
# define SERIAL_DELAY 24 // micro sec
# define READ_WRITE_START_ADJUST 30 // cycles
# if __GNUC__ < 6
# define READ_WRITE_WIDTH_ADJUST 3 // cycles
# else
# define READ_WRITE_WIDTH_ADJUST 7 // cycles
# endif
# elif SELECT_SOFT_SERIAL_SPEED == 4
// Very Low speed
# define SERIAL_DELAY 36 // micro sec
# define READ_WRITE_START_ADJUST 30 // cycles
# if __GNUC__ < 6
# define READ_WRITE_WIDTH_ADJUST 3 // cycles
# else
# define READ_WRITE_WIDTH_ADJUST 7 // cycles
# endif
# elif SELECT_SOFT_SERIAL_SPEED == 5
// Ultra Low speed
# define SERIAL_DELAY 48 // micro sec
# define READ_WRITE_START_ADJUST 30 // cycles
# if __GNUC__ < 6
# define READ_WRITE_WIDTH_ADJUST 3 // cycles
# else
# define READ_WRITE_WIDTH_ADJUST 7 // cycles
# endif
# else
# error invalid SELECT_SOFT_SERIAL_SPEED value
# endif /* SELECT_SOFT_SERIAL_SPEED */
# endif /* SERIAL_DELAY */
# define SERIAL_DELAY_HALF1 (SERIAL_DELAY / 2)
# define SERIAL_DELAY_HALF2 (SERIAL_DELAY - SERIAL_DELAY / 2)
# define SLAVE_INT_WIDTH_US 1
# ifndef SERIAL_USE_MULTI_TRANSACTION
# define SLAVE_INT_RESPONSE_TIME SERIAL_DELAY
# else
# define SLAVE_INT_ACK_WIDTH_UNIT 2
# define SLAVE_INT_ACK_WIDTH 4
# endif
static SSTD_t * Transaction_table = NULL ;
static uint8_t Transaction_table_size = 0 ;
inline static void serial_delay ( void ) ALWAYS_INLINE ;
inline static
void serial_delay ( void ) {
_delay_us ( SERIAL_DELAY ) ;
}
inline static void serial_delay_half1 ( void ) ALWAYS_INLINE ;
inline static
void serial_delay_half1 ( void ) {
_delay_us ( SERIAL_DELAY_HALF1 ) ;
}
inline static void serial_delay_half2 ( void ) ALWAYS_INLINE ;
inline static
void serial_delay_half2 ( void ) {
_delay_us ( SERIAL_DELAY_HALF2 ) ;
}
inline static void serial_output ( void ) ALWAYS_INLINE ;
inline static
void serial_output ( void ) {
SERIAL_PIN_DDR | = SERIAL_PIN_MASK ;
}
// make the serial pin an input with pull-up resistor
inline static void serial_input_with_pullup ( void ) ALWAYS_INLINE ;
inline static
void serial_input ( void ) {
void serial_input _with_pullup ( void ) {
SERIAL_PIN_DDR & = ~ SERIAL_PIN_MASK ;
SERIAL_PIN_PORT | = SERIAL_PIN_MASK ;
}
inline static uint8_t serial_read_pin ( void ) ALWAYS_INLINE ;
inline static
uint8_t serial_read_pin ( void ) {
return ! ! ( SERIAL_PIN_INPUT & SERIAL_PIN_MASK ) ;
}
inline static void serial_low ( void ) ALWAYS_INLINE ;
inline static
void serial_low ( void ) {
SERIAL_PIN_PORT & = ~ SERIAL_PIN_MASK ;
}
inline static void serial_high ( void ) ALWAYS_INLINE ;
inline static
void serial_high ( void ) {
SERIAL_PIN_PORT | = SERIAL_PIN_MASK ;
}
void serial_master_init ( void ) {
serial_output ( ) ;
serial_high ( ) ;
void soft_serial_initiator_init ( SSTD_t * sstd_table , int sstd_table_size )
{
Transaction_table = sstd_table ;
Transaction_table_size = ( uint8_t ) sstd_table_size ;
serial_output ( ) ;
serial_high ( ) ;
}
void serial_slave_init ( void ) {
serial_input ( ) ;
// Enable INT0
EIMSK | = _BV ( INT0 ) ;
// Trigger on falling edge of INT0
EICRA & = ~ ( _BV ( ISC00 ) | _BV ( ISC01 ) ) ;
void soft_serial_target_init ( SSTD_t * sstd_table , int sstd_table_size )
{
Transaction_table = sstd_table ;
Transaction_table_size = ( uint8_t ) sstd_table_size ;
serial_input_with_pullup ( ) ;
// Enable INT0-INT3,INT6
EIMSK | = EIMSK_BIT ;
# if SERIAL_PIN_MASK == _BV(PE6)
// Trigger on falling edge of INT6
EICRB & = EICRx_BIT ;
# else
// Trigger on falling edge of INT0-INT3
EICRA & = EICRx_BIT ;
# endif
}
// Used by the master to synchronize timing with the slave.
// Used by the sender to synchronize timing with the reciver.
static void sync_recv ( void ) NO_INLINE ;
static
void sync_recv ( void ) {
serial_input ( ) ;
// This shouldn't hang if the slave disconnects because the
// serial line will float to high if the slave does disconnect.
for ( uint8_t i = 0 ; i < SERIAL_DELAY * 5 & & serial_read_pin ( ) ; i + + ) {
}
// This shouldn't hang if the target disconnects because the
// serial line will float to high if the target does disconnect.
while ( ! serial_read_pin ( ) ) ;
serial_delay ( ) ;
}
// Used by the slave to send a synchronization signal to the master.
// Used by the reciver to send a synchronization signal to the sender.
static void sync_send ( void ) NO_INLINE ;
static
void sync_send ( void ) {
serial_output ( ) ;
serial_low ( ) ;
serial_delay ( ) ;
serial_high ( ) ;
}
// Reads a byte from the serial line
static
uint8_t serial_read_byte ( void ) {
uint8_t byte = 0 ;
serial_input ( ) ;
for ( uint8_t i = 0 ; i < 8 ; + + i ) {
byte = ( byte < < 1 ) | serial_read_pin ( ) ;
serial_delay ( ) ;
_delay_us ( 1 ) ;
static uint8_t serial_read_chunk ( uint8_t * pterrcount , uint8_t bit ) NO_INLINE ;
static uint8_t serial_read_chunk ( uint8_t * pterrcount , uint8_t bit ) {
uint8_t byte , i , p , pb ;
_delay_sub_us ( READ_WRITE_START_ADJUST ) ;
for ( i = 0 , byte = 0 , p = PARITY ; i < bit ; i + + ) {
serial_delay_half1 ( ) ; // read the middle of pulses
if ( serial_read_pin ( ) ) {
byte = ( byte < < 1 ) | 1 ; p ^ = 1 ;
} else {
byte = ( byte < < 1 ) | 0 ; p ^ = 0 ;
}
_delay_sub_us ( READ_WRITE_WIDTH_ADJUST ) ;
serial_delay_half2 ( ) ;
}
/* recive parity bit */
serial_delay_half1 ( ) ; // read the middle of pulses
pb = serial_read_pin ( ) ;
_delay_sub_us ( READ_WRITE_WIDTH_ADJUST ) ;
serial_delay_half2 ( ) ;
* pterrcount + = ( p ! = pb ) ? 1 : 0 ;
return byte ;
}
// Sends a byte with MSB ordering
static
void serial_write_byte ( uint8_t data ) {
uint8_t b = 8 ;
serial_output ( ) ;
while ( b - - ) {
if ( data & ( 1 < < b ) ) {
serial_high ( ) ;
} else {
serial_low ( ) ;
void serial_write_chunk ( uint8_t data , uint8_t bit ) NO_INLINE ;
void serial_write_chunk ( uint8_t data , uint8_t bit ) {
uint8_t b , p ;
for ( p = PARITY , b = 1 < < ( bit - 1 ) ; b ; b > > = 1 ) {
if ( data & b ) {
serial_high ( ) ; p ^ = 1 ;
} else {
serial_low ( ) ; p ^ = 0 ;
}
serial_delay ( ) ;
}
/* send parity bit */
if ( p & 1 ) { serial_high ( ) ; }
else { serial_low ( ) ; }
serial_delay ( ) ;
}
}
// interrupt handle to be used by the slave device
ISR ( SERIAL_PIN_INTERRUPT ) {
sync_send ( ) ;
serial_low ( ) ; // sync_send() / senc_recv() need raise edge
}
uint8_t checksum = 0 ;
for ( int i = 0 ; i < SERIAL_SLAVE_BUFFER_LENGTH ; + + i ) {
serial_write_byte ( serial_slave_buffer [ i ] ) ;
static void serial_send_packet ( uint8_t * buffer , uint8_t size ) NO_INLINE ;
static
void serial_send_packet ( uint8_t * buffer , uint8_t size ) {
for ( uint8_t i = 0 ; i < size ; + + i ) {
uint8_t data ;
data = buffer [ i ] ;
sync_send ( ) ;
checksum + = serial_slave_buffer [ i ] ;
serial_write_chunk ( data , 8 ) ;
}
serial_write_byte ( checksum ) ;
sync_send ( ) ;
}
// wait for the sync to finish sending
serial_delay ( ) ;
static uint8_t serial_recive_packet ( uint8_t * buffer , uint8_t size ) NO_INLINE ;
static
uint8_t serial_recive_packet ( uint8_t * buffer , uint8_t size ) {
uint8_t pecount = 0 ;
for ( uint8_t i = 0 ; i < size ; + + i ) {
uint8_t data ;
sync_recv ( ) ;
data = serial_read_chunk ( & pecount , 8 ) ;
buffer [ i ] = data ;
}
return pecount = = 0 ;
}
// read the middle of pulses
_delay_us ( SERIAL_DELAY / 2 ) ;
inline static
void change_sender2reciver ( void ) {
sync_send ( ) ; //0
serial_delay_half1 ( ) ; //1
serial_low ( ) ; //2
serial_input_with_pullup ( ) ; //2
serial_delay_half1 ( ) ; //3
}
uint8_t checksum_computed = 0 ;
for ( int i = 0 ; i < SERIAL_MASTER_BUFFER_LENGTH ; + + i ) {
serial_master_buffer [ i ] = serial_read_byte ( ) ;
sync_send ( ) ;
checksum_computed + = serial_master_buffer [ i ] ;
inline static
void change_reciver2sender ( void ) {
sync_recv ( ) ; //0
serial_delay ( ) ; //1
serial_low ( ) ; //3
serial_output ( ) ; //3
serial_delay_half1 ( ) ; //4
}
static inline uint8_t nibble_bits_count ( uint8_t bits )
{
bits = ( bits & 0x5 ) + ( bits > > 1 & 0x5 ) ;
bits = ( bits & 0x3 ) + ( bits > > 2 & 0x3 ) ;
return bits ;
}
// interrupt handle to be used by the target device
ISR ( SERIAL_PIN_INTERRUPT ) {
# ifndef SERIAL_USE_MULTI_TRANSACTION
serial_low ( ) ;
serial_output ( ) ;
SSTD_t * trans = Transaction_table ;
# else
// recive transaction table index
uint8_t tid , bits ;
uint8_t pecount = 0 ;
sync_recv ( ) ;
bits = serial_read_chunk ( & pecount , 7 ) ;
tid = bits > > 3 ;
bits = ( bits & 7 ) ! = nibble_bits_count ( tid ) ;
if ( bits | | pecount > 0 | | tid > Transaction_table_size ) {
return ;
}
uint8_t checksum_received = serial_read_byte ( ) ;
sync_send ( ) ;
serial_delay_half1 ( ) ;
serial_input ( ) ; // end transaction
serial_high ( ) ; // response step1 low->high
serial_output ( ) ;
_delay_sub_us ( SLAVE_INT_ACK_WIDTH_UNIT * SLAVE_INT_ACK_WIDTH ) ;
SSTD_t * trans = & Transaction_table [ tid ] ;
serial_low ( ) ; // response step2 ack high->low
# endif
if ( checksum_computed ! = checksum_received ) {
status | = SLAVE_DATA_CORRUPT ;
// target send phase
if ( trans - > target2initiator_buffer_size > 0 )
serial_send_packet ( ( uint8_t * ) trans - > target2initiator_buffer ,
trans - > target2initiator_buffer_size ) ;
// target switch to input
change_sender2reciver ( ) ;
// target recive phase
if ( trans - > initiator2target_buffer_size > 0 ) {
if ( serial_recive_packet ( ( uint8_t * ) trans - > initiator2target_buffer ,
trans - > initiator2target_buffer_size ) ) {
* trans - > status = TRANSACTION_ACCEPTED ;
} else {
* trans - > status = TRANSACTION_DATA_ERROR ;
}
} else {
status & = ~ SLAVE_DATA_CORRUPT ;
* trans - > status = TRANSACTION_ACCEPTED ;
}
}
inline
bool serial_slave_DATA_CORRUPT ( void ) {
return status & SLAVE_DATA_CORRUPT ;
sync_recv ( ) ; //weit initiator output to high
}
// Copies the serial_slave_buffer to the master and sends the
// serial_master_buffer to the slave.
/////////
// start transaction by initiator
//
// int soft_serial_transaction(int sstd_index)
//
// Returns:
// 0 => no error
// 1 => slave did not respond
int serial_update_buffers ( void ) {
// this code is very time dependent, so we need to disable interrupts
// TRANSACTION_END
// TRANSACTION_NO_RESPONSE
// TRANSACTION_DATA_ERROR
// this code is very time dependent, so we need to disable interrupts
# ifndef SERIAL_USE_MULTI_TRANSACTION
int soft_serial_transaction ( void ) {
SSTD_t * trans = Transaction_table ;
# else
int soft_serial_transaction ( int sstd_index ) {
if ( sstd_index > Transaction_table_size )
return TRANSACTION_TYPE_ERROR ;
SSTD_t * trans = & Transaction_table [ sstd_index ] ;
# endif
cli ( ) ;
// signal to the slave that we want to start a transaction
// signal to the target that we want to start a transaction
serial_output ( ) ;
serial_low ( ) ;
_delay_us ( 1 ) ;
_delay_us ( SLAVE_INT_WIDTH_US ) ;
// wait for the slaves response
serial_input ( ) ;
serial_ high ( ) ;
_delay_us ( S ERIAL_DELAY ) ;
# ifndef SERIAL_USE_MULTI_TRANSACTION
// wait for the target response
serial_ input_with_pullup ( ) ;
_delay_us ( S LAVE_INT_RESPONSE_TIME ) ;
// check if the slave is present
// check if the target is present
if ( serial_read_pin ( ) ) {
// slave failed to pull the line low, assume not present
// target failed to pull the line low, assume not present
serial_output ( ) ;
serial_high ( ) ;
* trans - > status = TRANSACTION_NO_RESPONSE ;
sei ( ) ;
return 1 ;
return TRANSACTION_NO_RESPONSE ;
}
// if the slave is present syncronize with it
sync_recv ( ) ;
uint8_t checksum_computed = 0 ;
// receive data from the slave
for ( int i = 0 ; i < SERIAL_SLAVE_BUFFER_LENGTH ; + + i ) {
serial_slave_buffer [ i ] = serial_read_byte ( ) ;
sync_recv ( ) ;
checksum_computed + = serial_slave_buffer [ i ] ;
# else
// send transaction table index
int tid = ( sstd_index < < 3 ) | ( 7 & nibble_bits_count ( sstd_index ) ) ;
sync_send ( ) ;
_delay_sub_us ( TID_SEND_ADJUST ) ;
serial_write_chunk ( tid , 7 ) ;
serial_delay_half1 ( ) ;
// wait for the target response (step1 low->high)
serial_input_with_pullup ( ) ;
while ( ! serial_read_pin ( ) ) {
_delay_sub_us ( 2 ) ;
}
uint8_t checksum_received = serial_read_byte ( ) ;
sync_recv ( ) ;
if ( checksum_computed ! = checksum_received ) {
sei ( ) ;
return 1 ;
// check if the target is present (step2 high->low)
for ( int i = 0 ; serial_read_pin ( ) ; i + + ) {
if ( i > SLAVE_INT_ACK_WIDTH + 1 ) {
// slave failed to pull the line low, assume not present
serial_output ( ) ;
serial_high ( ) ;
* trans - > status = TRANSACTION_NO_RESPONSE ;
sei ( ) ;
return TRANSACTION_NO_RESPONSE ;
}
_delay_sub_us ( SLAVE_INT_ACK_WIDTH_UNIT ) ;
}
# endif
uint8_t checksum = 0 ;
// send data to the slave
for ( int i = 0 ; i < SERIAL_MASTER_BUFFER_LENGTH ; + + i ) {
serial_write_byte ( serial_master_buffer [ i ] ) ;
sync_recv ( ) ;
checksum + = serial_master_buffer [ i ] ;
// initiator recive phase
// if the target is present syncronize with it
if ( trans - > target2initiator_buffer_size > 0 ) {
if ( ! serial_recive_packet ( ( uint8_t * ) trans - > target2initiator_buffer ,
trans - > target2initiator_buffer_size ) ) {
serial_output ( ) ;
serial_high ( ) ;
* trans - > status = TRANSACTION_DATA_ERROR ;
sei ( ) ;
return TRANSACTION_DATA_ERROR ;
}
}
// initiator switch to output
change_reciver2sender ( ) ;
// initiator send phase
if ( trans - > initiator2target_buffer_size > 0 ) {
serial_send_packet ( ( uint8_t * ) trans - > initiator2target_buffer ,
trans - > initiator2target_buffer_size ) ;
}
serial_write_byte ( checksum ) ;
sync_recv ( ) ;
// always, release the line when not in use
serial_output ( ) ;
serial_high ( ) ;
sync_send ( ) ;
* trans - > status = TRANSACTION_END ;
sei ( ) ;
return 0 ;
return TRANSACTION_END ;
}
# endif /* SOFT_SERIAL_PIN */
# ifdef SERIAL_USE_MULTI_TRANSACTION
int soft_serial_get_and_clean_status ( int sstd_index ) {
SSTD_t * trans = & Transaction_table [ sstd_index ] ;
cli ( ) ;
int retval = * trans - > status ;
* trans - > status = 0 ; ;
sei ( ) ;
return retval ;
}
# endif
# endif
// Helix serial.c history
// 2018-1-29 fork from let's split and add PD2, modify sync_recv() (#2308, bceffdefc)
// 2018-6-28 bug fix master to slave comm and speed up (#3255, 1038bbef4)
// (adjusted with avr-gcc 4.9.2)
// 2018-7-13 remove USE_SERIAL_PD2 macro (#3374, f30d6dd78)
// (adjusted with avr-gcc 4.9.2)
// 2018-8-11 add support multi-type transaction (#3608, feb5e4aae)
// (adjusted with avr-gcc 4.9.2)
// 2018-10-21 fix serial and RGB animation conflict (#4191, 4665e4fff)
// (adjusted with avr-gcc 7.3.0)
// 2018-10-28 re-adjust compiler depend value of delay (#4269, 8517f8a66)
// (adjusted with avr-gcc 5.4.0, 7.3.0)
// 2018-12-17 copy to TOP/quantum/split_common/ and remove backward compatibility code (#4669)