STM VCP移植笔记

0x01 为什么要写这篇文章

  1. 官方sample针对的是stm提供的开发板,而我们往往使用的是自己买的或者diy的开发板,硬件上有一些不同,而对于新手来说,官方的sample有许多宏来配置针对他自己的开发板,导致新手看代码看起来很晕。
  2. 很多人并不想从头了解USB的协议,如何枚举,如何写描述符,只是想快速的搭建一个VCP。

0x02 sample的思路

以下内容都是从网上收集的

目前发布的STM32_USB-FS-Device_Lib中有一个USB虚拟串口的例程,这个例程演示了把STM32配置为一个USB虚拟串口设备,STM32从它的USART接口接收数据并通过USB传送到上位机,反之STM32也从USB接收上位机送来的数据并从USART接口发送出去。

在从USART接口接收数据再向USB端口发送数据的这个方向上,例程采取的策略是:在每次从USART接口收到一个字节后,就做成一个USB数据包并发送出去。这种方法的好处是程序简单明了,但如果USART端出现连续的数据流时,容易造成数据丢失的问题。

0x03 软硬件环境

所谓移植就是因为我们用的开发板和STM的开发板的硬件上是有区别,STM开发板可以通过IO口控制上拉电阻,以达到控制USB连接的目的,我们这个板子是1.5k电阻直接接在VCC上。

硬件环境(我的开发板)

软件环境

  • eclipse-cpp-kepler-SR2-macosx-cocoa.tar.gz
  • gcc-arm-none-eabi–4_8–2014q3–20140805-mac.tar.bz2
  • ilg.gnuarmeclipse.repository–2.4.2–201411261616.zip
  • JLink_MacOSX_V510c.pkg
  • STM32_USB-FS-Device_Lib_V3.3.0

0x04 开始移植

修改项目include配置

基础工作做好了,可以开始修改里面的代码了

现在我们开始从上到下修改一遍文件

hw_config.c

去掉include文件

#include "platform_config.h"
#include "stm32_eval.h"

添加include文件

#include "stm32f10x_conf.h"
#include "usb_conf.h"

注释掉下面这段,因为我们不用GPIO控制USB通断

  /* Enable USB_DISCONNECT GPIO clock */
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIO_DISCONNECT, ENABLE);

  /* Configure USB pull-up pin */
  GPIO_InitStructure.GPIO_Pin = USB_DISCONNECT_PIN;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
  GPIO_Init(USB_DISCONNECT, &GPIO_InitStructure);

注释掉下面这段,因为我们不用从USART接收数据再发送到USB,我们移植后肯定有自己的逻辑

  /* Enable USART Interrupt */
  NVIC_InitStructure.NVIC_IRQChannel = EVAL_COM1_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
  NVIC_Init(&NVIC_InitStructure);

注释掉下面这段,同上,我们不用GPIO控制USB通断

  if (NewState != DISABLE)
  {
    GPIO_ResetBits(USB_DISCONNECT, USB_DISCONNECT_PIN);
  }
  else
  {
    GPIO_SetBits(USB_DISCONNECT, USB_DISCONNECT_PIN);
  }

注释掉函数内部的内容,同上,USART是例子中的我们不用

void USART_Config_Default(void)

注释掉函数内部的内容,原理同上

bool USART_Config(void)

注释掉函数内部的内容

void USB_To_USART_Send_Data(uint8_t* data_buffer, uint8_t Nb_bytes)

注释掉函数内部的内容

void USART_To_USB_Send_Data(void)

这句没啥用,也注释掉吧

extern LINE_CODING linecoding;

stm32f10x_it.c

去掉include文件

#include "platform_config.h"
#include "stm32_eval.h"

注释掉函数内部的内容

void EVAL_COM1_IRQHandler(void)

main.c

#include "usb_lib.h"
#include "usb_desc.h"
#include "hw_config.h"
#include "usb_pwr.h"

int main(int argc, char* argv[]) {
    Set_System();
    Set_USBClock();
    USB_Interrupts_Config();
    USB_Init();

    while (1) {
        // Add your code here.
    }
}

至此项目就可以编译生成hex了,接下来我们把hex传到开发板上,我用的是JLink SWD方式

在项目的根目录建一个文本文件,命名为JLinkCommand.jlink,内容如下

r
speed 4000
device STM32F103RC
loadbin /Users/xuwen/Documents/workspace/MCU/stm32f103rcvirtualcom/Debug/stm32f103rcvirtualcom.hex 0x08000000
g
qc



xuwendeMacBook-Pro:~ xuwen$ /Applications/SEGGER/JLink/JLinkExe -device STM32F103RC -if SWD -speed 4000 -autoconnect 1 -CommanderScript /Users/xuwen/Documents/workspace/MCU/vcpdemo/JLinkCommand.jlink

将开发板接到机器上就可以看结果了,这里我测试win7可以自动安装驱动,osx不需要安装驱动。 但是上面的移植改造只是让系统能识别到串口,并没有啥实际功能,这里再放一段代码实现个简单的数据发送功能。

