1
0
Fork 0
mirror of https://github.com/betaflight/betaflight.git synced 2025-07-19 14:25:20 +03:00
betaflight/src/main/drivers/serial_uart.c
2024-12-21 05:01:00 +11:00

609 lines
19 KiB
C

/*
* This file is part of Cleanflight and Betaflight.
*
* Cleanflight and Betaflight are free software. You can redistribute
* this software and/or modify this software 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.
*
* Cleanflight and Betaflight are distributed in the hope that they
* 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 software.
*
* If not, see <http://www.gnu.org/licenses/>.
*/
/*
* Authors:
* jflyper - Refactoring, cleanup and made pin-configurable
* Dominic Clifton - Serial port abstraction, Separation of common STM32 code for cleanflight, various cleanups.
* Hamasaki/Timecop - Initial baseflight code
*/
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include "platform.h"
#ifdef USE_UART
#include "build/build_config.h"
#include <common/maths.h>
#include "common/utils.h"
#include "io/serial.h"
#include "drivers/dma.h"
#include "drivers/dma_reqmap.h"
#include "drivers/rcc.h"
#include "drivers/serial.h"
#include "drivers/serial_impl.h"
#include "drivers/serial_uart.h"
#include "drivers/serial_uart_impl.h"
#include "pg/serial_uart.h"
#if defined(STM32H7)
#define UART_TX_BUFFER_ATTRIBUTE DMA_RAM // D2 SRAM
#define UART_RX_BUFFER_ATTRIBUTE DMA_RAM // D2 SRAM
#elif defined(STM32G4)
#define UART_TX_BUFFER_ATTRIBUTE DMA_RAM_W // SRAM MPU NOT_BUFFERABLE
#define UART_RX_BUFFER_ATTRIBUTE DMA_RAM_R // SRAM MPU NOT CACHABLE
#elif defined(STM32F7)
#define UART_TX_BUFFER_ATTRIBUTE FAST_DATA_ZERO_INIT // DTCM RAM
#define UART_RX_BUFFER_ATTRIBUTE FAST_DATA_ZERO_INIT // DTCM RAM
#elif defined(STM32F4) || defined(AT32F4) || defined(APM32F4)
#define UART_TX_BUFFER_ATTRIBUTE // NONE
#define UART_RX_BUFFER_ATTRIBUTE // NONE
#else
#error Undefined UART_{TX,RX}_BUFFER_ATTRIBUTE for this MCU
#endif
#define UART_BUFFERS(n) \
UART_BUFFER(UART_TX_BUFFER_ATTRIBUTE, n, T); \
UART_BUFFER(UART_RX_BUFFER_ATTRIBUTE, n, R); struct dummy_s \
/**/
#ifdef USE_UART1
UART_BUFFERS(1);
#endif
#ifdef USE_UART2
UART_BUFFERS(2);
#endif
#ifdef USE_UART3
UART_BUFFERS(3);
#endif
#ifdef USE_UART4
UART_BUFFERS(4);
#endif
#ifdef USE_UART5
UART_BUFFERS(5);
#endif
#ifdef USE_UART6
UART_BUFFERS(6);
#endif
#ifdef USE_UART7
UART_BUFFERS(7);
#endif
#ifdef USE_UART8
UART_BUFFERS(8);
#endif
#ifdef USE_UART9
UART_BUFFERS(9);
#endif
#ifdef USE_UART10
UART_BUFFERS(10);
#endif
#ifdef USE_LPUART1
UART_BUFFERS(Lp1); // TODO - maybe some other naming scheme ?
#endif
#undef UART_BUFFERS
// store only devices configured for target (USE_UARTx)
// some entries may be unused, for example because of pin configuration
// uartDeviceIdx_e is direct index into this table
FAST_DATA_ZERO_INIT uartDevice_t uartDevice[UARTDEV_COUNT];
// map serialPortIdentifier_e to uartDeviceIdx_e
uartDeviceIdx_e uartDeviceIdxFromIdentifier(serialPortIdentifier_e identifier)
{
#ifdef USE_LPUART1
if (identifier == SERIAL_PORT_LPUART1) {
return UARTDEV_LP1;
}
#endif
#if 1 // TODO ...
// store +1 in table - unset values default to 0
// table is for UART only to save space (LPUART is handled separately)
#define _R(id, dev) [id] = (dev) + 1
static const uartDeviceIdx_e uartMap[] = {
#ifdef USE_UART1
_R(SERIAL_PORT_USART1, UARTDEV_1),
#endif
#ifdef USE_UART2
_R(SERIAL_PORT_USART2, UARTDEV_2),
#endif
#ifdef USE_UART3
_R(SERIAL_PORT_USART3, UARTDEV_3),
#endif
#ifdef USE_UART4
_R(SERIAL_PORT_UART4, UARTDEV_4),
#endif
#ifdef USE_UART5
_R(SERIAL_PORT_UART5, UARTDEV_5),
#endif
#ifdef USE_UART6
_R(SERIAL_PORT_USART6, UARTDEV_6),
#endif
#ifdef USE_UART7
_R(SERIAL_PORT_USART7, UARTDEV_7),
#endif
#ifdef USE_UART8
_R(SERIAL_PORT_USART8, UARTDEV_8),
#endif
#ifdef USE_UART9
_R(SERIAL_PORT_UART9, UARTDEV_9),
#endif
#ifdef USE_UART10
_R(SERIAL_PORT_USART10, UARTDEV_10),
#endif
};
#undef _R
if (identifier >= 0 && identifier < (int)ARRAYLEN(uartMap)) {
// UART case, but given USE_UARTx may not be defined
return uartMap[identifier] ? uartMap[identifier] - 1 : UARTDEV_INVALID;
}
#else
{
const int idx = identifier - SERIAL_PORT_UART_FIRST;
if (idx >= 0 && idx < SERIAL_UART_MAX) {
if (BIT(idx) & SERIAL_UART_MASK) {
// return number of enabled UART ports smaller than idx
return popcount((BIT(idx) - 1) & SERIAL_UART_MASK);
} else {
return UARTDEV_INVALID;
}
}
}
#endif
// neither LPUART nor UART
return UARTDEV_INVALID;
}
uartDevice_t *uartDeviceFromIdentifier(serialPortIdentifier_e identifier)
{
const uartDeviceIdx_e deviceIdx = uartDeviceIdxFromIdentifier(identifier);
return deviceIdx != UARTDEV_INVALID ? &uartDevice[deviceIdx] : NULL;
}
serialPort_t *uartOpen(serialPortIdentifier_e identifier, serialReceiveCallbackPtr rxCallback, void *rxCallbackData, uint32_t baudRate, portMode_e mode, portOptions_e options)
{
uartDevice_t *uartDevice = uartDeviceFromIdentifier(identifier);
if (!uartDevice) {
return NULL;
}
// fill identifier early, so initialization code can use it
uartDevice->port.port.identifier = identifier;
uartPort_t *uartPort = serialUART(uartDevice, baudRate, mode, options);
if (!uartPort) {
return NULL;
}
// common serial initialisation code should move to serialPort::init()
uartPort->port.rxBufferHead = uartPort->port.rxBufferTail = 0;
uartPort->port.txBufferHead = uartPort->port.txBufferTail = 0;
// callback works for IRQ-based RX ONLY
uartPort->port.rxCallback = rxCallback;
uartPort->port.rxCallbackData = rxCallbackData;
uartPort->port.mode = mode;
uartPort->port.baudRate = baudRate;
uartPort->port.options = options;
uartReconfigure(uartPort);
return (serialPort_t *)uartPort;
}
static void uartSetBaudRate(serialPort_t *instance, uint32_t baudRate)
{
uartPort_t *uartPort = (uartPort_t *)instance;
uartPort->port.baudRate = baudRate;
uartReconfigure(uartPort);
}
static void uartSetMode(serialPort_t *instance, portMode_e mode)
{
uartPort_t *uartPort = (uartPort_t *)instance;
uartPort->port.mode = mode;
uartReconfigure(uartPort);
}
static uint32_t uartTotalRxBytesWaiting(const serialPort_t *instance)
{
const uartPort_t *uartPort = (const uartPort_t*)instance;
#ifdef USE_DMA
if (uartPort->rxDMAResource) {
// XXX Could be consolidated
#ifdef USE_HAL_DRIVER
uint32_t rxDMAHead = __HAL_DMA_GET_COUNTER(uartPort->Handle.hdmarx);
#else
uint32_t rxDMAHead = xDMA_GetCurrDataCounter(uartPort->rxDMAResource);
#endif
// uartPort->rxDMAPos and rxDMAHead represent distances from the end
// of the buffer. They count DOWN as they advance.
if (uartPort->rxDMAPos >= rxDMAHead) {
return uartPort->rxDMAPos - rxDMAHead;
} else {
return uartPort->port.rxBufferSize + uartPort->rxDMAPos - rxDMAHead;
}
}
#endif
if (uartPort->port.rxBufferHead >= uartPort->port.rxBufferTail) {
return uartPort->port.rxBufferHead - uartPort->port.rxBufferTail;
} else {
return uartPort->port.rxBufferSize + uartPort->port.rxBufferHead - uartPort->port.rxBufferTail;
}
}
static uint32_t uartTotalTxBytesFree(const serialPort_t *instance)
{
const uartPort_t *uartPort = (const uartPort_t*)instance;
uint32_t bytesUsed;
if (uartPort->port.txBufferHead >= uartPort->port.txBufferTail) {
bytesUsed = uartPort->port.txBufferHead - uartPort->port.txBufferTail;
} else {
bytesUsed = uartPort->port.txBufferSize + uartPort->port.txBufferHead - uartPort->port.txBufferTail;
}
#ifdef USE_DMA
if (uartPort->txDMAResource) {
/*
* When we queue up a DMA request, we advance the Tx buffer tail before the transfer finishes, so we must add
* the remaining size of that in-progress transfer here instead:
*/
#ifdef USE_HAL_DRIVER
bytesUsed += __HAL_DMA_GET_COUNTER(uartPort->Handle.hdmatx);
#else
bytesUsed += xDMA_GetCurrDataCounter(uartPort->txDMAResource);
#endif
/*
* If the Tx buffer is being written to very quickly, we might have advanced the head into the buffer
* space occupied by the current DMA transfer. In that case the "bytesUsed" total will actually end up larger
* than the total Tx buffer size, because we'll end up transmitting the same buffer region twice. (So we'll be
* transmitting a garbage mixture of old and new bytes).
*
* Be kind to callers and pretend like our buffer can only ever be 100% full.
*/
if (bytesUsed >= uartPort->port.txBufferSize - 1) {
return 0;
}
}
#endif
return (uartPort->port.txBufferSize - 1) - bytesUsed;
}
static bool isUartTransmitBufferEmpty(const serialPort_t *instance)
{
const uartPort_t *uartPort = (const uartPort_t *)instance;
#ifdef USE_DMA
if (uartPort->txDMAResource) {
return uartPort->txDMAEmpty;
} else
#endif
{
return uartPort->port.txBufferTail == uartPort->port.txBufferHead;
}
}
static uint8_t uartRead(serialPort_t *instance)
{
uint8_t ch;
uartPort_t *uartPort = (uartPort_t *)instance;
#ifdef USE_DMA
if (uartPort->rxDMAResource) {
ch = uartPort->port.rxBuffer[uartPort->port.rxBufferSize - uartPort->rxDMAPos];
if (--uartPort->rxDMAPos == 0)
uartPort->rxDMAPos = uartPort->port.rxBufferSize;
} else
#endif
{
ch = uartPort->port.rxBuffer[uartPort->port.rxBufferTail];
if (uartPort->port.rxBufferTail + 1 >= uartPort->port.rxBufferSize) {
uartPort->port.rxBufferTail = 0;
} else {
uartPort->port.rxBufferTail++;
}
}
return ch;
}
static void uartWrite(serialPort_t *instance, uint8_t ch)
{
uartPort_t *uartPort = (uartPort_t *)instance;
// Check if the TX line is being pulled low by an unpowered peripheral
if (uartPort->checkUsartTxOutput && !uartPort->checkUsartTxOutput(uartPort)) {
// TX line is being pulled low, so don't transmit
return;
}
uartPort->port.txBuffer[uartPort->port.txBufferHead] = ch;
if (uartPort->port.txBufferHead + 1 >= uartPort->port.txBufferSize) {
uartPort->port.txBufferHead = 0;
} else {
uartPort->port.txBufferHead++;
}
#ifdef USE_DMA
if (uartPort->txDMAResource) {
uartTryStartTxDMA(uartPort);
} else
#endif
{
#if defined(USE_HAL_DRIVER)
__HAL_UART_ENABLE_IT(&uartPort->Handle, UART_IT_TXE);
#elif defined(USE_ATBSP_DRIVER)
usart_interrupt_enable(uartPort->USARTx, USART_TDBE_INT, TRUE);
#else
USART_ITConfig(uartPort->USARTx, USART_IT_TXE, ENABLE);
#endif
}
}
static void uartBeginWrite(serialPort_t *instance)
{
uartPort_t *uartPort = (uartPort_t *)instance;
// Check if the TX line is being pulled low by an unpowered peripheral
if (uartPort->checkUsartTxOutput) {
uartPort->checkUsartTxOutput(uartPort);
}
}
static void uartWriteBuf(serialPort_t *instance, const void *data, int count)
{
uartPort_t *uartPort = (uartPort_t *)instance;
uartDevice_t *uart = container_of(uartPort, uartDevice_t, port);
const uint8_t *bytePtr = (const uint8_t*)data;
// Test if checkUsartTxOutput() detected TX line being pulled low by an unpowered peripheral
if (uart->txPinState == TX_PIN_MONITOR) {
// TX line is being pulled low, so don't transmit
return;
}
while (count > 0) {
// Calculate the available space to the end of the buffer
const int spaceToEnd = uartPort->port.txBufferSize - uartPort->port.txBufferHead;
// Determine the amount to copy in this iteration
const int chunkSize = MIN(spaceToEnd, count);
// Copy the chunk
memcpy((void *)&uartPort->port.txBuffer[uartPort->port.txBufferHead], bytePtr, chunkSize);
// Advance source pointer
bytePtr += chunkSize;
// Advance head, wrapping if necessary
uartPort->port.txBufferHead = (uartPort->port.txBufferHead + chunkSize) % uartPort->port.txBufferSize;
// Decrease remaining count
count -= chunkSize;
}
}
static void uartEndWrite(serialPort_t *instance)
{
uartPort_t *uartPort = (uartPort_t *)instance;
uartDevice_t *uart = container_of(uartPort, uartDevice_t, port);
// Check if the TX line is being pulled low by an unpowered peripheral
if (uart->txPinState == TX_PIN_MONITOR) {
// TX line is being pulled low, so don't transmit
return;
}
#ifdef USE_DMA
if (uartPort->txDMAResource) {
uartTryStartTxDMA(uartPort);
} else
#endif
{
#if defined(USE_HAL_DRIVER)
__HAL_UART_ENABLE_IT(&uartPort->Handle, UART_IT_TXE);
#elif defined(USE_ATBSP_DRIVER)
usart_interrupt_enable(uartPort->USARTx, USART_TDBE_INT, TRUE);
#else
USART_ITConfig(uartPort->USARTx, USART_IT_TXE, ENABLE);
#endif
}
}
const struct serialPortVTable uartVTable[] = {
{
.serialWrite = uartWrite,
.serialTotalRxWaiting = uartTotalRxBytesWaiting,
.serialTotalTxFree = uartTotalTxBytesFree,
.serialRead = uartRead,
.serialSetBaudRate = uartSetBaudRate,
.isSerialTransmitBufferEmpty = isUartTransmitBufferEmpty,
.setMode = uartSetMode,
.setCtrlLineStateCb = NULL,
.setBaudRateCb = NULL,
.writeBuf = uartWriteBuf,
.beginWrite = uartBeginWrite,
.endWrite = uartEndWrite,
}
};
// TODO - move to serial_uart_hw.c
#ifdef USE_DMA
void uartConfigureDma(uartDevice_t *uartdev)
{
uartPort_t *uartPort = &(uartdev->port);
const uartHardware_t *hardware = uartdev->hardware;
#ifdef USE_DMA_SPEC
const serialPortIdentifier_e uartPortIdentifier = hardware->identifier;
const uartDeviceIdx_e uartDeviceIdx = uartDeviceIdxFromIdentifier(uartPortIdentifier);
if (uartDeviceIdx == UARTDEV_INVALID) {
return;
}
const int resourceIdx = serialResourceIndex(uartPortIdentifier);
const int ownerIndex = serialOwnerIndex(uartPortIdentifier);
const resourceOwner_e ownerTxRx = serialOwnerTxRx(uartPortIdentifier); // rx is always +1
const dmaChannelSpec_t *dmaChannelSpec;
const serialUartConfig_t *cfg = serialUartConfig(resourceIdx);
if (!cfg) {
return;
}
if (cfg->txDmaopt != DMA_OPT_UNUSED) {
dmaChannelSpec = dmaGetChannelSpecByPeripheral(DMA_PERIPH_UART_TX, uartDeviceIdx, cfg->txDmaopt);
if (dmaChannelSpec) {
uartPort->txDMAResource = dmaChannelSpec->ref;
#if defined(STM32F4) || defined(STM32F7) || defined(STM32H7) || defined(STM32G4) || defined(APM32F4)
uartPort->txDMAChannel = dmaChannelSpec->channel;
#elif defined(AT32F4)
uartPort->txDMAMuxId = dmaChannelSpec->dmaMuxId;
#endif
}
}
if (cfg->rxDmaopt != DMA_OPT_UNUSED) {
dmaChannelSpec = dmaGetChannelSpecByPeripheral(DMA_PERIPH_UART_RX, uartDeviceIdx, cfg->txDmaopt);
if (dmaChannelSpec) {
uartPort->rxDMAResource = dmaChannelSpec->ref;
#if defined(STM32F4) || defined(STM32F7) || defined(STM32H7) || defined(STM32G4) || defined(APM32F4)
uartPort->rxDMAChannel = dmaChannelSpec->channel;
#elif defined(AT32F4)
uartPort->rxDMAMuxId = dmaChannelSpec->dmaMuxId;
#endif
}
}
#else /* USE_DMA_SPEC */
// Non USE_DMA_SPEC does not support configurable ON/OFF of UART DMA
if (hardware->rxDMAResource) {
uartPort->rxDMAResource = hardware->rxDMAResource;
#if defined(STM32F4) || defined(STM32F7) || defined(STM32H7) || defined(STM32G4) || defined(APM32F4)
uartPort->rxDMAChannel = hardware->rxDMAChannel;
#elif defined(AT32F4)
uartPort->rxDMAMuxId = hardware->rxDMAMuxId;
#endif
}
if (hardware->txDMAResource) {
uartPort->txDMAResource = hardware->txDMAResource;
#if defined(STM32F4) || defined(STM32F7) || defined(STM32H7) || defined(STM32G4) || defined(APM32F4)
uartPort->txDMAChannel = hardware->txDMAChannel;
#elif defined(AT32F4)
uartPort->txDMAMuxId = hardware->txDMAMuxId;
#endif
}
#endif /* USE_DMA_SPEC */
if (uartPort->txDMAResource) {
const dmaIdentifier_e identifier = dmaGetIdentifier(uartPort->txDMAResource);
if (dmaAllocate(identifier, ownerTxRx, ownerIndex)) {
dmaEnable(identifier);
#if defined(AT32F4)
dmaMuxEnable(identifier, uartPort->txDMAMuxId);
#endif
dmaSetHandler(identifier, uartDmaIrqHandler, hardware->txPriority, (uint32_t)uartdev);
uartPort->txDMAPeripheralBaseAddr = (uint32_t)&UART_REG_TXD(hardware->reg);
}
}
if (uartPort->rxDMAResource) {
const dmaIdentifier_e identifier = dmaGetIdentifier(uartPort->rxDMAResource);
if (dmaAllocate(identifier, ownerTxRx + 1, ownerIndex)) {
dmaEnable(identifier);
#if defined(AT32F4)
dmaMuxEnable(identifier, uartPort->rxDMAMuxId);
#endif
uartPort->rxDMAPeripheralBaseAddr = (uint32_t)&UART_REG_RXD(hardware->reg);
}
}
}
#endif
#define UART_IRQHandler(type, number, dev) \
FAST_IRQ_HANDLER void type ## number ## _IRQHandler(void) \
{ \
uartPort_t *uartPort = &(uartDevice[(dev)].port); \
uartIrqHandler(uartPort); \
} \
/**/
#ifdef USE_UART1
UART_IRQHandler(USART, 1, UARTDEV_1) // USART1 Rx/Tx IRQ Handler
#endif
#ifdef USE_UART2
UART_IRQHandler(USART, 2, UARTDEV_2) // USART2 Rx/Tx IRQ Handler
#endif
#ifdef USE_UART3
UART_IRQHandler(USART, 3, UARTDEV_3) // USART3 Rx/Tx IRQ Handler
#endif
#ifdef USE_UART4
UART_IRQHandler(UART, 4, UARTDEV_4) // UART4 Rx/Tx IRQ Handler
#endif
#ifdef USE_UART5
UART_IRQHandler(UART, 5, UARTDEV_5) // UART5 Rx/Tx IRQ Handler
#endif
#ifdef USE_UART6
UART_IRQHandler(USART, 6, UARTDEV_6) // USART6 Rx/Tx IRQ Handler
#endif
#ifdef USE_UART7
UART_IRQHandler(UART, 7, UARTDEV_7) // UART7 Rx/Tx IRQ Handler
#endif
#ifdef USE_UART8
UART_IRQHandler(UART, 8, UARTDEV_8) // UART8 Rx/Tx IRQ Handler
#endif
#ifdef USE_UART9
UART_IRQHandler(UART, 9, UARTDEV_9) // UART9 Rx/Tx IRQ Handler
#endif
#ifdef USE_UART10
UART_IRQHandler(UART, 10, UARTDEV_10) // UART10 Rx/Tx IRQ Handler
#endif
#ifdef USE_LPUART1
UART_IRQHandler(LPUART, 1, UARTDEV_LP1) // LPUART1 Rx/Tx IRQ Handler
#endif
#endif // USE_UART