STM32
直播中

李波

7年用户 1411经验值
私信 关注
[问答]

如何去实现USB模拟串口收发的功能呢

如何去实现USB模拟串口收发的功能呢?

如何去使用STM32CubeMX生成HAL库的代码呢?

回帖(2)

张鹏

2021-12-6 09:16:52
      想要实现的功能是,USB模拟串口收发数据。串口助手发送数据至MCU,MCU接收后返回给串口助手。
      当初是想用标准库做这个功能的。但是因为后来了解到STM32CubeMX这个软件,在尝试之后实在是感觉,太方便了。所以,并没有使用标准库,而是直接用STM32CubeMX生成HAL库的代码用了。
(1)先点New Project,然后输入自己的MCU型号





(2)配置引脚与外设
这里我用的是ST-LINK进行DeBug,Tim5提供系统延时节拍,PE5与PB5点亮LED。而SysTick,用在FreeRtos提供系统节拍。










(3)时钟树配置





(4)配置外设
这个页面可以对外设进行功能的设置,比如GPIO的输出类型或者引脚初始电平。在这里主要设置FreeRTOS,创建2个初始任务。其他的比如USB,默认就可以使用了。





(5)Poject Settings
这里注意两个地方,我使用的是MDK。所以IDE选项选择的是MDK-ARM V5。CodeGenerator选项卡下,将Generated files下的第一个选项打上勾,这样就会启用模块化编程,不同的外设封装不同的.c   .h文件。至于Project Name跟Project Location,
自行设置便可。










然后,点击STM32CubeMX主界面的Project,Generate Code。就能在我们指定的文件夹内直接生成工程文件。生成之后软件提示你打开项目,点击打开后,工程内分组如图:






因为使用了RTOS,所以编程主要围绕两个文件,“u***d_cdc_if.c”以及“freertos.c”
“u***d_cdc_if.h”添加一个USB管理结构体的定义,并将“u***d_cdc_if.c”中两个定义移到“u***d_cdc_if.h”



/* USER CODE BEGIN PRIVATE_DEFINES */
/* Define size for the receive and transmit buffer over CDC */
/* It's up to user to redefine and/or remove those define */
#define APP_RX_DATA_SIZE  1000
#define APP_TX_DATA_SIZE  1000
         
typedef struct  
{  
   uint8_t   OutFlag;
   uint8_t   EFlag[2];
   uint8_t   SFlag;  
   uint16_t  ReLen;        
}USB_Dev;          
         
/* USER CODE END PRIVATE_DEFINES */
/* USER CODE BEGIN INCLUDE */
“u***d_cdc_if.c”声明USB管理结构体变量并赋值,且修改“CDC_Receive_FS”函数。


/* Private typedef -----------------------------------------------------------*/
USB_Dev  USB_S =
{
        0,
        {0x0D,0x0A},
        0,
        0,
};
static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)
{
        HAL_GPIO_TogglePin(GPIOE,GPIO_PIN_5); //开启接收指示灯
       
        //将已接收数据长度赋值给USB_S.ReLen
        USB_S.ReLen += *Len;
       
        //判断是否有结束标志以及接收数据长度是否达到UserRxBufferFS长度上限
        if( USB_S.ReLen             UserRxBufferFS[USB_S.ReLen-2] != USB_S.EFlag[0] &&
                UserRxBufferFS[USB_S.ReLen-1] != USB_S.EFlag[1]
              )
                {
                  //设置下一次接收数据的位置
                USBD_CDC_SetRxBuffer(&hU***DeviceFS, UserRxBufferFS + USB_S.ReLen);
                USBD_CDC_ReceivePacket(&hU***DeviceFS);   //准备接收数据
                }
                else  //长度达到,或者检测到标志位,触发数据输出
                {
                  USB_S.OutFlag = 1;
                }
               
        return (USBD_OK);
}

“freertos.c”中,添加头文件“u***d_cdc_if.h”并在对应的任务中添加对应功能:


/* LED_Toggle function */
void LED_Toggle(void const * argument)
{

  for(;;)
  {
     HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_5);
     osDelay(500);
  }

}

