Add RX support and interactive color-switcher command demo

MCU_UART: full RX with ISR and DMA modes, ring buffer, blocking
ReceiveByte, async ReceiveBuffer with request fulfillment, blocking
ReceiveBufferBlocking. Per-byte RX callback (ISR mode).

MCU_USB: blocking ReceiveByte via getchar_timeout_us, cached-byte
peek for bIsRxDataAvailable.

HAL_COM: RX function pointer types + dispatch (ReceiveByte,
bIsRxDataAvailable) with USB wrappers in cfg.

APP_CLSW: interactive command parser. Accumulates input into a
buffer, parses on delimiter. Supports red/green/blue/help commands
with echo and response. SYS_ECU tick reduced to 10ms for responsive
input.
This commit is contained in:
Mohamed Salem 2026-04-13 01:29:20 +02:00
parent 77152a0718
commit 863da8f75c
14 changed files with 861 additions and 242 deletions

View File

@ -1,10 +1,9 @@
/******************************************************************************
* File: APP_CLSW_cfg.h
* Component: APP_CLSW
* Description: Configuration header for the APP_CLSW component.
* Declares configuration structures and constants that can be
* edited to adapt the color switcher application behavior
* (e.g., command strings, timing intervals, color sequences).
* Description: Configuration header for the color switcher application.
* Defines command buffer sizing, the HAL_COM channel used
* for host communication, and the command delimiter.
*
* Layer: Application - configuration
*****************************************************************************/
@ -12,6 +11,13 @@
#ifndef APP_CLSW_CFG_H
#define APP_CLSW_CFG_H
/* Configuration constants and structure declarations will go here */
#include "STD_TYPES.h"
/** @brief Maximum length of a single command string (excluding delimiter).
* Commands longer than this are truncated. */
#define APP_CLSW_CMD_BUFFER_SIZE 32U
/** @brief HAL_COM channel used for host communication. */
#define APP_CLSW_COM_CHANNEL ((u8)HAL_COM_CHANNEL_0)
#endif /* APP_CLSW_CFG_H */

View File

