这次依旧是HC32相关:)
本次的目标是将CherryUSB移植到HC32F460上,并且实现两个CDC类虚拟串口,一个用于发送数据,另一个用于对接命令行进行调试。如果后面有时间的话大概率还会额外再加一个MSC(大容量存储设备),毕竟HC32F460那么大的FLASH不用白不用是不是(~ ̄▽ ̄)~
本次移植的CherryUSB版本为1.5.2,HC32驱动库使用的是HC32F460_DDL_Rev3.3.0
对于CherryUSB不熟悉的可以先看看官方的使用指南,以及这篇移植教程。
关于HC32的USB
HC32的USB使用的IP是DWC2,在从机模式下仅支持全速(FS,12Mb/s)模式,并且内置PHY。
拥有1个双向控制端点0;5个OUT端点,可以配置为支持批量传输、中断传输或同步传输;5个IN端点,可以配置为支持批量传输、中断传输或同步传输。
USBIP共有1.25KB(320字,1字=4Byte)专享FIFO,可配置为6个发送FIFO(大小可以自定义,每个IN端点一个FIFO),剩下的空间为一个接收FIFO(所有OUT端点共享这一个FIFO)。
HC32的USB IP是DWC2,要实现的类是CDC,那么按照上面的移植教程拷贝对应文件夹到工程中,文件夹结构如下:
展开代码CherryUSB ├─class │ ├─cdc │ └─hub ├─common ├─core └─port └─dwc2
dwc2文件夹中应该保留的文件如下
展开代码dwc2 ├─usb_dc_dwc2.c ├─usb_dwc2_param.h ├─usb_dwc2_reg.h └─usb_glue_hc.c
为了CherryUSB中的变量类型名能被正确识别,需要在usb_dwc2_reg.h中包含hc32_ll.h

usb_glue_hc.c相关信息
usb_glue_hc.c里面的glue是胶水的意思,胶水逻辑,一般是在不同模块里面耦合用的。这里需要实现和平台相关的函数。
注意
注意,不同版本的CherryUSB的usb_glue_xx.c文件中要实现的东西不一样。如果不是V1.5.2,请根据对应版本的usb_glue_xx.c文件自行编写usb_glue_hc.c
仓库中自带的usb_glue_hc.c文件中的内容并不完整,直接拿来编译会报一堆错误,基本都是xx函数未定义的错误。根据同目录下其他的usb_glue_xx.c可知要实现的内容:
c展开代码void usbd_dwc2_delay_ms(uint8_t ms)
//获取用户配置,包括PHY类型,是否使用DMA,rx_fifo和tx_fifo的大小
void dwc2_get_user_params(uint32_t reg_base, struct dwc2_user_params *params)
//对接HC32 USB中断的函数
void USB_IRQ_Handler(void)
//HC32 USB外设的低级初始化
void usb_dc_low_level_init(uint8_t busid)
一顿操作猛如虎,得到完整的usb_glue_hc.c文件如下,其中USB的时钟源和中断向量序号可以根据你自己的需求修改(对于时钟源的讲解可以看我之前的博客),关于fifo的配置下面会具体讲解:
c展开代码#include "usb_config.h"
#include "usb_dwc2_reg.h"
#include "usbd_core.h"
#include "usbh_core.h"
#include "usb_dwc2_param.h"
/* When using [GPIO_SetFunc(USBF_VBUS_PORT, USBF_VBUS_PIN, USBF_VBUS_FUNC);], there is no need to configure GOTGCTL */
#define USB_OTG_GLB ((DWC2_GlobalTypeDef *)(reg_base))
const struct dwc2_user_params param_pa11_pa12 = {
.phy_type = DWC2_PHY_TYPE_PARAM_FS,
.device_dma_enable = false,
.device_dma_desc_enable = false,
.device_rx_fifo_size = (320 - 16 - 32 - 8 - 16 - 8 - 32),
.device_tx_fifo_size = {
[0] = 16, // 64 byte
[1] = 32, // 128 byte
[2] = 8, // 32 byte
[3] = 16, // 64 byte
[4] = 8, // 32 byte
[5] = 32,
[6] = 0,
[7] = 0,
[8] = 0,
[9] = 0,
[10] = 0,
[11] = 0,
[12] = 0,
[13] = 0,
[14] = 0,
[15] = 0},
.device_gccfg = 0,
.total_fifo_size = 320, // 1280 byte
.b_session_valid_override = true,
};
void usbd_dwc2_delay_ms(uint8_t ms)
{
SysTick_Delay(ms);
}
void dwc2_get_user_params(uint32_t reg_base, struct dwc2_user_params *params)
{
memcpy(params, ¶m_pa11_pa12, sizeof(struct dwc2_user_params));
}
void USB_IRQ_Handler(void)
{
extern void USBD_IRQHandler(uint8_t busid);
USBD_IRQHandler(0);
}
/* USBFS Core*/
#define USBF_DP_PORT (GPIO_PORT_A)
#define USBF_DP_PIN (GPIO_PIN_12)
#define USBF_DM_PORT (GPIO_PORT_A)
#define USBF_DM_PIN (GPIO_PIN_11)
void usb_dc_low_level_init(uint8_t busid)
{
stc_gpio_init_t stcGpioCfg;
CLK_SetUSBClockSrc(CLK_USBCLK_PLLXR); // UPLLR
GPIO_SetFunc(GPIO_PORT_A, GPIO_PIN_11, GPIO_FUNC_10); // USBFS-DM
GPIO_SetFunc(GPIO_PORT_A, GPIO_PIN_12, GPIO_FUNC_10); // USBFS-DP
(void)GPIO_StructInit(&stcGpioCfg);
stcGpioCfg.u16PinAttr = PIN_ATTR_ANALOG;
(void)GPIO_Init(USBF_DM_PORT, USBF_DM_PIN, &stcGpioCfg);
(void)GPIO_Init(USBF_DP_PORT, USBF_DP_PIN, &stcGpioCfg);
FCG_Fcg1PeriphClockCmd(FCG1_PERIPH_USBFS, ENABLE); // enable usbfs clock
stc_irq_signin_config_t stcIrqRegiConf;
/* Register INT_SRC_USBFS_GLB Int to Vect.No.030 */
// stcIrqRegiConf.enIRQn = INT030_IRQn;
stcIrqRegiConf.enIRQn = INT080_IRQn;
/* Select interrupt function */
stcIrqRegiConf.enIntSrc = INT_SRC_USBFS_GLB;
/* Callback function */
stcIrqRegiConf.pfnCallback = &USB_IRQ_Handler;
/* Registration IRQ */
(void)INTC_IrqSignIn(&stcIrqRegiConf);
/* Clear Pending */
NVIC_ClearPendingIRQ(stcIrqRegiConf.enIRQn);
/* Set priority */
NVIC_SetPriority(stcIrqRegiConf.enIRQn, DDL_IRQ_PRIO_15);
/* Enable NVIC */
NVIC_EnableIRQ(stcIrqRegiConf.enIRQn);
}
void usb_dc_low_level_deinit(uint8_t busid)
{
}
usb_config.h将CherryUSB根目录下的cherryusb_config_template.h文件复制到工程中,并重命名为usb_config.h。
这里不需要对usb_config.h进行修改,保持原样即可。
注意
注意,usb_config.h依旧是不通用的,请确保版本和文件对应。
usb_config.h中有打印日志的接口,默认是printf,可以直接重定向到串口输出日志。这里我为了少接几根线,稍微修改了下打印的接口,对接到了SEGGER的RTT上,通过SWD接口直接输出到RTT终端:

关于RTT的一个小坑
市面上的xx32一般都遵循某tm32做法,把RAM起始地址放到0x20000000,但是hc32不走寻常路,把flash的起始地址放到了0x00000000,RAM起始地址放到了0x1FFF8000。就会导致RTT Viewer找不到RTT缓冲区的位置,这时候需要到编译生成的.map文件中手动查找_SEGGER_RTT的地址,然后在RTT Viewer连接前选择手动输入地址:

以上的步骤完成后,整个工程再次编译应该就不会报错了。如果编译器还是提示xx函数未定义的错误,那就是编译器把未使用的代码也加入编译了,像下面一样写空实现就行了:

将CherryUSB中demo文件夹下的cdc_acm_template.c拷贝到工程中,在main.c中调用初始化USB协议栈的cdc_acm_init函数:
c展开代码// USB Device CDC Init
extern void cdc_acm_init(uint8_t busid, uint32_t reg_base);
cdc_acm_init(0, CM_USBFS_BASE);
然后在while(1)中调用cdc_acm_data_send_with_dtr_test:
c展开代码 while(1){
extern void cdc_acm_data_send_with_dtr_test(uint8_t busid);
cdc_acm_data_send_with_dtr_test(0);
}
这时候编译烧录代码,再插上USB,设备管理器中就能识别到我们的设备了:

打开RTT Viewer连接单片机,也能看见输出的日志:

(已作废,是代码问题)关于HC32硬件上的坑
25.11.07更新:
已经找到问题,在param_pa11_pa12中未配置.b_session_valid_override = true,,导致某些寄存器未正确初始化,本文有关代码均已修改。
如果日志正常输出,但是插上USB后设备管理器中没有设备的话,可能是HC32硬件上的BUG,请测量DP引脚(PA12)上的电压,正常应该是接近3.1V的高电平,才能让主机正常识别到我们的设备。
USBFS从机的DP引脚用于插入检测,当主机检测到DP引脚为高电平时,才会和该接口上的从机进行握手:
在DWC2的IP中,PA12的引脚上集成了1.5K的上拉电阻,只需要将DCTL寄存器的SDIS位置零即可启用,cherryUSB的DWC2从机初始化中默认启用了这个内置的上拉电阻:
但是很奇怪的是,HC32F460烧录代码之后,PA12不但没有上拉,反而变成了一个接近15K电阻(同样是DWC2中集成的电阻)的下拉,导致PA12一直是低电平,所以主机一直识别不到设备。我不知道这个是不是HC32F460固有的问题,反正我烧录了俩片子都是这个情况,试了各种方法也没有搞出来1.5K上拉。(肿么这么多坑啊◢▆▅▄▃崩╰(〒皿〒)╯潰▃▄▅▇◣)
最后的解决方法是:手动给PA12飞一个1K的上拉电阻到3V3。
然后再打开串口调试工具,开启DTR硬件流控制,就能看见单片机往主机发送的一堆a了(忘记截图了,这个是实现了两个cdc之后的截图):

