RT-Thread 知识总结

参考链接2:https://club.rt-thread.org/ask/article/a9405a80d7d8f079.html # 《RT-Thread设备驱动开发指南》—— 基础篇之第1章RT-Thread与设备驱动框架简介

0、其他

1、RT-Thread 如何根据对象名,查找对象?

rt_object_find() 函数用于在内核对象容器中查找指定类型和名称的对象。

RT-Thread 内核将所有对象视为一种特殊的数据结构,使用一个全局的对象容器数组 rt_object_contain[] 记录对象信息。
对象容器给每类内核对象分配了一个链表,所有内核对象都链接到该链表上。
内核根据名称遍历对应的链表,找到名称一致的对象并返回。

2、RT-Thread 如何打开一个设备?

1)查找设备:通过设备名查找对应的设备(rt_device_find()),若有设备则返回相应的设备句柄,否则返回 NULL;
2)打开设备:根据设备句柄,对设备进行初始化操作(调用设备的初始化函数 dev->init),并设置设备的状态为打开;
3)访问设备:通过读、写、控制等函数访问和控制设备

一、内核

1、RT-Thread 内核介绍

RT-Thread 内核包括对象管理(object.c)、线程管理(thread.c)、线程间通信(thread.c)、内存管理(mem.cmemheap.c...)、中断管理、时钟管理(clock.ctimer.c)、设备管理(device.c)、实时调度器(schedule.c)、等。

2、线程管理

2.1、线程调度

线程调度基于优先级全抢占式多线程调度算法,即在系统中除了中断处理函数、上锁和禁止中断的代码是不可抢占外,其他都可以抢占。线程优先级共 256 个/32 个,0 优先级代表最高优先级、最低优先级留给空闲线程。相同优先级采用时间片调度算法。 RT-Thread 总是让最高优先级线程优先调度,只要最高优先级线程就绪,总能得到 CPU 使用权。

线程对象由 3 个重要内容组成,分别是线程控制块、线程栈、入口地址等。线程栈地址和入口地址都存储在线程控制块。

// 第一个参数是入口地址,第二个参数存储在线程栈
std::thread th = std::thread(&LogImplement::run, this);
2.1.1、线程控制块结构体

线程控制块结构体:包含 1)记录线程栈的相关变量(如栈底地址、栈顶指针、栈大小);2)记录线程入口的相关变量(如入口地址、参数等);3)用于时间片调度的相关变量(如初始滴答数、剩余滴答数、定时器);4)用于同步的记录线程感兴趣的事件变量。

2.1.2 静态初始化线程过程

初始化线程,即将线程对象初始化注册到系统管理器,将线程属性记录到线程控制块中。

2.1.3 线程恢复

线程恢复函数将线程从阻塞线程链表移除,并插入就绪链表,设置链表状态为就绪态。

2.1.4 线程启动

线程启动函数,先将线程从初始态切换为挂起态,然后调用线程恢复函数将挂起态切换为就绪态,再执行一次调度操作。调度操作选择一个最高优先级的就绪线程,并判断能否立即抢占(若就绪线程优先级小于等于当前运行线程,均需等待运行线程释放 CPU)。

2.2、线程同步

RTT 通过信号量、互斥量与事件集实现线程间同步。

  • 1)线程通过对信号量、互斥量的获取与释放进行同步。互斥量通过优先级继承的方式解决了实时操作系统常见的优先级反转问题。
  • 2)线程通过对事件的发送与接收进行同步。事件集支持多事件的“或触发”和“与触发”,适合于线程等待多个事件的情况。
2.2.1 事件

参考链接:https://doc.embedfire.com/rtos/rtthread/zh/latest/application/event.html

一个事件发生即是一个同步。事件可以提供一对多、多对多的同步操作。一对多同步模型:一个线程等待多个事件的触发;多对多同步模型:多个线程等待多个事件的触发。

  • 事件与全局变量的区别:全局变量需要程序员实现互斥,并且线程要轮询全局变量,消耗系统资源;
  • 事件与信号的区别:信号只能提供一对一同步(一个线程等待一个信号触发),事件能提供一对多,多对多同步;

事件控制块是对 32 位事件标志位的封装:

struct rt_event {
  struct rt_ipc_object parent;
  rt_uint32_t set;  // 事件标志位
};
typedef struct rt_event *rt_event_t;

2.3、线程通信

RTT 通过信号、消息队列、邮箱等方式通信。

2.2.2 消息队列

