嗯,我们看下消息对列,消息对列就是给一个任务发消息,他跟我们的任务通知是差不多的一个功能。嗯,但是他们几个是有所差异的,就是任务通知他可以代替消息对列的一个功能。 呃,消息队列,它可以一个任务可以从任意一个消息队列接受和发送,同时多个任务能够向同一消息队列接受发送通知。消息队列就是什么呢? 就比如说你有很多个任务,对吧?嗯,搞错了,应该是这里有这里有一些任务,对吧?然后呢?然后呢?任务任务之间他就可以通过定一个 消息对立,然后呢你通过给他,给他发消息,然后呢他就可以通过他取消息,那么你消息对立呢?你可以创建很多个消息对立 很多消息的店啊。那你可以通过他发消息,他取消息,也可以通过他发消息,通过他取消息能说很多个任务,甚至还不止一个任务, 还有多个任务,比如说他有个任务,他有个任务,那么你可以给我给他发个消息,给他发个消息,然后呢他就可以取消息,这就是相当一个缓冲的一个信件的快递件或者一个盒子。嗯, 然后我们知道了就可以清楚这个东西。然后我们介绍一下这个小姐姐, 出对阻塞,入对阻塞,阻塞时间是节拍, 那么串,然后我们要知道的就是 a p i 嘛? a p i 我们要掌握就是。嗯,串进消息热点就是串进一个它,然后是写录,我们是可以在前面写,也可以在后面写。 对,然后他就取嘛,就这样就这么简单,控制快,不用管,控制快很难的,比较底层 啊。 a p i, a p i 就是创建删除,创建删除要发送,对吧?发送接受,那么就没有了 注意事项,创建我们用的 e p s, 这个就是对列创建吧,然后呢就是, 嗯,就是定义对列了一个最大的长度,还有一个那个每个单元的一个大角, 然后呢就是创建静态的,静态的不用管,我们创建动态的,你要删除的话就是把这个句柄直接删除就可以了。然后发送,发送怎么发送? 就是 clean sens, 对面发送对立发送倒返回这, 呃,发送它有两个 a p i, 我们看对列发送,一个是距离,一个是指向发送对列的一个消息,一个是等待时间,就是它发送对列,它也是有阻塞。 嗯, 你要是中断了一个, 这是发送在对手的一个函数 啊,他只是用普通函数,然后就是中断函数的一个 number, 然后读读就是接生了, 就是 g b, 然后就是你的一个指针保存了数据,还有个等待时间,中调版本就没有了, 这里订立了这个对立,你前面要声明一下这个边脸,然后就可以用了。 像这样,这就是发送, 你是发送要定一个盒子或者区 buffer, 然后距离,然后就是 内容,这是发送的内容,要就是等待时间,都是传入的,都是地址来的, 然后呢就是发送,这里是发送,这是零等待。 hdm 就是发送 好,那么对立就到现在。
粉丝88获赞673

