From 3d5e63c790888bca3e93ef3068c7b9e22a3f608a Mon Sep 17 00:00:00 2001 From: Mohamed Salem Date: Mon, 13 Apr 2026 01:50:58 +0200 Subject: [PATCH] Redesign RX to non-blocking ring buffer model UART/USB now receive in the background and store into ring buffers. Callers read from the buffer via non-blocking ReadByte/ReadBuffer. Removed blocking ReceiveByte, async ReceiveBuffer with request state and callbacks. MCU_USB uses lazy drain from SDK stdio into its own ring buffer. MCU_UART ring buffer unchanged (ISR/DMA). HAL_COM updated with ReadByte/ReadBuffer function pointer dispatch. APP_CLSW updated to use new ReadByte API. --- src/APP_CLSW/prg/APP_CLSW_prg.c | 2 +- src/HAL_COM/cfg/HAL_COM_cfg.c | 21 +-- src/HAL_COM/inc/HAL_COM.h | 37 ++-- src/HAL_COM/prg/HAL_COM_prg.c | 18 +- src/MCU_UART/cfg/MCU_UART_cfg.c | 24 +-- src/MCU_UART/cfg/MCU_UART_cfg.h | 44 +---- src/MCU_UART/inc/MCU_UART.h | 221 ++++++----------------- src/MCU_UART/prg/MCU_UART_prg.c | 300 +++++++------------------------ src/MCU_UART/prg/MCU_UART_priv.h | 42 +---- src/MCU_USB/inc/MCU_USB.h | 101 +++++------ src/MCU_USB/prg/MCU_USB_prg.c | 218 ++++++++++++---------- 11 files changed, 342 insertions(+), 686 deletions(-) diff --git a/src/APP_CLSW/prg/APP_CLSW_prg.c b/src/APP_CLSW/prg/APP_CLSW_prg.c index 513ab5b..6ca66e0 100644 --- a/src/APP_CLSW/prg/APP_CLSW_prg.c +++ b/src/APP_CLSW/prg/APP_CLSW_prg.c @@ -144,7 +144,7 @@ void APP_CLSW_vRunnable(void) while (bDataAvailableLoc == STD_TRUE) { /* Read one byte */ - enuRxResultLoc = HAL_COM_enuReceiveByte(APP_CLSW_COM_CHANNEL, &u8ByteLoc); + enuRxResultLoc = HAL_COM_enuReadByte(APP_CLSW_COM_CHANNEL, &u8ByteLoc); if (enuRxResultLoc == STD_OK) { diff --git a/src/HAL_COM/cfg/HAL_COM_cfg.c b/src/HAL_COM/cfg/HAL_COM_cfg.c index dafbcde..72c3456 100644 --- a/src/HAL_COM/cfg/HAL_COM_cfg.c +++ b/src/HAL_COM/cfg/HAL_COM_cfg.c @@ -52,20 +52,20 @@ static STD_tenuResult vUsbSendBuffer(u8 u8Instance, const u8 *pu8Data, u16 u16Le * (u8 u8Instance as the first parameter), so no wrapper is needed * for TX or RX. Its functions can be assigned directly. */ -/* --- USB RX wrappers (same rationale as TX — normalize missing instance) --- */ +/* --- USB RX wrappers (normalize missing instance parameter) --- */ -/** - * @brief Wrapper for MCU_USB_enuReceiveByte to match HAL_COM_tpfReceiveByte. - */ -static STD_tenuResult vUsbReceiveByte(u8 u8Instance, u8 *pu8Byte) +static STD_tenuResult vUsbReadByte(u8 u8Instance, u8 *pu8Byte) { (void)u8Instance; - return MCU_USB_enuReceiveByte(pu8Byte); + return MCU_USB_enuReadByte(pu8Byte); +} + +static STD_tenuResult vUsbReadBuffer(u8 u8Instance, u8 *pu8Data, u16 u16MaxLength, u16 *pu16Read) +{ + (void)u8Instance; + return MCU_USB_enuReadBuffer(pu8Data, u16MaxLength, pu16Read); } -/** - * @brief Wrapper for MCU_USB_bIsRxDataAvailable to match HAL_COM_tpfIsRxDataAvailable. - */ static STD_tBool vUsbIsRxDataAvailable(u8 u8Instance) { (void)u8Instance; @@ -98,7 +98,8 @@ const HAL_COM_tstrChannelConfig HAL_COM_astrChannelConfig[HAL_COM_NUM_CHANNELS] { .pfSendByte = vUsbSendByte, .pfSendBuffer = vUsbSendBuffer, - .pfReceiveByte = vUsbReceiveByte, + .pfReadByte = vUsbReadByte, + .pfReadBuffer = vUsbReadBuffer, .pfIsRxDataAvailable = vUsbIsRxDataAvailable, .u8Instance = 0U, }, diff --git a/src/HAL_COM/inc/HAL_COM.h b/src/HAL_COM/inc/HAL_COM.h index 518184c..3b0750b 100644 --- a/src/HAL_COM/inc/HAL_COM.h +++ b/src/HAL_COM/inc/HAL_COM.h @@ -54,19 +54,17 @@ typedef STD_tenuResult (*HAL_COM_tpfSendByte)(u8 u8Instance, u8 u8Byte); typedef STD_tenuResult (*HAL_COM_tpfSendBuffer)(u8 u8Instance, const u8 *pu8Data, u16 u16Length); /** - * @brief Generic receive-byte function pointer type (blocking). - * - * @param u8Instance Peripheral instance index. - * @param pu8Byte Pointer to store the received byte. - * @return STD_tenuResult + * @brief Generic read-byte function pointer type (non-blocking). */ -typedef STD_tenuResult (*HAL_COM_tpfReceiveByte)(u8 u8Instance, u8 *pu8Byte); +typedef STD_tenuResult (*HAL_COM_tpfReadByte)(u8 u8Instance, u8 *pu8Byte); + +/** + * @brief Generic read-buffer function pointer type (non-blocking). + */ +typedef STD_tenuResult (*HAL_COM_tpfReadBuffer)(u8 u8Instance, u8 *pu8Data, u16 u16MaxLength, u16 *pu16Read); /** * @brief Generic RX-data-available check function pointer type. - * - * @param u8Instance Peripheral instance index. - * @return STD_tBool */ typedef STD_tBool (*HAL_COM_tpfIsRxDataAvailable)(u8 u8Instance); @@ -89,7 +87,8 @@ typedef struct { HAL_COM_tpfSendByte pfSendByte; /**< Driver's send-byte function */ HAL_COM_tpfSendBuffer pfSendBuffer; /**< Driver's send-buffer function */ - HAL_COM_tpfReceiveByte pfReceiveByte; /**< Driver's receive-byte function */ + HAL_COM_tpfReadByte pfReadByte; /**< Driver's read-byte function */ + HAL_COM_tpfReadBuffer pfReadBuffer; /**< Driver's read-buffer function */ HAL_COM_tpfIsRxDataAvailable pfIsRxDataAvailable; /**< Driver's RX-available check */ u8 u8Instance; /**< Peripheral instance to pass through */ } HAL_COM_tstrChannelConfig; @@ -135,21 +134,17 @@ STD_tenuResult HAL_COM_enuSendByte(u8 u8Channel, u8 u8Byte); STD_tenuResult HAL_COM_enuSendBuffer(u8 u8Channel, const u8 *pu8Data, u16 u16Length); /** - * @brief Receive one byte through the specified channel (blocking). - * - * @param u8Channel Channel index. - * @param pu8Byte Pointer to store the received byte. Must not be NULL. - * @return STD_OK byte received, - * STD_NULL_POINTER_ERROR if pu8Byte is NULL. + * @brief Read one byte from the specified channel (non-blocking). */ -STD_tenuResult HAL_COM_enuReceiveByte(u8 u8Channel, u8 *pu8Byte); +STD_tenuResult HAL_COM_enuReadByte(u8 u8Channel, u8 *pu8Byte); + +/** + * @brief Read up to u16MaxLength bytes from the specified channel (non-blocking). + */ +STD_tenuResult HAL_COM_enuReadBuffer(u8 u8Channel, u8 *pu8Data, u16 u16MaxLength, u16 *pu16Read); /** * @brief Check if the specified channel has RX data available. - * - * @param u8Channel Channel index. - * @return STD_TRUE if at least one byte is available, - * STD_FALSE if no data waiting. */ STD_tBool HAL_COM_bIsRxDataAvailable(u8 u8Channel); diff --git a/src/HAL_COM/prg/HAL_COM_prg.c b/src/HAL_COM/prg/HAL_COM_prg.c index 1605e62..271abd7 100644 --- a/src/HAL_COM/prg/HAL_COM_prg.c +++ b/src/HAL_COM/prg/HAL_COM_prg.c @@ -70,12 +70,26 @@ STD_tenuResult HAL_COM_enuSendBuffer(u8 u8Channel, const u8 *pu8Data, u16 u16Len /* RECEIVE BYTE (BLOCKING) */ /* ========================================================================= */ -STD_tenuResult HAL_COM_enuReceiveByte(u8 u8Channel, u8 *pu8Byte) +STD_tenuResult HAL_COM_enuReadByte(u8 u8Channel, u8 *pu8Byte) { STD_tenuResult enuResultLoc = STD_OK; const HAL_COM_tstrChannelConfig *pstrCfgLoc = &HAL_COM_astrChannelConfig[u8Channel]; - enuResultLoc = pstrCfgLoc->pfReceiveByte(pstrCfgLoc->u8Instance, pu8Byte); + enuResultLoc = pstrCfgLoc->pfReadByte(pstrCfgLoc->u8Instance, pu8Byte); + + return enuResultLoc; +} + +/* ========================================================================= */ +/* READ BUFFER (NON-BLOCKING) */ +/* ========================================================================= */ + +STD_tenuResult HAL_COM_enuReadBuffer(u8 u8Channel, u8 *pu8Data, u16 u16MaxLength, u16 *pu16Read) +{ + STD_tenuResult enuResultLoc = STD_OK; + const HAL_COM_tstrChannelConfig *pstrCfgLoc = &HAL_COM_astrChannelConfig[u8Channel]; + + enuResultLoc = pstrCfgLoc->pfReadBuffer(pstrCfgLoc->u8Instance, pu8Data, u16MaxLength, pu16Read); return enuResultLoc; } diff --git a/src/MCU_UART/cfg/MCU_UART_cfg.c b/src/MCU_UART/cfg/MCU_UART_cfg.c index 7eebe74..c490e8f 100644 --- a/src/MCU_UART/cfg/MCU_UART_cfg.c +++ b/src/MCU_UART/cfg/MCU_UART_cfg.c @@ -1,35 +1,14 @@ /****************************************************************************** * File: MCU_UART_cfg.c * Component: MCU_UART - * Description: Configuration implementation for the MCU_UART driver. - * 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. + * Description: Configuration array definition for the MCU_UART driver. * * 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 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] = @@ -43,6 +22,5 @@ const MCU_UART_tstrConfig MCU_UART_astrConfig[MCU_UART_NUM_INSTANCES] = .enuTxAsyncMode = MCU_UART_0_TX_ASYNC_MODE, .pfTxCompleteCallback = MCU_UART_0_TX_COMPLETE_CALLBACK, .enuRxAsyncMode = MCU_UART_0_RX_ASYNC_MODE, - .pfRxCallback = MCU_UART_0_RX_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 cb2daea..9166a19 100644 --- a/src/MCU_UART/cfg/MCU_UART_cfg.h +++ b/src/MCU_UART/cfg/MCU_UART_cfg.h @@ -1,10 +1,9 @@ /****************************************************************************** * File: MCU_UART_cfg.h * Component: MCU_UART - * Description: Configuration header for the MCU_UART driver. - * Defines which UART instances are active, their GPIO pin - * assignments, baud rates, data format, TX/RX async mechanisms, - * callbacks, and RX buffer sizing. + * Description: Configuration for the MCU_UART driver. Defines instances, + * pin assignments, baud rates, data format, TX/RX async modes, + * and RX buffer sizing. * * Layer: MCU (hardware abstraction) - configuration *****************************************************************************/ @@ -28,54 +27,25 @@ typedef enum /* RX RING BUFFER SIZING */ /* ------------------------------------------------------------------------ */ -/** @brief Number of address bits for the RX ring buffer. The actual buffer - * size is 2^N bytes. Must be a power of 2 because DMA ring mode - * wraps the write address at a 2^N boundary. Applies to all - * instances. Change this single value to resize the buffer: - * 5 = 32 bytes, 6 = 64 bytes, 7 = 128 bytes, 8 = 256 bytes. */ +/** @brief Number of address bits for the RX ring buffer (2^N bytes). + * Must be power of 2 for DMA ring wrap. + * 5 = 32, 6 = 64, 7 = 128, 8 = 256. */ #define MCU_UART_RX_BUFFER_SIZE_BITS 6U - -/** @brief Derived buffer size in bytes. Do not edit — change - * MCU_UART_RX_BUFFER_SIZE_BITS instead. */ #define MCU_UART_RX_BUFFER_SIZE (1U << MCU_UART_RX_BUFFER_SIZE_BITS) - -/** @brief Bitmask for ring buffer index wrapping. */ #define MCU_UART_RX_BUFFER_MASK (MCU_UART_RX_BUFFER_SIZE - 1U) /* ------------------------------------------------------------------------ */ /* INSTANCE 0 CONFIGURATION */ /* ------------------------------------------------------------------------ */ -/** @brief GPIO pin for UART0 TX. GP0 is the default for uart0. */ #define MCU_UART_0_TX_PIN 0U - -/** @brief GPIO pin for UART0 RX. GP1 is the default for uart0. */ #define MCU_UART_0_RX_PIN 1U - -/** @brief Baud rate for UART0 in bits per second. */ #define MCU_UART_0_BAUD_RATE 115200U - -/** @brief Data bits per frame for UART0. */ #define MCU_UART_0_DATA_BITS MCU_UART_DATA_BITS_8 - -/** @brief Stop bits per frame for UART0. */ #define MCU_UART_0_STOP_BITS MCU_UART_STOP_BITS_1 - -/** @brief Parity mode for UART0. */ #define MCU_UART_0_PARITY MCU_UART_PARITY_NONE - -/** @brief Async TX mechanism for UART0 (DMA or ISR). */ #define MCU_UART_0_TX_ASYNC_MODE MCU_UART_ASYNC_DMA - -/** @brief TX-complete callback for UART0. STD_NULL to disable. */ #define MCU_UART_0_TX_COMPLETE_CALLBACK STD_NULL - -/** @brief Async RX mechanism for UART0. - * DMA: hardware fills ring buffer, polling only (callback ignored). - * ISR: interrupt-driven, pfRxCallback fires per byte. */ #define MCU_UART_0_RX_ASYNC_MODE MCU_UART_ASYNC_ISR -/** @brief RX callback for UART0. Per-byte, ISR mode only. STD_NULL to disable. */ -#define MCU_UART_0_RX_CALLBACK STD_NULL - -#endif /* MCU_UART_CFG_H */ +#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 3a357c1..283a327 100644 --- a/src/MCU_UART/inc/MCU_UART.h +++ b/src/MCU_UART/inc/MCU_UART.h @@ -2,20 +2,11 @@ * File: MCU_UART.h * Component: MCU_UART * Description: Public interface for the MCU UART driver component. - * 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. + * TX: SendByte (blocking), SendBuffer (non-blocking DMA/ISR), + * SendBufferBlocking. + * RX: background ring buffer filled by ISR or DMA. + * ReadByte / ReadBuffer read from the buffer (non-blocking). * * Layer: MCU (hardware abstraction) *****************************************************************************/ @@ -23,27 +14,19 @@ #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" /* ------------------------------------------------------------------------ */ /* 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_PARITY_NONE = 0U, + MCU_UART_PARITY_EVEN, + MCU_UART_PARITY_ODD } MCU_UART_tenuParity; -/** - * @brief Number of data bits per UART frame. - */ typedef enum { MCU_UART_DATA_BITS_5 = 5U, @@ -52,184 +35,90 @@ typedef enum 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_ASYNC_DMA = 0U, + MCU_UART_ASYNC_ISR } 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 enuTxAsyncMode; /**< DMA or ISR for non-blocking TX */ - STD_tpfCallbackFunc pfTxCompleteCallback; /**< Called when TX finishes. STD_NULL to ignore. */ - MCU_UART_tenuAsyncMode enuRxAsyncMode; /**< DMA or ISR for RX data capture */ - STD_tpfCallbackFunc pfRxCallback; /**< Called per byte received (ISR mode only). - DMA mode = polling only, callback ignored. - STD_NULL to ignore. */ + u8 u8TxPin; + u8 u8RxPin; + u32 u32BaudRate; + MCU_UART_tenuDataBits enuDataBits; + MCU_UART_tenuStopBits enuStopBits; + MCU_UART_tenuParity enuParity; + MCU_UART_tenuAsyncMode enuTxAsyncMode; + STD_tpfCallbackFunc pfTxCompleteCallback; + MCU_UART_tenuAsyncMode enuRxAsyncMode; } MCU_UART_tstrConfig; /* ------------------------------------------------------------------------ */ -/* PUBLIC API */ +/* TX PUBLIC API */ +/* ------------------------------------------------------------------------ */ + +STD_tenuResult MCU_UART_enuInit(void); + +STD_tenuResult MCU_UART_enuSendByte(u8 u8Instance, u8 u8Byte); + +STD_tenuResult MCU_UART_enuSendBuffer(u8 u8Instance, const u8 *pu8Data, u16 u16Length); + +STD_tenuResult MCU_UART_enuSendBufferBlocking(u8 u8Instance, const u8 *pu8Data, u16 u16Length); + +STD_tBool MCU_UART_bIsTxBusy(u8 u8Instance); + +/* ------------------------------------------------------------------------ */ +/* RX PUBLIC API */ /* ------------------------------------------------------------------------ */ /** - * @brief Initialize all configured UART instances. + * @brief Read one byte from the RX ring buffer (non-blocking). * - * 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. + * The ring buffer is filled in the background by ISR or DMA. + * Returns immediately — STD_OK with the byte, or STD_NOK if empty. * * @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. + * @param pu8Byte Pointer to store the received byte. + * @return STD_OK byte read, + * STD_NULL_POINTER_ERROR if pu8Byte is NULL, + * STD_NOK if ring buffer is empty. */ -STD_tenuResult MCU_UART_enuSendBuffer(u8 u8Instance, const u8 *pu8Data, u16 u16Length); +STD_tenuResult MCU_UART_enuReadByte(u8 u8Instance, u8 *pu8Byte); /** - * @brief Send a buffer of bytes (blocking). + * @brief Read up to u16MaxLength bytes from the RX ring buffer (non-blocking). * - * Blocks until all bytes are transmitted. Calls the TX-complete callback - * synchronously at the end (if configured, not NULL). + * Copies as many bytes as are currently available (up to u16MaxLength) + * into pu8Data. Reports the actual number of bytes read via pu16Read. + * Returns immediately even if fewer than u16MaxLength bytes are available. * - * @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. + * @param u8Instance UART instance index. + * @param pu8Data Pointer to output buffer. + * @param u16MaxLength Maximum bytes to read. + * @param pu16Read Pointer to store actual bytes read. Must not be NULL. + * @return STD_OK at least one byte read, + * STD_NULL_POINTER_ERROR if pu8Data or pu16Read is NULL, + * STD_NOK if ring buffer is empty (zero bytes read). */ -STD_tenuResult MCU_UART_enuSendBufferBlocking(u8 u8Instance, const u8 *pu8Data, u16 u16Length); +STD_tenuResult MCU_UART_enuReadBuffer(u8 u8Instance, u8 *pu8Data, u16 u16MaxLength, u16 *pu16Read); /** - * @brief Check if an async TX transfer is in progress. + * @brief Check if the RX ring buffer has data. * * @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); - -/** - * @brief Receive one byte (blocking). - * - * Waits until a byte is available in the internal ring buffer (filled - * by RX ISR or DMA in the background), then returns it. - * - * @param u8Instance UART instance index. - * @param pu8Byte Pointer to store the received byte. Must not be NULL. - * @return STD_OK byte received, - * STD_NULL_POINTER_ERROR if pu8Byte is NULL. - */ -STD_tenuResult MCU_UART_enuReceiveByte(u8 u8Instance, u8 *pu8Byte); - -/** - * @brief Receive a buffer of bytes (non-blocking, default). - * - * Registers a request for u16Length bytes. As bytes arrive in the ring - * buffer, they are copied to pu8Data. When all requested bytes have been - * collected, the RX callback fires (if configured, not STD_NULL). - * Returns immediately after registering the request. - * - * The caller MUST keep the buffer valid until the callback fires or - * MCU_UART_bIsRxBusy() returns STD_FALSE. - * - * @param u8Instance UART instance index. - * @param pu8Data Pointer to buffer to fill. Must not be NULL. - * @param u16Length Number of bytes to receive. - * @return STD_OK request registered, - * STD_NULL_POINTER_ERROR if pu8Data is NULL, - * STD_NOK if a receive request is already active. - */ -STD_tenuResult MCU_UART_enuReceiveBuffer(u8 u8Instance, u8 *pu8Data, u16 u16Length); - -/** - * @brief Receive a buffer of bytes (blocking). - * - * Blocks until u16Length bytes have been received from the ring buffer. - * Calls the RX callback at the end (if configured). - * - * @param u8Instance UART instance index. - * @param pu8Data Pointer to buffer to fill. Must not be NULL. - * @param u16Length Number of bytes to receive. - * @return STD_OK all bytes received, - * STD_NULL_POINTER_ERROR if pu8Data is NULL. - */ -STD_tenuResult MCU_UART_enuReceiveBufferBlocking(u8 u8Instance, u8 *pu8Data, u16 u16Length); - -/** - * @brief Check if the RX ring buffer has data waiting. - * - * @param u8Instance UART instance index. - * @return STD_TRUE if at least one byte is available, - * STD_FALSE if the ring buffer is empty. + * @return STD_TRUE if at least one byte available, STD_FALSE if empty. */ STD_tBool MCU_UART_bIsRxDataAvailable(u8 u8Instance); -/** - * @brief Check if an async ReceiveBuffer request is in progress. - * - * @param u8Instance UART instance index. - * @return STD_TRUE if a non-blocking ReceiveBuffer is still collecting, - * STD_FALSE if idle. - */ -STD_tBool MCU_UART_bIsRxBusy(u8 u8Instance); - #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 4922e91..89b1138 100644 --- a/src/MCU_UART/prg/MCU_UART_prg.c +++ b/src/MCU_UART/prg/MCU_UART_prg.c @@ -1,13 +1,9 @@ /****************************************************************************** * File: MCU_UART_prg.c * Component: MCU_UART - * Description: Program (implementation) file for the MCU_UART driver. - * TX: blocking + non-blocking (DMA or ISR) with callback. - * RX: ISR or DMA fills a ring buffer in the background. - * ReceiveByte (blocking) reads from the ring buffer. - * ReceiveBuffer (non-blocking) registers a request — bytes - * are copied from ring buffer to caller's buffer as they - * arrive. ReceiveBufferBlocking waits until N bytes collected. + * Description: TX: blocking + non-blocking (DMA or ISR) with callback. + * RX: ISR or DMA fills ring buffer in background. ReadByte + * and ReadBuffer read from the buffer non-blocking. * * Layer: MCU (hardware abstraction) *****************************************************************************/ @@ -25,11 +21,7 @@ /* INSTANCE LOOKUP TABLE */ /* ------------------------------------------------------------------------ */ -static uart_inst_t * const apstrInstances[] = -{ - uart0, - uart1, -}; +static uart_inst_t * const apstrInstances[] = { uart0, uart1 }; /* ------------------------------------------------------------------------ */ /* RUNTIME STATE */ @@ -51,31 +43,17 @@ static void vCallTxCallback(u8 u8Instance) } } -static void vCallRxCallback(u8 u8Instance) -{ - STD_tpfCallbackFunc pfCbLoc = MCU_UART_astrConfig[u8Instance].pfRxCallback; - - if (pfCbLoc != STD_NULL) - { - pfCbLoc(); - } -} - -/** - * @brief Get the current RX ring buffer head position. - * In DMA mode, derived from the DMA write pointer. - * In ISR mode, read directly from the control struct. - */ +/** @brief Get current RX head. DMA mode derives from hw write pointer. */ static u16 u16GetRxHead(u8 u8Instance) { u16 u16HeadLoc; if (MCU_UART_astrConfig[u8Instance].enuRxAsyncMode == MCU_UART_ASYNC_DMA) { - s8 s8RxChLoc = strControl.as8RxDmaChannel[u8Instance]; - u32 u32WriteAddrLoc = (u32)dma_channel_hw_addr((u32)s8RxChLoc)->write_addr; - u32 u32BufferBaseLoc = (u32)(&strControl.aau8RxBuffer[u8Instance][0]); - u16HeadLoc = (u16)((u32WriteAddrLoc - u32BufferBaseLoc) & MCU_UART_RX_BUFFER_MASK); + s8 s8ChLoc = strControl.as8RxDmaChannel[u8Instance]; + u32 u32WrLoc = (u32)dma_channel_hw_addr((u32)s8ChLoc)->write_addr; + u32 u32BaseLoc = (u32)(&strControl.aau8RxBuffer[u8Instance][0]); + u16HeadLoc = (u16)((u32WrLoc - u32BaseLoc) & MCU_UART_RX_BUFFER_MASK); } else { @@ -85,52 +63,18 @@ static u16 u16GetRxHead(u8 u8Instance) return u16HeadLoc; } -/** - * @brief Try to fulfill an active async ReceiveBuffer request by copying - * available bytes from the ring buffer to the caller's buffer. - * Called from the RX ISR after new bytes land in the ring buffer, - * or from polling contexts. - */ -static void vFulfillRxRequest(u8 u8Instance) -{ - u16 u16HeadLoc = u16GetRxHead(u8Instance); - u16 u16TailLoc = strControl.au16RxTail[u8Instance]; - - /* Copy available bytes from ring buffer to request buffer */ - while ((u16HeadLoc != u16TailLoc) && - (strControl.au16RxReqIndex[u8Instance] < strControl.au16RxReqLength[u8Instance])) - { - strControl.apu8RxReqBuffer[u8Instance][strControl.au16RxReqIndex[u8Instance]] = - strControl.aau8RxBuffer[u8Instance][u16TailLoc]; - strControl.au16RxReqIndex[u8Instance]++; - u16TailLoc = (u16TailLoc + 1U) & MCU_UART_RX_BUFFER_MASK; - } - - /* Update tail — bytes have been consumed */ - strControl.au16RxTail[u8Instance] = u16TailLoc; - - /* Check if request is fully fulfilled */ - if (strControl.au16RxReqIndex[u8Instance] >= strControl.au16RxReqLength[u8Instance]) - { - strControl.abRxReqActive[u8Instance] = STD_FALSE; - vCallRxCallback(u8Instance); - } -} - /* ------------------------------------------------------------------------ */ /* TX DMA IRQ HANDLER (INSTANCE 0) */ /* ------------------------------------------------------------------------ */ static void vTxDmaIrqHandler0(void) { - s8 s8ChannelLoc = strControl.as8TxDmaChannel[MCU_UART_INSTANCE_0]; - u32 u32StatusLoc; - - u32StatusLoc = dma_channel_get_irq0_status((u32)s8ChannelLoc); + s8 s8ChLoc = strControl.as8TxDmaChannel[MCU_UART_INSTANCE_0]; + u32 u32StatusLoc = dma_channel_get_irq0_status((u32)s8ChLoc); if (u32StatusLoc != 0U) { - dma_irqn_acknowledge_channel(0, (u32)s8ChannelLoc); + dma_irqn_acknowledge_channel(0, (u32)s8ChLoc); strControl.abTxBusy[MCU_UART_INSTANCE_0] = STD_FALSE; vCallTxCallback((u8)MCU_UART_INSTANCE_0); } @@ -140,53 +84,31 @@ static void vTxDmaIrqHandler0(void) /* UART ISR HANDLER (RX + TX) */ /* ------------------------------------------------------------------------ */ -/** - * @brief Combined UART interrupt handler for RX and TX. - * - * RX: drains the RX FIFO into the ring buffer. If an async ReceiveBuffer - * request is active, copies bytes from ring buffer to the request buffer. - * Calls per-byte RX callback (ISR mode only). - * - * TX: fills the TX FIFO from the send buffer. When all bytes sent, disables - * TX interrupt and calls TX callback. - */ static void vUartIsrHandler(u8 u8Instance) { uart_inst_t *pstrUartLoc = apstrInstances[u8Instance]; /* --- RX: drain FIFO into ring buffer --- */ - STD_tBool bRxReadable = STD_FALSE; + STD_tBool bReadableLoc = (uart_is_readable(pstrUartLoc) != 0) ? STD_TRUE : STD_FALSE; - bRxReadable = (uart_is_readable(pstrUartLoc) != 0) ? STD_TRUE : STD_FALSE; - - while (bRxReadable == STD_TRUE) + while (bReadableLoc == STD_TRUE) { u8 u8ByteLoc = (u8)uart_getc(pstrUartLoc); u16 u16HeadLoc = strControl.au16RxHead[u8Instance]; - /* Push byte into ring buffer */ strControl.aau8RxBuffer[u8Instance][u16HeadLoc] = u8ByteLoc; strControl.au16RxHead[u8Instance] = (u16HeadLoc + 1U) & MCU_UART_RX_BUFFER_MASK; - bRxReadable = (uart_is_readable(pstrUartLoc) != 0) ? STD_TRUE : STD_FALSE; + bReadableLoc = (uart_is_readable(pstrUartLoc) != 0) ? STD_TRUE : STD_FALSE; } - /* If an async ReceiveBuffer request is active, try to fulfill it */ - if (strControl.abRxReqActive[u8Instance] == STD_TRUE) - { - vFulfillRxRequest(u8Instance); - } - - /* --- TX: fill FIFO from buffer (if TX is active in ISR mode) --- */ + /* --- TX: fill FIFO from buffer (if TX ISR active) --- */ STD_tBool bTxBusyLoc = strControl.abTxBusy[u8Instance]; if (bTxBusyLoc == STD_TRUE) { - STD_tBool bFifoReady = STD_FALSE; - STD_tBool bDataLeft = STD_FALSE; - - bFifoReady = (uart_is_writable(pstrUartLoc) != 0) ? STD_TRUE : STD_FALSE; - bDataLeft = (strControl.au16TxIndex[u8Instance] < strControl.au16TxLength[u8Instance]) ? STD_TRUE : STD_FALSE; + STD_tBool bFifoReady = (uart_is_writable(pstrUartLoc) != 0) ? STD_TRUE : STD_FALSE; + STD_tBool bDataLeft = (strControl.au16TxIndex[u8Instance] < strControl.au16TxLength[u8Instance]) ? STD_TRUE : STD_FALSE; while ((bFifoReady == STD_TRUE) && (bDataLeft == STD_TRUE)) { @@ -195,12 +117,11 @@ static void vUartIsrHandler(u8 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; + bDataLeft = (strControl.au16TxIndex[u8Instance] < strControl.au16TxLength[u8Instance]) ? STD_TRUE : STD_FALSE; } if (bDataLeft == STD_FALSE) { - /* Disable TX interrupt, keep RX interrupt enabled */ uart_set_irqs_enabled(pstrUartLoc, false, true); strControl.abTxBusy[u8Instance] = STD_FALSE; vCallTxCallback(u8Instance); @@ -222,35 +143,23 @@ 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++) { - /* TX */ strControl.apu8TxBuffer[u8IndexLoc] = STD_NULL; strControl.au16TxLength[u8IndexLoc] = 0U; strControl.au16TxIndex[u8IndexLoc] = 0U; strControl.abTxBusy[u8IndexLoc] = STD_FALSE; strControl.as8TxDmaChannel[u8IndexLoc] = -1; - - /* RX */ strControl.au16RxHead[u8IndexLoc] = 0U; strControl.au16RxTail[u8IndexLoc] = 0U; strControl.as8RxDmaChannel[u8IndexLoc] = -1; - - /* RX request */ - strControl.apu8RxReqBuffer[u8IndexLoc] = STD_NULL; - strControl.au16RxReqLength[u8IndexLoc] = 0U; - strControl.au16RxReqIndex[u8IndexLoc] = 0U; - strControl.abRxReqActive[u8IndexLoc] = STD_FALSE; } - /* 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]; - /* Basic peripheral setup */ uart_init(pstrUartLoc, pstrCfgLoc->u32BaudRate); gpio_set_function(pstrCfgLoc->u8TxPin, GPIO_FUNC_UART); gpio_set_function(pstrCfgLoc->u8RxPin, GPIO_FUNC_UART); @@ -259,13 +168,12 @@ STD_tenuResult MCU_UART_enuInit(void) pstrCfgLoc->enuStopBits, pstrCfgLoc->enuParity); - /* --- TX async setup --- */ + /* TX async setup */ if (pstrCfgLoc->enuTxAsyncMode == MCU_UART_ASYNC_DMA) { - s8 s8ChannelLoc = (s8)dma_claim_unused_channel(true); - strControl.as8TxDmaChannel[u8IndexLoc] = s8ChannelLoc; - - dma_channel_set_irq0_enabled((u32)s8ChannelLoc, true); + s8 s8ChLoc = (s8)dma_claim_unused_channel(true); + strControl.as8TxDmaChannel[u8IndexLoc] = s8ChLoc; + dma_channel_set_irq0_enabled((u32)s8ChLoc, true); if (u8IndexLoc == (u8)MCU_UART_INSTANCE_0) { @@ -274,13 +182,9 @@ STD_tenuResult MCU_UART_enuInit(void) } } - /* --- RX async setup --- */ + /* RX async setup */ if (pstrCfgLoc->enuRxAsyncMode == MCU_UART_ASYNC_DMA) { - /* DMA RX: continuously fills ring buffer using DMA ring wrap. - * The write address wraps at the buffer size boundary, so the - * DMA writes in circles. Application reads from the tail and - * derives the head from the DMA write pointer. */ s8 s8RxChLoc = (s8)dma_claim_unused_channel(true); strControl.as8RxDmaChannel[u8IndexLoc] = s8RxChLoc; @@ -302,16 +206,11 @@ STD_tenuResult MCU_UART_enuInit(void) } else { - /* ISR RX: enable UART RX interrupt. The combined handler - * (vUartIsrHandler) drains the FIFO into the ring buffer. */ if (u8IndexLoc == (u8)MCU_UART_INSTANCE_0) { irq_set_exclusive_handler(UART0_IRQ, vUart0IrqHandler); irq_set_enabled(UART0_IRQ, true); } - - /* Enable RX interrupt (second param). TX stays off until - * SendBuffer is called. */ uart_set_irqs_enabled(pstrUartLoc, false, true); } } @@ -333,7 +232,7 @@ STD_tenuResult MCU_UART_enuSendByte(u8 u8Instance, u8 u8Byte) } /* ========================================================================= */ -/* SEND BUFFER (NON-BLOCKING, DEFAULT) */ +/* SEND BUFFER (NON-BLOCKING) */ /* ========================================================================= */ STD_tenuResult MCU_UART_enuSendBuffer(u8 u8Instance, const u8 *pu8Data, u16 u16Length) @@ -359,42 +258,31 @@ STD_tenuResult MCU_UART_enuSendBuffer(u8 u8Instance, const u8 *pu8Data, u16 u16L if (enuModeLoc == MCU_UART_ASYNC_DMA) { - s8 s8ChannelLoc = strControl.as8TxDmaChannel[u8Instance]; + s8 s8ChLoc = strControl.as8TxDmaChannel[u8Instance]; uart_inst_t *pstrUartLoc = apstrInstances[u8Instance]; - 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)); + dma_channel_config strCfgLoc = dma_channel_get_default_config((u32)s8ChLoc); + channel_config_set_transfer_data_size(&strCfgLoc, DMA_SIZE_8); + channel_config_set_read_increment(&strCfgLoc, true); + channel_config_set_write_increment(&strCfgLoc, false); + channel_config_set_dreq(&strCfgLoc, uart_get_dreq(pstrUartLoc, true)); dma_channel_configure( - (u32)s8ChannelLoc, - &strDmaCfgLoc, - &uart_get_hw(pstrUartLoc)->dr, - pu8Data, - u16Length, - true - ); + (u32)s8ChLoc, &strCfgLoc, + &uart_get_hw(pstrUartLoc)->dr, pu8Data, u16Length, true); } else { - /* ISR mode: kick-start by filling the FIFO */ uart_inst_t *pstrUartLoc = apstrInstances[u8Instance]; - STD_tBool bFifoReady = STD_FALSE; - STD_tBool bDataLeft = STD_FALSE; - - bFifoReady = (uart_is_writable(pstrUartLoc) != 0) ? STD_TRUE : STD_FALSE; - bDataLeft = (strControl.au16TxIndex[u8Instance] < u16Length) ? STD_TRUE : STD_FALSE; + STD_tBool bFifoReady = (uart_is_writable(pstrUartLoc) != 0) ? STD_TRUE : STD_FALSE; + STD_tBool bDataLeft = (strControl.au16TxIndex[u8Instance] < u16Length) ? STD_TRUE : STD_FALSE; while ((bFifoReady == STD_TRUE) && (bDataLeft == STD_TRUE)) { - uart_putc_raw(pstrUartLoc, - pu8Data[strControl.au16TxIndex[u8Instance]]); + 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; + bDataLeft = (strControl.au16TxIndex[u8Instance] < u16Length) ? STD_TRUE : STD_FALSE; } if (bDataLeft == STD_FALSE) @@ -404,7 +292,6 @@ STD_tenuResult MCU_UART_enuSendBuffer(u8 u8Instance, const u8 *pu8Data, u16 u16L } else { - /* Enable TX interrupt for remaining bytes. Keep RX on. */ uart_set_irqs_enabled(pstrUartLoc, true, true); } } @@ -432,7 +319,6 @@ STD_tenuResult MCU_UART_enuSendBufferBlocking(u8 u8Instance, const u8 *pu8Data, { uart_putc_raw(apstrInstances[u8Instance], pu8Data[u16IndexLoc]); } - vCallTxCallback(u8Instance); } @@ -449,10 +335,10 @@ STD_tBool MCU_UART_bIsTxBusy(u8 u8Instance) } /* ========================================================================= */ -/* RECEIVE BYTE (BLOCKING) */ +/* READ BYTE (NON-BLOCKING) */ /* ========================================================================= */ -STD_tenuResult MCU_UART_enuReceiveByte(u8 u8Instance, u8 *pu8Byte) +STD_tenuResult MCU_UART_enuReadByte(u8 u8Instance, u8 *pu8Byte) { STD_tenuResult enuResultLoc = STD_OK; @@ -462,96 +348,55 @@ STD_tenuResult MCU_UART_enuReceiveByte(u8 u8Instance, u8 *pu8Byte) } else { - u16 u16HeadLoc; - u16 u16TailLoc; + u16 u16HeadLoc = u16GetRxHead(u8Instance); + u16 u16TailLoc = strControl.au16RxTail[u8Instance]; - /* Spin-wait until the ring buffer has at least one byte. - * The ring buffer is filled in the background by the RX ISR or DMA, - * so this loop will unblock as soon as a byte arrives on the wire. */ - do + if (u16HeadLoc == u16TailLoc) { - u16HeadLoc = u16GetRxHead(u8Instance); - u16TailLoc = strControl.au16RxTail[u8Instance]; - } while (u16HeadLoc == u16TailLoc); - - /* Read one byte from the tail and advance */ - *pu8Byte = strControl.aau8RxBuffer[u8Instance][u16TailLoc]; - strControl.au16RxTail[u8Instance] = (u16TailLoc + 1U) & MCU_UART_RX_BUFFER_MASK; + enuResultLoc = STD_NOK; + } + else + { + *pu8Byte = strControl.aau8RxBuffer[u8Instance][u16TailLoc]; + strControl.au16RxTail[u8Instance] = (u16TailLoc + 1U) & MCU_UART_RX_BUFFER_MASK; + } } return enuResultLoc; } /* ========================================================================= */ -/* RECEIVE BUFFER (NON-BLOCKING, DEFAULT) */ +/* READ BUFFER (NON-BLOCKING) */ /* ========================================================================= */ -STD_tenuResult MCU_UART_enuReceiveBuffer(u8 u8Instance, u8 *pu8Data, u16 u16Length) +STD_tenuResult MCU_UART_enuReadBuffer(u8 u8Instance, u8 *pu8Data, u16 u16MaxLength, u16 *pu16Read) { STD_tenuResult enuResultLoc = STD_OK; - STD_tBool bActiveLocl = strControl.abRxReqActive[u8Instance]; - if (pu8Data == STD_NULL) - { - enuResultLoc = STD_NULL_POINTER_ERROR; - } - else if (bActiveLocl == STD_TRUE) - { - /* A previous receive request is still pending */ - enuResultLoc = STD_NOK; - } - else - { - /* Register the request */ - strControl.apu8RxReqBuffer[u8Instance] = pu8Data; - strControl.au16RxReqLength[u8Instance] = u16Length; - strControl.au16RxReqIndex[u8Instance] = 0U; - strControl.abRxReqActive[u8Instance] = STD_TRUE; - - /* Try to fulfill immediately from bytes already in the ring buffer. - * If enough bytes are available, the request completes synchronously - * and the callback fires here. Otherwise it completes later when - * the ISR pushes more bytes (ISR mode) or when the application - * polls again (DMA mode). */ - vFulfillRxRequest(u8Instance); - } - - return enuResultLoc; -} - -/* ========================================================================= */ -/* RECEIVE BUFFER (BLOCKING) */ -/* ========================================================================= */ - -STD_tenuResult MCU_UART_enuReceiveBufferBlocking(u8 u8Instance, u8 *pu8Data, u16 u16Length) -{ - STD_tenuResult enuResultLoc = STD_OK; - u16 u16IndexLoc = 0U; - - if (pu8Data == STD_NULL) + if ((pu8Data == STD_NULL) || (pu16Read == STD_NULL)) { enuResultLoc = STD_NULL_POINTER_ERROR; } else { - /* Read N bytes from the ring buffer, blocking until each is available */ - while (u16IndexLoc < u16Length) + u16 u16HeadLoc = u16GetRxHead(u8Instance); + u16 u16TailLoc = strControl.au16RxTail[u8Instance]; + u16 u16CountLoc = 0U; + + while ((u16HeadLoc != u16TailLoc) && (u16CountLoc < u16MaxLength)) { - u16 u16HeadLoc = u16GetRxHead(u8Instance); - u16 u16TailLoc = strControl.au16RxTail[u8Instance]; - - /* Copy all currently available bytes */ - while ((u16HeadLoc != u16TailLoc) && (u16IndexLoc < u16Length)) - { - pu8Data[u16IndexLoc] = strControl.aau8RxBuffer[u8Instance][u16TailLoc]; - u16TailLoc = (u16TailLoc + 1U) & MCU_UART_RX_BUFFER_MASK; - u16IndexLoc++; - } - - strControl.au16RxTail[u8Instance] = u16TailLoc; + pu8Data[u16CountLoc] = strControl.aau8RxBuffer[u8Instance][u16TailLoc]; + u16TailLoc = (u16TailLoc + 1U) & MCU_UART_RX_BUFFER_MASK; + u16CountLoc++; } - vCallRxCallback(u8Instance); + strControl.au16RxTail[u8Instance] = u16TailLoc; + *pu16Read = u16CountLoc; + + if (u16CountLoc == 0U) + { + enuResultLoc = STD_NOK; + } } return enuResultLoc; @@ -573,13 +418,4 @@ STD_tBool MCU_UART_bIsRxDataAvailable(u8 u8Instance) } return bResultLoc; -} - -/* ========================================================================= */ -/* RX BUSY CHECK */ -/* ========================================================================= */ - -STD_tBool MCU_UART_bIsRxBusy(u8 u8Instance) -{ - return strControl.abRxReqActive[u8Instance]; -} +} \ No newline at end of file diff --git a/src/MCU_UART/prg/MCU_UART_priv.h b/src/MCU_UART/prg/MCU_UART_priv.h index 2fc00bb..59178c1 100644 --- a/src/MCU_UART/prg/MCU_UART_priv.h +++ b/src/MCU_UART/prg/MCU_UART_priv.h @@ -1,10 +1,7 @@ /****************************************************************************** * File: MCU_UART_priv.h * Component: MCU_UART - * Description: Private header for the MCU_UART driver. - * Contains the internal control structure for per-instance - * runtime state (TX async progress, RX ring buffer, DMA - * channels), and the extern config array declaration. + * Description: Private header — control struct and extern config array. * * Layer: MCU (hardware abstraction) - internal use only *****************************************************************************/ @@ -15,35 +12,8 @@ #include "MCU_UART.h" #include "MCU_UART_cfg.h" -/* ------------------------------------------------------------------------ */ -/* CONFIG ARRAY (EXTERN) */ -/* ------------------------------------------------------------------------ */ - 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 (struct-of-arrays). - * - * TX fields: - * apu8TxBuffer — caller's buffer being transmitted (non-blocking) - * au16TxLength — total bytes to transmit - * au16TxIndex — bytes transmitted so far (ISR mode) - * abTxBusy — STD_TRUE while async TX is in progress - * as8TxDmaChannel — DMA channel for TX (-1 if ISR mode) - * - * RX fields: - * aau8RxBuffer — per-instance ring buffer for received bytes. - * Aligned to buffer size for DMA ring mode compatibility. - * au16RxHead — write index (ISR writes here, DMA updates via hw pointer) - * au16RxTail — read index (application reads from here) - * as8RxDmaChannel — DMA channel for RX (-1 if ISR mode) - */ typedef struct { /* TX state */ @@ -53,18 +23,12 @@ typedef struct STD_tBool abTxBusy[MCU_UART_NUM_INSTANCES]; s8 as8TxDmaChannel[MCU_UART_NUM_INSTANCES]; - /* RX ring buffer state */ + /* RX ring buffer — filled by ISR or DMA in the background */ u8 aau8RxBuffer[MCU_UART_NUM_INSTANCES][MCU_UART_RX_BUFFER_SIZE] __attribute__((aligned(MCU_UART_RX_BUFFER_SIZE))); u16 au16RxHead[MCU_UART_NUM_INSTANCES]; u16 au16RxTail[MCU_UART_NUM_INSTANCES]; s8 as8RxDmaChannel[MCU_UART_NUM_INSTANCES]; - - /* RX async request state (for non-blocking ReceiveBuffer) */ - u8 *apu8RxReqBuffer[MCU_UART_NUM_INSTANCES]; /**< caller's buffer */ - u16 au16RxReqLength[MCU_UART_NUM_INSTANCES]; /**< total bytes requested */ - u16 au16RxReqIndex[MCU_UART_NUM_INSTANCES]; /**< bytes fulfilled so far */ - STD_tBool abRxReqActive[MCU_UART_NUM_INSTANCES]; /**< is a request pending? */ } MCU_UART_tstrControl; -#endif /* MCU_UART_PRIV_H */ \ No newline at end of file +#endif /* MCU_UART_PRIV_H */ diff --git a/src/MCU_USB/inc/MCU_USB.h b/src/MCU_USB/inc/MCU_USB.h index 0947812..9bac727 100644 --- a/src/MCU_USB/inc/MCU_USB.h +++ b/src/MCU_USB/inc/MCU_USB.h @@ -1,12 +1,10 @@ /****************************************************************************** * File: MCU_USB.h * Component: MCU_USB - * Description: Public interface for the MCU USB driver component. - * This header exposes the functions and types that other - * components are allowed to use to send and receive data over - * the RP2040 USB-CDC (virtual serial port) interface. From the - * host computer's perspective, the Pico appears as a regular - * serial device (/dev/tty.usbmodem* on macOS, COMx on Windows). + * Description: Public interface for the MCU USB-CDC driver. + * TX: SendByte, SendBuffer (both fire-and-forget via putchar_raw). + * RX: background ring buffer drained lazily from the SDK's + * stdio layer. ReadByte / bIsRxDataAvailable read from it. * * Layer: MCU (hardware abstraction) *****************************************************************************/ @@ -14,76 +12,57 @@ #ifndef MCU_USB_H #define MCU_USB_H -/* STD_TYPES brings in the fixed-width integer typedefs (u8, u32) and the - * STD_tenuResult enum used to report success/failure from every function. */ #include "STD_TYPES.h" -#define MCU_USB_WAIT_FOR_CONNECTION_DISABLED 0U +#define MCU_USB_WAIT_FOR_CONNECTION_DISABLED 0U #define MCU_USB_WAIT_FOR_CONNECTION_ENABLED 1U + /* ------------------------------------------------------------------------ */ -/* PUBLIC API */ +/* TX PUBLIC API */ /* ------------------------------------------------------------------------ */ -/** - * @brief Initialize the USB-CDC interface. - * - * Sets up the RP2040 USB peripheral and the TinyUSB CDC device so the - * board enumerates as a virtual serial port on the host. If the config - * macro MCU_USB_WAIT_FOR_CONNECTION is MCU_USB_WAIT_FOR_CONNECTION_ENABLED, - * this function blocks until the host opens the port (so early bytes are - * not lost), subject to MCU_USB_CONNECTION_TIMEOUT_MS in MCU_USB_cfg.h. - * If the timeout elapses before the host connects, returns STD_NOK. - * - * Must be called exactly once, before any Send/Receive function. - * - * @return STD_OK on success (USB-CDC initialized, host connected if wait enabled), - * STD_NOK on init failure or connection timeout. - */ STD_tenuResult MCU_USB_enuInit(void); -/** - * @brief Send a single byte over USB-CDC. - * - * Blocks until the byte has been handed off to the USB stack or the - * transmit timeout (MCU_USB_TRANSMIT_TIMEOUT_MS) elapses. - * - * @param u8Byte The byte to transmit. - * @return STD_OK on success, - * STD_NOK on transmit failure or timeout. - */ STD_tenuResult MCU_USB_enuSendByte(u8 u8Byte); -/** - * @brief Send a buffer of bytes over USB-CDC. - * - * Transmits u32Length bytes starting at pu8Data. Blocks until all bytes - * are sent or the transmit timeout elapses. The buffer is not modified. - * - * @param pu8Data Pointer to the byte buffer to transmit. 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 transmit failure or timeout. - */ STD_tenuResult MCU_USB_enuSendBuffer(const u8 *pu8Data, u16 u16Length); -/** - * @brief Receive one byte over USB-CDC (blocking). - * - * Blocks until a byte arrives from the host serial monitor. - * - * @param pu8Byte Pointer to store the received byte. Must not be NULL. - * @return STD_OK byte received, - * STD_NULL_POINTER_ERROR if pu8Byte is NULL. - */ -STD_tenuResult MCU_USB_enuReceiveByte(u8 *pu8Byte); +/* ------------------------------------------------------------------------ */ +/* RX PUBLIC API */ +/* ------------------------------------------------------------------------ */ /** - * @brief Check if USB-CDC has data waiting to be read. + * @brief Read one byte from the USB RX ring buffer (non-blocking). * - * @return STD_TRUE if at least one byte is available, - * STD_FALSE if no data waiting. + * Internally drains any pending bytes from the SDK's stdio layer into + * the ring buffer before checking. Returns immediately. + * + * @param pu8Byte Pointer to store the received byte. + * @return STD_OK byte read, + * STD_NULL_POINTER_ERROR if pu8Byte is NULL, + * STD_NOK if no data available. + */ +STD_tenuResult MCU_USB_enuReadByte(u8 *pu8Byte); + +/** + * @brief Read up to u16MaxLength bytes from the USB RX ring buffer. + * + * @param pu8Data Output buffer. + * @param u16MaxLength Maximum bytes to read. + * @param pu16Read Actual bytes read. + * @return STD_OK at least one byte read, + * STD_NULL_POINTER_ERROR if pu8Data or pu16Read is NULL, + * STD_NOK if no data available. + */ +STD_tenuResult MCU_USB_enuReadBuffer(u8 *pu8Data, u16 u16MaxLength, u16 *pu16Read); + +/** + * @brief Check if USB RX data is available. + * + * Drains pending bytes from the SDK first, then checks the ring buffer. + * + * @return STD_TRUE if data available, STD_FALSE if empty. */ STD_tBool MCU_USB_bIsRxDataAvailable(void); -#endif /* MCU_USB_H */ +#endif /* MCU_USB_H */ \ No newline at end of file diff --git a/src/MCU_USB/prg/MCU_USB_prg.c b/src/MCU_USB/prg/MCU_USB_prg.c index 3fbb2ee..9256900 100644 --- a/src/MCU_USB/prg/MCU_USB_prg.c +++ b/src/MCU_USB/prg/MCU_USB_prg.c @@ -1,114 +1,139 @@ /****************************************************************************** * File: MCU_USB_prg.c * Component: MCU_USB - * Description: Program (implementation) file for the MCU_USB driver. - * Contains the actual implementations of the public functions - * declared in MCU_USB.h. Wraps the Pico SDK's USB-CDC / stdio - * facilities to provide a simple send/receive API for the - * virtual serial port exposed over the Pico's USB connection. + * Description: USB-CDC driver. TX via putchar_raw (fire-and-forget). + * RX via internal ring buffer, lazily drained from the SDK's + * stdio layer on every ReadByte / ReadBuffer / IsDataAvailable + * call. No separate RX task or interrupt needed — the SDK's + * TinyUSB background task fills stdio internally, and we pull + * from stdio into our ring buffer on demand. * * Layer: MCU (hardware abstraction) *****************************************************************************/ + #include "STD_TYPES.h" -#include "pico/stdio_usb.h" /* stdio_usb_init(), stdio_usb_connected() */ -#include "pico/stdio.h" /* putchar_raw() - writes one byte into the stdio driver chain */ -#include "pico/time.h" /* absolute_time_t, make_timeout_time_ms(), time_reached() */ +#include "pico/stdio_usb.h" +#include "pico/stdio.h" +#include "pico/time.h" + #include "MCU_USB.h" #include "MCU_USB_priv.h" #include "MCU_USB_cfg.h" +/* ------------------------------------------------------------------------ */ +/* RX RING BUFFER */ +/* ------------------------------------------------------------------------ */ + +/** @brief RX ring buffer size — must be power of 2. */ +#define USB_RX_BUFFER_SIZE_BITS 6U +#define USB_RX_BUFFER_SIZE (1U << USB_RX_BUFFER_SIZE_BITS) +#define USB_RX_BUFFER_MASK (USB_RX_BUFFER_SIZE - 1U) + +static u8 au8RxBuffer[USB_RX_BUFFER_SIZE]; +static u16 u16RxHead = 0U; +static u16 u16RxTail = 0U; + +/** + * @brief Drain any available bytes from the SDK's stdio into our ring buffer. + * + * Called lazily from ReadByte, ReadBuffer, and bIsRxDataAvailable. + * getchar_timeout_us(0) is non-blocking — returns PICO_ERROR_TIMEOUT (-1) + * immediately if no data. We keep pulling until the SDK has nothing left + * or our ring buffer is full. + */ +static void vDrainStdio(void) +{ + s32 s32ByteLoc; + u16 u16NextHeadLoc; + + s32ByteLoc = (s32)getchar_timeout_us(0); + + while (s32ByteLoc >= 0) + { + u16NextHeadLoc = (u16RxHead + 1U) & USB_RX_BUFFER_MASK; + + /* If the ring buffer is full, stop draining (oldest data preserved, + * newest data from SDK is lost). Caller should read faster. */ + if (u16NextHeadLoc == u16RxTail) + { + /* Buffer full — can't store this byte. Break out. */ + break; + } + + au8RxBuffer[u16RxHead] = (u8)s32ByteLoc; + u16RxHead = u16NextHeadLoc; + + s32ByteLoc = (s32)getchar_timeout_us(0); + } +} + +/* ========================================================================= */ +/* INIT */ +/* ========================================================================= */ + STD_tenuResult MCU_USB_enuInit(void) { STD_tenuResult enuResultLoc = STD_OK; STD_tBool bSdkInitSuccess = STD_FALSE; - /* Call the Pico SDK's USB-only stdio init. This brings up the TinyUSB - * device stack, registers the USB-CDC stdio driver, and starts the - * background task that services USB events. Returns true on success. */ bSdkInitSuccess = (stdio_usb_init() != 0) ? STD_TRUE : STD_FALSE; if (bSdkInitSuccess == STD_FALSE) - { - enuResultLoc = STD_NOK; /* Initialization failed */ - }else + { + enuResultLoc = STD_NOK; + } + else { #if MCU_USB_WAIT_FOR_CONNECTION == MCU_USB_WAIT_FOR_CONNECTION_ENABLED - /* Wait for the host to open the CDC port, with a timeout. */ absolute_time_t absTimeout = make_timeout_time_ms(MCU_USB_CONNECTION_TIMEOUT_MS); STD_tBool bHostOpen = STD_FALSE; STD_tBool bTimeoutReached = STD_FALSE; do { - /* Yield for 10 ms between checks. This serves two purposes: - * 1. Avoids burning 100% CPU on a busy-wait spin loop - * 2. Gives the TinyUSB background task time to process USB - * enumeration events — without yielding, the USB stack - * may not advance and the host connection is delayed. - * 10 ms matches the interval used by the Pico SDK's own - * stdio_usb_init() connection-wait implementation. */ sleep_ms(10); - - /* Update status variables — avoid function calls in the - * while condition for readability and debuggability */ bHostOpen = (stdio_usb_connected() != 0) ? STD_TRUE : STD_FALSE; bTimeoutReached = (time_reached(absTimeout) != 0) ? STD_TRUE : STD_FALSE; } while ((bHostOpen == STD_FALSE) && (bTimeoutReached == STD_FALSE)); - /* If we exited the loop because of timeout rather than a successful - * connection, report failure so the caller knows the host never - * opened the port within the configured window. */ if (bHostOpen == STD_FALSE) { enuResultLoc = STD_NOK; } #endif } - return enuResultLoc; /* Return the result */ + + return enuResultLoc; } +/* ========================================================================= */ +/* SEND BYTE */ +/* ========================================================================= */ + STD_tenuResult MCU_USB_enuSendByte(u8 u8Byte) { STD_tenuResult enuResultLoc = STD_OK; - /* putchar_raw is the stdio framework's "push one byte into the driver - * chain" primitive. It is declared as `int putchar_raw(int c)` because - * the C stdio family uses EOF (-1) as a sentinel return value. Passing - * u8Byte directly relies on the implicit widening conversion u8 -> int, - * which is always safe (every u8 value fits in an int) and deliberately - * keeps native C type names out of our code. - * - * Note on semantics: putchar_raw is fire-and-forget at this layer - it - * queues the byte into the USB stdio driver and returns immediately. - * The actual USB transfer happens in the TinyUSB background task. There - * is no way to detect a transmit failure from this call, so we always - * return STD_OK. When we need real delivery guarantees, we will upgrade - * this to tud_cdc_write_char + tud_cdc_write_flush. */ putchar_raw(u8Byte); return enuResultLoc; } +/* ========================================================================= */ +/* SEND BUFFER */ +/* ========================================================================= */ + STD_tenuResult MCU_USB_enuSendBuffer(const u8 *pu8Data, u16 u16Length) { STD_tenuResult enuResultLoc = STD_OK; u16 u16IndexLoc; - /* Guard against null pointer dereference. On the RP2040 (Cortex-M0+), - * reading address 0x00000000 does NOT fault — it silently reads from - * the beginning of flash (the vector table), which means the firmware - * would send garbage bytes over USB instead of crashing. The explicit - * check catches the mistake at the source with a clear error code. */ if (pu8Data == STD_NULL) { enuResultLoc = STD_NULL_POINTER_ERROR; } else { - /* Send each byte individually via putchar_raw. Same fire-and-forget - * semantics as MCU_USB_enuSendByte — bytes are queued into the USB - * stdio driver and transmitted by the TinyUSB background task. - * No per-byte error detection is possible at this layer. */ for (u16IndexLoc = 0U; u16IndexLoc < u16Length; u16IndexLoc++) { putchar_raw(pu8Data[u16IndexLoc]); @@ -119,21 +144,10 @@ STD_tenuResult MCU_USB_enuSendBuffer(const u8 *pu8Data, u16 u16Length) } /* ========================================================================= */ -/* RECEIVE BYTE (BLOCKING) */ +/* READ BYTE (NON-BLOCKING) */ /* ========================================================================= */ -/** - * @brief Internal cached-byte state for bIsRxDataAvailable. - * - * The Pico SDK has no "peek" function for USB-CDC — the only way to check - * if data is available is to try reading. If a read succeeds during - * bIsRxDataAvailable, the byte is cached here so ReceiveByte can return it - * without a second read. bHasCachedByte tracks whether the cache is valid. - */ -static STD_tBool bHasCachedByte = STD_FALSE; -static u8 u8CachedByte = 0U; - -STD_tenuResult MCU_USB_enuReceiveByte(u8 *pu8Byte) +STD_tenuResult MCU_USB_enuReadByte(u8 *pu8Byte) { STD_tenuResult enuResultLoc = STD_OK; @@ -143,25 +157,53 @@ STD_tenuResult MCU_USB_enuReceiveByte(u8 *pu8Byte) } else { - /* Check if bIsRxDataAvailable already cached a byte */ - if (bHasCachedByte == STD_TRUE) + /* Pull any pending data from SDK into our ring buffer */ + vDrainStdio(); + + if (u16RxHead == u16RxTail) { - *pu8Byte = u8CachedByte; - bHasCachedByte = STD_FALSE; + enuResultLoc = STD_NOK; } else { - /* Block until a byte arrives. getchar_timeout_us with 0 is - * non-blocking — loop until we get a real byte. PICO_ERROR_TIMEOUT - * is returned as a negative value when no data is available. */ - s32 s32ResultLoc; + *pu8Byte = au8RxBuffer[u16RxTail]; + u16RxTail = (u16RxTail + 1U) & USB_RX_BUFFER_MASK; + } + } - do - { - s32ResultLoc = (s32)getchar_timeout_us(0); - } while (s32ResultLoc < 0); + return enuResultLoc; +} - *pu8Byte = (u8)s32ResultLoc; +/* ========================================================================= */ +/* READ BUFFER (NON-BLOCKING) */ +/* ========================================================================= */ + +STD_tenuResult MCU_USB_enuReadBuffer(u8 *pu8Data, u16 u16MaxLength, u16 *pu16Read) +{ + STD_tenuResult enuResultLoc = STD_OK; + + if ((pu8Data == STD_NULL) || (pu16Read == STD_NULL)) + { + enuResultLoc = STD_NULL_POINTER_ERROR; + } + else + { + vDrainStdio(); + + u16 u16CountLoc = 0U; + + while ((u16RxHead != u16RxTail) && (u16CountLoc < u16MaxLength)) + { + pu8Data[u16CountLoc] = au8RxBuffer[u16RxTail]; + u16RxTail = (u16RxTail + 1U) & USB_RX_BUFFER_MASK; + u16CountLoc++; + } + + *pu16Read = u16CountLoc; + + if (u16CountLoc == 0U) + { + enuResultLoc = STD_NOK; } } @@ -176,24 +218,12 @@ STD_tBool MCU_USB_bIsRxDataAvailable(void) { STD_tBool bResultLoc = STD_FALSE; - if (bHasCachedByte == STD_TRUE) - { - /* Already have a cached byte from a previous check */ - bResultLoc = STD_TRUE; - } - else - { - /* Try a non-blocking read. If successful, cache the byte so - * the next ReceiveByte call can return it immediately. */ - s32 s32ResultLoc = (s32)getchar_timeout_us(0); + vDrainStdio(); - if (s32ResultLoc >= 0) - { - u8CachedByte = (u8)s32ResultLoc; - bHasCachedByte = STD_TRUE; - bResultLoc = STD_TRUE; - } + if (u16RxHead != u16RxTail) + { + bResultLoc = STD_TRUE; } return bResultLoc; -} \ No newline at end of file +}