STM32 USB数据接收与数据发送程序流程分析

发布者:心灵之舞最新更新时间:2016-12-16 来源: eefocus关键字:STM32  USB  数据接收  数据发送 手机看文章 扫描二维码
随时随地手机看文章

既然学习了USB,那就必须的搞懂USB设备与USB主机数据是怎么通讯的。这里主要讲设备端,因为我们的代码是做USB设备用的。

我们需要必须要定义了USB中断。起始在STM32的中断向量表中给USB两个中断,我们可以在stm32f10x.h中找到这两个中断:

USB_HP_CAN1_TX_IRQn         = 19,   /*!< USB Device High Priority or CAN1 TX Interrupts  */
  USB_LP_CAN1_RX0_IRQn        = 20,   /*!< USB Device Low Priority or CAN1 RX0 Interrupts  */

这两个中断是USB与CAN复用的中断,在做USB用时,表示USB设备的高优先级与低优先级中断。在我的工程中,我选择用低优先级的USB中断。代码如下:

void USB_LP_CAN1_RX0_IRQHandler(void)
{
  USB_Istr();
}

中断服务程序很简单,就是在发生中断的时候调用USB_istr()函数。USB_istr()这个函数我们之前说过的,在usb_istr.c中定义的。这个函数处理ISTR中断状态寄存器中定义的中断,包括:CTR正确传输中断、RESET复位中断,DOVR分组缓冲溢出中断、ERR错误中断、WAKEUP中断、SUSP挂起中断、SOF帧首中断、ESOF期望帧首中断。这里重点是CTR中断,在USB在正确发送或正确接收数据后,USB模块自动回将ISTR寄存器的该位置1,触发中断CTR中断。在USB_istr()中CTR的处理代码如下:

#if (IMR_MSK & ISTR_CTR)	//正确传输中断CTR标志
  if (wIstr & ISTR_CTR & wInterrupt_Mask)//读出的中断标志是CRT中断标志,且CRT中断使能了
  {
    CTR_LP();			//调用正确传输中断服务程序
#ifdef CTR_CALLBACK
    CTR_Callback();		//当定义了CTR_CALLBACK,则调用CTR_Callback,像钩子函数一样,在发生CRT中断时做点什么
#endif
  }

首先要解释下 #if (IMR_MSK & ISTR_CTR) 这句话。

#define IMR_MSK (CNTR_CTRM | CNTR_WKUPM | CNTR_SUSPM | CNTR_ERRM | CNTR_SOFM \ | CNTR_ESOFM | CNTR_RESETM )

这是IMR_MSK的定义,表示包含所有中断的掩码,IMR_MSK & ISTR_CTR表示:如果ISTR_CTR是规定的中断类别,则编译#if与#endif之间的代码。很明显这里符合。然后,判断下从CNTR寄存器中读出来的中断值是CRT中断,且该中断已经在CNTR中使能了。接着调用CTR_LP()函数处理,如果定义了CTR_CALLBACK,则调用CTR_Callback()函数,该函数是个钩子函数,让用户在正确接收到数据后能够做些什么,比如说亮下灯或通过串口打印些消息。

这里需要着分析下CTR_LP()这个函数在usb_int.c中定义。代码如下:

/*******************************************************************************
* Function Name  : CTR_LP.
* Description    : 低优先级的端点正确传输中断服务程序
* Input          : None.
* Output         : None.
* Return         : None.
*******************************************************************************/
void CTR_LP(void)
{
  __IO uint16_t wEPVal = 0;
  while (((wIstr = _GetISTR()) & ISTR_CTR) != 0)	//读取中断状态寄存器的值,看是否是CRT(正确传输中断)
  {
    EPindex = (uint8_t)(wIstr & ISTR_EP_ID);		//获取产生中断的端点号,
    if (EPindex == 0)					//如果端点0
    {
	    SaveRState = _GetENDPOINT(ENDP0);		//读取端点0的状态寄存器
	    SaveTState = SaveRState & EPTX_STAT;	//保存端点0发送状态
	    SaveRState &=  EPRX_STAT;			//保存端点0接收状态

	    _SetEPRxTxStatus(ENDP0,EP_RX_NAK,EP_TX_NAK);//设置端点0对主机以NAK方式响应所有的接收和发送请求
      if ((wIstr & ISTR_DIR) == 0)			//如果是IN令牌
      {
        _ClearEP_CTR_TX(ENDP0);				//清除端点0正确发送标志位
        In0_Process();					//处理IN令牌包

           /* before terminate set Tx & Rx status */

            _SetEPRxTxStatus(ENDP0,SaveRState,SaveTState);//在传输之前设置端点0接收发送状态位
		  return;
      }
      else						//OUT令牌
      {
        wEPVal = _GetENDPOINT(ENDP0);			//获取端点0的端点寄存器的值
        
        if ((wEPVal &EP_SETUP) != 0)			//SETUP分组传输完成标志位
        {
          _ClearEP_CTR_RX(ENDP0); 			//清除端点0的接收标志位
          Setup0_Process();				//端点0建立阶段的数据处理

		      _SetEPRxTxStatus(ENDP0,SaveRState,SaveTState);//设置端点0阶接收发送标志位
          return;
        }

        else if ((wEPVal & EP_CTR_RX) != 0)		//正确接收标志位
        {
          _ClearEP_CTR_RX(ENDP0);			//清除端点0正确标志位
          Out0_Process();				//处理OUT令牌包
     
		     _SetEPRxTxStatus(ENDP0,SaveRState,SaveTState);//设置端点0的接收发送状态
          return;
        }
      }
    }/* if(EPindex == 0) */
    else						//如果非0端点
    {
      wEPVal = _GetENDPOINT(EPindex);			//获取该端点的端点寄存器的值
      if ((wEPVal & EP_CTR_RX) != 0)			//正确接收标志
      {
        _ClearEP_CTR_RX(EPindex);			//清除端点正确接收标志

        (*pEpInt_OUT[EPindex-1])();			//调用注册过的端点OUT处理函数

      } /* if((wEPVal & EP_CTR_RX) */

      if ((wEPVal & EP_CTR_TX) != 0)			//正确发送标志
      {
        _ClearEP_CTR_TX(EPindex);			//清除正确发送标志

        (*pEpInt_IN[EPindex-1])();			//调用注册过的端点IN处理函数
      } /* if((wEPVal & EP_CTR_TX) != 0) */

    }/* if(EPindex == 0) else */

  }/* while(...) */
}

这个函数首先会判断是否真的CTR中断,如果是,执行while()中的代码,用EPindex来保存产生中断的端点号。EPindex为0表示是端点0产生的中断,说明此时USB还处于枚举阶段。EPindex不为0,表示枚举已经成功了,USB处于正常工作状态。

在枚举阶段,SaveRState保存端点0寄存器的值,接着SaveTState = SaveRState & EPTX_STAT;和SaveRState &=  EPRX_STAT;这两句,SaveTState保存当前发送端点0的状态, SaveRState 保存当前接收端点的状态。接着设置接收端点0为NAk状态,发送端点0也设置成NAK状态,也就是说当主机发送任何数据,从机只以NAK回应,从机也只能发送NAK数据,即不允许在数据处理阶段进行数据通讯。然后判断是输入还是输出。如果是输入(注意这里的输入是相对于主机来说的)则清除端点寄存器的EP_CTR_TX标志位,并且调用IN令牌包处理函数In0_Process()(在usb_core.c中定义)。如果是输出(注意这里的输出是相对于主机来说的),则还要判断接收到是SETUP包还是OUT令牌包,如果是SETUP包,清除端点0寄存器的EP_SETUP位,并且调动SETUP处理函数Setup0_Process(),同时还要回复原来的接发端点的状态,准备处理下一次的中断处理。如果是OUT令牌包,清除端点0寄存器的EP_CRT_RX位,调用OUT处理函数Out0_Process(),同时还要回复原来接法端口的状态,准备处理下一次的中断处理。

在工作阶段或者说是非枚举阶段,首先要判断下是EP_CTR_RX还EP_CTR_TX标志,如果是EP_CTR_RX正确接收标志,则清除该标志,调用对应端点的OUT处理函数(*pEpInt_OUT[EPindex-1])()(在usb_istr中有注册过),如果是EP_CTR_TX标志,则清除该标志,调用对应端点的IN处理函数(*pEpInt_IN[EPindex-1])()(在usb_istr中有注册过)。

在usb_istr.c中非别注册了7个端点输入函数和端点输出函数。如下:

/*定义指向指针的函数指针数组,函数指针分别指向7个端点输入服务程序*/
void (*pEpInt_IN[7])(void) =
  {
    EP1_IN_Callback,
    EP2_IN_Callback,
    EP3_IN_Callback,
    EP4_IN_Callback,
    EP5_IN_Callback,
    EP6_IN_Callback,
    EP7_IN_Callback,
  };

