it编程 > 硬件开发 > 驱动开发

【RT-Thread 设备驱动开发】I/O抽象层+总线层+驱动层+硬件层

54人参与 2024-08-06 驱动开发

[rt-thread 设备驱动开发] i/o抽象层+总线层+驱动层+硬件层

一、框架介绍

问题提出

基于以上问题,提出几个想法:

  1. 更换新硬件平台时,可不可以不用去查阅用户使用手册实现接口? -> 事先有人提你实现好
  2. 更换新硬件平台时,可不可以不做应用层业务代码的修改? -> 调用统一的抽象接口
  3. 更换回旧硬件平台时,可不可以一键进行切换? -> 引入"总线"替我们进行管理

思考一下上面几个问题,我们可以发现本质上是因为我们的业务代码和底层硬件代码是直接依赖的、强耦合的,在程序设计中我们常用的解耦方法有:使用抽象层(抽象数据结构和函数接口、模块化编程)、模块间通信(回调函数、消息传递)、依赖注入(通过参数传递所需的依赖)、接口设计(避免直接访问对方的内部实现)等。

解决方案

至此前面三个问题其实都能得到解决了,在总线这一抽象层可以提供统一的接口来对硬件进行操作。但是还不够优雅,除了spi还有iic、uart、can等系列总线,对于大型项目只开发应用层代码的人,他学起来还是很困难太多了,再给我抽象一下也就是rt-thread中的i/o设备管理层

需要注意的是,对于简单设备会省略设备驱动框架层这一步骤,而是直接向上层i/o设备管理层进行注册,这样的坏处是不利于统一管理,对应linux就是不使用platform框架进行字符设备开发。

image-20240801223947677

image-20240801225032355

image-20240730213950708

二、设备驱动开发

api接口函数

image-20240801225859601

rt_device_t rt_device_create(int type, int attach_size); // attach_size 用户数据大小
void rt_device_destroy(rt_device_t device);
/*--------------------------------------type-----------------------------------------*/
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    /* 杂类设备        */
/*-----------------------------------------------------------------------------------*/
typedef struct rt_device *rt_device_t;
struct rt_device {...
#ifdef rt_using_device_ops 
    const struct rt_device_ops *ops;
#else
    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_ssize_t (*read)(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size);
    rt_ssize_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); 
#endif 
...};

image-20240730214150708

rt_err_t rt_device_register(rt_device_t dev, const char* name, rt_uint8_t flags);
rt_err_t rt_device_unregister(rt_device_t dev);
/*--------------------------------------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_t->ops------------------------------------*/
struct rt_device_ops {
    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);
};

image-20240801233137590

rt_device_t rt_device_find(const char* name); // 名称匹配
rt_err_t rt_device_init(rt_device_t dev);
rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflags); // 检查初始化
rt_err_t rt_device_close(rt_device_t dev);
rt_err_t rt_device_control(rt_device_t dev, rt_uint8_t cmd, void* arg);
rt_size_t rt_device_read(rt_device_t dev, rt_off_t pos,void* buffer, rt_size_t size);
rt_size_t rt_device_write(rt_device_t dev, rt_off_t pos,const void* buffer, rt_size_t size);
rt_err_t rt_device_set_rx_indicate(rt_device_t dev, rt_err_t (*rx_ind)(rt_device_t dev,rt_size_t size));
/*-------------------------------------oflags----------------------------------------*/
#define rt_device_oflag_close 0x000   /* 设备已经关闭(内部使用)*/
#define rt_device_oflag_rdonly 0x001  /* 以只读方式打开设备 */
#define rt_device_oflag_wronly 0x002  /* 以只写方式打开设备 */
#define rt_device_oflag_rdwr 0x003    /* 以读写方式打开设备 */
#define rt_device_oflag_open 0x008    /* 设备已经打开(内部使用)*/
#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 发送模式打开 */
/*--------------------------------------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   /* 获取中断状态 */
/* 当硬件设备收到数据时,可以通过如下函数回调另一个函数来设置数据接收指示,通知上层应用线程有数据到达。 */
rt_err_t rt_device_set_rx_indicate(rt_device_t dev, rt_err_t (*rx_ind)(rt_device_t dev,rt_size_t size));

驱动快速使用

image-20240802000742203

image-20240729012957564

#include <rtthread.h>
#include <rtdevice.h>

// #define drv_debug
#define log_tag "drv.test"
#include <drv_log.h>

