You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
328 lines
6.6 KiB
328 lines
6.6 KiB
#include "main.h"
|
|
#include "malloc_safe.h"
|
|
#include "iface_usart.h"
|
|
|
|
#include "utils/timebase.h"
|
|
#include "utils/circbuf.h"
|
|
|
|
#include "com/com_fileio.h"
|
|
#include "com/debug.h"
|
|
|
|
#include "bus/event_queue.h"
|
|
|
|
|
|
#define DEF_WAIT_TO_MS 5
|
|
|
|
|
|
typedef struct {
|
|
uint16_t wait_timeout;
|
|
USART_TypeDef *dev;
|
|
CircBuf *rxbuf; // allocated receive buffer
|
|
CircBuf *txbuf; // allocated transmit buffer, can be NULL -> no buffer
|
|
} usart_opts;
|
|
|
|
#define opts(iface) ((usart_opts *)(iface)->opts)
|
|
|
|
// ---- Instances ----
|
|
// (needed for async rx/tx with interrupts)
|
|
|
|
static ComIface *usart1_iface = NULL;
|
|
static ComIface *usart2_iface = NULL;
|
|
static ComIface *usart3_iface = NULL;
|
|
|
|
// -------------------
|
|
|
|
|
|
/** Check if data is ready for reading */
|
|
static bool if_rx_rdy(ComIface *iface)
|
|
{
|
|
CircBuf *buf = opts(iface)->rxbuf;
|
|
|
|
return !cbuf_empty(buf);
|
|
}
|
|
|
|
|
|
/** Check if sending is done */
|
|
static bool if_tx_rdy(ComIface *iface)
|
|
{
|
|
CircBuf *buf = opts(iface)->txbuf;
|
|
|
|
if (buf == NULL) {
|
|
// non-buffered mode
|
|
USART_TypeDef* USARTx = opts(iface)->dev;
|
|
return (USARTx->SR & USART_SR_TXE);
|
|
}
|
|
|
|
return !cbuf_full(buf);
|
|
}
|
|
|
|
|
|
/** Check if sending is done */
|
|
static bool if_tx_done(ComIface *iface)
|
|
{
|
|
CircBuf *buf = opts(iface)->txbuf;
|
|
USART_TypeDef* USARTx = opts(iface)->dev;
|
|
|
|
// NULL buffer is considered empty
|
|
return cbuf_empty(buf) && (USARTx->SR & USART_SR_TC);
|
|
}
|
|
|
|
|
|
/** Read one byte (wait for it). False on error. */
|
|
static bool if_rx(ComIface *iface, uint8_t *b)
|
|
{
|
|
// wait for data in the buffer
|
|
if (!com_rx_wait(iface, opts(iface)->wait_timeout) || b == NULL) {
|
|
return false;
|
|
}
|
|
|
|
// read from buffer
|
|
cbuf_pop(opts(iface)->rxbuf, b);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/** Try to unreceive a byte. False on error. */
|
|
static bool if_unrx(ComIface *iface, uint8_t b)
|
|
{
|
|
// push back
|
|
return cbuf_push(opts(iface)->rxbuf, &b);
|
|
}
|
|
|
|
|
|
/** Send one byte (waits for tx) */
|
|
static bool if_tx(ComIface *iface, uint8_t b)
|
|
{
|
|
usart_opts *uopts = opts(iface);
|
|
USART_TypeDef* USARTx = uopts->dev;
|
|
|
|
if (uopts->txbuf == NULL) {
|
|
|
|
// Blocking tx mode
|
|
until_timeout(uopts->wait_timeout) {
|
|
if (USARTx->SR & USART_SR_TXE) {
|
|
USARTx->DR = b;
|
|
return true; // success
|
|
}
|
|
}
|
|
|
|
return false;
|
|
|
|
} else {
|
|
|
|
// Buffered mode
|
|
|
|
// wait for free space in the buffer
|
|
bool ready = com_tx_rdy_wait(iface, uopts->wait_timeout);
|
|
|
|
if (ready) {
|
|
// append to the buffer
|
|
cbuf_append(uopts->txbuf, &b);
|
|
}
|
|
|
|
// regardless ready state, start Tx if there are bytes
|
|
// (should fix a bug where full buffer was never emptied)
|
|
|
|
// If TXE (send buffer empty), start sending the buffer
|
|
// Otherwise, IRQ chain is in progress.
|
|
if (USARTx->SR & USART_SR_TXE) {
|
|
USART_ITConfig(USARTx, USART_IT_TXE, ENABLE); // start tx chain
|
|
}
|
|
|
|
return ready;
|
|
}
|
|
}
|
|
|
|
|
|
/** Read a blob. Returns real read size */
|
|
static size_t if_rxb(ComIface *iface, void *buf, size_t buflen)
|
|
{
|
|
if (buf == NULL) return false;
|
|
//if (!com_rx_wait(iface, opts(iface)->wait_timeout)) return 0;
|
|
|
|
uint8_t* byteptr = (uint8_t*)buf;
|
|
for (size_t i = 0; i < buflen; i++) {
|
|
if (!if_rx(iface, byteptr++)) return i;
|
|
}
|
|
|
|
return buflen;
|
|
}
|
|
|
|
|
|
/** Send a binary blob. False on error. */
|
|
static size_t if_txb(ComIface *iface, const void *blob, size_t size)
|
|
{
|
|
if (blob == NULL) return false;
|
|
//if (!com_tx_rdy_wait(iface, opts(iface)->wait_timeout)) return false;
|
|
|
|
const uint8_t* byteptr = (const uint8_t*)blob;
|
|
for (size_t i = 0; i < size; i++) {
|
|
if (!if_tx(iface, *byteptr++)) return i;
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
|
|
/** Check for incoming data */
|
|
static void if_poll(ComIface *iface)
|
|
{
|
|
if (if_rx_rdy(iface)) {
|
|
// call user cb
|
|
if (iface->rx_callback) {
|
|
iface->rx_callback(iface);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// ---- Init interface ----
|
|
|
|
//void usart_iface_set_baudrate(ComIface *iface, uint32_t baud)
|
|
//{
|
|
// USART_TypeDef* USARTx = opts(iface)->dev;
|
|
//
|
|
// USART_SetBaudrate(USARTx, baud);
|
|
//}
|
|
|
|
|
|
/* public */
|
|
ComIface *usart_iface_init(USART_TypeDef* USARTx, uint32_t baud, size_t rxbuf_len, size_t txbuf_len)
|
|
{
|
|
assert_param(IS_USART_BAUDRATE(baud));
|
|
assert_param(rxbuf_len > 0);
|
|
|
|
ComIface *iface = malloc_s(sizeof(ComIface));
|
|
|
|
// --- setup device specific iface data ---
|
|
|
|
// Allocate the opts config object
|
|
iface->opts = malloc_s(sizeof(usart_opts));
|
|
|
|
// Set device ID
|
|
opts(iface)->dev = USARTx;
|
|
opts(iface)->wait_timeout = DEF_WAIT_TO_MS;
|
|
|
|
// Initialize the rx/tx buffers (malloc's the array)
|
|
opts(iface)->rxbuf = cbuf_create(rxbuf_len, 1);
|
|
|
|
// zero-length TX buffer -> blocking Tx
|
|
opts(iface)->txbuf = (txbuf_len == 0) ? NULL : cbuf_create(txbuf_len, 1);
|
|
|
|
// --- driver instance ---
|
|
|
|
iface->rx_rdy = if_rx_rdy;
|
|
iface->tx_rdy = if_tx_rdy;
|
|
iface->tx_done = if_tx_done;
|
|
|
|
iface->rx = if_rx;
|
|
iface->unrx = if_unrx;
|
|
iface->tx = if_tx;
|
|
|
|
iface->txb = if_txb;
|
|
iface->rxb = if_rxb;
|
|
|
|
iface->poll = if_poll;
|
|
|
|
iface->rx_callback = NULL; // user can replace
|
|
|
|
|
|
/* enable peripehral clock, assign to holder var, enable IRQ */
|
|
IRQn_Type irqn;
|
|
|
|
if (USARTx == USART1) {
|
|
// USART1
|
|
usart1_iface = iface;
|
|
irqn = USART1_IRQn;
|
|
RCC->APB2ENR |= RCC_APB2ENR_USART1EN;
|
|
|
|
} else if (USARTx == USART2) {
|
|
// USART2
|
|
usart2_iface = iface;
|
|
irqn = USART2_IRQn;
|
|
RCC->APB1ENR |= RCC_APB1ENR_USART2EN;
|
|
|
|
} else {
|
|
// USART3
|
|
usart3_iface = iface;
|
|
irqn = USART3_IRQn;
|
|
RCC->APB1ENR |= RCC_APB1ENR_USART3EN;
|
|
}
|
|
|
|
// Enable IRQ
|
|
NVIC_EnableIRQ(irqn);
|
|
|
|
// lower priority than SysTick, but high
|
|
NVIC_SetPriority(irqn, 1); // function does the shifting
|
|
|
|
|
|
/* Setup UART parameters. */
|
|
USART_InitTypeDef usart;
|
|
USART_StructInit(&usart);
|
|
usart.USART_BaudRate = baud;
|
|
USART_Init(USARTx, &usart);
|
|
|
|
/* Enable */
|
|
USART_Cmd(USARTx, ENABLE);
|
|
|
|
// Enable Rx interrupt
|
|
USART_ITConfig(USARTx, USART_IT_RXNE, ENABLE); // disable IRQ
|
|
|
|
return iface;
|
|
}
|
|
|
|
|
|
// ---- IRQ handlers for chained writing and rx ----
|
|
|
|
|
|
/**
|
|
* @brief Common USART irq handler
|
|
* @param iface the interface at which the event occured
|
|
*/
|
|
static void usart_iface_irq_base(ComIface* iface)
|
|
{
|
|
USART_TypeDef* USARTx = opts(iface)->dev;
|
|
|
|
if (USARTx->SR & USART_SR_RXNE) {
|
|
// Byte received.
|
|
uint8_t rxb = USARTx->DR & 0xFF;
|
|
if (!cbuf_append(opts(iface)->rxbuf, &rxb)) {
|
|
// Buffer overflow
|
|
// Can't print debug msg, can cause deadlock
|
|
}
|
|
}
|
|
|
|
if (USARTx->SR & USART_SR_TXE) {
|
|
// Byte sent.
|
|
uint8_t txb;
|
|
|
|
// Send next byte (if buffer is NULL, cbuf_pop also returns false)
|
|
if (cbuf_pop(opts(iface)->txbuf, &txb)) {
|
|
USARTx->DR = txb; // send data
|
|
} else {
|
|
USART_ITConfig(USARTx, USART_IT_TXE, DISABLE); // disable IRQ
|
|
}
|
|
}
|
|
|
|
// Clear ORE flag if set
|
|
USART_ClearFlag(USARTx, USART_FLAG_ORE);
|
|
}
|
|
|
|
|
|
void USART1_IRQHandler(void)
|
|
{
|
|
usart_iface_irq_base(usart1_iface);
|
|
}
|
|
|
|
|
|
void USART2_IRQHandler(void)
|
|
{
|
|
usart_iface_irq_base(usart2_iface);
|
|
}
|
|
|
|
|
|
void USART3_IRQHandler(void)
|
|
{
|
|
usart_iface_irq_base(usart3_iface);
|
|
}
|
|
|