哈喽,大家好,我是 dk 四五,专注于单片机嵌入式物联网技术的分享。那今天呢,来给大家分享一个使用 freer toss 的过程中容易忽略掉的一个小问题,这个问题呢,是我曾经在使用 freer toss 的过程中真是遇到的呢, 当第一次出现的时候,我大概花了两到三天的时间才解决掉第二次呢,我大概花了十分钟,但是第三次的 肯定没有第三次了,我不可能在同一个地方栽倒三次吧。那今天呢,就来给大家来探讨一下我出现的是哪一个,哪些问题啊,以及我是如何解决掉的? 好,接下来我们来看我这个标题啊,使用 freestops 的过程中,中断无法得到及时处理好,那接下来呢,我就用图的形式来给大家来给大家描述一下这,这是一种什么样的 使用场景啊?就说我们在用单片机的编程的时候呢,用到中段对吧?中段都会有中段服务函数, 中段服务函数,那么中段服务函数呢?特别是在使用嵌入式实时操作系系统的时候呢?我们中断的服务函数都是尽可能的,尽可能的短啊, 尽可能的短就是尽快的让他处理完,处理完以后呢,就就可以退出这个中断,这样呢,你的 tus 就能得到一个响应。 好,那所以呢,我们不会在中断里面中断服务函数里面去做大量处理的代码。通常呢,我们都是设定一个标志,或者是发出一个事件啊,事件啊,或者是一个信号量,这样通知到一个任务啊, 通知到一个任务,任务呢,这个 toss 哥呢,他就会等待这里啊, wait wait 就是等待这个事件的发生,当这个事件到来了以后呢,他就会往下继续执行,执行你的一个处理的一个流程啊,处理完以后呢,他会重新返回来继续来等待。 这是我们在使用 freer toss 的或者是其他嵌入式操作系统的时候,和中断配合起来一个常用的模型。当然在逻辑编程下呢,我们可能也是借用类似的一个框架啊。好,那我们的问题点究竟是在哪里呢? 通常我们中断触发了以后,是需要一个得到一个及时的响应,对吧?所以当我比如说我按 按下按键的时候,按下按键的时候,这个中段就会立马得到执行,中段得到执行以后呢,他发出去这个这个,呃,比如说事件的标志吧,这时候呢,我们的 top 四个应该是也是立马得到执行的, 但是我曾经有一次发现,就是在我按下按键或者是其他中断的啊,然后触发以后呢,我的 task 等待了大概两秒钟以后才得到了一个执行, 所以当时就很纳闷,一直找不到根本的原因。后来呢,我仔细去翻译了弗瑞亚特斯的手册,才发现根本的原因是什么。 好,那今天呢,我顺便来打开弗瑞亚 toss 了,来给大家看一下啊,弗瑞亚 toss 的一个参考手册,来给大家先看一下啊。在这里 可以看到福瑞亚套子下面的他有很多接口的函数,对吧?但是从他的接口函数的名称呢,我们可以看出来一些,就比如说我针对呃信号量的信号量的,我可以看到里面有很多这种 from from isr 的,也就是 give from isr, 还有 take from isr, 还有除了信号量呢,还有一些就比如我们的 timer, timer 下面呢,也是有很多 framesa 的,那这个 framaisa 呢,从名称也可以看得出来,它是从中段里面调用的函数, 所以我们在中段服务函数里面要调用负压 toss 的接口函数呢,只能调用结尾是 fromesisr 的这个函数,所以呢这个地方大家是一定要注意的, 那吊用这个函数也没错,我也是按这样吊用的,但是呢,我的 touch 还是得不到执行,那这个原因到底是什么?那现在我来看一下我这个实际的代码,来给大家做一下分析啊,这是我现在写的捞辣的一个驱动的代码, 这个呢 lower gpiolq 含的了,这个是一个中段服务函数啊,中段服务函数呢,我们这里面去做了什么事情呢?可以看到这个几个 abos 都是大同小异的啊,我们只用来重点看一个就可以了。这第一个呢, 他是去检查去看一下这个 io 口呢,他有没有被,他有没有触发,如果他触发呢,就把他这个标志中段的这个标志清除掉啊,然后呢他就向任务发出来一个通知, 可以看到我这里调用的函数呢,也是从也是用福尔弗朗姆 isr 的,然后呢他就通知到一个 toss 的 dio 的这个任务里面去啊,然后接下来我们来看一下这个任务里面去做了什么 任务呢?就是这个 loladio task 这里面呢,这个任务首先会等,一直等在这里啊,他会一永远 post max d 类,就是永远在等在这里,直到有事件触发,他就是这里的事件已经发出来了,然后呢,这个任务呢,就可以继续往下运行, 然后他去判断这个直看是哪一个呃,哪一个银角来触发的这个中断啊,然后他就执行对应的一个呃中断的一个处理啊, io 零, irq 还得了,这个就是具体 io 零, dio 零他的处理的一个代码,他 这里面的代码可能会比较长一些,所以我们都是在 touch 的里面处理的啊。那问题就在于,当我这个 dio 处的 dio 触发的时候,他这个中断出发的时候,我这个 touch 并没有适时的得到运行, 那根本原因在哪里啊?实际上呢,他就在这个防磨 is 啊,这个函数,他这个结尾的有一个参数啊, 结尾有一个关键的性的参数,那这个参数是什么意思呢?我们看一下这个参数,他是一个拜特太补类型,拜特太补类型的贝斯太补类型啊,他贝特太补类型呢,他只有处和 foss, 就是相当于布尔类型的,是弗瑞亚 toss 下面定义的类型 支架,就像我们常见的布尔啊,就只有处和 force 的值,那当我们前面呢,是先把这个值设为 force 作为 force 以后呢,他是作为一个参数传进去的,同时呢,他有可以传出来一个参数,可以看到他是用指针的形式传进去啊,指针的形式传进去就证明呃,他是可以在这个福尔摩埃萨里面是可以修改到这个变量的一个值啊。 所以传出来以后呢,当这个 task notify 双木 sr 这个函数执行完以后呢,这个值就有可能变化,那他的变化代表什么意思呢? 当他是 boss 的时候呢,代表没有更高更高优先级的任务去已经就绪了,如果他为处的时候呢,就证明有帕斯科 已经处于就绪的状态了。我们这时候可以进行一个手动的调度啊。手动的调度呢,实际上就很简单,可以看到我下面最后只想 这样一条语句,那么以前我出现问题的时候呢,就是忽略了这样一条语句, 当然我这个参数也传进去了,但是我一直以为这个参数没什么卵用啊,后来我就没管他,但是出现问题我才发现这个参数还是我们需要去做一定的处理的。假如这个参数为处的时候呢,他就会去执行一个调度啊, 但是,但是呢,我们这里没看到,没有做判断啊,实际上他已经在 potear 的这个 fromois 这个函数里面去自己去做的一个判断啊,我们去跳过去看一下啊,哎,跳不过去,那我们来全局搜索一下,搜索一下,看一下这个函数的定义。 好,这还是 在这里定义的,那他的定义呢?可以看到又是指向了这个这个函数啊,这个红定义,这红定义他是什么意思呢?如果这个值为 不等于破为 b 不等于 force 的时候,也就是为真的时候呢,他就会执行一个 potel 的这个值,这个函数呢,就是去执行一次 freertos 的任务调度啊,所以呢,就可以理解了, 就是当我这里没有吊用这个函数的时候呢,可能我这个任务已经就绪了,但是呢,我没有手动去 调度他这个任务呢,他就会,他就没法得到立马的一个执行啊,因为福瑞亚透视他就相对来说比较灵活一点,他把这一这个操作呢留给用户了,他 并不会自己去自己去执行一个从中断退出以后,他并不会自己去执行这个调度啊,他会让用户来进行一个判断,你看你需不需要调度,如果你需要调度呢,你就去你需要这个任务立马得到执行呢,你就去调用这样一条函数接口啊, 然后当调用了这个以后呢,呃,这个 touch, 这个就绪的这个 touch 他就可以立马得到执行,所以呢,他中间就不会再有什么延时了, 那最根本的问题也就在这里,希望大家以后用 freertos 的时候呢,可以注意到这个问题点啊。好,那今天我们的分享呢就到这里,好,谢谢大家。

