基于STM320B1使用CherryUSB协议栈来做虚拟串口。这个协议栈是中国人自己写的一个USB协议栈,据说比STM32自带的好用。
之前作为螺丝钉,项目的芯片选型,技术选型一直不受我的控制。加入新公司之后,因为新公司人比较少,甚至没有产品经理,所以从硬件选型到技术选型都由我自己设计了。
于是我觉得项目中80%用旧技术,20%用新技术,也算是一种学习。如果实在搞不定,就在硬件上加一个TLL to USB的芯片。
这款USB协议栈是基于USB的IP核所设计的,可以从它的port文件夹内看到支持的IP类型。它的适配是比较全的,基本上市面上常有的芯片都有支持。
因为IP核设计是比较困难的,需要设计出符合规范的IP核需要一个庞大的IC团队,其中Synopsys的dwc2这款IP核是经过无数芯片验证的。
早年间ST也自己设计过自己的IP核fsdev(应用在F1,F0等一些低端的芯片上),但是不支持HOST。
在高端芯片上,ST采取了支付授权费的方式,采用了dwc2的IP核。
我们国产的很多芯片例如GD32,AT32,HC32也是采用支付授权费的方式,使用dwc2这款IP核。
usb_dc_xxx.c 是芯片所对应不同ip和的驱动,对不同的IP抽象出了统一的接口。
本次使用的单片机为STM32G0系列,可以通过阅读数据手册,对比寄存器来判断到底用的是哪个IP核。如果寄存器中有USB_CNTR,USB_ISTR,USB_EPnR字样,说明这个IP核是ST自研的fsdev。
根据上面的分析,将port\fsdev中的文件加入工程。其中usb_glue_st是针对STM32CubeMX生成的代码所设计的胶水层。
我的工程中没有用CubeMX生成代码,自然也没有里面使用到的HAL库的API接口。所以需要自己实现以下函数:
//初始化USB
void usb_dc_low_level_init(uint8_t busid)
{
}
//反初始化USB
void usb_dc_low_level_deinit(uint8_t busid)
{
}
并且在USB中断中调用
USB控制器有三种类型:
从机是被控方,主机是控制方,OTG是既是从机也是主机的设备
在core目录下有着usbd_core、usbh_core和usbotg_core三个文件。
从名字就能看出来对应device,host和otg三种设备。
我这儿用到的从机,也就是device。
从机:例如单片机当U盘。
主机:例如读取U盘的单片机。
OTG:通过ID线切换角色,或者通过type-c的cc线协议切换。我没用过
在USB出现前,每个硬件厂商都要为自己的设备写一个Windows驱动程序。用户买个鼠标还需要先从软盘装驱动,非常的痛苦。
USB Class本质上是一套标准化的沟通协议。
USB组织规定:所有的鼠标键盘都按HID Class说话。所有的U盘都按MSC Class说话。所有的摄像头都按 UVC Class说话。
这样的结果就是windows只需要内置几种通用的驱动,就能驱动世界上好几亿的设备。实现了免驱。
进阶:还有一种复合设备,例如4G模组,你插入后,它既是网卡又是串口还是U盘。这个就是定义了多个interface,每个interface声明不同的Class。
这样子相当于在usbd_core,用户应用层之间还有一个中间层。
CherryUSB实现了许多常用的Class,本次使用的虚拟串口。将class/usbd_cdc_acm.c代码加入即可。
一直去发现进不了中断。
最后在群里询问CherryUSB的作者。群主说是不支持G0。
调试结束 OVER
最后使用了ST的官方库实现了USB CDC的功能,
// 这里输入的参数(也就是busid)是usb接口的文件描述符。用于区分不同的USB
USBD_IRQHandler(0);
/*
* Copyright (c) 2025 by Lu Xianfan.
* @FilePath : app_usb_cdc.c
* @Author : lxf
* @Date : 2025-01-20 10:00:00
* @LastEditors : lxf_zjnb@qq.com
* @LastEditTime : 2026-01-21 16:00:00
* @Brief : USB CDC (虚拟串口) 应用层
* @usage :
* @code
* app_usb_cdc_init();
* while (1) {
* app_usb_cdc_poll();
* }
* @endcode
*/
/*---------- includes ----------*/
#include "app_main.h"
#include "usbd_core.h"
#include "usbd_cdc.h"
/*---------- macro ----------*/
/* USB配置描述符总长度 = 配置描述符(9) + CDC描述符(67) */
#define USB_CONFIG_SIZE (9 + CDC_ACM_DESCRIPTOR_LEN)
/* CDC最大包大小 (Full Speed) */
#define CDC_MAX_MPS CONFIG_USB_CDC_MAX_MPS
/* CDC端点定义 */
#define CDC_IN_EP 0x81
#define CDC_OUT_EP 0x02
#define CDC_INT_EP 0x83
/*---------- type define ----------*/
/*---------- variable prototype ----------*/
/*---------- function prototype ----------*/
static const uint8_t *device_descriptor_callback(uint8_t speed);
static const uint8_t *config_descriptor_callback(uint8_t speed);
static const uint8_t *device_quality_descriptor_callback(uint8_t speed);
static const char *string_descriptor_callback(uint8_t speed, uint8_t index);
static void usbd_event_handler(uint8_t busid, uint8_t event);
/*---------- variable ----------*/
/* CDC端点结构体 */
static struct usbd_endpoint cdc_in_ep = {
.ep_addr = CDC_IN_EP,
.ep_cb = NULL
};
static struct usbd_endpoint cdc_out_ep = {
.ep_addr = CDC_OUT_EP,
.ep_cb = NULL
};
static struct usbd_endpoint cdc_int_ep = {
.ep_addr = CDC_INT_EP,
.ep_cb = NULL
};
/* CDC接口 (需要2个接口:通信控制接口 + 数据接口) */
static struct usbd_interface intf0;
static struct usbd_interface intf1;
struct usb_descriptor cdc_descriptor = {
.device_descriptor_callback = device_descriptor_callback,
.config_descriptor_callback = config_descriptor_callback,
.device_quality_descriptor_callback = device_quality_descriptor_callback,
.string_descriptor_callback = string_descriptor_callback
};
/*---------- function ----------*/
/* USB 设备描述符 (18字节) */
static const uint8_t device_descriptor[] = {
USB_DEVICE_DESCRIPTOR_INIT(
USB_2_0, /* bcdUSB: USB 2.0 */
0xEF, /* bDeviceClass: 0xEF (Miscellaneous) 用于复合设备或IAD */
0x02, /* bDeviceSubClass: 0x02 (Common) */
0x01, /* bDeviceProtocol: 0x01 (IAD) */
USBD_VID, /* idVendor: VID */
USBD_PID, /* idProduct: PID */
0x0100, /* bcdDevice: V1.00 */
0x01 /* bNumConfigurations: 1个配置 */
)
};
static const uint8_t config_descriptor[] = {
USB_CONFIG_DESCRIPTOR_INIT(USB_CONFIG_SIZE, 0x02, 0x01, USB_CONFIG_BUS_POWERED, USBD_MAX_POWER),
CDC_ACM_DESCRIPTOR_INIT(0x00, CDC_INT_EP, CDC_OUT_EP, CDC_IN_EP, CDC_MAX_MPS, 0x02)
};
static const char *string_descriptors[] = {
(const char[]){ 0x09, 0x04 }, /* Langid */
USBD_MANUFACTURER_STRING, /* Manufacturer */
USBD_PRODUCT_STRING, /* Product */
USBD_SERIAL_STRING, /* Serial Number */
};
static const uint8_t *device_descriptor_callback(uint8_t speed)
{
return device_descriptor;
}
static const uint8_t *config_descriptor_callback(uint8_t speed)
{
return config_descriptor;
}
static const uint8_t *device_quality_descriptor_callback(uint8_t speed)
{
return NULL;
}
static const char *string_descriptor_callback(uint8_t speed, uint8_t index)
{
if (index > 3) {
return NULL;
}
return string_descriptors[index];
}
static void usbd_event_handler(uint8_t busid, uint8_t event)
{
switch (event) {
case USBD_EVENT_RESET:
break;
case USBD_EVENT_CONNECTED:
break;
case USBD_EVENT_DISCONNECTED:
break;
case USBD_EVENT_RESUME:
break;
case USBD_EVENT_SUSPEND:
break;
case USBD_EVENT_CONFIGURED:
// USB配置完成,可以开始传输数据
// ep_tx_busy_flag = false;
/* setup first out ep read transfer */
// usbd_ep_start_read(busid, CDC_OUT_EP, read_buffer, 2048);
break;
case USBD_EVENT_SET_REMOTE_WAKEUP:
break;
case USBD_EVENT_CLR_REMOTE_WAKEUP:
break;
default:
break;
}
}
/**
* @brief USB CDC 初始化
* @return 0=成功, <0=失败
*/
int app_usb_cdc_init(void)
{
/* 1. 注册描述符 */
usbd_desc_register(0, &cdc_descriptor);
/* 2. 添加CDC接口 (2个接口:通信控制接口 + 数据接口) */
usbd_add_interface(0, usbd_cdc_acm_init_intf(0, &intf0));
usbd_add_interface(0, usbd_cdc_acm_init_intf(0, &intf1));
/* 3. 添加端点 (OUT + IN + INT) */
usbd_add_endpoint(0, &cdc_out_ep);
usbd_add_endpoint(0, &cdc_in_ep);
usbd_add_endpoint(0, &cdc_int_ep);
/* 4. 初始化USB设备栈 */
usbd_initialize(0, USB_BASE, usbd_event_handler);
return 0;
}
/*---------- end of file ----------*/