RTThread入门

article/2025/8/25 21:54:00

RT-Thread入门

1.初识RT-Thread

嵌入式系统是一种完全嵌入在装置或设备内部,为满足特定需求而设计的计算机系统,譬如生活中常见的嵌入式系统就有:电视机顶盒、路由器、电冰箱、微波炉与移动电话等。

嵌入式操作系统是应用于嵌入式系统的软件。

2.动态内存堆的使用

  • 裸机系统动态内存
    动态内存配置
    动态内存使用
  • RTT动态内存
    动态内存配置
    动态内存使用(源码解析)
  • 动态内存注意事项
    内存复位
    内存泄漏
  • 其他相关API
    rt_realloc
    rt_calloc

2.1 简述堆栈

在单边机应用中,我们经常提到堆栈这个词,实际上堆和栈是两个不同的概念。
(stack):由编译器自动分配释放
(heap):一般由程序员分配和释放

int a = 0;	//全局初始化区
char *p1;	//全局未初始化区
main()
{int b;				//栈char s[] = "abc";	//栈char *p2;			//栈char *p3 = "123456";	//123456\0在常量区,p3在栈上。static int c = 0;	//全局静态初始化区p1 = (char*)malloc(10);	//堆p2 = (char*)malloc(20);	//堆
}

全局初始化区
全局静态初始化区
全局未初始化区
常量区
栈区
堆区

2.2 MDK裸机系统动态内存配置和使用

使用方式1:startup_stm32f103xe.s

; Amount of memory (in bytes) allocated for Stack
; Tailor this value to your application needs
; <h> Stack Configuration
;   <o> Stack Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>Stack_Size      EQU     0x00000400AREA    STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem       SPACE   Stack_Size
__initial_sp; <h> Heap Configuration
;   <o>  Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>Heap_Size       EQU     0x00000200AREA    HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem        SPACE   Heap_Size
__heap_limit

栈空间的设置和堆空间的设置

使用方式2:

char *p;
p = (char*)malloc(10)
free(p);

2.3 RT-Thread动态内存配置和使用

与裸机系统下使用相差不大。
RT-Thread中,使用动态内存前,需要使用动态内存函数rt_system_heap_init 配置好动态内存区。
之后,使用rt_mallocrt_free函数获得动态内存及释放动态内存。

char*p;
p = (char *)rt_malloc(10);
rt_free(p);

2.3.1 rt_system_heap_init

board.c

rt_system_heap_init((void *)HEAP_BEGIN, (void *)HEAP_END);

参数为起始地址和结束地址,两地址之间的空间作为动态内存的空间
可以从芯片总的内存空间中提取一部分作为动态内存空间。

起始地址 HEAP_BEGIN

#define HEAP_BEGIN  ((void *)&Image$$RW_IRAM1$$ZI$$Limit)

Image R W I R A M 1 RW_IRAM1 RWIRAM1ZI$$Limit 是一个链接器导出的符号。代表ZI段的结束,也就是程序执行区的RAM结束后的地址,另一个角度,也就是我们执行区RAM未使用的区域的起始地址。

从工程的map文件中,可以看到:

	Total RO  Size (Code + RO Data)                63528 (  62.04kB)Total RW  Size (RW Data + ZI Data)             22576 (  22.05kB)Total ROM Size (Code + RO Data + RW Data)      63676 (  62.18kB)					

整个工程中用到的静态RAM空间用掉了22.05K
芯片总RAM有64K,除去用掉的22.05K的空间,剩余的空间的起始地址,在MDK中使用 宏

Image$$RW_IRAM1$$ZI$$Limit 表示。

结束地址 HEAP_END

#define HEAP_END    STM32_SRAM_END
#define STM32_SRAM_END (0x20000000 + STM32_SRAM_SIZE * 1024)

使用片内RAM的结束地址作为动态内存的结束地址。

2.4 动态内存堆使用注意点

内存复位
当我们每次申请到新的内存块之后,建议对所申请到的内存块进行清零操作。

p = (char *)rt_malloc(10);
if(p!=RT_NULL)
{rt_memset(p,0,10); //对申请到的内存清零
}

内存泄漏
内存泄漏(Memory Leak)是指程序中已动态分配的堆内存,由于某种原因未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

我们在使用动态内存时需要注意:rt_malloc 需要和 rt_free 配套使用。

2.5 其他动态内存相关的API

void *rt_realloc(void *rmem,rt_size_t newsize)

在已分配内存块的基础上重新分配内存块的大小(增加或减少)
在进行重新分配内存块时,原来的内存块数据保持不变(缩小的情况下,后面的数据被自动截断)

