核心目标:深入掌握实时操作系统(FreeRTOS)的高级同步机制(解决多任务资源竞争与协同问题),并结合硬件级可靠性设计( watchdog、电源监控、抗干扰),开发一个 “工业步进电机控制系统”—— 实现多任务协同下的精准运动控制(位置 / 速度闭环)、故障自恢复、抗干扰运行,这是工业控制领域(如数控机床、自动化流水线)的核心技术能力。
一、FreeRTOS 高级同步机制:解决多任务 “协同与冲突”(50 分钟)
在复杂嵌入式系统中,多任务并发时会面临两大问题:资源竞争(如多个任务同时操作串口 / 传感器)和协同依赖(如 “数据采集完成后才能执行控制算法”)。FreeRTOS 提供的信号量、互斥锁、事件标志组等同步机制,是解决这些问题的核心工具。
1. 四大核心同步机制及应用场景
| 同步机制 | 核心作用 | 典型应用场景 |
|---|---|---|
| 二进制信号量 | 任务间 “通知” 或 “唤醒”,实现异步协同 | 传感器数据就绪后,通知控制任务处理;外部中断唤醒阻塞任务 |
| 互斥锁(Mutex) | 保护共享资源(如串口、全局变量),防止并发访问冲突 | 多个任务需要向串口打印日志时,保证数据不混乱 |
| 计数信号量 | 限制同时访问某资源的任务数量(资源池管理) | 限制最多 3 个任务同时使用 SPI 总线(避免总线冲突) |
| 事件标志组 | 一个任务等待多个事件中的一个或多个发生 | 控制任务等待 “传感器就绪 + 上位机指令” 两个事件都发生后执行 |
2. 实操:同步机制解决实际问题
以 “步进电机控制系统” 为例,存在 3 个核心任务:
- 「传感器采集任务」:读取电机位置反馈(编码器数据);
- 「控制算法任务」:根据目标位置和当前位置,计算输出脉冲;
- 「上位机通信任务」:接收 Modbus 指令(设置目标位置),发送状态信息。
这三个任务存在资源竞争(共享 “目标位置” 全局变量)和协同依赖(控制任务需等待传感器数据和上位机指令),需用同步机制解决。
(1)互斥锁:保护共享资源(目标位置变量)
c
/* USER CODE BEGIN 0 */
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
// 共享资源:目标位置(单位:步)
static int32_t g_target_pos = 0;
// 互斥锁:保护目标位置的读写
SemaphoreHandle_t xMutex_TargetPos;
// 上位机通信任务:接收指令修改目标位置(需获取互斥锁)
void CommTask(void const * argument) {
int32_t new_target;
for(;;) {
// 从Modbus接收新目标位置(简化)
new_target = Modbus_GetNewTargetPos();
// 请求互斥锁(等待100ms,获取不到则放弃)
if (xSemaphoreTake(xMutex_TargetPos, pdMS_TO_TICKS(100)) == pdTRUE) {
g_target_pos = new_target; // 安全修改共享变量
xSemaphoreGive(xMutex_TargetPos); // 释放互斥锁
} else {
// 获取锁失败(可能被控制任务占用),记录警告日志
LOG_WARN("修改目标位置失败:资源被占用");
}
vTaskDelay(100);
}
}
// 控制算法任务:读取目标位置(需获取互斥锁)
void ControlTask(void const * argument) {
int32_t current_target;
for(;;) {
// 请求互斥锁(阻塞等待,直到获取)
xSemaphoreTake(xMutex_TargetPos, portMAX_DELAY);
current_target = g_target_pos; // 安全读取共享变量
xSemaphoreGive(xMutex_TargetPos);
// 执行控制算法(根据current_target和当前位置计算输出)
StepMotor_Control(current_target, GetCurrentPos());
vTaskDelay(10); // 10ms控制周期
}
}
/* USER CODE END 0 */
(2)二进制信号量:传感器数据就绪通知
c
运行
// 二进制信号量:传感器数据就绪标志
SemaphoreHandle_t xSemaphore_SensorReady;
// 传感器采集任务:读取编码器数据后释放信号量
void SensorTask(void const * argument) {
for(;;) {
// 读取编码器数据(阻塞操作,耗时约5ms)
int32_t pos = Encoder_Read();
// 存储到全局变量(已用互斥锁保护,此处省略)
SetCurrentPos(pos);
// 释放信号量,通知控制任务“数据已更新”
xSemaphoreGive(xSemaphore_SensorReady);
vTaskDelay(10); // 10ms采集周期
}
}
// 控制任务:等待传感器数据就绪后再执行算法
void ControlTask(void const * argument) {
for(;;) {
// 等待传感器数据就绪(最多等待20ms,超时则报警)
if (xSemaphoreTake(xSemaphore_SensorReady, pdMS_TO_TICKS(20)) == pdTRUE) {
// 数据就绪,执行控制算法
StepMotor_Control(current_target, GetCurrentPos());
} else {
// 超时:传感器可能故障,触发故障处理
Fault_Handle(FAULT_SENSOR_TIMEOUT);
}
vTaskDelay(10);
}
}
(3)事件标志组:等待多事件触发
控制任务需要同时满足两个条件才执行:① 传感器数据就绪;② 收到上位机使能指令。用事件标志组实现:
c
// 事件标志组:bit0=传感器就绪,bit1=上位机使能
EventGroupHandle_t xEventGroup_Control;
#define EVENT_SENSOR_READY (1 << 0)
#define EVENT_UART_ENABLE (1 << 1)
// 传感器任务:设置“传感器就绪”标志
void SensorTask(void const * argument) {
for(;;) {
// 读取数据...
xEventGroupSetBits(xEventGroup_Control, EVENT_SENSOR_READY);
vTaskDelay(10);
}
}
// 通信任务:收到使能指令后设置“上位机使能”标志
void CommTask(void const * argument) {
for(;;) {
if (Modbus_GetEnableCmd() == 1) {
xEventGroupSetBits(xEventGroup_Control, EVENT_UART_ENABLE);
} else {
xEventGroupClearBits(xEventGroup_Control, EVENT_UART_ENABLE);
}
vTaskDelay(100);
}
}
// 控制任务:等待两个事件都发生(逻辑与)
void ControlTask(void const * argument) {
EventBits_t uxBits;
for(;;) {
// 等待bit0和bit1都置位(pdMS_TO_TICKS(100)超时)
uxBits = xEventGroupWaitBits(
xEventGroup_Control,
EVENT_SENSOR_READY | EVENT_UART_ENABLE,
pdTRUE, // 退出时清除标志位
pdTRUE, // 等待所有位(逻辑与)
pdMS_TO_TICKS(100)
);
if ((uxBits & (EVENT_SENSOR_READY | EVENT_UART_ENABLE)) ==
(EVENT_SENSOR_READY | EVENT_UART_ENABLE)) {
// 两个事件都满足,执行控制
StepMotor_Control(...);
} else {
// 未满足条件,停止电机
StepMotor_Stop();
}
}
}
3. 同步机制关键注意事项
- 死锁避免:互斥锁使用时需保持 “同一顺序获取、及时释放”,避免 A 任务持有锁 1 等待锁 2,B 任务持有锁 2 等待锁 1;
- 优先级反转:高优先级任务等待低优先级任务释放互斥锁时,可能被中等优先级任务抢占,导致高优先级任务延迟。FreeRTOS 的互斥锁支持 “优先级继承”(将低优先级任务临时提升到高优先级),解决此问题;
- 超时处理:所有同步操作(
xSemaphoreTake、xEventGroupWaitBits)需设置合理超时,避免任务永久阻塞。
二、嵌入式系统可靠性设计:硬件级 “抗造” 能力(40 分钟)
工业控制设备需在强电磁干扰、电源波动、温湿度剧烈变化的环境中长期运行(7×24 小时),仅靠软件容错远远不够,需结合硬件级设计和软件协同,实现 “故障自恢复 + 抗干扰”。
1. 三大核心可靠性技术
| 技术点 | 核心作用 | 实现方式 |
|---|---|---|
| 独立看门狗(IWDG) | 防止软件死锁(如任务卡死、中断异常) | 硬件定时器,需软件定期 “喂狗”,超时则强制复位系统 |
| 电源监控(PMIC) | 检测电源电压波动,防止欠压 / 过压导致异常 | 外部电源监控芯片(如 TC1185),电压异常时触发复位 |
| 信号抗干扰 | 减少电磁干扰(EMI)对通信 / 传感器的影响 | 硬件滤波(RC 滤波、磁珠)+ 软件滤波(数字滤波、CRC 校验) |
2. 实操:可靠性设计落地
(1)独立看门狗配置与喂狗策略
STM32 内置独立看门狗(IWDG),时钟来自低速内部 RC(LSI,约 40kHz),不受主时钟影响,确保系统时钟故障时仍能复位:
c
// 初始化独立看门狗(超时时间=1秒)
void IWDG_Init() {
// 使能写访问
IWDG->KR = 0x5555;
// 分频系数=64(40kHz/64=625Hz,周期1.6ms)
IWDG->PR = IWDG_PR_PR_2; // PR[2:0]=010 → 64分频
// 重装载值=625(625×1.6ms=1000ms=1秒)
IWDG->RLR = 625;
// 启动看门狗
IWDG->KR = 0xCCCC;
}
// 喂狗函数(需在1秒内调用一次)
void IWDG_Feed() {
IWDG->KR = 0xAAAA; // 重装载计数器
}
// 喂狗策略:在系统管理任务中定期喂狗,同时检查关键任务状态
void SysManageTask(void const * argument) {
for(;;) {
// 检查核心任务是否存活(通过任务心跳标志)
if (g_sensor_task_alive && g_control_task_alive && g_comm_task_alive) {
IWDG_Feed(); // 所有任务正常,喂狗
} else {
// 有任务卡死,不喂狗,触发系统复位
LOG_ERROR("任务卡死,触发看门狗复位");
}
vTaskDelay(500); // 500ms喂狗一次(小于超时时间1秒)
}
}
(2)电源监控与欠压保护
通过 STM32 内部低电压检测(PVD)或外部芯片,监测电源电压(如 3.3V 供电),低于阈值时保存关键数据并进入安全状态:
c
// 配置PVD(低电压检测,阈值2.9V)
void PVD_Init() {
RCC->APB1ENR |= RCC_APB1ENR_PWREN; // 使能PWR时钟
PWR->CR |= PWR_CR_PLS_2; // PLS[2:0]=010 → 检测阈值2.9V
PWR->CR |= PWR_CR_PVDE; // 使能PVD
// 配置PVD中断(上升沿和下降沿触发)
EXTI->IMR |= EXTI_IMR_MR16; // 使能EXTI16中断
EXTI->RTSR |= EXTI_RTSR_TR16; // 上升沿触发
EXTI->FTSR |= EXTI_FTSR_TR16; // 下降沿触发
HAL_NVIC_EnableIRQ(PVD_IRQn); // 使能PVD中断
}
// PVD中断服务函数
void PVD_IRQHandler() {
if (EXTI->PR & EXTI_PR_PR16) {
EXTI->PR |= EXTI_PR_PR16; // 清除中断标志
if (PWR->CSR & PWR_CSR_PVDO) { // PVDO=1 → 电压低于阈值
// 电压过低:保存关键数据(当前位置、故障状态)到Flash
SaveCriticalDataToFlash();
// 停止电机,关闭外设,进入低功耗
StepMotor_Stop();
Periph_DeInit();
} else {
// 电压恢复:重新初始化外设,恢复运行
Periph_Init();
StepMotor_Resume();
}
}
}
(3)信号抗干扰设计(以编码器信号为例)
编码器是电机位置反馈的核心,信号受干扰会导致位置计算错误,需硬件 + 软件双重滤波:
- 硬件滤波:编码器 A/B 相信号线串联 100Ω 电阻 + 并联 100nF 电容(RC 滤波),减少高频干扰;
- 软件滤波:对连续 3 次读取的信号进行一致性校验,剔除突变噪声:
c
// 编码器信号软件滤波(连续3次相同才确认有效)
uint8_t Encoder_Filter(uint8_t raw_signal) {
static uint8_t signal_buf[3] = {0};
static uint8_t index = 0;
signal_buf[index] = raw_signal;
index = (index + 1) % 3;
// 连续3次相同则认为有效
if (signal_buf[0] == signal_buf[1] && signal_buf[1] == signal_buf[2]) {
return signal_buf[0];
} else {
// 不一致,返回上一次有效信号(避免跳变)
return signal_buf[(index + 2) % 3]; // 上一次的前一个值
}
}
三、实战项目:工业步进电机控制系统(50 分钟)
整合 RTOS 高级同步机制和可靠性设计,实现一个闭环步进电机控制系统,核心功能:
- 位置闭环控制(目标位置由 Modbus 设置,当前位置由编码器反馈);
- 速度限制(最高 1000 步 / 秒,避免过载);
- 故障自恢复(传感器超时、电源欠压、任务卡死时自动处理)。
1. 硬件组成
| 模块 | 与 STM32 连接方式 | 作用 |
|---|---|---|
| 步进电机驱动器 | PB0-PB3(脉冲 / 方向 / 使能 / 复位) | 接收 STM32 信号,驱动电机运转 |
| 编码器 | TIM2_CH1(PA0,A 相)、TIM2_CH2(PA1,B 相) | 反馈电机实际位置(每转 4000 脉冲) |
| RS485 模块 | USART1(PA9/TX,PA10/RX,PA8/DE) | Modbus 通信,接收上位机指令 |
| 指示灯 | PC13(运行灯)、PB8(故障灯) | 指示系统状态 |
2. 软件架构(FreeRTOS 任务 + 同步机制)
| 任务名称 | 优先级 | 核心功能 | 同步机制依赖 |
|---|---|---|---|
| 控制算法任务 | 4(最高) | 位置闭环 PID 计算,输出脉冲信号 | 互斥锁(目标位置)、事件标志组(传感器 + 使能) |
| 传感器采集任务 | 3 | 读取编码器位置,滤波后更新当前位置 | 二进制信号量(数据就绪通知) |
| 通信任务 | 2 | Modbus 接收目标位置 / 使能指令,发送状态 | 互斥锁(目标位置) |
| 系统管理任务 | 1 | 看门狗喂狗、故障检测、状态指示 | 任务心跳标志 |
3. 核心代码:位置闭环控制
c
// PID参数(根据电机特性调整)
#define KP 5.0f
#define KI 0.1f
#define KD 0.5f
// 控制算法任务:位置闭环PID
void ControlTask(void const * argument) {
int32_t target_pos = 0, current_pos = 0;
int32_t error = 0, last_error = 0, integral = 0, derivative = 0;
int32_t output_pulse = 0;
EventBits_t uxBits;
for(;;) {
// 等待传感器就绪和上位机使能(事件标志组)
uxBits = xEventGroupWaitBits(
xEventGroup_Control,
EVENT_SENSOR_READY | EVENT_UART_ENABLE,
pdTRUE, pdTRUE, pdMS_TO_TICKS(100)
);
if ((uxBits & (EVENT_SENSOR_READY | EVENT_UART_ENABLE)) ==
(EVENT_SENSOR_READY | EVENT_UART_ENABLE)) {
// 读取当前位置(已通过传感器任务更新)
current_pos = GetCurrentPos();
// 读取目标位置(互斥锁保护)
xSemaphoreTake(xMutex_TargetPos, portMAX_DELAY);
target_pos = g_target_pos;
xSemaphoreGive(xMutex_TargetPos);
// 计算PID误差
error = target_pos - current_pos;
integral += error;
// 积分限幅(防止饱和)
if (integral > 1000) integral = 1000;
else if (integral < -1000) integral = -1000;
derivative = error - last_error;
last_error = error;
// 计算输出脉冲(限幅最高1000步/秒)
output_pulse = (int32_t)(KP*error + KI*integral + KD*derivative);
if (output_pulse > 1000) output_pulse = 1000;
else if (output_pulse < -1000) output_pulse = -1000;
// 输出脉冲控制电机
StepMotor_OutputPulse(output_pulse);
// 更新任务心跳
g_control_task_alive = 1;
} else {
// 未满足条件,停止电机
StepMotor_Stop();
g_control_task_alive = 0; // 任务异常标志
}
vTaskDelay(10); // 10ms控制周期
}
}
4. 测试验证
- 资源竞争测试:上位机快速发送目标位置指令,观察控制任务是否能正确读取(无数据错乱);
- 同步协同测试:断开编码器连接,控制任务因 “传感器就绪事件未发生” 停止输出(无失控);
- 可靠性测试:
- 人为让传感器任务卡死,系统管理任务因 “心跳丢失” 不喂狗,1 秒后系统复位;
- 降低供电电压至 2.8V(低于 PVD 阈值 2.9V),系统自动保存数据并停止电机,电压恢复后重启运行。
四、第十九天必掌握的 3 个核心点
- RTOS 同步机制:能根据场景选择信号量(通知)、互斥锁(资源保护)、事件标志组(多条件等待),解决多任务协同与冲突问题;
- 硬件级可靠性设计:会配置独立看门狗(防死锁)、PVD(电源监控),结合软硬件滤波处理信号干扰;
- 工业控制思维:理解闭环控制中 “实时性”“可靠性” 的核心地位,能设计多任务协同的控制逻辑。
总结
第 19 天聚焦工业控制的 “灵魂技术”——RTOS 高级同步确保多任务有序协同,可靠性设计保证设备在恶劣环境下稳定运行,两者结合是工业嵌入式系统区别于消费级产品的核心竞争力。

933

被折叠的 条评论
为什么被折叠?



