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:
parent
77152a0718
commit
863da8f75c
@ -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 */
|
||||
@ -1,11 +1,15 @@
|
||||
/******************************************************************************
|
||||
* File: APP_CLSW_prg.c
|
||||
* Component: APP_CLSW
|
||||
* Description: Program (implementation) file for the Application Color
|
||||
* Switcher. Contains the actual implementations of the public
|
||||
* functions declared in APP_CLSW.h. Communicates with the host
|
||||
* exclusively through HAL_COM - never touches MCU drivers
|
||||
* directly.
|
||||
* Description: Color switcher application logic. Receives commands from the
|
||||
* host via HAL_COM, parses them, and responds with confirmation.
|
||||
*
|
||||
* Supported commands:
|
||||
* "red" → "Color set to: RED\r\n"
|
||||
* "green" → "Color set to: GREEN\r\n"
|
||||
* "blue" → "Color set to: BLUE\r\n"
|
||||
* "help" → prints available commands
|
||||
* other → "Unknown command: <cmd>\r\n"
|
||||
*
|
||||
* Layer: Application
|
||||
*****************************************************************************/
|
||||
@ -14,27 +18,158 @@
|
||||
#include "APP_CLSW_priv.h"
|
||||
#include "APP_CLSW_cfg.h"
|
||||
|
||||
/* HAL_COM is the only communication interface this component uses.
|
||||
* It abstracts away whether bytes go over USB-CDC, hardware UART, or both. */
|
||||
#include "HAL_COM.h"
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
/* INTERNAL STATE */
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
static APP_CLSW_tstrState strState;
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
/* INTERNAL HELPERS */
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
/**
|
||||
* @brief Compare two null-terminated byte arrays.
|
||||
* @return STD_TRUE if equal, STD_FALSE if different.
|
||||
*/
|
||||
static STD_tBool bStrEqual(const u8 *pu8A, const u8 *pu8B)
|
||||
{
|
||||
STD_tBool bResultLoc = STD_TRUE;
|
||||
u8 u8IndexLoc = 0U;
|
||||
|
||||
while ((pu8A[u8IndexLoc] != 0U) || (pu8B[u8IndexLoc] != 0U))
|
||||
{
|
||||
if (pu8A[u8IndexLoc] != pu8B[u8IndexLoc])
|
||||
{
|
||||
bResultLoc = STD_FALSE;
|
||||
}
|
||||
|
||||
/* Only advance if we haven't found a mismatch yet, OR if we
|
||||
* need to reach the end of both strings to confirm equality.
|
||||
* Actually: always advance — we need to check all characters.
|
||||
* But once we found a mismatch, we know the result. We still
|
||||
* loop to avoid early return (single exit point). */
|
||||
u8IndexLoc++;
|
||||
}
|
||||
|
||||
return bResultLoc;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Send a null-terminated string via HAL_COM on the configured channel.
|
||||
*/
|
||||
static void vSendString(const u8 *pu8Str)
|
||||
{
|
||||
u16 u16LenLoc = 0U;
|
||||
|
||||
/* Calculate string length */
|
||||
while (pu8Str[u16LenLoc] != 0U)
|
||||
{
|
||||
u16LenLoc++;
|
||||
}
|
||||
|
||||
HAL_COM_enuSendBuffer(APP_CLSW_COM_CHANNEL, pu8Str, u16LenLoc);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Process a complete command string.
|
||||
* Called when a delimiter (\r or \n) is received.
|
||||
*/
|
||||
static void vProcessCommand(void)
|
||||
{
|
||||
/* Null-terminate the command buffer */
|
||||
strState.au8CmdBuffer[strState.u8CmdIndex] = 0U;
|
||||
|
||||
/* Skip empty commands (just pressing Enter) */
|
||||
if (strState.u8CmdIndex == 0U)
|
||||
{
|
||||
/* Do nothing — no command to process */
|
||||
}
|
||||
else if (bStrEqual(strState.au8CmdBuffer, (const u8 *)"red") == STD_TRUE)
|
||||
{
|
||||
vSendString((const u8 *)"Color set to: RED\r\n");
|
||||
}
|
||||
else if (bStrEqual(strState.au8CmdBuffer, (const u8 *)"green") == STD_TRUE)
|
||||
{
|
||||
vSendString((const u8 *)"Color set to: GREEN\r\n");
|
||||
}
|
||||
else if (bStrEqual(strState.au8CmdBuffer, (const u8 *)"blue") == STD_TRUE)
|
||||
{
|
||||
vSendString((const u8 *)"Color set to: BLUE\r\n");
|
||||
}
|
||||
else if (bStrEqual(strState.au8CmdBuffer, (const u8 *)"help") == STD_TRUE)
|
||||
{
|
||||
vSendString((const u8 *)"Available commands: red, green, blue, help\r\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
vSendString((const u8 *)"Unknown command: ");
|
||||
vSendString(strState.au8CmdBuffer);
|
||||
vSendString((const u8 *)"\r\n");
|
||||
}
|
||||
|
||||
/* Reset the buffer for the next command */
|
||||
strState.u8CmdIndex = 0U;
|
||||
}
|
||||
|
||||
/* ========================================================================= */
|
||||
/* INIT */
|
||||
/* ========================================================================= */
|
||||
|
||||
STD_tenuResult APP_CLSW_enuInit(void)
|
||||
{
|
||||
STD_tenuResult enuResultLoc = STD_OK;
|
||||
|
||||
/* APP_CLSW has no internal state to set up yet. When color sequences,
|
||||
* state machines, or command tables are added, initialize them here.
|
||||
* The communication stack (HAL_COM, MCU_USB, etc.) is already
|
||||
* initialized by SYS_ECU before this function is called. */
|
||||
/* Clear the command buffer state */
|
||||
strState.u8CmdIndex = 0U;
|
||||
|
||||
return enuResultLoc;
|
||||
}
|
||||
|
||||
/* ========================================================================= */
|
||||
/* RUNNABLE */
|
||||
/* ========================================================================= */
|
||||
|
||||
void APP_CLSW_vRunnable(void)
|
||||
{
|
||||
/* Proof-of-life: send a repeating test message to the host via the
|
||||
* transport-agnostic HAL_COM layer. This will be replaced with real
|
||||
* color-switching command logic once the communication stack is
|
||||
* verified end-to-end. */
|
||||
HAL_COM_enuSendBuffer((u8)HAL_COM_CHANNEL_0, (const u8 *)"this is a color switcher application!\r\n", 40U);
|
||||
STD_tBool bDataAvailableLoc = STD_FALSE;
|
||||
u8 u8ByteLoc = 0U;
|
||||
STD_tenuResult enuRxResultLoc = STD_OK;
|
||||
|
||||
/* Check if the host sent any data */
|
||||
bDataAvailableLoc = HAL_COM_bIsRxDataAvailable(APP_CLSW_COM_CHANNEL);
|
||||
|
||||
while (bDataAvailableLoc == STD_TRUE)
|
||||
{
|
||||
/* Read one byte */
|
||||
enuRxResultLoc = HAL_COM_enuReceiveByte(APP_CLSW_COM_CHANNEL, &u8ByteLoc);
|
||||
|
||||
if (enuRxResultLoc == STD_OK)
|
||||
{
|
||||
/* Echo the character back so the user sees what they typed */
|
||||
HAL_COM_enuSendByte(APP_CLSW_COM_CHANNEL, u8ByteLoc);
|
||||
|
||||
/* Check for command delimiter */
|
||||
if ((u8ByteLoc == (u8)'\r') || (u8ByteLoc == (u8)'\n'))
|
||||
{
|
||||
/* Send a newline for display, then process the command */
|
||||
vSendString((const u8 *)"\r\n");
|
||||
vProcessCommand();
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Accumulate into the command buffer (truncate if full) */
|
||||
if (strState.u8CmdIndex < (APP_CLSW_CMD_BUFFER_SIZE - 1U))
|
||||
{
|
||||
strState.au8CmdBuffer[strState.u8CmdIndex] = u8ByteLoc;
|
||||
strState.u8CmdIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Check for more data */
|
||||
bDataAvailableLoc = HAL_COM_bIsRxDataAvailable(APP_CLSW_COM_CHANNEL);
|
||||
}
|
||||
}
|
||||
@ -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 */
|
||||
@ -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,15 +83,14 @@ 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,
|
||||
* .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] =
|
||||
{
|
||||
@ -79,6 +98,8 @@ const HAL_COM_tstrChannelConfig HAL_COM_astrChannelConfig[HAL_COM_NUM_CHANNELS]
|
||||
{
|
||||
.pfSendByte = vUsbSendByte,
|
||||
.pfSendBuffer = vUsbSendBuffer,
|
||||
.pfReceiveByte = vUsbReceiveByte,
|
||||
.pfIsRxDataAvailable = vUsbIsRxDataAvailable,
|
||||
.u8Instance = 0U,
|
||||
},
|
||||
};
|
||||
|
||||
@ -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 */
|
||||
/* ------------------------------------------------------------------------ */
|
||||
@ -72,6 +89,8 @@ typedef struct
|
||||
{
|
||||
HAL_COM_tpfSendByte pfSendByte; /**< Driver's send-byte function */
|
||||
HAL_COM_tpfSendBuffer pfSendBuffer; /**< Driver's send-buffer function */
|
||||
HAL_COM_tpfReceiveByte pfReceiveByte; /**< Driver's receive-byte function */
|
||||
HAL_COM_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 */
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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,
|
||||
},
|
||||
};
|
||||
@ -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
|
||||
|
||||
/** @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 */
|
||||
@ -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 */
|
||||
@ -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,108 +25,166 @@
|
||||
/* 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];
|
||||
|
||||
/* --- RX: drain FIFO into ring buffer --- */
|
||||
STD_tBool bRxReadable = STD_FALSE;
|
||||
|
||||
bRxReadable = (uart_is_readable(pstrUartLoc) != 0) ? STD_TRUE : STD_FALSE;
|
||||
|
||||
while (bRxReadable == STD_TRUE)
|
||||
{
|
||||
u8 u8ByteLoc = (u8)uart_getc(pstrUartLoc);
|
||||
u16 u16HeadLoc = strControl.au16RxHead[u8Instance];
|
||||
|
||||
/* Push byte into ring buffer */
|
||||
strControl.aau8RxBuffer[u8Instance][u16HeadLoc] = u8ByteLoc;
|
||||
strControl.au16RxHead[u8Instance] = (u16HeadLoc + 1U) & MCU_UART_RX_BUFFER_MASK;
|
||||
|
||||
bRxReadable = (uart_is_readable(pstrUartLoc) != 0) ? STD_TRUE : STD_FALSE;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
|
||||
/* 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;
|
||||
|
||||
@ -143,28 +198,21 @@ static void vTxIsrHandler(u8 u8Instance)
|
||||
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);
|
||||
|
||||
/* 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++)
|
||||
{
|
||||
/* TX */
|
||||
strControl.apu8TxBuffer[u8IndexLoc] = STD_NULL;
|
||||
strControl.au16TxLength[u8IndexLoc] = 0U;
|
||||
strControl.au16TxIndex[u8IndexLoc] = 0U;
|
||||
strControl.abTxBusy[u8IndexLoc] = STD_FALSE;
|
||||
strControl.as8DmaChannel[u8IndexLoc] = -1;
|
||||
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];
|
||||
}
|
||||
|
||||
@ -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 */
|
||||
@ -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 */
|
||||
|
||||
@ -117,3 +117,83 @@ 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;
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user