diff --git a/src/APP_CLSW/cfg/APP_CLSW_cfg.h b/src/APP_CLSW/cfg/APP_CLSW_cfg.h index 91faffe..5918948 100644 --- a/src/APP_CLSW/cfg/APP_CLSW_cfg.h +++ b/src/APP_CLSW/cfg/APP_CLSW_cfg.h @@ -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 */ \ No newline at end of file diff --git a/src/APP_CLSW/prg/APP_CLSW_prg.c b/src/APP_CLSW/prg/APP_CLSW_prg.c index a6ab98d..513ab5b 100644 --- a/src/APP_CLSW/prg/APP_CLSW_prg.c +++ b/src/APP_CLSW/prg/APP_CLSW_prg.c @@ -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: \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); -} \ No newline at end of file + 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); + } +} diff --git a/src/APP_CLSW/prg/APP_CLSW_priv.h b/src/APP_CLSW/prg/APP_CLSW_priv.h index 1720ffc..17a94ea 100644 --- a/src/APP_CLSW/prg/APP_CLSW_priv.h +++ b/src/APP_CLSW/prg/APP_CLSW_priv.h @@ -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 */ \ No newline at end of file diff --git a/src/HAL_COM/cfg/HAL_COM_cfg.c b/src/HAL_COM/cfg/HAL_COM_cfg.c index e4799cb..dafbcde 100644 --- a/src/HAL_COM/cfg/HAL_COM_cfg.c +++ b/src/HAL_COM/cfg/HAL_COM_cfg.c @@ -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, }, }; diff --git a/src/HAL_COM/inc/HAL_COM.h b/src/HAL_COM/inc/HAL_COM.h index c28b032..518184c 100644 --- a/src/HAL_COM/inc/HAL_COM.h +++ b/src/HAL_COM/inc/HAL_COM.h @@ -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 */ \ No newline at end of file diff --git a/src/HAL_COM/prg/HAL_COM_prg.c b/src/HAL_COM/prg/HAL_COM_prg.c index 53b0dfc..1605e62 100644 --- a/src/HAL_COM/prg/HAL_COM_prg.c +++ b/src/HAL_COM/prg/HAL_COM_prg.c @@ -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); +} diff --git a/src/MCU_UART/cfg/MCU_UART_cfg.c b/src/MCU_UART/cfg/MCU_UART_cfg.c index e9f92e7..7eebe74 100644 --- a/src/MCU_UART/cfg/MCU_UART_cfg.c +++ b/src/MCU_UART/cfg/MCU_UART_cfg.c @@ -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, }, }; \ No newline at end of file diff --git a/src/MCU_UART/cfg/MCU_UART_cfg.h b/src/MCU_UART/cfg/MCU_UART_cfg.h index 23d6ef6..cb2daea 100644 --- a/src/MCU_UART/cfg/MCU_UART_cfg.h +++ b/src/MCU_UART/cfg/MCU_UART_cfg.h @@ -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 */ \ No newline at end of file +/** @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 */ diff --git a/src/MCU_UART/inc/MCU_UART.h b/src/MCU_UART/inc/MCU_UART.h index ff0e1b9..3a357c1 100644 --- a/src/MCU_UART/inc/MCU_UART.h +++ b/src/MCU_UART/inc/MCU_UART.h @@ -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 */ \ No newline at end of file diff --git a/src/MCU_UART/prg/MCU_UART_prg.c b/src/MCU_UART/prg/MCU_UART_prg.c index 6cd2edc..4922e91 100644 --- a/src/MCU_UART/prg/MCU_UART_prg.c +++ b/src/MCU_UART/prg/MCU_UART_prg.c @@ -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]; +} diff --git a/src/MCU_UART/prg/MCU_UART_priv.h b/src/MCU_UART/prg/MCU_UART_priv.h index 35c3620..2fc00bb 100644 --- a/src/MCU_UART/prg/MCU_UART_priv.h +++ b/src/MCU_UART/prg/MCU_UART_priv.h @@ -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 */ \ No newline at end of file diff --git a/src/MCU_USB/inc/MCU_USB.h b/src/MCU_USB/inc/MCU_USB.h index 9a47d6f..0947812 100644 --- a/src/MCU_USB/inc/MCU_USB.h +++ b/src/MCU_USB/inc/MCU_USB.h @@ -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 */ diff --git a/src/MCU_USB/prg/MCU_USB_prg.c b/src/MCU_USB/prg/MCU_USB_prg.c index 00109c6..3fbb2ee 100644 --- a/src/MCU_USB/prg/MCU_USB_prg.c +++ b/src/MCU_USB/prg/MCU_USB_prg.c @@ -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; } \ No newline at end of file diff --git a/src/SYS_ECU/prg/SYS_ECU.c b/src/SYS_ECU/prg/SYS_ECU.c index b9d26fc..4ecd4b3 100644 --- a/src/SYS_ECU/prg/SYS_ECU.c +++ b/src/SYS_ECU/prg/SYS_ECU.c @@ -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); } } \ No newline at end of file