@ -1,11 +1,15 @@
/******************************************************************************
* File: APP_CLSW_prg.c
* Component: APP_CLSW
* Description: Program (implementation) file for the Application Color
* Switcher. Contains the actual implementations of the public
* functions declared in APP_CLSW.h. Communicates with the host
* exclusively through HAL_COM - never touches MCU drivers
* directly.
* Description: Color switcher application logic. Receives commands from the
* host via HAL_COM, parses them, and responds with confirmation.
*
* Supported commands:
* "red" "Color set to: RED\r\n"
* "green" "Color set to: GREEN\r\n"
* "blue" "Color set to: BLUE\r\n"
* "help" prints available commands
* other "Unknown command: <cmd>\r\n"
*
* Layer: Application
*****************************************************************************/
@ -14,27 +18,158 @@
#include "APP_CLSW_priv.h"
#include "APP_CLSW_cfg.h"
/* HAL_COM is the only communication interface this component uses.
* It abstracts away whether bytes go over USB-CDC, hardware UART, or both. */
#include "HAL_COM.h"
/* ------------------------------------------------------------------------ */
/* INTERNAL STATE */
/* ------------------------------------------------------------------------ */
static APP_CLSW_tstrState strState;
/* ------------------------------------------------------------------------ */
/* INTERNAL HELPERS */
/* ------------------------------------------------------------------------ */
/**
* @brief Compare two null-terminated byte arrays.
* @return STD_TRUE if equal, STD_FALSE if different.
*/
static STD_tBool bStrEqual(const u8 *pu8A, const u8 *pu8B)
{
STD_tBool bResultLoc = STD_TRUE;
u8 u8IndexLoc = 0U;
while ((pu8A[u8IndexLoc] != 0U) || (pu8B[u8IndexLoc] != 0U))
{
if (pu8A[u8IndexLoc] != pu8B[u8IndexLoc])
{
bResultLoc = STD_FALSE;
}
/* Only advance if we haven't found a mismatch yet, OR if we
* need to reach the end of both strings to confirm equality.
* Actually: always advance we need to check all characters.
* But once we found a mismatch, we know the result. We still
* loop to avoid early return (single exit point). */
u8IndexLoc++;
}
return bResultLoc;
}
/**
* @brief Send a null-terminated string via HAL_COM on the configured channel.
*/
static void vSendString(const u8 *pu8Str)
{
u16 u16LenLoc = 0U;
/* Calculate string length */
while (pu8Str[u16LenLoc] != 0U)
{
u16LenLoc++;
}
HAL_COM_enuSendBuffer(APP_CLSW_COM_CHANNEL, pu8Str, u16LenLoc);
}
/**
* @brief Process a complete command string.
* Called when a delimiter (\r or \n) is received.
*/
static void vProcessCommand(void)
{
/* Null-terminate the command buffer */
strState.au8CmdBuffer[strState.u8CmdIndex] = 0U;
/* Skip empty commands (just pressing Enter) */
if (strState.u8CmdIndex == 0U)
{
/* Do nothing — no command to process */
}
else if (bStrEqual(strState.au8CmdBuffer, (const u8 *)"red") == STD_TRUE)
{
vSendString((const u8 *)"Color set to: RED\r\n");
}
else if (bStrEqual(strState.au8CmdBuffer, (const u8 *)"green") == STD_TRUE)
{
vSendString((const u8 *)"Color set to: GREEN\r\n");
}
else if (bStrEqual(strState.au8CmdBuffer, (const u8 *)"blue") == STD_TRUE)
{
vSendString((const u8 *)"Color set to: BLUE\r\n");
}
else if (bStrEqual(strState.au8CmdBuffer, (const u8 *)"help") == STD_TRUE)
{
vSendString((const u8 *)"Available commands: red, green, blue, help\r\n");
}
else
{
vSendString((const u8 *)"Unknown command: ");
vSendString(strState.au8CmdBuffer);
vSendString((const u8 *)"\r\n");
}
/* Reset the buffer for the next command */
strState.u8CmdIndex = 0U;
}
/* ========================================================================= */
/* INIT */
/* ========================================================================= */
STD_tenuResult APP_CLSW_enuInit(void)
{
STD_tenuResult enuResultLoc = STD_OK;
/* APP_CLSW has no internal state to set up yet. When color sequences,
* state machines, or command tables are added, initialize them here.
* The communication stack (HAL_COM, MCU_USB, etc.) is already
* initialized by SYS_ECU before this function is called. */
/* Clear the command buffer state */
strState.u8CmdIndex = 0U;
return enuResultLoc;
}
/* ========================================================================= */
/* RUNNABLE */
/* ========================================================================= */
void APP_CLSW_vRunnable(void)
{
/* Proof-of-life: send a repeating test message to the host via the
* transport-agnostic HAL_COM layer. This will be replaced with real
* color-switching command logic once the communication stack is
* verified end-to-end. */
HAL_COM_enuSendBuffer((u8)HAL_COM_CHANNEL_0, (const u8 *)"this is a color switcher application!\r\n", 40U);
}
STD_tBool bDataAvailableLoc = STD_FALSE;
u8 u8ByteLoc = 0U;
STD_tenuResult enuRxResultLoc = STD_OK;
/* Check if the host sent any data */
bDataAvailableLoc = HAL_COM_bIsRxDataAvailable(APP_CLSW_COM_CHANNEL);
while (bDataAvailableLoc == STD_TRUE)
{
/* Read one byte */
enuRxResultLoc = HAL_COM_enuReceiveByte(APP_CLSW_COM_CHANNEL, &u8ByteLoc);
if (enuRxResultLoc == STD_OK)
{
/* Echo the character back so the user sees what they typed */
HAL_COM_enuSendByte(APP_CLSW_COM_CHANNEL, u8ByteLoc);
/* Check for command delimiter */
if ((u8ByteLoc == (u8)'\r') || (u8ByteLoc == (u8)'\n'))
{
/* Send a newline for display, then process the command */
vSendString((const u8 *)"\r\n");
vProcessCommand();
}
else
{
/* Accumulate into the command buffer (truncate if full) */
if (strState.u8CmdIndex < (APP_CLSW_CMD_BUFFER_SIZE - 1U))
{
strState.au8CmdBuffer[strState.u8CmdIndex] = u8ByteLoc;
strState.u8CmdIndex++;
}
}
}
/* Check for more data */
bDataAvailableLoc = HAL_COM_bIsRxDataAvailable(APP_CLSW_COM_CHANNEL);
}
}

