qmk-firmware/quantum/split_common/transactions.c

817 lines
34 KiB
C
Raw Normal View History

/* Copyright 2021 QMK
*
* 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/>.
*/
#include <string.h>
#include <stddef.h>
#include "crc.h"
#include "debug.h"
#include "matrix.h"
#include "quantum.h"
#include "transactions.h"
#include "transport.h"
#include "transaction_id_define.h"
#include "split_util.h"
#include "synchronization_util.h"
#define SYNC_TIMER_OFFSET 2
#ifndef FORCED_SYNC_THROTTLE_MS
# define FORCED_SYNC_THROTTLE_MS 100
#endif // FORCED_SYNC_THROTTLE_MS
#define sizeof_member(type, member) sizeof(((type *)NULL)->member)
#define trans_initiator2target_initializer_cb(member, cb) \
{ sizeof_member(split_shared_memory_t, member), offsetof(split_shared_memory_t, member), 0, 0, cb }
#define trans_initiator2target_initializer(member) trans_initiator2target_initializer_cb(member, NULL)
#define trans_target2initiator_initializer_cb(member, cb) \
{ 0, 0, sizeof_member(split_shared_memory_t, member), offsetof(split_shared_memory_t, member), cb }
#define trans_target2initiator_initializer(member) trans_target2initiator_initializer_cb(member, NULL)
2021-11-01 20:18:33 +01:00
#define transport_write(id, data, length) transport_execute_transaction(id, data, length, NULL, 0)
#define transport_read(id, data, length) transport_execute_transaction(id, NULL, 0, data, length)
#if defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER)
// Forward-declare the RPC callback handlers
void slave_rpc_info_callback(uint8_t initiator2target_buffer_size, const void *initiator2target_buffer, uint8_t target2initiator_buffer_size, void *target2initiator_buffer);
void slave_rpc_exec_callback(uint8_t initiator2target_buffer_size, const void *initiator2target_buffer, uint8_t target2initiator_buffer_size, void *target2initiator_buffer);
#endif // defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER)
////////////////////////////////////////////////////
// Helpers
Make solo half of split keyboards (more) usable. (#13523) * Make solo half of split keyboards (more) usable. Using only one half of a split keyboard (that's using the split_common framework to communicate) is not a great experience, since several read timeouts per scan cycle cause an unusably slow scan rate. This change blocks all split communication attempts for 500 ms (configurable) after an error occurs, causing the scan rate to become at least _more_ usable, but might need some tweaking to work fully on most keyboards. One read timeout still needs to occur after the 500 ms has passed, and if that timeout isn't low enough, some scan cycles may still be too slow. * Fix lint complaint. * Require 25 consecutive comm errors to see comms as disconnected. The number of max errors can be overridden by defining `SPLIT_MAX_CONNECTION_ERRORS`. * Add comments to new defines, and ability to disable disconnection check. Also increase `SPLIT_MAX_CONNECTION_ERRORS` to 40, since it's divisible by most relevant numbers for the description. * Make lint happy ...again * Only update `connection_check_timer` when needed. * Add new defines to split keyboard documentation. * Move connection timeout logic to transport.c, add `is_transport_connected`. * Use split_common disconnection logic in matrix.c. Instead of doing more or less the same thing twice. * Move disconnection logic to `transport_master`. Is a cleaner implementation, and causes the scan rate while disconnected to increase instead of decrease. * Lint fixes. * Lower default `SERIAL_USART_TIMEOUT` to 20 ms. The read timeout must be low enough to not cause exessively long scan cycles when using a solo split half. 10 ms was determined from testing to work fine even with the slowest defined baudrate of 19200 (5 ms was too low for that case), so 20 ms should be fine for most cases. * Remove `SERIAL_USART_TIMEOUT` from ergodox_infinity/config.h Was somewhat mistakenly included in an earlier PR. * Fix building with `USE_I2C`. * Reduce built firmware size. Not really sure why this works, the idea was taken from tzarc's work on split disconnection. * Tweak and improve opt-out for split disconnection logic. There are now two ways to opt out from this feature: * Set `SPLIT_MAX_CONNECTION_ERRORS` to 0. This will completely disable the connection status checks (also affects the slave matrix reset logic in matrix.c, though). * Set `SPLIT_CONNECTION_CHECK_TIMEOUT` to 0. This will only disable the communication throttling while disconnected. Will make the firmware smaller. * Make split disconnection logic work with custom transports. Includes a fallback implementation for keyboards using a custom split_util.c but not a custom matrix.c (currently no such keyboard seems to be merged, though). * Remove unnecessary include of timer.h Co-authored-by: Joel Challis <git@zvecr.com> Co-authored-by: Joel Challis <git@zvecr.com>
2021-08-22 02:51:17 +02:00
static bool transaction_handler_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[], const char *prefix, bool (*handler)(matrix_row_t master_matrix[], matrix_row_t slave_matrix[])) {
int num_retries = is_transport_connected() ? 10 : 1;
for (int iter = 1; iter <= num_retries; ++iter) {
if (iter > 1) {
for (int i = 0; i < iter * iter; ++i) {
wait_us(10);
}
}
Make solo half of split keyboards (more) usable. (#13523) * Make solo half of split keyboards (more) usable. Using only one half of a split keyboard (that's using the split_common framework to communicate) is not a great experience, since several read timeouts per scan cycle cause an unusably slow scan rate. This change blocks all split communication attempts for 500 ms (configurable) after an error occurs, causing the scan rate to become at least _more_ usable, but might need some tweaking to work fully on most keyboards. One read timeout still needs to occur after the 500 ms has passed, and if that timeout isn't low enough, some scan cycles may still be too slow. * Fix lint complaint. * Require 25 consecutive comm errors to see comms as disconnected. The number of max errors can be overridden by defining `SPLIT_MAX_CONNECTION_ERRORS`. * Add comments to new defines, and ability to disable disconnection check. Also increase `SPLIT_MAX_CONNECTION_ERRORS` to 40, since it's divisible by most relevant numbers for the description. * Make lint happy ...again * Only update `connection_check_timer` when needed. * Add new defines to split keyboard documentation. * Move connection timeout logic to transport.c, add `is_transport_connected`. * Use split_common disconnection logic in matrix.c. Instead of doing more or less the same thing twice. * Move disconnection logic to `transport_master`. Is a cleaner implementation, and causes the scan rate while disconnected to increase instead of decrease. * Lint fixes. * Lower default `SERIAL_USART_TIMEOUT` to 20 ms. The read timeout must be low enough to not cause exessively long scan cycles when using a solo split half. 10 ms was determined from testing to work fine even with the slowest defined baudrate of 19200 (5 ms was too low for that case), so 20 ms should be fine for most cases. * Remove `SERIAL_USART_TIMEOUT` from ergodox_infinity/config.h Was somewhat mistakenly included in an earlier PR. * Fix building with `USE_I2C`. * Reduce built firmware size. Not really sure why this works, the idea was taken from tzarc's work on split disconnection. * Tweak and improve opt-out for split disconnection logic. There are now two ways to opt out from this feature: * Set `SPLIT_MAX_CONNECTION_ERRORS` to 0. This will completely disable the connection status checks (also affects the slave matrix reset logic in matrix.c, though). * Set `SPLIT_CONNECTION_CHECK_TIMEOUT` to 0. This will only disable the communication throttling while disconnected. Will make the firmware smaller. * Make split disconnection logic work with custom transports. Includes a fallback implementation for keyboards using a custom split_util.c but not a custom matrix.c (currently no such keyboard seems to be merged, though). * Remove unnecessary include of timer.h Co-authored-by: Joel Challis <git@zvecr.com> Co-authored-by: Joel Challis <git@zvecr.com>
2021-08-22 02:51:17 +02:00
bool this_okay = true;
this_okay = handler(master_matrix, slave_matrix);
Make solo half of split keyboards (more) usable. (#13523) * Make solo half of split keyboards (more) usable. Using only one half of a split keyboard (that's using the split_common framework to communicate) is not a great experience, since several read timeouts per scan cycle cause an unusably slow scan rate. This change blocks all split communication attempts for 500 ms (configurable) after an error occurs, causing the scan rate to become at least _more_ usable, but might need some tweaking to work fully on most keyboards. One read timeout still needs to occur after the 500 ms has passed, and if that timeout isn't low enough, some scan cycles may still be too slow. * Fix lint complaint. * Require 25 consecutive comm errors to see comms as disconnected. The number of max errors can be overridden by defining `SPLIT_MAX_CONNECTION_ERRORS`. * Add comments to new defines, and ability to disable disconnection check. Also increase `SPLIT_MAX_CONNECTION_ERRORS` to 40, since it's divisible by most relevant numbers for the description. * Make lint happy ...again * Only update `connection_check_timer` when needed. * Add new defines to split keyboard documentation. * Move connection timeout logic to transport.c, add `is_transport_connected`. * Use split_common disconnection logic in matrix.c. Instead of doing more or less the same thing twice. * Move disconnection logic to `transport_master`. Is a cleaner implementation, and causes the scan rate while disconnected to increase instead of decrease. * Lint fixes. * Lower default `SERIAL_USART_TIMEOUT` to 20 ms. The read timeout must be low enough to not cause exessively long scan cycles when using a solo split half. 10 ms was determined from testing to work fine even with the slowest defined baudrate of 19200 (5 ms was too low for that case), so 20 ms should be fine for most cases. * Remove `SERIAL_USART_TIMEOUT` from ergodox_infinity/config.h Was somewhat mistakenly included in an earlier PR. * Fix building with `USE_I2C`. * Reduce built firmware size. Not really sure why this works, the idea was taken from tzarc's work on split disconnection. * Tweak and improve opt-out for split disconnection logic. There are now two ways to opt out from this feature: * Set `SPLIT_MAX_CONNECTION_ERRORS` to 0. This will completely disable the connection status checks (also affects the slave matrix reset logic in matrix.c, though). * Set `SPLIT_CONNECTION_CHECK_TIMEOUT` to 0. This will only disable the communication throttling while disconnected. Will make the firmware smaller. * Make split disconnection logic work with custom transports. Includes a fallback implementation for keyboards using a custom split_util.c but not a custom matrix.c (currently no such keyboard seems to be merged, though). * Remove unnecessary include of timer.h Co-authored-by: Joel Challis <git@zvecr.com> Co-authored-by: Joel Challis <git@zvecr.com>
2021-08-22 02:51:17 +02:00
if (this_okay) return true;
}
Make solo half of split keyboards (more) usable. (#13523) * Make solo half of split keyboards (more) usable. Using only one half of a split keyboard (that's using the split_common framework to communicate) is not a great experience, since several read timeouts per scan cycle cause an unusably slow scan rate. This change blocks all split communication attempts for 500 ms (configurable) after an error occurs, causing the scan rate to become at least _more_ usable, but might need some tweaking to work fully on most keyboards. One read timeout still needs to occur after the 500 ms has passed, and if that timeout isn't low enough, some scan cycles may still be too slow. * Fix lint complaint. * Require 25 consecutive comm errors to see comms as disconnected. The number of max errors can be overridden by defining `SPLIT_MAX_CONNECTION_ERRORS`. * Add comments to new defines, and ability to disable disconnection check. Also increase `SPLIT_MAX_CONNECTION_ERRORS` to 40, since it's divisible by most relevant numbers for the description. * Make lint happy ...again * Only update `connection_check_timer` when needed. * Add new defines to split keyboard documentation. * Move connection timeout logic to transport.c, add `is_transport_connected`. * Use split_common disconnection logic in matrix.c. Instead of doing more or less the same thing twice. * Move disconnection logic to `transport_master`. Is a cleaner implementation, and causes the scan rate while disconnected to increase instead of decrease. * Lint fixes. * Lower default `SERIAL_USART_TIMEOUT` to 20 ms. The read timeout must be low enough to not cause exessively long scan cycles when using a solo split half. 10 ms was determined from testing to work fine even with the slowest defined baudrate of 19200 (5 ms was too low for that case), so 20 ms should be fine for most cases. * Remove `SERIAL_USART_TIMEOUT` from ergodox_infinity/config.h Was somewhat mistakenly included in an earlier PR. * Fix building with `USE_I2C`. * Reduce built firmware size. Not really sure why this works, the idea was taken from tzarc's work on split disconnection. * Tweak and improve opt-out for split disconnection logic. There are now two ways to opt out from this feature: * Set `SPLIT_MAX_CONNECTION_ERRORS` to 0. This will completely disable the connection status checks (also affects the slave matrix reset logic in matrix.c, though). * Set `SPLIT_CONNECTION_CHECK_TIMEOUT` to 0. This will only disable the communication throttling while disconnected. Will make the firmware smaller. * Make split disconnection logic work with custom transports. Includes a fallback implementation for keyboards using a custom split_util.c but not a custom matrix.c (currently no such keyboard seems to be merged, though). * Remove unnecessary include of timer.h Co-authored-by: Joel Challis <git@zvecr.com> Co-authored-by: Joel Challis <git@zvecr.com>
2021-08-22 02:51:17 +02:00
dprintf("Failed to execute %s\n", prefix);
return false;
}
Make solo half of split keyboards (more) usable. (#13523) * Make solo half of split keyboards (more) usable. Using only one half of a split keyboard (that's using the split_common framework to communicate) is not a great experience, since several read timeouts per scan cycle cause an unusably slow scan rate. This change blocks all split communication attempts for 500 ms (configurable) after an error occurs, causing the scan rate to become at least _more_ usable, but might need some tweaking to work fully on most keyboards. One read timeout still needs to occur after the 500 ms has passed, and if that timeout isn't low enough, some scan cycles may still be too slow. * Fix lint complaint. * Require 25 consecutive comm errors to see comms as disconnected. The number of max errors can be overridden by defining `SPLIT_MAX_CONNECTION_ERRORS`. * Add comments to new defines, and ability to disable disconnection check. Also increase `SPLIT_MAX_CONNECTION_ERRORS` to 40, since it's divisible by most relevant numbers for the description. * Make lint happy ...again * Only update `connection_check_timer` when needed. * Add new defines to split keyboard documentation. * Move connection timeout logic to transport.c, add `is_transport_connected`. * Use split_common disconnection logic in matrix.c. Instead of doing more or less the same thing twice. * Move disconnection logic to `transport_master`. Is a cleaner implementation, and causes the scan rate while disconnected to increase instead of decrease. * Lint fixes. * Lower default `SERIAL_USART_TIMEOUT` to 20 ms. The read timeout must be low enough to not cause exessively long scan cycles when using a solo split half. 10 ms was determined from testing to work fine even with the slowest defined baudrate of 19200 (5 ms was too low for that case), so 20 ms should be fine for most cases. * Remove `SERIAL_USART_TIMEOUT` from ergodox_infinity/config.h Was somewhat mistakenly included in an earlier PR. * Fix building with `USE_I2C`. * Reduce built firmware size. Not really sure why this works, the idea was taken from tzarc's work on split disconnection. * Tweak and improve opt-out for split disconnection logic. There are now two ways to opt out from this feature: * Set `SPLIT_MAX_CONNECTION_ERRORS` to 0. This will completely disable the connection status checks (also affects the slave matrix reset logic in matrix.c, though). * Set `SPLIT_CONNECTION_CHECK_TIMEOUT` to 0. This will only disable the communication throttling while disconnected. Will make the firmware smaller. * Make split disconnection logic work with custom transports. Includes a fallback implementation for keyboards using a custom split_util.c but not a custom matrix.c (currently no such keyboard seems to be merged, though). * Remove unnecessary include of timer.h Co-authored-by: Joel Challis <git@zvecr.com> Co-authored-by: Joel Challis <git@zvecr.com>
2021-08-22 02:51:17 +02:00
#define TRANSACTION_HANDLER_MASTER(prefix) \
do { \
if (!transaction_handler_master(master_matrix, slave_matrix, #prefix, &prefix##_handlers_master)) return false; \
} while (0)
#define TRANSACTION_HANDLER_SLAVE(prefix) \
do { \
split_shared_memory_lock(); \
prefix##_handlers_slave(master_matrix, slave_matrix); \
split_shared_memory_unlock(); \
} while (0)
inline static bool read_if_checksum_mismatch(int8_t trans_id_checksum, int8_t trans_id_retrieve, uint32_t *last_update, void *destination, const void *equiv_shmem, size_t length) {
uint8_t curr_checksum;
bool okay = transport_read(trans_id_checksum, &curr_checksum, sizeof(curr_checksum));
if (okay && (timer_elapsed32(*last_update) >= FORCED_SYNC_THROTTLE_MS || curr_checksum != crc8(equiv_shmem, length))) {
okay &= transport_read(trans_id_retrieve, destination, length);
okay &= curr_checksum == crc8(equiv_shmem, length);
if (okay) {
*last_update = timer_read32();
}
} else {
memcpy(destination, equiv_shmem, length);
}
return okay;
}
inline static bool send_if_condition(int8_t trans_id, uint32_t *last_update, bool condition, void *source, size_t length) {
bool okay = true;
if (timer_elapsed32(*last_update) >= FORCED_SYNC_THROTTLE_MS || condition) {
okay &= transport_write(trans_id, source, length);
if (okay) {
*last_update = timer_read32();
}
}
return okay;
}
inline static bool send_if_data_mismatch(int8_t trans_id, uint32_t *last_update, void *source, const void *equiv_shmem, size_t length) {
// Just run a memcmp to compare the source and equivalent shmem location
return send_if_condition(trans_id, last_update, (memcmp(source, equiv_shmem, length) != 0), source, length);
}
////////////////////////////////////////////////////
// Slave matrix
static bool slave_matrix_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
static uint32_t last_update = 0;
static matrix_row_t last_matrix[(MATRIX_ROWS) / 2] = {0}; // last successfully-read matrix, so we can replicate if there are checksum errors
matrix_row_t temp_matrix[(MATRIX_ROWS) / 2]; // holding area while we test whether or not checksum is correct
bool okay = read_if_checksum_mismatch(GET_SLAVE_MATRIX_CHECKSUM, GET_SLAVE_MATRIX_DATA, &last_update, temp_matrix, split_shmem->smatrix.matrix, sizeof(split_shmem->smatrix.matrix));
if (okay) {
// Checksum matches the received data, save as the last matrix state
memcpy(last_matrix, temp_matrix, sizeof(temp_matrix));
}
// Copy out the last-known-good matrix state to the slave matrix
memcpy(slave_matrix, last_matrix, sizeof(last_matrix));
return okay;
}
static void slave_matrix_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
memcpy(split_shmem->smatrix.matrix, slave_matrix, sizeof(split_shmem->smatrix.matrix));
split_shmem->smatrix.checksum = crc8(split_shmem->smatrix.matrix, sizeof(split_shmem->smatrix.matrix));
}
// clang-format off
Make solo half of split keyboards (more) usable. (#13523) * Make solo half of split keyboards (more) usable. Using only one half of a split keyboard (that's using the split_common framework to communicate) is not a great experience, since several read timeouts per scan cycle cause an unusably slow scan rate. This change blocks all split communication attempts for 500 ms (configurable) after an error occurs, causing the scan rate to become at least _more_ usable, but might need some tweaking to work fully on most keyboards. One read timeout still needs to occur after the 500 ms has passed, and if that timeout isn't low enough, some scan cycles may still be too slow. * Fix lint complaint. * Require 25 consecutive comm errors to see comms as disconnected. The number of max errors can be overridden by defining `SPLIT_MAX_CONNECTION_ERRORS`. * Add comments to new defines, and ability to disable disconnection check. Also increase `SPLIT_MAX_CONNECTION_ERRORS` to 40, since it's divisible by most relevant numbers for the description. * Make lint happy ...again * Only update `connection_check_timer` when needed. * Add new defines to split keyboard documentation. * Move connection timeout logic to transport.c, add `is_transport_connected`. * Use split_common disconnection logic in matrix.c. Instead of doing more or less the same thing twice. * Move disconnection logic to `transport_master`. Is a cleaner implementation, and causes the scan rate while disconnected to increase instead of decrease. * Lint fixes. * Lower default `SERIAL_USART_TIMEOUT` to 20 ms. The read timeout must be low enough to not cause exessively long scan cycles when using a solo split half. 10 ms was determined from testing to work fine even with the slowest defined baudrate of 19200 (5 ms was too low for that case), so 20 ms should be fine for most cases. * Remove `SERIAL_USART_TIMEOUT` from ergodox_infinity/config.h Was somewhat mistakenly included in an earlier PR. * Fix building with `USE_I2C`. * Reduce built firmware size. Not really sure why this works, the idea was taken from tzarc's work on split disconnection. * Tweak and improve opt-out for split disconnection logic. There are now two ways to opt out from this feature: * Set `SPLIT_MAX_CONNECTION_ERRORS` to 0. This will completely disable the connection status checks (also affects the slave matrix reset logic in matrix.c, though). * Set `SPLIT_CONNECTION_CHECK_TIMEOUT` to 0. This will only disable the communication throttling while disconnected. Will make the firmware smaller. * Make split disconnection logic work with custom transports. Includes a fallback implementation for keyboards using a custom split_util.c but not a custom matrix.c (currently no such keyboard seems to be merged, though). * Remove unnecessary include of timer.h Co-authored-by: Joel Challis <git@zvecr.com> Co-authored-by: Joel Challis <git@zvecr.com>
2021-08-22 02:51:17 +02:00
#define TRANSACTIONS_SLAVE_MATRIX_MASTER() TRANSACTION_HANDLER_MASTER(slave_matrix)
#define TRANSACTIONS_SLAVE_MATRIX_SLAVE() TRANSACTION_HANDLER_SLAVE(slave_matrix)
#define TRANSACTIONS_SLAVE_MATRIX_REGISTRATIONS \
[GET_SLAVE_MATRIX_CHECKSUM] = trans_target2initiator_initializer(smatrix.checksum), \
[GET_SLAVE_MATRIX_DATA] = trans_target2initiator_initializer(smatrix.matrix),
// clang-format on
////////////////////////////////////////////////////
// Master matrix
#ifdef SPLIT_TRANSPORT_MIRROR
static bool master_matrix_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
static uint32_t last_update = 0;
return send_if_data_mismatch(PUT_MASTER_MATRIX, &last_update, master_matrix, split_shmem->mmatrix.matrix, sizeof(split_shmem->mmatrix.matrix));
}
static void master_matrix_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
// Always copy to the master matrix
memcpy(master_matrix, split_shmem->mmatrix.matrix, sizeof(split_shmem->mmatrix.matrix));
}
2021-11-01 20:18:33 +01:00
# define TRANSACTIONS_MASTER_MATRIX_MASTER() TRANSACTION_HANDLER_MASTER(master_matrix)
# define TRANSACTIONS_MASTER_MATRIX_SLAVE() TRANSACTION_HANDLER_SLAVE(master_matrix)
# define TRANSACTIONS_MASTER_MATRIX_REGISTRATIONS [PUT_MASTER_MATRIX] = trans_initiator2target_initializer(mmatrix.matrix),
#else // SPLIT_TRANSPORT_MIRROR
# define TRANSACTIONS_MASTER_MATRIX_MASTER()
# define TRANSACTIONS_MASTER_MATRIX_SLAVE()
# define TRANSACTIONS_MASTER_MATRIX_REGISTRATIONS
#endif // SPLIT_TRANSPORT_MIRROR
////////////////////////////////////////////////////
// Encoders
#ifdef ENCODER_ENABLE
static bool encoder_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
static uint32_t last_update = 0;
uint8_t temp_state[NUM_ENCODERS_MAX_PER_SIDE];
bool okay = read_if_checksum_mismatch(GET_ENCODERS_CHECKSUM, GET_ENCODERS_DATA, &last_update, temp_state, split_shmem->encoders.state, sizeof(temp_state));
if (okay) encoder_update_raw(temp_state);
return okay;
}
static void encoder_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
uint8_t encoder_state[NUM_ENCODERS_MAX_PER_SIDE];
encoder_state_raw(encoder_state);
// Always prepare the encoder state for read.
memcpy(split_shmem->encoders.state, encoder_state, sizeof(encoder_state));
// Now update the checksum given that the encoders has been written to
split_shmem->encoders.checksum = crc8(encoder_state, sizeof(encoder_state));
}
// clang-format off
Make solo half of split keyboards (more) usable. (#13523) * Make solo half of split keyboards (more) usable. Using only one half of a split keyboard (that's using the split_common framework to communicate) is not a great experience, since several read timeouts per scan cycle cause an unusably slow scan rate. This change blocks all split communication attempts for 500 ms (configurable) after an error occurs, causing the scan rate to become at least _more_ usable, but might need some tweaking to work fully on most keyboards. One read timeout still needs to occur after the 500 ms has passed, and if that timeout isn't low enough, some scan cycles may still be too slow. * Fix lint complaint. * Require 25 consecutive comm errors to see comms as disconnected. The number of max errors can be overridden by defining `SPLIT_MAX_CONNECTION_ERRORS`. * Add comments to new defines, and ability to disable disconnection check. Also increase `SPLIT_MAX_CONNECTION_ERRORS` to 40, since it's divisible by most relevant numbers for the description. * Make lint happy ...again * Only update `connection_check_timer` when needed. * Add new defines to split keyboard documentation. * Move connection timeout logic to transport.c, add `is_transport_connected`. * Use split_common disconnection logic in matrix.c. Instead of doing more or less the same thing twice. * Move disconnection logic to `transport_master`. Is a cleaner implementation, and causes the scan rate while disconnected to increase instead of decrease. * Lint fixes. * Lower default `SERIAL_USART_TIMEOUT` to 20 ms. The read timeout must be low enough to not cause exessively long scan cycles when using a solo split half. 10 ms was determined from testing to work fine even with the slowest defined baudrate of 19200 (5 ms was too low for that case), so 20 ms should be fine for most cases. * Remove `SERIAL_USART_TIMEOUT` from ergodox_infinity/config.h Was somewhat mistakenly included in an earlier PR. * Fix building with `USE_I2C`. * Reduce built firmware size. Not really sure why this works, the idea was taken from tzarc's work on split disconnection. * Tweak and improve opt-out for split disconnection logic. There are now two ways to opt out from this feature: * Set `SPLIT_MAX_CONNECTION_ERRORS` to 0. This will completely disable the connection status checks (also affects the slave matrix reset logic in matrix.c, though). * Set `SPLIT_CONNECTION_CHECK_TIMEOUT` to 0. This will only disable the communication throttling while disconnected. Will make the firmware smaller. * Make split disconnection logic work with custom transports. Includes a fallback implementation for keyboards using a custom split_util.c but not a custom matrix.c (currently no such keyboard seems to be merged, though). * Remove unnecessary include of timer.h Co-authored-by: Joel Challis <git@zvecr.com> Co-authored-by: Joel Challis <git@zvecr.com>
2021-08-22 02:51:17 +02:00
# define TRANSACTIONS_ENCODERS_MASTER() TRANSACTION_HANDLER_MASTER(encoder)
# define TRANSACTIONS_ENCODERS_SLAVE() TRANSACTION_HANDLER_SLAVE(encoder)
# define TRANSACTIONS_ENCODERS_REGISTRATIONS \
[GET_ENCODERS_CHECKSUM] = trans_target2initiator_initializer(encoders.checksum), \
[GET_ENCODERS_DATA] = trans_target2initiator_initializer(encoders.state),
// clang-format on
#else // ENCODER_ENABLE
# define TRANSACTIONS_ENCODERS_MASTER()
# define TRANSACTIONS_ENCODERS_SLAVE()
# define TRANSACTIONS_ENCODERS_REGISTRATIONS
#endif // ENCODER_ENABLE
////////////////////////////////////////////////////
// Sync timer
#ifndef DISABLE_SYNC_TIMER
static bool sync_timer_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
static uint32_t last_update = 0;
bool okay = true;
if (timer_elapsed32(last_update) >= FORCED_SYNC_THROTTLE_MS) {
uint32_t sync_timer = sync_timer_read32() + SYNC_TIMER_OFFSET;
okay &= transport_write(PUT_SYNC_TIMER, &sync_timer, sizeof(sync_timer));
if (okay) {
last_update = timer_read32();
}
}
return okay;
}
static void sync_timer_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
static uint32_t last_sync_timer = 0;
if (last_sync_timer != split_shmem->sync_timer) {
last_sync_timer = split_shmem->sync_timer;
sync_timer_update(last_sync_timer);
}
}
2021-11-01 20:18:33 +01:00
# define TRANSACTIONS_SYNC_TIMER_MASTER() TRANSACTION_HANDLER_MASTER(sync_timer)
# define TRANSACTIONS_SYNC_TIMER_SLAVE() TRANSACTION_HANDLER_SLAVE(sync_timer)
# define TRANSACTIONS_SYNC_TIMER_REGISTRATIONS [PUT_SYNC_TIMER] = trans_initiator2target_initializer(sync_timer),
#else // DISABLE_SYNC_TIMER
# define TRANSACTIONS_SYNC_TIMER_MASTER()
# define TRANSACTIONS_SYNC_TIMER_SLAVE()
# define TRANSACTIONS_SYNC_TIMER_REGISTRATIONS
#endif // DISABLE_SYNC_TIMER
////////////////////////////////////////////////////
// Layer state
#if !defined(NO_ACTION_LAYER) && defined(SPLIT_LAYER_STATE_ENABLE)
static bool layer_state_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
static uint32_t last_layer_state_update = 0;
static uint32_t last_default_layer_state_update = 0;
bool okay = send_if_condition(PUT_LAYER_STATE, &last_layer_state_update, (layer_state != split_shmem->layers.layer_state), &layer_state, sizeof(layer_state));
if (okay) {
okay &= send_if_condition(PUT_DEFAULT_LAYER_STATE, &last_default_layer_state_update, (default_layer_state != split_shmem->layers.default_layer_state), &default_layer_state, sizeof(default_layer_state));
}
return okay;
}
static void layer_state_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
layer_state = split_shmem->layers.layer_state;
default_layer_state = split_shmem->layers.default_layer_state;
}
// clang-format off
Make solo half of split keyboards (more) usable. (#13523) * Make solo half of split keyboards (more) usable. Using only one half of a split keyboard (that's using the split_common framework to communicate) is not a great experience, since several read timeouts per scan cycle cause an unusably slow scan rate. This change blocks all split communication attempts for 500 ms (configurable) after an error occurs, causing the scan rate to become at least _more_ usable, but might need some tweaking to work fully on most keyboards. One read timeout still needs to occur after the 500 ms has passed, and if that timeout isn't low enough, some scan cycles may still be too slow. * Fix lint complaint. * Require 25 consecutive comm errors to see comms as disconnected. The number of max errors can be overridden by defining `SPLIT_MAX_CONNECTION_ERRORS`. * Add comments to new defines, and ability to disable disconnection check. Also increase `SPLIT_MAX_CONNECTION_ERRORS` to 40, since it's divisible by most relevant numbers for the description. * Make lint happy ...again * Only update `connection_check_timer` when needed. * Add new defines to split keyboard documentation. * Move connection timeout logic to transport.c, add `is_transport_connected`. * Use split_common disconnection logic in matrix.c. Instead of doing more or less the same thing twice. * Move disconnection logic to `transport_master`. Is a cleaner implementation, and causes the scan rate while disconnected to increase instead of decrease. * Lint fixes. * Lower default `SERIAL_USART_TIMEOUT` to 20 ms. The read timeout must be low enough to not cause exessively long scan cycles when using a solo split half. 10 ms was determined from testing to work fine even with the slowest defined baudrate of 19200 (5 ms was too low for that case), so 20 ms should be fine for most cases. * Remove `SERIAL_USART_TIMEOUT` from ergodox_infinity/config.h Was somewhat mistakenly included in an earlier PR. * Fix building with `USE_I2C`. * Reduce built firmware size. Not really sure why this works, the idea was taken from tzarc's work on split disconnection. * Tweak and improve opt-out for split disconnection logic. There are now two ways to opt out from this feature: * Set `SPLIT_MAX_CONNECTION_ERRORS` to 0. This will completely disable the connection status checks (also affects the slave matrix reset logic in matrix.c, though). * Set `SPLIT_CONNECTION_CHECK_TIMEOUT` to 0. This will only disable the communication throttling while disconnected. Will make the firmware smaller. * Make split disconnection logic work with custom transports. Includes a fallback implementation for keyboards using a custom split_util.c but not a custom matrix.c (currently no such keyboard seems to be merged, though). * Remove unnecessary include of timer.h Co-authored-by: Joel Challis <git@zvecr.com> Co-authored-by: Joel Challis <git@zvecr.com>
2021-08-22 02:51:17 +02:00
# define TRANSACTIONS_LAYER_STATE_MASTER() TRANSACTION_HANDLER_MASTER(layer_state)
# define TRANSACTIONS_LAYER_STATE_SLAVE() TRANSACTION_HANDLER_SLAVE(layer_state)
# define TRANSACTIONS_LAYER_STATE_REGISTRATIONS \
[PUT_LAYER_STATE] = trans_initiator2target_initializer(layers.layer_state), \
[PUT_DEFAULT_LAYER_STATE] = trans_initiator2target_initializer(layers.default_layer_state),
// clang-format on
#else // !defined(NO_ACTION_LAYER) && defined(SPLIT_LAYER_STATE_ENABLE)
# define TRANSACTIONS_LAYER_STATE_MASTER()
# define TRANSACTIONS_LAYER_STATE_SLAVE()
# define TRANSACTIONS_LAYER_STATE_REGISTRATIONS
#endif // !defined(NO_ACTION_LAYER) && defined(SPLIT_LAYER_STATE_ENABLE)
////////////////////////////////////////////////////
// LED state
#ifdef SPLIT_LED_STATE_ENABLE
static bool led_state_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
static uint32_t last_update = 0;
uint8_t led_state = host_keyboard_leds();
return send_if_data_mismatch(PUT_LED_STATE, &last_update, &led_state, &split_shmem->led_state, sizeof(led_state));
}
static void led_state_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
void set_split_host_keyboard_leds(uint8_t led_state);
set_split_host_keyboard_leds(split_shmem->led_state);
}
2021-11-01 20:18:33 +01:00
# define TRANSACTIONS_LED_STATE_MASTER() TRANSACTION_HANDLER_MASTER(led_state)
# define TRANSACTIONS_LED_STATE_SLAVE() TRANSACTION_HANDLER_SLAVE(led_state)
# define TRANSACTIONS_LED_STATE_REGISTRATIONS [PUT_LED_STATE] = trans_initiator2target_initializer(led_state),
#else // SPLIT_LED_STATE_ENABLE
# define TRANSACTIONS_LED_STATE_MASTER()
# define TRANSACTIONS_LED_STATE_SLAVE()
# define TRANSACTIONS_LED_STATE_REGISTRATIONS
#endif // SPLIT_LED_STATE_ENABLE
////////////////////////////////////////////////////
// Mods
#ifdef SPLIT_MODS_ENABLE
static bool mods_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
static uint32_t last_update = 0;
bool mods_need_sync = timer_elapsed32(last_update) >= FORCED_SYNC_THROTTLE_MS;
split_mods_sync_t new_mods;
new_mods.real_mods = get_mods();
if (!mods_need_sync && new_mods.real_mods != split_shmem->mods.real_mods) {
mods_need_sync = true;
}
new_mods.weak_mods = get_weak_mods();
if (!mods_need_sync && new_mods.weak_mods != split_shmem->mods.weak_mods) {
mods_need_sync = true;
}
# ifndef NO_ACTION_ONESHOT
new_mods.oneshot_mods = get_oneshot_mods();
if (!mods_need_sync && new_mods.oneshot_mods != split_shmem->mods.oneshot_mods) {
mods_need_sync = true;
}
# endif // NO_ACTION_ONESHOT
bool okay = true;
if (mods_need_sync) {
okay &= transport_write(PUT_MODS, &new_mods, sizeof(new_mods));
if (okay) {
last_update = timer_read32();
}
}
return okay;
}
static void mods_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
set_mods(split_shmem->mods.real_mods);
set_weak_mods(split_shmem->mods.weak_mods);
# ifndef NO_ACTION_ONESHOT
set_oneshot_mods(split_shmem->mods.oneshot_mods);
# endif
}
2021-11-01 20:18:33 +01:00
# define TRANSACTIONS_MODS_MASTER() TRANSACTION_HANDLER_MASTER(mods)
# define TRANSACTIONS_MODS_SLAVE() TRANSACTION_HANDLER_SLAVE(mods)
# define TRANSACTIONS_MODS_REGISTRATIONS [PUT_MODS] = trans_initiator2target_initializer(mods),
#else // SPLIT_MODS_ENABLE
# define TRANSACTIONS_MODS_MASTER()
# define TRANSACTIONS_MODS_SLAVE()
# define TRANSACTIONS_MODS_REGISTRATIONS
#endif // SPLIT_MODS_ENABLE
////////////////////////////////////////////////////
// Backlight
#ifdef BACKLIGHT_ENABLE
static bool backlight_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
static uint32_t last_update = 0;
uint8_t level = is_backlight_enabled() ? get_backlight_level() : 0;
return send_if_condition(PUT_BACKLIGHT, &last_update, (level != split_shmem->backlight_level), &level, sizeof(level));
}
static void backlight_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
backlight_set(split_shmem->backlight_level);
}
2021-11-01 20:18:33 +01:00
# define TRANSACTIONS_BACKLIGHT_MASTER() TRANSACTION_HANDLER_MASTER(backlight)
# define TRANSACTIONS_BACKLIGHT_SLAVE() TRANSACTION_HANDLER_SLAVE(backlight)
# define TRANSACTIONS_BACKLIGHT_REGISTRATIONS [PUT_BACKLIGHT] = trans_initiator2target_initializer(backlight_level),
#else // BACKLIGHT_ENABLE
# define TRANSACTIONS_BACKLIGHT_MASTER()
# define TRANSACTIONS_BACKLIGHT_SLAVE()
# define TRANSACTIONS_BACKLIGHT_REGISTRATIONS
#endif // BACKLIGHT_ENABLE
////////////////////////////////////////////////////
// RGBLIGHT
#if defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT)
static bool rgblight_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
static uint32_t last_update = 0;
rgblight_syncinfo_t rgblight_sync;
rgblight_get_syncinfo(&rgblight_sync);
if (send_if_condition(PUT_RGBLIGHT, &last_update, (rgblight_sync.status.change_flags != 0), &rgblight_sync, sizeof(rgblight_sync))) {
rgblight_clear_change_flags();
} else {
return false;
}
return true;
}
static void rgblight_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
// Update the RGB with the new data
if (split_shmem->rgblight_sync.status.change_flags != 0) {
rgblight_update_sync(&split_shmem->rgblight_sync, false);
split_shmem->rgblight_sync.status.change_flags = 0;
}
}
2021-11-01 20:18:33 +01:00
# define TRANSACTIONS_RGBLIGHT_MASTER() TRANSACTION_HANDLER_MASTER(rgblight)
# define TRANSACTIONS_RGBLIGHT_SLAVE() TRANSACTION_HANDLER_SLAVE(rgblight)
# define TRANSACTIONS_RGBLIGHT_REGISTRATIONS [PUT_RGBLIGHT] = trans_initiator2target_initializer(rgblight_sync),
#else // defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT)
# define TRANSACTIONS_RGBLIGHT_MASTER()
# define TRANSACTIONS_RGBLIGHT_SLAVE()
# define TRANSACTIONS_RGBLIGHT_REGISTRATIONS
#endif // defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT)
////////////////////////////////////////////////////
// LED Matrix
#if defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT)
static bool led_matrix_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
static uint32_t last_update = 0;
led_matrix_sync_t led_matrix_sync;
memcpy(&led_matrix_sync.led_matrix, &led_matrix_eeconfig, sizeof(led_eeconfig_t));
led_matrix_sync.led_suspend_state = led_matrix_get_suspend_state();
return send_if_data_mismatch(PUT_LED_MATRIX, &last_update, &led_matrix_sync, &split_shmem->led_matrix_sync, sizeof(led_matrix_sync));
}
static void led_matrix_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
memcpy(&led_matrix_eeconfig, &split_shmem->led_matrix_sync.led_matrix, sizeof(led_eeconfig_t));
led_matrix_set_suspend_state(split_shmem->led_matrix_sync.led_suspend_state);
}
2021-11-01 20:18:33 +01:00
# define TRANSACTIONS_LED_MATRIX_MASTER() TRANSACTION_HANDLER_MASTER(led_matrix)
# define TRANSACTIONS_LED_MATRIX_SLAVE() TRANSACTION_HANDLER_SLAVE(led_matrix)
# define TRANSACTIONS_LED_MATRIX_REGISTRATIONS [PUT_LED_MATRIX] = trans_initiator2target_initializer(led_matrix_sync),
#else // defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT)
# define TRANSACTIONS_LED_MATRIX_MASTER()
# define TRANSACTIONS_LED_MATRIX_SLAVE()
# define TRANSACTIONS_LED_MATRIX_REGISTRATIONS
#endif // defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT)
////////////////////////////////////////////////////
// RGB Matrix
#if defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT)
static bool rgb_matrix_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
static uint32_t last_update = 0;
rgb_matrix_sync_t rgb_matrix_sync;
memcpy(&rgb_matrix_sync.rgb_matrix, &rgb_matrix_config, sizeof(rgb_config_t));
rgb_matrix_sync.rgb_suspend_state = rgb_matrix_get_suspend_state();
return send_if_data_mismatch(PUT_RGB_MATRIX, &last_update, &rgb_matrix_sync, &split_shmem->rgb_matrix_sync, sizeof(rgb_matrix_sync));
}
static void rgb_matrix_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
memcpy(&rgb_matrix_config, &split_shmem->rgb_matrix_sync.rgb_matrix, sizeof(rgb_config_t));
rgb_matrix_set_suspend_state(split_shmem->rgb_matrix_sync.rgb_suspend_state);
}
2021-11-01 20:18:33 +01:00
# define TRANSACTIONS_RGB_MATRIX_MASTER() TRANSACTION_HANDLER_MASTER(rgb_matrix)
# define TRANSACTIONS_RGB_MATRIX_SLAVE() TRANSACTION_HANDLER_SLAVE(rgb_matrix)
# define TRANSACTIONS_RGB_MATRIX_REGISTRATIONS [PUT_RGB_MATRIX] = trans_initiator2target_initializer(rgb_matrix_sync),
#else // defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT)
# define TRANSACTIONS_RGB_MATRIX_MASTER()
# define TRANSACTIONS_RGB_MATRIX_SLAVE()
# define TRANSACTIONS_RGB_MATRIX_REGISTRATIONS
#endif // defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT)
////////////////////////////////////////////////////
// WPM
#if defined(WPM_ENABLE) && defined(SPLIT_WPM_ENABLE)
static bool wpm_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
static uint32_t last_update = 0;
uint8_t current_wpm = get_current_wpm();
return send_if_condition(PUT_WPM, &last_update, (current_wpm != split_shmem->current_wpm), &current_wpm, sizeof(current_wpm));
}
static void wpm_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
set_current_wpm(split_shmem->current_wpm);
}
2021-11-01 20:18:33 +01:00
# define TRANSACTIONS_WPM_MASTER() TRANSACTION_HANDLER_MASTER(wpm)
# define TRANSACTIONS_WPM_SLAVE() TRANSACTION_HANDLER_SLAVE(wpm)
# define TRANSACTIONS_WPM_REGISTRATIONS [PUT_WPM] = trans_initiator2target_initializer(current_wpm),
#else // defined(WPM_ENABLE) && defined(SPLIT_WPM_ENABLE)
# define TRANSACTIONS_WPM_MASTER()
# define TRANSACTIONS_WPM_SLAVE()
# define TRANSACTIONS_WPM_REGISTRATIONS
#endif // defined(WPM_ENABLE) && defined(SPLIT_WPM_ENABLE)
////////////////////////////////////////////////////
// OLED
#if defined(OLED_ENABLE) && defined(SPLIT_OLED_ENABLE)
static bool oled_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
static uint32_t last_update = 0;
bool current_oled_state = is_oled_on();
return send_if_condition(PUT_OLED, &last_update, (current_oled_state != split_shmem->current_oled_state), &current_oled_state, sizeof(current_oled_state));
}
static void oled_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
if (split_shmem->current_oled_state) {
oled_on();
} else {
oled_off();
}
}
2021-11-01 20:18:33 +01:00
# define TRANSACTIONS_OLED_MASTER() TRANSACTION_HANDLER_MASTER(oled)
# define TRANSACTIONS_OLED_SLAVE() TRANSACTION_HANDLER_SLAVE(oled)
# define TRANSACTIONS_OLED_REGISTRATIONS [PUT_OLED] = trans_initiator2target_initializer(current_oled_state),
#else // defined(OLED_ENABLE) && defined(SPLIT_OLED_ENABLE)
# define TRANSACTIONS_OLED_MASTER()
# define TRANSACTIONS_OLED_SLAVE()
# define TRANSACTIONS_OLED_REGISTRATIONS
#endif // defined(OLED_ENABLE) && defined(SPLIT_OLED_ENABLE)
////////////////////////////////////////////////////
// ST7565
#if defined(ST7565_ENABLE) && defined(SPLIT_ST7565_ENABLE)
static bool st7565_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
static uint32_t last_update = 0;
bool current_st7565_state = st7565_is_on();
return send_if_condition(PUT_ST7565, &last_update, (current_st7565_state != split_shmem->current_st7565_state), &current_st7565_state, sizeof(current_st7565_state));
}
static void st7565_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
if (split_shmem->current_st7565_state) {
st7565_on();
} else {
st7565_off();
}
}
2021-11-01 20:18:33 +01:00
# define TRANSACTIONS_ST7565_MASTER() TRANSACTION_HANDLER_MASTER(st7565)
# define TRANSACTIONS_ST7565_SLAVE() TRANSACTION_HANDLER_SLAVE(st7565)
# define TRANSACTIONS_ST7565_REGISTRATIONS [PUT_ST7565] = trans_initiator2target_initializer(current_st7565_state),
#else // defined(ST7565_ENABLE) && defined(SPLIT_ST7565_ENABLE)
# define TRANSACTIONS_ST7565_MASTER()
# define TRANSACTIONS_ST7565_SLAVE()
# define TRANSACTIONS_ST7565_REGISTRATIONS
#endif // defined(ST7565_ENABLE) && defined(SPLIT_ST7565_ENABLE)
////////////////////////////////////////////////////
// POINTING
#if defined(POINTING_DEVICE_ENABLE) && defined(SPLIT_POINTING_ENABLE)
static bool pointing_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
# if defined(POINTING_DEVICE_LEFT)
if (is_keyboard_left()) {
return true;
}
# elif defined(POINTING_DEVICE_RIGHT)
if (!is_keyboard_left()) {
return true;
}
# endif
static uint32_t last_update = 0;
static uint16_t last_cpi = 0;
report_mouse_t temp_state;
uint16_t temp_cpi;
bool okay = read_if_checksum_mismatch(GET_POINTING_CHECKSUM, GET_POINTING_DATA, &last_update, &temp_state, &split_shmem->pointing.report, sizeof(temp_state));
if (okay) pointing_device_set_shared_report(temp_state);
temp_cpi = pointing_device_get_shared_cpi();
if (temp_cpi && memcmp(&last_cpi, &temp_cpi, sizeof(temp_cpi)) != 0) {
memcpy(&split_shmem->pointing.cpi, &temp_cpi, sizeof(temp_cpi));
okay = transport_write(PUT_POINTING_CPI, &split_shmem->pointing.cpi, sizeof(split_shmem->pointing.cpi));
if (okay) {
last_cpi = temp_cpi;
}
}
return okay;
}
extern const pointing_device_driver_t pointing_device_driver;
static void pointing_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
# if defined(POINTING_DEVICE_LEFT)
if (!is_keyboard_left()) {
return;
}
# elif defined(POINTING_DEVICE_RIGHT)
if (is_keyboard_left()) {
return;
}
# endif
report_mouse_t temp_report;
uint16_t temp_cpi;
# if (POINTING_DEVICE_TASK_THROTTLE_MS > 0)
static uint32_t last_exec = 0;
if (timer_elapsed32(last_exec) < POINTING_DEVICE_TASK_THROTTLE_MS) {
return;
}
last_exec = timer_read32();
# endif
temp_cpi = !pointing_device_driver.get_cpi ? 0 : pointing_device_driver.get_cpi(); // check for NULL
if (split_shmem->pointing.cpi && memcmp(&split_shmem->pointing.cpi, &temp_cpi, sizeof(temp_cpi)) != 0) {
if (pointing_device_driver.set_cpi) {
pointing_device_driver.set_cpi(split_shmem->pointing.cpi);
}
}
memset(&temp_report, 0, sizeof(temp_report));
temp_report = pointing_device_driver.get_report(temp_report);
memcpy(&split_shmem->pointing.report, &temp_report, sizeof(temp_report));
// Now update the checksum given that the pointing has been written to
split_shmem->pointing.checksum = crc8(&temp_report, sizeof(temp_report));
}
# define TRANSACTIONS_POINTING_MASTER() TRANSACTION_HANDLER_MASTER(pointing)
# define TRANSACTIONS_POINTING_SLAVE() TRANSACTION_HANDLER_SLAVE(pointing)
# define TRANSACTIONS_POINTING_REGISTRATIONS [GET_POINTING_CHECKSUM] = trans_target2initiator_initializer(pointing.checksum), [GET_POINTING_DATA] = trans_target2initiator_initializer(pointing.report), [PUT_POINTING_CPI] = trans_initiator2target_initializer(pointing.cpi),
#else // defined(POINTING_DEVICE_ENABLE) && defined(SPLIT_POINTING_ENABLE)
# define TRANSACTIONS_POINTING_MASTER()
# define TRANSACTIONS_POINTING_SLAVE()
# define TRANSACTIONS_POINTING_REGISTRATIONS
#endif // defined(POINTING_DEVICE_ENABLE) && defined(SPLIT_POINTING_ENABLE)
////////////////////////////////////////////////////
split_transaction_desc_t split_transaction_table[NUM_TOTAL_TRANSACTIONS] = {
// Set defaults
[0 ...(NUM_TOTAL_TRANSACTIONS - 1)] = {0, 0, 0, 0, 0},
#ifdef USE_I2C
[I2C_EXECUTE_CALLBACK] = trans_initiator2target_initializer(transaction_id),
#endif // USE_I2C
// clang-format off
TRANSACTIONS_SLAVE_MATRIX_REGISTRATIONS
TRANSACTIONS_MASTER_MATRIX_REGISTRATIONS
TRANSACTIONS_ENCODERS_REGISTRATIONS
TRANSACTIONS_SYNC_TIMER_REGISTRATIONS
TRANSACTIONS_LAYER_STATE_REGISTRATIONS
TRANSACTIONS_LED_STATE_REGISTRATIONS
TRANSACTIONS_MODS_REGISTRATIONS
TRANSACTIONS_BACKLIGHT_REGISTRATIONS
TRANSACTIONS_RGBLIGHT_REGISTRATIONS
TRANSACTIONS_LED_MATRIX_REGISTRATIONS
TRANSACTIONS_RGB_MATRIX_REGISTRATIONS
TRANSACTIONS_WPM_REGISTRATIONS
TRANSACTIONS_OLED_REGISTRATIONS
TRANSACTIONS_ST7565_REGISTRATIONS
TRANSACTIONS_POINTING_REGISTRATIONS
// clang-format on
#if defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER)
[PUT_RPC_INFO] = trans_initiator2target_initializer_cb(rpc_info, slave_rpc_info_callback),
[PUT_RPC_REQ_DATA] = trans_initiator2target_initializer(rpc_m2s_buffer),
[EXECUTE_RPC] = trans_initiator2target_initializer_cb(rpc_info.payload.transaction_id, slave_rpc_exec_callback),
[GET_RPC_RESP_DATA] = trans_target2initiator_initializer(rpc_s2m_buffer),
#endif // defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER)
};
bool transactions_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
TRANSACTIONS_SLAVE_MATRIX_MASTER();
TRANSACTIONS_MASTER_MATRIX_MASTER();
TRANSACTIONS_ENCODERS_MASTER();
TRANSACTIONS_SYNC_TIMER_MASTER();
TRANSACTIONS_LAYER_STATE_MASTER();
TRANSACTIONS_LED_STATE_MASTER();
TRANSACTIONS_MODS_MASTER();
TRANSACTIONS_BACKLIGHT_MASTER();
TRANSACTIONS_RGBLIGHT_MASTER();
TRANSACTIONS_LED_MATRIX_MASTER();
TRANSACTIONS_RGB_MATRIX_MASTER();
TRANSACTIONS_WPM_MASTER();
TRANSACTIONS_OLED_MASTER();
TRANSACTIONS_ST7565_MASTER();
TRANSACTIONS_POINTING_MASTER();
Make solo half of split keyboards (more) usable. (#13523) * Make solo half of split keyboards (more) usable. Using only one half of a split keyboard (that's using the split_common framework to communicate) is not a great experience, since several read timeouts per scan cycle cause an unusably slow scan rate. This change blocks all split communication attempts for 500 ms (configurable) after an error occurs, causing the scan rate to become at least _more_ usable, but might need some tweaking to work fully on most keyboards. One read timeout still needs to occur after the 500 ms has passed, and if that timeout isn't low enough, some scan cycles may still be too slow. * Fix lint complaint. * Require 25 consecutive comm errors to see comms as disconnected. The number of max errors can be overridden by defining `SPLIT_MAX_CONNECTION_ERRORS`. * Add comments to new defines, and ability to disable disconnection check. Also increase `SPLIT_MAX_CONNECTION_ERRORS` to 40, since it's divisible by most relevant numbers for the description. * Make lint happy ...again * Only update `connection_check_timer` when needed. * Add new defines to split keyboard documentation. * Move connection timeout logic to transport.c, add `is_transport_connected`. * Use split_common disconnection logic in matrix.c. Instead of doing more or less the same thing twice. * Move disconnection logic to `transport_master`. Is a cleaner implementation, and causes the scan rate while disconnected to increase instead of decrease. * Lint fixes. * Lower default `SERIAL_USART_TIMEOUT` to 20 ms. The read timeout must be low enough to not cause exessively long scan cycles when using a solo split half. 10 ms was determined from testing to work fine even with the slowest defined baudrate of 19200 (5 ms was too low for that case), so 20 ms should be fine for most cases. * Remove `SERIAL_USART_TIMEOUT` from ergodox_infinity/config.h Was somewhat mistakenly included in an earlier PR. * Fix building with `USE_I2C`. * Reduce built firmware size. Not really sure why this works, the idea was taken from tzarc's work on split disconnection. * Tweak and improve opt-out for split disconnection logic. There are now two ways to opt out from this feature: * Set `SPLIT_MAX_CONNECTION_ERRORS` to 0. This will completely disable the connection status checks (also affects the slave matrix reset logic in matrix.c, though). * Set `SPLIT_CONNECTION_CHECK_TIMEOUT` to 0. This will only disable the communication throttling while disconnected. Will make the firmware smaller. * Make split disconnection logic work with custom transports. Includes a fallback implementation for keyboards using a custom split_util.c but not a custom matrix.c (currently no such keyboard seems to be merged, though). * Remove unnecessary include of timer.h Co-authored-by: Joel Challis <git@zvecr.com> Co-authored-by: Joel Challis <git@zvecr.com>
2021-08-22 02:51:17 +02:00
return true;
}
void transactions_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
TRANSACTIONS_SLAVE_MATRIX_SLAVE();
TRANSACTIONS_MASTER_MATRIX_SLAVE();
TRANSACTIONS_ENCODERS_SLAVE();
TRANSACTIONS_SYNC_TIMER_SLAVE();
TRANSACTIONS_LAYER_STATE_SLAVE();
TRANSACTIONS_LED_STATE_SLAVE();
TRANSACTIONS_MODS_SLAVE();
TRANSACTIONS_BACKLIGHT_SLAVE();
TRANSACTIONS_RGBLIGHT_SLAVE();
TRANSACTIONS_LED_MATRIX_SLAVE();
TRANSACTIONS_RGB_MATRIX_SLAVE();
TRANSACTIONS_WPM_SLAVE();
TRANSACTIONS_OLED_SLAVE();
TRANSACTIONS_ST7565_SLAVE();
TRANSACTIONS_POINTING_SLAVE();
}
#if defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER)
void transaction_register_rpc(int8_t transaction_id, slave_callback_t callback) {
// Prevent invoking RPC on QMK core sync data
if (transaction_id <= GET_RPC_RESP_DATA) return;
// Set the callback
split_transaction_table[transaction_id].slave_callback = callback;
split_transaction_table[transaction_id].initiator2target_offset = offsetof(split_shared_memory_t, rpc_m2s_buffer);
split_transaction_table[transaction_id].target2initiator_offset = offsetof(split_shared_memory_t, rpc_s2m_buffer);
}
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) {
Make solo half of split keyboards (more) usable. (#13523) * Make solo half of split keyboards (more) usable. Using only one half of a split keyboard (that's using the split_common framework to communicate) is not a great experience, since several read timeouts per scan cycle cause an unusably slow scan rate. This change blocks all split communication attempts for 500 ms (configurable) after an error occurs, causing the scan rate to become at least _more_ usable, but might need some tweaking to work fully on most keyboards. One read timeout still needs to occur after the 500 ms has passed, and if that timeout isn't low enough, some scan cycles may still be too slow. * Fix lint complaint. * Require 25 consecutive comm errors to see comms as disconnected. The number of max errors can be overridden by defining `SPLIT_MAX_CONNECTION_ERRORS`. * Add comments to new defines, and ability to disable disconnection check. Also increase `SPLIT_MAX_CONNECTION_ERRORS` to 40, since it's divisible by most relevant numbers for the description. * Make lint happy ...again * Only update `connection_check_timer` when needed. * Add new defines to split keyboard documentation. * Move connection timeout logic to transport.c, add `is_transport_connected`. * Use split_common disconnection logic in matrix.c. Instead of doing more or less the same thing twice. * Move disconnection logic to `transport_master`. Is a cleaner implementation, and causes the scan rate while disconnected to increase instead of decrease. * Lint fixes. * Lower default `SERIAL_USART_TIMEOUT` to 20 ms. The read timeout must be low enough to not cause exessively long scan cycles when using a solo split half. 10 ms was determined from testing to work fine even with the slowest defined baudrate of 19200 (5 ms was too low for that case), so 20 ms should be fine for most cases. * Remove `SERIAL_USART_TIMEOUT` from ergodox_infinity/config.h Was somewhat mistakenly included in an earlier PR. * Fix building with `USE_I2C`. * Reduce built firmware size. Not really sure why this works, the idea was taken from tzarc's work on split disconnection. * Tweak and improve opt-out for split disconnection logic. There are now two ways to opt out from this feature: * Set `SPLIT_MAX_CONNECTION_ERRORS` to 0. This will completely disable the connection status checks (also affects the slave matrix reset logic in matrix.c, though). * Set `SPLIT_CONNECTION_CHECK_TIMEOUT` to 0. This will only disable the communication throttling while disconnected. Will make the firmware smaller. * Make split disconnection logic work with custom transports. Includes a fallback implementation for keyboards using a custom split_util.c but not a custom matrix.c (currently no such keyboard seems to be merged, though). * Remove unnecessary include of timer.h Co-authored-by: Joel Challis <git@zvecr.com> Co-authored-by: Joel Challis <git@zvecr.com>
2021-08-22 02:51:17 +02:00
// Prevent transaction attempts while transport is disconnected
if (!is_transport_connected()) {
return false;
}
// Prevent invoking RPC on QMK core sync data
if (transaction_id <= GET_RPC_RESP_DATA) return false;
// Prevent sizing issues
if (initiator2target_buffer_size > RPC_M2S_BUFFER_SIZE) return false;
if (target2initiator_buffer_size > RPC_S2M_BUFFER_SIZE) return false;
// Prepare the metadata block
rpc_sync_info_t info = {.payload = {.transaction_id = transaction_id, .m2s_length = initiator2target_buffer_size, .s2m_length = target2initiator_buffer_size}};
info.checksum = crc8(&info.payload, sizeof(info.payload));
// Make sure the local side knows that we're not sending the full block of data
split_transaction_table[PUT_RPC_REQ_DATA].initiator2target_buffer_size = initiator2target_buffer_size;
split_transaction_table[GET_RPC_RESP_DATA].target2initiator_buffer_size = target2initiator_buffer_size;
// Run through the sequence:
// * set the transaction ID and lengths
// * send the request data
// * execute RPC callback
// * retrieve the response data
if (!transport_write(PUT_RPC_INFO, &info, sizeof(info))) {
return false;
}
if (!transport_write(PUT_RPC_REQ_DATA, initiator2target_buffer, initiator2target_buffer_size)) {
return false;
}
if (!transport_write(EXECUTE_RPC, &transaction_id, sizeof(transaction_id))) {
return false;
}
if (!transport_read(GET_RPC_RESP_DATA, target2initiator_buffer, target2initiator_buffer_size)) {
return false;
}
return true;
}
void slave_rpc_info_callback(uint8_t initiator2target_buffer_size, const void *initiator2target_buffer, uint8_t target2initiator_buffer_size, void *target2initiator_buffer) {
// The RPC info block contains the intended transaction ID, as well as the sizes for both inbound and outbound data.
// Ignore the args -- the `split_shmem` already has the info, we just need to act upon it.
// We must keep the `split_transaction_table` non-const, so that it is able to be modified at runtime.
split_transaction_table[PUT_RPC_REQ_DATA].initiator2target_buffer_size = split_shmem->rpc_info.payload.m2s_length;
split_transaction_table[GET_RPC_RESP_DATA].target2initiator_buffer_size = split_shmem->rpc_info.payload.s2m_length;
}
void slave_rpc_exec_callback(uint8_t initiator2target_buffer_size, const void *initiator2target_buffer, uint8_t target2initiator_buffer_size, void *target2initiator_buffer) {
// We can assume that the buffer lengths are correctly set, now, given that sequentially the rpc_info callback was already executed.
// Go through the rpc_info and execute _that_ transaction's callback, with the scratch buffers as inputs.
// As a safety precaution we check that the received payload matches its checksum first.
if (crc8(&split_shmem->rpc_info.payload, sizeof(split_shmem->rpc_info.payload)) != split_shmem->rpc_info.checksum) {
return;
}
int8_t transaction_id = split_shmem->rpc_info.payload.transaction_id;
if (transaction_id < NUM_TOTAL_TRANSACTIONS) {
split_transaction_desc_t *trans = &split_transaction_table[transaction_id];
if (trans->slave_callback) {
trans->slave_callback(split_shmem->rpc_info.payload.m2s_length, split_shmem->rpc_m2s_buffer, split_shmem->rpc_info.payload.s2m_length, split_shmem->rpc_s2m_buffer);
}
}
}
#endif // defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER)