diff options
author | Nick Brassel <nick@tzarc.org> | 2021-06-18 09:10:06 +1000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-06-18 09:10:06 +1000 |
commit | 172e6a703041363decd6fc829542f33180c13beb (patch) | |
tree | a5d4afaa672ab44826865fd76b201e3899083192 | |
parent | ef92c9ee2cf4745637635ec1895399e4f013914c (diff) |
Extensible split data sync (#11930)
* Extensible split data sync capability through transactions.
- Split common transport has been split up between the transport layer
and data layer.
- Split "transactions" model used, with convergence between I2C and
serial data definitions.
- Slave matrix "generation count" is used to determine if the full slave
matrix needs to be retrieved.
- Encoders get the same "generation count" treatment.
- All other blocks of data are synchronised when a change is detected.
- All transmissions have a globally-configurable deadline before a
transmission is forced (`FORCED_SYNC_THROTTLE_MS`, default 100ms).
- Added atomicity for all core-synced data, preventing partial updates
- Added retries to AVR i2c_master's i2c_start, to minimise the number of
failed transactions when interrupts are disabled on the slave due to
atomicity checks.
- Some keyboards have had slight modifications made in order to ensure
that they still build due to firmware size restrictions.
* Fixup LED_MATRIX compile.
* Parameterise ERROR_DISCONNECT_COUNT.
29 files changed, 1387 insertions, 691 deletions
diff --git a/common_features.mk b/common_features.mk index 1a079a8df2..15637d7608 100644 --- a/common_features.mk +++ b/common_features.mk @@ -537,7 +537,11 @@ ifeq ($(strip $(SPLIT_KEYBOARD)), yes) # Determine which (if any) transport files are required ifneq ($(strip $(SPLIT_TRANSPORT)), custom) - QUANTUM_LIB_SRC += $(QUANTUM_DIR)/split_common/transport.c + QUANTUM_SRC += $(QUANTUM_DIR)/split_common/transport.c \ + $(QUANTUM_DIR)/split_common/transactions.c + + OPT_DEFS += -DSPLIT_COMMON_TRANSACTIONS + # Functions added via QUANTUM_LIB_SRC are only included in the final binary if they're called. # Unused functions are pruned away, which is why we can add multiple drivers here without bloat. ifeq ($(PLATFORM),AVR) diff --git a/docs/config_options.md b/docs/config_options.md index 26fe8cea55..980195ac68 100644 --- a/docs/config_options.md +++ b/docs/config_options.md @@ -274,7 +274,7 @@ There are a few different ways to set handedness for split keyboards (listed in ### Other Options * `#define USE_I2C` - * For using I2C instead of Serial (defaults to serial) + * For using I2C instead of Serial (default is serial; serial transport is supported on ARM -- I2C is AVR-only) * `#define SOFT_SERIAL_PIN D0` * When using serial, define this. `D0` or `D1`,`D2`,`D3`,`E6`. @@ -303,7 +303,7 @@ There are a few different ways to set handedness for split keyboards (listed in * `#define SPLIT_USB_DETECT` * Detect (with timeout) USB connection when delegating master/slave * Default behavior for ARM - * Required for AVR Teensy + * Required for AVR Teensy (without hardware mods) * `#define SPLIT_USB_TIMEOUT 2000` * Maximum timeout when detecting master/slave when using `SPLIT_USB_DETECT` @@ -311,6 +311,28 @@ There are a few different ways to set handedness for split keyboards (listed in * `#define SPLIT_USB_TIMEOUT_POLL 10` * Poll frequency when detecting master/slave when using `SPLIT_USB_DETECT` +* `#define FORCED_SYNC_THROTTLE_MS 100` + * Deadline for synchronizing data from master to slave when using the QMK-provided split transport. + +* `#define SPLIT_TRANSPORT_MIRROR` + * Mirrors the master-side matrix on the slave when using the QMK-provided split transport. + +* `#define SPLIT_LAYER_STATE_ENABLE` + * Ensures the current layer state is available on the slave when using the QMK-provided split transport. + +* `#define SPLIT_LED_STATE_ENABLE` + * Ensures the current host indicator state (caps/num/scroll) is available on the slave when using the QMK-provided split transport. + +* `#define SPLIT_MODS_ENABLE` + * Ensures the current modifier state (normal, weak, and oneshot) is available on the slave when using the QMK-provided split transport. + +* `#define SPLIT_WPM_ENABLE` + * Ensures the current WPM is available on the slave when using the QMK-provided split transport. + +* `#define SPLIT_TRANSACTION_IDS_KB .....` +* `#define SPLIT_TRANSACTION_IDS_USER .....` + * Allows for custom data sync with the slave when using the QMK-provided split transport. See [custom data sync between sides](feature_split_keyboard.md#custom-data-sync) for more information. + # The `rules.mk` File This is a [make](https://www.gnu.org/software/make/manual/make.html) file that is included by the top-level `Makefile`. It is used to set some information about the MCU that we will be compiling for as well as enabling and disabling certain features. diff --git a/docs/feature_split_keyboard.md b/docs/feature_split_keyboard.md index 4ebf585f5c..603c387c2d 100644 --- a/docs/feature_split_keyboard.md +++ b/docs/feature_split_keyboard.md @@ -8,8 +8,7 @@ QMK Firmware has a generic implementation that is usable by any board, as well a For this, we will mostly be talking about the generic implementation used by the Let's Split and other keyboards. -!> ARM is not yet fully supported for Split Keyboards and has many limitations. Progress is being made, but we have not yet reached 100% feature parity. - +!> ARM split supports most QMK subsystems when using the 'serial' and 'serial_usart' drivers. I2C slave is currently unsupported. ## Compatibility Overview @@ -169,7 +168,7 @@ Because not every split keyboard is identical, there are a number of additional #define USE_I2C ``` -This enables I<sup>2</sup>C support for split keyboards. This isn't strictly for communication, but can be used for OLED or other I<sup>2</sup>C-based devices. +This configures the use of I<sup>2</sup>C support for split keyboard transport (AVR only). ```c #define SOFT_SERIAL_PIN D0 @@ -193,20 +192,115 @@ If you're having issues with serial communication, you can change this value, as * **`5`**: about 20kbps ```c -#define SPLIT_MODS_ENABLE +#define FORCED_SYNC_THROTTLE_MS 100 ``` -This enables transmitting modifier state (normal, weak and oneshot) to the non -primary side of the split keyboard. This adds a few bytes of data to the split -communication protocol and may impact the matrix scan speed when enabled. -The purpose of this feature is to support cosmetic use of modifer state (e.g. -displaying status on an OLED screen). +This sets the maximum number of milliseconds before forcing a synchronization of data from master to slave. Under normal circumstances this sync occurs whenever the data _changes_, for safety a data transfer occurs after this number of milliseconds if no change has been detected since the last sync. ```c #define SPLIT_TRANSPORT_MIRROR ``` -This mirrors the master side matrix to the slave side for features that react or require knowledge of master side key presses on the slave side. This adds a few bytes of data to the split communication protocol and may impact the matrix scan speed when enabled. The purpose of this feature is to support cosmetic use of key events (e.g. RGB reacting to Keypresses). +This mirrors the master side matrix to the slave side for features that react or require knowledge of master side key presses on the slave side. The purpose of this feature is to support cosmetic use of key events (e.g. RGB reacting to keypresses). This adds overhead to the split communication protocol and may negatively impact the matrix scan speed when enabled. + +```c +#define SPLIT_LAYER_STATE_ENABLE +``` + +This enables syncing of the layer state between both halves of the split keyboard. The main purpose of this feature is to enable support for use of things like OLED display of the currently active layer. This adds overhead to the split communication protocol and may negatively impact the matrix scan speed when enabled. + +```c +#define SPLIT_LED_STATE_ENABLE +``` + +This enables syncing of the Host LED status (caps lock, num lock, etc) between both halves of the split keyboard. The main purpose of this feature is to enable support for use of things like OLED display of the Host LED status. This adds overhead to the split communication protocol and may negatively impact the matrix scan speed when enabled. + +```c +#define SPLIT_MODS_ENABLE +``` + +This enables transmitting modifier state (normal, weak and oneshot) to the non primary side of the split keyboard. The purpose of this feature is to support cosmetic use of modifer state (e.g. displaying status on an OLED screen). This adds overhead to the split communication protocol and may negatively impact the matrix scan speed when enabled. + +```c +#define SPLIT_WPM_ENABLE +``` + +This enables transmitting the current WPM to the slave side of the split keyboard. The purpose of this feature is to support cosmetic use of WPM (e.g. displaying the current value on an OLED screen). This adds overhead to the split communication protocol and may negatively impact the matrix scan speed when enabled. + +### Custom data sync between sides :id=custom-data-sync + +QMK's split transport allows for arbitrary data transactions at both the keyboard and user levels. This is modelled on a remote procedure call, with the master invoking a function on the slave side, with the ability to send data from master to slave, process it slave side, and send data back from slave to master. + +To leverage this, a keyboard or user/keymap can define a comma-separated list of _transaction IDs_: + +```c +// for keyboard-level data sync: +#define SPLIT_TRANSACTION_IDS_KB KEYBOARD_SYNC_A, KEYBOARD_SYNC_B +// or, for user: +#define SPLIT_TRANSACTION_IDS_USER USER_SYNC_A, USER_SYNC_B, USER_SYNC_C +``` + +These _transaction IDs_ then need a slave-side handler function to be registered with the split transport, for example: + +```c +typedef struct _master_to_slave_t { + int m2s_data; +} master_to_slave_t; + +typedef struct _slave_to_master_t { + int s2m_data; +} slave_to_master_t; + +void user_sync_a_slave_handler(uint8_t in_buflen, const void* in_data, uint8_t out_buflen, void* out_data) { + const master_to_slave_t *m2s = (const master_to_slave_t*)in_data; + slave_to_master_t *s2m = (slave_to_master_t*)out_data; + s2m->s2m_data = m2s->m2s_data + 5; // whatever comes in, add 5 so it can be sent back +} + +void keyboard_post_init_user(void) { + transaction_register_rpc(USER_SYNC_A, user_sync_a_slave_handler); +} +``` + +The master side can then invoke the slave-side handler - for normal keyboard functionality to be minimally affected, any keyboard- or user-level code attempting to sync data should be throttled: + +```c +void housekeeping_task_user(void) { + if (is_keyboard_master()) { + // Interact with slave every 500ms + static uint32_t last_sync = 0; + if (timer_elapsed32(last_sync) > 500) { + master_to_slave_t m2s = {6}; + slave_to_master_t s2m = {0}; + if(transaction_rpc_exec(USER_SYNC_A, sizeof(m2s), &m2s, sizeof(s2m), &s2m)) { + last_sync = timer_read32(); + dprintf("Slave value: %d\n", s2m.s2m_data); // this will now be 11, as the slave adds 5 + } else { + dprint("Slave sync failed!\n"); + } + } + } +} +``` + +!> It is recommended that any data sync between halves happens during the master side's _housekeeping task_. This ensures timely retries should failures occur. + +If only one-way data transfer is needed, helper methods are provided: + +```c +bool transaction_rpc_exec(int8_t transaction_id, uint8_t initiator2target_buffer_size, const void *initiator2target_buffer, uint8_t target2initiator_buffer_size, void *target2initiator_buffer); +bool transaction_rpc_send(int8_t transaction_id, uint8_t initiator2target_buffer_size, const void *initiator2target_buffer); +bool transaction_rpc_recv(int8_t transaction_id, uint8_t target2initiator_buffer_size, void *target2initiator_buffer); +``` + +By default, the inbound and outbound data is limited to a maximum of 32 bytes each. The sizes can be altered if required: + +```c +// Master to slave: +#define RPC_M2S_BUFFER_SIZE 48 +// Slave to master: +#define RPC_S2M_BUFFER_SIZE 48 +``` ### Hardware Configuration Options diff --git a/drivers/avr/i2c_master.c b/drivers/avr/i2c_master.c index b1e4885298..2773e00778 100644 --- a/drivers/avr/i2c_master.c +++ b/drivers/avr/i2c_master.c @@ -28,8 +28,14 @@ # define F_SCL 400000UL // SCL frequency #endif +#ifndef I2C_START_RETRY_COUNT +# define I2C_START_RETRY_COUNT 20 +#endif // I2C_START_RETRY_COUNT + #define TWBR_val (((F_CPU / F_SCL) - 16) / 2) +#define MAX(X, Y) ((X) > (Y) ? (X) : (Y)) + void i2c_init(void) { TWSR = 0; /* no prescaler */ TWBR = (uint8_t)TWBR_val; @@ -47,7 +53,7 @@ void i2c_init(void) { #endif } -i2c_status_t i2c_start(uint8_t address, uint16_t timeout) { +static i2c_status_t i2c_start_impl(uint8_t address, uint16_t timeout) { // reset TWI control register TWCR = 0; // transmit START condition @@ -86,6 +92,17 @@ i2c_status_t i2c_start(uint8_t address, uint16_t timeout) { return I2C_STATUS_SUCCESS; } +i2c_status_t i2c_start(uint8_t address, uint16_t timeout) { + // Retry i2c_start_impl a bunch times in case the remote side has interrupts disabled. + uint16_t timeout_timer = timer_read(); + uint16_t time_slice = MAX(1, (timeout == (I2C_TIMEOUT_INFINITE)) ? 5 : (timeout / (I2C_START_RETRY_COUNT))); // if it's infinite, wait 1ms between attempts, otherwise split up the entire timeout into the number of retries + i2c_status_t status; + do { + status = i2c_start_impl(address, time_slice); + } while ((status < 0) && ((timeout == I2C_TIMEOUT_INFINITE) || (timer_elapsed(timeout_timer) < timeout))); + return status; +} + i2c_status_t i2c_write(uint8_t data, uint16_t timeout) { // load data into data register TWDR = data; diff --git a/drivers/avr/i2c_slave.c b/drivers/avr/i2c_slave.c index 62a378165a..2907f164c0 100644 --- a/drivers/avr/i2c_slave.c +++ b/drivers/avr/i2c_slave.c @@ -17,6 +17,7 @@ * GitHub repository: https://github.com/g4lvanix/I2C-slave-lib */ +#include <stddef.h> #include <avr/io.h> #include <util/twi.h> #include <avr/interrupt.h> @@ -24,6 +25,12 @@ #include "i2c_slave.h" +#if defined(USE_I2C) && defined(SPLIT_COMMON_TRANSACTIONS) +# include "transactions.h" + +static volatile bool is_callback_executor = false; +#endif // defined(USE_I2C) && defined(SPLIT_COMMON_TRANSACTIONS) + volatile uint8_t i2c_slave_reg[I2C_SLAVE_REG_COUNT]; static volatile uint8_t buffer_address; @@ -48,11 +55,14 @@ ISR(TWI_vect) { case TW_SR_SLA_ACK: // The device is now a slave receiver slave_has_register_set = false; +#if defined(USE_I2C) && defined(SPLIT_COMMON_TRANSACTIONS) + is_callback_executor = false; +#endif // defined(USE_I2C) && defined(SPLIT_COMMON_TRANSACTIONS) break; case TW_SR_DATA_ACK: // This device is a slave receiver and has received data - // First byte is the location then the bytes will be writen in buffer with auto-incriment + // First byte is the location then the bytes will be writen in buffer with auto-increment if (!slave_has_register_set) { buffer_address = TWDR; @@ -60,10 +70,25 @@ ISR(TWI_vect) { ack = 0; buffer_address = 0; } - slave_has_register_set = true; // address has been receaved now fill in buffer + slave_has_register_set = true; // address has been received now fill in buffer + +#if defined(USE_I2C) && defined(SPLIT_COMMON_TRANSACTIONS) + // Work out if we're attempting to execute a callback + is_callback_executor = buffer_address == split_transaction_table[I2C_EXECUTE_CALLBACK].initiator2target_offset; +#endif // defined(USE_I2C) && defined(SPLIT_COMMON_TRANSACTIONS) } else { i2c_slave_reg[buffer_address] = TWDR; buffer_address++; + +#if defined(USE_I2C) && defined(SPLIT_COMMON_TRANSACTIONS) + // If we're intending to execute a transaction callback, do so, as we've just received the transaction ID + if (is_callback_executor) { + split_transaction_desc_t *trans = &split_transaction_table[split_shmem->transaction_id]; + if (trans->slave_callback) { + trans->slave_callback(trans->initiator2target_buffer_size, split_trans_initiator2target_buffer(trans), trans->target2initiator_buffer_size, split_trans_target2initiator_buffer(trans)); + } + } +#endif // defined(USE_I2C) && defined(SPLIT_COMMON_TRANSACTIONS) } break; diff --git a/drivers/avr/i2c_slave.h b/drivers/avr/i2c_slave.h index 1cd0625ef4..a8647c9da3 100644 --- a/drivers/avr/i2c_slave.h +++ b/drivers/avr/i2c_slave.h @@ -22,7 +22,18 @@ #pragma once -#define I2C_SLAVE_REG_COUNT 30 +#ifndef I2C_SLAVE_REG_COUNT + +# if defined(USE_I2C) && defined(SPLIT_COMMON_TRANSACTIONS) +# include "transport.h" +# define I2C_SLAVE_REG_COUNT sizeof(split_shared_memory_t) +# else // defined(USE_I2C) && defined(SPLIT_COMMON_TRANSACTIONS) +# define I2C_SLAVE_REG_COUNT 30 +# endif // defined(USE_I2C) && defined(SPLIT_COMMON_TRANSACTIONS) + +#endif // I2C_SLAVE_REG_COUNT + +_Static_assert(I2C_SLAVE_REG_COUNT < 256, "I2C target registers must be single byte"); extern volatile uint8_t i2c_slave_reg[I2C_SLAVE_REG_COUNT]; diff --git a/drivers/avr/serial.c b/drivers/avr/serial.c index 3647bee0d3..9a7345a53d 100644 --- a/drivers/avr/serial.c +++ b/drivers/avr/serial.c @@ -224,15 +224,8 @@ # 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; +# define SLAVE_INT_ACK_WIDTH_UNIT 2 +# define SLAVE_INT_ACK_WIDTH 4 inline static void serial_delay(void) ALWAYS_INLINE; inline static void serial_delay(void) { _delay_us(SERIAL_DELAY); } @@ -259,16 +252,12 @@ inline static void serial_low(void) { writePinLow(SOFT_SERIAL_PIN); } inline static void serial_high(void) ALWAYS_INLINE; inline static void serial_high(void) { writePinHigh(SOFT_SERIAL_PIN); } -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; +void soft_serial_initiator_init(void) { 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; +void soft_serial_target_init(void) { serial_input_with_pullup(); // Enable INT0-INT7 @@ -395,19 +384,14 @@ static inline uint8_t nibble_bits_count(uint8_t 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); + bits = serial_read_chunk(&pecount, 8); tid = bits >> 3; - bits = (bits & 7) != nibble_bits_count(tid); - if (bits || pecount > 0 || tid > Transaction_table_size) { + bits = (bits & 7) != (nibble_bits_count(tid) & 7); + if (bits || pecount > 0 || tid > NUM_TOTAL_TRANSACTIONS) { return; } serial_delay_half1(); @@ -415,18 +399,22 @@ ISR(SERIAL_PIN_INTERRUPT) { 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]; + split_transaction_desc_t *trans = &split_transaction_table[tid]; serial_low(); // response step2 ack high->low -# endif + + // If the transaction has a callback, we can execute it now + if (trans->slave_callback) { + trans->slave_callback(trans->initiator2target_buffer_size, split_trans_initiator2target_buffer(trans), trans->target2initiator_buffer_size, split_trans_target2initiator_buffer(trans)); + } // target send phase - if (trans->target2initiator_buffer_size > 0) serial_send_packet((uint8_t *)trans->target2initiator_buffer, trans->target2initiator_buffer_size); + if (trans->target2initiator_buffer_size > 0) serial_send_packet((uint8_t *)split_trans_target2initiator_buffer(trans), 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)) { + if (serial_recive_packet((uint8_t *)split_trans_initiator2target_buffer(trans), trans->initiator2target_buffer_size)) { *trans->status = TRANSACTION_ACCEPTED; } else { *trans->status = TRANSACTION_DATA_ERROR; @@ -448,14 +436,12 @@ ISR(SERIAL_PIN_INTERRUPT) { // 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 + if (sstd_index > NUM_TOTAL_TRANSACTIONS) return TRANSACTION_TYPE_ERROR; + split_transaction_desc_t *trans = &split_transaction_table[sstd_index]; + + if (!trans->status) return TRANSACTION_TYPE_ERROR; // not registered + cli(); // signal to the target that we want to start a transaction @@ -463,27 +449,11 @@ int soft_serial_transaction(int sstd_index) { 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_write_chunk(tid, 8); serial_delay_half1(); // wait for the target response (step1 low->high) @@ -504,12 +474,11 @@ int soft_serial_transaction(int sstd_index) { } _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)) { + if (!serial_recive_packet((uint8_t *)split_trans_target2initiator_buffer(trans), trans->target2initiator_buffer_size)) { serial_output(); serial_high(); *trans->status = TRANSACTION_DATA_ERROR; @@ -523,7 +492,7 @@ int soft_serial_transaction(int sstd_index) { // initiator send phase if (trans->initiator2target_buffer_size > 0) { - serial_send_packet((uint8_t *)trans->initiator2target_buffer, trans->initiator2target_buffer_size); + serial_send_packet((uint8_t *)split_trans_initiator2target_buffer(trans), trans->initiator2target_buffer_size); } // always, release the line when not in use @@ -534,9 +503,8 @@ int soft_serial_transaction(int sstd_index) { 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]; + split_transaction_desc_t *trans = &split_transaction_table[sstd_index]; cli(); int retval = *trans->status; *trans->status = 0; @@ -544,8 +512,6 @@ int soft_serial_get_and_clean_status(int sstd_index) { sei(); return retval; } -# endif - #endif // Helix serial.c history diff --git a/drivers/avr/serial.h b/drivers/avr/serial.h deleted file mode 100644 index 53e66cf905..0000000000 --- a/drivers/avr/serial.h +++ /dev/null @@ -1,62 +0,0 @@ -#pragma once - -#include <stdbool.h> - -// ///////////////////////////////////////////////////////////////// -// Need Soft Serial defines in config.h -// ///////////////////////////////////////////////////////////////// -// ex. -// #define SOFT_SERIAL_PIN ?? // ?? = D0,D1,D2,D3,E6 -// OPTIONAL: #define SELECT_SOFT_SERIAL_SPEED ? // ? = 1,2,3,4,5 -// // 1: about 137kbps (default) -// // 2: about 75kbps -// // 3: about 39kbps -// // 4: about 26kbps -// // 5: about 20kbps -// -// //// USE simple API (using signle-type transaction function) -// /* nothing */ -// //// USE flexible API (using multi-type transaction function) -// #define SERIAL_USE_MULTI_TRANSACTION -// -// ///////////////////////////////////////////////////////////////// - -// Soft Serial Transaction Descriptor -typedef struct _SSTD_t { - uint8_t *status; - uint8_t initiator2target_buffer_size; - uint8_t *initiator2target_buffer; - uint8_t target2initiator_buffer_size; - uint8_t *target2initiator_buffer; -} SSTD_t; -#define TID_LIMIT(table) (sizeof(table) / sizeof(SSTD_t)) - -// initiator is transaction start side -void soft_serial_initiator_init(SSTD_t *sstd_table, int sstd_table_size); -// target is interrupt accept side -void soft_serial_target_init(SSTD_t *sstd_table, int sstd_table_size); - -// initiator resullt -#define TRANSACTION_END 0 -#define TRANSACTION_NO_RESPONSE 0x1 -#define TRANSACTION_DATA_ERROR 0x2 -#define TRANSACTION_TYPE_ERROR 0x4 -#ifndef SERIAL_USE_MULTI_TRANSACTION -int soft_serial_transaction(void); -#else -int soft_serial_transaction(int sstd_index); -#endif - -// target status -// *SSTD_t.status has -// initiator: -// TRANSACTION_END -// or TRANSACTION_NO_RESPONSE -// or TRANSACTION_DATA_ERROR -// target: -// TRANSACTION_DATA_ERROR -// or TRANSACTION_ACCEPTED -#define TRANSACTION_ACCEPTED 0x8 -#ifdef SERIAL_USE_MULTI_TRANSACTION -int soft_serial_get_and_clean_status(int sstd_index); -#endif diff --git a/drivers/chibios/serial.c b/drivers/chibios/serial.c index 54f7e1321f..f54fbcee4e 100644 --- a/drivers/chibios/serial.c +++ b/drivers/chibios/serial.c @@ -74,21 +74,12 @@ static THD_FUNCTION(Thread1, arg) { } } -static SSTD_t *Transaction_table = NULL; -static uint8_t Transaction_table_size = 0; - -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; - +void soft_serial_initiator_init(void) { 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; - +void soft_serial_target_init(void) { serial_input(); palEnablePadEvent(PAL_PORT(SOFT_SERIAL_PIN), PAL_PAD(SOFT_SERIAL_PIN), PAL_EVENT_MODE_FALLING_EDGE); @@ -154,16 +145,14 @@ void interrupt_handler(void *arg) { uint8_t checksum_computed = 0; int sstd_index = 0; -#ifdef SERIAL_USE_MULTI_TRANSACTION sstd_index = serial_read_byte(); sync_send(); -#endif - SSTD_t *trans = &Transaction_table[sstd_index]; + split_transaction_desc_t *trans = &split_transaction_table[sstd_index]; for (int i = 0; i < trans->initiator2target_buffer_size; ++i) { - trans->initiator2target_buffer[i] = serial_read_byte(); + split_trans_initiator2target_buffer(trans)[i] = serial_read_byte(); sync_send(); - checksum_computed += trans->initiator2target_buffer[i]; + checksum_computed += split_trans_initiator2target_buffer(trans)[i]; } checksum_computed ^= 7; uint8_t checksum_received = serial_read_byte(); @@ -172,12 +161,17 @@ void interrupt_handler(void *arg) { // wait for the sync to finish sending serial_delay(); + // Allow any slave processing to occur + if (trans->slave_callback) { + trans->slave_callback(trans->initiator2target_buffer_size, split_trans_initiator2target_buffer(trans), trans->target2initiator_buffer_size, split_trans_target2initiator_buffer(trans)); + } + uint8_t checksum = 0; for (int i = 0; i < trans->target2initiator_buffer_size; ++i) { - serial_write_byte(trans->target2initiator_buffer[i]); + serial_write_byte(split_trans_target2initiator_buffer(trans)[i]); sync_send(); serial_delay_half(); - checksum += trans->target2initiator_buffer[i]; + checksum += split_trans_target2initiator_buffer(trans)[i]; } serial_write_byte(checksum ^ 7); sync_send(); @@ -206,15 +200,10 @@ void interrupt_handler(void *arg) { // 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) { - int sstd_index = 0; -#else int soft_serial_transaction(int sstd_index) { -#endif - - if (sstd_index > Transaction_table_size) return TRANSACTION_TYPE_ERROR; - SSTD_t *trans = &Transaction_table[sstd_index]; + if (sstd_index > NUM_TOTAL_TRANSACTIONS) return TRANSACTION_TYPE_ERROR; + split_transaction_desc_t *trans = &split_transaction_table[sstd_index]; + if (!trans->status) return TRANSACTION_TYPE_ERROR; // not registered // TODO: remove extra delay between transactions serial_delay(); @@ -244,14 +233,13 @@ int soft_serial_transaction(int sstd_index) { uint8_t checksum = 0; // send data to the slave -#ifdef SERIAL_USE_MULTI_TRANSACTION serial_write_byte(sstd_index); // first chunk is transaction id sync_recv(); -#endif + for (int i = 0; i < trans->initiator2target_buffer_size; ++i) { - serial_write_byte(trans->initiator2target_buffer[i]); + serial_write_byte(split_trans_initi |