View File

@ -1,10 +1,9 @@
/******************************************************************************
* File: APP_CLSW_priv.h
* Component: APP_CLSW
* Description: Private header for the APP_CLSW component.
* Contains internal macros, helper declarations, and any
* state/definitions that are only used inside this component.
* Nothing declared here is exposed to external components.
* Description: Private header for the color switcher application.
* Contains the internal command buffer state used to accumulate
* incoming bytes until a complete command is received.
*
* Layer: Application - internal use only
*****************************************************************************/
@ -12,6 +11,24 @@
#ifndef APP_CLSW_PRIV_H
#define APP_CLSW_PRIV_H
/* Private declarations, internal macros and helpers will go here */
#include "APP_CLSW.h"
#include "APP_CLSW_cfg.h"
/* ------------------------------------------------------------------------ */
/* COMMAND BUFFER STATE */
/* ------------------------------------------------------------------------ */
/**
* @brief Internal state for command accumulation.
*
* As bytes arrive from the host, they are stored in au8CmdBuffer until
* a delimiter (\r or \n) is received. At that point the buffer is
* null-terminated, parsed, and the index is reset for the next command.
*/
typedef struct
{
u8 au8CmdBuffer[APP_CLSW_CMD_BUFFER_SIZE]; /**< Accumulated command bytes */
u8 u8CmdIndex; /**< Next write position */
} APP_CLSW_tstrState;
#endif /* APP_CLSW_PRIV_H */

View File

