/****************************************************************************** * File: MCU_UART_prg.c * Component: MCU_UART * Description: Program (implementation) file for the MCU_UART driver. * Wraps the Pico SDK's hardware_uart and hardware_dma libraries * to provide blocking and non-blocking send APIs for the RP2040 * PL011 UART peripheral(s). * * Non-blocking TX supports two mechanisms (per-instance config): * - DMA: hardware DMA channel transfers bytes autonomously * - ISR: UART TX interrupt feeds one byte per interrupt * Both invoke the configured TX-complete callback when done. * * Layer: MCU (hardware abstraction) *****************************************************************************/ #include "MCU_UART.h" #include "MCU_UART_priv.h" #include "MCU_UART_cfg.h" /* Pico SDK headers */ #include "hardware/uart.h" #include "hardware/gpio.h" #include "hardware/dma.h" #include "hardware/irq.h" /* ------------------------------------------------------------------------ */ /* INSTANCE LOOKUP TABLE */ /* ------------------------------------------------------------------------ */ /** * @brief Maps a config array index to the corresponding Pico SDK uart * instance pointer (0 = uart0, 1 = uart1). */ static uart_inst_t * const apstrInstances[] = { uart0, /* index 0 = RP2040 uart0 peripheral */ uart1, /* index 1 = RP2040 uart1 peripheral */ }; /* ------------------------------------------------------------------------ */ /* RUNTIME STATE */ /* ------------------------------------------------------------------------ */ /** * @brief Single static control structure holding all per-instance TX state. * * NOTE: abTxBusy is written from interrupt context (DMA/UART ISR) and * read from main context (MCU_UART_bIsTxBusy, MCU_UART_enuSendBuffer). * On the single-core RP2040 with interrupts disabled during read-modify- * write sequences, this is safe. If dual-core access is ever needed, * add volatile qualifiers or use a spin lock. */ static MCU_UART_tstrControl strControl; /* ------------------------------------------------------------------------ */ /* INTERNAL HELPERS */ /* ------------------------------------------------------------------------ */ /** * @brief Call the TX-complete callback for a given instance if configured. * Safe to invoke from ISR context. * * @param u8Instance Index into MCU_UART_astrConfig[]. */ static void vCallTxCallback(u8 u8Instance) { STD_tpfCallbackFunc pfCallbackLoc = MCU_UART_astrConfig[u8Instance].pfTxCompleteCallback; if (pfCallbackLoc != STD_NULL) { pfCallbackLoc(); } } /* ------------------------------------------------------------------------ */ /* DMA IRQ HANDLER (INSTANCE 0) */ /* ------------------------------------------------------------------------ */ /** * @brief DMA completion interrupt handler for UART instance 0. * * Fires when the DMA channel finishes transferring the TX buffer. * Flow: clear IRQ flag -> set abTxBusy to STD_FALSE -> call callback. */ static void vDmaIrqHandler0(void) { s8 s8ChannelLoc = strControl.as8DmaChannel[MCU_UART_INSTANCE_0]; u32 u32StatusLoc; u32StatusLoc = dma_channel_get_irq0_status((u32)s8ChannelLoc); if (u32StatusLoc != 0U) { /* Step 1: clear the DMA interrupt flag */ dma_irqn_acknowledge_channel(0, (u32)s8ChannelLoc); /* Step 2: mark instance as idle (written here in ISR, read in main) */ strControl.abTxBusy[MCU_UART_INSTANCE_0] = STD_FALSE; /* Step 3: notify the application */ vCallTxCallback((u8)MCU_UART_INSTANCE_0); } } /* Note: when UART1 DMA is added, define vDmaIrqHandler1 here and * use DMA_IRQ_1 to avoid sharing a single IRQ. */ /* ------------------------------------------------------------------------ */ /* UART TX ISR HANDLER */ /* ------------------------------------------------------------------------ */ /** * @brief UART TX interrupt handler logic — fills the FIFO on each invocation. * * The PL011 TX interrupt fires when the FIFO level drops at or below the * programmable threshold. This handler writes as many bytes as the FIFO * can accept before returning, minimizing the number of interrupts needed * for a given transfer (one interrupt per FIFO drain cycle, not per byte). * * When all bytes are sent, disables the TX interrupt, marks the instance * as idle, and invokes the TX-complete callback. * * @param u8Instance Index of the UART instance that fired the interrupt. */ static void vTxIsrHandler(u8 u8Instance) { uart_inst_t *pstrUartLoc = apstrInstances[u8Instance]; STD_tBool bFifoReady = STD_FALSE; STD_tBool bDataLeft = STD_FALSE; /* Fill the FIFO with as many bytes as it can accept */ bFifoReady = (uart_is_writable(pstrUartLoc) != 0) ? STD_TRUE : STD_FALSE; bDataLeft = (strControl.au16TxIndex[u8Instance] < strControl.au16TxLength[u8Instance]) ? STD_TRUE : STD_FALSE; while ((bFifoReady == STD_TRUE) && (bDataLeft == STD_TRUE)) { uart_putc_raw(pstrUartLoc, strControl.apu8TxBuffer[u8Instance][strControl.au16TxIndex[u8Instance]]); strControl.au16TxIndex[u8Instance]++; bFifoReady = (uart_is_writable(pstrUartLoc) != 0) ? STD_TRUE : STD_FALSE; bDataLeft = (strControl.au16TxIndex[u8Instance] < strControl.au16TxLength[u8Instance]) ? STD_TRUE : STD_FALSE; } /* If all bytes have been sent, shut down the interrupt */ if (bDataLeft == STD_FALSE) { uart_set_irqs_enabled(pstrUartLoc, false, false); strControl.abTxBusy[u8Instance] = STD_FALSE; vCallTxCallback(u8Instance); } } /** * @brief UART0 TX interrupt entry point. * Dispatches to the common handler with instance 0. */ static void vUart0IrqHandler(void) { vTxIsrHandler((u8)MCU_UART_INSTANCE_0); } /* Note: when UART1 ISR mode is added, define vUart1IrqHandler here. */ /* ========================================================================= */ /* INIT */ /* ========================================================================= */ STD_tenuResult MCU_UART_enuInit(void) { STD_tenuResult enuResultLoc = STD_OK; u8 u8IndexLoc; /* Zero-initialize all control state */ for (u8IndexLoc = 0U; u8IndexLoc < (u8)MCU_UART_NUM_INSTANCES; u8IndexLoc++) { strControl.apu8TxBuffer[u8IndexLoc] = STD_NULL; strControl.au16TxLength[u8IndexLoc] = 0U; strControl.au16TxIndex[u8IndexLoc] = 0U; strControl.abTxBusy[u8IndexLoc] = STD_FALSE; strControl.as8DmaChannel[u8IndexLoc] = -1; } /* Configure each UART instance */ for (u8IndexLoc = 0U; u8IndexLoc < (u8)MCU_UART_NUM_INSTANCES; u8IndexLoc++) { const MCU_UART_tstrConfig *pstrCfgLoc = &MCU_UART_astrConfig[u8IndexLoc]; uart_inst_t *pstrUartLoc = apstrInstances[u8IndexLoc]; /* Enable the UART peripheral and set the baud rate */ uart_init(pstrUartLoc, pstrCfgLoc->u32BaudRate); /* Assign TX and RX GPIO pins to the UART function */ gpio_set_function(pstrCfgLoc->u8TxPin, GPIO_FUNC_UART); gpio_set_function(pstrCfgLoc->u8RxPin, GPIO_FUNC_UART); /* Set data format: data bits, stop bits, parity */ uart_set_format(pstrUartLoc, pstrCfgLoc->enuDataBits, pstrCfgLoc->enuStopBits, pstrCfgLoc->enuParity); /* Set up the async TX mechanism based on config */ if (pstrCfgLoc->enuAsyncMode == MCU_UART_ASYNC_DMA) { /* Claim a free DMA channel. true = panic if none available. */ s8 s8ChannelLoc = (s8)dma_claim_unused_channel(true); strControl.as8DmaChannel[u8IndexLoc] = s8ChannelLoc; /* Enable DMA completion interrupt for this channel */ dma_channel_set_irq0_enabled((u32)s8ChannelLoc, true); /* Install the per-instance DMA IRQ handler */ if (u8IndexLoc == (u8)MCU_UART_INSTANCE_0) { irq_set_exclusive_handler(DMA_IRQ_0, vDmaIrqHandler0); irq_set_enabled(DMA_IRQ_0, true); } } else { /* ISR mode: install the per-instance UART TX interrupt handler. * The TX interrupt itself is NOT enabled here — it gets enabled * in SendBuffer and disabled by the ISR when transfer completes. */ if (u8IndexLoc == (u8)MCU_UART_INSTANCE_0) { irq_set_exclusive_handler(UART0_IRQ, vUart0IrqHandler); irq_set_enabled(UART0_IRQ, true); } } } return enuResultLoc; } /* ========================================================================= */ /* SEND BYTE (BLOCKING) */ /* ========================================================================= */ STD_tenuResult MCU_UART_enuSendByte(u8 u8Instance, u8 u8Byte) { STD_tenuResult enuResultLoc = STD_OK; /* Blocking single-byte send. uart_putc_raw waits until the TX FIFO * has space, then writes the byte. */ uart_putc_raw(apstrInstances[u8Instance], u8Byte); return enuResultLoc; } /* ========================================================================= */ /* SEND BUFFER (NON-BLOCKING, DEFAULT) */ /* ========================================================================= */ STD_tenuResult MCU_UART_enuSendBuffer(u8 u8Instance, const u8 *pu8Data, u16 u16Length) { STD_tenuResult enuResultLoc = STD_OK; STD_tBool bBusyLoc = strControl.abTxBusy[u8Instance]; MCU_UART_tenuAsyncMode enuModeLoc = MCU_UART_astrConfig[u8Instance].enuAsyncMode; if (pu8Data == STD_NULL) { enuResultLoc = STD_NULL_POINTER_ERROR; } else if (bBusyLoc == STD_TRUE) { /* A previous async transfer is still in progress */ enuResultLoc = STD_NOK; } else { /* Store buffer info in the control structure. * abTxBusy is set here (main context) and cleared by the ISR/DMA * handler (interrupt context) when the transfer completes. */ strControl.apu8TxBuffer[u8Instance] = pu8Data; strControl.au16TxLength[u8Instance] = u16Length; strControl.au16TxIndex[u8Instance] = 0U; strControl.abTxBusy[u8Instance] = STD_TRUE; if (enuModeLoc == MCU_UART_ASYNC_DMA) { /* --- DMA mode --- */ s8 s8ChannelLoc = strControl.as8DmaChannel[u8Instance]; uart_inst_t *pstrUartLoc = apstrInstances[u8Instance]; /* Configure DMA: memory -> UART TX FIFO, 1 byte at a time, * paced by UART TX DREQ so we never overflow the FIFO. */ dma_channel_config strDmaCfgLoc = dma_channel_get_default_config((u32)s8ChannelLoc); channel_config_set_transfer_data_size(&strDmaCfgLoc, DMA_SIZE_8); channel_config_set_read_increment(&strDmaCfgLoc, true); channel_config_set_write_increment(&strDmaCfgLoc, false); channel_config_set_dreq(&strDmaCfgLoc, uart_get_dreq(pstrUartLoc, true)); /* Start transfer. DMA IRQ fires on completion -> * vDmaIrqHandler0 clears abTxBusy -> calls callback. */ dma_channel_configure( (u32)s8ChannelLoc, &strDmaCfgLoc, &uart_get_hw(pstrUartLoc)->dr, /* dest: UART data register */ pu8Data, /* source: caller's buffer */ u16Length, /* transfer count */ true /* start immediately */ ); } else { /* --- ISR mode --- */ uart_inst_t *pstrUartLoc = apstrInstances[u8Instance]; STD_tBool bFifoReady = STD_FALSE; STD_tBool bDataLeft = STD_FALSE; /* Kick-start: fill the FIFO with as many bytes as it can * accept right now. This gets the first bytes transmitting * immediately without waiting for an interrupt context switch. * Identical logic to what the ISR does on subsequent fills. */ bFifoReady = (uart_is_writable(pstrUartLoc) != 0) ? STD_TRUE : STD_FALSE; bDataLeft = (strControl.au16TxIndex[u8Instance] < u16Length) ? STD_TRUE : STD_FALSE; while ((bFifoReady == STD_TRUE) && (bDataLeft == STD_TRUE)) { uart_putc_raw(pstrUartLoc, pu8Data[strControl.au16TxIndex[u8Instance]]); strControl.au16TxIndex[u8Instance]++; bFifoReady = (uart_is_writable(pstrUartLoc) != 0) ? STD_TRUE : STD_FALSE; bDataLeft = (strControl.au16TxIndex[u8Instance] < u16Length) ? STD_TRUE : STD_FALSE; } /* If all bytes fit into the FIFO in one go, the transfer is * already complete — no interrupt needed. */ if (bDataLeft == STD_FALSE) { strControl.abTxBusy[u8Instance] = STD_FALSE; vCallTxCallback(u8Instance); } else { /* Bytes remain — enable the TX interrupt. The ISR fills * the FIFO each time it drains below threshold until the * entire buffer is transmitted. */ uart_set_irqs_enabled(pstrUartLoc, true, false); } } } return enuResultLoc; } /* ========================================================================= */ /* SEND BUFFER (BLOCKING) */ /* ========================================================================= */ STD_tenuResult MCU_UART_enuSendBufferBlocking(u8 u8Instance, const u8 *pu8Data, u16 u16Length) { STD_tenuResult enuResultLoc = STD_OK; u16 u16IndexLoc; if (pu8Data == STD_NULL) { enuResultLoc = STD_NULL_POINTER_ERROR; } else { /* Send each byte blocking — waits for TX FIFO space per byte */ for (u16IndexLoc = 0U; u16IndexLoc < u16Length; u16IndexLoc++) { uart_putc_raw(apstrInstances[u8Instance], pu8Data[u16IndexLoc]); } /* Invoke callback synchronously (no ISR in blocking mode) */ vCallTxCallback(u8Instance); } return enuResultLoc; } /* ========================================================================= */ /* TX BUSY CHECK */ /* ========================================================================= */ STD_tBool MCU_UART_bIsTxBusy(u8 u8Instance) { return strControl.abTxBusy[u8Instance]; }