1. 项目概述
最近在做一个基于FT32F072芯片的项目,需要将USB功能移植到RT-Thread Nano实时操作系统上。厂家提供的官方例程是HID和CDC的复合设备,作为初次接触这款芯片的开发者,我决定从简单的移植工作开始入手。本文将详细记录整个移植过程,包括RT-Thread Nano的移植、Bootloader的实现以及USB CDC功能的移植。
FT32F072是一款基于ARM Cortex-M0内核的微控制器,内置USB全速设备控制器。在实际项目中,我们经常需要实现固件的远程升级功能,这就需要配合Bootloader使用。同时,通过USB CDC(通信设备类)可以实现虚拟串口功能,方便与上位机通信。
2. RT-Thread Nano移植
2.1 准备工作
首先需要获取RT-Thread Nano的芯片支持包。有以下几种方式:
- 通过Keil MDK的Pack Installer直接安装
- 从RT-Thread官网下载芯片包手动安装
- 使用env工具配置工程
我选择了第一种方式,因为最为便捷。在Keil5中打开Pack Installer,搜索"RT-Thread"即可找到Nano版本。
提示:如果使用官网下载的芯片包,需要手动解压到Keil的ARM/Packs目录下。
2.2 工程配置
创建新工程时,我选择了仅包含内核的最小配置。虽然RT-Thread提供了设备驱动框架,但对于资源有限的M0芯片来说,直接操作寄存器更为高效。
关键配置点:
- 在rtconfig.h中关闭不需要的组件
- 内存管理选择动态内存分配
- 调整系统时钟和Tick频率
c复制// rtconfig.h 关键配置
#define RT_USING_HEAP
#define RT_USING_SMALL_MEM
#define RT_USING_CONSOLE
#define RT_CONSOLEBUF_SIZE 128
2.3 板级初始化
board.c文件需要根据具体硬件进行修改。主要修改点包括:
- 系统时钟初始化
- SysTick中断配置
- 堆内存大小设置
c复制// board.c 修改示例
void rt_hw_board_init()
{
/* 配置系统时钟为72MHz */
SystemClock_Config();
/* 初始化SysTick */
SysTick_Config(SystemCoreClock / RT_TICK_PER_SECOND);
/* 设置堆大小 */
rt_system_heap_init((void*)HEAP_BEGIN, (void*)HEAP_END);
/* 初始化硬件外设 */
rt_hw_usart_init();
}
特别注意:PendSV_Handler中断不要自行定义,这个由RT-Thread内核用于任务切换。
3. Bootloader实现
3.1 ARM M0的启动机制
与M3/M4内核不同,Cortex-M0没有VTOR寄存器,无法直接重映射中断向量表。因此需要在应用程序中将向量表复制到SRAM中,并在启动时进行重映射。
3.2 Bootloader跳转逻辑
Bootloader的主要任务包括:
- 检查应用程序是否有效
- 设置MSP指针
- 跳转到应用程序
c复制#define APPLICATION_ADDRESS 0x08004000 // 应用程序起始地址
void JumpToApp(void)
{
if (((*(__IO uint32_t*)APPLICATION_ADDRESS) & 0x2FFE0000) == 0x20000000)
{
__disable_irq();
__set_MSP(*(__IO uint32_t *) APPLICATION_ADDRESS);
((void (*)())(*(volatile unsigned long *)(APPLICATION_ADDRESS + 0x04)))();
}
}
3.3 应用程序的修改
应用程序需要在启动时重映射向量表:
c复制__IO uint32_t VectorTable[48] __attribute__((at(0x20000000)));
void NVIC_SetVectorTable(void)
{
for(uint8_t i=0; i<48; i++) {
VectorTable[i] = *(__IO uint32_t*)(APPLICATION_ADDRESS + (i<<2));
}
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
SYSCFG_MemoryRemapConfig(SYSCFG_MemoryRemap_SRAM);
}
注意:向量表大小需要根据具体芯片的中断数量调整,可以在startup.s文件中查看__Vectors的大小。
3.4 Keil工程配置
为了正确生成Bootloader和应用程序,需要在Keil中设置正确的ROM和RAM地址:
-
Bootloader:
- IROM1: 0x08000000 - 0x08003FFF
- IRAM1: 0x20000000 - 0x20007FFF
-
应用程序:
- IROM1: 0x08004000 - 0x0801FFFF
- IRAM1: 0x20000000 - 0x20007FFF
4. USB CDC移植
4.1 USB时钟配置
FT32F072的USB控制器需要精确的48MHz时钟。如果主时钟不是48MHz的整数倍,需要使用内部HSI48时钟:
c复制void BspUSBInit(void)
{
RCC_USBCLKConfig(RCC_USBCLK_HSI48); // 使用内部48MHz时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USB, ENABLE);
// 其他USB初始化代码...
}
4.2 USB库文件移植
从官方例程中需要移植以下关键文件:
- usb_core.c
- usb_init.c
- usb_int.c
- usb_mem.c
- usb_regs.c
- usb_sil.c
以及对应的头文件。这些文件实现了USB协议栈的核心功能。
4.3 中断处理
USB中断需要与RT-Thread的中断管理系统兼容:
c复制void USB_IRQHandler(void)
{
rt_interrupt_enter();
USB_FS_IRQHandler(); // 调用原始的USB中断处理函数
rt_interrupt_leave();
}
4.4 数据收发实现
USB CDC的数据收发主要通过端点实现。以下是发送数据的实现:
c复制ErrorStatus USB_EP_Tx(uint8_t Ep, uint8_t *ptr, uint8_t data_len)
{
uint32_t timeout = 0;
if (gnDevState > DEVSTATE_ADDRESS) {
gepbin2.byEP = Ep;
gepbin2.nBytesLeft = data_len;
gepbin2.pData = ptr;
EndpointBulkIn(&gepbin2, M_EP_NORMAL);
while (MREAD_BYTE(M_REG_INCSR1) & 0x01) {
timeout++;
if (timeout >= 0xFFFFFFF) return ERROR;
}
return SUCCESS;
}
return ERROR;
}
对于大数据量传输,需要分包处理:
c复制void usb_send_data(uint8_t *buf, uint32_t len, uint8_t Ep)
{
uint32_t nBytes = 0;
while (len) {
nBytes = (len > 64) ? 64 : len;
if (USB_EP_Tx(Ep, buf, nBytes) != SUCCESS) break;
buf += nBytes;
len -= nBytes;
}
}
5. 应用层实现
5.1 USB通信线程
创建一个专用线程处理USB通信:
c复制static void usb_thread_entry(void *parameter)
{
while (!USBD_Configured(0U)) {
rt_thread_delay(100);
}
for (;;) {
rt_sem_take(usb_rx_sem, RT_WAITING_FOREVER);
if (gBulkoutData2.recvStatus == 1) {
int len = gBulkoutData2.recvNum;
memcpy(temp_buf, gBulkoutData2.recvBuf, len);
gBulkoutData2.recvStatus = 0;
usb_rx_data(temp_buf, len);
}
}
}
5.2 信号量同步
使用信号量同步数据接收:
c复制rt_sem_t usb_rx_sem = RT_NULL;
// 在USB中断中释放信号量
if (usb_rx_sem != RT_NULL) {
rt_sem_release(usb_rx_sem);
}
5.3 初始化流程
c复制int usb_init(void)
{
usb_rx_sem = rt_sem_create("usb_rx", 0, RT_IPC_FLAG_PRIO);
rt_thread_t tid = rt_thread_create("usb", usb_thread_entry, RT_NULL,
RT_USB_THREAD_STACK_SIZE, 0, 20);
rt_thread_startup(tid);
return 0;
}
INIT_APP_EXPORT(usb_init);
6. 常见问题与解决方案
6.1 USB无法识别
可能原因:
- 时钟配置不正确 - 确保USB时钟为精确的48MHz
- 上拉电阻未使能 - 检查USB_PDCTRLConfig配置
- 描述符错误 - 使用USB分析工具检查描述符
6.2 数据传输不稳定
解决方案:
- 增加数据包超时检测
- 优化缓冲区管理
- 调整USB中断优先级
6.3 Bootloader跳转失败
检查要点:
- 应用程序地址是否正确
- 向量表重映射是否成功
- 堆栈指针是否有效
7. 性能优化建议
- 双缓冲机制:为USB端点实现双缓冲,提高吞吐量
- DMA传输:如果芯片支持,使用DMA进行数据搬移
- 零拷贝设计:避免不必要的数据拷贝
- 优先级调整:合理设置USB中断优先级
8. 移植心得
在实际移植过程中,有几点特别值得注意:
-
时钟配置要精确:USB对时钟要求严格,必须保证48MHz±0.25%的精度。如果使用PLL,需要仔细计算分频系数。
-
中断处理要快:USB中断处理函数中不要做复杂操作,必要时使用线程处理数据。
-
内存管理要谨慎:M0芯片资源有限,动态内存分配要适度,避免碎片化。
-
调试工具很重要:使用USB协议分析仪可以大大加快调试进度。
-
兼容性测试:在不同主机和设备上测试USB功能,确保兼容性。
这个移植项目让我对RT-Thread Nano和USB协议栈有了更深入的理解。虽然M0芯片资源有限,但通过合理的设计和优化,完全可以实现稳定的USB通信功能。