RT-thread内核之线程调度算法

 一个操作系统如果只是具备了高优先级任务能够“立即”获得处理器并得到执行的特点,那么它仍然不算是实时操作系统。因为这个查找最高优先级线程的过程决定了调度时间是否具有确定性,例如一个包含n个就绪任务的系统中,如果仅仅从头找到尾,那么这个时间将直接和n相关,而下一个就绪线程抉择时间的长短将会极大的影响系统的实时性。当所有就绪线程都链接在它们对应的优先级队列中时,抉择过程就将演变为在优先级数组中寻找具有最高优先级线程的非空链表。

      RT-Thread内核中采用了基于位图(bitmap)的优先级算法(时间复杂度O(1),即与就绪线程的多少无关),通过位图的定位快速的获得优先级最高的线程。大致来说,就是每次调度的时间是恒定的:无论当前的系统中存在多少个线程,多少个优先级,rt-thread的调度函数总是可以在一个恒定的时间内选择出最高优先级的那个线程来执行。对不同优先级的线程,RT-Thread采用可抢占的方式:即高优先级的线程会“立刻”抢占低优先级的线程。

     RT-Thread内核中也允许创建相同优先级的线程。相同优先级的线程采用时间片轮转方式进行调度(也就是通常说的分时调度器),时间片轮转调度仅在当前系统中无更高优先级就绪线程存在的情况下才有效。每个线程的时间片大小都可以在初始化或创建这个线程时指定。在src/scheduler.c中:

复制代码
rt_list_t rt_thread_priority_table[RT_THREAD_PRIORITY_MAX];//就绪线程优先级链表数组在rt_schedule_insert_thread函数中将线程设置为就绪状态后,将当前线程链表节点插入对应优先级线程链表中
struct rt_thread *rt_current_thread; //保存当前运行的线程(在线程跳转时设置为目标线程to_thread)
rt_uint8_t rt_current_priority;
//保存当前运行线程优先级(在线程跳转时设置为目标线程to_thread的优先级)

#if RT_THREAD_PRIORITY_MAX > 32
/* Maximum priority level, 256 */
rt_uint32_t rt_thread_ready_priority_group;//32位二级位图,用于查找一级位图中32个字节的最低非0字节(即当前所有就绪线程中最高优先级对应的字节)
rt_uint8_t rt_thread_ready_table[
32]; //256位一级位图,代表32个字节,分别对应256个线程优先级。比如第一个字节的bit0表示优先级0,bit7表示优先级7。第二个字节bit0表示优先级8,bit7表示优先级15
#else
/* Maximum priority level, 32 */
rt_uint32_t rt_thread_ready_priority_group;
/32位位图变量,当Maximum priority level==32时,可看成一级位图,该变量32bit分别对应32个线程优先级(0-31)
#endif
复制代码

1、当最大优先级为8时:

若最大优先级取8,则位图变量可用一个字节表示,且取值范围为0-255,字节的每一位分别对应优先级0-7。当位图变量取0-255之间的任意一个数字时,它的最低为1的BIT位置都是预知的。我们可以预先将这位图变量的所有取值对应的最低为1的BIT位置(最高优先级)计算出来,并存成一张表格,而只需要查表即可,这个执行时间自然是恒定的。实际上,查表法就是一种常用的用空间换取时间的方法。在src/kservice.c中:

复制代码
const rt_uint8_t __lowest_bit_bitmap[] =
{
    /* 00 */ 0, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    /* 10 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    /* 20 */ 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    /* 30 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    /* 40 */ 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    /* 50 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    /* 60 */ 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    /* 70 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    /* 80 */ 7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    /* 90 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    /* A0 */ 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    /* B0 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    /* C0 */ 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    /* D0 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    /* E0 */ 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    /* F0 */ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0
};
复制代码

上表可由下面的python简单程序生成:

复制代码
#coding=gbk  
#打印一个字节的最低bit位,可能的值为0,1,2,3,4,5,6,7  
samples = 256  
  
def getlowbit(byte):  
    c = 0  
    for i in range(0,8):  
        if(byte & 0x01):  
            return c  
        c = c+1  
        byte = byte >> 1  
    return 0  
  
line =""  
for i in range(0,samples):  
    print "%d," %getlowbit(i),  
    if((i+1)%16 == 0):  
        print "\n"  
复制代码

2、当最大优先级为32时:

当进程优先级为8时,我们可以通过查表直接解决,但是当系统存在32个优先级时,如果直接制作表格的话,这个表格的元素个数将是 2^32 = 4294967296L= 4G字节,显然这是不可接受的。若当前最大优先级为32,即优先级位图变量可以使用u32型,也就是等价于4个字节,我们可以对这4个字节从字节0开始依次查表,如果字节0中非0,则最高优先级一定存在于字节0中,我们对字节0查表rt_lowest_bitmap,即可以得到最高优先级。 如果字节0为0,字节1非0,我们对字节1查表得到的是字节1中为1的最低bit位,然后加上8,就是系统的最高优先级。对字节2,字节3同样处理。当Maximum priority level==32时,则位图变量为:

/* Maximum priority level, 32 */
rt_uint32_t rt_thread_ready_priority_group;    //32位位图变量,当Maximum priority level==32时,该变量32bit分别对应32个线程优先级
复制代码
/* 
  * rt_thread_ready_priority_group 用来表示当前系统优先级位图。 
  * highest_ready_priority表示当前系统中最高优先级 
  */
这里仅仅说明如何获取最高优先级,在实际源码中可能有一些小改动。
 if (rt_thread_ready_priority_group & 0xff)  
 {  
     highest_ready_priority = __lowest_bit_bitmap[rt_thread_ready_priority_group & 0xff];  
 }  
 else if (rt_thread_ready_priority_group & 0xff00)  
 {  
     highest_ready_priority = __lowest_bit_bitmap[(rt_thread_ready_priority_group >> 8) & 0xff] + 8;  
 }  
 else if (rt_thread_ready_priority_group & 0xff0000)  
 {  
     highest_ready_priority = __lowest_bit_bitmap[(rt_thread_ready_priority_group >> 16) & 0xff] + 16;  
 }  
 else  
 {  
     highest_ready_priority = __lowest_bit_bitmap[(rt_thread_ready_priority_group >> 24) & 0xff] + 24;  
 }  
复制代码

3、当最大优先级为256时:

现在我们解决了32个系统优先级时的调度问题,现在来考虑线程优先级为256的情况。读者可能会想了,这没什么不同,256个bit=32个字节,依然采用算法3的思路,对着32个字节依次查表。问题是,当位图变量有32个字节时,对这32个字节依次查表耗费的时间就不可以忽略了,为了提升系统实时调度的性能,我们需要对算法3进行改进。为了解决这个问题,我们使用二级位图。即,256个bit由32个字节存储,每一个字节的8个bit代表着位图变量中的8个优先级,如果某个字节非0,则表示其中必有非0的bit位。rtt中对应的数组为:

rt_uint8_t rt_thread_ready_table[32];      //256位一级位图,代表32个字节,分别对应256个线程优先级。比如第一个字节的bit0表示优先级0,bit7表示优先级7。第二个字节bit0表示优先级8,bit7表示优先级15。

所谓二级位图,即我们首先确定32个字节中最低的非0的字节。为了实现这个效果,我们需要对这32个字节引入一个32个bit的位图变量,每一个bit位表示对应的字节是否为0。例如,这个32bit的位图变量的bit5为0,表示系统线程优先级256bit所分成的32个字节中的byte5为非0。为了区分,称这个32个bit的位图变量-字节位图变量 ,rt-thread中使用的是:

/* Maximum priority level, 256 */
rt_uint32_t rt_thread_ready_priority_group;//32位二级位图,用于查找一级位图就绪表中32个字节的最低非0字节(即当前所有就绪线程中最高优先级对应的字节)

显然我们查找系统系统最高优先级时,先确定非0的最低字节,这实际上依然是算法3,然后再对该字节进行查表,即得到该字节内最低为1的bit位,然后两者叠加(注意不是简单的加)即可。根据上面的分析,要想使用这个二级位图算法,rtt在跟踪线程的状态转换时,不仅需要维护256bit的位图变量数组rt_thread_ready_table[thread->number] |= thread->high_mask,还需要维护32bit的字节位图变量 rt_thread_ready_priority_group。参看如下代码

复制代码
在rtdef.h中定义的线程控制发块
/* priority */ rt_uint8_t current_priority; /**< current priority */ rt_uint8_t init_priority; /**< initialized priority */ #if RT_THREAD_PRIORITY_MAX > 32 rt_uint8_t number; rt_uint8_t high_mask; #endif rt_uint32_t number_mask;
注:只有当用户定义的最大优先级大于32个时,才会存在number和high_mask两个成员变量,这两个成员变量及另一个成员变量number_mask都是用来作位图运算用的,
只不过后面那个成员变量number_mask不管用户定义的优先级个数大于32还是在32个优先级以内都会存在。
复制代码
在thread.c中_rt_thread_init函数:
thread->init_priority = priority; thread->current_priority = priority;
复制代码
在thread.c中rt_thread_startup函数:
/* set current priority to init priority */
    thread->current_priority = thread->init_priority;//将当前优先级设置为初始值