hello, 大家好,我是 michael。 在这个视频里面,我们会讲解一下 free rtox task 的创建和删除。首先啊, 让我们来看一下这个 menu 里面,这个 free rtos 的 这个 free rtos reference menu 里面啊,关于这个 task 的创建函数, ok, so, 嗯,我们来看一下啊,这个啊,这里是 task 创建函数 task create 这里是 task create static, 这是 task create restricted 啊,他有三个创创建函数啊,多数我们用第一个啊,我们看一看,这就是我们所需要的 task 创建函数 x task create 啊,这就是这个函数。 然后我们回到我们啊,最开始的 hollower 这个实力,我们来看看基于这一个啊, example code, 我们怎样来创建一个?创建一个 task 用这个,哎, we task create 这个 function。 首先啊,首先我们必须创建一个啊, task 主体,这是个 task 主体函数。 what and my task 啊,这是一个 task 主体函数,这个 task 的 imple pyramid 一输入参数是一个 word 指形, word 指针型啊, word 指针型的输入参数 为什么定义它?之所以啊,它是一个定义为 word 指针型。呃, 用这种方式的话,我们就可以强制转换任何其他类型啊,传,传入这个 task 啊,函数 在这个 task 函数里面,我们再显示的转换回来。好,那, 那我们看一下这个,这个 task 里面应该怎样编写这个代码。 好,我们先来看一看啊,这个 task created function, 这第一个就是这个 task coat the task function 这个函数的指针啊,这个函数的指针, 这个函数呢,它是一个,它是一个简单的 c 函数啊,没有返回,没有退出啊,它是一个无限,无限循环,无限循环体啊,一个 y loop。 然后呢,这个参数啊,是一个简单的这个参数啊,是一个简单的指针 指向一个 function, 是一个简单的指针指向一个 function。 好,那我们来啊,编写这个代码,在这个代码里面,首先它是一个无限循环的循环体,所以我们用啊 wire loop 啊,你也可以用 full loop why one 去创造一个这样的无线循环题。好,在这个循环题里面我们做什么呢?我们打印一些信息出来啊,我们从以前的扣里面拷贝一些信息,拷贝, 拷贝这个 hollower, okay, 我们拷贝到我们的 task function 里面。好,在这个 well loop 里面,我们不断的 print out 这个 hollower in the。 啊, in the task 表示这个信息是从这个 task 里面打印出来的。啊,这个 task 里面打印出来的信息。 好,下一步我们做一些延时啊, delay, 我们做一些 delay。 延时啊,在每一个 print alt 之间啊,有一些延时。好, 好,下一步啊,下一步我们啊,注视掉所有的源代码啊, remove 掉所有的源代码,去掉原来的 hello over 的源代码。在这个 a p p may 里面,我们要调用这个啊, task create 这个函数来创建一个 task。 okay, 我们要调用它来创建一个 task。 好,这是我们的 test create 函数。第一个参数啊,第一个参数是这个 test 的指针啊, 那么呢,就看一下这个。呃,函数啊,第一个参数是它的 task function 指针, 也就是我们现在创建的 my task。 好的,嗯,这就是我们的第一个参数。 第二个参数,第二个参数是它的 task name, 它的名字。 task name 啊,我们可以在这里写为 my task。 一啊,我们的第一个 task。 好,下一个参数,下一个参数是它的 stack, stack 表示分配有多少内存给这个 task, 这个 stack 是一个很大的呃呃, topic。 然后呢,我们啊会 讨论,以后再讨论,详细讨论。然后我们现在设置它为一零二四 e k 的 stock。 好, 暂时设置为这一个啊,具体的值得确定,我们会在以后的视频中讨论。然后下一个参数,这个 是 task 呃的传递参数,这个参数会由这个 create task 传递到 my task 里面去,通过这个 avoid 指针传递进去。如果不设置的话,我们可以设置为空 啊,设置为空 a, 下一个是 priority, priority 是 priority 是他的优先级别。这个啊,空闲 task 的优先级别是零,最低优先级别是零。所以我们可以设置我们的 task 为一 啊,比这个空闲 task 的级别高一个级别。然后下面这个是 task 的句柄啊,这个 task handle 啊,有很多的作用,我们可以 通过这个 task handle 获得许多关于创建的 task 的信息啊,比如说 task 名字啊,停止 task, 启动 task 等等。啊,在这里我们没有用到这个 task。 我们先设置它尾空。先设置它尾空。 好啊,啊,我们先简单的创建一个 task, 用这种方法先简单的创建这个 task。 好,我们保存这个,找存这个代码,在这个代码里面。呃,创建一个 my task, 这个 my task 是 print out hollower in the task print 这个 information。 下一步我们需要 bne, 按着下载。 好,我们在这里啊,这是 command three 啊,这个是芯片 test 芯片类型啊,这个是编译 project, 这是 fresh, 这是 monitor。 呃,这一个按钮呢?它可以编译下载和 打印信息一起啊,这一个是把前面三个命令集中在了一起啊,我们可以按一下,试一下,他就会先编一整个项目,他会先编一整个项目, 先对它进行 b a, e 啊,等于啊,需要一定的时间。 yeah, 生成了 e, l f 文件生成了并文件啊,生成了并文件,现在生成并文件好,编译成功,编译成功。 好,下一步,正在下载。好,正在 download 好, fresh done, fresh done, 下载成功,下一步,启动我们的创口监视器 monitor, 启动我们的 monitor 好,已经 print out hallower in the task, 这说明我们的 task 已经成功的创建,并且,呃,打印出我们 所需要的消息。 hello, what in the task 啊,这表示已经成功的创建了一个 task。 好,我们已经成功的创建了一个 task。 下一步就要啊删除这个 task, 删除这个 task 呢?呃,一个重要的方法是啊,获取这个 task 的句柄 task handle, 根据这个 task handle, 然后调用 task delete 函数来删除这个,删除这个。呃, 与 handle 相关的 task。 好,我们来看一下 该怎么做。 我们先拷贝啊,这个定义。 好,被这个 task handle 这个变量这个定义啊,好,搬到这,然后我们定义一个变量, 定义一个 my 啊,定义一个 my handle 是 task handle 类型的一个病量 好,这个变量复制为空,这个变量复制为空。在这个最后这个参数啊,在这个最后这个参数。我们先保存一下,把这个 my handle 这个这个变量,呃, 他需要传递到。啊,那再看一下这个啊, 看一下这个函数, 这个 task delete 这个函数,这个 task delete 这个函数会根据这个 task handle 啊删除。哎, 这个就是他是个 handle 所在的位置,我们需要传递这个,呃,地址,这个,这个任务问物巨丙的地址啊, 这个任务剧本的地址,所以我们应该的应该写代码,像这样写代码,写他的地址,取取这个 my handle 的地址啊,在这个最后这个参数这里。 好,然后呢,我们再调用这个 we task delete 这个函数,去去删除,除, 去删除这个创建了的函数。呃,创建了的任务去删除这个差,创建了的任务。好,这个据屏递换掉我们自己的据屏。好, 我们再进行一些判断,判断这个啊,句柄是不是为空,如果不,如果不为空的情况下,我们再进行删除, 如果不为空啊,我们对他这对这个任务进行删除。 对这个啊,与 my handle 相关的任务进行删除。现在我们格式化一下代码,你可以看到在 visual studio 里面啊, 啊,编写代码是多么舒服的一件事情,可以很容易的格式化代码。好,这就是 task delete function, 它会删除。然后我们又开始呃, b a e 啊,我们开始 b a e download 这个代码。这个任务啊,这个任务被创建之后马上被删除。 啊,这个是任务被创建之后马上被删除。然后我们看一下啊,这个,呃, print 信息是否还会输出? 是否是否仍然会?呃,像以前一样输出。 好,现在正在链接串进这个并文件好,编译成功,然后现在正在 download 好, fresh done, 然后我们启动这个监控,监控程序 monitor 查看这个信息。好,你,你会看到这个啊,你会看到 在这个 starting scheduler 之后, cpu start 之后没有输出任何啊。 hello, 我的信息,这意味着我们创建这个,创建这个,创建这个任务以后, 这个任务马上被删除了,他还来不及,他还来不及 print out 这些信息。所以啊,那我们应该怎样处理呢?来看看我们应该怎样处理 好。我们应该在这个删除之前,这个创建之后加一些延时啊,让这个 print out 在这个 print out 有时间执行让这个 print out 有时间执行。好, 好,我们再 copy 这个 delay 函数,我们 copy 这个 delay 函数加到这个 task create 的后面啊,然后再删除。删除这个任务,然后我们看是否可以 已打印出这个消息。那我们看,我们来,那我们来继续编译啊。编译,编译,这个项目 正在链接 好产生,并文件 编一成功,然后再 fresh fresh 成功, fresh done, 然后打开我们的 monitor。 好,你会看到 hollower in the task 被 print out 了一次啊, 被 print out 了一次,在我们呃延时之后被 print out 了一次,然后这个任务就被这个无一的例删掉删除了。 那我们现在改改它,改这个比例为八八千,看看我们会 print out 啊,多少次啊?看我们 print out 多少次,这个八千应该会 print out。 八 是 hello, 我,好,那我们来看一看他是不是会 print out 八次, 仍然按这个按钮啊。 build fresh and monitor 好,仍然开始编译这个项目。 链接 产生 e l f 文件好产 生这个,并文件好,编译成功,然后我们开始 fresh。 好,我们打开我们的新的 monitor, 打开我们的新的经验式创口顺序。 好,你会看到一个,两个,三个,四个,五个,六个,七个,八个。好,我们再来看一下 啊,一个,两个,三个,四个,五个,六个,七个,八个。 so 啊,他 print out 了,八个啊,信息八个 hollower in the task。 证明啊,这个 delay 是是成功的。 delay 了八,呃,八倍, 然后呢? delete 了八倍以前的时间,然后呢?这个,呃 task 的列函数删除了这个 task, 呃,删除这个 task 以后就。呃,再也不能 print out。 呃,任何的信息,再也不能 print out 任何信息。 好,呃,我们还有一种方法来删除这个 task, 那应该怎么做呢?我们在会在这个 task function 里面删除,然后我们先啊, 我们先把这一段代码给去掉,先把这一段代码去掉, 好,注视掉,把它注视掉,然后把这 这一段代码也注视掉,然后改这个地方是,呃,仍然为空,仍然为空。然后呢?我们在这个啊 拷贝这个 v task 得利函数记 my task, 把这个 my handle 设置为空, 当它设置为空的时候,这个 task delete 函数会删除当前的 task, 它会删除当前的 task。 这个 yo 循环呢,它会一直在这里循环,所以我们要去掉这个 yo, 去掉这个 yo, 让它有机会,让这个 task 有机会去跑这个 task delete 函数。然后呢?我们也 copy 啊,这个 print out information 选的这,呃,这个函,这些方式,这些代码,然后我们修改啊,第一次 print alt 为 first, 这是第一次 print alt 啊,改为 first first time。 第一次 print out, 这边呢就是第二次 print out second time 啊,这边呢就是 third time, 第三次,表示我们会 print 三次 however 出去,然后再 delete 这个 task 好, 然后再删除这个 task。 这种方法呢就是删除这个 task, 在这个 task 里面啊,在这个 task 里面自己删除自己,当这个 task 被创建以后,然后再自己把自己给删除掉。好,这就是这个 task, 这个 my task delete 掉,他自己先 print out 三次啊。 information, 然后我们仍然啊 build fresh and monitor, 然后来查看一下这个 oppo 信息。 好,来查看一下这些打印信息是否是跟我们想的一样。 好,他在 b a e 这个 项目, bne 这个项目 产生的,并文件编译成功。然后呢?啊,他正在 fresh, 好,开始运,开始啊,开始运行啊,他已经已经运行完了,我们来看一看。 在这里 hollower, first hollower, first hollower, second hollower, third, 这三个信息已经被啊打印出来了,这三个信息已经被打印出来了。在这个啊,在这个 函数,在这个任务里面,我们调用了 v task delete 这个函数来删除啊,这个函数本身,这个任务本身来删除这个任务本身。 好,这就是我们另外一种啊,这是我们另外一种删除任务的方法。这就是啊,我们 这个视频所要讲的任务的创建和删除,任务的创建和删除。好,这个视频啊,就到这里,我是麦克啊,我们下个视频再见,谢谢。

