完善 freertos 移植的基本工程,本期视频完善一下之前移植的 freertos 工程,这三个文件是今天需要的, 分别是 freertos 配置文件和串口的初始化文件。我们先打开看一下吧, 这两个是串口初始化文件,用来实现 printf 打印信息,这里已经实现了 f port c 和 f get c 重定向了。 这是串口的头文件,这是 freertos 的配置文件,之前一只时候只是复制了很少的配置箱,这个文件是我整理的比较全的红配置,可以看到比之前的多了很多。 现在我们把这几个文件加入到工程,打开之前视频移植的 freertos 工程文件夹,找到这里的 config 同 头文件,把准备好的 config 头文件拖到这里,替换一下它。回到这里新建一个 app 文件夹,在 app 文件夹内新建一个 usrt 文件夹,用来存放串口初始化文件, 将这两个文件拖进来就可以了。接下来打开工程,将刚才的文件添加进工程,这里的 ctrl 文件已经替换为新的了。接下来把串口文件添加进来,先添加一下文件路径,再添加一下 rpp 组以及串口初始化文件, 这样文件就添加进来了。我们在梅函数这里包含一下头文件, 在 ma 函书里一定要用一下串口初始化配置函数,这样工程就 可以使用 printf 了。我们编译一下,这里有一个错误,是因为我们开启了静态分配内存,需要我们自己实现一下这个函数。把之前静态创建任务工程里的函数直接复制过来就可以了。 打开之前视频的工程,打开 user 文件夹下的没函数文件,复制一下这个函数,粘贴到我们的工程, 重新编译一下,可以看到已经没有错误了。我们现在来大概看一下这个 config 文件。这些配置的红定义我是怎么知道的呢?其实这些红配置在官网都是有的, 我们现在打开浏览器找一下,直接搜索 freertos 就可以了,这个网址就是他的官网了,点进去找到上方的内核 格,点击开发者文档,这里有一个 free rtos config 点 h 就是了。这个里面有着全部的红定义配置,下方还有对应红定义的解释,有不懂的都可以来这里找。 这些红配置是为了方便我们裁剪系统使用的,用到哪些功能就配置哪些红。回到我们的工程,大概了解几个常见的红配置, 这个 config usc preamption 是配置调度器强战与非强战的,现在非强战几乎不用了,所以这个配置唯一就好了。下方这里的是 cpu 和 system 的十种频率, 这个 config tickerate hz 是系统节拍,也就是任务切换的时间,这里一千代表一千赫兹,也就是一毫秒切换一次任务。下方这里有很多红定义,被 注视掉了,你需要哪个就可以将对应的红打开。最后我们再来看一下比较重要的红,这里的红是用来配置函数的,你要用到哪个函数就要把对应的红打开,不需要的就不打开,有助于减少内存占用。 最后这里这个 system 中断函数的红注视掉了,之前为了方便移植,就这样定义了一下,其实是不规范的,现在我们规范一下,搜索一下这个函数, 这个函数是系统开启调度器后用来上下文切换的,所以需要在 cst 中断中调用它。复制一下,打开 s t m 三十二 f 一零 x i t 点 c 文件,拉到最下面,找到 c s t c handler 中段入口函数。之前一直是后直接红定义, 他把这里屏蔽了,现在我们去掉注视,将刚才的函数在这里调用一下这里不知道这个函数在哪,我们 exter 声明一下, 现在这样系统也能运行,不过如果我们没开启调度器,这里还是一直执行,这样是不合理的, 我们需要做一下判断,判断调度器是否开启获取状态函数。需要开启 include x task gt。 schedule state 这个红才能使用这里之一就可以了。复制一下这个函数, 回到 siztic handler 中断入口函数,这里写一下判断。我们右键转到函数定义,看下返回值,这里可以看到,如果调度器没有开启,就会返回 task schedule not started。 复制一下这个返回值,如果不是这个返回值,就表明已经开启调度器了。回到中段函数这里,这里改为不等于这个状态。 现在有错误,找不到这个函数,我们在上方包含一下头文件,回到这里看到还有错误,他是 task 开头,表明在 task 文件内,我们在上方包含一下,回到下方这里 可以看到已经没有错误了。现在这样写可以运行,但是代码还不见状,这样写可以正确运行的前提是打开了这里的这个红定义,如果没打开,那么就会错误,我们还要修改一下 条件,判断一下这个红就可以了。回到中段函数这里,这里添加一下条件编译,如果这个红等于一,在编译下方 这个 f 判断,如果不等于一,就不编译他,这样代码才比较健壮。现在整个工程已经优化好了,我们编译一下 现在这个工程就可以方便使用红配置和使用 printf 打印信息了,我们来实验一下,这里任务对战大小不需要这么多,改成幺二八就可以了。上方这里测试代码就可以删除了。使用 printf 打印一下信息, 现在编译一下将 debug, 这里设置为模拟仿真,点击上方 debug 按钮进入仿真。我们要看一下串口打印的信息,点击上方这里选择 u r 的一,这里就是打印信息的地方了, 点击全速运行,这里就打印出来信息了,这样就比烧路径板子看现象方便了很多。今天的视频就到这里了,欢迎留言评论,我们下期见。
粉丝7309获赞3.0万


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 看下现象,点击全速运行, 可以看到现象是正确的,这样就实现了任务间依次传递多种阐述了。本期的视频就到这里了,欢迎留言评论,我们下期见。

哈喽,大家好,我是 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 的时候呢,可以注意到这个问题点啊。好,那今天我们的分享呢就到这里,好,谢谢大家。

简单介绍下 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 五,其他都很少用。到了今天的视频就到这里了,欢迎留言评论,我们下期见!