/* calculate priority attribute */
#if RT_THREAD_PRIORITY_MAX > 32
    thread->number      = thread->current_priority >> 3;            /* high-5bit *///右移3位就是除以8,因为一个字节表示8个优先级。这样就可以得到当前这个优先级对应一级位图(32个字节)中的第几个字节
    thread->number_mask = 1L << thread->number;      //thread->number范围是0到31,将当前线程优先级所对应的一级位图字节在二级(32bit)位图变量中对应的bit置1,表示该字节代表的8个优先级至少存在一个就绪线程
    thread->high_mask   = 1L << (thread->current_priority & 0x07);  /* low-3bit */ //current_priority的低3位表示这个优先级在上面字节中的第几个bit
#else
    thread->number_mask = 1L << thread->current_priority;           //将当前线程的优先级在位图变量rt_thread_ready_priority_group中对应的bit置1,表示该优先级存在就绪线程
#endif
复制代码
复制代码
在scheduler.c中rt_schedule_insert_thread函数:
在rt_thread_startup函数首先调用rt_thread_resume函数,在resume函数中调用
rt_schedule_insert_thread函数,然后在insert函数中将线程状态设置为就绪状态,即所有在调度器中的线程均为就绪状态。
#if RT_THREAD_PRIORITY_MAX > 32
rt_thread_ready_table[thread->number] |= thread->high_mask;//将当前线程优先级所处一级位图(32个字节)中对应字节(由thread->number确定)的对应位(由thread->high_mask确定)置1,表示该优先级存在就绪线程
#endif
rt_thread_ready_priority_group |= thread->number_mask; //若最大优先级为32,则将当前线程优先级在位图变量rt_thread_ready_priority_group中对应的bit置1,表示该优先级存在就绪线程 //若最大优先级为256,则将当前线程优先级所对应的一级位图字节在二级(32bit)位图变量中对应的bit置1,表示该字节代表的8个优先级至少存在一个冰绪线程
复制代码

上文已说明,thread->number就表示当前线程优先级在32个字节的位图数组中的字节位置。为了提高效率,rt-thread另外使用了一个u32类型的变量rt_thread_ready_priority_group 来加快速度。如果这32个bit中某一个bit为1,就表示对应的某个字节非0(想想看,这意味着该字节所表示的8个优先级中存在就绪线程)。rt_thread_ready_priority_group变量为32位宽度,长度上等于4个字节,因此可以对每一个字节查表(上面生成的表格)就可以得到为1的最低的bit位置。概括起来就是,rtt首先确定32个字节的位图中,非0的最低的那个字节,然后再查表得到这个字节非0的最低那个bit。这两步骤正好可以利用两次上面的表格__lowest_bit_bitmap。

4、在线程调度时获取所有就绪线程优先级的最高优先级:

复制代码
在ksevicer.c中:
int __rt_ffs(int value)//该函数用于获取32位value第一个bit位为1的bit值加1。以低8位为例,0x00--0(特殊情况),0x01--0+1,0x02--1+1,0x03--0+1,0x04--2+1
{
    if (value == 0) return 0;

    if (value & 0xff)
        return __lowest_bit_bitmap[value & 0xff] + 1;

    if (value & 0xff00)
        return __lowest_bit_bitmap[(value & 0xff00) >> 8] + 9;

    if (value & 0xff0000)
        return __lowest_bit_bitmap[(value & 0xff0000) >> 16] + 17;

    return __lowest_bit_bitmap[(value & 0xff000000) >> 24] + 25;
}
复制代码
复制代码
在scheduler.c中rt_system_scheduler_start函数:
#if RT_THREAD_PRIORITY_MAX > 32
    register rt_ubase_t number;        
  number
