- 2024-12-30
-
发表了主题帖:
全志模块设备开发之I2C编程基础介绍
# 12 I2C编程
I2C(Inter-Integrated Circuit BUS)是I2C BUS简称,中文为集成电路总线,是目前应用最广泛的总线之一。和IMX6ULL有些相关的是,刚好该总线是NXP前身的PHILIPS设计。
## 12.1 I2C协议
### **12.1.1** **概述**
I2C是一种串行通信总线,使用多主从架构,最初设计目的为了让主板、嵌入式系统或手机用来连接低速周边设备。多用于小数据量的场合,有传输距离短,任意时刻只能有一个主机等特性。严格意义上讲,I2C应该是软硬件结合体,所以我们将分物理层和协议层来介绍该总线。
I2C总线结构如下图:
![](https://i-blog.csdnimg.cn/img_convert/0a1bf0a02f8c605141a8ad56432c0b0b.png)
传输数据时,我们需要发数据,从主设备发送到从设备上去;也需要把数据从从设备传送到主设备上去,数据涉及到双向传输。
对于I2C通信的过程,下面使用一个形象的生活例子进行类比。
![](https://i-blog.csdnimg.cn/img_convert/cf35a297a2a9315f1e96e727b10e57b9.jpeg)
体育老师:可以把球发给学生,也可以把球从学生中接过来。
① 发球:
- a. 老师说:注意了(start);
- b. 老师对A学生说,我要球发给你(A就是地址);
- c. 老师就把球发出去了(传输);
- d. A收到球之后,应该告诉老师一声(回应);
- e. 老师说下课(停止)。
② 接球:
- a. 老师说注意了(start);
- b. 老师说:B把球发给我(B是地址);
- c. B就把球发给老师(传输);
- d. 老师收到球之后,给B说一声,表示收到球了(回应);
- e. 老师说下课(停止)。
我们就使用这个简单的例子,来解释一下I2C的传输协议:
① 老师说注意了,表示开始信号(start)
② 老师告诉某个学生,表示发送地址(address)
③ 老师发球/接球,表示数据的传输
④ 老师/学生收到球,回应表示:回应信号(ACK)
⑤ 老师说下课,表示I2C传输接受(P)
### **12.2.2 **物理层**
##### 1) 特性1:半双工(非全双工)
I2C总线中只使用两条线路:SDA、SCL。
**① SDA(串行数据线):**
主芯片通过一根SDA线既可以把数据发给从设备,也可以从SDA上读取数据。在I2C设备内部有两个引脚(发送引脚/接受引脚),它们都连接到外部的SDA线上,具体可以参考下图device端里面的I2Cn_SDA(output/input)。
**② SCL(串行时钟线):**
I2C主设备发出时钟,从设备接收时钟。
SDA和SCL引脚的内部电路结构一致,引脚的输出驱动与输入缓冲连在一起。其中输出为漏极开路的场效应管、输入缓冲为一只高输入阻抗的同相器。这样结构有如下特性:
a. 由于 SDA、SCL 为漏极开路结构,借助于外部的上拉电阻实现了信号的“线与”逻辑;
b. 引脚在输出信号的同时还作用输入信号供内部进行检测,当输出与输入不一致时,就表示有问题发生了。这为 “时钟同步”和“总线仲裁”提供硬件基础。
SDA和CLK连接线上连有两个上拉电阻,当总线空闲时,两根线均为高电平。连到总线上的任一器件输出的低电平,都将使总线的信号变低。
物理层连接如下图所示:
![](https://i-blog.csdnimg.cn/img_convert/1b4b49eb753196d45b5ff5f7cb725ae7.png)
##### **2) 特性2:地址和角色可配置**
每个连接到总线的器件都可以通过唯一的地址和其它器件通信,主机/从机角色和地址可配置,主机可以作为主机发送器和主机接收器。
##### **3) 特性3:多主机**
I2C是真正的多主机总线,I2C设备可以在通讯过程转变成主机。如果两个或更多的主机同时请求总线,可以通过冲突检测和仲裁防止总线数据被破坏。
##### 4) 特性4:传输速率
传输速率在标准模式下可以达到100kb/s,快速模式下可以达到400kb/s。
##### 5) 特性5:负载和距离
节点的最大数量受限于地址空间以及总线电容决定,另外总电容也限制了实际通信距离只有几米。
### **12.2.3** 协议层
##### 1) 数据有效性
I2C协议的数据有效性是靠时钟来保证的,在时钟的高电平周期内,SDA线上的数据必须保持稳定。数据线仅可以在时钟SCL为低电平时改变。
![](https://i-blog.csdnimg.cn/img_convert/b6db2437b83c0e66c111ff8700812ec0.png)
##### 2) 起始和结束条件
**起始条件:**当SCL为高电平的时候,SDA线上由高到低的跳变被定义为起始条件。
**结束条件:**当SCL为高电平的时候,SDA线上由低到高的跳变被定义为停止条件。
要注意起始和终止信号都是由主机发出的,连接到I2C总线上的器件,若具有I2C总线的硬件接口,则很容易检测到起始和终止信号。
![](https://i-blog.csdnimg.cn/img_convert/a77184814307986c9c6f6749407ca486.png)
总线在起始条件之后,视为忙状态,在停止条件之后被视为空闲状态。
##### 3) 应答
每当主机向从机发送完一个字节的数据,主机总是需要等待从机给出一个应答信号,以确认从机是否成功接收到了数据,从机应答主机所需要的时钟仍是主机提供的,应答出现在每一次主机完成8个数据位传输后紧跟着的时钟周期,低电平0表示应答,1表示非应答。
##### 4) 数据帧格式
SDA线上每个字节必须是8位长,在每个传输(transfer)中所传输字节数没有限制,每个字节后面必须跟一个ACK。8位数据中,先传输最高有效位(MSB)传输。
![](https://i-blog.csdnimg.cn/img_convert/a72513fbc1a5b6a09c055d62d90a87da.png)
## 12.2 在linux系统下操作I2C总线的外设
### 12.2.1 概述
下图是在linux系统环境里操作i2c总线上的外设流程框图。我们按照从下向上的顺序研究一下该流程中各个角色的功能。
在硬件层中,I2C硬件总线只有两条线路,上面可以挂载多个I2C-device,这些I2C-device有的在I2C总线里充当主机的角色,一般情况该主机为板子上的主cpu中的I2C控制器,比如我们用的100ask_imx6UL板子,这个I2C主机就是imx6中的I2C控制器模块;其他的I2C-device在I2C总线里充当从机的角色,通常这些从机是板子上完成特定功能的传感器外设,只不过该外设与主控cpu的通信方式是只需要两条线路的I2C总线,比如在我们的100ask_imx6UL板子中就有eeprom和AP3216两个外设,它们在I2C总线中充当的都是I2C从机的角色,它们和主控芯片imx6中的I2C控制器1都是以并联的方式挂在这个I2C总线上。
在内核中,驱动程序对下要完成I2C总线上的I2C通信协议,收集硬件传感器的I2C数据并封装成标准的linux操作接口供用户空间的应用程序操作。对上要实现可以通过linux程序把数据流组织成I2C协议下发到硬件层的相应的外设传感器中。
在用户空间的应用程序中,应用工程师完全可以不必理会I2C协议的详细规定。只需要按照驱动层提供给我们的操作I2C外设的操作接口函数就可以像操作linux中其他普通设备文件那样轻松的操作I2C外设了。
![](https://i-blog.csdnimg.cn/img_convert/a17931f7a488ea5cf181967814cf9f94.png)
### **12.2.2** **简述I2C的linux驱动**
I2C在linux内核层的驱动框架主要由三部分组成:
#### 1) I2C核心层:
I2C核心提供了I2C总线驱动和设备驱动的注册、注销方法,I2C通信方法(algorithm)的上层部分,并且还提供了一系列与具体硬件平台无关的接口函数以及探测设备,检测设备地址的上层代码等。它位于内核源码目录下的drivers/i2c/i2c-core.c文件中,是I2C总线驱动和设备驱动之间依赖于I2C核心作为纽带。
I2C核心中的主要函数包括:
增加/删除i2c_adapter
```c
int i2c_add_adapter(struct i2c_adapter *adap);
int i2c_del_adapter(struct i2c_adapter *adap);
```
增加/删除i2c_driver
```c
int i2c_register_driver(struct module *owner, struct i2c_driver *driver);
int i2c_del_driver(struct i2c_driver *driver);
inline int i2c_add_driver(struct i2c_driver *driver);
```
i2c_client依附/脱离
```c
int i2c_attach_client(struct i2c_client *client);
int i2c_detach_client(struct i2c_client *client);
```
i2c传输、发送和接收
```c
int i2c_transfer(struct i2c_adapter * adap, struct i2c_msg *msgs, int num);
```
用于进行I2C适配器和I2C设备之间的一组消息交互。其本身不具备驱动适配器物理硬件完成消息交互的能力,它只是寻找到i2c_adapter对应的i2c_algorithm,并使用i2c_algorithm的master_xfer()函数真正驱动硬件流程。
```c
int i2c_master_send(struct i2c_client *client,const char *buf ,int count);
int i2c_master_recv(struct i2c_client *client, char *buf ,int count);
```
i2c_master_send()和i2c_master_recv()函数内部会调用i2c_transfer()函数分别完成一条写消息和一条读消息。
a) I2C控制命令分派
下面函数有助于将发给I2C适配器设备文件ioctl的命令分派给对应适配器的algorithm的algo_control()函数或i2c_driver的command()函数:
```c
int i2c_control(struct i2c_client *client, unsigned int cmd, unsigned long arg);
void i2c_clients_command(struct i2c_adapter *adap, unsigned int cmd, void *arg);
```
#### 2) I2C总线驱动层:
I2C总线驱动是对I2C硬件体系结构中适配器端的实现,适配器可由CPU控制,甚至可以直接集成在CPU内部。
它主要完成的功能有:
a) 初始化I2C适配器所使用的硬件资源,申请I/O地址、中断号等。
b) 通过i2c_add_adapter()添加i2c_adapter的数据结构,当然这个i2c_adapter数据结构的成员已经被xxx适配器的相应函数指针所初始化。
c) 释放I2C适配器所使用的硬件资源,释放I/O地址、中断号等。
d) 通过i2c_del_adapter()删除i2c_adapter的数据结构。
#### 3) I2C总线驱动层:
I2C设备驱动(也称为客户驱动)是对I2C硬件体系结构中设备端的实现,设备一般挂接在受CPU控制的I2C适配器上,通过I2C适配器与CPU交换数据。I2C设备驱动模块加载函数通用的方法是在I2C设备驱动模块加载函数中完成两件事:通过register_chrdev()函数将I2C设备注册为一个字符设备。通过I2C核心的i2c_add_driver()函数添加i2c_driver。
## 12.3 在linux应用层使用I2C
前面我们讲解了I2C的协议及在linux驱动框架,那么当你拿到开发板或者是从公司的硬件同事拿到一个带有I2C外设的板子,我们应该如何最快速的使用起来这个I2C设备呢?既然我们总是说这个I2C总线在嵌入式开发中被广泛的使用,那么是否有现成的测试工具帮我们完成这个快速使用板子的I2C设备呢?答案是有的,而且这个测试工具的代码还是开源的,它被广泛的应用在linux应用层来快速验证I2C外设是否可用,为我们测试I2C设备提供了很好的捷径。
### **12.3.1** **如何使用I2C tools测试I2C外设**
#### 1) I2C tools概述:
I2C tools包含一套用于Linux应用层测试各种各样I2C功能的工具。它的主要功能包括:总线探测工具、SMBus访问帮助程序、EEPROM解码脚本、EEPROM编程工具和用于SMBus访问的python模块。只要你所使用的内核中包含I2C设备驱动,那么就可以在你的板子中正常使用这个测试工具。
#### 2) 下载I2C tools源码:
前面我们已经说过了这个I2C tools工具是开源的,那么这个源码在哪里可以找到呢?
下载方法一:直接在内核的网站https://mirrors.edge.kernel.org/pub/software/utils/i2c-tools/下载I2C tools代码的压缩包。
下载方法二:利用git管理工具下载这个I2C tools的源代码,命令为git clone git://git.kernel.org/pub/scm/utils/i2c-tools/i2c-tools.git强烈建议读者采用第二种方法下载这个代码,因为你可以通过git快速地了解这个开源代码的不同版本的功能改进及bug修复,而且使用git开发也是作为一名优秀的开发人员必备的一项技能。
#### 3) 编译I2C tools源码:
进入刚才利用git下载好的iic-tools源码目录,修改编译工具为你当前使用的交叉编译工具:
```c
26 CC ?= arm-linux-gnueabihf-gcc
27 AR ?= arm-linux-gnueabihf-ar
```
编译源码:如果你想编译静态版本,你可以输入命令:make USE_STATIC_LIB=1;如果使用动态库的话,可以直接输入make进行编译。安装命令为:make install,如果你想要让最后生成的二进制文件最小的话,可以在“make install”之前运行“make strip”。但是,这将不能生成任何调试库,也就不能尝试进一步调试。然后将tools目录下的5个可执行文件i2cdetect,i2cdump,i2cget,i2cset和i2ctransfer复制到板子的/usr/sbin/中;将lib目录下的libi2c.so.0.1.1文件复制到板子的/usr/lib/libi2c.so.0。之后别忘了将上面的文件修改为可执行的权限。
#### 4) 介绍I2C tools各功能之—i2cdetect
i2cdetect的主要功能就是I2C设备查询,它用于扫描I2C总线上的设备。它输出一个表,其中包含指定总线上检测到的设备的列表。
该命令的常用格式为:i2cdetect [-y] [-a] [-q|-r] i2cbus [first last]。具体参数的含义如下:
| -y | 取消交互模式。默认情况下,i2cdetect将等待用户的确认, 当使用此标志时,它将直接执行操作。 |
| ---------- | ------------------------------------------------------------ |
| -a | 强制扫描非规则地址。一般不推荐。 |
| -q | 使用SMBus“快速写入”命令进行探测。一般不推荐。 |
| -r | 使用SMBus“接收字节”命令进行探测。一般不推荐。 |
| -F | 显示适配器实现的功能列表并退出。 |
| -V | 显示I2C工具的版本并推出。 |
| -l | 显示已经在系统中使用的I2C总线。 |
| i2cbus | 表示要扫描的I2C总线的编号或名称。 |
| first last | 表示要扫描的从设备地址范围。 |
该功能的常用方式:
第一,先通过i2cdetect -l查看当前系统中的I2C的总线情况:
![](https://i-blog.csdnimg.cn/img_convert/e51bb6446b8fc676bb242fc6ef390228.png)
第二,若总线上挂载I2C从设备,可通过i2cdetect扫描某个I2C总线上的所有设备。可通过控制台输入i2cdetect -y 1:(其中"--"表示地址被探测到了,但没有芯片应答; "UU"因为这个地址目前正在被一个驱动程序使用,探测被省略;而16进制的地址号60,1e和50则表示发现了一个外部片选从地址为0x60,0x1e(AP3216)和0x50(eeprom)的外设芯片。
![](https://i-blog.csdnimg.cn/img_convert/926834a424e47cd0dfb664cd26b541a5.png)
![](https://i-blog.csdnimg.cn/img_convert/228a16818fff81358813663f6742f899.png)
第三,查询I2C总线1 (I2C -1)的功能,命令为i2cdetect -F 1:
![](https://i-blog.csdnimg.cn/img_convert/bde03f5e8beb4acb48226af4fefde343.png)
#### 5) 介绍I2C tools各功能之—i2cget
i2cget的主要功能是获取I2C外设某一寄存器的内容。该命令的常用格式为:
i2cget [-f] [-y] [-a] i2cbus chip-address [data-address [mode]]。具体参数的含义如下:
| -f | 强制访问设备,即使它已经很忙。 默认情况下,i2cget将拒绝访问 已经在内核驱动程序控制下的设备。 |
| ------------ | ------------------------------------------------------------ |
| -y | 取消交互模式。默认情况下,i2cdetect将等待用户的确认,当使用此 标志时,它将直接执行操作。 |
| -a | 允许在0x00 - 0x07和0x78 - 0x7f之间使用地址。一般不推荐。 |
| i2cbus | 表示要扫描的I2C总线的编号或名称。这个数字应该与i2cdetect -l列出 的总线之一相对应。 |
| chip-address | 要操作的外设从地址。 |
| data-address | 被查看外设的寄存器地址。 |
| mode | 显示数据的方式: b (read byte data, default) w (read word data) c (write byte/read byte) |
下面是完成读取0总线上从地址为0x50的外设的0x10寄存器的数据,命令为:
i2cget -y -f 0 0x50 0x10
![](https://i-blog.csdnimg.cn/img_convert/2307a4335c62ca92cb1fe16d9651773c.png)
#### 6) 介绍I2C tools各功能之—i2cdump
i2cdump的主要功能查看I2C从设备器件所有寄存器的值。 该命令的常用格式为:i2cdump [-f] [-r first-last] [-y] [-a] i2cbus address [mode [bank [bankreg]]]。具体参数的含义如下:
| -f | 强制访问设备,即使它已经很忙。 默认情况下,i2cget将拒绝访问已经在内核驱动程序控制下的设备。 |
| ---------- | ------------------------------------------------------------ |
| -r | 限制正在访问的寄存器范围。 此选项仅在模式b,w,c和W中可用。对于模式W,first必须是偶数,last必须是奇数。 |
| -y | 取消交互模式。默认情况下,i2cdetect将等待用户的确认,当使用此标志时,它将直接执行操作。 |
| -V | 显示I2C工具的版本并推出。 |
| i2cbus | 表示要扫描的I2C总线的编号或名称。这个数字应该对应于i2cdetect -l列出的总线之一。 |
| first last | 表示要扫描的从设备地址范围。 |
| mode | b: 单个字节 w:16位字 s:SMBus模块 i:I2C模块的读取大小 c: 连续读取所有字节,对于具有地址自动递增功能的芯片(如EEPROM)非常有用。W与 w类似,只是读命令只能在偶数寄存器地址上发出;这也是主要用于EEPROM的。 |
下面是完成读取0总线上从地址为0x50的eeprom的数据,命令为:
i2cdump -f -y 0 0x50
![](https://i-blog.csdnimg.cn/img_convert/4082cf6143179101804c135820eb10f4.png)
#### 7) 介绍I2C tools各功能之—i2cset
i2cset的主要功能是通过I2C总线设置设备中某寄存器的值。该命令的常用格式为:
i2cset [-f] [-y] [-m mask] [-r] i2cbus chip-address data-address [value] ...[mode]
具体参数的含义如下:
| -f | 强制访问设备,即使它已经很忙。 默认情况下,i2cget将拒绝访问已经在内核驱动程序控制下的设备。 |
| ------- | ------------------------------------------------------------ |
| -r | 在写入值之后立即读取它,并将结果与写入的值进行比较。 |
| -y | 取消交互模式。默认情况下,i2cdetect将等待用户的确认,当使用此标志时,它将直接执行操作。 |
| -V | 显示I2C工具的版本并推出。 |
| i2cbus | 表示要扫描的I2C总线的编号或名称。这个数字应该对应于i2cdetect -l列出的总线之一。 |
| -m mask | 如果指定mask参数,那么描述哪些value位将是实际写入data-addres的。掩码中设置为1的位将从值中取出,而设置为0的位将从数据地址中读取,从而由操作保存。 |
| mode | b: 单个字节 w:16位字 s:SMBus模块 i:I2C模块的读取大小 c: 连续读取所有字节,对于具有地址自动递增功能的芯片(如EEPROM)非常有用。 W与 w类似,只是读命令只能在偶数寄存器地址上发出;这也是主要用于EEPROM的。 |
下面是完成向0总线上从地址为0x50的eeprom的0x10寄存器写入0x55,命令为:
i2cset -y -f 0 0x50 0x10 0x55
然后用i2cget读取0总线上从地址为0x50的eeprom的0x10寄存器的数据,命令为:i2cget -y -f 0 0x50 0x10
![](https://i-blog.csdnimg.cn/img_convert/45a0a21c712ce27e38c36e26e6674f67.png)
#### 8) 介绍I2C tools各功能之—i2ctransfer
i2ctransfer的主要功能是在一次传输中发送用户定义的I2C消息。i2ctransfer是一个创建I2C消息并将其合并为一个传输发送的程序。对于读消息,接收缓冲区的内容被打印到stdout,每个读消息一行。
该命令的常用格式为:i2ctransfer [-f] [-y] [-v] [-a] i2cbus desc [data] [desc [data]]
具体参数的含义如下:
| -f | 强制访问设备,即使它已经很忙。 默认情况下,i2cget将拒绝访问已经在内核驱动程序控制下的设备。 |
| ------ | ------------------------------------------------------------ |
| -y | 取消交互模式。默认情况下,i2cdetect将等待用户的确认,当使用此标志时,它将直接执行操作。 |
| -v | 启用详细输出。它将打印所有信息发送,即不仅为读消息,也为写消息。 |
| -V | 显示I2C工具的版本并推出。 |
| -a | 允许在0x00 - 0x02和0x78 - 0x7f之间使用地址。一般不推荐。 |
| i2cbus | 表示要扫描的I2C总线的编号或名称。这个数字应该对应于i2cdetect -l列出的总线之一。 |
下面是完成向0总线上从地址为0x50的eeprom的0x20开始的4个寄存器写入0x01,0x02,0x03,0x04命令为:i2ctransfer -f -y 0 w5@0x50 0x20 0x01 0x02 0x03 0x04然后再通过命令i2ctransfer -f -y 0 w1@0x50 0x20 r4将0x20地址的4个寄存器数据读出来,见下图:
![](https://i-blog.csdnimg.cn/img_convert/62a1304a2db3c7f0e0b6f7c13aaa26b6.png)
### **12.3.2** **在linux应用程序中读写I2C外设**
首先通过前面的介绍,我们已经知道站在cpu的角度来看,操作I2C外设实际上就是通过控制cpu中挂载该I2C外设的I2C控制器,而这个I2C控制器在linux系统中被称为“I2C适配器”,这个已经在驱动简介中介绍过了。而且众所周知,在linux系统中,每一个设备都是以文件的形式存在的,所以在linux中操作I2C外设就变成了操作I2C适配器设备文件。Linux系统(也就是内核)为每个I2C适配器生成了一个主设备号为89的设备节点(次设备号为0-255),它并没有针对特定的I2C外设而设计,只是提供了通用的read(),write(),和ioctl()等文件操作接口,在用户空间的应用层就可以借用这些接口访问挂接在适配器上的I2C设备的存储空间或寄存器,并控制I2C设备的工作方式。
操作流程:
#### 1) 确定I2C适配器的设备文件节点
i2c适配器的设备节点是/dev/i2c-x,其中x是数字。由于适配器编号是动态分配的(和注册次序有关),所以想了解哪一个适配器对应什么编号,可以查看/sys/class/i2c-dev/目录下的文件内容(在这里笔者强烈建议读者好好利用好sys文件系统):
```c
cat /sys/class/i2c-dev/i2c-0/name
cat /sys/class/i2c-dev/i2c-1/name
```
![](https://i-blog.csdnimg.cn/img_convert/a68a5d31cca12f3aa98a736ea8c728d4.png)
然后查看硬件原理图中eeprom是挂在cpu的i2c1控制器中了,然后查看IMX6UL芯片手册中I2C1的寄存器地址为21A_0000。
![](https://i-blog.csdnimg.cn/img_convert/a59364cfeb634ce830c92ab744cbe94c.png)
比对后,我们就很容易知道eeprom外设对应的I2C控制器的设备节点为:/dev/i2c-0。
#### 2) 打开适配器对应的设备节点
当用户打开适配器设备节点的时候,Kernel中的i2c-dev代码为其建立一个i2c_client,但是这个i2c_client并不加到i2c_adapter的client链表当中。当用户关闭设备节点时,它自动被释放。
#### 3) IOCTL控制
这个可以参考内核源码中的include/linux/i2c-dev.h文件。下面举例说明主要的IOCTL命令:
| I2C_SLAVE_FORCE | 设置I2C从设备地址(只有在该地址空闲的情况下成功) |
| --------------- | ------------------------------------------------------------ |
| I2C_SLAVE_FORCE | 强制设置I2C从设备地址(无论内核中是否已有驱动在使用这个地址都会成功) |
| I2C_TENBIT | 选择地址位长: 0 表示是7bit地址 ; 不等于0 就是10 bit的地址。只有适配器支持I2C_FUNC_10BIT_ADDR,这个请求才是有效的。 |
| I2C_FUNCS | 获取适配器支持的功能,详细的可以参考文件include/linux/i2c.h |
| I2C_RDWR | 设置为可读写 |
| I2C_RETRIES | 设置收不到ACK时的重试次数 |
| I2C_TIMEOUT | 设置超时的时限 |
#### 4) 使用I2C协议和设备进行通信
代码为:ioctl(file,I2C_RDWR,(struct i2c_rdwr_ioctl_data *)msgset); 它可以进行连续的读写,中间没有间歇。只有当适配器支持I2C_FUNC_I2C此命令才有效。参数msgset是一个指针,指向一个i2c_rdwr_ioctl_data类型的结构体,该结构体的功能就是让应用程序可以向内核传递消息,其成员包括:struct i2c_msg __ user *msgs; 和表示i2c_msgs 个数的 __u32 nmsgs,它也决定了在硬件I2C总线的硬件通信中有多少个开始信号。由于I2C适配器与外设通信是以消息为单位的,所以struct i2c_msg对我们来说是非常重要的,它可以包含多条消息,而一条消息有可能包含多个数据,比如对于eeprom页写就包含多个数据。下面就介绍一下这个结构体的内容:
| __u16 addr; | 从设备地址 |
| ------------------ | ------------------------------------------------------------ |
| __u16 flags; | 标志(读/写) |
| I2C_M_TEN | 这是一个10位芯片地址 |
| I2C_M_RD | 从设备到适配器读数据 |
| I2C_M_NOSTART | 不发送起始位 |
| I2C_M_REV_DIR_ADDR | 翻转读写标志 |
| I2C_M_IGNORE_NAK | 忽略I2C的NACK信号 |
| I2C_M_NO_RD_ACK | 读操作的时候不发ACK信号 |
| I2C_M_RECV_LEN | 第一次接收数据的长度 |
| __u16 len; | 写入或者读出数据的个数(字节) |
| __u8 *buf; | 写入或者读出数据的地址 buf[0]。 注意:千万不要忘记给 2c_rdwr_ioctl_data结构体中的最重要的结构i2c_msg中的buf分配内存。 |
#### 5) 用read和write读写I2C设备
当然你可以使用read()/write()来与I2C设备进行通信,代码如下(以eeprom为例简要概述操作过程):
第一,打开I2C控制器文件节点: fd =open(“/dev/i2c-0”, O_RDWR);
第二,设置eeprom的设备地址:ioctl(fd,I2C_SLAVE, 0x50);
第三,向eeprom写数据:
首先将要操作的eeprom的第一个寄存器地址赋给写buf的第0个元素wr_buf[0] = 0x10;
然后把要写入的数据写入到后面的buf中for(i=1;i
- 2024-12-16
-
发表了主题帖:
全志模块设备开发之I2C编程基础介绍
# 12 I2C编程
I2C(Inter-Integrated Circuit BUS)是I2C BUS简称,中文为集成电路总线,是目前应用最广泛的总线之一。和IMX6ULL有些相关的是,刚好该总线是NXP前身的PHILIPS设计。
## 12.1 I2C协议
### **12.1.1** **概述**
I2C是一种串行通信总线,使用多主从架构,最初设计目的为了让主板、嵌入式系统或手机用来连接低速周边设备。多用于小数据量的场合,有传输距离短,任意时刻只能有一个主机等特性。严格意义上讲,I2C应该是软硬件结合体,所以我们将分物理层和协议层来介绍该总线。
I2C总线结构如下图:
![](https://i-blog.csdnimg.cn/img_convert/e26420294687afa03b266c743637b169.png)
传输数据时,我们需要发数据,从主设备发送到从设备上去;也需要把数据从从设备传送到主设备上去,数据涉及到双向传输。
对于I2C通信的过程,下面使用一个形象的生活例子进行类比。
![](https://i-blog.csdnimg.cn/img_convert/32dad515e9ae771ceb6e07a4283dd7bf.jpeg)
体育老师:可以把球发给学生,也可以把球从学生中接过来。
① 发球:
- a. 老师说:注意了(start);
- b. 老师对A学生说,我要球发给你(A就是地址);
- c. 老师就把球发出去了(传输);
- d. A收到球之后,应该告诉老师一声(回应);
- e. 老师说下课(停止)。
② 接球:
- a. 老师说注意了(start);
- b. 老师说:B把球发给我(B是地址);
- c. B就把球发给老师(传输);
- d. 老师收到球之后,给B说一声,表示收到球了(回应);
- e. 老师说下课(停止)。
我们就使用这个简单的例子,来解释一下I2C的传输协议:
① 老师说注意了,表示开始信号(start)
② 老师告诉某个学生,表示发送地址(address)
③ 老师发球/接球,表示数据的传输
④ 老师/学生收到球,回应表示:回应信号(ACK)
⑤ 老师说下课,表示I2C传输接受(P)
### **12.2.2 **物理层**
##### 1) 特性1:半双工(非全双工)
I2C总线中只使用两条线路:SDA、SCL。
**① SDA(串行数据线):**
主芯片通过一根SDA线既可以把数据发给从设备,也可以从SDA上读取数据。在I2C设备内部有两个引脚(发送引脚/接受引脚),它们都连接到外部的SDA线上,具体可以参考下图device端里面的I2Cn_SDA(output/input)。
**② SCL(串行时钟线):**
I2C主设备发出时钟,从设备接收时钟。
SDA和SCL引脚的内部电路结构一致,引脚的输出驱动与输入缓冲连在一起。其中输出为漏极开路的场效应管、输入缓冲为一只高输入阻抗的同相器。这样结构有如下特性:
a. 由于 SDA、SCL 为漏极开路结构,借助于外部的上拉电阻实现了信号的“线与”逻辑;
b. 引脚在输出信号的同时还作用输入信号供内部进行检测,当输出与输入不一致时,就表示有问题发生了。这为 “时钟同步”和“总线仲裁”提供硬件基础。
SDA和CLK连接线上连有两个上拉电阻,当总线空闲时,两根线均为高电平。连到总线上的任一器件输出的低电平,都将使总线的信号变低。
物理层连接如下图所示:
![](https://i-blog.csdnimg.cn/img_convert/273742893f05045e66a0f3c9835cf728.png)
##### **2) 特性2:地址和角色可配置**
每个连接到总线的器件都可以通过唯一的地址和其它器件通信,主机/从机角色和地址可配置,主机可以作为主机发送器和主机接收器。
##### **3) 特性3:多主机**
I2C是真正的多主机总线,I2C设备可以在通讯过程转变成主机。如果两个或更多的主机同时请求总线,可以通过冲突检测和仲裁防止总线数据被破坏。
##### 4) 特性4:传输速率
传输速率在标准模式下可以达到100kb/s,快速模式下可以达到400kb/s。
##### 5) 特性5:负载和距离
节点的最大数量受限于地址空间以及总线电容决定,另外总电容也限制了实际通信距离只有几米。
### **12.2.3** 协议层
##### 1) 数据有效性
I2C协议的数据有效性是靠时钟来保证的,在时钟的高电平周期内,SDA线上的数据必须保持稳定。数据线仅可以在时钟SCL为低电平时改变。
![](https://i-blog.csdnimg.cn/img_convert/ecaa888c7baa16a1d72c2467efe218ae.png)
##### 2) 起始和结束条件
**起始条件:**当SCL为高电平的时候,SDA线上由高到低的跳变被定义为起始条件。
**结束条件:**当SCL为高电平的时候,SDA线上由低到高的跳变被定义为停止条件。
要注意起始和终止信号都是由主机发出的,连接到I2C总线上的器件,若具有I2C总线的硬件接口,则很容易检测到起始和终止信号。
![](https://i-blog.csdnimg.cn/img_convert/4df089c9507f0f7b610f8d6db968aeb8.png)
总线在起始条件之后,视为忙状态,在停止条件之后被视为空闲状态。
##### 3) 应答
每当主机向从机发送完一个字节的数据,主机总是需要等待从机给出一个应答信号,以确认从机是否成功接收到了数据,从机应答主机所需要的时钟仍是主机提供的,应答出现在每一次主机完成8个数据位传输后紧跟着的时钟周期,低电平0表示应答,1表示非应答。
##### 4) 数据帧格式
SDA线上每个字节必须是8位长,在每个传输(transfer)中所传输字节数没有限制,每个字节后面必须跟一个ACK。8位数据中,先传输最高有效位(MSB)传输。
![](https://i-blog.csdnimg.cn/img_convert/a50a2a1a70edc3ba7c599c9cf8814f18.png)
## 12.2 在linux系统下操作I2C总线的外设
### 12.2.1 概述
下图是在linux系统环境里操作i2c总线上的外设流程框图。我们按照从下向上的顺序研究一下该流程中各个角色的功能。
在硬件层中,I2C硬件总线只有两条线路,上面可以挂载多个I2C-device,这些I2C-device有的在I2C总线里充当主机的角色,一般情况该主机为板子上的主cpu中的I2C控制器,比如我们用的100ask_imx6UL板子,这个I2C主机就是imx6中的I2C控制器模块;其他的I2C-device在I2C总线里充当从机的角色,通常这些从机是板子上完成特定功能的传感器外设,只不过该外设与主控cpu的通信方式是只需要两条线路的I2C总线,比如在我们的100ask_imx6UL板子中就有eeprom和AP3216两个外设,它们在I2C总线中充当的都是I2C从机的角色,它们和主控芯片imx6中的I2C控制器1都是以并联的方式挂在这个I2C总线上。
在内核中,驱动程序对下要完成I2C总线上的I2C通信协议,收集硬件传感器的I2C数据并封装成标准的linux操作接口供用户空间的应用程序操作。对上要实现可以通过linux程序把数据流组织成I2C协议下发到硬件层的相应的外设传感器中。
在用户空间的应用程序中,应用工程师完全可以不必理会I2C协议的详细规定。只需要按照驱动层提供给我们的操作I2C外设的操作接口函数就可以像操作linux中其他普通设备文件那样轻松的操作I2C外设了。
![](https://i-blog.csdnimg.cn/img_convert/fbbb86679e616bd10c718406a0d82e23.png)
### **12.2.2** **简述I2C的linux驱动**
I2C在linux内核层的驱动框架主要由三部分组成:
#### 1) I2C核心层:
I2C核心提供了I2C总线驱动和设备驱动的注册、注销方法,I2C通信方法(algorithm)的上层部分,并且还提供了一系列与具体硬件平台无关的接口函数以及探测设备,检测设备地址的上层代码等。它位于内核源码目录下的drivers/i2c/i2c-core.c文件中,是I2C总线驱动和设备驱动之间依赖于I2C核心作为纽带。
I2C核心中的主要函数包括:
增加/删除i2c_adapter
```c
int i2c_add_adapter(struct i2c_adapter *adap);
int i2c_del_adapter(struct i2c_adapter *adap);
```
增加/删除i2c_driver
```c
int i2c_register_driver(struct module *owner, struct i2c_driver *driver);
int i2c_del_driver(struct i2c_driver *driver);
inline int i2c_add_driver(struct i2c_driver *driver);
```
i2c_client依附/脱离
```c
int i2c_attach_client(struct i2c_client *client);
int i2c_detach_client(struct i2c_client *client);
```
i2c传输、发送和接收
```c
int i2c_transfer(struct i2c_adapter * adap, struct i2c_msg *msgs, int num);
```
用于进行I2C适配器和I2C设备之间的一组消息交互。其本身不具备驱动适配器物理硬件完成消息交互的能力,它只是寻找到i2c_adapter对应的i2c_algorithm,并使用i2c_algorithm的master_xfer()函数真正驱动硬件流程。
```c
int i2c_master_send(struct i2c_client *client,const char *buf ,int count);
int i2c_master_recv(struct i2c_client *client, char *buf ,int count);
```
i2c_master_send()和i2c_master_recv()函数内部会调用i2c_transfer()函数分别完成一条写消息和一条读消息。
a) I2C控制命令分派
下面函数有助于将发给I2C适配器设备文件ioctl的命令分派给对应适配器的algorithm的algo_control()函数或i2c_driver的command()函数:
```c
int i2c_control(struct i2c_client *client, unsigned int cmd, unsigned long arg);
void i2c_clients_command(struct i2c_adapter *adap, unsigned int cmd, void *arg);
```
#### 2) I2C总线驱动层:
I2C总线驱动是对I2C硬件体系结构中适配器端的实现,适配器可由CPU控制,甚至可以直接集成在CPU内部。
它主要完成的功能有:
a) 初始化I2C适配器所使用的硬件资源,申请I/O地址、中断号等。
b) 通过i2c_add_adapter()添加i2c_adapter的数据结构,当然这个i2c_adapter数据结构的成员已经被xxx适配器的相应函数指针所初始化。
c) 释放I2C适配器所使用的硬件资源,释放I/O地址、中断号等。
d) 通过i2c_del_adapter()删除i2c_adapter的数据结构。
#### 3) I2C总线驱动层:
I2C设备驱动(也称为客户驱动)是对I2C硬件体系结构中设备端的实现,设备一般挂接在受CPU控制的I2C适配器上,通过I2C适配器与CPU交换数据。I2C设备驱动模块加载函数通用的方法是在I2C设备驱动模块加载函数中完成两件事:通过register_chrdev()函数将I2C设备注册为一个字符设备。通过I2C核心的i2c_add_driver()函数添加i2c_driver。
## 12.3 在linux应用层使用I2C
前面我们讲解了I2C的协议及在linux驱动框架,那么当你拿到开发板或者是从公司的硬件同事拿到一个带有I2C外设的板子,我们应该如何最快速的使用起来这个I2C设备呢?既然我们总是说这个I2C总线在嵌入式开发中被广泛的使用,那么是否有现成的测试工具帮我们完成这个快速使用板子的I2C设备呢?答案是有的,而且这个测试工具的代码还是开源的,它被广泛的应用在linux应用层来快速验证I2C外设是否可用,为我们测试I2C设备提供了很好的捷径。
### **12.3.1** **如何使用I2C tools测试I2C外设**
#### 1) I2C tools概述:
I2C tools包含一套用于Linux应用层测试各种各样I2C功能的工具。它的主要功能包括:总线探测工具、SMBus访问帮助程序、EEPROM解码脚本、EEPROM编程工具和用于SMBus访问的python模块。只要你所使用的内核中包含I2C设备驱动,那么就可以在你的板子中正常使用这个测试工具。
#### 2) 下载I2C tools源码:
前面我们已经说过了这个I2C tools工具是开源的,那么这个源码在哪里可以找到呢?
下载方法一:直接在内核的网站https://mirrors.edge.kernel.org/pub/software/utils/i2c-tools/下载I2C tools代码的压缩包。
下载方法二:利用git管理工具下载这个I2C tools的源代码,命令为git clone git://git.kernel.org/pub/scm/utils/i2c-tools/i2c-tools.git强烈建议读者采用第二种方法下载这个代码,因为你可以通过git快速地了解这个开源代码的不同版本的功能改进及bug修复,而且使用git开发也是作为一名优秀的开发人员必备的一项技能。
#### 3) 编译I2C tools源码:
进入刚才利用git下载好的iic-tools源码目录,修改编译工具为你当前使用的交叉编译工具:
```c
26 CC ?= arm-linux-gnueabihf-gcc
27 AR ?= arm-linux-gnueabihf-ar
```
编译源码:如果你想编译静态版本,你可以输入命令:make USE_STATIC_LIB=1;如果使用动态库的话,可以直接输入make进行编译。安装命令为:make install,如果你想要让最后生成的二进制文件最小的话,可以在“make install”之前运行“make strip”。但是,这将不能生成任何调试库,也就不能尝试进一步调试。然后将tools目录下的5个可执行文件i2cdetect,i2cdump,i2cget,i2cset和i2ctransfer复制到板子的/usr/sbin/中;将lib目录下的libi2c.so.0.1.1文件复制到板子的/usr/lib/libi2c.so.0。之后别忘了将上面的文件修改为可执行的权限。
#### 4) 介绍I2C tools各功能之—i2cdetect
i2cdetect的主要功能就是I2C设备查询,它用于扫描I2C总线上的设备。它输出一个表,其中包含指定总线上检测到的设备的列表。
该命令的常用格式为:i2cdetect [-y] [-a] [-q|-r] i2cbus [first last]。具体参数的含义如下:
| -y | 取消交互模式。默认情况下,i2cdetect将等待用户的确认, 当使用此标志时,它将直接执行操作。 |
| ---------- | ------------------------------------------------------------ |
| -a | 强制扫描非规则地址。一般不推荐。 |
| -q | 使用SMBus“快速写入”命令进行探测。一般不推荐。 |
| -r | 使用SMBus“接收字节”命令进行探测。一般不推荐。 |
| -F | 显示适配器实现的功能列表并退出。 |
| -V | 显示I2C工具的版本并推出。 |
| -l | 显示已经在系统中使用的I2C总线。 |
| i2cbus | 表示要扫描的I2C总线的编号或名称。 |
| first last | 表示要扫描的从设备地址范围。 |
该功能的常用方式:
第一,先通过i2cdetect -l查看当前系统中的I2C的总线情况:
![](https://i-blog.csdnimg.cn/img_convert/83fd88da995e0f0188f4b198fbd75b43.png)
第二,若总线上挂载I2C从设备,可通过i2cdetect扫描某个I2C总线上的所有设备。可通过控制台输入i2cdetect -y 1:(其中"--"表示地址被探测到了,但没有芯片应答; "UU"因为这个地址目前正在被一个驱动程序使用,探测被省略;而16进制的地址号60,1e和50则表示发现了一个外部片选从地址为0x60,0x1e(AP3216)和0x50(eeprom)的外设芯片。
![](https://i-blog.csdnimg.cn/img_convert/e209133e3de3bdedc798495f2b9294da.png)
![](https://i-blog.csdnimg.cn/img_convert/f9b39b3aa50e697ba8a7b6a16b998885.png)
第三,查询I2C总线1 (I2C -1)的功能,命令为i2cdetect -F 1:
![](https://i-blog.csdnimg.cn/img_convert/bec6504d6a475053936b02a72040a2f3.png)
#### 5) 介绍I2C tools各功能之—i2cget
i2cget的主要功能是获取I2C外设某一寄存器的内容。该命令的常用格式为:
i2cget [-f] [-y] [-a] i2cbus chip-address [data-address [mode]]。具体参数的含义如下:
| -f | 强制访问设备,即使它已经很忙。 默认情况下,i2cget将拒绝访问 已经在内核驱动程序控制下的设备。 |
| ------------ | ------------------------------------------------------------ |
| -y | 取消交互模式。默认情况下,i2cdetect将等待用户的确认,当使用此 标志时,它将直接执行操作。 |
| -a | 允许在0x00 - 0x07和0x78 - 0x7f之间使用地址。一般不推荐。 |
| i2cbus | 表示要扫描的I2C总线的编号或名称。这个数字应该与i2cdetect -l列出 的总线之一相对应。 |
| chip-address | 要操作的外设从地址。 |
| data-address | 被查看外设的寄存器地址。 |
| mode | 显示数据的方式: b (read byte data, default) w (read word data) c (write byte/read byte) |
下面是完成读取0总线上从地址为0x50的外设的0x10寄存器的数据,命令为:
i2cget -y -f 0 0x50 0x10
![](https://i-blog.csdnimg.cn/img_convert/159bbfe1133283229df14e142ac065e2.png)
#### 6) 介绍I2C tools各功能之—i2cdump
i2cdump的主要功能查看I2C从设备器件所有寄存器的值。 该命令的常用格式为:i2cdump [-f] [-r first-last] [-y] [-a] i2cbus address [mode [bank [bankreg]]]。具体参数的含义如下:
| -f | 强制访问设备,即使它已经很忙。 默认情况下,i2cget将拒绝访问已经在内核驱动程序控制下的设备。 |
| ---------- | ------------------------------------------------------------ |
| -r | 限制正在访问的寄存器范围。 此选项仅在模式b,w,c和W中可用。对于模式W,first必须是偶数,last必须是奇数。 |
| -y | 取消交互模式。默认情况下,i2cdetect将等待用户的确认,当使用此标志时,它将直接执行操作。 |
| -V | 显示I2C工具的版本并推出。 |
| i2cbus | 表示要扫描的I2C总线的编号或名称。这个数字应该对应于i2cdetect -l列出的总线之一。 |
| first last | 表示要扫描的从设备地址范围。 |
| mode | b: 单个字节 w:16位字 s:SMBus模块 i:I2C模块的读取大小 c: 连续读取所有字节,对于具有地址自动递增功能的芯片(如EEPROM)非常有用。W与 w类似,只是读命令只能在偶数寄存器地址上发出;这也是主要用于EEPROM的。 |
下面是完成读取0总线上从地址为0x50的eeprom的数据,命令为:
i2cdump -f -y 0 0x50
![](https://i-blog.csdnimg.cn/img_convert/ad8e540b02ee0307d90894a1f10565d3.png)
#### 7) 介绍I2C tools各功能之—i2cset
i2cset的主要功能是通过I2C总线设置设备中某寄存器的值。该命令的常用格式为:
i2cset [-f] [-y] [-m mask] [-r] i2cbus chip-address data-address [value] ...[mode]
具体参数的含义如下:
| -f | 强制访问设备,即使它已经很忙。 默认情况下,i2cget将拒绝访问已经在内核驱动程序控制下的设备。 |
| ------- | ------------------------------------------------------------ |
| -r | 在写入值之后立即读取它,并将结果与写入的值进行比较。 |
| -y | 取消交互模式。默认情况下,i2cdetect将等待用户的确认,当使用此标志时,它将直接执行操作。 |
| -V | 显示I2C工具的版本并推出。 |
| i2cbus | 表示要扫描的I2C总线的编号或名称。这个数字应该对应于i2cdetect -l列出的总线之一。 |
| -m mask | 如果指定mask参数,那么描述哪些value位将是实际写入data-addres的。掩码中设置为1的位将从值中取出,而设置为0的位将从数据地址中读取,从而由操作保存。 |
| mode | b: 单个字节 w:16位字 s:SMBus模块 i:I2C模块的读取大小 c: 连续读取所有字节,对于具有地址自动递增功能的芯片(如EEPROM)非常有用。 W与 w类似,只是读命令只能在偶数寄存器地址上发出;这也是主要用于EEPROM的。 |
下面是完成向0总线上从地址为0x50的eeprom的0x10寄存器写入0x55,命令为:
i2cset -y -f 0 0x50 0x10 0x55
然后用i2cget读取0总线上从地址为0x50的eeprom的0x10寄存器的数据,命令为:i2cget -y -f 0 0x50 0x10
![](https://i-blog.csdnimg.cn/img_convert/127be650b498ddf90005292312fcf288.png)
#### 8) 介绍I2C tools各功能之—i2ctransfer
i2ctransfer的主要功能是在一次传输中发送用户定义的I2C消息。i2ctransfer是一个创建I2C消息并将其合并为一个传输发送的程序。对于读消息,接收缓冲区的内容被打印到stdout,每个读消息一行。
该命令的常用格式为:i2ctransfer [-f] [-y] [-v] [-a] i2cbus desc [data] [desc [data]]
具体参数的含义如下:
| -f | 强制访问设备,即使它已经很忙。 默认情况下,i2cget将拒绝访问已经在内核驱动程序控制下的设备。 |
| ------ | ------------------------------------------------------------ |
| -y | 取消交互模式。默认情况下,i2cdetect将等待用户的确认,当使用此标志时,它将直接执行操作。 |
| -v | 启用详细输出。它将打印所有信息发送,即不仅为读消息,也为写消息。 |
| -V | 显示I2C工具的版本并推出。 |
| -a | 允许在0x00 - 0x02和0x78 - 0x7f之间使用地址。一般不推荐。 |
| i2cbus | 表示要扫描的I2C总线的编号或名称。这个数字应该对应于i2cdetect -l列出的总线之一。 |
下面是完成向0总线上从地址为0x50的eeprom的0x20开始的4个寄存器写入0x01,0x02,0x03,0x04命令为:i2ctransfer -f -y 0 w5@0x50 0x20 0x01 0x02 0x03 0x04然后再通过命令i2ctransfer -f -y 0 w1@0x50 0x20 r4将0x20地址的4个寄存器数据读出来,见下图:
![](https://i-blog.csdnimg.cn/img_convert/af9a0da6756a56d63dfd37c313130bf7.png)
### **12.3.2** **在linux应用程序中读写I2C外设**
首先通过前面的介绍,我们已经知道站在cpu的角度来看,操作I2C外设实际上就是通过控制cpu中挂载该I2C外设的I2C控制器,而这个I2C控制器在linux系统中被称为“I2C适配器”,这个已经在驱动简介中介绍过了。而且众所周知,在linux系统中,每一个设备都是以文件的形式存在的,所以在linux中操作I2C外设就变成了操作I2C适配器设备文件。Linux系统(也就是内核)为每个I2C适配器生成了一个主设备号为89的设备节点(次设备号为0-255),它并没有针对特定的I2C外设而设计,只是提供了通用的read(),write(),和ioctl()等文件操作接口,在用户空间的应用层就可以借用这些接口访问挂接在适配器上的I2C设备的存储空间或寄存器,并控制I2C设备的工作方式。
操作流程:
#### 1) 确定I2C适配器的设备文件节点
i2c适配器的设备节点是/dev/i2c-x,其中x是数字。由于适配器编号是动态分配的(和注册次序有关),所以想了解哪一个适配器对应什么编号,可以查看/sys/class/i2c-dev/目录下的文件内容(在这里笔者强烈建议读者好好利用好sys文件系统):
```c
cat /sys/class/i2c-dev/i2c-0/name
cat /sys/class/i2c-dev/i2c-1/name
```
![](https://i-blog.csdnimg.cn/img_convert/7f4d073336e172459733a53a27017293.png)
然后查看硬件原理图中eeprom是挂在cpu的i2c1控制器中了,然后查看IMX6UL芯片手册中I2C1的寄存器地址为21A_0000。
![](https://i-blog.csdnimg.cn/img_convert/676140e18403baee9d0518b9990e056b.png)
比对后,我们就很容易知道eeprom外设对应的I2C控制器的设备节点为:/dev/i2c-0。
#### 2) 打开适配器对应的设备节点
当用户打开适配器设备节点的时候,Kernel中的i2c-dev代码为其建立一个i2c_client,但是这个i2c_client并不加到i2c_adapter的client链表当中。当用户关闭设备节点时,它自动被释放。
#### 3) IOCTL控制
这个可以参考内核源码中的include/linux/i2c-dev.h文件。下面举例说明主要的IOCTL命令:
| I2C_SLAVE_FORCE | 设置I2C从设备地址(只有在该地址空闲的情况下成功) |
| --------------- | ------------------------------------------------------------ |
| I2C_SLAVE_FORCE | 强制设置I2C从设备地址(无论内核中是否已有驱动在使用这个地址都会成功) |
| I2C_TENBIT | 选择地址位长: 0 表示是7bit地址 ; 不等于0 就是10 bit的地址。只有适配器支持I2C_FUNC_10BIT_ADDR,这个请求才是有效的。 |
| I2C_FUNCS | 获取适配器支持的功能,详细的可以参考文件include/linux/i2c.h |
| I2C_RDWR | 设置为可读写 |
| I2C_RETRIES | 设置收不到ACK时的重试次数 |
| I2C_TIMEOUT | 设置超时的时限 |
#### 4) 使用I2C协议和设备进行通信
代码为:ioctl(file,I2C_RDWR,(struct i2c_rdwr_ioctl_data *)msgset); 它可以进行连续的读写,中间没有间歇。只有当适配器支持I2C_FUNC_I2C此命令才有效。参数msgset是一个指针,指向一个i2c_rdwr_ioctl_data类型的结构体,该结构体的功能就是让应用程序可以向内核传递消息,其成员包括:struct i2c_msg __ user *msgs; 和表示i2c_msgs 个数的 __u32 nmsgs,它也决定了在硬件I2C总线的硬件通信中有多少个开始信号。由于I2C适配器与外设通信是以消息为单位的,所以struct i2c_msg对我们来说是非常重要的,它可以包含多条消息,而一条消息有可能包含多个数据,比如对于eeprom页写就包含多个数据。下面就介绍一下这个结构体的内容:
| __u16 addr; | 从设备地址 |
| ------------------ | ------------------------------------------------------------ |
| __u16 flags; | 标志(读/写) |
| I2C_M_TEN | 这是一个10位芯片地址 |
| I2C_M_RD | 从设备到适配器读数据 |
| I2C_M_NOSTART | 不发送起始位 |
| I2C_M_REV_DIR_ADDR | 翻转读写标志 |
| I2C_M_IGNORE_NAK | 忽略I2C的NACK信号 |
| I2C_M_NO_RD_ACK | 读操作的时候不发ACK信号 |
| I2C_M_RECV_LEN | 第一次接收数据的长度 |
| __u16 len; | 写入或者读出数据的个数(字节) |
| __u8 *buf; | 写入或者读出数据的地址 buf[0]。 注意:千万不要忘记给 2c_rdwr_ioctl_data结构体中的最重要的结构i2c_msg中的buf分配内存。 |
#### 5) 用read和write读写I2C设备
当然你可以使用read()/write()来与I2C设备进行通信,代码如下(以eeprom为例简要概述操作过程):
第一,打开I2C控制器文件节点: fd =open(“/dev/i2c-0”, O_RDWR);
第二,设置eeprom的设备地址:ioctl(fd,I2C_SLAVE, 0x50);
第三,向eeprom写数据:
首先将要操作的eeprom的第一个寄存器地址赋给写buf的第0个元素wr_buf[0] = 0x10;
然后把要写入的数据写入到后面的buf中for(i=1;i
- 2024-12-13
-
发表了主题帖:
百问MQTT协议分析 - 报文分析①
## 16.3 报文分析
### 16.3.1 CONNECT-连接服务端
客户端到服务端的网络连接建立(完成三次握手)后,客户端发送给服务端的第一个报文必须是 CONNECT 报文。
![图3.1 三次握手与mqtt connect交互过程](http://photos.100ask.net/NewHomeSite/MQTT_Image0009.png)
在一个网络连接上,客户端只能发送一次 CONNECT 报文。服务端必须将客户端发送的第二个 CONNECT报文当作协议违规处理并断开客户端的连接。
有效载荷包含一个或多个编码的字段。 包括客户端的唯一标识符, Will 主题, Will 消息, 用户名和密码。 除了客户端标识之外, 其它的字段都是可选的, 基于标志位来决定可变报头中是否需要包含这些字段。
![图3.2 connect报文组成](http://photos.100ask.net/NewHomeSite/MQTT_Image0010.png)
#### 16.3.1.1 connect固定报头
bit
7
6
5
4
3
2
1
0
Byte1
Mqtt报文类型(1)
Reserved(保留位)
0
0
0
1
0
0
0
0
Byte2~n
剩余长度
表格3.1
#### 16.3.1.2 协议名字节组成
说明
7
6
5
4
3
2
1
0
协议名
Byte1
协议名长度MSB(0)
0
0
0
0
0
0
0
0
Byte2
协议名长度LSB(4)
0
0
0
0
0
1
0
0
Byte3
‘M’
0
1
0
0
1
1
0
1
Byte4
‘Q’
0
1
0
1
0
0
0
1
Byte5
‘T’
0
1
0
1
0
1
0
0
Byte6
‘T’
0
1
0
1
0
1
0
0
数据包检测工具, 例如防火墙, 可以使用协议名来识别 MQTT 流量。
#### 16.3.1.3 协议级别
说明
7
6
5
4
3
2
1
0
协议级别
Byte7
Level(4)
0
0
0
0
0
1
0
0
客户端用 8 位的无符号值表示协议的修订版本。对于 3.1.1 版协议,协议级别字段的值是 4(0x04)。如果发现不支持的协议级别,服务端必须给发送一个返回码为 0x01(不支持的协议级别)的CONNACK 报文响应CONNECT 报文, 然后断开客户端的连接。
#### 16.3.1.4 连接标记
| **bit** | **7** | **6** | **5** | **4** | **3** | **2** | **1** | **0** |
| ---------- | ---------- | ------------ | ------------ | -------- | --------- | -------- | -------- | ----- |
| | 用户名标记 | 用户密码标记 | Will retain | Will qos | Will flag | 清除会话 | reserved | |
| **Byte 8** | x | x | x | x | x | x | 0 | |
**bit1清除会话**
一般来说, 客户端连接时总是将清理会话标志设置为 0 或 1, 并且不交替使用两种值。 这个选择取决于具体的应用。 清理会话标志设置为 1 的客户端不会收到旧的应用消息, 而且在每次连接成功后都需要重新订阅任何相关的主题。清理会话标志设置为 0 的客户端会收到所有在它连接断开期间发布的 QoS 1 和 QoS 2 级别的消息。因此, 要确保不丢失连接断开期间的消息, 需要使用 QoS 1 或QoS 2 级别,同时将清理会话标志设置为 0。
**Bit2遗嘱标志**
遗嘱标志(Will Flag) 被设置为 1,表示如果连接请求被接受了, 遗嘱(Will Message) 消息必须被存储在服务端并且与这个网络连接关联。之后网络连接关闭时,服务端必须发布这个遗嘱消息, 除非服务端收到DISCONNECT 报文时删除了这个遗嘱消息。
**Bit3和 bit4遗嘱 QoS**
这两位用于指定发布遗嘱消息时使用的服务质量等级, 如果遗嘱标志被设置为 0, 遗嘱 QoS 也必须设置为 0(0x00),如果遗嘱标志被设置为 1, 遗嘱 QoS 的值可以等于 0(0x00), 1(0x01), 2(0x02), 它的值不能等于 3。
**Bit5遗嘱保留**
如果遗嘱消息被发布时需要保留,需要指定这一位的值, 如果遗嘱标志被设置为 0, 遗嘱保留(Will Retain) 标志也必须设置为 0 。
如果遗嘱标志被设置为 1:
· 如果遗嘱保留被设置为 0, 服务端必须将遗嘱消息当作非保留消息发布 。
· 如果遗嘱保留被设置为 1, 服务端必须将遗嘱消息当作保留消息发布。
**Bit7** **用户名标志**
如果用户名(User Name) 标志被设置为 0, 有效载荷中不能包含用户名字段。
如果用户名(User Name) 标志被设置为 1, 有效载荷中必须包含用户名字段。
**Bit6** **用户名密码标记**
如果密码(Password) 标志被设置为 0, 有效载荷中不能包含密码字段 。
如果密码(Password) 标志被设置为 1, 有效载荷中必须包含密码字段 。
如果用户名标志被设置为 0, 密码标志也必须设置为 0 。
#### 16.3.1.5 保持连接
bit
7
6
5
4
3
2
1
0
Byte9
保持连接 Keep Alive MSB
Byte10
保持连接 Keep Alive LSB
a) 保持连接(Keep Alive) 是一个以秒为单位的时间间隔,表示为一个 16 位的字,它是指在客户端传输完成。
b) 一个控制报文的时刻到发送下一个报文的时刻, 两者之间允许空闲的最大时间间隔。 客户端负责保证控制。
c) 报文发送的时间间隔不超过保持连接的值。 如果没有任何其它的控制报文可以发送, 客户端必须发送一个PINGREQ 报文。
d) 不管保持连接的值是多少,客户端任何时候都可以发送 PINGREQ 报文,并且使用 PINGRESP 报文判断网络和服务端的活动状态。
e) 如果保持连接的值非零,并且服务端在一点五倍的保持连接时间内没有收到客户端的控制报文, 它必须断开客户端的网络连接, 认为网络连接已断开。
f) 客户端发送了 PINGREQ 报文之后, 如果在合理的时间内仍没有收到 PINGRESP 报文, 它应该关闭到服务端的网络连接。
g) 保持连接的值为零表示关闭保持连接功能。 这意味着,服务端不需要因为客户端不活跃而断开连接。 注意:不管保持连接的值是多少, 任何时候,只要服务端认为客户端是不活跃或无响应的, 可以断开客户端的连接。
#### 16.3.1.6 客户端标识符
服务端使用客户端标识符 (ClientId) 识别客户端。 连接服务端的每个客户端都有唯一的客户端标识符(ClientId) 。客户端和服务端都必须使用 ClientId 识别两者之间的 MQTT 会话相关的状态, 客户端标识符 (ClientId) 必须存在而且必须是 CONNECT 报文有效载荷的第一个字段,客户端标识符必须是UTF-8 编码字符串。
#### 16.3.1.7 遗嘱主题
如果遗嘱标志被设置为 1, 有效载荷的下一个字段是遗嘱主题(Will Topic) 。 遗嘱主题必须是 UTF-8 编码字符串。
#### 16.3.1.8 遗嘱消息
如果遗嘱标志被设置为 1, 有效载荷的下一个字段是遗嘱消息。 遗嘱消息定义了将被发布到遗嘱主题的应用消息。
#### 16.3.1.9 用户名和密码
如果用户名( User Name) 标志被设置为 1, 有效载荷的下一个字段就是它。 用户名必须是定义的UTF-8 编码字符串。服务端可以将它用于身份验证和授权。
如果密码( Password) 标志被设置为 1, 有效载荷的下一个字段就是它。密码字段包含一个两字节的长度字段, 长度表示二进制数据的字节数( 不包含长度字段本身占用的两个字节),后面跟着 0 到 65535 字节的二进制数据。
![图3.2 用户名和密码在connect报文中的组成](http://photos.100ask.net/NewHomeSite/MQTT_Image0011.png)
#### 16.3.10.1 wirshark抓包分析connect报文
从抓包可知,从上到下分别是固定报头,可变报头,连接标记,保持连接,用户名,用名密码,其中没有遗嘱相关消息字段,与3.1.1节分析的固定报头组成分析一致。
![图 3.3使用wireshark抓包分析connect报文组成格式](http://photos.100ask.net/NewHomeSite/MQTT_Image0012.png)
#### 16.3.10.2 c语言构造mqtt connect报文
```C
static uint8_t client_id[512] = {"mqtt_client"};
static uint8_t user_name[512] = {"mqtt"};
static uint8_t passwd[512] = {"12345678"};
#define KEEP_ALIVE 20
int mqtt_connect(int sockfd)
{
uint8 flags = 0x00;
uint8 *packet = NULL;
uint16 packet_length = 0;
uint16 clientidlen = strlen(client_id);
uint16 usernamelen = strlen(user_name);
uint16 passwordlen = strlen(passwd);
uint16 payload_len = clientidlen + 2;
// Variable header
uint8 var_header[10] = {
0x00,0x04,/*len*/
0x4d,0x51,0x54,0x54,/*mqtt*/
0x04,/*协议版本*/};
uint8 fixedHeaderSize = 2; // Default size = one byte Message Type + one byte Remaining Length
uint8 remainLen = 0;
uint8 *fixed_header = NULL;
uint16 offset = 0;
// Preparing the flags
if(usernamelen) { /*用户名长度(可选)*/
payload_len += usernamelen + 2;
flags |= MQTT_USERNAME_FLAG;/*或上用户名标记*/
}
if(passwordlen) { /*用户密码(可选)*/
payload_len += passwordlen + 2;
flags |= MQTT_PASSWORD_FLAG;/*用户密码标记位*/
}
flags |= MQTT_CLEAN_SESSION;
var_header[7] = flags;/*连接标记*/
var_header[8] = KEEP_ALIVE>>8;/*保持连接字段,占用两个字节*/
var_header[9] = KEEP_ALIVE&0xFF;
remainLen = sizeof(var_header)+payload_len; /*剩余长度,也就是可变报头加上负载的长度*/
if (remainLen > 127) {
fixedHeaderSize++;// add an additional byte for Remaining Length
}
fixed_header = (uint8 *)malloc(fixedHeaderSize); /*固定报头*/
// Message Type
*fixed_header = MQTT_MSG_CONNECT;/*报文类型,connect*/
if (remainLen >8;// Client ID - UTF encoded,填充clientid长度+clientid
packet[offset++] = clientidlen&0xFF;
memcpy(packet+offset, client_id, clientidlen);
offset += clientidlen;
if(usernamelen) {// Username - UTF encoded,填充用户名+用户名长度
packet[offset++] = usernamelen>>8;
packet[offset++] = usernamelen&0xFF;
memcpy(packet+offset, user_name, usernamelen);
offset += usernamelen;
}
if(passwordlen) {// Password - UTF encoded,填充用户密码+用户名密码长度
packet[offset++] = passwordlen>>8;
packet[offset++] = passwordlen&0xFF;
memcpy(packet+offset, passwd, passwordlen);
offset += passwordlen;
}
// Send the packet
if (client_send(sockfd,packet, packet_length) < 0){
free(packet);
return -1;
}
free(packet);
return 1;
}
```
### 16.3.2 CONNACK-确认连接请求
服务端发送 CONNACK 报文响应从客户端收到的 CONNECT 报文。服务端发送给客户端的第一个报文必须是 CONNACK。
#### 16.3.2.1 固定报头
bit
7
6
5
4
3
2
1
0
Byte1
MQTT 控制报文类型 (2)
Reserved 保留位
0
0
1
0
0
0
0
0
Byte2
剩余长度
0
0
0
0
0
0
1
0
剩余长度字段表示可变报头的长度。 对于 CONNACK 报文这个值等于 2。
#### 16.3.2.2 可变报头
描述
7
6
5
4
3
2
1
0
连接确认标记
保留位
SP1
Byte1
0
0
0
0
0
0
0
X
连接返回码
Byte2
x
x
x
x
x
x
x
x
**Byte1,Bit0连接确认标志**
位 7-1 是保留位且必须设置为 0,
对于bit0,如果服务端收到一个 CleanSession 为 0 的连接, 当前会话标志的值取决于服务端是否已经保存了 ClientId对应客户端的会话状态。 如果服务端已经保存了会话状态, 它必须将 CONNACK 报文中的当前会话标志设置为 1 。 如果服务端没有已保存的会话状态, 它必须将 CONNACK 报文中的当前会话设置为 0。 还需要将 CONNACK 报文中的返回码设置为 0。
**连接返回码**
如果服务端发送了一个包含非零返回码的 CONNACK 报文, 那么它必须关
闭网络连接。
| **值** | **返回码响应** | **描述** |
| --------- | -------------- | -------------------------------------------------------- |
| **0** | 0x00 | 连接已被服务端接受 |
| **1** | 0x01 | 服务端不支持客户端请求的协议版本 |
| **2** | 0x02 | 客户端标识符是正确的 UTF-8 编码, 但服务 端不允许使用 |
| **3** | 0x03 | 网络连接已建立, 但 MQTT 服务不可用 |
| **4** | 0x04 | 用户名或密码的数据格式无效 |
| **5** | 0x05 | 客户端未被授权连接到此服务器 |
| **6-255** | | 保留 |
CONNACK没有有效载荷。
#### 16.3.2.3 CONNACK报文wireshark抓包分析
![图3.4 CONNACK 抓包报文](https://i-blog.csdnimg.cn/img_convert/ef0fbbf3c28f79686e163ab3d3cb4278.png)
#### 16.3.2.4 c语言构造connect ack报文
```c
void mqtt_connect_ack(int sockfd)
{
uint8_t cmd[]={ 0x20/*报文类型*/, 0x02/*剩余长度*/ ,0x00,0x00/*最后两个字节可变报头表示返回状态码*/ };
send_msg(sockfd,cmd,sizeof(cmd));
socket_record_t *socket_record = look_up_by_sokfd(sockfd);
if(socket_record==NULL){
return;
}
socket_record->is_connect=0x01;
}
```
### 16.3.3 PUBLISH-发布消息
PUBLISH 控制报文是指从客户端向服务端或者服务端向客户端传输一个应用消息。
![图 3.5 publish报文组成格式](https://i-blog.csdnimg.cn/img_convert/9c959463266236b8e92531190b33b73e.png)
#### 16.3.3.1 固定报头
bit
7
6
5
4
3
2
1
0
Byte 1
MQTT报文类型(3)
dup
Qos等级
RETAIN
0
0
1
1
x
x
x
x
Byte2
剩余长度
**Bit3 dup**
如果 DUP 标志被设置为 0, 表示这是客户端或服务端第一次请求发送这个 PUBLISH 报文。 如果 DUP 标志被设置为 1,表示这可能是一个早前报文请求的重发。客户端或服务端请求重发一个 PUBLISH 报文时, 必须将 DUP 标志设置为 1.。 对于 QoS0 的消息, DUP 标志必须设置为 0。
**Bit1和bit2 qos等级**
| Qos值 | bit2 | bit1 | 描述 |
| ----- | ----- | ----- | ---------------- |
| 0 | **0** | **0** | **最多分发一次** |
| 1 | **0** | **1** | **至少分发一次** |
| 2 | **1** | **0** | **只分发一次** |
| - | **1** | **1** | **保留不使用** |
qos由发送端决定,发送端发送什么qos的消息,接收端就回复什么qos的消息。
![不同qos等级mqtt报文交互流程](https://i-blog.csdnimg.cn/img_convert/aaa8f74c1e6ce818f57094db6f895ba1.png)
**Bit0** **保留标记位**
一般设置为0。
**剩余长度**
等于可变报头的长度加上有效载荷的长度。
**可变报头**
可变报头按顺序包含主题名和标识符。主题,用于识别有效载荷数据应该被发布到哪一个信息通道,标识符,只有当 QoS 等级是 1 或 2 时,报文标识符( Packet Identifier) 字段才能出现在 PUBLISH 报文中。
#### 16.3.3.2 抓包分析PUBLISH报文
![图 3.6 PUBLISH 抓包报文](https://i-blog.csdnimg.cn/img_convert/0fe1deef08b30e8ed5cd6d313bbad71f.png)
#### 16.3.3.3 构造publish 报文
```c
int mqtt_publish_with_qos(int sockfd,const char* topic, const char* msg, uint16 msgl, uint8 retain, uint8 qos, uint16* message_id)
{
socket_record_t *socket_record = look_up_by_sokfd(sockfd);
if(NULL == socket_record){
return -1;
}
DEBUG_INFO("sockfd:%d",socket_record->sockfd);
uint16 topiclen = strlen(topic);
uint16 msglen = msgl;
uint8 *var_header = NULL; // Topic size (2 bytes), utf-encoded topic
uint8 *fixed_header = NULL;
uint8 fixedHeaderSize = 0,var_headerSize = 0; // Default size = one byte Message Type + one byte Remaining Length
uint16 remainLen = 0;
uint8 *packet = NULL;
uint16 packet_length = 0;
uint8 qos_flag = MQTT_QOS0_FLAG; /*qos标记*/
uint8 qos_size = 0; // No QoS included
if(qos == 1) {
qos_size = 2; // 2 bytes for QoS
qos_flag = MQTT_QOS1_FLAG;
}
else if(qos == 2) {
qos_size = 2; // 2 bytes for QoS
qos_flag = MQTT_QOS2_FLAG;
}
// Variable header
var_headerSize = topiclen/*主题内容*/+2/*主题长度占用两字节*/+qos_size/*标识符*/;
var_header = (uint8 *)malloc(var_headerSize);
memset(var_header, 0, var_headerSize);
*var_header = topiclen>>8;
*(var_header+1) = topiclen&0xFF;
memcpy(var_header+2, topic, topiclen);
if(qos_size) {//qos1和qos2的报文需要填充标识符,有点像tcp的seq
socket_record->publish_seq++;
if(socket_record->publish_seq == 0){
//unsigned short 表示范围0~65535,标识符必须是非零整数
socket_record->publish_seq = 1;
}
var_header[topiclen+2] = (socket_record->publish_seq & 0xff00)>>8;
var_header[topiclen+3] = socket_record->publish_seq & 0x00ff;
if(message_id) {
*message_id = socket_record->publish_seq;
}
}
fixedHeaderSize = 2; // Default size = one byte Message Type + one byte Remaining Length
remainLen = var_headerSize+msglen;
if (remainLen > 127) {/*剩余长度*/
fixedHeaderSize++; // add an additional byte for Remaining Length
}
fixed_header = (uint8 *)malloc(fixedHeaderSize);/*固定报头+剩余长度*/
// Message Type, DUP flag, QoS level, Retain
*fixed_header = MQTT_MSG_PUBLISH | qos_flag;/*报文类型和qos标记*/
if(retain) {
*fixed_header |= MQTT_RETAIN_FLAG;/*是否保留*/
}
// Remaining Length,剩余长度
if (remainLen >8 , msg_id&0x00ff/*最后两个字节是报文标识符*/};
send_msg(sockfd,qos2_pubrec_respon,sizeof(qos2_pubrec_respon));
}
```
### 16.3.5 PUBREL-发布释放
PUBREL 报文是对 PUBREC 报文的响应。 它是 QoS 2 等级协议交换的第三个报文。
#### 16.3.5.1 固定报头
bit
7
6
5
4
3
2
1
0
Byte 1
MQTT报文类型(6)
保留位
0
1
1
0
0
0
0
0
Byte2
剩余长度
**剩余长度**
表示可变报头的长度。 对 PUBREL 报文它的值等于 2。
**可变报头**
bit
7
6
5
4
3
2
1
0
Byte1
报文标识符MSB
Byte2
报文标识符LSB
**有效载荷**
PUBREL 报文没有有效载荷。
#### 16.3.5.2 PUBREL抓包报文
![图 3.8 PUBREL抓包报文图示](https://i-blog.csdnimg.cn/img_convert/3ffe71c54b543e7dd6d3630cde2e019b.png)
#### 16.3.5.3 c语言构造pubrel报文
```c
//head_type=0x62
void mqtt_qos2_pubrel(int sockfd , unsigned char *data,unsigned char head_type)
{
uint16 msg_id = mqtt_parse_msg_id(data);
unsigned char qos2_pubrel_respon[]={head_type/*报文类型*/,0x02/*剩余长度*/, (msg_id & 0xff00)>>8 , msg_id & 0x00ff/*最后两个字节是报文标识符*/};
send_msg(sockfd,qos2_pubrel_respon,sizeof(qos2_pubrel_respon));
}
```
### 16.3.6 PUBCOMP-发布完成
PUBCOMP 报文是对 PUBREL 报文的响应。 它是 QoS 2 等级协议交换的第四个也是最后一个报文。
#### 16.3.6.1 固定报头
bit
7
6
5
4
3
2
1
0
Byte 1
MQTT报文类型(7)
保留位
0
1
1
1
0
0
0
0
Byte2
剩余长度
**剩余长度**
表示可变报头的长度。 对 PUBCOMP 报文它的值等于 2。
**可变报头**
bit
7
6
5
4
3
2
1
0
Byte1
报文标识符MSB
Byte2
报文标识符LSB
**有效载荷**
PUBCOMP 报文没有有效载荷。
#### 16.3.6.2 PUBCOMP抓包报文
![图 3.9 PUBCOMP抓包报文图示](https://i-blog.csdnimg.cn/img_convert/3ae06ec107cc45fd5b7afc597c63f70e.png)
#### 16.3.6.3 c语言构造pubcom报文
```c
//head_type=0x70
void mqtt_qos2_pubcomp(int sockfd , unsigned char *data,unsigned char head_type)
{
uint16 msg_id = mqtt_parse_msg_id(data);/*报文标识符*/
unsigned char qos2_pubcomp_respon[]={head_type/*报文类型*/,0x02/*剩余长度*/, (msg_id & 0xff00)>>8 , msg_id & 0x00ff/*最后两个字节报文标识符*/};
send_msg(sockfd,qos2_pubcomp_respon,sizeof(qos2_pubcomp_respon));
}
```
### 16.3.7 PINGREQ-心跳请求
客户端发送 PINGREQ 报文给服务端的。用于:
a) 在没有任何其它控制报文从客户端发给服务的时,告知服务端客户端还活着。
b) 请求服务端发送 响应确认它还活着。
c) 使用网络以确认网络连接没有断开。
#### 16.3.7.1 固定报头
bit
7
6
5
4
3
2
1
0
Byte 1
MQTT报文类型(12)
保留位
1
1
0
0
0
0
0
0
Byte2
剩余长度 0
**可变报头**
报文没有可变报头。
**有效载荷**
PINGREQ 报文没有有效载荷。
#### 16.3.7.2 PINGREQ 抓包报文
![图 3.10 PINGREQ抓包报文图示](https://i-blog.csdnimg.cn/img_convert/74e2cdd136ed0d2a7e4e8d00a6a5596d.png)
#### 16.3.7.3 c语言构造pingreq报文
```c
int mqtt_ping(int sockfd)
{
uint8 packet[] = {MQTT_MSG_PINGREQ/*报文类型*/,0x00/*剩余长度*/};
int ret = send_msg(sockfd,packet, sizeof(packet));
return ret;
}
```
- 2024-12-04
-
发表了主题帖:
百问FB网络编程 - 主要函数介绍
## **6.3** **网络编程主要函数介绍**
下面全部函数的头文件都是
```c
#include
#include
```
### 6.3.1 socket函数
```c
int socket(int domain, int type,int protocol);
```
此函数用于创建一个套接字。
**domain**是网络程序所在的主机采用的通讯协族(AF_UNIX和AF_INET等)。
AF_UNIX只能够用于单一的Unix 系统进程间通信,而AF_INET是针对Internet的,因而可以允许远程通信使用。
**type**是网络程序所采用的通讯协议(SOCK_STREAM,SOCK_DGRAM等)。
SOCK_STREAM表明用的是TCP 协议,这样会提供按顺序的,可靠,双向,面向连接的比特流。
SOCK_DGRAM 表明用的是UDP协议,这样只会提不可靠,无连接的通信。
关于**protocol**,由于指定了type,所以这个地方一般只要用0来代替就可以了。
此函数执行成功时返回文件描述符,失败时返回-1,看errno可知道出错的详细情况。
### 6.3.2 bind函数
```c
int bind(int sockfd, struct sockaddr *my_addr, int addrlen);
```
从函数用于将地址绑定到一个套接字。一台电脑上可能有多个IP和端口,这个套接字要绑定到哪个IP和端口需要用bind函数来绑定。
**sockfd**是由socket函数调用返回的文件描述符。
**my_addr**是一个指向sockaddr的指针。
**addrlen**是sockaddr结构的长度。
**sockaddr**的定义:
```c
struct sockaddr{
unisgned short as_family;
char sa_data[14]; // 这14个字节里面,含有 IP 和 端口,但是不明显
};
```
不过由于系统的兼容性,我们一般使用另外一个结构(struct sockaddr_in) 来代替。
**sockaddr_in**的定义: **sockaddr** 和 **sockaddr_in** 结构体的大小是完全一样的,
```c
struct sockaddr_in{
unsigned short sin_family;
unsigned short sin_port; // 2字节 表示端口
struct in_addr sin_addr; // 4字节 表示IP地址
unsigned char sin_zero[8]; // 8字节 不用 2+4+8=14字节,和上面那个结构体一样
}
```
如果使用Internet所以sin_family一般为AF_INET。
sin_addr还是一个结构体,sin_addr.s_addr 设置为INADDR_ANY表示可以和主机的所有IP通信,也就是监测所有的IP。
sin_port是要监听的端口号。要使用 htons(SERVER_PORT) 端口号转换为网络字节序
bind将本地的端口同socket返回的文件描述符捆绑在一起.
成功是返回0,失败的情况和socket一样,返回 -1。
### 6.3.3 listen函数
```c
int listen(int sockfd,int backlog);
```
此函数宣告服务器可以接受连接请求。
**sockfd**是bind后的文件描述符。
**backlog**设置请求排队的最大长度。当有多个客户端程序和服务端相连时,使用这个表示可以介绍的排队长度。
listen函数将bind的文件描述符变为监听套接字。
成功是返回0,失败的情况和socket一样,返回 -1。
### 6.3.4 accept函数
```c
int accept(int sockfd, struct sockaddr *addr,int *addrlen);
```
服务器使用此函数获得连接请求,并且建立连接。
**sockfd**是listen后的文件描述符。
**addr**,**addrlen**是用来给客户端的程序填写的,服务器端只要传递指针就可以了, bind,listen和accept是服务器端用的函数。
accept调用时,服务器端的程序会一直阻塞到有一个客户程序发出了连接。
accept成功时返回最后的服务器端的文件描述符,这个时候服务器端可以向该描述符写信息了,失败时返回-1 。
(可以认为这个描述符是这个客户端的象征,之后接收发送就向该描述符操作)
问:如何把客户端的IP地址转换为我们常见的形式?
答:inet_ntoa(sockaddr.sin_addr) 把这个 sin_addr 转换为 ascii 格式的字符串
### 6.3.5 connect函数
对于TCP的连接,这里会有3次握手
对于UDP的连接,这里是虚假的连接,目的只是为了获得IP地址这些数据而已
```c
int connect(int sockfd, struct sockaddr * serv_addr,int addrlen);
```
可以用connect建立一个连接,在connect中所指定的地址是想与之通信的服务器的地址。
**sockfd**是socket函数返回的文件描述符,客户端的文件描述符。
**serv_addr**储存了服务器端的连接信息,其中sin_add是服务端的地址。
**addrlen**是serv_addr的长度。
connect函数是客户端用来同服务端连接的
成功时返回0,sockfd是同服务端通讯的文件描述符(客户端),失败时返回-1。
### 6.3.6 send函数
```c
ssize_t send(int sockfd, const void \*buf, size_t len, int flags);
```
**sockfd** 指定发送端套接字描述符;
**buf** 指明一个存放应用程序要发送数据的缓冲区;
**len** 指明实际要发送的数据的字节数;
**flags** 一般置0。
客户或者服务器应用程序都用send函数来向TCP连接的另一端发送数据
### 6.3.7 recv函数
【没有数据会休眠】
```c
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
```
**sockfd** 指定接收端套接字描述符;
**buf** 指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;
**len** 指明buf的长度,也就是最多可以接收多少字节的数据;
**flags** 一般置0。
客户或者服务器应用程序都用recv函数从TCP连接的另一端接收数据。
返回值:平时会阻塞,有数据就返回实际接收到了多少个数据
if(iRecvLen
-
发表了主题帖:
百问FB网络编程 - 网络编程简介
## **6.1** **网络编程简介**
要编写通过计算机网络通信的程序,首先要确定这些程序同通信的协议(protocol),在设计一个协议的细节之前,首先要分清程序是由哪个程序发起以及响应何时产生。
举例来说,一般认为WEB服务器程序是一个长时间运行的程序(守护进程deamon),它只在响应来自网络的请求时才发送网络消息。协议的另一端是web客户程序,如某种浏览器,与服务器进程的通信总是由客户进程发起。大多数网络应用就是按照划分为客户(clinet)和服务器(server)来组织的。
### **6.1.1** **五层因特网协议栈**
为了给网络协议的设计提供一个结构,网络设计者以分层(layer)的方式组织协议以及实现这些协议的网络硬件和软件。
分层提供了一种结构化方式来讨论系统组件。模块化使更新系统组件更为容易。
协议栈是各层所有协议的总和。
![NetworkProgram_Image001](http://photos.100ask.net/NewHomeSite/NetworkProgram_Image001.png)
五层因特网协议栈
应用层:应用层是网络应用程序及它们的应用层协议存留的地方。
运输层:因特网的运输层在应用程序端点之间传从应用层报文。
网络层:因特网呃网络层负责将称为数据包(datagram)的网络层分组从一台主机移动到另一台主机。
链路层:因特网的网络层通过源和目的地之间的一系列路由器路由数据报。
物理层:虽然链路层的任务是将整个帧从一个网络元素移动到临近的网络元素,而物理层的任务是将该帧的一个一个比特从一个节点移动到下一个节点。
### 6.1.2 传输层和应用层的常见协议
我们重点介绍和应用层编程关系密切的应用层和运输层。
应用层:
因特网的应用层包含很多协议,例如HTTP,SMTP,和 FTP。我们看到的某些网络功能,比如将www.baidu.com这样对人友好的端系统名字转换为32比特网络地址,也是借助于特定的应用层协议即域名系统(DNS)完成的。
应用层的协议分布在多个端系统上,一个端系统中的应用程序使用协议与另一个端系统中的应用程序交换信息分组。
运输层:
在英特网中有两个运输协议,即TCP和UDP,利用其中的任何一个都能运输应用层报文。我们写应用程序的时候具体选择哪个运输层协议应该根据实际情况来确定(后面会具体讲解)。
## **6.2** **网络编程之TCP/UDP比较**
### 6.2.1 TCP和UDP 原理上的区别
TCP向它的应用程序提供了面向连接的服务。这种服务包括了应用层报文向目的地的确保传递和流量控制(即发送方/接收方速率匹配)。这种服务包括了应用层报文划分为短报文,并提供拥塞控制机制,因此当网络拥塞时源抑制其传输速率。
UDP协议向它的应用程序提供无连接服务。这是一种不提供不必要服务的服务,没有可靠性,没有流量控制,也没有拥塞控制。
### 6.2.2 为何存在UDP协议
既然TCP提供了可靠数据传输服务,而UDP不能提供,那么TCP是否总是首选呢?答案是否定的,因为有许多应用更适合用UDP,原因有以下几点:
a. 关于何时发送什么数据控制的更为精细。
采用UDP时只要应用进程将数据传递给UDP,UDP就会立即将其传递给网络层。而TCP有重传机制,而不管可靠交付需要多长时间。但是实时应用通常不希望过分的延迟报文段的传送,且能容忍一部分数据丢失。
b. 无需建立连接,不会引入建立连接时的延迟。
c. 无连接状态,能支持更多的活跃客户。
d. 分组首部开销较小。
### 6.2.3 TCP/UDP网络通信大概交互图
下面我们分别画出运用TCP协议和运用UDP协议的客户端和服务器大概交互图。
![NetworkProgram_Image002](http://photos.100ask.net/NewHomeSite/NetworkProgram_Image002.png)
面向连接的TCP流模式
![NetworkProgram_Image003](http://photos.100ask.net/NewHomeSite/NetworkProgram_Image003.png)
UDP用户数据包模式
- 2024-11-28
-
发表了主题帖:
百问FB显示开发图像处理 - JPEG图像处理
## 2.2 JPEG图像处理
### 2.2.1 JPEG文件格式和libjpeg编译
JPEG的后缀名为.jpg的图像文件。对于图像内容和信息相同的JPEG文件和BMP文件,JPEG格式的文件要比BMP格式的文件小得多,这是因为JPEG文件是经过JPEG压缩算法后得到的一种文件格式。
相对于BMP格式的文件,JPEG由于压缩算法的关系,其文件解析较为复杂,我们可以利用Linux系统开源的优点,使用开源工具对jpeg文件进行格式的解析和转换。
我们可以使用libjpeg库来对jpeg文件进行格式的解析和转换。libjpeg支持X86,ARM等架构。libjpeg是开源工具,所以可以在网上免费下载。
在使用libjpeg之前,我们先要交叉编译libjpeg的库文件和头文件并存到开发板的文件系统中。以下是libjpeg的编译过程:
1. 解压并进入文件目录
```c
tar xzf libjpeg-turbo-1.2.1.tar.gz
cd libjpeg-turbo-1.2.1/
```
2. 交叉编译
```c
tar xzf libjpeg-turbo-1.2.1.tar.gz
./configure --prefix=/work/projects/libjpeg-turbo-1.2.1/tmp/ --host=arm-linux
make
make install
```
3. 将编译出来的头文件和库文件拷贝到交叉编译器的相应目录下
```c
cd /work/projects/libjpeg-turbo-1.2.1/tmp/include
cp * /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/usr/include
cd /work/projects/libjpeg-turbo-1.2.1/tmp/lib
cp *so* -d /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/armv4t/lib
```
4. 将编译出来的头文件和库文件拷贝到开发板文件系统的相应目录下
```c
cd /work/projects/libjpeg-turbo-1.2.1/tmp/lib
cp *.so* /work/nfs_root/fs_mini_mdev_new/lib/ -d
```
### 2.2.2 libjpeg接口函数的解析和使用
libjpeg的使用方法可以参考解压包中的使用说明libjpeg.txt和例程example.c。libjpeg的使用步骤简单总结如下:
**1.** 分配和初始化一个jpeg_compress_struct结构体
```c
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_decompress(&cinfo);
```
**2.** 指定源文件
```c
jpeg_stdio_src(&cinfo, infile);
```
参数1是步骤1中分配的jpeg_compress_struct类型的结构体
参数2是要解析的JPEG文件的文件句柄。
**3.** 获得jpg信息头并设置解压参数
```c
jpeg_read_header(&cinfo, TRUE);
```
当调用完这个参数之后,我们就可以通过cinfo中的image_width,image_height等成员来获得图像的信息了。此外我们还可以设置cinfo中的scale_num和scale_denom等成员变量来设置解压参数。
**4.** 启动解压
```c
jpeg_start_decompress(&cinfo);
```
调用这个函数后,就可以对cinfo所指定的源文件进行解压,并将解压后的数据存到cinfo结构体的成员变量中。
**5.** 读取解压后数据
```c
jpeg_read_scanlines(&cinfo, buffer, 1);
```
调用这个函数后,可以读取RGB数据到buffer中,参数3能指定读取多少行
**6.** 完成读取
```c
jpeg_finish_decompress(&cinfo);
```
**7.** 释放jpeg_compress_struct结构体
```c
jpeg_destroy_decompress(&cinfo);
```
完成读取后释放结构体
### 2.2.3 使用libjpeg把JPEG文件解析为RGB格式,在LCD上显示
根据上节的解析,利用上述的库函数将JPEG文件解析为RGB格式了。
```c
代码清单2.2
1. /**********************************************************************
2. * 函数名称: IsJpg
3. * 功能描述:判断是否为Jpg文件
4. * 输入参数: ptData - 内含图像信息
5. strFileName - 文件名
6. * 返 回 值:0 - 不是JPG格式 其他-是JPG格式
7. ***********************************************************************/
8. static int IsJpg(PT_PictureData ptData, const char *strFileName)
9. {
10. int iRet;
11.
12. jpeg_stdio_src(&ptData->tInfo, ptData->ptFp);
13.
14. /* 用jpeg_read_header获得jpeg信息*/
15. iRet = jpeg_read_header(&ptData->tInfo, TRUE);
16.
17. return (iRet == JPEG_HEADER_OK);
18. }
19.
20. /**********************************************************************
21. * 函数名称: DecodeJpg2Rgb
22. * 功能描述:把JPG文件解析为RGB888格式
23. * 输入参数: ptData - 内含文件信息
24. * strFileName - 文件名
25. * 输出参数:PT_PictureData->pucRgbData - 内含rgb数据
26. * 返 回 值:0 - 成功 其他-失败
27. ***********************************************************************/
28. static int DecodeJpg2Rgb(const char *strFileName, PT_PictureData ptData){
29. int iRowSize;
30. unsigned char *pucbuffer;
31. unsigned char *pucHelp;//辅助拷贝变量
32.
33. /* 1.分配和初始化一个jpeg_compress_struct结构体 */
34. ptData->tInfo.err = jpeg_std_error(&ptData->tJerr);
35. jpeg_create_decompress(&ptData->tInfo);
36.
37.
38. /* 2.指定源文件*/
39. if ((ptData->ptFp= fopen(strFileName, "rb")) == NULL) {
40. fprintf(stderr, "can't open %s\n", strFileName);
41. return -1;
42. }
43.
44. /* 3.获得jpg信息头并设置解压参数并判断是否为JPEG格式文件 */
45. if (!IsJpg(ptData, strFileName)) {
46. printf("file is not jpg ...\n");
47. return -1;
48. }
49.
50.
51.
52. /* 默认尺寸为原尺寸 */
53. ptData->tInfo.scale_num = 1;
54. ptData->tInfo.scale_denom = 1;
55. /* 4. 启动解压:jpeg_start_decompress */
56. jpeg_start_decompress(&ptData->tInfo);
57.
58.
59. /* 解压完成后可以通过tInfo中的成员获得图像的某些信息 */
60. ptData->iWidth= ptData->tInfo.output_width;
61. ptData->iHeight = ptData->tInfo.output_height;
62. ptData->iBpp = ptData->tInfo.output_components*8;
63. /* 计算一行的数据长度 */
64. iRowSize = ptData->iWidth * ptData->tInfo.output_components;
65. pucbuffer = malloc(iRowSize);
66. ptData->iRgbSize= iRowSize * ptData->iHeight;
67. ptData->pucRgbData = malloc(ptData->iRgbSize);
68.
69. /* pucHelp指向ptData->pucRgbData首地址 */
70. pucHelp = ptData->pucRgbData;
71. /* 5.循环调用jpeg_read_scanlines来一行一行地获得解压的数据 */
72. while (ptData->tInfo.output_scanline < ptData->tInfo.output_height)
73. {
74. /* 调用jpeg_read_scanlines得到的时候会存到pucbuffer中 */
75. jpeg_read_scanlines(&ptData->tInfo, &pucbuffer, 1);
76. /* 将数据一行行读到缓冲区中 */
77. memcpy(pucHelp,pucbuffer,iRowSize);
78. pucHelp += iRowSize;
79. }
80. free(pucbuffer);
81. /* 6.完成读取 */
82. jpeg_finish_decompress(&ptData->tInfo);
83. /* 7.释放jpeg_compress_struct结构体 */
84. jpeg_destroy_decompress(&ptData->tInfo);
85. return 0;
86. }
```
-
发表了主题帖:
百问FB显示开发图像处理 - BMP图像处理
# 2 图像处理
前言:所有的图像文件,都是一种二进制格式文件,每一个图像文件,都可以通过解析文件中的每一组二进制数的含义来获得文件中的各种信息,如图像高度,宽度,像素位数等等。只是不同的文件格式所代表的二进制数含义不一样罢了。我们可以通过UltraEdit软件打开图像文件并查看里面的二进制数排列。
## 2.1 BMP图像处理
### 2.1.1 BMP文件格式解析
BMP是一种常见的图像格式,BMP文件可看成由4个部分组成:位图文件头(bitmap-file header)、位图信息头(bitmap-information header)、调色板(color palette)和定义位图的字节阵列。以最简单的24位真彩色BMP文件作例子讲解:
1. 位图文件头(bitmap-file header)
这部分可以理解为是一个结构体,里面的每一个成员都表示一个属性
位数文件头由以下信息组成:
| 名称 | 字节数 | 含义 |
| :---------- | ------ | :----------------------------------------------------------- |
| bfType | 2字节 | 表明它是BMP格式的文件,内容固定为0x42,0x4D,即ASCII字符中的“B”“M” |
| bfSize | 4字节 | BMP文件的大小,单位为字节 |
| bfReserved1 | 2字节 | 保留 |
| bfReserved2 | 2字节 | 保留 |
我们用UltraEdit打开一个BMP文件,可以看到如下信息
![ImageProcess_Image001](http://photos.100ask.net/NewHomeSite/ImageProcess_Image001.png)
这是该BMP文件前32字节的数据,可以看到,前两个字节分别为0x42,0x4D;
接着后面4个字节依次是0x36,0xF9,0x15,0x00。
在BMP格式中,文件的存储方式是小端模式,即如果一个数据需要用几个字节来表示的话,那么,低位数据存在低位地址上,高位数据存在高位地址上。类似的,还有大端模式,即:如果一个数据需要用几个字节来表示的话,那么,低位数据存在高位地址上,高位数据存在低位地址上。
所以0x36,0xF9,0x15,0x00四个数据拼接方法应该是:0x0015F936(在数字中个位即最右边才是最低位),它正好就是这个文件的大小:
![ImageProcess_Image002](http://photos.100ask.net/NewHomeSite/ImageProcess_Image002.png)
紧接着是4个保留位字节,其数据必须为0x00。
最后是4个字节的便宜位,可以看到位图文件头+位图信息头+调色板的大小应该是0x36。
2. 位图信息头(bitmap-information header)
位图信息头也可以理解为是一个结构体,其成员有:
| 名称 | 字节数 | 含义 |
| :-------------- | :----- | :----------------------------------------------------------- |
| biSize | 4 | 整个位图信息头结构体的大小 |
| biWidth | 4 | 图像宽度,单位为像素 |
| biHeight | 4 | 图像高度,单位为像素。 此外,这个数的正负可以判断图像是正向还是倒向的,若为正,则表示是正向;若为负,则表示反向。其实根本不同就是坐标系的建立方法不一样。后面写代码时会讲。 |
| biPlanes | 2 | 颜色平面书,其值总为1 |
| biBitCount | 2 | 即1个像素用多少位的数据来表示,其值可能为1,4,8,16,24,32。我们是以24位真彩色为例子讲解的 |
| biCompression | 4 | 数据的压缩类型 |
| biSizeImage | 4 | 图像数据的大小,单位为字节 |
| biXPelsPerMeter | 4 | 水平分辨率,单位是像素/米 |
| biYPelsPerMeter | 4 | 垂直分辨率,单位是像素/米 |
| biClrUsed | 4 | 调色板中的颜色索引数 |
| biClrImportant | 4 | 说明有对图像有重要影响的颜色索引的数目,若为0,表示都重要 |
对照源文件数据:
![ImageProcess_Image003](http://photos.100ask.net/NewHomeSite/ImageProcess_Image003.png)
0E-11:00000028h = 40,表示这个结构体大小是40字节。
12-15:00000320h = 800,图像宽为800像素。
16-19:00000258h = 600,图像高为600像素,与文件属性一致。这是一个正数,说明图像是正向的,数据是以图像左下角为原点,以水平向右为X轴正方向,以垂直向上为Y轴正方向排列的。若为负,则说明图像是反向的,数据是以图像左上角角为原点,以水平向右为X轴正方向,以垂直向下为Y轴正方向排列的。
1A-1B:0001h, 该值总为1。
1C-1D:0018h = 24, 表示每个像素占24个比特,即24位真彩色
上面这几个信息跟文件属性是一致的:
![ImageProcess_Image004](http://photos.100ask.net/NewHomeSite/ImageProcess_Image004.png)
1E-21:00000000h,BI_RGB, 说明本图像不压缩。
22-25:00000000h,图像的大小,因为使用BI_RGB,所以设置为0。
26-29:00000000h,水平分辨率,缺省。
2A-2D:00000000h,垂直分辨率,缺省。
2E-31:00000000h,对于24位真彩色来说,是没有调色板的,所以为0。
32-35:00000000h,对于24位真彩色来说,是没有调色板的,所以为0。
3. 调色板(color palette)
24位真彩色没有调色板,这里为了简化不赘诉。
4. 定义位图的字节阵列
这一部分就是真正的图像数据了,24位真彩色数据是按按BGR各一字节循环排列而成。
### 2.1.2 代码实现:将BMP文件解析为RGB格式,在LCD上显示
让BMP文件在开发板的LCD上显示出来,有几个需要注意的点:
1. 开发板LCD上的显示格式是RGB格式的,而且有多种表示格式:可能用2字节表示(RGB565格式),可能用3字节表示(RGB888),而原始的24位真彩色BMP文件则是按BGR格式排列的,需要对原始的图像数据进行转化。
2. 在转化过程中,LCD上的显存地址固定是以LCD左上角为首地址,而BMP格式中正向图像是以图片的左下角为数据首地址的。因此在进行数据转化时还需要注意坐标的变换。
代码清单2.1实现了将24位真彩色的BMP图像转化为RGB格式
```c
代码清单2.1
1. /**********************************************************************
2. * 函数名称: IsBmp
3. * 功能描述: 判断该文件是否为BMP文件
4. * 输入参数: ptFileMap - 内含文件信息
5. * 输出参数: 无
6. * 返 回 值: 0 - 是BMP格式, -1 -不是BMP格式
7. ***********************************************************************/
8. int IsBmp(FILE **ppFp, const char *strFileName)
9. {
10. char strCheckHeader[2];
11. *ppFp= fopen(strFileName, "rb+");
12. if (*ppFp== NULL) {
13. return -1;
14. }
15. if (fread(strCheckHeader, 1, 2, *ppFp) != 2)
16. return -1;
17.
18. if (strCheckHeader[0] != 0x42 || strCheckHeader[1] != 0x4d)
19. return -1;
20. else
21. return 0;
22. }
23.
24.
25.
26. /**********************************************************************
27. * 函数名称: MapFile
28. * 功能描述: 使用mmap函数映射一个文件到内存,以后就可以直接通过内存来访问文件
29. * 输入参数: PT_PictureData ptData 内含图像数据
30. * 输出参数: ptData->iFileSize : 文件大小
31. * ptData->pucFileData : 映射内存的首地址
32. * 返 回 值: 0 - 成功其他值 - 失败
33. ***********************************************************************/
34. int MapFile(PT_PictureData ptData)
35. {
36. int iFd;
37. struct stat tStat;
38.
39. /* 打开文件 */
40. iFd = fileno(ptData->ptFp);
41. fstat(iFd, &tStat);
42. ptData->iFileSize= tStat.st_size;
43. ptData->pucFileData= (unsigned char *)mmap(NULL , tStat.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, iFd, 0);
44. if (ptData->pucFileData == (unsigned char *)-1)
45. {
46. printf("mmap error!\n");
47. return -1;
48. }
49. return 0;
50. }
51.
52. /**********************************************************************
53. * 函数名称: DecodeBmp2Rgb
54. * 功能描述:把BMP文件转化为rgb格式
55. * 输入参数: strFileName - 文件名
56. * ptData - 内含图像信息
57. * 返 回 值: 0 - 成功其他值 - 失败
58. * -1 - 文件不是BMP格式
59. * -2 - 不支持的bpp
60. * -3 - 图像缓存区分配失败
61. ***********************************************************************/
62. static int DecodeBmp2Rgb(const char *strFileName, PT_PictureData ptData) {
63. int x,y;
64. int iPos = 0;
65. int iLineWidthAlign;
66. BITMAPFILEHEADER *ptBITMAPFILEHEADER;
67. BITMAPINFOHEADER *ptBITMAPINFOHEADER;
68. unsigned char *aFileHead;
69. unsigned char *pucSrc;
70. unsigned char *pucDest;
71. int iLineBytes;
72.
73. /* 判断该文件是否为BMP格式 */
74. if (IsBmp(&ptData->ptFp, strFileName))
75. return -1;
76.
77. /* 将BMP文件映射到内存空间 */
78. MapFile(ptData);
79.
80.
81. aFileHead = ptData->pucFileData;
82.
83. ptBITMAPFILEHEADER = (BITMAPFILEHEADER *)aFileHead;
84. ptBITMAPINFOHEADER = (BITMAPINFOHEADER *)(aFileHead + sizeof(BITMAPFILEHEADER));
85. /* 获取必要的图像信息 */
86. ptData->iWidth = ptBITMAPINFOHEADER->biWidth;
87. ptData->iHeight = ptBITMAPINFOHEADER->biHeight;
88. ptData->iBpp = ptBITMAPINFOHEADER->biBitCount;
89. iLineBytes = ptData->iWidth*ptData->iBpp/8;//一行数据的字节数
90. ptData->iBmpDataSize= ptData->iHeight * iLineBytes;//整个BMP图像的字节数
91. /*暂时只支持24bpp格式*/
92. if (ptData->iBpp != 24)
93. {
94. printf("iBMPBpp = %d\n", ptData->iBpp);
95. printf("sizeof(BITMAPFILEHEADER) = %d\n", sizeof(BITMAPFILEHEADER));
96. return -2;
97. }
98.
99. /* 分配空间 */
100. ptData->pucBmpData = malloc(ptData->iBmpDataSize);
101. ptData->pucRgbData = malloc(ptData->iBmpDataSize);
102.
103. if (NULL == ptData->pucBmpData||NULL == ptData->pucRgbData)
104. return -2;
105.
106. /* 从bmp文件中读取图像信息,24bpp的BMP图像为BGR格式 */
107. pucDest = ptData->pucBmpData;
108. iLineWidthAlign = (iLineBytes + 3) & ~0x3; /* 向4取整 */
109. pucSrc = aFileHead + ptBITMAPFILEHEADER->bfOffBits;
110.
111. pucSrc = pucSrc + (ptData->iHeight - 1) * iLineWidthAlign;
112.
113. /* 对于bmp文件中的源数据,是以左下角为原点计算坐标的,因此拷贝数据时需要转换坐标 */
114. for (y = 0; y < ptData->iHeight; y++)
115. {
116. memcpy(pucDest, pucSrc, ptData->iWidth*3);
117. pucSrc -= iLineWidthAlign;
118. pucDest += iLineBytes;
119. }
120.
121.
122. /* 将得到的BGR数据转化为RGB数据 */
123. for (y = 0; y < ptData->iHeight; y++){
124. for(x = 0;xiWidth*3;x+=3){
125. ptData->pucRgbData[iPos++] = ptData->pucBmpData[y*ptData->iWidth*3+x+2];
126. ptData->pucRgbData[iPos++] = ptData->pucBmpData[y*ptData->iWidth*3+x+1];
127. ptData->pucRgbData[iPos++] = ptData->pucBmpData[y*ptData->iWidth*3+x+0];
128. }
129. }
130.
131. return 0;
132.
133. }
```
- 2024-11-25
-
发表了主题帖:
百问LCD Framebuffer应用开发 - freetype搭建与使用
## 1.5 搭建freetype相关环境
### 1.5.1 交叉编译freetype,并安装
①解压freetype源文件
```c
tar xjf freetype-2.4.10.tar.bz2
```
②进入解压后的freetype-2.4.10目录
```c
cd freetype-2.4.10
```
③配置freetype-2.4.10
```c
./configure --host=arm-linux-gnueabihf --prefix=/home/book/100ask_imx6ull-sdk/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/arm-linux-gnueabihf/libc/usr/
```
④建个目录,避免后面安装出错提示缺少这个internal目录
```c
mkdir /home/book/100ask_imx6ull-sdk/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/arm-linux-gnueabihf/libc/usr/include/freetype2/freetype/internal -p
```
④编译
```c
make
```
⑤安装
```c
make install
```
⑥移动freetype头文件,避免以后编译总是需要指定头文件路径
```c
mv /home/book/100ask_imx6ull-sdk/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/arm-linux-gnueabihf/libc/usr/include/freetype2/freetype /home/book/100ask_imx6ull-sdk/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/arm-linux-gnueabihf/libc/usr/include/
```
#### 1.5.2 freetype库,头文件移植至开发板
由于100ask开发板已经有freetype相关的库和头文件,因此不需要移植,如果开发板没有freetype库和头文件就需要按以下方法移植
/home/book/100ask_imx6ull-sdk/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/arm-linux-gnueabihf/libc/usr/include/* 复制到开发板的头文件目录中
/home/book/100ask_imx6ull-sdk/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/arm-linux-gnueabihf/libc/usr/lib/*so* 复制到开发板的库文件目录中
注:链接文件需要保持它的链接属性(即加-d选项)。
## 1.6 使用freetype
### 1.5.1 矢量字体引入
点阵显示英文字母,汉字时,大小固定,如果放大会有锯齿出现,为了解决这个问题,引用矢量字体。
矢量字体形成分三步,若干的关键点,数学曲线(贝塞尔曲线),填充颜色组合而成。
①假设A字母的关键点如图中的黄色圈圈,确定关键点。
![FramebufferAPP_Image00010](http://photos.100ask.net/NewHomeSite/FramebufferAPP_Image00010.png)
②用数学曲线将关键点都连接起来,成为封闭的曲线。
![FramebufferAPP_Image00011](http://photos.100ask.net/NewHomeSite/FramebufferAPP_Image00011.png)
③最后把封闭空间填满颜色,就显示出一个A字母。
![FramebufferAPP_Image00012](http://photos.100ask.net/NewHomeSite/FramebufferAPP_Image00012.png)
如果需要放大或者缩小字体,关键点的相对位置是不变的,跟进放大比例放大或缩小,但是相对位置不变,好像分数中的1/2 和 2/4,比例是不变的,但是值却大了,类似这个味道。
### 1.5.2 Freetype理论介绍
开源的Freetype字体引擎库它提供统一的接口来访问多种字体格式文件,从而实现矢量字体显示。我们只需要移植这个字体引擎,调用对应的API接口,提供字体关键点,就可以让freetype库帮我们实现闭合曲线,填充颜色,达到显示矢量字体的目的。
关键点(glyph)存在字体文件中,Windows使用的字体文件在FONTS目录下,扩展名为TTF的都是矢量字库,本次使用实验使用的是新宋字体simsun.ttc。
![FramebufferAPP_Image00013](http://photos.100ask.net/NewHomeSite/FramebufferAPP_Image00013.png)
字体文件结构如上图
Charmaps表示字符映射表,字体文件可能支持哪一些编码,GBK,UNICODE,BIG5还是别的编码,如果字体文件支持该编码,跟进编码,通过charmap,找到对应的glyph,一般而言都支持UNICODE码。
有了以上基础,我们想象一个文字的显示过程
- ①给定一个文字吗‘A’(0x41),‘中’(GBK,UNICODE ,BIG5)可以确定它的编码值;
- ②跟进编码值,从枝头文件中通过charmap找到对应的关键点(glyph);
- ③设置字体大;
- ④用某些函数把关键点(glyph)缩放为我们设置的字体大小;
- ⑤转换为位图点阵
- ⑥在LCD上显示出来
![FramebufferAPP_Image00014](http://photos.100ask.net/NewHomeSite/FramebufferAPP_Image00014.png)
如上图,参照step1,step2,step3里的内容,可以学习如何使用freetype库,大致总结下,为如下步骤。
①初始化:FT_InitFreetype
②加载(打开)字体Face:FT_New_Face
③设置字体大小:FT_Set_Char_Sizes 或 FT_Set_Pixel_Sizes
④选择charmap:FT_Select_Charmap
⑤根据编码值charcode找到glyph : glyph_index = FT_Get_Char_Index(face,charcode)
⑥根据glyph_index取出glyph:FT_Load_Glyph(face,glyph_index)
⑦转为位图:FT_Render_Glyph
⑧移动或旋转:FT_Set_Transform
### 1.5.2 在LCD上显示一个矢量字体
![FramebufferAPP_Image00015](http://photos.100ask.net/NewHomeSite/FramebufferAPP_Image00015.png)
我们可以参考上图位置的c程序,编写程序。
①初始化freetype库
程序文件:freetype_show_font.c
```c
4872 error = FT_Init_FreeType( &library ); /* initialize library */
```
②用freetype库中的FT_New_Face函数创建一个face字体文件对象,保存在&face中
程序文件:freetype_show_font.c
```c
4875 error = FT_New_Face( library, argv[1], 0, &face ); /* create face object */
```
③提取face对象中的glyph,即关键点集
程序文件:freetype_show_font.c
```c
4877 slot = face->glyph;
```
④设置像素点大小,24*24
程序文件:freetype_show_font.c
```c
4879 FT_Set_Pixel_Sizes(face, 24, 0);
```
⑤确定坐标
目前我们前面所用的都是LCD的坐标系对应的x与y坐标,然后在freetype上却是使用的笛卡尔坐标系,因此我们还需要转换x与y坐标。
![FramebufferAPP_Image00017](http://photos.100ask.net/NewHomeSite/FramebufferAPP_Image00017.png)
我们将要显示的是‘繁’字,根据上图可知,先计算在lcd坐标系的情况下‘繁’字
的左下角的x坐标与y坐标,因为在笛卡尔坐标中左下角为字符的原点,‘A’是的左上角为整个屏幕的中心点,即(xres/2,yres/2)。
- lcd_x = var.xres/2 + 8 + 16;lcd_y = var.yres/2 + 16
- 则笛卡尔座标系:x = lcd_x = var.xres/2 + 8 + 16 ; y = var.yres - lcd_y = var.yres/2 – 16
- 单位是1/64像素,所以需要乘以64
程序文件:freetype_show_font.c
```c
4888 pen.x = (var.xres/2 + 8 + 16) * 64;
4889 pen.y = (var.yres/2 - 16) * 64;
4890
4891 /* set transformation */
4892 FT_Set_Transform( face, 0, &pen);
```
⑥找到glyph的位置,然后取出,并转换为位图
```c
4895 error = FT_Load_Char( face, chinese_str[0], FT_LOAD_RENDER );
4896 if (error)
4897 {
4898 printf("FT_Load_Char error\n");
4899 return -1;
4900 }
```
![FramebufferAPP_Image00018](http://photos.100ask.net/NewHomeSite/FramebufferAPP_Image00018.png)
FT_Load_Char函数调用替代了上图这3步。
最后把转换出来的位图打印出来,也是参考example1.c编写
程序文件:freetype_show_font.c
```c
4902 draw_bitmap( &slot->bitmap,
4903 slot->bitmap_left,
4904 var.yres - slot->bitmap_top);
```
程序文件:example1.c
![FramebufferAPP_Image00019](http://photos.100ask.net/NewHomeSite/FramebufferAPP_Image00019.png)
修改上图3处位置
- Width宽度:因为在LCD上显示,宽度自然就是x方向的像素点数,var.xres;
- Height高度:因为在LCD上显示,高度自然就是y方向的像素点数,var.yres;
- 用点阵实验中的的描点函数lcd_put_pixel替代image数组
lcd_put_pixel(i, j, bitmap->buffer[q * bitmap->width + p]);
⑥编译C程序文件freetype_show_font.c
编译命令:arm-linux-gnueabihf-gcc -finput-charset=GBK -fexec-charset=GBK -o freetype_show_font freetype_show_font.c -lfreetype -lm
⑦将编译好的freetype_show_font的文件与simsun.ttc字体文件拷贝至开发板,simsun.ttc字体文件放在freetype_show_font执行文件的上一层目录下,执行以下命令。
执行命令:./freetype_show_font ../simsun.ttc
如果实验成功,我们将看到屏幕中间会比之前实验多出一个蓝色的‘繁’字。
### 1.5.3 在LCD上令矢量字体旋转某个角度
在实现显示一个矢量字体后,我们可以添加让该字旋转某个角度的功能。
我们根据输入的第二个参数,判断其旋转角度,主要代码还是参照example1.c
![FramebufferAPP_Image00020](http://photos.100ask.net/NewHomeSite/FramebufferAPP_Image00020.png)
根据上图,增加旋转角度功能,旋转的角度由执行命令的第二个参数指定。
程序文件:freetype_show_font_angle.c
```c
/* use 25 degrees */
4894 angle = ( 1.0 * strtoul(argv[2], NULL, 0) / 360 ) * 3.14159 * 2;
4895 /* set up matrix */
4896 matrix.xx = (FT_Fixed)( cos( angle ) * 0x10000L );
4897 matrix.xy = (FT_Fixed)(-sin( angle ) * 0x10000L );
4898 matrix.yx = (FT_Fixed)( sin( angle ) * 0x10000L );
4899 matrix.yy = (FT_Fixed)( cos( angle ) * 0x10000L );
4900
4901 /* set transformation */
4902 FT_Set_Transform( face, &matrix, &pen);
```
最后编译,在开发板上运行
编译命令如下:
编译命令:arm-linux-gnueabihf-gcc -finput-charset=GBK -fexec-charset=GBK -o freetype_show_font_angle freetype_show_font_angle.c -lfreetype -lm
编译出的文件名为freetype_show_font_angle,将文件拷贝至开发板
在含有该文件的目录下执行以下命令,以下命令正确执行前提是执行文件freetype_show_font在此目录,而且字体文件simsun.ttc,在上一级目录:
执行命令:./freetype_show_font_angle ../simsun.ttc 90
如果实验成功,我们将看到屏幕中间的蓝色‘繁’字,旋转了90度。
-
发表了主题帖:
百问LCD Framebuffer应用开发 - 在LCD上使用点阵写字
本帖最后由 aleksib 于 2024-11-25 10:52 编辑
## 1.4 在LCD上使用点阵写字
### 1.4.1 在LCD上显示英文字母
①找出英文字母在点阵数组中的地址,c所代表的是一个英文字母(ASCII值)。
程序文件:show_ascii.c
```c
4693 unsigned char *dots = (unsigned char *)&fontdata_8x16[c*16];
```
②根据获得的英文字母点阵,每一位依次判断,描点,‘1’表示白色,‘0’表示黑色。
![FramebufferAPP_Image00007](http://photos.100ask.net/NewHomeSite/FramebufferAPP_Image00007.png)
根据上图,我们分析下如何利用点阵在LCD上显示一个英文字母,因为有十六行,所以首先要有一个循环16次的大循环,然后每一行里有8位,那么在每一个大循环里也需要一个循环8次的小循环,小循环里的判断单行的描点情况,如果是1,就填充白色,如果是0就填充黑色,如此一来,就可以显示出黑色底,白色轮廓的英文字母。
程序文件:show_ascii.c
```c
4697 for (i = 0; i < 16; i++)
4698 {
4699 byte = dots;
4700 for (b = 7; b >= 0; b--)
4701 {
4702 if (byte & (1
- 2024-11-22
-
发表了主题帖:
全志T113双核异构处理器的使用基于Tina Linux5.0——RTOS系统定制开发
**8、RTOS系统定制开发**
此处以在rtos/components/aw目录下创建一个简单的软件包为例,帮助客户了解RTOS环境,为RTOS系统定制开发提供基础。
RTOS环境下的软件包主要由三部分组成,源文件,Makefile,Kconfig,如下:
```
hello_world
├──hello_world.c
├──Kconfig
└──Makefile
```
其中Makefile指定该模块的编译规则,Kconfig则指定该模块的编译配置,从而达到功能可裁剪,镜像文件大小可灵活配置的目的。
示例软件包实现简单的打印字符串“hello,world!”的功能,如下:
```
#include#include<
intcmd_hello_world(void)
{
printf("hello,world!\n");return0;
}
FINSH_FUNCTION_EXPORT_CMD(cmd_hello_world,hello_world,helpforhello_world)
/*这一行表示将函数cmd_hello_world封装成一个名为hello_world的命令,在RV控制台输入hello_world,即运行cmd_hello_world函数,最后一个参数为帮助信息。此外,头文件需要加上hal_cmd.h.*/
```
Kconfig的写法如下:
```
configCOMPONENT_HELLO_WORLD
bool"HELLO_WORLDSupport"defaultn
defaultn
help
supporthello_worldcommand.
```
Kconfile的写法如下:
```
obj-$(CONFIGCOMPONENTHELLOWORLD)+=hello_world.o
```
此外,需要在上层目录的Kconfile以及Kconfig中加上该软件包,如下:
```
上层Makefile
obj-$(CONFIGCOMPONENT_HELLO_WORLD)+=helloworld
上层Kconfig
sourcecomponents/aw/hello_world/Kconfig
```
开发完成后,运行mrtos_menuconfig,选中该模块对应的CONFIG后退出,然后mrtos进行编译,这样新增的软件包便可以编译到RTOS镜像文件中了。
将RTOS镜像文件移动到TinaLinux环境下重新编译打包,新生成的固件打包烧录进开发板后,启动RV核,在RV核控制台输入help命令。
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1731986106891-ca1b9cd8-13d0-4c04-8044-b0484894ee2c-image.png)
可以看到有新增的hello_world命令,控制台输入hello_world命令,控制台输出如下:
```
hello,world!
```
RTOS下一个简单的软件包便创建成功了。
-
发表了主题帖:
全志T113双核异构处理器的使用基于Tina Linux5.0——RTOS新增方案
**7、RTOS新增方案**
此处以在t113_s3p_c906 芯片下创建一个example_demo方案为例,帮助客户快速创建定制方案。
第一步:进入rtos/board/t113_s3p_c906目录,创建 example_demo 文件夹(从evb1_auto方案拷贝过来)
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1731985181313-7fc09581-42af-4d90-adfd-6ee0f6a87990-image.png)
第二步:进入rtos/lichee/rtos/projects/t113_s3p_c906目录,创建example_demo文件夹(从evb1_auto方案拷贝过来)
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1731985192308-be496c61-e43e-4d69-839c-dd4557ef6f1e-image.png)
第三步:修改rtos/lichee/rtos/projects/Kconfig,新增example_demo配置项(从evb1_auto配置项拷贝过来)
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1731985204030-45d5e227-21c1-48cd-9022-0c7be628398a-image.png)
第四步:修改rtos/lichee/rtos/projects/t113_s3p_c906/Makefile,关联了example_demo方案编译
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1731985216073-228bcb7d-f89e-48d5-ae4f-7217e89b5216-image.png)
第五步:进入rtos目录,重新配置方案选择,这时候我们发现会多了t113_s3p_c906_example_demo 方案选择,我们重新选择t113_s3p_c906_example_demo 方案
```
source envsetup.sh
lunch_rtos
You're building on Linux
Lunch menu... pick a combo:
1. t113_i_c906_evb1_auto
2. t113_s3p_c906_evb1_auto
3. t113_s3p_c906_evb1_auto_fastboot
4. t113_s3p_c906_evb1_auto_fastboot_video
5. t113_s3p_c906_evb1_auto_non_os
6. t113_s3p_c906_example_demo
7. t113_s4_c906_evb1_auto
8. t113_s4_c906_evb1_auto_fastboot_video
9. t113_s4p_c906_evb1_auto
Which would you like? [Default t113_s3p_c906_example_demo]:
```
第六步:打开配置defconfig文件,选择正确方案配置项
```
mrtos_menuconfig
```
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1731985268822-ef170378-33a5-48b1-af90-cf39c9298a23-image.png)
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1731985275109-fb443d12-8369-475d-b9e7-2afecd27e915-image.png)
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1731985282049-3be9ef3b-20e3-432c-81c9-aa1365d77b24-image.png)
第七步:重新编译
```
mrtos clean
mrtos
```
- 2024-11-20
-
发表了主题帖:
全志T113双核异构处理器的使用基于Tina Linux5.0——异构双核通信验证
**6、双核通信验证**
**6.1、C906小核创建通讯节点**
在C906小核串口终端建立两个通讯节点用于监听数据,输入eptdev_bind test 2
```
cpu0 >eptdev_bind test 2
```
查看监听节点,输入rpmsg_list_listen
```
cpu0 >rpmsg_list_listen
name listen alive
test 2 0
console 100 0
```
**6.2、大核创建通讯节点**
在Tina Linux下也创建两个通讯监听节点,输入以下两个命令
```
echo test > /sys/class/rpmsg/rpmsg_ctrl-c906_rproc@0/open
echo test > /sys/class/rpmsg/rpmsg_ctrl-c906_rproc@0/open
```
输入后,如下所示:
```
root@TinaLinux:/sys/class/rpmsg# echo test > /sys/class/rpmsg/rpmsg_ctrl-c906_rp
roc@0/open
[ 946.762321] virtio_rpmsg_bus virtio0: creating channel sunxi,rpmsg_client addr 0x403
root@TinaLinux:/sys/class/rpmsg# echo test > /sys/class/rpmsg/rpmsg_ctrl-c906_rp
roc@0/open
[ 951.246499] virtio_rpmsg_bus virtio0: creating channel sunxi,rpmsg_client addr 0x404
root@TinaLinux:/sys/class/rpmsg#
```
在大核TIna Linux中也创建了两个监听节点,输入ls /dev/rpmsg*查看节点信息
```
root@TinaLinux:~# ls /dev/rpmsg*
/dev/rpmsg0 /dev/rpmsg1 /dev/rpmsg_ctrl-c906_rproc@0
```
创建完成后,可以在C906小核终端中查看自动输出的信息
```
cpu0 >ctrldev: Rx 44 Bytes
client: Rx 8 Bytes
rpmsg0: binding
send 0x13131411 to rpmsg0
create rpmsg0 client success
ctrldev: Rx 44 Bytes
client: Rx 8 Bytes
rpmsg1: binding
send 0x13131411 to rpmsg1
create rpmsg1 client success
```
**6.3、大核传输至C906小核**
在Tina Linux下输入
```
echo "hello Embfly SBC-T113S Board" > /dev/rpmsg0
echo "hello Tina Linux" > /dev/rpmsg1
```
将Linux Message 0信息通过创建的监听节点传输到C906小核,例如:
```
root@TinaLinux:~# echo "hello Embfly SBC-T113S Board" > /dev/rpmsg0
root@TinaLinux:~# echo "hello Tina Linux" > /dev/rpmsg1
```
输入后,打开C906串口终端可以发现,大核传输过来的信息。
```
rpmsg0: Rx 22 Bytes
Data:hello Embfly SBC-T113S Board
rpmsg1: Rx 17 Bytes
Data:hello Tina Linux
```
**6.4、C906小核传输至大核**
在小核端需要使用命令 eptdev_send 用法 eptdev_send ,这里的id号从0开始,我们设置有两个通信节点,所以id号分别为0和1。
在小核的串口终端输入以下命令:
```
eptdev_send 0 "hello C906"
eptdev_send 1 "hello C906"
```
例如:
```
cpu0 >eptdev_send 0 "hello C906"
will send hello C906 to rpmsg0
cpu0 >eptdev_send 1 "hello C906"
will send hello C906 to rpmsg1
```
输入完成后,小核会将信息分别传入rpmsg0和rpmsg1两个通讯节点。可以在大核Tina Linux端输入
```
cat /dev/rpmsg0
cat /dev/rpmsg1
```
可查看从C906小核传输过来的信息。例如:
```
root@TinaLinux:~# cat /dev/rpmsg0
hello C906
^C
root@TinaLinux:~# cat /dev/rpmsg1
hello C906
^C
```
按下Crtl+C结束监听前持续监听该节点。
您可以在小核端多次传输信息到该节点,该节点支持持续接受小核传输的信息,例如:
在C906小核,多次传输信息到监听节点rpmsg0
```
cpu0 >eptdev_send 0 "hello C906 "
will send hello C906 to rpmsg0
cpu0 >eptdev_send 0 "hello C906 "
will send hello C906 to rpmsg0
cpu0 >eptdev_send 0 "hello C906 "
will send hello C906 to rpmsg0
cpu0 >eptdev_send 0 "hello C906 "
will send hello C906 to rpmsg0
cpu0 >eptdev_send 0 "hello C906 "
will send hello C906 to rpmsg0
cpu0 >eptdev_send 0 "hello C906 "
will send hello C906 to rpmsg0
cpu0 >eptdev_send 0 "hello C906 "
will send hello C906 to rpmsg0
```
在大核端则会一直接收到小核传输过来的信息
```
root@TinaLinux:~# cat /dev/rpmsg0
hello C906 hello C906 hello C906 hello C906 hello C906 hello C906 hello C906
```
**6.5、关闭相互通讯**
要关闭通信,只要在大核Tina Linux端操作节点即可。输入以下命令,echo 给到rpmsg的控制关闭节点即可。
```
echo 0 > /sys/class/rpmsg/rpmsg_ctrl-c906_rproc@0/close
echo 1 > /sys/class/rpmsg/rpmsg_ctrl-c906_rproc@0/close
```
例如:
```
root@TinaLinux:~# echo 0 > /sys/class/rpmsg/rpmsg_ctrl-c906_rproc@0/close
[ 6783.156899] virtio_rpmsg_bus virtio0: destroying channel sunxi,rpmsg_client addr 0x403
root@TinaLinux:~# echo 1 > /sys/class/rpmsg/rpmsg_ctrl-c906_rproc@0/close
root@TinaLinux:~# [ 6784.224740] virtio_rpmsg_bus virtio0: destroying channel sunxi,rpmsg_client addr 0x404
```
此时C906小核端也会自动关闭通信节点,自动输出以下信息
```
send 0x13131411 to rpmsg0
rpmsg0: unbinding
ctrldev: Rx 44 Bytes
send 0x13131411 to rpmsg1
rpmsg1: unbinding
```
-
发表了主题帖:
全志T113双核异构处理器的使用基于Tina Linux5.0——异构双核通信的具体实现
**5、TinaLinux异构双核通信的具体实现:**
本章节以SBC-T113S4主板的TinaLinux为例,介绍异构双核通信的实现。该方法也同样适用于T113i平台。
本章节主要涉及到Tina Linux内核的配置、Tina Linux文件系统(openwrt)的配置、Freertos的配置。其中Tina Linux内核的配置包括设备树的配置及相关内核驱动及协议的配置;Tina Linux文件系统(openwrt)的配置包括异构双核通信测试程序和小核C906终端的配置;Freertos的配置包括通信协议的配置。
**5.1、TinaLinux的配置**
**5.1.1、Tina内核设备树配置**
在Tina根目录下,进入设备树目录(根据不同的处理器,进入不同的处理器目录),如下演示的是以SBC-T113S主板为例,该主板的主处理器是T113-S4,其配置文件都放在device/config/chips/t113_s4/configs/sbc_t113s4_nand/的目录下,内核的设备树则放在device/config/chips/t113_s4/configs/sbc_t113s4_nand/linux-5.4目录:
```
xxx@xxx:~/workspaces/t113_tina5.0$ cd device/config/chips/t113_s4/configs/sbc_t113s4_nand/
```
**编辑设备树**
```
xxx@xxx:~/workspaces/t113_tina5.0/device/config/chips t113_s4/configs/sbc_t113s4_nand/linux-5.4 $ vi board.dts
```
在设备树文件中找到C906相关的设备树节点,设备树默认设置为:
```
reserved-memory {
#address-cells = ;
#size-cells = ;
ranges;
/* c906 */
c906_ddr: c906_ddr@42300000 {
reg = ;
no-map;
};
/*
* The name should be "vdev%dbuffer".
* Its size should be not less than
* RPMSG_BUF_SIZE * (num of buffers in a vring) * 2
* = 512 * (num of buffers in a vring) * 2
*/
rv_vdev0buffer: vdev0buffer@42900000 {
compatible = "shared-dma-pool";
reg = ;
no-map;
};
/*
* The name should be "vdev%dvring%d".
* The size of each should be not less than
* PAGE_ALIGN(vring_size(num, align))
* = PAGE_ALIGN(16 * num + 6 + 2 * num + (pads for align) + 6 + 8 * num)
*
* (Please refer to the vring layout in include/uapi/linux/virtio_ring.h)
*/
rv_vdev0vring0: vdev0vring0@42940000 {
reg = ;
no-map;
};
rv_vdev0vring1: vdev0vring1@42942000 {
reg = ;
no-map;
};
/* dsp0 */
dsp0ddr: dsp0ddr@42000000 {
reg = ;
no-map;
};
dsp0_rpbuf_reserved: dsp0_rpbuf@42244000 {
compatible = "shared-dma-pool";
no-map;
reg = ;
};
/*
* The name should be "vdev%dbuffer".
* Its size should be not less than
* RPMSG_BUF_SIZE * (num of buffers in a vring) * 2
* = 512 * (num of buffers in a vring) * 2
*/
vdev0buffer: vdev0buffer@42200000 {
compatible = "shared-dma-pool";
reg = ;
no-map;
};
/*
* The name should be "vdev%dvring%d".
* The size of each should be not less than
* PAGE_ALIGN(vring_size(num, align))
* = PAGE_ALIGN(16 * num + 6 + 2 * num + (pads for align) + 6 + 8 * num)
*
* (Please refer to the vring layout in include/uapi/linux/virtio_ring.h)
*/
vdev0vring0: vdev0vring0@42240000 {
reg = ;
no-map;
};
vdev0vring1: vdev0vring1@42242000 {
reg = ;
no-map;
};
/*
* dsp ram addr
*/
dsp0dram: dsp0dram@400000 {
reg = ;
no-map;
};
dsp0iram0: dsp0iram0@420000 {
reg = ;
no-map;
};
dsp0iram1: dsp0iram1@440000 {
reg = ;
no-map;
};
};
mailbox_heartbeat: mailbox_heartbeat@0 {
compatible = "mailbox-heartbeat";
rproc-np = ;
mboxes = , ;
mbox-names = "tx", "rx";
status = "okay";
};
dsp0_rproc: dsp_rproc@0 {
compatible = "allwinner,hifi4-rproc", "simple-bus";
clock-frequency = ;
clocks = , , , ;
clock-names = "pll", "mod", "cfg", "ahbs";
resets = , , , ;
reset-names = "mod-rst", "cfg-rst", "dbg-rst", "msg-rst";
reg = ,
;
reg-names = "sram-for-cpux", "hifi4-cfg";
mboxes = ;
mbox-names = "arm-kick";
memory-region = , , , ,
, , ;
memory-mappings =
/* < DA len PA > */
/* local SRAM via external bus */
< 0x28000 0x20000 0x28000 >,
/* local SRAM via internal bus */
< 0x400000 0x10000 0x400000 >,
< 0x420000 0x8000 0x420000 >,
< 0x440000 0x8000 0x440000 >,
/* DDR front 256MB */
< 0x10000000 0x10000000 0x40000000 >,
/* local SRAM via internal bus */
< 0x20028000 0x10000 0x400000 >,
< 0x20038000 0x8000 0x420000 >,
< 0x20040000 0x8000 0x440000 >,
/* DDR front 256MB */
< 0x30000000 0x10000000 0x40000000 >,
/* DDR front 1GB */
< 0x40000000 0x40000000 0x40000000 >,
/* DDR front 1GB */
< 0x80000000 0x40000000 0x40000000 >,
/* DDR front 1GB */
< 0xC0000000 0x40000000 0x40000000 >;
id = ;
status = "okay";
};
rpbuf_controller0: rpbuf_controller@0 {
compatible = "allwinner,rpbuf-controller";
remoteproc = ;
ctrl_id = ; /* index of /dev/rpbuf_ctrl */
//iommus = ;
memory-region = ;
status = "okay";
};
rpbuf_sample: rpbuf_sample@0 {
compatible = "allwinner,rpbuf-sample";
rpbuf = ;
status = "okay";
};
c906_rproc: c906_rproc@0 {
compatible = "allwinner,c906-rproc";
clock-frequency = ;
clocks = , , , , ;
clock-names = "pll", "mod", "cfg", "riscv-rst", "riscv-gate";
resets = , ;
reset-names = "cfg-rst", "msg-rst";
memory-region = , , , ;
reg = ;
reg-names = "c906-cfg";
mboxes = ;
mbox-names = "arm-kick";
memory-mappings =
/* DA len PA */
/* DDR for c906 */
< 0x40000000 0x10000000 0x40000000 >;
firmware-name = "amp_rv0.bin";
status = "okay";
};
```
SBC-T113S主板暂时使用uart3打印C906小核的打印信息(可以分配其他串口作为C906的终端,只要大核和小核的分配不冲突即可),为防止Tina内核抢占uart3,所以务必禁用uart3节点。
```
&uart3 {
pinctrl-names = "default", "sleep";
pinctrl-0 = ;
pinctrl-1 = ;
status = "disabled";
};
```
**5.1.2、Tina内核配置**
在Tina根目录下,执行make kernel_menuconfig,例如:
```
xxx@xxx:~/workspaces/t113_tina5.0$ make kernel_menuconfig
```
* **使能硬件支持**
进入内核配置界面后,进入Device Drivers 目录,选中Mailbox Hardware Support:
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1731979568775-9140a6ef-b546-41b4-968d-4766c0248be4-image.png)
选中后进入Mailbox Hardware Support选项中,选中Allwinner Mailbox support:
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1731979583564-459fda62-69d7-4b3f-999c-92af0dddccc8-image.png)
* **使能RPMsg驱动**
进入如下目录中
→ Device Drivers
→ Rpmsg drivers
选中如下配置
allwinnertech rpmsg hearbeat driver
allwinner rpmsg tty driver
sunxi rpmsg ctrl driver
Virtio RPMSG bus driver
选中完成后如下图所示:
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1731979619834-5fe56d87-3005-4552-aa3b-5398d3390679-image.png)
* **使能RPBuf驱动**
进入如下目录中
→ Device Drivers
→ Rpbuf drivers
-*- Rpbuf device interface
Rpmsg-based Rpbuf service driver
Allwinner Rpbuf controller driver
Allwinner Rpbuf sample driver
注:
1)Allwinner Rpbuf sample driver只是内核层的一个demo程序,可以不选。
2)如果异构双核不进行大数据传输,RPBuf驱动可以不选。
选中完成后如下图所示:
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1731979662300-8810021b-226a-4785-b61f-4fc582b3cef5-image.png)
* **使能共享内存驱动**
进入如下目录中
→ Device Drivers
→ Remoteproc drivers
选中如下配置
SUNXI remote processor support --->
Allwinner remoteproc support
Allwinner remoteproc hifi4 boot //控制hifi4 dsp小核
Allwinner remoteproc c906 boot ////控制c906小核
如下图所示:
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1731979694675-07da5097-ed02-4f1e-a62f-b09002e00414-image.png)
修改完成后,保存内核配置并退出。
**5.1.3、Tina文件系统配置(Openwrt)**
配置中增加了amp_shell,这个是C906的控制台。另外也增加了rpbuf和rpmsg的演示程序,便于后续测试验证。
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1731979735077-8a7bfefd-5684-48dc-8f8b-bd56a7c69c54-image.png)
**5.1.4、编译Tina新固件**
在Tina根目录下,输入mkernel指令编译刚刚选中的内核驱动,编译完成后,输入pack,打包生成新镜像(此时RTOS的固件并没有进行更新),例如:
```
xxx@xxx:~/workspaces/t113_tina5.0$ mkernel
...
xxx@xxx:~/workspaces/t113_tina5.0$ pack
...
```
如果需要将RTOS更新一并打包到固件,只要执行make –j32(32表示计算机处理器的线程数,根据具体计算机而定),编译结束后执行pack指令将生成的t113_s4_linux_sbc_t113s4_xxx.img(以SBC-T113S4主板为例)文件拷贝到Windows主机端,此时Tina的固件里面已经包含了新配置的RTOS。
**5.2、C906 FreeRTOS内核配置及应用**
麻雀虽小,一应俱全!FreeRTOS是一个实时的微型操作系统,它和大多数操作系统都一样,都具备内核和文件系统两个部分,内核重点是任务调度和文件系统管理等,文件系统则集成部分现成的指令和相关应用库并方便启动应用(APP)。另外,FreeRTOS还可以配置终端,刚刚说的指令,就是在终端里面执行的。有了调试终端给FreeRTOS应用调试带来了很大的方便。以下将如何配置FreeRTOS终端、如何配置FreeRTOS驱动组件、如何配置FreeRTOS异构通信演示程序等进行说明。
**5.2.1、修改C906链接脚本**
C906的FreeRTOS是有运行地址的,该运行地址在Tina内核的DTS中有明确,在RTOS对应的项目中也必须一致。以t113_s4_c906_evb1_auto项目为例,这里说的项目该项目涉及的相关代码核配置信息在SDK所在目录/rtos/lichee/rtos/projects/t113_s4_c906/evb1_auto中,该目录中有如下文件:
```
xxx@xxx:~/workspace/t113_tina5.0/rtos/lichee/rtos/projects/t113_s4_c906/evb1_auto$ tree -l
.
├── defconfig
├── defconfig_org
├── freertos.lds.S
├── Kconfig
├── Makefile
└── src
├── alsa_config.c
├── assert.c
├── card_default.c
├── FreeRTOSConfig.h
├── hooks.c
└── main.c
```
找到freertos.lds.S文件,该文件保存有C906小核的链接信息。
修改freertos.lds.S,找到MEMORY节点,确认起始地址为0x42300000,长度为0x00600000。此参数需要和Tina设备树中的C906内存参数一致,查看kernel.lds 中MEMORY节点参数为:
```
/* Linker script to configure memory regions. */
MEMORY
{
RAM (rwx) : ORIGIN = CONFIG_ARCH_START_ADDRESS, LENGTH = CONFIG_ARCH_MEM_LENGTH
}
```
CONFIG_ARCH_START_ADDRESS和CONFIG_ARCH_MEM_LENGTH这两个参数在defconfig(和freertos.lds.S同一目录)中,查看defconfig内容如下:
```
#
# Architecture Options
#
# CONFIG_ARCH_ARM is not set
CONFIG_ARCH_RISCV=y
CONFIG_ARCH_START_ADDRESS=0x42300000
CONFIG_ARCH_MEM_LENGTH=0x600000
CONFIG_LITTLE_ENDIAN=y
CONFIG_BITS_PER_LONG=64
# CONFIG_CACHE_ALIGN_CHECK is not set
CONFIG_TOOLCHAIN_FLOAT_HARD=y
CONFIG_PANIC_CLI=y
CONFIG_PANIC_CLI_PWD=y
CONFIG_IMG_VERSION_MESSAGE=y
```
确认和Tina Linux内核的dts配置一致。
Tina Linux内核的dts的配置如下:
```
/* c906 */
c906_ddr: c906_ddr@42300000 {
reg = ;
no-map;
};
```
**5.2.2、RTOS终端使用uart3**
在RTOS定制中,修改引脚分配也是通过修改sys_config.fex来完成的,以t113_s4_c906_evb1_auto项目为例,该文件在在SDK所在目录/rtos/board/t113_s4_c906/evb1_auto/configs目录下。
通过查询数据手册,查看引脚复用功能,假如我们使用PE8和PE9作为uart3作为终端串口:
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1731979924630-51d97141-803f-4c54-8324-2a9a0fa12fb8-image.png)
修改uart3节点作为终端串口:
```
[uart_para]
uart_debug_port = 3
uart_debug_tx = port:PE08
uart_debug_rx = port:PE09
```
**5.3、修改RTOS相关组件的配置并编译**
进入SDK所在目录/rtos/,执行source envsetup.sh,单独开起RTOS的编译环境。
然后按照3.2.2章节进入RTOS方案的选择和配置,执行配置指令mrtos_menuconfig,
进入后选中如下配置:
```
→ Drivers Options
→ soc related device drviers
→ UART Decives
[*] support uart3 device
[3] cli uart port number
```
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1731979983847-3eae4129-7af5-49ac-801b-03b6b807a05a-image.png)
进入如下目录,选中[*] enable sysconfig,启用读取解析 sys_config.fex 功能
```
→ Drivers Options
→ soc related device drivers
→ Common Option
[*] enable sysconfig
```
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1731980011889-c75c4609-f137-4a3e-a944-1be021dccca6-image.png)
分别进入如下界面,选择对应的rpmsg/rpbuf/messagebox驱动,这些驱动与Tina Linux中的驱动对应。
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1731980023832-22f32ad2-0dd7-4991-99ac-c48d8f1d31e9-image.png)
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1731980029764-720ad873-a333-457f-be05-b4754dba8a71-image.png)
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1731980040628-c593e5d8-7f5e-4409-8fad-253e08086cc9-image.png)
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1731980046634-c6cb74ef-097e-48c1-9968-bdc9f25fb65b-image.png)
进入如下界面,选中Freertos终端相关配置
[*] Multi Console Support
[*] Uart Multi Console Support
[ ] Uart Multi Console As Main Console
[*] Rpmsg Multi Console Support
[*] Rpmsg Multi Console Enable Cache Cmd
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1731980088972-0285106f-cb98-47ab-9887-f331d1814fe5-image.png)
注:Rpmsg Multi Console Support这个配置是和Tina Linux中的amp_shell控制台对应。
本次演示主要为了演示异构双核通信,配置基本完成。如果用户有其他应用需求,用户可以根据自己的需求进行配置。保存配置退出后,接下来进行编译。
```
ping@embedall:~/workspace/t113_tina5.0/t113_tinasdk5.0-v1/rtos$ m
build rtos ...
===There isn't tina environment.===
Note: will use shell command origin rather than the functon.
Dark Builder
Version (1.6.0 - BiCEP2 (Gravitational Waves))
*[CC] [SCRIPT] build/t113_s4_c906_evb1_auto/img/sys_config.fex
[LDS] [Linker] projects/t113_s4_c906/evb1_auto/freertos.lds
CC build/t113_s4_c906_evb1_auto/arch/common/common.o
CC build/t113_s4_c906_evb1_auto/arch/risc-v/common/exception.o
CC build/t113_s4_c906_evb1_auto/arch/risc-v/common/clocksource.o
AS build/t113_s4_c906_evb1_auto/arch/common/sys_config.o
CC build/t113_s4_c906_evb1_auto/arch/common/version.o
CC build/t113_s4_c906_evb1_auto/arch/risc-v/sun8iw20p1/sun8i.o
AS build/t113_s4_c906_evb1_auto/arch/risc-v/c906/head_s.o
CC build/t113_s4_c906_evb1_auto/arch/risc-v/c906/plic.o
CC build/t113_s4_c906_evb1_auto/arch/risc-v/c906/cache.o
CC build/t113_s4_c906_evb1_auto/arch/risc-v/c906/spinlock.o
CC build/t113_s4_c906_evb1_auto/arch/risc-v/c906/clic.o
CC build/t113_s4_c906_evb1_auto/arch/risc-v/c906/platform/platform_sun8iw20.o
CC build/t113_s4_c906_evb1_auto/arch/risc-v/c906/interrupt.o
CC build/t113_s4_c906_evb1_auto/components/thirdparty/console/FreeRTOS_CLI.o
CC build/t113_s4_c906_evb1_auto/components/common/thirdparty/md5/md5.o
……
LD build/t113_s4_c906_evb1_auto/components/common/thirdparty/openamp/open-amp/obj-in.o
LD build/t113_s4_c906_evb1_auto/components/common/thirdparty/openamp/obj-in.o
LD build/t113_s4_c906_evb1_auto/components/common/thirdparty/obj-in.o
LD build/t113_s4_c906_evb1_auto/components/obj-in.o
LD build/t113_s4_c906_evb1_auto/drivers/rtos-hal/hal/source/ccmu/sunxi-ng/obj-in.o
LD build/t113_s4_c906_evb1_auto/drivers/rtos-hal/hal/source/ccmu/obj-in.o
LD build/t113_s4_c906_evb1_auto/drivers/rtos-hal/hal/source/twi/obj-in.o
LD build/t113_s4_c906_evb1_auto/drivers/rtos-hal/hal/source/obj-in.o
LD build/t113_s4_c906_evb1_auto/drivers/rtos-hal/hal/obj-in.o
LD build/t113_s4_c906_evb1_auto/drivers/obj-in.o
LD build/t113_s4_c906_evb1_auto/kernel/FreeRTOS-orig/obj-in.o
LD build/t113_s4_c906_evb1_auto/kernel/obj-in.o
[LD] [Tina-RT-Builder] build/t113_s4_c906_evb1_auto/img/rt_system.elf
if [ -n /home/ping/workspace/t113_tina5.0/t113_tinasdk5.0-v1/rtos/lichee/rtos/build/t113_s4_c906_evb1_auto/img/ ]; then mkdir -p /home/ping/workspace/t113_tina5.0/t113_tinasdk5.0-v1/rtos/lichee/rtos/build/t113_s4_c906_evb1_auto/img; fi
Memory region Used Size Region Size %age Used
RAM: 214096 B 6 MB 3.40%
*[IMAGE] [Tina-RT-Builder] build/t113_s4_c906_evb1_auto/img/rt_system.bin
*[SYMS] [Tina-RT-Builder] build/t113_s4_c906_evb1_auto/img/rt_system.syms
text data bss dec hex filename
134576 69048 10472 214096 34450 build/t113_s4_c906_evb1_auto/img/rt_system.elf
copying /home/ping/workspace/t113_tina5.0/t113_tinasdk5.0-v1/rtos/lichee/rtos/build/t113_s4_c906_evb1_auto/img/rt_system.bin to /home/ping/workspace/t113_tina5.0/t113_tinasdk5.0-v1/rtos/board/t113_s4_c906/evb1_auto/bin/freertos.fex
#### make completed successfully
'/home/ping/workspace/t113_tina5.0/t113_tinasdk5.0-v1/rtos/lichee/rtos/build/t113_s4_c906_evb1_auto/img/rt_system.bin' -> '/home/ping/workspace/t113_tina5.0/t113_tinasdk5.0-v1/rtos/board/t113_s4_c906/evb1_auto/bin/rtos_riscv_sun8iw20p1.f
```
编译完成后会在SDK目录下的/rtos/board/t113_s4_c906/evb1_auto/bin目录生成rtos_riscv_sun8iw20p1.fex和freertos.fex,这两个文件是一样的。
**5.4、核对所选FreeRTOS组件**
配置FreeRTOS组件是为了完成FreeRTOS APP的开发。本次的APP要完成的是异构通信,我们查看t113_s4_c906_evb1_auto项目代码了解其运行过程。
t113_s4_c906_evb1_auto项目的相关代码核配置信息在SDK所在目录/rtos/lichee/rtos/projects/t113_s4_c906/evb1_auto中,该目录中有如下文件:
```
xxx@xxx:~/workspace/t113_tina5.0/rtos/lichee/rtos/projects/t113_s4_c906/evb1_auto$ tree -l
.
├── defconfig
├── defconfig_org
├── freertos.lds.S
├── Kconfig
├── Makefile
└── src
├── alsa_config.c
├── assert.c
├── card_default.c
├── FreeRTOSConfig.h
├── hooks.c
└── main.c
```
重点查看main.c文件,该文件内容如下:
```
#include
#include
#include
#include
#include "interrupt.h"
#include
#include "FreeRTOS.h"
#include "task.h"
#include
#include
#ifdef CONFIG_DRIVERS_MSGBOX
#include
#endif
#ifdef CONFIG_COMPONENTS_AW_DEVFS
#include
#endif
#ifdef CONFIG_COMPONENTS_OPENAMP
#include
extern int openamp_init(void);
extern int rpbuf_init(void);
void openamp_init_thread(void *param)
{
(void)param;
openamp_init();
#ifdef CONFIG_RPMSG_CLIENT
rpmsg_ctrldev_create();
#endif
#ifdef CONFIG_RPMSG_HEARBEAT
extern int rpmsg_heart_init(void);
rpmsg_heart_init();
#endif
#ifdef CONFIG_MULTI_CONSOLE
extern int multiple_console_init(void);
multiple_console_init();
#endif
#ifdef CONFIG_COMPONENTS_RPBUF
extern int rpbuf_init(void);
rpbuf_init();
#endif
hal_thread_stop(NULL);
}
#endif
void cpu0_app_entry(void *param)
{
(void)param;
#ifdef CONFIG_COMPONENTS_AW_DEVFS
devfs_mount("/dev");
#endif
#if defined CONFIG_COMPONENTS_OPENAMP
void *thread;
thread = hal_thread_create(openamp_init_thread, NULL,
"amp_init", 8 * 1024, HAL_THREAD_PRIORITY_SYS);
if (thread != NULL)
hal_thread_start(thread);
#endif
#ifdef CONFIG_COMPONENT_CLI
vCommandConsoleStart(0x1000, HAL_THREAD_PRIORITY_CLI, NULL);
#endif
vTaskDelete(NULL);
}
```
该程序的入口函数是cpu0_app_entry,该程序通过函数hal_thread_create建立了openamp_init_thread线程。该线程通过openamp_init函数进行了openamp框架的初始化,同时如果选择了rpmsg通信,也进行了rpmsg通信的初始化等。可判定所选的FreeRTOS相关组件可以满足要求。
**5.5、主板内使能C906**
启动主板,打开串口终端进入主板控制台,将freertos.fex拷贝到/lib/firmware目录下。假设使用ADB功能将文件拷贝到主板的root/目录下
```
root@TinaLinux:~# cd /root/
root@TinaLinux:~# ls
freertos.fex
```
将root目录下的freertos.fex拷贝到/lib/firmware目录下
```
root@TinaLinux:~# cp freertos.fex /lib/firmware/
root@TinaLinux:~# ls /lib/firmware/
boot_xr829.bin fw_xr829.bin freertos.fex sdd_xr829.bin
etf_xr829.bin fw_xr829_bt.bin regulatory.db
```
拷贝完成后,可以在/lib/firmware目录下看到小核固件。接下来把主板的UART3(在RTOS配置中已经把UART3配置成终端串口)与计算机的串口连接起来,并配置计算机串口波特率为115200,通过计算机串口可以查看小核的相关信息。在Tina Linux主板串口终端输入指令:
```
root@TinaLinux:~# echo freertos.fex > /sys/class/remoteproc/remoteproc1/firmware
```
这一步是将freertos.fex固件放在硬件节点firmware 。接下来启动C906固件,指令如下:
```
root@TinaLinux:~#echo start > /sys/class/remoteproc/remoteproc1/state
remoteproc remoteproc1: powering up c906_rproc
[ 1489.549950] remoteproc remoteproc1: Booting fw image amp_rv0.bin, size 224392
[ 1489.558282] remoteproc remoteproc1: the version: UTS - Thu, 24 Oct 2024 14:39:36 +0800
[ 1489.558282] Compile Time - 14:39:36
[ 1489.571527] remoteproc1#vdev0buffer: assigned reserved memory node vdev0buffer@42900000
[ 1489.581143] virtio_rpmsg_bus virtio0: rpmsg host is online
[ 1489.587680] remoteproc1#vdev0buffer: registered virtio0 (type 7)
[ 1489.594617] remoteproc remoteproc1: remote processor c906_rproc is now up
root@TinaLinux:/# [ 1489.695824] virtio_rpmsg_bus virtio0: creating channel sunxi,rpmsg_ctrl addr 0x400
[ 1489.715788] virtio_rpmsg_bus virtio0: creating channel rpbuf-service addr 0x401
[ 1489.724381] rpbuf_service_rpmsg virtio0.rpbuf-service.-1.1025: rpmsg device parent 0: virtio0
[ 1489.734036] rpbuf_service_rpmsg virtio0.rpbuf-service.-1.1025: rpmsg device parent 1: remoteproc1#vdev0buffer
[ 1489.745237] rpbuf_service_rpmsg virtio0.rpbuf-service.-1.1025: rpmsg device parent 2: remoteproc1
[ 1489.755242] rpbuf_service_rpmsg virtio0.rpbuf-service.-1.1025: rpmsg device parent 3: 6010000.c906_rproc
[ 1489.766110] virtio_rpmsg_bus virtio0: creating channel sunxi,rpmsg_heartbeat addr 0x402
使能后可以在UART3上查看RV核的信息:
cpu0>help
Lists all the registered commands
[ help-built-in]--------------Lists all the built-in registered commands
[ ts]--------------Displays a table showing the state of each FreeRTOS task
[ run-time-stats]--------------Displays a table showing how much processing time each FreeRTOS task has used
[ echo-3-parameters]--------------echo-3-parameters : Expects three parameters, echos each in turn
[ echo-parameters]--------------echo-parameters : Take variable number of parameters, echos each in turn
[ rpbuf_demo]--------------rpbuf demo
[ rpbuf_test]--------------rpbuf test demo
[ console_dump]--------------dum all cli console info
[ exit]--------------Console Exit Command
[ msgbox_demo]--------------msgbox demo
[ help]--------------List all registered commands
[ backtrace]--------------Backtrace Command
[ md5sum]--------------Calculate md5sum
[ rproc_dump_mapping]--------------rproc dump mapping
[ eptdev_send]--------------send data used by rpmsg ept test
[ eptdev_close]--------------close rpmsg ept client
[ rpmsg_list_epts]--------------list endpoints
[ rpmsg_list_listen]--------------list listen
[ jtag]--------------init jtag gpio
[rpmsg_ctrlde_release]--------------release rpmsg ctrldev
[ rpmsg_ctrldev_init]--------------init rpmsg ctrldev
[ eptdev_reset]--------------reset rpmsg ctrl
[ eptdev_clear]--------------clear rpmsg name group
[ eptdev_unbind]--------------unbind rpmsg ept listen
[ eptdev_bind]--------------bind rpmsg ept name
[ rpmsg_test]--------------rpmsg test
[ rpmsg_test_send]--------------rpmsg test send
[ rpmsg_test_init]--------------init rpmsg test
[ rpmsg_test_extend]--------------rpmsg test with another rproc
[ hal_msgbox]--------------hal msgbox
cpu0>
查看RV核目前的任务:
cpu0>ts
Task State Priority Stack #
************************************************
Name State Pri HWM Idx StkCur StkBot
CLI X 18 3742 5 0x42363e30 0 x4235c510
IDLE R 0 980 2 0x42348250 0 x423463b0
Tmr Svc B 31 1978 3 0x4234c220 0 x423483c0
ctrldev B 6 4026 8 0x4237d460 0 x42375690
cpu-vring-ipi B 31 8128 7 0x42374780 0 x42364950
```
- 2024-11-18
-
发表了主题帖:
全志T113双核异构处理器的使用基于Tina Linux5.0——RTOS编译开发说明
**3、RTOS编译开发说明**
**3.1、RTOS SDK与TinaLinux开发环境**
RTOS SDK相关代码已集成到Tina Linux开发环境,Tina Linux开发环境下的rtos子目录即为RTOS开发环境。
```
├──brandy
├──bsp
├──build
├──buildroot
├──build.sh >build/top_build.sh
├──device
├──kernel
├──openwrt
├──out
├──platform
├──prebuilt
├──rtos #RTOS环境
├──tee_kit
├──test
└──tools
```
**3.2、RTOS编译**
Tina集成了RTOS的编译、打包功能,而且适用于openwrt、buildroot等文件系统。这里以T113 S4平台作为例子进行阐述。
**3.2.1、关联RTOS方案**
TinaLinux的SDK中,SDK根目录下有device目录,该目录存放的是不同处理器平台及主板的配置,以SBC-T113S4_NAND为例,它是以T113_S4为主控制器的,其配置文件就存放在device/config/chips/t113_s4/configs/sbc_t113s4_nand目录下,那么该主板如何关联RTOS方案呢?
```
xxx@xxx:~/workspace/t113_tina5.0/t113_tinasdk5.0-v1/device/config/chips/t113_s4/configs/sbc_t113s4_nand$ tree -l
.
├── bin
│ ├── amp_dsp0.bin
│ └── amp_rv0.bin
├── BoardConfig.mk //关联RTOS项目的关键
├── board.dts -> linux-5.4/board.dts
├── bsp
│ ├── env.cfg
│ └── sys_partition.fex
├── buildroot
│ ├── BoardConfig.mk
│ ├── BoardConfig_nor.mk
│ ├── bootlogo.bmp
│ ├── env_nor.cfg
│ ├── swupdate
│ │ ├── env_ab.cfg
│ │ ├── sw-description-ab
│ │ ├── sw-description-ab-rdiff
│ │ ├── sw-description-recovery
│ │ ├── sw-description-recovery-sign
│ │ ├── sw-subimgs-ab.cfg
│ │ ├── sw-subimgs-ab-rdiff.cfg
│ │ ├── sw-subimgs-recovery.cfg
│ │ ├── sw-subimgs-recovery-secure.cfg
│ │ ├── sw-subimgs-recovery-sign.cfg
│ │ ├── sys_partition_ab.fex
│ │ └── sys_partition_recovery.fex
│ ├── sys_partition.fex
│ └── sys_partition_nor.fex
├── linux-5.4
│ ├── board_backup.dts
│ ├── board.dts
│ ├── board_lvds1024x600.dts
│ ├── board_lvds1024x768.dts
│ ├── board_old.dts
│ ├── board_spi4bit_drv30.dts
│ ├── board_spi4bit.dts
│ ├── board_spi_sbc-t113s_v1p0.dts
│ ├── board_tft1024x600.dts
│ ├── board_uart3.dts
│ ├── board_xr829_sbc-t113s_v1p1.dts
│ ├── config-5.4
│ ├── config-5.4-recovery
│ ├── config_uart3-5.4
│ ├── env.cfg
│ ├── env_uart3.cfg
│ └── sys_partition.fex
├── openwrt
│ ├── bootlogo.bmp
│ ├── sys_partition.fex
│ └── sys_partition_nor.fex
├── sys_config.fex
├── sys_config_old.fex
├── sys_config_uart3.fex
├── uboot-board_backup.dts
├── uboot-board.dts
├── uboot-board-lvds-lcd1024x600.dts
├── uboot-board_spi0_1wire.dts
├── uboot-board_spi_sbc-t113s_v1p0.dts
├── uboot-board_spi_sbc-t113s_v1p1.dts
└── uboot-board-tft-lcd1024x600.dts
```
在这些配置文件中有一个文件BoardConfig.mk,它就是关联RTOS应用的关键,这个文件的内容如下:
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1731640284360-a7612410-1de1-45b4-aa50-de4f5ded5cc2-image.png)
配置RTOS方案名的关键字段LICHEE_RTOS_PROJECT_NAME:=xxxxx。以我司SBC-T113S主板为例,在BoardConfig.mk中配置的是t113_s4_c906_evb1_auto的RTOS方案。开发者可以通过修改LICHEE_RTOS_PROJECT_NAME字段,更改构建RTOS方案。
先进入Tina的rtos目录,rtos目录的内容如下:
```
├──board
├──envsetup.sh >tools/scripts/source_envsetup.sh
├──lichee
├──out
└──tools
```
执行如下指令进行RTOS的环境配置
```
source envsetup.sh #配置环境变量
```
我们看到可以选择的RTOS方案,即是LICHEE_RTOS_PROJECT_NAME字段可以配置的RTOS方案。
```
last=t113_s4_c906_evb1_auto
You're building on Linux
Lunch menu... pick a combo:
1. t113_i_c906_evb1_auto
2. t113_s3p_c906_evb1_auto
3. t113_s3p_c906_evb1_auto_fastboot
4. t113_s3p_c906_evb1_auto_fastboot_video
5. t113_s3p_c906_evb1_auto_non_os
6. t113_s3p_c906_example_demo
7. t113_s4_c906_evb1_auto
8. t113_s4_c906_evb1_auto_fastboot_video
9. t113_s4p_c906_evb1_auto
Which would you like? [Default t113_s4_c906_evb1_auto]:
```
从以上RTOS的方案中,可以看到有支持t113-i处理器的,也有支持t113_s4处理器的,当前默认是t113_s4_c906_evb1_auto,这个是基于t113_s4处理器的方案。另外,这个方案是可以添加的,如果主板是基于T113I的,可以把t113_i_c906_evb1_auto作为蓝本增加自己方案,如果主板是基于T113-S4的,可以把t113_i_c906_evb1_auto作为蓝本增加自己方案。如何添加方案请看本手册的第六章节。
**3.2.2、单独编译RTOS方案**
执行lunch_rtos进行编译方案选择后,可以通过mrtos_menuconfig进行方案的配置,配置完成后即可通过m或者mrtos指令进行RTOS的编译。
```
xxx@xxx:~/workspace/t113_tina5.0/t113_tinasdk5.0-v1/rtos$ lunch_rtos
last=t113_s4_c906_evb1_auto
You're building on Linux
Lunch menu... pick a combo:
1. t113_i_c906_evb1_auto
2. t113_s3p_c906_evb1_auto
3. t113_s3p_c906_evb1_auto_fastboot
4. t113_s3p_c906_evb1_auto_fastboot_video
5. t113_s3p_c906_evb1_auto_non_os
6. t113_s3p_c906_example_demo
7. t113_s4_c906_evb1_auto
8. t113_s4_c906_evb1_auto_fastboot_video
9. t113_s4p_c906_evb1_auto
Which would you like? [Default t113_s4_c906_evb1_auto]: 7
select=7...
t113_s4_c906/evb1_auto
'/home/ping/workspace/t113_tina5.0/t113_tinasdk5.0-v1/rtos/lichee/rtos/projects/t113_s4_c906/evb1_auto/defconfig' -> '/home/ping/workspace/t113_tina5.0/t113_tinasdk5.0-v1/rtos/lichee/rtos/.config'
============================================
RTOS_BUILD_TOP=/home/ping/workspace/t113_tina5.0/t113_tinasdk5.0-v1/rtos
RTOS_TARGET_ARCH=riscv
RTOS_TARGET_CHIP=sun8iw20p1
RTOS_TARGET_DEVICE=t113_s4_c906
RTOS_PROJECT_NAME=t113_s4_c906_evb1_auto
============================================
Run mrtos_menuconfig to config rtos
Run m or mrtos to build rtos
```
以上提示我们通过mrtos_menuconfig可以进行应用的配置,执行结果如下:
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1731640399111-b6e0e6d4-a8f7-4b86-85fc-9e49dc078c0e-image.png)
从配置情况看,和Linux的内核配置比较接近,也是可以配置各种驱动外设,见下图:
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1731640414907-c84cfc5d-79b7-4699-89a1-1bc1e79d3863-image.png)
T113系列拥有双核Cortex-A7核心,也拥有RV64-C906核心,这两个核心使用的外设是共通的,因此选择C906选择外设的时候,要避开Cortex-A7已经使用的外设。例如:Cortex-A7核心已经把PE2/PE3这两个GPIO作为UART来使用,C906就不能再使用PE2/PE3。
另外,这里要明确的一点是:每个RTOS应用也是有对应的配置文件的,对于t113_s4_c906_evb1_auto这个项目的配置文件在如下目录:
```
TinaSDK所在目录/rtos/lichee/rtos/projects/t113_s4_c906/evb1_auto/defconfig
```
配置完成后即可执行m或者mrtos执行进行编译,指令执行结果如下:
```
xxx@xxx:~/workspace/t113_tina5.0/t113_tinasdk5.0-v1/rtos$ mrtos
build rtos ...
===There isn't tina environment.===
Note: will use shell command origin rather than the functon.
Dark Builder
Version (1.6.0 - BiCEP2 (Gravitational Waves))
*[CC] [SCRIPT] build/t113_s4_c906_evb1_auto/img/sys_config.fex
[CONF] [Tina-RT-Builder] .dbuild/..//include/generated/t113_s4_c906_evb1_aut o/autoconf.h
[LDS] [Linker] projects/t113_s4_c906/evb1_auto/freertos.lds
CC build/t113_s4_c906_evb1_auto/arch/common/version.o
LD build/t113_s4_c906_evb1_auto/arch/common/obj-in.o
LD build/t113_s4_c906_evb1_auto/arch/obj-in.o
[LD] [Tina-RT-Builder] build/t113_s4_c906_evb1_auto/img/rt_system.elf
if [ -n /home/ping/workspace/t113_tina5.0/t113_tinasdk5.0-v1/rtos/lichee/rtos/bu ild/t113_s4_c906_evb1_auto/img/ ]; then mkdir -p /home/ping/workspace/t113_tina 5.0/t113_tinasdk5.0-v1/rtos/lichee/rtos/build/t113_s4_c906_evb1_auto/img; fi
Memory region Used Size Region Size %age Used
RAM: 214096 B 6 MB 3.40%
*[SYMS] [Tina-RT-Builder] build/t113_s4_c906_evb1_auto/img/rt_system.syms
*[IMAGE] [Tina-RT-Builder] build/t113_s4_c906_evb1_auto/img/rt_system.bin
text data bss dec hex filename
134576 69048 10472 214096 34450 build/t113_s4_c906_evb1_auto/img/rt_syst em.elf
copying /home/ping/workspace/t113_tina5.0/t113_tinasdk5.0-v1/rtos/lichee/rtos/bu ild/t113_s4_c906_evb1_auto/img/rt_system.bin to /home/ping/workspace/t113_tina5. 0/t113_tinasdk5.0-v1/rtos/board/t113_s4_c906/evb1_auto/bin/freertos.fex
#### make completed successfully
'/home/ping/workspace/t113_tina5.0/t113_tinasdk5.0-v1/rtos/lichee/rtos/build/t11 3_s4_c906_evb1_auto/img/rt_system.bin' -> '/home/ping/workspace/t113_tina5.0/t11 3_tinasdk5.0-v1/rtos/board/t113_s4_c906/evb1_auto/bin/rtos_riscv_sun8iw20p1.fex'
ping@embedall:~/workspace/t113_tina5.0/t113_tinasdk5.0-v1/rtos$
```
从以上信息我们得到一些信息,编译出来的固件名字是freertos.fex/rtos_riscv_sun8iw20p1.fex,其实两个固件是一样的。固件是编译出来,但是这个固件如何打包到Tina生成的根文件系统中呢?请看下一章。
**3.2.3、在Tina下编译RTOS方案**
在TinaSDK根目录中,先选择好整体平台方案,见如下说明:
* buildroot方案
1、首先使用如下命令选择整体平台方案
```
./build.sh config
```
2、然后RTOS相关操作命令如下
```
./build.sh rtos #单独编译RTOS方案
./build.sh rtos menuconfig #修改RTOS配置文件
./build.sh rtos clean #清除RTOS编译中间文件
```
注意:执行./build.sh不会编译RTOS
* openwrt方案
1、首先使用如下命令选择整体平台方案
```
source build/envsetup.sh #生效环境变量
lunch #选择openwrt方案
```
2、执行make会先编译RTOS,再编译Tina
* 快捷命令
在Tina根目录下,执行了source build/envsetup.sh配置环境后,可使用RTOS快捷命令进行RTOS的配置和编译等。
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1731640728957-20b3f0e3-505c-4e2a-b368-f7b0e9e0b542-image.png)
**3.3、RTOS固件打包**
我们以编译openwrt方案为例,在的编译openwrt的过程中,会首先编译RTOS,然后将生成的镜像文件自动拷贝到对应平台方案中,并且rt_system.elf重名为amp_rv0.bin,见如下信息:
```
xxx@xxx:~/workspace/t113_tina5.0/t113_tinasdk5.0-v1$ make -j32
===There is tina environment.===
Note: make is the shell functon in envsetup.sh.
== action: openwrt_build, action_args: -j32 ==
========ACTION List: build_rtos ;========
options :
INFO: build rtos ...
Setup env done!
Run lunch_rtos to select project
last=t113_s4_c906_evb1_auto
select=t113_s4_c906_evb1_auto...
t113_s4_c906/evb1_auto
'/home/ping/workspace/t113_tina5.0/t113_tinasdk5.0-v1/rtos/lichee/rtos/projects/t113_s4_c906/evb1_auto/defconfig' -> '/home/ping/workspace/t113_tina5.0/t113_tinasdk5.0-v1/rtos/lichee/rtos/.config'
============================================
RTOS_BUILD_TOP=/home/ping/workspace/t113_tina5.0/t113_tinasdk5.0-v1/rtos
RTOS_TARGET_ARCH=riscv
RTOS_TARGET_CHIP=sun8iw20p1
RTOS_TARGET_DEVICE=t113_s4_c906
RTOS_PROJECT_NAME=t113_s4_c906_evb1_auto
============================================
Run mrtos_menuconfig to config rtos
Run m or mrtos to build rtos
build rtos ...
Dark Builder
Version (1.6.0 - BiCEP2 (Gravitational Waves))
*[CC] [SCRIPT] build/t113_s4_c906_evb1_auto/img/sys_config.fex
[CONF] [Tina-RT-Builder] .dbuild/..//include/generated/t113_s4_c906_evb1_auto/autoconf.h
[LDS] [Linker] projects/t113_s4_c906/evb1_auto/freertos.lds
CC build/t113_s4_c906_evb1_auto/arch/common/version.o
LD build/t113_s4_c906_evb1_auto/arch/common/obj-in.o
LD build/t113_s4_c906_evb1_auto/arch/obj-in.o
[LD] [Tina-RT-Builder] build/t113_s4_c906_evb1_auto/img/rt_system.elf
if [ -n /home/ping/workspace/t113_tina5.0/t113_tinasdk5.0-v1/rtos/lichee/rtos/build/t113_s4_c906_evb1_auto/img/ ]; then mkdir -p /home/ping/workspace/t113_tina5.0/t113_tinasdk5.0-v1/rtos/lichee/rtos/build/t113_s4_c906_evb1_auto/img; fi
Memory region Used Size Region Size %age Used
RAM: 214096 B 6 MB 3.40%
*[IMAGE] [Tina-RT-Builder] build/t113_s4_c906_evb1_auto/img/rt_system.bin
*[SYMS] [Tina-RT-Builder] build/t113_s4_c906_evb1_auto/img/rt_system.syms
text data bss dec hex filename
134576 69048 10472 214096 34450 build/t113_s4_c906_evb1_auto/img/rt_system.elf
copying /home/ping/workspace/t113_tina5.0/t113_tinasdk5.0-v1/rtos/lichee/rtos/build/t113_s4_c906_evb1_auto/img/rt_system.bin to /home/ping/workspace/t113_tina5.0/t113_tinasdk5.0-v1/rtos/board/t113_s4_c906/evb1_auto/bin/freertos.fex
#### make completed successfully
'/home/ping/workspace/t113_tina5.0/t113_tinasdk5.0-v1/rtos/lichee/rtos/build/t113_s4_c906_evb1_auto/img/rt_system.bin' -> '/home/ping/workspace/t113_tina5.0/t113_tinasdk5.0-v1/rtos/board/t113_s4_c906/evb1_auto/bin/rtos_riscv_sun8iw20p1.fex'
'/home/ping/workspace/t113_tina5.0/t113_tinasdk5.0-v1/rtos/lichee/rtos/build/t113_s4_c906_evb1_auto/img/rt_system.elf' -> '/home/ping/workspace/t113_tina5.0/t113_tinasdk5.0-v1/device/config/chips/t113_s4/configs/sbc_t113s4_nand/bin/amp_rv0.bin'
```
然后在Tina环境中打包,确保RTOS编译固件amp_rv0.bin打包到根文件系统中。
说明:
1)buildroot需要执行./build.sh&&./build.sh pack命令
2)openwrt需要执行make&&pack命令
打包完成后,生产的固件位于tina5.0/out/t113_s4_linux_sbc_t113s4_uart0.img,可以使用PhoenixSuit工具烧录到主板上。
**3.4、RISC-V核启动**
进入主核Linux控制台后,执行如下命令即可完成RV核异构通信框架的初始化:
```
echo start>/sys/class/remoteproc/remoteproc1/state
```
注:T113-S4处理器包括了DSP HIFI核心,也包括了RISC-V核心,因此它应该具备remoteproc0/remoteproc1两个节点(这两个节点在内核的DTS中是可配置的,可以删除任何一个节点)。如果两个节点都在,那么remoteproc0节点是DSP核心,remoteproc1节点是RISC-V核心。
**3.5、RTOS镜像文件更新**
更新RTOS镜像文件的方法有以下两种:
1、Tina Linux环境下,按照RTOS编译、RTOS固件打包步骤重新编译固件,并烧录进开发板,之后再启动RV核。
2、通过adb push将新生成的镜像文件amp_rv0.bin推到/lib/firmware目录下,在Linux控制台依执行如下命令:
```
echo stop>/sys/class/remoteproc/remoteproc1/state #关闭RV。
echo amp_rv0.bin>/sys/class/remoteproc/remoteproc1/firmware
echo start>/sys/class/remoteproc/remoteproc1/state #启动RV
```
-
发表了主题帖:
全志T113双核异构处理器的使用基于Tina Linux5.0——RTOS简介
全志T113-i是一款双核Cortex-A7国产工业级处理器平台,并内置玄铁C906 RISC-V和HiFi4 DSP双副核心,可流畅运行Linux系统与Qt界面,并已适配OpenWRT系统、Preempt Linux系统。
而其中的RISC-V属于超高能效副核心,标配内存管理单元,可运行RTOS或裸机程序。T113的主核运行Linux(Openwrt或者Preempt Linux)进行人机界面的交互和应用流程,而RISC-V则是后台
英雄,可进行大数据数据采集,或者相关编码器的控制等,降低主核被中断的次数,大大提供了主核的运行效率。
以盈鹏飞嵌入式SBC-T113主板为例(以T113-i为主控的主板),我们先介绍下主板特性:
**产品特性**
* 采用全志Cortex-A7双核T113-i处理器,运行最高速度为1.2GHZ;
* 列表内置64-bit XuanTie C906 RISC-V协处理器;
* 列表支持JPEG/MJPEG视频编码,最大分辨率1080p@60fps;支持多格式1080P@60fps视频解码 (H.265,H.264, MPEG-1/2/4);
* 列表支持双通道LVDS/MIPI-DSI,分辨率最高1920x1080;
* 列表支持512-1G Bytes DDR3 SDRAM;
* 列表支持SPI NAND存储和启动(默认:256MB;可选128MB)或者EMMC启动(默认:4GB,最大32GB);
* 列表支持四路USB2.0 HOST;
* 列表支持六路RS232通信;
* 列表支持双路CAN BUS通信(隔离);
* 列表支持双路以太网,一路10/100M;一路10/100/1000M;
* 列表稳定的操作系统的支持,可预装Ubuntu20.04/LINUX 5.4 Preempt/Tina5.0;
* 列表标准3.5寸主板, 尺寸为:146*102MM;
**SBC-T113产品功能图:**
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1731634940802-sbc-t113.png)
****1、RTOS系统概述****
****1.1、概述****
全志Tina Linux SDK中包含了RTOS系统,该系统是基于FreeRTOS内核的软件开发包,包含了系统开发用到的内核源码、驱动、工具、组件与应用程序包。通过Makefile脚本和Kconfig配置文件,使得用户可以通过menuconfig进行个性化裁减,编译出一个可以直接烧写到机器上运行的RTOS系统软件。
**1.2、系统框图**
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1731635049377-e28014db-faf1-40b1-8aee-4c82580cf06e-image.png)
RTOS 系统框图如图所示,仅从软件的角度来看,从下至上分为内核层、组件层、应用层三个层次。各层次主要内容如下:
* Kernel:内核层包括 FreeRTOS核心系统、文件系统、网络系统、BSP驱动等。
* Component:组件层包括控制台、多媒体、功耗管理、OTA、音频系统、显示系统、图像采集等。
* APP:应用层包括各种应用 demo。
**2、RTOS SDK目录结构**
```
rtos
├──board #包含各SoC板级配置目录
│└──mr527_e906 #mr527_e906板级配置目录
│└──t113_s3p_c906 #t113_s3p_c906板级配置目录
│└──t113_s4_c906 #t113_s4_c906板级配置目录
│└──t113_s4p_c906 #t113_s4p_c906板级配置目录
│└──XXX #XXX平台板级配置目录
├──envsetup.sh #SDK环境初始化脚本
├──lichee
│├──dsp #DSP FreeRTOS系统
│├──rtos #C906/E906FreeRTOS系统
│├──rtos components#FreeRTOS公共组件
│└──rtos hal #BSP驱动
└──tools #打包相关工具脚本目录
```
所使用RTOS SDK目录结构如上所示,主要包括如下几个关键目录:
board:板级配置目录,用于存放芯片方案的配置文件,主要包括系统配置文件sys_config.fex等。
lichee/dsp:存放DSPFreeRTOS系统、组件、应用。
lichee/rtos:存放E906FreeRTOS系统、组件、应用。
lichee/rtos components:公共组件目录,lichee/dsp与lichee/rtos都可以使用该组件。
lichee/rtos hal:BSP驱动目录,用于存放各种驱动代码。对lichee/dsp与lichee/rtos通用。
tools:工具目录,用于存放编译打包相关的脚本、工具等。
下面对lichee/rtos、lichee/rtos hal目录进行详细说明。lichee/dsp目录与lichee/rtos目录类似,此处不做介绍。
**2.1、lichee/rtos目录**
```
├──arch #处理器架构相关
├──build #编译临时文件输出目录
├──components #组件
├──drivers#驱动
├──include#头文件
├──kernel#FreeRTOS内核#方案工程
├──projects
├──scripts
└──tools#工具链
```
lichee/rtos目录主要包括arch(架构相关)、components(组件)、drivers(驱动)、include(头文件)、kernel(内核)、projects(工程)、toos(工具链)等目录,下面对常用重要目录分别进行介绍。
**2.1.1、arch目录**
arch目录主要放置跟SoC架构相关的内容,每个SoC单独目录管理,主要包括跟risc v架构相关的ARCH初始化、中断处理、异常处理、内存映射相关功能的实现。
```
lichee/rtos/arch/
├──common
└──risc v
├──arch.mk
├──c906
├──common
├──e906
├──includes
├──Kconfig
├──Makefile
├──sun55iw3p1
└──sun8iw20p1
```
**2.1.2、components目录**
components 目录包含 allwinner 和第三方的组件。
```
lichee/rtos/components/
├──aw
│├──blkpart
│├──bluetooth
│├──csi
│├──devfs
│├──healthd
│├──......
│├──watchpoint
│└──wireless_video
├──common >../../rtos components
└──thirdparty
├──common
├──console
├──cplusplus
├──elmfat
├──finsh_cli
├──......
└──vfs
```
**2.1.3、drivers目录**
drivers目录包含所需的外设驱动,主要包括各外设控制器驱动的具体实现(hal软连接)以及OSAL层接口(osal)。
```
lichee/rtos/drivers/
├──drv
├─ CPUfreq #POSIX头文件
├──leds
├── uart
├──.....
├── wireless
├─hal ->.../../rtos-hal/
└──osal
```
**2.1.4、include 目录**
include 目录统一管理各模块提供的数据结构定义及函数声明。
```
lichee/rtos/include/
├── arch
# 架构相关头文件
├── FreeRTOS_POSIX # POSIX头文件
├── ......
└── vsprintf.h
```
**2.1.5、kernel目录**
kernel目录主要包含FreeRTOS的kernel源码,全志实现的系统功能相关代码。
```
lichee/rtos/kernel/
├──FreeRTOS orig
│└──Source
└──Posix
```
**2.1.6 projects 目录**
projects目录下的每一个子目录代表一个project,实现main入口,选择不同的project编译出来的bin具有不同功能,每个project有独立的FreeRTOSConfig配置。例如t113-s4,其对应于t113_s4_c906子目录,这个子目录下面根据应用的不同建立不同的应用配置,如下有evb1_auto应用和evb1_auto_fastboot_video应用。
```
rtos/lichee/rtos/projects/t113_s4_c906/
├── evb1_auto
│ ├── defconfig
│ ├── defconfig_org
│ ├── freertos.lds.S
│ ├── Kconfig
│ ├── Makefile
│ └── src
│ ├── alsa_config.c
│ ├── assert.c
│ ├── card_default.c
│ ├── FreeRTOSConfig.h
│ ├── hooks.c
│ └── main.c
├── evb1_auto_fastboot_video
│ ├── defconfig
│ ├── freertos.lds.S
│ ├── Kconfig
│ ├── Makefile
│ └── src
│ ├── alsa_config.c
│ ├── assert.c
│ ├── card_default.c
│ ├── FreeRTOSConfig.h
│ ├── hooks.c
│ └── main.c
└── Makefile
```
**2.1.7、tools目录**
这个目录主要包含一些预编译好的交叉编译工具链。
```
xxx@xxx:lichee/rtos/tools$ ls -al
total 663248
drwxrwxr-x 3 ping ping 4096 Oct 21 14:28 .
drwxrwxr-x 13 ping ping 4096 Oct 21 16:02 ..
-rw-rw-r-- 1 ping ping 103333888 Jan 8 2024 gcc-arm-melis-eabi-8-2019-q3-update-linux.tar.bz2
-rw-rw-r-- 1 ping ping 106566166 Jan 8 2024 gcc-arm-none-eabi-8-2019-q3-update-linux.tar.bz2
-rwxrwxr-x 1 ping ping 137020992 Jan 8 2024 gcc-arm-none-eabi-8-2019-q3-update-win32.zip
drwxr-xr-x 9 ping ping 4096 Oct 21 14:29 riscv64-elf-x86_64-20201104
-rwxrwxr-x 1 ping ping 164604965 Jan 8 2024 riscv64-elf-x86_64-20201104.tar.gz
-rwxrwxr-x 1 ping ping 167614189 Jan 8 2024 Xuantie-900-gcc-elf-newlib-mingw-V2.6.1-20220906.tar.gz
xxx@xxx:lichee/rtos/tools$
```
目前risc v基于GCC8.4.0的交叉编译器。
```
xxx@xxx:lichee/rtos/tools$./riscv64-elf-x86_64-20201104/bin/riscv64-unknown-elf-gcc -v
Using built-in specs.
COLLECT_GCC=./riscv64-elf-x86_64-20201104/bin/riscv64-unknown-elf-gcc
COLLECT_LTO_WRAPPER=/home/ping/workspace/t113_tina5.0/t113_tinasdk5.0-v1/rtos/lichee/rtos/tools/riscv64-elf-x86_64-20201104/bin/../libexec/gcc/riscv64-unknown-elf/8.4.0/lto-wrapper
Target: riscv64-unknown-elf
Configured with: /ldhome/software/toolsbuild/slave/workspace/riscv64_build_elf_x86_64/build/../source/riscv/riscv-gcc/configure --target=riscv64-unknown-elf --with-mpc=/ldhome/software/toolsbuild/slave/workspace/riscv64_build_elf_x86_64/lib-for-gcc-x86_64-linux/ --with-mpfr=/ldhome/software/toolsbuild/slave/workspace/riscv64_build_elf_x86_64/lib-for-gcc-x86_64-linux/ --with-gmp=/ldhome/software/toolsbuild/slave/workspace/riscv64_build_elf_x86_64/lib-for-gcc-x86_64-linux/ --prefix=/ldhome/software/toolsbuild/slave/workspace/riscv64_build_elf_x86_64/install --disable-shared --disable-threads --enable-languages=c,c++ --with-system-zlib --enable-tls --enable-libgcctf --with-newlib --with-sysroot=/ldhome/software/toolsbuild/slave/workspace/riscv64_build_elf_x86_64/install/riscv64-unknown-elf --with-native-system-header-dir=/include --disable-libmudflap --disable-libssp --disable-libquadmath --disable-libgomp --disable-nls --src=../../source/riscv/riscv-gcc --with-pkgversion='T-HEAD RISCV Tools V1.10.2 B20201104' --enable-multilib --with-abi=lp64d --with-arch=rv64gcxthead 'CFLAGS_FOR_TARGET=-Os -mcmodel=medany' 'CXXFLAGS_FOR_TARGET=-Os -mcmodel=medany' CC=gcc CXX=g++
Thread model: single
gcc version 8.4.0 (T-HEAD RISCV Tools V1.10.2 B20201104)
```
**2.2、lichee/rtos hal目录**
lichee/rtos hal目录为BSP驱动目录,用于存放各种驱动代码。lichee/rtos/drivers目录下的rtos hal子目录软链接到该目录,下面对该目录进行介绍。
```
lichee/rtos hal
├──hal #BSP驱动代码
├──include #驱动相关头文件
└──tools
```
lichee/rtos hal目录主要包括hal(BSP驱动代码)、include(驱动相关头文件)等目录,下面分别对其进行介绍。
**2.2.1、hal目录**
hal目录主要包含各外设驱动代码以及驱动测试代码,source子目录为驱动代码,test子目录为驱动测试代码
```
lichee/rtos hal/hal
├──Makefile
├──source
│├──ccmu
│├──gpio
│├──......
│├──uart
│└──watchdog
└──test
├──ccmu
├──gpio
├──......
├──uart
└──watchdog
```
**2.2.2、include目录**
include目录主要包含驱动相关头文件以及系统相关接口头文件。
```
lichee/rtos hal/include
├──hal
│├──aw alsa lib
│├──aw_common.h
│├──......
│├──sunxi_hal_usb.h
│├──sunxi_hal_watchdog.h
│└──video
└──osal
├──hal_atomic.h
├──hal_cache.h
├──......
├──hal_waitqueue.h
└──hal_workqueue.h
```
- 2024-11-13
-
发表了主题帖:
DIY了一台无人机,用全志T113芯片
无人机飞控是无人机的核心部分,一般包括传感器、机载计算机和伺服作动设备三大部分,能否在对重量和体积有严苛要求的无人机结构上部署具有稳定功能的飞控,是影响无人机飞行表现的重要因素。
基于此,作者就基于全志T113-S3设计了一款仅有30x30孔距的超轻量无人机飞控,可以实现无人机的自稳飞行及远距离图传。
![在这里插入图片描述](https://www.aw-ol.com/storage/portal/83_BCDD_3_F_4_BC_5_46f6_BB_68_620053580440_fa8f4d8483.png)
![在这里插入图片描述](https://www.aw-ol.com/storage/portal/10142_ezgif_com_optimize_b4454ac037.gif)
![在这里插入图片描述](https://www.aw-ol.com/storage/portal/101411_ezgif_com_optimize_e72a1f479f.gif)
# 飞控设置
这款使用全志T113-S3处理器制作的PX4飞控整体硬件设计并不复杂,飞控板载主控、IMU、磁力计、气压计以及其它常见的元件,并由T113直接输出PWM控制飞行器,并使用1路ADC来采集电池的电压,PCB孔距30x30,可以与市面上常见的四合一电调搭配使用。
![在这里插入图片描述](https://www.aw-ol.com/storage/portal/D677_F783_958_D_40eb_BBA_1_AA_68_EB_9_C5300_7969347431.png)
远程通信方面,T113通过USB直接与WiFi模块连接,接收机则是连接串口通过SBUS和T113直接连接,收到指令信号后由4路PWM直接给电机控制输出,以实现自稳飞行的功能。
![在这里插入图片描述](https://www.aw-ol.com/storage/portal/4_E6_D13_A7_2553_429d_8_B8_B_FA_52384_CF_961_0d93dcd395.png)
- 主控:全志T113,双核ARM A7
- 内存:内置128MB DDR
- 磁力计:IST8310
- 气压计:BMP388
- IMU:BMI088
- 操作系统:全志Tina Linux
# 无人机自稳飞行功能测试
无人机通过飞控和WiFi模块结合,形成一个SBUS接收机,来实现本地的数据传输与远程控制。
如下图中的画面所示,左侧是飞控通过WiFi与QGC连接后将无人机姿态数据返回的显示界面,右侧的画面则是T113芯片的负载情况显示,T113是双核处理器,作者这里只把T113的核心1做了隔离,只运行PX4程序,所以可以通过核心1的反馈直接看运行PX4程序的负载情况,核心0是负责系统上的其他程序运行。
![在这里插入图片描述](https://www.aw-ol.com/storage/portal/10141_ezgif_com_video_to_gif_converter_5cc946080d.gif)
首先在自稳模式下将PID参数调稳定,调试Multicopter Rate Control菜单下的相关参数,自稳PID调试完成后,此时飞行会发现飞行器总是会朝某个方向“倾斜”飞行,此时最好先将机体各部位都固定稳定,中心最好在机体中心(电池位置最好也固定,不然电池的拆卸也是影响重心的一个要点)。
然后调试Sensors菜单下的两个参数,最完美的状态是调试到roll和pitch不总是朝一个方向飞行,只会随机朝某个方向缓慢飞行。随后切换到offboard模式进行定位调试,如果设置指定高度后飞行器一直飞行不到指定高度,调试定位的参数。
-
发表了主题帖:
成本400元,DIY一个高刷新率热成像相机
在市面上开源的热成像作品中,有一部分颜值高,但**分辨率太低**;也有一部分把分辨率提高了,但使用起来**却不太流畅**。
基于此,作者本人结合二者的优势,设计了一款**热成像相机——LiThermal**,成本算下来只要400出头,还具备了万全的功能。
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1730081760045-ac71b06c-eeb3-47df-920b-d328846fac9d-image.png)
- 拍照
- 录像
- 查看相册
- 查看温度最大值、最小值、中心值
- 温度统计图
- 修改调色板
- 重定向后台管理页面,电脑访问
这款热成像相机不仅拥有高分辨率及丝滑流畅的拍摄,在主控全志T113的加持下,UI界面的几乎所有动画都能达到**90Hz刷新率**,并支持随意的动画打断,最重要的是,作者将热成像相机的**软硬件全部开源**了出来!
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1731460913551-%E5%BC%80%E6%9C%BA%E7%95%8C%E9%9D%A2.gif)
热成像相机开机动画演示
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1731460937210-%E6%B8%A9%E5%BA%A6%E5%9B%BE%E8%B0%B1.gif)
PCB温度检测演示
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1731460950031-%E8%BF%9C%E8%B7%9D%E7%9B%B8%E6%9C%BA%E6%A3%80%E6%B5%8B.gif)
远距相机检测演示
# 热成像相机功能
相机不仅支持高清拍照功能,而且在拍照的同时能够即时捕捉并显示全屏范围内的温度数据,为用户提供直观的温度分布视图,拍摄后的照片和视频都可以在相册中查看。
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1731460963424-%E7%9B%B8%E5%86%8C.gif)
此外,该设备还提供了温度数据的最大值、最小值以及中心值的查看功能,帮助快速识别温度异常区域,为了更直观地展示温度变化趋势,设备还贴心配备了温度统计图功能。
为了满足不同用户的需求,设备还支持自定义调色板,根据个人偏好或特定应用场景调整色彩显示方案,重定向后台管理页面的功能,可以通过电脑访问后台管理系统,实现更高级的设置和数据管理操作。
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1731460972978-%E7%9B%B8%E6%9C%BA%E8%AE%BE%E7%BD%AE1.gif)
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1731460972963-%E7%9B%B8%E6%9C%BA%E8%AE%BE%E7%BD%AE.gif)
# 系统配置
作者直接为相机在全志Tina Linux系统上基于LVGL8设计一套全新的UI界面,并顺利的在2.4寸的320x240分辨率TFT LCD屏幕上以最高94.3 Hz的刷新率流畅运行。
在热成像方面,该设备表现尤为突出,刷新率达到了25Hz,能够实时捕捉温度变化,测温范围也覆盖到0-106.4 ℃,零下的条件下作者未进行测试,但问题不大,基本满足了多种应用场景的需求。传感器方面,设备采用了160*120分辨率的传感器,确保了温度数据的精确捕捉。
- 主控:全志T113
- 存储:SIP 128MB
- 屏幕:2.4寸 TFT LCD, 320x240 @ 94.3 Hz
- 热成像刷新率:25Hz
- 测温范围:0-106.4 ℃ (零下没试过,据说可以测到-20 ℃)
- 传感器分辨率:160*120
- 操作系统:全志Tina Linux,基于OpenWRT
- GUI:LVGL8
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1731460985951-%E5%8A%A8%E7%94%BB%E6%89%93%E6%96%AD.gif)
复刻注意事项:本作品难度较大,想要完整复刻需要能够焊接0402元件和0.3毫米间距的QFP引脚,并且需要有一定Linux系统使用经验和计算机网络基础,请做好心理准备。
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1731460995553-%E7%84%8A%E6%8E%A5.gif)
- 2024-09-02
-
发表了主题帖:
全志Linux磁盘操作基础命令
# 磁盘操作
## fdisk命令
> fidsk是一个用来创建和维护磁盘设备分区的一个实用工具。
```bash
[ubuntu@book:~]$ fdisk -l //列出当前系统所有的磁盘设备
[ubuntu@book:~]$ fdisk /dev/sdc //操作设备节点为 /dev/sdc的一个设备。
```
* p : 显示所有的分区。
* d: 删除分区。
* n: 创建一个新的分区。
* t : 更改分区类型。
* w: 保存修改并退出。
* m: 显示帮助信息。
## gparted
> 图形化分区操作工具。
```bash
[ubuntu@book:~]$ sudo apt install gparted
```
## mkfs.fat
> 格式化分区为 fat32类型
```bash
[ubuntu@book:~]$ sudo mkfs.fat /dev/sdc1
```
## mkfs.ext2/3/4
> 格式化文件系统类型为 Linux EXT类型。
```bash
[ubuntu@book:~]$ sudo mkfs.ext4 /dev/sdc2
```
## mount命令
> 挂载系统外的磁盘分区或者目录等。
```ba
[ubuntu@book:~]$ sudo mount -t vfat /dev/sdc1 /mnt
[ubuntu@book:~]$ sudo umont /mnt
[ubuntu@book:~]$ sudo umont /dev/sdc1
[ubuntu@book:~]$ sudo mount -t ext4 /dev/sdc2 /mnt
[ubuntu@book:~]$ sudo umont /mnt
[ubuntu@book:~]$ sudo umont /dev/sdc2
```
## df命令
> 显示Linux上文件系统磁盘使用的占用情况。
```bash
[ubuntu@book:~]$ df //显示文件系统的磁盘使用情况。
[ubuntu@book:~]$ df -h //以易读的形式展示输出磁盘的使用情况
```
## du命令
> 用于显示目录或文件大小。
```bash
[ubuntu@book:~]$ du //显示当前目录下的所有文件及目录大小。
[ubuntu@book:~]$ du -h //以比较直观的方式显示所有文件及目录大小。
[ubuntu@book:~]$ du dir -h //以比较直观方式显示dir目录大小。
[ubuntu@book:~]$ du file1 //显示file文件大小。
[ubuntu@book:~]$ du dir -h --max-depth=1 //以比较直观的方式显示dir目录并只显示目录深度为1级。
```
## dd命令
> dd命令用于数据的读取转换等操作。
```ba
[ubuntu@book:~]$ dd --help //查看dd帮助命令。
[ubuntu@book:~]$ dd if=/dev/zero of=dd.img count=1024 bs=1M //生产一个1G大小的dd.img镜像
[ubuntu@book:~]$ mkfs.ext4 dd.img //格式化为 ext4文件系统类型。
[ubuntu@book:~]$ sudo mount -t ext4 dd.img /mnt //挂载到 /mnt目录下,之后进行操作。
[ubuntu@book:~]$ sudo umount /mnt //操作完成后卸载该挂载的镜像。
```
-
发表了主题帖:
8月全志芯片开源项目分享合集
# T113环境温湿度采集与监控板
### 作者:lin_xiaoyan
本项目的基本原理是由下位机采集温湿度信息到监控端,并由T113读取SHT30高精度温湿度芯片,UI采用高仿HomeAssistant的样式显示室内温湿度情况,同时通过网络获取天气、室外温湿度,Lottie动画图标显示当前天气情况和室外温湿度参数,可谓可视化拉满。
T113监控端跑的Tina-Linux系统,Gui使用LVGL,采用10.1寸超大屏,采用高斯模糊(毛玻璃特效)背景,有高大上的动画时钟显示,它可以是天气时钟,也可以是家庭中控,通过TCP通信(后续改成MQTT),可以进行智能家居控制。
在PCB设计时对一些功能进行取舍,去掉了TV功能和TP(电阻触摸)功能,在传输完成后上位机会进行休眠,休眠功耗仅达uA级。
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1725241538415-577ab4f5-714a-483f-88aa-1decdccacaef-image.png)
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1725241557336-e9bbfb9d-53d7-4a19-b277-a69eca8cc077-image.png)
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1725241560285-79cad74f-faad-4905-b169-f0931af2b8b4-image.png)
# 全志T113-S3自稳PX4飞控
### 作者:光轮电子
使用全志T113-S3处理器制作的PX4飞控,飞控板载主控、IMU、磁力计、气压计以及其它常见的元件,并由T113直接输出PWM控制飞行器,PCB孔距30x30,可以与市面上常见的四合一电调搭配使用。
远程通信方面,T113通过USB直接与WiFi模块连接,接收机则是连接串口通过SBUS和T113直接连接,收到指令信号后由4路PWM直接给电机控制输出,以实现自稳飞行的功能。
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1725241595827-228959ee-4194-4b68-97c1-6342c87dd68e-image.png)
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1725241603653-5e972cf5-5700-490e-b2c7-a736619d82d9-image.png)
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1725241608803-a17fda04-f865-4f11-9c95-2eca2492f5c1-image.png)
# 太极派LVGL版USB口袋显示屏
### 作者:新范者1986
上一期开源项目T113太极派的搬运工,作者在上面适配了开源软件参考,可以仅连接一根USB线,实现养宠物、qq放入、视频播放、歌词流动等副屏功能,最重要的是,该显示屏支持多屏同时连接,不受hdmi口限制。
副屏使用全志t113-s3为主控,分辩率480*480,支持win10/win11系统的扩展屏,支持电容屏触,电容屏hid触摸屏软件开发中 。
该项目有开源了windows驱动、lvgl界面app应用、tina Linux内核驱动的源码。
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1725241619978-93be30f9-df9a-42e0-b642-11980686078b-image.png)
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1725241632371-f4bbe6db-9d49-493b-8337-64cb9cfdc1e4-image.png)
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1725241635230-207f6f81-717f-4dcc-9c63-34abd62563eb-image.png)
# linux-card
### 作者:syske
作者本人的第一块全志F1C200S的实验板,踩了很多坑,更新了六个版本的PCB才所完美解决所有的问题,板子成功驱动、u-boot编译运行成功、linux成功运行。
板子本身并没有什么很突出的亮点,但很适合作为初学者小白进行Linux开发板设计学习的第一块开发板,成本25元就可以完整走一趟板子的设计、打板、焊接、编译、烧录,直到完全熟悉整个PCB制作过程。
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1725241688093-84b9eb85-ed12-4565-8656-e39e2baced51-image.png)
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1725241699891-2c6a1e6c-97cd-4372-bbf7-d88fda21150f-image.png)
- 2024-08-27
-
发表了主题帖:
为了学习内核开发,大佬手搓了一个轻量级操作系统YiYiYa OS
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1724635206585-705f822f-02f3-4227-abfc-cfa47c633ff5.png)
YiYiYa操作系统是一个朴实无华的操作系统,追求快速开发,最小实现,同时遵循SOLID原则。编码简洁明了,非常适合学习操作系统的同学。目前既有宏内核,也逐步实现了微内核架构,未来将会是混合内核。
目前YiYiYa OS支持很多种架构和平台,包括ARM-Cortex A7系列的V3s、T113-S3等芯片、ARM9内核的F1C系列芯片以及部分RISC-V内核芯片,所有移植教程及方法都在Github上公开。
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1724635230142-2024-01-20-15-53-16.png)
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1724635236167-2024-01-20-16-37-45-1.png)
# 系统架构
架构从上而下分层设计,同一层从左到右为模块。越下层与用户离得远,就会变得越通用越公共。分层设计便于扩展,方便维护,每一层的职责单一,让每一层依赖都是固定,不会胡乱调用(实现低耦合)。同时高层依赖底层,依赖接口,不依赖具体细节实现,底层的改动不至于影响太大,这就是基本的设计原则。
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1724635252850-arch.png)
## 应用层
应用层直接和用户操作打交道。有图形界面和shell命令交互。此层面对用户开发应用和常用库,对应源码目录为app。
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1724635283122-2024-01-21-17-19-45.png)
## 应用层
应用层直接和用户操作打交道。有图形界面和shell命令交互。此层面对用户开发应用和常用库,对应源码目录为app。
## 接口层(C标准库)
接口层为操作系统内核向外核提供的基本功能。通过libc和libmusl可选,实现基本的标准库,方便开发者直接移植代码或者适配应用app。
## 内核层
内核层则为核心领域业务。每一小个模块都提供基本的功能。按各自的职责划分,分别对应与传统内核层则为核心领域业务。每一小个模块都提供基本的功能。按各自的职责划分,分别对应与传统操作系统的内存管理、进程通信、文件系统、进程通信、等等。在演进操作系统时候,可以修改这里,对应源码目录为kernel。
## 基础设施层(公共层和硬件层)
将硬件CPU相关和平台模块相关的单独作为公用库使用。目录为arch、platform、libs/kernel。
在移植适配的时候基本上和这些模块打交道比较多,同时单独一个库可以提高内核代码复用性,内核有专门的内核库,以便增加代码安全,减少出错。
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1724635311995-828c40bf93c9-resized.jpg)
* 支持荔枝派系列开发板,全志V3s,F1C200S,T113-S3等芯片,RISC-V的支持在规划中
* 支持uboot引导内核,同时支持不需要uboot引导模式
* 支持lcd屏幕,st7789、st7735、通用40pin rgb屏幕
* 支持vfs,fat32文件格式
* 支持elf文件,ipc管道等
* 支持ahci、gpio、i2c、spi驱动等
# 系统移植教程
作者本人及团队自制的开源掌机FunKey所采用的操作系统就是基于Linux内核和YiYiYa OS,YiYiYa OS内提供了丰富的桌面系统和游戏开发框架,其非常简易的移植过程,也让开发者可以轻松地运用YiYiYa OS裸机开发自己的设备。
以基于T113-S3开发的FunKey掌机为例,在进行平台移植时,需要在新增的duck/platform目录下添加gpio.h头文件以及编写 init.c 包含基本的串口功能,然后再执行以下三个步骤,就可以轻松运行起YiYiYa OS:
克隆项目
```
git clone https://github.com/evilbinary/YiYiYa.git
make pull
```
安装构建工具
```
pip install yymake
```
编译
```
ya -m debug -p t113-s3 -r t113-s3
```
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1724635467911-jkjkk.gif)
#其它功能及开源资料获取
整体来说,YiYiYa操作系统非常轻量,支持POSIX操作系统标准协议,同时还支持扩展,方便使用其他语言开发模块。在应用层,可以通过libc/libmusl标准库和常用的库开发应用;在内核层可以调用基本内核模块的功能和共用库,开发者可以在DIY过程中自行选择。
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1724635490893-2024-01-20-16-25-29.png)
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1724635500117-2024-01-20-16-26-27.png)
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://bbs.aw-ol.com/assets/uploads/files/1724635505670-2024-01-20-16-26-56.png)