Implement MCU_UART driver with blocking and non-blocking TX
Full hardware UART driver with config-driven per-instance setup. Non-blocking SendBuffer supports DMA (zero-CPU) and ISR (FIFO-fill) modes selectable per instance in MCU_UART_cfg.h. Both modes and blocking SendBufferBlocking invoke a configurable TX-complete callback (STD_NULL to ignore). All public functions take a u8 instance parameter. Config uses struct-of-arrays pattern for runtime state, designated-initializer array for per-instance settings, and value enums in the public header for parity/data bits/stop bits/async mode. Added hardware_dma to CMake link list.
This commit is contained in:
parent
3687e48684
commit
f50e877ffc
@ -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
|
||||
|
||||
@ -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 */
|
||||
/* ------------------------------------------------------------------------ */
|
||||
/* 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,
|
||||
},
|
||||
};
|
||||
@ -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 */
|
||||
@ -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 */
|
||||
@ -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 */
|
||||
/* 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];
|
||||
}
|
||||
|
||||
@ -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 */
|
||||
Loading…
x
Reference in New Issue
Block a user