Initial: MCU_USB USB-CDC driver with ring buffer RX

This commit is contained in:
Mohamed Salem 2026-04-13 03:51:37 +02:00
commit 9cb8f89571
5 changed files with 415 additions and 0 deletions

14
cfg/MCU_USB_cfg.c Normal file
View File

@ -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 */

86
cfg/MCU_USB_cfg.h Normal file
View File

@ -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 */

68
inc/MCU_USB.h Normal file
View File

@ -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 */

229
prg/MCU_USB_prg.c Normal file
View File

@ -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;
}

18
prg/MCU_USB_priv.h Normal file
View File

@ -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 */