简单使用下 freertos 队列,队列是 freertos 数据传输方式的一种,可以用于任务间数据传输,也可用于任务与中断间数据传输。在之前的视频中,我们使用串口打印信息,在每个任务中都有使用,也就是这种结构。 这种结构中每个任务既需要产生数据,比如处理传感器数据等,又需要关心数据的显示,导致任务逻辑层次不清晰。每个任务都有显示程序,我们就可以使用对列,将程序分层一下,把显示相关程序单独拿出来,组成一个显示任务, 其他几个任务只需要将各自要显示的数据都写入队列。显示任务只需要从队列中读取数据进行显示,这样整体结构就变得很清晰。 任务一到三就可以专注自己的数据产生,而不需要关心怎么显示数据。显示任务就只关心怎么显示数据,而不需要关心数据怎么产生的。下面我们就使用对列简单实现一下这种结构。这是之前多任务视频的代码,我们先删除多余的程序, 每个任务只保留打印程序,然后保存编译一下没有错误,就可以开始本期实验了。 我们需要使用队列,所以要先包含一下队列的头文件。本次实验实现一下任务一任务二写队列,任务三读数据并打一显示。 首先我们需要创建一个队列,使用 x q create 可以动态创建一个队列。我们右键转到定义,看 下他的参数,这里他是使用了一个红定义,定义了一下这个函数,方便了我们的使用。第一个参数就是对列的长度,也就是对列可以存放多少个数据。第二个参数是每个数据的大小,单位是字节, 所以这里的参数就可以这样写。上面有两个任务需要写数据,对列长度我们就设置为二,而每个任务要写的数据是一个字符串,我们就设置二十个字节的数据大小吧。 现在创建队列参数已经写好了,可是创建的队列我们去哪里找呢?其实这个函数的返回值就是我们创建的队列,我们可以看一下函数原形,右键跳转过去,这里返回值是这种类型,我们也要定义这样一个变量,用来接收创建好的队列句柄, 复制一下回到我们的代码,在上方定义一个变量,随便起一个对列名字, 用这个变量接收一下返回值,这样一个队列就创建好了。更严谨的写法,还需要在下方判断一下返回值是否为空为空,队列创建失败,我这里就不写了。队列创建好后就可以写队列了, 现在开始修改任务函数,将打印程序注释掉,写对列。我们使用 xq 三的函数,可以右键转到定义,看下参数, 也是一个红定义函数。第一个参数是堆列句柄,第二个参数是数据的地址,这个数据的值会被复制进堆列。第三个参数是阻塞时间, 对列满无法写入新数据时,会阻塞我们设定的时间,回到我们的代码。第一个参数我们填入对列句柄, 第二个参数是数据地址,我们要先定义一下数据,在上方定义一个字符数组,数组大小就是创建对列时指定的数据大小,二十字节,不能随便定义大小。 我们初始化为这个字符串,将数组名填入第二个参数,第三个参数是组色时间,我这里写零,也就是无法写入数据时,函数会立刻返回,这样写入对列就完成了。我们把任务二同样的操作修改一下, 这里字符串改为任务二,写入对列完成后,就开始读对列并显示数 据了。我们修改一下任务三,读对列,使用 xq receive 函数,同样转到定义看下参数,这里和写对列参数基本一致,就不过多介绍了。回到我们的代码,直接写参数,第一个是对列句柄, 第二个是读取的数据存放地址。我们也定义一个二十字节字符数组,用来接收读取的数据。将数组名填入参数, 第三个阻塞,我们填入一个 port max delay, 也就是读不到数据就一直阻塞在这里。这样做的目的是没有数据时,就等在这里有数据才会继续执行下方的显示程序,这样就不执行无用代码,提高效率。 现在读对列已经完成了,我们开始写显示程序。显示之前要先判断一下是否读取成功。 右键转到定义,看下返回之类型。我们定义一个这种类型的变量,用来接收返回的状态。回到代码,在这里定义一下, 用这个变量接收一下返回值,读取成功会返回 p d two。 在下方判断一下返回值, 这里应该是 p d 处,写 p d pass 也没问题。然后把下方的显示代码放到 f 判断里面,修改一下,打印函数,打印接收数据 data 数组中的字符串, 这样就可以了,由于独对列没有数据是一直阻塞的,下方的 delay 也可以不需要了,现在我们编译一下,没有错误,我们点击进入 debug 看下现象,点击全速运行,可以看到串口是正常打 音数据的,这样就实现了任务键传输字符串。那要是需要传输很多不同类型的数据该怎么办呢? 大家可以暂停思考下,我这里告诉大家答案,可以使用结构体,具体怎么做,我们现在来写一下代码。首先我们要定义一下要传输数据的结构体类型,在上方定义一下结构体, 结构体中定义要传输的数据,我这里传输一下这个字符串和一个 int 变量。定义好结构体后,要修改一下创建对列,这里的数据大小,数据大小我们使用 size of 来计算一下结构体大小, 这样修改后再修改一下任务函数,先定义一个结构体变量,再 初始画一下结构题的字符串参数,下方这里数据要取地址这里我们再让变量艾字加下方的任务二,同样的方法修改一下, 然后修改一下显示任务,这里也要定义一个结构体变量,用来接收读对列数据,这里别忘了要取地址, 下方这里我们就打印一下读取的结构题数据,这样修改就可以使用对列传递结构题数据了。我们编译一下,进入 debug 看下现象,点击全速运行, 可以看到现象是正确的,这样就实现了任务间依次传递多种阐述了。本期的视频就到这里了,欢迎留言评论,我们下期见。

