无人机上,利用 ucos2 实现 stm32 采集磁力计数据,并通过CAN 总线发送功能

要实现基于 μ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();
}

🛠️ 五、关键优化与调试技巧

  1. 实时性保障

    • CAN发送任务优先级最高,避免发送延迟

    • 使用 DMA传输磁力计数据,减少CPU占用6

    • 在CAN中断中调用 OSIntEnter()/OSIntExit() 确保任务切换正确

  2. 错误处理机制

    • 监控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或报警
      }
  3. 低功耗设计

    • 磁力计DRDY引脚触发外部中断唤醒MCU

    • 空闲任务中调用 __WFI() 进入睡眠模式6

  4. 调试工具

    • 逻辑分析仪:检查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;
}
相关推荐
EutoCool6 小时前
Qt:布局管理器Layout
开发语言·c++·windows·嵌入式硬件·qt·前端框架
datascome7 小时前
文章发布易优CMS(Eyoucms)网站技巧
数据库·经验分享·爬虫·数据采集·eyoucms·易优cms
网易独家音乐人Mike Zhou7 小时前
【Linux应用】开发板USB共享网络,网线或USB以太网共享网络(局域网连接PC和开发板,实现PC给开发板共享网络,USB通过NDIS驱动共享)
linux·网络·单片机·mcu·物联网·嵌入式·iot
小眼睛FPGA8 小时前
【RK3568+PG2L50H开发板实验例程】FPGA部分 | DDR3 读写实验例程
科技·嵌入式硬件·ai·fpga开发·fpga
森焱森10 小时前
单片机中 main() 函数无 while 循环的后果及应对策略
c语言·单片机·算法·架构·无人机
学不动CV了10 小时前
ARM单片机OTA解析(二)
arm开发·数据结构·stm32·单片机·嵌入式硬件
majingming12311 小时前
esp8266-01S实现PPM波形
单片机·嵌入式硬件
TESmart碲视12 小时前
USB一线连多屏?Display Link技术深度解析
stm32·单片机·嵌入式硬件·物联网·计算机外设·电脑·智能硬件
森焱森12 小时前
60 美元玩转 Li-Fi —— 开源 OpenVLC 平台入门(附 BeagleBone Black 驱动简单解析)
c语言·单片机·算法·架构·开源
__基本操作__12 小时前
stm32计时的两个方法
stm32·单片机·嵌入式硬件