使用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類

iamge

接下來就要添加相應的文件,包括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是用來配置設備描述符、配置描述符、字符串描述符的
這裏寫圖片描述
USBD_Config_CDC_0 是用來配置接口描述符的
這裏寫圖片描述

配置完描述符,還要配置RTX。修改默認線程棧尺寸和內核時鐘頻率。如圖所示

這裏寫圖片描述

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

這裏寫圖片描述

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,該目錄下游驅動。安裝該驅動即可使用。注意:安裝過程中可能提示未找到簽名。這就需要自行百度解決辦法了,很簡單。

這裏寫圖片描述

四、使用串口調試助手測試

以上過程都解決好了,就可以使用串口調試助手來使用了。這個串口的波特率設置成多少都無所謂了,因爲該芯片是全速設備,速率有12Mbit/s。影響傳輸速率的是SPI和LoRA本身的速率。一個高速一個低速,最後的結果是跟低速平衡。測試使用的2個收發器不停的互發,結果很是滿意。後面就可以根據Qt的串口設備類,自定義協議自己做一個上位機了。另附上例子代碼(包含驅動)USB_CDC_Example.zip
這裏寫圖片描述

我做的實物效果圖
這裏寫圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章