朋友们大家好,今天我来介绍属于 r t o s 操作系统的任务调度。之前的几期视频中,我们介绍了 r t o s 的概念,以及为什么要使用 r t o s 操作系统,然后介绍了如何在 free r t o s 中创建任务。 那么今天我来介绍 freert os 中任务调度的规则。一个单片机需要同时实现很多功能,比如屏幕显示、串口通信、电机控制、读取传感器数据等。这就如同一个人在同时做很多件事。 我们把单片机比作餐厅的大厨,他在忙碌的时候需要一个人操作好几个锅,同时炒好几道菜。如果让普通人去做这件事,难免会手忙脚乱,但是餐厅的大厨却可以轻松应对。同理, 我们在给单片机编程的时候,也希望一个单片机可以做尽可能多的事情,这样不仅可以简化硬件方案,也可以降低产品的成本,在市场上取得更大的竞争力。就比如一个厨师同时做几道菜这件事情 用裸机编程的思维该如何实现呢?大致流程会是下边这个样子,先进入闷函数,然后各种初始化,直到进入一个 vs 循环,在里边按照事先写死的顺序,一遍一遍重复执行。 这种操作方式的弊端在上期视频中我们已经讨论过,这里不再赘述,因为时间相隔久远,没有印象的同学可以去看之前的视频。如果把上边的代码移至到 freertos 中,则会是下边这样在经过一系列的初始化之后,把所需要的工 以任务的形式创建出来。这些任务在执行的时候没有固定的先后顺序,是通过一系列的规则来确定哪些任务先被执行,哪些任务暂时不能执行。 freerts 中的任务可以有以下几种状态,运行、就绪,阻塞、挂起。第一,运行。他是指当前任务正在执行,他处于运行状态,而且正在使用处理器。 如果运行 rtys 的处理器只有一个内核,那么在任何给定的时间都只能有一个任务处于运行状态。第二,就绪。指任务能够执行,但目前没有被执行, 因为同等或者更高优先级的不同任务已经处于运行状态,就绪状态不同于阻塞或者挂起状态。第三,阻塞。如果任务正在等待 时间或者外部时间,则任务处于阻塞状态。例如,如果一个任务调用 v 塔斯格迪丽函数,他将会被阻塞,直到设定的延时结束。任务也可以通过阻塞来等待队列信号量、事件组通知等。处于阻塞状态的任务通常有个超时时间, 超过这个时间后,任务将被解除阻塞状态,即使该任务所等待的事件没有发生。 阻塞状态下任务不使用处理器,时间也不能进入运行状态。第四,挂起。与阻塞状态下的任务一样,挂起状态下的任务不能被选择进入运行状态,但处于挂起状态的任务不会超时。 任务只有通过 spend 和 resume 函数才能进入或退出挂起状态。再知道一个任务可以处于的四种状态 之后,我们来看如何对任务进行调度。第一种调度方式,人群调度。我们规定一个非常短暂的时间为一个时间片,这里以一毫秒为例,假如有两个任务处于同一个优先级, 每个任务运行一个时间片之后,切换给另外一个任务运行,依次轮流运行,这种方式就是轮巡调度,就是处于同一个优先级的任务,他们依次轮流的执行每个任务,每次执行一个时间片。 第二种调度方式,抢占式调度是指调度器始终运行优先级最高,而且处于就绪状态的任务。当一个低优先级的任务正在执行时,发生外部中断,使一个处于阻塞状态但优先级更高的任务获得了 他正在等待的信号量,那么调度器就会停止当前正在运行的低优先级的任务,切换到高优先级的任务,即便当前任务的时间片并未结束。 freertos 默认使用强战士调度策略 会同等优先级的任务使用时间片。轮巡调度调度器不会永久更改任务的优先级,尽管他可能会因为优先级继承而暂时提高任务的优先级。 最近工作上的事情比较多,所以更新频率很低,后边有空的话会尽可能多做几期视频,那么这期视频就到这里了,谢谢大家。