这个视频说一下 fleet r t o s 的任务料度,之前我们在前面的视频中介绍了 fleet r t o s 的时候,是不是遇到了新问题,什么问题呢? 就是 freetitos 这么多的任务中, cpu 是怎么知道哪一个任务是要先执行的,哪一个任务是后执行的呢?是这样的, freetitos 系统里面有一个东西叫任务调度器,我感觉嘛,就是这行 这行代码的功能吧,这个功能就是控制哪一个任务进行, cpu 被执行,哪一个任务要退出 cpu。 我们有我们有两种任务调度的方式,一个是潜藏式的调度,一个是时间片轮转 的调度方式。先说一下浅藏式调度,这里先记住一一句话,就是数,数字越大,优先级就越高,这是在 fleet rtos 里面的, 这个和我们裸机中的那个中段是不一样的,中段是相反的,如果你数字越大的话,他是入线级就越低。 我们在分任务的时候会把任会把任务的优先级,首先是设置好的高优先级的任务, 在抢占抢占式的调度中,高优先级的任务是可以打断低优先级的任务的运行,可以取得 cpu 的使用权的,这样就保证了那些紧急的任务可以得到优先的运行。比如这里是有两个任务, 一个是打游戏的任务,一个是回复信息的任务。你说为了让女朋友不生气的话,我们应该怎么去设置这个优先级呢?是不是?要 是不是要让这个任务二回复消消息的,这个任务的优先级要高一点呢?这里我们就设置这个回复信息的优先级是五吧, 这个是射程五,然后这个是射程优先级是三,那这样的话,无论我打游戏的紧急有多么的紧,我我打游戏的情况是有多么的紧急,比如我要五杀了,但是一旦 女朋友那边需要我回复信息了,如果这边要要我回复信息了,那我立刻的从这里从这个打游戏的这个 任务就切换到了回复回复信息的这个任务中。总结一句话就是在抢战士的调路中总是运行最高最高优先级的任务,最高优先级的任务, 对,这个时候就有可能有人会问,那中断,那之前裸机的那个中断方式还存在这个 fit r t o s 中吗?答案是存在的,这里的中断和裸机的中断一模一样。 中段是最高优先级的,无论你的你的任务的优的优先级,任务中的优先级有多高,中段一来,你这个 cpu 都要回到中段那里 去执行,用去执行那个中断的那个函数的总结就是这样,低优先级的任务会 被,会被高优先级的任务打断,但是这个最高优先级的任务也会被这个中断中断去打断低和高,这里就是这样, 就是就是这样的排序。然后接下来说一下的是时间片轮转掉路,这个这个掉路的方式的前提是 这个优是你所有的任务都是同优先级的,我们是这个前提就是同优先级的下的任务才有这个调度方式,比如这里打游戏和回复信息这两个优先级我们都设置为删吧, 然后这是任务一,这是任务二,然后我们这个如果你想一下就这些 任务,这个优先级相同的任务需不需要自己去控制一下自己?什么时候从 cpu 中说你要退出了?我要,我,我要进进去执行了,我要退出了,这个需要不需,不需要我们来控制他这个 这个退出和进进去呢?那是不需要的,在 freedios 中是不需要的,因为我们这个 freedidos 呢,是有那个时间片的这个轮转调度的。通俗来说就是我们 在这些相同的任务中,相同的任务中会设设置一个时钟,比如我们设置的是一毫秒什么的,如果你这个时间一到用这个相同的任务,比如相同的任务就会 切换吗?一秒钟一毫秒切换一次,就是这样的。然后比如这里我们先看一下这个例子吧,然后这个例子就是,比如这里的任务一是打游戏,他的优先级三,任务二是回复信息,优先级也是三, 你看到这里是没有了这个这个延时函数之前是有的,你看之前是不是有这个延 fit r t o s 的延时函数,现在的话这里是没有的。 然后我们之前也是说了,如果你一旦在这里延时的话,优先级一样的话,延时的话,如果你在执行这个打游戏的的时候, 一旦进入了延时,这个延时就会跳转到任务二中执行码,但现在没有了,那他是怎么跳的呢?他是会不会再跳到任务二呢? 会,会吗?这个就是时间片段轮转掉路的一个功能吧,就是他是这样的,如果我没有这个功能的话,那我是不是一直会会在这里打游戏?比如我们现在在在执行这个 cpu, 现在正执行的就是打游戏这个任务啊, 打游戏这个任务任务一吗?然后如果你不设置这个时间让他退出 cpu 的话,那是不是因为他是这里妙音是死循环,那永远都是在这个里占的 cpu, 是不是?那这样的会就会不合理,对,不合理。然后, 然后你,你现在就要设设置一个什么呢?一个时时间节拍,比如一毫秒,一毫秒这是什么?就我打游戏,打游戏 任务进入了 cpu, 一毫秒之后我就要退出 cpu 了,我这个打游戏的任务就要退出 cpu 了,就让下一个任务进来这里的任务就是回复信息嘛,就是任务二嘛,我们就 现在我一毫秒了之后,他这个打游戏就会退出,是吧?会退出,然后我们这个回复信息的时候就进入了 cpu, 一秒后我这个回复信息又退出,然后又到了打游戏进去就是一个反复循环,这这样这个就是时间片轮转调度的一个方式。 然后最后说一下,就是在 fleet i t o s 中,这两个这这里它是可以同时存在的。在 fleet i t o s 中,这两种调度方式是可以同时存在的。不同优先级之间的任务之间呢,就会使用的是 是潜藏式的这个优调度,然后同优先级任务之间呢,就用的是时间片段轮转轮转调度,就是大概对调度任务调度就是这样。