static rt_err_t dev_test_init(rt_device_t dev) {
    log_i("test dev init");
    return rt_eok;
}
static rt_err_t dev_test_open(rt_device_t dev, rt_uint16_t oflag) {
    log_i("test dev open flag = %d", oflag);
    return rt_eok;
}
static rt_err_t dev_test_close(rt_device_t dev) {
    log_i("test dev close");
    return rt_eok;
}
static rt_ssize_t dev_test_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size) {
    log_i("tese dev read pos = %d, size = %d", pos, size);
    return rt_eok;
}
static rt_ssize_t drv_test_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size) {
    log_i("tese dev write pos = %d, size = %d", pos, size);
    return rt_eok;
}
static rt_err_t drv_test_control(rt_device_t dev, int cmd, void *args) {
    log_i("test dev 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 dev create 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 = drv_test_write;
    test_dev->control = drv_test_control;
    if (rt_device_register(test_dev, "test_dev", rt_device_flag_rdwr) != rt_eok) {
        log_e("test dev register failed.");
        return -rt_error;
    }
    return rt_eok;
}
init_board_export(rt_drv_test_init); // 内核启动前进行组件初始化
if getdepend(['bsp_using_test']):
    src += ['drv_test.c']
#    path += [os.path.join(cwd, 'drv_test')] # 用于添加头文件

​ 修改libraries\hal_drivers\drivers\drv_test.c ,使用宏定义包含所以代码内容:

#if defined(bsp_using_test)
...
#endif /* bsp_using_test */

​ 作者选择将驱动添加到hardware drivers config->board extend module drivers菜单界面中,故修改board\kconfig文件,在menu "board extended module drivers"下新增如下内容:

menu "board extended module drivers"
    config bsp_using_test
        bool "enable test driver"
        default  n
    ...
endmenu

​ 空格开启test驱动模块,即在rtconfig.h中定义bsp_using_test宏。

image-20240802004729147

新增applications\drv_test_app.c文件,内容如下:

#include <rtthread.h>
#include <rtdevice.h>

#define log_tag "drv.test"
#define log_lvl log_lvl_dbg
#include <ulog.h>

static int dev_test_app(void)
{
    rt_device_t test_dev = rt_device_find("test_dev");
    if (test_dev == rt_null)
    {
        log_e("can't find test dev.");
        return -rt_error;
    }
    rt_device_open(test_dev, rt_device_oflag_rdwr);
    rt_device_control(test_dev, rt_device_ctrl_config, rt_null);
    rt_device_write(test_dev, 100, rt_null, 1024);
    rt_device_read(test_dev, 20, rt_null, 128);
    rt_device_close(test_dev);
    return rt_eok;
}
msh_cmd_export(dev_test_app, dev_test_app);

image-20240802010816594

pin设备

芯片上的引脚一般分为4类:电源、时钟、控制与i/o,其中i/o口在使用模式上又分为通用i/o功能复用i/o(如i2c/spi/uart等)。需要注意的是大多数mcu的引脚都不止一个功能,可以通过对总线矩阵的配置来切换实际功能。

rt_base_t rt_pin_get(const char *name); // rt_pin_get("pf.9"); or get_pin(f,  9)
void rt_pin_mode(rt_base_t pin, rt_base_t mode);
void rt_pin_write(rt_base_t pin, rt_base_t value);
int rt_pin_read(rt_base_t pin);
rt_err_t rt_pin_attach_irq(rt_int32_t pin, rt_uint32_t mode, void (*hdr)(void *args), void *args);
rt_err_t rt_pin_irq_enable(rt_base_t pin, rt_uint32_t enabled);
rt_err_t rt_pin_detach_irq(rt_int32_t pin);

这里的rt_pin_xx()就相当于前面应用部分i/o设备管理层的rt_device_xx(),其会调用pin.c下的全局变量static struct rt_device_pin _hw_pin中的设备驱动框架层const struct rt_pin_ops *ops,最终调用设备驱动层所提供的ops具体内容,如 _stm32_pin_ops。

rt_pin_xx                                               // 接口函数         应用层
    struct rt_device_pin                                // i/o设备管理层    内核层
        struct rt_pin_ops                               // 设备驱动框架层   总线层
            stm32、gd32 driver,such as _stm32_pin_ops  // 设备驱动层       驱动层
            										//                 硬件层
-----------------------------------------------------------------------------------------
rt_device_xxx                  // 接口函数                          应用层
	rt_device_t               // struct rt_device*,i/o设备管理层    内核层
		rt_device_ops        // 设备驱动框架层                      总线层
    		my_drvier        // 设备驱动层                          驱动层
           					//                 				     硬件层

由于bsp官方已经帮我们适配好了,我们只需创建applications\pin_irq.c文件,通过接口函数简单调用i/o设备管理层的内容即可。

#include <rtthread.h>
#include <rtdevice.h>
#define log_tag "pin.irq"
#define log_lvl log_lvl_dbg
#include <ulog.h>
#include <drv_gpio.h>

#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)

void key_up_callback(void *args) {
    int value = rt_pin_read(key_up);
    log_i("key up! %d", value);
}
void key_down_callback(void *args) {
    int value = rt_pin_read(key_down);
    log_i("key down! %d", value);
}
void key_left_callback(void *args) {
    int value = rt_pin_read(key_left);
    log_i("key left! %d", value);
}
void key_right_callback(void *args) {
    int value = rt_pin_read(key_right);
    log_i("key right! %d", value);
}
static int rt_pin_irq_example(void) {
    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;
}
msh_cmd_export(rt_pin_irq_example, rt_pin_irq_example);

image-20240802112522679

i2c设备

image-20240802113424938

image-20240730233820565

板载外设驱动:指 mcu 之外,开发板上外设,例如 tf 卡、以太网和 lcd 等

片上外设驱动:指 mcu 芯片上的外设,例如硬件定时器、adc 和看门狗等

扩展模块驱动:指可以通过扩展接口或者杜邦线连接的开发板的模块,例如 esp8266 模块

image-20240802114615974

image-20240730235558536

​ 片上外设驱动代码修改,其实就是根据kconfig进行代码构建和引脚信息获取

image-20240730235807564

image-20240730235840837

image-20240730235903943

image-20240802121532122

image-20240802121715707

image-20240802121653901

image-20240802122455512

#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>
#include <icm20608.h>
#define log_tag "icm.app"
#define log_lvl log_lvl_dbg
#include <ulog.h>

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");
    }
    /* 对 icm20608 进行零值校准:采样 10 次,求取平均值作为零值 */
    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 (count++ < 100) {
        rt_int16_t accel_x, accel_y, accel_z;
        rt_int16_t gyros_x, gyros_y, gyros_z;
        /* 读取三轴加速度 */
        result = icm20608_get_accel(dev, &accel_x, &accel_y, &accel_z);
        if (result == rt_eok) {
            log_d("current accelerometer: accel_x%6d, accel_y%6d, accel_z%6d", accel_x, accel_y, accel_z);
        } else {
            log_e("the sensor does not work");
        }
        /* 读取三轴陀螺仪 */
        result = icm20608_get_gyro(dev, &gyros_x, &gyros_y, &gyros_z);
        if (result == rt_eok) {
            log_d("current gyroscope    : gyros_x%6d, gyros_y%6d, gyros_z%6d", gyros_x, gyros_y, gyros_z);
        } else {
            log_e("the sensor does not work");
            break;
        }
        rt_thread_mdelay(1000);
    }
}

