summaryrefslogtreecommitdiffstats
path: root/drivers/chibios/serial_usart_duplex.c
blob: cc9b889ac201d841669ac8c4f4f50b59011e9e82 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
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;
}