消息队列是邮箱的扩展。消息队列是一种异步消息队列,它接收来自线程不固定长度的消息,其他线程可以从消息队列读取相应的信息。若消息队列为空,则挂起读取线程。若收到新的消息,挂起的线程将被唤醒以接收并处理消息。

3、时钟管理

时钟管理基于时钟节拍及定时器。时钟节拍由中断触发模式的硬件定时器产生。每经过一个时钟节拍,都会检查当前线程的时间片是否用完,以及是否有定时器超时。若有定时器超时,则调用相应的超时函数。

定时器工作机制
创建的定时器链表会以超时时间升序排序,RT-Thread 通过跳表算法来加快搜索链表元素的速度。

4、中断管理

Cortex-M 系列有 R0~R15 共 16 个寄存器,其中 R13 是堆栈指针寄存器,可以是主堆栈指针(MSP),也可以是进程堆栈指针(PSP);R14 是连接寄存器(LR,存储返回地址),R15 是程序计数器。

4.1 中断向量表

在 Cortex-M 内核上,所有中断都采用中断向量表的方式进行处理。即一个中断触发时,处理器将直接判定是哪个中断源,然后跳转到相应的处理函数。如下中断向量表:

__Vectors DCD __initial_sp ; Top of Stack
DCD Reset_Handler ; Reset 处理函数
DCD NMI_Handler ; NMI 处理函数
DCD HardFault_Handler ; Hard Fault 处理函数
DCD MemManage_Handler ; MPU Fault 处理函数
DCD BusFault_Handler ; Bus Fault 处理函数
DCD UsageFault_Handler ; Usage Fault 处理函数
DCD 0 ; 保留
DCD 0 ; 保留
DCD 0 ; 保留
DCD 0 ; 保留
DCD SVC_Handler ; SVCall 处理函数
DCD DebugMon_Handler ; Debug Monitor 处理函数
DCD 0 ; 保留
DCD PendSV_Handler ; PendSV 处理函数
DCD SysTick_Handler ; SysTick 处理函数
… …
NMI_Handler PROC
EXPORT NMI_Handler [WEAK]
B .
ENDP
HardFault_Handler PROC
EXPORT HardFault_Handler [WEAK]
B .
ENDP
… …

4.2 中断处理过程

中断处理程序分为中断前导程序、中断服务程序、中断后续程序等三部分。

4.2.1 中断前导程序

1)保存 CPU 中断现场:将上下文寄存器自动压入中断栈中;
2)通知内核进入中断状态;

4.2.2 中断服务程序

执行中断服务程序

4.2.3 中断后续程序

1)通知内核离开中断状态;
2)恢复中断前的 CPU 上下文;

5、内核管理

5.1 ARM A核-R核-M核的区别

  • A 核(Cortex-A):主要应用于高性能计算和通用操作系统的执行,如智能手机、平板电脑等;(功耗高成本高)
  • R 核(Cortex-R):专注于实时性能和可预测性,用于实时控制任务,如汽车电子、工业控制等;(在低功耗和高可靠性方面进行了专门优化)
  • M 核(Cortex-M):主要用于低功耗、实时控制和物联网设备,如微控制器(MCU)等;(在功耗和成本方面采取了优化措施)

RT-Thread 提供了一个 libcpu 抽象层来适配不同的 CPU 架构。libcpu 抽象层向上堆内核提供统一接口,包括全局中断开关、线程栈初始化、上下文切换等。
内核移植重点是实现上下文切换。

二、设备和驱动

1、I/O 设备类型

I/O 设备类型包括块设备、字符设备、内存设备等。块设备每次传输一个数据块。

1.1、创建和注册 I/O 设备

驱动层负责创建设备实例,并注册到 I/O 设备管理器中。

1.2 I/O 设备模型框架

与裸机或其他 RTOS 相比,RT-Thread 引入I/O设备管理框架,I/O 设备模型框架从上到下分别是应用程序、I/O 设备管理层设备驱动框架层设备驱动层、硬件层。
引入 I/O 设备驱动框架的目的是将不同的硬件设备封装起来,提供统一接口,方便管理与使用。

  • I/O 设备管理层:实现了对设备驱动程序的封装,使得设备的硬件操作相关的代码能够独立于应用程序而存在;
  • 设备驱动框架层:对同类硬件设备驱动的抽象,它提供统一的接口和机制;
  • 设备驱动层:一组驱使硬件设备工作的程序,实现访问硬件设备的功能。它负责创建和注册 I/O 设备



    对于一些设备,会将创建的设备实例先注册到对应的设备驱动框架中,再由设备驱动框架向 I/O 设备管理器注册:

提问: 当设备产生一个接收数据的中断时,中断服务程序由哪一层执行?
回答: 中断服务程序(Interrupt Service Routine, ISR)主要与设备驱动层和设备驱动框架相关。

  • 设备驱动层:此层包含一个中断服务程序,中断发生时,由硬件调用。中断服务程序在读取数据、清除中断标志后,通知设备驱动框架层有数据到达。
  • 设备驱动框架层:若设备驱动层不读取数据,则由设备驱动框架层进行读取。

2、UART 设备

  • UART 串口传输的格式为:1 bit 起始位 + 8 bit 数据位 + 1 bit奇偶校验位 + 1 bit 停止位;
  • 串口接收数据的模式分为 3 种:中断模式、轮询模式、DMA 模式。一般是中断接收/DMA 接收-轮询发送模式
  • 接收缓冲区:当串口使用中断接收模式打开时,串口驱动框架会开辟一块缓冲区,底层驱动每接收到一个数据,都会在中断服务程序里将数据放入缓冲区;
  • 与 I2C、SPI 相比,由于 UART 传输的是用户可见的字符串,因此 UART 需要在应用层定义中断机制,即定义中断回调函数。I2C、SPI 的中断回调由硬件机制完成;

2.1 串口打开

串口打开流程依次为:查找串口设备是否存在->设置串口接收回调函数->按指定方式打开串口。

/* 接收数据回调函数 */
static rt_err_t uart_input(rt_device_t dev, rt_size_t size)
{
  /* 串口接收到数据后产生中断,调用此回调函数,然后发送接收信号量 */
  rt_sem_release(&rx_sem);
  return RT_EOK;
}

int uart_open()
{
  /* 查找串口设备 */
  serial = rt_device_find(uart_name);
  ...
  /* 设置接收回调函数 */
  rt_device_set_rx_indicate(serial, uart_input);
  /* 以读写及中断接收方式打开串口设备 */
  rt_device_open(serial, RT_DEVICE_OFLAG_RDWR | RT_DEVICE_FLAG_INT_RX);
  /* 以 DMA 接收及轮询发送方式打开串口设备 */
  // rt_device_open(serial, RT_DEVICE_FLAG_DMA_RX);
  /* 发送字符串 */
  rt_device_write(serial, 0, str, (sizeof(str) - 1));
}

2.1 发送数据

向串口写入数据,每写完一次数据,硬件设备将数据发送完成后,设备驱动程序回调应用层的回调函数,把发送数据 buffer 地址作为参数传递给上层应用,发送回调函数通过信号或事件通知写数据线程数据发送成功。

void uart_putchar(const rt_uint8_t c)
{
  rt_size_t len = 0;
  rt_uint32_t timeout = 0;
  do {
  len = rt_device_write(uart_device, 0, &c, 1);
  timeout++;
  }while (len != 1 && timeout < 500);
}

【通过如下调用过程理解 I/O 设备驱动框架】调用 uart_putchar() 发生的数据流向如下:
1)uart_putchar()【应用程序】-> 2)设备写操作接口 rt_device_write()【I/O 设备管理层】-> 3)串口底层驱动框架 rt_serial_write()【I/O 设备驱动管理层】-> 4)串口写驱动 drv_putc()【驱动层】

2.2 接收数据

若串口以中断模式接收,每接收到一个数据产生中断时,都会调用回调函数;若串口以 DMA 模式接收,则 DMA 完成一批数据后,会调用此回调函数。接收回调函数通过信号或事件通知写数据线程有数据到达。

  • 如下 uart_getchar() 实现了串口接收中断回调机制和异步通信机制(通过事件机制),它具有阻塞特性:
#define UART_RX_EVENT (1<<0)
static struct rt_event event;
static rt_device_t uart_device = RT_NULL;

// 中断回调函数
static rt_err_t uart_input(rt_device_t dev, rt_size_t size)
{
  rt_event_send(&event, UART_RX_EVENT);
  return RT_EOK;
} 

// 获取字符线程入口地址
rt_uint8_t uart_getchar(void)
{
  rt_uint8_t e;
  rt_uint8_t ch;
  while (rt_device_read(uart_device, 0, &ch, 1) != 1)
  {
    // 若串口无数据可读,获取字符线程会阻塞在等待事件;若串口有数据可读,会调用回调函数,触发事件,获取字符线程又可 read 字符
    rt_event_recv(&event, UART_RX_EVENT, RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, RT_WAITING_FOREVER, &e);
  }
  return ch;
} 