static int icm_app(void)
{
    rt_thread_t res = rt_thread_create("icm", icm_thread_entry, rt_null, 1024, 20, 50);
    if (res == rt_null) {
        return -rt_error;
    }
    rt_thread_startup(res);
    return rt_eok;
}
msh_cmd_export(icm_app, icm_app);

image-20240802122311690

相关api接口

rt_device_t rt_device_find(const char* name);
// 此函数会调用rt_mutex_take,不能在中断服务程序里面调用,会导致assertion断言错误
rt_size_t rt_i2c_transfer(struct rt_i2c_bus_device *bus,
                          struct rt_i2c_msg         msgs[],
                          rt_uint32_t               num); // 消息数组的元素个数
// 由rt_i2c_transfer()封装而来,更加简单易用,推荐使用。
rt_size_t rt_i2c_master_send(struct rt_i2c_bus_device *bus,
                             rt_uint16_t               addr,
                             rt_uint16_t               flags,
                             const rt_uint8_t         *buf,
                             rt_uint32_t               count);
rt_size_t rt_i2c_master_recv(struct rt_i2c_bus_device *bus,
                             rt_uint16_t               addr,
                             rt_uint16_t               flags,
                             rt_uint8_t               *buf,
                             rt_uint32_t               count);
struct rt_i2c_msg
{
    rt_uint16_t addr;    /* 从机地址 */
    rt_uint16_t flags;   /* 读、写标志等 */
    rt_uint16_t len;     /* 读写数据字节数 */
    rt_uint8_t  *buf;    /* 读写数据缓冲区指针 */
}
/*--------------------------------------flags----------------------------------*/
#define rt_i2c_wr              0x0000        /* 写标志,不可以和读标志进行“|”操作 */
#define rt_i2c_rd              (1u << 0)     /* 读标志,不可以和写标志进行“|”操作 */
#define rt_i2c_addr_10bit      (1u << 2)     /* 10 位地址模式 */
#define rt_i2c_no_start        (1u << 4)     /* 无开始条件 */
#define rt_i2c_ignore_nack     (1u << 5)     /* 忽视 nack */
#define rt_i2c_no_read_ack     (1u << 6)     /* 读的时候不发送 ack */
#define rt_i2c_no_stop         (1u << 7)     /* 不发送结束位 */