@ -49,8 +49,28 @@ static STD_tenuResult vUsbSendBuffer(u8 u8Instance, const u8 *pu8Data, u16 u16Le
}
/* MCU_UART already matches the HAL_COM function pointer signatures
* (u8 u8Instance as the first parameter), so no wrapper is needed.
* Its functions can be assigned directly to the config struct. */
* (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) --- */
/**
* @brief Wrapper for MCU_USB_enuReceiveByte to match HAL_COM_tpfReceiveByte.
*/
static STD_tenuResult vUsbReceiveByte(u8 u8Instance, u8 *pu8Byte)
{
(void)u8Instance;
return MCU_USB_enuReceiveByte(pu8Byte);
}
/**
* @brief Wrapper for MCU_USB_bIsRxDataAvailable to match HAL_COM_tpfIsRxDataAvailable.
*/
static STD_tBool vUsbIsRxDataAvailable(u8 u8Instance)
{
(void)u8Instance;
return MCU_USB_bIsRxDataAvailable();
}
/* ------------------------------------------------------------------------ */
/* CHANNEL CONFIGURATION ARRAY */
@ -63,22 +83,23 @@ static STD_tenuResult vUsbSendBuffer(u8 u8Instance, const u8 *pu8Data, u16 u16Le
*
* To add a UART channel:
* 1. Add HAL_COM_CHANNEL_1 to the enum in HAL_COM_cfg.h
* 2. Add an entry here:
* 2. Add an entry here with MCU_UART functions (no wrapper needed):
* [HAL_COM_CHANNEL_1] = {
* .pfSendByte = MCU_UART_enuSendByte,
* .pfSendBuffer = MCU_UART_enuSendBuffer,
* .u8Instance = 0U,
* .pfSendByte = MCU_UART_enuSendByte,
* .pfSendBuffer = MCU_UART_enuSendBuffer,
* .pfReceiveByte = MCU_UART_enuReceiveByte,
* .pfIsRxDataAvailable = MCU_UART_bIsRxDataAvailable,
* .u8Instance = 0U,
* },
*
* To add SPI, I2C, or any other transport: create a matching MCU driver
* with the same function signature, or add a wrapper here like vUsbSendByte.
*/
const HAL_COM_tstrChannelConfig HAL_COM_astrChannelConfig[HAL_COM_NUM_CHANNELS] =
{
[HAL_COM_CHANNEL_0] =
{
.pfSendByte = vUsbSendByte,
.pfSendBuffer = vUsbSendBuffer,
.u8Instance = 0U,
.pfSendByte = vUsbSendByte,
.pfSendBuffer = vUsbSendBuffer,
.pfReceiveByte = vUsbReceiveByte,
.pfIsRxDataAvailable = vUsbIsRxDataAvailable,
.u8Instance = 0U,
},
};

View File

@ -53,6 +53,23 @@ 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
*/
typedef STD_tenuResult (*HAL_COM_tpfReceiveByte)(u8 u8Instance, u8 *pu8Byte);
/**
* @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);
/* ------------------------------------------------------------------------ */
/* CONFIGURATION STRUCTURE */
/* ------------------------------------------------------------------------ */
@ -70,9 +87,11 @@ typedef STD_tenuResult (*HAL_COM_tpfSendBuffer)(u8 u8Instance, const u8 *pu8Data
*/
typedef struct
{
HAL_COM_tpfSendByte pfSendByte; /**< Driver's send-byte function */
HAL_COM_tpfSendBuffer pfSendBuffer; /**< Driver's send-buffer function */
u8 u8Instance; /**< Peripheral instance to pass through */
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_tpfIsRxDataAvailable pfIsRxDataAvailable; /**< Driver's RX-available check */
u8 u8Instance; /**< Peripheral instance to pass through */
} HAL_COM_tstrChannelConfig;
/* ------------------------------------------------------------------------ */
@ -115,4 +134,23 @@ 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.
*/
STD_tenuResult HAL_COM_enuReceiveByte(u8 u8Channel, u8 *pu8Byte);
/**
* @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);
#endif /* HAL_COM_H */

View File

@ -65,3 +65,28 @@ STD_tenuResult HAL_COM_enuSendBuffer(u8 u8Channel, const u8 *pu8Data, u16 u16Len
return enuResultLoc;
}
/* ========================================================================= */
/* RECEIVE BYTE (BLOCKING) */
/* ========================================================================= */
STD_tenuResult HAL_COM_enuReceiveByte(u8 u8Channel, u8 *pu8Byte)
{
STD_tenuResult enuResultLoc = STD_OK;
const HAL_COM_tstrChannelConfig *pstrCfgLoc = &HAL_COM_astrChannelConfig[u8Channel];
enuResultLoc = pstrCfgLoc->pfReceiveByte(pstrCfgLoc->u8Instance, pu8Byte);
return enuResultLoc;
}
/* ========================================================================= */
/* RX DATA AVAILABLE CHECK */
/* ========================================================================= */
STD_tBool HAL_COM_bIsRxDataAvailable(u8 u8Channel)
{
const HAL_COM_tstrChannelConfig *pstrCfgLoc = &HAL_COM_astrChannelConfig[u8Channel];
return pstrCfgLoc->pfIsRxDataAvailable(pstrCfgLoc->u8Instance);
}

View File

@ -40,7 +40,9 @@ const MCU_UART_tstrConfig MCU_UART_astrConfig[MCU_UART_NUM_INSTANCES] =
.enuDataBits = MCU_UART_0_DATA_BITS,
.enuStopBits = MCU_UART_0_STOP_BITS,
.enuParity = MCU_UART_0_PARITY,
.enuAsyncMode = MCU_UART_0_ASYNC_MODE,
.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,
},
};

View File