2.3 中断接收及轮询发送过程

1)查找串口设备获取设备句柄;
2)设置接收回调函数,创建数据接收线程,并初始化回调函数通知处理线程使用的信号量;
3)数据处理线程阻塞等待信号量;
4)串口接收一个数据触发串口中断,中断服务程序将数据放入缓冲区,并回调接收回调函数,回调函数发送信号量唤醒数据处理线程,处理线程读取数据

2.4 串口如何接收不定长数据

参考链接:https://www.wpgdadatong.com.cn/blog/detail/74347 # STM32串口 DMA 接收不定长数据的一种方法

1)设置结束标志符:每接收到一个字节,应用层的处理数据函数处理数据,若遇到数据结束标志则认为数据传输结束;
如:根据数据结束标志\r来判断一帧数据是否结束;

if(ch == DATA_CMD_END)
{
    data[i++] = '\0';
    rt_kprintf("data=%s\r\n",data);
    i = 0;
    continue;
}

2)设置空闲中断:当数据空闲时,自动产生一个中断。空闲中断是检测到有数据被接收后,总线上在一个字节的时间内没有再接收到数据的发生,则会产生一次空闲中断;
3)设置定时器:若定时器超时,则认为数据发送完毕,应用程序调用处理数据函数;
4)DMA 利用硬件接收超时中断:DMA 先将数据存放在缓存区域,待一段时间无收到数据,则会引发一个中断,通知应用程序处理数据

3、CAN 设备

CAN(Controller Area Network)即控制器局域网络。CAN 控制器根据两根线上的电位差来判断总线电平。
CAN 总线特点:
1)【重要】多主控制:所有单元都可发送消息(多主控制)。
2)消息发送依靠优先级:多个单元开始发送时,发送高优先级 ID 消息的单元可获得发送权。

3.1、控制 CAN 设备

应用程序通过命令控制字实现对 CAN 设备的配置:

// dev:设备句柄,cmd:控制命令、arg:控制参数
rt_err_t rt_device_control(rt_device_t dev, rt_uint8_t cmd, void* arg);

cmd(控制命令)取值如下:

#define RT_DEVICE_CTRL_RESUME           0x01    /* 恢复设备 */
#define RT_DEVICE_CTRL_SUSPEND          0x02    /* 挂起设备 */
#define RT_DEVICE_CTRL_CONFIG           0x03    /* 配置设备 */

#define RT_CAN_CMD_SET_FILTER           0x13    /* 设置硬件过滤表 */
#define RT_CAN_CMD_SET_BAUD             0x14    /* 设置波特率 */
#define RT_CAN_CMD_SET_MODE             0x15    /* 设置 CAN 工作模式 */
#define RT_CAN_CMD_SET_PRIV             0x16    /* 设置发送优先级 */
#define RT_CAN_CMD_GET_STATUS           0x17    /* 获取 CAN 设备状态 */
#define RT_CAN_CMD_SET_STATUS_IND       0x18    /* 设置状态回调函数 */
#define RT_CAN_CMD_SET_BUS_HOOK         0x19    /* 设置 CAN 总线钩子函数 */

3.2、CAN 的过滤表

CAN 的过滤表的作用时允许 CAN 控制器根据预先设定的条件筛选接收到的 CAN 消息。

4、I2C 总线设备

I2C 总线只需 2 根信号线,分别是双向数据线 SDA 和双向时钟线 SCL。
与 SPI 相比:

  • 数据传输总线数:SPI 通过 2 根线用于主从设备的接收与发送,I2C 只通过 1 根总线用于主从设备的接收与发送;
  • 主从架构:SPI 是一主多从,I2C 允许多个主设备存在,但同一时刻只运行一个主设备;
  • 外设选取:SPI 通过片选信号选择从设备,I2C 通过地址选择从设备;
  • 双工性:I2C 是半双工通信,SPI 是全双工通信。由于 I2C 还有一根数据线,同一时刻只能读或写数据。
  • 应答机制:I2C 需要应答信号,SPI 无需应答信号。理由:I2C 只有一根数据传输总线,应答信号使得数据传输过程更加可控。而 SPI 有 2 根数据传输线,支持全双工,主设备可以同时发送和接收数据。

4.1 I2C 传输时序

