Files
edgetx-gx12/radio/src/targets/common/arm/stm32/stm32_serial_driver.cpp
T

631 lines
15 KiB
C++

/*
* Copyright (C) EdgeTx
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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.
*/
#include "stm32_serial_driver.h"
#include <string.h>
#define MIN(a, b) ((a) < (b) ? (a) : (b))
// Serial buffer state
struct stm32_buffer_state {
volatile uint32_t ridx;
volatile uint32_t widx;
};
struct stm32_send_buffer {
volatile const uint8_t* buf;
volatile uint32_t len;
};
struct stm32_serial_state {
const stm32_serial_port* sp;
stm32_buffer_state rx_buf;
union {
stm32_buffer_state tx_fifo;
stm32_send_buffer tx_buf;
} u;
etx_serial_callbacks_t callbacks;
};
enum _STM32_USART {
#if defined(USART1)
_STM32_USART1,
#endif
#if defined(USART2)
_STM32_USART2,
#endif
#if defined(USART3)
_STM32_USART3,
#endif
#if defined(UART4)
_STM32_UART4,
#endif
#if defined(UART5) && (defined(STM32H7) || defined(STM32H7RS))
_STM32_UART5,
#endif
#if defined(USART6)
_STM32_USART6,
#endif
#if defined(UART7)
_STM32_UART7,
#endif
#if defined(UART8)
_STM32_UART8,
#endif
_STM32_MAX_UARTS
};
#define STM32_MAX_UART_PORTS _STM32_MAX_UARTS
// allocated as needed: index does not correspond
static stm32_serial_state _serial_states[STM32_MAX_UART_PORTS];
void stm32_serial_init_driver()
{
memset(_serial_states, 0, sizeof(_serial_states));
}
// Serial context to be used in callbacks
// defined in this driver
static volatile stm32_serial_state* _isr_state;
static uint8_t _on_send_fifo(uint8_t* data)
{
auto st = _isr_state;
auto& buf_st = st->u.tx_fifo;
if (buf_st.ridx == buf_st.widx)
return 0;
const auto& tx_buf = st->sp->tx_buffer;
auto buf = tx_buf.buffer;
*data = buf[buf_st.ridx];
auto buf_len = tx_buf.length;
buf_st.ridx = (buf_st.ridx + 1) & (buf_len - 1);
return 1;
}
static uint8_t _on_send_single_buffer(uint8_t* data)
{
auto sb = &_isr_state->u.tx_buf;
if (!sb->len) return 0;
*data = *(sb->buf++);
sb->len--;
return 1;
}
static inline void _usart_isr_handler(_STM32_USART n)
{
auto st = &(_serial_states[n]);
// This tricks is necessary for now to allow
// callbacks to use the serial context while
// keeping the callbacks re-entrant
auto old_st = _isr_state;
_isr_state = st;
stm32_usart_isr(st->sp->usart, &st->callbacks);
_isr_state = old_st;
}
#define DEFINE_USART_IRQ(usart) \
extern "C" void usart ## _IRQHandler(void) \
{ \
_usart_isr_handler(_STM32_ ## usart); \
}
#if defined (USART1)
DEFINE_USART_IRQ(USART1);
#endif
#if defined (USART2)
DEFINE_USART_IRQ(USART2);
#endif
#if defined (USART3)
DEFINE_USART_IRQ(USART3);
#endif
#if defined (UART4)
DEFINE_USART_IRQ(UART4);
#endif
#if defined (UART5) && (defined(STM32H7) || defined(STM32H7RS))
DEFINE_USART_IRQ(UART5);
#endif
#if defined (USART6)
DEFINE_USART_IRQ(USART6);
#endif
#if defined (UART7)
DEFINE_USART_IRQ(UART7);
#endif
#if defined (UART8)
DEFINE_USART_IRQ(UART8);
#endif
static stm32_serial_state* stm32_serial_find_state(const stm32_usart_t* usart)
{
#if defined (USART1)
if (usart->USARTx == USART1) return &_serial_states[_STM32_USART1];
#endif
#if defined (USART2)
if (usart->USARTx == USART2) return &_serial_states[_STM32_USART2];
#endif
#if defined (USART3)
if (usart->USARTx == USART3) return &_serial_states[_STM32_USART3];
#endif
#if defined (UART4)
if (usart->USARTx == UART4) return &_serial_states[_STM32_UART4];
#endif
#if defined (UART5) && (defined(STM32H7) || defined(STM32H7RS))
if (usart->USARTx == UART5) return &_serial_states[_STM32_UART5];
#endif
#if defined (USART6)
if (usart->USARTx == USART6) return &_serial_states[_STM32_USART6];
#endif
#if defined (UART7)
if (usart->USARTx == UART7) return &_serial_states[_STM32_UART7];
#endif
#if defined (UART8)
if (usart->USARTx == UART8) return &_serial_states[_STM32_UART8];
#endif
return nullptr;
}
static void stm32_serial_free_state(stm32_serial_state* st)
{
memset(st, 0, sizeof(stm32_serial_state));
}
static inline uint32_t _dma_get_data_length(DMA_TypeDef* DMAx, uint32_t stream)
{
#if defined(STM32H7RS)
return LL_DMA_GetBlkDataLength(DMAx, stream);
#else
return LL_DMA_GetDataLength(DMAx, stream);
#endif
}
static inline void _dma_clear(stm32_buffer_state* buf_st, uint32_t length,
DMA_TypeDef* DMAx, uint32_t stream)
{
buf_st->ridx = length - _dma_get_data_length(DMAx, stream);
}
static inline void _fifo_clear(stm32_buffer_state* buf_st)
{
buf_st->widx = buf_st->ridx = 0;
}
static inline bool _fifo_full(stm32_buffer_state* buf_st, uint32_t length)
{
return ((buf_st->widx + 1) & (length - 1)) == buf_st->ridx;
}
static inline void _fifo_push(uint8_t c, stm32_buffer_state* buf_st,
uint32_t length, uint8_t* buf)
{
buf[buf_st->widx] = c;
buf_st->widx = (buf_st->widx + 1) & (length - 1);
}
static void _on_rx_fifo(uint8_t data)
{
auto st = _isr_state;
stm32_buffer_state* buf_st = (stm32_buffer_state*)&st->rx_buf;
auto buf_len = st->sp->rx_buffer.length;
if (_fifo_full(buf_st, buf_len)) return;
auto buf = st->sp->rx_buffer.buffer;
_fifo_push(data, buf_st, buf_len, buf);
}
static void* stm32_serial_init(void* hw_def, const etx_serial_init* params)
{
auto sp = (const stm32_serial_port*)hw_def;
if (!sp) return nullptr;
auto usart = sp->usart;
auto st = stm32_serial_find_state(usart);
if (!st || st->sp) return nullptr;
// Set serial port instance *before* in case an interrupt is triggered
// before this method returns.
st->sp = sp;
if (!stm32_usart_init(usart, params)) {
st->sp = nullptr;
return nullptr;
}
if (params->direction & ETX_Dir_TX) {
// prepare for send_byte()
if (sp->tx_buffer.length > 0) {
st->callbacks.on_send = _on_send_fifo;
}
}
if (params->direction & ETX_Dir_RX) {
auto rx_buf = sp->rx_buffer.buffer;
auto buf_len = sp->rx_buffer.length;
if (usart->rxDMA) {
stm32_usart_init_rx_dma(usart, rx_buf, buf_len);
auto dma = usart->rxDMA;
auto stream = usart->rxDMA_Stream;
_dma_clear(&st->rx_buf, buf_len, dma, stream);
} else {
st->callbacks.on_receive = _on_rx_fifo;
}
}
return (void*)st;
}
static void stm32_serial_deinit(void* ctx)
{
auto st = (stm32_serial_state*)ctx;
if (!st) return;
stm32_usart_deinit(st->sp->usart);
stm32_serial_free_state(st);
}
static void stm32_serial_send_byte(void* ctx, uint8_t c)
{
// When sending single bytes,
// send is based on IRQ, so that this
// only enables the corresponding IRQ
auto st = (stm32_serial_state*)ctx;
if (!st) return;
auto sp = st->sp;
auto buf_len = sp->tx_buffer.length;
if (buf_len > 0) {
auto buf_st = &st->u.tx_fifo;
auto buf = sp->tx_buffer.buffer;
if (_fifo_full(buf_st, buf_len)) return;
_fifo_push(c, buf_st, buf_len, buf);
stm32_usart_enable_tx_irq(sp->usart);
} else {
// No TX FIFO -> fall back to sync send
stm32_usart_send_byte(sp->usart, c);
}
}
#if defined(STM32F4)
extern uint32_t _sram;
extern uint32_t _eram;
#define _IS_DMA_BUFFER(addr) \
((intptr_t)(addr) >= (intptr_t)&_sram && (intptr_t)(addr) <= (intptr_t)&_eram)
#elif defined(STM32H7) || defined(STM32H7RS)
extern uint32_t _s_dram;
extern uint32_t _e_dram;
#define _IS_DMA_BUFFER(addr) \
((intptr_t)(addr) >= (intptr_t)&_s_dram && \
(intptr_t)(addr) <= (intptr_t)&_e_dram)
#else
#define _IS_DMA_BUFFER(addr) (true)
#endif
#define _IS_ALIGNED(addr) (((intptr_t)(addr) & 3U) == 0U)
static void stm32_serial_send_buffer(void* ctx, const uint8_t* data, uint32_t size)
{
auto st = (stm32_serial_state*)ctx;
if (!st) return;
// try TX DMA first
auto sp = st->sp;
auto usart = sp->usart;
if (usart->txDMA && _IS_DMA_BUFFER(data) && _IS_ALIGNED(data)) {
stm32_usart_send_buffer(usart, data, size);
return;
}
// no internal buffer: send one buffer at a time
if (!sp->tx_buffer.length) {
st->u.tx_buf.buf = data;
st->u.tx_buf.len = size;
st->callbacks.on_send = _on_send_single_buffer;
stm32_usart_enable_tx_irq(usart);
return;
}
// else stack single bytes into our internal buffer
while(size > 0) {
stm32_serial_send_byte(ctx, *data++);
size--;
}
}
static bool stm32_serial_tx_completed(void* ctx)
{
auto st = (stm32_serial_state*)ctx;
if (!st) return 1;
return stm32_usart_tx_completed(st->sp->usart);
}
static void stm32_wait_tx_completed(void* ctx)
{
auto st = (stm32_serial_state*)ctx;
if (!st) return;
while(!stm32_usart_tx_completed(st->sp->usart));
}
static void stm32_enable_rx(void* ctx)
{
auto st = (stm32_serial_state*)ctx;
if (!st) return;
stm32_usart_enable_rx(st->sp->usart);
}
static int stm32_serial_get_byte(void* ctx, uint8_t* data)
{
auto st = (stm32_serial_state*)ctx;
if (!st) return -1;
auto sp = st->sp;
const auto& rx_buf = sp->rx_buffer;
auto buf_len = rx_buf.length;
if (!buf_len) return -1;
auto buf = rx_buf.buffer;
auto& buf_st = st->rx_buf;
uint32_t widx;
auto usart = sp->usart;
if (LL_USART_IsEnabledDMAReq_RX(usart->USARTx)) {
auto dma = usart->rxDMA;
auto stream = usart->rxDMA_Stream;
widx = buf_len - _dma_get_data_length(dma, stream);
} else {
widx = buf_st.widx;
}
if (buf_st.ridx == widx)
return 0;
*data = buf[buf_st.ridx];
buf_st.ridx = (buf_st.ridx + 1) & (buf_len - 1);
return 1;
}
static int stm32_serial_get_last_byte(void* ctx, uint32_t idx, uint8_t* data)
{
auto st = (stm32_serial_state*)ctx;
if (!st) return -1;
auto sp = st->sp;
const auto& rx_buf = sp->rx_buffer;
auto buf_len = rx_buf.length;
if (!buf_len) return -1;
auto buf = rx_buf.buffer;
auto& buf_st = st->rx_buf;
uint32_t widx;
auto usart = sp->usart;
if (LL_USART_IsEnabledDMAReq_RX(usart->USARTx)) {
auto dma = usart->rxDMA;
auto stream = usart->rxDMA_Stream;
widx = buf_len - _dma_get_data_length(dma, stream);
} else {
widx = buf_st.widx;
}
// Please note that we do not check the read cursor
// so that this function might return data that
// has already been read
uint32_t ridx = (buf_len + widx - idx) & (buf_len - 1);
*data = buf[ridx];
return 1;
}
static int stm32_serial_get_buffered_bytes(void* ctx)
{
auto st = (stm32_serial_state*)ctx;
if (!st) return -1;
auto sp = st->sp;
const auto& rx_buf = sp->rx_buffer;
auto buf_len = rx_buf.length;
if (!buf_len) return -1;
uint32_t widx;
auto usart = sp->usart;
const auto& buf_st = st->rx_buf;
if (LL_USART_IsEnabledDMAReq_RX(usart->USARTx)) {
auto dma = usart->rxDMA;
auto stream = usart->rxDMA_Stream;
widx = buf_len - _dma_get_data_length(dma, stream);
} else {
widx = buf_st.widx;
}
return (widx - buf_st.ridx) & (buf_len - 1);
}
static inline void _copy_buffer_chunk(const stm32_serial_buffer& rx_buf,
stm32_buffer_state& buf_st,
uint8_t* buf, uint32_t len)
{
memcpy(buf, rx_buf.buffer + buf_st.ridx, len);
buf_st.ridx = (buf_st.ridx + len) & (rx_buf.length - 1);
}
static int stm32_serial_copy_rx_buffer(void* ctx, uint8_t* buf, uint32_t len)
{
auto st = (stm32_serial_state*)ctx;
if (!st) return -1;
auto sp = st->sp;
const auto& rx_buf = sp->rx_buffer;
auto buf_len = rx_buf.length;
if (!buf_len) return -1;
uint32_t widx;
auto usart = sp->usart;
auto& buf_st = st->rx_buf;
if (LL_USART_IsEnabledDMAReq_RX(usart->USARTx)) {
auto dma = usart->rxDMA;
auto stream = usart->rxDMA_Stream;
widx = buf_len - _dma_get_data_length(dma, stream);
} else {
widx = buf_st.widx;
}
if (buf_st.ridx == widx) return 0;
int res = 0;
if (buf_st.ridx > widx) {
auto cp_len = MIN(len, buf_len - buf_st.ridx);
_copy_buffer_chunk(rx_buf, buf_st, buf, cp_len);
buf += cp_len;
len -= cp_len;
res += cp_len;
}
if (buf_st.ridx < widx) {
auto cp_len = MIN(len, widx - buf_st.ridx);
_copy_buffer_chunk(rx_buf, buf_st, buf, cp_len);
len -= cp_len;
res += cp_len;
}
return res;
}
static void stm32_serial_clear_rx_buffer(void* ctx)
{
auto st = (stm32_serial_state*)ctx;
if (!st) return;
auto sp = st->sp;
auto buf_st = &st->rx_buf;
auto usart = sp->usart;
if (usart->rxDMA) {
auto buf_len = sp->rx_buffer.length;
auto dma = usart->rxDMA;
auto stream = usart->rxDMA_Stream;
_dma_clear(buf_st, buf_len, dma, stream);
} else {
_fifo_clear(buf_st);
}
}
static uint32_t stm32_serial_get_baudrate(void* ctx)
{
auto st = (stm32_serial_state*)ctx;
if (!st) return 0;
auto sp = st->sp;
auto usart = sp->usart;
return stm32_usart_get_baudrate(usart);
}
static void stm32_serial_set_baudrate(void* ctx, uint32_t baudrate)
{
auto st = (stm32_serial_state*)ctx;
if (!st) return;
auto sp = st->sp;
auto usart = sp->usart;
stm32_usart_set_baudrate(usart, baudrate);
}
static void stm32_serial_hw_option(void* ctx, uint32_t option)
{
auto st = (stm32_serial_state*)ctx;
if (!st) return;
auto sp = st->sp;
auto usart = sp->usart;
stm32_usart_set_hw_option(usart, option);
}
static void stm32_serial_set_idle_cb(void* ctx, void (*on_idle)(void*), void* param)
{
auto st = (stm32_serial_state*)ctx;
if (!st) return;
st->callbacks.on_idle = on_idle;
st->callbacks.on_idle_ctx = param;
uint32_t enabled = (on_idle != nullptr);
stm32_usart_set_idle_irq(st->sp->usart, enabled);
}
const etx_serial_driver_t STM32SerialDriver = {
.init = stm32_serial_init,
.deinit = stm32_serial_deinit,
.sendByte = stm32_serial_send_byte,
.sendBuffer = stm32_serial_send_buffer,
.txCompleted = stm32_serial_tx_completed,
.waitForTxCompleted = stm32_wait_tx_completed,
.enableRx = stm32_enable_rx,
.getByte = stm32_serial_get_byte,
.getLastByte = stm32_serial_get_last_byte,
.getBufferedBytes = stm32_serial_get_buffered_bytes,
.copyRxBuffer = stm32_serial_copy_rx_buffer,
.clearRxBuffer = stm32_serial_clear_rx_buffer,
.getBaudrate = stm32_serial_get_baudrate,
.setBaudrate = stm32_serial_set_baudrate,
.setPolarity = nullptr,
.setHWOption = stm32_serial_hw_option,
.setReceiveCb = nullptr, // TODO
.setIdleCb = stm32_serial_set_idle_cb,
.setBaudrateCb = nullptr,
};