当前位置: 代码网 > 服务器>服务器>Linux > RTThread studio 驱动开发

RTThread studio 驱动开发

2024年08月02日 Linux 我要评论
rtthread driver

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   /* 获取中断状态 */

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com