diff --git a/docs/development/targets/timer-dma-conflicts.md b/docs/development/targets/timer-dma-conflicts.md
index c8910d63403..af97f7fbd1e 100644
--- a/docs/development/targets/timer-dma-conflicts.md
+++ b/docs/development/targets/timer-dma-conflicts.md
@@ -150,6 +150,108 @@ STM32H7 uses DMAMUX which eliminates most DMA conflicts:
- Fewer headaches for target developers
- Conflicts still possible but rare, and some channels (e.g. `TIM15_CH2`, see above) have no DMA request line at all regardless of DMAMUX
+## UART Receive Through DMA
+
+A UART can receive through a DMA stream instead of taking an interrupt for every byte.
+A target turns this on per port, by naming a stream for it in `target.h`:
+
+```c
+#define UART2_RX_DMA DMA_TAG(2, 5, 0) // DMA2 stream 5
+```
+
+A port without `UARTx_RX_DMA` behaves exactly as before, and a target that names none
+builds exactly as before.
+
+**What it changes.** The stream writes into the port's receive ring by itself, so a busy
+port no longer costs an interrupt per byte, and bytes that arrive while interrupts are
+held off (an internal flash write, for instance) are not lost. The ring and its size do
+not change. On an H7 at 480 MHz the saving measured around 240 cycles per byte, a fraction
+of a percent of the CPU; relatively it is larger on a slower MCU with a busy port.
+
+**Which ports use it.** Those whose owner reads through the ring: GPS, MSP, telemetry and
+the like. Serial receivers (CRSF, SBUS, IBUS, GHST, SRXL2, ...) take each byte through a
+callback as it lands, because they find their frames by timing, so their port stays on the
+interrupt even when the target names a stream for it.
+
+### Choosing the stream
+
+**F4 and F7.** Each UART's receiver is wired to fixed streams on a fixed channel, and the
+build fails if the tag names any other:
+
+| UART | Stream and channel |
+|------|--------------------|
+| UART1 | `DMA_TAG(2, 2, 4)` or `DMA_TAG(2, 5, 4)` |
+| UART2 | `DMA_TAG(1, 5, 4)` |
+| UART3 | `DMA_TAG(1, 1, 4)` |
+| UART4 | `DMA_TAG(1, 2, 4)` |
+| UART5 | `DMA_TAG(1, 0, 4)` |
+| UART6 | `DMA_TAG(2, 1, 5)` or `DMA_TAG(2, 2, 5)` |
+| UART7 | `DMA_TAG(1, 3, 5)` |
+| UART8 | `DMA_TAG(1, 6, 5)` |
+
+**H7 and AT32F43x.** The DMAMUX connects any UART to any stream (channel, on the AT32),
+so only the controller and the stream matter; leave the last field 0.
+
+**The stream has to be free on that board.** The streams are shared, so this is a
+property of the target rather than of the MCU:
+
+- *Timer outputs.* A stream a timer output will use is refused, and the port stays on the
+ interrupt, so a motor never loses DSHOT to a serial port:
+ - with the motors on DSHOT, every output's stream until the motors start, and afterwards the
+ streams the motors took. Ports opened before the motors (MSP, DJI HD OSD, SmartPort
+ master, LOG) therefore get no output's stream on a DSHOT board;
+ - the LED strip's pad, when that feature is on.
+
+ Outputs that end up as servos or unused, and every output except the LED strip's when the
+ motors are not on DSHOT, leave their streams to the serial ports. This is decided at each
+ boot: after changing the motor protocol, the mixer, the output modes or the LED strip
+ feature, a port gets or gives back its stream at the next reboot. On an H7 the `dmavar` of
+ `DEF_TIM()` is the stream: 0 to 7 are DMA1 streams 0 to 7, 8 to 15 are DMA2 streams 0 to 7.
+- *The ADC* is not checked, so avoid its stream: on F4 and AT32 `ADC1_DMA_STREAM` where the
+ target sets it, otherwise DMA2 stream 0 and DMA2 channel 1; DMA2 stream 0 on F7; DMA2
+ stream 0, 1 or 2 on H7, for `ADC_INSTANCE` ADC1, ADC2 or ADC3.
+- *The SD card* on F4 and F7 SDIO uses DMA2 stream 3 or 6 (`SDCARD_SDIO_DMA`) and is not
+ checked against the serial ports either: if a UART takes that stream first, the card is
+ left unused.
+- *Two UARTs* cannot share one; the second keeps its interrupt.
+
+On an H7 the ring of a port receiving through DMA moves to D2 SRAM, where the data cache
+does not reach: 256 bytes per port named.
+
+## UART Transmit Through DMA
+
+The same for sending: `UARTx_TX_DMA` names the stream that empties the port's transmit
+ring.
+
+```c
+#define UART2_TX_DMA DMA_TAG(2, 7, 0) // DMA2 stream 7
+```
+
+**What it changes.** The port takes one interrupt per transfer instead of one per byte. A
+message written whole with `serialWriteBuf()`, as MSP and MAVLink do, goes out in one
+transfer; bytes written one at a time, as the serial blackbox does, go out a few transfers
+at a time, since whatever is queued while one runs goes in the next. On an H7 at 480 MHz,
+MAVLink at 921600 baud sending 12.6 kB/s took 12 959 interrupts a second, 0.66% of the CPU,
+and 144 through DMA, 0.006%. It is worth a stream on a port that sends a lot: serial
+blackbox, MAVLink, MSP DisplayPort.
+
+**F4 and F7.** Each UART's transmitter is wired to fixed streams too:
+
+| UART | Stream and channel |
+|------|--------------------|
+| UART1 | `DMA_TAG(2, 7, 4)` |
+| UART2 | `DMA_TAG(1, 6, 4)` |
+| UART3 | `DMA_TAG(1, 3, 4)` or `DMA_TAG(1, 4, 7)` |
+| UART4 | `DMA_TAG(1, 4, 4)` |
+| UART5 | `DMA_TAG(1, 7, 4)` |
+| UART6 | `DMA_TAG(2, 6, 5)` or `DMA_TAG(2, 7, 5)` |
+| UART7 | `DMA_TAG(1, 1, 5)` |
+| UART8 | `DMA_TAG(1, 0, 5)` |
+
+On H7 and AT32F43x any free stream (channel) will do, and the rules for a free one are the
+ones above. A port's transmit stream has to be a different one from its receive stream,
+and on an H7 its transmit ring moves to D2 SRAM as well.
+
## Related Documentation
- **overview.md** - Target system basics
diff --git a/src/main/drivers/pwm_mapping.c b/src/main/drivers/pwm_mapping.c
index 06cdf5ea279..897284587ad 100644
--- a/src/main/drivers/pwm_mapping.c
+++ b/src/main/drivers/pwm_mapping.c
@@ -526,12 +526,14 @@ static void pwmInitServos(timMotorServoHardware_t * timOutputs)
static timMotorServoHardware_t timOutputsStatic;
+static bool motorsInitialised = false;
bool pwmMotorAndServoInit(void)
{
pwmBuildTimerOutputList(&timOutputsStatic);
pwmInitMotors(&timOutputsStatic);
pwmInitServos(&timOutputsStatic);
+ motorsInitialised = true;
return (pwmInitError == PWM_INIT_ERROR_NONE);
}
@@ -540,6 +542,27 @@ const timMotorServoHardware_t *pwmGetOutputAssignment(void)
return &timOutputsStatic;
}
+bool pwmIsDmaStreamReserved(DMA_t dma)
+{
+ // Started DSHOT motors own their streams; before they start nothing can have changed the protocol
+ const bool motorsWillClaim = !motorsInitialised && getMotorProtocolProperties(motorConfig()->motorPwmProtocol)->isDSHOT;
+
+ for (int i = 0; i < timerHardwareCount; i++) {
+ const timerHardware_t *timHw = &timerHardware[i];
+ if (dmaGetByTag(timHw->dmaTag) != dma) {
+ continue;
+ }
+ if (motorsWillClaim) {
+ return true;
+ }
+ // The LED strip claims its stream only once it is enabled, late in init
+ if (feature(FEATURE_LED_STRIP) && (TIM_IS_LED(timHw->usageFlags) || timerOverrides(timer2id(timHw->tim))->outputMode == OUTPUT_MODE_LED)) {
+ return true;
+ }
+ }
+ return false;
+}
+
// Upper bound for timerHardware[] size across all supported targets.
// timerHardwareCount is a runtime value; this constant prevents a VLA on the MSP
// task stack. Targets with 22+ entries (e.g. OMNIBUSF4) need more than MAX_PWM_OUTPUTS.
diff --git a/src/main/drivers/pwm_mapping.h b/src/main/drivers/pwm_mapping.h
index 158ab8ce6b5..4e1754ca264 100644
--- a/src/main/drivers/pwm_mapping.h
+++ b/src/main/drivers/pwm_mapping.h
@@ -103,5 +103,7 @@ pwmInitError_e getPwmInitError(void);
const char * getPwmInitErrorMessage(void);
#ifndef SITL_BUILD
const timMotorServoHardware_t *pwmGetOutputAssignment(void);
+// A stream a timer output will claim later in init; one it already owns is not counted
+bool pwmIsDmaStreamReserved(DMA_t dma);
void pwmCalculateAssignment(timMotorServoHardware_t *out, const uint8_t *proposedModes);
#endif // SITL_BUILD
diff --git a/src/main/drivers/serial.c b/src/main/drivers/serial.c
index 1cba2609914..83ed5de3443 100644
--- a/src/main/drivers/serial.c
+++ b/src/main/drivers/serial.c
@@ -20,6 +20,10 @@
#include "platform.h"
+#include "build/atomic.h"
+
+#include "drivers/nvic.h"
+
#include "serial.h"
void serialPrint(serialPort_t *instance, const char *str)
@@ -133,3 +137,24 @@ bool serialIsIdle(serialPort_t *instance)
else
return false;
}
+
+// Swaps in a larger receive buffer; what the old one held is dropped
+void serialSetRxBuffer(serialPort_t *instance, volatile uint8_t *buffer, uint32_t size)
+{
+ if (size <= instance->rxBufferSize) {
+ return;
+ }
+
+ if (instance->vTable->setRxBuffer) {
+ instance->vTable->setRxBuffer(instance, buffer, size);
+ return;
+ }
+
+ // The receive interrupt must never see the new buffer with the old size or indices
+ ATOMIC_BLOCK(NVIC_PRIO_MAX) {
+ instance->rxBuffer = buffer;
+ instance->rxBufferSize = size;
+ instance->rxBufferHead = 0;
+ instance->rxBufferTail = 0;
+ }
+}
diff --git a/src/main/drivers/serial.h b/src/main/drivers/serial.h
index 6ca0a7d1ec2..15935bcca89 100644
--- a/src/main/drivers/serial.h
+++ b/src/main/drivers/serial.h
@@ -106,6 +106,9 @@ struct serialPortVTable {
// Optional functions used to buffer large writes.
void (*beginWrite)(serialPort_t *instance);
void (*endWrite)(serialPort_t *instance);
+
+ // Optional: for a port whose reception is more than the ring, such as a DMA stream
+ void (*setRxBuffer)(serialPort_t *instance, volatile uint8_t *buffer, uint32_t size);
};
void serialWrite(serialPort_t *instance, uint8_t ch);
@@ -122,6 +125,7 @@ void serialPrint(serialPort_t *instance, const char *str);
uint32_t serialGetBaudRate(serialPort_t *instance);
bool serialIsConnected(const serialPort_t *instance);
bool serialIsIdle(serialPort_t *instance);
+void serialSetRxBuffer(serialPort_t *instance, volatile uint8_t *buffer, uint32_t size);
// A shim that adapts the bufWriter API to the serialWriteBuf() API.
void serialWriteBufShim(void *instance, const uint8_t *data, int count);
diff --git a/src/main/drivers/serial_uart.c b/src/main/drivers/serial_uart.c
index e2009331c6b..6e194cd41b2 100644
--- a/src/main/drivers/serial_uart.c
+++ b/src/main/drivers/serial_uart.c
@@ -135,6 +135,9 @@ serialPort_t *uartOpen(USART_TypeDef *USARTx, serialReceiveCallbackPtr rxCallbac
return (serialPort_t *)s;
}
+ // A transfer still on its way would go on reading the ring about to be reset
+ uartTxDmaStop(s);
+
// common serial initialisation code should move to serialPort::init()
s->port.rxBufferHead = s->port.rxBufferTail = 0;
s->port.txBufferHead = s->port.txBufferTail = 0;
@@ -147,12 +150,12 @@ serialPort_t *uartOpen(USART_TypeDef *USARTx, serialReceiveCallbackPtr rxCallbac
uartReconfigure(s);
- if (mode & MODE_RX) {
+ if ((mode & MODE_RX) && !uartRxDmaStart(s)) {
USART_ClearITPendingBit(s->USARTx, USART_IT_RXNE);
USART_ITConfig(s->USARTx, USART_IT_RXNE, ENABLE);
}
- if (mode & MODE_TX) {
+ if ((mode & MODE_TX) && !uartTxDmaStart(s)) {
USART_ITConfig(s->USARTx, USART_IT_TXE, ENABLE);
}
@@ -186,10 +189,12 @@ uint32_t uartTotalRxBytesWaiting(const serialPort_t *instance)
{
const uartPort_t *s = (const uartPort_t*)instance;
- if (s->port.rxBufferHead >= s->port.rxBufferTail) {
- return s->port.rxBufferHead - s->port.rxBufferTail;
+ const uint32_t head = uartRxBufferHead(s);
+
+ if (head >= s->port.rxBufferTail) {
+ return head - s->port.rxBufferTail;
} else {
- return s->port.rxBufferSize + s->port.rxBufferHead - s->port.rxBufferTail;
+ return s->port.rxBufferSize + head - s->port.rxBufferTail;
}
}
@@ -229,17 +234,52 @@ uint8_t uartRead(serialPort_t *instance)
return ch;
}
-void uartWrite(serialPort_t *instance, uint8_t ch)
+static void uartQueue(uartPort_t *s, uint8_t ch)
{
- uartPort_t *s = (uartPort_t *)instance;
s->port.txBuffer[s->port.txBufferHead] = ch;
if (s->port.txBufferHead + 1 >= s->port.txBufferSize) {
s->port.txBufferHead = 0;
} else {
s->port.txBufferHead++;
}
+}
- USART_ITConfig(s->USARTx, USART_IT_TXE, ENABLE);
+void uartWrite(serialPort_t *instance, uint8_t ch)
+{
+ uartPort_t *s = (uartPort_t *)instance;
+ uartQueue(s, ch);
+
+ if (uartTxDmaRunning(s)) {
+ uartStartTxDMA(s);
+ } else {
+ USART_ITConfig(s->USARTx, USART_IT_TXE, ENABLE);
+ }
+}
+
+// Queued whole before the stream starts, so a message goes out in one transfer
+static void uartWriteBuf(serialPort_t *instance, const void *data, int count)
+{
+ uartPort_t *s = (uartPort_t *)instance;
+ const uint8_t *p = data;
+
+ for (; count > 0; count--, p++) {
+ while (!uartTotalTxBytesFree(instance)) {
+ // The ring is full: sending is what makes room
+ if (uartTxDmaRunning(s)) {
+ uartStartTxDMA(s);
+ }
+ }
+
+ if (uartTxDmaRunning(s)) {
+ uartQueue(s, *p);
+ } else {
+ uartWrite(instance, *p);
+ }
+ }
+
+ if (uartTxDmaRunning(s)) {
+ uartStartTxDMA(s);
+ }
}
bool isUartIdle(serialPort_t *instance)
@@ -264,9 +304,10 @@ const struct serialPortVTable uartVTable[] = {
.setMode = uartSetMode,
.setOptions = uartSetOptions,
.isConnected = NULL,
- .writeBuf = NULL,
+ .writeBuf = uartWriteBuf,
.beginWrite = NULL,
.endWrite = NULL,
.isIdle = isUartIdle,
+ .setRxBuffer = uartSetRxBuffer,
}
};
diff --git a/src/main/drivers/serial_uart.h b/src/main/drivers/serial_uart.h
index 4c1e6d57508..ee6fdd8fd71 100644
--- a/src/main/drivers/serial_uart.h
+++ b/src/main/drivers/serial_uart.h
@@ -20,11 +20,10 @@
#define UART_AF(uart, af) CONCAT3(GPIO_AF, af, _ ## uart)
// Since serial ports can be used for any function these buffer sizes should be equal
-// The two largest things that need to be sent are: 1, MSP responses, 2, UBLOX SVINFO packet.
+// The largest thing that needs to be sent is an MSP response.
-// Size must be a power of two due to various optimizations which use 'and' instead of 'mod'
-// Various serial routines return the buffer occupied size as uint8_t which would need to be extended in order to
-// increase size further.
+// Size must be a power of two due to various optimizations which use 'and' instead of 'mod'.
+// The GPS port gets a larger receive buffer of its own, see gps.c.
#define UART1_RX_BUFFER_SIZE 256
#define UART1_TX_BUFFER_SIZE 256
#define UART2_RX_BUFFER_SIZE 256
@@ -66,6 +65,13 @@ typedef struct {
USART_TypeDef *USARTx;
#endif
+#ifdef USE_UART_RX_DMA
+ struct dmaChannelDescriptor_s *rxDma; // the stream filling rxBuffer, NULL where the byte interrupt does
+#endif
+#ifdef USE_UART_TX_DMA
+ struct dmaChannelDescriptor_s *txDma; // the stream emptying txBuffer, NULL where the byte interrupt does
+ volatile uint32_t txDmaCount; // bytes of the transfer on its way, 0 when the stream is idle
+#endif
} uartPort_t;
void uartGetPortPins(UARTDevice_e device, serialPortPins_t * pins);
diff --git a/src/main/drivers/serial_uart_at32f43x.c b/src/main/drivers/serial_uart_at32f43x.c
index 438af01c665..5f54dbf1efa 100644
--- a/src/main/drivers/serial_uart_at32f43x.c
+++ b/src/main/drivers/serial_uart_at32f43x.c
@@ -357,9 +357,245 @@ static uartDevice_t* uartHardwareMap[] = {
#endif
};
+#if defined(USE_UART_RX_DMA) || defined(USE_UART_TX_DMA)
+static UARTDevice_e uartDeviceOf(const uartPort_t *s)
+{
+ for (int device = 0; device < UARTDEV_MAX; device++) {
+ if (uartHardwareMap[device] && &uartHardwareMap[device]->port == s) {
+ return device;
+ }
+ }
+ return UARTDEV_MAX;
+}
+
+#endif
+
+#ifdef USE_UART_RX_DMA
+typedef struct {
+ dmaTag_t tag;
+ uint32_t request; // this UART's receiver on the DMAMUX
+} uartRxDmaConfig_t;
+
+static const uartRxDmaConfig_t uartRxDmaConfig[UARTDEV_MAX] = {
+#ifdef UART1_RX_DMA
+ [UARTDEV_1] = { UART1_RX_DMA, DMAMUX_DMAREQ_ID_USART1_RX },
+#endif
+#ifdef UART2_RX_DMA
+ [UARTDEV_2] = { UART2_RX_DMA, DMAMUX_DMAREQ_ID_USART2_RX },
+#endif
+#ifdef UART3_RX_DMA
+ [UARTDEV_3] = { UART3_RX_DMA, DMAMUX_DMAREQ_ID_USART3_RX },
+#endif
+#ifdef UART4_RX_DMA
+ [UARTDEV_4] = { UART4_RX_DMA, DMAMUX_DMAREQ_ID_UART4_RX },
+#endif
+#ifdef UART5_RX_DMA
+ [UARTDEV_5] = { UART5_RX_DMA, DMAMUX_DMAREQ_ID_UART5_RX },
+#endif
+#ifdef UART6_RX_DMA
+ [UARTDEV_6] = { UART6_RX_DMA, DMAMUX_DMAREQ_ID_USART6_RX },
+#endif
+#ifdef UART7_RX_DMA
+ [UARTDEV_7] = { UART7_RX_DMA, DMAMUX_DMAREQ_ID_UART7_RX },
+#endif
+#ifdef UART8_RX_DMA
+ [UARTDEV_8] = { UART8_RX_DMA, DMAMUX_DMAREQ_ID_UART8_RX },
+#endif
+};
+
+static void uartRxDmaStop(uartPort_t *s)
+{
+ if (s->rxDma) {
+ usart_dma_receiver_enable(s->USARTx, FALSE);
+ dma_channel_enable(s->rxDma->ref, FALSE);
+ s->rxDma = NULL;
+ }
+}
+
+bool uartRxDmaStart(uartPort_t *s)
+{
+ uartRxDmaStop(s);
+
+ const UARTDevice_e device = uartDeviceOf(s);
+ if (device == UARTDEV_MAX || uartRxDmaConfig[device].tag == DMA_NONE || !(s->port.mode & MODE_RX) || s->port.rxCallback) {
+ return false;
+ }
+
+ const DMA_t dma = dmaGetByTag(uartRxDmaConfig[device].tag);
+ if (!dma || !uartDmaStreamAvailable(dma, device)) {
+ return false;
+ }
+
+ dmaInit(dma, OWNER_SERIAL, RESOURCE_INDEX(device));
+ dma_reset(dma->ref);
+
+ dma_init_type init;
+ dma_default_para_init(&init);
+ init.peripheral_base_addr = (uint32_t)&s->USARTx->dt;
+ init.memory_base_addr = (uint32_t)s->port.rxBuffer;
+ init.direction = DMA_DIR_PERIPHERAL_TO_MEMORY;
+ init.buffer_size = s->port.rxBufferSize;
+ init.peripheral_inc_enable = FALSE;
+ init.memory_inc_enable = TRUE;
+ init.peripheral_data_width = DMA_PERIPHERAL_DATA_WIDTH_BYTE;
+ init.memory_data_width = DMA_MEMORY_DATA_WIDTH_BYTE;
+ init.loop_mode_enable = TRUE;
+ init.priority = DMA_PRIORITY_MEDIUM;
+ dma_init(dma->ref, &init);
+ dmaMuxEnable(dma, uartRxDmaConfig[device].request);
+ dma_channel_enable(dma->ref, TRUE);
+
+ s->port.rxBufferHead = s->port.rxBufferTail = 0;
+ s->rxDma = dma;
+
+ usart_interrupt_enable(s->USARTx, USART_RDBF_INT, FALSE);
+ usart_dma_receiver_enable(s->USARTx, TRUE);
+ return true;
+}
+#endif
+
+#ifdef USE_UART_TX_DMA
+typedef struct {
+ dmaTag_t tag;
+ uint32_t request; // this UART's transmitter on the DMAMUX
+} uartTxDmaConfig_t;
+
+static const uartTxDmaConfig_t uartTxDmaConfig[UARTDEV_MAX] = {
+#ifdef UART1_TX_DMA
+ [UARTDEV_1] = { UART1_TX_DMA, DMAMUX_DMAREQ_ID_USART1_TX },
+#endif
+#ifdef UART2_TX_DMA
+ [UARTDEV_2] = { UART2_TX_DMA, DMAMUX_DMAREQ_ID_USART2_TX },
+#endif
+#ifdef UART3_TX_DMA
+ [UARTDEV_3] = { UART3_TX_DMA, DMAMUX_DMAREQ_ID_USART3_TX },
+#endif
+#ifdef UART4_TX_DMA
+ [UARTDEV_4] = { UART4_TX_DMA, DMAMUX_DMAREQ_ID_UART4_TX },
+#endif
+#ifdef UART5_TX_DMA
+ [UARTDEV_5] = { UART5_TX_DMA, DMAMUX_DMAREQ_ID_UART5_TX },
+#endif
+#ifdef UART6_TX_DMA
+ [UARTDEV_6] = { UART6_TX_DMA, DMAMUX_DMAREQ_ID_USART6_TX },
+#endif
+#ifdef UART7_TX_DMA
+ [UARTDEV_7] = { UART7_TX_DMA, DMAMUX_DMAREQ_ID_UART7_TX },
+#endif
+#ifdef UART8_TX_DMA
+ [UARTDEV_8] = { UART8_TX_DMA, DMAMUX_DMAREQ_ID_UART8_TX },
+#endif
+};
+
+// Full, half transfer and error flags; the interrupt enables sit at the same bits
+#define UART_TX_DMA_FLAGS (DMA_IT_TCIF | DMA_IT_HTIF | DMA_IT_DMEIF)
+#define UART_TX_DMA_ENDED (DMA_IT_TCIF | DMA_IT_DMEIF)
+
+void uartTxDmaStop(uartPort_t *s)
+{
+ if (s->txDma) {
+ usart_dma_transmitter_enable(s->USARTx, FALSE);
+ dma_interrupt_enable(s->txDma->ref, UART_TX_DMA_ENDED, FALSE);
+ dma_channel_enable(s->txDma->ref, FALSE);
+ DMA_CLEAR_FLAG(s->txDma, UART_TX_DMA_FLAGS);
+ s->txDma = NULL;
+ s->txDmaCount = 0;
+ }
+}
+
+void uartStartTxDMA(uartPort_t *s)
+{
+ // Masked: the end-of-transfer interrupt also starts the next one
+ ATOMIC_BLOCK(NVIC_PRIO_SERIALUART) {
+ const uint32_t head = s->port.txBufferHead;
+ const uint32_t tail = s->port.txBufferTail;
+
+ if (s->txDma && !s->txDmaCount && head != tail) {
+ // Up to the head, or to the end of the ring if the queue wraps: the rest follows
+ const uint32_t count = (head > tail) ? head - tail : s->port.txBufferSize - tail;
+
+ s->txDmaCount = count;
+ // Unlike an STM32 stream, a channel stays enabled after its transfer, and
+ // takes a new count and address only while disabled
+ dma_channel_enable(s->txDma->ref, FALSE);
+ s->txDma->ref->maddr = (uint32_t)&s->port.txBuffer[tail];
+ dma_data_number_set(s->txDma->ref, count);
+ dma_channel_enable(s->txDma->ref, TRUE);
+ }
+ }
+}
+
+// An error ends the transfer too, or the port would wait for it forever
+static void uartTxDmaHandler(DMA_t dma)
+{
+ uartPort_t *s = (uartPort_t *)dma->userParam;
+
+ // Clear every flag: one left set re-raises the interrupt, starving USB at this priority
+ const bool ended = DMA_GET_FLAG_STATUS(dma, UART_TX_DMA_ENDED);
+ DMA_CLEAR_FLAG(dma, UART_TX_DMA_FLAGS);
+
+ if (ended) {
+ // After an error the rest goes again, unless nothing went: that error would only repeat
+ const uint32_t sent = s->txDmaCount - dma_data_number_get(dma->ref);
+ s->port.txBufferTail = (s->port.txBufferTail + (sent ? sent : s->txDmaCount)) % s->port.txBufferSize;
+ s->txDmaCount = 0;
+ uartStartTxDMA(s);
+ }
+}
+
+bool uartTxDmaStart(uartPort_t *s)
+{
+ uartTxDmaStop(s);
+
+ const UARTDevice_e device = uartDeviceOf(s);
+ if (device == UARTDEV_MAX || uartTxDmaConfig[device].tag == DMA_NONE || !(s->port.mode & MODE_TX)) {
+ return false;
+ }
+
+ const DMA_t dma = dmaGetByTag(uartTxDmaConfig[device].tag);
+ if (!dma || !uartDmaStreamAvailable(dma, device)) {
+ return false;
+ }
+#ifdef USE_UART_RX_DMA
+ // The channel this port receives on is this UART's too, so it looks free to it
+ if (dma == s->rxDma) {
+ return false;
+ }
+#endif
+
+ dmaInit(dma, OWNER_SERIAL, RESOURCE_INDEX(device));
+ dmaSetHandler(dma, uartTxDmaHandler, NVIC_PRIO_SERIALUART, (uint32_t)s);
+ dma_reset(dma->ref);
+
+ dma_init_type init;
+ dma_default_para_init(&init);
+ init.peripheral_base_addr = (uint32_t)&s->USARTx->dt;
+ init.memory_base_addr = (uint32_t)s->port.txBuffer;
+ init.direction = DMA_DIR_MEMORY_TO_PERIPHERAL;
+ init.peripheral_inc_enable = FALSE;
+ init.memory_inc_enable = TRUE;
+ init.peripheral_data_width = DMA_PERIPHERAL_DATA_WIDTH_BYTE;
+ init.memory_data_width = DMA_MEMORY_DATA_WIDTH_BYTE;
+ init.loop_mode_enable = FALSE;
+ init.priority = DMA_PRIORITY_MEDIUM;
+ dma_init(dma->ref, &init);
+ dmaMuxEnable(dma, uartTxDmaConfig[device].request);
+ DMA_CLEAR_FLAG(dma, UART_TX_DMA_FLAGS);
+ dma_interrupt_enable(dma->ref, UART_TX_DMA_ENDED, TRUE);
+
+ s->txDmaCount = 0;
+ s->txDma = dma;
+
+ usart_interrupt_enable(s->USARTx, USART_TDBE_INT, FALSE);
+ usart_dma_transmitter_enable(s->USARTx, TRUE);
+ return true;
+}
+#endif
+
void uartIrqHandler(uartPort_t *s)
{
- if (usart_flag_get(s->USARTx, USART_RDBF_FLAG) == SET) {
+ // With RX on DMA the interrupt still fires for TX: an RX byte is the stream's to take
+ if (usart_flag_get(s->USARTx, USART_RDBF_FLAG) == SET && !uartRxDmaRunning(s)) {
if (s->port.rxCallback) {
s->port.rxCallback(s->USARTx->dt, s->port.rxCallbackData);
} else {
@@ -368,7 +604,8 @@ void uartIrqHandler(uartPort_t *s)
}
}
- if (usart_flag_get(s->USARTx, USART_TDBE_FLAG) == SET) {
+ // With TX on DMA the ring is the stream's to empty
+ if (usart_flag_get(s->USARTx, USART_TDBE_FLAG) == SET && !uartTxDmaRunning(s)) {
if (s->port.txBufferTail != s->port.txBufferHead) {
usart_data_transmit(s->USARTx, s->port.txBuffer[s->port.txBufferTail]);
s->port.txBufferTail = (s->port.txBufferTail + 1) % s->port.txBufferSize;
diff --git a/src/main/drivers/serial_uart_hal.c b/src/main/drivers/serial_uart_hal.c
index 659fbf3c0d3..4119e8c7e8b 100644
--- a/src/main/drivers/serial_uart_hal.c
+++ b/src/main/drivers/serial_uart_hal.c
@@ -108,7 +108,11 @@ static void uartReconfigure(uartPort_t *uartPort)
HAL_UART_Init(&uartPort->Handle);
}
- if (uartPort->port.mode & MODE_RX) {
+ if ((uartPort->port.mode & MODE_RX) && uartRxDmaRunning(uartPort)) {
+ /* The de-init cleared the DMA request */
+ SET_BIT(uartPort->USARTx->CR3, USART_CR3_DMAR);
+ }
+ else if (uartPort->port.mode & MODE_RX) {
/* Enable the UART Parity Error Interrupt */
SET_BIT(uartPort->USARTx->CR1, USART_CR1_PEIE);
@@ -120,7 +124,11 @@ static void uartReconfigure(uartPort_t *uartPort)
}
// Transmit IRQ
- if (uartPort->port.mode & MODE_TX) {
+ if ((uartPort->port.mode & MODE_TX) && uartTxDmaRunning(uartPort)) {
+ /* The de-init cleared the DMA request */
+ SET_BIT(uartPort->USARTx->CR3, USART_CR3_DMAT);
+ }
+ else if (uartPort->port.mode & MODE_TX) {
/* Enable the UART Transmit Data Register Empty Interrupt */
SET_BIT(uartPort->USARTx->CR1, USART_CR1_TXEIE);
}
@@ -169,6 +177,9 @@ serialPort_t *uartOpen(USART_TypeDef *USARTx, serialReceiveCallbackPtr callback,
}
+ // A transfer still on its way would go on reading the ring about to be reset
+ uartTxDmaStop(s);
+
// common serial initialisation code should move to serialPort::init()
s->port.rxBufferHead = s->port.rxBufferTail = 0;
s->port.txBufferHead = s->port.txBufferTail = 0;
@@ -179,6 +190,9 @@ serialPort_t *uartOpen(USART_TypeDef *USARTx, serialReceiveCallbackPtr callback,
s->port.baudRate = baudRate;
s->port.options = options;
+ // Before the UART, so it comes up with the stream already running
+ uartRxDmaStart(s);
+ uartTxDmaStart(s);
uartReconfigure(s);
return (serialPort_t *)s;
@@ -209,10 +223,12 @@ uint32_t uartTotalRxBytesWaiting(const serialPort_t *instance)
{
uartPort_t *s = (uartPort_t*)instance;
- if (s->port.rxBufferHead >= s->port.rxBufferTail) {
- return s->port.rxBufferHead - s->port.rxBufferTail;
+ const uint32_t head = uartRxBufferHead(s);
+
+ if (head >= s->port.rxBufferTail) {
+ return head - s->port.rxBufferTail;
} else {
- return s->port.rxBufferSize + s->port.rxBufferHead - s->port.rxBufferTail;
+ return s->port.rxBufferSize + head - s->port.rxBufferTail;
}
}
@@ -253,17 +269,52 @@ uint8_t uartRead(serialPort_t *instance)
return ch;
}
-void uartWrite(serialPort_t *instance, uint8_t ch)
+static void uartQueue(uartPort_t *s, uint8_t ch)
{
- uartPort_t *s = (uartPort_t *)instance;
s->port.txBuffer[s->port.txBufferHead] = ch;
if (s->port.txBufferHead + 1 >= s->port.txBufferSize) {
s->port.txBufferHead = 0;
} else {
s->port.txBufferHead++;
}
+}
+
+void uartWrite(serialPort_t *instance, uint8_t ch)
+{
+ uartPort_t *s = (uartPort_t *)instance;
+ uartQueue(s, ch);
- __HAL_UART_ENABLE_IT(&s->Handle, UART_IT_TXE);
+ if (uartTxDmaRunning(s)) {
+ uartStartTxDMA(s);
+ } else {
+ __HAL_UART_ENABLE_IT(&s->Handle, UART_IT_TXE);
+ }
+}
+
+// Queued whole before the stream starts, so a message goes out in one transfer
+static void uartWriteBuf(serialPort_t *instance, const void *data, int count)
+{
+ uartPort_t *s = (uartPort_t *)instance;
+ const uint8_t *p = data;
+
+ for (; count > 0; count--, p++) {
+ while (!uartTotalTxBytesFree(instance)) {
+ // The ring is full: sending is what makes room
+ if (uartTxDmaRunning(s)) {
+ uartStartTxDMA(s);
+ }
+ }
+
+ if (uartTxDmaRunning(s)) {
+ uartQueue(s, *p);
+ } else {
+ uartWrite(instance, *p);
+ }
+ }
+
+ if (uartTxDmaRunning(s)) {
+ uartStartTxDMA(s);
+ }
}
bool isUartIdle(serialPort_t *instance)
@@ -288,9 +339,10 @@ const struct serialPortVTable uartVTable[] = {
.setMode = uartSetMode,
.setOptions = uartSetOptions,
.isConnected = NULL,
- .writeBuf = NULL,
+ .writeBuf = uartWriteBuf,
.beginWrite = NULL,
.endWrite = NULL,
.isIdle = isUartIdle,
+ .setRxBuffer = uartSetRxBuffer,
}
};
diff --git a/src/main/drivers/serial_uart_hal_at32f43x.c b/src/main/drivers/serial_uart_hal_at32f43x.c
index f30726332f4..bd79ed7970e 100644
--- a/src/main/drivers/serial_uart_hal_at32f43x.c
+++ b/src/main/drivers/serial_uart_hal_at32f43x.c
@@ -138,6 +138,9 @@ serialPort_t *uartOpen(usart_type *USARTx, serialReceiveCallbackPtr rxCallback,
return (serialPort_t *)s;
}
+ // A transfer still on its way would go on reading the ring about to be reset
+ uartTxDmaStop(s);
+
// common serial initialisation code should move to serialPort::init()
s->port.rxBufferHead = s->port.rxBufferTail = 0;
s->port.txBufferHead = s->port.txBufferTail = 0;
@@ -150,13 +153,13 @@ serialPort_t *uartOpen(usart_type *USARTx, serialReceiveCallbackPtr rxCallback,
uartReconfigure(s);
- if (mode & MODE_RX) {
+ if ((mode & MODE_RX) && !uartRxDmaStart(s)) {
usart_flag_clear(s->USARTx, USART_RDBF_FLAG);
usart_interrupt_enable (s->USARTx, USART_RDBF_INT, TRUE);
}
- if (mode & MODE_TX) {
+ if ((mode & MODE_TX) && !uartTxDmaStart(s)) {
usart_interrupt_enable (s->USARTx, USART_TDBE_INT, TRUE);
}
@@ -190,10 +193,12 @@ uint32_t uartTotalRxBytesWaiting(const serialPort_t *instance)
{
const uartPort_t *s = (const uartPort_t*)instance;
- if (s->port.rxBufferHead >= s->port.rxBufferTail) {
- return s->port.rxBufferHead - s->port.rxBufferTail;
+ const uint32_t head = uartRxBufferHead(s);
+
+ if (head >= s->port.rxBufferTail) {
+ return head - s->port.rxBufferTail;
} else {
- return s->port.rxBufferSize + s->port.rxBufferHead - s->port.rxBufferTail;
+ return s->port.rxBufferSize + head - s->port.rxBufferTail;
}
}
@@ -233,18 +238,52 @@ uint8_t uartRead(serialPort_t *instance)
return ch;
}
-void uartWrite(serialPort_t *instance, uint8_t ch)
+static void uartQueue(uartPort_t *s, uint8_t ch)
{
- uartPort_t *s = (uartPort_t *)instance;
s->port.txBuffer[s->port.txBufferHead] = ch;
if (s->port.txBufferHead + 1 >= s->port.txBufferSize) {
s->port.txBufferHead = 0;
} else {
s->port.txBufferHead++;
}
+}
+
+void uartWrite(serialPort_t *instance, uint8_t ch)
+{
+ uartPort_t *s = (uartPort_t *)instance;
+ uartQueue(s, ch);
+
+ if (uartTxDmaRunning(s)) {
+ uartStartTxDMA(s);
+ } else {
+ usart_interrupt_enable (s->USARTx, USART_TDBE_INT, TRUE);
+ }
+}
- usart_interrupt_enable (s->USARTx, USART_TDBE_INT, TRUE);
+// Queued whole before the stream starts, so a message goes out in one transfer
+static void uartWriteBuf(serialPort_t *instance, const void *data, int count)
+{
+ uartPort_t *s = (uartPort_t *)instance;
+ const uint8_t *p = data;
+
+ for (; count > 0; count--, p++) {
+ while (!uartTotalTxBytesFree(instance)) {
+ // The ring is full: sending is what makes room
+ if (uartTxDmaRunning(s)) {
+ uartStartTxDMA(s);
+ }
+ }
+
+ if (uartTxDmaRunning(s)) {
+ uartQueue(s, *p);
+ } else {
+ uartWrite(instance, *p);
+ }
+ }
+ if (uartTxDmaRunning(s)) {
+ uartStartTxDMA(s);
+ }
}
bool isUartIdle(serialPort_t *instance)
@@ -270,9 +309,10 @@ const struct serialPortVTable uartVTable[] = {
.setMode = uartSetMode,
.setOptions = uartSetOptions,
.isConnected = NULL,
- .writeBuf = NULL,
+ .writeBuf = uartWriteBuf,
.beginWrite = NULL,
.endWrite = NULL,
.isIdle = isUartIdle,
+ .setRxBuffer = uartSetRxBuffer,
}
};
diff --git a/src/main/drivers/serial_uart_impl.h b/src/main/drivers/serial_uart_impl.h
index d983570285b..c4b56463931 100644
--- a/src/main/drivers/serial_uart_impl.h
+++ b/src/main/drivers/serial_uart_impl.h
@@ -19,10 +19,155 @@
// device specific uart implementation is defined here
+#include "build/atomic.h"
+
+#include "drivers/nvic.h"
+
extern const struct serialPortVTable uartVTable[];
+#if defined(USE_UART_RX_DMA) || defined(USE_UART_TX_DMA)
+#include "common/utils.h"
+#include "drivers/dma.h"
+#include "drivers/pwm_mapping.h"
+#include "drivers/timer.h"
+
+#if defined(STM32F4) || defined(STM32F7)
+// Fixed streams and channel per UART receiver and transmitter (reference manual request
+// tables): a target naming another gets a build error instead of a silent port
+#define UART_DMA_IS(tag, dma, stream, channel) (DMATAG_GET_DMA(tag) == (dma) && DMATAG_GET_STREAM(tag) == (stream) && DMATAG_GET_CHANNEL(tag) == (channel))
+#ifdef UART1_RX_DMA
+STATIC_ASSERT(UART_DMA_IS(UART1_RX_DMA, 2, 2, 4) || UART_DMA_IS(UART1_RX_DMA, 2, 5, 4), UART1_RX_DMA_is_DMA2_stream_2_or_5_channel_4);
+#endif
+#ifdef UART2_RX_DMA
+STATIC_ASSERT(UART_DMA_IS(UART2_RX_DMA, 1, 5, 4), UART2_RX_DMA_is_DMA1_stream_5_channel_4);
+#endif
+#ifdef UART3_RX_DMA
+STATIC_ASSERT(UART_DMA_IS(UART3_RX_DMA, 1, 1, 4), UART3_RX_DMA_is_DMA1_stream_1_channel_4);
+#endif
+#ifdef UART4_RX_DMA
+STATIC_ASSERT(UART_DMA_IS(UART4_RX_DMA, 1, 2, 4), UART4_RX_DMA_is_DMA1_stream_2_channel_4);
+#endif
+#ifdef UART5_RX_DMA
+STATIC_ASSERT(UART_DMA_IS(UART5_RX_DMA, 1, 0, 4), UART5_RX_DMA_is_DMA1_stream_0_channel_4);
+#endif
+#ifdef UART6_RX_DMA
+STATIC_ASSERT(UART_DMA_IS(UART6_RX_DMA, 2, 1, 5) || UART_DMA_IS(UART6_RX_DMA, 2, 2, 5), UART6_RX_DMA_is_DMA2_stream_1_or_2_channel_5);
+#endif
+#ifdef UART7_RX_DMA
+STATIC_ASSERT(UART_DMA_IS(UART7_RX_DMA, 1, 3, 5), UART7_RX_DMA_is_DMA1_stream_3_channel_5);
+#endif
+#ifdef UART8_RX_DMA
+STATIC_ASSERT(UART_DMA_IS(UART8_RX_DMA, 1, 6, 5), UART8_RX_DMA_is_DMA1_stream_6_channel_5);
+#endif
+#ifdef UART1_TX_DMA
+STATIC_ASSERT(UART_DMA_IS(UART1_TX_DMA, 2, 7, 4), UART1_TX_DMA_is_DMA2_stream_7_channel_4);
+#endif
+#ifdef UART2_TX_DMA
+STATIC_ASSERT(UART_DMA_IS(UART2_TX_DMA, 1, 6, 4), UART2_TX_DMA_is_DMA1_stream_6_channel_4);
+#endif
+#ifdef UART3_TX_DMA
+STATIC_ASSERT(UART_DMA_IS(UART3_TX_DMA, 1, 3, 4) || UART_DMA_IS(UART3_TX_DMA, 1, 4, 7), UART3_TX_DMA_is_DMA1_stream_3_channel_4_or_stream_4_channel_7);
+#endif
+#ifdef UART4_TX_DMA
+STATIC_ASSERT(UART_DMA_IS(UART4_TX_DMA, 1, 4, 4), UART4_TX_DMA_is_DMA1_stream_4_channel_4);
+#endif
+#ifdef UART5_TX_DMA
+STATIC_ASSERT(UART_DMA_IS(UART5_TX_DMA, 1, 7, 4), UART5_TX_DMA_is_DMA1_stream_7_channel_4);
+#endif
+#ifdef UART6_TX_DMA
+STATIC_ASSERT(UART_DMA_IS(UART6_TX_DMA, 2, 6, 5) || UART_DMA_IS(UART6_TX_DMA, 2, 7, 5), UART6_TX_DMA_is_DMA2_stream_6_or_7_channel_5);
+#endif
+#ifdef UART7_TX_DMA
+STATIC_ASSERT(UART_DMA_IS(UART7_TX_DMA, 1, 1, 5), UART7_TX_DMA_is_DMA1_stream_1_channel_5);
+#endif
+#ifdef UART8_TX_DMA
+STATIC_ASSERT(UART_DMA_IS(UART8_TX_DMA, 1, 0, 5), UART8_TX_DMA_is_DMA1_stream_0_channel_5);
+#endif
+#endif
+
+// Free, or this UART's own from an earlier open, and not one a timer output claims later in init
+static inline bool uartDmaStreamAvailable(DMA_t dma, UARTDevice_e device)
+{
+ if (pwmIsDmaStreamReserved(dma)) {
+ return false;
+ }
+ return dmaGetOwner(dma) == OWNER_FREE || (dmaGetOwner(dma) == OWNER_SERIAL && dma->resourceIndex == RESOURCE_INDEX(device));
+}
+#endif
+
+#ifdef USE_UART_RX_DMA
+// False leaves the port on the byte interrupt: no stream named or free, no RX, or an
+// rxCallback that wants each byte as it lands
+bool uartRxDmaStart(uartPort_t *s);
+
+static inline bool uartRxDmaRunning(const uartPort_t *s)
+{
+ return s->rxDma != NULL;
+}
+
+// NDTR counts down the rest of the lap round the ring
+static inline uint32_t uartRxBufferHead(const uartPort_t *s)
+{
+ if (s->rxDma) {
+#if defined(AT32F43x)
+ const uint32_t left = s->rxDma->ref->dtcnt;
+#else
+ const uint32_t left = s->rxDma->ref->NDTR;
+#endif
+ return (s->port.rxBufferSize - left) % s->port.rxBufferSize;
+ }
+ return s->port.rxBufferHead;
+}
+#else
+static inline bool uartRxDmaStart(uartPort_t *s) { (void)s; return false; }
+static inline bool uartRxDmaRunning(const uartPort_t *s) { (void)s; return false; }
+static inline uint32_t uartRxBufferHead(const uartPort_t *s) { return s->port.rxBufferHead; }
+#endif
+
+#ifdef USE_UART_TX_DMA
+// False leaves the port on the byte interrupt: no stream named or free, or no TX
+bool uartTxDmaStart(uartPort_t *s);
+// Stops the stream, dropping any transfer on its way: for a port about to be reset
+void uartTxDmaStop(uartPort_t *s);
+// Sends what is queued, unless a transfer is already on its way: its end starts the next
void uartStartTxDMA(uartPort_t *s);
+static inline bool uartTxDmaRunning(const uartPort_t *s)
+{
+ return s->txDma != NULL;
+}
+
+#if defined(STM32F7) || defined(STM32H7)
+// The stream reads RAM, and a buffer a feature swaps in (MSP DisplayPort, gimbal) can still have its bytes in the D-cache
+static inline void uartTxDmaCleanCache(const volatile uint8_t *data, uint32_t count)
+{
+ const uint32_t start = (uint32_t)data & ~31U; // this CMSIS does not align to the 32 byte line
+ SCB_CleanDCache_by_Addr((uint32_t *)start, (uint32_t)data + count - start);
+}
+#endif
+#else
+static inline bool uartTxDmaStart(uartPort_t *s) { (void)s; return false; }
+static inline void uartTxDmaStop(uartPort_t *s) { (void)s; }
+static inline void uartStartTxDMA(uartPort_t *s) { (void)s; }
+static inline bool uartTxDmaRunning(const uartPort_t *s) { (void)s; return false; }
+#endif
+
+// A stream keeps writing where it was started, so it is started again on the new ring
+static inline void uartSetRxBuffer(serialPort_t *instance, volatile uint8_t *buffer, uint32_t size)
+{
+ uartPort_t *s = (uartPort_t *)instance;
+
+ ATOMIC_BLOCK(NVIC_PRIO_MAX) {
+ s->port.rxBuffer = buffer;
+ s->port.rxBufferSize = size;
+ s->port.rxBufferHead = 0;
+ s->port.rxBufferTail = 0;
+ }
+ if (uartRxDmaRunning(s)) {
+ uartRxDmaStart(s);
+ }
+}
+
uartPort_t *serialUART1(uint32_t baudRate, portMode_t mode, portOptions_t options);
uartPort_t *serialUART2(uint32_t baudRate, portMode_t mode, portOptions_t options);
uartPort_t *serialUART3(uint32_t baudRate, portMode_t mode, portOptions_t options);
diff --git a/src/main/drivers/serial_uart_stm32f4xx.c b/src/main/drivers/serial_uart_stm32f4xx.c
index 0f54fb69142..484714dcd22 100644
--- a/src/main/drivers/serial_uart_stm32f4xx.c
+++ b/src/main/drivers/serial_uart_stm32f4xx.c
@@ -213,6 +213,232 @@ static uartDevice_t* uartHardwareMap[] = {
#endif
};
+#if defined(USE_UART_RX_DMA) || defined(USE_UART_TX_DMA)
+static UARTDevice_e uartDeviceOf(const uartPort_t *s)
+{
+ for (int device = 0; device < UARTDEV_MAX; device++) {
+ if (uartHardwareMap[device] && &uartHardwareMap[device]->port == s) {
+ return device;
+ }
+ }
+ return UARTDEV_MAX;
+}
+
+#endif
+
+#ifdef USE_UART_RX_DMA
+static const dmaTag_t uartRxDmaTag[UARTDEV_MAX] = {
+#ifdef UART1_RX_DMA
+ [UARTDEV_1] = UART1_RX_DMA,
+#endif
+#ifdef UART2_RX_DMA
+ [UARTDEV_2] = UART2_RX_DMA,
+#endif
+#ifdef UART3_RX_DMA
+ [UARTDEV_3] = UART3_RX_DMA,
+#endif
+#ifdef UART4_RX_DMA
+ [UARTDEV_4] = UART4_RX_DMA,
+#endif
+#ifdef UART5_RX_DMA
+ [UARTDEV_5] = UART5_RX_DMA,
+#endif
+#ifdef UART6_RX_DMA
+ [UARTDEV_6] = UART6_RX_DMA,
+#endif
+#ifdef UART7_RX_DMA
+ [UARTDEV_7] = UART7_RX_DMA,
+#endif
+#ifdef UART8_RX_DMA
+ [UARTDEV_8] = UART8_RX_DMA,
+#endif
+};
+
+static void uartRxDmaStop(uartPort_t *s)
+{
+ if (s->rxDma) {
+ USART_DMACmd(s->USARTx, USART_DMAReq_Rx, DISABLE);
+ DMA_Cmd(s->rxDma->ref, DISABLE);
+ while (DMA_GetCmdStatus(s->rxDma->ref) != DISABLE);
+ s->rxDma = NULL;
+ }
+}
+
+bool uartRxDmaStart(uartPort_t *s)
+{
+ uartRxDmaStop(s);
+
+ const UARTDevice_e device = uartDeviceOf(s);
+ if (device == UARTDEV_MAX || uartRxDmaTag[device] == DMA_NONE || !(s->port.mode & MODE_RX) || s->port.rxCallback) {
+ return false;
+ }
+
+ const DMA_t dma = dmaGetByTag(uartRxDmaTag[device]);
+ if (!dma || !uartDmaStreamAvailable(dma, device)) {
+ return false;
+ }
+
+ dmaInit(dma, OWNER_SERIAL, RESOURCE_INDEX(device));
+ DMA_DeInit(dma->ref);
+
+ DMA_InitTypeDef init;
+ DMA_StructInit(&init);
+ init.DMA_Channel = DMATAG_GET_CHANNEL(uartRxDmaTag[device]) << 25; // DMA_Channel_n is n in CHSEL
+ init.DMA_PeripheralBaseAddr = (uint32_t)&s->USARTx->DR;
+ init.DMA_Memory0BaseAddr = (uint32_t)s->port.rxBuffer;
+ init.DMA_DIR = DMA_DIR_PeripheralToMemory;
+ init.DMA_BufferSize = s->port.rxBufferSize;
+ init.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
+ init.DMA_MemoryInc = DMA_MemoryInc_Enable;
+ init.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
+ init.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
+ init.DMA_Mode = DMA_Mode_Circular;
+ init.DMA_Priority = DMA_Priority_Medium;
+ // No FIFO: a byte is in the ring as soon as the count says so
+ init.DMA_FIFOMode = DMA_FIFOMode_Disable;
+ DMA_Init(dma->ref, &init);
+ DMA_Cmd(dma->ref, ENABLE);
+
+ s->port.rxBufferHead = s->port.rxBufferTail = 0;
+ s->rxDma = dma;
+
+ USART_ITConfig(s->USARTx, USART_IT_RXNE, DISABLE);
+ USART_DMACmd(s->USARTx, USART_DMAReq_Rx, ENABLE);
+ return true;
+}
+#endif
+
+#ifdef USE_UART_TX_DMA
+static const dmaTag_t uartTxDmaTag[UARTDEV_MAX] = {
+#ifdef UART1_TX_DMA
+ [UARTDEV_1] = UART1_TX_DMA,
+#endif
+#ifdef UART2_TX_DMA
+ [UARTDEV_2] = UART2_TX_DMA,
+#endif
+#ifdef UART3_TX_DMA
+ [UARTDEV_3] = UART3_TX_DMA,
+#endif
+#ifdef UART4_TX_DMA
+ [UARTDEV_4] = UART4_TX_DMA,
+#endif
+#ifdef UART5_TX_DMA
+ [UARTDEV_5] = UART5_TX_DMA,
+#endif
+#ifdef UART6_TX_DMA
+ [UARTDEV_6] = UART6_TX_DMA,
+#endif
+#ifdef UART7_TX_DMA
+ [UARTDEV_7] = UART7_TX_DMA,
+#endif
+#ifdef UART8_TX_DMA
+ [UARTDEV_8] = UART8_TX_DMA,
+#endif
+};
+
+#define UART_TX_DMA_FLAGS (DMA_IT_TCIF | DMA_IT_HTIF | DMA_IT_TEIF | DMA_IT_DMEIF | DMA_IT_FEIF)
+#define UART_TX_DMA_ENDED (DMA_IT_TCIF | DMA_IT_TEIF)
+
+void uartTxDmaStop(uartPort_t *s)
+{
+ if (s->txDma) {
+ USART_DMACmd(s->USARTx, USART_DMAReq_Tx, DISABLE);
+ DMA_ITConfig(s->txDma->ref, DMA_IT_TC | DMA_IT_TE, DISABLE);
+ DMA_Cmd(s->txDma->ref, DISABLE);
+ while (DMA_GetCmdStatus(s->txDma->ref) != DISABLE);
+ DMA_CLEAR_FLAG(s->txDma, UART_TX_DMA_FLAGS);
+ s->txDma = NULL;
+ s->txDmaCount = 0;
+ }
+}
+
+void uartStartTxDMA(uartPort_t *s)
+{
+ // Masked: the end-of-transfer interrupt also starts the next one
+ ATOMIC_BLOCK(NVIC_PRIO_SERIALUART) {
+ const uint32_t head = s->port.txBufferHead;
+ const uint32_t tail = s->port.txBufferTail;
+
+ if (s->txDma && !s->txDmaCount && head != tail) {
+ // Up to the head, or to the end of the ring if the queue wraps: the rest follows
+ const uint32_t count = (head > tail) ? head - tail : s->port.txBufferSize - tail;
+
+ s->txDmaCount = count;
+ s->txDma->ref->M0AR = (uint32_t)&s->port.txBuffer[tail];
+ DMA_SetCurrDataCounter(s->txDma->ref, count);
+ DMA_Cmd(s->txDma->ref, ENABLE);
+ }
+ }
+}
+
+// An error ends the transfer too, or the port would wait for it forever
+static void uartTxDmaHandler(DMA_t dma)
+{
+ uartPort_t *s = (uartPort_t *)dma->userParam;
+
+ // Clear every flag: one left set re-raises the interrupt, starving USB at this priority
+ const bool ended = DMA_GET_FLAG_STATUS(dma, UART_TX_DMA_ENDED);
+ DMA_CLEAR_FLAG(dma, UART_TX_DMA_FLAGS);
+
+ if (ended) {
+ // After an error the rest goes again, unless nothing went: that error would only repeat
+ const uint32_t sent = s->txDmaCount - DMA_GetCurrDataCounter(dma->ref);
+ s->port.txBufferTail = (s->port.txBufferTail + (sent ? sent : s->txDmaCount)) % s->port.txBufferSize;
+ s->txDmaCount = 0;
+ uartStartTxDMA(s);
+ }
+}
+
+bool uartTxDmaStart(uartPort_t *s)
+{
+ uartTxDmaStop(s);
+
+ const UARTDevice_e device = uartDeviceOf(s);
+ if (device == UARTDEV_MAX || uartTxDmaTag[device] == DMA_NONE || !(s->port.mode & MODE_TX)) {
+ return false;
+ }
+
+ const DMA_t dma = dmaGetByTag(uartTxDmaTag[device]);
+ if (!dma || !uartDmaStreamAvailable(dma, device)) {
+ return false;
+ }
+#ifdef USE_UART_RX_DMA
+ // The stream this port receives on is this UART's too, so it looks free to it
+ if (dma == s->rxDma) {
+ return false;
+ }
+#endif
+
+ dmaInit(dma, OWNER_SERIAL, RESOURCE_INDEX(device));
+ dmaSetHandler(dma, uartTxDmaHandler, NVIC_PRIO_SERIALUART, (uint32_t)s);
+ DMA_DeInit(dma->ref);
+
+ DMA_InitTypeDef init;
+ DMA_StructInit(&init);
+ init.DMA_Channel = DMATAG_GET_CHANNEL(uartTxDmaTag[device]) << 25; // DMA_Channel_n is n in CHSEL
+ init.DMA_PeripheralBaseAddr = (uint32_t)&s->USARTx->DR;
+ init.DMA_Memory0BaseAddr = (uint32_t)s->port.txBuffer;
+ init.DMA_DIR = DMA_DIR_MemoryToPeripheral;
+ init.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
+ init.DMA_MemoryInc = DMA_MemoryInc_Enable;
+ init.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
+ init.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
+ init.DMA_Mode = DMA_Mode_Normal;
+ init.DMA_Priority = DMA_Priority_Medium;
+ init.DMA_FIFOMode = DMA_FIFOMode_Disable;
+ DMA_Init(dma->ref, &init);
+ DMA_CLEAR_FLAG(dma, UART_TX_DMA_FLAGS);
+ DMA_ITConfig(dma->ref, DMA_IT_TC | DMA_IT_TE, ENABLE);
+
+ s->txDmaCount = 0;
+ s->txDma = dma;
+
+ USART_ITConfig(s->USARTx, USART_IT_TXE, DISABLE);
+ USART_DMACmd(s->USARTx, USART_DMAReq_Tx, ENABLE);
+ return true;
+}
+#endif
+
void uartIrqHandler(uartPort_t *s)
{
if (USART_GetITStatus(s->USARTx, USART_IT_RXNE) == SET) {
diff --git a/src/main/drivers/serial_uart_stm32f7xx.c b/src/main/drivers/serial_uart_stm32f7xx.c
index 944634c31ea..b19e57af7c5 100644
--- a/src/main/drivers/serial_uart_stm32f7xx.c
+++ b/src/main/drivers/serial_uart_stm32f7xx.c
@@ -251,11 +251,302 @@ static uartDevice_t* uartHardwareMap[] = {
#endif
};
+#if defined(USE_UART_RX_DMA) || defined(USE_UART_TX_DMA)
+static UARTDevice_e uartDeviceOf(const uartPort_t *s)
+{
+ for (int device = 0; device < UARTDEV_MAX; device++) {
+ if (uartHardwareMap[device] && &uartHardwareMap[device]->port == s) {
+ return device;
+ }
+ }
+ return UARTDEV_MAX;
+}
+
+#endif
+
+#ifdef USE_UART_RX_DMA
+static const dmaTag_t uartRxDmaTag[UARTDEV_MAX] = {
+#ifdef UART1_RX_DMA
+ [UARTDEV_1] = UART1_RX_DMA,
+#endif
+#ifdef UART2_RX_DMA
+ [UARTDEV_2] = UART2_RX_DMA,
+#endif
+#ifdef UART3_RX_DMA
+ [UARTDEV_3] = UART3_RX_DMA,
+#endif
+#ifdef UART4_RX_DMA
+ [UARTDEV_4] = UART4_RX_DMA,
+#endif
+#ifdef UART5_RX_DMA
+ [UARTDEV_5] = UART5_RX_DMA,
+#endif
+#ifdef UART6_RX_DMA
+ [UARTDEV_6] = UART6_RX_DMA,
+#endif
+#ifdef UART7_RX_DMA
+ [UARTDEV_7] = UART7_RX_DMA,
+#endif
+#ifdef UART8_RX_DMA
+ [UARTDEV_8] = UART8_RX_DMA,
+#endif
+};
+
+// Only with the D-cache on, which leaves DMA_RAM uncached: from the cache the CPU would not see what the stream wrote
+#ifdef DMA_RAM_UNCACHED
+#ifdef UART1_RX_DMA
+static DMA_RAM uint8_t uart1RxDmaBuffer[UART_RX_BUFFER_SIZE];
+#endif
+#ifdef UART2_RX_DMA
+static DMA_RAM uint8_t uart2RxDmaBuffer[UART_RX_BUFFER_SIZE];
+#endif
+#ifdef UART3_RX_DMA
+static DMA_RAM uint8_t uart3RxDmaBuffer[UART_RX_BUFFER_SIZE];
+#endif
+#ifdef UART4_RX_DMA
+static DMA_RAM uint8_t uart4RxDmaBuffer[UART_RX_BUFFER_SIZE];
+#endif
+#ifdef UART5_RX_DMA
+static DMA_RAM uint8_t uart5RxDmaBuffer[UART_RX_BUFFER_SIZE];
+#endif
+#ifdef UART6_RX_DMA
+static DMA_RAM uint8_t uart6RxDmaBuffer[UART_RX_BUFFER_SIZE];
+#endif
+#ifdef UART7_RX_DMA
+static DMA_RAM uint8_t uart7RxDmaBuffer[UART_RX_BUFFER_SIZE];
+#endif
+#ifdef UART8_RX_DMA
+static DMA_RAM uint8_t uart8RxDmaBuffer[UART_RX_BUFFER_SIZE];
+#endif
+
+static volatile uint8_t * const uartRxDmaBuffer[UARTDEV_MAX] = {
+#ifdef UART1_RX_DMA
+ [UARTDEV_1] = uart1RxDmaBuffer,
+#endif
+#ifdef UART2_RX_DMA
+ [UARTDEV_2] = uart2RxDmaBuffer,
+#endif
+#ifdef UART3_RX_DMA
+ [UARTDEV_3] = uart3RxDmaBuffer,
+#endif
+#ifdef UART4_RX_DMA
+ [UARTDEV_4] = uart4RxDmaBuffer,
+#endif
+#ifdef UART5_RX_DMA
+ [UARTDEV_5] = uart5RxDmaBuffer,
+#endif
+#ifdef UART6_RX_DMA
+ [UARTDEV_6] = uart6RxDmaBuffer,
+#endif
+#ifdef UART7_RX_DMA
+ [UARTDEV_7] = uart7RxDmaBuffer,
+#endif
+#ifdef UART8_RX_DMA
+ [UARTDEV_8] = uart8RxDmaBuffer,
+#endif
+};
+#endif
+
+static void uartRxDmaStop(uartPort_t *s)
+{
+ if (s->rxDma) {
+ const uint32_t stream = DMATAG_GET_STREAM(s->rxDma->tag); // LL_DMA_STREAM_n is n
+ CLEAR_BIT(s->USARTx->CR3, USART_CR3_DMAR);
+ LL_DMA_DisableStream(s->rxDma->dma, stream);
+ while (LL_DMA_IsEnabledStream(s->rxDma->dma, stream));
+ s->rxDma = NULL;
+ }
+}
+
+// USART_CR3_DMAR is set again in uartReconfigure(): the HAL clears CR3 whenever it reprograms the port
+bool uartRxDmaStart(uartPort_t *s)
+{
+ uartRxDmaStop(s);
+
+ const UARTDevice_e device = uartDeviceOf(s);
+ if (device == UARTDEV_MAX || uartRxDmaTag[device] == DMA_NONE || !(s->port.mode & MODE_RX) || s->port.rxCallback) {
+ return false;
+ }
+
+ const DMA_t dma = dmaGetByTag(uartRxDmaTag[device]);
+ if (!dma || !uartDmaStreamAvailable(dma, device)) {
+ return false;
+ }
+
+ dmaInit(dma, OWNER_SERIAL, RESOURCE_INDEX(device));
+
+ const uint32_t stream = DMATAG_GET_STREAM(uartRxDmaTag[device]);
+ LL_DMA_DeInit(dma->dma, stream);
+
+ LL_DMA_InitTypeDef init;
+ LL_DMA_StructInit(&init);
+ init.Channel = DMATAG_GET_CHANNEL(uartRxDmaTag[device]) << DMA_SxCR_CHSEL_Pos; // LL_DMA_CHANNEL_n is n in CHSEL
+ init.PeriphOrM2MSrcAddress = (uint32_t)&s->USARTx->RDR;
+ init.MemoryOrM2MDstAddress = (uint32_t)s->port.rxBuffer;
+ init.Direction = LL_DMA_DIRECTION_PERIPH_TO_MEMORY;
+ init.Mode = LL_DMA_MODE_CIRCULAR;
+ init.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT;
+ init.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT;
+ init.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_BYTE;
+ init.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_BYTE;
+ init.NbData = s->port.rxBufferSize;
+ init.Priority = LL_DMA_PRIORITY_MEDIUM;
+ // No FIFO: a byte is in the ring as soon as the count says so
+ init.FIFOMode = LL_DMA_FIFOMODE_DISABLE;
+ LL_DMA_Init(dma->dma, stream, &init);
+ LL_DMA_EnableStream(dma->dma, stream);
+ // Restarted on a new ring (serialSetRxBuffer) the port gets no reprogramming to set it
+ SET_BIT(s->USARTx->CR3, USART_CR3_DMAR);
+
+ s->port.rxBufferHead = s->port.rxBufferTail = 0;
+ s->rxDma = dma;
+ return true;
+}
+#endif
+
+#ifdef USE_UART_TX_DMA
+static const dmaTag_t uartTxDmaTag[UARTDEV_MAX] = {
+#ifdef UART1_TX_DMA
+ [UARTDEV_1] = UART1_TX_DMA,
+#endif
+#ifdef UART2_TX_DMA
+ [UARTDEV_2] = UART2_TX_DMA,
+#endif
+#ifdef UART3_TX_DMA
+ [UARTDEV_3] = UART3_TX_DMA,
+#endif
+#ifdef UART4_TX_DMA
+ [UARTDEV_4] = UART4_TX_DMA,
+#endif
+#ifdef UART5_TX_DMA
+ [UARTDEV_5] = UART5_TX_DMA,
+#endif
+#ifdef UART6_TX_DMA
+ [UARTDEV_6] = UART6_TX_DMA,
+#endif
+#ifdef UART7_TX_DMA
+ [UARTDEV_7] = UART7_TX_DMA,
+#endif
+#ifdef UART8_TX_DMA
+ [UARTDEV_8] = UART8_TX_DMA,
+#endif
+};
+
+#define UART_TX_DMA_FLAGS (DMA_IT_TCIF | DMA_IT_HTIF | DMA_IT_TEIF | DMA_IT_DMEIF | DMA_IT_FEIF)
+#define UART_TX_DMA_ENDED (DMA_IT_TCIF | DMA_IT_TEIF)
+
+void uartTxDmaStop(uartPort_t *s)
+{
+ if (s->txDma) {
+ const uint32_t stream = DMATAG_GET_STREAM(s->txDma->tag); // LL_DMA_STREAM_n is n
+ CLEAR_BIT(s->USARTx->CR3, USART_CR3_DMAT);
+ LL_DMA_DisableIT_TC(s->txDma->dma, stream);
+ LL_DMA_DisableIT_TE(s->txDma->dma, stream);
+ LL_DMA_DisableStream(s->txDma->dma, stream);
+ while (LL_DMA_IsEnabledStream(s->txDma->dma, stream));
+ DMA_CLEAR_FLAG(s->txDma, UART_TX_DMA_FLAGS);
+ s->txDma = NULL;
+ s->txDmaCount = 0;
+ }
+}
+
+void uartStartTxDMA(uartPort_t *s)
+{
+ // Masked: the end-of-transfer interrupt also starts the next one
+ ATOMIC_BLOCK(NVIC_PRIO_SERIALUART) {
+ const uint32_t head = s->port.txBufferHead;
+ const uint32_t tail = s->port.txBufferTail;
+
+ if (s->txDma && !s->txDmaCount && head != tail) {
+ // Up to the head, or to the end of the ring if the queue wraps: the rest follows
+ const uint32_t count = (head > tail) ? head - tail : s->port.txBufferSize - tail;
+ const uint32_t stream = DMATAG_GET_STREAM(s->txDma->tag);
+
+ s->txDmaCount = count;
+ LL_DMA_SetMemoryAddress(s->txDma->dma, stream, (uint32_t)&s->port.txBuffer[tail]);
+ LL_DMA_SetDataLength(s->txDma->dma, stream, count);
+ // Also orders the writes to the ring before the stream starts
+ uartTxDmaCleanCache(&s->port.txBuffer[tail], count);
+ LL_DMA_EnableStream(s->txDma->dma, stream);
+ }
+ }
+}
+
+// An error ends the transfer too, or the port would wait for it forever
+static void uartTxDmaHandler(DMA_t dma)
+{
+ uartPort_t *s = (uartPort_t *)dma->userParam;
+
+ // Clear every flag: one left set re-raises the interrupt, starving USB at this priority
+ const bool ended = DMA_GET_FLAG_STATUS(dma, UART_TX_DMA_ENDED);
+ DMA_CLEAR_FLAG(dma, UART_TX_DMA_FLAGS);
+
+ if (ended) {
+ // After an error the rest goes again, unless nothing went: that error would only repeat
+ const uint32_t sent = s->txDmaCount - LL_DMA_GetDataLength(dma->dma, DMATAG_GET_STREAM(dma->tag));
+ s->port.txBufferTail = (s->port.txBufferTail + (sent ? sent : s->txDmaCount)) % s->port.txBufferSize;
+ s->txDmaCount = 0;
+ uartStartTxDMA(s);
+ }
+}
+
+// USART_CR3_DMAT is set in uartReconfigure(), as DMAR is
+bool uartTxDmaStart(uartPort_t *s)
+{
+ uartTxDmaStop(s);
+
+ const UARTDevice_e device = uartDeviceOf(s);
+ if (device == UARTDEV_MAX || uartTxDmaTag[device] == DMA_NONE || !(s->port.mode & MODE_TX)) {
+ return false;
+ }
+
+ const DMA_t dma = dmaGetByTag(uartTxDmaTag[device]);
+ if (!dma || !uartDmaStreamAvailable(dma, device)) {
+ return false;
+ }
+#ifdef USE_UART_RX_DMA
+ // The stream this port receives on is this UART's too, so it looks free to it
+ if (dma == s->rxDma) {
+ return false;
+ }
+#endif
+
+ dmaInit(dma, OWNER_SERIAL, RESOURCE_INDEX(device));
+ dmaSetHandler(dma, uartTxDmaHandler, NVIC_PRIO_SERIALUART, (uint32_t)s);
+
+ const uint32_t stream = DMATAG_GET_STREAM(uartTxDmaTag[device]);
+ LL_DMA_DeInit(dma->dma, stream);
+
+ LL_DMA_InitTypeDef init;
+ LL_DMA_StructInit(&init);
+ init.Channel = DMATAG_GET_CHANNEL(uartTxDmaTag[device]) << DMA_SxCR_CHSEL_Pos; // LL_DMA_CHANNEL_n is n in CHSEL
+ init.PeriphOrM2MSrcAddress = (uint32_t)&s->USARTx->TDR;
+ init.MemoryOrM2MDstAddress = (uint32_t)s->port.txBuffer;
+ init.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH;
+ init.Mode = LL_DMA_MODE_NORMAL;
+ init.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT;
+ init.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT;
+ init.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_BYTE;
+ init.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_BYTE;
+ init.Priority = LL_DMA_PRIORITY_MEDIUM;
+ init.FIFOMode = LL_DMA_FIFOMODE_DISABLE;
+ LL_DMA_Init(dma->dma, stream, &init);
+ DMA_CLEAR_FLAG(dma, UART_TX_DMA_FLAGS);
+ LL_DMA_EnableIT_TC(dma->dma, stream);
+ LL_DMA_EnableIT_TE(dma->dma, stream);
+
+ s->txDmaCount = 0;
+ s->txDma = dma;
+ return true;
+}
+#endif
+
void uartIrqHandler(uartPort_t *s)
{
UART_HandleTypeDef *huart = &s->Handle;
/* UART in mode Receiver ---------------------------------------------------*/
- if ((__HAL_UART_GET_IT(huart, UART_IT_RXNE) != RESET)) {
+ // With RX on DMA the interrupt still fires for TX: an RX byte is the stream's to take
+ if ((__HAL_UART_GET_IT(huart, UART_IT_RXNE) != RESET) && !uartRxDmaRunning(s)) {
uint8_t rbyte = (uint8_t)(huart->Instance->RDR & (uint8_t) 0xff);
if (s->port.rxCallback) {
@@ -293,7 +584,8 @@ void uartIrqHandler(uartPort_t *s)
}
/* UART in mode Transmitter ------------------------------------------------*/
- if (__HAL_UART_GET_IT(huart, UART_IT_TXE) != RESET) {
+ // With TX on DMA the ring is the stream's to empty
+ if ((__HAL_UART_GET_IT(huart, UART_IT_TXE) != RESET) && !uartTxDmaRunning(s)) {
/* Check that a Tx process is ongoing */
if (huart->gState != HAL_UART_STATE_BUSY_TX) {
if (s->port.txBufferTail == s->port.txBufferHead) {
@@ -351,6 +643,11 @@ uartPort_t *serialUART(UARTDevice_e device, uint32_t baudRate, portMode_t mode,
s->port.baudRate = baudRate;
s->port.rxBuffer = uart->rxBuffer;
+#if defined(USE_UART_RX_DMA) && defined(DMA_RAM_UNCACHED)
+ if (uartRxDmaBuffer[device]) {
+ s->port.rxBuffer = uartRxDmaBuffer[device];
+ }
+#endif
s->port.txBuffer = uart->txBuffer;
s->port.rxBufferSize = sizeof(uart->rxBuffer);
s->port.txBufferSize = sizeof(uart->txBuffer);
diff --git a/src/main/drivers/serial_uart_stm32h7xx.c b/src/main/drivers/serial_uart_stm32h7xx.c
index 233aa7057a1..bb916621c62 100644
--- a/src/main/drivers/serial_uart_stm32h7xx.c
+++ b/src/main/drivers/serial_uart_stm32h7xx.c
@@ -297,11 +297,312 @@ void uartGetPortPins(UARTDevice_e device, serialPortPins_t * pins)
}
}
+#if defined(USE_UART_RX_DMA) || defined(USE_UART_TX_DMA)
+static UARTDevice_e uartDeviceOf(const uartPort_t *s)
+{
+ for (int device = 0; device < UARTDEV_MAX; device++) {
+ if (uartHardwareMap[device] && &uartHardwareMap[device]->port == s) {
+ return device;
+ }
+ }
+ return UARTDEV_MAX;
+}
+
+#endif
+
+#ifdef USE_UART_RX_DMA
+// DMA rings go in D2 SRAM, which the MPU marks shareable and so uncached: in AXI SRAM the CPU
+// would read the cache rather than what the stream wrote
+#ifdef UART1_RX_DMA
+static DMA_RAM uint8_t uart1RxDmaBuffer[UART_RX_BUFFER_SIZE];
+#endif
+#ifdef UART2_RX_DMA
+static DMA_RAM uint8_t uart2RxDmaBuffer[UART_RX_BUFFER_SIZE];
+#endif
+#ifdef UART3_RX_DMA
+static DMA_RAM uint8_t uart3RxDmaBuffer[UART_RX_BUFFER_SIZE];
+#endif
+#ifdef UART4_RX_DMA
+static DMA_RAM uint8_t uart4RxDmaBuffer[UART_RX_BUFFER_SIZE];
+#endif
+#ifdef UART5_RX_DMA
+static DMA_RAM uint8_t uart5RxDmaBuffer[UART_RX_BUFFER_SIZE];
+#endif
+#ifdef UART6_RX_DMA
+static DMA_RAM uint8_t uart6RxDmaBuffer[UART_RX_BUFFER_SIZE];
+#endif
+#ifdef UART7_RX_DMA
+static DMA_RAM uint8_t uart7RxDmaBuffer[UART_RX_BUFFER_SIZE];
+#endif
+#ifdef UART8_RX_DMA
+static DMA_RAM uint8_t uart8RxDmaBuffer[UART_RX_BUFFER_SIZE];
+#endif
+
+typedef struct {
+ dmaTag_t tag;
+ uint32_t request; // this UART's receiver on the DMAMUX
+ volatile uint8_t *buffer;
+} uartRxDmaConfig_t;
+
+static const uartRxDmaConfig_t uartRxDmaConfig[UARTDEV_MAX] = {
+#ifdef UART1_RX_DMA
+ [UARTDEV_1] = { UART1_RX_DMA, LL_DMAMUX1_REQ_USART1_RX, uart1RxDmaBuffer },
+#endif
+#ifdef UART2_RX_DMA
+ [UARTDEV_2] = { UART2_RX_DMA, LL_DMAMUX1_REQ_USART2_RX, uart2RxDmaBuffer },
+#endif
+#ifdef UART3_RX_DMA
+ [UARTDEV_3] = { UART3_RX_DMA, LL_DMAMUX1_REQ_USART3_RX, uart3RxDmaBuffer },
+#endif
+#ifdef UART4_RX_DMA
+ [UARTDEV_4] = { UART4_RX_DMA, LL_DMAMUX1_REQ_UART4_RX, uart4RxDmaBuffer },
+#endif
+#ifdef UART5_RX_DMA
+ [UARTDEV_5] = { UART5_RX_DMA, LL_DMAMUX1_REQ_UART5_RX, uart5RxDmaBuffer },
+#endif
+#ifdef UART6_RX_DMA
+ [UARTDEV_6] = { UART6_RX_DMA, LL_DMAMUX1_REQ_USART6_RX, uart6RxDmaBuffer },
+#endif
+#ifdef UART7_RX_DMA
+ [UARTDEV_7] = { UART7_RX_DMA, LL_DMAMUX1_REQ_UART7_RX, uart7RxDmaBuffer },
+#endif
+#ifdef UART8_RX_DMA
+ [UARTDEV_8] = { UART8_RX_DMA, LL_DMAMUX1_REQ_UART8_RX, uart8RxDmaBuffer },
+#endif
+};
+
+static void uartRxDmaStop(uartPort_t *s)
+{
+ if (s->rxDma) {
+ const uint32_t stream = DMATAG_GET_STREAM(s->rxDma->tag); // LL_DMA_STREAM_n is n
+ CLEAR_BIT(s->USARTx->CR3, USART_CR3_DMAR);
+ LL_DMA_DisableStream(s->rxDma->dma, stream);
+ while (LL_DMA_IsEnabledStream(s->rxDma->dma, stream));
+ s->rxDma = NULL;
+ }
+}
+
+// USART_CR3_DMAR is set again in uartReconfigure(): the HAL clears CR3 whenever it reprograms the port
+bool uartRxDmaStart(uartPort_t *s)
+{
+ uartRxDmaStop(s);
+
+ const UARTDevice_e device = uartDeviceOf(s);
+ if (device == UARTDEV_MAX || uartRxDmaConfig[device].tag == DMA_NONE || !(s->port.mode & MODE_RX) || s->port.rxCallback) {
+ return false;
+ }
+
+ const DMA_t dma = dmaGetByTag(uartRxDmaConfig[device].tag);
+ if (!dma || !uartDmaStreamAvailable(dma, device)) {
+ return false;
+ }
+
+ dmaInit(dma, OWNER_SERIAL, RESOURCE_INDEX(device));
+
+ const uint32_t stream = DMATAG_GET_STREAM(uartRxDmaConfig[device].tag);
+ LL_DMA_DeInit(dma->dma, stream);
+
+ LL_DMA_InitTypeDef init;
+ LL_DMA_StructInit(&init);
+ init.PeriphRequest = uartRxDmaConfig[device].request;
+ init.PeriphOrM2MSrcAddress = (uint32_t)&s->USARTx->RDR;
+ init.MemoryOrM2MDstAddress = (uint32_t)s->port.rxBuffer;
+ init.Direction = LL_DMA_DIRECTION_PERIPH_TO_MEMORY;
+ init.Mode = LL_DMA_MODE_CIRCULAR;
+ init.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT;
+ init.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT;
+ init.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_BYTE;
+ init.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_BYTE;
+ init.NbData = s->port.rxBufferSize;
+ init.Priority = LL_DMA_PRIORITY_MEDIUM;
+ // No FIFO: a byte is in the ring as soon as the count says so
+ init.FIFOMode = LL_DMA_FIFOMODE_DISABLE;
+ LL_DMA_Init(dma->dma, stream, &init);
+ LL_DMA_EnableStream(dma->dma, stream);
+ // Restarted on a new ring (serialSetRxBuffer) the port gets no reprogramming to set it
+ SET_BIT(s->USARTx->CR3, USART_CR3_DMAR);
+
+ s->port.rxBufferHead = s->port.rxBufferTail = 0;
+ s->rxDma = dma;
+ return true;
+}
+#endif
+
+#ifdef USE_UART_TX_DMA
+// TX rings go in uncached D2 SRAM too, so the stream reads what the CPU wrote
+#ifdef UART1_TX_DMA
+static DMA_RAM uint8_t uart1TxDmaBuffer[UART_TX_BUFFER_SIZE];
+#endif
+#ifdef UART2_TX_DMA
+static DMA_RAM uint8_t uart2TxDmaBuffer[UART_TX_BUFFER_SIZE];
+#endif
+#ifdef UART3_TX_DMA
+static DMA_RAM uint8_t uart3TxDmaBuffer[UART_TX_BUFFER_SIZE];
+#endif
+#ifdef UART4_TX_DMA
+static DMA_RAM uint8_t uart4TxDmaBuffer[UART_TX_BUFFER_SIZE];
+#endif
+#ifdef UART5_TX_DMA
+static DMA_RAM uint8_t uart5TxDmaBuffer[UART_TX_BUFFER_SIZE];
+#endif
+#ifdef UART6_TX_DMA
+static DMA_RAM uint8_t uart6TxDmaBuffer[UART_TX_BUFFER_SIZE];
+#endif
+#ifdef UART7_TX_DMA
+static DMA_RAM uint8_t uart7TxDmaBuffer[UART_TX_BUFFER_SIZE];
+#endif
+#ifdef UART8_TX_DMA
+static DMA_RAM uint8_t uart8TxDmaBuffer[UART_TX_BUFFER_SIZE];
+#endif
+
+typedef struct {
+ dmaTag_t tag;
+ uint32_t request; // this UART's transmitter on the DMAMUX
+ volatile uint8_t *buffer;
+} uartTxDmaConfig_t;
+
+static const uartTxDmaConfig_t uartTxDmaConfig[UARTDEV_MAX] = {
+#ifdef UART1_TX_DMA
+ [UARTDEV_1] = { UART1_TX_DMA, LL_DMAMUX1_REQ_USART1_TX, uart1TxDmaBuffer },
+#endif
+#ifdef UART2_TX_DMA
+ [UARTDEV_2] = { UART2_TX_DMA, LL_DMAMUX1_REQ_USART2_TX, uart2TxDmaBuffer },
+#endif
+#ifdef UART3_TX_DMA
+ [UARTDEV_3] = { UART3_TX_DMA, LL_DMAMUX1_REQ_USART3_TX, uart3TxDmaBuffer },
+#endif
+#ifdef UART4_TX_DMA
+ [UARTDEV_4] = { UART4_TX_DMA, LL_DMAMUX1_REQ_UART4_TX, uart4TxDmaBuffer },
+#endif
+#ifdef UART5_TX_DMA
+ [UARTDEV_5] = { UART5_TX_DMA, LL_DMAMUX1_REQ_UART5_TX, uart5TxDmaBuffer },
+#endif
+#ifdef UART6_TX_DMA
+ [UARTDEV_6] = { UART6_TX_DMA, LL_DMAMUX1_REQ_USART6_TX, uart6TxDmaBuffer },
+#endif
+#ifdef UART7_TX_DMA
+ [UARTDEV_7] = { UART7_TX_DMA, LL_DMAMUX1_REQ_UART7_TX, uart7TxDmaBuffer },
+#endif
+#ifdef UART8_TX_DMA
+ [UARTDEV_8] = { UART8_TX_DMA, LL_DMAMUX1_REQ_UART8_TX, uart8TxDmaBuffer },
+#endif
+};
+
+#define UART_TX_DMA_FLAGS (DMA_IT_TCIF | DMA_IT_HTIF | DMA_IT_TEIF | DMA_IT_DMEIF | DMA_IT_FEIF)
+#define UART_TX_DMA_ENDED (DMA_IT_TCIF | DMA_IT_TEIF)
+
+void uartTxDmaStop(uartPort_t *s)
+{
+ if (s->txDma) {
+ const uint32_t stream = DMATAG_GET_STREAM(s->txDma->tag); // LL_DMA_STREAM_n is n
+ CLEAR_BIT(s->USARTx->CR3, USART_CR3_DMAT);
+ LL_DMA_DisableIT_TC(s->txDma->dma, stream);
+ LL_DMA_DisableIT_TE(s->txDma->dma, stream);
+ LL_DMA_DisableStream(s->txDma->dma, stream);
+ while (LL_DMA_IsEnabledStream(s->txDma->dma, stream));
+ DMA_CLEAR_FLAG(s->txDma, UART_TX_DMA_FLAGS);
+ s->txDma = NULL;
+ s->txDmaCount = 0;
+ }
+}
+
+void uartStartTxDMA(uartPort_t *s)
+{
+ // Masked: the end-of-transfer interrupt also starts the next one
+ ATOMIC_BLOCK(NVIC_PRIO_SERIALUART) {
+ const uint32_t head = s->port.txBufferHead;
+ const uint32_t tail = s->port.txBufferTail;
+
+ if (s->txDma && !s->txDmaCount && head != tail) {
+ // Up to the head, or to the end of the ring if the queue wraps: the rest follows
+ const uint32_t count = (head > tail) ? head - tail : s->port.txBufferSize - tail;
+ const uint32_t stream = DMATAG_GET_STREAM(s->txDma->tag);
+
+ s->txDmaCount = count;
+ LL_DMA_SetMemoryAddress(s->txDma->dma, stream, (uint32_t)&s->port.txBuffer[tail]);
+ LL_DMA_SetDataLength(s->txDma->dma, stream, count);
+ // Also orders the writes to the ring before the stream starts
+ uartTxDmaCleanCache(&s->port.txBuffer[tail], count);
+ LL_DMA_EnableStream(s->txDma->dma, stream);
+ }
+ }
+}
+
+// An error ends the transfer too, or the port would wait for it forever
+static void uartTxDmaHandler(DMA_t dma)
+{
+ uartPort_t *s = (uartPort_t *)dma->userParam;
+
+ // Clear every flag: one left set re-raises the interrupt, starving USB at this priority
+ const bool ended = DMA_GET_FLAG_STATUS(dma, UART_TX_DMA_ENDED);
+ DMA_CLEAR_FLAG(dma, UART_TX_DMA_FLAGS);
+
+ if (ended) {
+ // After an error the rest goes again, unless nothing went: that error would only repeat
+ const uint32_t sent = s->txDmaCount - LL_DMA_GetDataLength(dma->dma, DMATAG_GET_STREAM(dma->tag));
+ s->port.txBufferTail = (s->port.txBufferTail + (sent ? sent : s->txDmaCount)) % s->port.txBufferSize;
+ s->txDmaCount = 0;
+ uartStartTxDMA(s);
+ }
+}
+
+// USART_CR3_DMAT is set in uartReconfigure(), as DMAR is
+bool uartTxDmaStart(uartPort_t *s)
+{
+ uartTxDmaStop(s);
+
+ const UARTDevice_e device = uartDeviceOf(s);
+ if (device == UARTDEV_MAX || uartTxDmaConfig[device].tag == DMA_NONE || !(s->port.mode & MODE_TX)) {
+ return false;
+ }
+
+ const DMA_t dma = dmaGetByTag(uartTxDmaConfig[device].tag);
+ if (!dma || !uartDmaStreamAvailable(dma, device)) {
+ return false;
+ }
+#ifdef USE_UART_RX_DMA
+ // The stream this port receives on is this UART's too, so it looks free to it
+ if (dma == s->rxDma) {
+ return false;
+ }
+#endif
+
+ dmaInit(dma, OWNER_SERIAL, RESOURCE_INDEX(device));
+ dmaSetHandler(dma, uartTxDmaHandler, NVIC_PRIO_SERIALUART, (uint32_t)s);
+
+ const uint32_t stream = DMATAG_GET_STREAM(uartTxDmaConfig[device].tag);
+ LL_DMA_DeInit(dma->dma, stream);
+
+ LL_DMA_InitTypeDef init;
+ LL_DMA_StructInit(&init);
+ init.PeriphRequest = uartTxDmaConfig[device].request;
+ init.PeriphOrM2MSrcAddress = (uint32_t)&s->USARTx->TDR;
+ init.MemoryOrM2MDstAddress = (uint32_t)s->port.txBuffer;
+ init.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH;
+ init.Mode = LL_DMA_MODE_NORMAL;
+ init.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT;
+ init.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT;
+ init.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_BYTE;
+ init.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_BYTE;
+ init.Priority = LL_DMA_PRIORITY_MEDIUM;
+ init.FIFOMode = LL_DMA_FIFOMODE_DISABLE;
+ LL_DMA_Init(dma->dma, stream, &init);
+ DMA_CLEAR_FLAG(dma, UART_TX_DMA_FLAGS);
+ LL_DMA_EnableIT_TC(dma->dma, stream);
+ LL_DMA_EnableIT_TE(dma->dma, stream);
+
+ s->txDmaCount = 0;
+ s->txDma = dma;
+ return true;
+}
+#endif
+
void uartIrqHandler(uartPort_t *s)
{
UART_HandleTypeDef *huart = &s->Handle;
/* UART in mode Receiver ---------------------------------------------------*/
- if ((__HAL_UART_GET_IT(huart, UART_IT_RXNE) != RESET)) {
+ // With RX on DMA the interrupt still fires for TX: an RX byte is the stream's to take
+ if ((__HAL_UART_GET_IT(huart, UART_IT_RXNE) != RESET) && !uartRxDmaRunning(s)) {
uint8_t rbyte = (uint8_t)(huart->Instance->RDR & (uint8_t) 0xff);
if (s->port.rxCallback) {
@@ -339,7 +640,8 @@ void uartIrqHandler(uartPort_t *s)
}
/* UART in mode Transmitter ------------------------------------------------*/
- if (__HAL_UART_GET_IT(huart, UART_IT_TXE) != RESET) {
+ // With TX on DMA the data register is the stream's to feed
+ if ((__HAL_UART_GET_IT(huart, UART_IT_TXE) != RESET) && !uartTxDmaRunning(s)) {
/* Check that a Tx process is ongoing */
if (huart->gState != HAL_UART_STATE_BUSY_TX) {
if (s->port.txBufferTail == s->port.txBufferHead) {
@@ -383,7 +685,17 @@ uartPort_t *serialUART(UARTDevice_e device, uint32_t baudRate, portMode_t mode,
s->port.baudRate = baudRate;
s->port.rxBuffer = uart->rxBuffer;
+#ifdef USE_UART_RX_DMA
+ if (uartRxDmaConfig[device].buffer) {
+ s->port.rxBuffer = uartRxDmaConfig[device].buffer;
+ }
+#endif
s->port.txBuffer = uart->txBuffer;
+#ifdef USE_UART_TX_DMA
+ if (uartTxDmaConfig[device].buffer) {
+ s->port.txBuffer = uartTxDmaConfig[device].buffer;
+ }
+#endif
s->port.rxBufferSize = sizeof(uart->rxBuffer);
s->port.txBufferSize = sizeof(uart->txBuffer);
diff --git a/src/main/io/gps.c b/src/main/io/gps.c
index 89657541eed..7cdeab04ef0 100755
--- a/src/main/io/gps.c
+++ b/src/main/io/gps.c
@@ -91,6 +91,22 @@ gpsStatistics_t gpsStats;
gpsSolutionData_t gpsSolDRV; //filled by driver
gpsSolutionData_t gpsSol; //used in the rest of the code
+#ifdef USE_GPS_PROTO_UBLOX
+// Covers 230400; at 460800 and above a NAV-PVT plus a large NAV-SIG can still overflow it
+#define GPS_RX_BUFFER_SIZE 512
+STATIC_ASSERT((GPS_RX_BUFFER_SIZE & (GPS_RX_BUFFER_SIZE - 1)) == 0, gps_rx_buffer_size_not_power_of_2);
+// A ring a stream can reach and the D-cache does not hide: not DTCM or cached RAM on F7 and H7, not CCM on F4
+// (RAM1 on AT32F43x is unknown)
+#if defined(USE_UART_RX_DMA) && (defined(STM32F7) || defined(STM32H7))
+#define GPS_RX_BUFFER_RAM DMA_RAM
+#elif defined(USE_UART_RX_DMA) && (defined(STM32F4) || defined(AT32F43x))
+#define GPS_RX_BUFFER_RAM
+#else
+#define GPS_RX_BUFFER_RAM FASTRAM
+#endif
+static GPS_RX_BUFFER_RAM volatile uint8_t gpsRxBuffer[GPS_RX_BUFFER_SIZE];
+#endif
+
// Map gpsBaudRate_e index to baudRate_e
baudRate_e gpsToSerialBaudRate[GPS_BAUDRATE_COUNT] = { BAUD_115200, BAUD_57600, BAUD_38400, BAUD_19200, BAUD_9600, BAUD_230400, BAUD_460800, BAUD_921600 };
@@ -526,6 +542,10 @@ void gpsInit(void)
return;
}
+#ifdef USE_GPS_PROTO_UBLOX
+ serialSetRxBuffer(gpsState.gpsPort, gpsRxBuffer, sizeof(gpsRxBuffer));
+#endif
+
gpsSetState(GPS_INITIALIZING);
}
diff --git a/src/main/io/gps_ublox.c b/src/main/io/gps_ublox.c
index 703242d10dd..193ccf3e998 100755
--- a/src/main/io/gps_ublox.c
+++ b/src/main/io/gps_ublox.c
@@ -955,6 +955,12 @@ static uint16_t hz2rate(uint8_t hz)
return 1000 / hz;
}
+// Satellite details only feed gpssats and the OSD's GPS extra stats: about 2 Hz, in epochs
+static uint8_t ubloxSatInfoRate(void)
+{
+ return MAX(1, (gpsState.gpsConfig->ubloxNavHz + 1) / 2);
+}
+
STATIC_PROTOTHREAD(gpsConfigure)
{
ptBegin(gpsConfigure);
@@ -1073,7 +1079,7 @@ STATIC_PROTOTHREAD(gpsConfigure)
{UBLOX_CFG_MSGOUT_NAV_VELNED_UART1, 0}, // 2
{UBLOX_CFG_MSGOUT_NAV_TIMEUTC_UART1, 0}, // 3
{UBLOX_CFG_MSGOUT_NAV_PVT_UART1, 1}, // 4
- {UBLOX_CFG_MSGOUT_NAV_SIG_UART1, 1}, // 5
+ {UBLOX_CFG_MSGOUT_NAV_SIG_UART1, ubloxSatInfoRate()}, // 5
{UBLOX_CFG_MSGOUT_NAV_SAT_UART1, 0} // 6
};
@@ -1101,7 +1107,7 @@ STATIC_PROTOTHREAD(gpsConfigure)
configureMSG(MSG_CLASS_UBX, MSG_PVT, 1);
ptWait(_ack_state == UBX_ACK_GOT_ACK || _ack_state == UBX_ACK_GOT_NAK);
- configureMSG(MSG_CLASS_UBX, MSG_NAV_SAT, 1);
+ configureMSG(MSG_CLASS_UBX, MSG_NAV_SAT, ubloxSatInfoRate());
ptWait(_ack_state == UBX_ACK_GOT_ACK || _ack_state == UBX_ACK_GOT_NAK);
} else { // Really old stuff, consider upgrading :), ols setting API, no PVT or NAV_SAT or NAV_SIG
// TODO: remove in INAV 9.0.0
diff --git a/src/main/target/TBS_LUCID_H7_WING/target.h b/src/main/target/TBS_LUCID_H7_WING/target.h
index 93d3888770e..ef84c003c43 100644
--- a/src/main/target/TBS_LUCID_H7_WING/target.h
+++ b/src/main/target/TBS_LUCID_H7_WING/target.h
@@ -46,6 +46,9 @@
#define USE_UART2
#define UART2_TX_PIN PD5
#define UART2_RX_PIN PD6
+// Outputs are mapped to DMA1 0-7 and DMA2 1-4, the ADC uses DMA2 0
+#define UART2_RX_DMA DMA_TAG(2, 5, 0)
+#define UART2_TX_DMA DMA_TAG(2, 7, 0)
#define USE_UART3
#define UART3_TX_PIN PD8
diff --git a/src/main/target/common_post.h b/src/main/target/common_post.h
index 183b44a3908..c7a60e767d5 100644
--- a/src/main/target/common_post.h
+++ b/src/main/target/common_post.h
@@ -185,6 +185,19 @@ extern uint8_t __config_end;
#endif
#endif
+// Only where the target names a stream (UARTx_RX_DMA): which one is free depends on the
+// board, as the timers, the ADC and the SD card share them
+#if defined(UART1_RX_DMA) || defined(UART2_RX_DMA) || defined(UART3_RX_DMA) || defined(UART4_RX_DMA) || \
+ defined(UART5_RX_DMA) || defined(UART6_RX_DMA) || defined(UART7_RX_DMA) || defined(UART8_RX_DMA)
+#define USE_UART_RX_DMA
+#endif
+
+// The same for sending (UARTx_TX_DMA): one interrupt per transfer instead of one per byte
+#if defined(UART1_TX_DMA) || defined(UART2_TX_DMA) || defined(UART3_TX_DMA) || defined(UART4_TX_DMA) || \
+ defined(UART5_TX_DMA) || defined(UART6_TX_DMA) || defined(UART7_TX_DMA) || defined(UART8_TX_DMA)
+#define USE_UART_TX_DMA
+#endif
+
#ifdef USE_ESC_SENSOR
#define USE_RPM_FILTER
#endif
diff --git a/src/test/unit/gps_heartbeat_unittest.cc b/src/test/unit/gps_heartbeat_unittest.cc
index 75505ec1ff0..e79dc19a9ae 100644
--- a/src/test/unit/gps_heartbeat_unittest.cc
+++ b/src/test/unit/gps_heartbeat_unittest.cc
@@ -72,6 +72,7 @@ void serialWriteBuf(serialPort_t *instance, const uint8_t *data, int count) { UN
void serialPrint(serialPort_t *instance, const char *str) { UNUSED(instance); UNUSED(str); }
void serialSetMode(serialPort_t *instance, portMode_t mode) { UNUSED(instance); UNUSED(mode); }
void serialSetBaudRate(serialPort_t *instance, uint32_t baudRate) { UNUSED(instance); UNUSED(baudRate); }
+void serialSetRxBuffer(serialPort_t *instance, volatile uint8_t *buffer, uint32_t size) { UNUSED(instance); UNUSED(buffer); UNUSED(size); }
serialPort_t *openSerialPort(serialPortIdentifier_e identifier, serialPortFunction_e function,
serialReceiveCallbackPtr rxCallback, void *rxCallbackData,
diff --git a/src/test/unit/gps_null_port_unittest.cc b/src/test/unit/gps_null_port_unittest.cc
index 288b68995c9..cd41bb2e6ff 100644
--- a/src/test/unit/gps_null_port_unittest.cc
+++ b/src/test/unit/gps_null_port_unittest.cc
@@ -189,6 +189,13 @@ void serialSetBaudRate(serialPort_t *instance, uint32_t baudRate)
UNUSED(baudRate);
}
+void serialSetRxBuffer(serialPort_t *instance, volatile uint8_t *buffer, uint32_t size)
+{
+ UNUSED(instance);
+ UNUSED(buffer);
+ UNUSED(size);
+}
+
serialPort_t *openSerialPort(serialPortIdentifier_e identifier,
serialPortFunction_e function,
serialReceiveCallbackPtr rxCallback,
diff --git a/src/test/unit/uart_dma_stream_unittest.cc b/src/test/unit/uart_dma_stream_unittest.cc
new file mode 100644
index 00000000000..67e5a755f2b
--- /dev/null
+++ b/src/test/unit/uart_dma_stream_unittest.cc
@@ -0,0 +1,251 @@
+/*
+ * This file is part of INAV.
+ *
+ * INAV 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.
+ *
+ * INAV 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 INAV. If not, see .
+ */
+
+/*
+ * Which DMA streams a UART may take: pwmIsDmaStreamReserved() (pwm_mapping.c) and uartDmaStreamAvailable()
+ * (serial_uart_impl.h). pwm_mapping.c is not built for the host, so, as in pwm_mapping_beeper_unittest.cc, the
+ * logic is mirrored on plain data and a SourceSync test checks that the live function still reads the same.
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "gtest/gtest.h"
+#include "unittest_macros.h"
+
+namespace {
+
+enum { TIM_USE_MOTOR = 1 << 2, TIM_USE_SERVO = 1 << 3, TIM_USE_LED = 1 << 24 };
+enum { OUTPUT_MODE_AUTO = 0, OUTPUT_MODE_LED = 3 };
+enum owner_e { OWNER_FREE, OWNER_TIMER, OWNER_SERIAL };
+
+struct pad_t {
+ int timer; // index into the per-timer output modes
+ int stream; // the DMA stream the pad is mapped to, -1 for none
+ uint32_t usageFlags;
+};
+
+struct board_t {
+ const pad_t *pads;
+ int padCount;
+ uint8_t outputMode[4];
+ bool dshot;
+ bool ledStrip;
+ bool motorsInitialised;
+};
+
+// Mirror of pwmIsDmaStreamReserved()
+bool reserved(const board_t &b, int stream)
+{
+ const bool motorsWillClaim = !b.motorsInitialised && b.dshot;
+
+ for (int i = 0; i < b.padCount; i++) {
+ const pad_t *timHw = &b.pads[i];
+ if (timHw->stream != stream) {
+ continue;
+ }
+ if (motorsWillClaim) {
+ return true;
+ }
+ if (b.ledStrip && ((timHw->usageFlags & TIM_USE_LED) || b.outputMode[timHw->timer] == OUTPUT_MODE_LED)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// Mirror of uartDmaStreamAvailable()
+bool available(const board_t &b, int stream, owner_e owner, int ownerIndex, int device)
+{
+ if (reserved(b, stream)) {
+ return false;
+ }
+ return owner == OWNER_FREE || (owner == OWNER_SERIAL && ownerIndex == device);
+}
+
+// Four motor-capable outputs on streams 0-3, a LED pad on stream 4 (timer 2), stream 6 unmapped
+const pad_t pads[] = {
+ { 0, 0, TIM_USE_MOTOR | TIM_USE_SERVO },
+ { 0, 1, TIM_USE_MOTOR | TIM_USE_SERVO },
+ { 1, 2, TIM_USE_MOTOR | TIM_USE_SERVO },
+ { 1, 3, TIM_USE_MOTOR | TIM_USE_SERVO },
+ { 2, 4, TIM_USE_LED },
+};
+
+board_t board(bool dshot, bool ledStrip, bool motorsInitialised)
+{
+ return board_t{ pads, (int)(sizeof(pads) / sizeof(pads[0])), { OUTPUT_MODE_AUTO, OUTPUT_MODE_AUTO, OUTPUT_MODE_AUTO, OUTPUT_MODE_AUTO },
+ dshot, ledStrip, motorsInitialised };
+}
+
+} // namespace
+
+TEST(UartDmaStream, DshotMotorsNotStartedReserveEveryOutputStream)
+{
+ const board_t b = board(true, false, false);
+ for (int s = 0; s <= 4; s++) {
+ EXPECT_TRUE(reserved(b, s)) << "stream " << s;
+ }
+ EXPECT_FALSE(reserved(b, 6));
+}
+
+TEST(UartDmaStream, StartedMotorsLeaveTheRestToOwnership)
+{
+ const board_t b = board(true, false, true);
+ for (int s = 0; s <= 4; s++) {
+ EXPECT_FALSE(reserved(b, s)) << "stream " << s;
+ }
+ // The started motors own their streams; an unused output's stream is free
+ EXPECT_FALSE(available(b, 0, OWNER_TIMER, 0, 5));
+ EXPECT_TRUE(available(b, 3, OWNER_FREE, 0, 5));
+}
+
+TEST(UartDmaStream, MotorsOffDshotNeverReserveMotorStreams)
+{
+ const board_t b = board(false, false, false);
+ for (int s = 0; s <= 4; s++) {
+ EXPECT_FALSE(reserved(b, s)) << "stream " << s;
+ }
+}
+
+TEST(UartDmaStream, LedStripKeepsItsStreamOnlyWhenEnabled)
+{
+ EXPECT_TRUE(reserved(board(false, true, false), 4));
+ EXPECT_TRUE(reserved(board(true, true, true), 4));
+ EXPECT_FALSE(reserved(board(false, false, false), 4));
+ EXPECT_FALSE(reserved(board(true, false, true), 4));
+}
+
+TEST(UartDmaStream, LedOutputModeReservesTheTimersPads)
+{
+ board_t b = board(false, true, true);
+ b.outputMode[1] = OUTPUT_MODE_LED;
+ EXPECT_TRUE(reserved(b, 2));
+ EXPECT_TRUE(reserved(b, 3));
+ EXPECT_FALSE(reserved(b, 0));
+}
+
+TEST(UartDmaStream, OwnershipStillDecidesFreeStreams)
+{
+ const board_t b = board(false, false, true);
+ EXPECT_TRUE(available(b, 6, OWNER_FREE, 0, 5));
+ EXPECT_TRUE(available(b, 6, OWNER_SERIAL, 5, 5));
+ EXPECT_FALSE(available(b, 6, OWNER_SERIAL, 2, 5));
+ EXPECT_FALSE(available(b, 6, OWNER_TIMER, 0, 5));
+}
+
+/*
+ * SourceSync: the live pwmIsDmaStreamReserved() must read as below, whitespace and line comments aside, so the
+ * mirror above keeps testing the real logic.
+ */
+namespace {
+
+std::string normalize(const std::string &text)
+{
+ std::string noComments;
+ for (size_t i = 0; i < text.size();) {
+ if (text[i] == '/' && i + 1 < text.size() && text[i + 1] == '/') {
+ while (i < text.size() && text[i] != '\n') {
+ i++;
+ }
+ continue;
+ }
+ noComments += text[i++];
+ }
+ std::string result;
+ bool lastWasSpace = true;
+ for (char c : noComments) {
+ if (std::isspace(static_cast(c))) {
+ if (!lastWasSpace) {
+ result += ' ';
+ lastWasSpace = true;
+ }
+ } else {
+ result += c;
+ lastWasSpace = false;
+ }
+ }
+ while (!result.empty() && result.back() == ' ') {
+ result.pop_back();
+ }
+ return result;
+}
+
+const char *expectedFunction = R"(
+bool pwmIsDmaStreamReserved(DMA_t dma)
+{
+ const bool motorsWillClaim = !motorsInitialised && getMotorProtocolProperties(motorConfig()->motorPwmProtocol)->isDSHOT;
+
+ for (int i = 0; i < timerHardwareCount; i++) {
+ const timerHardware_t *timHw = &timerHardware[i];
+ if (dmaGetByTag(timHw->dmaTag) != dma) {
+ continue;
+ }
+ if (motorsWillClaim) {
+ return true;
+ }
+ if (feature(FEATURE_LED_STRIP) && (TIM_IS_LED(timHw->usageFlags) || timerOverrides(timer2id(timHw->tim))->outputMode == OUTPUT_MODE_LED)) {
+ return true;
+ }
+ }
+ return false;
+}
+)";
+
+const char *expectedAvailable = R"(
+static inline bool uartDmaStreamAvailable(DMA_t dma, UARTDevice_e device)
+{
+ if (pwmIsDmaStreamReserved(dma)) {
+ return false;
+ }
+ return dmaGetOwner(dma) == OWNER_FREE || (dmaGetOwner(dma) == OWNER_SERIAL && dma->resourceIndex == RESOURCE_INDEX(device));
+}
+)";
+
+::testing::AssertionResult liveSourceContains(const char *relativePath, const char *snippet)
+{
+ std::string thisFile = __FILE__;
+ const size_t slash = thisFile.find_last_of("/\\");
+ const std::string path = (slash == std::string::npos ? std::string(".") : thisFile.substr(0, slash))
+ + "/../../main/drivers/" + relativePath;
+ std::ifstream file(path);
+ if (!file.is_open()) {
+ return ::testing::AssertionFailure() << "cannot open " << path;
+ }
+ std::ostringstream buffer;
+ buffer << file.rdbuf();
+ if (normalize(buffer.str()).find(normalize(snippet)) == std::string::npos) {
+ return ::testing::AssertionFailure() << relativePath << " changed: update the mirror in this test";
+ }
+ return ::testing::AssertionSuccess();
+}
+
+} // namespace
+
+TEST(UartDmaStreamSourceSync, ReservedMatchesMirror)
+{
+ EXPECT_TRUE(liveSourceContains("pwm_mapping.c", expectedFunction));
+}
+
+TEST(UartDmaStreamSourceSync, AvailableMatchesMirror)
+{
+ EXPECT_TRUE(liveSourceContains("serial_uart_impl.h", expectedAvailable));
+}