1. 项目概述与背景
在工业控制和汽车电子领域,实时性和可靠性是嵌入式系统设计的核心要求。STM32F107作为一款基于Cortex-M3内核的微控制器,搭配UCOS-II实时操作系统,能够为CAN总线通信提供稳定的多任务调度基础。这个项目展示了如何将UCOS-II移植到STM32F107平台,并实现高效的CAN总线通信框架。
我曾在多个工业控制项目中采用类似架构,特别是在需要处理多个传感器数据同时又要保证实时响应的场景下。这种组合的优势在于:UCOS提供了确定性的任务调度,而STM32的硬件CAN控制器则确保了通信的可靠性。通过信号量和消息队列等机制,可以构建一个既稳定又灵活的系统架构。
2. 开发环境搭建
2.1 硬件选型与配置
STM32F107VCT6是本次项目的核心硬件平台,选择它的主要原因包括:
- Cortex-M3内核提供72MHz主频,足够处理中等复杂度的实时任务
- 内置CAN控制器,支持CAN 2.0A/B协议
- 丰富的外设接口,便于扩展调试功能
硬件连接要点:
- CAN总线接口使用PA11(CAN_RX)和PA12(CAN_TX)
- 建议添加120Ω终端电阻在总线两端
- 调试用的LED和按键可连接到任意GPIO
2.2 软件工具链准备
开发环境配置步骤如下:
-
安装Keil MDK-ARM 5.34
- 注意安装对应的STM32F1xx Device Family Pack
- 建议安装J-Link或ST-Link驱动用于调试
-
获取UCOS-II源码
- 从Micrium官网下载2.92版本
- 关键文件包括:
code复制/Ports/ARM/Generic/RealView ├─ os_cpu.h ├─ os_cpu_a.asm ├─ os_cpu_c.c /Source ├─ os_core.c ├─ os_task.c ├─ os_time.c ├─ os_mem.c
-
STM32 HAL库配置
- 使用STM32CubeMX生成基础工程
- 在Project Manager中勾选"Copy only necessary library files"
提示:建议在工程中创建独立的UCOS目录结构,将移植文件与应用代码分离,便于后续维护。
3. UCOS-II系统移植
3.1 移植文件修改要点
移植UCOS-II到STM32F107需要特别注意以下几个关键文件的适配:
-
os_cpu.h - 处理器相关定义
c复制#define OS_CPU_ARM_FP_EN 0 // 不使用浮点单元 #define OS_TASK_SW() OSCtxSw() // 任务切换宏 -
os_cpu_a.asm - 汇编级移植
- 修改PendSV_Handler中断优先级
- 确保OS_CPU_PendSVHandler与向量表一致
-
os_cpu_c.c - 钩子函数实现
c复制void OSTaskStkInit(void *(*task)(void *), void *p_arg, void *ptos, INT16U opt) { // 初始化任务堆栈,注意M3使用的是满递减堆栈 }
3.2 工程配置细节
在Keil中的关键配置步骤:
-
添加预定义宏:
code复制USE_HAL_DRIVER STM32F107xC OS_CPU_ARM -
设置编译选项:
- Optimization Level: -O2
- 勾选"Use MicroLIB"以减小代码体积
-
分散加载文件(.sct)配置:
code复制LR_IROM1 0x08000000 0x00080000 { ; Flash ER_IROM1 0x08000000 0x00080000 { *.o (RESET, +First) *(InRoot$$Sections) .ANY (+RO) } RW_IRAM1 0x20000000 0x00010000 { .ANY (+RW +ZI) } }
3.3 系统初始化流程
完整的UCOS启动流程如下:
c复制int main(void) {
HAL_Init();
SystemClock_Config();
// 硬件外设初始化
LED_Init();
CAN_GPIO_Config();
// UCOS初始化
OSInit();
// 创建启动任务
OSTaskCreate(AppTaskStart,
(void *)0,
&AppTaskStartStk[APP_TASK_START_STK_SIZE-1],
APP_TASK_START_PRIO);
OSStart(); // 启动调度器
return 0;
}
注意:OSStart()之后不会返回,所有应用任务都应在启动任务中创建。
4. CAN总线通信实现
4.1 CAN硬件配置详解
4.1.1 引脚与时钟配置
c复制void CAN_GPIO_Config(void) {
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_CAN1_CLK_ENABLE();
// CAN_RX - PA11
GPIO_InitStruct.Pin = GPIO_PIN_11;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
// CAN_TX - PA12
GPIO_InitStruct.Pin = GPIO_PIN_12;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
4.1.2 CAN控制器参数设置
波特率计算公式:
code复制tq = (BRP[9:0] + 1) * tPCLK
波特率 = 1 / (Sync_Seg + Prop_Seg + Phase_Seg1 + Phase_Seg2)
以500kbps为例的配置:
c复制CAN_HandleTypeDef hcan;
void CAN_Init(void) {
hcan.Instance = CAN1;
hcan.Init.Prescaler = 5; // 72MHz/(5*(1+8+7)) = 500kHz
hcan.Init.Mode = CAN_MODE_NORMAL;
hcan.Init.SyncJumpWidth = CAN_SJW_1TQ;
hcan.Init.TimeSeg1 = CAN_BS1_8TQ;
hcan.Init.TimeSeg2 = CAN_BS2_7TQ;
hcan.Init.TimeTriggeredMode = DISABLE;
hcan.Init.AutoBusOff = DISABLE;
hcan.Init.AutoWakeUp = DISABLE;
hcan.Init.AutoRetransmission = ENABLE;
hcan.Init.ReceiveFifoLocked = DISABLE;
hcan.Init.TransmitFifoPriority = DISABLE;
if (HAL_CAN_Init(&hcan) != HAL_OK) {
Error_Handler();
}
}
4.2 CAN中断处理机制
4.2.1 中断优先级配置
在UCOS环境下,中断优先级设置需要特别注意:
c复制void CAN_NVIC_Config(void) {
HAL_NVIC_SetPriority(USB_LP_CAN_RX0_IRQn, 0, 1);
HAL_NVIC_EnableIRQ(USB_LP_CAN_RX0_IRQn);
// 使能CAN接收中断
HAL_CAN_ActivateNotification(&hcan, CAN_IT_RX_FIFO0_MSG_PENDING);
}
4.2.2 中断服务函数实现
c复制void USB_LP_CAN_RX0_IRQHandler(void) {
OSIntEnter(); // 通知UCOS进入中断
HAL_CAN_IRQHandler(&hcan); // 处理CAN中断
OSIntExit(); // 通知UCOS退出中断
}
// HAL库回调函数
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) {
CanRxMsgTypeDef rx_msg;
if (HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &rx_msg) == HAL_OK) {
// 将消息放入队列
OSQPost(CanQueue, &rx_msg);
}
}
重要提示:在中断服务中不要执行耗时操作,建议仅做简单数据搬运和信号量触发。
5. 多任务系统设计
5.1 任务划分与优先级分配
根据工业控制系统的典型需求,建议的任务划分如下:
| 任务名称 | 优先级 | 堆栈大小 | 功能描述 |
|---|---|---|---|
| SystemMonitor | 5 | 128 | 系统状态监控 |
| CAN_RecvTask | 10 | 256 | CAN消息接收处理 |
| CAN_SendTask | 12 | 256 | 周期发送CAN消息 |
| DataProcessTask | 15 | 512 | 数据解析与业务逻辑 |
| LED_Task | 20 | 64 | 系统状态指示灯控制 |
优先级设置原则:
- 数值越小优先级越高
- 硬件相关任务优先级高于应用任务
- 周期性任务优先级高于事件驱动任务
5.2 任务间通信机制
5.2.1 信号量使用实例
c复制OS_EVENT *CanRecvSem; // CAN接收信号量
// 发送任务
void CAN_SendTask(void *p_arg) {
CanTxMsgTypeDef tx_msg;
while(1) {
// 等待发送时机
OSSemPend(CanSendSem, 0, &err);
// 准备并发送消息
HAL_CAN_AddTxMessage(&hcan, &tx_header, tx_data, &tx_mailbox);
OSTimeDlyHMSM(0, 0, 0, 100); // 100ms周期
}
}
// 接收任务
void CAN_RecvTask(void *p_arg) {
CanRxMsgTypeDef rx_msg;
while(1) {
// 等待消息到达
OSSemPend(CanRecvSem, 0, &err);
// 处理接收到的消息
ProcessCANMessage(&rx_msg);
}
}
5.2.2 消息队列实战
c复制#define CAN_QUEUE_SIZE 20
OS_EVENT *CanQueue;
void *CanQueueTbl[CAN_QUEUE_SIZE];
void Queue_Init(void) {
CanQueue = OSQCreate(&CanQueueTbl[0], CAN_QUEUE_SIZE);
}
// 消息生产者(中断上下文)
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) {
INT8U err;
CanRxMsgTypeDef *msg = OSMemGet(CanMemPool, &err);
if (err == OS_ERR_NONE) {
HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, msg);
OSQPost(CanQueue, msg); // 发送到队列
}
}
// 消息消费者(任务上下文)
void DataProcessTask(void *p_arg) {
CanRxMsgTypeDef *msg;
INT8U err;
while(1) {
msg = OSQPend(CanQueue, 0, &err);
if (err == OS_ERR_NONE) {
// 处理消息
ProcessMessage(msg);
// 释放内存
OSMemPut(CanMemPool, msg);
}
}
}
6. 调试技巧与性能优化
6.1 常见问题排查
-
UCOS启动失败
- 检查堆栈初始化是否正确(M3使用满递减堆栈)
- 确认OS_CPU_SR的宽度与处理器匹配
- 验证中断优先级分组设置(建议使用NVIC_PriorityGroup_2)
-
CAN通信异常
- 使用示波器检查总线电平
- 确认波特率计算正确
- 检查终端电阻是否连接(120Ω)
-
系统死机
- 检查任务堆栈是否溢出(可在任务创建时填充特定模式如0xAA)
- 确认临界区保护完整(OS_ENTER_CRITICAL/OS_EXIT_CRITICAL配对使用)
6.2 性能优化建议
-
内存管理优化
c复制// 创建内存分区 #define CAN_MSG_POOL_SIZE 10 OS_MEM *CanMemPool; CanRxMsgTypeDef CanMsgPool[CAN_MSG_POOL_SIZE]; void Mem_Init(void) { CanMemPool = OSMemCreate(CanMsgPool, CAN_MSG_POOL_SIZE, sizeof(CanRxMsgTypeDef), &err); } -
中断响应优化
- 将CAN中断优先级设为最高(数值最小)
- 在中断服务函数中只做必要的最小操作
- 使用Deferred Procedure Call(DPC)机制处理耗时操作
-
任务堆栈设置
- 通过实际测试确定最小安全堆栈
- 使用UCOS提供的堆栈检查功能:
c复制
OS_STK_DATA stk_data; OSTaskStkChk(TASK_PRIO, &stk_data);
7. 实际应用案例
在工业电机控制系统中,我们采用此架构实现了以下功能:
-
实时状态监控
- CAN总线以1ms周期发送电机参数
- UCOS任务以不同优先级处理:
- 高优先级:紧急停止信号
- 中优先级:实时参数显示
- 低优先级:历史数据存储
-
多节点通信
c复制typedef struct { uint32_t id; uint8_t data[8]; uint8_t len; } CANMsg_t; // 过滤器配置示例 void CAN_Filter_Config(void) { CAN_FilterTypeDef filter; filter.FilterIdHigh = 0x123 << 5; // 标准ID 0x123 filter.FilterIdLow = 0; filter.FilterMaskIdHigh = 0x7FF << 5; // 精确匹配 filter.FilterMaskIdLow = 0; filter.FilterFIFOAssignment = CAN_FILTER_FIFO0; filter.FilterBank = 0; filter.FilterMode = CAN_FILTERMODE_IDMASK; filter.FilterScale = CAN_FILTERSCALE_32BIT; filter.FilterActivation = ENABLE; HAL_CAN_ConfigFilter(&hcan, &filter); } -
故障处理机制
- 硬件看门狗与软件心跳包结合
- 关键任务设置超时监控
c复制void WatchdogTask(void *p_arg) { while(1) { if (OSTaskStkChk(HIGH_PRIO_TASK, &stk_data) != OS_ERR_NONE) { // 触发系统复位 NVIC_SystemReset(); } OSTimeDlyHMSM(0, 0, 1, 0); // 1秒周期 } }
在实际项目中,这种架构表现出色,即使在总线负载率达到70%的情况下,仍然能保证关键任务的实时响应。通过合理设置任务优先级和优化中断处理,系统的最坏响应时间可以控制在100μs以内。
