0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

stm32裸机RT—thread开始创建线程详解

嵌入式应用开发 来源:嵌入式应用开发 作者:嵌入式应用开发 2022-05-19 15:02 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

在裸机系统 中,他们统统放在一个叫栈的地方,栈是单片机RAM里面一段连续的内存空间,栈的大小一般在启动文件或者链接脚 本里面指定,最后由C库函数_main进行初始化。

在多线程系统中,每个线程都是独立的,互不干扰的,所以要为每个线程都分配独立的栈空间,这个栈空间通常是一个预先定义好的全局数组,也可以是动态分配的一段内存空间,但它们都存在于RAM中。

创建线程栈,需要几个线程创建几个线程栈,这里线程栈实际上就是全局变量,大小为512,这里创建两个工作线程,如下:

ALIGN(RT_ALIGN_SIZE)//
/* 定义线程栈*/
rt_uint8_t rt_flag1_thread_stack[512];
rt_uint8_t rt_flag2_thread_stack[512];

ALIGN是一个 带参宏,在rtdef.h中定义,设置变量需要多少个字节对齐,对在它下面的变量起作用。RT_ALIGN_SIZE是一个在rtconfig.h(rtconfig.h第一次使用需要在User文件夹下面新建然后添加到工程user这个组文件)中定义 的宏,默认为4,表示4个字节对齐。

#ifndef __RTTHREAD_CFG_H__
#define __RTTHREAD_CFG_H__

#define RT_THREAD_PRIORITY_MAX  32     /* 最大优先级 */
#define RT_ALIGN_SIZE           4      /* 多少个字节对齐 */

#endif /* __RTTHREAD_CFG_H__ */

下一步,定义线程函数:

这里我们建立线程函数,并给函数命名flag1_thread_entry,flag2_thread_entry两个函数 ,分别代表着两个无限循环不返回的线程

/* 软件延时 */
void delay (uint32_t count)
{
    for(; count!====0; count--);
}
/* 线程1 */
void flag1_thread_entry( void *p_arg )//   (1)
{
    for( ;; )
    {
        flag1 ==== 1;
        delay( 100 );
        flag1 ==== 0;
        delay( 100 );

        /* 线程切换,这里是手动切换 */
        rt_schedule();
    }
}
/* 线程2 */
void flag2_thread_entry( void *p_arg )//   (2)
{
    for( ;; )
    {
        flag2 ==== 1;
        delay( 100 );
        flag2 ==== 0;
        delay( 100 );

        /* 线程切换,这里是手动切换 */
        rt_schedule();
    }
}

下一步定义线程控制块

在裸机系统中,程序的主体是CPU按照顺序执行的。而在多线程系统中,线程的执行是由系统调度的。系统为了顺利的调度线程,为每个线程都额外定义了一个线程控制块,这个线程控制块就相当于线程的身份证,里面存有线程的所有信息,比如线程的栈指针,线程名称,线程的形参等。有了这个线程控制块之后,以后系统对线程的全部操作都可以通过这个线程控制块来实现。定义一个线程控制块需要一个新的数据类型,该数据类型在rtdef.h这个头 文件中声明,使用它可以为每个线程都定义一个线程控制块实体。下面来看一下,此结构体定义函数及内容:

struct rt_thread
{
    void        *sp;                    /* 线程栈指针 */
    void        *entry;              /* 线程入口地址 */
    void        *parameter;       /* 线程形参 */
    void        *stack_addr;      /* 线程起始地址 */
    rt_uint32_t stack_size;       /* 线程栈大小,单位为字节 */

    rt_list_t   tlist;            /* 线程链表节点 */
};
typedef struct rt_thread *rt_thread_t;// 

我们在main.c文件中为两个线程定义的线程控制块。

/* 定义线程控制块 */
struct rt_thread rt_flag1_thread;
struct rt_thread rt_flag2_thread;

下一步,创建线程实现函数

线程的栈,线程的函数实体,线程的控制块最终需要联系起来才能由系统进行统一调度。那么这个联系的工作就由线 程初始化函数rt_thread_init()来实现,该函数在thread.c(thread.c第一次使用需要自行在文件夹rtthread/3.0.3/src中新建并添加到工程的rtt/source组)中定义,在rtthread.h中声明,所有跟线程相关的函数都在这个文件定义。rt_thread_init()函数的实现如下 :

