rtthread 驱动使用的两种情况
rtthread studio 自动生成
由 rt thread studio 自动生成,无需修改任何文件或者简单定义几个宏即可直接使用的驱动,如 gpio,uart,i2c,spi,sdio 和 eth 等。
使用 rt-thread studio 新建完整版工程时,用户不需要修改任何代码
使用hal库实现
没有对接到设备驱动框架,可直接使用 hal 库函数进行开发的驱动,如 dac,fsmc 等。
使用rtthread studio自动生成的外设驱动
注:增加修改外设通过rt-thread setting和 board.h文件进行配置。配置完成烧写运行均可通过list_device命令进行查看外设驱动是否已经注册。
gpio外设
使用 pin 驱动需要使用 get_pin 获取相应的引脚编号,获取到引脚编号后,可使用 rt_pin_write 等函数来操作引脚。 例如,stm32l475-atk-pandora 开发板的 led 所接的引脚为 pe7,所以修改为
#define led0_pin get_pin(e, //gpio port
7) //gpio pin
usart外设
由于rtthread 默认启用了uart外设进行debug输出;默认使用 uart1 进行输出,若要修改为串口 2 (tx->pa2、rx->pa3)进行输出,则在 board.h 中定义宏 bsp_using_uart2,并将串口 2 对应的引脚信息修改为实际所使用的引脚即可
新增串口只需要在 board.h 文件中定义相关串口的宏定义 bsp_using_uartx 及修改引脚信息即可,新增串口的步骤总结如下
1、新增对应串口的宏定义,如 bsp_using_uart1、bsp_using_uart2等。
2、修改串口 tx/rx 所使用的端口,如 “pa9”、"pa10"等。
3、基于修改控制台章节新增串口 1 的示例如下
#define bsp_using_uart1
#define bsp_uart1_tx_pin "pa9"
#define bsp_uart1_rx_pin "pa10"
#define bsp_using_uart2
#define bsp_uart2_tx_pin "pa2"
#define bsp_uart2_rx_pin "pa3"
如果需要使用串口 dma 只需要在 board.h 文件中定义如下宏即可。
#define bsp_uartx_rx_using_dma
#define bsp_uartx_tx_using_dma
uartx 表示的是哪个串口需要使用 dma,使用的是 dma 的发送还是接收功能。
i2c外设
软件i2c,在 board.h 文件中定义软件 i2c 相关的宏
#define bsp_using_i2c1 /* 使用 i2c1 总线 */
#define bsp_i2c1_scl_pin get_pin(c, 1) /* scl -> pc1 */
#define bsp_i2c1_sda_pin get_pin(d, 6) /* sda -> pd6 */
spi外设
在 board.h 文件中定义 spi 总线相关的宏,本例中使用 spi3 总线,只需定义如下宏即可
#define bsp_using_spi3
在 stm32xxxx_hal_config.h 文件中打开对 spi 的支持,也就是取消掉 hal_spi_module_enabled 这个宏定义的注释,如下所示:
#define hal_spi_module_enabled
定义了 bsp_using_spi3 宏之后,drv_spi.c 文件就会参与编译,该文件只是配置了 spi 的工作方式和传输函数,具体 spi 外设的时钟和引脚的初始化需要借助 stm32cubemx 生成的代码。
例如 stm32l475-atk-pandora 开发板的 spi3 外设连接了一个 lcd 屏幕,所以需要将 cubemx 生成的 spi3 的初始化代码(一般在 stm32_xxxx_hal_msp.c 文件中)复制到自己工程的 board.c 文件的末尾,使之参与编译
void hal_spi_mspinit(spi_handletypedef* hspi)
{
gpio_inittypedef gpio_initstruct = {0};
if(hspi->instance == spi3)
{
/* user code begin spi3_mspinit 0 */
/* user code end spi3_mspinit 0 */
/* peripheral clock enable */
__hal_rcc_spi3_clk_enable();
__hal_rcc_gpioc_clk_enable();
__hal_rcc_gpiob_clk_enable();
/**spi3 gpio configuration
pc11 ------> spi3_miso
pb3 (jtdo-traceswo) ------> spi3_sck
pb5 ------> spi3_mosi
*/
gpio_initstruct.pin = gpio_pin_11;
gpio_initstruct.mode = gpio_mode_af_pp;
gpio_initstruct.pull = gpio_nopull;
gpio_initstruct.speed = gpio_speed_freq_very_high;
gpio_initstruct.alternate = gpio_af6_spi3;
hal_gpio_init(gpioc, &gpio_initstruct);
gpio_initstruct.pin = gpio_pin_3 | gpio_pin_5;
gpio_initstruct.mode = gpio_mode_af_pp;
gpio_initstruct.pull = gpio_nopull;
gpio_initstruct.speed = gpio_speed_freq_very_high;
gpio_initstruct.alternate = gpio_af6_spi3;
hal_gpio_init(gpiob, &gpio_initstruct);
/* user code begin spi3_mspinit 1 */
/* user code end spi3_mspinit 1 */
}
}
如果需要注册更多的 spi 总线设备,只需参考 board.h 文件中 spi 相关的宏定义并拷贝引脚初始化函数即可。
eth外设
eth 设备驱动的开发可总结为如下:
1、新建 rt-thread 完整版项目
2、board.h中定义 bsp_using_eth 和 phy 相关的宏
3、board.c中初始化 eth 相关的引脚和时钟
4、stm32xxxx_hal_config.h中打开 hal 库函数对 eth 的支持
5、board.c 中实现自己的 phy 复位函数
6、配置 lwip 协议栈
定位到工程文件 board.h 中 eth 配置说明部分,按照注释部分的说明分别定义 bsp_using_eth 和 phy 相关的宏,本例中使用板载以太网 phy 芯片为 lan8720a, 所以 eth 相关的宏定义如下 :
#define bsp_using_eth
#ifdef bsp_using_eth
#define phy_using_lan8720a
#endif
定义了 bsp_using_eth 宏之后,drv_eth.c 文件就会参与编译,该文件只是配置了 eth 的工作方式和传输函数等,具体 eth 外设的时钟和引脚的初始化需要借助 stm32cubemx 生成的代码。
将 stm32cubemx 工具生成的 eth 引脚和时钟初始化代码(一般在 stm32_xxxx_hal_msp.c 文件中)复制到自己工程的 board.c 文件的末尾,使之参与编译,如下所示:
void hal_eth_mspinit(eth_handletypedef* heth)
{
gpio_inittypedef gpio_initstruct = {0};
if(heth->instance==eth)
{
/* user code begin eth_mspinit 0 */
/* user code end eth_mspinit 0 */
/* peripheral clock enable */
__hal_rcc_eth_clk_enable();
__hal_rcc_gpioc_clk_enable();
__hal_rcc_gpioa_clk_enable();
__hal_rcc_gpiog_clk_enable();
/**eth gpio configuration
pc1 ------> eth_mdc
pa1 ------> eth_ref_clk
pa2 ------> eth_mdio
pa7 ------> eth_crs_dv
pc4 ------> eth_rxd0
pc5 ------> eth_rxd1
pg11 ------> eth_tx_en
pg13 ------> eth_txd0
pg14 ------> eth_txd1
*/
gpio_initstruct.pin = gpio_pin_1|gpio_pin_4|gpio_pin_5;
gpio_initstruct.mode = gpio_mode_af_pp;
gpio_initstruct.pull = gpio_nopull;
gpio_initstruct.speed = gpio_speed_freq_very_high;
gpio_initstruct.alternate = gpio_af11_eth;
hal_gpio_init(gpioc, &gpio_initstruct);
gpio_initstruct.pin = gpio_pin_1|gpio_pin_2|gpio_pin_7;
gpio_initstruct.mode = gpio_mode_af_pp;
gpio_initstruct.pull = gpio_nopull;
gpio_initstruct.speed = gpio_speed_freq_very_high;
gpio_initstruct.alternate = gpio_af11_eth;
hal_gpio_init(gpioa, &gpio_initstruct);
gpio_initstruct.pin = gpio_pin_11|gpio_pin_13|gpio_pin_14;
gpio_initstruct.mode = gpio_mode_af_pp;
gpio_initstruct.pull = gpio_nopull;
gpio_initstruct.speed = gpio_speed_freq_very_high;
gpio_initstruct.alternate = gpio_af11_eth;
hal_gpio_init(gpiog, &gpio_initstruct);
/* user code begin eth_mspinit 1 */
/* user code end eth_mspinit 1 */
}
}
在 stm32_xxxx_hal_config.h 文件中打开对 eth 的支持,也就是取消掉 hal_eth_module_enabled 这个宏定义的注释,如下所示:
#define hal_eth_module_enabled
实现phy复位函数:
在 drv_eth.c 文件中会调用 phy_reset 函数,该函数需要根据自己的实际情况进行实现,本例中 phy 的复位引脚接在了 pd3 引脚,所以复位函数的实现如下所示 :
#include <rtdevice.h>
#define reset_io get_pin(d, 3)
void phy_reset(void)
{
rt_pin_mode(reset_io, pin_mode_output);
rt_pin_write(reset_io, pin_high);
rt_thread_mdelay(50);
rt_pin_write(reset_io, pin_low);
rt_thread_mdelay(50);
rt_pin_write(reset_io, pin_high);
}
配置 lwip 协议栈
打开 rt-thread settings 文件,在图形化配置界面中左键单击 lwip 图标即可打开 lwip 协议栈的支持(组件开启,相应的图标会高亮)。
使用hal库实现外设驱动
1、使用 rt-thread studio 新建 rt-thread 工程
2、使用 stm32cubemx 配置外设和系统时钟
3、复制 stm32xxxx_hal_msp.c 函数
4、修改 stm32xxxx_hal_config.h 文件,打开相应外设支持。
5、替换 board.c 文件中时钟配置函数
6、使用外设
使用cubemx新建目标板卡工程
将 cubemx 生成的代码 stm32l4xx_hal_msp.c 函数复制到 rt-thread studio 生成的工程中,并参与工程编译。复制完成后的结果如下图所示
由于我们并没有使用 cubemx 生成的工程,所以这里需要将 stm32l4xx_hal_msp.c 文件中 #include “main.h” 替换为 #include “board.h”。
打开 hal 库配置文件对应外设的支持宏
需要在 stm32l4xx_hal_config.h 文件使能相关外设模块
使用hal库在rtthread编写外设功能代码
void adc_init(void)
{
}
驱动开发
i/o设备模型框架
rt-thread 提供了一套简单的 i/o 设备模型框架,如下图所示,它位于硬件和应用程序之间,共分成三层,从上到下分别是 i/o 设备管理层、设备驱动框架层、设备驱动层。
设备驱动层是一组驱使硬件设备工作的程序,实现访问硬件设备的功能。它负责创建和注册 i/o 设备,对于操作逻辑简单的设备,可以不经过设备驱动框架层,直接将设备注册到 i/o 设备管理器中,使用序列图如下图所示,主要有以下 2 点:
1、设备驱动根据设备模型定义,创建出具备硬件访问能力的设备实例,将该设备通过rt_device_register() 接口注册到 i/o 设备管理器中。
2、应用程序通过 rt_device_find() 接口查找到设备,然后使用 i/o 设备管理接口来访问硬件。
对于另一些设备,如看门狗等,则会将创建的设备实例先注册到对应的设备驱动框架中,再由设备驱动框架向 i/o 设备管理器进行注册,主要有以下几点:
1、看门狗设备驱动程序根据看门狗设备模型定义,创建出具备硬件访问能力的看门狗设备实例,并将该看门狗设备通过 rt_hw_watchdog_register() 接口注册到看门狗设备驱动框架中。
2、看门狗设备驱动框架通过 rt_device_register() 接口将看门狗设备注册到 i/o 设备管理器中。
应用程序通过 i/o 设备管理接口来访问看门狗设备硬件。
i/o 设备模型,rt-thread 的设备模型是建立在内核对象模型基础之上的,设备被认为是一类对象,被纳入对象管理器的范畴。每个设备对象都是由基对象派生而来,每个具体设备都可以继承其父类对象的属性,并派生出其私有属性,下图是设备对象的继承和派生关系示意图。
设备对象具体定义如下所示:
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;
i/o 设备类型
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 /* 杂类设备 */
创建和注册 i/o 设备
创建设备
驱动层负责创建设备实例,并注册到 i/o 设备管理器中,可以通过静态申明的方式创建设备实例,也可以用下面的接口进行动态创建:
//返回值为设备句柄类型 非null表示创建成功
rt_device_t rt_device_create(int type, //设备类型
int attach_size); //用户数据大小
调用该接口时,系统会从动态堆内存中分配一个设备控制块,大小为 struct rt_device 和 attach_size 的和,设备的类型由参数 type 设定。
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); //控制操作
};
设备被创建后,需要实现它访问硬件的操作方法。
当此设备不再使用时通过以下函数进行销毁
#### 删除设备
```c
void rt_device_destroy(rt_device_t device);
#### 注册设备
驱动层负责创建设备实例,并注册到 i/o 设备管理器中,可以通过静态申明的方式创建设备实例,也可以用下面的接口进行动态创建:
```c
//返回值非erro,表示注册成功
rt_err_t rt_device_register(rt_device_t dev, //设备句柄
const char* name, //设备名称
rt_uint8_t flags); //设备模式标志
flags 参数支持下列参数 (可以采用或的方式支持多种参数):
#define rt_device_flag_rdonly 0x001 /* 只读 */
#define rt_device_flag_wronly 0x002 /* 只写 */
#define rt_device_flag_rdwr 0x003 /* 读写 */
#define rt_device_flag_removable 0x004 /* 可移除 */
#define rt_device_flag_standalone 0x008 /* 独立 */
#define rt_device_flag_suspended 0x020 /* 挂起 */
#define rt_device_flag_stream 0x040 /* 流模式 */
#define rt_device_flag_int_rx 0x100 /* 中断接收 */
#define rt_device_flag_dma_rx 0x200 /* dma 接收 */
#define rt_device_flag_int_tx 0x400 /* 中断发送 */
#define rt_device_flag_dma_tx 0x800 /* dma 发送 */
设备流模式 rt_device_flag_stream 参数用于向串口终端输出字符串:当输出的字符是 “\n” 时,自动在前面补一个 “\r” 做分行。
注册成功的设备可以在 finsh 命令行使用 list_device 命令查看系统中所有的设备信息,包括设备名称、设备类型和设备被打开次数。
当设备注销后的,设备将从设备管理器中移除,也就不能再通过设备查找搜索到该设备。注销设备不会释放设备控制块占用的内存。注销设备的函数如下所示:
注销设备
rt_err_t rt_device_unregister(rt_device_t dev); //传入设备句柄进行注销设备
访问设备
查找设备
应用程序根据设备名称获取设备句柄,进而可以操作设备。查找设备函数如下所示:
rt_device_t rt_device_find(const char* name);
初始化设备
获得设备句柄后,应用程序可使用如下函数对设备进行初始化操作:
rt_err_t rt_device_init(rt_device_t dev);
控制设备
通过命令控制字,应用程序也可以对设备进行控制,通过如下函数完成:
rt_err_t rt_device_control(rt_device_t dev, rt_uint8_t cmd, void* arg);
参数cmd的通用设备命令可取如下宏定义
#define rt_device_ctrl_resume 0x01 /* 恢复设备 */
#define rt_device_ctrl_suspend 0x02 /* 挂起设备 */
#define rt_device_ctrl_config 0x03 /* 配置设备 */
#define rt_device_ctrl_set_int 0x10 /* 设置中断 */
#define rt_device_ctrl_clr_int 0x11 /* 清中断 */
#define rt_device_ctrl_get_int 0x12 /* 获取中断状态 */
发表评论