= __rt_ffs(rt_thread_ready_priority_group) - 1;//number为一中间参数,表示根据二级位图rt_thread_ready_priority_group查表得到一级位图32个字节中最低非0字节,取值范围为0-31
highest_ready_priority
= (number << 3) + __rt_ffs(rt_thread_ready_table[number]) - 1;//查表得到最低非0字节所代表的8位中最低为1的位(取值范围为0-7),再与最低非0字节乘以8相加得到最高优先级(0-255) #else highest_ready_priority = __rt_ffs(rt_thread_ready_priority_group) - 1;//若最大优先级为32,则直接根据32位位图变量查表得到32位中最低为1的位(取值范围为0-31),即最高优先级 #endif 在scheduler.c中rt_schedule函数: #if RT_THREAD_PRIORITY_MAX <= 32 highest_ready_priority = __rt_ffs(rt_thread_ready_priority_group) - 1;//若最大优先级为32,则直接根据32位位图变量查表得到32位中最低为1的位(取值范围为0-31),即最高优先级
#else register rt_ubase_t number; number = __rt_ffs(rt_thread_ready_priority_group) - 1;//number为一中间参数,表示根据二级位图rt_thread_ready_priority_group查表得到一级位图32个字节中最低非0字节,取值范围为0-31
highest_ready_priority
= (number << 3) + __rt_ffs(rt_thread_ready_table[number]) - 1;//查表得到最低非0字节所代表的8位中最低为1的位(取值范围为0-7),再与最低非0字节乘以8相加得到最高优先级(0-255)
#endif
复制代码

 5、线程失去占用cpu时参数变化:

主动失去cpu:(1)调用sleep,delay函数使用线程放弃CPU;(2)等待信号量,互斥锁,事件,邮箱或消息队列过程中调用suspend使线程挂起。

                   在线程主动失去CPU时,程序都会在rt_thread_suspend函数中执行rt_schedule_remove_thread函数,将当前线程从调度器中移除。

                   在rt_schedule_remove_thread函数中执行rt_list_remove(&(thread->tlist));将当前线程从调度器中移除,同时将该线程优先级对应的位图变量所在位清0。

被动失去cpu:(1)线程的时间片耗尽,被迫放弃CPU;(2)系统产生中断,线程暂时失去CPU,一旦中断例程执行完,还是会还原,这些是由硬件自动完成的。

                  被动失去CPU时调用线程让出rt_thread_yield函数(这里指(1),(2)完全由硬件来完成,不需要软件干预),此函数中程序会执行rt_list_remove(&(thread->tlist));即将当前线程从调度器中移除,

                  然后再执行rt_list_insert_before((rt_thread_priority_table[thread->current_priority]),&(thread->tlist));将当前线程加入到调度器中对应优先级的就绪线程链表末尾

                  紧接着执行rt_schedule();重新调度线程。在被动失去CPU的过程中,程序并未操作与获取线程最高优先级算法相关的几个参数。

复制代码
在scheduler.c中rt_schedule_remove_thread函数:
该函数在rt_thread_suspend函数中调用,调用之前在suspend函数中会将线程状态设置为挂起状态,即所有从调度器中移除的线程(不包括detach和delete函数中的移除)均为挂起状态。
/* remove thread from ready list */ rt_list_remove(&(thread->tlist));//重置线程链表节点为初始值,即节点next与prev均指向自身节点,即将当前线程从调度器中移除 if (rt_list_isempty(&(rt_thread_priority_table[thread->current_priority])))//若当前优先级线程链表中不存在就绪线程 { #if RT_THREAD_PRIORITY_MAX > 32 rt_thread_ready_table[thread->number] &= ~thread->high_mask;//将该线程优先级在一级位图中对应字节的对应位清0 if (rt_thread_ready_table[thread->number] == 0) //若该线程优先级在一级位图中对应字节的对应位清0后,该对应字节仍然为0,则说明该对应字节所代表的8个优先级均不存在就绪线程 { rt_thread_ready_priority_group &= ~thread->number_mask; //若该线程优先级在一级位图中对应字节所代表的8个优先级均不存在就绪线程,则将二级位图中对应位清0 } #else rt_thread_ready_priority_group &= ~thread->number_mask; //若最大优先级为32,则将32位图变量中当前线程优先级的对应位清0,表示当前优先级不存在就绪线程 #endif }
复制代码