​ 简单使用示例

#include <rtthread.h>
#include <rtdevice.h>
#define log_tag "i2c.drv"
#define log_lvl log_lvl_dbg
#include <ulog.h>
/*--------------------------------------写操作----------------------------------*/
void i2c_single_byte_write()
{
    struct rt_i2c_bus_device *i2c_bus;
    i2c_bus = (struct rt_i2c_bus_device *)rt_device_find("i2c2");
    if (i2c_bus == rt_null)
    {
        log_e("can't find %s device!\n", "i2c2");
    }

    struct rt_i2c_msg msgs;
    rt_uint8_t buf = 0x68;
    msgs.addr = 0x68;
    msgs.flags = rt_i2c_wr;
    msgs.buf = &buf;
    msgs.len = 1;
    if (rt_i2c_transfer(i2c_bus, &msgs, 1) == 1)
    {
        log_i("single byte write success!");
    }
    else
    {
        log_e("single byte write failed!");
    }
}
msh_cmd_export(i2c_single_byte_write, i2c_single_byte_write);
void i2c_mult_byte_write()
{
    struct rt_i2c_bus_device *i2c_bus;
    i2c_bus = (struct rt_i2c_bus_device *)rt_device_find("i2c2");
    if (i2c_bus == rt_null)
    {
        log_e("can't find %s device!\n", "i2c2");
    }

    struct rt_i2c_msg msgs;
    rt_uint8_t buf[3] = {0x01, 0x02, 0x03};
    msgs.addr = 0x68;
    msgs.flags = rt_i2c_wr;
    msgs.buf = buf;
    msgs.len = 3;
    if (rt_i2c_transfer(i2c_bus, &msgs, 1) == 1)
        log_i("mult byte write success!");
    else
        log_e("mult byte write failed!");
}
msh_cmd_export(i2c_mult_byte_write, i2c_mult_byte_write);
/*--------------------------------------读操作----------------------------------*/
void i2c_single_byte_read()
{
    struct rt_i2c_bus_device *i2c_bus;
    i2c_bus = (struct rt_i2c_bus_device *)rt_device_find("i2c2");
    if (i2c_bus == rt_null)
    {
        log_e("can't find %s device!\n", "i2c2");
    }

    struct rt_i2c_msg msgs[2];
    rt_uint8_t send_buf[1] = {0x6b};
    rt_uint8_t recv_buf[1] = {0};

    msgs[0].addr = 0x68;
    msgs[0].flags = rt_i2c_wr;
    msgs[0].buf = send_buf;
    msgs[0].len = 1;

    msgs[1].addr = 0x68;
    msgs[1].flags = rt_i2c_rd;
    msgs[1].len = 1;
    msgs[1].buf = recv_buf;

    if (rt_i2c_transfer(i2c_bus, msgs, 2) == 2)
    {
        log_i("single byte read success!");
    }
    else
    {
        log_e("single byte read failed!");
    }
}
msh_cmd_export(i2c_single_byte_read, i2c_single_byte_read);
void i2c_mult_byte_read()
{
    struct rt_i2c_bus_device *i2c_bus;
    i2c_bus = (struct rt_i2c_bus_device *)rt_device_find("i2c2");
    if (i2c_bus == rt_null)
    {
        log_e("can't find %s device!\n", "i2c2");
    }

    struct rt_i2c_msg msgs[2];
    rt_uint8_t send_buf[1] = {0x68};
    rt_uint8_t recv_buf[2] = {0};

    msgs[0].addr = 0x68;
    msgs[0].flags = rt_i2c_wr;
    msgs[0].buf = send_buf;
    msgs[0].len = 1;

    msgs[1].addr = 0x68;
    msgs[1].flags = rt_i2c_rd;
    msgs[1].buf = recv_buf;
    msgs[1].len = 2;

    if (rt_i2c_transfer(i2c_bus, msgs, 2) == 2)
    {
        log_i("mult byte read success!");
    }
    else
    {
        log_e("mult byte read failed");
    }
}
msh_cmd_export(i2c_mult_byte_read, i2c_mult_byte_read);

