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 * File: APP_CLSW_cfg.h
* Component: APP_CLSW * Component: APP_CLSW
* Description: Configuration header for the APP_CLSW component. * Description: Configuration header for the color switcher application.
* Declares configuration structures and constants that can be * Defines command buffer sizing, the HAL_COM channel used
* edited to adapt the color switcher application behavior * for host communication, and the command delimiter.
* (e.g., command strings, timing intervals, color sequences).
* *
* Layer: Application - configuration * Layer: Application - configuration
*****************************************************************************/ *****************************************************************************/
@ -12,6 +11,13 @@
#ifndef APP_CLSW_CFG_H #ifndef APP_CLSW_CFG_H
#define 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 */ #endif /* APP_CLSW_CFG_H */

View File

@ -1,11 +1,15 @@
/****************************************************************************** /******************************************************************************
* File: APP_CLSW_prg.c * File: APP_CLSW_prg.c
* Component: APP_CLSW * Component: APP_CLSW
* Description: Program (implementation) file for the Application Color * Description: Color switcher application logic. Receives commands from the
* Switcher. Contains the actual implementations of the public * host via HAL_COM, parses them, and responds with confirmation.
* functions declared in APP_CLSW.h. Communicates with the host *
* exclusively through HAL_COM - never touches MCU drivers * Supported commands:
* directly. * "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 * Layer: Application
*****************************************************************************/ *****************************************************************************/
@ -14,27 +18,158 @@
#include "APP_CLSW_priv.h" #include "APP_CLSW_priv.h"
#include "APP_CLSW_cfg.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" #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 APP_CLSW_enuInit(void)
{ {
STD_tenuResult enuResultLoc = STD_OK; STD_tenuResult enuResultLoc = STD_OK;
/* APP_CLSW has no internal state to set up yet. When color sequences, /* Clear the command buffer state */
* state machines, or command tables are added, initialize them here. strState.u8CmdIndex = 0U;
* The communication stack (HAL_COM, MCU_USB, etc.) is already
* initialized by SYS_ECU before this function is called. */
return enuResultLoc; return enuResultLoc;
} }
/* ========================================================================= */
/* RUNNABLE */
/* ========================================================================= */
void APP_CLSW_vRunnable(void) void APP_CLSW_vRunnable(void)
{ {
/* Proof-of-life: send a repeating test message to the host via the STD_tBool bDataAvailableLoc = STD_FALSE;
* transport-agnostic HAL_COM layer. This will be replaced with real u8 u8ByteLoc = 0U;
* color-switching command logic once the communication stack is STD_tenuResult enuRxResultLoc = STD_OK;
* verified end-to-end. */
HAL_COM_enuSendBuffer((u8)HAL_COM_CHANNEL_0, (const u8 *)"this is a color switcher application!\r\n", 40U); /* 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 * File: APP_CLSW_priv.h
* Component: APP_CLSW * Component: APP_CLSW
* Description: Private header for the APP_CLSW component. * Description: Private header for the color switcher application.
* Contains internal macros, helper declarations, and any * Contains the internal command buffer state used to accumulate
* state/definitions that are only used inside this component. * incoming bytes until a complete command is received.
* Nothing declared here is exposed to external components.
* *
* Layer: Application - internal use only * Layer: Application - internal use only
*****************************************************************************/ *****************************************************************************/
@ -12,6 +11,24 @@
#ifndef APP_CLSW_PRIV_H #ifndef APP_CLSW_PRIV_H
#define 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 */ #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 /* MCU_UART already matches the HAL_COM function pointer signatures
* (u8 u8Instance as the first parameter), so no wrapper is needed. * (u8 u8Instance as the first parameter), so no wrapper is needed
* Its functions can be assigned directly to the config struct. */ * 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 */ /* CHANNEL CONFIGURATION ARRAY */
@ -63,22 +83,23 @@ static STD_tenuResult vUsbSendBuffer(u8 u8Instance, const u8 *pu8Data, u16 u16Le
* *
* To add a UART channel: * To add a UART channel:
* 1. Add HAL_COM_CHANNEL_1 to the enum in HAL_COM_cfg.h * 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] = { * [HAL_COM_CHANNEL_1] = {
* .pfSendByte = MCU_UART_enuSendByte, * .pfSendByte = MCU_UART_enuSendByte,
* .pfSendBuffer = MCU_UART_enuSendBuffer, * .pfSendBuffer = MCU_UART_enuSendBuffer,
* .u8Instance = 0U, * .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] = const HAL_COM_tstrChannelConfig HAL_COM_astrChannelConfig[HAL_COM_NUM_CHANNELS] =
{ {
[HAL_COM_CHANNEL_0] = [HAL_COM_CHANNEL_0] =
{ {
.pfSendByte = vUsbSendByte, .pfSendByte = vUsbSendByte,
.pfSendBuffer = vUsbSendBuffer, .pfSendBuffer = vUsbSendBuffer,
.u8Instance = 0U, .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); 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 */ /* CONFIGURATION STRUCTURE */
/* ------------------------------------------------------------------------ */ /* ------------------------------------------------------------------------ */
@ -70,9 +87,11 @@ typedef STD_tenuResult (*HAL_COM_tpfSendBuffer)(u8 u8Instance, const u8 *pu8Data
*/ */
typedef struct 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 */
u8 u8Instance; /**< Peripheral instance to pass through */ 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; } 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); 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 */ #endif /* HAL_COM_H */

View File

@ -65,3 +65,28 @@ STD_tenuResult HAL_COM_enuSendBuffer(u8 u8Channel, const u8 *pu8Data, u16 u16Len
return enuResultLoc; 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, .enuDataBits = MCU_UART_0_DATA_BITS,
.enuStopBits = MCU_UART_0_STOP_BITS, .enuStopBits = MCU_UART_0_STOP_BITS,
.enuParity = MCU_UART_0_PARITY, .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, .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 * Component: MCU_UART
* Description: Configuration header for the MCU_UART driver. * Description: Configuration header for the MCU_UART driver.
* Defines which UART instances are active, their GPIO pin * Defines which UART instances are active, their GPIO pin
* assignments, baud rates, data format, async TX mechanism, * assignments, baud rates, data format, TX/RX async mechanisms,
* and TX-complete callback. Each instance is one entry in * callbacks, and RX buffer sizing.
* MCU_UART_astrConfig[]; the array index equals the RP2040
* UART instance number (0 = uart0, 1 = uart1).
*
* To add a second UART: add MCU_UART_INSTANCE_1 to the enum,
* define MCU_UART_1_* macros, and add a [MCU_UART_INSTANCE_1]
* initializer in MCU_UART_cfg.c.
* *
* Layer: MCU (hardware abstraction) - configuration * Layer: MCU (hardware abstraction) - configuration
*****************************************************************************/ *****************************************************************************/
@ -24,53 +18,64 @@
/* INSTANCE ENUMERATION */ /* 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 typedef enum
{ {
MCU_UART_INSTANCE_0 = 0U, MCU_UART_INSTANCE_0 = 0U,
MCU_UART_NUM_INSTANCES MCU_UART_NUM_INSTANCES
} MCU_UART_tenuInstance; } 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 */ /* 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 #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 #define MCU_UART_0_RX_PIN 1U
/** @brief Baud rate for UART0 in bits per second. 115200 is the most common /** @brief Baud rate for UART0 in bits per second. */
* default for serial monitors and USB-to-UART adapters. */
#define MCU_UART_0_BAUD_RATE 115200U #define MCU_UART_0_BAUD_RATE 115200U
/** @brief Data bits per frame for UART0. 8 bits is the standard for binary /** @brief Data bits per frame for UART0. */
* and ASCII data transfer (8-N-1 is the universal default). */
#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. 1 stop bit is the standard default. /** @brief Stop bits per frame for UART0. */
* Use 2 only for slower receivers that need extra settling time. */
#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. No parity is the standard default (8-N-1). /** @brief Parity mode for UART0. */
* Enable parity only when the receiving device requires it. */
#define MCU_UART_0_PARITY MCU_UART_PARITY_NONE #define MCU_UART_0_PARITY MCU_UART_PARITY_NONE
/** @brief Async TX mechanism for UART0. /** @brief Async TX mechanism for UART0 (DMA or ISR). */
* MCU_UART_ASYNC_DMA: zero-CPU DMA transfer (best for large buffers). #define MCU_UART_0_TX_ASYNC_MODE MCU_UART_ASYNC_DMA
* MCU_UART_ASYNC_ISR: interrupt-driven byte-by-byte (no DMA channel used). */
#define MCU_UART_0_ASYNC_MODE MCU_UART_ASYNC_DMA
/** @brief TX-complete callback for UART0. /** @brief TX-complete callback for UART0. STD_NULL to disable. */
* Called from interrupt context when an async or blocking SendBuffer
* finishes. Set to STD_NULL to disable (no notification). */
#define MCU_UART_0_TX_COMPLETE_CALLBACK STD_NULL #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_tenuDataBits enuDataBits; /**< Data bits per frame */
MCU_UART_tenuStopBits enuStopBits; /**< Stop bits per frame */ MCU_UART_tenuStopBits enuStopBits; /**< Stop bits per frame */
MCU_UART_tenuParity enuParity; /**< Parity mode */ 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. */ 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; } MCU_UART_tstrConfig;
/* ------------------------------------------------------------------------ */ /* ------------------------------------------------------------------------ */
@ -163,4 +167,69 @@ STD_tenuResult MCU_UART_enuSendBufferBlocking(u8 u8Instance, const u8 *pu8Data,
*/ */
STD_tBool MCU_UART_bIsTxBusy(u8 u8Instance); 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 */ #endif /* MCU_UART_H */

View File

@ -2,14 +2,12 @@
* 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: Program (implementation) file for the MCU_UART driver.
* Wraps the Pico SDK's hardware_uart and hardware_dma libraries * TX: blocking + non-blocking (DMA or ISR) with callback.
* to provide blocking and non-blocking send APIs for the RP2040 * RX: ISR or DMA fills a ring buffer in the background.
* PL011 UART peripheral(s). * ReceiveByte (blocking) reads from the ring buffer.
* * ReceiveBuffer (non-blocking) registers a request bytes
* Non-blocking TX supports two mechanisms (per-instance config): * are copied from ring buffer to caller's buffer as they
* - DMA: hardware DMA channel transfers bytes autonomously * arrive. ReceiveBufferBlocking waits until N bytes collected.
* - ISR: UART TX interrupt feeds one byte per interrupt
* Both invoke the configured TX-complete callback when done.
* *
* Layer: MCU (hardware abstraction) * Layer: MCU (hardware abstraction)
*****************************************************************************/ *****************************************************************************/
@ -18,7 +16,6 @@
#include "MCU_UART_priv.h" #include "MCU_UART_priv.h"
#include "MCU_UART_cfg.h" #include "MCU_UART_cfg.h"
/* Pico SDK headers */
#include "hardware/uart.h" #include "hardware/uart.h"
#include "hardware/gpio.h" #include "hardware/gpio.h"
#include "hardware/dma.h" #include "hardware/dma.h"
@ -28,143 +25,194 @@
/* INSTANCE LOOKUP TABLE */ /* 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[] = static uart_inst_t * const apstrInstances[] =
{ {
uart0, /* index 0 = RP2040 uart0 peripheral */ uart0,
uart1, /* index 1 = RP2040 uart1 peripheral */ uart1,
}; };
/* ------------------------------------------------------------------------ */ /* ------------------------------------------------------------------------ */
/* RUNTIME STATE */ /* 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; static MCU_UART_tstrControl strControl;
/* ------------------------------------------------------------------------ */ /* ------------------------------------------------------------------------ */
/* INTERNAL HELPERS */ /* 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) 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) */
/* ------------------------------------------------------------------------ */ /* ------------------------------------------------------------------------ */
/** static void vTxDmaIrqHandler0(void)
* @brief DMA completion interrupt handler for UART instance 0.
*
* Fires when the DMA channel finishes transferring the TX buffer.
* Flow: clear IRQ flag -> set abTxBusy to STD_FALSE -> call callback.
*/
static void vDmaIrqHandler0(void)
{ {
s8 s8ChannelLoc = strControl.as8DmaChannel[MCU_UART_INSTANCE_0]; s8 s8ChannelLoc = strControl.as8TxDmaChannel[MCU_UART_INSTANCE_0];
u32 u32StatusLoc; u32 u32StatusLoc;
u32StatusLoc = dma_channel_get_irq0_status((u32)s8ChannelLoc); u32StatusLoc = dma_channel_get_irq0_status((u32)s8ChannelLoc);
if (u32StatusLoc != 0U) if (u32StatusLoc != 0U)
{ {
/* Step 1: clear the DMA interrupt flag */
dma_irqn_acknowledge_channel(0, (u32)s8ChannelLoc); 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; strControl.abTxBusy[MCU_UART_INSTANCE_0] = STD_FALSE;
/* Step 3: notify the application */
vCallTxCallback((u8)MCU_UART_INSTANCE_0); 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 * RX: drains the RX FIFO into the ring buffer. If an async ReceiveBuffer
* programmable threshold. This handler writes as many bytes as the FIFO * request is active, copies bytes from ring buffer to the request buffer.
* can accept before returning, minimizing the number of interrupts needed * Calls per-byte RX callback (ISR mode only).
* for a given transfer (one interrupt per FIFO drain cycle, not per byte).
* *
* When all bytes are sent, disables the TX interrupt, marks the instance * TX: fills the TX FIFO from the send buffer. When all bytes sent, disables
* as idle, and invokes the TX-complete callback. * TX interrupt and calls TX callback.
*
* @param u8Instance Index of the UART instance that fired the interrupt.
*/ */
static void vTxIsrHandler(u8 u8Instance) static void vUartIsrHandler(u8 u8Instance)
{ {
uart_inst_t *pstrUartLoc = apstrInstances[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 */ /* --- RX: drain FIFO into ring buffer --- */
bFifoReady = (uart_is_writable(pstrUartLoc) != 0) ? STD_TRUE : STD_FALSE; STD_tBool bRxReadable = STD_FALSE;
bDataLeft = (strControl.au16TxIndex[u8Instance] < strControl.au16TxLength[u8Instance]) ? STD_TRUE : 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, u8 u8ByteLoc = (u8)uart_getc(pstrUartLoc);
strControl.apu8TxBuffer[u8Instance][strControl.au16TxIndex[u8Instance]]); u16 u16HeadLoc = strControl.au16RxHead[u8Instance];
strControl.au16TxIndex[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; 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 all bytes have been sent, shut down the interrupt */ while ((bFifoReady == STD_TRUE) && (bDataLeft == STD_TRUE))
if (bDataLeft == STD_FALSE) {
{ uart_putc_raw(pstrUartLoc,
uart_set_irqs_enabled(pstrUartLoc, false, false); 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) 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 */ /* INIT */
/* ========================================================================= */ /* ========================================================================= */
@ -177,11 +225,23 @@ STD_tenuResult MCU_UART_enuInit(void)
/* Zero-initialize all control state */ /* 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++)
{ {
strControl.apu8TxBuffer[u8IndexLoc] = STD_NULL; /* TX */
strControl.au16TxLength[u8IndexLoc] = 0U; strControl.apu8TxBuffer[u8IndexLoc] = STD_NULL;
strControl.au16TxIndex[u8IndexLoc] = 0U; strControl.au16TxLength[u8IndexLoc] = 0U;
strControl.abTxBusy[u8IndexLoc] = STD_FALSE; strControl.au16TxIndex[u8IndexLoc] = 0U;
strControl.as8DmaChannel[u8IndexLoc] = -1; 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 */ /* Configure each UART instance */
@ -190,46 +250,69 @@ STD_tenuResult MCU_UART_enuInit(void)
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];
/* Enable the UART peripheral and set the baud rate */ /* Basic peripheral setup */
uart_init(pstrUartLoc, pstrCfgLoc->u32BaudRate); 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->u8TxPin, GPIO_FUNC_UART);
gpio_set_function(pstrCfgLoc->u8RxPin, GPIO_FUNC_UART); gpio_set_function(pstrCfgLoc->u8RxPin, GPIO_FUNC_UART);
/* Set data format: data bits, stop bits, parity */
uart_set_format(pstrUartLoc, uart_set_format(pstrUartLoc,
pstrCfgLoc->enuDataBits, pstrCfgLoc->enuDataBits,
pstrCfgLoc->enuStopBits, pstrCfgLoc->enuStopBits,
pstrCfgLoc->enuParity); pstrCfgLoc->enuParity);
/* Set up the async TX mechanism based on config */ /* --- TX async setup --- */
if (pstrCfgLoc->enuAsyncMode == MCU_UART_ASYNC_DMA) 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); 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); dma_channel_set_irq0_enabled((u32)s8ChannelLoc, true);
/* Install the per-instance DMA IRQ handler */
if (u8IndexLoc == (u8)MCU_UART_INSTANCE_0) 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); 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 else
{ {
/* ISR mode: install the per-instance UART TX interrupt handler. /* ISR RX: enable UART RX interrupt. The combined handler
* The TX interrupt itself is NOT enabled here it gets enabled * (vUartIsrHandler) drains the FIFO into the ring buffer. */
* in SendBuffer and disabled by the ISR when transfer completes. */
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);
} }
} }
@ -244,8 +327,6 @@ STD_tenuResult MCU_UART_enuSendByte(u8 u8Instance, u8 u8Byte)
{ {
STD_tenuResult enuResultLoc = STD_OK; 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); uart_putc_raw(apstrInstances[u8Instance], u8Byte);
return enuResultLoc; return enuResultLoc;
@ -259,7 +340,7 @@ STD_tenuResult MCU_UART_enuSendBuffer(u8 u8Instance, const u8 *pu8Data, u16 u16L
{ {
STD_tenuResult enuResultLoc = STD_OK; STD_tenuResult enuResultLoc = STD_OK;
STD_tBool bBusyLoc = strControl.abTxBusy[u8Instance]; 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) 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) else if (bBusyLoc == STD_TRUE)
{ {
/* A previous async transfer is still in progress */
enuResultLoc = STD_NOK; enuResultLoc = STD_NOK;
} }
else 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.apu8TxBuffer[u8Instance] = pu8Data;
strControl.au16TxLength[u8Instance] = u16Length; strControl.au16TxLength[u8Instance] = u16Length;
strControl.au16TxIndex[u8Instance] = 0U; 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) if (enuModeLoc == MCU_UART_ASYNC_DMA)
{ {
/* --- DMA mode --- */ s8 s8ChannelLoc = strControl.as8TxDmaChannel[u8Instance];
s8 s8ChannelLoc = strControl.as8DmaChannel[u8Instance];
uart_inst_t *pstrUartLoc = apstrInstances[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); dma_channel_config strDmaCfgLoc = dma_channel_get_default_config((u32)s8ChannelLoc);
channel_config_set_transfer_data_size(&strDmaCfgLoc, DMA_SIZE_8); channel_config_set_transfer_data_size(&strDmaCfgLoc, DMA_SIZE_8);
channel_config_set_read_increment(&strDmaCfgLoc, true); channel_config_set_read_increment(&strDmaCfgLoc, true);
channel_config_set_write_increment(&strDmaCfgLoc, false); channel_config_set_write_increment(&strDmaCfgLoc, false);
channel_config_set_dreq(&strDmaCfgLoc, uart_get_dreq(pstrUartLoc, true)); 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( dma_channel_configure(
(u32)s8ChannelLoc, (u32)s8ChannelLoc,
&strDmaCfgLoc, &strDmaCfgLoc,
&uart_get_hw(pstrUartLoc)->dr, /* dest: UART data register */ &uart_get_hw(pstrUartLoc)->dr,
pu8Data, /* source: caller's buffer */ pu8Data,
u16Length, /* transfer count */ u16Length,
true /* start immediately */ true
); );
} }
else else
{ {
/* --- ISR mode --- */ /* 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 = STD_FALSE;
STD_tBool bDataLeft = 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; 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;
@ -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; 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) if (bDataLeft == STD_FALSE)
{ {
strControl.abTxBusy[u8Instance] = STD_FALSE; strControl.abTxBusy[u8Instance] = STD_FALSE;
@ -338,10 +404,8 @@ STD_tenuResult MCU_UART_enuSendBuffer(u8 u8Instance, const u8 *pu8Data, u16 u16L
} }
else else
{ {
/* Bytes remain — enable the TX interrupt. The ISR fills /* Enable TX interrupt for remaining bytes. Keep RX on. */
* the FIFO each time it drains below threshold until the uart_set_irqs_enabled(pstrUartLoc, true, true);
* entire buffer is transmitted. */
uart_set_irqs_enabled(pstrUartLoc, true, false);
} }
} }
} }
@ -364,13 +428,11 @@ STD_tenuResult MCU_UART_enuSendBufferBlocking(u8 u8Instance, const u8 *pu8Data,
} }
else else
{ {
/* Send each byte blocking — waits for TX FIFO space per byte */
for (u16IndexLoc = 0U; u16IndexLoc < u16Length; u16IndexLoc++) for (u16IndexLoc = 0U; u16IndexLoc < u16Length; u16IndexLoc++)
{ {
uart_putc_raw(apstrInstances[u8Instance], pu8Data[u16IndexLoc]); uart_putc_raw(apstrInstances[u8Instance], pu8Data[u16IndexLoc]);
} }
/* Invoke callback synchronously (no ISR in blocking mode) */
vCallTxCallback(u8Instance); vCallTxCallback(u8Instance);
} }
@ -385,3 +447,139 @@ STD_tBool MCU_UART_bIsTxBusy(u8 u8Instance)
{ {
return strControl.abTxBusy[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 * File: MCU_UART_priv.h
* Component: MCU_UART * Component: MCU_UART
* Description: Private header for the MCU_UART driver. * Description: Private header for the MCU_UART driver.
* Contains the internal control structure used to track * Contains the internal control structure for per-instance
* per-instance async TX state, the extern declaration of * runtime state (TX async progress, RX ring buffer, DMA
* the config array (which is only accessed internally by * channels), and the extern config array declaration.
* _prg.c), and any other helpers private to the component.
* *
* Layer: MCU (hardware abstraction) - internal use only * Layer: MCU (hardware abstraction) - internal use only
*****************************************************************************/ *****************************************************************************/
@ -20,14 +19,6 @@
/* CONFIG ARRAY (EXTERN) */ /* 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]; 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. * @brief Internal runtime state for all UART instances.
* *
* Each field is an array indexed by instance number. This struct-of-arrays * Each field is an array indexed by instance number (struct-of-arrays).
* layout keeps all per-instance state in a single static object inside
* MCU_UART_prg.c, making it easy to find, zero-initialize, and inspect
* in a debugger.
* *
* Fields: * TX fields:
* apu8TxBuffer pointer to the caller's buffer being transmitted * apu8TxBuffer caller's buffer being transmitted (non-blocking)
* au16TxLength total number of bytes to transmit * au16TxLength total bytes to transmit
* au16TxIndex number of bytes transmitted so far (ISR mode only; * au16TxIndex bytes transmitted so far (ISR mode)
* DMA mode does not use this - the DMA controller * abTxBusy STD_TRUE while async TX is in progress
* tracks progress internally) * as8TxDmaChannel DMA channel for TX (-1 if ISR mode)
* abTxBusy STD_TRUE while an async transfer is in progress *
* as8DmaChannel DMA channel number claimed during Init for each * RX fields:
* instance configured in DMA mode. Set to -1 for * aau8RxBuffer per-instance ring buffer for received bytes.
* instances using ISR mode (no DMA channel needed). * 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 */
const u8 *apu8TxBuffer[MCU_UART_NUM_INSTANCES]; const u8 *apu8TxBuffer[MCU_UART_NUM_INSTANCES];
u16 au16TxLength[MCU_UART_NUM_INSTANCES]; u16 au16TxLength[MCU_UART_NUM_INSTANCES];
u16 au16TxIndex[MCU_UART_NUM_INSTANCES]; u16 au16TxIndex[MCU_UART_NUM_INSTANCES];
STD_tBool abTxBusy[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; } MCU_UART_tstrControl;
#endif /* MCU_UART_PRIV_H */ #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); 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 */ #endif /* MCU_USB_H */

View File

@ -116,4 +116,84 @@ STD_tenuResult MCU_USB_enuSendBuffer(const u8 *pu8Data, u16 u16Length)
} }
return enuResultLoc; 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(); SYS_ECU_vInitAll();
/* Phase 2: super-loop scheduler - dispatches runnables each tick. /* Phase 2: super-loop scheduler - dispatches runnables each tick.
* For the proof-of-life test, APP_CLSW_vRunnable sends a message * 10 ms tick rate gives responsive interactive input while keeping
* over USB-CDC once per second. The sleep_ms call here controls the * CPU usage reasonable. Will eventually be replaced by a proper
* scheduler tick rate - it will eventually be replaced by a proper
* timer-driven scheduler. */ * timer-driven scheduler. */
while (1) while (1)
{ {
APP_CLSW_vRunnable(); APP_CLSW_vRunnable();
sleep_ms(1000); sleep_ms(10);
} }
} }