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.
This commit is contained in:
Mohamed Salem 2026-04-13 01:50:58 +02:00
parent 863da8f75c
commit 3d5e63c790
11 changed files with 342 additions and 686 deletions

View File

@ -144,7 +144,7 @@ void APP_CLSW_vRunnable(void)
while (bDataAvailableLoc == STD_TRUE) while (bDataAvailableLoc == STD_TRUE)
{ {
/* Read one byte */ /* 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) if (enuRxResultLoc == STD_OK)
{ {

View File

@ -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 * (u8 u8Instance as the first parameter), so no wrapper is needed
* for TX or RX. Its functions can be assigned directly. */ * 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) --- */
/** static STD_tenuResult vUsbReadByte(u8 u8Instance, u8 *pu8Byte)
* @brief Wrapper for MCU_USB_enuReceiveByte to match HAL_COM_tpfReceiveByte.
*/
static STD_tenuResult vUsbReceiveByte(u8 u8Instance, u8 *pu8Byte)
{ {
(void)u8Instance; (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) static STD_tBool vUsbIsRxDataAvailable(u8 u8Instance)
{ {
(void)u8Instance; (void)u8Instance;
@ -98,7 +98,8 @@ const HAL_COM_tstrChannelConfig HAL_COM_astrChannelConfig[HAL_COM_NUM_CHANNELS]
{ {
.pfSendByte = vUsbSendByte, .pfSendByte = vUsbSendByte,
.pfSendBuffer = vUsbSendBuffer, .pfSendBuffer = vUsbSendBuffer,
.pfReceiveByte = vUsbReceiveByte, .pfReadByte = vUsbReadByte,
.pfReadBuffer = vUsbReadBuffer,
.pfIsRxDataAvailable = vUsbIsRxDataAvailable, .pfIsRxDataAvailable = vUsbIsRxDataAvailable,
.u8Instance = 0U, .u8Instance = 0U,
}, },

View File

@ -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); typedef STD_tenuResult (*HAL_COM_tpfSendBuffer)(u8 u8Instance, const u8 *pu8Data, u16 u16Length);
/** /**
* @brief Generic receive-byte function pointer type (blocking). * @brief Generic read-byte function pointer type (non-blocking).
*
* @param u8Instance Peripheral instance index.
* @param pu8Byte Pointer to store the received byte.
* @return STD_tenuResult
*/ */
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. * @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); 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_tpfSendByte pfSendByte; /**< Driver's send-byte function */
HAL_COM_tpfSendBuffer pfSendBuffer; /**< Driver's send-buffer 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 */ HAL_COM_tpfIsRxDataAvailable pfIsRxDataAvailable; /**< Driver's RX-available check */
u8 u8Instance; /**< Peripheral instance to pass through */ u8 u8Instance; /**< Peripheral instance to pass through */
} HAL_COM_tstrChannelConfig; } 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); STD_tenuResult HAL_COM_enuSendBuffer(u8 u8Channel, const u8 *pu8Data, u16 u16Length);
/** /**
* @brief Receive one byte through the specified channel (blocking). * @brief Read one byte from the specified channel (non-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.
*/ */
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. * @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); STD_tBool HAL_COM_bIsRxDataAvailable(u8 u8Channel);

View File

