Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
RT-Thread一般讨论
【RSOC - 2024】DAY4 设备驱动
发布于 2024-07-29 19:48:25 浏览:391
订阅该版
[tocm] # 一、I/O 设备模型 ## I/O 设备模型框架 应用程序通过 I/O 设备管理接口获得正确的设备驱动,然后通过这个设备驱动与底层 I/O 硬件设备进行数据(或控制)交互。 [![pkqWpAP.md.png](https://s21.ax1x.com/2024/07/28/pkqWpAP.md.png)](https://imgse.com/i/pkqWpAP) ### 优点 - **解耦**:设备硬件操作代码与应用程序代码分离,降低了系统的耦合性,提高了代码的可维护性和可扩展性。 - **抽象化**:设备驱动框架层提供了对硬件设备的抽象,使得不同厂家的设备可以通过统一的接口进行访问。 - **灵活性**:设备驱动的升级或更换不需要修改上层应用代码,提高了系统的灵活性和稳定性。 ### 设备驱动框架层 对同类硬件设备驱动的抽象,将不同厂家的同类硬件设备驱动中相同的部分抽取出来,将不同部分留出接口,由驱动程序实现。 ### 设备驱动层 - 设备驱动根据设备模型定义,创建出具备硬件访问能力的设备实例,将该设备通过 `rt_device_register()` 接口注册到 I/O 设备管理器中。 - 应用程序通过 `rt_device_find()` 接口查找到设备,然后使用 I/O 设备管理接口来访问硬件。 [![pkqWkcQ.png](https://s21.ax1x.com/2024/07/28/pkqWkcQ.png)](https://imgse.com/i/pkqWkcQ) # 二、I2C 总线设备 ## 特点 1. 传输数据仅需两根信号线,一根是双向数据线 SDA(serial data),另一根是双向时钟线 SCL(serial clock)。 2. SPI 总线有两根线分别用于主从设备之间接收数据和发送数据,而 I2C 总线只使用一根线进行数据收发(但实际上是两根线,这里可能是指SPI和I2C在数据传输方式上的区别)。 3. 允许同时有多个主设备存在,每个连接到总线上的器件都有唯一的地址,主设备启动数据传输并产生时钟信号,从设备被主设备寻址。 4. 任何器件既可以作为主机也可以作为从机,但同一时刻只允许有一个主机。 ## 传输速度 - 标准模式(Standard Mode):100 Kbps - 快速模式(Fast Mode):400 Kbps - 高速模式(High speed mode):3.4 Mbps - 超快速模式(Ultra fast mode):5 Mbps [![pkqWKhT.png](https://s21.ax1x.com/2024/07/28/pkqWKhT.png)](https://imgse.com/i/pkqWKhT) ## 数据传输格式 ### 传输过程 #### 开始条件 SCL为高电平时,主机将SDA拉低,表示数据传输开始。 #### 从机地址 主机发送的第一个字节为从机地址,高七位为地址,最低位为R/W读写控制位(1读0写)。 - 如果是 10 位地址模式,第一个字节的头 7 位是 11110XX 的组合,其中最后两位(XX)是 10 位地址的两个最高位,第二个字节为 10 位从机地址的剩下8位。 [![pkqWl3F.md.png](https://s21.ax1x.com/2024/07/28/pkqWl3F.md.png)](https://imgse.com/i/pkqWl3F) #### 应答信号 每传输完成一个字节的数据,接收方就需要回复一个 ACK(acknowledge)。写数据时由从机发送 ACK,读数据时由主机发送 ACK。当主机读到最后一个字节数据时,可发送 NACK(Not acknowledge)然后跟停止条件。 #### 数据 从机地址发送完后可能会发送一些指令(依从机而定),然后开始传输数据,由主机或者从机发送,每个数据为 8 位,数据的字节数没有限制。 #### 重复开始条件 在一次通信过程中,主机可能需要和不同的从机传输数据或者需要切换读写操作时,主机可以再发送一个开始条件。 #### 停止条件 在 SDA 为低电平时,主机将 SCL 拉高并保持高电平,然后在将 SDA 拉高,表示传输结束。 # 三、SPI 一种高速、全双工、同步通信总线,常用于短距离通讯,主要应用于 EEPROM、FLASH、实时时钟、AD 转换器、还有数字信号处理器和数字信号解码器之间。SPI 一般使用 4 根线通信,如下图所示: [![pkqWa4K.png](https://s21.ax1x.com/2024/07/28/pkqWa4K.png)](https://imgse.com/i/pkqWa4K) - **MOSI** – 主机输出 / 从机输入数据线(SPI Bus Master Output/Slave Input)。 - **MISO** – 主机输入 / 从机输出数据线(SPI Bus Master Input/Slave Output)。 - **SCLK** – 串行时钟线(Serial Clock),主设备输出时钟信号至从设备。 - **CS** – 从设备选择线 (Chip select),也叫 SS、CSB、CSN、EN 等,主设备输出片选信号至从设备。 ## 一主多从 通信由主设备发起,主设备通过 CS 选择要通信的从设备,然后通过 SCLK 给从设备提供时钟信号,数据通过 MOSI 输出给从设备,同时通过 MISO 接收从设备发送的数据。 主设备通过控制 CS 引脚对从设备进行片选,一般为低电平有效。任何时刻,一个 SPI 主设备上只有一个 CS 引脚处于有效状态,与该有效 CS 引脚连接的从设备此时可以与主设备通信。 ## 工作时序 SPI 的工作时序模式由 CPOL(Clock Polarity,时钟极性)和 CPHA(Clock Phase,时钟相位)之间的相位关系决定。 - **CPOL**:表示时钟信号的初始电平的状态,CPOL 为 0 表示时钟信号初始状态为低电平,为 1 表示时钟信号的初始电平是高电平。 - **CPHA**:表示在哪个时钟沿采样数据,CPHA 为 0 表示在首个时钟变化沿采样数据,而 CPHA 为 1 则表示在第二个时钟变化沿采样数据。 4种工作时序模式: 1. CPOL=0,CPHA=0 2. CPOL=0,CPHA=1 3. CPOL=1,CPHA=0 4. CPOL=1,CPHA=1 [![pkqWyDA.png](https://s21.ax1x.com/2024/07/28/pkqWyDA.png)](https://imgse.com/i/pkqWyDA) # 四、RT-Thread ulog 日志组件 以下仅为个人总结,具体可参考以下文章:https://blog.csdn.net/HiWangWenBing/article/details/138403986?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522172214983016800185899867%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=172214983016800185899867&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-1-138403986-null-null.142^v100^pc_search_result_base1&utm_term=ulog%20rtthread&spm=1018.2226.3001.4187 ## 简介 ulog 是一个非常简洁、易用的 C/C++ 日志组件,第一个字母 u 代表 μ,即微型的意思。它能做到最低ROM<1K, RAM<0.2K的资源占用。ulog 不仅有小巧体积,同样也有非常全面的功能,其设计理念参考的是另外一款 C/C++ 开源日志库:EasyLogger(简称 elog),并在功能和性能等方面做了非常多的改进。主要特性如下: - 日志输出的后端多样化,可支持例如:串口、网络,文件、闪存等后端形式。 - 日志输出被设计为线程安全的方式,并支持异步输出模式。 - 日志系统高可靠,在中断 ISR 、Hardfault 等复杂环境下依旧可用。 - 日志支持运行期 / 编译期设置输出级别。 - 日志内容支持按关键词及标签方式进行全局过滤。 ## 架构 [![pkqWj8U.md.png](https://s21.ax1x.com/2024/07/28/pkqWj8U.md.png)](https://imgse.com/i/pkqWj8U) - **前端**:提供syslog和 LOG_X 两类 API 接口。 - **核心**:将上层传递过来的日志,按照不同的配置要求进行格式化与过滤然后生成日志帧,最终通过不同的输出模块,输出到最底层的后端设备上。 - **后端**:接收到核心层发来的日志帧后,将日志输出到已经注册的日志后端设备上,例如:文件、控制台、日志服务器等等。 ## 配置 路径: - **RT-Thread Components → Utilities → Enable ulog** ## 日志级别 [![pkqfSKJ.md.png](https://s21.ax1x.com/2024/07/28/pkqfSKJ.md.png)](https://imgse.com/i/pkqfSKJ) ## 使用方法 在文件顶部定义 `LOG_TAG` 宏和日志级别: ```c #define LOG_TAG "example" // 该模块对应的标签。不定义时,默认:NO_TAG #define LOG_LVL LOG_LVL_DBG // 该模块对应的日志输出级别。不定义时,默认:调试级别 #include
// 必须在 LOG_TAG 与 LOG_LVL 下面 ``` ## 常用API 使用 LOG_X API 输出不同级别的日志 /* output different level log by LOG_X API */ ```c LOG_D("LOG_D(%d): RT-Thread is an open source IoT operating system from China.", count); LOG_I("LOG_I(%d): RT-Thread is an open source IoT operating system from China.", count); LOG_W("LOG_W(%d): RT-Thread is an open source IoT operating system from China.", count); LOG_E("LOG_E(%d): RT-Thread is an open source IoT operating system from China.", count); ``` - 注:这些日志输出 API 均支持 printf 格式,并且会在日志末尾自动换行。 # 五、练习 - 练习1(设备注册) ```c #include
#include
static int rt_dev_test_init(void) { rt_device_t test_dev = rt_device_create(RT_Device_Class_Char, 0); if(!test_dev) { rt_kprintf("test_dev create failed\n"); return -RT_ERROR; } if(rt_device_register(test_dev, "test_dev", RT_DEVICE_FLAG_RDWR) != RT_EOK) { rt_kprintf("test_dev register failed\n"); return -RT_ERROR; } return RT_EOK; } MSH_CMD_EXPORT(rt_dev_test_init , rt dev test);//命令行的形式导出 ``` - 练习2(按键触发中断) ```c #include
#include
#include
// #include
#include
#define LOG_TAG "pin.irq" #define LOG_LVL "LOG_LVL_DBG" #define key_up GET_PIN(C, 5) #define key_down GET_PIN(C, 1) #define key_left GET_PIN(C, 0) #define key_right GET_PIN(C, 4) static int key_up_callback(void *args) { uint8_t value = 0; value = rt_pin_read(key_up); if(!value) LOG_I("key up is %d",value); } static int key_down_callback(void *args) { uint8_t value = 0; value = rt_pin_read(key_down); if(!value) LOG_I("key down is %d",value); } static int key_left_callback(void *args) { uint8_t value = 0; value = rt_pin_read(key_left); if(!value) LOG_I("key left is %d",value); } static int key_right_callback(void *args) { uint8_t value = 0; value = rt_pin_read(key_right); if(!value) LOG_I("key right is %d",value); } static void KEY_example(void *p) { rt_pin_mode(key_up, PIN_MODE_INPUT_PULLUP); rt_pin_mode(key_down, PIN_MODE_INPUT_PULLUP); rt_pin_mode(key_left, PIN_MODE_INPUT_PULLUP); rt_pin_mode(key_right, PIN_MODE_INPUT_PULLUP); //选择上升沿触发 rt_pin_attach_irq(key_up, PIN_IRQ_MODE_FALLING, key_up_callback, RT_NULL); rt_pin_attach_irq(key_down, PIN_IRQ_MODE_FALLING, key_down_callback, RT_NULL); rt_pin_attach_irq(key_left, PIN_IRQ_MODE_FALLING, key_left_callback ,RT_NULL); rt_pin_attach_irq(key_right, PIN_IRQ_MODE_FALLING, key_right_callback, RT_NULL); //使能中断 rt_pin_irq_enable(key_up ,PIN_IRQ_ENABLE); rt_pin_irq_enable(key_down,PIN_IRQ_ENABLE); rt_pin_irq_enable(key_left,PIN_IRQ_ENABLE); rt_pin_irq_enable(key_right,PIN_IRQ_ENABLE); return RT_EOK; } ``` -练习3(驱动模块,以I2C为例) ```c /**单个字节的写入**/ #include
#include
void i2c_ sample _ single _ byte _ write(void) { struct rt _i2c_ bus _ device *i2c_ bus;struct rt _i2c_ msg msgs; rt _uint8_t buf[2]; i2c_ bus = (struct rt _i2c_ bus _ device *) rt _ device _ find("i2c2"); if(i2c_ bus == RT_NULL){rt _ kprintf("can't find %s device!\n", "i2c2");} buff[0] = 0x6B;msgs. add r = 0x68; msgs. flags = RT_I2C_WR; msgs. buff=buff;msgs. len = 1; if(rt _i2c_ transfer(i2c_ bus, &msgs, 1) == 1) rt _ kprintf("single byte write success!\n"); else rt _ kprintf("single by te write failed…\n"); } /**多个字节的写入**/ void i2c_ sample _ multi _ byte _ write(void) { struct rt _i2c_ bus _ device *i2c_ bus; i2c_ bus = (struct rt _i2c_ bus _ device *) rt _ device _ find("i2c2"); if(i2c_ bus == RT_NULL) { rt _ kprintf("can't find %s device!\n", "i2c2"); } buff[0] = 0x01; buf[1] = 0x02; buf[2] = 0x03; msgs. add r = 0x68; msgs. flags = RT_I2C_WR; msgs. buff= buff;msgs. le n = 3; if(rt _i2c_ transfer(i2c_ bus, &msgs, 1) == 1) rt _ kprintf("multi byte write success!\n"); else rt _ kprintf("multi by te write failed...\n"); } /**字节的读取**/ void i2c_ sample _ multi _ byte _ write(void) { struct rt _i2c_ bus _ device *i2c_ bus; struct rt_i2c_msg msgs[2]; rt_uint8_t send_buf[1], recv_buf[1]; i2c_ bus = (struct rt _i2c_ bus _ device *) rt _ device _ find("i2c2"); if(i2c_ bus == RT_NULL) { rt _ kprintf("can't find %s device!\n", "i2c2"); } send_buf[0] = 0x6B; recv_buf[0] = 0x6A; msgs[0]. add r = 0x68; msgs[0]. flags = RT_I2C_WR; msgs[0]. buf = send_buf ; msgs. len = 1; msgs[1].addr = 0x68; msgs[1].flags = RT_I2C_RD; msgs[1].buf = recv_buf; msgs[1].len = 1; if(rt _i2c_ transfer(i2c_ bus, msgs, 2) == 2) rt _ kprintf("single byte read: 0x%o2x success!\n", rexv_buf[0]); else rt _ kprintf("single byte read failed...\n"); } ``` # 六、实战 ## 实验内容 - 根据上方的按键代码更改,与星火一号自带的矩阵LED相结合,实现按键切换LED不同闪光,并在串口打印 ##代码部分 ```c //按键部分已在上方展示,此处展示LED简单线程 static void led_matrix_entry_1(void *p) { while (1) { for(int i=0 ; i++ ; i<10) { Set_LEDColor(i, WHITE);/* code */ RGB_Reflash(); rt_thread_delay(100); } for(int i=0 ; i++ ; i<10) { Set_LEDColor(i, DARK);/* code */ RGB_Reflash(); rt_thread_delay(100); } } } static void led_matrix_entry_2(void *p) { while (1) { for(int i=0 ; i++ ; i<10) { Set_LEDColor(i, GREEN);/* code */ RGB_Reflash(); rt_thread_delay(100); } for(int i=0 ; i++ ; i<10) { Set_LEDColor(i, DARK);/* code */ RGB_Reflash(); rt_thread_delay(100); } } } static void led_example_1(void *p) { rt_thread_t Led_thread_1 = rt_thread_create("led_matrix",led_matrix_entry_1,RT_NULL,512,15,10); if(!Led_thread_1) { LOG_I("LED_entry_1 failed"); return 0; } rt_thread_startup(Led_thread_1); rt_thread_delay(200); } static void led_example_2(void *p) { rt_thread_t Led_thread_2 = rt_thread_create("led_matrix",led_matrix_entry_2,RT_NULL,512,14,10); if(!Led_thread_2) { LOG_I("LED_entry_2 failed"); return 0; } rt_thread_startup(Led_thread_2); rt_thread_delay(200); } ``` ## 实验结果 - 1 [![pkL8WRS.md.png](https://s21.ax1x.com/2024/07/29/pkL8WRS.md.png)](https://imgse.com/i/pkL8WRS) - 2 [![pkL84MQ.md.jpg](https://s21.ax1x.com/2024/07/29/pkL84MQ.md.jpg)](https://imgse.com/i/pkL84MQ)
0
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
留矽兮
这家伙很懒,什么也没写!
文章
4
回答
0
被采纳
0
关注TA
发私信
相关文章
1
有关动态模块加载的一篇论文
2
最近的调程序总结
3
晕掉了,这么久都不见layer2的踪影啊
4
继续K9ii的历程
5
[GUI相关] FreeType 2
6
[GUI相关]嵌入式系统中文输入法的设计
7
20081101 RT-Thread开发者聚会总结
8
嵌入式系统基础
9
linux2.4.19在at91rm9200 上的寄存器设置
10
[转]基于嵌入式Linux的通用触摸屏校准程序
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
国产MCU移植系列教程汇总,欢迎查看!
4
机器人操作系统 (ROS2) 和 RT-Thread 通信
5
五分钟玩转RT-Thread新社区
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
热门标签
RT-Thread Studio
串口
Env
LWIP
SPI
AT
Bootloader
Hardfault
CAN总线
FinSH
ART-Pi
USB
DMA
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
FAL
rt-smart
ESP8266
I2C_IIC
WIZnet_W5500
ota在线升级
UART
PWM
cubemx
freemodbus
flash
packages_软件包
BSP
潘多拉开发板_Pandora
定时器
ADC
GD32
flashDB
socket
中断
Debug
编译报错
msh
SFUD
keil_MDK
rt_mq_消息队列_msg_queue
at_device
ulog
C++_cpp
本月问答贡献
踩姑娘的小蘑菇
7
个答案
3
次被采纳
张世争
8
个答案
2
次被采纳
rv666
5
个答案
2
次被采纳
用户名由3_15位
11
个答案
1
次被采纳
KunYi
6
个答案
1
次被采纳
本月文章贡献
程序员阿伟
6
篇文章
2
次点赞
hhart
3
篇文章
4
次点赞
大龄码农
1
篇文章
2
次点赞
ThinkCode
1
篇文章
1
次点赞
Betrayer
1
篇文章
1
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部