​ 调用关系及分层对比(个人理解)

--------------------------------------rt-thread------------------------------------------
icm_20608_app.c                                       // 应用层     
    icm20608_xx                                       // 应用层    
        write_regs、read_regs                         // 应用层  
            rt_i2c_transfer                           // 接口函数		  应用层
                struct rt_i2c_bus_device              // i/o设备管理层     内核层
                    struct rt_i2c_bus_device_ops      // 设备驱动框架层	总线层
                            stm32_i2c_master_xfer     // 设备驱动层        驱动层
                                                      //                  硬件层
----------------------------------------linux-------------------------------------------
icm_20608_app.c                                     // 应用层              
    icm20608_xx                                     // 驱动层 		      
        write_regs、read_regs                       // 驱动层		
            rt_i2c_transfer						  // core核心层,提供接口函数 
                struct rt_i2c_bus_device            // 适配器设备管理层
                    struct rt_i2c_bus_device_ops    // 适配器驱动框架层
                            stm32_i2c_master_xfer   // 适配器驱动层
                        						  // 硬件层

软件i2c,调用流程没搞懂,后续用到再仔细看。

struct stm32_i2c 
	struct rt_i2c_bit_ops 

spi设备

image-20240731010951494

image-20240731011027911

image-20240802172840922

​ 新建applications\spi_example.c文件,添加如下内容

#include <rtthread.h>
#include <rtdevice.h>
#include <drv_spi.h>
#include <drv_gpio.h>
static int spi_attach(void)
{
    //挂载spi设备
    return rt_hw_spi_device_attach("spi2", "spi20", get_pin(b, 12), rt_null);
    //struct rt_spi_device *spi_device = rt_null;
  	//rt_spi_bus_attach_device_cspin(spi_device, "spi20", "spi2",get_pin(b, 12), rt_null));
}
init_device_export(spi_attach);

可以发现spi2总线已打开,相应的spi20设备也已挂载成功

image-20240802175159885

image-20240731012026179

​ 简单示例,补充applications\spi_example.c内容如下:

static int spi_transfer_one_data(void) 
{
    rt_err_t ret = rt_eok;
    struct rt_spi_device *spi20 = (struct rt_spi_device *)rt_device_find("spi20");

    struct rt_spi_configuration cfg;
    cfg.data_width = 8;
    cfg.mode = rt_spi_master | rt_spi_mode_0 | rt_spi_msb;
    cfg.max_hz = 1 * 1000 * 1000;
    rt_spi_configure(spi20, &cfg);

    rt_uint8_t sendbuff = 0xda;
    rt_uint8_t recvbuff = 0;
    ret = rt_spi_transfer(spi20, &sendbuff, &recvbuff, 1); // 来回
    rt_kprintf("ret = %d\n", ret);
    return rt_eok;
}
msh_cmd_export(spi_transfer_one_data, spi_transfer_one_data);

static int spi_send_one_data(void) 
{
    rt_err_t ret = rt_eok;
    struct rt_spi_device *spi20 = (struct rt_spi_device *)rt_device_find("spi20");

    struct rt_spi_configuration cfg;
    cfg.data_width = 8;
    cfg.mode = rt_spi_master | rt_spi_mode_0 | rt_spi_msb;
    cfg.max_hz = 1 * 1000 * 1000;
    rt_spi_configure(spi20, &cfg);

    rt_uint8_t sendbuff = 0x1a;
    ret = rt_spi_send(spi20, &sendbuff, 1); // 单独发
    rt_kprintf("ret = %d\n", ret);
    return rt_eok;
}
msh_cmd_export(spi_send_one_data, spi_send_one_data);

static int spi_recv_one_data(void)
{
    rt_err_t ret = rt_eok;
    struct rt_spi_device *spi20 = (struct rt_spi_device *)rt_device_find("spi20");

    struct rt_spi_configuration cfg;
    cfg.data_width = 8;
    cfg.mode = rt_spi_master | rt_spi_mode_0 | rt_spi_msb;
    cfg.max_hz = 1 * 1000 * 1000;
    rt_spi_configure(spi20, &cfg);

    rt_uint8_t recvbuff = 0;
    ret = rt_spi_recv(spi20, &recvbuff, 1); //单独接
    rt_kprintf("ret = %d\n", ret);

    return rt_eok;
}
msh_cmd_export(spi_recv_one_data, spi_recv_one_data);