@ -3,14 +3,8 @@
* 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, async TX mechanism,
* and TX-complete callback. Each instance is one entry in
* MCU_UART_astrConfig[]; the array index equals the RP2040
* UART instance number (0 = uart0, 1 = uart1).
*
* To add a second UART: add MCU_UART_INSTANCE_1 to the enum,
* define MCU_UART_1_* macros, and add a [MCU_UART_INSTANCE_1]
* initializer in MCU_UART_cfg.c.
* assignments, baud rates, data format, TX/RX async mechanisms,
* callbacks, and RX buffer sizing.
*
* Layer: MCU (hardware abstraction) - configuration
*****************************************************************************/
@ -24,53 +18,64 @@
/* INSTANCE ENUMERATION */
/* ------------------------------------------------------------------------ */
/**
* @brief Enumeration of configured UART instances.
*
* Each enumerator's value matches the RP2040 UART peripheral index
* (0 = uart0, 1 = uart1). MCU_UART_NUM_INSTANCES is auto-calculated
* as the count and also used to size the config array.
*/
typedef enum
{
MCU_UART_INSTANCE_0 = 0U,
MCU_UART_NUM_INSTANCES
} MCU_UART_tenuInstance;
/* ------------------------------------------------------------------------ */
/* 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. */
#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 (transmit). GP0 is the default for uart0. */
/** @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 (receive). GP1 is the default for uart0. */
/** @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. 115200 is the most common
* default for serial monitors and USB-to-UART adapters. */
/** @brief Baud rate for UART0 in bits per second. */
#define MCU_UART_0_BAUD_RATE 115200U
/** @brief Data bits per frame for UART0. 8 bits is the standard for binary
* and ASCII data transfer (8-N-1 is the universal default). */
/** @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. 1 stop bit is the standard default.
* Use 2 only for slower receivers that need extra settling time. */
/** @brief Stop bits per frame for UART0. */
#define MCU_UART_0_STOP_BITS MCU_UART_STOP_BITS_1
/** @brief Parity mode for UART0. No parity is the standard default (8-N-1).
* Enable parity only when the receiving device requires it. */
/** @brief Parity mode for UART0. */
#define MCU_UART_0_PARITY MCU_UART_PARITY_NONE
/** @brief Async TX mechanism for UART0.
* MCU_UART_ASYNC_DMA: zero-CPU DMA transfer (best for large buffers).
* MCU_UART_ASYNC_ISR: interrupt-driven byte-by-byte (no DMA channel used). */
#define MCU_UART_0_ASYNC_MODE MCU_UART_ASYNC_DMA
/** @brief 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.
* Called from interrupt context when an async or blocking SendBuffer
* finishes. Set to STD_NULL to disable (no notification). */
/** @brief TX-complete callback for UART0. STD_NULL to disable. */
#define MCU_UART_0_TX_COMPLETE_CALLBACK STD_NULL
#endif /* MCU_UART_CFG_H */
/** @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 */

View File

@ -93,8 +93,12 @@ typedef struct
MCU_UART_tenuDataBits enuDataBits; /**< Data bits per frame */
MCU_UART_tenuStopBits enuStopBits; /**< Stop bits per frame */
MCU_UART_tenuParity enuParity; /**< Parity mode */
MCU_UART_tenuAsyncMode enuAsyncMode; /**< DMA or ISR for non-blocking TX */
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. */
} MCU_UART_tstrConfig;
/* ------------------------------------------------------------------------ */
@ -163,4 +167,69 @@ STD_tenuResult MCU_UART_enuSendBufferBlocking(u8 u8Instance, const u8 *pu8Data,
*/
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);
/**
* @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 */

View File

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

View File

