声明:
这个是本人学习(韦东山)过程中的笔记和理解。
1.同步与互斥
比如两个人上厕所但只有一个位置,同一时间只能有一个人使用。这叫做互斥。
在车赛中,一个人负责软件,一个人负责硬件。软件必须等硬件把板子焊好才可以调试,这就叫同步。
在团队活动中,同事A已经使用会议室了,经理B也想使用,即使经理B是领导,他也得等着,这就叫互斥。经理B跟同事A说:你用完会议室就提醒我。这就是使用“同步”来实现“互斥”。
互斥:资源独占(会议室一次仅一人使用)。
同步:依赖协作(B需等待A通知后使用)。
有缺陷同步的例子:
如A要计算1->1000000消耗的时间,然后通过B把时间打印出来。
可以通过全局变量的方法来实现同步,就是B要等到A中的全局变量为1,而A要运行到结束才会把值置1,这样的话A本来只需要运行1s,B就可以拿到,但是经过调度,花在A和B上运行同样的时间,实际2s后B才能拿到。
所以用全局变量的方法效率很低。
我们可以等A运行完毕,然后唤醒任务B,这样效率就大大提高。
使用同步的时候,让那些等待的任务阻塞,不要让他们参与CPU的调度。
当要传递信息的时候,不可能A的信息没写好,B就过来读取他,此时得到的是一个错误的值。
Freertos的解决方案:
正确性。
效率:等待者进入阻塞
1.队列:相当于一个传送带,把做好的东西放到上面
2.事件组:把做好的事情设置为1.
3.信号量:放的是计数值
2. 队列(有阻塞,唤醒的环形缓冲区)
在实际过程中:
A是放数据的,B是接收数据的。B要接收数据但是队列中没有数据
1.阻塞,超时唤醒
2.阻塞,任务A发送数据到队列中,A不知道数据是给B的,A只会敲敲队列,队列知道要发送给B,因为队列有一个链表指向B。
同样A要发送数据,但是队列已经满了,此时
1.阻塞,超时唤醒
2.阻塞,任务B读取队列中的数据,B会敲敲队列,队列中空出位置,唤醒A发送数据,因为阻塞队列有一个A。
队列的本质
1.环形buffer
2.两个链表,一个(如果A想发送但队列满了,此时他就会进发送队列)放发送者,一个放接收者(如果B想接收但队列为空,此时他就会进接收队列)

如果任务B转到运行状态, --while(1){读取队列}--,但是队列中没有值,此时,他会从就绪链表里面移除,添加到 (队列的)receiver list链表和阻塞(delay list)链表中。

此时A唤醒接收链表里面的第一个任务,就是任务B,此时B进入就绪链表。
但是如果一直没有被唤醒,并且超过了读取队列的超时时间的话,也会将从receiver list链表和阻塞(delay list)链表中删除,并且进入就绪链表。
就像韦东山中,控制挡球板运动
1.通过红外接收按键
2.通过旋转编码器
按键按下,红外接收产生中断产生一个值,放进队列A,挡球板的任务接收到这个值就会运动
2.同理,旋转编码器中断接收到中断,产生的值放进队列B,然后编码器任务拿到队列b的值并且处理,然后再放到队列A。

挡球板移动的任务(读取队列A数据的任务)每次读取数据都会使挡球板运动固定个单位,如果编码器速度快,就会向队列A中多写几个数据,此时挡球板就可以一直运动。
用环形缓冲区的时候,读取的任务不会阻塞(会一直占用CPU资源)。用队列之后读取的任务会阻塞,其他任务就会执行,提高cpu效率。



对于写队列读队列,就是利用中断来写队列,写入队列的数据可以是个结构体,再通过任务来读队列,每次速度和方向是怎么确定的呢?比如方向是传入到队列的数据,速度就是传入数据的个数,如果一直读取到数据,挡球板就会一直运动。

旋转编码器的消抖问题:
在编码器快到达最左端的时候,像左滑动编码器,确始终抖动,滑不到最左边。
延时两毫秒没用,如果抖动大于2ms呢?
像上面一样,每次上升的间隔<2ms,认为是抖动,直接返回不处理。
应用:接收到的数据可以放在队列中,然后用接收函数来都队列。

3.队列集

如果像上面一样,每一个中断就写一个驱动程序,就会使用很多的栈空间。

我们可以使用轮询或者是队列集的方式来优化。(轮询如果要阻塞的话,效果不好)用队列集


4.用队列集增加姿态控制
在初始化的时候创建队列,所以我们要把各个函数创建队列的函数初始化,放在最开始,因为游戏一开始就要初始化,就要给各个创建队列。

我们先在初始化里面创建队列,然后队列集里面添加读队列,然后创建任务,把读到的数据传进姿态控制的队列。

任务参数的作用:就是当很多创建的任务都要用到同一个函数时,此时这个函数的参数就起到作用。