#include "usb_lib.h"
#include "usb_desc.h"
#include "hw_config.h"
#include "usb_pwr.h"
#include "usb_conf.h"
#include "stdio.h"

uint8_t buff[16] = { 0 };

void delay_ms(u16 nms) {
    u32 temp;
    SysTick->LOAD = 9000 * nms;
    SysTick->VAL = 0X00; //清空计数器
    SysTick->CTRL = 0X01; //使能,减到零是无动作,采用外部时钟源
    do {
        temp = SysTick->CTRL; //读取当前倒计数值
    } while ((temp & 0x01) && (!(temp & (1 << 16)))); //等待时间到达
    SysTick->CTRL = 0x00; //关闭计数器
    SysTick->VAL = 0X00; //清空计数器
}

int main(int argc, char* argv[]) {
    Set_System();
    Set_USBClock();
    USB_Interrupts_Config();
    USB_Init();

    while (1) {
        // Add your code here.
        sprintf(buff, "temp:%9.3f\r\n", 3.145);
        UserToPMABufferCopy(buff, ENDP1_TXADDR, 16);
        SetEPTxCount(ENDP1, 16);
        SetEPTxValid(ENDP1);

        delay_ms(1000);
    }
}

0x05 网上资料

下面是我在做上面工作的时候在网上搜索的内容,感觉对移植工作理解有所帮助也就拷贝下来了

网上参考:usb stm32f103rc 虚拟串口的数据收发 2011–12–06 17:19阅读:855 1、时钟定义 //由72M分频1.5后得到 RCC_USBCLKConfig(RCC_USBCLKSource_PLLCLK_1Div5); RCC_APB1PeriphClockCmd(RCC_APB1Periph_USB, ENABLE); 2、中断向量定义 NVIC_InitStructure.NVIC_IRQChannel = USB_LP_CAN1_RX0_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); 3、中断函数 void USB_LP_CAN1_RX0_IRQHandler(void) {//添加usb中断的处理函数 USB_Istr(); } 4、例程中用的是端点1发数据,端点3收数据,数据的处理函数放入下表中 void (pEpInt_IN[7])(void) = { EP1_IN_Callback,—– }; void (pEpInt_OUT[7])(void) = { EP3_OUT_Callback,—- }; 5、USB的相关初始化在STM公司提供的例程中有,主线是把握中断方式,在主机隔一段时间查询是否有数据收发时处理数据。

网上参考:使用STM32F102/103 USB函数库 进行USB通信

第一步: 根据应用的需求,定义使用到的端点数量

usb_conf.h

define EP_NUM (3)

以上意味着应用需要使用到EP0,EP1和EP2 第二步: 初始化每个使用到的端点

usb_prop.c

SetEPType(ENDP2, EP_INTERRUPT); 定义端点2为中断端点

SetEPTxAddr(ENDP2, ENDP2_TXADDR); 如果需要进行EP2 IN通信,需要定义端点2的发送缓存区的地址,也就是在Packet Buffer中的偏移地址

SetEPRxAddr(ENDP2, ENDP2_RXADDR); 如果需要进行EP2 OUT通信,需要定义端点2的接收缓存区在Packet Buffer中的偏移地址

SetEPRxStatus(ENDP2, EP_RX_NAK); 设置端点2的接收状态为NAK,设备将以NAK来响应主机发起的所有OUT通信。

SetEPTxStatus(ENDP2, EP_TX_NAK); 设置端点2的发送状态为NAK,设备将以NAK来响应主机发起的所有IN通信。

第三步: 使能端点的通信

对于IN端点的使能: UserToPMABufferCopy(Send_Buffer, ENDP2_TXADDR, 8); 拷贝用户数据到端点2的发送缓存区

SetEPTxCount(ENDP2, 8); 设置端点2发送数据长度

SetEPTxValid(ENDP2); 设置端点2的发送状态为VALID

以上三句可以在应用代码的任意位置调用,一旦调用,即使能了一次USB IN通信。

USB设备将在收到主机的IN TOKEN后,自动发送缓存区中的数据到主机,并在发送完毕后产生EP2_IN_Callback中断,同时将端点2的发送状态自动改为NAK。

如果需要再次进行数据传送,需要再次调用以上的三句函数。

对于OUT端点的使能:

SetEPRxValid(ENDP2); 设置端点2的接收状态为VALID。

以上的这句函数即使能了端点2的OUT通信,可以在任意位置调用。

一旦调用,即使能了一次OUT通信。USB设备将以ACK来响应主机随后的OUT通信,并在接收数据完毕后,产生EP2_OUT_Callback中断,同时自动将端点的接收状态改为NAK。

在EP2_OUT_Callback中断函数中调用 USB_SIL_Read(EP2_OUT, Receive_Buffer); 可以将端点2接收缓存区中收到的数据拷贝到用户数据区

posted on 2017-12-18 15:21  BH4LM  阅读(612)  评论(0编辑  收藏  举报

导航