整个调度算法分析完毕,具体算法分析可参考http://blog.csdn.net/prife/article/details/7077120

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
RT-Thread作品秀】智能管家精灵作者:七星岩 概述(说明应用产生的背景、实现功能)主控平台以 ART-PI 开发板为核心,主要任务是抓取网络数据、如天气、疫情等,并由OLED实时显示最新数据,同时将抓取的数据当做log文件存储在SD卡上,供后续分析使用。 并推送室内环境信息如温度,湿度到ONENET云端,,后续将扩展更多室内传感器,并同步的云端。由于时间原因,计划要做的摄像头室外监控,图片、视频传送没有在本次提交中实现。 开发环境(所采用的软、硬件方案)硬件: 主控:ART-PI(STM32H750XB) MicoKit-Ext 板 软件: RT-Thread 版本:RT-Thread V 4.0.3 开发工具及版本:RT-studio 2.0.0 RT-Thread使用情况概述(简要总结下应用中RT-Thread使用情况:内核部分、组件部分、软件包部分、内核、其他)(1)内核部分:调度器,信号量,libcpu/BSP。 调度器:创建多个线程来实现不同的工作。 信号量:用来同步线程。 libcpu/BSP:UART 驱动。 (2)组件部分:UART 框架, UART框:使用UART框架来与ART-PI-DOCK扩展板进行数据交互。 Fal Flash 抽象: Flash 抽象层,是对 Flash 及基于 Flash 的分区进行管理、操作的抽象层,对上层统一了 Flash 及 分区操作的 API。 LittleFs: 适合嵌入式系统的小而安全的文件系统 (3)软件包部分: EasyFlash: 一款开源的轻量级嵌入式Flash存储器库 cJSON:C 语言实现的极简的解析 JSON 格式的软件包。 Dht11: 读取DHT11数字温湿度传感器的温度和湿度值。 WebNetClient 软件包:由 RT-Thread 自主研发的,基于 HTTP 协议的 Web 服务器实现,它不仅提供设备与 HTTP Client 通讯的基本功能, 而且支持多种模块功能扩展,且资源占用少、可裁剪性强,充分满足 开发者对嵌入式设备服务器的功能需求。 Onenet: RT-Thread 针对 OneNET 平台连接做的的适配,通过 这个软件包,可以让设备在 RT-Thread 上非常方便的连接 OneNet 平台,完成数据的发送、接收、设备的注册和控制等功能。 MbedTLS: C编程语言以最小的编码占用空间实现了 SSL/TLS 功能及各种加密算法,易于理解、使用、集成和扩展,方便开发人员轻松地在嵌入式产品中使用 SSL/TLS 功能. Netutils: RT-Thread可用的全部网络小工具集合。 Pahomqtt:是 Eclipse 实现的基于 MQTT 协议的客户端,本软件包是在 Eclipse paho-mqtt 源码包的基础上设计的一套 MQTT 客户端程序。 u8g2:单色屏驱动,现在移植到了 rt-thread,可以满足各种各种各样的需求。 硬件框架(概述应用所采用的硬件方案框图,并对核心部分做介绍)硬件部分主要是ART-PI + 一块IOT外扩板 软件框架说明(介绍应用所采用的软件方案框图、流程图等,并加以解说)软件部分也相对简单,基于ART-PI 的WiFi样例程序,加上几个独立的模块(流程图见文档) 按键扫描模块 OLED数据显示模块 DHT11设备温湿度读取模块 心知天气预报API解析模块 疫情数据解析模块(不完善) 本地数据上传OneNet云 软件模块说明(介绍应用软件关键部分的逻辑、采用的实现方式等) 主要函数说明: (1)get_Weather_Data():读取并解析心知API天气数据,写入封装数据结构,保存到SD卡log文件。 (2)get_NCOV_Data:读取并解析腾讯疫情API数据写入封装数据结构,; (3)oled_display:实时扫描三个按键,并得到显示状态,并根据当前状态显示指定页面; (4)main_interface:,根据网络通断显示图标,根据按键左移右移显示当前状态,根据选中主题,显示指定页面; (5)oled_draw_menu:画出主页面布局 (6)oled_time_show:获取实时时间,并在OLED屏幕,画出小时:分钟,和年月日。 (7)oled_weather_report:在OLED屏幕画出室内温湿度和天气预报的温度天气情况。 (8)oled_covid_report:在OLED屏幕画出疫情解析所得数据。 (9)onenet_upload_entry:与onenet同步任务。 (10)onenet_mqtt_upload_digit:向onenet网络平台上温度传数据。 (11)key_thread_entry:按键扫描任务。 演示效果(演示效果请采用3张高清图片,并录
RT-Thread作品秀】物联网智能门锁 作者:张林 概述 随着人们生活水平的提高、物联网技术与人工智能技术的大力发展,住宅小区的智能化发展已成科学发展的必然趋势,智能化小区是当代计算机科学在住宅产业的综合应用,集成了信息、物联网、传感器、人工智能算法等技术,旨在改善住宅质量、住房舒适度、提高生活水平。继互联网、计算机科学之后,物联网技术成为第三次信息产业发展的浪潮,在不久的将来、物联网技术将会像互联网一样走进人类社会,深入我们的生活,本文根据物联网智能小区的设计理念进行了一些探索,并对智能小区的设计实现提供了思路或解决方案。 开发环境(所采用的软、硬件方案) 硬件:ART-PI、OPENMV、继电器 RT-Thread版本:4.03 开发工具及版本:RT-Thread Studio V1.5 RT-Thread使用情况概述 内核部分:信号量、互斥量、邮箱、线程管理、定时器 组件部分:wlan组件、PIN设备 软件包部分:Cjson、fal、kawaii_mqtt 硬件框架 通过art-pi控制openmv、采集openmv发送的信息、进行开关门处理,并通过mqtt发送至腾讯云服务器。 软件框架说明 启动程序后、进行硬件系统初始化、初始化内核对象、初始化相关软件包,初始化openmv,采集一openmv数据后送入邮箱线程,等待mqtt连接成功后将邮箱数据发送至服务器。 软件模块说明 数据采集线程采集到数据后送入全局邮箱、mqtt线程获取邮件、并将其发送至云服务器。 演示效果 比赛感悟 时间过得很快,rtt全连接大赛就要结束了,很感谢主办方提供给我这样一个机会去锻炼自己,去做自己喜欢的事情,在本次实践中我学会了很多东西,加深了对rtt操作系统的学习印象、掌握了rtt的使用方法、相信在不久的将来,这款操作系统将会在我的更多项目中得到应用、我会尽自己最大努力去提供一些rtt现在没有的传感器软件包、为rtt开源生态贡献自己微小的一份力量。这一路走来、有苦有甜,这些都是可贵的经历、是短暂大学生活浓彩画的一处斑斓。很感谢老师提供了这样一次锻炼自己的机会、在这次实践中、我深刻的认识到了rtt系统,认识到了ART-PI这款开发板的适用性,相信通过本次实践、我会更加喜爱嵌入式开发、将自己所学的知识运用到实际生活中,去解决更多的现实问题。
RT-Thread作品秀】写字机器人作者:乔城阳 概述(说明应用产生的背景、实现功能)写字机器人在实际生产线上由于效率赶不上打印机,应用不是很广,然而由于其结构简单,成本低,并且符合人手写风格,在学习阶段很有价值。写字机器人设计上包含了路径规划、直线插补、加减速控制等常用电机运动控制算法,在软件上也会用到DXF文件解析、openCV图像处理等G代码生成工具,对后期深入研究激光切割机、雕刻机、3D打印机等大型设备有很好的铺垫作用。因此我的作品以写字机器人为题目分享我的制作过程和学习体验,希望对各位小伙伴的学习有所帮助。 开发环境(所采用的软、硬件方案)硬件:ART-PI、arduino、TM4C123GXL RT-Thread版本:3.14 开发工具及版本:MDK-ARM5.31、VSCode RT-Thread使用情况概述(简要总结下应用中RT-Thread使用情况:内核部分、组件部分、软件包部分、内核、其他)内核部分:线程调度,资源分配、同步通信、设备驱动框架 组件:DFS文件系统、UART串行异步通信、CAN通信 其他:UDP通信、cJSON编解码 硬件框架(概述应用所采用的硬件方案框图,并对核心部分做介绍)软件框架说明(介绍应用所采用的软件方案框图、流程图等,并加以解说)在写字机器人设计上我将其分为三个部分: 图像处理模块 运动控制模块 G代码生成模块 图像处理模块负责把文本图像和照片等图像文件做预处理,去除杂点,然后以合适的算法将图像二值化,突现主要信息,然后提取轮廓骨架以适合机器书写。 G代码生成模块将图像轮廓序列化后进行路径规划,选取一个适合的加工精度,然后按数控加工常用的G代码规范生成NC加工文件。另外对于标准的DXF文件则通过文件解析的方法生成G代码。 运动控制模块是写字机器人中最杂的一个模块,负责各个运动机构的精准快速移动。收到G代码指令后需要结合前后指令信息生成当前指令的合适初速度、最大运行速度,根据运动轨迹进行直线插补点,最后按SPTA梯形加减速算法向电机发出脉冲。 软件模块说明(介绍应用软件关键部分的逻辑、采用的实现方式等)Grbl是一种高性能低成本的开源CNC控制器,基于ATmega328型芯片输出高速精准的电机控制脉冲,完美支持各种标准G代码,并包含了完整的前瞻性加速控制,可以实现平稳的加速和无冲击的转弯动作。 正是由于grbl的前瞻性速度控制功能,所以要保证grbl控制器内一直保持着16-20个G代码指令。这需要ART-PI通过读取grbl控制器缓冲区状态,及时发送G代码指令,避免出现缓冲溢出和缓冲区为空等现象出现。 因此需要运行RT-thread实时操作系统,通读读取SD卡上的NC文件,并与grbl控制器通信,控制设备正常运行。另外RT-thread也可提供丰富的人机交互功能,将设备工作路径、进度等状态实时显示在屏幕上,也可以增加暂停续写等功能。 演示效果(演示效果请采用3张高清图片,并录制一段不少于1min视频解说应用所实现的效果,视频上传至B站或者腾讯视频或其他视频平台,给出链接即可)演示视频 比赛感悟同样是C代码,为什么别人写得那么好,这次比赛用到两个开源软件,一个是RT-thread,负责人机交互和发送控制指令,另一个是grbl,用来完成运动控制。两款软件都设计的非常精巧,只需在2kB大小的RAM上运行,而功能却又非常强大,实现了很多复杂的功能,所有的代码封装的很好,模块启用只需打开对应的宏定义即可。这次专门去图书馆借阅了RTT相关的图书,了解线程是如何调度,设备驱动应该怎么实现,但是内部还是有很多内容不理解,后期需要继续不断充实自己。运动控制一直是我的兴趣所在,这次也终于有机会把它实现,看着电机在一行行指令的控制下乖乖地转动,听着不断变化着频率的电机声音,真正体会到了伺服servo的含义,非常开心~
RT-Thread作品秀】基于RT-Thread的姿态解算控制平台设计作者:黄国盛 概述是一个姿态解算算法验证平台以及控制算法验证平台 实现步骤: 使用CubeMax HAL库开发; 使用Python project_generator生成工程; 精简RTT Master内核并加入C++支持; 配置调试接口; 构建C/C++混合编程框架; 硬件IIC驱动GY-86传感器驱动; MATLAB设计IIR Butterworth四阶低通滤波器; 传感器校准; Mahony互补滤波算法 实现姿态解算; 加入mavlink/私有协议、UDP/USB上传数据到PC; 通过MATLAB/STM32验证和比较各种算法; 搭建单轴单桨一维角度控制平台; …… 开发环境硬件: 碳纤管碳纤桨电机座、电机电调电源、ART_Pi、GY86、按键模块、硅胶线…… RT-Thread版本: Master 4.0.3 开发工具及版本: STM32CubeMX 6.0.1、IARforARM8.32.1、BeyondCompare4、MATLAB R2019b、VSCode、SmartGit20.1.5、匿名飞控地面站-0512…… RT-Thread使用情况概述Pin、serial、cplusplus、time、finsh 硬件框架ART_Pi、GY86、按键模块、电调…… 软件框架说明C/C++混合编程,进程和Loop Schedule结合 软件模块说明核心部分:关于GY86的驱动以及姿态解算的Mahony 演示效果演示视频: 比赛感悟4/11/2020Will_Watson 关于AHRS,卡尔曼滤波或互补滤波理解起来 简单但实施难度较大因为很多参数有待测定; 发现各开源项目用的最多的是Mahony算法,关于它的PI部分的本质着实难以理解; 当发现BP神经网络逼近好像可以和卡尔曼滤波一样,就当成滤波器,而且它还可以是一个逼近式的滤波器,免不了一番测试比较; 而后查看Madgwick原文中提到“梯度下降法”,瞬间明白了它的根源和本质; 当学习BP神经网络逼近的时候的思考流程如下: 逼近的需求来源是什么?可能是为了预测系统下一个输出,为了做前馈控制? 预测 ——> 观测器 ——> 逼近式滤波器??? 神经逼近网络的特性有哪些?不具有长时记忆,短时记忆即可以说只具有最近时刻原系统的逼近特性; 尝试检测滤波特性,从这个角度理解逼近网络也是一个不断修正的预估观测器或者说卡尔曼滤波器 从执行流程/滤波效果上 分析一维卡尔曼滤波 和 NNI-BP逼近一维非线性系统的 区别: 卡尔曼滤波流程:先验估计值——>先验估计方差——>依据各方差融合传感器数据做后验估计——>更新后验估计方差; NNI-BP逼近流程: 由网络前状态和新输入直接计算输出——>与原系统输出比较修正网络状态(是为了下一次直接获得预估输出); 注:把原系统输出当成需滤波数据;把神经网络输出当成滤波后数据 卡尔曼滤波没有阶数的概念,而NNI-BP逼近的滤波特性具有一定延迟 卡尔曼滤波器的主要控制参数(基于现实过程噪声和观测噪声)不可控,转移矩阵协方差矩阵需测得; NNI-BP逼近 的主要 控制参数(学习速率和动量因子)可控 即滤波效果可调节,逼近系统特性; 也有一部分“内闭环软拟硬输入输出”思想 当然算法的表现依然可以看起来就是加减乘除那么简单,双重卡尔曼看起来和做起来可能没那么友好;
RT-Thread作品秀】AFDX端系统实现与时间调度算法时间 作者:shenkh 概述新一代航空电子系统随着飞行条件复杂和对安全飞行、舒适旅行等要求,数据传输已由传统的音频通信扩大为图像、多媒体、确定性控制信号等多数据流业务航空电子全双工交换以太网(AFDX)在此多元化应用背景下应用而生。基于IEEE 802.3标准,结合航空电子应用背景下对协议MAC部分进行适应性修改,使其成为新的具有高速和确定性的航空以太网数据标准网络。 ADFX协议主要包括端系统、交换机、应用系统。本次大赛主要实现端系统和交换机的通信、以及交换机的基于时间的同步算法。 开发环境硬件:ART-Pi开发板,正点原子Stm32F407开发板 RT-Thread版本:4.03 开发工具及版本:RT Thread Stdio最新版本,MDK5.25 RT-Thread使用情况概述ART-Pi开发板作为交换机,使用的RT-Thread内核未作修改,组件部分使用了finsh、SAL、LWIP、DFS、串口、Pin。 其中finsh和串口用于调试,LWIP作为AFDX协议层,SAL用于实现AFDX数据发送和接受。 硬件框架硬件使用的是ART-PI开发板。 软件框架说明软件模块说明软件实现的关键点在于 1:AFDX协议内容部分实现, AFDX应用数据包括虚电路符和交换数据;虚电路控制模块VLcblk则包括基本控制信息,地址端口映射信息、配置信息等。 2:时间调度:端系统和交换机异步上电,各自都采用高精度的时钟用于周期性计数0~24ms,交换机在读取本地时间后发送给端系统,端系统在接收到时间后本地同步,且在下次时间计数到来时,发送本地时间帧给交换机,交换机将收到的时间帧和本地做对比,只当时间计数保持一致后,才开始数据交换。 演示效果同步失败时,一直尝试同步。 同步成功后打印接收的端系统数据 比赛感悟本次比赛最大的收获就是熟悉和实操了RTThread操作系统。第一次接触到RTThread操作系统时,就被其配置的简单所惊艳。menuconfig的配置方式更是将需要的外设、内核等内容图形化显示出来,方便了开发者的使用,不需要像其它RTOS那样去代码里手动改代码。不经感叹,国产操作系统也能做到很出色的。 对于个人而言,本次比赛最大的收获就是加深了对LWIP协议和SAL的理解。纸上得来终觉浅,绝知此事要躬行,以往看协议栈的书籍时,过于注重理论的理解,实际对于开发者而言,真正的实操是非常重要的。 最后感谢大赛组织方给了我们这次机会,也祝RTThread团队将这款操作系统发挥更大的光和热。
RT-Thread是一个开源的实时操作系统,它的设计目标是提供一个简单、高效、可靠的实时操作系统内核。下面是RT-Thread的原理分析: 1. 内核对象的设计:RT-Thread内核对象包括线程、信号量、邮箱、消息队列、内存池等。每个内核对象都有一个控制块,用于描述该对象的状态和属性。内核对象的设计使得RT-Thread内核具有高度的可扩展性和灵活性。 2. 线程调度的机制:RT-Thread采用抢占式的优先级调度算法,支持多级优先级和时间片轮转调度。线程的优先级越高,被调度的机会就越大。当多个线程的优先级相同时,采用时间片轮转调度算法。 3. 中断处理机制:RT-Thread支持中断嵌套和中断优先级控制。当一个中断处理程序正在执行时,如果发生了更高优先级的中断,RT-Thread会挂起当前中断处理程序,转而执行更高优先级的中断处理程序。 4. 内存管理机制:RT-Thread采用动态内存管理机制,支持内存池和动态内存分配。内存池可以提高内存分配的效率,动态内存分配可以更灵活地管理内存。 5. 设备驱动机制:RT-Thread采用设备驱动框架,支持字符设备、块设备、网络设备等。设备驱动程序可以通过注册设备驱动的方式来实现。 6. 文件系统机制:RT-Thread支持多种文件系统,包括FAT、YAFFS、ROMFS等。文件系统可以通过挂载的方式来使用。 7. 网络协议栈:RT-Thread支持TCP/IP协议栈,包括TCP、UDP、IP、ICMP、ARP等协议。网络协议栈可以通过配置的方式来启用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值