extern USB_Dev  USB_S;
extern USBD_HandleTypeDef hU***DeviceFS;
extern uint8_t UserRxBufferFS[APP_RX_DATA_SIZE];
/* USB_SendMess function */
void USB_SendMess(void const * argument)
{
   uint16_t timeout = 0xffff;
   uint8_t temp;
       
    for(;;)
   {
         timeout = 0xffff;
               
         if(USB_S.OutFlag)
         {
            temp = !(USB_S.ReLen%64);  //判断长度是否为64整数倍
            while( CDC_Transmit_FS(UserRxBufferFS, USB_S.ReLen - temp) != USBD_OK && timeout--);
                         
          if(temp)  //当发送数据为64整数倍时,无法发送成功,故分成2次发送
            {
               while( CDC_Transmit_FS(UserRxBufferFS + USB_S.ReLen -1, temp) != USBD_OK && timeout--);
            }
                               
            USB_S.ReLen = 0;
            USBD_CDC_SetRxBuffer(&hU***DeviceFS, UserRxBufferFS);
            USBD_CDC_ReceivePacket(&hU***DeviceFS);
            USB_S.OutFlag = 0;       
            HAL_GPIO_WritePin(GPIOE, GPIO_PIN_5, GPIO_PIN_SET); //接收指示灯关闭                               
         }
    osDelay(1);
  }
}


启动文件:需将堆的容量调大,因为USB缓存使用的是堆空间。我将它调为4K:
Heap_Size      EQU     0x1000  
至此,代码创建并修改完毕。

下载代码到板子,下载完毕后关闭重上电,然后打开串口助手对应的串口。波特率之类全都不需设置。
发送一篇长文章,MCU接收后返回给串口助手
现象如图:








写在最后

(1)MCU端接收数据问题。
        USB传输一次最多64字节。所以,如果想一次传输大量数据给MCU。需制定协议。如图的串口助手,在发送完毕数据之后,会补上2字节结束标志:0x0D,0x0A。MCU端可根据结束标志判断数据是否接收完毕。需注意的是,不同的串口助手协议不一定相同。有的是根据数据包长度判断是否接收完毕。
         




(2)连续输出的问题。
        1、需要轮询发送函数返回值是否是“USBD_OK”
        2、输出字符串,如果不去掉字符串最后的结束符。某些上位机软件,只能显示1次发送的数据,第二次
             发送的数据不能正常显示。(本博客使用的串口助手就是如此。。。。)  ,但使用过另外一款串口
             助手,并未发现此情况。   
        3、轮询返回值有可能导致程序卡死,因为主机若是没有接收MCU发送的数据,MCU会一直轮询
             直到主机接收完数据为止,解决办法是加个timeout--到while里面,到时间跳出循环。
(3)WINDOWS下不能识别的串口有黄色感叹号。
        这个有可能是堆设置不够大。我在F4下遇到了这问题,将堆空间“Heap_Size      EQU     0x200”
        设置为“Heap_Size      EQU     0x1000”,黄色感叹号消失
  (4)通讯速度问题
       参考“http://bbs.21ic.com/icview-811704-1-1.html”,附件中有测试速度的软件。
              串口助手当通讯速度40KB/S左右,接收不到数据。当然,如果是连续发送'0',串口助手上窗口不
       显示字符,是可以接收到数据的,但当速度超过500KB/S,依旧不能接收数据。所以,网上很多反映
       VCP速度只有几十KB/S的,估计是上位机软件问题。
              使用附件中的软件,可以成功测出速度。STM32CubeMX生成的程序,经测试发送到主机的速度可以
       达到1000KB/S以上。
      




(5)“CDC_Receive_FS”函数的解析
      这个函数的作用是两个,一是设置下一次接收数据的Buff,二是处理接收端点。
      这个函数是在MCU接收完数据之后才调用的,而不是进入这个函数才开始接收数据。比方MCU接收到64byte的数据,
      接收完成后进入这个函数,设置下一次接收数据的Buff。然后调用“USBD_CDC_ReceivePacket”处理接收端点。
      如果不调用“USBD_CDC_ReceivePacket”,是无法进行下一次的接收的,但发送是可以的。
(6)CDC_Transmit_FS 发送64整数倍字节数的数据出错
      当我调用“CDC_Transmit_FS”发送64字节的数据时,串口助手并不能接收到数据。

      总的来说,USB的bulk协议以发送小于64字节或者是字长为0的数据包作为结束动作。
      发送64字节的包的时候,只需要末尾再发送个字长为0的包即可。

