2025-06-17 7:31 AM
I've been on an adventure trying to get my first CubeMX/STM32 project off the ground. Here's as much context as I can give for my debugging process, and hopefully someone can spot what I'm missing.
Dev Board: Nucleo-STM32H563ZIT-144 (Arm M33 Core)
IDE: VSCode running STM32 V3 (Pre-Release) Toolchain (not using Clang -> using VSCode C/C++ Language Server for Intellisense) on Windows 11
Build Tooling: CMake and GCC with code generated from CubeMX using FW/HAL v1.5.0
Middlewares Used: ThreadX, NetXDuo, USBX
Initial Debugging:
First off, the rest of the code base has been working fine, the general ThreadX Application, Netx stack/App, and any additional code I have added hasn't generated unaligned access faults. I have not been using the MPU, and have explicitly disabled it in the entrypoint using HAL_MPU_Disable() as well as inside Cube MX
main.c
int main(void)
{
/* USER CODE BEGIN 1 */
/* Disable MPU */
HAL_MPU_Disable();
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* Disable MPU */
HAL_MPU_Disable();
/* Setup EEPROM Emulation */
EE_InitStorage();
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* GTZC initialisation */
MX_GTZC_Init();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPDMA1_Init();
MX_GPIO_Init();
MX_ICACHE_Init();
MX_SPI1_Init();
MX_FLASH_Init();
MX_UCPD1_Init();
MX_RNG_Init();
MX_HASH_Init();
/* Call PreOsInit function */
USBPD_PreInitOs();
/* USER CODE BEGIN 2 */
// NOTE: Don't call MX_USB_PCD_Init() here, as it is called by the ThreadX application
// NOTE: The ThreadX Application calls MX_ETH_Init() to initialize the Ethernet interface
/* Configure DMA for VCP UART Receive*/
/* USER CODE END 2 */
MX_ThreadX_Init();
/* USER CODE BEGIN WHILE */
/* We should never get here as control is now taken by the scheduler */
while (1) {
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
I followed the resolutions in this post and this post , but even with the MPU disabled, USBX was still causing an UNALIGNED hard fault when trying to build the descriptors due to the pEpDesc->wMaxPacketSize being only a uint_16t. I then added a post-generation codemod to update the macros to work with 32Bit-Aligned memory as well as telling the compiler to "unpack" some of the structs. Below is the code I ended up with for the USBX stack. This code WORKED and did not generate any UNALIGNED hard faults. However, I was weary of using unpacked structs in the case that they were bitwise serialized from memory in a lower-level function somewhere in the stack.
First Attempt: Code worked with a simple CDC ACM, non-composite device.
ux_device_descriptors.h
typedef struct
{
uint8_t bLength;
uint8_t bDescriptorType;
uint16_t bcdUSB;
uint8_t bDeviceClass;
uint8_t bDeviceSubClass;
uint8_t bDeviceProtocol;
uint8_t bMaxPacketSize;
uint16_t idVendor;
uint16_t idProduct;
uint16_t bcdDevice;
uint8_t iManufacturer;
uint8_t iProduct;
uint8_t iSerialNumber;
uint8_t bNumConfigurations;
} USBD_DeviceDescTypedef; // was } __PACKED USBD_DeviceDescTypedef;
// ...
typedef struct
{
uint8_t bLength;
uint8_t bDescriptorType;
uint8_t bInterfaceNumber;
uint8_t bAlternateSetting;
uint8_t bNumEndpoints;
uint8_t bInterfaceClass;
uint8_t bInterfaceSubClass;
uint8_t bInterfaceProtocol;
uint8_t iInterface;
} USBD_IfDescTypedef; // was } __PACKED USBD_IfDescTypedef;
// ...
typedef struct
{
uint8_t bLength;
uint8_t bDescriptorType;
uint8_t bEndpointAddress;
uint8_t bmAttributes;
uint16_t wMaxPacketSize;
uint8_t bInterval;
} USBD_EpDescTypedef; // was } __PACKED USBD_EpDescTypedef;
// ...
/* USER CODE BEGIN Private_macro */
#define __USBD_FRAMEWORK_SET_EP_MEMSAFE(epadd, eptype, epsize, HSinterval, FSinterval) do { \
USBD_EpDescTypedef localEpDesc; \
memset((void *)&localEpDesc, 0, sizeof(USBD_EpDescTypedef)); \
\
/* Append Endpoint descriptor to Configuration descriptor */ \
pEpDesc = ((USBD_EpDescTypedef*)((uint32_t)pConf + *Sze)); \
localEpDesc.bLength = (uint8_t)sizeof(USBD_EpDescTypedef); \
localEpDesc.bDescriptorType = USB_DESC_TYPE_ENDPOINT; \
localEpDesc.bEndpointAddress = (epadd); \
localEpDesc.wMaxPacketSize = (epsize); \
localEpDesc.bmAttributes = (eptype); \
if (pdev->Speed == USBD_HIGH_SPEED) \
{ \
localEpDesc.bInterval = (HSinterval); \
} \
else \
{ \
localEpDesc.bInterval = (FSinterval); \
} \
* Sze += (uint32_t)sizeof(USBD_EpDescTypedef); \
\
memcpy((void *)pEpDesc, (void *)&localEpDesc, sizeof(USBD_EpDescTypedef)); \
} while (0)
/* USER CODE END Private_macro */
ux_device_cdc_acm.c
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file ux_device_cdc_acm.c
* @author MCD Application Team
* @brief USBX Device applicative file
******************************************************************************
* @attention
*
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "ux_device_cdc_acm.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "main.h"
#include "string.h"
#include "app_threadx.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
__aligned(4) UX_SLAVE_CLASS_CDC_ACM* cdc_acm;
__IO ITStatus UartUSBReady = RESET;
UX_SLAVE_CLASS_CDC_ACM_LINE_CODING_PARAMETER CDC_VCP_LineCoding =
{
115200, /* baud rate */
0x00, /* stop bits-1 */
0x00, /* parity - none */
0x08 /* nb. of bits 8 */
};
extern TX_QUEUE tx_app_cmd_queue; /* Command queue for the application thread */
TX_THREAD ux_usb_cdc_read_thread;
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/* USER CODE BEGIN 1 */
VOID usbx_cdc_acm_read_thread_entry(ULONG thread_input) {
/* Private Variables */
UNUSED(thread_input);
ULONG __aligned(4) rx_actual_length;
uint8_t __aligned(4) UserRxBuffer[64];
printf("USBX CDC ACM Read Thread Started\n");
/* Infinite Loop */
while (1) {
if (cdc_acm != UX_NULL) {
// printf("Reading from USB CDC ACM... at pointer: %p\r\n", (void*)cdc_acm);
ux_device_class_cdc_acm_read(cdc_acm, (UCHAR*)UserRxBuffer, 64, &rx_actual_length);
if (rx_actual_length == 0) {
printf("No data received from USB CDC ACM, sleeping...\r\n");
tx_thread_sleep(5); // Sleep for a while if no data received
continue; // Skip to the next iteration
}
printf("Received %lu bytes from USB CDC ACM\r\n", rx_actual_length);
// We actually read something, so we can process it
for (uint32_t i = 0; i < rx_actual_length; i++) {
if (!putCharToRingBuffer(&vcpRxRingBuffer2, UserRxBuffer[i])) {
printf("Error: Failed to put character to VCP ring buffer\r\n");
}
}
UartUSBReady = SET; /* Set the flag to indicate that data is ready */
// Shorter sleep if receiving to help with straming
tx_thread_sleep(2);
} else {
printf("Error: cdc_acm is NULL, cannot read data\r\n");
tx_thread_sleep(100); /* Sleep for a while if cdc_acm is NULL */
}
}
}
void tx_vcp_usb_thread_entry(ULONG thread_input) {
UNUSED(thread_input);
printf("VCP USB HL Thread Started\r\n");
char tempChar = 0;
uint16_t cmdBytesRead = 0;
while (1) {
/* Wait for data to be received */
if (UartUSBReady == SET) {
UartUSBReady = RESET; /* Reset the flag */
// Read from the ring buffer
uint32_t availableCount = uart_ringbuf_get_available_count(&vcpRxRingBuffer2);
if (availableCount > 0) {
uint32_t bytesRead = 0;
while (bytesRead < availableCount && bytesRead <= TX_UART_RX_MAX_CMD_SIZE) {
if (cmdBytesRead >= TX_UART_RX_MAX_CMD_SIZE) {
printf("Error: Command buffer overflow\r\n");
memset((uint8_t*)tempVcpThreadReadBuff2, 0, TX_UART_RX_MAX_CMD_SIZE); /* Clear the ring buffer structure */
cmdBytesRead = 0; /* Reset the command bytes read counter */
break; /* Exit if we reach the maximum command size */
}
if (getCharFromRingBuffer(&vcpRxRingBuffer2, (uint8_t*)&tempChar)) {
bytesRead++; /* Increment the bytes read counter */
if (tempChar == '\r') tempChar = '\n'; /* Convert carriage return to newline for consistency */
tempVcpThreadReadBuff2[cmdBytesRead++] = tempChar; /* Store the character in the temporary buffer */
// Check for line delimiter
if (tempChar == TX_UART_LINE_DLIM_1 || tempChar == TX_UART_LINE_DLIM_2 || tempChar == TX_UART_LINE_DLIM_3) {
tempVcpThreadReadBuff2[cmdBytesRead] = '\0'; /* Null-terminate the string */
if (cmdBytesRead >= TX_UART_RX_MAX_CMD_SIZE) {
printf("Error: Command buffer overflow\r\n");
memset((uint8_t*)tempVcpThreadReadBuff2, 0, TX_UART_RX_MAX_CMD_SIZE); /* Clear the ring buffer structure */
cmdBytesRead = 0; /* Reset the command bytes read counter */
break; /* Exit if we reach the maximum command size */
} else if (cmdBytesRead <= 2) {
memset((uint8_t*)tempVcpThreadReadBuff2, 0, TX_UART_RX_MAX_CMD_SIZE); /* Clear the ring buffer structure */
cmdBytesRead = 0; /* Reset the command bytes read counter */
break; /* Exit if we receive an empty command */
}
if (strlen((char*)tempVcpThreadReadBuff2) <= 2) {
memset((uint8_t*)tempVcpThreadReadBuff2, 0, TX_UART_RX_MAX_CMD_SIZE); /* Clear the ring buffer structure */
cmdBytesRead = 0; /* Reset the command bytes read counter */
break; /* Exit if we receive a command that is too short */
}
// Be extra-safe and replace any remaining line delimiters with null terminators
for (uint16_t i = 0; i < cmdBytesRead; i++) {
if (tempVcpThreadReadBuff2[i] == TX_UART_LINE_DLIM_1 ||
tempVcpThreadReadBuff2[i] == TX_UART_LINE_DLIM_2 ||
tempVcpThreadReadBuff2[i] == TX_UART_LINE_DLIM_3) {
tempVcpThreadReadBuff2[i] = '\0'; /* Null-terminate the string */
}
}
// Queue the command in application command queue
cmd_queue_put_message((unsigned char*)tempVcpThreadReadBuff2, UART_VCP_USB); /* Put the command into the command queue */
memset((uint8_t*)tempVcpThreadReadBuff2, 0, TX_UART_RX_MAX_CMD_SIZE); /* Clear the ring buffer structure */
cmdBytesRead = 0; /* Reset the command bytes read counter */
break; /* Stop reading if we reach the line delimiter */
}
} else {
printf("Error: Failed to get character from ring buffer\r\n");
break; /* Exit if we cannot read more characters */
}
}
}
}
tx_thread_sleep(5); /* Sleep for a while to avoid busy waiting */
}
}
/* USER CODE END 1 */
app_usbx_device.c
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file app_usbx_device.c
* @author MCD Application Team
* @brief USBX Device applicative file
******************************************************************************
* @attention
*
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "app_usbx_device.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "main.h"
#include "usb.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
static ULONG cdc_acm_interface_number;
static ULONG cdc_acm_configuration_number;
static UX_SLAVE_CLASS_CDC_ACM_PARAMETER cdc_acm_parameter;
static TX_THREAD ux_device_app_thread;
/* USER CODE BEGIN PV */
extern PCD_HandleTypeDef hpcd_USB_DRD_FS;
static TX_THREAD ux_cdc_read_thread;
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
static VOID app_ux_device_thread_entry(ULONG thread_input);
static UINT USBD_ChangeFunction(ULONG Device_State);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/**
* @brief Application USBX Device Initialization.
* @PAram memory_ptr: memory pointer
* @retval status
*/
UINT MX_USBX_Device_Init(VOID* memory_ptr) {
UINT ret = UX_SUCCESS;
UCHAR* device_framework_high_speed;
UCHAR* device_framework_full_speed;
ULONG device_framework_hs_length;
ULONG device_framework_fs_length;
ULONG string_framework_length;
ULONG language_id_framework_length;
UCHAR* string_framework;
UCHAR* language_id_framework;
UCHAR* pointer;
TX_BYTE_POOL* byte_pool = (TX_BYTE_POOL*)memory_ptr;
/* USER CODE BEGIN MX_USBX_Device_Init0 */
/* USER CODE END MX_USBX_Device_Init0 */
/* Allocate the stack for USBX Memory */
if (tx_byte_allocate(byte_pool, (VOID**)&pointer,
USBX_DEVICE_MEMORY_STACK_SIZE, TX_NO_WAIT) != TX_SUCCESS) {
/* USER CODE BEGIN USBX_ALLOCATE_STACK_ERROR */
return TX_POOL_ERROR;
/* USER CODE END USBX_ALLOCATE_STACK_ERROR */
}
/* Initialize USBX Memory */
if (ux_system_initialize(pointer, USBX_DEVICE_MEMORY_STACK_SIZE, UX_NULL, 0) != UX_SUCCESS) {
/* USER CODE BEGIN USBX_SYSTEM_INITIALIZE_ERROR */
return UX_ERROR;
/* USER CODE END USBX_SYSTEM_INITIALIZE_ERROR */
}
/* Get Device Framework High Speed and get the length */
device_framework_high_speed = USBD_Get_Device_Framework_Speed(USBD_HIGH_SPEED,
&device_framework_hs_length);
/* Get Device Framework Full Speed and get the length */
device_framework_full_speed = USBD_Get_Device_Framework_Speed(USBD_FULL_SPEED,
&device_framework_fs_length);
/* Get String Framework and get the length */
string_framework = USBD_Get_String_Framework(&string_framework_length);
/* Get Language Id Framework and get the length */
language_id_framework = USBD_Get_Language_Id_Framework(&language_id_framework_length);
/* Install the device portion of USBX */
if (ux_device_stack_initialize(device_framework_high_speed,
device_framework_hs_length,
device_framework_full_speed,
device_framework_fs_length,
string_framework,
string_framework_length,
language_id_framework,
language_id_framework_length,
USBD_ChangeFunction) != UX_SUCCESS) {
/* USER CODE BEGIN USBX_DEVICE_INITIALIZE_ERROR */
printf("Error: USBX Device initialization failed\n");
return UX_ERROR;
/* USER CODE END USBX_DEVICE_INITIALIZE_ERROR */
}
/* Initialize the cdc acm class parameters for the device */
cdc_acm_parameter.ux_slave_class_cdc_acm_instance_activate = USBD_CDC_ACM_Activate;
cdc_acm_parameter.ux_slave_class_cdc_acm_instance_deactivate = USBD_CDC_ACM_Deactivate;
cdc_acm_parameter.ux_slave_class_cdc_acm_parameter_change = USBD_CDC_ACM_ParameterChange;
/* USER CODE BEGIN CDC_ACM_PARAMETER */
/* USER CODE END CDC_ACM_PARAMETER */
/* Get cdc acm configuration number */
cdc_acm_configuration_number = USBD_Get_Configuration_Number(CLASS_TYPE_CDC_ACM, 0);
/* Find cdc acm interface number */
cdc_acm_interface_number = USBD_Get_Interface_Number(CLASS_TYPE_CDC_ACM, 0);
/* Initialize the device cdc acm class */
if (ux_device_stack_class_register(_ux_system_slave_class_cdc_acm_name,
ux_device_class_cdc_acm_entry,
cdc_acm_configuration_number,
cdc_acm_interface_number,
&cdc_acm_parameter) != UX_SUCCESS) {
/* USER CODE BEGIN USBX_DEVICE_CDC_ACM_REGISTER_ERROR */
return UX_ERROR;
/* USER CODE END USBX_DEVICE_CDC_ACM_REGISTER_ERROR */
}
/* Allocate the stack for device application main thread */
if (tx_byte_allocate(byte_pool, (VOID**)&pointer, UX_DEVICE_APP_THREAD_STACK_SIZE,
TX_NO_WAIT) != TX_SUCCESS) {
/* USER CODE BEGIN MAIN_THREAD_ALLOCATE_STACK_ERROR */
return TX_POOL_ERROR;
/* USER CODE END MAIN_THREAD_ALLOCATE_STACK_ERROR */
}
/* Create the device application main thread */
if (tx_thread_create(&ux_device_app_thread, UX_DEVICE_APP_THREAD_NAME, app_ux_device_thread_entry,
0, pointer, UX_DEVICE_APP_THREAD_STACK_SIZE, UX_DEVICE_APP_THREAD_PRIO,
UX_DEVICE_APP_THREAD_PREEMPTION_THRESHOLD, UX_DEVICE_APP_THREAD_TIME_SLICE,
UX_DEVICE_APP_THREAD_START_OPTION) != TX_SUCCESS) {
/* USER CODE BEGIN MAIN_THREAD_CREATE_ERROR */
return TX_THREAD_ERROR;
/* USER CODE END MAIN_THREAD_CREATE_ERROR */
}
/* USER CODE BEGIN MX_USBX_Device_Init1 */
/* Allocate the stack for usbx cdc acm read thread */
if (tx_byte_allocate(byte_pool, (VOID**)&pointer, 1024, TX_NO_WAIT) != TX_SUCCESS) {
printf("Error: Failed to allocate memory for usbx cdc acm read thread\n");
return TX_POOL_ERROR;
}
/* Create the usbx_cdc_acm_read_thread_entry thread */
if (tx_thread_create(&ux_cdc_read_thread, "cdc_acm_read_usbx_app_thread_entry",
usbx_cdc_acm_read_thread_entry, 1, pointer,
1024, 9, 9, TX_NO_TIME_SLICE,
TX_AUTO_START) != TX_SUCCESS) {
printf("Error: Failed to create usbx cdc acm read thread\n");
return TX_THREAD_ERROR;
}
/* Allocate the stack for usbx cdc acm LHL Read thread */
if (tx_byte_allocate(byte_pool, (VOID**)&pointer, 1024, TX_NO_WAIT) != TX_SUCCESS) {
printf("Error: Failed to allocate memory for usbx cdc acm HL read thread\n");
return TX_POOL_ERROR;
}
/* Create the usbx_cdc_acm_read_thread_entry thread */
if (tx_thread_create(&ux_usb_cdc_read_thread, "cdc_usb_acm_read_usbx_app_thread_entry",
tx_vcp_usb_thread_entry, 1, pointer,
1024, 9, 9, TX_NO_TIME_SLICE,
TX_AUTO_START) != TX_SUCCESS) {
printf("Error: Failed to create usbx cdc acm HL read thread\n");
return TX_THREAD_ERROR;
}
/* USER CODE END MX_USBX_Device_Init1 */
return ret;
}
/**
* @brief Function implementing app_ux_device_thread_entry.
* @PAram thread_input: User thread input parameter.
* @retval none
*/
static VOID app_ux_device_thread_entry(ULONG thread_input) {
/* USER CODE BEGIN app_ux_device_thread_entry */
printf("USBX Device Thread Started\n");
TX_PARAMETER_NOT_USED(thread_input);
/* Init the UART Rx Ring Buffer*/
UART_Init_Rx_Ringbuf(&vcpRxRingBuffer2, (uint8_t*)vcpRxBuffer2, TX_UART_RX_RINGBUF_SIZE);
HAL_PWREx_EnableVddUSB();
/* USB_DRD_FS init function */
MX_USB_PCD_Init();
/*USB packet memory area configuration*/
HAL_PCDEx_PMAConfig(&hpcd_USB_DRD_FS, 0x00, PCD_SNG_BUF, 0x14);
HAL_PCDEx_PMAConfig(&hpcd_USB_DRD_FS, 0x80, PCD_SNG_BUF, 0x54);
HAL_PCDEx_PMAConfig(&hpcd_USB_DRD_FS, USBD_CDCACM_EPOUT_ADDR, PCD_SNG_BUF, 0x94);
HAL_PCDEx_PMAConfig(&hpcd_USB_DRD_FS, USBD_CDCACM_EPIN_ADDR, PCD_SNG_BUF, 0x98);
HAL_PCDEx_PMAConfig(&hpcd_USB_DRD_FS, USBD_CDCACM_EPINCMD_ADDR, PCD_SNG_BUF, 0x9C);
/* initialize the device controller driver*/
ux_dcd_stm32_initialize((ULONG)USB_DRD_FS, (ULONG)&hpcd_USB_DRD_FS);
/* Start device USB */
HAL_PCD_Start(&hpcd_USB_DRD_FS);
while (1) {
/* Check if the device is connected */
tx_thread_sleep(100);
}
/* USER CODE END app_ux_device_thread_entry */
}
/**
* @brief USBD_ChangeFunction
* This function is called when the device state changes.
* @PAram Device_State: USB Device State
* @retval status
*/
static UINT USBD_ChangeFunction(ULONG Device_State) {
UINT status = UX_SUCCESS;
/* USER CODE BEGIN USBD_ChangeFunction0 */
/* USER CODE END USBD_ChangeFunction0 */
switch (Device_State) {
case UX_DEVICE_ATTACHED:
/* USER CODE BEGIN UX_DEVICE_ATTACHED */
printf("USB Device Attached\r\n");
/* USER CODE END UX_DEVICE_ATTACHED */
break;
case UX_DEVICE_REMOVED:
/* USER CODE BEGIN UX_DEVICE_REMOVED */
printf("USB Device Removed\r\n");
/* USER CODE END UX_DEVICE_REMOVED */
break;
case UX_DCD_STM32_DEVICE_CONNECTED:
/* USER CODE BEGIN UX_DCD_STM32_DEVICE_CONNECTED */
printf("USB Device Connected\r\n");
/* USER CODE END UX_DCD_STM32_DEVICE_CONNECTED */
break;
case UX_DCD_STM32_DEVICE_DISCONNECTED:
/* USER CODE BEGIN UX_DCD_STM32_DEVICE_DISCONNECTED */
printf("USB Device Disconnected\r\n");
/* USER CODE END UX_DCD_STM32_DEVICE_DISCONNECTED */
break;
case UX_DCD_STM32_DEVICE_SUSPENDED:
/* USER CODE BEGIN UX_DCD_STM32_DEVICE_SUSPENDED */
printf("USB Device Suspended\r\n");
/* USER CODE END UX_DCD_STM32_DEVICE_SUSPENDED */
break;
case UX_DCD_STM32_DEVICE_RESUMED:
/* USER CODE BEGIN UX_DCD_STM32_DEVICE_RESUMED */
printf("USB Device Resumed\r\n");
/* USER CODE END UX_DCD_STM32_DEVICE_RESUMED */
break;
case UX_DCD_STM32_SOF_RECEIVED:
/* USER CODE BEGIN UX_DCD_STM32_SOF_RECEIVED */
/* USER CODE END UX_DCD_STM32_SOF_RECEIVED */
break;
default:
/* USER CODE BEGIN DEFAULT */
/* USER CODE END DEFAULT */
break;
}
/* USER CODE BEGIN USBD_ChangeFunction1 */
/* USER CODE END USBD_ChangeFunction1 */
return status;
}
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
The above code worked flawlessly, USB enumerated correctly, and as far as I could tell, nothing looked out of place using UPCap to debug the USB handshake or bulk transfers.
Then I tried making a composite device...
Using CubeMX to make a composite CDC ACM and CustomHID and using my codemod to unpack the above structs did not work. I could not get the composite descriptor to send any strings properly, and the device would not enumerate properly in windows. I was a bit stumped, but I figured that the compiler was adding in some extra zero-padding to the descriptor structs to make the members 32Bit-Aligned.
I then re-generated the code without my codemod, effectively keeping the stock USBX code CubeMX generated. This immediately resulted in the same UNALIGNED hard fault as before. Again, I'm absolutely sure the MPU is not enabled. Back to square one!
More to follow...
2025-06-17 7:32 AM - edited 2025-06-17 8:08 AM
Attempt 2: Composite device/USBx Unaligned-Access Compiler Fixes
I then dug into the Arm compiler options in the official ARM docs. Keeping all of the generated code as-is out of CubeMX, I modified the root CMakeLists.txt file as well as the generated stm32cubemx/CMakeLists.txt to include the following compiler flag in the build process:
add_compile_options("-mno-unaligned-access")
And magically, with the compiler handling the generated code via aligned-access calls, all of a sudden, the USB composite device enumerated correctly, descriptor strings were working as expected, and there were no UNALIGNED hard faults with the unmodified USBx code. (using packed structs and default member access macro)
Current Status:
I now have a mostly working USBx app where the composite device enumerates correctly, Here's the weird part...
For some reason, ux_device_class_cdc_acm_read() seems to never have the semaphore released, or something at a lower level is not finalizing the bulk OUT transfer to the STM32. Again, the STM32 can do bulk IN Transfers to the host without issue. I have tested with multiple terminal programs, and can even see that something is causing the transfer to hang via a custom serial debugger I made here . The transfer is never finished according to my serial program, and once the first transfer is started, the transaction remains open. The STM32 can still send data back to the host without issues, only bulk OUT transfers seem to be affected. Below is the current code for the composite Custom HID and CDC ACM stack.
main.c
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "app_threadx.h"
#include "main.h"
#include "flash.h"
#include "gpdma.h"
#include "gtzc.h"
#include "hash.h"
#include "icache.h"
#include "memorymap.h"
#include "rng.h"
#include "spi.h"
#include "ucpd.h"
#include "usb.h"
#include "gpio.h"
#include "usbpd.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "app_threadx.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
COM_InitTypeDef BspCOMInit;
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* Disable MPU */
HAL_MPU_Disable();
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* Disable MPU */
HAL_MPU_Disable();
/* Setup EEPROM Emulation */
EE_InitStorage();
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* GTZC initialisation */
MX_GTZC_Init();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPDMA1_Init();
MX_GPIO_Init();
MX_ICACHE_Init();
MX_SPI1_Init();
MX_FLASH_Init();
MX_UCPD1_Init();
MX_RNG_Init();
MX_HASH_Init();
/* Call PreOsInit function */
USBPD_PreInitOs();
/* USER CODE BEGIN 2 */
// NOTE: Don't call MX_USB_PCD_Init() here, as it is called by the ThreadX application
// NOTE: The ThreadX Application calls MX_ETH_Init() to initialize the Ethernet interface
/* Configure DMA for VCP UART Receive*/
/* USER CODE END 2 */
MX_ThreadX_Init();
/* Initialize leds */
BSP_LED_Init(LED_GREEN);
BSP_LED_Init(LED_YELLOW);
BSP_LED_Init(LED_RED);
/* Initialize USER push-button, will be used to trigger an interrupt each time it's pressed.*/
BSP_PB_Init(BUTTON_USER, BUTTON_MODE_EXTI);
/* Initialize COM1 port (115200, 8 bits (7-bit data + 1 stop bit), no parity */
BspCOMInit.BaudRate = 115200;
BspCOMInit.WordLength = COM_WORDLENGTH_8B;
BspCOMInit.StopBits = COM_STOPBITS_1;
BspCOMInit.Parity = COM_PARITY_NONE;
BspCOMInit.HwFlowCtl = COM_HWCONTROL_NONE;
if (BSP_COM_Init(COM1, &BspCOMInit) != BSP_ERROR_NONE)
{
Error_Handler();
}
/* We should never get here as control is now taken by the scheduler */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1) {
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_CRSInitTypeDef RCC_CRSInitStruct = {0};
/** Configure the main internal regulator output voltage
*/
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE0);
while(!__HAL_PWR_GET_FLAG(PWR_FLAG_VOSRDY)) {}
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI48|RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSIDiv = RCC_HSI_DIV1;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.HSI48State = RCC_HSI48_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLL1_SOURCE_HSI;
RCC_OscInitStruct.PLL.PLLM = 4;
RCC_OscInitStruct.PLL.PLLN = 31;
RCC_OscInitStruct.PLL.PLLP = 2;
RCC_OscInitStruct.PLL.PLLQ = 2;
RCC_OscInitStruct.PLL.PLLR = 2;
RCC_OscInitStruct.PLL.PLLRGE = RCC_PLL1_VCIRANGE_3;
RCC_OscInitStruct.PLL.PLLVCOSEL = RCC_PLL1_VCORANGE_WIDE;
RCC_OscInitStruct.PLL.PLLFRACN = 2048;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2
|RCC_CLOCKTYPE_PCLK3;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB3CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
{
Error_Handler();
}
/** Enable the CRS APB clock
*/
__HAL_RCC_CRS_CLK_ENABLE();
/** Configures CRS
*/
RCC_CRSInitStruct.Prescaler = RCC_CRS_SYNC_DIV1;
RCC_CRSInitStruct.Source = RCC_CRS_SYNC_SOURCE_USB;
RCC_CRSInitStruct.Polarity = RCC_CRS_SYNC_POLARITY_RISING;
RCC_CRSInitStruct.ReloadValue = __HAL_RCC_CRS_RELOADVALUE_CALCULATE(48000000,1000);
RCC_CRSInitStruct.ErrorLimitValue = 34;
RCC_CRSInitStruct.HSI48CalibrationValue = 32;
HAL_RCCEx_CRSConfig(&RCC_CRSInitStruct);
/** Configure the programming delay
*/
__HAL_FLASH_SET_PROGRAM_DELAY(FLASH_PROGRAMMING_DELAY_2);
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief Period elapsed callback in non blocking mode
* @note This function is called when TIM6 interrupt took place, inside
* HAL_TIM_IRQHandler(). It makes a direct call to HAL_IncTick() to increment
* a global variable "uwTick" used as application time base.
* htim : TIM handle
* @retval None
*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
/* USER CODE BEGIN Callback 0 */
/* USER CODE END Callback 0 */
if (htim->Instance == TIM6)
{
HAL_IncTick();
}
/* USER CODE BEGIN Callback 1 */
/* USER CODE END Callback 1 */
}
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1) {
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* file: pointer to the source file name
* line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
More to follow
2025-06-17 7:34 AM - edited 2025-06-17 7:49 AM
app_threadx.c
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file app_threadx.c
* @author MCD Application Team
* @brief ThreadX applicative file
******************************************************************************
* @attention
*
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "app_threadx.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "main.h"
#include "linked_list.h"
#include "gpdma.h"
#include "eth.h"
#include "usb.h"
#include "stm32h5xx_nucleo.h"
#include "stm32h5xx_hal_usart.h"
#include "ux_device_cdc_acm.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define TX_APP_UART_RECEIVE_STACK_SIZE 2048 /* Size of the UART receive buffer */
#define TX_APP_CMD_THREAD_STACK_SIZE 2048 /* Size of the UART receive buffer */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
TX_THREAD tx_app_thread;
TX_QUEUE tx_app_msg_queue;
/* USER CODE BEGIN PV */
__IO ITStatus UartReady = RESET;
USHORT spinnerSate = 0; /* Used to display a spinner in the console */
TX_THREAD tx_vcp_thread;
TX_THREAD tx_cmd_thread;
TX_QUEUE tx_app_cmd_queue; /* Command queue for the application thread */
static __aligned(4) unsigned char tx_app_cmd_queue_buffer[TX_APP_UART_RX_COMMAND_QUE_NB_MSG][TX_APP_UART_RECEIVE_MSG_SIZE_BYTES] = { 0 }; /* Buffer for the command queue */
static __aligned(4) uint32_t tx_app_cmd_queue_last_written_index = 0; /* Last written index for the command queue */
static __aligned(4) uint32_t tx_app_cmd_queue_current_msg_count = 0; /* Current message count in the command queue */
uint8_t rxByte = 0; /* Variable to store the received byte from UART */
volatile uint8_t __aligned(4) tempUart2ThreadReadBuff[TX_UART_RX_MAX_CMD_SIZE] = { 0 }; /* Temporary buffer for VXP thread read operations */
volatile uint8_t __aligned(4) uart2RxBuffer[TX_UART_RX_RINGBUF_SIZE] = { 0 }; /* Buffer to store received data from UART */
UART_RingReceiveBuffer_t huart2RxRingBuffer = { 0 }; /* Ring buffer to store received data from UART */
volatile uint8_t __aligned(4) tempVcpThreadReadBuff2[TX_UART_RX_MAX_CMD_SIZE] = { 0 }; /* Temporary buffer for VXP thread read operations */
volatile uint8_t __aligned(4) vcpRxBuffer2[TX_UART_RX_RINGBUF_SIZE] = { 0 }; /* Buffer to store received data from UART */
UART_RingReceiveBuffer_t vcpRxRingBuffer2 = { 0 }; /* Ring buffer to store received data from UART */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN PFP */
char getNextSpinnerState(void);
void printSpinner(void);
void tx_vcp_thread_entry(ULONG thread_input);
void tx_cmd_thread_entry(ULONG thread_input);
void cmd_queue_put_message(unsigned char* message, UART_RETURN_PORTTypeDef port) {
if (message == NULL) {
printf("Error: Message is NULL\r\n");
return; /* Return if the message is NULL */
}
if (tx_app_cmd_queue_current_msg_count >= TX_APP_UART_RX_COMMAND_QUE_NB_MSG) {
printf("Error: Command queue is full, cannot put message\r\n");
return; /* Return if the command queue is full */
}
/* Get the last written pointer and wrap around the array*/
const uint32_t old_index = tx_app_cmd_queue_last_written_index; /* Store the last written index */
const uint32_t next_index = old_index + 1 % TX_APP_UART_RX_COMMAND_QUE_NB_MSG;
const unsigned char* next_cmd_buffer_ptr = (const unsigned char*)&(tx_app_cmd_queue_buffer[next_index]);
if (next_cmd_buffer_ptr == NULL) {
printf("Error: Next command buffer pointer is NULL\r\n");
return; /* Return if the next command buffer pointer is NULL */
}
memset((uint8_t*)next_cmd_buffer_ptr, 0, TX_APP_UART_RECEIVE_MSG_SIZE_BYTES); /* Clear the next command buffer */
strncpy((char*)next_cmd_buffer_ptr, (const char*)message, TX_APP_UART_RECEIVE_MSG_SIZE_BYTES - 1); /* Copy the message to the next command buffer */
/* This function puts a message into the command queue */
tx_app_cmd_queue_last_written_index = next_index; /* Update the last written index */
tx_app_cmd_queue_current_msg_count++; /* Increment the current message count */
uint32_t passedValue = (uint32_t)next_cmd_buffer_ptr; /* Convert the pointer to a uint32_t value */
UART_CommandQueueItem_t cmdQueueItem = { 0 }; /* Create a command queue item */
cmdQueueItem.message_ptr = passedValue; /* Set the message pointer */
cmdQueueItem.port = port; /* Set the port */
if (tx_queue_send(&tx_app_cmd_queue, (void*)&cmdQueueItem, 0x0FFF) != TX_SUCCESS) {
tx_app_cmd_queue_last_written_index = old_index; /* Update the last written index */
tx_app_cmd_queue_current_msg_count--; /* Increment the current message count */
printf("Error: Failed to put message in command queue\r\n");
return; /* Return if the message could not be put in the command queue */
}
}
bool cmd_queue_get_message(unsigned char* message, UART_RETURN_PORTTypeDef* port) {
/* This function gets a message from the command queue */
UART_CommandQueueItem_t cmdQueueItem = { 0 }; /* Create a command queue item */
int ret = tx_queue_receive(&tx_app_cmd_queue, (void*)&cmdQueueItem, TX_WAIT_FOREVER); /* Try to receive a message from the command queue */
uint32_t buffr_ptr = cmdQueueItem.message_ptr; /* Pointer to the buffer where the message will be stored */
if (ret == TX_SUCCESS) {
if (buffr_ptr == 0) {
printf("Error: buffr_ptr is NULL\r\n");
return false; /* Return if the message is NULL */
}
if (message == NULL) {
printf("Error: Provided message buffer is NULL\r\n");
return false; /* Return if the provided message buffer is NULL */
}
memset(message, 0, TX_APP_UART_RECEIVE_MSG_SIZE_BYTES); /* Clear the message buffer */
strncpy((char*)message, (const char*)buffr_ptr, TX_APP_UART_RECEIVE_MSG_SIZE_BYTES - 1); /* Copy the message from the command queue to the provided message buffer */
if (port != NULL) {
*port = cmdQueueItem.port; /* Set the port if it is not NULL */
} else {
printf("Error: Provided port pointer is NULL\r\n");
return false; /* Return if the provided port pointer is NULL */
}
/* Decrement the current message count */
if (tx_app_cmd_queue_current_msg_count == 0) {
printf("Error: Command queue is empty, cannot get message\r\n");
return false; /* Return if the command queue is empty */
}
tx_app_cmd_queue_current_msg_count--; /* Decrement the current message count */
/* NOTE: Don't update the last written index here, as it is not necessary for reading messages */
return true;
}
return false;
}
/* USER CODE END PFP */
/**
* @brief Application ThreadX Initialization.
* memory_ptr: memory pointer
* @retval int
*/
UINT App_ThreadX_Init(VOID *memory_ptr)
{
UINT ret = TX_SUCCESS;
TX_BYTE_POOL *byte_pool = (TX_BYTE_POOL*)memory_ptr;
/* USER CODE BEGIN App_ThreadX_MEM_POOL */
printf("App_ThreadX_Init called\r\n");
/* USER CODE END App_ThreadX_MEM_POOL */
CHAR *pointer;
/* Allocate the stack for tx app thread */
if (tx_byte_allocate(byte_pool, (VOID**) &pointer,
TX_APP_STACK_SIZE, TX_NO_WAIT) != TX_SUCCESS)
{
return TX_POOL_ERROR;
}
/* Create tx app thread. */
if (tx_thread_create(&tx_app_thread, "tx app thread", tx_app_thread_entry, 0, pointer,
TX_APP_STACK_SIZE, TX_APP_THREAD_PRIO, TX_APP_THREAD_PREEMPTION_THRESHOLD,
TX_APP_THREAD_TIME_SLICE, TX_APP_THREAD_AUTO_START) != TX_SUCCESS)
{
return TX_THREAD_ERROR;
}
/* Allocate the stack for tx app queue. */
if (tx_byte_allocate(byte_pool, (VOID **) &pointer,
TX_APP_MSG_QUEUE_FULL_SIZE * sizeof(ULONG), TX_NO_WAIT) != TX_SUCCESS)
{
return TX_POOL_ERROR;
}
/* Create tx app queue. */
if (tx_queue_create(&tx_app_msg_queue, "tx app queue", TX_APP_SINGLE_MSG_SIZE,
pointer, TX_APP_MSG_QUEUE_FULL_SIZE * sizeof(ULONG)) != TX_SUCCESS)
{
return TX_QUEUE_ERROR;
}
/* USER CODE BEGIN App_ThreadX_Init */
/* Allocate the stack for vdp uart parse thread */
if (tx_byte_allocate(byte_pool, (VOID**)&pointer,
TX_APP_UART_RECEIVE_STACK_SIZE, TX_NO_WAIT) != TX_SUCCESS) {
return TX_POOL_ERROR;
}
/* Create tx app thread. */
if (tx_thread_create(&tx_vcp_thread, "tx vcp thread", tx_vcp_thread_entry, 0, pointer,
TX_APP_UART_RECEIVE_STACK_SIZE, TX_APP_THREAD_PRIO, TX_APP_THREAD_PREEMPTION_THRESHOLD,
TX_APP_THREAD_TIME_SLICE, TX_APP_THREAD_AUTO_START) != TX_SUCCESS) {
return TX_THREAD_ERROR;
}
// Allocate the Command Queue
if (tx_byte_allocate(byte_pool, (VOID**)&pointer,
TX_APP_UART_RECEIVE_QUEUE_FULL_SIZE_BYTES * sizeof(ULONG), TX_NO_WAIT) != TX_SUCCESS) {
printf("Error allocating memory for UART command queue\r\n");
return TX_POOL_ERROR;
}
/* Create tx uart command queue. */
if (tx_queue_create(&tx_app_cmd_queue, "tx cmd queue",
TX_APP_UART_RECEIVE_MSG_QUEUEITEM_SIZE, pointer,
TX_APP_UART_RECEIVE_QUEUE_FULL_SIZE_BYTES * sizeof(ULONG)) != TX_SUCCESS) {
printf("Error creating UART command queue, full size: %d, msg size: %d, Error: %d\r\n",
TX_APP_UART_RECEIVE_QUEUE_FULL_SIZE_BYTES, TX_APP_UART_RECEIVE_MSG_SIZE_BYTES, TX_QUEUE_ERROR);
return TX_QUEUE_ERROR;
}
// Allocate the stack for tx cmd thread
if (tx_byte_allocate(byte_pool, (VOID**)&pointer,
TX_APP_CMD_THREAD_STACK_SIZE, TX_NO_WAIT) != TX_SUCCESS) {
printf("Error allocating memory for command thread stack\r\n");
return TX_POOL_ERROR;
}
/* Create tx app thread. */
if (tx_thread_create(&tx_cmd_thread, "tx cmd thread", tx_cmd_thread_entry, 0, pointer,
TX_APP_CMD_THREAD_STACK_SIZE, TX_APP_THREAD_PRIO, TX_APP_THREAD_PREEMPTION_THRESHOLD,
TX_APP_THREAD_TIME_SLICE, TX_APP_THREAD_AUTO_START) != TX_SUCCESS) {
printf("Error creating command thread, Error: %d\r\n", TX_THREAD_ERROR);
return TX_THREAD_ERROR;
}
/* USER CODE END App_ThreadX_Init */
return ret;
}
/**
* @brief Function implementing the tx_app_thread_entry thread.
* thread_input: Hardcoded to 0.
* @retval None
*/
void tx_app_thread_entry(ULONG thread_input)
{
/* USER CODE BEGIN tx_app_thread_entry */
UNUSED(thread_input);
printf("ThreadX App Thread Started\r\n");
do {
/* -- Sample board code to toggle leds ---- */
BSP_LED_Toggle(LED_GREEN);
printSpinner();
tx_thread_sleep(100);
BSP_LED_Toggle(LED_YELLOW);
printSpinner();
tx_thread_sleep(100);
BSP_LED_Toggle(LED_RED);
printSpinner();
tx_thread_sleep(100);
/* Sleep for a while */
printSpinner();
tx_thread_sleep(200);
} while (1);
/* USER CODE END tx_app_thread_entry */
}
/**
* @brief Function that implements the kernel's initialization.
* None
* @retval None
*/
void MX_ThreadX_Init(void)
{
/* USER CODE BEGIN Before_Kernel_Start */
printf("MX_ThreadX_Init called\r\n");
/* Initialize leds */
BSP_LED_Init(LED_GREEN);
BSP_LED_Init(LED_YELLOW);
BSP_LED_Init(LED_RED);
/* Initialize USER push-button, will be used to trigger an interrupt each time it's pressed.*/
BSP_PB_Init(BUTTON_USER, BUTTON_MODE_EXTI);
/* Init the UART Rx Ring Buffer*/
UART_Init_Rx_Ringbuf(&huart2RxRingBuffer, (uint8_t*)uart2RxBuffer, TX_UART_RX_RINGBUF_SIZE);
/* Initialize COM1 port (115200, 8 bits (7-bit data + 1 stop bit), no parity */
BspCOMInit.BaudRate = 115200;
BspCOMInit.WordLength = COM_WORDLENGTH_8B;
BspCOMInit.StopBits = COM_STOPBITS_1;
BspCOMInit.Parity = COM_PARITY_NONE;
BspCOMInit.HwFlowCtl = COM_HWCONTROL_NONE;
if (BSP_COM_Init(COM1, &BspCOMInit) != BSP_ERROR_NONE) {
Error_Handler();
}
HAL_StatusTypeDef status = HAL_UART_Receive_IT(&hcom_uart[COM1], &rxByte, 1);
if (status != HAL_OK) {
printf("Error initializing UART IT receive: %d\r\n", status);
Error_Handler();
}
/* -- Sample board code to send message over COM1 port ---- */
// printf("Initializng BSP...\r\n");
/* -- Sample board code to switch on leds ---- */
BSP_LED_On(LED_GREEN);
BSP_LED_On(LED_YELLOW);
BSP_LED_On(LED_RED);
/* Init Ethernet Phy with Debugging*/
// printf("Delaying before init ETH PHY...\r\n");
HAL_Delay(NX_APP_ETHERNET_PHY_SETTLE_TIME_MS); /* Wait for PHY to stabilize */
MX_ETH_Init(); //Due to inability to hardware reset the PHY, we need to wait for it to stabilize before initializing it.
/* USER CODE END Before_Kernel_Start */
tx_kernel_enter();
/* USER CODE BEGIN Kernel_Start_Error */
/* USER CODE END Kernel_Start_Error */
}
/* USER CODE BEGIN 1 */
char getNextSpinnerState(void) {
/* This function returns the next spinner state */
char spinnerChars[] = { '|', '/', '-', '\\' };
spinnerSate = (spinnerSate + 1) % 4; /* Cycle through the spinner states */
return spinnerChars[spinnerSate];
}
void printSpinner(void) {
/* This function prints the spinner state to the console */
// printf("\x1B[2K\x1B[H(%c) ThreadX App Running... \r", getNextSpinnerState());
}
void UART_Init_Rx_Ringbuf(UART_RingReceiveBuffer_t* hringbuf, uint8_t* buffer, uint32_t size) {
if (hringbuf == NULL || buffer == NULL || size == 0) {
printf("Error: Invalid parameters for UART receive ring buffer initialization\r\n");
Error_Handler();
}
/* Initialize the UART receive ring buffer */
memset(hringbuf, 0, sizeof(UART_RingReceiveBuffer_t)); /* Clear the ring buffer structure */
hringbuf->head = 0; /* Reset the head index */
hringbuf->tail = 0; /* Reset the tail index */
hringbuf->size = size; /* Set the size of the ring buffer */
hringbuf->buffer = buffer;
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef* huart) {
if (huart == &hcom_uart[COM1]) { /* Check if the interrupt is from USART2 */
// Add to the ringbuf
if (!putCharToRingBuffer(&huart2RxRingBuffer, rxByte)) {
printf("Error: Failed to put character to ring buffer\r\n");
} else {
UartReady = SET; /* Set the UartReady flag to indicate data is ready */
}
HAL_StatusTypeDef status = HAL_UART_Receive_IT(&hcom_uart[COM1], /* Reinitialize the UART receive interrupt */
&rxByte, 1);
if (status != HAL_OK) {
printf("Error reinitializing UART DMA receive: %d\r\n", status);
}
}
}
/**
* @brief USART Initialization Function - OVERRIDE _weak directive in HAL BSP
* huart: UART handle
* COM_Init: Pointer to the UART initialization structure
* @retval HAL status
*/
HAL_StatusTypeDef MX_USART_Init(UART_HandleTypeDef* huart, MX_UART_InitTypeDef* COM_Init) {
/* USART configuration */
huart->Instance = COM_USART[COM1];
huart->Init.BaudRate = COM_Init->BaudRate;
huart->Init.Mode = UART_MODE_TX_RX;
huart->Init.Parity = (uint32_t)COM_Init->Parity;
huart->Init.WordLength = (uint32_t)COM_Init->WordLength;
huart->Init.StopBits = (uint32_t)COM_Init->StopBits;
huart->Init.HwFlowCtl = (uint32_t)COM_Init->HwFlowCtl;
huart->Init.OverSampling = UART_OVERSAMPLING_8;
huart->Init.ClockPrescaler = UART_PRESCALER_DIV1;
huart->AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
/* Enable the USART interrupt in NVIC */
// Finally start the UART
if (HAL_UART_Init(huart) != HAL_OK) {
Error_Handler();
}
HAL_NVIC_SetPriority(USART3_IRQn, 0, 0); // Set priority for USART3
HAL_NVIC_EnableIRQ(USART3_IRQn);
// // Disable FIFO mode for the USART
// if (HAL_UARTEx_SetTxFifoThreshold(huart, UART_TXFIFO_THRESHOLD_1_8) != HAL_OK) {
// Error_Handler();
// }
// if (HAL_UARTEx_SetRxFifoThreshold(huart, UART_RXFIFO_THRESHOLD_1_8) != HAL_OK) {
// Error_Handler();
// }
// if (HAL_UARTEx_DisableFifoMode(huart) != HAL_OK) {
// Error_Handler();
// }
// printf("USART %d initialized DMA with baud rate %d\r\n", (uint32_t)COM1, (uint32_t)COM_Init->BaudRate);
return HAL_OK;
}
/**
* @brief UART error callbacks
* UartHandle: UART handle
* @note This example shows a simple way to report transfer error, and you can
* add your own implementation.
* @retval None
*/
void HAL_UART_ErrorCallback(UART_HandleTypeDef* huart) {
printf("\r\nUART Error Code: %d\r\n", (int)huart->ErrorCode);
HAL_StatusTypeDef status = HAL_UART_Receive_IT(&hcom_uart[COM1], /* Reinitialize the UART receive interrupt */
&rxByte, 1);
if (status != HAL_OK) {
printf("Error reinitializing UART IT receive: %d\r\n", status);
}
}
void tx_vcp_thread_entry(ULONG thread_input) {
UNUSED(thread_input);
printf("VCP Thread Started\r\n");
char tempChar = 0;
uint16_t cmdBytesRead = 0;
while (1) {
/* Wait for data to be received */
if (UartReady == SET) {
UartReady = RESET; /* Reset the flag */
// Read from the ring buffer
uint32_t availableCount = uart_ringbuf_get_available_count(&huart2RxRingBuffer);
if (availableCount > 0) {
uint32_t bytesRead = 0;
while (bytesRead < availableCount && bytesRead <= TX_UART_RX_MAX_CMD_SIZE) {
if (cmdBytesRead >= TX_UART_RX_MAX_CMD_SIZE) {
printf("Error: Command buffer overflow\r\n");
memset((uint8_t*)tempUart2ThreadReadBuff, 0, TX_UART_RX_MAX_CMD_SIZE); /* Clear the ring buffer structure */
cmdBytesRead = 0; /* Reset the command bytes read counter */
break; /* Exit if we reach the maximum command size */
}
if (getCharFromRingBuffer(&huart2RxRingBuffer, (uint8_t*)&tempChar)) {
bytesRead++; /* Increment the bytes read counter */
if (tempChar == '\r') tempChar = '\n'; /* Convert carriage return to newline for consistency */
tempUart2ThreadReadBuff[cmdBytesRead++] = tempChar; /* Store the character in the temporary buffer */
// Check for line delimiter
if (tempChar == TX_UART_LINE_DLIM_1 || tempChar == TX_UART_LINE_DLIM_2 || tempChar == TX_UART_LINE_DLIM_3) {
tempUart2ThreadReadBuff[cmdBytesRead] = '\0'; /* Null-terminate the string */
if (cmdBytesRead >= TX_UART_RX_MAX_CMD_SIZE) {
printf("Error: Command buffer overflow\r\n");
memset((uint8_t*)tempUart2ThreadReadBuff, 0, TX_UART_RX_MAX_CMD_SIZE); /* Clear the ring buffer structure */
cmdBytesRead = 0; /* Reset the command bytes read counter */
break; /* Exit if we reach the maximum command size */
} else if (cmdBytesRead <= 2) {
memset((uint8_t*)tempUart2ThreadReadBuff, 0, TX_UART_RX_MAX_CMD_SIZE); /* Clear the ring buffer structure */
cmdBytesRead = 0; /* Reset the command bytes read counter */
break; /* Exit if we receive an empty command */
}
if (strlen((char*)tempUart2ThreadReadBuff) <= 2) {
memset((uint8_t*)tempUart2ThreadReadBuff, 0, TX_UART_RX_MAX_CMD_SIZE); /* Clear the ring buffer structure */
cmdBytesRead = 0; /* Reset the command bytes read counter */
break; /* Exit if we receive a command that is too short */
}
// Be extra-safe and replace any remaining line delimiters with null terminators
for (uint16_t i = 0; i < cmdBytesRead; i++) {
if (tempUart2ThreadReadBuff[i] == TX_UART_LINE_DLIM_1 ||
tempUart2ThreadReadBuff[i] == TX_UART_LINE_DLIM_2 ||
tempUart2ThreadReadBuff[i] == TX_UART_LINE_DLIM_3) {
tempUart2ThreadReadBuff[i] = '\0'; /* Null-terminate the string */
}
}
// Queue the command in application command queue
cmd_queue_put_message((unsigned char*)tempUart2ThreadReadBuff, UART_UART2); /* Put the command into the command queue */
memset((uint8_t*)tempUart2ThreadReadBuff, 0, TX_UART_RX_MAX_CMD_SIZE); /* Clear the ring buffer structure */
cmdBytesRead = 0; /* Reset the command bytes read counter */
break; /* Stop reading if we reach the line delimiter */
}
} else {
printf("Error: Failed to get character from ring buffer\r\n");
break; /* Exit if we cannot read more characters */
}
}
}
}
tx_thread_sleep(5); /* Sleep for a while to avoid busy waiting */
}
}
uint32_t uart_ringbuf_get_available_count(UART_RingReceiveBuffer_t* hringbuf) {
/* This function returns the number of available bytes in the UART receive ring buffer */
if (hringbuf == NULL) {
printf("Error: Invalid ring buffer pointer\r\n");
return 0;
}
if (hringbuf->head >= hringbuf->tail) {
return hringbuf->head - hringbuf->tail; /* Normal case */
} else {
return hringbuf->size - (hringbuf->tail - hringbuf->head); /* Wrap around case */
}
}
bool putCharToRingBuffer(UART_RingReceiveBuffer_t* hringbuf, uint8_t data) {
/* This function puts a character into the UART receive ring buffer */
if (hringbuf == NULL || hringbuf->buffer == NULL) {
printf("Error: Invalid ring buffer pointer or buffer is NULL\r\n");
return false;
}
if ((hringbuf->head + 1) % hringbuf->size == hringbuf->tail) {
printf("Error: Ring buffer is full, cannot put data\r\n");
return false; /* Buffer is full, cannot put data */
}
hringbuf->buffer[hringbuf->head] = data; /* Store the data in the buffer */
hringbuf->head = (hringbuf->head + 1) % hringbuf->size; /* Update the head index */
return true; /* Successfully put data into the buffer */
}
bool getCharFromRingBuffer(UART_RingReceiveBuffer_t* hringbuf, uint8_t* data) {
/* This function gets a character from the UART receive ring buffer */
if (hringbuf == NULL || hringbuf->buffer == NULL || data == NULL) {
printf("Error: Invalid ring buffer pointer, buffer is NULL or data pointer is NULL\r\n");
return false;
}
if (hringbuf->head == hringbuf->tail) {
printf("Error: Ring buffer is empty, cannot get data\r\n");
return false; /* Buffer is empty, cannot get data */
}
*data = hringbuf->buffer[hringbuf->tail]; /* Get the data from the buffer */
hringbuf->tail = (hringbuf->tail + 1) % hringbuf->size; /* Update the tail index */
return true; /* Successfully got data from the buffer */
}
/**
* @brief Command thread entry function
* thread_input: Hardcoded to 0.
* @retval None
*
* @note this handles the command queue and processes commands received from the UART.
*
*/
void tx_cmd_thread_entry(ULONG thread_input) {
UNUSED(thread_input);
printf("Command Thread Started\r\n");
unsigned char cmdBuffer[TX_APP_UART_RECEIVE_MSG_SIZE_BYTES] = { 0 }; /* Buffer to store the command */
while (1) {
UART_RETURN_PORTTypeDef returnPort = UART_UART2; /* Default return port is COM1 */
/* Get a command from the command queue */
if (cmd_queue_get_message(cmdBuffer, &returnPort)) { /* Get the command from the command queue */
bool success = parseCommand((const unsigned char*)cmdBuffer, returnPort); /* Parse the command */
if (!success) {
printf("Error: Invalid command\r\n");
}
}
tx_thread_sleep(20); /* Sleep for a while to avoid busy waiting */
}
}
void BSP_PB_Callback(Button_TypeDef Button) {
char* Tx_Buffer = "Button Pressed~\r\n"; /* Message to send when the button is pressed */
if (Button == BUTTON_USER) {
printf(Tx_Buffer);
ULONG actual_length = 0; /* Variable to store the actual length of the written data */
ux_device_class_cdc_acm_write(cdc_acm, (unsigned char*)Tx_Buffer, (strlen(Tx_Buffer)), &actual_length);
}
}
/* USER CODE END 1 */
2025-06-17 7:35 AM - edited 2025-06-17 7:55 AM
app_usbx_device.c
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file app_usbx_device.c
* @author MCD Application Team
* @brief USBX Device applicative file
******************************************************************************
* @attention
*
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "app_usbx_device.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "main.h"
#include "usb.h"
#include "ux_device_descriptors.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
static ULONG hid_custom_interface_number;
static ULONG hid_custom_configuration_number;
static ULONG cdc_acm_interface_number;
static ULONG cdc_acm_configuration_number;
static UX_SLAVE_CLASS_HID_PARAMETER custom_hid_parameter;
static UX_SLAVE_CLASS_CDC_ACM_PARAMETER cdc_acm_parameter;
static TX_THREAD ux_device_app_thread;
/* USER CODE BEGIN PV */
extern PCD_HandleTypeDef hpcd_USB_DRD_FS;
static TX_THREAD ux_cdc_read_thread;
static TX_THREAD ux_customhid_thread;
TX_QUEUE ux_hid_msgqueue;
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
static VOID app_ux_device_thread_entry(ULONG thread_input);
static UINT USBD_ChangeFunction(ULONG Device_State);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/**
* @brief Application USBX Device Initialization.
* memory_ptr: memory pointer
* @retval status
*/
UINT MX_USBX_Device_Init(VOID *memory_ptr)
{
UINT ret = UX_SUCCESS;
UCHAR *device_framework_high_speed;
UCHAR *device_framework_full_speed;
ULONG device_framework_hs_length;
ULONG device_framework_fs_length;
ULONG string_framework_length;
ULONG language_id_framework_length;
UCHAR *string_framework;
UCHAR *language_id_framework;
UCHAR *pointer;
TX_BYTE_POOL *byte_pool = (TX_BYTE_POOL*)memory_ptr;
/* USER CODE BEGIN MX_USBX_Device_Init0 */
/* USER CODE END MX_USBX_Device_Init0 */
/* Allocate the stack for USBX Memory */
if (tx_byte_allocate(byte_pool, (VOID **) &pointer,
USBX_DEVICE_MEMORY_STACK_SIZE, TX_NO_WAIT) != TX_SUCCESS)
{
/* USER CODE BEGIN USBX_ALLOCATE_STACK_ERROR */
return TX_POOL_ERROR;
/* USER CODE END USBX_ALLOCATE_STACK_ERROR */
}
/* Initialize USBX Memory */
if (ux_system_initialize(pointer, USBX_DEVICE_MEMORY_STACK_SIZE, UX_NULL, 0) != UX_SUCCESS)
{
/* USER CODE BEGIN USBX_SYSTEM_INITIALIZE_ERROR */
return UX_ERROR;
/* USER CODE END USBX_SYSTEM_INITIALIZE_ERROR */
}
/* Get Device Framework High Speed and get the length */
device_framework_high_speed = USBD_Get_Device_Framework_Speed(USBD_HIGH_SPEED,
&device_framework_hs_length);
/* Get Device Framework Full Speed and get the length */
device_framework_full_speed = USBD_Get_Device_Framework_Speed(USBD_FULL_SPEED,
&device_framework_fs_length);
/* Get String Framework and get the length */
string_framework = USBD_Get_String_Framework(&string_framework_length);
/* Get Language Id Framework and get the length */
language_id_framework = USBD_Get_Language_Id_Framework(&language_id_framework_length);
/* Install the device portion of USBX */
if (ux_device_stack_initialize(device_framework_high_speed,
device_framework_hs_length,
device_framework_full_speed,
device_framework_fs_length,
string_framework,
string_framework_length,
language_id_framework,
language_id_framework_length,
USBD_ChangeFunction) != UX_SUCCESS)
{
/* USER CODE BEGIN USBX_DEVICE_INITIALIZE_ERROR */
printf("Error: USBX Device initialization failed\n");
return UX_ERROR;
/* USER CODE END USBX_DEVICE_INITIALIZE_ERROR */
}
/* Initialize the hid custom class parameters for the device */
custom_hid_parameter.ux_slave_class_hid_instance_activate = USBD_Custom_HID_Activate;
custom_hid_parameter.ux_slave_class_hid_instance_deactivate = USBD_Custom_HID_Deactivate;
custom_hid_parameter.ux_device_class_hid_parameter_report_address = USBD_HID_ReportDesc(INTERFACE_HID_CUSTOM);
custom_hid_parameter.ux_device_class_hid_parameter_report_length = USBD_HID_ReportDesc_length(INTERFACE_HID_CUSTOM);
custom_hid_parameter.ux_device_class_hid_parameter_report_id = UX_FALSE;
custom_hid_parameter.ux_device_class_hid_parameter_callback = USBD_Custom_HID_SetFeature;
custom_hid_parameter.ux_device_class_hid_parameter_get_callback = USBD_Custom_HID_GetReport;
#ifdef UX_DEVICE_CLASS_HID_INTERRUPT_OUT_SUPPORT
custom_hid_parameter.ux_device_class_hid_parameter_receiver_initialize = ux_device_class_hid_receiver_initialize;
custom_hid_parameter.ux_device_class_hid_parameter_receiver_event_max_number = USBD_Custom_HID_EventMaxNumber();
custom_hid_parameter.ux_device_class_hid_parameter_receiver_event_max_length = USBD_Custom_HID_EventMaxLength();
custom_hid_parameter.ux_device_class_hid_parameter_receiver_event_callback = USBD_Custom_HID_SetReport;
#endif /* UX_DEVICE_CLASS_HID_INTERRUPT_OUT_SUPPORT */
/* USER CODE BEGIN CUSTOM_HID_PARAMETER */
/* USER CODE END CUSTOM_HID_PARAMETER */
/* Get Custom hid configuration number */
hid_custom_configuration_number = USBD_Get_Configuration_Number(CLASS_TYPE_HID, INTERFACE_HID_CUSTOM);
/* Find Custom hid interface number */
hid_custom_interface_number = USBD_Get_Interface_Number(CLASS_TYPE_HID, INTERFACE_HID_CUSTOM);
/* Initialize the device hid custom class */
if (ux_device_stack_class_register(_ux_system_slave_class_hid_name,
ux_device_class_hid_entry,
hid_custom_configuration_number,
hid_custom_interface_number,
&custom_hid_parameter) != UX_SUCCESS)
{
/* USER CODE BEGIN USBX_DEVICE_HID_CUSTOM_REGISTER_ERROR */
return UX_ERROR;
/* USER CODE END USBX_DEVICE_HID_CUSTOM_REGISTER_ERROR */
}
/* Initialize the cdc acm class parameters for the device */
cdc_acm_parameter.ux_slave_class_cdc_acm_instance_activate = USBD_CDC_ACM_Activate;
cdc_acm_parameter.ux_slave_class_cdc_acm_instance_deactivate = USBD_CDC_ACM_Deactivate;
cdc_acm_parameter.ux_slave_class_cdc_acm_parameter_change = USBD_CDC_ACM_ParameterChange;
/* USER CODE BEGIN CDC_ACM_PARAMETER */
/* USER CODE END CDC_ACM_PARAMETER */
/* Get cdc acm configuration number */
cdc_acm_configuration_number = USBD_Get_Configuration_Number(CLASS_TYPE_CDC_ACM, 0);
/* Find cdc acm interface number */
cdc_acm_interface_number = USBD_Get_Interface_Number(CLASS_TYPE_CDC_ACM, 0);
/* Initialize the device cdc acm class */
if (ux_device_stack_class_register(_ux_system_slave_class_cdc_acm_name,
ux_device_class_cdc_acm_entry,
cdc_acm_configuration_number,
cdc_acm_interface_number,
&cdc_acm_parameter) != UX_SUCCESS)
{
/* USER CODE BEGIN USBX_DEVICE_CDC_ACM_REGISTER_ERROR */
return UX_ERROR;
/* USER CODE END USBX_DEVICE_CDC_ACM_REGISTER_ERROR */
}
/* Allocate the stack for device application main thread */
if (tx_byte_allocate(byte_pool, (VOID **) &pointer, UX_DEVICE_APP_THREAD_STACK_SIZE,
TX_NO_WAIT) != TX_SUCCESS)
{
/* USER CODE BEGIN MAIN_THREAD_ALLOCATE_STACK_ERROR */
return TX_POOL_ERROR;
/* USER CODE END MAIN_THREAD_ALLOCATE_STACK_ERROR */
}
/* Create the device application main thread */
if (tx_thread_create(&ux_device_app_thread, UX_DEVICE_APP_THREAD_NAME, app_ux_device_thread_entry,
0, pointer, UX_DEVICE_APP_THREAD_STACK_SIZE, UX_DEVICE_APP_THREAD_PRIO,
UX_DEVICE_APP_THREAD_PREEMPTION_THRESHOLD, UX_DEVICE_APP_THREAD_TIME_SLICE,
UX_DEVICE_APP_THREAD_START_OPTION) != TX_SUCCESS)
{
/* USER CODE BEGIN MAIN_THREAD_CREATE_ERROR */
return TX_THREAD_ERROR;
/* USER CODE END MAIN_THREAD_CREATE_ERROR */
}
/* USER CODE BEGIN MX_USBX_Device_Init1 */
/* Allocate the stack for usbx customhid thread */
if (tx_byte_allocate(byte_pool, (VOID**)&pointer, UX_DEVICE_APP_THREAD_STACK_SIZE, TX_NO_WAIT) != UX_SUCCESS) {
return TX_POOL_ERROR;
}
/* Create the usbx custom hid thread */
if (tx_thread_create(&ux_customhid_thread, "usbx_customhid_app_thread_entry",
usbx_cutomhid_thread_entry,
0, pointer, UX_DEVICE_APP_THREAD_STACK_SIZE, UX_DEVICE_APP_THREAD_PRIO,
UX_DEVICE_APP_THREAD_PREEMPTION_THRESHOLD, UX_DEVICE_APP_THREAD_TIME_SLICE,
UX_DEVICE_APP_THREAD_START_OPTION) != TX_SUCCESS) {
return TX_THREAD_ERROR;
}
/* Allocate the stack for usbx cdc acm read thread */
if (tx_byte_allocate(byte_pool, (VOID**)&pointer, 1024, TX_NO_WAIT) != TX_SUCCESS) {
printf("Error: Failed to allocate memory for usbx cdc acm read thread\n");
return TX_POOL_ERROR;
}
/* Create the usbx_cdc_acm_read_thread_entry thread */
if (tx_thread_create(&ux_cdc_read_thread, "cdc_acm_read_usbx_app_thread_entry",
usbx_cdc_acm_read_thread_entry, 1, pointer,
1024, 9, 9, TX_NO_TIME_SLICE,
TX_AUTO_START) != TX_SUCCESS) {
printf("Error: Failed to create usbx cdc acm read thread\n");
return TX_THREAD_ERROR;
}
/* Allocate the stack for usbx cdc acm LHL Read thread */
if (tx_byte_allocate(byte_pool, (VOID**)&pointer, 1024, TX_NO_WAIT) != TX_SUCCESS) {
printf("Error: Failed to allocate memory for usbx cdc acm HL read thread\n");
return TX_POOL_ERROR;
}
/* Create the usbx_cdc_acm_read_thread_entry thread */
if (tx_thread_create(&ux_usb_cdc_read_thread, "cdc_usb_acm_read_usbx_app_thread_entry",
tx_vcp_usb_thread_entry, 1, pointer,
1024, 9, 9, TX_NO_TIME_SLICE,
TX_AUTO_START) != TX_SUCCESS) {
printf("Error: Failed to create usbx cdc acm HL read thread\n");
return TX_THREAD_ERROR;
}
/* USER CODE END MX_USBX_Device_Init1 */
return ret;
}
/**
* @brief Function implementing app_ux_device_thread_entry.
* @PAram thread_input: User thread input parameter.
* @retval none
*/
static VOID app_ux_device_thread_entry(ULONG thread_input) {
/* USER CODE BEGIN app_ux_device_thread_entry */
printf("USBX Device Thread Started\n");
TX_PARAMETER_NOT_USED(thread_input);
/* Init the UART Rx Ring Buffer*/
UART_Init_Rx_Ringbuf(&vcpRxRingBuffer2, (uint8_t*)vcpRxBuffer2, TX_UART_RX_RINGBUF_SIZE);
HAL_PWREx_EnableVddUSB();
/* USB_DRD_FS init function */
MX_USB_PCD_Init();
HAL_StatusTypeDef status = HAL_OK;
/*USB packet memory area configuration*/
// Composite device PMA configuration
status |= HAL_PCDEx_PMAConfig(&hpcd_USB_DRD_FS, 0x00, PCD_SNG_BUF, 0x0014); // Class COmposite (64 bytes)
status |= HAL_PCDEx_PMAConfig(&hpcd_USB_DRD_FS, 0x80, PCD_SNG_BUF, 0x0054); // Device IN EP0 (64 bytes)
// HID Custom PMA configuration
status |= HAL_PCDEx_PMAConfig(&hpcd_USB_DRD_FS, USBD_HID_CUSTOM_EPOUT_ADDR, PCD_SNG_BUF, 0x0094); // HID Custom IN EP
status |= HAL_PCDEx_PMAConfig(&hpcd_USB_DRD_FS, USBD_HID_CUSTOM_EPIN_ADDR, PCD_SNG_BUF, 0x00D4); // HID Custom OUT EP
// CDC ACM PMA configuration
status |= HAL_PCDEx_PMAConfig(&hpcd_USB_DRD_FS, USBD_CDCACM_EPOUT_ADDR, PCD_SNG_BUF, 0x0114); // CDC ACM OUT EP
status |= HAL_PCDEx_PMAConfig(&hpcd_USB_DRD_FS, USBD_CDCACM_EPIN_ADDR, PCD_SNG_BUF, 0x0154); // CDC ACM IN EP
status |= HAL_PCDEx_PMAConfig(&hpcd_USB_DRD_FS, USBD_CDCACM_EPINCMD_ADDR, PCD_SNG_BUF, 0x0194); // CDC ACM IN CMD EP
// HAL_PCDEx_SetRxFiFo(&hpcd_USB_DRD_FS, 0x200);
// HAL_PCDEx_SetTxFiFo(&hpcd_USB_DRD_FS, 0, 0x100);
// HAL_PCDEx_SetTxFiFo(&hpcd_USB_DRD_FS, 1, 0x100);
// HAL_PCDEx_SetTxFiFo(&hpcd_USB_DRD_FS, 2, 0x100);
// HAL_PCDEx_SetTxFiFo(&hpcd_USB_DRD_FS, 3, 0x100);
if (status != HAL_OK) {
printf("Error: Failed to configure PMA for USB endpoints, status: %d\n", status);
Error_Handler();
}
/* initialize the device controller driver*/
ux_dcd_stm32_initialize((ULONG)USB_DRD_FS, (ULONG)&hpcd_USB_DRD_FS);
/* Start device USB */
HAL_PCD_Start(&hpcd_USB_DRD_FS);
printf("USBX: USB Device Started\n");
while (1) {
/* Check if the device is connected */
tx_thread_sleep(100);
}
/* USER CODE END app_ux_device_thread_entry */
}
/**
* @brief USBD_ChangeFunction
* This function is called when the device state changes.
* Device_State: USB Device State
* @retval status
*/
static UINT USBD_ChangeFunction(ULONG Device_State)
{
UINT status = UX_SUCCESS;
/* USER CODE BEGIN USBD_ChangeFunction0 */
/* USER CODE END USBD_ChangeFunction0 */
switch (Device_State)
{
case UX_DEVICE_ATTACHED:
/* USER CODE BEGIN UX_DEVICE_ATTACHED */
printf("USB Device Attached\r\n");
/* USER CODE END UX_DEVICE_ATTACHED */
break;
case UX_DEVICE_REMOVED:
/* USER CODE BEGIN UX_DEVICE_REMOVED */
printf("USB Device Removed\r\n");
/* USER CODE END UX_DEVICE_REMOVED */
break;
case UX_DCD_STM32_DEVICE_CONNECTED:
/* USER CODE BEGIN UX_DCD_STM32_DEVICE_CONNECTED */
printf("USB Device Connected\r\n");
/* USER CODE END UX_DCD_STM32_DEVICE_CONNECTED */
break;
case UX_DCD_STM32_DEVICE_DISCONNECTED:
/* USER CODE BEGIN UX_DCD_STM32_DEVICE_DISCONNECTED */
printf("USB Device Disconnected\r\n");
/* USER CODE END UX_DCD_STM32_DEVICE_DISCONNECTED */
break;
case UX_DCD_STM32_DEVICE_SUSPENDED:
/* USER CODE BEGIN UX_DCD_STM32_DEVICE_SUSPENDED */
printf("USB Device Suspended\r\n");
/* USER CODE END UX_DCD_STM32_DEVICE_SUSPENDED */
break;
case UX_DCD_STM32_DEVICE_RESUMED:
/* USER CODE BEGIN UX_DCD_STM32_DEVICE_RESUMED */
printf("USB Device Resumed\r\n");
/* USER CODE END UX_DCD_STM32_DEVICE_RESUMED */
break;
case UX_DCD_STM32_SOF_RECEIVED:
/* USER CODE BEGIN UX_DCD_STM32_SOF_RECEIVED */
/* USER CODE END UX_DCD_STM32_SOF_RECEIVED */
break;
default:
/* USER CODE BEGIN DEFAULT */
/* USER CODE END DEFAULT */
break;
}
/* USER CODE BEGIN USBD_ChangeFunction1 */
/* USER CODE END USBD_ChangeFunction1 */
return status;
}
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
2025-06-17 7:39 AM - edited 2025-06-17 7:50 AM
ux_device_descriptors.c
/* The generic device descriptor buffer that will be filled by builder
Size of the buffer is the maximum possible device FS descriptor size. */
#if defined ( __ICCARM__ ) /* IAR Compiler */
#pragma data_alignment=4
#endif /* defined ( __ICCARM__ ) */
__ALIGN_BEGIN static uint8_t DevFrameWorkDesc_FS[USBD_FRAMEWORK_MAX_DESC_SZ] __ALIGN_END = {0};
/* The generic device descriptor buffer that will be filled by builder
Size of the buffer is the maximum possible device HS descriptor size. */
#if defined ( __ICCARM__ ) /* IAR Compiler */
#pragma data_alignment=4
#endif /* defined ( __ICCARM__ ) */
__ALIGN_BEGIN static uint8_t DevFrameWorkDesc_HS[USBD_FRAMEWORK_MAX_DESC_SZ] __ALIGN_END = {0};
static uint8_t *pDevFrameWorkDesc_FS = DevFrameWorkDesc_FS;
static uint8_t *pDevFrameWorkDesc_HS = DevFrameWorkDesc_HS;
/* USER CODE BEGIN PV0 */
/* USER CODE END PV0 */
#if USBD_HID_CUSTOM_ACTIVATED == 1U
#if defined ( __ICCARM__ ) /* IAR Compiler */
#pragma data_alignment=4
#endif /* defined ( __ICCARM__ ) */
__ALIGN_BEGIN uint8_t USBD_CustomHID_ReportDesc[]
__ALIGN_END =
{
/* USER CODE BEGIN USBD_CustomHID_ReportDesc */
0x06, 0x00, 0xff, // Usage Page(Undefined )
0x09, 0x01, // USAGE (Undefined)
0xa1, 0x01, // COLLECTION (Application)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x26, 0xff, 0x00, // LOGICAL_MAXIMUM (255)
0x75, 0x08, // REPORT_SIZE (8)
0x95, 0x40, // REPORT_COUNT (64)
0x09, 0x01, // USAGE (Undefined)
0x81, 0x02, // INPUT (Data,Var,Abs)
0x95, 0x40, // REPORT_COUNT (64)
0x09, 0x01, // USAGE (Undefined)
0x91, 0x02, // OUTPUT (Data,Var,Abs)
0x95, 0x01, // REPORT_COUNT (1)
0x09, 0x01, // USAGE (Undefined)
0xb1, 0x02, // FEATURE (Data,Var,Abs)
/* USER CODE END USBD_CustomHID_ReportDesc */
0xc0 /* End Collection */
};
/**
* @brief USBD_Device_Framework_Builder
* Device Framework builder
* pdev: device instance
* pDevFrameWorkDesc: Pointer to the device framework descriptor
* UserClassInstance: type of the class to be added
* Speed: Speed parameter HS or FS
* @retval status
*/
static uint8_t *USBD_Device_Framework_Builder(USBD_DevClassHandleTypeDef *pdev,
uint8_t *pDevFrameWorkDesc,
uint8_t *UserClassInstance,
uint8_t Speed)
{
static USBD_DeviceDescTypedef *pDevDesc;
static USBD_DevQualiDescTypedef *pDevQualDesc;
uint8_t Idx_Instance = 0U;
/* Set Dev and conf descriptors size to 0 */
pdev->CurrConfDescSz = 0U;
pdev->CurrDevDescSz = 0U;
/* Set the pointer to the device descriptor area*/
pDevDesc = (USBD_DeviceDescTypedef *)pDevFrameWorkDesc;
/* Start building the generic device descriptor common part */
pDevDesc->bLength = (uint8_t)sizeof(USBD_DeviceDescTypedef);
pDevDesc->bDescriptorType = UX_DEVICE_DESCRIPTOR_ITEM;
pDevDesc->bcdUSB = USB_BCDUSB;
pDevDesc->bDeviceClass = 0x00;
pDevDesc->bDeviceSubClass = 0x00;
pDevDesc->bDeviceProtocol = 0x00;
pDevDesc->bMaxPacketSize = USBD_MAX_EP0_SIZE;
pDevDesc->idVendor = USBD_VID;
pDevDesc->idProduct = USBD_PID;
pDevDesc->bcdDevice = 0x0200;
pDevDesc->iManufacturer = USBD_IDX_MFC_STR;
pDevDesc->iProduct = USBD_IDX_PRODUCT_STR;
pDevDesc->iSerialNumber = USBD_IDX_SERIAL_STR;
pDevDesc->bNumConfigurations = USBD_MAX_NUM_CONFIGURATION;
pdev->CurrDevDescSz += (uint32_t)sizeof(USBD_DeviceDescTypedef);
/* Check if USBx is in high speed mode to add qualifier descriptor */
if (Speed == USBD_HIGH_SPEED)
{
pDevQualDesc = (USBD_DevQualiDescTypedef *)(pDevFrameWorkDesc + pdev->CurrDevDescSz);
pDevQualDesc->bLength = (uint8_t)sizeof(USBD_DevQualiDescTypedef);
pDevQualDesc->bDescriptorType = UX_DEVICE_QUALIFIER_DESCRIPTOR_ITEM;
pDevQualDesc->bcdDevice = 0x0200;
pDevQualDesc->Class = 0x00;
pDevQualDesc->SubClass = 0x00;
pDevQualDesc->Protocol = 0x00;
pDevQualDesc->bMaxPacketSize = 0x40;
pDevQualDesc->bNumConfigurations = 0x01;
pDevQualDesc->bReserved = 0x00;
pdev->CurrDevDescSz += (uint32_t)sizeof(USBD_DevQualiDescTypedef);
}
/* Build the device framework */
while (Idx_Instance < USBD_MAX_SUPPORTED_CLASS)
{
if ((pdev->classId < USBD_MAX_SUPPORTED_CLASS) &&
(pdev->NumClasses < USBD_MAX_SUPPORTED_CLASS) &&
(UserClassInstance[Idx_Instance] != CLASS_TYPE_NONE))
{
/* Call the composite class builder */
(void)USBD_FrameWork_AddClass(pdev,
(USBD_CompositeClassTypeDef)UserClassInstance[Idx_Instance],
0, Speed,
(pDevFrameWorkDesc + pdev->CurrDevDescSz));
/* Increment the ClassId for the next occurrence */
pdev->classId ++;
pdev->NumClasses ++;
}
Idx_Instance++;
}
/* Check if there is a composite class and update device class */
if (pdev->NumClasses > 1)
{
pDevDesc->bDeviceClass = 0xEF;
pDevDesc->bDeviceSubClass = 0x02;
pDevDesc->bDeviceProtocol = 0x01;
}
else
{
/* Check if the CDC ACM class is set and update device class */
if (UserClassInstance[0] == CLASS_TYPE_CDC_ACM)
{
pDevDesc->bDeviceClass = 0x02;
pDevDesc->bDeviceSubClass = 0x02;
pDevDesc->bDeviceProtocol = 0x00;
}
}
return pDevFrameWorkDesc;
}
#if USBD_CDC_ACM_CLASS_ACTIVATED == 1
/**
* @brief USBD_FrameWork_CDCDesc
* Configure and Append the CDC Descriptor
* pdev: device instance
* pConf: Configuration descriptor pointer
* Sze: pointer to the current configuration descriptor size
* @retval None
*/
static void USBD_FrameWork_CDCDesc(USBD_DevClassHandleTypeDef *pdev,
uint32_t pConf, uint32_t *Sze)
{
static USBD_IfDescTypedef *pIfDesc;
static USBD_EpDescTypedef *pEpDesc;
static USBD_CDCHeaderFuncDescTypedef *pHeadDesc;
static USBD_CDCCallMgmFuncDescTypedef *pCallMgmDesc;
static USBD_CDCACMFuncDescTypedef *pACMDesc;
static USBD_CDCUnionFuncDescTypedef *pUnionDesc;
#if USBD_COMPOSITE_USE_IAD == 1
static USBD_IadDescTypedef *pIadDesc;
#endif /* USBD_COMPOSITE_USE_IAD == 1 */
#if USBD_COMPOSITE_USE_IAD == 1
pIadDesc = ((USBD_IadDescTypedef *)(pConf + *Sze));
pIadDesc->bLength = (uint8_t)sizeof(USBD_IadDescTypedef);
pIadDesc->bDescriptorType = USB_DESC_TYPE_IAD; /* IAD descriptor */
pIadDesc->bFirstInterface = pdev->tclasslist[pdev->classId].Ifs[0];
pIadDesc->bInterfaceCount = 2U; /* 2 interfaces */
pIadDesc->bFunctionClass = 0x02U;
pIadDesc->bFunctionSubClass = 0x02U;
pIadDesc->bFunctionProtocol = 0x01U;
pIadDesc->iFunction = 0; /* String Index */
*Sze += (uint32_t)sizeof(USBD_IadDescTypedef);
#endif /* USBD_COMPOSITE_USE_IAD == 1 */
/* Control Interface Descriptor */
__USBD_FRAMEWORK_SET_IF(pdev->tclasslist[pdev->classId].Ifs[0], 0U, 1U, 0x02,
0x02U, 0x01U, 0U);
/* Control interface headers */
pHeadDesc = ((USBD_CDCHeaderFuncDescTypedef *)((uint32_t)pConf + *Sze));
/* Header Functional Descriptor*/
pHeadDesc->bLength = 0x05U;
pHeadDesc->bDescriptorType = 0x24U;
pHeadDesc->bDescriptorSubtype = 0x00U;
pHeadDesc->bcdCDC = 0x0110;
*Sze += (uint32_t)sizeof(USBD_CDCHeaderFuncDescTypedef);
/* Call Management Functional Descriptor*/
pCallMgmDesc = ((USBD_CDCCallMgmFuncDescTypedef *)((uint32_t)pConf + *Sze));
pCallMgmDesc->bLength = 0x05U;
pCallMgmDesc->bDescriptorType = 0x24U;
pCallMgmDesc->bDescriptorSubtype = 0x01U;
pCallMgmDesc->bmCapabilities = 0x00U;
pCallMgmDesc->bDataInterface = pdev->tclasslist[pdev->classId].Ifs[1];
*Sze += (uint32_t)sizeof(USBD_CDCCallMgmFuncDescTypedef);
/* ACM Functional Descriptor*/
pACMDesc = ((USBD_CDCACMFuncDescTypedef *)((uint32_t)pConf + *Sze));
pACMDesc->bLength = 0x04U;
pACMDesc->bDescriptorType = 0x24U;
pACMDesc->bDescriptorSubtype = 0x02U;
pACMDesc->bmCapabilities = 0x02;
*Sze += (uint32_t)sizeof(USBD_CDCACMFuncDescTypedef);
/* Union Functional Descriptor*/
pUnionDesc = ((USBD_CDCUnionFuncDescTypedef *)((uint32_t)pConf + *Sze));
pUnionDesc->bLength = 0x05U;
pUnionDesc->bDescriptorType = 0x24U;
pUnionDesc->bDescriptorSubtype = 0x06U;
pUnionDesc->bMasterInterface = pdev->tclasslist[pdev->classId].Ifs[0];
pUnionDesc->bSlaveInterface = pdev->tclasslist[pdev->classId].Ifs[1];
*Sze += (uint32_t)sizeof(USBD_CDCUnionFuncDescTypedef);
/* Append Endpoint descriptor to Configuration descriptor */
__USBD_FRAMEWORK_SET_EP_MEMSAFE(pdev->tclasslist[pdev->classId].Eps[2].add, \
USBD_EP_TYPE_INTR,
(uint16_t)pdev->tclasslist[pdev->classId].Eps[2].size,
USBD_CDCACM_EPINCMD_HS_BINTERVAL,
USBD_CDCACM_EPINCMD_FS_BINTERVAL);
/* Data Interface Descriptor */
__USBD_FRAMEWORK_SET_IF(pdev->tclasslist[pdev->classId].Ifs[1], 0U, 2U, 0x0A,
0U, 0U, 0U);
/* Append Endpoint descriptor to Configuration descriptor */
__USBD_FRAMEWORK_SET_EP_MEMSAFE((pdev->tclasslist[pdev->classId].Eps[0].add), \
(USBD_EP_TYPE_BULK),
(uint16_t)(pdev->tclasslist[pdev->classId].Eps[0].size),
(0x00U), (0x00U));
/* Append Endpoint descriptor to Configuration descriptor */
__USBD_FRAMEWORK_SET_EP_MEMSAFE((pdev->tclasslist[pdev->classId].Eps[1].add), \
(USBD_EP_TYPE_BULK),
(uint16_t)(pdev->tclasslist[pdev->classId].Eps[1].size),
(0x00U), (0x00U));
/* Update Config Descriptor and IAD descriptor */
((USBD_ConfigDescTypedef *)pConf)->bNumInterfaces += 2U;
((USBD_ConfigDescTypedef *)pConf)->wDescriptorLength = *Sze;
}
#endif /* USBD_CDC_ACM_CLASS_ACTIVATED == 1 */
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
2025-06-17 7:41 AM - edited 2025-06-17 7:50 AM
ux_device_cdc_acm.c
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
__aligned(4) UX_SLAVE_CLASS_CDC_ACM* cdc_acm;
__IO ITStatus UartUSBReady = RESET;
UX_SLAVE_CLASS_CDC_ACM_LINE_CODING_PARAMETER CDC_VCP_LineCoding =
{
115200, /* baud rate */
0x00, /* stop bits-1 */
0x00, /* parity - none */
0x08 /* nb. of bits 8 */
};
extern TX_QUEUE tx_app_cmd_queue; /* Command queue for the application thread */
TX_THREAD ux_usb_cdc_read_thread;
/* USER CODE END PV */
/**
* @brief USBD_CDC_ACM_Activate
* This function is called when insertion of a CDC ACM device.
* cdc_acm_instance: Pointer to the cdc acm class instance.
* @retval none
*/
VOID USBD_CDC_ACM_Activate(VOID* cdc_acm_instance) {
/* USER CODE BEGIN USBD_CDC_ACM_Activate */
/* Save the CDC instance */
cdc_acm = (UX_SLAVE_CLASS_CDC_ACM*)cdc_acm_instance;
// printf("USB CDC ACM Activated at pointer: %p\r\n", (void*)cdc_acm);
/* Set device class_cdc_acm with default parameters */
if (ux_device_class_cdc_acm_ioctl(cdc_acm, UX_SLAVE_CLASS_CDC_ACM_IOCTL_SET_LINE_CODING,
&CDC_VCP_LineCoding) != UX_SUCCESS) {
Error_Handler();
}
/* USER CODE END USBD_CDC_ACM_Activate */
return;
}
/**
* @brief USBD_CDC_ACM_Deactivate
* This function is called when extraction of a CDC ACM device.
* cdc_acm_instance: Pointer to the cdc acm class instance.
* @retval none
*/
VOID USBD_CDC_ACM_Deactivate(VOID* cdc_acm_instance) {
/* USER CODE BEGIN USBD_CDC_ACM_Deactivate */
UX_PARAMETER_NOT_USED(cdc_acm_instance);
/* USER CODE END USBD_CDC_ACM_Deactivate */
return;
}
/**
* @brief USBD_CDC_ACM_ParameterChange
* This function is invoked to manage the CDC ACM class requests.
* cdc_acm_instance: Pointer to the cdc acm class instance.
* @retval none
*/
VOID USBD_CDC_ACM_ParameterChange(VOID* cdc_acm_instance) {
/* USER CODE BEGIN USBD_CDC_ACM_ParameterChange */
UX_PARAMETER_NOT_USED(cdc_acm_instance);
ULONG request;
UX_SLAVE_TRANSFER* transfer_request;
UX_SLAVE_DEVICE* device;
/* Get the pointer to the device. */
device = &_ux_system_slave->ux_system_slave_device;
/* Get the pointer to the transfer request associated with the control endpoint. */
transfer_request = &device->ux_slave_device_control_endpoint.ux_slave_endpoint_transfer_request;
request = *(transfer_request->ux_slave_transfer_request_setup + UX_SETUP_REQUEST);
switch (request) {
case UX_SLAVE_CLASS_CDC_ACM_SET_LINE_CODING:
/* Get the Line Coding parameters */
if (ux_device_class_cdc_acm_ioctl(cdc_acm, UX_SLAVE_CLASS_CDC_ACM_IOCTL_GET_LINE_CODING,
&CDC_VCP_LineCoding) != UX_SUCCESS) {
Error_Handler();
}
break;
case UX_SLAVE_CLASS_CDC_ACM_GET_LINE_CODING:
/* Set the Line Coding parameters */
if (ux_device_class_cdc_acm_ioctl(cdc_acm, UX_SLAVE_CLASS_CDC_ACM_IOCTL_SET_LINE_CODING,
&CDC_VCP_LineCoding) != UX_SUCCESS) {
Error_Handler();
}
break;
case UX_SLAVE_CLASS_CDC_ACM_SET_CONTROL_LINE_STATE:
default:
break;
}
printf("USB CDC ACM Parameter Change: Request %lu\r\n", request);
/* USER CODE END USBD_CDC_ACM_ParameterChange */
return;
}
/* USER CODE BEGIN 1 */
VOID usbx_cdc_acm_read_thread_entry(ULONG thread_input) {
/* Private Variables */
UNUSED(thread_input);
ULONG __aligned(4) rx_actual_length;
uint8_t __aligned(4) UserRxBuffer[64];
printf("USBX CDC ACM Read Thread Started\n");
/* Infinite Loop */
while (1) {
if (cdc_acm != UX_NULL) {
// printf("Reading from USB CDC ACM... at pointer: %p\r\n", (void*)cdc_acm);
ux_device_class_cdc_acm_read(cdc_acm, (UCHAR*)UserRxBuffer, sizeof(UserRxBuffer), &rx_actual_length);
if (rx_actual_length == 0) {
printf("No data received from USB CDC ACM, sleeping...\r\n");
tx_thread_sleep(5); // Sleep for a while if no data received
continue; // Skip to the next iteration
}
printf("Received %lu bytes from USB CDC ACM\r\n", rx_actual_length);
// We actually read something, so we can process it
for (uint32_t i = 0; i < rx_actual_length; i++) {
if (!putCharToRingBuffer(&vcpRxRingBuffer2, UserRxBuffer[i])) {
printf("Error: Failed to put character to VCP ring buffer\r\n");
}
}
UartUSBReady = SET; /* Set the flag to indicate that data is ready */
// Shorter sleep if receiving to help with straming
tx_thread_sleep(2);
} else {
printf("Error: cdc_acm is NULL, cannot read data\r\n");
tx_thread_sleep(100); /* Sleep for a while if cdc_acm is NULL */
}
}
}
void tx_vcp_usb_thread_entry(ULONG thread_input) {
UNUSED(thread_input);
printf("VCP USB HL Thread Started\r\n");
char tempChar = 0;
uint16_t cmdBytesRead = 0;
while (1) {
/* Wait for data to be received */
if (UartUSBReady == SET) {
UartUSBReady = RESET; /* Reset the flag */
// Read from the ring buffer
uint32_t availableCount = uart_ringbuf_get_available_count(&vcpRxRingBuffer2);
if (availableCount > 0) {
uint32_t bytesRead = 0;
while (bytesRead < availableCount && bytesRead <= TX_UART_RX_MAX_CMD_SIZE) {
if (cmdBytesRead >= TX_UART_RX_MAX_CMD_SIZE) {
printf("Error: Command buffer overflow\r\n");
memset((uint8_t*)tempVcpThreadReadBuff2, 0, TX_UART_RX_MAX_CMD_SIZE); /* Clear the ring buffer structure */
cmdBytesRead = 0; /* Reset the command bytes read counter */
break; /* Exit if we reach the maximum command size */
}
if (getCharFromRingBuffer(&vcpRxRingBuffer2, (uint8_t*)&tempChar)) {
bytesRead++; /* Increment the bytes read counter */
if (tempChar == '\r') tempChar = '\n'; /* Convert carriage return to newline for consistency */
tempVcpThreadReadBuff2[cmdBytesRead++] = tempChar; /* Store the character in the temporary buffer */
// Check for line delimiter
if (tempChar == TX_UART_LINE_DLIM_1 || tempChar == TX_UART_LINE_DLIM_2 || tempChar == TX_UART_LINE_DLIM_3) {
tempVcpThreadReadBuff2[cmdBytesRead] = '\0'; /* Null-terminate the string */
if (cmdBytesRead >= TX_UART_RX_MAX_CMD_SIZE) {
printf("Error: Command buffer overflow\r\n");
memset((uint8_t*)tempVcpThreadReadBuff2, 0, TX_UART_RX_MAX_CMD_SIZE); /* Clear the ring buffer structure */
cmdBytesRead = 0; /* Reset the command bytes read counter */
break; /* Exit if we reach the maximum command size */
} else if (cmdBytesRead <= 2) {
memset((uint8_t*)tempVcpThreadReadBuff2, 0, TX_UART_RX_MAX_CMD_SIZE); /* Clear the ring buffer structure */
cmdBytesRead = 0; /* Reset the command bytes read counter */
break; /* Exit if we receive an empty command */
}
if (strlen((char*)tempVcpThreadReadBuff2) <= 2) {
memset((uint8_t*)tempVcpThreadReadBuff2, 0, TX_UART_RX_MAX_CMD_SIZE); /* Clear the ring buffer structure */
cmdBytesRead = 0; /* Reset the command bytes read counter */
break; /* Exit if we receive a command that is too short */
}
// Be extra-safe and replace any remaining line delimiters with null terminators
for (uint16_t i = 0; i < cmdBytesRead; i++) {
if (tempVcpThreadReadBuff2[i] == TX_UART_LINE_DLIM_1 ||
tempVcpThreadReadBuff2[i] == TX_UART_LINE_DLIM_2 ||
tempVcpThreadReadBuff2[i] == TX_UART_LINE_DLIM_3) {
tempVcpThreadReadBuff2[i] = '\0'; /* Null-terminate the string */
}
}
// Queue the command in application command queue
cmd_queue_put_message((unsigned char*)tempVcpThreadReadBuff2, UART_VCP_USB); /* Put the command into the command queue */
memset((uint8_t*)tempVcpThreadReadBuff2, 0, TX_UART_RX_MAX_CMD_SIZE); /* Clear the ring buffer structure */
cmdBytesRead = 0; /* Reset the command bytes read counter */
break; /* Stop reading if we reach the line delimiter */
}
} else {
printf("Error: Failed to get character from ring buffer\r\n");
break; /* Exit if we cannot read more characters */
}
}
}
}
tx_thread_sleep(5); /* Sleep for a while to avoid busy waiting */
}
}
/* USER CODE END 1 */
ux_device_customhid.c
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "ux_device_descriptors.h"
/* USER CODE END Includes */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
UX_SLAVE_CLASS_HID* hid_custom;
UX_SLAVE_CLASS_HID_EVENT hid_event;
UX_DEVICE_CLASS_HID_RECEIVED_EVENT hid_received_event;
extern TX_QUEUE ux_hid_msgqueue;
/* USER CODE END PV */
/**
* @brief USBD_Custom_HID_Activate
* This function is called when insertion of a Custom HID device.
* hid_instance: Pointer to the hid class instance.
* @retval none
*/
VOID USBD_Custom_HID_Activate(VOID *hid_instance)
{
/* USER CODE BEGIN USBD_Custom_HID_Activate */
/* Save the Custom HID instance */
hid_custom = (UX_SLAVE_CLASS_HID*)hid_instance;
printf("Custom HID Activated\r\n");
/* USER CODE END USBD_Custom_HID_Activate */
return;
}
/**
* @brief USBD_Custom_HID_Deactivate
* This function is called when extraction of a Custom HID device.
* hid_instance: Pointer to the hid class instance.
* @retval none
*/
VOID USBD_Custom_HID_Deactivate(VOID *hid_instance)
{
/* USER CODE BEGIN USBD_Custom_HID_Deactivate */
UX_PARAMETER_NOT_USED(hid_instance);
/* Reset the Custom HID instance */
hid_custom = UX_NULL;
/* USER CODE END USBD_Custom_HID_Deactivate */
return;
}
/**
* @brief USBD_Custom_HID_SetFeature
* This function is invoked when the host sends a HID SET_REPORT
* to the application over Endpoint 0 (Set Feature).
* hid_instance: Pointer to the hid class instance.
* hid_event: Pointer to structure of the hid event.
* @retval status
*/
UINT USBD_Custom_HID_SetFeature(UX_SLAVE_CLASS_HID *hid_instance,
UX_SLAVE_CLASS_HID_EVENT *hid_event)
{
UINT status = UX_SUCCESS;
/* USER CODE BEGIN USBD_Custom_HID_SetFeature */
UX_PARAMETER_NOT_USED(hid_instance);
UX_PARAMETER_NOT_USED(hid_event);
/* USER CODE END USBD_Custom_HID_SetFeature */
return status;
}
/**
* @brief USBD_Custom_HID_GetReport
* This function is invoked when host is requesting event through
* control GET_REPORT request.
* hid_instance: Pointer to the hid class instance.
* hid_event: Pointer to structure of the hid event.
* @retval status
*/
UINT USBD_Custom_HID_GetReport(UX_SLAVE_CLASS_HID *hid_instance,
UX_SLAVE_CLASS_HID_EVENT *hid_event)
{
UINT status = UX_SUCCESS;
/* USER CODE BEGIN USBD_Custom_HID_GetReport */
UX_PARAMETER_NOT_USED(hid_instance);
UX_PARAMETER_NOT_USED(hid_event);
/* USER CODE END USBD_Custom_HID_GetReport */
return status;
}
#ifdef UX_DEVICE_CLASS_HID_INTERRUPT_OUT_SUPPORT
/**
* @brief USBD_Custom_HID_SetReport
* This function is invoked when the host sends a HID SET_REPORT
* to the application over Endpoint OUT (Set Report).
* hid_instance: Pointer to the hid class instance.
* @retval none
*/
VOID USBD_Custom_HID_SetReport(struct UX_SLAVE_CLASS_HID_STRUCT *hid_instance)
{
/* USER CODE BEGIN USBD_Custom_HID_SetReport */
UX_PARAMETER_NOT_USED(hid_instance);
/* USER CODE END USBD_Custom_HID_SetReport */
return;
}
/**
* @brief USBD_Custom_HID_EventMaxNumber
* This function to set receiver event max number parameter.
* none
* @retval receiver event max number
*/
ULONG USBD_Custom_HID_EventMaxNumber(VOID)
{
ULONG max_number = 0U;
/* USER CODE BEGIN USBD_Custom_HID_EventMaxNumber */
max_number = 1;
/* USER CODE END USBD_Custom_HID_EventMaxNumber */
return max_number;
}
/**
* @brief USBD_Custom_HID_EventMaxLength
* This function to set receiver event max length parameter.
* none
* @retval receiver event max length
*/
ULONG USBD_Custom_HID_EventMaxLength(VOID)
{
ULONG max_length = 0U;
/* USER CODE BEGIN USBD_Custom_HID_EventMaxLength */
/* Set max event length */
max_length = USBD_HID_CUSTOM_EPOUT_FS_MPS;
/* USER CODE END USBD_Custom_HID_EventMaxLength */
return max_length;
}
#endif /* UX_DEVICE_CLASS_HID_INTERRUPT_OUT_SUPPORT */
/* USER CODE BEGIN 1 */
/**
* @brief Function implementing usbx_cutomhid_thread_entry.
* thread_input: not used
* @retval none
*/
VOID usbx_cutomhid_thread_entry(ULONG thread_input) {
// UX_SLAVE_DEVICE* device;
printf("Custom HID Thread Started\n");
UX_PARAMETER_NOT_USED(thread_input);
// device = &_ux_system_slave->ux_system_slave_device;
// UNUSED(device);
// ux_utility_memory_set(&hid_event, 0, sizeof(UX_SLAVE_CLASS_HID_EVENT));
while (1) {
/* Check if the device state already configured */
// if ((device->ux_slave_device_state == UX_DEVICE_CONFIGURED) && (hid_custom != UX_NULL)) {
// /* Wait for a hid event */
// if (tx_queue_receive(&ux_hid_msgqueue, &hid_event, TX_WAIT_FOREVER) != TX_SUCCESS) {
// Error_Handler();
// }
// /* Send hid event */
// ux_device_class_hid_event_set(hid_custom, &hid_event);
// } else {
/* Sleep thread for 10ms */
// printf("Custom HID Thread Sleeping\n");
tx_thread_sleep(1000);
// }
}
}
/* USER CODE END 1 */
2025-06-17 7:42 AM - edited 2025-06-17 8:00 AM
Current Status:
Using add_compile_options("-mno-unaligned-access") inside the CMakeLists.txt,
I now have a mostly working USBx app where the composite device enumerates correctly, Here's the weird part...
For some reason, ux_device_class_cdc_acm_read() seems to never have the semaphore released, or something at a lower level is not finalizing the bulk OUT transfer to the STM32. Again, the STM32 can do bulk IN Transfers to the host without issue. I have tested with multiple terminal programs, and can even see that something is causing the transfer to hang via a custom serial debugger I made here . The transfer is never finished according to my serial program, and once the first transfer is started, the transaction remains open. The STM32 can still send data back to the host without issues, only bulk OUT transfers seem to be affected. Below is the current code for the composite Custom HID and CDC ACM stack.
In Summary:
I thought the issue was with the initial setup of the low-level UXB stack and linking it to the middleware, but I can't seem to find an issue there.
/**
* @brief Function implementing app_ux_device_thread_entry.
* thread_input: User thread input parameter.
* @retval none
*/
static VOID app_ux_device_thread_entry(ULONG thread_input) {
/* USER CODE BEGIN app_ux_device_thread_entry */
printf("USBX Device Thread Started\n");
TX_PARAMETER_NOT_USED(thread_input);
/* Init the UART Rx Ring Buffer*/
UART_Init_Rx_Ringbuf(&vcpRxRingBuffer2, (uint8_t*)vcpRxBuffer2, TX_UART_RX_RINGBUF_SIZE);
HAL_PWREx_EnableVddUSB();
/* USB_DRD_FS init function */
MX_USB_PCD_Init();
HAL_StatusTypeDef status = HAL_OK;
/*USB packet memory area configuration*/
// Composite device PMA configuration
status |= HAL_PCDEx_PMAConfig(&hpcd_USB_DRD_FS, 0x00, PCD_SNG_BUF, 0x0014); // Class COmposite (64 bytes)
status |= HAL_PCDEx_PMAConfig(&hpcd_USB_DRD_FS, 0x80, PCD_SNG_BUF, 0x0054); // Device IN EP0 (64 bytes)
// HID Custom PMA configuration
status |= HAL_PCDEx_PMAConfig(&hpcd_USB_DRD_FS, USBD_HID_CUSTOM_EPOUT_ADDR, PCD_SNG_BUF, 0x0094); // HID Custom IN EP
status |= HAL_PCDEx_PMAConfig(&hpcd_USB_DRD_FS, USBD_HID_CUSTOM_EPIN_ADDR, PCD_SNG_BUF, 0x00D4); // HID Custom OUT EP
// CDC ACM PMA configuration
status |= HAL_PCDEx_PMAConfig(&hpcd_USB_DRD_FS, USBD_CDCACM_EPOUT_ADDR, PCD_SNG_BUF, 0x0114); // CDC ACM OUT EP
status |= HAL_PCDEx_PMAConfig(&hpcd_USB_DRD_FS, USBD_CDCACM_EPIN_ADDR, PCD_SNG_BUF, 0x0154); // CDC ACM IN EP
status |= HAL_PCDEx_PMAConfig(&hpcd_USB_DRD_FS, USBD_CDCACM_EPINCMD_ADDR, PCD_SNG_BUF, 0x0194); // CDC ACM IN CMD EP
// HAL_PCDEx_SetRxFiFo(&hpcd_USB_DRD_FS, 0x200);
// HAL_PCDEx_SetTxFiFo(&hpcd_USB_DRD_FS, 0, 0x100);
// HAL_PCDEx_SetTxFiFo(&hpcd_USB_DRD_FS, 1, 0x100);
// HAL_PCDEx_SetTxFiFo(&hpcd_USB_DRD_FS, 2, 0x100);
// HAL_PCDEx_SetTxFiFo(&hpcd_USB_DRD_FS, 3, 0x100);
if (status != HAL_OK) {
printf("Error: Failed to configure PMA for USB endpoints, status: %d\n", status);
Error_Handler();
}
/* initialize the device controller driver*/
ux_dcd_stm32_initialize((ULONG)USB_DRD_FS, (ULONG)&hpcd_USB_DRD_FS);
/* Start device USB */
HAL_PCD_Start(&hpcd_USB_DRD_FS);
printf("USBX: USB Device Started\n");
while (1) {
/* Check if the device is connected */
tx_thread_sleep(100);
}
/* USER CODE END app_ux_device_thread_entry */
}
My ACM Read task seems to never have the semaphore resolved from
VOID usbx_cdc_acm_read_thread_entry(ULONG thread_input) {
/* Private Variables */
UNUSED(thread_input);
ULONG __aligned(4) rx_actual_length;
uint8_t __aligned(4) UserRxBuffer[64];
printf("USBX CDC ACM Read Thread Started\n");
/* Infinite Loop */
while (1) {
if (cdc_acm != UX_NULL) {
// printf("Reading from USB CDC ACM... at pointer: %p\r\n", (void*)cdc_acm);
ux_device_class_cdc_acm_read(cdc_acm, (UCHAR*)UserRxBuffer, 64, &rx_actual_length);
if (rx_actual_length == 0) {
printf("No data received from USB CDC ACM, sleeping...\r\n");
tx_thread_sleep(5); // Sleep for a while if no data received
continue; // Skip to the next iteration
}
printf("Received %lu bytes from USB CDC ACM\r\n", rx_actual_length);
// We actually read something, so we can process it
for (uint32_t i = 0; i < rx_actual_length; i++) {
if (!putCharToRingBuffer(&vcpRxRingBuffer2, UserRxBuffer[i])) {
printf("Error: Failed to put character to VCP ring buffer\r\n");
}
}
UartUSBReady = SET; /* Set the flag to indicate that data is ready */
// Shorter sleep if receiving to help with straming
tx_thread_sleep(2);
} else {
printf("Error: cdc_acm is NULL, cannot read data\r\n");
tx_thread_sleep(100); /* Sleep for a while if cdc_acm is NULL */
}
}
}
Does anyone have any insight as to what I'm missing here? I've followed just about every tutorial for USBX on STM32 H5/H7. I'm either stuck with UNALIGNED access hard faults, or this current issue.
My guess is that the compiler is modifying some low-level buffer operations that are responsible for transferring the low-level USB packet buffers for the CDC read endpoint to the USBx stack, but I'm not exactly sure where to look. Or, hopefully I'm just missing something on the USBx middleware init and it's a user error!
Thanks for any help and insight :)