rt_err_t rt_thread_init(struct rt_thread *thread,//          (1)
                        void (*entry)(void *parameter),//    (2)
                        void             *parameter,//       (3)
                        void             *stack_start,//     (4)
                        rt_uint32_t       stack_size)//      (5)
{
    rt_list_init(&(thread->tlist));//                         (6)

    thread->entry ==== (void *)entry;//                       (7)
    thread->parameter ==== parameter;//                       (8)

    thread->stack_addr ==== stack_start;//                    (9)
    thread->stack_size ==== stack_size;//                     (10)

    /* 初始化线程栈,并返回线程栈指针 */ //                      (11)
    thread->sp ==== (void *)rt_hw_stack_init( thread->entry,
                                        thread->parameter,
                                    (void *)((char *)thread->stack_addr + thread->stack_size - 4) );

    return RT_EOK;//                                          (12)
}

(1) :thread是线程控制块指针。

(2) :entry 是线程函数名, 表示线程的入口。

(3) :parameter是线程形参,用于传递线程参数。

(4) :stack_start 用于指向线程栈的起始地址。

(5) :stack_size表示线程栈的大小,单位为字节。

(7) :将线程入口保存到线程控制块的entry成员中。

(8) :将线程入口形参保存到线程控制块的parameter成员中。

(9) :将线程栈起始地址保存到线程控制块的stack_start成员中。

(10) :将线程栈起大小保存到线程控制块的stack_size成员中。

(11) :初始化线程栈,并返回线程栈顶指针。

rt_hw_stack_init()用来初始化线程栈, 当线程第一次运行的时候,加载到CPU寄存器的参数就放在线程栈里面,该函数在cpuport.c中实现。cpuport.c第一次使用需要自行 在rtthread/3.0.3/ libcpu/arm/cortex-m3(cortex-m4或cortex-m7)文件夹下新建,然后添加到工程 的rtt/ports组中。

下一步,定义链表节点数据类型:

struct rt_list_node
{
   struct rt_list_node *next;              /* 指向后一个节点 */
   struct rt_list_node *prev;              /* 指向前一个节点 */
};
typedef struct rt_list_node rt_list_t;

rt_list_t 类型的节点里面有两个rt_list_t类型的节点指针next和prev,分别用来指向链表中的下一个节点和上一个节点。

pYYBAGKF8peAQwaOAAB3seF5MG4606.png双向链表轮询示意图

双向链表的相关操作,这些函数均在rtservice.h中实现,rtservice.h第一次使用需要自行在rtthread/3.0.3/include文件夹下新建,然后添加到工程的rtt/source组中。下面从链表操作逐步实现。

链表节点初始化:

rt_inline void rt_list_init(rt_list_t *l)
{
    l->next ==== l->prev ==== l;
}

实际上进行了如下操作:

poYBAGKF9FaAZa-8AAA6i6E1JnY592.png链表初始化示意图

下面在链表头部和尾部分别插入一个链表节点:

表头后面插入一个节点

/* 在双向链表头部插入一个节点*/
rt_inline void rt_list_insert_after(rt_list_t *l, rt_list_t *n)
{
    l->next->prev ==== n; /* 第 1 步*/
    n->next ==== l->next; /* 第 2 步*/
    l->next ==== n; /* 第 3 步*/
    n->prev ==== l; /* 第 4 步*/
}

表头前边插入一个节点

rt_inline void rt_list_insert_before(rt_list_t *l, rt_list_t *n)
{
    l->prev->next ==== n; /* 第 1 步*/
    n->prev ==== l->prev; /* 第 2 步*/
    l->prev ==== n; /* 第 3 步*/
    n->next ==== l; /* 第 4 步*/
}

从双向链表删除一个节点

rt_inline void rt_list_remove(rt_list_t *n)
{
    n->next->prev ==== n->prev; /* 第 1 步*/
    n->prev->next ==== n->next; /* 第 2 步*/
    n->next ==== n->prev ==== n; /* 第 3 步*/
}

创建线程初始化函数