对于一个红外的三个键值控制3辆车,每当按下一个按钮的时候,红外按键会向队列写3个值,分别给到3辆车,只有那辆对应键值的车才会动,此时我们梳理一下程序。






后来就是每个car把接收到的任务读一遍,属于自己的就运动,不属于就静止(判断)。
5.信号量的本质
信号:提醒的作用;量:计数。

左边是队列:它需要有写队列,读队列,计数值,读队列的链表,写队列的链表,还有长度。
右边是信号量:长度、计数值还有接收链表。
写队列:1.把值copy进队列。2.cnt++。3.唤醒等待接收的队列。
读队列:1.把值copy进任务。2.cnt--。3.唤醒发送队列。
信号量的give(写入):1.cnt++。2.唤醒接收队列。
信号量的take(获取):1.cnt--。(只需要获取,不需要干别的)。
5.1 计数信号量



最后实现第一辆车子第二辆车子先行,等有一辆车子到达终点后第3辆车子才动。
5.2 二进制信号量
二进制信号量最大值是1,初始值为0。


但是存在问题,就是如果任务1优先级为2,任务2优先级为3,任务3优先级为4.
此时任务1首先开始运行,任务1拿到二值信号量,然后任务2开始运行,抢占了任务1,任务1进入就绪态,任务3开始运行,因为任务3没有拿到任务1的信号量进入了阻塞。这就造成了优先级高的抢占不了优先级低的。
例子:

当调用
函数启动发送,然后会调用
这个函数,这个函数如果发送完成就会释放信号量,
(阻塞,等待发送完成)这个就会得到信号量,发送完成。
6.优先级反转

此时就要对车的任务重新编写,因为车1车3需要读取信号量,车2不用。
7.互斥量解决优先级反转
使用互斥量,任务3运行时会提高任务1的优先级使其优先级等于任务3,然后任务1优先级就比任务2高,然后任务1释放互斥量,任务3运行。
同时使用互斥量解决临界区的问题:像互斥量上锁也只能由他开锁,可以解决临界区的问题。
8.事件组
对于队列,当写入队列的时候只能唤醒一个任务。
当读队列的时候也只能唤醒一个写入的任务。
有没有一种广播机制,一个任务(写入某种数据)可以唤起多个任务(接收)

等待的任务A,B。
他的构成就是:高8位是确定or/and的关系,后八位是满足任务开始的关系。
时间组是一个链表。当A和B是如图所示的任务时,写bit2时,任务A、B都不运行,写bit7时,任务B运行,写bit0时,任务A、B都运行。当写入任务时,会遍历整个链表。

第23个程序,就是先创建一个事件组,然后car1设置写入bit0,car2、car3设置bit0启动,此时car1到达终点后,car2,car3启动了。
第24个程序,就是先创建一个事件组,然后car1,car2设置写入bit0,car3设置bit0启动,此时car1或car2到达终点后,car3启动了。
第25个程序,就是先创建一个事件组,然后car1设置写入bit0,car2设置写入bit7,car3设置bit0和bit7同时启动,此时car1且car2到达终点后,car3启动了。
程序就是在上面信号量的程序上面修改就行了。创建信号量->创建时间组。然后按上述的逻辑写一下就行了
如果我们创建一个读I2C(读MPU6050的动作)的任务,读到数据写到队列中,然后vtaskdelay(50),此时cpu会访问这个任务,I2C很慢,会占用大量CPU资源。怎么办呢?
使用事件组,我们可以利用中断,当MPU6050检测到晃动,产生中断,写事件组。MPUTask()开始没有接收到事件时会阻塞。接收到事件之后就会开始读I2C并且写队列,挡球板可以开始运动。
9.任务通知

好像全是接收方的状态。

这个 分简化版和专业版。
下面说说简化版:
比如有任务A和任务B.
任务A发出通知给任务B(A喜欢B,给B买礼物),
任务B不取出通知(任务B处于不在等待通知的状态)(相当于B现在不喜欢A,东西放在那里,不去管他),
,但是A给B发了通知,所以B也处于
状态,
任务A一直送礼物(cnt一直++);当任务B想要接受消息的时候,
,此时就会看到A的消息。
实战:1.先把句柄准备好,待会要利用句柄发通知。


在任务1中给任务2发通知,此时任务2通知值+1,给任务3发消息,通知值为100.

任务3一直接收通知值,如果通知值是对的,就触发下面的功能。
10.软件定时器

定时器的删除,开始,复位都是通过写队列来完成的,开始创建定时器任务,然后开始调度


创建的是一次性定时器,时间超时就会执行回调函数。

那么软件定时器到底是什么?在接收到start,reset或者period队列信号后运行,在不运行的时候他是不是像普通任务一样一直等待队列呢?


调用这个函数相当于蜂鸣器的开启和定时停止,一开始开启蜂鸣器并设置频率,然后启动定时器的倒计时并修改周期为time_ms,时间到后就会调用回调函数停止蜂鸣器。


2462

被折叠的 条评论
为什么被折叠?



