一、动态静态任务的创建
void Task1Function( void *param )
{
while(1)
{
printf("1");
}
}
void Task2Function( void *param )
{
while(1)
{
printf("2");
}
}
void Task3Function( void *param )
{
while(1)
{
printf("3");
}
}
/*静态任务的TCB结构体和栈缓冲区放在main外面*/
StaticTask_t xIdleTaskTCB;
StackType_t xIdleTaskStack[100];//堆栈缓冲区
StackType_t xStackBuffer[100];//创建静态任务需要的类型
StaticTask_t xTask3TCB;
void vApplicationGetIdleTaskMemory( StaticTask_t ** ppxIdleTaskTCBBuffer,
StackType_t ** ppxIdleTaskStackBuffer,
uint32_t * pulIdleTaskStackSize )
{
* ppxIdleTaskTCBBuffer = &xIdleTaskTCB;
* ppxIdleTaskStackBuffer = xIdleTaskStack;
* pulIdleTaskStackSize = 100;
}
int main( void )
{
TaskHandle_t xHandleTask1;//Task1的句柄,可以定义为全局变量
#ifdef DEBUG
debug();
#endif
prvSetupHardware();//RCC和外设的初始化
printf("Hello, world!\r\n");
xTaskCreate(Task1Function,"Task1",100,NULL,1,&xHandleTask1);
xTaskCreate(Task2Function,"Task2",100,NULL,1,NULL);
xTaskCreateStatic(Task3Function,"Task3",100,NULL,1,xStackBuffer,&xTask3TCB);
/* Start the scheduler. */
vTaskStartScheduler();//开启调度程序
/* Will only get here if there was not enough heap space to create the
idle task. */
return 0;
}
静态任务的函数体和数据类型
普通任务的函数体和数据类型
二、进一步实验
现象 。 注意:更高优先级的、或者后面创建的任务先运行。
void *)4,(void *)5 类型匹配
//任务4和5的栈是不一样的,所以用同一个函数可以执行不同的效果
结束任务实验
void Task2Function(void * param) { int i = 0; while (1) { task1flagrun = 0; task2flagrun = 1; task3flagrun = 0; printf("2"); if (i++ == 100) { vTaskDelete(xHandleTask1);//杀死任务1 } if (i == 200) { vTaskDelete(NULL);//自杀 } } }
程序崩溃
三、任务状态
Tack相当于中断,1ms切换一次任务
![]()
四、两个Delay函数
在vTaskDelay退出来之前会更新&tStart 然后下次运行时就会用更新的tStart + 20
五、空闲任务及其钩子函数
对任务的清理工作是放在空闲任务里面的。如果自己自杀,那自己就不能清理内存。会导致创建失败,要让空闲任务杀死。下面自己自杀,导致内存不足,无法创建任务。
或者再创建一个空闲任务,再自己自杀也是可以的。这个时候可以创建钩子函数:
需要创建configUSE_IDLE_HOOK 为 1。创建空闲任务,调用钩子函数。清除内存。
六、任务调度算法
阻塞状态的任务,等待的事件分为两类:
1.时间相关的事件:两个Delay函数
2.同步事件:同步事件就是,某个任务在等待某些信息,别的任务或者中断服务程序会给它发送 信息。
发送信息的方法: 任务通知(Task notification)
队列(Queue)
事件组(event group)
信号量(semaphoe)
互斥量(mutex)
这些方法用来发送同步信息比如表示某个外设得到了数据。
这节课分为三部分的理解:
1.是否抢占? (配置项: configUSE_PREEMPTION)
![]()
2.允许抢占时,是否允许时间片轮转(是否轮流执行)(配置项:configUSE_TIME_SLICING)
3.允许抢占,允许时间片轮转,空闲任务是否让步?(配置项:configIDLE_SHOULD_YIELD)
七、同步与互斥
通过flagCalcEnd标志来通知任务2实现同步:
缺点是浪费cpu资源。最好让等待的任务进入阻塞状态或者进入休眠状态。需要用的时候再唤醒。这样就不用一直轮询任务2了,大大节省资源。
下面举一个串口互斥的例子
这样就可以实现独立打印一句话的功能了。但是也会出错。
我们知道,任务在没有执行完成时,只要1个Tick到了就会切换另一个任务 。
那么就可能发生下面的情况:
为了解决这些问题引入新的概念。细节后面再讲
八、队列
把数据写到队列头部不会覆盖原来的数据,队列中的数据使用环形缓冲区管理数据,把数据放到头部时,会先移动头部位置,并不会覆盖原来数据。
来学习队列的创建
然后往队列里发送数据
如果xTicksToWait不是零,那么他就会把这个调用这个函数的任务放到 List_t xTasksWaitingToSend ; 里面等待发送
读队列
如果xTicksToWait不是零,那么他就在List中等待
用队列实现同步:
首先定义Handle,创建队列
编写任务实现同步,两秒后打印val的值
用队列实现互斥:
InitUARTLock();在main函数中初始化一下。
同样的如果不Delay,就绪态的任务3,抢不过运行中的任务4。用taskYIELD()会主动发起一次任务切换,不用阻塞。
如果有很多不同的任务发送不同的数据,那么接收的时候如何区分?
当传输大数据时就可以传输他的地址:
九、队列集
邮箱也是队列,只不过长度为1.
从多个队列中得到数据(队列集)
在实际应用中,我们需要从多个队列中得到数据:
队列集放的都是队列。队列集实质上也是队列
任务1创建队列 任务2创建队列 任务3读QueueSet
十、信号量
无法通过信号量传输数据,只能表示数据的数量,核心是计数值。
信号量最小为0
前面我们用队列实现同步,现在我们用信号量实现同步:
使用信号量时不需要传递数据,不需要复制数据,节省空间和提高效率。
可以计数很多次,这里只计数一次
这个程序只要删除掉
打印出来的数就不是一千万。也就是无法保证数据的完整性。怎么解决这个问题?
阻塞一下或者用taskYIELD主动发起任务切换,让任务2运行
说明一下,任务2先运行,然后它被一直阻塞,直到有信号量可以获取,只要任务1give
任务二就可以运行。
用二进制信号量实现互斥
main函数创建
实验结果可以互斥访问串口。
十一、互斥量
互斥量用来保护临界资源。有了二进制信号量为什么还要有互斥量?
二进制信号量不能做到谁上锁就由谁解锁。这会导致A上锁 ,B开锁,C和A抢占资源。要解决这个问题就要做到:上锁解锁的代码成对出现,在临界代码中不要解锁。
互斥量可以提高优先(优先级继承)
优先级反转的解决方法是优先级继承
递归上锁
怎么解决
递归锁是互斥量的另一种形式
互斥量有两种,一种是普通的互斥量它具有优先级继承的功能,另一种是递归锁,他除了有优先级继承的功能外他还有递归的功能。
一种
另一种
做实验 常规使用
Handle和信号量一样,Take Give也是一样 。
不需要手动Give。互斥量的初始值就是1
实验结果互斥访问
实验优先级反转
实验递归锁,谁上锁谁释放
十二、事件组
事件组可以应用于 某个事件 若干个事件中的某个事件 若干个事件中的所有事件 但是不能用于若干个事件中的几个事件。
简单实现一下事件组的使用
,保留之前的队列
![]()
事件组不能传递数据,只是起到通知作用,等不到事件就会阻塞,数据保护还需要自己来做。比如使用队列。
同步点
xEventGroupSync的函数定义
十三、 任务通知
使用队列、信号量、事件组时,我们都要先创建对应得结构体,双方通过中间的结构体通信
队列的结构体
通知状态有三种取值
任务通知实现轻量级信号量
main函数里面只需要创建任务就可以了
任务通知实现轻量级队列
任务通知总是不能等待指定的事件,不能等待若干个事件中的任意一个,一旦有事件就会唤醒任务。
它指定的是在入口时要不要清除某些位,在退出的时候要不要清除某些位。
任务通知实现轻量级事件组
十四、定时器
定时器三要素:超时时间、函数、单次触发还是周期触发。
定时器的常规使用
定时器消抖
任务1,任务2不做事。
最后的效果是按好几次才捕获到一次按键输入。
十五、中断管理
使用 xTimerReset(xMyTimerHandle, 0);时不能等待
十六、资源管理
如果这个中断没有这么多,那么他会有转换