为了让RobomasterC板(这块板用的是STM32F407IGHX的芯片)能与上位机进行通讯。我最近翻了不少博客和CSDN文章,看到了很多文章存在一些问题,经过了一下午试错,我成功实现了STM32F407IGHX利用STM32CubeIDE进行配置并然后用HAL库进行编程,与安装有ROS的Ubuntu进行虚拟串口通信。
在翻看博客的时候我发现,RM以及上下位机通信资料并不多,而且很多已有资料都只讲述了实现原理,却没有讲如何具体一步步实现某个功能,这就导致初学者可能在翻看过程中,越看越懵,反而写不出一份能用的代码。
所以这篇文章会尽可能详细的讲怎么实现串口通信,而尽量少讲其原理,由于很多文章都已经详尽的写出了串口通信的原理了,所以我就不在赘述原理而着重于实现过程。
此外,我也会把一些小问题和建议写出来,以便一篇文章就解决所有可能存在的问题。
一、概述
1、STM32端(所谓的下位机):这边采用的是通过有图形化的STM32CubeIDE配置工程,配置好USB-CDC创建一个虚拟串口,与上位机通信。
2、Ubuntu端(所谓的上位机):上位机是版本20.04的ubuntu,安装有版本为noetic的ROS,通过建立一个ROS节点来打开串口并建立通信。
二、STM32端具体实现过程
思路:利用STM32CubeIDE配置好USB-CDC,接着修改对应的头文件,自定义所需的函数。
1、配置过程
1)先配置时钟RCC,设置高速时钟High Speed Clock为内部时钟(Crystal/Ceramic Resonator),另一个暂时用不到所以不设置。
![](https://i-blog.csdnimg.cn/blog_migrate/282d88eef5d210ae2b210129002b5fd2.png)
2)配置下载与调试(必须设置,否则会锁芯片,到时候还需要通过BOOT重启,比较麻烦)
设置为Serial Wire,时钟为SysTick(当然看你到底有什么,如果你拥有的是ST-LINK,那么可以这样设置)
![](https://i-blog.csdnimg.cn/blog_migrate/292b78ddc8a5aab8fc09a49abf81958d.png)
3)设置USB模式,打开Connectivity,选择USB-OYG-FS(快速),选择Mode的Device_only(从机模式)。然后点开左下方的NVIC Settings,勾选Enabled,从而能够开启中断。
![](https://i-blog.csdnimg.cn/blog_migrate/78f5db76cba1130d2c6657e591c54221.png)
![](https://i-blog.csdnimg.cn/blog_migrate/765e4a38a0b1522dec0ce2ab1c85a26a.png)
![](https://i-blog.csdnimg.cn/blog_migrate/645da86f6bd3e7d0dcf062b5123c09db.png)
备注:还要返回到NVIC中,设置USB中断的优先级,这里设置个4就行(毕竟没有启动其他外设,所以中断就不需要太严谨)、
4)打开MiddleWare,设置USB的具体工作方式,选择Class For FS IP的Communication Device Class,即VCP(虚拟串口),其余设置保持默认即可,不需要额外修改。
![](https://i-blog.csdnimg.cn/blog_migrate/e157e6e91d4b26915a981f369eb3fa78.png)
5)时钟树设置(时钟树的设置,需要查阅所使用开发板的具体原理图)
例如,RobomasterC板原理图里是如此说明的,所以Input frequency要设置成12MHz。此外,下方画红线部分是USB的时钟,USB的时钟需要设置成48MHz才能工作,其余部分看自己的需求。
![](https://i-blog.csdnimg.cn/blog_migrate/01d8a0561540380dbc1e7c905debccfe.png)
![](https://i-blog.csdnimg.cn/blog_migrate/d148113e8fed99e62feedf3bc4481fbe.png)
6)堆栈设置,堆栈的大小需要足够大,才能满足USB初始化的需求,此处设置Heap Size为0X600即可解决初始化失败的问题,另一个不用改。
![](https://i-blog.csdnimg.cn/blog_migrate/2b4b8bdcf969a5742e46fe0baa82fabf.png)
7)到此,所有的初始化已经结束了,只需要Ctrl+s,保存并生成代码即可,下方两个选项均选择Yes,即可生成STM32CubeIDE工程
![](https://i-blog.csdnimg.cn/blog_migrate/9e6acbea429855e5d2620e8039d04ec3.png)
![](https://i-blog.csdnimg.cn/blog_migrate/dafd0464071b4863e243d61f25fcd064.png)
2、代码的修改
这里要先打开工程里的USB_DEVICE中的App的usbd_cdc_if.c,重构官方给出的代码,具体内容如下
![](https://i-blog.csdnimg.cn/blog_migrate/841e7fa8eeaa2b02b7c8df6a31f755d3.png)
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : usbd_cdc_if.c
* @version : v1.0_Cube
* @brief : Usb device for Virtual Com Port.
******************************************************************************
* @attention
*
* Copyright (c) 2023 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "usbd_cdc_if.h"
/* USER CODE BEGIN INCLUDE */
/* USER CODE END INCLUDE */
/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* Private variables ---------------------------------------------------------*/
/* USER CODE END PV */
/** @addtogroup STM32_USB_OTG_DEVICE_LIBRARY
* @brief Usb device library.
* @{
*/
/** @addtogroup USBD_CDC_IF
* @{
*/
/** @defgroup USBD_CDC_IF_Private_TypesDefinitions USBD_CDC_IF_Private_TypesDefinitions
* @brief Private types.
* @{
*/
/* USER CODE BEGIN PRIVATE_TYPES */
/* USER CODE END PRIVATE_TYPES */
/**
* @}
*/
/** @defgroup USBD_CDC_IF_Private_Defines USBD_CDC_IF_Private_Defines
* @brief Private defines.
* @{
*/
/* USER CODE BEGIN PRIVATE_DEFINES */
/* USER CODE END PRIVATE_DEFINES */
/**
* @}
*/
/** @defgroup USBD_CDC_IF_Private_Macros USBD_CDC_IF_Private_Macros
* @brief Private macros.
* @{
*/
/* USER CODE BEGIN PRIVATE_MACRO */
/* USER CODE END PRIVATE_MACRO */
/**
* @}
*/
/** @defgroup USBD_CDC_IF_Private_Variables USBD_CDC_IF_Private_Variables
* @brief Private variables.
* @{
*/
/* Create buffer for reception and transmission */
/* It's up to user to redefine and/or remove those define */
/** Received data over USB are stored in this buffer */
uint8_t UserRxBufferFS[APP_RX_DATA_SIZE];
/** Data to send over USB CDC are stored in this buffer */
uint8_t UserTxBufferFS[APP_TX_DATA_SIZE];
/* USER CODE BEGIN PRIVATE_VARIABLES */
/* USER CODE END PRIVATE_VARIABLES */
/**
* @}
*/
/** @defgroup USBD_CDC_IF_Exported_Variables USBD_CDC_IF_Exported_Variables
* @brief Public variables.
* @{
*/
extern USBD_HandleTypeDef hUsbDeviceFS;
/* USER CODE BEGIN EXPORTED_VARIABLES */
/* USER CODE END EXPORTED_VARIABLES */
/**
* @}
*/
/** @defgroup USBD_CDC_IF_Private_FunctionPrototypes USBD_CDC_IF_Private_FunctionPrototypes
* @brief Private functions declaration.
* @{
*/
static int8_t CDC_Init_FS(void);
static int8_t CDC_DeInit_FS(void);
static int8_t CDC_Control_FS(uint8_t cmd, uint8_t* pbuf, uint16_t length);
static int8_t CDC_Receive_FS(uint8_t* pbuf, uint32_t *Len);
static int8_t CDC_TransmitCplt_FS(uint8_t *pbuf, uint32_t *Len, uint8_t epnum);
/* USER CODE BEGIN PRIVATE_FUNCTIONS_DECLARATION */
/* USER CODE END PRIVATE_FUNCTIONS_DECLARATION */
/**
* @}
*/
USBD_CDC_ItfTypeDef USBD_Interface_fops_FS =
{
CDC_Init_FS,
CDC_DeInit_FS,
CDC_Control_FS,
CDC_Receive_FS,
CDC_TransmitCplt_FS
};
/* Private functions ---------------------------------------------------------*/
/**
* @brief Initializes the CDC media low layer over the FS USB IP
* @retval USBD_OK if all operations are OK else USBD_FAIL
*/
static int8_t CDC_Init_FS(void)
{
/* USER CODE BEGIN 3 */
/* Set Application Buffers */
USBD_CDC_SetTxBuffer(&hUsbDeviceFS, UserTxBufferFS, 0);
USBD_CDC_SetRxBuffer(&hUsbDeviceFS, UserRxBufferFS);
return (USBD_OK);
/* USER CODE END 3 */
}
/**
* @brief DeInitializes the CDC media low layer
* @retval USBD_OK if all operations are OK else USBD_FAIL
*/
static int8_t CDC_DeInit_FS(void)
{
/* USER CODE BEGIN 4 */
return (USBD_OK);
/* USER CODE END 4 */
}
/**
* @brief Manage the CDC class requests
* @param cmd: Command code
* @param pbuf: Buffer containing command data (request parameters)
* @param length: Number of data to be sent (in bytes)
* @retval Result of the operation: USBD_OK if all operations are OK else USBD_FAIL
*/
static int8_t CDC_Control_FS(uint8_t cmd, uint8_t* pbuf, uint16_t length)
{
/* USER CODE BEGIN 5 */
switch(cmd)
{
case CDC_SEND_ENCAPSULATED_COMMAND:
break;
case CDC_GET_ENCAPSULATED_RESPONSE:
break;
case CDC_SET_COMM_FEATURE:
break;
case CDC_GET_COMM_FEATURE:
break;
case CDC_CLEAR_COMM_FEATURE:
break;
/*******************************************************************************/
/* Line Coding Structure */
/*-----------------------------------------------------------------------------*/
/* Offset | Field | Size | Value | Description */
/* 0 | dwDTERate | 4 | Number |Data terminal rate, in bits per second*/
/* 4 | bCharFormat | 1 | Number | Stop bits */
/* 0 - 1 Stop bit */
/* 1 - 1.5 Stop bits */
/* 2 - 2 Stop bits */
/* 5 | bParityType | 1 | Number | Parity */
/* 0 - None */
/* 1 - Odd */
/* 2 - Even */
/* 3 - Mark */
/* 4 - Space */
/* 6 | bDataBits | 1 | Number Data bits (5, 6, 7, 8 or 16). */
/*******************************************************************************/
case CDC_SET_LINE_CODING:
break;
case CDC_GET_LINE_CODING:
break;
case CDC_SET_CONTROL_LINE_STATE:
break;
case CDC_SEND_BREAK:
break;
default:
break;
}
return (USBD_OK);
/* USER CODE END 5 */
}
/**
* @brief Data received over USB OUT endpoint are sent over CDC interface
* through this function.
*
* @note
* This function will issue a NAK packet on any OUT packet received on
* USB endpoint until exiting this function. If you exit this function
* before transfer is complete on CDC interface (ie. using DMA controller)
* it will result in receiving more data while previous ones are still
* not sent.
*
* @param Buf: Buffer of data to be received
* @param Len: Number of data received (in bytes)
* @retval Result of the operation: USBD_OK if all operations are OK else USBD_FAIL
*/
static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)
{
/* USER CODE BEGIN 6 */
USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);
USBD_CDC_ReceivePacket(&hUsbDeviceFS);
return (USBD_OK);
/* USER CODE END 6 */
}
/**
* @brief CDC_Transmit_FS
* Data to send over USB IN endpoint are sent over CDC interface
* through this function.
* @note
*
*
* @param Buf: Buffer of data to be sent
* @param Len: Number of data to be sent (in bytes)
* @retval USBD_OK if all operations are OK else USBD_FAIL or USBD_BUSY
*/
uint8_t CDC_Transmit_FS(uint8_t* Buf, uint16_t Len)
{
uint8_t result = USBD_OK;
/* USER CODE BEGIN 7 */
USBD_CDC_HandleTypeDef *hcdc = (USBD_CDC_HandleTypeDef*)hUsbDeviceFS.pClassData;
if (hcdc->TxState != 0){
return USBD_BUSY;
}
USBD_CDC_SetTxBuffer(&hUsbDeviceFS, Buf, Len);
result = USBD_CDC_TransmitPacket(&hUsbDeviceFS);
/* USER CODE END 7 */
return result;
}
/**
* @brief CDC_TransmitCplt_FS
* Data transmitted callback
*
* @note
* This function is IN transfer complete callback used to inform user that
* the submitted Data is successfully sent over USB.
*
* @param Buf: Buffer of data to be received
* @param Len: Number of data received (in bytes)
* @retval Result of the operation: USBD_OK if all operations are OK else USBD_FAIL
*/
static int8_t CDC_TransmitCplt_FS(uint8_t *Buf, uint32_t *Len, uint8_t epnum)
{
uint8_t result = USBD_OK;
/* USER CODE BEGIN 13 */
if(flag)
{
CDC_Transmit_FS(UserTxBufferFS, APP_TX_DATA_SIZE);
}
UNUSED(Buf);
UNUSED(Len);
UNUSED(epnum);
/* USER CODE END 13 */
return result;
}
/* USER CODE BEGIN PRIVATE_FUNCTIONS_IMPLEMENTATION */
/* USER CODE END PRIVATE_FUNCTIONS_IMPLEMENTATION */
/**
* @}
*/
/**
* @}
*/
注意,不要轻易重新初始化代码,否则这些对官方代码的修改会被重新覆盖,导致又要再改一遍,最好一次就初始化好。
3、自定义结构体
在这里我不会给出具体的代码,但我会举个例子来说明如何定义所需结构体。
typedef struct ControlData_Chassis _Controldata_Chassis;//这里是定义该结构体的别名
typedef struct ControlData_Chassis{
uint8_t Y_Speed; //纵轴方向速度
uint8_t X_Speed; //横轴方向速度
uint8_t Rotational_Speed; //小车旋转速度
uint8_t Chassis_State; //底盘状态
}*_Controldata_ChassisInfo;//这里定义了该结构体的结构体指针。C语言允许这样的操作!
在实际操作的时候,可以把这种结构体变量的数值放入到指定的数组中(这也就是所谓的打包。而把接收到的数组中的数据按结构体成员形式放入到指定结构体的过程,就称之为解包。),从而实现打包。
此外,可以把结构体定义在头文件中,便于在.c文件里函数的具体实现。
4、自定义解包/打包函数
这里我也只会给出一个例子。
void Pack_Data(_FeedBack* feedback,uint8_t* feedArray)
{ //把数组中信息封入数据包中
feedArray[0] = 0XFF;//这是帧头
feedArray[1] = feedback->Shoot_Mode;
feedArray[2] = feedback->Shoot_Speed;
feedArray[3] = feedback->Armor_Id;
feedArray[4] = (uint8_t)(feedback->HP_Remain);
feedArray[5] = (uint8_t)(feedback->HP_Remain >> 8);
feedArray[6] = 0XAA;//暂时无意义
feedArray[7] = 0XFE;//芝士帧尾
}
实际上,解包函数也是类似上文的操作,只不过是反了过来。
注:1.可以利用与 “ | ” 来将两个数据拼成一个,将拆分的数据合成一个。
2.帧头和帧尾起到了验证的作用,可以用来验证数据完整性。
5、自定义发送/接收函数
int CDC_SendFeed(uint8_t* Fed, uint16_t Len)
{
CDC_Transmit_FS(Fed, Len);
return 0;
}
上文调用了之前修改过的官方代码,这样模块化的代码更容易理解与阅读。
6、备注
1)如果你要定义一个结构体指针并想给它赋值,那么你需要在赋值前给它分配空间,否则这个指针无法进行赋值。
例子:
_FeedBack* ft,fd;
ft=(_FeedBack*)malloc(sizeof(_FeedBack));//这里是结构体的空间分配以及具体赋值
//先更新这点,等我有时间继续更
三、Ubuntu端具体实现过程
思路:利用ROS的serial包来实现串口通信。
//先更新这点,等我有时间继续更
四、产生的结果以及可能存在的报错
//先更新这点,等我有时间继续更
五、备注
1、如果PC无法连接到虚拟串口,并显示“无法获取设备描述符”。
我的解决办法:
1)线路连接不良或者线路有问题,建议重新连接或者换一根线(有一定可能)
2)工程配置错误,时钟树有误(需要根据你的开发板,重新观察时钟树的配置。是否引入了正确的时钟,以及是否配置好了USB时钟(48MHz))
3)
2、Ubuntu无法打开串口
1)连接有问题或者根本没有连接
2)没有权限打开串口(进入管理员模式(终端输入sudo -i),接着编辑/etc/udev/rules.d/70-ttyusb.rules,加上一行KERNEL=="ttyUSB[0-9]*",MODE="0666" 保存退出即可。注意,要看具体需要给什么串口权限,虚拟串口一般叫做/dev/ttyACM0,所以可以写入KERNEL=="ttyACM[0-9]*",MODE="0666" ,而真实串口一般叫/dev/ttyUSB0,可以用前面所说的内容。)
3)STM32CubeIDE报错GDB服务端无法打开。
我在博客里已经给出了详尽的解释
关于STM32CubeIDE无法正常启动GDB服务端的解决办法 - 墨髯 - 博客园 (cnblogs.com)
//先更新这点,等我有时间继续更