STM32-USB使用方法
开发板买的是奋斗mini开发板,芯片是:STM32F103VET6. 如果需要源程序keil4-arm工程,上位机vb。加qq339396264要程序。
USB开发涉及主机和设备,为了避免开发驱动程序,使用Windows自带的驱动程序。所以设备枚举成HID类设备。USB鼠标就是标准的USB-HID设备。不过操作系统阻止了应用程序直接访问USB鼠标返回的报告。所以本例使用自定义HID设备。一来免去了开发驱动程序,二来自定义的HID设备应用程序和设备可以自由收发数据(仅指数据内容)。
本文主要介绍STM32的USB模块的简单使用,不会介绍USB协议。主要是介绍一下STM32F103的USB模块使用。
USB模块从初始化首先是配置和使能时钟
下面是时钟的初始化:
void Set_USBClock(void) { }
首先系统时钟要设置为72MHz,然后配置USB时钟为48MHz并使能。
然后是配置中断
void USB_Interrupts_Config(void) { }
//【使能USB中断】
NVIC_InitStructure.NVIC_IRQChannel = USB_LP_CAN1_RX0_IRQn; //【USB低优先级中断】 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); NVIC_InitTypeDef NVIC_InitStructure; #define USB_LP_CAN1_RX0_IRQn 20
//RCC_USBCLKSource_PLLCLK_1Div5表示【USB时钟 = PLL时钟除以1.5】【72/1.5=48MHz】 RCC_USBCLKConfig(RCC_USBCLKSource_PLLCLK_1Div5);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USB, ENABLE);//【使能配置好了的USB时钟】
然后初始化模块的一些寄存器 USART_send_str(\断开\\r\\n\
USB_DISCONNECT//这是断开1.5k上拉电阻 delay(0x7AFFFF);//延时一会
USB_CONNECT//使能1.5K上拉电阻 USART_send_str(\连接\\r\\n\
DADDR = 0x0080;//USB模块使能位。EF必须置1
CNTR = 0x0001;//强制复位 CNTR = 0x0000;//退出复位
ISTR = 0x0000;//清除可能产生的假中断
CNTR = (I_CTR | I_RESET);//使能一些中断,为了简单这里只使能了【总线复位中断】和【数据正确传输中断】 ISTR = 0x0000;//清除可能产生的假中断
自此USB模块初始化完毕。
以后的流程就在USB中断的驱动下执行。
USB中断函数:
void USB_LP_CAN1_RX0_IRQHandler(void) { u16 wIstr; //USB中断状态寄存器 wIstr = (u16)ISTR; //【USB复位中断】 if(wIstr &I_RESET) { USB_RST_Process(); return; } //【正确传输中断】 if (wIstr & I_CTR) { USB_CTR_Process(); return; } }
在USB中断函数中就是判断中断源,并转向相应的中断服务函数。
USB总线复位后,USB相关的一些寄存器会复位。在USB总线复位中要配置一下寄存器和数据收发的缓冲区。程序如下 void USB_RST_Process(void) { //清除中断 ISTR = (u16)(~I_RESET);//清除ISTR寄存器中断RESET位。写0清除,写1寄存器位内容不变。
// USART_send_str(\【USB】【复】【位】【中】【断】\\r\\n\ usb_status = 0; //复位处理 DADDR = 0x0080; //USB模块使能位。EF必须置1 BTABLE = 0; //复位后默认为0 EP0REG = (u16)(0x0220); //0000 0010 0010 0000 端点0初始化:控制端点,NAK主机的IN令牌,端点号0 //设置TX/RX起始地址 USB_ADDR0_TX = 32;//32-95 端点0的发送缓冲区从32开始到95,共64字节 USB_ADDR0_RX = 96;//96-159 端点0的接收缓冲区从96开始到159,共64字节 //设置端点0接收缓冲区大小:64Byte USB_CNT0_RX = 0x8400;//1000,0100,0000,0000
//【端点1】
EP1REG = (u16)(0x0621);//0000 0110 0010 0001 端点0初始化:中断端点,NAK主机的IN令牌,端点号1
//设置TX/RX起始地址
USB_ADDR1_TX = 160;//160-223 端点1的发送缓冲区从160开始到223,共64字节 USB_ADDR1_RX = 224;//224-287 端点1的接收缓冲区从224开始到287,共64字节 //设置端点1接收缓冲区大小:64Byte
USB_CNT1_RX = 0x8400;//1000,0100,0000,0000 //设置RX Valid .端点0可用于接收 { u16 t = EP0REG;//读出端点0寄存器值 t &= 0x8F8F;//1000 1111 1000 1111 由于t位写0不影响原来的值,写1翻转,所以把 t位都设置为0 t |= 0x8080;//1000 0000 1000 0000 由于w0位写0清除,写1无影响,所以w0位都设置为1 t |= 0x3000;//0011 0000 0000 0000 把 SRAT_RX[1:0]设置为1,通过写1翻转来实现。 EP0REG = t; } }
下面是正确传输中断 //USB分组正确传输处理 void USB_CTR_Process(void) {
/******************************************【端点0】******************************************/ if((ISTR & 0x0F) == 0)//端点0 { // //
if((ISTR & 0x10) == 0)//in分组 { }
USART_send_str(\端点0的IN分组发送完成****************\\r\\n\
if(usb_status == 1) { usb_status = 2; DADDR = 0x80 | (usb_addr&0xFF);//设置地址一定要找控制传输的状态完成后写入地址寄存器 USART_send_str(\【写入地址完成】\\r\\n\}
//发送剩余数据 USB_ep0_send();
//清除CTR_TX { u16 t = EP0REG; t &= 0x8F8F;//1000 1111 1000 1111 把 t位都设置为0 t |= 0x8080;//1000 0000 1000 0000 把w0位都设置为1 t &= 0xFF7F;//1111 1111 0111 1111 把CTR_TX设置为0,清除CTR_RX位 EP0REG = t; }
if(ISTR & 0x10)//【OUT】分组 or 【SETUP】分组 { u16 ep0r= EP0REG;
//清除CTR_RX { u16 t = EP0REG; t &= 0x8F8F;//1000 1111 1000 1111 把 t位都设置为0 t |= 0x8080;//1000 0000 1000 0000 把w0位都设置为1 t &= 0x7FFF;//0111 1111 1111 1111 把CTR_RX设置为0,清除CTR_RX位 EP0REG = t; }
if(ep0r & 0x0800)//SETUP令牌包 { //USART_send_str(\端点0收到【SETUP】分组\\r\\n\ //默认控制端点处理——枚举处理 ep0_setup_Process(); memset(USB_rcv_buffer, 0x00, sizeof(USB_rcv_buffer)); USB_enable_ep0_rx(); } else { //USART_send_str(\控制读传输:状态阶段\\r\\n\ USB_read_EP0_buf(USB_rcv_buffer, (u32*)(PMAAddr+96*2)); USB_enable_ep0_rx(); } } //else//【IN分组传输成功】 }
/******************************************【端点1】******************************************/ else if((ISTR & 0x0F) == 1) { USART_send_str(\端点1数据完成传输\\r\\n\ if(ISTR & 0x10)//【OUT分组】 { //清除CTR_RX { u16 t = EP1REG; t &= 0x8F8F;//1000 1111 1000 1111 把 t位都设置为0 t |= 0x8080;//1000 0000 1000 0000 把w0位都设置为1 t &= 0x7FFF;//0111 1111 1111 1111 把CTR_RX设置为0,清除CTR_RX位 EP1REG = t; } USB_read_EP1_buf(USB_rcv_buffer, (u32*)(PMAAddr+224*2)); //【读 取】端点1数据 //【处 理】端点1数据 USB_enable_ep1_rx(); //【继续接受】端点1数据 }
}
else//【IN分组】 { //清除CTR_TX { u16 t = EP1REG; }
t &= 0x8F8F;//1000 1111 1000 1111 把 t位都设置为0 t |= 0x8080;//1000 0000 1000 0000 把w0位都设置为1 t &= 0xFF7F;//1111 1111 0111 1111 把CTR_TX设置为0,清除CTR_RX位 EP1REG = t; }
//标记发送完成
/******************************************【端点ERR】******************************************/ else { USART_send_str(\出错!端点号是没有开启对端点。\\r\\n\ } }
其他代码:
//设置RX Valid .使端点继续接收 void USB_enable_ep0_rx(void) { u16 t = EP0REG;
// USART_send_str(\ t &= 0x8F8F;//1000 1111 1000 1111 把 t位都设置为0 t |= 0x8080;//1000 0000 1000 0000 把w0位都设置为1 switch(EP0REG & 0x3000) { case 0x0000: t |= 0x3000;break; case 0x1000: t |= 0x2000;break; case 0x2000: t |= 0x1000;break; case 0x3000: t |= 0x0000;break; } EP0REG = t;
// t = EP0REG;
// USART_send_str(\}
//设置RX Valid .使端点继续接收 void USB_enable_ep1_rx(void) { u16 t = EP1REG; t &= 0x8F8F;//1000 1111 1000 1111 把 t位都设置为0 t |= 0x8080;//1000 0000 1000 0000 把w0位都设置为1 switch(EP1REG & 0x3000) { case 0x0000: t |= 0x3000;break; case 0x1000: t |= 0x2000;break; case 0x2000: t |= 0x1000;break; case 0x3000: t |= 0x0000;break; } EP1REG = t; }
//读取端点0收到的数据
u8 USB_read_EP0_buf(u8 *buf, u32 *USB_buf) {