举报

陆轶文

2021-12-6 09:16:52
      想要实现的功能是,USB模拟串口收发数据。串口助手发送数据至MCU,MCU接收后返回给串口助手。
      当初是想用标准库做这个功能的。但是因为后来了解到STM32CubeMX这个软件,在尝试之后实在是感觉,太方便了。所以,并没有使用标准库,而是直接用STM32CubeMX生成HAL库的代码用了。
(1)先点New Project,然后输入自己的MCU型号





(2)配置引脚与外设
这里我用的是ST-LINK进行DeBug,Tim5提供系统延时节拍,PE5与PB5点亮LED。而SysTick,用在FreeRtos提供系统节拍。










(3)时钟树配置





(4)配置外设
这个页面可以对外设进行功能的设置,比如GPIO的输出类型或者引脚初始电平。在这里主要设置FreeRTOS,创建2个初始任务。其他的比如USB,默认就可以使用了。





(5)Poject Settings
这里注意两个地方,我使用的是MDK。所以IDE选项选择的是MDK-ARM V5。CodeGenerator选项卡下,将Generated files下的第一个选项打上勾,这样就会启用模块化编程,不同的外设封装不同的.c   .h文件。至于Project Name跟Project Location,
自行设置便可。










然后,点击STM32CubeMX主界面的Project,Generate Code。就能在我们指定的文件夹内直接生成工程文件。生成之后软件提示你打开项目,点击打开后,工程内分组如图:






因为使用了RTOS,所以编程主要围绕两个文件,“u***d_cdc_if.c”以及“freertos.c”
“u***d_cdc_if.h”添加一个USB管理结构体的定义,并将“u***d_cdc_if.c”中两个定义移到“u***d_cdc_if.h”



/* USER CODE BEGIN PRIVATE_DEFINES */
/* Define size for the receive and transmit buffer over CDC */
/* It's up to user to redefine and/or remove those define */
#define APP_RX_DATA_SIZE  1000
#define APP_TX_DATA_SIZE  1000
         
typedef struct  
{  
   uint8_t   OutFlag;
   uint8_t   EFlag[2];
   uint8_t   SFlag;  
   uint16_t  ReLen;        
}USB_Dev;          
         
/* USER CODE END PRIVATE_DEFINES */
/* USER CODE BEGIN INCLUDE */
“u***d_cdc_if.c”声明USB管理结构体变量并赋值,且修改“CDC_Receive_FS”函数。


/* Private typedef -----------------------------------------------------------*/
USB_Dev  USB_S =
{
        0,
        {0x0D,0x0A},
        0,
        0,
};
static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)
{
        HAL_GPIO_TogglePin(GPIOE,GPIO_PIN_5); //开启接收指示灯
       
        //将已接收数据长度赋值给USB_S.ReLen
        USB_S.ReLen += *Len;
       
        //判断是否有结束标志以及接收数据长度是否达到UserRxBufferFS长度上限
        if( USB_S.ReLen             UserRxBufferFS[USB_S.ReLen-2] != USB_S.EFlag[0] &&
                UserRxBufferFS[USB_S.ReLen-1] != USB_S.EFlag[1]
              )
                {
                  //设置下一次接收数据的位置
                USBD_CDC_SetRxBuffer(&hU***DeviceFS, UserRxBufferFS + USB_S.ReLen);
                USBD_CDC_ReceivePacket(&hU***DeviceFS);   //准备接收数据
                }
                else  //长度达到,或者检测到标志位,触发数据输出
                {
                  USB_S.OutFlag = 1;
                }
               
        return (USBD_OK);
}

“freertos.c”中,添加头文件“u***d_cdc_if.h”并在对应的任务中添加对应功能:


/* LED_Toggle function */
void LED_Toggle(void const * argument)
{

  for(;;)
  {
     HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_5);
     osDelay(500);
  }

}

