要实现基于 μC/OS-II 的 STM32 磁力计数据采集与 CAN 总线发送功能,需综合实时操作系统调度、传感器驱动、CAN 通信协议及多任务协同设计。以下是分步实现方案,结合硬件配置、软件架构和系统集成要点:
🧰 一、系统架构设计
1. 硬件平台选型
-
MCU:STM32F103/F407(带CAN控制器,如CAN1/CAN2)
-
磁力计:QMC5883L(I²C接口)或 MMC5983MA(SPI接口)
-
CAN收发器:TJA1050(高速CAN)
-
接口连接
-
磁力计:
SCL/PB6
,SDA/PB7
(I²C)或SCK/PA5
,MISO/PA6
,MOSI/PA7
(SPI) -
CAN:
CAN_TX/PA12
,CAN_RX/PA11
(需120Ω终端电阻)38
-
2. 软件架构
图表
代码
graph TD
A[μC/OS-II内核] --> B[任务1:磁力计数据采集]
A --> C[任务2:数据滤波/校准]
A --> D[任务3:CAN报文封装与发送]
A --> E[任务4:系统监控]
B --> C --> D
E --> B & D
-
任务优先级:CAN发送 > 磁力计采集 > 数据处理 > 监控(优先级递减)
-
关键资源:
-
共享数据缓冲区(磁力计原始数据)→ 信号量保护
-
CAN发送邮箱 → 消息队列管理
-
μC/OS-II内核 任务分配
任务1:磁力计数据采集
任务2:数据滤波/校准
任务3:CAN报文封装与发送
任务4:系统监控
-
任务优先级:CAN发送 > 磁力计采集 > 数据处理 > 监控(优先级递减)
-
关键资源:
-
共享数据缓冲区(磁力计原始数据)→ 信号量保护
-
CAN发送邮箱 → 消息队列管理
-
⚙️ 二、μC/OS-II 环境搭建
1. 系统初始化
#include "ucos_ii.h"
void main(void) {
OSInit(); // 初始化μC/OS-II
HAL_Init(); // 初始化HAL库
SystemClock_Config();
// 创建任务
OSTaskCreate(Task_Mag, NULL, &Task_Mag_Stk[TASK_STK_SIZE-1], 5);
OSTaskCreate(Task_CAN, NULL, &Task_CAN_Stk[TASK_STK_SIZE-1], 3); // CAN任务优先级更高
OSStart(); // 启动任务调度
}
2. 任务堆栈与优先级分配
任务 | 优先级 | 堆栈大小 | 功能 |
---|---|---|---|
Task_CAN |
3 | 256B | CAN报文发送 |
Task_Mag |
5 | 256B | 磁力计数据采集 |
Task_Calib |
6 | 512B | 数据校准与滤波 |
Task_Monitor |
7 | 128B | 系统状态监控 |
🔧 三、磁力计驱动与数据采集
1. I²C/SPI 驱动配置(以QMC5883L为例)
// I²C初始化(HAL库)
void MAG_I2C_Init(void) {
hi2c.Instance = I2C1;
hi2c.Init.ClockSpeed = 400000; // 400kHz
HAL_I2C_Init(&hi2c);
// 配置磁力计寄存器
uint8_t cfg[2] = {0x09, 0x1D}; // 连续测量模式,10Hz
HAL_I2C_Mem_Write(&hi2c, 0x0D<<1, 0x09, 1, &cfg[0], 1, 100);
}
2. 数据采集任务
void Task_Mag(void *p_arg) {
int16_t mag_raw[3];
OS_SEM pend_err;
while (1) {
HAL_I2C_Mem_Read(&hi2c, 0x0D<<1, 0x00, 1, (uint8_t*)mag_raw, 6, 100);
// 申请信号量访问共享缓冲区
OSSemPend(&MagData_Sem, 0, &pend_err);
memcpy(mag_buffer, mag_raw, sizeof(mag_raw)); // 写入缓冲区
OSSemPost(&MagData_Sem);
OSTimeDlyHMSM(0, 0, 0, 100); // 10Hz采样(100ms延时)
}
}
3. 校准与滤波(独立任务)
void Task_Calib(void *p_arg) {
float mag_calib[3];
while (1) {
OSSemPend(&MagData_Sem, 0, OS_OPT_PEND_BLOCKING, 0, &pend_err);
// 硬铁校准:mag_calib[x] = (mag_raw[x] - offset_x) / sensitivity;
// 低通滤波:mag_filtered[x] = 0.9 * mag_filtered[x] + 0.1 * mag_calib[x];
OSSemPost(&MagData_Sem);
// 发送消息至CAN任务队列
OSQPost(&CAN_Msg_Q, (void*)mag_filtered, sizeof(float)*3, OS_OPT_POST_FIFO);
}
}
📡 四、CAN总线通信实现
1. CAN外设初始化(基于HAL库)
void CAN_Init(void) {
hcan.Instance = CAN1;
hcan.Init.Prescaler = 6; // 500kbps (APB1=42MHz)
hcan.Init.Mode = CAN_MODE_NORMAL;
hcan.Init.SyncJumpWidth = CAN_SJW_1TQ;
hcan.Init.TimeSeg1 = CAN_BS1_7TQ;
hcan.Init.TimeSeg2 = CAN_BS2_8TQ;
HAL_CAN_Init(&hcan);
// 过滤器配置(接收所有报文)
CAN_FilterTypeDef filter = {
.FilterIdHigh = 0, .FilterMaskIdHigh = 0, // 无过滤
.FilterFIFOAssignment = CAN_FILTER_FIFO0,
.FilterActivation = ENABLE
};
HAL_CAN_ConfigFilter(&hcan, &filter);
HAL_CAN_Start(&hcan);
}
2. CAN发送任务
void Task_CAN(void *p_arg) {
CAN_TxHeaderTypeDef tx_header = {
.StdId = 0x100, // CAN ID
.RTR = CAN_RTR_DATA,
.IDE = CAN_ID_STD,
.DLC = 8 // 8字节数据
};
uint8_t tx_data[8];
void *msg;
while (1) {
// 从队列获取滤波后数据
msg = OSQPend(&CAN_Msg_Q, 0, OS_OPT_PEND_BLOCKING, 0, &pend_err);
float *mag_data = (float*)msg;
// 封装数据(大端序)
tx_data[0] = (int16_t)(mag_data[0]*1000) >> 8; // X轴高字节
tx_data[1] = (int16_t)(mag_data[0]*1000) & 0xFF;
// ... 同理填充Y/Z轴(保留2字节备用)
HAL_CAN_AddTxMessage(&hcan, &tx_header, tx_data, &tx_mailbox);
OSTimeDlyHMSM(0, 0, 0, 50); // 20Hz发送
}
}
3. 中断配置(接收与错误处理)
// 在HAL_CAN_Start后使能中断
HAL_CAN_ActivateNotification(&hcan, CAN_IT_RX_FIFO0_MSG_PENDING);
// 中断回调函数(需在OS_CRITICAL_ENTER/EXIT中保护)
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) {
OS_CRITICAL_ENTER();
CAN_RxHeaderTypeDef rx_header;
uint8_t rx_data[8];
HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &rx_header, rx_data);
// 解析数据(可选)
OS_CRITICAL_EXIT();
}
🛠️ 五、关键优化与调试技巧
-
实时性保障
-
CAN发送任务优先级最高,避免发送延迟
-
使用 DMA传输磁力计数据,减少CPU占用6
-
在CAN中断中调用
OSIntEnter()
/OSIntExit()
确保任务切换正确
-
-
错误处理机制
-
监控CAN错误计数器:
uint8_t tec = (hcan.Instance->ESR & CAN_ESR_TEC) >> 16; // 发送错误计数 if (tec > 128) HAL_CAN_ResetError(&hcan); // 错误被动状态恢复
-
磁力计数据超时检测:
if (OSSemPend(&MagData_Sem, 10, &err) == OS_ERR_TIMEOUT) { // 重启I²C或报警 }
-
-
低功耗设计
-
磁力计DRDY引脚触发外部中断唤醒MCU
-
空闲任务中调用
__WFI()
进入睡眠模式6
-
-
调试工具
-
逻辑分析仪:检查I²C时序与CAN波形(显性/隐性电平)
-
CANalyzer:监控总线报文,验证ID与数据格式
-
串口打印 :通过
OS_CRITICAL_ENTER()
保护下的printf
输出任务状态8
-
💎 六、完整代码框架示例
c
复制
下载
// 全局资源定义
OS_SEM MagData_Sem; // 磁力计数据信号量
OS_Q CAN_Msg_Q; // CAN消息队列
int main(void) {
OSInit();
// 初始化外设与OS对象
MAG_I2C_Init();
CAN_Init();
OSSemCreate(&MagData_Sem, 1); // 二进制信号量
OSQCreate(&CAN_Msg_Q, 10); // 消息队列容量10
// 创建任务
OSTaskCreate(Task_Mag, ..., 5);
OSTaskCreate(Task_Calib, ..., 6);
OSTaskCreate(Task_CAN, ..., 3);
OSStart();
return 0;
}