3. 嵌入式开发环境准备
好,咱们正式开始动手了。这一章我带你把开发环境搭起来,顺便把FreeRTOS和串口通信这两个核心知识点啃透。别小看这一步,环境没配好,后面全是坑。我自己就吃过这个亏——第一次用STM32CubeIDE时,固件包下载到一半断了,结果折腾了一下午才发现是网络代理的问题。
3.1 STM32CubeIDE安装与配置
STM32CubeIDE是ST官方推出的免费IDE,基于Eclipse,集成了CubeMX的图形化配置功能。说白了,你画个引脚图、配个时钟树,它直接帮你生成初始化代码。我个人习惯用它做原型开发,效率确实高。
3.1.1 下载与安装
- 去ST官网搜"STM32CubeIDE",下载对应操作系统的安装包(Windows/Linux/macOS都支持)。
- 安装时注意:路径不要带中文或空格。我见过有人装在"Program Files (x86)"下,结果编译时莫名其妙报错。
- 安装完成后,第一次启动会提示下载固件包。这一步很关键——选你用的芯片型号,比如STM32F407或STM32H743。网络慢的话,可以手动下载离线包再导入。
3.1.2 创建第一个工程
打开IDE,点击"File → New → STM32 Project"。选芯片型号时,我建议你直接用"MCU Selector"搜索,比如输入"STM32F103C8T6"。然后配置项目名和存储路径——记住,路径里别有中文。
进入CubeMX图形界面后,你会看到芯片引脚图。这里要做三件事:
- 配时钟:在"Clock Configuration"标签页里,把HCLK调到最大频率(比如72MHz)。
- 配串口:在"Pinout & Configuration"里,找到USART1,模式选"Asynchronous",波特率设115200。
- 生成代码:点"Project Manager",Code Generator选"Copy only the necessary library files",然后点"GENERATE CODE"。
/* USER CODE BEGIN */和/* USER CODE END */之间写自己的代码——这样下次重新生成时,你的代码不会被覆盖。
3.2 FreeRTOS基础
FreeRTOS是个轻量级实时操作系统,专门跑在MCU上。你想想看,如果不用RTOS,你只能用超级循环(while(1))轮询所有任务——按键、显示、通信全挤在一起,稍微复杂点就乱套。FreeRTOS帮你把任务拆开,每个任务独立运行,优先级高的先跑。
3.2.1 任务创建与调度
在STM32CubeIDE里集成FreeRTOS很简单:CubeMX里勾选"Middleware → FREERTOS",选"CMSIS_V2"接口。然后添加任务:
// 任务函数定义
void vTask1(void *argument) {
for(;;) {
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
vTaskDelay(pdMS_TO_TICKS(500)); // 延时500ms
}
}
// 在main.c中创建任务
osThreadNew(vTask1, NULL, &task1_attributes);
嗯,这里要注意:vTaskDelay的单位是系统时钟节拍(tick),用pdMS_TO_TICKS把毫秒转成tick。我刚开始写时直接传了500,结果LED闪得飞快——因为tick默认是1ms,500个tick才500ms,但我想的是500个tick?不对,其实pdMS_TO_TICKS(500)才是正确的写法。
3.2.2 任务优先级与堆栈
每个任务都有优先级,数值越大优先级越高。但别把所有任务都设成最高优先级——那样低优先级的任务永远得不到CPU时间。我踩过的坑是:把通信任务优先级设得比显示任务高,结果显示任务卡死,屏幕一直不刷新。
| 任务名称 | 优先级 | 堆栈大小(字) | 说明 |
|---|---|---|---|
| vTaskLED | 1 | 128 | LED闪烁,最低优先级 |
| vTaskUART | 2 | 256 | 串口收发,中等优先级 |
| vTaskControl | 3 | 512 | 控制逻辑,最高优先级 |
uxTaskGetStackHighWaterMark()检查实际使用量。
3.3 串口通信协议基础
串口是嵌入式设备最常用的通信方式。ROS2和嵌入式设备之间,很多时候就是通过串口传数据。说白了,就是一根TX、一根RX、一根GND,波特率对上就能聊。
3.3.1 串口参数配置
在CubeMX里配串口时,这几个参数必须一致:
- 波特率:常用115200、9600。ROS2端和MCU端必须一样。
- 数据位:通常8位。
- 停止位:1位。
- 校验位:无(None)。
为什么是115200?因为它在16MHz晶振下误差最小。我试过用250000波特率,结果丢包率高达10%——后来查手册才发现,那个波特率对时钟精度要求太高。
3.3.2 收发代码示例
用HAL库发送数据很简单:
// 发送字符串
char msg[] = "Hello ROS2!\r\n";
HAL_UART_Transmit(&huart1, (uint8_t*)msg, strlen(msg), 1000);
接收数据稍微复杂点,因为要处理不定长数据。我习惯用中断方式:
// 定义接收缓冲区
uint8_t rx_buffer[64];
uint8_t rx_len = 0;
// 启动中断接收(接收1个字节)
HAL_UART_Receive_IT(&huart1, rx_buffer, 1);
// 中断回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if(huart->Instance == USART1) {
// 收到一个字节,存入缓冲区
rx_len++;
// 继续接收下一个字节
HAL_UART_Receive_IT(&huart1, &rx_buffer[rx_len], 1);
}
}
printf,结果串口中断被阻塞,导致后续数据丢失。正确的做法是:在回调里只存数据,用信号量或消息队列通知任务去处理。
3.3.3 自定义通信协议
裸发数据容易乱,所以需要定义协议帧。我常用的格式是:
帧头(0xAA) + 数据长度(1字节) + 数据内容(N字节) + 校验和(1字节)
接收端解析时,先找帧头,再读长度,然后收够N个字节,最后校验。如果校验失败,直接丢弃整帧。这样做的好处是:即使中间丢了一个字节,下一帧也能重新同步。
3.4 本章知识体系
下面这张图把本章的核心逻辑串起来了。你一看就明白:环境是基础,FreeRTOS是骨架,串口是血管——三者缺一不可。
嗯,这一章的内容就这些。你先把STM32CubeIDE装好,跑通一个LED闪烁的FreeRTOS任务,再用串口打印"Hello ROS2"。这三步走完,后面ROS2和嵌入式通信的实战就水到渠成了。