RT-Thread— 知识点总结(RTT认证+面试题汇总)
Posted Rb菌
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了RT-Thread— 知识点总结(RTT认证+面试题汇总)相关的知识,希望对你有一定的参考价值。
RT-Thread— 知识点总结
内核
RO: 只读数据段,存放程序中定义的常量 RO Size: code + RO Data ----> 占用flash大小
RW:读写数据段,存放非0全局变量 RW Size: RW Data + ZI Data ----> 运行时占用RAM大小
ROM Size: code + RO Data + RW Data ----> 烧写占用flash大小
ZI: 0 数据段,存放未初始化全局变量 + 初始化为0变量
flash相当于后台仓库,程序和数据存在这里,上电RAM就走向前台,从flash取各种东西
例:const static int data = 0x00000FE ------> RO段
int sensor_data; ------> ZI 段
bool sensor_data = true; ------> RW段
线程
线程五种状态:初始------->就绪------>运行------>挂起------>关闭
空闲线程:优先级最低,永远为就绪态,不被挂起。 用处:回收被删除线程资源(回收僵尸线程)
当线程优先级相同时,采用时间片轮转方式调度,单位一个时钟节拍
比如:A:10,B:5,那么A线程执行10个节拍,B线程执行5个节拍
rt_thread_yield():当前线程被换出,相同优先级的下一个就绪线程将被执行。
rt_schedule():当前线程并不一定被换出,而是在系统中选取就绪的优先级最高的线程执行。
创建线程(静态)-----占用RAM空间(RW/ZI 空间),用户分配栈空间和线程句柄:
好处:运行时不需要动态分配内存,运行时效率较高,实时性较好,但内存不能被释放,
只能使用 rt_thread_detach() 函数将该线程控制块从对象管理器中脱离。
static rt_uint8_t thread2_stack[512]; //线程栈
static struct rt_thread thread2; //线程控制块
rt_thread_init(&thread2, //线程handle
"thread2", //线程名称
thread2_entry, //线程入口函数
RT_NULL, //线程入口参数
&thread2_stack[0], //线程栈地址
sizeof(thread2_stack), //线程栈大小
15, //线程优先级
5); //线程时间片
rt_thread_startup(&thread2); //线程进入就绪态
创建线程(动态)-------依赖与内存堆管理器,系统自动从动态内存堆分配栈空间:
好处:运行时需要动态分配内存,效率没有静态方式高,调用 rt_thread_delete() 函数就会将这段申请的内存空间重新释放到内存堆中。
static rt_thread_t sht30_thread_id = RT_NULL;
sht30_thread_id = rt_thread_create("sht30_th", //名称
sht30_entry, //线程代码
RT_NULL, //参数
1024, //栈大小
15, //优先级
20); //时间片
if (sht30_thread_id != RT_NULL)
rt_thread_startup(sht30_thread_id); //线程进入就绪态
else
rt_kprintf("sht30_thread create failure\\n");
return RT_EOK;
定时器
动态定时器:
static rt_timer_t timer;
/* 定时器超时函数 */
static void timeout(void *parameter)
{
rt_kprintf("one shot timer is timeout\\n");
}
/* 创建定时器单次定时器 */
timer = rt_timer_create("timer", timer,
RT_NULL, 30,
RT_TIMER_FLAG_ONE_SHOT);
/* 启动定时器*/,
if (timer != RT_NULL) rt_timer_start(timer);
静态定时器:
/* 定时器的控制块 */
static struct rt_timer timer1;
static void timeout1(void* parameter)
{
rt_kprintf("one shot timer is timeout\\n");
}
/* 初始化定时器 */
rt_timer_init(&timer1,
"timer1", /* 定时器名字是 timer1 */
timeout1, /* 超时时回调的处理函数 */
RT_NULL, /* 超时函数的入口参数 */
10, /* 定时长度,以 OS Tick 为单位*/
RT_TIMER_FLAG_PERIODIC); /* 周期性定时器 */
/* 启动定时器 */
rt_timer_start(&timer1);
线程间同步——信号量
LED闪烁–信号量控制
#define LED_PIN GET_PIN(F, 9)
static rt_thread_t tid1 = RT_NULL;
static rt_thread_t tid2 = RT_NULL;
static rt_sem_t led_sem = RT_NULL;
//static struct rt_semaphore led_sem;
static void sem_entry(void *parameter)
{
while(1)
{
//释放信号量 rt_sem_release(&led_sem);
rt_sem_release(led_sem);
rt_thread_mdelay(500);
}
}
static void led_entry(void *parameter)
{
static unsigned char cnt = 0;
while(1)
{
//获取信号量 rt_sem_take(&led_sem, RT_WAITING_FOREVER);
rt_sem_take(led_sem, RT_WAITING_FOREVER);
if(cnt++ % 2)
rt_pin_write(LED_PIN, PIN_HIGH);
else
rt_pin_write(LED_PIN, PIN_LOW);
}
}
int led_sample(void)
{
//初始化信号量 rt_sem_init(&led_sem, "led_sem", 0, RT_IPC_FLAG_FIFO);
led_sem = rt_sem_creat("led_sem", 1, RT_IPC_FLAG_FIFO)
if (led_sem == RT_NULL)
{
rt_kprintf("creat led sem fail!\\n");
return -RT_ERROR;
}
tid1 = rt_thread_creat("ctl_sem",sem_entry,RT_NULL,512,20,0);
if (tid1 != RT_NULL)
rt_thread_startup(tid1);
tid2 = rt_thread_creat("ctl_sem",led_entry,RT_NULL,512,25,0);
if (tid2 != RT_NULL)
rt_thread_startup(tid2);
}
INIT_APP_EXPORT(led_sample, led dample)
线程间通讯——邮箱/队列
**消息邮箱:**开销低,效率高,不可以在中断中接收邮件,不可以在中断中等待方式发送邮件
/* 邮箱控制块 */
static struct rt_mailbox mb;
/* 用于放邮件的内存池 */
static char mb_pool[128];
/* 线程 1 入口 */
static void thread1_entry(void *parameter)
{
char *str;
while (1)
{
/* 从邮箱中收取邮件 */
if (rt_mb_recv(&mb, (rt_uint32_t *)&str, RT_WAITING_FOREVER) == RT_EOK)
{
rt_kprintf("thread1: get a mail from mailbox, the content:%s\\n", str);
}
}
/* 执行邮箱对象脱离 */
rt_mb_detach(&mb);
}
/* 线程 2 入口 */
static void thread2_entry(void *parameter)
{
rt_uint8_t count = 0;
while (count < 10)
{
/* 发送 mb_str1 地址到邮箱中 */
rt_mb_send(&mb, (rt_uint32_t)&mb_str1);
}
}
int mailbox_sample(void)
{
rt_err_t result;
/* 初始化一个 mailbox */
result = rt_mb_init(&mb,
"mbt", /* 名称是 mbt */
&mb_pool[0], /* 邮箱用到的内存池是 mb_pool */
sizeof(mb_pool) / 4, /* 邮箱中的邮件数目,因为一封邮件占 4 字节 */
RT_IPC_FLAG_FIFO); /* 采用 FIFO 方式进行线程等待 */
if (result != RT_EOK)
{
rt_kprintf("init mailbox failed.\\n");
return -1;
}
}
MSH_CMD_EXPORT(mailbox_sample, mailbox sample);
**消息队列:**发送不定长数据,线程之间的数据交换,不可以在中断中接收队列消息
/* 消息队列控制块 */
static struct rt_messagequeue mq;
/* 消息队列中用到的放置消息的内存池 */
static rt_uint8_t msg_pool[2048];
static void thread1_entry(void *parameter)
{
char buf = 0;
while (1)
{
/* 从消息队列中接收消息 */
if (rt_mq_recv(&mq, &buf, sizeof(buf), RT_WAITING_FOREVER) == RT_EOK)
{
rt_kprintf("thread1: recv msg from msg queue, the content:%c\\n", buf);
}
}
rt_mq_detach(&mq);
}
static void thread2_entry(void *parameter)
{
int result;
char buf = 'A';
while (1)
{
/* 发送紧急消息到消息队列中 */
result = rt_mq_urgent(&mq, &buf, 1);
/* 发送消息到消息队列中 */
result = rt_mq_send(&mq, &buf, 1);
if (result != RT_EOK)
{
rt_kprintf("rt_mq_send ERR\\n");
}
}
}
/* 消息队列示例的初始化 */
int msgq_sample(void)
{
rt_err_t result;
/* 初始化消息队列 */
result = rt_mq_init(&mq,
"mqt",
&msg_pool[0], /* 内存池指向 msg_pool */
1, /* 每个消息的大小是 1 字节 */
sizeof(msg_pool), /* 内存池的大小是 msg_pool 的大小 */
RT_IPC_FLAG_FIFO); /* 如果有多个线程等待,按照先来先得到的方法分配消息 */
if (result != RT_EOK)
{
rt_kprintf("init message queue failed.\\n");
return -1;
}
return 0;
}
外设接口
SPI: 高速,全双工,同步通信总线
MOSI: 主机输出/从机输入 (MASTER OUTPUT / SLAVE INPUT)
MISO: 主机输入/从机输出 (MASTER INPUT/ SLAVE OUTPUT)
SCLK: 串行时钟线, 主设备输出时钟信号到从设备
CS: 从设备选择线, 主设备输出片选信号到从设备
工作方式: 主从方式, 一个主设备和一个/多个从设备. 主设备发起, 通过CS选择从设备, 通过SCLK提供时钟信号,
数据通过MOSI输出给从设备. MISO接收从设备发送的数据. 每个从设备的CS引脚是独立的.
任何时刻, SPI主设备上只有一个CS引脚是有效的.
IIC: 半双工、双向二线制同步串行总线
不同于 SPI 一主多从的结构,它允许同时有多个主设备存在,每个连接到总线上的器件都有唯一的地址,主设备启动数据传输并产生时钟信号,
从设备被主设备寻址,同一时刻只允许有一个主设备。
当总线空闲时,SDA 和 SCL 都处于高电平状态
开始条件: SCL 为高电平时,主机将 SDA 拉低,表示数据传输即将开始。
虚拟文件系统
有一系列文件如:1.txt, 12.txt, 123.txt,从中找出1.txt,并将文件内容输出出来。
static void findfile_sample(void)
{
DIR *dirp;
struct dirrnt *d;
char *f;
char buffer[100];
/*打开根目录*/
dirp = opendir("/");
if (dirp == RT_NULL)
{
rt_kprintf("open directory error\\n");
}
else
{
/*读取目录*/
while ((d = readdir(dirp) != RT_NULL))
{
//读取文件名称
f = d->d_name;
if(!strcmp(f, "1.txt"))
{
fd = open("1.txt", O_RDONLY);
if (fd >= 0)
{
read(fd, buffer, sizeof(buffer));
rt_kprintf("file 1.txt was found, the content is %s", buffer)
close(fd);
}
}
}
closedir(dirp);
}
}
MSH_CMD_EXPORT(findfile_sample, find file)
读写文件:
#include <rtthread.h>
#include <dfs_posix.h> /* 当需要使用文件操作时,需要包含这个头文件 */
static void readwrite_sample(void)
{
int fd, size;
char s[] = "hello world\\n", buffer[80];
/* 以创建和读写模式打开 /text.txt 文件,如果该文件不存在则创建该文件 */
fd = open("/text.txt", O_WRONLY | O_CREAT);
if (fd >= 0)
{
write(fd, s, sizeof(s));
close(fd);
}
/* 以只读模式打开 /text.txt 文件 */
fd = open("/text.txt", O_RDONLY);
if (fd>= 0)
{
//读取内容 read(int fd, void *buf, size_t len);
size = read(fd, buffer, sizeof(buffer));
close(fd);
rt_kprintf("Read from file test.txt : %s \\n", buffer);
if (size < 0)
return;
}
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(readwrite_sample, readwrite sample);
例题(面试题)
C语言
在32 位的系统上
char | 1 字节 | -128 ~127 / 0 ~ 255 |
---|---|---|
unsigned char | 1 字节 | 0 ~ 255 |
signed char | 1 字节 | -128 ~ 127 |
int | 4 字节 | -2,147,483,648 ~ 2,147,483,647 |
unsigned int | 4 字节 | 0 ~ 4,294,967,295 |
short | 2 字节 | -32,768 ~ 32,767 |
unsigned short | 2 字节 | 0 ~ 65,535 |
long | 4 字节 | -2,147,483,648 ~ 2,147,483,647 |
unsigned long | 4 字节 | 0 ~ 4,294,967,295 |
float | 4 字节 | 1.2E-38 ~ 3.4E+38 |
double | 8 字节 | 2.3E-308 ~ 1.7E+308 |
在32位系统中,在#pragma pack(4)和#pragma pack(8)的情况下,结构体的大小分别是
struct One{
double d;
char c;
int i;
}
struct Two{
char c;
double d;
int i;
}
#prama pack(n):指定c编译器按照n个字节对齐
#pragma pack(4):
#pragma pack(8):
为表示关系x≥y≥z,应使用C语言表达式
(x>=y)&(y>=z)
(x>=y)AND(y>=z)
(x>=y>=z)
(x>=y)&&(y>=z)
操作系统
进程与程序不是一一对应的,一个程序可以启动多个进程
执行一个作业可能会运行多个进程
进程是动态的
倘若一款存储器的数据线条数为 16 条, 地址线条数为 20 条, 那么此存储器的容量有多少?
2^20 * 16=16MB
计算机在一个指令周期的过程中,为从内存读取指令操作码,首先要将( C )的内容送到地址总线上
A.指令寄存器(IR)
B.通用寄存器(GR)
C.程序计数器(PC)
D.状态寄存器(PSW)
下面代码有什么错误?
#include <stdio.h>
void main()
{
char *s = "AAA";
s[0] = 'B';
printf("%s", s);
}
(1)"AAA"是字符串常量(定义在只读区域),s是指针,指向这个字符串常量,所以声明s的时候就有问题,应该是cosnt char* s=“AAA”。
(2)然后又因为是常量,所以对是s[0]的赋值操作是不合法的。
若修改:char s[] = “AAA”; char* str2 = s; str2[0] = ‘B’; 这样"AAA"就存到了栈区中,可以修改
下面代码运行后会是什么现象?
#include <stdio.h>
#define N 500
void main()
{
unsigned char count;
for(count = 0; count < N; count++)
{
printf("---%d---\\n", count);
}
}
进入不断打印count值的死循环
因为unsigned char 类型变量的最大值为255,所以count只能从0一直增加到255,然后又恢复为0,无法退出for循环。
下面函数的返回值是?
int foo(void)
{
int i;
char c = 0x80;
i = c;
if(i > 0)
return 1;
return 2;
}
返回值为2
因为0x80 == 128,超出了char类型变量c的表示范围(-128~127),所以c == -128,进而i == -128,i < 0
判断下列表达式正确与否?
char str[2][3] = {“a”, “b”}; // 正确,str是一个可存放两个字符串的字符串数组
char str[2][3] = {{1, 2}, {3, 4}, {5, 6}}; // 错误,行列不匹配
char str[] = {“a”, “b”}; // 错误,字符数组不能存放两个字符串 char str[] = {'a', 'b'}; / char str[] = "ab";
char str[2] = {“a”, “b”}; // 错误,字符数组不能存放两个字符串 char str[] = {'a', 'b'};
在C语言中字符用’ '括起来,而字符串用” ”括起来。
ARM
CPU的内部结构
-
控制单元(指令寄存器、指令译码器、操作控制器)
-
运算单元(算术逻辑单元)
-
存储单元(专用寄存器和通用寄存器)
-
时钟
中断的优缺点
-
优点:实现CPU和I/O设备的并行,提高CPU的利用率和系统的性能
-
缺点:中断处理过程需要保护现场、恢复现场,整个过程需要一定的时间和空间开销。如果中断的频率太高,会降低系统的性能
SRAM、DRAM、SDRAM的区别
- SRAM:静态的随机存储器,加电情况下,不需要刷新,数据不会丢失,CPU的缓存就是SRAM
- DRAM:动态随机存储器,加电情况下,也需要不断刷新,才能保存数据,最为常见的系统内存
- SDRAM:同步动态随机存储器,即数据的读取需要时钟来同步,也可用作内存
GPIO的输入输出模式有哪些
输入模式:浮空输入、带上拉输入、带下拉输入、模拟输入
输出模式:开漏输出、推挽输出、开漏复用输出、推挽复用输出
UART、SPI 是全双工类型
IIC、USB 是半双工类型
UART和TTL、RS-232、RS-485的关系
TTL(晶体管-晶体管逻辑电平) | 规定+5V(或>=2.4V)等于逻辑“1”,0V(或<=0.4V)等于逻辑“0”,噪声容限为0.4V |
---|---|
RS-232 | 采用负逻辑传输,规定**-5V ~ -15V等于逻辑“1”,+5V ~ + 15V为逻辑“0”**,噪声容限为2V |
RS-485 | 采用差分传输,规定A线电平比B线电平高200mV以上时为逻辑“1”,A线电平比B线电平低200mV以上时为逻辑“0” |
RS-232与RS-485的区别
- 区别:
①抗干扰性:RS-485接口的抗干扰性比RS-232接口强,因为RS-485采用差分传输。
②传输距离:RS-485接口(1200m)的传输距离比RS-232接口(50m)远。
③通信能力:RS485接口在总线上允许连接多达128个收发器,而RS-232接口只允许一对一通信。
④传输速率:RS-485接口的最高传输速率为10Mbps,而RS-232接口为20Kbps。
⑤信号线:RS-485接口组成半双工需要两根信号线,全双工需要四根信号线;RS-232接口一般使用RXD、TXD、GND三根线组成全双工。
UART一帧可以传5/6/7/8位,IIC必须是8位,SPI可以8/16位。
CAN总线接口相对于RS-232接口、RS-485接口的优点
CAN总线接口相对于RS-232接口的优点是抗干扰能力强、传输距离远。它采用差分传输,内置CRC校验,传输可靠性强。
CAN总线接口相对于RS-485接口的优点是能构成多主系统,同一时刻可以有两个或两个以上的设备处于发送状态,适用于实时性要求高的工控领域。
网络
TCP与UDP区别:
连接方面:TCP面向连接,UDP是无连接的
安全方面:TCP提供可靠的服务,无差错,不丢失、不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付
传输效率:TCP传输效率相对较低,UDP传输效率高
连接对象数量:TCP只能是点到点、一对一的;UDP支持一对一,一对多,多对一和多对多通讯
为什么需要三次握手,第三次握手去掉行不行
不行,第三次握手为了防止已经失效的连接请求报文突然传输到了服务器,从而建立错误的连接,浪费资源
还能防止发生死锁,若服务器发出第二次握手而客户端没有收到,服务器开始传输数据报后客户端便不会理会,导致服务器以为丢包而源源不断地发送数据报,造成死锁
以上是关于RT-Thread— 知识点总结(RTT认证+面试题汇总)的主要内容,如果未能解决你的问题,请参考以下文章