void *rt_calloc(rt_size_t count,rt_size_t size)

从内存堆中分配连续内存地址的多个相同大小的内存块

2.6 动态内存使用的Demo代码

int i = 0;char *ptr = RT_NULL;	//内存块指针for(i=0;;i++){ptr = rt_malloc(1<<i);	//每次申请 (1<<i)大小的内存if(ptr != RT_NULL){rt_kprintf("rt_malloc OK! size=%d\n",(1<<i));rt_memset(ptr,0,(1<<i));	//清空申请到的内存rt_kprintf("rt_memset OK!\n");rt_free(ptr);	//释放申请到的内存rt_kprintf("rt_free OK!\n");}else{rt_kprintf("rt_malloc Err!\n");return -1;}}

3.线程的创建

3.1 线程的概念

RT-Thread名为实施线程RTOS,那么什么叫线程?

  1. 人们在生活中处理复杂问题时,惯用的方法是“分而治之”,即把一个大问题分解成多个相对简单、比较容易解决的小问题。小问题逐个被解决了,大问题也就随之解决了。同样,在设计一个较为复杂的应用程序时,也通常吧一个大型任务分解成多个小任务,然后通过运行这些小任务,最终达到完成大任务的目的。
  2. 在RT-Thread中,与上述小任务对应的程序实体就叫做“线程”(或任务),RT-Thread就是一个能对这些小“线程”进行管理和调度的多”线程“操作系统。
  3. 线程是实现任务的载体,它是RT-Thread中最基本的调度单位,它描述了一个任务执行的运行环境,也描述了这个任务所处的优先等级。

3.2 线程的组成

RT-Thread中,线程由三部分组成:线程代码(入口函数)、线程控制块线程堆栈

线程代码

//无限循环结构
void thread_entry(void *parameter)
{while(1){/* 等待事件发生 *//* 处理事件 */}
}//顺序执行结构
void thread_entry(void *parameter)
{/* 事务1处理 *//* 事务2处理 *//* 事务N处理 */
}

无限循环结构中一般加入让出CPU的API调用,否则这个线程一直占用CPU,其他线程无法被执行。

线程控制块

线程控制块是操作系统用于管理线程的一个数据结构。它会存放线程的一些信息,例如优先级、线程名称、线程状态等,也包括线程与线程之间连接用的链表结构,线程等待事件集合等。

struct rt_thread
struct tr_thread *rt_thread_t

线程栈

RT-Thread每个线程都具有独立的栈空间,当进行线程切换时,系统会将当前线程的上下文保存在线程栈中,当线程要恢复运行时,再从线程栈中读取上下文信息,恢复线程的运行。

线程上下文是指线程执行的环境,具体说就是各个变量和数据,包括所有寄存器变量、堆栈信息、内存信息等。

线程栈在形式上是一段连续的内存空间,我们可以通过定义一个数组或者申请一段动态内存来作为线程的栈。

3.3 线程创建

创建线程

创建动态线程

rt_thread_t rt_thread_create(const char *name,void (*entry)(void *parameter),void       *parameter,rt_uint32_t stack_size,rt_uint8_t  priority,rt_uint32_t tick);

创建静态线程

rt_err_t rt_thread_init(struct rt_thread *thread,const char       *name,void (*entry)(void *parameter),void             *parameter,void             *stack_start,rt_uint32_t       stack_size,rt_uint8_t        priority,rt_uint32_t       tick);

动态线程的线程控制块和线程栈都是自动分配的。
创建静态线程需要提前定义好线程控制块和线程栈。

启动线程

rt_err_t rt_thread_startup(rt_thread_t thread);

调用此函数后,创建的线程会被加入到线程的就绪队列,执行调度。

3.4 静态线程VS动态线程

相关资源分配形式
静态线程需要将线程控制块与线程栈先定义出来,动态线程不需要,动态线程的线程控制块和线程栈是自动分配的。

运行效率
1.如果动态线程和静态线程的线程控制块和线程栈都处于芯片的RAM中,则动态线程和静态线程的运行效率没有区别。
2.如果系统使用了外扩的外部RAM,动态线程的线程控制块和线程栈是处于外部RAM中。则动态线程的运行效率有所下降。
3.两种方式创建的线程,本质上没有区别。

3.5 线程创建Demo