/* 线程栈初始化 */
rt_uint8_t *rt_hw_stack_init(void       *tentry,//                  (1)
                            void       *parameter,//                 (2)
                            rt_uint8_t *stack_addr)// 线程栈顶地址-4,在该函数调用的时候传进来的是线程栈的栈顶地址-4
{

    struct stack_frame *stack_frame;//                               (4)
    rt_uint8_t         *stk;
    unsigned long       i;

    /* 获取栈顶指针
    rt_hw_stack_init 在调用的时候,传给stack_addr的是(栈顶指针)*/
    stk  ==== stack_addr + sizeof(rt_uint32_t);//                       (5)

    /* 让stk指针向下8字节对齐 */
    stk  ==== (rt_uint8_t *)RT_ALIGN_DOWN((rt_uint32_t)stk, 8);//       (6)

    /* stk指针继续向下移动sizeof(struct stack_frame)个偏移 */
    stk -==== sizeof(struct stack_frame);//                             (7)

    /* 将stk指针强制转化为stack_frame类型后存到stack_frame */
    stack_frame ==== (struct stack_frame *)stk;//                       (8)

    /* 以stack_frame为起始地址,将栈空间里面的sizeof(struct stack_frame)
    个内存初始化为0xdeadbeef */
    for (i ==== 0; i < sizeof(struct stack_frame) / sizeof(rt_uint32_t); i ++)//   (9)
    {
            ((rt_uint32_t *)stack_frame)[i] ==== 0xdeadbeef;
    }

    /* 初始化异常发生时自动保存的寄存器 *///                            (10)
    stack_frame->exception_stack_frame.r0  ==== (unsigned long)parameter; /* r0 : argument */
    stack_frame->exception_stack_frame.r1  ==== 0;                        /* r1 */
    stack_frame->exception_stack_frame.r2  ==== 0;                        /* r2 */
    stack_frame->exception_stack_frame.r3  ==== 0;                        /* r3 */
    stack_frame->exception_stack_frame.r12 ==== 0;                        /* r12 */
    stack_frame->exception_stack_frame.lr  ==== 0;                        /* lr */
    stack_frame->exception_stack_frame.pc  ==== (unsigned long)tentry;    /* entry point, pc */
    stack_frame->exception_stack_frame.psr ==== 0x01000000L;              /* PSR */

    /* 返回线程栈指针 */
    return stk;//                                                    (11)
}

(5) :获取栈顶指针,将栈顶指针传给指针stk。rt_hw_stack_init()函数 在rt_thread_init ()函数中调用的时候传给形参stack_addr的值是栈顶指针减去4,所以现在 加上sizeof(rt_uint32_t)刚好与减掉的4相互抵消,即传递给stk的是栈顶指针。

(6) :让stk这个指针向下8个字节对齐,确保stk是8字节对齐的地址。 在Cortex-M3(Cortex-M4或Cortex-M7)内核的单片机中,因为总线宽度是32位的,通常只要栈保持4字节对齐就 行,可这样为啥要8字节?难道有哪些操作是64位的?确实有,那就是浮 点运算,所以要8字节对齐(但是目前我们都还没有涉及到浮点运算,只是为了后续兼容浮点运行的考虑)。 如果栈顶指针是8字节对齐的,在进行向下8字节对齐的时候,指针不会移动,如果不是8字节对齐的, 在做向下8字节对齐的时候,就会空出几个字节,不会使用,比如当stk是33,明显不能整除8, 进行向下8字节对齐就是32,那么就会空出一个字节不使用。

(7) :stk指针继续向下移动sizeof(struct stack_frame) 个偏移,即16个字的大小。

ok!!!!!!!

这些了解了之后,我们在主函数内加入线程初始化即可

/* 初始化线程 */
rt_thread_init(&rt_flag1_thread,                 /* 线程控制块 */
                flag1_thread_entry,               /* 线程入口地址 */
                RT_NULL,                          /* 线程形参 */
                &rt_flag1_thread_stack[0],        /* 线程栈起始地址 */
                sizeof(rt_flag1_thread_stack) );  /* 线程栈大小,单位为字节 */
/* 将线程插入到就绪列表 */

/* 初始化线程 */
rt_thread_init(&rt_flag2_thread,                 /* 线程控制块 */
                flag2_thread_entry,               /* 线程入口地址 */
                RT_NULL,                          /* 线程形参 */
                &rt_flag2_thread_stack[0],        /* 线程栈起始地址 */
                sizeof(rt_flag2_thread_stack) );  /* 线程栈大小,单位为字节 */