@ -2,10 +2,9 @@
* File: MCU_UART_priv.h
* Component: MCU_UART
* Description: Private header for the MCU_UART driver.
* Contains the internal control structure used to track
* per-instance async TX state, the extern declaration of
* the config array (which is only accessed internally by
* _prg.c), and any other helpers private to the component.
* 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
*****************************************************************************/
@ -20,14 +19,6 @@
/* CONFIG ARRAY (EXTERN) */
/* ------------------------------------------------------------------------ */
/**
* @brief Configuration array indexed by MCU_UART_tenuInstance.
*
* Defined in MCU_UART_cfg.c. Each entry holds the full configuration for
* one UART instance. The driver iterates this array during Init.
* Declared here (not in _cfg.h or .h) because only _prg.c needs to
* access it no external component should read the raw config array.
*/
extern const MCU_UART_tstrConfig MCU_UART_astrConfig[MCU_UART_NUM_INSTANCES];
/* ------------------------------------------------------------------------ */
@ -37,29 +28,43 @@ extern const MCU_UART_tstrConfig MCU_UART_astrConfig[MCU_UART_NUM_INSTANCES];
/**
* @brief Internal runtime state for all UART instances.
*
* Each field is an array indexed by instance number. This struct-of-arrays
* layout keeps all per-instance state in a single static object inside
* MCU_UART_prg.c, making it easy to find, zero-initialize, and inspect
* in a debugger.
* Each field is an array indexed by instance number (struct-of-arrays).
*
* Fields:
* apu8TxBuffer pointer to the caller's buffer being transmitted
* au16TxLength total number of bytes to transmit
* au16TxIndex number of bytes transmitted so far (ISR mode only;
* DMA mode does not use this - the DMA controller
* tracks progress internally)
* abTxBusy STD_TRUE while an async transfer is in progress
* as8DmaChannel DMA channel number claimed during Init for each
* instance configured in DMA mode. Set to -1 for
* instances using ISR mode (no DMA channel needed).
* 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 */
const u8 *apu8TxBuffer[MCU_UART_NUM_INSTANCES];
u16 au16TxLength[MCU_UART_NUM_INSTANCES];
u16 au16TxIndex[MCU_UART_NUM_INSTANCES];
STD_tBool abTxBusy[MCU_UART_NUM_INSTANCES];
s8 as8DmaChannel[MCU_UART_NUM_INSTANCES];
s8 as8TxDmaChannel[MCU_UART_NUM_INSTANCES];
/* RX ring buffer state */
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 */

View File

@ -67,4 +67,23 @@ STD_tenuResult MCU_USB_enuSendByte(u8 u8Byte);
*/
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);
/**
* @brief Check if USB-CDC has data waiting to be read.
*
* @return STD_TRUE if at least one byte is available,
* STD_FALSE if no data waiting.
*/
STD_tBool MCU_USB_bIsRxDataAvailable(void);
#endif /* MCU_USB_H */

View File

@ -116,4 +116,84 @@ STD_tenuResult MCU_USB_enuSendBuffer(const u8 *pu8Data, u16 u16Length)
}
return enuResultLoc;
}
/* ========================================================================= */
/* RECEIVE BYTE (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 enuResultLoc = STD_OK;
if (pu8Byte == STD_NULL)
{
enuResultLoc = STD_NULL_POINTER_ERROR;
}
else
{
/* Check if bIsRxDataAvailable already cached a byte */
if (bHasCachedByte == STD_TRUE)
{
*pu8Byte = u8CachedByte;
bHasCachedByte = STD_FALSE;
}
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;
do
{
s32ResultLoc = (s32)getchar_timeout_us(0);
} while (s32ResultLoc < 0);
*pu8Byte = (u8)s32ResultLoc;
}
}
return enuResultLoc;
}
/* ========================================================================= */
/* RX DATA AVAILABLE CHECK */
/* ========================================================================= */
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);
if (s32ResultLoc >= 0)
{
u8CachedByte = (u8)s32ResultLoc;
bHasCachedByte = STD_TRUE;
bResultLoc = STD_TRUE;
}
}
return bResultLoc;
}

View File

@ -55,13 +55,12 @@ int main(void)
SYS_ECU_vInitAll();
/* Phase 2: super-loop scheduler - dispatches runnables each tick.
* For the proof-of-life test, APP_CLSW_vRunnable sends a message
* over USB-CDC once per second. The sleep_ms call here controls the
* scheduler tick rate - it will eventually be replaced by a proper
* 10 ms tick rate gives responsive interactive input while keeping
* CPU usage reasonable. Will eventually be replaced by a proper
* timer-driven scheduler. */
while (1)
{
APP_CLSW_vRunnable();
sleep_ms(1000);
sleep_ms(10);
}
}