summaryrefslogtreecommitdiff
path: root/drivers/chibios/serial_usart_duplex.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/chibios/serial_usart_duplex.c')
-rw-r--r--drivers/chibios/serial_usart_duplex.c261
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;
+}