static int spi_send_then_send_data(void)
{
    rt_err_t ret = rt_eok;
    struct rt_spi_device *spi20 = (struct rt_spi_device *)rt_device_find("spi20");

    struct rt_spi_configuration cfg;
    cfg.data_width = 8;
    cfg.mode = rt_spi_master | rt_spi_mode_0 | rt_spi_msb;
    cfg.max_hz = 1 * 1000 * 1000;
    rt_spi_configure(spi20, &cfg);

    rt_uint8_t sendbuff1[2] = {0x1a, 0x99};
    rt_uint8_t sendbuff2[2] = {0x12, 0x22};
    ret = rt_spi_send_then_send(spi20, &sendbuff1, 2, &sendbuff2, 2); // 多字节多次发送
    rt_kprintf("ret = %d\n", ret);

    return rt_eok;
}
msh_cmd_export(spi_send_then_send_data, spi_send_then_send_data);

static int spi_send_then_recv_data(void)
{
    rt_err_t ret = rt_eok;
    struct rt_spi_device *spi20 = (struct rt_spi_device *)rt_device_find("spi20");

    struct rt_spi_configuration cfg;
    cfg.data_width = 8;
    cfg.mode = rt_spi_master | rt_spi_mode_0 | rt_spi_msb;
    cfg.max_hz = 1 * 1000 * 1000;
    rt_spi_configure(spi20, &cfg);

    rt_uint8_t sendbuff1[2] = {0x1a, 0x99};
    rt_uint8_t recvbuff2[2] = {0};

    ret = rt_spi_send_then_recv(spi20, &sendbuff1, 2, &recvbuff2, 2); // 多字节来回
    rt_kprintf("ret = %d\n", ret);

    return rt_eok;
}
msh_cmd_export(spi_send_then_recv_data, spi_send_then_recv_data);

image-20240802182347715

image-20240802182256351

硬件版本有变,修改默认引脚

image-20240731194812211

成功上网

image-20240802185442735

can设备

打开板载外设can1

image-20240803141520887

编译报错,drv_can.h添加头文件stm32f4xx_hal_can.h

image-20240803141744330

image-20240803141550931

继续编译,出现链接错误,修改stm32f4xx_hal_conf.h文件,定义hal_can_module_enabled

image-20240803141839718

image-20240803142018253

编写can发送函数,运行出现init错误

#include <rtthread.h>
#include <drv_can.h>
#define can_dev_name "can1"
static rt_device_t can_dev;

void can_entry(void *parameter)
{
    struct rt_can_msg msg = {0};
    rt_ssize_t size = 0;

    can_dev = rt_device_find(can_dev_name);
    rt_device_open(can_dev, rt_device_flag_int_tx | rt_device_flag_int_rx);

    msg.id = 0x78;
    msg.ide = rt_can_stdid;
    msg.rtr = rt_can_dtr;
    msg.len = 8;

    msg.data[0] = 0x00;
    msg.data[1] = 0x11;
    msg.data[2] = 0x22;
    msg.data[3] = 0x33;
    msg.data[4] = 0x44;
    msg.data[5] = 0x55;
    msg.data[6] = 0x66;
    msg.data[7] = 0x77;
    size = rt_device_write(can_dev, 0, &msg, sizeof(msg));

    rt_kprintf("rt_device_write size : %d\n", size);

    rt_thread_mdelay(2000);

    rt_device_close(can_dev);
}
msh_cmd_export(can_entry, can_entry);

image-20240803142225243

调试发现是卡死在hal_can_init()函数等待应答处,查阅相关资料原因是要提前将can1的引脚状态配置好才可以正常初始化。

...
	/* wait initialisation acknowledge */
    while ((hcan->instance->msr & can_msr_inak) == 0u)
    {
        if ((hal_gettick() - tickstart) > can_timeout_value)
        {
            /* update error code */
            hcan->errorcode |= hal_can_error_timeout;
            /* change can state */
            hcan->state = hal_can_state_error;
            return hal_error;
        }
    }
...

故在stm32f4xx_hal_msp.c文件添加can1初始化代码,该部分可以用stm32cubemx生成拷贝复制。

/**
 * @brief can msp initialization
 * this function configures the hardware resources used in this example
 * @param hcan: can handle pointer
 * @retval none
 */
