USB (Universal Serial BUS),即通用串行总线,是一个外部总线标准,用于规范电脑与外部设备的链接和通信, USB在1994年末由英特尔、康柏、IBM、Microsoft等多家公司联合提出。web
USB有USB1.0/1.1/2.0/3.0等多个版本。目前用的最多的是USB2.0和USB3.0。标准USB由四根线组成:VCC、GND、D+和D-,D+和D-是数据线,采用差分传输。在USB主机上,D-和D+都是接了15K的电阻到地的,因此在没有设备接入的时候,D+、D-均是低电平。而在USB设备中,若是是高速设备,则会在D+上接一个1.5K的电阻到VCC,而若是是低速设备,则会在D-上接一个1.5K的电阻到VCC,当设备接入主机时,主机就能够判断是否有设备接入,并能判断设备是高速设备仍是低速设备。数组
STM32F103系列芯片,都自带了一个USB从控制器,支持USB全速(FS,12Mbps)通讯,符合USB 2.0技术规范。缓存
STM32F103的USB模块实现了标准USB接口的全部特性,它由:串行接口控制器(SIE)、定时器、分组缓冲器接口、端点相关寄存器、控制寄存器和中断寄存器等部分组成。app
USB数据传输经过一个专用的数据缓冲区来完成,它能被USB外设直接访问,其大小由所使用的端点数目和每一个端点最大数据分组大小所决定,每一个端点最大可以使用512字节缓冲区(专用的512字节,和CAN共用),最多可用于16个单向或8个双向端点。USB模块同PC主机通讯,根据USB规范实现了:令牌分组的检测、数据发送/接收的处理、握手分组的处理。整个传输的格式由硬件完成,其中包括CRC的生成和校验。
注意:USB与CAN共用一个专用的512字节SRAM存储器,用于数据的收发,他们必须互斥访问,因此CAN和USB不能同时使用!!!
详细的USB协议是极其复杂的,建议买本书看吧。
electron
要正常使用STM32F103的USB,就得编写USB驱动,而整个USB通讯的详细过程是很复杂的,若是要咱们本身编写USB驱动,那是一件至关困难的事情。
不过,ST提供了咱们一个完整的USB FS从机驱动库和一个操做说明文件“UM0424 User manual.pdf”,经过这些,咱们能够很方便的实现咱们所要的功能,而不须要详细了解USB的整个驱动,大大缩短了咱们的开发时间和精力。svg
ST提供的USB FS从机驱动库,咱们能够在ST官方社区: http://www.stmcu.org下载到(搜索关键字:UM0424)。下载的文件名:STSW-STM32121.zip 。该库包含了STM32F103的USB从机(Device)驱动库,并提供了8个例程供咱们参考。
这8个例程,虽然都是基于官方EVAL板,可是很容易移植到咱们的STM32开发板上,本例程咱们就移植:STM32_USB-FS-Device_Lib_V4.0.0
Projects\Virtual_COM_Port这个例程,以实现USB虚拟串口的功能。函数
STM32F103芯片具备USB功能,其引脚对应以下:
PA11----USBDM----D-
PA12----USBDP----D+
测试
一、ST提供的USB FS从机驱动库,咱们能够在ST官方社区: http://www.stmcu.org下载到(搜索关键字:UM0424)。下载的文件名:STSW-STM32121.zip 。该库包含了STM32F103的USB从机(Device)驱动库,并提供了8个例程供咱们参考。
二、TFTLCD工程实验工程大数据
一、打开官方的例程 STM32_USB-FS-Device_Lib_V4.0.0 --> Projects --> Virtual_COM_Port,打开时提示须要安装部分固件库,直接忽视,随后选择红色箭头的标识。此时编译会有报错,这是由于例程是在MDK4下的,使用MDK5的话须要在 inc 文件夹里添加core_cm3.h、 core_cm3.h(库函数版本的工程),或者 core_cm3.h、core_cmInstr.h、core_cmFunc.h(寄存器版本的工程,从寄存器例程里拷贝)。ui
这里只是打开查看下驱动文件,core3_cm3.h等文件在TFTLCD卡实验工程里有的。
二、拷贝USB从机驱动
1)在 TFTLCD 工程目录下新建 USB 文件夹。
2)打开 STM32_USB-FS-Device_Lib_V4.0.0 --> Libraries 找到 STM32_USB-FS-Device_Driver 文件夹,将该文件夹拷贝到 USB 目录下。
3)在USB目录下新建config文件夹,拷贝 Virtual_COM_Port 相关代码进去。
三、打开 TFTLCD 工程,添加USB代码
四、编译代码,根据报错修改(12个报错)
1)platform_config.h的include部分,使用以下代码替代:
#include "sys.h"
2)修改hw_config.c
a. 去掉全部的头文件,包括stm32_it.h,并添加一些其余头文件,以下:
#include "usb_lib.h" #include "usb_prop.h" #include "usb_desc.h" #include "usb_istr.h" #include "hw_config.h" #include "usb_pwr.h" #include "usart.h" #include "string.h" #include "stdarg.h" #include "stdio.h"
b. 去掉HSEStartUpStatus和EXTI_InitStructure等结构体和变量的定义,采用以下代码替代:
usb_usart_fifo uu_txfifo;//USB串口发送FIFO结构体,在hw_config.h定义 u8 USART_PRINTF_Buffer[USB_USART_REC_LEN];//usb_printf发送缓冲区 //用相似串口1(串口通讯实验)接收数据的方法,来处理USB虚拟串口接收到的数据. u8 USB_USART_RX_BUF[USB_USART_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节 //接收状态 //bit15, 接收完成标志 //bit14, 接收到0x0d //bit13~0, 接收到的有效字节数目 u16 USB_USART_RX_STA=0;//接收状态标记 extern LINE_CODING linecoding;//USB虚拟串口配置信息(波特率,位数等),在usb_prop.c定义
c. 删掉Set_System函数,并添加USBWakeUp_IRQHandler和USB_LP_
CAN1_RX0_IRQHandler函数(官方例程是在stm32_it.c里面,咱们将其移到这里),代码以下:
//USB唤醒中断服务函数 void USBWakeUp_IRQHandler(void) { EXTI_ClearITPendingBit(EXTI_Line18);//清除USB唤醒中断挂起位 } //USB中断处理函数 void USB_LP_CAN1_RX0_IRQHandler(void) { USB_Istr();//处理各类USB中断事件 }
d. 修改Set_USBClock函数为:
//USB时钟配置函数,USBclk=48Mhz@HCLK=72Mhz void Set_USBClock(void) { RCC_USBCLKConfig(RCC_USBCLKSource_PLLCLK_1Div5); //USBclk=PLLclk/1.5=48Mhz RCC_APB1PeriphClockCmd(RCC_APB1Periph_USB, ENABLE); //USB时钟使能 }
e. 修改Enter_LowPowerMode函数为:
void Enter_LowPowerMode(void) { printf("usb enter low power mode\r\n"); bDeviceState=SUSPENDED; //bDeviceState记录USB链接状态,在usb_pwr.c里面定义 }
f. 修改Leave_LowPowerMode函数为:
void Leave_LowPowerMode(void) { DEVICE_INFO *pInfo=&Device_Info; printf("leave low power mode\r\n"); if (pInfo->Current_Configuration!=0)bDeviceState=CONFIGURED; else bDeviceState = ATTACHED; }
g. 修改USB_Interrupts_Config函数为
void USB_Interrupts_Config(void) { NVIC_InitTypeDef NVIC_InitStructure; EXTI_InitTypeDef EXTI_InitStructure; /* Configure the EXTI line 18 connected internally to the USB IP */ EXTI_ClearITPendingBit(EXTI_Line18); // 开启线18上的中断 EXTI_InitStructure.EXTI_Line = EXTI_Line18; // USB resume from suspend mode EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; //line 18上事件上升降沿触发 EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStructure); /* Enable the USB interrupt */ NVIC_InitStructure.NVIC_IRQChannel = USB_LP_CAN1_RX0_IRQn;//组2,优先级次之 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); /* Enable the USB Wake-up interrupt */ NVIC_InitStructure.NVIC_IRQChannel = USBWakeUp_IRQn; //组2,优先级最高 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_Init(&NVIC_InitStructure); }
h. 修改USB_Cable_Config函数为
//USB接口配置(配置1.5K上拉电阻,ALIENTEK的M3系列开发板,不须要配置,固定加了上拉电阻) //NewState:DISABLE,不上拉,ENABLE,上拉 void USB_Cable_Config (FunctionalState NewState) { if (NewState!=DISABLE)printf("usb pull up enable\r\n"); else printf("usb pull up disable\r\n"); }
i. 删除USART_Config_Default函数 ,新增USB_Port_Set函数,代码以下
//USB使能链接/断线 enable:0,断开;1,容许链接 void USB_Port_Set(u8 enable) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //使能PORTA时钟 if(enable)_SetCNTR(_GetCNTR()&(~(1<<1)));//退出断电模式 else { _SetCNTR(_GetCNTR()|(1<<1)); // 断电模式 GPIOA->CRH&=0XFFF00FFF; GPIOA->CRH|=0X00033000; PAout(12)=0; } }
j. 修改USART_Config函数为:
//USB COM口的配置信息,经过此函数打印出来. bool USART_Config(void) { uu_txfifo.readptr=0; //清空读指针 uu_txfifo.writeptr=0; //清空写指针 USB_USART_RX_STA=0; //USB USART接收状态清零 printf("linecoding.format:%d\r\n",linecoding.format); printf("linecoding.paritytype:%d\r\n",linecoding.paritytype); printf("linecoding.datatype:%d\r\n",linecoding.datatype); printf("linecoding.bitrate:%d\r\n",linecoding.bitrate); return (TRUE); }
k. 修改USB_To_USART_Send_Data函数为:
//处理从USB虚拟串口接收到的数据。参数:databuffer:数据缓存区,Nb_bytes:接收到的字节数. void USB_To_USART_Send_Data(u8* data_buffer, u8 Nb_bytes) { u8 i; u8 res; for(i=0;i<Nb_bytes;i++) { res=data_buffer[i]; if((USB_USART_RX_STA&0x8000)==0) //接收未完成 { if(USB_USART_RX_STA&0x4000) //接收到了0x0d { if(res!=0x0a)USB_USART_RX_STA=0; //错误,从新开始 else USB_USART_RX_STA|=0x8000; //接收完成了 }else //还没收到0X0D { if(res==0x0d)USB_USART_RX_STA|=0x4000;//标记接收到了0X0d else { USB_USART_RX_BUF[USB_USART_RX_STA&0X3FFF]=res;USB_USART_RX_STA++; if(USB_USART_RX_STA>(USB_USART_REC_LEN-1))USB_USART_RX_STA=0;//错误,重收 } } } } }
l. 删除Handle_USBAsynchXfer和USART_To_USB_Send_Data这两个函数,而后,新增USB_USART_SendData函数,用于虚拟串口发送一个字节到USB(这里实际上只写到了发送FIFO,最终仍是由EP1_IN_Callback函数实现输出给USB):
//发送一个字节数据到USB虚拟串口 void USB_USART_SendData(u8 data) { uu_txfifo.buffer[uu_txfifo.writeptr]=data; uu_txfifo.writeptr++; if(uu_txfifo.writeptr==USB_USART_TXFIFO_SIZE)//超过buf大小了,归零. { uu_txfifo.writeptr=0; } }
m .删除IntToUnicode函数前面的static关键字,新增usb_printf函数,用于实现USB虚拟串口的printf,代码以下:
//usb虚拟串口,printf 函数 //确保一次发送数据不超USB_USART_REC_LEN字节 void usb_printf(char* fmt,...) { u16 i,j; va_list ap; va_start(ap,fmt); vsprintf((char*)USART_PRINTF_Buffer,fmt,ap); va_end(ap); i=strlen((const char*)USART_PRINTF_Buffer);//这次发送数据的长度 for(j=0;j<i;j++)//循环发送数据 { USB_USART_SendData(USART_PRINTF_Buffer[j]); } }
n. 修改hw_config.h,删除MASS_MEMORY_START等宏定义,而后,新增USB_USART_TXFIFO_SIZE等宏定义和结构体,代码以下:
#define USB_USART_TXFIFO_SIZE 1024 //USB虚拟串口发送FIFO大小 #define USB_USART_REC_LEN 200 //USB串口接收缓冲区最大字节数 //定义一个USB USART FIFO结构体 typedef struct { u8 buffer[USB_USART_TXFIFO_SIZE]; //buffer vu16 writeptr; //写指针 vu16 readptr; //读指针 }_usb_usart_fifo; extern _usb_usart_fifo uu_txfifo; //USB串口发送FIFO extern u8 USB_USART_RX_BUF[USB_USART_REC_LEN]; //接收缓冲,最大USB_USART_REC_LEN个字节 extern u16 USB_USART_RX_STA; //接收状态标记
o. 修改hw_config.h, 新增IntToUnicode、USB_USART_SendData、USB_Port_Set和usb_printf等函数的声明,代码以下:
void IntToUnicode (u32 value,u8 *pbuf,u8 len); void USB_USART_SendData(u8 data); void USB_Port_Set(u8 enable); void usb_printf(char* fmt,...);
void EP1_IN_Callback (void) { u16 USB_Tx_ptr; u16 USB_Tx_length; if(uu_txfifo.readptr==uu_txfifo.writeptr)return; //无任何数据要发送,直接退出 if(uu_txfifo.readptr<uu_txfifo.writeptr) //没有超过数组,读指针<写指针 { USB_Tx_length=uu_txfifo.writeptr-uu_txfifo.readptr; //获得要发送的数据长度 }else USB_Tx_length=USB_USART_TXFIFO_SIZE-uu_txfifo.readptr;//读指针>写指针时发送数据长度 if(USB_Tx_length>VIRTUAL_COM_PORT_DATA_SIZE) //超过64字节? { USB_Tx_length=VIRTUAL_COM_PORT_DATA_SIZE;//这次发送数据量 } USB_Tx_ptr=uu_txfifo.readptr; //发送起始地址 uu_txfifo.readptr+=USB_Tx_length; //读指针偏移 if(uu_txfifo.readptr>=USB_USART_TXFIFO_SIZE) //读指针归零 { uu_txfifo.readptr=0; } UserToPMABufferCopy(&uu_txfifo.buffer[USB_Tx_ptr], ENDP1_TXADDR, USB_Tx_length);//拷贝数据 SetEPTxCount(ENDP1, USB_Tx_length); //设置端点1发送数据长度 SetEPTxValid(ENDP1); //设置端点1发送有效 }
b. 修改SOF_Callback函数为:
//帧首信号回调函数,对全速设备每1ms有一个帧首信号 void SOF_Callback(void) { static uint32_t FrameCount = 0; if(bDeviceState == CONFIGURED) { if (FrameCount++ == VCOMPORT_IN_FRAME_INTERVAL) { FrameCount = 0;//重设帧计数器 EP1_IN_Callback();//经过EP1_IN_Callback函数实现TX数据发送给USB } } }
修改usb_prop.c文件。本例程没有用到USART_Config_Default函数,因此:注释掉Virtual_Com_Port_init函数里面对该函数的调用。
,修改usb_pwr.c文件。
修改Suspend函数为:
void Suspend(void) { uint32_t i =0;uint16_t wCNTR;__IO uint32_t savePWR_CR=0; wCNTR = _GetCNTR(); //Store CNTR value for (i=0;i<8;i++) EP[i] = _GetENDPOINT(i); wCNTR|=CNTR_RESETM;//unmask RESET flag _SetCNTR(wCNTR); wCNTR|=CNTR_FRES;//apply FRES _SetCNTR(wCNTR); wCNTR&=~CNTR_FRES;//clear FRES _SetCNTR(wCNTR); while((_GetISTR()&ISTR_RESET) == 0); //poll for RESET flag in ISTR _SetISTR((uint16_t)CLR_RESET); //clear RESET flag in ISTR for (i=0;i<8;i++)_SetENDPOINT(i, EP[i]); //restore Enpoints wCNTR |= CNTR_FSUSP; _SetCNTR(wCNTR); wCNTR = _GetCNTR();//force low-power mode in the macrocell wCNTR |= CNTR_LPMODE; _SetCNTR(wCNTR); Enter_LowPowerMode(); }
注意:USB虚拟串口实验,在测试的时候,须要安装ST提供的一个电脑端虚拟串口驱动软件VCP_V1.4.0_Setup.exe,不然电脑将没法识别。 若是测试时,不能自动安装成功,请手动选择驱动路径(见:C:\Program Files (x86)\STMicroelectronics Software\Virtual comport driver\WIN7/8),让电脑完成安装便可。