Initial: MCU_USB USB-CDC driver with ring buffer RX
This commit is contained in:
commit
9cb8f89571
14
cfg/MCU_USB_cfg.c
Normal file
14
cfg/MCU_USB_cfg.c
Normal 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
86
cfg/MCU_USB_cfg.h
Normal 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
68
inc/MCU_USB.h
Normal 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
229
prg/MCU_USB_prg.c
Normal 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
18
prg/MCU_USB_priv.h
Normal 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 */
|
||||
Loading…
x
Reference in New Issue
Block a user