审核编辑:符乾江

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 嵌入式
    +关注

    关注

    5186

    文章

    20146

    浏览量

    328831
  • 线程机制
    +关注

    关注

    0

    文章

    2

    浏览量

    5649
  • RT Thread操作系统

    关注

    0

    文章

    4

    浏览量

    800
收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    RISC-V单片机快速入门02,移植RT_Thread Nano系统

    前言: 上一节,我们使用芯来科技Nuclei Studio IDE搭建了裸机开发环境,本节我们开始正式移植RT_Thread系统。 一、基础知识 1.RT_Thread简介
    发表于 11-10 07:23

    使用rt_thread无法输出PWM怎么解决?

    ); /* 创建线程,名称是 pwm_thread ,入口是 pwm_entry*/ tid1 = rt_thread_create("pwm_
    发表于 10-14 07:05

    RTThread线程退出后rt_malloc动态创建的资源没有释放怎么解决?

    测试过程中,在一个线程中用rt_malloc动态创建4KB的资源,在线程运行过程中用rt_thread_delete()使
    发表于 10-13 07:06

    rt-thread studio 如何进行多线程编译?

    使用 rt-thread studio在工程配置 C/C++构建->Behavior->parallel build数量修改,CPU的占用率没有明显的改变
    发表于 10-11 09:16

    线程问题,线程已经创建成功了,为什么线程调用的函数不会运行呢?

    我这个线程创建成功了,为啥ai_thread_entry()函数不运行呢? void airun_thread() { /* 创建 se
    发表于 10-10 08:02

    创建stm32f103c8工程后为什么终端没有打印Hello RT_Thread?

    创建stm32f103c8工程后为什么终端没有打印Hello RT_Thread!,程序好像也没有下载进去
    发表于 09-26 08:24

    RT-Thread Nano移植后动态创建线程创建不了怎么解决?

    RT-Thread Nano 移植后动态创建线程创建不了,静态可以.直接烧录DEMO也一样,将RT_USING_HEAP开起来,使用动态
    发表于 09-19 06:28

    STM32已经用标准库写好的代码,怎么导入RT THREAD

    之前已经在裸机上实现了SAE J1939协议,因为要添加IOT 功能,现在想添加RT THREAD.因为之前写裸机程序的时候用的是标准库。而RT
    发表于 09-19 06:16

    线程中调用rt_thread_mdelay()函数卡死的原因?怎么解决?

    线程中调用rt_thread_mdelay()函数程序卡死。搞了两天也不知道问题出在哪,怎么解决。 int main(void) { interrupt_config
    发表于 09-11 08:11

    安装RT thread studio后创建RT thread 项目报错怎么解决?

    安装RT thread studio后创建RT thread 项目,报错 hello工程是完全新建的项目 不论新建什么名称都会报错!!!!!
    发表于 09-09 06:51

    如何移植 RT-Thread Nano 并创建 2 个线程

    基于 BSP 中的 GPIO_OutputInput 演示,展示了如何移植 RT-Thread Nano 并创建 2 个线程
    发表于 08-19 07:45

    深度剖析 RT-Thread 线程调度流程

    RT-Thread调度第一个线程的主要流程分如下:rtthread_startup:RTT的启动函数,主要负责板级驱动,调度器,系统线程初始化,启动调度的工作
    的头像 发表于 06-25 18:24 1437次阅读
    深度剖析 <b class='flag-5'>RT-Thread</b> <b class='flag-5'>线程</b>调度流程

    RT-Thread Nano移植后动态创建线程创建不了怎么处理?

    RT-Thread Nano移植后动态创建线程创建不了,静态可以.直接烧录DEMO也一样,将RT_USING_HEAP开起来,使用动态
    发表于 06-11 06:36

    STM32已经用标准库写好的代码,怎么导入RT THREAD

    之前已经在裸机上实现了SAE J1939协议,因为要添加IOT 功能,现在想添加RT THREAD.因为之前写裸机程序的时候用的是标准库。而RT
    发表于 05-27 06:01

    创建stm32f103c8工程后为什么终端没有打印Hello RT_Thread

    创建stm32f103c8工程后为什么终端没有打印Hello RT_Thread!,程序好像也没有下载进去
    发表于 04-01 06:55