嵌入式软件简单的可以直接写裸机代码,main函数里先初始化硬件然后就跑while死循环,里面放几个函数,比如控制电机,比如读传感器啥的;还可以通过定时器定时执行。
如果任务多了,比如目前要读10个传感器、控制5个不同的电机设备、切换串口、切换I2C进行通信,最后还有综合的控制算法,这样的逻辑就比较复杂。如果还用裸机的代码架构,逻辑互相耦合的太多,代码写起来容易乱套。
你也可以直接用别人写好的rtos去管理任务,但是我用的少,我用我自己这套更多些,觉得挺好用。
前置:你要了解什么是链表,然后看看基于C语言的构建链表的代码。
1、构建一个简单的链表
建立一个C文件和对应的H文件,可以命名为Link.c,Link.h。定义链表结点的添加函数以及链表结点的结构体等。(这里没写删除结点,插入节点。我目前遇到的嵌入式项目中软件的生命周期都是伴随着板子上电,所有任务都开始工作,断电就都结束,用不上删除和插入的功能。)
Link.c
S_Link* Link_Add(S_Link* node_1, S_Link* node_2)
{
if (node_1 == NULL)//头
{
node_1 = node_2;
}
else
{
S_Link* p = NULL;
for (p = node_1; (p != node_2) && (p->pNext != NULL); p = p->pNext)
{
;//直到p->pNext为空跳出
}
if (node_2 != p)
{
p->pNext = node_2;
}
}
return node_1;
}
分割线///
Link.h
typedef struct Link
{
struct Link *pNext;
}S_Link;
S_Link* Link_Add(S_Link* node_1, S_Link* node_2);
以上是最基础的链表代码,请根据项目自行添加内容即可。
2、构建任务管理架构
创建Task.c和Task.h,用来写任务架构的代码。可以分为三大类任务,一种是轮询任务,挨个儿执行即可。(比如不依赖时间,而依赖某些标志来执行的函数);二是非中断定时任务,在大概的时间范围内执行即可(一般在这里执行的函数,间隔时间都较长,比如读传感器,1秒1读,就可以放在这里);三是中断定时任务,由定时器的中断决定何时执行(与硬件直接通信的都需要在这里严格定时,保证时序,比如有的ADC你得用SPI通信,有的可编程驱动器得用I2C通信,反正与硬件直接相关的,放这里面就对啦)。
time_ms放在中断里累加即可,用来计时,单位是1毫秒。
Task.c
uint64_t time_ms = 0;
S_LoopTask* loopTask = NULL;
S_TimerTask* timerTask = NULL;
S_TimerTask* timerTask_Irq = NULL;
//遍历
void LoopTask_Traversal(S_LoopTask* task)
{
while (task != 0)
{
if (task->function)
{
task->function();
}
task = task->pNext;
}
}
void TimerTask_Traversal(S_TimerTask* task)
{
while (task != 0)
{
if(task->nextRunTime <= time_ms)
{
task->function();
task->nextRunTime += task->interval;
}
task = task->pNext;
}
}
void LoopTask_Add(S_LoopTask* task)
{
loopTask = (S_LoopTask *)Link_Add((S_Link *)loopTask, (S_Link *)task);
}
void TimerTask_Add(S_TimerTask* task)
{
timerTask = (S_TimerTask *)Link_Add((S_Link *)timerTask, (S_Link *)task);
}
void IrqTimerTask_Add(S_TimerTask* task)
{
timerTask_Irq = (S_TimerTask *)Link_Add((S_Link *)timerTask_Irq, (S_Link *)task);
}
Task.h
typedef struct LoopTask
{
struct LoopTask *pNext;
void (*function)(void);
}S_LoopTask;
typedef struct TimerTask
{
struct TimerTask *pNext;
void (*function)(void);
uint32_t interval;
uint64_t nextRunTime;
}S_TimerTask;
以上是简化过的任务结构体,你还可以加入任务开关,控制计数等其他成员。
3、以具体的例子来讲如何使用。
当前我们要使用pwm信号控制一个电机的转速。这里面可能会包含MCU的PWM初始化C文件,电机驱动的C文件,还有电机转速的控制算法、滤波算法等等C文件。
推荐在控制算法的C文件中加入一个全局的静态变量,然后在初始化函数的最后添加任务。
static S_TimerTask timerTask_MotorContorl = {NULL, Motor_Control, 250, 500};//250ms控制一次
void Motor_Control(void)
{
//具体控制算法
}
void Motor_Init(void)
{
//一系列初始化
//XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
TimerTask_Add(&timerTask_MotorContorl);
}
上一步我们已经构建好了定时任务的遍历,放在main函数中执行即可。
main.c
int main(void)
{
//xxxxxxxxxxx
while(1)
{
LoopTask_Traversal(loopTask);
if(isAllowTimerTaskRun)
{
isAllowTimerTaskRun= false;
TimerTask_Traversal(timerTask);
}
}
}
标志位放在定时器里置true,频率按照你要执行的所有任务的最小公约数来定即可。
4、总结
这样写就非常清爽,想加几个任务就加几个,每个任务都没有耦合,不会写成一大坨代码,调试起来也非常方便。
但要注意,每个任务里不要加入长时间的延时,不然后面就全堵住了!