无人机上,利用 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;
}
相关推荐
2401_888859711 小时前
STM32 CAN简介及帧格式
stm32·单片机·嵌入式硬件
东芝-铠侠-技术王工2 小时前
TB62211FNG是一款采用时钟输入控制的PWM斩波器的两相双极步进电机驱动器
单片机·嵌入式硬件
懒惰的bit2 小时前
STM32F103C8T6 学习笔记摘要(三)
笔记·stm32·学习
happygrilclh2 小时前
Modbus仿真器 Modbus Poll 和Modbus Slave详细图文教程
stm32·单片机·嵌入式硬件
懒惰的bit2 小时前
STM32F103C8T6 学习笔记摘要(一)
笔记·stm32·学习
ILOVECOMPUTING3 小时前
无人机上,利用 ucos2 实现 stm32 采集陀螺仪数据
单片机·嵌入式硬件·无人机·数据采集·陀螺仪·ucos2
乌萨奇也要立志学C++3 小时前
【STM32】STM32的中断系统&寄存器NVIC、EXTI
stm32·单片机·嵌入式硬件
橡木树的叶子3 小时前
正点原子STM32cubeide学习——TFTLCD(MCU 屏)实验
ide·stm32·单片机·嵌入式硬件·学习
华清远见成都中心3 小时前
Linux嵌入式和单片机嵌入式的区别?
linux·运维·单片机·嵌入式
学渣676564 小时前
单片机开发日志cv MDK-ARM工具链迁移到MAKE
arm开发·单片机·嵌入式硬件