USB复合设备是一个USB设备芯片实现了多个USB设备功能,是通过USB接口描述符来实现不同的设备功能。要实现两个cdc,就要在描述符上下手,并实现对应的接口(usbd_interface)。
参考demo文件夹中的cdc_acm_multi_template.c(这个例程是4个CDC复合设备),下面实现我们自己的usb_cdc2_msc.h、usb_cdc2_msc.c
这里补充一下USB端点的知识:
USB的所有指令和数据的收发都是通过端点进行的,端点分为双向端点和单向端点。双向端点就是能收能发;单向端点就是只能收或者只能发,那么单向端点就分为这两类:IN端点和OUT端点。IN和OUT的方向是相对于主机(HOST)来说的。也就是说数据从主机到单片机是OUT,单片机到主机是IN。
在文章的最开头提到了HC32拥有1个双向控制端点0,5个OUT端点和5个IN端点。其中端点0一般用于USB控制,是用来收发指令的,用户一般不控制该端点。那我们能使用的就是剩下的10个单向端点了。
CDC的端点要求如下:一个用于中断(INT)的IN端点,一个用于批量数据传输的IN端点和一个用于批量数据传输的OUT端点。两个CDC需要的就是4个IN端点,2个OUT端点。HC32的资源是能满足这个要求的。
在代码中,配置端点的分配如下:
c展开代码/*!< endpoint address */
#define CDC1_IN_EP 0x81
#define CDC1_INT_EP 0x82
#define CDC2_IN_EP 0x83
#define CDC2_INT_EP 0x84
#define MSC_IN_EP 0x85
#define CDC1_OUT_EP 0x01
#define CDC2_OUT_EP 0x02
#define MSC_OUT_EP 0x03
IN端点地址是0x80-0x8F,OUT端点地址是0x00-0x0F,其中0x80和0x00是端点0的地址。这里把IN端点1、2,OUT端点1给CDC1,IN端点3、4,OUT端点2给CDC2。
对于每个IN端点,可以单独配置FIFO的大小。在我的应用中,CDC1负责发送大量数据,CDC2负责对接Letter Shell。那么,需要把CDC1的数据IN端点和MSC的数据IN端点1、5的FIFO弄大一点,CDC1和CDC2的中断IN端点2、4的FIFO弄小一点才比较合适。所以,在usb_glue_hc.c中的配置结构体中配置FIFO如下:
c展开代码const struct dwc2_user_params param_pa11_pa12 = {
.phy_type = DWC2_PHY_TYPE_PARAM_FS,
.device_dma_enable = false,
.device_dma_desc_enable = false,
.device_rx_fifo_size = (320 - 16 - 32 - 8 - 16 - 8 - 32), // OUT端点FIFO大小
.device_tx_fifo_size = {
[0] = 16, // 64 byte, 端点0,IN方向
[1] = 32, // 128 byte,IN端点1
[2] = 8, // 32 byte, IN端点2
[3] = 16, // 64 byte, IN端点3
[4] = 8, // 32 byte, IN端点4
[5] = 32, // IN端点5
[6] = 0,
[7] = 0,
[8] = 0,
[9] = 0,
[10] = 0,
[11] = 0,
[12] = 0,
[13] = 0,
[14] = 0,
[15] = 0},
.device_gccfg = 0,
.total_fifo_size = 320, // 1280 byte
.b_session_valid_override = true,
};
.device_tx_fifo_size是IN端点的FIFO,按需分配。这里的大小是以字(word)为单位,一个字(word)是4字节(Byte),1.25KB就是1280B,即320字。
IN端点的FIFO分配完了,剩下的就全是OUT端点的FIFO了(所有OUT端点共享这一个FIFO),即.device_rx_fifo_size,把tx_fifo减掉即是OUT端点FIFO的大小。
首先修改配置描述符的长度,两个CDC的长度。:
c展开代码/*!< config descriptor size */
#define USB_CONFIG_SIZE (9 + CDC_ACM_DESCRIPTOR_LEN*2)
然后是配置配置描述符具体内容的修改:
c展开代码static const uint8_t config_descriptor[] = {
USB_CONFIG_DESCRIPTOR_INIT(USB_CONFIG_SIZE, 0x04, 0x01, USB_CONFIG_BUS_POWERED, USBD_MAX_POWER),
CDC_ACM_DESCRIPTOR_INIT(0x00, CDC1_INT_EP, CDC1_OUT_EP, CDC1_IN_EP, CDC_MAX_MPS, 0x02),
CDC_ACM_DESCRIPTOR_INIT(0x02, CDC2_INT_EP, CDC2_OUT_EP, CDC2_IN_EP, CDC_MAX_MPS, 0x02),
};
剩下的描述符保持原样即可。
给每个CDC都配置单独的接收发送缓冲区,并在.h文件中extern出去:
c展开代码USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t read_buffer_cdc1[2048];
USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t write_buffer_cdc1[2048];
USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t read_buffer_cdc2[2048];
USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t write_buffer_cdc2[2048];
因为DWC2发送数据需要的缓冲区需要4byte对齐,所以USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX这个前缀不能删除,而且要发送数据之前,必须把数据拷贝到这个write_buffer缓冲区中,再把write_buffer传给USB才能正常发送。
usbd_event_handler
在这里配置两个CDC的第一次读取:c展开代码volatile bool ep1_tx_busy_flag = false;
volatile bool ep2_tx_busy_flag = false;
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:
ep1_tx_busy_flag = false;
ep2_tx_busy_flag = false;
/* setup first out ep read transfer */
usbd_ep_start_read(busid, CDC1_OUT_EP, read_buffer_cdc1, 2048);
usbd_ep_start_read(busid, CDC2_OUT_EP, read_buffer_cdc2, 2048);
break;
case USBD_EVENT_SET_REMOTE_WAKEUP:
break;
case USBD_EVENT_CLR_REMOTE_WAKEUP:
break;
default:
break;
}
}
usbd_cdc1_bulk_out,usbd_cdc1_bulk_in,usbd_cdc2_bulk_out,usbd_cdc2_bulk_in:c展开代码// CDC1回调
void usbd_cdc1_bulk_out(uint8_t busid, uint8_t ep, uint32_t nbytes)
{
USB_LOG_RAW("CDC1 out len:%d\r\n", nbytes);
// 处理CDC1接收的数据...
// 重启CDC1的读取
usbd_ep_start_read(busid, CDC1_OUT_EP, read_buffer_cdc1, 2048);
}
void usbd_cdc1_bulk_in(uint8_t busid, uint8_t ep, uint32_t nbytes)
{
USB_LOG_RAW("CDC1 in len:%d\r\n", nbytes);
if ((nbytes % usbd_get_ep_mps(busid, ep)) == 0 && nbytes) {
/* send zlp */
usbd_ep_start_write(busid, CDC1_IN_EP, NULL, 0);
} else {
ep1_tx_busy_flag = false;
}
}
// CDC2回调
void usbd_cdc2_bulk_out(uint8_t busid, uint8_t ep, uint32_t nbytes)
{
USB_LOG_RAW("CDC2 out len:%d\r\n", nbytes);
// 处理CDC2接收的数据
// 重启CDC2的读取
usbd_ep_start_read(busid, CDC2_OUT_EP, read_buffer_cdc2, 2048);
}
void usbd_cdc2_bulk_in(uint8_t busid, uint8_t ep, uint32_t nbytes)
{
USB_LOG_RAW("CDC2 in len:%d\r\n", nbytes);
if ((nbytes % usbd_get_ep_mps(busid, ep)) == 0 && nbytes) {
/* send zlp */
usbd_ep_start_write(busid, CDC2_IN_EP, NULL, 0);
} else {
ep2_tx_busy_flag = false;
}
}
/*!< endpoint call back */
struct usbd_endpoint cdc_out_ep1 = {
.ep_addr = CDC1_OUT_EP,
.ep_cb = usbd_cdc1_bulk_out
};
struct usbd_endpoint cdc_in_ep1 = {
.ep_addr = CDC1_IN_EP,
.ep_cb = usbd_cdc1_bulk_in
};
struct usbd_endpoint cdc_out_ep2 = {
.ep_addr = CDC2_OUT_EP,
.ep_cb = usbd_cdc2_bulk_out
};
struct usbd_endpoint cdc_in_ep2 = {
.ep_addr = CDC2_IN_EP,
.ep_cb = usbd_cdc2_bulk_in
};
c展开代码struct usbd_interface intf0;
struct usbd_interface intf1;
struct usbd_interface intf2;
struct usbd_interface intf3;
void usb_cdc_init(uint8_t busid, uintptr_t reg_base)
{
usbd_desc_register(busid, &cdc_descriptor);
usbd_add_interface(busid, usbd_cdc_acm_init_intf(busid, &intf0));
usbd_add_interface(busid, usbd_cdc_acm_init_intf(busid, &intf1));
usbd_add_endpoint(busid, &cdc_out_ep1);
usbd_add_endpoint(busid, &cdc_in_ep1);
usbd_add_interface(busid, usbd_cdc_acm_init_intf(busid, &intf2));
usbd_add_interface(busid, usbd_cdc_acm_init_intf(busid, &intf3));
usbd_add_endpoint(busid, &cdc_out_ep2);
usbd_add_endpoint(busid, &cdc_in_ep2);
usbd_initialize(busid, reg_base, usbd_event_handler);
}
将以上的代码和原有的模板代码整合后,编译烧录后就能在设备管理器中看见单片机枚举出来两个CDC:

上面的工作只完成了USBFS的初始化和枚举,下面将程序中其他部分代码的收发数据对接到USB上。
cherryUSB中收发数据的函数是:usbd_ep_start_read()、usbd_ep_start_write()。但是这俩函数都是异步收发,即只负责将收发数据的长度和地址对接到FIFO上,具体数据的接收,数据的分包,发送都由更底层的代码或者USB收发器自行处理。所以在例程中发送使用busy_flag进行阻塞式发送的方式并不适合我的情况,因为两个CDC只要有一个FIFO被塞满被阻塞住,整个程序就卡死在阻塞里面了,肥肠滴不优雅。
于是参考了cherryUSB作者的另一个项目cherrDAP中的代码,使用环形缓冲区(Ring Buffer)进行数据的流转。正好,cherryUSB的third_party文件夹中就有作者写好的cherryRB,一个.c,一个.h,直接拷到工程里面用。
上面提到,CDC1用于大量数据的发送,然后用VOFA+对数据进行显示,因此CDC1只需要对接发送函数即可。
首先初始化环形缓冲区:
c展开代码chry_ringbuffer_t vofa_tx;
uint8_t rb_vofa_tx[1024];
chry_ringbuffer_init(&vofa_tx, rb_vofa_tx, 1024);
然后创建发送数据的函数,数据写入环形缓冲区:
c展开代码void vofaShow(uint8_t busid)
{
uint8_t temp[128];
uint16_t len = sprintf( temp,
"%d,%d\n\r",
Iron.Temperature.OriginalValue,
Iron.Current.OriginalValue);
if (chry_ringbuffer_get_free(&vofa_tx) >= CDC_MAX_MPS){
chry_ringbuffer_write(&vofa_tx, temp, len);
}
}
在while(1)中监控环形缓冲区的使用情况:
c展开代码uint32_t size;
uint8_t *buffer;
if (chry_ringbuffer_get_used(&vofa_tx) && (ep1_tx_busy_flag == 0)) {
buffer = chry_ringbuffer_linear_read_setup(&vofa_tx, &size);
memcpy(write_buffer_cdc1, buffer, size);
ep1_tx_busy_flag = 1;
usbd_ep_start_write(0, CDC1_IN_EP, write_buffer_cdc1, size);
}
环形缓冲区的数据读取完成后移动环形缓冲区中读指针的索引,这里放到USB的数据发送完成中断中处理:
c展开代码void usbd_cdc1_bulk_in(uint8_t busid, uint8_t ep, uint32_t nbytes)
{
USB_LOG_RAW("CDC1 in len:%d\r\n", nbytes);
chry_ringbuffer_linear_read_done(&vofa_tx, nbytes);
if ((nbytes % usbd_get_ep_mps(busid, ep)) == 0 && nbytes) {
/* send zlp */
usbd_ep_start_write(busid, CDC1_IN_EP, NULL, 0);
} else {
ep1_tx_busy_flag = false;
}
}
这里对接的命令行是Letter Shell,不了解的可以看我之前的博客文章。
对于命令行来说,要对接收和发送两个函数。如法炮制即可。
首先初始化收发的环形缓冲区:
c展开代码chry_ringbuffer_init(&shellrx, rb_shellrx, 1024);
chry_ringbuffer_init(&shelltx, rb_shelltx, 1024);
接收数据写入环形缓冲区放在USB的数据接收完成中断中处理:
c展开代码// CDC2回调
void usbd_cdc2_bulk_out(uint8_t busid, uint8_t ep, uint32_t nbytes)
{
USB_LOG_RAW("CDC2 out len:%d\r\n", nbytes);
// 处理CDC2接收的数据,放入环形缓冲区
chry_ringbuffer_write(&shellrx,read_buffer_cdc2, nbytes);
// 重启CDC2的读取
usbd_ep_start_read(busid, CDC2_OUT_EP, read_buffer_cdc2, 2048);
}
在while(1)中监控shellrx的使用情况,有收到数据时shellHandler解析字符串,shellrx移动读指针的索引:
c展开代码if (chry_ringbuffer_get_used(&shellrx)) {
buffer = chry_ringbuffer_linear_read_setup(&shellrx, &size);
Shell_handle(buffer, size);
}
void Shell_handle(uint8_t *data, uint16_t len)
{
if( len > 0){
for (uint16_t i = 0; i < len; i++) {
shellHandler(&cdcShell, data[i]);
}
}
chry_ringbuffer_linear_read_done(&shellrx, len);
}
shellHandler()在解析完字符后会立马调用shell的发送函数,这里将要发送的数据写入shelltx中:
c展开代码/**
* @brief cdc shell写
*
* @param data 数据
* @param len 数据长度
*
* @return short 实际写入的数据长度
*/
short cdcShellWrite(char *data, unsigned short len)
{
if(chry_ringbuffer_get_free(&shelltx) >= len){
chry_ringbuffer_write(&shelltx, data, len);
}
return len;
}
同样在while(1)中监控shelltx的使用情况:
c展开代码if (chry_ringbuffer_get_used(&shelltx) && (ep2_tx_busy_flag == 0)) {
buffer = chry_ringbuffer_linear_read_setup(&shelltx, &size);
memcpy(write_buffer_cdc2, buffer, size);
ep2_tx_busy_flag = 1;
usbd_ep_start_write(0, CDC2_IN_EP, write_buffer_cdc2, size);
}
然后在CDC2的数据发送完成中断中移动shelltx读指针的索引:
c展开代码void usbd_cdc2_bulk_in(uint8_t busid, uint8_t ep, uint32_t nbytes)
{
USB_LOG_RAW("CDC2 in len:%d\r\n", nbytes);
chry_ringbuffer_linear_read_done(&shelltx, nbytes);
if ((nbytes % usbd_get_ep_mps(busid, ep)) == 0 && nbytes) {
/* send zlp */
usbd_ep_start_write(busid, CDC2_IN_EP, NULL, 0);
} else {
ep2_tx_busy_flag = false;
}
}
最后,在main()中对shell进行初始化:
c展开代码cdcShellInit(); // 初始化cdc Shell
至此,整个移植过程结束。
单片机连上电脑,VOFA+连上第一个CDC串口,MobaXterm连上第二个CDC串口,就能看见代码效果辣,这里录了个视频:https://www.bilibili.com/video/BV1VAHAzeExg
这里贴一下完整的代码:
usb_cdc2_msc.h:
c展开代码#ifndef USB_CDC2_MSC_H
#define USB_CDC2_MSC_H
#include "usbd_core.h"
#include "usbd_cdc_acm.h"
#include "shell_port.h"
/*!< endpoint address */
#define CDC1_IN_EP 0x81
#define CDC1_INT_EP 0x82
#define CDC2_IN_EP 0x83
#define CDC2_INT_EP 0x84
#define MSC_IN_EP 0x85
#define CDC1_OUT_EP 0x01
#define CDC2_OUT_EP 0x02
#define MSC_OUT_EP 0x03
extern volatile bool ep1_tx_busy_flag;
extern volatile bool ep2_tx_busy_flag;
extern uint8_t write_buffer_cdc1[2048];
extern uint8_t write_buffer_cdc2[2048];
void usb_cdc_init(uint8_t busid, uintptr_t reg_base);
void vofaShow(uint8_t busid);
#endif /* USB_CDC2_MSC_H */
usb_cdc2_msc.c:
c展开代码#include <stdbool.h>
#include <stdint.h>
#include "usb_cdc2_msc.h"
#include "loop.h"
#define USBD_VID 0xFFFF
#define USBD_PID 0xFFFF
#define USBD_MAX_POWER 100
#define USBD_LANGID_STRING 1033
/*!< config descriptor size */
#define USB_CONFIG_SIZE (9 + CDC_ACM_DESCRIPTOR_LEN*2)
#ifdef CONFIG_USB_HS
#define CDC_MAX_MPS 512
#else
#define CDC_MAX_MPS 64
#endif
static const uint8_t device_descriptor[] = {
USB_DEVICE_DESCRIPTOR_INIT(USB_2_0, 0xEF, 0x02, 0x01, USBD_VID, USBD_PID, 0x0100, 0x01)
};
static const uint8_t config_descriptor[] = {
USB_CONFIG_DESCRIPTOR_INIT(USB_CONFIG_SIZE, 0x04, 0x01, USB_CONFIG_BUS_POWERED, USBD_MAX_POWER),
CDC_ACM_DESCRIPTOR_INIT(0x00, CDC1_INT_EP, CDC1_OUT_EP, CDC1_IN_EP, CDC_MAX_MPS, 0x02),
CDC_ACM_DESCRIPTOR_INIT(0x02, CDC2_INT_EP, CDC2_OUT_EP, CDC2_IN_EP, CDC_MAX_MPS, 0x02),
};
static const uint8_t device_quality_descriptor[] = {
///////////////////////////////////////
/// device qualifier descriptor
///////////////////////////////////////
0x0a,
USB_DESCRIPTOR_TYPE_DEVICE_QUALIFIER,
0x00,
0x02,
0x00,
0x00,
0x00,
0x40,
0x00,
0x00,
};
static const char *string_descriptors[] = {
(const char[]){0x09, 0x04}, /* Langid */
"CherryUSB", /* Manufacturer */
"CherryUSB CDC DEMO", /* Product */
"2022123456", /* 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 device_quality_descriptor;
}
static const char *string_descriptor_callback(uint8_t speed, uint8_t index)
{
if (index > 3) {
return NULL;
}
return string_descriptors[index];
}
const 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
};
USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t read_buffer_cdc1[2048];
USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t write_buffer_cdc1[2048];
USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t read_buffer_cdc2[2048];
USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t write_buffer_cdc2[2048];
volatile bool ep1_tx_busy_flag = false;
volatile bool ep2_tx_busy_flag = false;
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:
ep1_tx_busy_flag = false;
ep2_tx_busy_flag = false;
/* setup first out ep read transfer */
usbd_ep_start_read(busid, CDC1_OUT_EP, read_buffer_cdc1, 2048);
usbd_ep_start_read(busid, CDC2_OUT_EP, read_buffer_cdc2, 2048);
break;
case USBD_EVENT_SET_REMOTE_WAKEUP:
break;
case USBD_EVENT_CLR_REMOTE_WAKEUP:
break;
default:
break;
}
}
// CDC1回调
void usbd_cdc1_bulk_out(uint8_t busid, uint8_t ep, uint32_t nbytes)
{
USB_LOG_RAW("CDC1 out len:%d\r\n", nbytes);
// 处理CDC1接收的数据...
// 重启CDC1的读取
usbd_ep_start_read(busid, CDC1_OUT_EP, read_buffer_cdc1, 2048);
}
void usbd_cdc1_bulk_in(uint8_t busid, uint8_t ep, uint32_t nbytes)
{
USB_LOG_RAW("CDC1 in len:%d\r\n", nbytes);
chry_ringbuffer_linear_read_done(&vofa_tx, nbytes);
if ((nbytes % usbd_get_ep_mps(busid, ep)) == 0 && nbytes) {
/* send zlp */
usbd_ep_start_write(busid, CDC1_IN_EP, NULL, 0);
} else {
ep1_tx_busy_flag = false;
}
}
// CDC2回调
void usbd_cdc2_bulk_out(uint8_t busid, uint8_t ep, uint32_t nbytes)
{
USB_LOG_RAW("CDC2 out len:%d\r\n", nbytes);
// 处理CDC2接收的数据,放入环形缓冲区
chry_ringbuffer_write(&shellrx,read_buffer_cdc2, nbytes);
// 重启CDC2的读取
usbd_ep_start_read(busid, CDC2_OUT_EP, read_buffer_cdc2, 2048);
}
void usbd_cdc2_bulk_in(uint8_t busid, uint8_t ep, uint32_t nbytes)
{
USB_LOG_RAW("CDC2 in len:%d\r\n", nbytes);
chry_ringbuffer_linear_read_done(&shelltx, nbytes);
if ((nbytes % usbd_get_ep_mps(busid, ep)) == 0 && nbytes) {
/* send zlp */
usbd_ep_start_write(busid, CDC2_IN_EP, NULL, 0);
} else {
ep2_tx_busy_flag = false;
}
}
/*!< endpoint call back */
struct usbd_endpoint cdc_out_ep1 = {
.ep_addr = CDC1_OUT_EP,
.ep_cb = usbd_cdc1_bulk_out
};
struct usbd_endpoint cdc_in_ep1 = {
.ep_addr = CDC1_IN_EP,
.ep_cb = usbd_cdc1_bulk_in
};
struct usbd_endpoint cdc_out_ep2 = {
.ep_addr = CDC2_OUT_EP,
.ep_cb = usbd_cdc2_bulk_out
};
struct usbd_endpoint cdc_in_ep2 = {
.ep_addr = CDC2_IN_EP,
.ep_cb = usbd_cdc2_bulk_in
};
struct usbd_interface intf0;
struct usbd_interface intf1;
struct usbd_interface intf2;
struct usbd_interface intf3;
void usb_cdc_init(uint8_t busid, uintptr_t reg_base)
{
usbd_desc_register(busid, &cdc_descriptor);
usbd_add_interface(busid, usbd_cdc_acm_init_intf(busid, &intf0));
usbd_add_interface(busid, usbd_cdc_acm_init_intf(busid, &intf1));
usbd_add_endpoint(busid, &cdc_out_ep1);
usbd_add_endpoint(busid, &cdc_in_ep1);
usbd_add_interface(busid, usbd_cdc_acm_init_intf(busid, &intf2));
usbd_add_interface(busid, usbd_cdc_acm_init_intf(busid, &intf3));
usbd_add_endpoint(busid, &cdc_out_ep2);
usbd_add_endpoint(busid, &cdc_in_ep2);
usbd_initialize(busid, reg_base, usbd_event_handler);
}
void vofaShow(uint8_t busid)
{
uint8_t temp[128];
uint16_t len = sprintf( temp,
"%d,%d\n\r",
Iron.Temperature.OriginalValue,
Iron.Current.OriginalValue);
if (chry_ringbuffer_get_free(&vofa_tx) >= CDC_MAX_MPS){
chry_ringbuffer_write(&vofa_tx, temp, len);
}
}
shell_port.h:
c展开代码/**
* @file shell_port.h
* @author Letter (NevermindZZT@gmail.com)
* @brief
* @version 0.1
* @date 2019-02-22
*
* @copyright (c) 2019 Letter
*
*/
/*
加在.rodata :
_shell_command_start = .;
KEEP (*(shellCommand))
_shell_command_end = .;
*/
#ifndef __SHELL_PORT_H__
#define __SHELL_PORT_H__
#include "shell.h"
#include "chry_ringbuffer.h"
extern Shell cdcShell;
extern chry_ringbuffer_t shellrx;
extern chry_ringbuffer_t shelltx;
extern uint8_t rb_shellrx[1024]; // 1K缓冲区
extern uint8_t rb_shelltx[1024]; // 1K缓冲区
void cdcShellInit(void);
short cdcShellRead(char *data, unsigned short len);
short cdcShellWrite(char *data, unsigned short len);
void Shell_handle(uint8_t *data, uint16_t len);
#endif
shell_port.c:
c展开代码/**
* @file shell_port.c
* @author Letter (NevermindZZT@gmail.com)
* @brief
* @version 0.1
* @date 2019-02-22
*
* @copyright (c) 2019 Letter
*
*/
#include "shell.h"
#include "shell_port.h"
#include "usb_cdc2_msc.h"
Shell cdcShell;
char cdcShellBuffer[512];
chry_ringbuffer_t shellrx;
chry_ringbuffer_t shelltx;
uint8_t rb_shellrx[1024]; // 1K缓冲区
uint8_t rb_shelltx[1024]; // 1K缓冲区
/**
* @brief cdc shell写
*
* @param data 数据
* @param len 数据长度
*
* @return short 实际写入的数据长度
*/
short cdcShellWrite(char *data, unsigned short len)
{
if(chry_ringbuffer_get_free(&shelltx) >= len){
chry_ringbuffer_write(&shelltx, data, len);
}
return len;
}
/**
* @brief cdc shell读
*
* @param data 数据
* @param len 数据长度
*
* @return short 实际读取的数据长度
*/
short cdcShellRead(char *data, unsigned short len)
{
return 0; // 实际不会用到,所以直接返回0
}
/**
* @brief 用户shell初始化
*
*/
void cdcShellInit(void)
{
cdcShell.write = cdcShellWrite;
cdcShell.read = cdcShellRead;
shellInit(&cdcShell, cdcShellBuffer, 512);
}
void Shell_handle(uint8_t *data, uint16_t len)
{
if( len > 0){
for (uint16_t i = 0; i < len; i++) {
shellHandler(&cdcShell, data[i]);
}
}
chry_ringbuffer_linear_read_done(&shellrx, len);
}
其余代码:
初始化:
c展开代码usb_cdc_init(0, CM_USBFS_BASE);
chry_ringbuffer_init(&shellrx, rb_shellrx, 1024);
chry_ringbuffer_init(&shelltx, rb_shelltx, 1024);
chry_ringbuffer_init(&vofa_tx, rb_vofa_tx, 1024);
cdcShellInit(); // 初始化cdc Shell
while(1)中调用USB_cdcHandle()以及vofaShow():
c展开代码chry_ringbuffer_t vofa_tx;
uint8_t rb_vofa_tx[1024];
void USB_cdcHandle()
{
uint32_t size;
uint8_t *buffer;
if (chry_ringbuffer_get_used(&shellrx)) {
buffer = chry_ringbuffer_linear_read_setup(&shellrx, &size);
Shell_handle(buffer, size);
}
if (chry_ringbuffer_get_used(&vofa_tx) && (ep1_tx_busy_flag == 0)) {
buffer = chry_ringbuffer_linear_read_setup(&vofa_tx, &size);
memcpy(write_buffer_cdc1, buffer, size);
ep1_tx_busy_flag = 1;
usbd_ep_start_write(0, CDC1_IN_EP, write_buffer_cdc1, size);
}
if (chry_ringbuffer_get_used(&shelltx) && (ep2_tx_busy_flag == 0)) {
buffer = chry_ringbuffer_linear_read_setup(&shelltx, &size);
memcpy(write_buffer_cdc2, buffer, size);
ep2_tx_busy_flag = 1;
usbd_ep_start_write(0, CDC2_IN_EP, write_buffer_cdc2, size);
}
}
cherrUSB还是很好用滴,我对USB协议栈啥都不懂,看了几篇教程,仔细研究一下就能很快上手,设备的初始化对着模板也能很快敲出来,不需要对USB了解很深就能发挥出USB的大半实力。
CherryUSB移植笔记 APM32F407VGT6 DWC2移植
整合 LetterShell 和 CherryUSB 功能到 STM32
https://github.com/LHYHHD/My-USB-Study
华大HC32F460XXX移植cherryusb协议栈,实现USB CDC ACM
HC32 RTT打印无输出-文章最末有解决方法
本文作者:zxcli
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!