void hal_can_mspinit(can_handletypedef *hcan)
{
  gpio_inittypedef gpio_initstruct = {0};
  if (hcan->instance == can1)
  {
    /* user code begin can1_mspinit 0 */

    /* user code end can1_mspinit 0 */
    /* peripheral clock enable */
    __hal_rcc_can1_clk_enable();

    __hal_rcc_gpiob_clk_enable();
    /**can1 gpio configuration
    pb8     ------> can1_rx
    pb9     ------> can1_tx
    */
    gpio_initstruct.pin = gpio_pin_8 | gpio_pin_9;
    gpio_initstruct.mode = gpio_mode_af_pp;
    gpio_initstruct.pull = gpio_nopull;
    gpio_initstruct.speed = gpio_speed_freq_very_high;
    gpio_initstruct.alternate = gpio_af9_can1;
    hal_gpio_init(gpiob, &gpio_initstruct);

    /* can1 interrupt init */
    hal_nvic_setpriority(can1_tx_irqn, 0, 0);
    hal_nvic_enableirq(can1_tx_irqn);
    hal_nvic_setpriority(can1_rx0_irqn, 0, 0);
    hal_nvic_enableirq(can1_rx0_irqn);
    /* user code begin can1_mspinit 1 */

    /* user code end can1_mspinit 1 */
  }
}

/**
 * @brief can msp de-initialization
 * this function freeze the hardware resources used in this example
 * @param hcan: can handle pointer
 * @retval none
 */
void hal_can_mspdeinit(can_handletypedef *hcan)
{
  if (hcan->instance == can1)
  {
    /* user code begin can1_mspdeinit 0 */

    /* user code end can1_mspdeinit 0 */
    /* peripheral clock disable */
    __hal_rcc_can1_clk_disable();

    /**can1 gpio configuration
    pb8     ------> can1_rx
    pb9     ------> can1_tx
    */
    hal_gpio_deinit(gpiob, gpio_pin_8 | gpio_pin_9);

    /* can1 interrupt deinit */
    hal_nvic_disableirq(can1_tx_irqn);
    hal_nvic_disableirq(can1_rx0_irqn);
    /* user code begin can1_mspdeinit 1 */

    /* user code end can1_mspdeinit 1 */
  }
}

成功发送, 但不知道为何我明明发送的是标准帧13.5个字节,但实现显示我发送了16个字节(扩展帧),切换为rt_can_extid也没有什么改变。

image-20240803143715413

image-20240803143442900

可以发送以后,直接下载官网can设备应用示例进行验证,现象一样说明基本配置没有问题,随后便可以学习相关api进行自己的应用层开发使用。

/*
 * 程序清单:这是一个 can 设备使用例程
 * 例程导出了 can_sample 命令到控制终端
 * 命令调用格式:can_sample can1
 * 命令解释:命令第二个参数是要使用的 can 设备名称,为空则使用默认的 can 设备
 * 程序功能:通过 can 设备发送一帧,并创建一个线程接收数据然后打印输出。
 */
#include <rtthread.h>
#include "rtdevice.h"

#define can_dev_name "can1" /* can 设备名称 */

static struct rt_semaphore rx_sem; /* 用于接收消息的信号量 */
static rt_device_t can_dev;        /* can 设备句柄 */

/* 接收数据回调函数 */
static rt_err_t can_rx_call(rt_device_t dev, rt_size_t size)
{
    /* can 接收到数据后产生中断,调用此回调函数,然后发送接收信号量 */
    rt_sem_release(&rx_sem);

    return rt_eok;
}

static void can_rx_thread(void *parameter)
{
    int i;
    rt_err_t res;
    struct rt_can_msg rxmsg = {0};

    /* 设置接收回调函数 */
    rt_device_set_rx_indicate(can_dev, can_rx_call);

#ifdef rt_can_using_hdr
    struct rt_can_filter_item items[5] =
        {
            rt_can_filter_item_init(0x100, 0, 0, 0, 0x700, rt_null, rt_null), /* std,match id:0x100~0x1ff,hdr 为 - 1,设置默认过滤表 */
            rt_can_filter_item_init(0x300, 0, 0, 0, 0x700, rt_null, rt_null), /* std,match id:0x300~0x3ff,hdr 为 - 1 */
            rt_can_filter_item_init(0x211, 0, 0, 0, 0x7ff, rt_null, rt_null), /* std,match id:0x211,hdr 为 - 1 */
            rt_can_filter_std_init(0x486, rt_null, rt_null),                  /* std,match id:0x486,hdr 为 - 1 */
            {
                0x555,
                0,
                0,
                0,
                0x7ff,
                7,
            } /* std,match id:0x555,hdr 为 7,指定设置 7 号过滤表 */
        };
    struct rt_can_filter_config cfg = {5, 1, items}; /* 一共有 5 个过滤表 */
    /* 设置硬件过滤表 */
    res = rt_device_control(can_dev, rt_can_cmd_set_filter, &cfg);
    rt_assert(res == rt_eok);
#endif

    while (1)
    {
        /* hdr 值为 - 1,表示直接从 uselist 链表读取数据 */
        rxmsg.hdr_index = -1;
        /* 阻塞等待接收信号量 */
        rt_sem_take(&rx_sem, rt_waiting_forever);
        /* 从 can 读取一帧数据 */
        rt_device_read(can_dev, 0, &rxmsg, sizeof(rxmsg));
        /* 打印数据 id 及内容 */
        rt_kprintf("id:%x", rxmsg.id);
        for (i = 0; i < 8; i++)
        {
            rt_kprintf("%2x", rxmsg.data[i]);
        }

        rt_kprintf("\n");
    }
}

