mirror of
				https://github.com/mfulz/qmk_firmware.git
				synced 2025-10-30 21:02:32 +01:00 
			
		
		
		
	Remove leftover old implementations of the lily58 split code. (#12442)
This commit is contained in:
		
							parent
							
								
									8a950a7116
								
							
						
					
					
						commit
						c5ddada32e
					
				| @ -35,6 +35,8 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
| #define MATRIX_ROW_PINS { C6, D7, E6, B4, B5 } | ||||
| #define MATRIX_COL_PINS { F6, F7, B1, B3, B2, B6 } | ||||
| 
 | ||||
| #define SOFT_SERIAL_PIN D2 | ||||
| #define SERIAL_USE_MULTI_TRANSACTION | ||||
| 
 | ||||
| /* define if matrix has ghost */ | ||||
| //#define MATRIX_HAS_GHOST
 | ||||
|  | ||||
| @ -1,357 +0,0 @@ | ||||
| /*
 | ||||
| Copyright 2012 Jun Wako <wakojun@gmail.com> | ||||
| 
 | ||||
| This program is free software: you can redistribute it and/or modify | ||||
| it under the terms of the GNU General Public License as published by | ||||
| the Free Software Foundation, either version 2 of the License, or | ||||
| (at your option) any later version. | ||||
| 
 | ||||
| This program is distributed in the hope that it will be useful, | ||||
| but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| GNU General Public License for more details. | ||||
| 
 | ||||
| You should have received a copy of the GNU General Public License | ||||
| along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| */ | ||||
| 
 | ||||
| /*
 | ||||
|  * scan matrix | ||||
|  */ | ||||
| #include <stdint.h> | ||||
| #include <stdbool.h> | ||||
| #include <string.h> | ||||
| #include <avr/io.h> | ||||
| #include <avr/wdt.h> | ||||
| #include <avr/interrupt.h> | ||||
| #include <util/delay.h> | ||||
| #include "print.h" | ||||
| #include "debug.h" | ||||
| #include "util.h" | ||||
| #include "matrix.h" | ||||
| #include "split_util.h" | ||||
| #include "quantum.h" | ||||
| 
 | ||||
| #ifdef USE_MATRIX_I2C | ||||
| #  include "i2c.h" | ||||
| #else // USE_SERIAL
 | ||||
| #  include "split_scomm.h" | ||||
| #endif | ||||
| 
 | ||||
| #ifndef DEBOUNCE | ||||
| #  define DEBOUNCE	5 | ||||
| #endif | ||||
| 
 | ||||
| #define ERROR_DISCONNECT_COUNT 5 | ||||
| 
 | ||||
| static uint8_t debouncing = DEBOUNCE; | ||||
| static const int ROWS_PER_HAND = MATRIX_ROWS/2; | ||||
| static uint8_t error_count = 0; | ||||
| uint8_t is_master = 0 ; | ||||
| 
 | ||||
| static const uint8_t row_pins[MATRIX_ROWS] = MATRIX_ROW_PINS; | ||||
| static const uint8_t col_pins[MATRIX_COLS] = MATRIX_COL_PINS; | ||||
| 
 | ||||
| /* matrix state(1:on, 0:off) */ | ||||
| static matrix_row_t matrix[MATRIX_ROWS]; | ||||
| static matrix_row_t matrix_debouncing[MATRIX_ROWS]; | ||||
| 
 | ||||
| static matrix_row_t read_cols(void); | ||||
| static void init_cols(void); | ||||
| static void unselect_rows(void); | ||||
| static void select_row(uint8_t row); | ||||
| static uint8_t matrix_master_scan(void); | ||||
| 
 | ||||
| 
 | ||||
| __attribute__ ((weak)) | ||||
| void matrix_init_kb(void) { | ||||
|     matrix_init_user(); | ||||
| } | ||||
| 
 | ||||
| __attribute__ ((weak)) | ||||
| void matrix_scan_kb(void) { | ||||
|     matrix_scan_user(); | ||||
| } | ||||
| 
 | ||||
| __attribute__ ((weak)) | ||||
| void matrix_init_user(void) { | ||||
| } | ||||
| 
 | ||||
| __attribute__ ((weak)) | ||||
| void matrix_scan_user(void) { | ||||
| } | ||||
| 
 | ||||
| inline | ||||
| uint8_t matrix_rows(void) | ||||
| { | ||||
|     return MATRIX_ROWS; | ||||
| } | ||||
| 
 | ||||
| inline | ||||
| uint8_t matrix_cols(void) | ||||
| { | ||||
|     return MATRIX_COLS; | ||||
| } | ||||
| 
 | ||||
| void matrix_init(void) | ||||
| { | ||||
|     split_keyboard_setup(); | ||||
| 
 | ||||
|     // initialize row and col
 | ||||
|     unselect_rows(); | ||||
|     init_cols(); | ||||
| 
 | ||||
|     setPinOutput(B0); | ||||
|     setPinOutput(D5); | ||||
|     writePinHigh(B0); | ||||
|     writePinHigh(D5); | ||||
| 
 | ||||
|     // initialize matrix state: all keys off
 | ||||
|     for (uint8_t i=0; i < MATRIX_ROWS; i++) { | ||||
|         matrix[i] = 0; | ||||
|         matrix_debouncing[i] = 0; | ||||
|     } | ||||
| 
 | ||||
|     is_master = has_usb(); | ||||
| 
 | ||||
|     matrix_init_quantum(); | ||||
| } | ||||
| 
 | ||||
| uint8_t _matrix_scan(void) | ||||
| { | ||||
|     // Right hand is stored after the left in the matirx so, we need to offset it
 | ||||
|     int offset = isLeftHand ? 0 : (ROWS_PER_HAND); | ||||
| 
 | ||||
|     for (uint8_t i = 0; i < ROWS_PER_HAND; i++) { | ||||
|         select_row(i); | ||||
|         _delay_us(30);  // without this wait read unstable value.
 | ||||
|         matrix_row_t cols = read_cols(); | ||||
|         if (matrix_debouncing[i+offset] != cols) { | ||||
|             matrix_debouncing[i+offset] = cols; | ||||
|             debouncing = DEBOUNCE; | ||||
|         } | ||||
|         unselect_rows(); | ||||
|     } | ||||
| 
 | ||||
|     if (debouncing) { | ||||
|         if (--debouncing) { | ||||
|             _delay_ms(1); | ||||
|         } else { | ||||
|             for (uint8_t i = 0; i < ROWS_PER_HAND; i++) { | ||||
|                 matrix[i+offset] = matrix_debouncing[i+offset]; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return 1; | ||||
| } | ||||
| 
 | ||||
| #ifdef USE_MATRIX_I2C | ||||
| 
 | ||||
| // Get rows from other half over i2c
 | ||||
| int i2c_transaction(void) { | ||||
|     int slaveOffset = (isLeftHand) ? (ROWS_PER_HAND) : 0; | ||||
| 
 | ||||
|     int err = i2c_master_start(SLAVE_I2C_ADDRESS + I2C_WRITE); | ||||
|     if (err) goto i2c_error; | ||||
| 
 | ||||
|     // start of matrix stored at 0x00
 | ||||
|     err = i2c_master_write(0x00); | ||||
|     if (err) goto i2c_error; | ||||
| 
 | ||||
|     // Start read
 | ||||
|     err = i2c_master_start(SLAVE_I2C_ADDRESS + I2C_READ); | ||||
|     if (err) goto i2c_error; | ||||
| 
 | ||||
|     if (!err) { | ||||
|         int i; | ||||
|         for (i = 0; i < ROWS_PER_HAND-1; ++i) { | ||||
|             matrix[slaveOffset+i] = i2c_master_read(I2C_ACK); | ||||
|         } | ||||
|         matrix[slaveOffset+i] = i2c_master_read(I2C_NACK); | ||||
|         i2c_master_stop(); | ||||
|     } else { | ||||
| i2c_error: // the cable is disconnceted, or something else went wrong
 | ||||
|         i2c_reset_state(); | ||||
|         return err; | ||||
|     } | ||||
| 
 | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| #else // USE_SERIAL
 | ||||
| 
 | ||||
| int serial_transaction(int master_changed) { | ||||
|     int slaveOffset = (isLeftHand) ? (ROWS_PER_HAND) : 0; | ||||
| #ifdef SERIAL_USE_MULTI_TRANSACTION | ||||
|     int ret=serial_update_buffers(master_changed); | ||||
| #else | ||||
|     int ret=serial_update_buffers(); | ||||
| #endif | ||||
|     if (ret ) { | ||||
|         if(ret==2) writePinLow(B0); | ||||
|         return 1; | ||||
|     } | ||||
|     writePinHigh(B0); | ||||
|     memcpy(&matrix[slaveOffset], | ||||
|         (void *)serial_slave_buffer, SERIAL_SLAVE_BUFFER_LENGTH); | ||||
|     return 0; | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
| uint8_t matrix_scan(void) | ||||
| { | ||||
|     if (is_master) { | ||||
|         matrix_master_scan(); | ||||
|     }else{ | ||||
|         matrix_slave_scan(); | ||||
|         int offset = (isLeftHand) ? ROWS_PER_HAND : 0; | ||||
|         memcpy(&matrix[offset], | ||||
|                (void *)serial_master_buffer, SERIAL_MASTER_BUFFER_LENGTH); | ||||
|         matrix_scan_quantum(); | ||||
|     } | ||||
|     return 1; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| uint8_t matrix_master_scan(void) { | ||||
| 
 | ||||
|     int ret = _matrix_scan(); | ||||
|     int mchanged = 1; | ||||
| 
 | ||||
|     int offset = (isLeftHand) ? 0 : ROWS_PER_HAND; | ||||
| 
 | ||||
| #ifdef USE_MATRIX_I2C | ||||
| //    for (int i = 0; i < ROWS_PER_HAND; ++i) {
 | ||||
|         /* i2c_slave_buffer[i] = matrix[offset+i]; */ | ||||
| //        i2c_slave_buffer[i] = matrix[offset+i];
 | ||||
| //    }
 | ||||
| #else // USE_SERIAL
 | ||||
|   #ifdef SERIAL_USE_MULTI_TRANSACTION | ||||
|     mchanged = memcmp((void *)serial_master_buffer, | ||||
| 		      &matrix[offset], SERIAL_MASTER_BUFFER_LENGTH); | ||||
|   #endif | ||||
|     memcpy((void *)serial_master_buffer, | ||||
| 	   &matrix[offset], SERIAL_MASTER_BUFFER_LENGTH); | ||||
| #endif | ||||
| 
 | ||||
| #ifdef USE_MATRIX_I2C | ||||
|     if( i2c_transaction() ) { | ||||
| #else // USE_SERIAL
 | ||||
|     if( serial_transaction(mchanged) ) { | ||||
| #endif | ||||
|         // turn on the indicator led when halves are disconnected
 | ||||
|         writePinLow(D5); | ||||
| 
 | ||||
|         error_count++; | ||||
| 
 | ||||
|         if (error_count > ERROR_DISCONNECT_COUNT) { | ||||
|             // reset other half if disconnected
 | ||||
|             int slaveOffset = (isLeftHand) ? (ROWS_PER_HAND) : 0; | ||||
|             for (int i = 0; i < ROWS_PER_HAND; ++i) { | ||||
|                 matrix[slaveOffset+i] = 0; | ||||
|             } | ||||
|         } | ||||
|     } else { | ||||
|         // turn off the indicator led on no error
 | ||||
|         writePinHigh(D5); | ||||
|         error_count = 0; | ||||
|     } | ||||
|     matrix_scan_quantum(); | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| void matrix_slave_scan(void) { | ||||
|     _matrix_scan(); | ||||
| 
 | ||||
|     int offset = (isLeftHand) ? 0 : ROWS_PER_HAND; | ||||
| 
 | ||||
| #ifdef USE_MATRIX_I2C | ||||
|     for (int i = 0; i < ROWS_PER_HAND; ++i) { | ||||
|         /* i2c_slave_buffer[i] = matrix[offset+i]; */ | ||||
|         i2c_slave_buffer[i] = matrix[offset+i]; | ||||
|     } | ||||
| #else // USE_SERIAL
 | ||||
|   #ifdef SERIAL_USE_MULTI_TRANSACTION | ||||
|     int change = 0; | ||||
|   #endif | ||||
|     for (int i = 0; i < ROWS_PER_HAND; ++i) { | ||||
|   #ifdef SERIAL_USE_MULTI_TRANSACTION | ||||
|         if( serial_slave_buffer[i] != matrix[offset+i] ) | ||||
| 	    change = 1; | ||||
|   #endif | ||||
|         serial_slave_buffer[i] = matrix[offset+i]; | ||||
|     } | ||||
|   #ifdef SERIAL_USE_MULTI_TRANSACTION | ||||
|     slave_buffer_change_count += change; | ||||
|   #endif | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| bool matrix_is_modified(void) | ||||
| { | ||||
|     if (debouncing) return false; | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| inline | ||||
| bool matrix_is_on(uint8_t row, uint8_t col) | ||||
| { | ||||
|     return (matrix[row] & ((matrix_row_t)1<<col)); | ||||
| } | ||||
| 
 | ||||
| inline | ||||
| matrix_row_t matrix_get_row(uint8_t row) | ||||
| { | ||||
|     return matrix[row]; | ||||
| } | ||||
| 
 | ||||
| void matrix_print(void) | ||||
| { | ||||
|     print("\nr/c 0123456789ABCDEF\n"); | ||||
|     for (uint8_t row = 0; row < MATRIX_ROWS; row++) { | ||||
|         print_hex8(row); print(": "); | ||||
|         print_bin_reverse16(matrix_get_row(row)); | ||||
|         print("\n"); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| uint8_t matrix_key_count(void) | ||||
| { | ||||
|     uint8_t count = 0; | ||||
|     for (uint8_t i = 0; i < MATRIX_ROWS; i++) { | ||||
|         count += bitpop16(matrix[i]); | ||||
|     } | ||||
|     return count; | ||||
| } | ||||
| 
 | ||||
| static void  init_cols(void) | ||||
| { | ||||
|     for(int x = 0; x < MATRIX_COLS; x++) { | ||||
|         _SFR_IO8((col_pins[x] >> 4) + 1) &=  ~_BV(col_pins[x] & 0xF); | ||||
|         _SFR_IO8((col_pins[x] >> 4) + 2) |= _BV(col_pins[x] & 0xF); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static matrix_row_t read_cols(void) | ||||
| { | ||||
|     matrix_row_t result = 0; | ||||
|     for(int x = 0; x < MATRIX_COLS; x++) { | ||||
|         result |= (_SFR_IO8(col_pins[x] >> 4) & _BV(col_pins[x] & 0xF)) ? 0 : (1 << x); | ||||
|     } | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| static void unselect_rows(void) | ||||
| { | ||||
|     for(int x = 0; x < ROWS_PER_HAND; x++) { | ||||
|         _SFR_IO8((row_pins[x] >> 4) + 1) &=  ~_BV(row_pins[x] & 0xF); | ||||
|         _SFR_IO8((row_pins[x] >> 4) + 2) |= _BV(row_pins[x] & 0xF); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static void select_row(uint8_t row) | ||||
| { | ||||
|     _SFR_IO8((row_pins[row] >> 4) + 1) |=  _BV(row_pins[row] & 0xF); | ||||
|     _SFR_IO8((row_pins[row] >> 4) + 2) &= ~_BV(row_pins[row] & 0xF); | ||||
| } | ||||
| @ -1,4 +0,0 @@ | ||||
| #ifndef SOFT_SERIAL_PIN | ||||
| #define SOFT_SERIAL_PIN D2 | ||||
| #define SERIAL_USE_MULTI_TRANSACTION | ||||
| #endif | ||||
| @ -1,100 +0,0 @@ | ||||
| #include <avr/io.h> | ||||
| #include <avr/wdt.h> | ||||
| #include <avr/power.h> | ||||
| #include <avr/interrupt.h> | ||||
| #include <util/delay.h> | ||||
| #include <avr/eeprom.h> | ||||
| #include "split_util.h" | ||||
| #include "matrix.h" | ||||
| #include "keyboard.h" | ||||
| #include "wait.h" | ||||
| 
 | ||||
| #ifdef USE_MATRIX_I2C | ||||
| #  include "i2c.h" | ||||
| #else | ||||
| #  include "split_scomm.h" | ||||
| #endif | ||||
| 
 | ||||
| #ifndef SPLIT_USB_TIMEOUT | ||||
| #    define SPLIT_USB_TIMEOUT 2500 | ||||
| #endif | ||||
| 
 | ||||
| volatile bool isLeftHand = true; | ||||
| 
 | ||||
| bool waitForUsb(void) { | ||||
|     for (uint8_t i = 0; i < (SPLIT_USB_TIMEOUT / 100); i++) { | ||||
|         // This will return true of a USB connection has been established
 | ||||
|         if (UDADDR & _BV(ADDEN)) { | ||||
|             return true; | ||||
|         } | ||||
|         wait_ms(100); | ||||
|     } | ||||
| 
 | ||||
|     // Avoid NO_USB_STARTUP_CHECK - Disable USB as the previous checks seem to enable it somehow
 | ||||
|     (USBCON &= ~(_BV(USBE) | _BV(OTGPADE))); | ||||
| 
 | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| __attribute__((weak)) bool is_keyboard_left(void) { | ||||
| #if defined(SPLIT_HAND_PIN) | ||||
|     // Test pin SPLIT_HAND_PIN for High/Low, if low it's right hand
 | ||||
|     setPinInput(SPLIT_HAND_PIN); | ||||
|     return readPin(SPLIT_HAND_PIN); | ||||
| #elif defined(EE_HANDS) | ||||
|     return eeconfig_read_handedness(); | ||||
| #elif defined(MASTER_RIGHT) | ||||
|     return !has_usb(); | ||||
| #endif | ||||
| 
 | ||||
|     return has_usb(); | ||||
| } | ||||
| 
 | ||||
| __attribute__((weak)) bool has_usb(void) { | ||||
|     static enum { UNKNOWN, MASTER, SLAVE } usbstate = UNKNOWN; | ||||
| 
 | ||||
|     // only check once, as this is called often
 | ||||
|     if (usbstate == UNKNOWN) { | ||||
| #if defined(SPLIT_USB_DETECT) | ||||
|         usbstate = waitForUsb() ? MASTER : SLAVE; | ||||
| #elif defined(__AVR__) | ||||
|         USBCON |= (1 << OTGPADE);  // enables VBUS pad
 | ||||
|         wait_us(5); | ||||
| 
 | ||||
|         usbstate = (USBSTA & (1 << VBUS)) ? MASTER : SLAVE;  // checks state of VBUS
 | ||||
| #else | ||||
|         usbstate = MASTER; | ||||
| #endif | ||||
|     } | ||||
| 
 | ||||
|     return (usbstate == MASTER); | ||||
| } | ||||
| 
 | ||||
| static void keyboard_master_setup(void) { | ||||
| 
 | ||||
| #ifdef USE_MATRIX_I2C | ||||
|     i2c_master_init(); | ||||
| #else | ||||
|     serial_master_init(); | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| static void keyboard_slave_setup(void) { | ||||
| 
 | ||||
| #ifdef USE_MATRIX_I2C | ||||
|     i2c_slave_init(SLAVE_I2C_ADDRESS); | ||||
| #else | ||||
|     serial_slave_init(); | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| void split_keyboard_setup(void) { | ||||
|    isLeftHand = is_keyboard_left(); | ||||
| 
 | ||||
|    if (has_usb()) { | ||||
|       keyboard_master_setup(); | ||||
|    } else { | ||||
|       keyboard_slave_setup(); | ||||
|    } | ||||
|    sei(); | ||||
| } | ||||
| @ -1,589 +0,0 @@ | ||||
| /*
 | ||||
|  * 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 | ||||
| #define F_CPU 16000000 | ||||
| #endif | ||||
| 
 | ||||
| #include <avr/io.h> | ||||
| #include <avr/interrupt.h> | ||||
| #include <util/delay.h> | ||||
| #include <stddef.h> | ||||
| #include <stdbool.h> | ||||
| #include "serial.h" | ||||
| 
 | ||||
| #ifdef SOFT_SERIAL_PIN | ||||
| 
 | ||||
| #ifdef __AVR_ATmega32U4__ | ||||
|   // if using ATmega32U4 I2C, can not use PD0 and PD1 in soft serial.
 | ||||
|   #ifdef USE_I2C | ||||
|     #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 | ||||
|     #define SERIAL_PIN_DDR   DDRD | ||||
|     #define SERIAL_PIN_PORT  PORTD | ||||
|     #define SERIAL_PIN_INPUT PIND | ||||
|     #if SOFT_SERIAL_PIN == D0 | ||||
|       #define SERIAL_PIN_MASK _BV(PD0) | ||||
|       #define EIMSK_BIT       _BV(INT0) | ||||
|       #define EICRx_BIT       (~(_BV(ISC00) | _BV(ISC01))) | ||||
|       #define SERIAL_PIN_INTERRUPT INT0_vect | ||||
|     #elif  SOFT_SERIAL_PIN == D1 | ||||
|       #define SERIAL_PIN_MASK _BV(PD1) | ||||
|       #define EIMSK_BIT       _BV(INT1) | ||||
|       #define EICRx_BIT       (~(_BV(ISC10) | _BV(ISC11))) | ||||
|       #define SERIAL_PIN_INTERRUPT INT1_vect | ||||
|     #elif  SOFT_SERIAL_PIN == D2 | ||||
|       #define SERIAL_PIN_MASK _BV(PD2) | ||||
|       #define EIMSK_BIT       _BV(INT2) | ||||
|       #define EICRx_BIT       (~(_BV(ISC20) | _BV(ISC21))) | ||||
|       #define SERIAL_PIN_INTERRUPT INT2_vect | ||||
|     #elif  SOFT_SERIAL_PIN == D3 | ||||
|       #define SERIAL_PIN_MASK _BV(PD3) | ||||
|       #define EIMSK_BIT       _BV(INT3) | ||||
|       #define EICRx_BIT       (~(_BV(ISC30) | _BV(ISC31))) | ||||
|       #define SERIAL_PIN_INTERRUPT INT3_vect | ||||
|     #endif | ||||
|   #elif  SOFT_SERIAL_PIN == E6 | ||||
|     #define SERIAL_PIN_DDR   DDRE | ||||
|     #define SERIAL_PIN_PORT  PORTE | ||||
|     #define SERIAL_PIN_INPUT PINE | ||||
|     #define SERIAL_PIN_MASK  _BV(PE6) | ||||
|     #define EIMSK_BIT        _BV(INT6) | ||||
|     #define EICRx_BIT        (~(_BV(ISC60) | _BV(ISC61))) | ||||
|     #define SERIAL_PIN_INTERRUPT INT6_vect | ||||
|   #else | ||||
|   #error invalid SOFT_SERIAL_PIN value | ||||
|   #endif | ||||
| 
 | ||||
| #else | ||||
|  #error serial.c now support ATmega32U4 only | ||||
| #endif | ||||
| 
 | ||||
| //////////////// for backward compatibility ////////////////////////////////
 | ||||
| #ifndef SERIAL_USE_MULTI_TRANSACTION | ||||
| /* --- USE Simple API (OLD API, compatible with let's split serial.c) */ | ||||
|   #if SERIAL_SLAVE_BUFFER_LENGTH > 0 | ||||
|   uint8_t volatile serial_slave_buffer[SERIAL_SLAVE_BUFFER_LENGTH] = {0}; | ||||
|   #endif | ||||
|   #if SERIAL_MASTER_BUFFER_LENGTH > 0 | ||||
|   uint8_t volatile serial_master_buffer[SERIAL_MASTER_BUFFER_LENGTH] = {0}; | ||||
|   #endif | ||||
|   uint8_t volatile status0 = 0; | ||||
| 
 | ||||
| SSTD_t transactions[] = { | ||||
|     { (uint8_t *)&status0, | ||||
|   #if SERIAL_MASTER_BUFFER_LENGTH > 0 | ||||
|       sizeof(serial_master_buffer), (uint8_t *)serial_master_buffer, | ||||
|   #else | ||||
|       0, (uint8_t *)NULL, | ||||
|   #endif | ||||
|   #if SERIAL_SLAVE_BUFFER_LENGTH > 0 | ||||
|       sizeof(serial_slave_buffer), (uint8_t *)serial_slave_buffer | ||||
|   #else | ||||
|       0, (uint8_t *)NULL, | ||||
|   #endif | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| void serial_master_init(void) | ||||
| { soft_serial_initiator_init(transactions, TID_LIMIT(transactions)); } | ||||
| 
 | ||||
| void serial_slave_init(void) | ||||
| { soft_serial_target_init(transactions, TID_LIMIT(transactions)); } | ||||
| 
 | ||||
| // 0 => no error
 | ||||
| // 1 => slave did not respond
 | ||||
| // 2 => checksum error
 | ||||
| int serial_update_buffers() | ||||
| { | ||||
|     int result; | ||||
|     result = soft_serial_transaction(); | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| #endif // end of Simple API (OLD API, compatible with let's split serial.c)
 | ||||
| ////////////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
| #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
 | ||||
| //  1: about 137kbps (default)
 | ||||
| //  2: about 75kbps
 | ||||
| //  3: about 39kbps
 | ||||
| //  4: about 26kbps
 | ||||
| //  5: about 20kbps
 | ||||
| #endif | ||||
| 
 | ||||
| #if __GNUC__ < 6 | ||||
|   #define TID_SEND_ADJUST 14 | ||||
| #else | ||||
|   #define TID_SEND_ADJUST 2 | ||||
| #endif | ||||
| 
 | ||||
| #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_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 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 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 sender to synchronize timing with the reciver.
 | ||||
| static void sync_recv(void) NO_INLINE; | ||||
| static | ||||
| void sync_recv(void) { | ||||
|   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()); | ||||
| } | ||||
| 
 | ||||
| // 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_low(); | ||||
|   serial_delay(); | ||||
|   serial_high(); | ||||
| } | ||||
| 
 | ||||
| // Reads a byte from the serial line
 | ||||
| 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
 | ||||
| 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(); | ||||
| 
 | ||||
|     serial_low(); // sync_send() / senc_recv() need raise edge
 | ||||
| } | ||||
| 
 | ||||
| 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(); | ||||
|     serial_write_chunk(data,8); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| 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; | ||||
| } | ||||
| 
 | ||||
| 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
 | ||||
| } | ||||
| 
 | ||||
| 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; | ||||
|   } | ||||
|   serial_delay_half1(); | ||||
| 
 | ||||
|   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 | ||||
| 
 | ||||
|   // 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 { | ||||
|       *trans->status = TRANSACTION_ACCEPTED; | ||||
|   } | ||||
| 
 | ||||
|   sync_recv(); //weit initiator output to high
 | ||||
| } | ||||
| 
 | ||||
| /////////
 | ||||
| //  start transaction by initiator
 | ||||
| //
 | ||||
| // int  soft_serial_transaction(int sstd_index)
 | ||||
| //
 | ||||
| // Returns:
 | ||||
| //    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 target that we want to start a transaction
 | ||||
|   serial_output(); | ||||
|   serial_low(); | ||||
|   _delay_us(SLAVE_INT_WIDTH_US); | ||||
| 
 | ||||
| #ifndef SERIAL_USE_MULTI_TRANSACTION | ||||
|   // wait for the target response
 | ||||
|   serial_input_with_pullup(); | ||||
|   _delay_us(SLAVE_INT_RESPONSE_TIME); | ||||
| 
 | ||||
|   // check if the target is present
 | ||||
|   if (serial_read_pin()) { | ||||
|     // target failed to pull the line low, assume not present
 | ||||
|     serial_output(); | ||||
|     serial_high(); | ||||
|     *trans->status = TRANSACTION_NO_RESPONSE; | ||||
|     sei(); | ||||
|     return TRANSACTION_NO_RESPONSE; | ||||
|   } | ||||
| 
 | ||||
| #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); | ||||
|   } | ||||
| 
 | ||||
|   // 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 | ||||
| 
 | ||||
|   // 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); | ||||
|   } | ||||
| 
 | ||||
|   // always, release the line when not in use
 | ||||
|   sync_send(); | ||||
| 
 | ||||
|   *trans->status = TRANSACTION_END; | ||||
|   sei(); | ||||
|   return TRANSACTION_END; | ||||
| } | ||||
| 
 | ||||
| #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)
 | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Nick Brassel
						Nick Brassel