I2C 总线传输时序依次为:
开始条件-> 7 bit 从设备地址-> 1 bit读写控制位R/W-> 1 bit 应答信号 ACK-> N字节数据+ACK->应答/非应答->停止条件

  • 开始条件:串行时钟线 SCL 为高电平时,主设备将串行数据线 SDA 拉低,表示数据传输开始;
  • 从机地址:主机发送的第一个字节为从机地址,高 7 位为地址,最低位为 R/W 读写控制位,1 表示读操作,0 表示写操作;
  • 应答信号:每传输完成一个字节的数据,接收方需要回复 1 bit 的应答信号;(写数据时从设备发送 ACK 应答信号;读数据时主设备发送 ACK 应答信号)
  • 停止条件:当串行数据线 SDA 为低电平时,主设备将串行时钟线 SCL 拉高并保持高电平,再将串行数据线 SDA 拉高表示传输结束。

5、SPI 简介

SPI 通过 4 根线通信,分别是:
1)MOSI:主输出从输入数据线;
2)MISO:主输入从输出;
3)SCLK:串行时钟线,主设备输出时钟信号至从设备;
4)CS:片选信号,主设备输出片选信号至从设备【独立】

5.1 SPI 通信过程

SPI 以一主多从的方式工作,通信由主设备发起,主设备通过 CS 选择要通信的从设备,然后时钟总线 SCLK 给从设备发送时钟信号,数据通过 MOSI 输出给从设备,同时通过 MISO 接收从设备发送的数据。

5.2 SPI 4 种工作时序模式

SPI 的工作时序由时钟极性(Clock Polarity, CPOL)和时钟相位(Clock Phase, CPHA)决定。
时钟极性表示总线空闲时,串行同步时钟总线(SCK)上的信号是低电平(CPOL=0)还是高电平(CPOL=1);时钟相位表示在第一个跳变沿(CPHA=0)采样还是第二个跳变沿(CPHA=1)采样。
根据时钟极性和时钟相位的组合就有 4 种工作时序模式。

5.3 SPI 设备驱动框架

RTT SPI 设备驱动框架把 MCU 的 SPI 硬件控制器虚拟成 SPI 总线,总线上可以挂很多 SPI 设备,但每个 SPI 设备只能挂载到一个 SPI 总线上。

5.4 配置 SPI 设备

为满足不同设备的时钟、数据宽度等要求,挂载 SPI 设备到 SPI 总线后,需要配置 SPI 模式、频率参数等,核心是主从模式与时序模式(受时钟极性与时钟相位影响)。

rt_err_t rt_spi_configure(struct rt_spi_device *device, struct rt_spi_configuration *cfg)

其中 spi 配置结构体如下:

struct rt_spi_configuration
{
    rt_uint8_t mode;        /* 模式 */
    rt_uint8_t data_width;  /* 数据宽度,可取8位、16位、32位 */
    rt_uint16_t reserved;   /* 保留 */
    rt_uint32_t max_hz;     /* 最大频率 */
};

常见的配置为:

struct rt_spi_configuration cfg;
cfg.data_width = 8;
cfg.mode = RT_SPI_MASTER | RT_SPI_MODE_0 | RT_SPI_MSB;
cfg.max_hz = 20 * 1000 *1000;      /* 20M */

rt_spi_configure(spi_dev, &cfg);

备注:SPI 是一种全双工通信总线,发送一字节的同时会接收一字节数据,因此无需向 I2C 那样需要应答信号。

5.5 SFUD 框架

三、FAL 介绍

文件系统通过 FAL 的分区管理,驱动不同 Flash 的读、写、擦除等操作!!

FAL 即 Flash 抽象层,是对 Flash 及基于 Flash 的分区进行管理、操作的抽象层.FAL 提供了统一的 Flash 及分区操作接口,上层应用无需关注底层驱动。

3.1 Flash 设备表、Flash 分区表、Flash 与 设备定义

FAL 移植核心操作时正确配置 Flash 设备表、Flash 分区表、Flash 设备定义:

3.1.1 Flash 设备定义

Flash 设备类封装了对该设备的初始化、读、写、擦除等操作函数。不同的 Flash 设备这 4 种操作函数调用不同。

Flash 设备结构体定义:

struct fal_flash_dev {
  char name[FAL_DEV_NAME_MAX]; // Flash 设备名称
  uint32_t addr;   //  对 Flash 操作的起始地址
  size_t len;  // Flash设备大小
  size_t blk_size;  // 扇区大小,即 Flash设备中用于擦除的最小块大小
  struct {
    int (*init)(void);
    int (*read)(long offset, uint8_t *buf, size_t size);
    int (*write)(long offset, const uint8_t *buf, size);
    int (*erase)(long offset, size_t size);
  } ops;  // Flash 设备操作函数指针

  size_t write_gran;  // 写操作的最小颗粒度
}

实例:

#define STM32_FLASH_START_ADRESS     ((uint32_t)0x08000000)

#define STM32_FLASH_START_ADRESS_16K  STM32_FLASH_START_ADRESS
#define STM32_FLASH_START_ADRESS_64K  (STM32_FLASH_START_ADRESS_16K + FLASH_SIZE_GRANULARITY_16K)

const struct fal_flash_dev stm32_onchip_flash_16k = { 
 .name = "onchip_flash_16k", 
 .addr = STM32_FLASH_START_ADRESS_16K, 
 .len = FLASH_SIZE_GRANULARITY_16K, 
 .blk_size = (16 * 1024),
 .ops = {NULL, fal_flash_read_16k, fal_flash_write_16k, fal_flash_erase_16k}
};

块设备struct fal_blk_device:

struct fal_blk_device
{
  struct rt_device parent;
  struct rt_device_blk_geometry   geometry;
  const struct fal_partition     *fal_part;
}

Flash 设备struct fal_flash_dev与块设备struct fal_blk_device的区别

  • Flash 设备侧重于对 Flash 硬件的直接描述和操作,提供了对 Flash 硬件的底层访问接口;
  • 块设备将 Flash 分区 与 Flash 设备对象关联起来,使得应用层只需调用分区的读写擦除等操作接口,即可访问底层设备,而无需关心底层硬件的实现。

3.1.2 Flash 设备表

#define FAL_FLASH_DEV_TABLE
{
  &stm32_onchip_flash_16k,
  &stm32_onchip_flash_64k, 
  &stm32_onchip_flash_128k,
  &flash_w25q,
}

3.1.3 Flash 分区表

#define FAL_PART_TABLE  
{
  {FAL_PART_MAGIC_WROD, "bootload","onchip_flash_16k",0, FLASH_SIZE_GRANULARITY_16K, 0},
  {FAL_PART_MAGIC_WROD, "param", "onchip_flash_64k",0, FLASH_SIZE_GRANULARITY_64K, 0},
  {FAL_PART_MAGIC_WROD, "app","onchip_flash_128k",0, FLASH_SIZE_GRANULARITY_128K, 0},
  {FAL_PART_MAGIC_WROD, "easyflash",FAL_USING_NOR_FLASH_DEV_NAME,0, 512 * 1024, 0},
  {FAL_PART_MAGIC_WROD, "download",FAL_USING_NOR_FLASH_DEV_NAME,512 * 1024, 512 * 1024,0},
  {FAL_PART_MAGIC_WROD, "filesystem",FAL_USING_NOR_FLASH_DEV_NAME,1024 * 1024,  1024 * 1024, 0}
}
分区名 Flash 设备名 偏移地址 大小 说明
"bootload" "onchip_flash_16k" 0 FLASH_SIZE_GRANULARITY_16K 引导程序
param "onchip_flash_64k" 0 FLASH_SIZE_GRANULARITY_64K 参数段
"app" "onchip_flash_128k" 0 FLASH_SIZE_GRANULARITY_128K 应用程序
"easyflash" "FAL_USING_NOR_FLASH_DEV_NAME" 0 512 * 1024 EasyFlash 参数存储
"download" "FAL_USING_NOR_FLASH_DEV_NAME" 512 * 1024 512 * 1024 OTA 下载区

备注:1)偏移地址是相对 Flash 设备内部的偏移地址;2)分区的起始地址和大小 不能超过 Flash 设备的地址范围;

四、设备虚拟文件系统 DFS

4.1 DFS 框架

DFS 框架由 3 层组成:1)POSIX 文件接口层;2)中间层:中间层是具体文件系统的实现,不同的文件系统类型是独立于存储设备驱动而实现的;3)存储设备驱动:包括 SPI Flash、SD 卡等。

4.2 创建存储设备

只有块设备才能和文件系统对接,所以需要根据 SPI 设备找到 SPI Flash 设备,并创建与其对应的块设备。

posted @ 2024-06-03 11:57  MasterBean  阅读(208)  评论(0)    收藏  举报