diff --git a/cmake/cmake_config/mcu_config.cmake b/cmake/cmake_config/mcu_config.cmake index a67d56e..b7a1dc9 100644 --- a/cmake/cmake_config/mcu_config.cmake +++ b/cmake/cmake_config/mcu_config.cmake @@ -57,9 +57,11 @@ function(mcu_link_target target) # Pick only the libraries we need: # - pico_stdlib: core runtime, GPIO, clocks, basic init # - hardware_uart: UART peripheral API (used by the MCU_UART driver) + # - hardware_dma: DMA controller API (used by MCU_UART non-blocking TX) target_link_libraries(${target} PRIVATE pico_stdlib hardware_uart + hardware_dma ) # Route stdio over USB-CDC: the Pico will appear as a virtual serial diff --git a/src/MCU_UART/cfg/MCU_UART_cfg.c b/src/MCU_UART/cfg/MCU_UART_cfg.c index 10dc6c6..e9f92e7 100644 --- a/src/MCU_UART/cfg/MCU_UART_cfg.c +++ b/src/MCU_UART/cfg/MCU_UART_cfg.c @@ -2,13 +2,45 @@ * File: MCU_UART_cfg.c * Component: MCU_UART * Description: Configuration implementation for the MCU_UART driver. - * Holds the actual configuration values (baud rate, pin numbers, - * UART instance selection, etc.) defined as constants or - * configuration structures consumed by MCU_UART_prg.c. + * Defines the MCU_UART_astrConfig[] array that holds the actual + * per-instance UART settings. Each entry is initialized using + * the named macros from MCU_UART_cfg.h so that no magic numbers + * appear in the initializer. * * Layer: MCU (hardware abstraction) - configuration *****************************************************************************/ +/* MCU_UART.h is included first because it defines the struct type + * (MCU_UART_tstrConfig) and the enum types (MCU_UART_tenuDataBits, etc.) + * that are used in the array definition below. */ +#include "MCU_UART.h" #include "MCU_UART_cfg.h" -/* Configuration definitions will go here */ \ No newline at end of file +/* ------------------------------------------------------------------------ */ +/* CONFIGURATION ARRAY DEFINITION */ +/* ------------------------------------------------------------------------ */ + +/** + * @brief Per-instance UART configuration, indexed by MCU_UART_tenuInstance. + * + * [MCU_UART_INSTANCE_0] = uart0 on the RP2040. Configured for standard + * 8-N-1 at 115200 baud on GP0 (TX) / GP1 (RX), with DMA-based async TX + * and no TX-complete callback. + * + * To add uart1: add a [MCU_UART_INSTANCE_1] entry here with the + * corresponding MCU_UART_1_* macros defined in MCU_UART_cfg.h. + */ +const MCU_UART_tstrConfig MCU_UART_astrConfig[MCU_UART_NUM_INSTANCES] = +{ + [MCU_UART_INSTANCE_0] = + { + .u8TxPin = MCU_UART_0_TX_PIN, + .u8RxPin = MCU_UART_0_RX_PIN, + .u32BaudRate = MCU_UART_0_BAUD_RATE, + .enuDataBits = MCU_UART_0_DATA_BITS, + .enuStopBits = MCU_UART_0_STOP_BITS, + .enuParity = MCU_UART_0_PARITY, + .enuAsyncMode = MCU_UART_0_ASYNC_MODE, + .pfTxCompleteCallback = MCU_UART_0_TX_COMPLETE_CALLBACK, + }, +}; \ No newline at end of file diff --git a/src/MCU_UART/cfg/MCU_UART_cfg.h b/src/MCU_UART/cfg/MCU_UART_cfg.h index 5c222be..23d6ef6 100644 --- a/src/MCU_UART/cfg/MCU_UART_cfg.h +++ b/src/MCU_UART/cfg/MCU_UART_cfg.h @@ -2,10 +2,15 @@ * File: MCU_UART_cfg.h * Component: MCU_UART * Description: Configuration header for the MCU_UART driver. - * Declares configuration structures and constants that can be - * edited to adapt the UART driver to the specific hardware - * setup (e.g., which UART instance, pin assignments, baud rate, - * data bits, stop bits, parity). + * Defines which UART instances are active, their GPIO pin + * assignments, baud rates, data format, async TX mechanism, + * and TX-complete callback. Each instance is one entry in + * MCU_UART_astrConfig[]; the array index equals the RP2040 + * UART instance number (0 = uart0, 1 = uart1). + * + * To add a second UART: add MCU_UART_INSTANCE_1 to the enum, + * define MCU_UART_1_* macros, and add a [MCU_UART_INSTANCE_1] + * initializer in MCU_UART_cfg.c. * * Layer: MCU (hardware abstraction) - configuration *****************************************************************************/ @@ -13,6 +18,59 @@ #ifndef MCU_UART_CFG_H #define MCU_UART_CFG_H -/* Configuration constants and structure declarations will go here */ +#include "STD_TYPES.h" + +/* ------------------------------------------------------------------------ */ +/* INSTANCE ENUMERATION */ +/* ------------------------------------------------------------------------ */ + +/** + * @brief Enumeration of configured UART instances. + * + * Each enumerator's value matches the RP2040 UART peripheral index + * (0 = uart0, 1 = uart1). MCU_UART_NUM_INSTANCES is auto-calculated + * as the count and also used to size the config array. + */ +typedef enum +{ + MCU_UART_INSTANCE_0 = 0U, + MCU_UART_NUM_INSTANCES +} MCU_UART_tenuInstance; + +/* ------------------------------------------------------------------------ */ +/* INSTANCE 0 CONFIGURATION */ +/* ------------------------------------------------------------------------ */ + +/** @brief GPIO pin for UART0 TX (transmit). GP0 is the default for uart0. */ +#define MCU_UART_0_TX_PIN 0U + +/** @brief GPIO pin for UART0 RX (receive). GP1 is the default for uart0. */ +#define MCU_UART_0_RX_PIN 1U + +/** @brief Baud rate for UART0 in bits per second. 115200 is the most common + * default for serial monitors and USB-to-UART adapters. */ +#define MCU_UART_0_BAUD_RATE 115200U + +/** @brief Data bits per frame for UART0. 8 bits is the standard for binary + * and ASCII data transfer (8-N-1 is the universal default). */ +#define MCU_UART_0_DATA_BITS MCU_UART_DATA_BITS_8 + +/** @brief Stop bits per frame for UART0. 1 stop bit is the standard default. + * Use 2 only for slower receivers that need extra settling time. */ +#define MCU_UART_0_STOP_BITS MCU_UART_STOP_BITS_1 + +/** @brief Parity mode for UART0. No parity is the standard default (8-N-1). + * Enable parity only when the receiving device requires it. */ +#define MCU_UART_0_PARITY MCU_UART_PARITY_NONE + +/** @brief Async TX mechanism for UART0. + * MCU_UART_ASYNC_DMA: zero-CPU DMA transfer (best for large buffers). + * MCU_UART_ASYNC_ISR: interrupt-driven byte-by-byte (no DMA channel used). */ +#define MCU_UART_0_ASYNC_MODE MCU_UART_ASYNC_DMA + +/** @brief TX-complete callback for UART0. + * Called from interrupt context when an async or blocking SendBuffer + * finishes. Set to STD_NULL to disable (no notification). */ +#define MCU_UART_0_TX_COMPLETE_CALLBACK STD_NULL #endif /* MCU_UART_CFG_H */ \ No newline at end of file diff --git a/src/MCU_UART/inc/MCU_UART.h b/src/MCU_UART/inc/MCU_UART.h index 2cbef32..ff0e1b9 100644 --- a/src/MCU_UART/inc/MCU_UART.h +++ b/src/MCU_UART/inc/MCU_UART.h @@ -2,26 +2,165 @@ * File: MCU_UART.h * Component: MCU_UART * Description: Public interface for the MCU UART driver component. - * This header exposes the functions and types that other - * components are allowed to use when interacting with the UART - * peripheral on the RP2040 microcontroller. + * This header exposes the functions, types, and configuration + * value enumerations that other components are allowed to use + * when interacting with the hardware UART peripheral(s) on the + * RP2040 microcontroller. + * + * Two send-buffer modes are provided: + * - SendBuffer (default, non-blocking): starts an async + * transfer via DMA or ISR and returns immediately. + * - SendBufferBlocking: loops byte-by-byte and returns + * only after all data is transmitted. + * Both invoke the TX-complete callback (if configured). + * + * All public functions take a u8 instance parameter so the + * caller selects which UART peripheral to operate on. * * Layer: MCU (hardware abstraction) *****************************************************************************/ #ifndef MCU_UART_H #define MCU_UART_H + +/* STD_TYPES provides fixed-width typedefs (u8, u16, u32), STD_tenuResult, + * STD_tBool, STD_tpfCallbackFunc, and STD_NULL. */ #include "STD_TYPES.h" - typedef enum { - MCU_UART_OK = 0, - MCU_UART_ERROR, - MCU_UART_TIMEOUT - } MCU_UART_Status_t; +/* ------------------------------------------------------------------------ */ +/* CONFIGURATION VALUE TYPES */ +/* ------------------------------------------------------------------------ */ + +/** + * @brief Parity mode options for UART data framing. + */ +typedef enum +{ + MCU_UART_PARITY_NONE = 0U, /**< No parity bit transmitted */ + MCU_UART_PARITY_EVEN, /**< Even parity */ + MCU_UART_PARITY_ODD /**< Odd parity */ +} MCU_UART_tenuParity; + +/** + * @brief Number of data bits per UART frame. + */ +typedef enum +{ + MCU_UART_DATA_BITS_5 = 5U, + MCU_UART_DATA_BITS_6 = 6U, + MCU_UART_DATA_BITS_7 = 7U, + MCU_UART_DATA_BITS_8 = 8U +} MCU_UART_tenuDataBits; + +/** + * @brief Number of stop bits per UART frame. + */ +typedef enum +{ + MCU_UART_STOP_BITS_1 = 1U, + MCU_UART_STOP_BITS_2 = 2U +} MCU_UART_tenuStopBits; + +/** + * @brief Async transmit mechanism selection. + * + * DMA: hardware DMA channel moves bytes autonomously. Zero CPU during + * transfer. Best for large buffers. + * ISR: UART TX interrupt fires per byte. Uses CPU per-byte but does not + * consume a DMA channel. + */ +typedef enum +{ + MCU_UART_ASYNC_DMA = 0U, /**< Use DMA for non-blocking TX */ + MCU_UART_ASYNC_ISR /**< Use UART TX interrupt for non-blocking TX */ +} MCU_UART_tenuAsyncMode; + +/* ------------------------------------------------------------------------ */ +/* CONFIGURATION STRUCTURE */ +/* ------------------------------------------------------------------------ */ + +/** + * @brief Per-instance UART configuration. + * + * One entry per hardware UART instance, stored in MCU_UART_astrConfig[]. + * The array index IS the UART instance number (0 = uart0, 1 = uart1). + */ +typedef struct +{ + u8 u8TxPin; /**< GPIO number for TX */ + u8 u8RxPin; /**< GPIO number for RX */ + u32 u32BaudRate; /**< Baud rate in bps */ + MCU_UART_tenuDataBits enuDataBits; /**< Data bits per frame */ + MCU_UART_tenuStopBits enuStopBits; /**< Stop bits per frame */ + MCU_UART_tenuParity enuParity; /**< Parity mode */ + MCU_UART_tenuAsyncMode enuAsyncMode; /**< DMA or ISR for non-blocking TX */ + STD_tpfCallbackFunc pfTxCompleteCallback; /**< Called when TX finishes. STD_NULL to ignore. */ +} MCU_UART_tstrConfig; + +/* ------------------------------------------------------------------------ */ +/* PUBLIC API */ +/* ------------------------------------------------------------------------ */ + +/** + * @brief Initialize all configured UART instances. + * + * Iterates MCU_UART_astrConfig[] and for each entry: sets baud rate, + * assigns GPIO pins, configures data format, claims DMA channel (if DMA + * mode), and sets up the appropriate IRQ handler. + * + * @return STD_OK on success, STD_NOK if any instance fails. + */ +STD_tenuResult MCU_UART_enuInit(void); + +/** + * @brief Send a single byte (blocking). + * + * @param u8Instance UART instance index (0 = uart0, 1 = uart1). + * @param u8Byte The byte to transmit. + * @return STD_OK on success, STD_NOK on failure. + */ +STD_tenuResult MCU_UART_enuSendByte(u8 u8Instance, u8 u8Byte); + +/** + * @brief Send a buffer of bytes (non-blocking, default). + * + * Starts an async transfer via DMA or UART TX ISR (per config). Returns + * immediately. The caller MUST keep the buffer valid until the callback + * fires or MCU_UART_bIsTxBusy() returns STD_FALSE. + * + * The TX-complete callback is invoked from interrupt context. + * + * @param u8Instance UART instance index. + * @param pu8Data Pointer to buffer. Must not be NULL. Must stay valid. + * @param u16Length Number of bytes to transmit. + * @return STD_OK transfer started, + * STD_NULL_POINTER_ERROR if pu8Data is NULL, + * STD_NOK if a transfer is already in progress. + */ +STD_tenuResult MCU_UART_enuSendBuffer(u8 u8Instance, const u8 *pu8Data, u16 u16Length); + +/** + * @brief Send a buffer of bytes (blocking). + * + * Blocks until all bytes are transmitted. Calls the TX-complete callback + * synchronously at the end (if configured, not NULL). + * + * @param u8Instance UART instance index. + * @param pu8Data Pointer to buffer. Must not be NULL. + * @param u16Length Number of bytes to transmit. + * @return STD_OK on success, + * STD_NULL_POINTER_ERROR if pu8Data is NULL, + * STD_NOK on failure. + */ +STD_tenuResult MCU_UART_enuSendBufferBlocking(u8 u8Instance, const u8 *pu8Data, u16 u16Length); + +/** + * @brief Check if an async TX transfer is in progress. + * + * @param u8Instance UART instance index. + * @return STD_TRUE if a non-blocking SendBuffer is still transmitting, + * STD_FALSE if the driver is idle and ready for a new transfer. + */ +STD_tBool MCU_UART_bIsTxBusy(u8 u8Instance); -/* Public API declarations will go here */ - MCU_UART_Status_t MCU_UART_enuInit(void); - MCU_UART_Status_t MCU_UART_enuSendByte(u8 byte); - MCU_UART_Status_t MCU_UART_enuSendBuffer(const u8 *data, - u16 length); #endif /* MCU_UART_H */ \ No newline at end of file diff --git a/src/MCU_UART/prg/MCU_UART_prg.c b/src/MCU_UART/prg/MCU_UART_prg.c index bcaaa71..6cd2edc 100644 --- a/src/MCU_UART/prg/MCU_UART_prg.c +++ b/src/MCU_UART/prg/MCU_UART_prg.c @@ -2,10 +2,14 @@ * File: MCU_UART_prg.c * Component: MCU_UART * Description: Program (implementation) file for the MCU_UART driver. - * Contains the actual implementations of the public functions - * declared in MCU_UART.h. This is where we call the Pico SDK - * UART APIs to initialize the peripheral, transmit bytes, and - * receive data. + * 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) *****************************************************************************/ @@ -14,4 +18,370 @@ #include "MCU_UART_priv.h" #include "MCU_UART_cfg.h" -/* Function implementations will go here */ \ No newline at end of file +/* 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]; +} diff --git a/src/MCU_UART/prg/MCU_UART_priv.h b/src/MCU_UART/prg/MCU_UART_priv.h index 9bcfb44..35c3620 100644 --- a/src/MCU_UART/prg/MCU_UART_priv.h +++ b/src/MCU_UART/prg/MCU_UART_priv.h @@ -2,10 +2,10 @@ * File: MCU_UART_priv.h * Component: MCU_UART * Description: Private header for the MCU_UART driver. - * Contains internal macros, helper declarations, and - * register-level definitions that are only used inside the - * component itself. Nothing declared here is exposed to - * external components. + * Contains the internal control structure used to track + * per-instance async TX state, the extern declaration of + * the config array (which is only accessed internally by + * _prg.c), and any other helpers private to the component. * * Layer: MCU (hardware abstraction) - internal use only *****************************************************************************/ @@ -13,6 +13,53 @@ #ifndef MCU_UART_PRIV_H #define MCU_UART_PRIV_H -/* Private declarations, internal macros and helpers will go here */ +#include "MCU_UART.h" +#include "MCU_UART_cfg.h" + +/* ------------------------------------------------------------------------ */ +/* CONFIG ARRAY (EXTERN) */ +/* ------------------------------------------------------------------------ */ + +/** + * @brief Configuration array indexed by MCU_UART_tenuInstance. + * + * Defined in MCU_UART_cfg.c. Each entry holds the full configuration for + * one UART instance. The driver iterates this array during Init. + * Declared here (not in _cfg.h or .h) because only _prg.c needs to + * access it — no external component should read the raw config array. + */ +extern const MCU_UART_tstrConfig MCU_UART_astrConfig[MCU_UART_NUM_INSTANCES]; + +/* ------------------------------------------------------------------------ */ +/* RUNTIME CONTROL STRUCTURE */ +/* ------------------------------------------------------------------------ */ + +/** + * @brief Internal runtime state for all UART instances. + * + * Each field is an array indexed by instance number. This struct-of-arrays + * layout keeps all per-instance state in a single static object inside + * MCU_UART_prg.c, making it easy to find, zero-initialize, and inspect + * in a debugger. + * + * Fields: + * apu8TxBuffer — pointer to the caller's buffer being transmitted + * au16TxLength — total number of bytes to transmit + * au16TxIndex — number of bytes transmitted so far (ISR mode only; + * DMA mode does not use this - the DMA controller + * tracks progress internally) + * abTxBusy — STD_TRUE while an async transfer is in progress + * as8DmaChannel — DMA channel number claimed during Init for each + * instance configured in DMA mode. Set to -1 for + * instances using ISR mode (no DMA channel needed). + */ +typedef struct +{ + const u8 *apu8TxBuffer[MCU_UART_NUM_INSTANCES]; + u16 au16TxLength[MCU_UART_NUM_INSTANCES]; + u16 au16TxIndex[MCU_UART_NUM_INSTANCES]; + STD_tBool abTxBusy[MCU_UART_NUM_INSTANCES]; + s8 as8DmaChannel[MCU_UART_NUM_INSTANCES]; +} MCU_UART_tstrControl; #endif /* MCU_UART_PRIV_H */ \ No newline at end of file