案发现场
完成所有的模块外设调试后开始编写业务代码,初次接触 RTOS 下的业务代码编写,出现了一些小 bug。
使用 CubeMX 生成 RTOS 任务(默认生成的工程有四个 DefaultTask 属实难绷):
/* Definitions for LoraTask */
osThreadId_t LoraTaskHandle;
const osThreadAttr_t LoraTask_attributes = {
.name = "LoraTask",
.priority = (osPriority_t) osPriorityNormal,
.stack_size = 256 * 4
};
/* Definitions for SensorTask */
osThreadId_t SensorTaskHandle;
const osThreadAttr_t SensorTask_attributes = {
.name = "SensorTask",
.priority = (osPriority_t) osPriorityLow,
.stack_size = 160 * 4
};
/* Definitions for SensorInitTask */
osThreadId_t SensorInitTaskHandle;
const osThreadAttr_t SensorInitTask_attributes = {
.name = "SensorInitTask",
.priority = (osPriority_t) osPriorityAboveNormal,
.stack_size = 128 * 4
};LoraTask 是主程序,负责通知SensorTask数据采集任务进行工作;SensorTask用阻塞方式读取三个传感器的数据,并将数据通过数据队列发送给LoraTask,LoraTask只有接收到数据才会进行发送;SensorInitTask是系统的初始化任务,包括初始化三个传感器和 LoRa 模块,完成任务后会调用osThreadTerminate删除自身。
开始调试时遇到的问题是系统启动日志乱序,SensorInit还没有跑完就进入了其他两个任务。可以看到我配置的任务优先级中 Init 任务是最高的,当时认为只要配置为更高级的优先级就可以实现让其他任务等待这个任务的完成(任务同步)。回顾 RTOS 的原理:
RTOS 内核会在系统时间片耗尽或任务状态改变时触发调度,如果高优先级任务正在全速运行,那么 CPU 自然就会被抢占;如果在心跳到来时,内核发现正在运行的高优先级任务自己释放了 CPU 的资源,比如正在运行osDelay,那么内核就会去处理其他的任务,尽管其他任务的优先级低于正在运行的任务。所以单纯依赖优先级来控制执行顺序是不可靠且危险的。
理解之后就清楚,要让整个系统在完成SensorInit后再运行,即其他任务等待SensorInit的任务同步,需要增加一个二值信号量sensor_ready_semaphore来处理。这个问题本质上是优先级饥饿和竞态条件的混合,修改后系统不再依赖猜测时间的同步方式,而是变成了时间驱动的方式,既安全又提高了系统响应速度。
void StartSensorInitTask(void *argument)
{
/* USER CODE BEGIN SensorInitTask */
...//Sensor init
osSemaphoreRelease(sensor_ready_semaphoreHandle);
osThreadTerminate(SensorInitTaskHandle);
/* USER CODE END SensorInitTask */
}使用信号量同步任务
CubeMX的bug?
为了同步任务我配置了多个二值信号量
/* Definitions for i2c_transaction_semaphore */
osSemaphoreId_t i2c_transaction_semaphoreHandle;
const osSemaphoreAttr_t i2c_transaction_semaphore_attributes = {
.name = "i2c_transaction_semaphore"
};
/* Definitions for lora_tx_semaphore */
osSemaphoreId_t lora_tx_semaphoreHandle;
const osSemaphoreAttr_t lora_tx_semaphore_attributes = {
.name = "lora_tx_semaphore"
};
/* Definitions for lora_rx_packet_semaphore */
osSemaphoreId_t lora_rx_packet_semaphoreHandle;
const osSemaphoreAttr_t lora_rx_packet_semaphore_attributes = {
.name = "lora_rx_packet_semaphore"
};
/* Definitions for lora_aux_semaphore */
osSemaphoreId_t lora_aux_semaphoreHandle;
const osSemaphoreAttr_t lora_aux_semaphore_attributes = {
.name = "lora_aux_semaphore"
};
/* Definitions for sensor_ready_semaphore */
osSemaphoreId_t sensor_ready_semaphoreHandle;
const osSemaphoreAttr_t sensor_ready_semaphore_attributes = {
.name = "sensor_ready_semaphore"
};二值信号量需要配置对应的初始值,我的代码如下:
/* creation of i2c_transaction_semaphore */
i2c_transaction_semaphoreHandle = osSemaphoreNew(1, 0, &i2c_transaction_semaphore_attributes);
/* creation of lora_tx_semaphore */
lora_tx_semaphoreHandle = osSemaphoreNew(1, 0, &lora_tx_semaphore_attributes);
/* creation of lora_rx_packet_semaphore */
lora_rx_packet_semaphoreHandle = osSemaphoreNew(1, 0, &lora_rx_packet_semaphore_attributes);
/* creation of lora_aux_semaphore */
lora_aux_semaphoreHandle = osSemaphoreNew(1, 0, &lora_aux_semaphore_attributes);
/* creation of sensor_ready_semaphore */
sensor_ready_semaphoreHandle = osSemaphoreNew(1, 0, &sensor_ready_semaphore_attributes);可以看到我设置的初始量都是 0。但是让我们来看一下 CubeMX 中的配置:

这完全对不上,如果这时候我们点击生成代码,就会发现:

CubeMX 中的 Initial State 配置完全不起作用,所以上面给出的代码中的初始量也是自行配置的。
以上测试基于 CubeMX 中的 STM32U5 v1.8

不清楚 CubeMX 版本会不会对代码生成产生影响。