简单介绍下 free r t o s 的内存管理,我们在移植 free r t o s 时,在 port 文件夹里复制了这些 hit 文件,这些文件都是内存管理文件,可是为什么我们在工程里只用到了 hip 四点 c 呢?我们先来介绍下为什么需要内存管理。 在我们创建任务时,有两种方式,动态创建和静态创建。在动态创建中,只需要提供一个任务对战大小和一个聚丙指针,不需要我们提供对战空间和任务控制快。这是因为动态创建任务时, freertos 会自动的帮我们动态分配这些资源, 而自动的动态分配空间就是内存管理。内存管理可以实现内存用到时分配,不使用时释放,在之后使用队列信号量等也都涉及内存分配。 使用内存的动态管理功能就不再需要自己提前规划各类对象和对战,简化了我们的程序设计。分配内存的方法就在这些 hip 文件中, hip 一到五这几个文件是不同的内存分配方法, 这张表就是他们的区别。我们先来看一下 hip 一, hip 一是指实现了 meleck 功能,没有实现 free 功能,所以它是只能分配内存,不能释放内存。它的实现原理就是先定义一个大数组,然后将合适的空间静态的分配给每一个任务, 如果某个任务或对列信号量等删除了,那么这个空间也没办法释放,所以它适用于只创建不删除的系统中。接下来我们再看一下 hip 二, hip 二也是在数组上分配内存,跟 hip 一不一样的地方 在于 keep 二使用最佳匹配算法来分配内存,并且支持了 free 功能。最佳匹配算法就是,假如我要创建一个任务,他需要十七字节空间。对,这里有这几块空闲内存, 算法就可以找出最小的能满足十七字结的内存,也就是二十字结这块空闲内存,然后会把这二十字结内存中的十七字结分配给任务,剩下的三字结仍然是空闲内存,可以被申请剩下的内存,情况就是这样的了。 之后在申请内存时,如果一直没有小于等于三字节,那么这个三字节空闲内存就一直不会被申请,就变成了内存碎片,这就是 hip 二产生内存碎片的原因了。我们再来看一下 hip 三, hip 三是直接调用的标准 c 库的 mylik 和 free 函数,由于标准西库的 malak 和 free 函数的实现过于复杂,占据的代码空间太大,不适合用在资源紧缺地嵌入式系统中, 所以我们一般不用 hip 三,这里就不过多介绍了。我们再来说一下 hip 四,跟 hip 一、 hip 二一样, hip 四也是使用大数组来分配内存, hip 四使用首次适应算法来分配内存,他还会把相邻的空闲内存合并为一个更大的空闲内存,这有助于较少内存的碎片问题。 首次试营算法就是,假如我们需要申请一个十二字结的内存,那么算法就会找到第一个符合大小的空闲内存块,也就是二十字结,而不会像 hip 二那样找到最小符合的内存块。十五字结,他把二十字结分为十二字结和 八字结,将十二字结分配给我们申请的剩下的八字结仍然是空闲内存可以被申请,当我们经过很多次申请释放后,内存可能就会成这样, 如果内存不能合并,此时再申请一个十五字节的任务空间就不能申请成功。而这里的十二八字节空闲内存就会称为内存碎片, keep 四会将相邻空闲的内存给合并到一起, 这样再申请一个十五字节的任务空间就能申请成功,也减少了内存碎片。与 hips 相比, hip 二不会合并相邻的空闲内存,所以 hip 二会导致严重的碎片化问题。我们都会使用 hip 四,而不使用 hip 二。最后我们再来看一下 hip 五。 hip 五分配内存释放内存的算法跟 hip 四是一样的, hip 五相比于 hip 四, hip 五可以管理多快分隔开的内存,在嵌入式系统中,内存的地址可能并不连续,这种场景下就可以使用 hip 五。 总的来说,我们在很多场景下都是使用 hip 四有多快内存会使用 hip 五,其他都很少用。到了今天的视频就到这里了,欢迎留言评论,我们下期见!


