Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
2024-RSOC
【2024-RSOC】DAY4 设备和驱动层
发布于 2024-07-26 00:16:22 浏览:477
订阅该版
[tocm] > 本文含代码约12w字,注意观看时间 ## I/O框架概念 首先我们需要知道,RT-Thread系统是针对各种微控制器的一个系统,那么就会碰到一个不可避免的问题:如何驱动不同类型、不同厂商的不同型号的设备。 RT-Thread最初的思路是:管你什么设备,只要有可以调用的API,那我们就可以把它连接到RT-Thread的驱动层封装为一个RT格式的驱动。 可是一些厂商还有自己的框架,如果仅仅使用单层封装,难免会出现一些问题。 最终,RT-Thread的I/O驱动模型演变为由设备管理层、设备驱动框架层、设备驱动层。 ![](https://www.rt-thread.org/document/site/rt-thread-version/rt-thread-standard/programming-manual/device/figures/io-dev.png) RT-Thread针对一些未使用设备框架的设备,可以直接使用其自身的框架,而针对使用了设备框架的设备,如I2C、SPI、UART等设备,RT-Thread做了特殊优化。可以在其原本的基础上实现RT-Thread的功能。 ## API记录 RT-Thread用于操作设备的结构体`rt_device`由基类`rt_object`派生而来,随后派生出多种设备操作类: ![](https://www.rt-thread.org/document/site/rt-thread-version/rt-thread-standard/programming-manual/device/figures/io-parent.png) `rt_device`的代码段如下: ```c struct rt_device { struct rt_object parent; /* 内核对象基类 */ enum rt_device_class_type type; /* 设备类型 */ rt_uint16_t flag; /* 设备参数 */ rt_uint16_t open_flag; /* 设备打开标志 */ rt_uint8_t ref_count; /* 设备被引用次数 */ rt_uint8_t device_id; /* 设备 ID,0 - 255 */ /* 数据收发回调函数 */ rt_err_t (*rx_indicate)(rt_device_t dev, rt_size_t size); rt_err_t (*tx_complete)(rt_device_t dev, void *buffer); const struct rt_device_ops *ops; /* 设备操作方法 */ /* 设备的私有数据 */ void *user_data; }; typedef struct rt_device *rt_device_t; ``` 目前RT-Thread支持如下几种设备类型: ```c RT_Device_Class_Char /* 字符设备 */ RT_Device_Class_Block /* 块设备 */ RT_Device_Class_NetIf /* 网络接口设备 */ RT_Device_Class_MTD /* 内存设备 */ RT_Device_Class_RTC /* RTC 设备 */ RT_Device_Class_Sound /* 声音设备 */ RT_Device_Class_Graphic /* 图形设备 */ RT_Device_Class_I2CBUS /* I2C 总线设备 */ RT_Device_Class_USBDevice /* USB device 设备 */ RT_Device_Class_USBHost /* USB host 设备 */ RT_Device_Class_SPIBUS /* SPI 总线设备 */ RT_Device_Class_SPIDevice /* SPI 设备 */ RT_Device_Class_SDIO /* SDIO 设备 */ RT_Device_Class_Miscellaneous /* 杂类设备 */ ``` 设备的创建:`rt_device_t rt_device_create(int type, int attach_size);` 如果创建成功,该函数将返回一个`rt_device`的设备句柄,否则返回`RT_NULL` 设备的注册:`rt_err_t rt_device_register(rt_device_t dev, const char* name, rt_uint8_t flags);` 返回`RT_EOK`或`-RT_ERROR`,用于判断是否注册成功。 小练手: ```c #include
#include
static int rt_device_test_init(void) { rt_device_t test_dev = rt_device_create(RT_Device_Class_Char, 0); if (!test_dev) { rt_kprintf("create device failed\n"); return -RT_ERROR; } if (rt_device_register(test_dev, "test_dev", RT_DEVICE_FLAG_RDWR) != RT_EOK) { rt_kprintf("register device failed\n"); return -RT_ERROR; } return RT_EOK; } MSH_CMD_EXPORT(rt_device_test_init, create a test device); ``` 烧录运行后在串口执行`list device`即可看见我们的设备。 RT-Thread为设备准备了这么几个操作方法: ```c struct rt_device_ops { /* common device interface */ rt_err_t (*init) (rt_device_t dev); rt_err_t (*open) (rt_device_t dev, rt_uint16_t oflag); rt_err_t (*close) (rt_device_t dev); rt_size_t (*read) (rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size); rt_size_t (*write) (rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size); rt_err_t (*control)(rt_device_t dev, int cmd, void *args); }; ``` 实际创建驱动的过程中,只需要绑定一下这些操作就行了。 接下来介绍GPIO、I2C和SPI设备的调用: > 接下来的内容讲的就少了,主要是当时笔记记录的较少,可以看[RT-Thread 文档中心](https://www.rt-thread.org/document/site/#/)补充 ## GPIO设备 GPIO在RT-Thread中使用PIN类型操作,主要有以下几个操作: | 函数 | 描述 | | ------------------- | -------------------- | | rt_pin_get() | 获取引脚编号 | | rt_pin_mode() | 设置引脚模式 | | rt_pin_write() | 设置引脚电平 | | rt_pin_read() | 读取引脚电平 | | rt_pin_attach_irq() | 绑定引脚中断回调函数 | | rt_pin_irq_enable() | 使能引脚中断 | | rt_pin_detach_irq() | 脱离引脚中断回调函数 | 火星一号下,获取GPIO引脚通过`GET_PIN()`方法,可以使用宏标记。 PIN的引脚模式主要有以下几种: ```c #define PIN_MODE_OUTPUT 0x00 /* 输出 */ #define PIN_MODE_INPUT 0x01 /* 输入 */ #define PIN_MODE_INPUT_PULLUP 0x02 /* 上拉输入 */ #define PIN_MODE_INPUT_PULLDOWN 0x03 /* 下拉输入 */ #define PIN_MODE_OUTPUT_OD 0x04 /* 开漏输出 */ ``` ## I2C设备 RT-Thread提供了多种查找、操作、读写I2C设备的API。以下为课堂记录(整理后) 查找:`rt_device_t rt_device_find(const char* name);` 数据传输: ```c rt_size_t rt_i2c_transfer(struct rt_i2c_bus_device *bus, struct rt_i2c_msg msgs[], rt_uint32_t num); ``` 消息结构: ```c struct rt_i2c_msg { rt_uint16_t addr; /* 从机地址 */ rt_uint16_t flags; /* 读、写标志等 */ rt_uint16_t len; /* 读写数据字节数 */ rt_uint8_t *buf; /* 读写数据缓冲区指针 */ } ``` ## SPI设备 在星火一号下,SPI设备的挂载:`rt_err_t rt_hw_spi_device_attach(const char *bus_name, const char *device_name, rt_base_t cs_pin);` SPI设备的配置: ```c rt_err_t rt_spi_configure(struct rt_spi_device *device, struct rt_spi_configuration *cfg) ``` `rt_spi_configuration`原型如下: ```c struct rt_spi_configuration { rt_uint8_t mode; /* 模式 */ rt_uint8_t data_width; /* 数据宽度,可取8位、16位、32位 */ rt_uint16_t reserved; /* 保留 */ rt_uint32_t max_hz; /* 最大频率 */ }; ``` 其中,模式包括以下几种: ```c /* 设置数据传输顺序是MSB位在前还是LSB位在前 */ #define RT_SPI_LSB (0<<2) /* bit[2]: 0-LSB */ #define RT_SPI_MSB (1<<2) /* bit[2]: 1-MSB */ /* 设置SPI的主从模式 */ #define RT_SPI_MASTER (0<<3) /* SPI master device */ #define RT_SPI_SLAVE (1<<3) /* SPI slave device */ /* 设置时钟极性和时钟相位 */ #define RT_SPI_MODE_0 (0 | 0) /* CPOL = 0, CPHA = 0 */ #define RT_SPI_MODE_1 (0 | RT_SPI_CPHA) /* CPOL = 0, CPHA = 1 */ #define RT_SPI_MODE_2 (RT_SPI_CPOL | 0) /* CPOL = 1, CPHA = 0 */ #define RT_SPI_MODE_3 (RT_SPI_CPOL | RT_SPI_CPHA) /* CPOL = 1, CPHA = 1 */ #define RT_SPI_CS_HIGH (1<<4) /* Chipselect active high */ #define RT_SPI_NO_CS (1<<5) /* No chipselect */ #define RT_SPI_3WIRE (1<<6) /* SI/SO pin shared */ #define RT_SPI_READY (1<<7) /* Slave pulls low to pause */ ``` ## 实操 ### 简单的外设调用 在观看老师演示lcd和运动传感器icm20608的使用之后,我突然有了一个想法:将运动传感器的数据输出到LCD屏上。 最终代码如下: ```c #include
#include
#define LOG_TAG "icm.lcd" #define LOG_LVL LOG_LVL_DBG #include
#include
#include
#include
static struct rt_messagequeue mq; static rt_int16_t msg_pool[3072]; static void icm_thread_entry(void *parameter) { icm20608_device_t dev = RT_NULL; const char *i2c_bus_name = "i2c2"; int count = 0; rt_err_t result; /* 初始化 icm20608 传感器 */ dev = icm20608_init(i2c_bus_name); if (dev == RT_NULL) { LOG_E("The sensor initializes failure"); } else { LOG_D("The sensor initializes success"); } result = icm20608_calib_level(dev, 10); if (result == RT_EOK) { LOG_D("The sensor calibrates success"); LOG_D("accel_offset: X%6d Y%6d Z%6d", dev->accel_offset.x, dev->accel_offset.y, dev->accel_offset.z); LOG_D("gyro_offset : X%6d Y%6d Z%6d", dev->gyro_offset.x, dev->gyro_offset.y, dev->gyro_offset.z); } else { LOG_E("The sensor calibrates failure"); icm20608_deinit(dev); } while (1) { rt_int16_t par[6]; result = icm20608_get_accel(dev, par, par+1, par+2); if (result == RT_EOK) { LOG_D("current accelerometer: accel_x%6d, accel_y%6d, accel_z%6d", par[0], par[1], par[2]); } else { LOG_E("The sensor does not work"); } result = icm20608_get_gyro(dev, par+3, par+4, par+5); if (result == RT_EOK) { LOG_D("current gyroscope : gyros_x%6d, gyros_y%6d, gyros_z%6d", par[3], par[4], par[5]); } else { LOG_E("The sensor does not work"); break; } rt_thread_mdelay(100); rt_mq_send(&mq, par, sizeof(par)); } } static void lcd_entry(void*parameter) { rt_device_t lcd = rt_device_find("lcd"); if (lcd == RT_NULL) { LOG_E("lcd device not found"); return; } lcd_clear(BLACK); lcd_set_color(BLACK, WHITE); while (1) { rt_int16_t par[6]; lcd_clear(BLACK); rt_mq_recv(&mq, par,sizeof(par) ,RT_WAITING_FOREVER); LOG_D("malibox recv: %d %d %d %d %d %d", par[0], par[1], par[2], par[3], par[4], par[5]); lcd_show_string(50, 10, 24,"%-06d",par[0]); lcd_show_string(50, 40, 24,"%-06d",par[1]); lcd_show_string(50, 70, 24,"%-06d",par[2]); lcd_show_string(50, 100, 24,"%-06d",par[3]); lcd_show_string(50, 130, 24,"%-06d",par[4]); lcd_show_string(50, 160, 24,"%-06d",par[5]); } } static int icm_lcd(void) { rt_err_t mq_re = rt_mq_init(&mq, "mq", msg_pool, sizeof(rt_int16_t)*6,3072, RT_IPC_FLAG_PRIO); if (mq_re != RT_EOK) { LOG_E("mailbox init failed"); return -RT_ERROR; } rt_thread_t icm = rt_thread_create("icm", icm_thread_entry, RT_NULL, 1024, 20, 50); if(icm == RT_NULL) { LOG_E("icm thread create failed"); return -RT_ERROR; } rt_thread_startup(icm); rt_thread_t lcd = rt_thread_create("lcd", lcd_entry, RT_NULL, 1024, 15, 50); if(lcd == RT_NULL) { LOG_E("lcd thread create failed"); return -RT_ERROR; } rt_thread_startup(lcd); return RT_EOK; } MSH_CMD_EXPORT(icm_lcd, icm lcd demo); ``` ### 构建伪驱动 首先在borad下修改kconfig文件,在728行后加入以下内容: ``` config BSP_USING_TEST bool "Enable Test Driver" default n ``` 随后,我们可以在menuconfig的`(Top) → Hardware Drivers Config → Board extended module Driver`找到新增的项,打开该项。 接下来我们在`libraries\HAL_Drivers\drivers\`新建一个名为`drv_test.c`的文件 输入以下内容: ```c #include
#include
#if defined(BSP_USING_TEST) #define DRV_DEBUG #define LOG_TAG "drv.test" #include
static rt_err_t dev_test_init(rt_device_t dev){ LOG_I("test device init"); return RT_EOK; } static rt_err_t dev_test_open(rt_device_t dev,rt_uint16_t oflag){ LOG_I("test device open flag=%d",oflag); return RT_EOK; } static rt_err_t dev_test_close(rt_device_t dev){ LOG_I("test device close"); return RT_EOK; } static rt_err_t dev_test_read(rt_device_t dev,rt_off_t pos,void* buffer,rt_size_t size){ LOG_I("test device read pos=%d size=%d",pos,size); return RT_EOK; } static rt_err_t dev_test_write(rt_device_t dev,rt_off_t pos,const void* buffer,rt_size_t size){ LOG_I("test device write pos=%d size=%d",pos,size); return RT_EOK; } static rt_err_t dev_test_control(rt_device_t dev,int cmd,void* args){ LOG_I("test device control cmd=%d",cmd); return RT_EOK; } int rt_drv_test_init(void){ rt_device_t test_dev = rt_device_create(RT_Device_Class_Char, 0); if(!test_dev){ LOG_E("test device failed"); return -RT_ERROR; } test_dev->init = dev_test_init; test_dev->open = dev_test_open; test_dev->close = dev_test_close; test_dev->read = dev_test_read; test_dev->write = dev_test_write; test_dev->control = dev_test_control; if(rt_device_register(test_dev,"test_dev",RT_DEVICE_FLAG_RDWR)!=RT_EOK){ LOG_E("register device failed"); return -RT_ERROR; } return RT_EOK; } INIT_BOARD_EXPORT(rt_drv_test_init); #endif ``` 最后,我们在`applications\`目录下新建文件,写入: ```c #include
#include
#include
#include
#define LOG_TAG "test.drv" #define LOG_LVL LOG_LVL_DBG #include
static int dev_test_app(void){ rt_device_t test_dev = rt_device_find("test_dev"); if(!test_dev){ LOG_E("find device failed"); return -RT_ERROR; } rt_device_open(test_dev,RT_DEVICE_OFLAG_RDWR); rt_device_read(test_dev,0,RT_NULL,0); rt_device_write(test_dev,0,RT_NULL,0); rt_device_control(test_dev,RT_DEVICE_OFLAG_RDWR,RT_NULL); rt_device_close(test_dev); return RT_EOK; } MSH_CMD_EXPORT(dev_test_app, test device); ``` 这样子,我们的驱动就完成了。
0
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
Lzh200
这家伙很懒,什么也没写!
文章
4
回答
0
被采纳
0
关注TA
发私信
相关文章
推荐文章
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
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部