/*定义指向指针的函数指针数组,函数指针分别指向7个端点输出服务程序*/
void (*pEpInt_OUT[7])(void) =
  {
    EP1_OUT_Callback,
    EP2_OUT_Callback,
    EP3_OUT_Callback,
    EP4_OUT_Callback,
    EP5_OUT_Callback,
    EP6_OUT_Callback,
    EP7_OUT_Callback,
  };

而这些函数的定义在usb_endp.c中,我们拿EP1_OUT_Callback()函数分析。

/*******************************************************************************
* Function Name  : EP1_OUT_Callback.
* Description    : 端点1输出回调函数
* Input          : None.
* Output         : None.
* Return         : None.
*******************************************************************************/
void EP1_OUT_Callback(void)
{
	PMAToUserBufferCopy(USB_Receive_Buffer, ENDP1_RXADDR, REPORT_COUNT); 	//PMA缓冲区接收到的数据拷贝到用户自定义缓冲区USB_Receive_Buffer中
  	SetEPRxStatus(ENDP1, EP_RX_VALID);					//设置端点的接收状态为有效,因为端点接收到数据后会端点状态自动设置成停止状态
 	USB_Received_Flag=1;							//设置接收到数据标志位
}

这个函数的工作很简单,首先因为数输出端点,是接收数据的,而USB模块接收到的数据又是暂存在PAM双缓冲区中,所以要线把数据从PMA中读取出来,放到用户自己缓冲区中。接着设置端点接收状态有效,因为当接收数据后,端点就会被关闭。最后置位接收带数据标志。

以上就是USB设备的接收的流程。接下去讲讲发送流程。发送比接收简单多了看看下面的代码就知道了。

/**
  * @brief  通过USB发送数据
  * @param  data 数据存储首地址
  * @param  dataNum 发送的数据字节数
  * @retval 发送的字节数
  */
uint32_t USB_SendData(uint8_t *data,uint32_t dataNum)		 
{
	//将数据通过USB发送出去
	UserToPMABufferCopy(data, ENDP2_TXADDR, dataNum);//拷贝数据到PMA中
	SetEPTxCount(ENDP2, REPORT_COUNT); //从端点2发送64字节数据
	SetEPTxValid(ENDP2);			   //使能端点2的发送状态
	return dataNum;  
}

把要发送的数据拷贝到PMA中,之后设置端点计数,使能下端点,数据就发送出去了。

总结下:

数据发送:UserToPMABufferCopy--->SetEPTxCount--->SetEPTxValid

数据接收:USB_LP_CAN1_RX0_IRQHandler--->USB_Istr---->CTR_LP--->EPx_OUT_Callback


关键字:STM32  USB  数据接收  数据发送 引用地址:STM32 USB数据接收与数据发送程序流程分析

上一篇:STM32 GPIO模式理解
下一篇:stm32中的数据类型定义

推荐阅读最新更新时间:2024-03-16 15:25

STM32的NVIC_PriorityGroupConfig使用及优先级分组方式理解
STM32有43个channel的settable的中断源;AIRC(Application Interrupt and Reset Register)寄存器中有用于指定优先级的4 bits。这4个bits用于分配preemption优先级和sub优先级,在STM32的固件库中定义如下 /* Preemption Priority Group -------------------------------------------------*/ #define NVIC_PriorityGroup_0 ((u32)0x700) /* 0 bits for pre-emption priority
[单片机]
基于STM32单片机设计的红外测温仪(带人脸检测)
由于医学发展的需要,在很多情况下,一般的温度计己经满足不了快速而又准确的测温要求,例如:车站、地铁、机场等人口密度较大的地方进行人体温度测量。 当前设计的这款红外非接触式测温仪由测温硬件+上位机软件组合而成,主要用在地铁、车站入口等地方,可以准确识别人脸进行测温,如果有人温度超标会进行语音提示并且保存当前人脸照片。 1、 硬件选型与设计思路 (1). 设备端 主控单片机采用STM32F103C8T6,人体测温功能采用非接触式红外测温模块。 (2). 上位机设计思路 上位机采用Qt5设计,Qt5是一套基于C++语言的跨平台软件库,性能非常强大,目前桌面端很多主流的软件都是采用QT开发。比如: 金山办公旗下的-WPS,字节
[单片机]
基于<font color='red'>STM32</font>单片机设计的红外测温仪(带人脸检测)
STM32单片机(六)-STM32F103 时钟与F407时钟
STM32f103的时钟有四个来源 高速外部时钟信号(HSE)、低速外部时钟信号(LSE)、高速内部时钟信号(HSI)和低速内部时钟信号(LSI),图中分别用蓝色的①~④标注。 ①HSE高速外部时钟:由外部4~16MHz的晶体或有源晶振提供,通常采用8MHz,ST三合一板上的也是8MHz。 ②LSI低速外部时钟:外部晶体提供,主要是给实时时钟(RTC),一般为32.768kHz。 ③HSI高速内部时钟:由内部RC振荡器产生的8MHz时钟,但不够稳定。④LSI低速内部时钟:内部RC振荡器产生的供给RTC的时钟,频率在30kHz~60kHz之间,通常约40kHz。 时钟在STM32内部最终是供给四大块,图中用红色
[单片机]
<font color='red'>STM32</font>单片机(六)-STM32F103 时钟与F407时钟
通用串行总线(USB)原理及接口设计
    摘要: 以USB1.1为基础讨论了USB的基本原理、工作流程、通信协议和相应的关键技术,并介绍了一种USB接口的10M以太网卡的设计方案。已经发布的USB2.0支持480Mbps的高速数据传输,这将使PC可以通过USB接口传输更高速更大量的数据。还论述了USB2.0的改进和优点。     关键词: 通用串行总线(USB)  设备驱动程序  WDM     通用串行总线USB (Universal Serial Bus)是Intel、Microsoft等大厂商为解决计算机外设种类的日益增加与有限的主板插槽和端口之间的矛盾而于1995年提出制定的。它是一种用于将适用USB的外围设备连接到主机的外部总线结
