目录
30 为什么在FreeRTOS中信号量、队列等设计了两套函数
41.1 RAM(Random Access Memory,随机存取存储器)(运行内存)
41.2 ROM(Read-Only Memory,只读存储器)
45.1 IP地址(Internet Protocol Address)
1 环形缓冲区
环形缓冲区经常被用于串口通信、网络通信。
#define BUFFER_SIZE 5 //环形缓冲区大小
#define uint8_t unsigned char
typedef struct {
uint8_t buffer[BUFFER_SIZE];
uint8_t head; //写指针
uint8_t tail; //尾指针
bool is_full;
} RingBuffer;
//初始化缓冲区
void ring_buffer_init(RingBuffer *rb)
{
rb->head = 0;
rb->tail = 0;
rb->is_full = false;
}
//写入数据到缓冲区
bool ring_buffer_write(RingBuffer *rb, uint8_t data)
{
//缓冲区已满
if (rb->is_full)
{
return false;
}
rb->buffer[rb->head] = data;
rb->head = (rb->head + 1) % BUFFER_SIZE;
//写指针追上读指针,表示缓冲区已满
if (rb->head == rb->tail)
{
rb->is_full = true;
}
}
//从缓冲区读取数据
bool ring_buffer_read(RingBuffer *rb, uint8_t *data)
{
//缓冲区为空
if (rb->head == rb->tail && !rb->is_full)
{
return false;
}
*data = rb->buffer[rb->tail];
rb->tail = (rb->tail + 1) % BUFFER_SIZE;
rb->is_full = false; //一旦读取,缓冲区就不会再满了
return true;
}
//获取缓冲区是否为空
bool ring_buffer_is_empty(RingBuffer *rb)
{
return (rb->head == rb->tail) && !rb->is_full;
}
//获取缓冲区是否已满
bool ring_buffer_is_full(RingBuffer *rb)
{
return rb->is_full;
}
int main()
{
RingBuffer rb;
ring_buffer_init(&rb);
//测试写入数据
for (uint8_t i = 0; i < 7; i++)
{
if (ring_buffer_write(&rb, i))
{
printf("写入数据:%d\n", i);
}
else
{
printf("缓冲区满,无法写入数据:%d\n", i);
}
}
return 0;
}
2 什么是FIFO
- 特性:
- 先进先出,可以减轻CPU负担
- 实现:
- 使用软件实现:环形缓冲区
- 使用硬件实现:比较高端单片机或者是Linux开发板自带
- 应用场景:
- 串口通信、数据采集、DMA、音频处理
3 什么是原子操作
原子操作(Atomic Operation)是指一种不可分割的操作,在计算机系统中它要么完全执行成功,要么完全不执行,即使在多线程或多进程环境下也不会被中断或打断。它是实现并发编程中线程安全的重要基础,是用于保证共享数据一致性和正确性的基础且高效的手段。
#include <stdatomic.h>
#define NUM_INCREMENTS 1000
int counter = 0;
//使用GCC的__sync_fetch_and_add实现原子递增
void *increment_counter(void *arg) {
for (int i = 0; i < NUM_INCREMENTS; i++) {
__sync_fetch_and_add(&counter, 1); //原子加1
}
}
#include <linux/kernel.h>
#include <linux/atomic.h>
#include <linux/delay.h>
#define NUM_INCREMENTS 1000
atomic_t counter; //定义一个原子变量
int increment_thread_fn(void *data)
{
for (int i = 0; i < NUM_INCREMENTS; i++)
{
atomic_inc(&counter); //原子加1
msleep(1); //模拟延迟
}
printk(KERN_INFO "Increment thread finished\n");
return 0;
}
3.1 原子操作的特点
- 不可分割性:原子操作在执行过程中不允许其他线程或进程插入操作,不能被中断。
- 一致性:在操作完成前,其他线程无法看到操作的中间状态,保证了数据的一致性。
3.2 原子操作的实现方式
主要取决于系统架构和编程语言的支持:
- 硬件支持的原子指令:由CPU直接提供支持的指令,执行时不会被打断,如Test-and-Set、Compare-and-Swap(CAS)、Fetch-and-Add等。
- 锁机制:在某些编程语言或操作系统中,原子操作可以通过锁(例如互斥锁mutex或自旋锁spinlick)来实现。锁定数据,使得在一个线程操作完成之前,其他线程无法访问该数据,保证操作的原子性。
- 内存屏障:内存屏障(Memory Barrier)是一种组织CPU或编译器对特定内存进行重新排序的指令,用于确保多线程环境下的数据一致性。
3.3 常见的原子操作实例
- 计数器递增/递减:如i++和i--操作,通常在多线程环境下需要保证其原子性。
- 交换(Swap):两个变量的交换操作需要原子性保证,以免中间状态被其他线程读取。
- 比较并交换(CAS):这是并发编程中常用的原子操作,用于判断变量是否具有预期值,只有在满足预期值
3.4 应用场景
- 多线程计数:计数器在多线程场景下频繁使用,原子操作保证了计数器的正确性。
- 无锁数据结构:一些高效的数据结构,如无锁队列、无锁栈,以来原子操作来实现多线程下的安全性和效率。
- 同步标志位:某些情况下,可以使用原子操作设置和检查标志位来实现简单的线程同步。
3.5 优缺点
- 优点:原子操作的开销比锁机制更小,因为它不需要上下文切换或操作系统内核干预;在大部分情况下,原子操作比锁更快。
- 缺点:原子操作通常只能用于简单的数值更新、布尔值检查等单步操作,无法处理复杂的多步骤操作。在某些复杂场景下,仍然需要锁机制来保证多步骤操作的完整性。
4 面向对象的三大特征
- 封装(Encapsulation)
- 封装时将数据和操作这些数据的代码(方法)放在一起,形成一个独立的单元——对象。对象的内部数据通常是私有的,只能通过公有方法访问,从而保护数据不被随意修改。
- 例:一个Car对象有私有的speed属性,只能通过setSpeed()和getSpeed()方法修改和获取速度。
- 继承(Inheritance)
- 继承允许一个类(子类)从另一个类(父类)继承属性和方法。子类可以重用父类的代码,并可以添加或修改功能。
- 例:Car类可以继承Vehicle类的属性和方法,如MOVE(),然后添加honk()方法。
- 多态(Polymorphism)
- 多态是指同一操作在不同对象上可以表现出不同的行为。通常通过方法重写实现(虚函数virtual),不同类的对象可以通过相同的接口调用不同的实现。
- 例:Dog和Cat都继承自Animal,他们都有speak()方法,但具体的speak()实现不同。
示例:
#include <iostream>
using namespace std;
//父类:Animal
class Animal
{
protected:
//封装:属性通常是私有或受保护的
string name;
public:
//构造函数
Animal(string n): name() {}
//虚函数:多态机制
virtual void speak()
{
cout << name << "makes a sound." << endl;
}
};
//子类:Dog继承自Animal
class Dog
{
public:
Dog(string n): Animal(n) {}
//重写父类的speak方法,实现多态
void speak() override
{
cout << name << "barks." << endl;
}
};
//子类:Cat继承自Animal
class Cat
{
public:
Cat(string n): Animal(n) {}
//重写父类的speak方法,实现多态
void speak() override
{
cout << name << "meows." << endl;
}
};
//主函数
int main()
{
//封装:通过构造函数创建对象
Dog dog("Rex");
Cat cat("Whiskers");
//继承:Dog和Cat继承了Animal的属性和方法
dog.speak(); //输出:Rex barks.
cat.speak(); //输出:Whiskers meows.
//多态:使用父类指针调用不同子类的实现
Animal *animal1 = &dog;
Animal *animal2 = &cat;
animal1->speak(); //输出:Rex barks.
animal2->speak(); //输出:Whiskers meows.
return 0;
}
5 SPI和IIC的寻址区别
- SPI寻址方式:
- 一共四根线:MISO、MOSI、SCLK、CS
- 使用CS片选引脚,选择对应的设备进行通信
- IIC寻址方式:
- 一共两根线:SDA(数据线)、SCL(时钟线)
- 通过从机地址(7位/10位)来进行寻址
6 GPIO工作模式
GPIO:通用输入输出端口。
GPIO一共有8种工作模式,4种输入,4种输出。
模式 | 方向 | 描述 |
浮空输入 | 输入 | 引脚悬空,无内部上拉或下拉,完全由外部电路决定状态,易受干扰。 |
上拉输入 | 输入 | 引脚内部连接上拉电阻,未连接时状态为高电平,减少悬空带来的干扰。 |
下拉输入 | 输入 | 引脚内部连接下拉电阻,未连接时状态为低电平,减少悬空带来的干扰。 |
模拟输入 | 输入 | 用于模拟信号的输入,如ADC,禁用数字输入电路以减少功耗。 |
推挽输出 | 输出 | 引脚既能输出高电平也能输出低电平,用于驱动负载,如LED。 |
开漏输出 | 输出 | 仅能输出低电平或高阻态,需外部上拉电阻,常用于IIC等总线通信。 |
复用功能模式 | 输入/输出 | GPIO引脚用于外设功能,如UART、SPI、PWM等信号传输。 |
模拟模式 | 输入/输出 | 引脚用于模拟功能,如DAC或ADC输入,禁用数字部分以减少干扰。 |
7 SPI的工作模式
SPI(串行外设接口,Serial Peripheral Interface)有四种不同的工作模式,主要是由时钟极性(CPOL)和时钟相位(CPHA)这两个参数来决定的,它们决定了时钟信号如何与数据交换进行同步。
7.1 SPI的基本概念
SPI是一种全双工、同步的通信协议,常用于距离的芯片间通信。它包括以下四个基本信号线:
- MOSI(Master Out Slave In):主设备发送数据,外设接收数据
- MISO(Master In Slave Out):外设发送数据,主设备接收数据
- SCLK(Serial Clock):时钟信号,由主设备生成
- SS/CS(Slave Select/Chip Select):选择某个从设备的信号线,低电平时选中从设备
7.2 时钟极性(CPOL)和时钟相位(CPHA)
- CPOL:控制时钟空闲状态时的电平
- CPOL = 0:时钟空闲时为低电平
- CPOL = 1:时钟空闲时为高电平
- CPHA:控制数据是在时钟的第一个还是第二个跳变边沿采样
- CPHA = 0:在第一个时钟跳变边沿(从空闲到活动)采样数据
- CPHA = 1:在第二个时钟跳变边沿采样数据
7.3 四种工作模式
SPI的工作模式由CPOL和CPHA两个信号组合定义的,总共有4种模式:
模式 | CPOL | CPHA | 说明 |
模式0 | 0 | 0 | 空闲时SLCK为低电平,数据在第一个跳变沿采样 |
模式1 | 0 | 1 | 空闲时SLCK为低电平,数据在第二个跳变沿采样 |
模式2 | 1 | 0 | 空闲时SLCK为高电平,数据在第一个跳变沿采样 |
模式3 | 1 | 1 | 空闲时SLCK为高电平,数据在第二个跳变沿采样 |
7.4 各模式的数据传输过程
- 模式0(CPOL = 0,CPHA = 0)
- 时钟空闲时SLCK为低电平,数据在SCLK的上升沿(低到高)采样,在下降沿(高到低)发送数据
- 模式1(CPOL = 0,CPHA = 1)
- 时钟空闲时SLCK为低电平,数据在SCLK的下降沿采样,在上升沿发送数据
- 模式2(CPOL = 1,CPHA = 0)
- 时钟空闲时SLCK为高电平,数据在SCLK的下降沿采样,在上升沿发送数据
- 模式3(CPOL = 1,CPHA = 1)
- 时钟空闲时SLCK为高电平,数据在SCLK的上升沿采样,在下降沿发送数据
8 SPI有几根线可以去除哪些线
有几根线呢?
- 四根线
- SCLK:时钟线,用于同步数据传输时的时序控制
- CS:片选线,用于选择对应的从机设备
- 两根数据线
- MOSI:主设备输出,从设备输入:
- MISO:主设备输入,从设备输出:
可以去除哪些呢?
- 若不需要双向通信:可以根据情况去除MOSI或MISO
- 若只进行一对一通信:可以去除CS片选线
9 UART、IIC、SPI的区别
- 通信方式区别:
- UART:异步通信,因为没有时钟线
- IIC和SPI:同步通信,IIC时钟线:SCL,SPI时钟线:SCLK
- 接线区别:
- UART:两根线。TX接收和RX发送。
- SPI:四根线。SCLK(时钟线)、CS(片选)、两根数据线:MOSI(主机输出从机输入)和MISO(主机输入从机输出)
- IIC:两根线。SDA(数据线)、SCL(时钟线)。
- 设备数量区别:
- UART:一对一通信
- IIC:支持多主机多从机器之间的通信
- SPI:一主机多从机通信
- 传输速率区别:
- UART:由波特率决定(如:115200)
- IIC:标准模式(100kbps)和快速模式(400kbps)高速模式(3.4Mbps)
- SPI:速度可配置
- 工作模式区别:
- UART:支持全双工和半双工
- IIC:半双工
- SPI:全双工
10 UART和USART的区别
UART(Universal Asynchronous Receive/Transmitter)和USART(Universal Synchronous/Asynchronous Receive/Transmitter)都是用于串行通信的接口,主要区别在于它们支持的通信模式:
10.1 UART
- 通信方式:仅支持异步通信,即发送端和接收端不使用统一的时钟信号。
- 时钟信号:无时钟信号,数据帧通过预定义的波特率进行同步。
- 工作原理:发送端在开始位(start bit)后发送数据,接收端在没有时钟的情况下依赖波特率来解释数据。
- 应用场景:适用于点对点的低速数据通信。
10.2 USART
- 通信方式:支持异步和同步通信两种模式。
- 异步模式:与UART相同,没有时钟信号,依靠波特率同步。
- 同步模式:发送端和接收端共享时钟信号,数据与时钟同步传输。
- 时钟信号:
- 在同步模式下使用时钟信号进行同步通信。
- 工作原理:
- 异步模式:与UART相同。
- 同步模式:发送和接收的数据与时钟信号严格同步,提高通信的准确性和速度。
- 应用场景:
- 可以用于高速和高可靠的串行通信,尤其是需要同步时钟的场合。
11 软件IIC和硬件IIC的区别
- 实现方式
- 软件IIC:通过控制GPIO(输入输出引脚)来模拟IIC的SCL和SDA信号来产生时序
- 硬件IIC:MCU内部的专用硬件模块(IIC外设)来实现,软件只负责发出命令
- 性能:
- 软件IIC:速度通常较慢,受限于CPU
- 硬件IIC:速度通常较快,可以高频工作,不受到CPU的区别
- 可靠性:
- 软件IIC:可靠性较低,取决于CPU的运行速度、程序员的代码设计
- 硬件IIC:可靠性较高
12 为什么IIC的GPIO配置要使用开漏输出
- 开漏输出是什么:
- 无法输出高电平,只能输出低电平,加上上拉电阻可以让引脚输出高电平
- 为什么使用开漏输出:
- 假设使用推挽输出,一个设备输出高电平,一个设备输出低电平,这个时候会导致短路烧毁设备。
- 在IIC中可以挂载多个设备,这时需要区分是谁在使用IIC总线,实现了线与的功能。
13 TCP三次握手和四次挥手
TCP采用三次握手和四次挥手的原因与其确保数据可靠性、全双工通信模式以及网络传输的状态管理有关。
13.1 为什么是三次握手
三次握手是为了确保双方的发送和接收能力都准备就绪,并防止已经失效的连接请求包被误处理。
- 第一次握手:客户端发送SYN报文给服务器,表示希望建立连接。这时,客户端只是告诉服务器,它想要开始通信,但并不知道服务器是否已经收到这个请求。
- 第二次握手:服务器收到客户端的SYN后,返回SYN+ACK(同步+确认)报文,表示它已经收到了客户端的请求,并且同意建立连接,同时发送自己的SYN。这一阶段确保服务器接收到客户端的请求并能够回复,但服务器也不知道客户端是否已收到回复。
- 第三次握手:客户端收到服务器的SYN+ACK后,再次发送ACK报文确认。这一阶段确认了客户端已收到服务器的回复,连接建成。
三次握手的核心是为了确保双向通信的可靠性:即客户端和服务器都能收到对方的请求,并确认彼此都准备好发送和接收数据,
13.2 为什么是四次挥手
四次挥手用于安全、可靠地终止连接,确保双方都可以独立的结束通信,因为TCP的连接是全双工的(双方可以同时发送和接收)。
- 第一次挥手:客户端发送FIN报文,表示它已经没有数据要发送了,希望终止通信。此时,客户端进入FIN_WAIT_1状态。
- 第二次挥手:服务器收到FIN报文后,回复ACK报文,表示它已收到客户端的关闭请求,但可能还有数据需要继续发送。此时,客户端进入FIN_WAIT_2状态,等待服务器发送数据。
- 第三次挥手:服务器发送完所有数据后,发送FIN报文,表示它的数据也发送完毕,准备关闭连接。
- 第四次挥手:客户端收到服务器的FIN报文后,回复ACK,表示双方连接已完全关闭。此时客户端进入TIME_WAIT状态,确保服务器能够收到ACK,避免丢包。
四次挥手的核心原因是TCP连接是全双工的:双方需要独立关闭各自的发送通道,确保在关闭前发送的所有数据都被正常接收。
总结:
- 三次握手是为了确保连接的可靠性,保证双方的收发能力都正常。
- 四次挥手则是由于TCP的全双工通信机制,双方都需要独立确认已经没有数据需要发送,确保连接安全、可靠地终止。
13.3 假设
如果TCP连接采用两次握手而不是三次握手,可能会引发以下问题,导致通信不可靠:
1.失效的连接请求可能被误处理
假设采用两次握手,当客户端发送一个SYN请求包时,服务器收到该请求并发送SYN-ACK确认。 此时,服务器就认为链接已经建立,直接准备接收数据。
但问题是,客户端可能因为某些原因没有收到服务器的数据包,导致客户端认为连接没有成功建立,从而不会发送ACK。此时:
- 旧请求引发错误通信:加入客户端发送的SYN请求包因为网络延迟等原因滞留在网络中,后来重新传到服务器时,服务器又会重新处理这个请求,误以为是新的连接,这会导致服务器建立了错误的连接,资源被浪费。
2.无法确认对方的接收能力
在两次握手中,客户端和服务器只进行了一次请求和确认,服务器能确认自己收到了客户端的请求,但客户端无法确认服务器是否接收了它的ACK。因从,可能导致服务器误以为客户端准备好发送数据,但实际客户端可能没有建立连接。
如:
- 未建立连接:假设客户端发起SYN请求,服务器返回SYN-ACK,服务器认为连接已建立。如果此时网络出现问题,客户端没有收到服务器的SYN-ACK并没有发送ACK,服务器将等待客户端的数据,但客户端从未认为连接成功,双方通信失败。
14 TCP通信如何保证通信的可靠性
TCP(Transmission Control Protocol)通过一系列机制来保证通信的可靠性,确保数据在传输过程中能够完整、无误的到达目的地。以下是主要的可靠性保证机制:
14.1 数据包分段和重组
TCP会将数据分成多个小的段(segment),然后按序号传输到接收方。接收方通过这些序号可以将数据重组为原始的顺序,即使他们到达的顺序可能不同。这样,TCP可以处理传输过程中数据顺序的变化。
14.2 确认应答(ACK)
每当接收方成功收到一个数据包,它会发送一个确认应答(ACK)给发送方。发送方收到ACK后才能认为该数据已成功传送并从发送队列中移除。如果发送方在超时时间内未收到ACK,则认为数据包可能丢失,需要重新发送。
14.3 超时重传
如果发送方在一定时间内没有收到对数据包的确认应答(ACK),它会自动重传该数据包,知道收到ACK为止。超时时间会根据网络状况动态调整,以确保通信效率。
14.4 滑动窗口机制
TCP使用滑动窗口控制数据的传输流量。滑动窗口的大小会根据接收方的处理能力和网络状况动态调整,从而防止网络拥堵。滑动窗口机制确保了数据的流速适中,不会超出接收方的处理能力。
14.5 流量控制
TCP协议中包含流量控制机制,避免了发送方数据过多、接收方处理不过来的情况。通过滑动窗口调整机制,接收方可以通知发送方当前能够处理的数据的数据量大小,从而调整发送速率。
14.6 拥塞控制
TCP协议通过慢启动、拥塞避免、快速重传和快速恢复等算法进行拥塞控制。通过检测丢包、超时等现象,判断网络是否拥塞,调整发送速率,以缓解网络负担,保证数据传输的稳定性。
14.7 数据校验和
TCP数据包头包含一个校验和字段,用于校验数据在传输过程中是否发生损坏。每个数据包在发送前都会计算校验和,接收方收到后也会计算并对比,确保数据包内容未发生改变。如果校验和不匹配,则认为该包数据损坏,将丢弃该包并请求重传。
14.8 连接管理(三次握手和四次挥手)
TCP使用三次握手和四次挥手来建立连接,确保通信双方都已准备好数据传输;使用四次挥手来关闭连接,确保所有数据在连接断开前已完全传输完毕。这种连接管理过程防止了网络上出现“半开”或“不完整”的连接状态。
14.9 序列号和确认号
每个TCP包头都有序列号和确认号,这使得接收方可以检测数据包的顺序并识别丢失的包。重发请求,同时,发送方也可以确认哪些数据包已成功到达,确保数据的完整性和顺序。
15 TCP粘包问题
15.1 为什么会出现TCP粘包
TCP是面向字节流的协议,数据在传输时可能会出现粘包问题。粘包问题是指多个应用层的数据包在传输过程中被合并在一个TCP报文中,接收端无法准确区分每个数据包的边界。以下是TCP粘包发生的主要原因:
- TCP是流式传输:TCP将应用程序的数据视为没有边界的字节流,发送端会根据发送缓冲区的大小来合并或拆分数据发送,数据的边界并不会保留在传输过程中。
- 数据发送频率不固定:如果应用层发送数据的频率较高,TCP会根据自身算法(例如Nagle算法)优化传输效率,合并多个小数据包成一个较大的TCP报文发送,导致粘包。
- 接收端读取不及时:接收端从TCP缓冲区读取数据时,如果不及时处理数据,多个数据包可能已经被缓冲区存储在一起,形成粘包。
15.2 如何防止TCP粘包
通常需要在应用层通过特定的协议设计来标识和解析数据包边界。以下是常见的防止TCP粘包的方式:
1. 固定长度的消息
- 每个数据包的长度是固定的,接收方按照这个固定的长度读取数据,不会读取超出或不足的数据。
- 优点:简单,易于实现。
- 缺点:对于小数据包会浪费网络带宽,因为需要传输固定大小。
2. 使用特殊的分隔符
- 在每个数据包的末尾添加一个特殊的字符或字符串作为分隔符,接收方根据这个分隔符来区分不同的数据。常用的分割符可以是\n、\r\n或者其他自定义符号。
- 优点:简单,适合文本类协议(如HTTP、Redis)等。
- 缺点:数据内容不能包含这个特殊字符,或者需要转义处理,增加复杂性。
3. 发送数据包时带上长度信息
- 在每个数据包的开头加上报包长度字段,接收方先读取长度字段,然后根据该长度读取相应长度的数据。常见方式是使用一个固定大小的前缀字段存储消息的长度。
- 优点:可靠性高,适合传输复杂的二进制数据。
- 缺点:需要额外的协议设计,增加编解码的复杂性。
实现示例:
//发送方:
int msg_len = message.size();
send(sock, &msg_len, sizeof(int), 0); //发送长度
send(sock, message.c_str(), msg_len, 0); //发送消息本体
//接收方:
int msg_len;
recv(sock, &msg, sizeof(int), 0); //先接受长度
char *buffer = new char(msg_len);
recv(sock, buffer, msg_len); //根据长度接收消息
4. 关闭Nagle算法
Nagle算法是TCP的一种优化算法,用于减少小数据包的发送次数。Nagle会等待一定时间,将多个小数据包合并为一个较大的数据包发送,从而提高网络效率。但在高频率发送小数据的场景下,容易引起粘包。
通过设置TCP选项TCP_NODELAY,可以关闭Nagle算法,避免合并小数据包。
int flag = 1;
setsocket(sock, IPPROTO_TCP, TCP_NODELAY, (void *)&flag, sizeof(flag));
16 TCP和UDP的区别
- 数据的可靠性
- TCP提供可靠的数据传输,因为TCP会经过三次握手,可以确保数据正常到达
- UDP无连接,提供不可靠的数据通信
- 通信方式
- TCP面向连接的通信方式
- UDP不需要连接
- 数据传输的效率
- TCP通信速率比UDP慢(UDP丢包概率比较大)
- 应用场景
- TCP一般用于邮件,文件传输(FTP协议)
- UDP一般用于视频流,在线游戏,直播
17 TCP和UDP通信的建立流程
17.1 TCP通信(面向连接)
17.1.1 TCP客户端
- socket函数创建套接字
- connect函数连接服务器
- read/write或recv/send函数进行读取/发送数据
- close函数关闭客户端
17.1.2 TCP服务器
- socket函数创建套接字
- bind函数绑定IP地址和端口号
- listen函数去监听对应的端口
- accept函数等待客户端的连接
- read/write函数进行读取/发送数据
- close函数关闭服务器
17.2 UDP通信
17.2.1 UDP客户端
- socket函数创建套接字
- sendto函数发送数据
- secvfrom函数来接受服务器的消息
- close函数关闭客户端
17.2.2 UDP服务器
- socket函数创建套接字
- bind函数绑定IP地址和端口号
- recvfrom函数接收数据
- sendto函数发送数据
- close函数关闭服务器
18 什么是DMA
直接内存存取器Direct Memory Access,一种无需CPU参与就可以让外设和系统之间的内存进行双向的数据传递的机制。
优点:可以减轻CPU负担,提高系统运行效率
19 什么是系统调用
系统调用就是用户和内核之间的接口,通过这个接口可以使用户空间访问到内核空间,因为直接访问内核空间非常不安全。
20 什么是状态机编程
状态机编程是一种通过管理系统状态和状态之间的转换来控制程序流程的编程方法。它特别适用于需要对多个状态进行跟踪并根据不同事件做出决策的场景,例如协议处理、嵌入式系统中的控制逻辑、用户界面设计等。
20.1 什么是状态机
状态机(State Machine)由以下几个基本元素组成:
- 状态(State):系统处于的某个特定条件或模式。
- 事件(Event):触发状态变化的外部或内部输入。
- 状态转换(Transition):由于某个事件的发生,系统从一个状态切换到另一个状态。
- 动作(Action):在状态转换过程中或在某个状态下执行的操作。
20.2 状态机的工作流程
状态机通过不断监控外部或内部事件,根据当前状态和发生的事件,确定系统需要保持当前状态还是进行状态转换。每次状态转换可能伴随着特定的操作,直到满足系统的目标或条件。
20.3 示例:自动售货机
我们用一个简单的自动售货机例子来演示状态机编程。假设该售货机有三个状态:
- 等待投币(WAIT_FOR_COIN)
- 选择商品(SELECT_PRODUCT)
- 发放商品(DISPENSE_PRODUCT)
事件可能包括:
- 投币(COIN_INSERTED)
- 选择商品(PRODUCT_SELECTED)
- 发放完成(DISPENSE_COMPLETED)
状态机的状态转移圈:
[WAIT_FOR_COIN]--->(COIN_INSERTED)--->[SELECT_PRODUCT]
[SELECT_PRODUCT]--->(PRODUCT_SELECTED)--->[DISPENSE_PRODUCT]
[DISPENSE_PRODUCT]--->(DISPENSE_COMPLETED)--->[WAIT_FOR_COIN]
状态机编程实现:
#include <stdio.h>
//定义状态
typedef enum {
WAIT_FOR_COIN,
SELECT_PRODUCT,
DISPENSE_PRODUCT
} State;
//定义事件
typedef enum {
COIN_INSERTED,
PRODUCT_SELECTED,
DISPENSE_COMPLETED
} Event;
//定义状态机的当前状态
State currentState = WAIT_FOR_COIN;
//定义事件处理函数
void handleEvent(Event event) {
switch (currentState) {
case WAIT_FOR_COIN:
if (event == COIN_INSERTED) {
printf("Coin inserted, please select a product.\n");
currentState = SELECT_PRODUCT; //状态转换
}
break;
case SELECT_PRODUCT:
if (event == PRODUCT_SELECTEDD) {
printf("Product selected, dispensing product...\n");
currentState = DISPENSE_PRODUCT; //状态转换
}
break;
case DISPENSE_PRODUCT:
if (event == DISPENSE_COMPLETED) {
printf("Product dispensed, ready for next customer.\n");
currentState = WAIT_FOR_COIN; //状态转换
}
break;
default:
break;
}
}
int main()
{
//模拟状态机事件流
handleEvent(COIN_INSERTED); //投币
handleEvent(PRODUCT_SELECTED); //选择商品
handleEvent(DISPENSE_COMPLETED); //商品发放完成
return 0;
}
20.4 状态编程机的优势
- 逻辑清晰:状态机将复杂的逻辑分解为一系列状态和时间的组合,每个状态和事件都有清晰的定义。
- 可扩展性强:可以很容易的添加新的状态或事件来扩展状态机的功能。
- 易于维护:状态机模型有助于简化代码,减少条件语句嵌套,提升代码可读性和可维护性。
状态机编程在嵌入式系统、协议设计、游戏开发、UI涉及等领域中非常常见。
21 Linux和RTOS的区别
- 应用场景:
- Linux:桌面计算机、服务器、嵌入式系统
- RTOS:嵌入式系统
- 实时性:
- Linux:不保证实时性
- RTOS:提供严格的实时性保证,任务调度和执行时间非常可预测,任务的响应都非常快
- 资源占用:
- Linux:资源占用比较庞大
- RTOS:轻量级操作系统,占用的资源比较少,适用于在嵌入式系统
- 内存方面:
- Linux:分为虚拟内存和物理内存,用户空间和内核空间
- RTOS:没有虚拟内存和用户空间 内核空间
- 扩展性:
- Linux:扩展性非常高,可以通过加载各种模块和各种库达到扩展功能
- RTOS:扩展性比较有限,功能比较专门化
22 RTOS中任务同步的方法
- 队列:
- 还可以进行数据的传递
- 信号量:分为二进制信号量和计数型信号量
- 二进制信号量:实现共享资源的访问
- 计数型信号量:实现生产者和消费者模型
- 互斥量:
- 实现共享资源的访问
- 事件组:
- 等待多个任务的通知
- 任务通知:
- 轻量级的任务同步方式,依据是任务结构体里的TCB控制块
- 不需要创建就可以使用,一般用于一对一通信
23 RTOS中时间片的大小由什么决定
可以在FreeRTOSConfig.h文件中找到宏定义configTICK_RATE_HZ,如下图:
24 FreeRTOS中的调度算法
- 抢占式调度:
- 高优先级的任务可以打断低优先级任务的执行(适用于优先级不同的任务)
- 时间片轮转:
- 相同优先级的任务有相同大小的时间片(1ms),当时间片被耗尽,任务会强制退出
- 协作式调度:
- 使用vtaskdelay函数(信号量、互斥量等)释放CPU的资源让其他任务运行
25 FreeRTOS中的空闲任务
在FreeRTOS或类似的实时操作系统(RTOS)中,空闲任务(Idle Task)是一个默认的、优先级最低的任务。它的主要功能是当系统中没有其他高优先级任务需要运行时占用CPU资源,使得系统始终保持有任务在运行,即避免CPU空转。
空闲任务的特点:
- 最低优先级:空闲任务的优先级是最低的,它只有在没有其他可运行的任务时才会执行。如果有其他任务进入就绪状态,空闲任务会立即被中断并停止运行。
- 自动创建:FreeRTOS在启动任务调度时,会自动创建空闲任务,开发者不需要手动创建。
- 任务的回收(删除):空闲任务通常负责回收系统中被删除任务的资源(如堆栈和任务控制块),确保系统资源能够被有效管理。
- 低功耗模式:空闲任务还可以被用来触发低功耗模式(如挂起CPU进入省电模式)。当系统处于空闲状态时,空闲任务可以调用低功耗API将系统转入低功耗状态,等待下一次任务激活。
- 后台处理:空闲任务(钩子函数)可以执行一些不紧急的后台处理任务,如系统维护、监控任务状态等,但这些操作通常不应该占用太多资源,以免影响其他任务的执行。
26 FreeRTOS中创建任务的方法
在FreeRTOS中,创建任务主要有两种方法:使用xTaskCreate和xTaskCreateStatic。
26.1 使用xTaskCreate
xTaskCreate时FreeRTOS中创建任务的标准方法。它会动态地分配内存来存储任务控制块(TCB)和任务栈。这种方法的函数原型如下:
BaseType_t xTaskCreate(
TaskFunction_t pvTaskCode, //任务函数的指针
const char * const pName, //任务的名称
uint16_t usStackDepth, //任务栈的大小(以字为单位)
void *pvParameters, //任务的参数
UBaseType_t uxPriority, //任务的优先级
TaskHandle_t *pxCreatedTask //任务句柄的指针(可选)
);
特点:
- 内存管理:xTaskCreate在堆上动态内存分配用于控制人任务块和栈。需要确保系统堆有足够的内存来满足这些需求。
- 灵活性:可以在运行时动态地创建任务,适合内存管理和任务数量不固定的场景。
- 优点:可以消除动态内存分配带来的风险和开销,是用于对内存使用有严格要求的系统。
26.2 主要区别
- 内存管理:
- xTaskCreate:动态分配内存,可能会导致内存不足或内存碎片。
- xTaskCreateStatic:静态分配内存,避免了动态内存管理的问题。
- 适用场景:
- xTaskCreate:适用于任务数量动态变化或内存管理较为宽松的场景。
xTaskCreateStatic:适用于内存受限或需要严格控制内存使用的场景。
- 内存预分配:
- xTaskCreate:无需预分配任务控制块和任务栈的内存。
- xTaskCreateStatic:需要预分配控制人物快和任务栈的内存。
- 资源消耗:
- xTaskCreate:对系统的资源消耗大,因为它是在系统运行的时候进行分配。
- xTaskCreateStatic:对资源的消耗比较小。
27 FreeRTOS中任务的状态
- 就绪态:当任务被创建的时候就会进入到就绪态
- 运行态:当任务的代码正在被执行的时
- 阻塞态:当任务在等待信号量或者是互斥量等的时候
- 挂起态:使用vTaskSuspend函数进入到挂起态
28 FreeRTOS中的TCB任务控制块
在FreeRTOS中,TCB(Task Control Block,任务控制块)是用于管理和维护任务状态的关键数据结构。每个任务都会有一个独立的TCB,里面存储了与该任务相关的各种信息。通过TCB,操作系统可以跟踪任务的执行状态、优先级、堆栈指针。
//可以看成用来描述任务的结构体
typedef struct tskTaskControlBlock
{
volatile StackType_t *pxTopOfStack; //指向任务栈中最后一个入栈项的位置,这个成员必须是TCB结构体的第一个成员
#if (portUSING_MPU_WRAPPERS == 1)
xMPU_SETTINGS xMPUSettings; //MPU设置是端口层的一部分定义,这个成员必须是TCB结构体的第二个成员
#endif
ListItem_t xStateListItem; //任务状态列表项所在的列表,表明任务的状态(就绪、阻塞、挂起等)
ListItem_t xEventListItem; //用于引用人物在事件列表中的位置
UBaseType_t uxPriority; //任务的优先级,0是最低优先级
StackType_t *pxStack; //指向任务堆栈的起始位置
char pcTaskName[configMax_TASK_NAME_LEN]; //创建任务时指定的描述性名称,仅用于调试
#if ((portSTACK_CROWTH > 0) || (configRECORD_STACK_HIGH_SDDRESS == 1))
StackType_t *pxEndofStack; //指向堆栈的最高有效地址
#endif
#if (portCRITICAL_NESTING_IN_TCB == 1)
UBaseType_t uxCriticalNesting; //保存临界区嵌套深度,适用于不在端口层维护计数的端口
#endif
#if (configUSE_TRACE_FACILITY == 1)
UBaseType_t uxTCBNumber; //保存每次创建TCB时递增的信号,便于调试器判断任务是否被删除或重新创建
UBaseType_t uxTaskNumber; //专门为第三方跟踪代码使用的任务编号
#endif
#if (configUSE_MUTEXES == 1)
UBaseType_t uxBasePriority; //最后分配给任务的优先级,用于优先级继承机制
UBaseType_t uxMuteseHeld; //任务当前持有的互斥锁数量
#endif
#if (configUSE_APPLICATION_TASK_TAG ==1)
TaskHookFunction_t pxTaskTag; //任务钩子函数,用于用户自定义任务行为
#endif
};
29 FreeRTOS和RT-Thread的区别
FreeRTOS和RT-Thread都是常见的嵌入式实时操作系统(RTOS),但它们的设计理念、内核结构和适用场景有所不同。下面是它们的主要区别:
29.1 基本概述
特性 | FreeRTOS | RT-Thread |
来源 | 英国Real Time Engineers Ltd(现Amazon维护) | 中国开源社区 |
开发语言 | C | C |
内核架构 | 轻量级微内核 | 组件化内核 |
适用场景 | 工业控制、物联网、低功耗设备 | 物联网、智能硬件、消费电子 |
许可证 | MIT | Apache 2.0 |
29.2 设计架构对比
29.2.1 内核架构
- FreeRTOS:
- 采用轻量级微内核,仅提供基本的任务调度、消息队列、信号量、互斥锁等,没有复杂的中间件。
- 适用于资源受限(如RAM ≤ 10KB)的嵌入式设备。
- 提供任务级调度,但不支持进程(进程隔离)。
- RT-Thread:
- 采用组件化设计,提供更完整的RTOS生态。
- 具备实时内核、组件扩展(文件系统、网络协议栈、图形UI),更像一个小型Linux。
- 适用于资源稍多(RAM ≥ 20KB)的设备,如物联网、智能家居。
29.2.2 任务调度
调度策略 | FreeRTOS | RT-Thread |
调度方式 | 优先级调度(抢占式 & 轮转) | 优先级调度(抢占式 & 轮转) |
支持时间片 | 是 | 是 |
支持多核SMP | 仅FreeRTOS SMP版本 | 支持(适用于多核架构) |
- FreeRTOS默认不支持多核调度,但有FreeRTOS SMP版本适用于多核系统(如ESP32)。
- RT-Thread支持对称多处理(SMP),适用于多核CPU(如RISC-V、ARM Cortex-A)。
29.2.3 内存管理
机制 | FreeRTOS | RT-Thread |
静态内存 | 支持 | 支持 |
动态内存 | 多种方式(malloc/free、堆分配) | rt_malloc(对象内存管理) |
内存池 | 有(但较简单) | 支持对象内存池(更适合嵌入式) |
- RT-Thread的动态内存管理更完善,支持对象管理、内存池、堆管理,适合GNU/网络应用。
- FreeRTOS主要基于堆(heap1 - heap5),需要自行管理碎片问题。
29.2.4 设备驱动
设备管理 | FreeRTOS | RT-Thread |
驱动模型 | 无(需手写底层驱动) | POSIX风格VFS设备驱动模型 |
文件系统 | FATFS(第三方) | 支持FAT、NFS\YAFFS、SPIFFS等 |
网络协议 | LWIP(第三方) | LwIP/TencentOS/TCP/IP协议栈 |
USB支持 | 无(需外部库) | 提供USB Host/Device |
- FreeRTOS没有原生驱动框架,使用时需要手动编写硬件驱动。
- RT-Thread提供完整的设备驱动框架,并支持文件系统、USB、网络协议,适用于智能设备。
29.3 生态 & 开发支持
生态 | FreeRTOS | RT-Thread |
开发工具 | Keil、IAR、Eclipse、VS Code | Keil、IAR、VS Code、RT-Studio |
社区支持 | 国际化社区、活跃度高 | 国产社区、中文文档丰富 |
官方支持厂商 | ST、TI、NXP、ESP | ST、全志、GD、瑞萨 |
UI框架 | 需第三方(LVGL) | 内置RTGUI、LVGL |
- FreeRTOS适合国际化项目,尤其适用于STM32、ESP32、TI MSP340等芯片。
- RT-Thread适合国内物联网设备,国产MCU(如GD32、全志、瑞萨)支持更好,并提供RT-Studio开发环境。
29.4 适用场景对比
应用场景 | 推荐RTOS |
小型MCU(低于10KB RAM) | FreeRTOS |
物联网设备(Wi-Fi / MQTT) | RT-Thread |
智能家居/机器人 | RT-Thread |
工业控制(PLC/传感器) | FreeRTOS |
图形界面GUI | RT-Thread |
汽车电子/车载设备 | FreeRTOS |
无线通信(ESP32/STM32) | FreeRTOS |
29.5 选择建议
- 选择FreeRTOS
- 超轻量级系统(RAM < 10KB)
- 工业控制、低功耗传感器
- ESP32、STM32单片机
- 需要低功耗 & 高实时性的应用
- 选择RT-Thread
- 需要文件系统、网络协议栈
- 智能硬件、物联网
- 支持国产MCU(GD32、瑞萨、全志)
- 图形界面(RTGUI/LVGL)
- 多核CPU / SMP调度
29.6 总结
特性 | FreeRTOS | RT-Thread |
架构 | 轻量级微内核 | 组件化内核 |
任务管理 | 抢占式调度 | 抢占式调度 |
内存管理 | 简单(堆管理) | 更完善(对象/内存池) |
驱动框架 | 无(需手写) | VFS设备驱动框架 |
文件系统 | FATFS(外部库) | 内置FAT、SPIFFS |
网络协议 | LwIP(外部库) | 内置LwIP/MbedTLS |
GUI支持 | 需外部(LVGL) | RTGUI/LVGL |
多核SMP | 仅FreeRTOS SMP版本 | 原生支持SMP |
国产MCU支持 | 一般 | 优秀(GD32、瑞萨) |
适用场景 | 工业控制、低功耗设备 | 物联网、智能硬件 |
总的来说:
- FreeRTOS适合超轻量、工业控制、低功耗嵌入式系统。
- RT-Thread适合物联网、智能设备、多任务应用,并且对国产MCU友好。
30 为什么在FreeRTOS中信号量、队列等设计了两套函数
FromISR函数是FreeRTOS中用于在中断服务例程(ISR)中调用的特殊版本API,它与普通的FreeRTOS函数的主要区别在于其设计用于中断上下文的操作。以下为xQueueGenericSendFromISR函数的特点,以及它与普通的xQUEUESend函数的区别:
30.1 中断上下文中的限制
- 中断优先级:普通的FreeRTOS API函数通常是在任务上下文中调用的,任务上下文允许在任意时刻被抢占和切换。然而,中断上下文中有更高的实时要求,所以不能使用普通的API。
- portASSERT_IF_INTERRUPT_PRIORITY_INVALID():在FromISR函数中,FreeRTOS会检查终端的优先级是否允许调用系统API。如果中断的优先级高于配置的最大可调用系统API优先级,调用时将触发断言失败。这是为了保证高优先级中断不会破坏系统的整体调用。
30.2 防止阻塞
- 不能阻塞:普通的xQUEUESend函数如果队列已满,会使任务进入阻塞状态,直到有空位可写入。但是在ISR中,任务是不能阻塞的。因此,FromISR版本函数不会阻塞,而是直接返回一个指示队列已满的错误值(errQUEUE_FULL)。
30.3 中断屏蔽与恢复
- 设置与恢复中断状态:在FromISR函数中,系统在操作关键数据结构(如队列)时使用portASSERT_IF_INTERRUPT_MASK_FROM_ISR()来屏蔽中断,以避免并发访问。这是因为终端可能会在任意时刻发生,若不屏蔽中断则可能导致数据一致性问题。操作完成后,再通过portCLEAR_INTERRUPT_MASK_FROM_ISR()恢复中断状态。而在普通的FreeRTOS函数中,可能使用vTaskSuspendAll等机制来控制任务切换。
30.4 任务唤醒机制
- pxHigherPriorityRaskWoken指针:中断不能直接切换任务(即从ISR切换到另一优先级更高的任务),因为调度程序在中断上下文不可用。因此,在FromISR函数中,如果需要唤醒更高优先级的任务,它不会立即执行上下文切换。这在ISR中非常关键,确保了任务调度的正确性。
30.5 与队列相关的锁定机制
- 队列锁定:普通API中,队列操作时会根据系统的状态来判断是否立即唤醒等待的任务,而在FromISR函数中,如果队列被锁定,它不会立即唤醒任务,而是通过pxQueue->cTxLock记录锁定期间的数据操作,直到队列解锁时再处理唤醒的任务。这种延迟处理的机制在中断中尤为重要,避免复杂的处理逻辑影响中断响应时间。
31 FreeRTOS中信号量和队列的区别
- 队列可以用于存储数据。
- 信号量一般不用于存储数据,信号量用于实现任务之间的同步和互斥。信号量是只有一个存储空间的特殊队列。
32 Linux驱动三大基础类
- 字符设备驱动:
- 主要用于处理顺序数据的读写操作
- 主要用于终端,串口,LED,蜂鸣器
- 块设备驱动:
- 主要用于以块为单位的数据的读写操作
- 主要用于硬盘、SSD
- 网络设备驱动:
- 处理网络数据包的发送和接收
- 主要用于网卡
33 什么是中断
在嵌入式系统中,中断是一种重要的机制,它允许处理器暂停当前执行的任务,去处理更加紧急的任务(中断服务程序)。当中断发生时,处理器会停止当前执行的代码,保存当前的状态,并且跳转到中断处理函数中去执行,当执行完成后又回到原来的地方去执行。
34 中断优先级
中断优先级是指在多个中断同时发生时,系统确定哪个中断首先被处理的机制。其主要作用是确保关键或时间敏感的任务能够及时响应。
- 中断优先级越高,越先被响应和执行。
- 中断优先级数值越小,优先级越大。
34.1 抢占优先级
- 定义:抢占优先级决定了中断能否打断当前正在执行的中断或任务。优先级数值越小,优先级越高。
- 功能:高优先级的中断可以打断低优先级的中断。当一个高优先级的中断发生时,当前的低优先级中断服务例程(ISR)会被挂起。
34.2 子优先级
- 定义:子优先级用于在相同抢占优先级的中断之间进一步区分。当多个中断具有相同的抢占优先级时,子优先级决定它们的执行顺序。
- 功能:优先级较低的终端不会影响正在处理的高优先级中断,但如果有多个相同抢占优先级的中断,子优先级将决定哪个中断先被处理。
35 中断函数能有返回值和参数吗
- 返回值
- 不允许有返回值:ISR被设计为快速响应中断事件,返回值可能导致不必要的复杂性和延迟。他们通常以"void *"类型定义。
- 参数
- 不接受参数:ISR通常是由硬件中断触发的,无法直接传递参数。虽然可以通过全局变量或静态变量在ISR中访问数据,但这不是标准做法。
36 什么是看门狗
看门狗旨在检测和恢复系统故障。它是一种硬件计数器,要求系统在规定时间内定期对其进行复位(喂狗),若没有及时复位看门狗,计时器将会溢出,并触发一种预定的响应,通常是系统复位或重启。
作用:
- 防止系统卡死:若系统由于软件错误、思索或其他原因导致无法继续正常工作,看门狗定时器可以自动复位系统,从而防止系统长时间卡死。
- 提高系统可靠性:在任务关键的嵌入式应用中,如工业控制、汽车电子、医疗设备等,看门狗定时器是确保系统持续可靠运行的重要手段。
- 恢复程序运行:当系统由于不可预见的故障导致异常行为时,看门狗定时器可以让系统迅速恢复到已知的初始状态,减少故障的影响。
示例代码:
//主程序
int main(void)
{
WDT_Init(); //初始化看门狗定时器
//模拟程序正常运行
while (1)
{
for (volatile int i = 0; i < 1000000; i++)
{
//程序的其他部分
}
//响应操作,防止看门狗定时器复位系统
IWDG->KR = 0xAAAA; //喂狗,重置看门狗定时器计数
}
}
37 ARM中的寄存器
37.1 通用寄存器(R0-R12)
- R0-R12:这13个寄存器是通用寄存器,主要用于存储临时数据、函数参数和中间计算结果。它们没有固定的特殊用途,可以根据需要在程序中使用
- 在一些情况下,部分寄存器会在调用约定中有特殊含义。例如,在ARM的函数调用中,R0-R3常用于传递函数参数,而函数的返回值一般会存放在R0)。
37.2 栈指针(R13, SP)
- R13(SP, Stack Pointer):这是栈指针寄存器,用于指向堆栈的顶部。栈指针用于管理栈空间,主要是在函数调用时,用来保存局部变量,函数参数和返回地址。在ARM Cortex-M系列处理器中,R13有两种模式:
- MSP(Main Stack Pointer,主栈指针):MSP是系统的主栈指针,通常用于处理核心系统任务和异常处理(如中断服务程序)。系统启动时,MSP是默认的栈指针。
- PSP(Process Stack Pointer,进程栈指针):PSP是用于用户进程或线程的栈指针,通常在处理用户级任务时使用。操作系统可以通过切换到PSP来隔离用户进程的栈空间,确保系统的安全性和稳定性。
在ARM Cortex-M架构中,可以通过CONTROL寄存器来切换当前使用的栈指针,决定使用MSP还是PSP。通常,内核和中断处理使用MSP,应用程序或任务使用PSP。
37.3 链接寄存器(R14,LR)
- R14(LR,Link Register):链接寄存器用于存储函数调用时的返回地址。当调用函数时,ARM指令会自动将当前的返回地址存入LR寄存器,执行完函数后,通过返回指令(如BX LR)跳回到LR中保存的地址,恢复到调用者的上下文。
37.4 程序计数器(R15,PC)
- R15(PC,Program Counter):程序计数器用于存储当前正在执行指令的地址。ARM指令集的大部分操作是基于PC进行的,PC会自动递增,通常每次递增4字节,指向下一条指令。PC也是跳转、分支指令的目标寄存器。
37.5 程序状态寄存器(PSR)
- CPSR(Current Program Status Register):当前程序状态寄存器保存指令执行时的条件标志位、处理器模式和中断使能状态。常用的标志位包括:
- N(Negative):指示运算结果是否为负数
- Z(Zero):只是运算结果是否为零
- C(Carry):指示是否有进位或借位发生
- V(Overflow):指示是否发生溢出
ARM还支持一些其他状态寄存器,如SPSR,用于一场处理时保存中断前的状态。
38 Cortex-M3和Cortex-M4
Cortex-M3和Cortex-M4都是ARM公司的微控制器(MCU)架构,主要用于嵌入式系统和物联网设备。它们在架构上非常相似,但Cortex-M4在Cortex-M3的基础上做了一些增强,主要区别如下:
38.1 DSP指令支持
- Cortex-M3:不支持DSP指令集,只能通过普通运算指令来处理信号计算。
- Cortex-M4:支持DSP(数字信号处理)指令,如SIMD(单指令多数据)和饱和运算,能更高效地处理音频、传感器数据等应用。
38.2 FPU(浮点运算单元)
- Cortex-M3:没有硬件浮点单元,所有浮点运算都需要软件模拟,计算速度较慢。
- Cortex-M4:可选集成单精度FPU(Floating Point Uint),提高浮点计算效率。
38.3 指令集
- Cortex-M3:和Cortex-M4都支持Thumb-2指令集,提高代码密度和执行效率。
- Cortex-M4:额外支持DSP指令,使其在数学运算、滤波等场景更高效。
38.4 性能
- Cortex-M3:和Cortex-M4都基于ARMv7-M架构,支持哈佛架构和三级流水线。
- Cortex-M4:由于支持DSP指令和可选FPU,在数学计算和信号处理方面比Cortex-M3速度更快。
38.5 功耗
- Cortex-M4:由于增加了DSP和可选FPU,功耗略高于Cortex-M3,但差异不大。具体取决于芯片设计和应用场景。
38.6 应用场景
处理器 | 适用场景 |
Cortex-M3 | 一般嵌入式控制,低功耗物联网设备、工业自动化 |
Cortex-M4 | 需要数字信号处理的应用,如电机控制、语音识别、医疗设备、传感器数据处理 |
39 什么是SMP
SMP(Symmetric Multi-Processing,对称多处理)是一种多处理器架构,在该架构中,多个CPU共享同一个内存和I/O设备,并且所有CPU具有相同的权限和对称性,可以同时运行任务,提高计算性能和系统吞吐量。
SMP的特点:
- 多个CPU共享同一物理内存,每个CPU访问相同的地址空间。
- 所有CPU权限相同,可均衡的调度任务,没有主从之分(区别于主从架构)。
- 多个CPU通过共享的缓存一致性协议(如MESI)保持数据同步,防止数据不一致问题。
- 任务可以在不同CPU之间动态调度。提高CPU资源利用率。
- 单个操作系统控制多个CPU,操作系统负责负载均衡。
40 Cache是什么
Cache(缓存)是一种用于加速数据访问的高速存储区域,它存储一部分频繁使用的数据或指令,以减少从主存(RAM)或更慢的存储设备(如硬盘)中重复读取数据的时间。
40.1 Cache的特点
- 高速:Cache存储器的访问速度比主存(RAM)更快,接近处理器的速度,因此能够快速提供数据,减少CPU等待时间。
- 临时性:Cache存储的数据是临时的,当数据不再频繁使用或缓存空间不足时,旧的数据会被替换。
- 层次结构:现在计算机通常由多级缓存,分别成为L1、L2、L3缓存。每一级缓存的大小和速度有所不同。
- L1 Cache(一级缓存):位于CPU内部,速度最快,但容量较小(一般几KB到几十KB)
- L2 Cache(二级缓存):位于CPU内或紧邻CPU,容量比L1大,速度稍慢(几百KB到几MB)
- L3 Cache(三级缓存):在多核CPU中共享,容量更大(几MB到几十MB),速度比L2慢,但仍远快于主存。
40.2 Cache的工作原理
- 缓存命中(Cache Hit):当CPU请求的数据已经在Cache中时,可以直接从Cache中读取数据,这称为缓存命中,能极大地提高性能。
- 缓存未命中(Cache Miss):如果CPU请求的数据不在Cache中,就必须从更慢的存储器(如RAM或硬盘)中读取数据,再将其写入Cache中,供以后快速访问。
40.3 Cache的常见用途
- CPU缓存:用于存储当前正在执行的程序的指令和数据,以减少CPU与RAM之间的访问延迟。
- 磁盘缓存:在硬盘和主存之间,缓存硬盘上的部分数据,加快文件的读写速度。
- Web缓存:浏览器缓存常访问的网站资源(如图片、CSS文件),减少每次访问相同网站时的加载时间。
- 数据库缓存:存储数据库中常用的查询结果或数据,以减少从磁盘中读取数据的时间。
40.4 Cache与其他存储器的区别
- 与RAM相比:Cache容量更小但速度更快;RAM容量更大但访问速度相对较慢。
- 与磁盘相比:Cache通常是临时存储,数据不断被替换,而磁盘是长期存储,容量很大但速度较慢。
41 RAM 、ROM、Flash的区别
RAM、ROM、Flash是常见的三种存储类型。
41.1 RAM(Random Access Memory,随机存取存储器)(运行内存)
- 特点:RAM是一种易失性存储器,这意味着当设备断电时,存储在RAM中的数据会丢失。
- 用途:RAM主要用于存储计算机或设备运行时需要的临时数据,比如运行程序时的数据,缓冲数据等。它的存取速度很快,是CPU直接访问的主要数据存储区域。
- 分类:
- SRAM(静态RAM):速度快,但成本高。通常用于CPU缓存。
- DRAM(动态RAM):相对便宜但速度较慢,常用于电脑的主存(内存条)。
41.2 ROM(Read-Only Memory,只读存储器)
- 特点:ROM是一种非易失性存储器,断电后数据不会丢失。ROM中的数据通常在制造时被写入,之后不能轻易修改或擦除。
- 用途:ROM常用于存储固件或启动代码,比如计算机的BIOS、嵌入式设备中的引导程序等。由于数据写入后无法轻易更改,所以非常适合存储不需要频繁更新的程序或数据。
- 分类:
- PROM(一次性可编程ROM):用户可以编程一次,之后不可更改。
- EPROM(可擦写可编程ROM):可以用紫外线擦除并重新写入数据。
- EEPROM(电可擦写可编程ROM):可以用电的方法擦除并重新编程。
41.3 Flash(闪存)
- 特点:Flash是一种非易失性存储器,断电后数据不会丢失。与ROM相比,Flash存储器允许多次擦除和写入,且无需像EEPROM那样逐字节擦除,它可以按块擦除。
- 用途:Flash广泛用于存储需要经常读写的非易失性数据,比如USB闪存盘,固态硬盘(SSD),嵌入式系统中的固件等。由于其可擦写性和耐用性,Flash逐渐取代了传统的EEPROM。
- 分类:
- NAND Flash:常用于大容量存储设备如SSD和存储卡,具有较高的存储密度和较快的写入速度。
- NOR Flash:用于需要快速随机访问的应用,常用于嵌入式系统中的固件存储。
41.4 总结
- RAM:临时存储、断电后数据丢失、读写速度快。
- ROM:永久存储、断电后数据不丢失、只读或极少修改。
- Flash:永久存储、可多次擦写、常用于大容量可移动存储设备。
42 什么是PWM
PWM是脉冲宽度调制技术。
- 主要原理:通过调制脉冲宽度,来等效获取所需的波形
- 占空比:高电平占整个周期的比例 * 100%
- 应用:舵机、LED灯亮度、LCD背光灯
43 配置一个引脚为输出输入功能的流程
void MX_GPIO_Iinit(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
_HAL_RCC_GPIOA_CLK_ENABLE();
_HAL_RCC_GPIOB_CLK_ENABLE();
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_RESET);
GPIO_InitStruct.Pin = GPIO_PIN_5;
GPIO_InitStruct.Node = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}
- 使能GPIO组的引脚时钟
- 选择对应的GPIO引脚
- 设置GPIO对应的模式(输入或者输出)
- 设置GPIO的电阻状态(上下拉,没有电阻)
- 设置GPIO的速度
- 初始化GPIO的设置
44 嵌入式中常用的调试方法有什么
44.1 串口调试(UART Debugging)
- 概述:通过串口向外部设备(如电脑)输出调试信息,是最常见的调试手段。可以通过发送字符或数据值来查看代码执行情况。
- 优点:
- 实现简单,成本低。
- 适合调试逻辑问题和流程控制。
- 缺点:
- 只能提供有限的信息。
- 影响系统的实时性,因为发送数据需要时间。
- 使用方法:
- 嵌入式设备的UART接口连接到电脑。
- 通过软件(如PuTTY或Tera Term)读取串口输出。
44.2 jTAG/SWD调试
- 概述:使用jTAG(Joint Test Action Group)或SWD(Serial Wire Debug)调试接口,连接调试器(如ST-Link,j-Link)直接控制和调试处理器。
- 优点:
- 可以设置断点、单步执行、查看和修改寄存器及内存状态。
- 能够调试代码在芯片内部的执行流程。
- 缺点:
- 需要特定的调试器和硬件支持。
- 调试速度取决于接口和芯片速度。
- 使用方法:
- 将调试器连接到目标板的jTAG或SWD接口。
- 使用IDE(如Keil、IAR、STM32CubeIDE)设置断点和观察变量。
- 将调试器连接到目标板的jTAG或SWD接口。
44.3 LED灯调试
- 概述:通过控制板上的LED等来显示程序运行的不同状态。例如:用不同闪烁频率或开关状态表示程序是否进入了某些关键环节。
- 优点:
- 硬件实现简单,容易看到直观结果。
- 不会影响系统的正常运行。
- 缺点:
- 信息表达能力有限。
- 只能用于简单的状态指示。
44.4 硬件逻辑分析仪
- 概述:用于捕捉和分析数字信号(如SPI、I2C、UART等)通信的数据波形。可以帮助检查嵌入式系统和外部设备之间的通信问题。
- 优点:
- 可以捕获高频信号的实时通信数据。
- 非侵入式调试,能直观查看信号波形。
- 缺点:
- 需要额外的硬件设备(如Saleae Logic分析仪)。
- 不适合调试软件逻辑。
- 使用方法:
- 将逻辑分析仪的探针连接到通信接口(如I2C/SPI总线)。
- 通过电脑软件捕捉信号并分析通信协议。
44.5 断点与单步调试
- 概述:通过IDE工具设置断点和单步执行代码,查看代码的执行路径和当前变量状态。
- 优点:
- 可以精确定位到代码中的问题。
- 查看当前运行状态、寄存器和内存的变化。
- 缺点:
- 可能会影响嵌入式系统的实时性。
- 需要专用的硬件支持(如jTAG调试器)。
44.6 内存查看与调试
- 概述:通过调试器查看芯片中的内容,分析内存状态、全局变量、堆栈使用情况等。
- 优点:
- 能够详细分析内存问题,如内存溢出、堆栈溢出等。
- 可以直接查看外设寄存器的状态。
- 缺点:
- 需要调试器或IDE的支持。
44.7 示波器
- 概述:示波器用于捕捉并显示嵌入式系统中各类电信号波形,如时钟信号、PWM、ADC输出等。
- 优点:
- 可以实时查看电压信号,分析硬件电路的行为。
- 对于时间敏感的信号(如时钟、脉冲等)调试非常有效。
- 缺点:
- 只能调试电平信号,无法调试软件问题。
44.8 看门狗调试
- 概述:嵌入式系统中的看门狗(Watchdog Timer)可以检测系统是否进入了死循环或卡死状态。通过故意不喂狗(即不定期重置看门狗),可以逼迫系统复位,以帮助排查系统中潜在的问题。
- 优点:
- 对于调试系统异常或程序锁死有帮助。
- 能检测到系统长时间未响应的情况。
- 缺点:
- 只能用来检测死锁或异常重启,无法提供具体的调试信息。
44.9 GDB调试
- 概述:GDB(GNU Debugger)是Linux下常用的调试工具,支持嵌入式系统的调试工作。它能够通过命令行或集成环境(IDE)进行调试,支持断点、单步执行、查看内存和寄存器等功能。GDB可以与jTAG或GDBServer结合使用,调试嵌入式设备上的代码。
45 IP地址、子网掩码、网关、DNS的作用
45.1 IP地址(Internet Protocol Address)
- 作用:IP地址是网络中每个设备的唯一标识符,用于设备之间的通信,唯一标识了网络中每一台设备,是数据能够准确的传输到目标设备。
- IPv4地址通常由四个十进制数构成,每个数值由0到255之间的数字表示,例如:192.168.1.1。这叫做点分十进制表示法。
- IPv6地址是下一代IP协议,使用128位地址空间,通常以16进制表示。例如:2001:0db8:85a3:0000:0000:8a2e:0370:7334。
- 功能:每个连接互联网的设备都需要一个IP地址,来表示自己并与其他设备进行通信。在局域网中,IP地址也用来识别不同的主机。
45.2 子网掩码(Subnet Mask)
- 作用:子网掩码用于划分IP地址的网络部分和主机部分,帮助确定设备所属的网络范围。在IP地址中,子网掩码帮助计算机识别哪些地址属于同一网络,哪些属于不同网络。它还定义了一个网络中可以容纳的主机数。
- 子网掩码通常也是四组十进制数,常见的子网掩码格式为:255.255.255.0。
- 在二进制表示中,子网掩码的前面是连续的1,表示网络部分,后面是连续的0,表示主机部分。例如:255.255.255.0表示前24位用于网络,后8位用于主机。
- 功能:通过子网掩码,设备可以知道与它通信的目标设备是在相同的子网中还是在不同的子网中。如果在同一子网内,设备可以直接通信;如果在不同子网中,则需要通过网关进行通信。
45.3 网关(Gateway)
- 作用:网关是一个网络设备,它充当本地网络(如局域网)与外部网络(如互联网)之间的出入口。当一台计算机需要与局域网外的设备通信时,数据首先被发送到网关,网关负责将数据转发到目的地。
- 默认网关:通常指本地网络中的一个路由器或其他网络设备。它能够将局域网内的设备连接到更大的网络(例如互联网)。每台计算机都需要配置一个默认网关地址,当目标设备不在同一子网内,数据包会自动发送到默认网关。
- 功能:当网络设备发现目标设备不在本地子网中时,它会将数据包发送到网关,网关负责将数据包转发给其他网络上的设备,确保设备间的跨网通信顺利进行。
45.4 DNS(Domain Name System)
- 作用:DNS用于将人类容易理解的域名(如www.google.com)转换为计算机能够识别的IP地址(如142.250.72.36).它是互联网中的“电话簿”,通过查询DNS服务器,可以将域名解析为相应的IP地址。
- 域名是人类可以记住的方式来标识网站,而计算机只能识别IP地址。DNS服务器负责域名和IP地址之间的映射。
- 当你在浏览器中输入域名时,计算机会向DNS服务器查询该域名对应的IP地址,然后通过该IP地址与服务器进行通信。
- 功能:DNS简化了用户访问互联网的方式,用户只需记住网站的域名而不是复杂的IP地址。同时DNS还可以提高网络的可扩展性和灵活性,因为服务器的IP地址可以改变,而用户访问的网站地址不变。
45.5 总结
- IP地址:为每个网络设备提供唯一的标识,确保数据能准备传输到目标设备。
- 子网掩码:帮助划分网络和主机部分,确定同一网络中的设备。
- 网关:充当不同网络之间的桥梁,负责跨网通信。
- DNS:将域名解析为IP地址,简化了网络访问。
46 网络拓扑
46.1 什么是网络拓扑
网络拓朴是指网络中各个节点(如计算机、服务器、路由器等)以及它们之间的连接关系的结构,拓扑决定了数据在网络中如何流动、如何进行通信,并影响网络的性能和管理。选择合适的拓扑结构取决于网络规模、预算和性能要求等因素。
46.2 常见的网络拓扑类型有哪些
46.2.1 星型拓扑(Star Topology)
- 所有节点都通过独立的连接线连接到一个中央节点(如交换机或集线器)。
- 优点:易于安装和管理,故障节点不会影响其他节点。
- 缺点:中央结点的故障会导致整个网络瘫痪。
46.2.2 总线拓扑(Bus Topology)
- 所有节点通过一条主干线(总线)相连。
- 优点:布线简单,成本低。
- 缺点:总线故障会导致整个网络失效,节点数量过多可能导致性能下降。
46.2.3 环形拓扑(Ring Topology)
- 每个节点与其他两个节点相连,形成一个闭合环。
- 优点:数据传输速度快,易于管理。
- 缺点:任何一个节点或连接的故障都会影响整个网络。
46.2.4 网状拓扑(Mesh Topology)
- 每个节点都可以直接与多个其他节点相连,形成多个连接路径。
- 优点:具有高冗余性和可靠性,任何节点的故障都不会影响网络的整体功能。
- 缺点:布线复杂,成本高。
46.2.4 树形拓扑(Tree Topology)
- 结合了星型和总线型拓扑,形成一种分层结构。
- 优点:易于扩展,便于管理和维护。
- 缺点:主干节点的故障会影响整个子网络。
46.2.5 混合拓扑(Hybird Topology)
- 结合了两种或多种不同的拓扑结构,以满足特定要求。
- 优点:灵活拓展,可以根据需求优化。
- 缺点:设计和管理较为复杂。
47 OSI 7层模型和TCP IP四层模型
47.1 OSI七层模型
OSI七层模型 | 功能描述 | 对应的TCP/IP协议层 | 常见协议 |
第7层-应用层 | 为用户提供应用程序服务,处理特定的网络任务 | 应用层 | HTTP,FTP,SMTP,DNS |
第6层-表示层 | 数据格式的转换、加密、解密、压缩等功能 |
应用层(合并) | MIME、TLS/SSL |
第5层-会话层 | 负责建立、管理和终止会话 | 应用层(合并) | RPC、PPTP、NetBIOS |
第4层-传输层 | 端到端的传输、数据完整性和流量控制 | 传输层 | TCP、UDP |
第3层-网络层 | 负责数据包的路由选择、网络间传输 | 网络层 | IP、ICMP、IGMP |
第2层-数据链路层 | 定义链路层数据传输的协议,帧传输和校验 | 网络接口层 | Ethernet、PPP、ARP |
第1层-物理层 | 负责数据的物理传输(如光纤、电缆) | 网络接口层 | 光纤、电缆、无线协议 |
说明:
- TCP/IP模型将OSI的表示层、会话层、应用层三层合并为一个应用层。
- OSI模型是理论模型,TCP/IP是现实使用的协议模型。
- 常见的协议:HTTP(超文本传输协议)、TCP(传输控制协议)、IP(互联网协议)
47.2 TCP/IP四层模型
TCP/IP四层模型 | 功能描述 | 对应的OSI七层模型 | 常见协议 |
应用层 | 为用户提供网络服务和应用程序的接口 | 应用层、表示层、会话层 | HTTP、FTP、SMTP、DNS、Telnet |
传输层 | 提供端到端的数据传输、流量控制、可靠性 | 传输层 | TCP、UCP |
网络层 | 负责数据包在网络中的路由选择和传输 | 网络层 | IP、ICMP、IGMP |
网络接口层 | 管理主机与网络之间的物理连接和帧的传输 | 数据链路层、物理层 | Ethernet、ARP、PPP、MAC |
说明:
- TCP/IP四层模型的应用层包含了OSI七层模型中的表示层、会话层、应用层。
- 传输层和网络层在TCP/IP模型和OSI模型中的功能基本一致。
- 网络接口层对应OSI模型中的数据链路层和物理层,负责具体的网络连接和数据帧传输。
48 DHCP协议
DHCP(Dynamic Host Configuration Protocol,动态主机配置协议)是一种网络管理协议,用于自动为网络上的设备分配ip地址以及其他网络参数,如子网掩码、网关地址和DNS服务器地址。DHCP简化了网络设备的配置,尤其在大型网络中,不需要手动为每台设备配置IP地址。
48.1 基本原理
DHCP基于客户端-服务器模型工作,客户端是需要获取网络配置的设备,服务器是负责分配这些网络参数的设备。其基本流程包括以下几个步骤:
- DHCP发现(DHCP Discovery)
- 客户端设备(如计算机、手机等)启动后,会发送一个广播消息,称为DHCP Discover,来寻找可用的DHCP服务器。由于此时客户端还没有IP地址,因此它发送的Discovery消息使用源地址为0.0.0.0,并使用广播地址255.255.255.255作为目标地址。
- DHCP提供(DHCP Offer)
- DHCP服务器收到客户端的Discover请求后,会响应一个DHCP Offer消息。此消息包含可分配的IP地址、子网掩码、网关地址和租期等信息,并发送给客户端。DHCP服务器通常保留一段IP地址池供分配。
- DHCP请求(DHCP Request)
- 客户端从收到的一个或多个DHCP Offer消息中选择一个DHCP服务器的提供,并发送DHCP Request消息,表示请求确认使用该服务器分配的IP地址及其他网络配置。
- DHCP确认(DHCP Acknowledgement,DHCP ACK)
- DHCP服务器收到客户端的请求后,会发送一个DHCP ACK消息,确认客户端所请求的IP地址和网络参数。此时,客户端正式获得网络配置并开始使用分配的IP地址。
如果由于某种原因,服务器无法分配地址(如IP池已满) ,服务器会返回一个DHCP NACK(拒绝)消息,客户端将重新开始发现过程。
48.2 工作过程图解
48.3 DHCP租约(Lease)机制
DHCP协议使用租约机制来管理IP地址的分配。每个设备获得的IP地址都有一个租约期(Lease Time),即设备可以使用该IP地址的时间期限。租约期结束前,客户端可以向DHCP服务器发出续约请求,以继续使用该IP地址。租约期结束后,如果客户端未续约或未使用该IP地址,服务器可以将此IP地址重新分配给其他设备。
续约的过程通常分为两个阶段:
- T1时间点:租期过半时,客户端发送请求(DHCP Request)向服务器续约。
- T2时间点:如果续约失败,客户端会在租约到期前的75%时间再次尝试续约。
48.4 DHCP重要功能
- IP地址自动分配:DHCP自动为网络设备分配IP地址,避免手动设置每台设备的网络参数,大大简化了网络配置工作,特别适用于局域网(LAN)中的设备。
- 避免地址冲突:DHCP服务器管理和跟踪已分配的IP地址,防止网络上出现两个设备使用相同的IP地址的冲突问题。
- 支持静态IP地址:虽然DHCP主要用于动态分配IP地址,但它也支持静态IP分配。管理员可以为某些设备(如服务器、打印机)分配固定的IP地址,并将这些地址保存在DHCP服务器的配置文件中。
- 分配其他网络配置:除了IP地址,DHCP还可以自动分配其他网络参数,例如:
- 子网掩码:帮助设备确定其网络范围
- 默认网关:用于设备与外部网络(如互联网)进行通信
- DNS服务器:为设备提供域名解析服务
48.5 DHCP使用场景
- 家庭网络:路由器内置的DHCP服务器可以为连接的所有设备自动分配IP地址,免去手动配置。
- 企业局域网:在大型企业网络中,DHCP服务器用于管理大量终端设备的IP地址,简化网络管理并减少错误配置的可能性。
- 无线网络(Wi-Fi):无线接入点通过DHCP为连接到Wi-Fi的设备动态分配IP地址。
48.6 DHCP的优缺点
- 优点
- 自动化配置:DHCP可以使得网络管理员不必为每台设备手动分配和管理IP地址
- 灵活性:设备可以动态获取可用的IP地址,并根据需要释放或续约
- 易于扩展:尤其在大型网络中,DHCP可以轻松处理成百上千台设备的IP分配工作
- 缺点:
- 单点故障:如果DHCP服务器出现故障,网络中的设备将无法自动获取IP地址,从而无法正常工作
- 安全性问题:未经授权的设备可以接入网络并获得IP地址,造成潜在的安全威胁。可以通过配置DHCP Snooping等安全措施来防止这一问题。
49 什么是MQTT协议
MQTT(消息队列遥测传输)是一种轻量级的消息发布/订阅协议,特别适合于低带宽、高延迟或不可靠的网络环境。其主要特点包括:
- 发布/订阅模型:客户端可以发布消息到特定注意,也可以订阅感兴趣的主题,从而接收相关消息
- 轻量级:MQTT的开销很小,适合资源受限的设备。(基于TCP/IP协议实现)
- 质量服务等级:MQTT提供三种消息传递服务质量(QoS)级别,确保消息的可靠传输。
QoS级别 | 描述 | 消息传递保证 |
0 | 至多一次(At most once) | 消息发送一次,不确认,可能丢失 |
1 | 至少一次(At least once) | 消息至少发送一次,确保接收,但可能重复 |
2 | 只有一次(Exactly once) | 消息确保只发送一次,最可靠的传输 |
- 保持连接:MQTT支持长连接,客户端可以保持与代理服务器的连接,以便及时接收消息。
- 适用于物联网:由于其高效性,MQTT常用于物联网设备之间的通信。
MQTT的保活机制用于确保客户端和代理(Broker)之间的连接保持活跃。这一机制的主要目的在于检测和管理连接的状态,防止因长时间不活动而造成的意外断开,以下是保活机制的关键点:
- 保活时间(Keep Alive Interval):客户端在连接时会指定一个保活时间(以秒为单位),表示再次时间间隔内必须向代理发送至少一条消息(如心跳包)以保持连接。
- 心跳包:客户端会定期发送PINGREQ消息给代理,以确认连接的活跃性。代理收到后,会回复PINGRESP消息。
- 超时处理:如果代理在保活时间未收到客户端的任何消息(包括应用消息或心跳包),它会认为连接已断开,随后会关闭连接并清理资源。
- 连接恢复:一旦客户端检测到连接断开,可以尝试重新连接到代理,并根据情况进行消息重传。
通过这一机制,MQTT能够有效地管理连接状态,确保设备在不活动时不会占用过多资源,同时保持必要的通信能力。
50 JSON格式的数据
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,广泛用于客户端和服务器之间的数据传输。JSON数据格式易于人类阅读和编写,同时也方便机器解析和生成。虽然它基于JavaScript对象的语法,但与编程语言无关,几乎所有编程语言都支持JSON。
50.1 JSON的基本结构
JSON主要有两种数据结构:
1. 对象(Object):由花括号{}包裹,内部是键值对(key-value pair)。键必须是字符串,值可以是多种类型的数据。
示例:
{
"name": "Alice",
"age": 25,
"isStudent": false
}
2. 数组(Array):由方括号[]包裹,内部是有序的值列表,值之间用逗号分隔。
示例:
[
"apple",
"banana",
"cherry"
]
50.2 JSON支持的数据类型
- 字符串(String):用双引号""包围的文本。
"name" : "Alice"
- 数字(Number):整数或浮点数。
"age": 25
- 布尔值(Boolean):表示true或false。
"isStudent": false
- 数组(Array):值的有序列表,可以是任意类型的值。
"favoriteNumbers": {3, 7, 9}
- 对象(Object):键值对的无序集合。
"address": {
"city": "New York",
"zipcode": "10001"
}
- null:表示空值或无值。
"middleName": null
JSON示例:
{
"name": "Alice",
"age": 25,
"isStudent": false,
"address": {
"city": "New York",
"zipcode": "10001"
},
"favoriteColors": ["red", "green", "blue"],
"profileCompleted": true,
"lastLogin": null
}
50.3 JSON的优点
- 轻量级:JSON的语法简洁,数据占用的空间小,传输效率高。
- 可读性强:它结构清晰,易于理解,适合人类阅读和书写。
- 跨平台:JSON可以在多种编程语言中使用,且解析速度快,应用广泛。
- 与JavaScript兼容:JSON与javaScirpt原生支持,前端开发中广泛使用,特别是在AJAX请求和响应数据中。
50.4 常见应用场景
- 数据交换:在客户端与服务器之间传递数据时,通常会使用JSON格式。
- 配置文件:许多应用程序和框架使用JSON作为配置文件格式。
- API传输:许多Web API返回的数据格式就是JSON。
至此,B站【嵌入式自学-DeepMeet学长】的课程《嵌入式八股文面试题合集》课程已结束!