/*
创建两个线程,一个动态线程,一个静态线程
*/#define THREAD_PORITY 25
#define THREAD_SLICE 5
#define THREAD_STCK_SIZE 512//动态线程
rt_thread_t tid1 = RT_NULL;	//静态线程
struct rt_thread thread2; //静态线程
char thread2_stack[THREAD_STCK_SIZE];/*** @brief 线程1的入口函数* * @param parameter [IN]线程的参数*/
void my_thread1_entry(void *parameter)
{uint32_t count = 0;while(1){count ++;rt_kprintf("thread1 count = %d\r\n",count);rt_thread_mdelay(100);}}/*** @brief 线程2的入口函数* * @param parameter [IN]线程的参数*/
void my_thread2_entry(void *parameter)
{uint32_t count = 0;for(count=0;count<10;count++){rt_kprintf("thread2 count = %d\r\n",count);}rt_kprintf("thread2 exit\r\n");
}int main(void)
{					 //创建动态线程tid1 = rt_thread_create("thread1",my_thread1_entry,RT_NULL,THREAD_STCK_SIZE,THREAD_PORITY,THREAD_SLICE);if(tid1 != RT_NULL){rt_thread_startup(tid1);	//将线程添加到线程就绪队列rt_kprintf("thread1 creater OK!\r\n");}else{rt_kprintf("thread1 creater Err!\r\n");}//创建静态线程rt_thread_init(&thread2,"thread2",my_thread2_entry,RT_NULL,&thread2_stack[0],THREAD_STCK_SIZE,THREAD_PORITY-1,THREAD_SLICE);rt_thread_startup(&thread2);	//将线程2添加到线程就绪列表return 0;
}

运行结果:

thread1 creater OK!
msh >thread2 count = 0
thread2 count = 1
thread2 count = 2
thread2 count = 3
thread2 count = 4
thread2 count = 5
thread2 count = 6
thread2 count = 7
thread2 count = 8
thread2 count = 9
thread2 exit
thread1 count = 1
thread1 count = 2
thread1 count = 3
thread1 count = 4
thread1 count = 5
thread1 count = 6

4.跑马灯线程实例

4.1 线程状态

线程状态转换图

线程状态转换图

4.2 系统滴答时钟

​ 每个操作系统中都存在一个“系统心跳”时钟,是操作系统中最小的时钟单位。这个时钟负责系统和时间相关的一些操作。作为操作系统运行的时间尺度,心跳时钟是由硬件定时器的定时中断产生。
​ 系统的心跳时钟我们也称之为系统滴答或时钟节拍,系统滴答的频率需要我们根据CPU的处理能力来决定。
​ 时钟节拍使得内核可以将线程延时若干个整数倍时钟节拍,以及线程等待事件发生时,提供等待超时的依据。
​ 频率越快,内核函数介入系统运行的几率就越大,内核占用的处理器时间就越长,系统的负荷就变大;频率太小,时间处理精度又不够。
​ 我们在STM32平台上一般设置系统滴答的频率为100Hz,即每个滴答的时间是10ms。

4.2.1 STM32上系统滴答的配置

//board.c
void SystemClock_Config(void)
{...//设置系统中断时间HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq() / RT_TICK_PER_SECOND);...
}//系统定时器中断
void SysTick_Handler(void)
{...rt_tick_increase();...
}//rt_config.h
#define RT_TICK_PER_SECOND 100 //配置滴答频率为100Hz

rt_thread_mdealy(500) 延时500ms。这个API的延时就是由系统滴答完成的。

4.3 GPIO驱动架构操作IO

IO初始化

void rt_pin_mode(rt_base_t pin,rt_base_t mode);
pin:引脚索引
mode:引脚模式PIN_MODE_OUTPUTPIN_MODE_INPUTPIN_MODE_INPUT_PULLUPPIN_MODE_INPUT_PULLDOWNPIN_MODE_OUTPUT_OD

IO写入

void rt_pin_write(rt_base_t pin,rt_base_t value);
pin:引脚索引
value:引脚电平PIN_HIGHPIN_LOW

IO读出

int rt_pin_read(rt_base_t pin);
pin:引脚索引
返回值:引脚电平

4.4 跑马灯Demo