@ -70,12 +70,26 @@ STD_tenuResult HAL_COM_enuSendBuffer(u8 u8Channel, const u8 *pu8Data, u16 u16Len
/* RECEIVE BYTE (BLOCKING) */ /* 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; STD_tenuResult enuResultLoc = STD_OK;
const HAL_COM_tstrChannelConfig *pstrCfgLoc = &HAL_COM_astrChannelConfig[u8Channel]; 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; return enuResultLoc;
} }

View File

@ -1,35 +1,14 @@
/****************************************************************************** /******************************************************************************
* File: MCU_UART_cfg.c * File: MCU_UART_cfg.c
* Component: MCU_UART * Component: MCU_UART
* Description: Configuration implementation for the MCU_UART driver. * Description: Configuration array definition 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.
* *
* Layer: MCU (hardware abstraction) - configuration * 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.h"
#include "MCU_UART_cfg.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] = const MCU_UART_tstrConfig MCU_UART_astrConfig[MCU_UART_NUM_INSTANCES] =
{ {
[MCU_UART_INSTANCE_0] = [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, .enuTxAsyncMode = MCU_UART_0_TX_ASYNC_MODE,
.pfTxCompleteCallback = MCU_UART_0_TX_COMPLETE_CALLBACK, .pfTxCompleteCallback = MCU_UART_0_TX_COMPLETE_CALLBACK,
.enuRxAsyncMode = MCU_UART_0_RX_ASYNC_MODE, .enuRxAsyncMode = MCU_UART_0_RX_ASYNC_MODE,
.pfRxCallback = MCU_UART_0_RX_CALLBACK,
}, },
}; };

View File

@ -1,10 +1,9 @@
/****************************************************************************** /******************************************************************************
* File: MCU_UART_cfg.h * File: MCU_UART_cfg.h
* Component: MCU_UART * Component: MCU_UART
* Description: Configuration header for the MCU_UART driver. * Description: Configuration for the MCU_UART driver. Defines instances,
* Defines which UART instances are active, their GPIO pin * pin assignments, baud rates, data format, TX/RX async modes,
* assignments, baud rates, data format, TX/RX async mechanisms, * and RX buffer sizing.
* callbacks, and RX buffer sizing.
* *
* Layer: MCU (hardware abstraction) - configuration * Layer: MCU (hardware abstraction) - configuration
*****************************************************************************/ *****************************************************************************/
@ -28,54 +27,25 @@ typedef enum
/* RX RING BUFFER SIZING */ /* RX RING BUFFER SIZING */
/* ------------------------------------------------------------------------ */ /* ------------------------------------------------------------------------ */
/** @brief Number of address bits for the RX ring buffer. The actual buffer /** @brief Number of address bits for the RX ring buffer (2^N bytes).
* size is 2^N bytes. Must be a power of 2 because DMA ring mode * Must be power of 2 for DMA ring wrap.
* wraps the write address at a 2^N boundary. Applies to all * 5 = 32, 6 = 64, 7 = 128, 8 = 256. */
* instances. Change this single value to resize the buffer:
* 5 = 32 bytes, 6 = 64 bytes, 7 = 128 bytes, 8 = 256 bytes. */
#define MCU_UART_RX_BUFFER_SIZE_BITS 6U #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) #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) #define MCU_UART_RX_BUFFER_MASK (MCU_UART_RX_BUFFER_SIZE - 1U)
/* ------------------------------------------------------------------------ */ /* ------------------------------------------------------------------------ */
/* INSTANCE 0 CONFIGURATION */ /* INSTANCE 0 CONFIGURATION */
/* ------------------------------------------------------------------------ */ /* ------------------------------------------------------------------------ */
/** @brief GPIO pin for UART0 TX. GP0 is the default for uart0. */
#define MCU_UART_0_TX_PIN 0U #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 #define MCU_UART_0_RX_PIN 1U
/** @brief Baud rate for UART0 in bits per second. */
#define MCU_UART_0_BAUD_RATE 115200U #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 #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 #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 #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 #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 #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 #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. */ #endif /* MCU_UART_CFG_H */
#define MCU_UART_0_RX_CALLBACK STD_NULL
#endif /* MCU_UART_CFG_H */

View File

@ -2,20 +2,11 @@
* File: MCU_UART.h * File: MCU_UART.h
* Component: MCU_UART * Component: MCU_UART
* Description: Public interface for the MCU UART driver component. * 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: * TX: SendByte (blocking), SendBuffer (non-blocking DMA/ISR),
* - SendBuffer (default, non-blocking): starts an async * SendBufferBlocking.
* transfer via DMA or ISR and returns immediately. * RX: background ring buffer filled by ISR or DMA.
* - SendBufferBlocking: loops byte-by-byte and returns * ReadByte / ReadBuffer read from the buffer (non-blocking).
* 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) * Layer: MCU (hardware abstraction)
*****************************************************************************/ *****************************************************************************/
@ -23,27 +14,19 @@
#ifndef MCU_UART_H #ifndef MCU_UART_H
#define 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" #include "STD_TYPES.h"
/* ------------------------------------------------------------------------ */ /* ------------------------------------------------------------------------ */
/* CONFIGURATION VALUE TYPES */ /* CONFIGURATION VALUE TYPES */
/* ------------------------------------------------------------------------ */ /* ------------------------------------------------------------------------ */
/**
* @brief Parity mode options for UART data framing.
*/
typedef enum typedef enum
{ {
MCU_UART_PARITY_NONE = 0U, /**< No parity bit transmitted */ MCU_UART_PARITY_NONE = 0U,
MCU_UART_PARITY_EVEN, /**< Even parity */ MCU_UART_PARITY_EVEN,
MCU_UART_PARITY_ODD /**< Odd parity */ MCU_UART_PARITY_ODD
} MCU_UART_tenuParity; } MCU_UART_tenuParity;
/**
* @brief Number of data bits per UART frame.
*/
typedef enum typedef enum
{ {
MCU_UART_DATA_BITS_5 = 5U, MCU_UART_DATA_BITS_5 = 5U,
@ -52,184 +35,90 @@ typedef enum
MCU_UART_DATA_BITS_8 = 8U MCU_UART_DATA_BITS_8 = 8U
} MCU_UART_tenuDataBits; } MCU_UART_tenuDataBits;
/**
* @brief Number of stop bits per UART frame.
*/
typedef enum typedef enum
{ {
MCU_UART_STOP_BITS_1 = 1U, MCU_UART_STOP_BITS_1 = 1U,
MCU_UART_STOP_BITS_2 = 2U MCU_UART_STOP_BITS_2 = 2U
} MCU_UART_tenuStopBits; } 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 typedef enum
{ {
MCU_UART_ASYNC_DMA = 0U, /**< Use DMA for non-blocking TX */ MCU_UART_ASYNC_DMA = 0U,
MCU_UART_ASYNC_ISR /**< Use UART TX interrupt for non-blocking TX */ MCU_UART_ASYNC_ISR
} MCU_UART_tenuAsyncMode; } MCU_UART_tenuAsyncMode;
/* ------------------------------------------------------------------------ */ /* ------------------------------------------------------------------------ */
/* CONFIGURATION STRUCTURE */ /* 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 typedef struct
{ {
u8 u8TxPin; /**< GPIO number for TX */ u8 u8TxPin;
u8 u8RxPin; /**< GPIO number for RX */ u8 u8RxPin;
u32 u32BaudRate; /**< Baud rate in bps */ u32 u32BaudRate;
MCU_UART_tenuDataBits enuDataBits; /**< Data bits per frame */ MCU_UART_tenuDataBits enuDataBits;
MCU_UART_tenuStopBits enuStopBits; /**< Stop bits per frame */ MCU_UART_tenuStopBits enuStopBits;
MCU_UART_tenuParity enuParity; /**< Parity mode */ MCU_UART_tenuParity enuParity;
MCU_UART_tenuAsyncMode enuTxAsyncMode; /**< DMA or ISR for non-blocking TX */ MCU_UART_tenuAsyncMode enuTxAsyncMode;
STD_tpfCallbackFunc pfTxCompleteCallback; /**< Called when TX finishes. STD_NULL to ignore. */ STD_tpfCallbackFunc pfTxCompleteCallback;
MCU_UART_tenuAsyncMode enuRxAsyncMode; /**< DMA or ISR for RX data capture */ MCU_UART_tenuAsyncMode enuRxAsyncMode;
STD_tpfCallbackFunc pfRxCallback; /**< Called per byte received (ISR mode only).
DMA mode = polling only, callback ignored.
STD_NULL to ignore. */
} MCU_UART_tstrConfig; } 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, * The ring buffer is filled in the background by ISR or DMA.
* assigns GPIO pins, configures data format, claims DMA channel (if DMA * Returns immediately STD_OK with the byte, or STD_NOK if empty.
* 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 u8Instance UART instance index.
* @param pu8Data Pointer to buffer. Must not be NULL. Must stay valid. * @param pu8Byte Pointer to store the received byte.
* @param u16Length Number of bytes to transmit. * @return STD_OK byte read,
* @return STD_OK transfer started, * STD_NULL_POINTER_ERROR if pu8Byte is NULL,
* STD_NULL_POINTER_ERROR if pu8Data is NULL, * STD_NOK if ring buffer is empty.
* STD_NOK if a transfer is already in progress.
*/ */
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 * Copies as many bytes as are currently available (up to u16MaxLength)
* synchronously at the end (if configured, not NULL). * 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 u8Instance UART instance index.
* @param pu8Data Pointer to buffer. Must not be NULL. * @param pu8Data Pointer to output buffer.
* @param u16Length Number of bytes to transmit. * @param u16MaxLength Maximum bytes to read.
* @return STD_OK on success, * @param pu16Read Pointer to store actual bytes read. Must not be NULL.
* STD_NULL_POINTER_ERROR if pu8Data is NULL, * @return STD_OK at least one byte read,
* STD_NOK on failure. * 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. * @param u8Instance UART instance index.
* @return STD_TRUE if a non-blocking SendBuffer is still transmitting, * @return STD_TRUE if at least one byte available, STD_FALSE if empty.
* 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.
*/ */
STD_tBool MCU_UART_bIsRxDataAvailable(u8 u8Instance); 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 */ #endif /* MCU_UART_H */

View File

@ -1,13 +1,9 @@
/****************************************************************************** /******************************************************************************
* File: MCU_UART_prg.c * File: MCU_UART_prg.c
* Component: MCU_UART * Component: MCU_UART
* Description: Program (implementation) file for the MCU_UART driver. * Description: TX: blocking + non-blocking (DMA or ISR) with callback.
* TX: blocking + non-blocking (DMA or ISR) with callback. * RX: ISR or DMA fills ring buffer in background. ReadByte
* RX: ISR or DMA fills a ring buffer in the background. * and ReadBuffer read from the buffer non-blocking.
* 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.
* *
* Layer: MCU (hardware abstraction) * Layer: MCU (hardware abstraction)
*****************************************************************************/ *****************************************************************************/
@ -25,11 +21,7 @@
/* INSTANCE LOOKUP TABLE */ /* INSTANCE LOOKUP TABLE */
/* ------------------------------------------------------------------------ */ /* ------------------------------------------------------------------------ */
static uart_inst_t * const apstrInstances[] = static uart_inst_t * const apstrInstances[] = { uart0, uart1 };
{
uart0,
uart1,
};
/* ------------------------------------------------------------------------ */ /* ------------------------------------------------------------------------ */
/* RUNTIME STATE */ /* RUNTIME STATE */
@ -51,31 +43,17 @@ static void vCallTxCallback(u8 u8Instance)
} }
} }
static void vCallRxCallback(u8 u8Instance) /** @brief Get current RX head. DMA mode derives from hw write pointer. */
{
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.
*/
static u16 u16GetRxHead(u8 u8Instance) static u16 u16GetRxHead(u8 u8Instance)
{ {
u16 u16HeadLoc; u16 u16HeadLoc;
if (MCU_UART_astrConfig[u8Instance].enuRxAsyncMode == MCU_UART_ASYNC_DMA) if (MCU_UART_astrConfig[u8Instance].enuRxAsyncMode == MCU_UART_ASYNC_DMA)
{ {
s8 s8RxChLoc = strControl.as8RxDmaChannel[u8Instance]; s8 s8ChLoc = strControl.as8RxDmaChannel[u8Instance];
u32 u32WriteAddrLoc = (u32)dma_channel_hw_addr((u32)s8RxChLoc)->write_addr; u32 u32WrLoc = (u32)dma_channel_hw_addr((u32)s8ChLoc)->write_addr;
u32 u32BufferBaseLoc = (u32)(&strControl.aau8RxBuffer[u8Instance][0]); u32 u32BaseLoc = (u32)(&strControl.aau8RxBuffer[u8Instance][0]);
u16HeadLoc = (u16)((u32WriteAddrLoc - u32BufferBaseLoc) & MCU_UART_RX_BUFFER_MASK); u16HeadLoc = (u16)((u32WrLoc - u32BaseLoc) & MCU_UART_RX_BUFFER_MASK);
} }
else else
{ {
@ -85,52 +63,18 @@ static u16 u16GetRxHead(u8 u8Instance)
return u16HeadLoc; 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) */ /* TX DMA IRQ HANDLER (INSTANCE 0) */
/* ------------------------------------------------------------------------ */ /* ------------------------------------------------------------------------ */
static void vTxDmaIrqHandler0(void) static void vTxDmaIrqHandler0(void)
{ {
s8 s8ChannelLoc = strControl.as8TxDmaChannel[MCU_UART_INSTANCE_0]; s8 s8ChLoc = strControl.as8TxDmaChannel[MCU_UART_INSTANCE_0];
u32 u32StatusLoc; u32 u32StatusLoc = dma_channel_get_irq0_status((u32)s8ChLoc);
u32StatusLoc = dma_channel_get_irq0_status((u32)s8ChannelLoc);
if (u32StatusLoc != 0U) 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; strControl.abTxBusy[MCU_UART_INSTANCE_0] = STD_FALSE;
vCallTxCallback((u8)MCU_UART_INSTANCE_0); vCallTxCallback((u8)MCU_UART_INSTANCE_0);
} }
@ -140,53 +84,31 @@ static void vTxDmaIrqHandler0(void)
/* UART ISR HANDLER (RX + TX) */ /* 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) static void vUartIsrHandler(u8 u8Instance)
{ {
uart_inst_t *pstrUartLoc = apstrInstances[u8Instance]; uart_inst_t *pstrUartLoc = apstrInstances[u8Instance];
/* --- RX: drain FIFO into ring buffer --- */ /* --- 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 (bReadableLoc == STD_TRUE)
while (bRxReadable == STD_TRUE)
{ {
u8 u8ByteLoc = (u8)uart_getc(pstrUartLoc); u8 u8ByteLoc = (u8)uart_getc(pstrUartLoc);
u16 u16HeadLoc = strControl.au16RxHead[u8Instance]; u16 u16HeadLoc = strControl.au16RxHead[u8Instance];
/* Push byte into ring buffer */
strControl.aau8RxBuffer[u8Instance][u16HeadLoc] = u8ByteLoc; strControl.aau8RxBuffer[u8Instance][u16HeadLoc] = u8ByteLoc;
strControl.au16RxHead[u8Instance] = (u16HeadLoc + 1U) & MCU_UART_RX_BUFFER_MASK; 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 */ /* --- TX: fill FIFO from buffer (if TX ISR active) --- */
if (strControl.abRxReqActive[u8Instance] == STD_TRUE)
{
vFulfillRxRequest(u8Instance);
}
/* --- TX: fill FIFO from buffer (if TX is active in ISR mode) --- */
STD_tBool bTxBusyLoc = strControl.abTxBusy[u8Instance]; STD_tBool bTxBusyLoc = strControl.abTxBusy[u8Instance];
if (bTxBusyLoc == STD_TRUE) if (bTxBusyLoc == STD_TRUE)
{ {
STD_tBool bFifoReady = STD_FALSE; STD_tBool bFifoReady = (uart_is_writable(pstrUartLoc) != 0) ? STD_TRUE : STD_FALSE;
STD_tBool bDataLeft = STD_FALSE; STD_tBool bDataLeft = (strControl.au16TxIndex[u8Instance] < strControl.au16TxLength[u8Instance]) ? STD_TRUE : STD_FALSE;
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)) while ((bFifoReady == STD_TRUE) && (bDataLeft == STD_TRUE))
{ {
@ -195,12 +117,11 @@ static void vUartIsrHandler(u8 u8Instance)
strControl.au16TxIndex[u8Instance]++; strControl.au16TxIndex[u8Instance]++;
bFifoReady = (uart_is_writable(pstrUartLoc) != 0) ? STD_TRUE : STD_FALSE; 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) if (bDataLeft == STD_FALSE)
{ {
/* Disable TX interrupt, keep RX interrupt enabled */
uart_set_irqs_enabled(pstrUartLoc, false, true); uart_set_irqs_enabled(pstrUartLoc, false, true);
strControl.abTxBusy[u8Instance] = STD_FALSE; strControl.abTxBusy[u8Instance] = STD_FALSE;
vCallTxCallback(u8Instance); vCallTxCallback(u8Instance);
@ -222,35 +143,23 @@ STD_tenuResult MCU_UART_enuInit(void)
STD_tenuResult enuResultLoc = STD_OK; STD_tenuResult enuResultLoc = STD_OK;
u8 u8IndexLoc; u8 u8IndexLoc;
/* Zero-initialize all control state */
for (u8IndexLoc = 0U; u8IndexLoc < (u8)MCU_UART_NUM_INSTANCES; u8IndexLoc++) for (u8IndexLoc = 0U; u8IndexLoc < (u8)MCU_UART_NUM_INSTANCES; u8IndexLoc++)
{ {
/* TX */
strControl.apu8TxBuffer[u8IndexLoc] = STD_NULL; strControl.apu8TxBuffer[u8IndexLoc] = STD_NULL;
strControl.au16TxLength[u8IndexLoc] = 0U; strControl.au16TxLength[u8IndexLoc] = 0U;
strControl.au16TxIndex[u8IndexLoc] = 0U; strControl.au16TxIndex[u8IndexLoc] = 0U;
strControl.abTxBusy[u8IndexLoc] = STD_FALSE; strControl.abTxBusy[u8IndexLoc] = STD_FALSE;
strControl.as8TxDmaChannel[u8IndexLoc] = -1; strControl.as8TxDmaChannel[u8IndexLoc] = -1;
/* RX */
strControl.au16RxHead[u8IndexLoc] = 0U; strControl.au16RxHead[u8IndexLoc] = 0U;
strControl.au16RxTail[u8IndexLoc] = 0U; strControl.au16RxTail[u8IndexLoc] = 0U;
strControl.as8RxDmaChannel[u8IndexLoc] = -1; 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++) for (u8IndexLoc = 0U; u8IndexLoc < (u8)MCU_UART_NUM_INSTANCES; u8IndexLoc++)
{ {
const MCU_UART_tstrConfig *pstrCfgLoc = &MCU_UART_astrConfig[u8IndexLoc]; const MCU_UART_tstrConfig *pstrCfgLoc = &MCU_UART_astrConfig[u8IndexLoc];
uart_inst_t *pstrUartLoc = apstrInstances[u8IndexLoc]; uart_inst_t *pstrUartLoc = apstrInstances[u8IndexLoc];
/* Basic peripheral setup */
uart_init(pstrUartLoc, pstrCfgLoc->u32BaudRate); uart_init(pstrUartLoc, pstrCfgLoc->u32BaudRate);
gpio_set_function(pstrCfgLoc->u8TxPin, GPIO_FUNC_UART); gpio_set_function(pstrCfgLoc->u8TxPin, GPIO_FUNC_UART);
gpio_set_function(pstrCfgLoc->u8RxPin, GPIO_FUNC_UART); gpio_set_function(pstrCfgLoc->u8RxPin, GPIO_FUNC_UART);
@ -259,13 +168,12 @@ STD_tenuResult MCU_UART_enuInit(void)
pstrCfgLoc->enuStopBits, pstrCfgLoc->enuStopBits,
pstrCfgLoc->enuParity); pstrCfgLoc->enuParity);
/* --- TX async setup --- */ /* TX async setup */
if (pstrCfgLoc->enuTxAsyncMode == MCU_UART_ASYNC_DMA) if (pstrCfgLoc->enuTxAsyncMode == MCU_UART_ASYNC_DMA)
{ {
s8 s8ChannelLoc = (s8)dma_claim_unused_channel(true); s8 s8ChLoc = (s8)dma_claim_unused_channel(true);
strControl.as8TxDmaChannel[u8IndexLoc] = s8ChannelLoc; strControl.as8TxDmaChannel[u8IndexLoc] = s8ChLoc;
dma_channel_set_irq0_enabled((u32)s8ChLoc, true);
dma_channel_set_irq0_enabled((u32)s8ChannelLoc, true);
if (u8IndexLoc == (u8)MCU_UART_INSTANCE_0) 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) 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); s8 s8RxChLoc = (s8)dma_claim_unused_channel(true);
strControl.as8RxDmaChannel[u8IndexLoc] = s8RxChLoc; strControl.as8RxDmaChannel[u8IndexLoc] = s8RxChLoc;
@ -302,16 +206,11 @@ STD_tenuResult MCU_UART_enuInit(void)
} }
else 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) if (u8IndexLoc == (u8)MCU_UART_INSTANCE_0)
{ {
irq_set_exclusive_handler(UART0_IRQ, vUart0IrqHandler); irq_set_exclusive_handler(UART0_IRQ, vUart0IrqHandler);
irq_set_enabled(UART0_IRQ, true); 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); 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) 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) if (enuModeLoc == MCU_UART_ASYNC_DMA)
{ {
s8 s8ChannelLoc = strControl.as8TxDmaChannel[u8Instance]; s8 s8ChLoc = strControl.as8TxDmaChannel[u8Instance];
uart_inst_t *pstrUartLoc = apstrInstances[u8Instance]; uart_inst_t *pstrUartLoc = apstrInstances[u8Instance];
dma_channel_config strDmaCfgLoc = dma_channel_get_default_config((u32)s8ChannelLoc); dma_channel_config strCfgLoc = dma_channel_get_default_config((u32)s8ChLoc);
channel_config_set_transfer_data_size(&strDmaCfgLoc, DMA_SIZE_8); channel_config_set_transfer_data_size(&strCfgLoc, DMA_SIZE_8);
channel_config_set_read_increment(&strDmaCfgLoc, true); channel_config_set_read_increment(&strCfgLoc, true);
channel_config_set_write_increment(&strDmaCfgLoc, false); channel_config_set_write_increment(&strCfgLoc, false);
channel_config_set_dreq(&strDmaCfgLoc, uart_get_dreq(pstrUartLoc, true)); channel_config_set_dreq(&strCfgLoc, uart_get_dreq(pstrUartLoc, true));
dma_channel_configure( dma_channel_configure(
(u32)s8ChannelLoc, (u32)s8ChLoc, &strCfgLoc,
&strDmaCfgLoc, &uart_get_hw(pstrUartLoc)->dr, pu8Data, u16Length, true);
&uart_get_hw(pstrUartLoc)->dr,
pu8Data,
u16Length,
true
);
} }
else else
{ {
/* ISR mode: kick-start by filling the FIFO */
uart_inst_t *pstrUartLoc = apstrInstances[u8Instance]; uart_inst_t *pstrUartLoc = apstrInstances[u8Instance];
STD_tBool bFifoReady = STD_FALSE; STD_tBool bFifoReady = (uart_is_writable(pstrUartLoc) != 0) ? STD_TRUE : STD_FALSE;
STD_tBool bDataLeft = STD_FALSE; STD_tBool bDataLeft = (strControl.au16TxIndex[u8Instance] < u16Length) ? STD_TRUE : STD_FALSE;
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)) while ((bFifoReady == STD_TRUE) && (bDataLeft == STD_TRUE))
{ {
uart_putc_raw(pstrUartLoc, uart_putc_raw(pstrUartLoc, pu8Data[strControl.au16TxIndex[u8Instance]]);
pu8Data[strControl.au16TxIndex[u8Instance]]);
strControl.au16TxIndex[u8Instance]++; strControl.au16TxIndex[u8Instance]++;
bFifoReady = (uart_is_writable(pstrUartLoc) != 0) ? STD_TRUE : STD_FALSE; 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) if (bDataLeft == STD_FALSE)
@ -404,7 +292,6 @@ STD_tenuResult MCU_UART_enuSendBuffer(u8 u8Instance, const u8 *pu8Data, u16 u16L
} }
else else
{ {
/* Enable TX interrupt for remaining bytes. Keep RX on. */
uart_set_irqs_enabled(pstrUartLoc, true, true); 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]); uart_putc_raw(apstrInstances[u8Instance], pu8Data[u16IndexLoc]);
} }
vCallTxCallback(u8Instance); 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; STD_tenuResult enuResultLoc = STD_OK;
@ -462,96 +348,55 @@ STD_tenuResult MCU_UART_enuReceiveByte(u8 u8Instance, u8 *pu8Byte)
} }
else else
{ {
u16 u16HeadLoc; u16 u16HeadLoc = u16GetRxHead(u8Instance);
u16 u16TailLoc; u16 u16TailLoc = strControl.au16RxTail[u8Instance];
/* Spin-wait until the ring buffer has at least one byte. if (u16HeadLoc == u16TailLoc)
* 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
{ {
u16HeadLoc = u16GetRxHead(u8Instance); enuResultLoc = STD_NOK;
u16TailLoc = strControl.au16RxTail[u8Instance]; }
} while (u16HeadLoc == u16TailLoc); else
{
/* Read one byte from the tail and advance */ *pu8Byte = strControl.aau8RxBuffer[u8Instance][u16TailLoc];
*pu8Byte = strControl.aau8RxBuffer[u8Instance][u16TailLoc]; strControl.au16RxTail[u8Instance] = (u16TailLoc + 1U) & MCU_UART_RX_BUFFER_MASK;
strControl.au16RxTail[u8Instance] = (u16TailLoc + 1U) & MCU_UART_RX_BUFFER_MASK; }
} }
return enuResultLoc; 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_tenuResult enuResultLoc = STD_OK;
STD_tBool bActiveLocl = strControl.abRxReqActive[u8Instance];
if (pu8Data == STD_NULL) if ((pu8Data == STD_NULL) || (pu16Read == 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)
{ {
enuResultLoc = STD_NULL_POINTER_ERROR; enuResultLoc = STD_NULL_POINTER_ERROR;
} }
else else
{ {
/* Read N bytes from the ring buffer, blocking until each is available */ u16 u16HeadLoc = u16GetRxHead(u8Instance);
while (u16IndexLoc < u16Length) u16 u16TailLoc = strControl.au16RxTail[u8Instance];
u16 u16CountLoc = 0U;
while ((u16HeadLoc != u16TailLoc) && (u16CountLoc < u16MaxLength))
{ {
u16 u16HeadLoc = u16GetRxHead(u8Instance); pu8Data[u16CountLoc] = strControl.aau8RxBuffer[u8Instance][u16TailLoc];
u16 u16TailLoc = strControl.au16RxTail[u8Instance]; u16TailLoc = (u16TailLoc + 1U) & MCU_UART_RX_BUFFER_MASK;
u16CountLoc++;
/* 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;
} }
vCallRxCallback(u8Instance); strControl.au16RxTail[u8Instance] = u16TailLoc;
*pu16Read = u16CountLoc;
if (u16CountLoc == 0U)
{
enuResultLoc = STD_NOK;
}
} }
return enuResultLoc; return enuResultLoc;
@ -573,13 +418,4 @@ STD_tBool MCU_UART_bIsRxDataAvailable(u8 u8Instance)
} }
return bResultLoc; return bResultLoc;
} }
/* ========================================================================= */
/* RX BUSY CHECK */
/* ========================================================================= */
STD_tBool MCU_UART_bIsRxBusy(u8 u8Instance)
{
return strControl.abRxReqActive[u8Instance];
}

View File

@ -1,10 +1,7 @@
/****************************************************************************** /******************************************************************************
* File: MCU_UART_priv.h * File: MCU_UART_priv.h
* Component: MCU_UART * Component: MCU_UART
* Description: Private header for the MCU_UART driver. * Description: Private header control struct and extern config array.
* Contains the internal control structure for per-instance
* runtime state (TX async progress, RX ring buffer, DMA
* channels), and the extern config array declaration.
* *
* Layer: MCU (hardware abstraction) - internal use only * Layer: MCU (hardware abstraction) - internal use only
*****************************************************************************/ *****************************************************************************/
@ -15,35 +12,8 @@
#include "MCU_UART.h" #include "MCU_UART.h"
#include "MCU_UART_cfg.h" #include "MCU_UART_cfg.h"
/* ------------------------------------------------------------------------ */
/* CONFIG ARRAY (EXTERN) */
/* ------------------------------------------------------------------------ */
extern const MCU_UART_tstrConfig MCU_UART_astrConfig[MCU_UART_NUM_INSTANCES]; 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 typedef struct
{ {
/* TX state */ /* TX state */
@ -53,18 +23,12 @@ typedef struct
STD_tBool abTxBusy[MCU_UART_NUM_INSTANCES]; STD_tBool abTxBusy[MCU_UART_NUM_INSTANCES];
s8 as8TxDmaChannel[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] u8 aau8RxBuffer[MCU_UART_NUM_INSTANCES][MCU_UART_RX_BUFFER_SIZE]
__attribute__((aligned(MCU_UART_RX_BUFFER_SIZE))); __attribute__((aligned(MCU_UART_RX_BUFFER_SIZE)));
u16 au16RxHead[MCU_UART_NUM_INSTANCES]; u16 au16RxHead[MCU_UART_NUM_INSTANCES];
u16 au16RxTail[MCU_UART_NUM_INSTANCES]; u16 au16RxTail[MCU_UART_NUM_INSTANCES];
s8 as8RxDmaChannel[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; } MCU_UART_tstrControl;
#endif /* MCU_UART_PRIV_H */ #endif /* MCU_UART_PRIV_H */

View File

@ -1,12 +1,10 @@
/****************************************************************************** /******************************************************************************
* File: MCU_USB.h * File: MCU_USB.h
* Component: MCU_USB * Component: MCU_USB
* Description: Public interface for the MCU USB driver component. * Description: Public interface for the MCU USB-CDC driver.
* This header exposes the functions and types that other * TX: SendByte, SendBuffer (both fire-and-forget via putchar_raw).
* components are allowed to use to send and receive data over * RX: background ring buffer drained lazily from the SDK's
* the RP2040 USB-CDC (virtual serial port) interface. From the * stdio layer. ReadByte / bIsRxDataAvailable read from it.
* host computer's perspective, the Pico appears as a regular
* serial device (/dev/tty.usbmodem* on macOS, COMx on Windows).
* *
* Layer: MCU (hardware abstraction) * Layer: MCU (hardware abstraction)
*****************************************************************************/ *****************************************************************************/
@ -14,76 +12,57 @@
#ifndef MCU_USB_H #ifndef MCU_USB_H
#define 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" #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 #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); 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); 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); STD_tenuResult MCU_USB_enuSendBuffer(const u8 *pu8Data, u16 u16Length);
/** /* ------------------------------------------------------------------------ */
* @brief Receive one byte over USB-CDC (blocking). /* RX PUBLIC API */
* /* ------------------------------------------------------------------------ */
* 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);
/** /**
* @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, * Internally drains any pending bytes from the SDK's stdio layer into
* STD_FALSE if no data waiting. * 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); STD_tBool MCU_USB_bIsRxDataAvailable(void);
#endif /* MCU_USB_H */ #endif /* MCU_USB_H */

View File

@ -1,114 +1,139 @@
/****************************************************************************** /******************************************************************************
* File: MCU_USB_prg.c * File: MCU_USB_prg.c
* Component: MCU_USB * Component: MCU_USB
* Description: Program (implementation) file for the MCU_USB driver. * Description: USB-CDC driver. TX via putchar_raw (fire-and-forget).
* Contains the actual implementations of the public functions * RX via internal ring buffer, lazily drained from the SDK's
* declared in MCU_USB.h. Wraps the Pico SDK's USB-CDC / stdio * stdio layer on every ReadByte / ReadBuffer / IsDataAvailable
* facilities to provide a simple send/receive API for the * call. No separate RX task or interrupt needed the SDK's
* virtual serial port exposed over the Pico's USB connection. * TinyUSB background task fills stdio internally, and we pull
* from stdio into our ring buffer on demand.
* *
* Layer: MCU (hardware abstraction) * Layer: MCU (hardware abstraction)
*****************************************************************************/ *****************************************************************************/
#include "STD_TYPES.h" #include "STD_TYPES.h"
#include "pico/stdio_usb.h" /* stdio_usb_init(), stdio_usb_connected() */ #include "pico/stdio_usb.h"
#include "pico/stdio.h" /* putchar_raw() - writes one byte into the stdio driver chain */ #include "pico/stdio.h"
#include "pico/time.h" /* absolute_time_t, make_timeout_time_ms(), time_reached() */ #include "pico/time.h"
#include "MCU_USB.h" #include "MCU_USB.h"
#include "MCU_USB_priv.h" #include "MCU_USB_priv.h"
#include "MCU_USB_cfg.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 MCU_USB_enuInit(void)
{ {
STD_tenuResult enuResultLoc = STD_OK; STD_tenuResult enuResultLoc = STD_OK;
STD_tBool bSdkInitSuccess = STD_FALSE; 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; bSdkInitSuccess = (stdio_usb_init() != 0) ? STD_TRUE : STD_FALSE;
if (bSdkInitSuccess == STD_FALSE) if (bSdkInitSuccess == STD_FALSE)
{ {
enuResultLoc = STD_NOK; /* Initialization failed */ enuResultLoc = STD_NOK;
}else }
else
{ {
#if MCU_USB_WAIT_FOR_CONNECTION == MCU_USB_WAIT_FOR_CONNECTION_ENABLED #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); absolute_time_t absTimeout = make_timeout_time_ms(MCU_USB_CONNECTION_TIMEOUT_MS);
STD_tBool bHostOpen = STD_FALSE; STD_tBool bHostOpen = STD_FALSE;
STD_tBool bTimeoutReached = STD_FALSE; STD_tBool bTimeoutReached = STD_FALSE;
do 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); 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; bHostOpen = (stdio_usb_connected() != 0) ? STD_TRUE : STD_FALSE;
bTimeoutReached = (time_reached(absTimeout) != 0) ? STD_TRUE : STD_FALSE; bTimeoutReached = (time_reached(absTimeout) != 0) ? STD_TRUE : STD_FALSE;
} while ((bHostOpen == STD_FALSE) && (bTimeoutReached == 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) if (bHostOpen == STD_FALSE)
{ {
enuResultLoc = STD_NOK; enuResultLoc = STD_NOK;
} }
#endif #endif
} }
return enuResultLoc; /* Return the result */
return enuResultLoc;
} }
/* ========================================================================= */
/* SEND BYTE */
/* ========================================================================= */
STD_tenuResult MCU_USB_enuSendByte(u8 u8Byte) STD_tenuResult MCU_USB_enuSendByte(u8 u8Byte)
{ {
STD_tenuResult enuResultLoc = STD_OK; 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); putchar_raw(u8Byte);
return enuResultLoc; return enuResultLoc;
} }
/* ========================================================================= */
/* SEND BUFFER */
/* ========================================================================= */
STD_tenuResult MCU_USB_enuSendBuffer(const u8 *pu8Data, u16 u16Length) STD_tenuResult MCU_USB_enuSendBuffer(const u8 *pu8Data, u16 u16Length)
{ {
STD_tenuResult enuResultLoc = STD_OK; STD_tenuResult enuResultLoc = STD_OK;
u16 u16IndexLoc; 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) if (pu8Data == STD_NULL)
{ {
enuResultLoc = STD_NULL_POINTER_ERROR; enuResultLoc = STD_NULL_POINTER_ERROR;
} }
else 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++) for (u16IndexLoc = 0U; u16IndexLoc < u16Length; u16IndexLoc++)
{ {
putchar_raw(pu8Data[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) */
/* ========================================================================= */ /* ========================================================================= */
/** STD_tenuResult MCU_USB_enuReadByte(u8 *pu8Byte)
* @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 enuResultLoc = STD_OK; STD_tenuResult enuResultLoc = STD_OK;
@ -143,25 +157,53 @@ STD_tenuResult MCU_USB_enuReceiveByte(u8 *pu8Byte)
} }
else else
{ {
/* Check if bIsRxDataAvailable already cached a byte */ /* Pull any pending data from SDK into our ring buffer */
if (bHasCachedByte == STD_TRUE) vDrainStdio();
if (u16RxHead == u16RxTail)
{ {
*pu8Byte = u8CachedByte; enuResultLoc = STD_NOK;
bHasCachedByte = STD_FALSE;
} }
else else
{ {
/* Block until a byte arrives. getchar_timeout_us with 0 is *pu8Byte = au8RxBuffer[u16RxTail];
* non-blocking loop until we get a real byte. PICO_ERROR_TIMEOUT u16RxTail = (u16RxTail + 1U) & USB_RX_BUFFER_MASK;
* is returned as a negative value when no data is available. */ }
s32 s32ResultLoc; }
do return enuResultLoc;
{ }
s32ResultLoc = (s32)getchar_timeout_us(0);
} while (s32ResultLoc < 0);
*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; STD_tBool bResultLoc = STD_FALSE;
if (bHasCachedByte == STD_TRUE) vDrainStdio();
{
/* 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);
if (s32ResultLoc >= 0) if (u16RxHead != u16RxTail)
{ {
u8CachedByte = (u8)s32ResultLoc; bResultLoc = STD_TRUE;
bHasCachedByte = STD_TRUE;
bResultLoc = STD_TRUE;
}
} }
return bResultLoc; return bResultLoc;
} }