int can_sample(int argc, char *argv[])
{
    struct rt_can_msg msg = {0};
    rt_err_t res;
    rt_size_t size;
    rt_thread_t thread;
    char can_name[rt_name_max];

    if (argc == 2)
    {
        rt_strncpy(can_name, argv[1], rt_name_max);
    }
    else
    {
        rt_strncpy(can_name, can_dev_name, rt_name_max);
    }
    /* 查找 can 设备 */
    can_dev = rt_device_find(can_name);
    if (!can_dev)
    {
        rt_kprintf("find %s failed!\n", can_name);
        return rt_error;
    }

    /* 初始化 can 接收信号量 */
    rt_sem_init(&rx_sem, "rx_sem", 0, rt_ipc_flag_fifo);

    /* 以中断接收及发送方式打开 can 设备 */
    res = rt_device_open(can_dev, rt_device_flag_int_tx | rt_device_flag_int_rx);
    rt_assert(res == rt_eok);
    /* 创建数据接收线程 */
    thread = rt_thread_create("can_rx", can_rx_thread, rt_null, 1024, 25, 10);
    if (thread != rt_null)
    {
        rt_thread_startup(thread);
    }
    else
    {
        rt_kprintf("create can_rx thread failed!\n");
    }

    msg.id = 0x78;          /* id 为 0x78 */
    msg.ide = rt_can_stdid; /* 标准格式 */
    msg.rtr = rt_can_dtr;   /* 数据帧 */
    msg.len = 8;            /* 数据长度为 8 */
    /* 待发送的 8 字节数据 */
    msg.data[0] = 0x00;
    msg.data[1] = 0x11;
    msg.data[2] = 0x22;
    msg.data[3] = 0x33;
    msg.data[4] = 0x44;
    msg.data[5] = 0x55;
    msg.data[6] = 0x66;
    msg.data[7] = 0x77;
    /* 发送一帧 can 数据 */
    size = rt_device_write(can_dev, 0, &msg, sizeof(msg));
    if (size == 0)
    {
        rt_kprintf("can dev write data failed!\n");
    }

    return res;
}
/* 导出到 msh 命令列表中 */
msh_cmd_export(can_sample, can device sample);

发送接收功能正常,说明配置无错误

image-20240803150355116

canfestival软件包依赖于can驱动和hwtimer驱动,can驱动前面已经测试通过,我们继续打开hwtimer驱动,这里选择tim14只是因为tim_config.h中已有相关宏定义,你也可以复制修改实现tim11和tim13。

image-20240803154158742

image-20240803154258493

然后开启canfestival软件包,采用注意can和hwtimer设备的名词,为上文配置的“can1”和“timer14”,优先级采取默认不做修改,pkgs --update下载,scons -j12编译,无报错。

image-20240803154650222

canfestival协议栈已经跑起来了,但是由于从机和主机没有构建对象字典所以出现问题write sdo failed但是can分析仪可以看到发出的协议。

image-20240803154005946

image-20240803155445041

三、参考内容

rt-thread 设备驱动官方文档

rt-thread can双机通讯

rt-thread canfestival移植1

rt-thread canfestival移植2

rt-thread canopennode移植

(0)
打赏 微信扫一扫 微信扫一扫

您想发表意见!!点此发布评论

推荐阅读

芯片原厂驱动开发工程师:初学到精通,如何快速成长?

08-06

OpenHarmony轻量系统开发【8】其它驱动开发示例

08-06

WDF驱动开发-DMA(一)

08-06

STM32配合CubeMX硬件SPI驱动0.96寸OLED

08-06

STM32 —— USB 转 TTL(CH340)

08-06

MCU的最佳存储方案CS创世 SD NAND

08-06

猜你喜欢

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

发表评论