From 9cb8f895714ebaaff1a6548c71d9dffbcab3ce65 Mon Sep 17 00:00:00 2001 From: Mohamed Salem Date: Mon, 13 Apr 2026 03:51:37 +0200 Subject: [PATCH] Initial: MCU_USB USB-CDC driver with ring buffer RX --- cfg/MCU_USB_cfg.c | 14 +++ cfg/MCU_USB_cfg.h | 86 +++++++++++++++++ inc/MCU_USB.h | 68 ++++++++++++++ prg/MCU_USB_prg.c | 229 +++++++++++++++++++++++++++++++++++++++++++++ prg/MCU_USB_priv.h | 18 ++++ 5 files changed, 415 insertions(+) create mode 100644 cfg/MCU_USB_cfg.c create mode 100644 cfg/MCU_USB_cfg.h create mode 100644 inc/MCU_USB.h create mode 100644 prg/MCU_USB_prg.c create mode 100644 prg/MCU_USB_priv.h diff --git a/cfg/MCU_USB_cfg.c b/cfg/MCU_USB_cfg.c new file mode 100644 index 0000000..1efe6f9 --- /dev/null +++ b/cfg/MCU_USB_cfg.c @@ -0,0 +1,14 @@ +/****************************************************************************** + * File: MCU_USB_cfg.c + * Component: MCU_USB + * Description: Configuration implementation for the MCU_USB driver. + * Holds the actual configuration values (timeouts, buffer + * sizes, mode flags) defined as constants or configuration + * structures consumed by MCU_USB_prg.c. + * + * Layer: MCU (hardware abstraction) - configuration + *****************************************************************************/ + +#include "MCU_USB_cfg.h" + +/* Configuration definitions will go here */ diff --git a/cfg/MCU_USB_cfg.h b/cfg/MCU_USB_cfg.h new file mode 100644 index 0000000..6fc02f7 --- /dev/null +++ b/cfg/MCU_USB_cfg.h @@ -0,0 +1,86 @@ +/****************************************************************************** + * File: MCU_USB_cfg.h + * Component: MCU_USB + * Description: Configuration header for the MCU_USB driver. + * Declares configuration structures and constants that can be + * edited to adapt the USB-CDC driver to the application's + * needs (e.g., enable/disable connection wait, timeout values, + * transmit buffer sizes). + * + * Layer: MCU (hardware abstraction) - configuration + *****************************************************************************/ + +#ifndef MCU_USB_CFG_H +#define MCU_USB_CFG_H + +/* STD_TYPES is needed for STD_TRUE / STD_FALSE and the u8/u16/u32 typedefs + * used by the config values and timeout comparisons below. */ +#include "STD_TYPES.h" + +/* ------------------------------------------------------------------------ */ +/* CONNECTION / INIT BEHAVIOR */ +/* ------------------------------------------------------------------------ */ + +/** + * @brief Whether MCU_USB_enuInit() should block until the host has opened + * the CDC serial port before returning. + * + * Set to STD_TRUE to avoid losing the first bytes sent by the application + * (the host needs ~1-2 s after power-up to enumerate and open the port). + * Set to STD_FALSE for a fire-and-forget init that returns immediately - + * useful if the firmware must not stall when no host is attached. + */ +#define MCU_USB_WAIT_FOR_CONNECTION MCU_USB_WAIT_FOR_CONNECTION_ENABLED + +/** + * @brief Maximum time (in milliseconds) to wait for the host to open + * the CDC serial port during MCU_USB_enuInit(). + * + * Only applies when MCU_USB_WAIT_FOR_CONNECTION is ENABLED. USB host + * enumeration typically takes ~1-2 seconds, so 3000 ms gives a + * comfortable margin. Set to 0 for an infinite wait (never times out). + * This is separate from TRANSMIT/RECEIVE timeouts because connection + * setup is a one-time event with different timing characteristics + * than per-byte I/O operations. + */ +#define MCU_USB_CONNECTION_TIMEOUT_MS 3000U + +/* ------------------------------------------------------------------------ */ +/* TIMEOUTS */ +/* ------------------------------------------------------------------------ */ + +/** + * @brief Maximum time (in milliseconds) to wait for a single transmit + * operation to complete before returning STD_NOK / STD_TIMEOUT. + * + * Applied to each transmit call in MCU_USB_prg.c. Prevents the driver + * from blocking forever if the host-side serial port is closed mid-send + * or the USB bus becomes unresponsive. + */ +#define MCU_USB_TRANSMIT_TIMEOUT_MS 1000U + +/** + * @brief Maximum time (in milliseconds) to wait for a byte to arrive on + * the receive side before returning a timeout result. + * + * Applied to each blocking receive call. Prevents the driver from hanging + * when the host is attached but simply not sending anything. + */ +#define MCU_USB_RECEIVE_TIMEOUT_MS 1000U + +/* ------------------------------------------------------------------------ */ +/* BUFFER SIZING */ +/* ------------------------------------------------------------------------ */ + +/** + * @brief Size (in bytes) of the software transmit buffer used by the + * USB driver to queue outbound data. + * + * A larger buffer lets the application call MCU_USB_enuSendBuffer with + * bigger chunks without having to wait for the USB peripheral to drain, + * at the cost of more SRAM. 64 bytes matches the USB Full-Speed bulk + * endpoint packet size, which is a convenient minimum for alignment. + */ +#define MCU_USB_TRANSMIT_BUFFER_SIZE 64U + +#endif /* MCU_USB_CFG_H */ diff --git a/inc/MCU_USB.h b/inc/MCU_USB.h new file mode 100644 index 0000000..9bac727 --- /dev/null +++ b/inc/MCU_USB.h @@ -0,0 +1,68 @@ +/****************************************************************************** + * File: MCU_USB.h + * Component: MCU_USB + * Description: Public interface for the MCU USB-CDC driver. + * TX: SendByte, SendBuffer (both fire-and-forget via putchar_raw). + * RX: background ring buffer drained lazily from the SDK's + * stdio layer. ReadByte / bIsRxDataAvailable read from it. + * + * Layer: MCU (hardware abstraction) + *****************************************************************************/ + +#ifndef MCU_USB_H +#define MCU_USB_H + +#include "STD_TYPES.h" + +#define MCU_USB_WAIT_FOR_CONNECTION_DISABLED 0U +#define MCU_USB_WAIT_FOR_CONNECTION_ENABLED 1U + +/* ------------------------------------------------------------------------ */ +/* TX PUBLIC API */ +/* ------------------------------------------------------------------------ */ + +STD_tenuResult MCU_USB_enuInit(void); + +STD_tenuResult MCU_USB_enuSendByte(u8 u8Byte); + +STD_tenuResult MCU_USB_enuSendBuffer(const u8 *pu8Data, u16 u16Length); + +/* ------------------------------------------------------------------------ */ +/* RX PUBLIC API */ +/* ------------------------------------------------------------------------ */ + +/** + * @brief Read one byte from the USB RX ring buffer (non-blocking). + * + * Internally drains any pending bytes from the SDK's stdio layer into + * the ring buffer before checking. Returns immediately. + * + * @param pu8Byte Pointer to store the received byte. + * @return STD_OK byte read, + * STD_NULL_POINTER_ERROR if pu8Byte is NULL, + * STD_NOK if no data available. + */ +STD_tenuResult MCU_USB_enuReadByte(u8 *pu8Byte); + +/** + * @brief Read up to u16MaxLength bytes from the USB RX ring buffer. + * + * @param pu8Data Output buffer. + * @param u16MaxLength Maximum bytes to read. + * @param pu16Read Actual bytes read. + * @return STD_OK at least one byte read, + * STD_NULL_POINTER_ERROR if pu8Data or pu16Read is NULL, + * STD_NOK if no data available. + */ +STD_tenuResult MCU_USB_enuReadBuffer(u8 *pu8Data, u16 u16MaxLength, u16 *pu16Read); + +/** + * @brief Check if USB RX data is available. + * + * Drains pending bytes from the SDK first, then checks the ring buffer. + * + * @return STD_TRUE if data available, STD_FALSE if empty. + */ +STD_tBool MCU_USB_bIsRxDataAvailable(void); + +#endif /* MCU_USB_H */ \ No newline at end of file diff --git a/prg/MCU_USB_prg.c b/prg/MCU_USB_prg.c new file mode 100644 index 0000000..9256900 --- /dev/null +++ b/prg/MCU_USB_prg.c @@ -0,0 +1,229 @@ +/****************************************************************************** + * File: MCU_USB_prg.c + * Component: MCU_USB + * Description: USB-CDC driver. TX via putchar_raw (fire-and-forget). + * RX via internal ring buffer, lazily drained from the SDK's + * stdio layer on every ReadByte / ReadBuffer / IsDataAvailable + * call. No separate RX task or interrupt needed — the SDK's + * TinyUSB background task fills stdio internally, and we pull + * from stdio into our ring buffer on demand. + * + * Layer: MCU (hardware abstraction) + *****************************************************************************/ + +#include "STD_TYPES.h" + +#include "pico/stdio_usb.h" +#include "pico/stdio.h" +#include "pico/time.h" + +#include "MCU_USB.h" +#include "MCU_USB_priv.h" +#include "MCU_USB_cfg.h" + +/* ------------------------------------------------------------------------ */ +/* RX RING BUFFER */ +/* ------------------------------------------------------------------------ */ + +/** @brief RX ring buffer size — must be power of 2. */ +#define USB_RX_BUFFER_SIZE_BITS 6U +#define USB_RX_BUFFER_SIZE (1U << USB_RX_BUFFER_SIZE_BITS) +#define USB_RX_BUFFER_MASK (USB_RX_BUFFER_SIZE - 1U) + +static u8 au8RxBuffer[USB_RX_BUFFER_SIZE]; +static u16 u16RxHead = 0U; +static u16 u16RxTail = 0U; + +/** + * @brief Drain any available bytes from the SDK's stdio into our ring buffer. + * + * Called lazily from ReadByte, ReadBuffer, and bIsRxDataAvailable. + * getchar_timeout_us(0) is non-blocking — returns PICO_ERROR_TIMEOUT (-1) + * immediately if no data. We keep pulling until the SDK has nothing left + * or our ring buffer is full. + */ +static void vDrainStdio(void) +{ + s32 s32ByteLoc; + u16 u16NextHeadLoc; + + s32ByteLoc = (s32)getchar_timeout_us(0); + + while (s32ByteLoc >= 0) + { + u16NextHeadLoc = (u16RxHead + 1U) & USB_RX_BUFFER_MASK; + + /* If the ring buffer is full, stop draining (oldest data preserved, + * newest data from SDK is lost). Caller should read faster. */ + if (u16NextHeadLoc == u16RxTail) + { + /* Buffer full — can't store this byte. Break out. */ + break; + } + + au8RxBuffer[u16RxHead] = (u8)s32ByteLoc; + u16RxHead = u16NextHeadLoc; + + s32ByteLoc = (s32)getchar_timeout_us(0); + } +} + +/* ========================================================================= */ +/* INIT */ +/* ========================================================================= */ + +STD_tenuResult MCU_USB_enuInit(void) +{ + STD_tenuResult enuResultLoc = STD_OK; + STD_tBool bSdkInitSuccess = STD_FALSE; + + bSdkInitSuccess = (stdio_usb_init() != 0) ? STD_TRUE : STD_FALSE; + + if (bSdkInitSuccess == STD_FALSE) + { + enuResultLoc = STD_NOK; + } + else + { + #if MCU_USB_WAIT_FOR_CONNECTION == MCU_USB_WAIT_FOR_CONNECTION_ENABLED + absolute_time_t absTimeout = make_timeout_time_ms(MCU_USB_CONNECTION_TIMEOUT_MS); + STD_tBool bHostOpen = STD_FALSE; + STD_tBool bTimeoutReached = STD_FALSE; + do + { + sleep_ms(10); + bHostOpen = (stdio_usb_connected() != 0) ? STD_TRUE : STD_FALSE; + bTimeoutReached = (time_reached(absTimeout) != 0) ? STD_TRUE : STD_FALSE; + } while ((bHostOpen == STD_FALSE) && (bTimeoutReached == STD_FALSE)); + + if (bHostOpen == STD_FALSE) + { + enuResultLoc = STD_NOK; + } + #endif + } + + return enuResultLoc; +} + +/* ========================================================================= */ +/* SEND BYTE */ +/* ========================================================================= */ + +STD_tenuResult MCU_USB_enuSendByte(u8 u8Byte) +{ + STD_tenuResult enuResultLoc = STD_OK; + + putchar_raw(u8Byte); + + return enuResultLoc; +} + +/* ========================================================================= */ +/* SEND BUFFER */ +/* ========================================================================= */ + +STD_tenuResult MCU_USB_enuSendBuffer(const u8 *pu8Data, u16 u16Length) +{ + STD_tenuResult enuResultLoc = STD_OK; + u16 u16IndexLoc; + + if (pu8Data == STD_NULL) + { + enuResultLoc = STD_NULL_POINTER_ERROR; + } + else + { + for (u16IndexLoc = 0U; u16IndexLoc < u16Length; u16IndexLoc++) + { + putchar_raw(pu8Data[u16IndexLoc]); + } + } + + return enuResultLoc; +} + +/* ========================================================================= */ +/* READ BYTE (NON-BLOCKING) */ +/* ========================================================================= */ + +STD_tenuResult MCU_USB_enuReadByte(u8 *pu8Byte) +{ + STD_tenuResult enuResultLoc = STD_OK; + + if (pu8Byte == STD_NULL) + { + enuResultLoc = STD_NULL_POINTER_ERROR; + } + else + { + /* Pull any pending data from SDK into our ring buffer */ + vDrainStdio(); + + if (u16RxHead == u16RxTail) + { + enuResultLoc = STD_NOK; + } + else + { + *pu8Byte = au8RxBuffer[u16RxTail]; + u16RxTail = (u16RxTail + 1U) & USB_RX_BUFFER_MASK; + } + } + + return enuResultLoc; +} + +/* ========================================================================= */ +/* READ BUFFER (NON-BLOCKING) */ +/* ========================================================================= */ + +STD_tenuResult MCU_USB_enuReadBuffer(u8 *pu8Data, u16 u16MaxLength, u16 *pu16Read) +{ + STD_tenuResult enuResultLoc = STD_OK; + + if ((pu8Data == STD_NULL) || (pu16Read == STD_NULL)) + { + enuResultLoc = STD_NULL_POINTER_ERROR; + } + else + { + vDrainStdio(); + + u16 u16CountLoc = 0U; + + while ((u16RxHead != u16RxTail) && (u16CountLoc < u16MaxLength)) + { + pu8Data[u16CountLoc] = au8RxBuffer[u16RxTail]; + u16RxTail = (u16RxTail + 1U) & USB_RX_BUFFER_MASK; + u16CountLoc++; + } + + *pu16Read = u16CountLoc; + + if (u16CountLoc == 0U) + { + enuResultLoc = STD_NOK; + } + } + + return enuResultLoc; +} + +/* ========================================================================= */ +/* RX DATA AVAILABLE CHECK */ +/* ========================================================================= */ + +STD_tBool MCU_USB_bIsRxDataAvailable(void) +{ + STD_tBool bResultLoc = STD_FALSE; + + vDrainStdio(); + + if (u16RxHead != u16RxTail) + { + bResultLoc = STD_TRUE; + } + + return bResultLoc; +} diff --git a/prg/MCU_USB_priv.h b/prg/MCU_USB_priv.h new file mode 100644 index 0000000..591d8e8 --- /dev/null +++ b/prg/MCU_USB_priv.h @@ -0,0 +1,18 @@ +/****************************************************************************** + * File: MCU_USB_priv.h + * Component: MCU_USB + * Description: Private header for the MCU_USB driver. + * Contains internal macros, helper declarations, and any + * lower-level definitions that are only used inside this + * component itself. Nothing declared here is exposed to + * external components. + * + * Layer: MCU (hardware abstraction) - internal use only + *****************************************************************************/ + +#ifndef MCU_USB_PRIV_H +#define MCU_USB_PRIV_H + +/* Private declarations, internal macros and helpers will go here */ + +#endif /* MCU_USB_PRIV_H */