使用keil5的USB::CDC类实现虚拟串口和SPI通讯

一、前言

最近因为做的几个项目上使用了LORA作为无线通讯,在现场安装完后,要联调时碰到需要查看主机和从机发的协议数据是否正确。还要测试控制,弄来弄去很麻烦,所以干脆自己做了个USBtoLoRa无线收发器。这个LORA芯片的通讯接口是SPI。总体的设计思路是这个收发器能够通过USB将接收到的数据上传给上位机,同时上位机也能做配置。一开始的方案是用libusb做上位机的驱动,使用USB的自定义设备类来驱动MCU与上位机通讯。但是研究了几天着实没解决掉libusb的读写掉包的问题。后来看到keil安装目录里有一个CDC类的驱动安装包,于是干脆就直接使用CDC类来做。研究了一两天CDC类的例子,发现keil5提供的中间件确实方便,底层的驱动都做好了,我们只需要做应用层的代码编写了。下面我就分享的使用经验。==不讲USB原理==。

二、建立USB::CDC类工程

我的keil版本是V5.23版本的,不同的版本的自带中间件版本可能不一样。但是可以升级中间件版本。

1.新建工程

keil5的USB中间件需要CMSIS_RTOS支持,它与其它uCOS、freeRTOS系统一样,用法差不多。可以单独使用(出门左转有使用其方法)。工程是基于STMF103CBT6型号的芯片上的,所以你的软件需要安装F1的软件包。在选择需要的部件时,运行环境管理器会检查是否缺少依赖,并提示。具体选择如下
- CMSIS
- CORE
- RTOS(API)
- Keil RTX 当前keil版本仅支持此版本的RTX调试
- CMSIS Driver 这相当于固件库一样,是keil的自有版本
- USB Device(API)
- USB Device USB的配置驱动等
- Device
- DMA DMA配置驱动
- GPIO GPIO配置驱动
- StdPeriph Drivers
- Framework STMF1系列的标准外设库框架
- EXTI lora芯片需要使用
- GPIO
- RCC
- SPI
- USART
- USB
- CORE usb内核必选
- Device 选择1,表示有一个物理USB Device
- Device STM32F103系列只有USBFullDevice功能
- CDC 选择CDC类

使用keil5的USB::CDC类实现虚拟串口和SPI通讯_第1张图片

接下来就要添加相应的文件,包括main.c stm32f103_it.c USBD_USER_CDC_ACM.c 文件。更改一下组名,像user文件夹添加模板文件。鼠标右移到USER文件夹上,右键点击“Add New Item to Group “User” ”。弹出窗口中,选择“User Code Template”,分别添加以下几个文件
- CMSIS
- RTOS:Keil RTX | CMSIS-RTOS’main’ function
- Device
- StdPeriph Drivers | FrameWork Interrupt Serive Routines
- USB
- Device:CDC USB | Device CDC ACM
USB中间件将底层驱动都封装好了,我们只需要在USBD_USER_CDC_ACM.c模板文件中添加自己的应用代码。什么底层的设备描述符、配置描述符、接口描述符等这些初始化的操作,都不需要我们关心。我们只需要配置好这些描述就行。现在我们不需要修改这些配置,使用默认的即可。

USBD_Config_0是用来配置设备描述符、配置描述符、字符串描述符的
使用keil5的USB::CDC类实现虚拟串口和SPI通讯_第2张图片
USBD_Config_CDC_0 是用来配置接口描述符的
使用keil5的USB::CDC类实现虚拟串口和SPI通讯_第3张图片

配置完描述符,还要配置RTX。修改默认线程栈尺寸和内核时钟频率。如图所示

使用keil5的USB::CDC类实现虚拟串口和SPI通讯_第4张图片

配置RTE_Device.h文件。需要注意的地方是外部晶振要按板载使用的晶振,USB控制接口也需要按实际的来修改

使用keil5的USB::CDC类实现虚拟串口和SPI通讯_第5张图片

2.添加代码

总体配置工作已经完成差不多了,下面就开始写代码了。我原工程有移植的LoRa驱动程序,使用的SPI接口。所以,以下介绍,完全可以根据自己的需要添加自己的代码。关键编辑文件在于USBD_User_CDC_ACM_0.c文件。
USB:CDC在启动时会创建2个线程用于中断端点和块传输端点的数据读写。还有一个是端点0的控制传输线程。启动时要调用USBD_Initialize()和USBD_Connect(),这2个函数会初始化USB内核和CDC类,并创建上面的3个线程。USB中间件会已事件驱动模式调用相应的回调函数。
我的代码中在这几个函数中添加了代码,因为是与SPI桥接,有些函数可以忽略。
- void USBD_CDC0_ACM_Initialize (void)
- void USBD_CDC0_ACM_DataReceived(uint32_t len)
- static void AppRadioProcess(const void* args)增添了一个LoRa数据处理的线程,用于LoRa->USB的数据传输。

