diff options
Diffstat (limited to 'drivers/chibios/serial_usart_duplex.c')
| -rw-r--r-- | drivers/chibios/serial_usart_duplex.c | 261 | 
1 files changed, 261 insertions, 0 deletions
| diff --git a/drivers/chibios/serial_usart_duplex.c b/drivers/chibios/serial_usart_duplex.c new file mode 100644 index 0000000000..cc9b889ac2 --- /dev/null +++ b/drivers/chibios/serial_usart_duplex.c @@ -0,0 +1,261 @@ +/* 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 3 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 "serial_usart.h" + +#include <stdatomic.h> + +#if !defined(USE_GPIOV1) +// The default PAL alternate modes are used to signal that the pins are used for USART +#    if !defined(SERIAL_USART_TX_PAL_MODE) +#        define SERIAL_USART_TX_PAL_MODE 7 +#    endif +#    if !defined(SERIAL_USART_RX_PAL_MODE) +#        define SERIAL_USART_RX_PAL_MODE 7 +#    endif +#endif + +#if !defined(SERIAL_USART_DRIVER) +#    define SERIAL_USART_DRIVER UARTD1 +#endif + +#if !defined(SERIAL_USART_TX_PIN) +#    define SERIAL_USART_TX_PIN A9 +#endif + +#if !defined(SERIAL_USART_RX_PIN) +#    define SERIAL_USART_RX_PIN A10 +#endif + +#define SIGNAL_HANDSHAKE_RECEIVED 0x1 + +void        handle_transactions_slave(uint8_t sstd_index); +static void receive_transaction_handshake(UARTDriver* uartp, uint16_t received_handshake); + +/* + * UART driver configuration structure. We use the blocking DMA enabled API and + * the rxchar callback to receive handshake tokens but only on the slave halve. + */ +// clang-format off +static UARTConfig uart_config = { +    .txend1_cb = NULL, +    .txend2_cb = NULL, +    .rxend_cb = NULL, +    .rxchar_cb = NULL, +    .rxerr_cb = NULL, +    .timeout_cb = NULL, +    .speed = (SERIAL_USART_SPEED), +    .cr1 = (SERIAL_USART_CR1), +    .cr2 = (SERIAL_USART_CR2), +    .cr3 = (SERIAL_USART_CR3) +}; +// clang-format on + +static SSTD_t*              Transaction_table      = NULL; +static uint8_t              Transaction_table_size = 0; +static atomic_uint_least8_t handshake              = 0xFF; +static thread_reference_t   tp_target              = NULL; + +/* + * This callback is invoked when a character is received but the application + * was not ready to receive it, the character is passed as parameter. + * Receive transaction table index from initiator, which doubles as basic handshake token. */ +static void receive_transaction_handshake(UARTDriver* uartp, uint16_t received_handshake) { +    /* Check if received handshake is not a valid transaction id. +     * Please note that we can still catch a seemingly valid handshake +     * i.e. a byte from a ongoing transfer which is in the allowed range. +     * So this check mainly prevents any obviously wrong handshakes and +     * subsequent wakeups of the receiving thread, which is a costly operation. */ +    if (received_handshake > Transaction_table_size) { +        return; +    } + +    handshake = (uint8_t)received_handshake; +    chSysLockFromISR(); +    /* Wakeup receiving thread to start a transaction. */ +    chEvtSignalI(tp_target, (eventmask_t)SIGNAL_HANDSHAKE_RECEIVED); +    chSysUnlockFromISR(); +} + +__attribute__((weak)) void usart_init(void) { +#if defined(USE_GPIOV1) +    palSetLineMode(SERIAL_USART_TX_PIN, PAL_MODE_STM32_ALTERNATE_PUSHPULL); +    palSetLineMode(SERIAL_USART_RX_PIN, PAL_MODE_INPUT); +#else +    palSetLineMode(SERIAL_USART_TX_PIN, PAL_MODE_ALTERNATE(SERIAL_USART_TX_PAL_MODE) | PAL_STM32_OTYPE_PUSHPULL | PAL_STM32_OSPEED_HIGHEST); +    palSetLineMode(SERIAL_USART_RX_PIN, PAL_MODE_ALTERNATE(SERIAL_USART_RX_PAL_MODE) | PAL_STM32_OTYPE_PUSHPULL | PAL_STM32_OSPEED_HIGHEST); +#endif +} + +/* + * This thread runs on the slave half and reacts to transactions initiated from the master. + */ +static THD_WORKING_AREA(waSlaveThread, 1024); +static THD_FUNCTION(SlaveThread, arg) { +    (void)arg; +    chRegSetThreadName("slave_usart_tx_rx"); + +    while (true) { +        /* We sleep as long as there is no handshake waiting for us. */ +        chEvtWaitAny((eventmask_t)SIGNAL_HANDSHAKE_RECEIVED); +        handle_transactions_slave(handshake); +    } +} + +void soft_serial_target_init(SSTD_t* const sstd_table, int sstd_table_size) { +    Transaction_table      = sstd_table; +    Transaction_table_size = (uint8_t)sstd_table_size; +    usart_init(); + +#if defined(USART_REMAP) +    USART_REMAP; +#endif + +    tp_target = chThdCreateStatic(waSlaveThread, sizeof(waSlaveThread), HIGHPRIO, SlaveThread, NULL); + +    // Start receiving handshake tokens on slave halve +    uart_config.rxchar_cb = receive_transaction_handshake; +    uartStart(&SERIAL_USART_DRIVER, &uart_config); +} + +/** + * @brief React to transactions started by the master. + * This version uses duplex send and receive usart pheriphals and DMA backed transfers. + */ +void inline handle_transactions_slave(uint8_t sstd_index) { +    size_t  buffer_size = 0; +    msg_t   msg         = 0; +    SSTD_t* trans       = &Transaction_table[sstd_index]; + +    /* Send back the handshake which is XORed as a simple checksum, +     to signal that the slave is ready to receive possible transaction buffers  */ +    sstd_index ^= HANDSHAKE_MAGIC; +    buffer_size = (size_t)sizeof(sstd_index); +    msg         = uartSendTimeout(&SERIAL_USART_DRIVER, &buffer_size, &sstd_index, TIME_MS2I(SERIAL_USART_TIMEOUT)); + +    if (msg != MSG_OK) { +        if (trans->status) { +            *trans->status = TRANSACTION_NO_RESPONSE; +        } +        return; +    } + +    /* Receive transaction buffer from the master. If this transaction requires it.*/ +    buffer_size = (size_t)trans->initiator2target_buffer_size; +    if (buffer_size) { +        msg = uartReceiveTimeout(&SERIAL_USART_DRIVER, &buffer_size, trans->initiator2target_buffer, TIME_MS2I(SERIAL_USART_TIMEOUT)); +        if (msg != MSG_OK) { +            if (trans->status) { +                *trans->status = TRANSACTION_NO_RESPONSE; +            } +            return; +        } +    } + +    /* Send transaction buffer to the master. If this transaction requires it. */ +    buffer_size = (size_t)trans->target2initiator_buffer_size; +    if (buffer_size) { +        msg = uartSendFullTimeout(&SERIAL_USART_DRIVER, &buffer_size, trans->target2initiator_buffer, TIME_MS2I(SERIAL_USART_TIMEOUT)); +        if (msg != MSG_OK) { +            if (trans->status) { +                *trans->status = TRANSACTION_NO_RESPONSE; +            } +            return; +        } +    } + +    if (trans->status) { +        *trans->status = TRANSACTION_ACCEPTED; +    } +} + +void soft_serial_initiator_init(SSTD_t* const sstd_table, int sstd_table_size) { +    Transaction_table      = sstd_table; +    Transaction_table_size = (uint8_t)sstd_table_size; +    usart_init(); + +#if defined(SERIAL_USART_PIN_SWAP) +    uart_config.cr2 |= USART_CR2_SWAP;  // master has swapped TX/RX pins +#endif + +#if defined(USART_REMAP) +    USART_REMAP; +#endif + +    uartStart(&SERIAL_USART_DRIVER, &uart_config); +} + +/** + * @brief Start transaction from the master to the slave. + * This version uses duplex send and receive usart pheriphals and DMA backed transfers. + * + * @param index Transaction Table index of the transaction to start. + * @return int TRANSACTION_NO_RESPONSE in case of Timeout. + *             TRANSACTION_TYPE_ERROR in case of invalid transaction index. + *             TRANSACTION_END in case of success. + */ +#if !defined(SERIAL_USE_MULTI_TRANSACTION) +int soft_serial_transaction(void) { +    uint8_t sstd_index = 0; +#else +int soft_serial_transaction(int index) { +    uint8_t sstd_index = index; +#endif + +    if (sstd_index > Transaction_table_size) { +        return TRANSACTION_TYPE_ERROR; +    } + +    SSTD_t* const trans       = &Transaction_table[sstd_index]; +    msg_t         msg         = 0; +    size_t        buffer_size = (size_t)sizeof(sstd_index); + +    /* Send transaction table index to the slave, which doubles as basic handshake token. */ +    uartSendFullTimeout(&SERIAL_USART_DRIVER, &buffer_size, &sstd_index, TIME_MS2I(SERIAL_USART_TIMEOUT)); + +    uint8_t sstd_index_shake = 0xFF; +    buffer_size              = (size_t)sizeof(sstd_index_shake); + +    /* Receive the handshake token from the slave. The token was XORed by the slave as a simple checksum. +     If the tokens match, the master will start to send and receive possible transaction buffers. */ +    msg = uartReceiveTimeout(&SERIAL_USART_DRIVER, &buffer_size, &sstd_index_shake, TIME_MS2I(SERIAL_USART_TIMEOUT)); +    if (msg != MSG_OK || (sstd_index_shake != (sstd_index ^ HANDSHAKE_MAGIC))) { +        dprintln("USART: Handshake Failed"); +        return TRANSACTION_NO_RESPONSE; +    } + +    /* Send transaction buffer to the slave. If this transaction requires it. */ +    buffer_size = (size_t)trans->initiator2target_buffer_size; +    if (buffer_size) { +        msg = uartSendFullTimeout(&SERIAL_USART_DRIVER, &buffer_size, trans->initiator2target_buffer, TIME_MS2I(SERIAL_USART_TIMEOUT)); +        if (msg != MSG_OK) { +            dprintln("USART: Send Failed"); +            return TRANSACTION_NO_RESPONSE; +        } +    } + +    /* Receive transaction buffer from the slave. If this transaction requires it. */ +    buffer_size = (size_t)trans->target2initiator_buffer_size; +    if (buffer_size) { +        msg = uartReceiveTimeout(&SERIAL_USART_DRIVER, &buffer_size, trans->target2initiator_buffer, TIME_MS2I(SERIAL_USART_TIMEOUT)); +        if (msg != MSG_OK) { +            dprintln("USART: Receive Failed"); +            return TRANSACTION_NO_RESPONSE; +        } +    } + +    return TRANSACTION_END; +} | 