操作系统的这个消息队列,他在设计的时候实际上考虑了很多的异常,就比如说原子性啊、互斥性啊,好在设计这种消息队列的时候实际上已经做过一些处理了,那我们如果是自己去做这种信号量全局的话,你可能在这个终端里面可以去操作他,另外一个终端里面也可以去操作他。 然后这个中段的优先级如果你不太确定的话,那就可能会导致这个代码最终执行就跟你期待的不一样,你去上这个虎牙 q s 的 话,你其实任务之间的优先级都是可以去指定的,获取这个数据,你传递这个信号就可以更明确一些,它就可以避免一些诡异的 bug。

free r t o s。 跑着跑着就卡死,百分之九十是内存碎片惹的祸。你知道 free r t o s 几种内存碎片管理算法的区别吗? hepe 算法最简单,只能分配,不能释放,没有碎片问题,执行时间完全确定,适用于任务创建后永不销毁的超高可信系统。 hip 二算法增加了释放功能,但不会合并相邻的空闲内存块,这会导致内存碎片问题。因此在内存资源紧张、分配释放频繁的场景中已极少使用,仅适用于内存充足且分配块大小固定的简单场景。 hip 三算法 对标准 c 库的 malik 和 free 进行了一层现成安全的包装。它的好处是功能强大,但缺点是依赖标准 c 库的 malik。 free 会引入 liq 的 体积开销,且不同变易器行为可能不一致。在资源紧张的单片机上要慎用。 hip 四当前的主力军, 它在 hip 二的基础上增加了相邻空闲块自动合并功能,能极大地抑制碎片产生。它管理的是一整块连续的物理内存,对于绝大多数单片机项目,这是默认的首选方案。 hip 五功能最强版本,它拥有 hip 四的合并算法,且支持多块地址不连续的物理内存,例如 内部 sram 加外部 sdram, 它能把物理地址支离破碎的内存缝合成一个统一的逻辑堆空间。总结建议,常规项目无脑选 hip 四 内存分布复杂,例如如带外扩 ram 的 一般,选黑补即可。如果你需要更多的学习资料,你可以在评论区扣个进阶,我会私信发给你,带你掌握大厂不外传的底层方案设计。