其实整个分析思想很简单,我的目的就是一个LoRa接到的数据通过USB传到上位机。一个是上位机的数据通过USB传给LoRa。整个过程就像串口的接收与发送。那像USB发送和接收的接口分别是哪2个呢?答案就是 void USBD_CDC0_ACM_DataReceived(uint32_t len)和void USBD_CDC0_ACM_DataReceived(uint32_t len)。当然还有其它的发送接收函数。将从USB接收到的数据发往SPI,从SPI接收到的数据发往USB。按这个思路就很好添加自己的应用代码。

// Called during USBD_Initialize to initialize the USB CDC class instance (ACM).
void USBD_CDC0_ACM_Initialize (void) {
  // Add code for initialization
    tid_lora_process = osThreadCreate(osThread(AppRadioProcess),NULL);
    mid_dio0_irq_type = osMessageCreate(osMessageQ(mid_dio0_irq_type),NULL);

  printf("USBD_CDC0_ACM_Initialize\r\n");
}

初始化时我创建了一个LORA数据处理线程,和一个用于LORA中断的消息队列。

void USBD_CDC0_ACM_DataReceived(uint32_t len)
{
  int32_t cnt;
  // Start USB -> UART
  cnt = USBD_CDC_ACM_ReadData(0U,lora_tx_buf,50); 
    Radio->RFTxData(lora_tx_buf,cnt);
}

USB数据接收函数中,调用读取数据函数,然后调用lora发送函数,将接收到的数据发送出去。此函数需要自己添加,模板上没有。当USB收到上位机发送过来的数据时,会调用该函数,因此,我们要在这里取数据。

static void AppRadioProcess(const void* args)
{       
    osEvent event;

        Radio = RadioDriverInit();
        Radio->Init();
        Radio->RFRxStateEnter();
        while(1)
        {
          event= osMessageGet(mid_dio0_irq_type,osWaitForever);
          if(event.status==osEventMessage)
          {       
            if(event.value.v == 1)
            {
                Radio->RFRxDataRead(lora_rx_buf,&lora_rx_count);
                if( lora_rx_count > 0 )
                {
                  printf("RxDone\r\n");
                  //LoRa --> USB
                  USBD_CDC_ACM_WriteData(0,lora_rx_buf,lora_rx_count);//调用USB写数据函数,向上位机发送数据
                  bsp_LED_Toggle(BLUE_LED);
                  lora_rx_count=0;
                }
                else
                {
                  Radio->RFRxStateEnter();
                }
            }
            else if(event.value.v == 2)
            {
                printf("TxDone\r\n");
                bsp_LED_Toggle(GREEN_LED);
                Radio->RFRxStateEnter();
            }
          }         
        }
}

通过创建一个LORA线程来像上位机发送数据。当LORA接收到数据时,线程得到消息运行,取出LORA中的数据,然后调用USBD_CDC_ACM_WriteData发往上位机。因为模板中其它的函数我用不到,我就没做修改。这些函数的使用可以参考文档

/*
 * main: initialize and start the system
 */
int main (void) {
  osKernelInitialize ();                    // initialize CMSIS-RTOS

  // initialize peripherals here
  stdout_init();
  bsp_Init();
  // create 'thread' functions that start executing,
  // example: tid_name = osThreadCreate (osThread(name), NULL);

  osKernelStart ();                         // start thread execution 

  USBD_Initialize(0);
  USBD_Connect(0);

  while(1)
  {
    osDelay(1000);
    bsp_LED_Toggle(YELLOW_LED);
//    printf("hello world!\r\n");
  }
}

主函数要记得初始化USBD。

三、安装驱动

现在到这一步已经完成80%了。下位机程序已经完成。接下来就要考虑到上位机了。程序编译通过后,向目标板下载代码。此刻插上电脑,win7/win8.1是会出现一个三角符号,提示找不到驱动(WIN10可以自己安装上)。这时要找到keil的安装目录C:\Users\AOC\Documents\Boards\Keil\MCBSTM32E\Middleware\USB\Device\VirtualCOM,该目录下游驱动。安装该驱动即可使用。注意:安装过程中可能提示未找到签名。这就需要自行百度解决办法了,很简单。

使用keil5的USB::CDC类实现虚拟串口和SPI通讯_第6张图片

四、使用串口调试助手测试

以上过程都解决好了,就可以使用串口调试助手来使用了。这个串口的波特率设置成多少都无所谓了,因为该芯片是全速设备,速率有12Mbit/s。影响传输速率的是SPI和LoRA本身的速率。一个高速一个低速,最后的结果是跟低速平衡。测试使用的2个收发器不停的互发,结果很是满意。后面就可以根据Qt的串口设备类,自定义协议自己做一个上位机了。另附上例子代码(包含驱动)USB_CDC_Example.zip
使用keil5的USB::CDC类实现虚拟串口和SPI通讯_第7张图片

我做的实物效果图
使用keil5的USB::CDC类实现虚拟串口和SPI通讯_第8张图片

你可能感兴趣的:(技术实践,STM32)