/*
跑马灯
*/
#include <rtthread.h>
#include <rtdevice.h>
#include <drv_gpio.h>//线程信息
#define THREAD_PORITY 25
#define THREAD_SLICE 5
#define THREAD_STCK_SIZE 512//线程控制块指针
rt_thread_t led_tid = RT_NULL;//LED引脚
#define MY_LED_PIN 14/*** @brief 线程的入口函数* * @param parameter [IN]线程的参数*/
void led_entry(void *parameter)
{//设置LED引脚为推挽输出rt_pin_mode(MY_LED_PIN,PIN_MODE_OUTPUT);	while(1){rt_pin_write(MY_LED_PIN,PIN_HIGH);	//引脚置高rt_thread_delay(50);				//延时50个时钟节拍  rt_thread_sleep(50)rt_pin_write(MY_LED_PIN,PIN_LOW);	//引脚置低rt_thread_mdelay(500);				//延时500ms}}int main(void)
{					 //创建线程led_tid = rt_thread_create("led",led_entry,RT_NULL,THREAD_STCK_SIZE,THREAD_PORITY,THREAD_SLICE);if(led_tid != NULL){rt_thread_startup(led_tid);	//加入到线程就绪队列}return 0;
}

4.5 线程栈大小分配

​ 先将线程栈大小设置一个固定值(比如2048),在线程运行时通过查看线程栈的使用情况,了解线程栈使用的实际情况,根据实际情况设置合理的线程栈大小。

msh >list_thread
thread pri  status      sp     stack size max used left tick  error
------ ---  ------- ---------- ----------  ------  ---------- ---
led     25  suspend 0x00000074 0x00000200    24%   0x00000005 000
tshell  20  ready   0x00000080 0x00001000    07%   0x00000005 000
tidle   31  ready   0x00000054 0x00000100    35%   0x00000010 000
msh >

​ 一般将线程栈最新大使用量设置为70%。

5.线程的时间片轮询调度

5.1 线程优先级

​ 优先级和时间片是线程的两个重要参数


http://chatgpt.dhexx.cn/article/Oe9dAMiT.shtml

相关文章

什么是RT-Thread?

一、RT-Thread的定义 RT-Thread&#xff0c;全称是 Real Time-Thread&#xff0c; 是一款主要由中国开源社区主导开发的开源实时操作系统&#xff08;许可证GPLv2&#xff09;&#xff0c;包含了实时、嵌入式系统相关的各个组件&#xff1a;TCP/IP协议栈、图形用户界面等。 相…

Redis启动失败的原因及解决方法

跑了近半年的Redis,今天早上来开启电脑运行程序的时候发现提示无法连接redis,暗想自己明明设置了开机自启的阿,以前也一直没问提,今天怎么就连不上了重启了下redis就提示如下错误 网上搜了好久都没找到解决办法,后来想起来去查看了下redis的日志文件 发现提示当前版本的redis无…

redis启动、获取密码及修改密码

一、启动redis服务的两种方式 查看密码是以redis服务已启动的前提下进行的&#xff0c;可直接在服务中右键启动redis或者安装根目录运行cmd输入《redis-server.exe》(不推荐不推荐不推荐&#xff0c;说三遍&#xff0c;命令行启动好像有bug&#xff0c;启动后redis能用&#x…

CentOS安装Redis及redis启动与关闭、配置(详细)

在项目使用redis过程中&#xff0c;在centos7上部署redis&#xff0c;查找相关资料并总结、记录&#xff0c;以备后续查看。 目录 一、Redis介绍 二、在CentOS上部署Redis 1、Redis安装包可以从官网上下载或者直接命令下载 升级到gcc 9.3&#xff1a; 3、Redis配置文件…

Redis启动和连接

一&#xff09;Redis简介 Redis不是简单的键值存储&#xff0c;它实际上是一个数据结构服务器&#xff0c;支持不同类型的值。 备注&#xff1a;由于我电脑是32位操作系统&#xff0c;所有就不提供redis软件下载地址了&#xff0c;请到官网下载使用。 软件解压之后&#xff0…

windows下Redis启动闪退问题解决经验汇总

最近使用Redis又遇到启动闪退的问题&#xff0c;之前记录的解决办法也失败了&#xff0c;一番研究后总算得到解决&#xff0c;感觉已经遇到了网上常见的各种问题&#xff0c;下面总结下。 我下载的是免安装版&#xff0c;解压便可使用。 官网下载传送门&#xff1a;Releases …

Windows下redis启动那些事儿

本文章主要描述我遇到的Windows下redis启动成功但Java项目无法连接问题 1.使用redis可视化工具可以连接&#xff0c;但是到Java项目中就报错连接失败 经过我的多方琢磨&#xff0c;还是密码没有配置正确&#xff0c;虽然是在redis.windows.conf配置文件中配置了 requirepass 密…

redis启动失败问题完美解决

1.输入启动命令redis-server.exe redis.windows.conf启动redis&#xff0c;发现启动失败报错&#xff1a;[8072] 07 May 09:28:52.241 # Creating Server TCP listening socket 127.0.0.1:6379: bind: No error D:\a\Main\redis> redis-server.exe redis.windows.conf[8072]…

windows redis启动

下载好redis后&#xff0c;只需解压。 然后打开dos窗口 进入redis解压目录 cd D:softwareRedis-x64-3.2.100运行下面命令启动 redis-server.exe redis.windows.conf成功启动 还可以把redis加入都开机自启动 redis-server --service-install redis.windows-service.conf …

redis启动和简单使用

redis启动和简单使用 1.redis启动 1.1 找到redis解压的位置,在里面输入cmd回车 1.2 输入redis-server redis.conf指令,然后回车,出现如下界面 注意&#xff1a;该界面不能关闭了 1.3 再进入一次redis解压的位置 输入cmd回车 1.4 输入redis-cli指令后的结果 1.5 补充 当出现…

Redis的启动方式三种

Redis的启动方式三种 启动一个 &#xff0c;进入到redis中的src目录下 在控制台输入指令&#xff1a;redis-server &#xff08;注意&#xff1a;这样启动默认端口是 6379 &#xff09; 进入客户端输入&#xff1a;redis-cli 查看进程&#xff0c;杀死进程 指定端口启动redi…

Redis的启动方法

一、Windows下 D: cd Redis //我的redis安装路径为D:\Redis redis-server.exe redis.windows.conf **如果报错creating server tcp listening socket 127.0.0.1:6379: bind No error D:\Redis>redis-cli.exe 127.0.0.1:6379> shutdown not connected> exit redi…

常见的配置中心:Apollo(二)-接入Apollo

1 配置 Apollo作为大型互联网系统生产级别的配置中心&#xff0c;在开发的积累当中构建了自己的配置维度体系。 1.1 配置四层维度 (1)Application(项目应用) 维度中的最顶层&#xff0c;在实际开发中我们一般以项目来作为最外层配置的区分维度。 (2)Environment(开发环境) …

apollo问题之 无法连接apollo配置中心

1.问题描述 1.1.现状场景 1.apollo的配置中心config-server 通过域名 http://demo-applo.wrok.com负载了两台机器10.10.10.02和 10.10.10.01 2.当前应用机器 可以访问并ping通 demo-applo.wrok.com域名 但是无法访问负载的机器1.2.报错信息 [demol-core-server:10.166.101.…

Apollo分布式配置中心(一)

目录 什么是配置&#xff1f; 什么是配置中心&#xff1f; 配置中心需要满足条件 什么是Apollo&#xff1f; Apollo特性 Apollo执行流程 Apollo安装 Git地址&#xff1a; 用户手册&#xff1a; 环境准备&#xff1a; 使用idea 的git clone 拉取源码 修改Apollo\scripts…

Apollo配置中心-手把手教你搭建Apollo配置中心运行环境

1、预置环境信息 运行环境 JDK > 1.8 具体安装及环境及环境变量配置这里不再赘述&#xff0c; 可自行查找资料。 2、下载程序包 演示版本使用的是apollo-1.9.2版本&#xff08;就是几个springboot的项目&#xff09; 应用包下载地址&#xff1a;https://github.com/apo…

Apollo 配置详解

Apollo的总体架构 Apollo的使用架构 Apollo的不同维度的配置分类 下面按照生效顺序 Application 在springboot的application.property定义appid这个key的value&#xff0c;标识该类型的应用。 Environment DEV&#xff08;开发环境&#xff09;FAT&#xff08;功能测试&#…

Apollo配置中心多环境配置

Apollo的快速启动项目中&#xff0c;只有一个DEV&#xff08;开发&#xff09;环境&#xff0c;但是本身的一个apollo-portal管控端可以管理不同环境下的配置&#xff1b;所以apollo配置中心多环境配置下的主要思路为&#xff08;这里以DEV和FAT两个环境举例说明&#xff09;&a…

Apollo配置中心与本地配置优先级

背景 在项目重构时&#xff0c;删除若干个application-{env}.yml文件&#xff0c;仅保留一个application.yml文件&#xff0c;该文件中保留的配置项都是几乎不会变更的配置&#xff0c;至于需要跟随不同环境而变更的配置项都放置在Apollo配置中心。 然后本地application.yml文…

Apollo配置中心搭建

目录 1. 下载安装包和源码包2. 创建数据库和表3. 启动Apollo服务端4. 访问Apollo客户端 1. 下载安装包和源码包 下载地址 找到要安装的版本&#xff0c;我这里选择的是1.3.0版本 下载好安装包后上传至linux的 /usr/local/src文件下并执行下面命令解压到对应文件夹 mkdir apo…