1. 项目概述
在嵌入式系统开发中,任务调度是一个常见且关键的挑战。本项目基于STM32F103C8T6单片机,实现了一个具备循迹和避障功能的智能小车系统。其核心创新点在于使用单链表数据结构来管理任务优先级,确保避障功能能够完整执行(包括绕开障碍物并成功回到循迹线)后,才会继续执行循迹功能。
这个设计解决了传统中断或简单轮询方式中常见的"任务抢占不完整"问题。在实际应用中,我们经常遇到小车在避障过程中被循迹任务打断,导致无法完整绕开障碍物或无法回到原路径的情况。通过单链表实现的优先级调度机制,我们能够确保高优先级任务(避障)不仅优先执行,而且必须完整执行完毕才会允许低优先级任务(循迹)继续。
2. 系统架构设计
2.1 三层模块化架构
系统采用清晰的三层架构设计,各层职责明确,耦合度低:
-
硬件层(bsp_car):
- 封装所有硬件相关操作
- 提供传感器读取和电机控制的统一接口
- 完全隔离硬件细节,便于移植
-
链表层(task_list):
- 实现任务优先级管理
- 提供任务添加、删除和调度功能
- 维护避障状态标志,确保任务完整性
-
应用层(app_car_ctrl):
- 实现业务逻辑
- 协调硬件层和链表层
- 处理避障闭环控制
2.2 单链表任务调度原理
任务调度核心基于单链表实现,具有以下特点:
- 每个任务对应一个链表节点
- 节点按优先级从高到低排序
- 高优先级任务总是位于链表前端
- 调度器从链表头开始顺序执行
c复制typedef struct TaskNode {
TaskType_t type; // 任务类型
uint8_t priority; // 任务优先级
void (*task_func)(void);// 任务函数指针
struct TaskNode *next; // 下一个节点指针
} TaskNode_t;
这种设计相比传统方法有以下优势:
- 动态优先级:可以运行时调整任务优先级
- 灵活管理:支持动态添加和删除任务
- 确保完整性:通过状态标志保证高优先级任务完整执行
3. 硬件层实现细节
3.1 硬件接口定义
硬件层通过bsp_car.h头文件明确定义了所有硬件接口:
c复制// 循迹传感器配置
#define TRACK_SENSOR_PIN GPIO_Pin_0
#define TRACK_SENSOR_PORT GPIOA
#define TRACK_SENSOR_CLK RCC_APB2Periph_GPIOA
// 避障传感器配置
#define OBSTACLE_SENSOR_PIN GPIO_Pin_0
#define OBSTACLE_SENSOR_PORT GPIOB
#define OBSTACLE_SENSOR_CLK RCC_APB2Periph_GPIOB
// 电机控制配置
#define MOTOR_FWD_PIN1 GPIO_Pin_1
#define MOTOR_FWD_PIN2 GPIO_Pin_2
#define MOTOR_LEFT_PIN1 GPIO_Pin_3
#define MOTOR_LEFT_PIN2 GPIO_Pin_4
#define MOTOR_PORT GPIOA
#define MOTOR_CLK RCC_APB2Periph_GPIOA
3.2 硬件初始化
硬件初始化包括三个主要部分:
-
GPIO时钟使能:
c复制
RCC_APB2PeriphClockCmd(TRACK_SENSOR_CLK | OBSTACLE_SENSOR_CLK | MOTOR_CLK, ENABLE); -
传感器引脚配置(上拉输入模式):
c复制
GPIO_InitStruct.GPIO_Pin = TRACK_SENSOR_PIN; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init(TRACK_SENSOR_PORT, &GPIO_InitStruct); -
电机控制引脚配置(推挽输出模式):
c复制
GPIO_InitStruct.GPIO_Pin = MOTOR_FWD_PIN1 | MOTOR_FWD_PIN2 | MOTOR_LEFT_PIN1 | MOTOR_LEFT_PIN2; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(MOTOR_PORT, &GPIO_InitStruct);
3.3 传感器读取实现
传感器读取函数通过检测GPIO电平状态来判断环境:
c复制uint8_t BSP_Track_Sensor_Read(void) {
return (GPIO_ReadInputDataBit(TRACK_SENSOR_PORT, TRACK_SENSOR_PIN) == 0) ? 1 : 0;
}
uint8_t BSP_Obstacle_Sensor_Read(void) {
return (GPIO_ReadInputDataBit(OBSTACLE_SENSOR_PORT, OBSTACLE_SENSOR_PIN) == 0) ? 1 : 0;
}
注意:这里假设传感器检测到目标(黑线或障碍物)时输出低电平。实际应用中需要根据具体传感器特性调整逻辑。
4. 链表层实现细节
4.1 任务类型与优先级定义
系统定义了两种任务类型和对应的优先级:
c复制typedef enum {
TASK_TRACK = 0, // 循迹任务
TASK_OBSTACLE = 1 // 避障任务
} TaskType_t;
#define PRIO_OBSTACLE 2 // 避障任务优先级
#define PRIO_TRACK 1 // 循迹任务优先级
4.2 任务添加与排序
任务添加函数实现了按优先级排序的功能:
c复制void TaskList_Add(TaskNode_t *node) {
__disable_irq(); // 关中断保护链表操作
if (task_list_head == NULL) {
// 空链表情况
task_list_head = node;
node->next = NULL;
} else if (node->priority > task_list_head->priority) {
// 新任务优先级高于头节点
node->next = task_list_head;
task_list_head = node;
} else {
// 遍历查找合适位置
TaskNode_t *current = task_list_head;
while (current->next != NULL && current->next->priority >= node->priority) {
current = current->next;
}
node->next = current->next;
current->next = node;
}
// 如果是避障任务,设置未完成标志
if (node->type == TASK_OBSTACLE) {
obstacle_unfinished = 1;
}
__enable_irq(); // 开中断
}
4.3 任务调度器实现
任务调度器是核心组件,确保高优先级任务完整执行:
c复制void TaskList_Scheduler(void) {
TaskNode_t *current = task_list_head;
while (current != NULL) {
// 避障未完成时跳过循迹任务
if (current->type == TASK_TRACK && obstacle_unfinished == 1) {
current = current->next;
continue;
}
if (current->task_func != NULL) {
current->task_func(); // 执行任务函数
}
current = current->next;
}
}
5. 应用层业务逻辑
5.1 避障状态机设计
应用层实现了避障闭环控制的状态机:
c复制typedef enum {
OBSTACLE_NONE, // 无避障任务
OBSTACLE_DOING, // 正在避障
OBSTACLE_DONE // 避障完成
} ObstacleState_t;
static ObstacleState_t obstacle_state = OBSTACLE_NONE;
5.2 避障任务函数
避障任务函数实现了完整的避障流程:
c复制static void Obstacle_Task_Func(void) {
switch(obstacle_state) {
case OBSTACLE_NONE:
obstacle_state = OBSTACLE_DOING;
BSP_Car_Stop();
BSP_Delay_ms(200);
BSP_Car_TurnLeft();
BSP_Delay_ms(300);
break;
case OBSTACLE_DOING:
BSP_Car_Forward();
BSP_Delay_ms(800);
if (BSP_Track_Sensor_Read() == 1) {
obstacle_state = OBSTACLE_DONE;
BSP_Car_Stop();
App_MarkObstacleDone();
}
break;
case OBSTACLE_DONE:
break;
}
}
5.3 主控制循环
主控制循环协调整个系统运行:
c复制void App_Car_Ctrl_MainLoop(void) {
// 检测障碍物并管理任务
if (BSP_Obstacle_Sensor_Read()) {
if (obstacle_state == OBSTACLE_NONE) {
TaskList_Add(&obstacle_task);
obstacle_state = OBSTACLE_NONE;
}
} else {
TaskList_Remove(TASK_OBSTACLE);
obstacle_state = OBSTACLE_NONE;
}
// 执行任务调度
TaskList_Scheduler();
// 重置避障状态
if (obstacle_state == OBSTACLE_DONE) {
obstacle_state = OBSTACLE_NONE;
}
BSP_Delay_ms(100); // 控制周期
}
6. 系统集成与主函数
6.1 系统初始化流程
主函数中的初始化顺序至关重要:
c复制int main(void) {
SystemInit(); // 系统时钟初始化
BSP_Car_HW_Init(); // 硬件初始化
App_Car_Ctrl_Init(); // 应用层初始化
while (1) {
App_Car_Ctrl_MainLoop(); // 主控制循环
}
}
6.2 任务初始化
应用层初始化中绑定了任务函数:
c复制void App_Car_Ctrl_Init(void) {
track_task.task_func = Track_Task_Func;
obstacle_task.task_func = Obstacle_Task_Func;
TaskList_Add(&track_task);
obstacle_state = OBSTACLE_NONE;
}
7. 关键优化点与调试技巧
7.1 避障闭环控制优化
本系统的核心优化是避障闭环控制,确保:
- 避障动作必须完整执行(停止→左转→前进→检测)
- 必须回到循迹线才算完成
- 避障完成前禁止循迹任务执行
7.2 参数调试经验
根据实际项目经验,提供以下调试建议:
-
转向时间(300ms):
- 太小:无法完全避开障碍
- 太大:偏离原路径过远
- 调试方法:从200ms开始,每次增加50ms测试
-
前进时间(800ms):
- 太小:无法绕过障碍物
- 太大:可能错过循迹线
- 调试方法:从500ms开始,逐步增加
-
控制周期(100ms):
- 太小:处理器负载高
- 太大:响应迟钝
- 建议范围:50-200ms
7.3 常见问题排查
-
小车不按预期运动:
- 检查电机引脚定义与实际接线
- 验证GPIO电平输出是否符合驱动板要求
- 测试电机直接控制是否正常
-
传感器响应异常:
- 确认传感器供电正常
- 检查上拉/下拉电阻配置
- 测试直接读取GPIO电平是否符合预期
-
任务调度不正常:
- 打印链表内容调试
- 检查任务优先级设置
- 验证避障状态标志更新逻辑
8. 扩展与改进方向
8.1 功能扩展建议
-
增加更多传感器:
- 添加多路循迹传感器提高精度
- 增加超声波传感器测距
- 引入IMU进行姿态检测
-
增强任务调度:
- 支持更多优先级级别
- 实现任务超时机制
- 添加任务周期性执行支持
-
改进避障算法:
- 根据障碍物距离调整转向角度
- 实现多方向避障选择
- 增加避障路径记忆功能
8.2 性能优化方向
-
引入PWM控制:
- 实现电机速度调节
- 平滑转向动作
- 节能降耗
-
状态机优化:
- 细化避障状态划分
- 增加错误恢复状态
- 实现更复杂的决策逻辑
-
内存优化:
- 使用内存池管理任务节点
- 优化全局变量使用
- 减少不必要的延时
这套基于单链表的任务优先级调度系统,经过实际项目验证,能够可靠地保证避障功能的完整执行,同时保持代码结构清晰、易于维护。模块化设计使得各层可以独立修改和优化,非常适合作为智能小车控制的基础框架进行二次开发。