extern USB_Dev  USB_S;
extern USBD_HandleTypeDef hU***DeviceFS;
extern uint8_t UserRxBufferFS[APP_RX_DATA_SIZE];
/* USB_SendMess function */
void USB_SendMess(void const * argument)
{
   uint16_t timeout = 0xffff;
   uint8_t temp;
       
    for(;;)
   {
         timeout = 0xffff;
               
         if(USB_S.OutFlag)
         {
            temp = !(USB_S.ReLen%64);  //判断长度是否为64整数倍
            while( CDC_Transmit_FS(UserRxBufferFS, USB_S.ReLen - temp) != USBD_OK && timeout--);
                         
          if(temp)  //当发送数据为64整数倍时,无法发送成功,故分成2次发送
            {
               while( CDC_Transmit_FS(UserRxBufferFS + USB_S.ReLen -1, temp) != USBD_OK && timeout--);
            }
                               
            USB_S.ReLen = 0;
            USBD_CDC_SetRxBuffer(&hU***DeviceFS, UserRxBufferFS);
            USBD_CDC_ReceivePacket(&hU***DeviceFS);
            USB_S.OutFlag = 0;       
            HAL_GPIO_WritePin(GPIOE, GPIO_PIN_5, GPIO_PIN_SET); //接收指示灯关闭                               
         }
    osDelay(1);
  }
}


启动文件:需将堆的容量调大,因为USB缓存使用的是堆空间。我将它调为4K:
Heap_Size      EQU     0x1000  
至此,代码创建并修改完毕。

下载代码到板子,下载完毕后关闭重上电,然后打开串口助手对应的串口。波特率之类全都不需设置。
发送一篇长文章,MCU接收后返回给串口助手
现象如图:








写在最后

(1)MCU端接收数据问题。
        USB传输一次最多64字节。所以,如果想一次传输大量数据给MCU。需制定协议。如图的串口助手,在发送完毕数据之后,会补上2字节结束标志:0x0D,0x0A。MCU端可根据结束标志判断数据是否接收完毕。需注意的是,不同的串口助手协议不一定相同。有的是根据数据包长度判断是否接收完毕。
         




(2)连续输出的问题。
        1、需要轮询发送函数返回值是否是“USBD_OK”
        2、输出字符串,如果不去掉字符串最后的结束符。某些上位机软件,只能显示1次发送的数据,第二次
             发送的数据不能正常显示。(本博客使用的串口助手就是如此。。。。)  ,但使用过另外一款串口
             助手,并未发现此情况。   
        3、轮询返回值有可能导致程序卡死,因为主机若是没有接收MCU发送的数据,MCU会一直轮询
             直到主机接收完数据为止,解决办法是加个timeout--到while里面,到时间跳出循环。
(3)WINDOWS下不能识别的串口有黄色感叹号。
        这个有可能是堆设置不够大。我在F4下遇到了这问题,将堆空间“Heap_Size      EQU     0x200”
        设置为“Heap_Size      EQU     0x1000”,黄色感叹号消失
  (4)通讯速度问题
       参考“http://bbs.21ic.com/icview-811704-1-1.html”,附件中有测试速度的软件。
              串口助手当通讯速度40KB/S左右,接收不到数据。当然,如果是连续发送'0',串口助手上窗口不
       显示字符,是可以接收到数据的,但当速度超过500KB/S,依旧不能接收数据。所以,网上很多反映
       VCP速度只有几十KB/S的,估计是上位机软件问题。
              使用附件中的软件,可以成功测出速度。STM32CubeMX生成的程序,经测试发送到主机的速度可以
       达到1000KB/S以上。
      




(5)“CDC_Receive_FS”函数的解析
      这个函数的作用是两个,一是设置下一次接收数据的Buff,二是处理接收端点。
      这个函数是在MCU接收完数据之后才调用的,而不是进入这个函数才开始接收数据。比方MCU接收到64byte的数据,
      接收完成后进入这个函数,设置下一次接收数据的Buff。然后调用“USBD_CDC_ReceivePacket”处理接收端点。
      如果不调用“USBD_CDC_ReceivePacket”,是无法进行下一次的接收的,但发送是可以的。
(6)CDC_Transmit_FS 发送64整数倍字节数的数据出错
      当我调用“CDC_Transmit_FS”发送64字节的数据时,串口助手并不能接收到数据。

      总的来说,USB的bulk协议以发送小于64字节或者是字长为0的数据包作为结束动作。
      发送64字节的包的时候,只需要末尾再发送个字长为0的包即可。

举报

更多回帖

发帖
×
20
完善资料,
赚取积分