[应用]
STM32步进电机加减速
电机的加减速为了,电机启动停止更加稳定,提高匀速速度。S曲线算法优化电机的运行。 整个过程就是,模仿S曲线设置电机的频率 程序采用,通过s_curve()函数映射一张小于1500个点的表, 输出一次pwm方波,中断一次,计数器step计数一次 ,TIM_SetAutoreload()重新设置arr频率。 S曲线函数,加速和减速同理,返回值是表的个数 uint16_t _stepmotor_calc(uint16_t min, uint16_t max, float rate, float val ) { uint16_t i; float Ainf_v; uint16_t AInf_t;
[单片机]
STM32之独立看门狗的那些事
为什么MCU会具有看门狗呢?带着这个疑问,来了解看门狗的那些事。就连51单片机都带有看门狗,说明这条狗对我们来说有着 不一般的意义。看门狗的目的一句话说:防止程序乱跑。MCU在不同的环境下程序的运行会受到干扰,比如陷入死循环怎么办? 这就是养狗的好处呀,就算你没养过狗,你也看过猪跑吧。 先看固件库的几个函数 void IWDG_WriteAccessCmd(uint16_t IWDG_WriteAccess); void IWDG_SetPrescaler(uint8_t IWDG_Prescaler); void IWDG_SetReload(uint16_t Reload); void IWDG_ReloadC
[单片机]
STM32延时注意事项
一般stm32延时函数大家都这样用,但是要注意nms的范围, SysTick- LOAD为24位寄存器,所以,最大延时为:nms =0xffffff*8*1000/SYSCLK SYSCLK的单位为Hz , nms的单位为 ms /对72M条件下,nms =1864。所以平时在写程序时,延时1000即1秒时对的,反而延时2000即2秒就不对。 void delay_ms(u16 nms) { u32 temp; SysTick- LOAD=(u32)nms*fac_ms; //时间加载(SysTick- LOAD为24bit) SysTick- VAL =0x0
[单片机]
基于STM32的FreeRTOS开发(1)----FreeRTOS简介
为什么使用freertos FreeRTOS 是一个免费和开源的实时操作系统,它主要用于嵌入式系统。它非常轻量级,可以在很小的硬件资源上运行,因此非常适合在限制硬件资源的嵌入式系统中使用。 FreeRTOS提供了一组简单的任务管理功能,可以让您在嵌入式系统中实现多任务环境,这对于涉及多个独立功能的系统是非常重要的。它还提供了一些高级功能,如事件组、信号量、邮箱等,可用于实现任务之间的同步和通信。 FreeRTOS还提供了许多可移植性,可以在各种不同的硬件平台上运行,并且有大量的文档和示例代码可以帮助您快速上手。 总之,FreeRTOS是一个非常受欢迎的嵌入式实时操作系统,因为它简单易用,资源占用小,功能丰富,可移植性好,对于嵌入式
[单片机]
基于<font color='red'>STM32</font>的FreeRTOS开发(1)----FreeRTOS简介
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved