首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

IOT-OS之RT-Thread(八)--- IIC设备对象管理与Sensor管理框架

  • 24-03-03 17:01
  • 3010
  • 8305
blog.csdn.net

文章目录

  • 一、IIC协议简介
    • 1.1 IIC总线简介
    • 1.2 硬件IIC与软件模拟IIC
  • 二、IIC设备对象管理
    • 2.1 IIC设备驱动框架层
  • 三、IIC应用示例之AHT10
    • 3.1 AHT10温湿度传感器简介
    • 3.2 IIC设备应用示例
  • 四、Sensor框架原理及示例
    • 4.1 Sensor设备描述
    • 4.2 Sensor设备访问接口
    • 4.3 AHT10移植到Sensor框架
    • 4.4 Sensor框架应用示例
  • 更多文章:

一、IIC协议简介

1.1 IIC总线简介

I2C(Inter Integrated Circuit)总线是 PHILIPS 公司开发的一种半双工、双向二线制同步串行总线。I2C 总线传输数据时只需两根信号线,一根是双向数据线 SDA(serial data),另一根是双向时钟线 SCL(serial clock)。SPI 总线有两根线分别用于主从设备之间接收数据和发送数据,而 I2C 总线只使用一根线进行数据收发。

I2C 和 SPI 一样以主从的方式工作,不同于 SPI 一主多从的结构,它允许同时有多个主设备存在,每个连接到总线上的器件都有唯一的地址,主设备启动数据传输并产生时钟信号,从设备被主设备寻址(SPI通过CS片选引脚选择目标从设备,IIC通过发送从设备地址以寻址方式选择目标从设备),同一时刻只允许有一个主设备。如下图所示:
IIC多主多从设备连接
SDA 线上的数据必须在时钟的高电平周期保持稳定,数据线的高或低电平状态只有在 SCL 线的时钟信号是低电平时才能改变。换言之,SCL为高电平时表示有效数据,SDA为高电平表示“1”,低电平表示“0”;SCL为低电平时表示无效数据,此时SDA会进行电平切换,为下次数据表示做准备。下图为数据有效性的时序图:
数据有效性时序图
I2C 总线主要的数据传输格式如下图所示:
IIC总线数据传输格式
当总线空闲时,SDA 和 SCL 都处于高电平状态,当主机要和某个从机通讯时,会先发送一个开始条件,然后发送从机地址和读写控制位,接下来传输数据(主机发送或者接收数据),数据传输结束时主机会发送停止条件。传输的每个字节为8位,高位在前,低位在后。数据传输过程中的不同名词详解如下所示:

  • 开始条件: SCL 为高电平时,主机将 SDA 拉低,表示数据传输即将开始,总线在开始条件后处于busy的状态,在停止条件的某段时间后,总线才再次处于空闲状态,下图为开始和停止条件的信号产生时序图:
    IIC开始条件与停止条件
  • 从机地址: 主机发送的第一个字节为从机地址,高 7 位为地址,最低位为 R/W 读写控制位,1 表示读操作,0 表示写操作。一般从机地址有 7 位地址模式和 10 位地址模式两种,如果是 10 位地址模式,第一个字节的头 7 位 是 11110XX 的组合,其中最后两位(XX)是 10 位地址的两个最高位,第二个字节为 10 位从机地址的剩下8位,如下图所示:
    从设备地址模式
  • 应答信号: 每传输完成一个字节的数据,接收方就需要回复一个 ACK(acknowledge)。写数据时由从机发送ACK,读数据时由主机发送 ACK。数据接收方收到传输的一个字节数据后,需要给出响应,此时处在第九个时钟,发送端释放SDA线控制权,将SDA电平拉高,由接收方控制。若希望继续,则给出“应答(ACK)”信号,即SDA为低电平;反之给出“非应答(NACK)”信号,即SDA为高电平,应答信号产生的时序图如下:
    IIC应答信号时序图
  • 数据: 从机地址发送完后可能会发送一些指令,依从机而定,然后开始传输数据,由主机或者从机发送,每个数据为 8 位,数据的字节数没有限制;
  • 重复开始条件: 在一次通信过程中,主机可能需要和不同的从机传输数据或者需要切换读写操作时,主机可以再发送一个开始条件;
  • 停止条件: 在 SDA 为低电平时,主机将 SCL 拉高并保持高电平,然后在将 SDA 拉高,表示传输结束。

1.2 硬件IIC与软件模拟IIC

在正点原子的教程中说,STM32的硬件IIC设计比较复杂,而且稳定性不佳(貌似是ST为了规避飞利浦IIC的版权问题),所以在CPU资源不紧张的情况下,很多人一般会选择GPIO模拟I2C。硬件IIC与软件模拟IIC有何区别呢?

  • 硬件IIC:跟之前介绍的SPI外设与USART外设类似,物理层有专门的电路支持,IIC引脚自然也是专用的,借助芯片厂商提供的固件库函数实现对IIC外设寄存器的访问,工作效率较高;
  • 软件模拟IIC:物理层借助GPIO外设,并不使用固件库的IIC函数访问IIC寄存器,可以根据需要配置模拟IIC通信的GPIO引脚,协议层需要自己实现,而且软件模拟IIC工作效率比硬件IIC低不少。

STM32平台由于软件模拟IIC比较常用,且方便移植,RT-Thread的IIC设备驱动也是使用的软件模拟方式实现的,所以下面就不介绍硬件IIC的功能框图、IIC固件库等内容了(自然也不需要CubeMX配置IIC外设了),下面开始介绍RT-Thread IIC驱动框架的实现。

二、IIC设备对象管理

介绍IIC设备对象管理前,再展示下RT-Thread I / O设备模型框架,按照模型框架一层层解析:
I / O设备模型框架

2.1 IIC设备驱动框架层

  • IIC总线控制块

先看IIC总线在驱动框架层是如何描述的:

// rt-thread-4.0.1\components\drivers\include\drivers\i2c.h

/*for i2c bus driver*/
struct rt_i2c_bus_device
{
    struct rt_device parent;
    const struct rt_i2c_bus_device_ops *ops;
    rt_uint16_t  flags;
    rt_uint16_t  addr;
    struct rt_mutex lock;
    rt_uint32_t  timeout;
    rt_uint32_t  retries;
    void *priv;
};

struct rt_i2c_bus_device_ops
{
    rt_size_t (*master_xfer)(struct rt_i2c_bus_device *bus,
                             struct rt_i2c_msg msgs[],
                             rt_uint32_t num);
    rt_size_t (*slave_xfer)(struct rt_i2c_bus_device *bus,
                            struct rt_i2c_msg msgs[],
                            rt_uint32_t num);
    rt_err_t (*i2c_bus_control)(struct rt_i2c_bus_device *bus,
                                rt_uint32_t,
                                rt_uint32_t);
};

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)  /* this is a ten bit chip address */
#define RT_I2C_NO_START         (1u << 4)
#define RT_I2C_IGNORE_NACK      (1u << 5)
#define RT_I2C_NO_READ_ACK      (1u << 6)  /* when I2C reading, we do not ACK */
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

IIC总线访问IIC设备的操作函数主要有master_xfer、slave_xfer与i2c_bus_control三种,这几个访问函数由下面的IIC设备驱动层实现,这也是IIC协议软件模拟实现的重点。

IIC总线传输数据按照IIC协议数据帧格式,封装成结构体rt_i2c_msg,该结构体成员包含了IIC协议数据帧中从机地址、各标志位、传输数据的起始地址及长度等内容。

  • IIC总线接口函数

I/O设备管理层要想访问某设备,需要在下面的设备驱动层创建设备实例,并将该设备注册到I/O设备管理层,下面先看看IIC总线的创建与注册过程:

// rt-thread-4.0.1\components\drivers\i2c\i2c_core.c
rt_err_t rt_i2c_bus_device_register(struct rt_i2c_bus_device *bus,
                                    const char               *bus_name)
{
    rt_err_t res = RT_EOK;

    rt_mutex_init(&bus->lock, "i2c_bus_lock", RT_IPC_FLAG_FIFO);

    if (bus->timeout == 0) bus->timeout = RT_TICK_PER_SECOND;

    res = rt_i2c_bus_device_device_init(bus, bus_name);

    i2c_dbg("I2C bus [%s] registered\n", bus_name);

    return res;
}

// rt-thread-4.0.1\components\drivers\i2c\i2c_dev.c
rt_err_t rt_i2c_bus_device_device_init(struct rt_i2c_bus_device *bus,
                                       const char               *name)
{
    struct rt_device *device;
    RT_ASSERT(bus != RT_NULL);

    device = &bus->parent;

    device->user_data = bus;

    /* set device type */
    device->type    = RT_Device_Class_I2CBUS;
    /* initialize device interface */
#ifdef RT_USING_DEVICE_OPS
    device->ops     = &i2c_ops;
#else
    device->init    = RT_NULL;
    device->open    = RT_NULL;
    device->close   = RT_NULL;
    device->read    = i2c_bus_device_read;
    device->write   = i2c_bus_device_write;
    device->control = i2c_bus_device_control;
#endif

    /* register to device manager */
    rt_device_register(device, name, RT_DEVICE_FLAG_RDWR);

    return RT_EOK;
}

#ifdef RT_USING_DEVICE_OPS
const static struct rt_device_ops i2c_ops = 
{
    RT_NULL, 
    RT_NULL,
    RT_NULL,
    i2c_bus_device_read,
    i2c_bus_device_write,
    i2c_bus_device_control
};
#endif

static rt_size_t i2c_bus_device_read(rt_device_t dev,
                                     rt_off_t    pos,
                                     void       *buffer,
                                     rt_size_t   count)
{
	......
    struct rt_i2c_bus_device *bus = (struct rt_i2c_bus_device *)dev->user_data;
	......
    return rt_i2c_master_recv(bus, addr, flags, buffer, count);
}

static rt_size_t i2c_bus_device_write(rt_device_t dev,
                                      rt_off_t    pos,
                                      const void *buffer,
                                      rt_size_t   count)
{
    ......
    struct rt_i2c_bus_device *bus = (struct rt_i2c_bus_device *)dev->user_data;
    ......
    return rt_i2c_master_send(bus, addr, flags, buffer, count);
}

static rt_err_t i2c_bus_device_control(rt_device_t dev,
                                       int         cmd,
                                       void       *args)
{
    rt_err_t ret;
    struct rt_i2c_priv_data *priv_data;
    struct rt_i2c_bus_device *bus = (struct rt_i2c_bus_device *)dev->user_data;

    RT_ASSERT(bus != RT_NULL);

    switch (cmd)
    {
    /* set 10-bit addr mode */
    case RT_I2C_DEV_CTRL_10BIT:
        bus->flags |= RT_I2C_ADDR_10BIT;
        break;
    case RT_I2C_DEV_CTRL_ADDR:
        bus->addr = *(rt_uint16_t *)args;
        break;
    case RT_I2C_DEV_CTRL_TIMEOUT:
        bus->timeout = *(rt_uint32_t *)args;
        break;
    case RT_I2C_DEV_CTRL_RW:
        priv_data = (struct rt_i2c_priv_data *)args;
        ret = rt_i2c_transfer(bus, priv_data->msgs, priv_data->number);
        if (ret < 0)
            return -RT_EIO;
        break;
    default:
        break;
    }

    return RT_EOK;
}

// rt-thread-4.0.1\components\drivers\include\drivers\i2c_dev.h
struct rt_i2c_priv_data
{
    struct rt_i2c_msg  *msgs;
    rt_size_t  number;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123

IIC驱动框架层向上层注册的操作函数集合i2c_ops最终通过调用rt_i2c_master_recv、rt_i2c_master_send与rt_i2c_transfer三个函数实现,IIC设备驱动层将这三个函数开放给用户了,用户除通过I / O设备统一访问接口访问IIC外,还可通过这三个函数直接访问IIC设备,这三个函数的实现代码如下:

// rt-thread-4.0.1\components\drivers\i2c\i2c_core.c

rt_size_t rt_i2c_transfer(struct rt_i2c_bus_device *bus,
                          struct rt_i2c_msg         msgs[],
                          rt_uint32_t               num)
{
    rt_size_t ret;

    if (bus->ops->master_xfer)
    {
        rt_mutex_take(&bus->lock, RT_WAITING_FOREVER);
        ret = bus->ops->master_xfer(bus, msgs, num);
        rt_mutex_release(&bus->lock);

        return ret;
    }
    else
        return 0;
}

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_err_t ret;
    struct rt_i2c_msg msg;

    msg.addr  = addr;
    msg.flags = flags & RT_I2C_ADDR_10BIT;
    msg.len   = count;
    msg.buf   = (rt_uint8_t *)buf;

    ret = rt_i2c_transfer(bus, &msg, 1);

    return (ret > 0) ? count : ret;
}

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)
{
    rt_err_t ret;
    struct rt_i2c_msg msg;
    RT_ASSERT(bus != RT_NULL);

    msg.addr   = addr;
    msg.flags  = flags & RT_I2C_ADDR_10BIT;
    msg.flags |= RT_I2C_RD;
    msg.len    = count;
    msg.buf    = buf;

    ret = rt_i2c_transfer(bus, &msg, 1);

    return (ret > 0) ? count : ret;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59

从上面的实现代码可以看出,rt_i2c_master_send与rt_i2c_master_recv最终是通过调用rt_i2c_transfer函数实现的,前两个函数对rt_i2c_transfer函数进行了再封装,不需要用户构造rt_i2c_msg结构体,调用更方便。

函数rt_i2c_transfer的实现最终是通过调用bus->ops->master_xfer函数来实现的,在前面介绍IIC总线设备控制块时介绍过,IIC总线操作函数集合rt_i2c_bus_device_ops需要IIC设备驱动层实现。

  • 软件模拟IIC协议实现

RT-Thread IIC设备采用软件模拟方式实现,通过GPIO设备模拟IIC通信协议实际上是通过控制GPIO引脚电平的置位操作实现的,STM32描述模拟IIC设备的数据结构如下:

// libraries\HAL_Drivers\drv_soft_i2c.h

/* stm32 i2c dirver class */
struct stm32_i2c
{
    struct rt_i2c_bit_ops ops;
    struct rt_i2c_bus_device i2c2_bus;
};

/* stm32 config class */
struct stm32_soft_i2c_config
{
    rt_uint8_t scl;
    rt_uint8_t sda;
    const char *bus_name;
};

// rt-thread-4.0.1\components\drivers\include\drivers\i2c-bit-ops.h

struct rt_i2c_bit_ops
{
    void *data;            /* private data for lowlevel routines */
    void (*set_sda)(void *data, rt_int32_t state);
    void (*set_scl)(void *data, rt_int32_t state);
    rt_int32_t (*get_sda)(void *data);
    rt_int32_t (*get_scl)(void *data);

    void (*udelay)(rt_uint32_t us);

    rt_uint32_t delay_us;  /* scl and sda line delay */
    rt_uint32_t timeout;   /* in tick */
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

结构体stm32_i2c相当于STM32 I2C设备驱动类,包含前面介绍的IIC总线设备rt_i2c_bus_device与IIC位操作函数集合rt_i2c_bit_ops,后者是软件模拟实现IIC通信协议的基础。

先看IIC设备的初始化与注册过程的实现代码:

// libraries\HAL_Drivers\drv_soft_i2c.c

/* I2C initialization function */
int rt_hw_i2c_init(void)
{
    rt_size_t obj_num = sizeof(i2c_obj) / sizeof(struct stm32_i2c);
    rt_err_t result;

    for (int i = 0; i < obj_num; i++)
    {
        i2c_obj[i].ops = stm32_bit_ops_default;
        i2c_obj[i].ops.data = (void*)&soft_i2c_config[i];
        i2c_obj[i].i2c2_bus.priv = &i2c_obj[i].ops;
        stm32_i2c_gpio_init(&i2c_obj[i]);
        result = rt_i2c_bit_add_bus(&i2c_obj[i].i2c2_bus, soft_i2c_config[i].bus_name);
        RT_ASSERT(result == RT_EOK);
        stm32_i2c_bus_unlock(&soft_i2c_config[i]);
    }
    return RT_EOK;
}
INIT_BOARD_EXPORT(rt_hw_i2c_init);

static const struct rt_i2c_bit_ops stm32_bit_ops_default =
{
    .data     = RT_NULL,
    .set_sda  = stm32_set_sda,
    .set_scl  = stm32_set_scl,
    .get_sda  = stm32_get_sda,
    .get_scl  = stm32_get_scl,
    .udelay   = stm32_udelay,
    .delay_us = 1,
    .timeout  = 100
};

static const struct stm32_soft_i2c_config soft_i2c_config[] =
{
#ifdef BSP_USING_I2C1
    I2C1_BUS_CONFIG,
#endif
......
};

static struct stm32_i2c i2c_obj[sizeof(soft_i2c_config) / sizeof(soft_i2c_config[0])];

// libraries\HAL_Drivers\drv_soft_i2c.h
#ifdef BSP_USING_I2C1
#define I2C1_BUS_CONFIG                                  \
    {                                                    \
        .scl = BSP_I2C1_SCL_PIN,                         \
        .sda = BSP_I2C1_SDA_PIN,                         \
        .bus_name = "i2c1",                              \
    }
#endif
......
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54

IIC设备初始化被RT-Thread自动初始化组件INIT_BOARD_EXPORT调用,不需要用户手动调用IIC初始化函数,只需要进行必要的引脚配置即可。

从条件宏BSP_USING_I2C1与宏定义BSP_I2C1_SCL_PIN、BSP_I2C1_SDA_PIN可以看出,软件模拟IIC的GPIO引脚需要用户完成配置,可以在Kconfig文件中增加软件模拟IIC外设的配置项,实现通过menuconfig图形化配置软件模拟IIC外设的效果。

IIC设备初始化函数rt_hw_i2c_init中调用的stm32_i2c_gpio_init、stm32_bit_ops_default、stm32_i2c_bus_unlock都是通过pin设备接口函数实现的,前篇博客详细介绍过pin设备驱动的实现,这里就略去不谈了。这里重点介绍下被调用的rt_i2c_bit_add_bus函数的实现:

// rt-thread-4.0.1\components\drivers\i2c\i2c-bit-ops.c

rt_err_t rt_i2c_bit_add_bus(struct rt_i2c_bus_device *bus,
                            const char               *bus_name)
{
    bus->ops = &i2c_bit_bus_ops;

    return rt_i2c_bus_device_register(bus, bus_name);
}

static const struct rt_i2c_bus_device_ops i2c_bit_bus_ops =
{
    i2c_bit_xfer,
    RT_NULL,
    RT_NULL
};

static rt_size_t i2c_bit_xfer(struct rt_i2c_bus_device *bus,
                              struct rt_i2c_msg         msgs[],
                              rt_uint32_t               num)
{
    struct rt_i2c_msg *msg;
    struct rt_i2c_bit_ops *ops = bus->priv;
    rt_int32_t i, ret;
    rt_uint16_t ignore_nack;

    bit_dbg("send start condition\n");
    i2c_start(ops);
    for (i = 0; i < num; i++)
    {
        msg = &msgs[i];
        ignore_nack = msg->flags & RT_I2C_IGNORE_NACK;
        if (!(msg->flags & RT_I2C_NO_START))
        {
            if (i)
            {
                i2c_restart(ops);
            }
            ret = i2c_bit_send_address(bus, msg);
            if ((ret != RT_EOK) && !ignore_nack)
            {
                bit_dbg("receive NACK from device addr 0x%02x msg %d\n",
                        msgs[i].addr, i);
                goto out;
            }
        }
        if (msg->flags & RT_I2C_RD)
        {
            ret = i2c_recv_bytes(bus, msg);
            if (ret >= 1)
                bit_dbg("read %d byte%s\n", ret, ret == 1 ? "" : "s");
            if (ret < msg->len)
            {
                if (ret >= 0)
                    ret = -RT_EIO;
                goto out;
            }
        }
        else
        {
            ret = i2c_send_bytes(bus, msg);
            if (ret >= 1)
                bit_dbg("write %d byte%s\n", ret, ret == 1 ? "" : "s");
            if (ret < msg->len)
            {
                if (ret >= 0)
                    ret = -RT_ERROR;
                goto out;
            }
        }
    }
    ret = i;

out:
    bit_dbg("send stop condition\n");
    i2c_stop(ops);

    return ret;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79

在函数rt_i2c_bit_add_bus中通过调用rt_i2c_bus_device_register实现IIC总线设备的注册,同时也将IIC总线设备操作函数集合i2c_bit_bus_ops注册到上面的IIC设备驱动框架层。

RT-Thread只实现了i2c_bit_xfer这一个操作函数,通过i2c_bit_xfer函数实现代码可以看到其中调用的i2c_start、i2c_restart、i2c_bit_send_address、i2c_recv_bytes、i2c_send_bytes、i2c_stop函数便是软件模拟实现IIC驱动协议的主体。

前面通过pin设备实现的位操作函数集合rt_i2c_bit_ops是实现上述IIC协议函数的基础,IIC驱动协议的实现实际上是按照IIC协议要求的信号时序图设置SDA与SCL引脚电平变换及其间隔时间实现的。

下面先以简单的i2c_start与i2c_stop函数为例,对照其信号产生时序图看其实现代码如下:
IIC开始与停止信号产生时序图

// rt-thread-4.0.1\components\drivers\i2c\i2c-bit-ops.c

static void i2c_start(struct rt_i2c_bit_ops *ops)
{
    SDA_L(ops);
    i2c_delay(ops);
    SCL_L(ops);
}

static void i2c_stop(struct rt_i2c_bit_ops *ops)
{
    SDA_L(ops);
    i2c_delay(ops);
    SCL_H(ops);
    i2c_delay(ops);
    SDA_H(ops);
    i2c_delay2(ops);
}

#define SDA_L(ops)          SET_SDA(ops, 0)
#define SDA_H(ops)          SET_SDA(ops, 1)
#define SCL_L(ops)          SET_SCL(ops, 0)

#define SET_SDA(ops, val)   ops->set_sda(ops->data, val)
#define SET_SCL(ops, val)   ops->set_scl(ops->data, val)
#define GET_SDA(ops)        ops->get_sda(ops->data)
#define GET_SCL(ops)        ops->get_scl(ops->data)

rt_inline void i2c_delay(struct rt_i2c_bit_ops *ops)
{
    ops->udelay((ops->delay_us + 1) >> 1);
}

rt_inline void i2c_delay2(struct rt_i2c_bit_ops *ops)
{
    ops->udelay(ops->delay_us);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

函数i2c_start与i2c_stop的实现完全是按照IIC协议要求的信号产生时序图设置SDA与SCL的电平进行的。

接下来看数据传输函数的实现,地址发送函数i2c_bit_send_address实际也是通过数据传输函数实现的,只是地址包含读写、7位/10位等标志位的设置。消息传输函数i2c_recv_bytes与i2c_send_bytes最终是通过位传输函数i2c_writeb与i2c_readb实现的。

从下面的IIC数据位传输与应答信号产生时序图可以看出,IIC写入数据时只能在SCL为低电平时改变SDA的电平,IIC读取数据时只能在SCL为高电平时读取SDA的电平状态才是有效信号(SDA高电平则表示数据‘1’,SDA低电平则表示数据‘0’),每写入一字节数据需要等待对方的应答/非应答信号,每读取一个字节数据需要向对方发送应答/非应答信号。

在传输一个字节数据后发送端释放SDA控制权,将SDA拉高并交由接收方控制,接收方若希望继续,则给出“应答(ACK)”信号,即SDA为低电平;反之给出“非应答(NACK)”信号,即SDA为高电平。
IIC数据传输与应答信号产生时序图
按照上面IIC数据位传输与应答信号产生的时序图,实现IIC协议中数据传输函数的代码如下:

// rt-thread-4.0.1\components\drivers\i2c\i2c-bit-ops.c

static rt_int32_t i2c_writeb(struct rt_i2c_bus_device *bus, rt_uint8_t data)
{
    rt_int32_t i;
    rt_uint8_t bit;

    struct rt_i2c_bit_ops *ops = bus->priv;

    for (i = 7; i >= 0; i--)
    {
        SCL_L(ops);
        bit = (data >> i) & 1;
        SET_SDA(ops, bit);
        i2c_delay(ops);
        if (SCL_H(ops) < 0)
            return -RT_ETIMEOUT;
    }
    SCL_L(ops);
    i2c_delay(ops);

    return i2c_waitack(ops);
}

static rt_int32_t i2c_readb(struct rt_i2c_bus_device *bus)
{
    rt_uint8_t i;
    rt_uint8_t data = 0;
    struct rt_i2c_bit_ops *ops = bus->priv;

    SDA_H(ops);
    i2c_delay(ops);
    for (i = 0; i < 8; i++)
    {
        data <<= 1;

        if (SCL_H(ops) < 0)
            return -RT_ETIMEOUT;

        if (GET_SDA(ops))
            data |= 1;
        SCL_L(ops);
        i2c_delay2(ops);
    }

    return data;
}

rt_inline rt_bool_t i2c_waitack(struct rt_i2c_bit_ops *ops)
{
    rt_bool_t ack;

    SDA_H(ops);
    i2c_delay(ops);

    if (SCL_H(ops) < 0)
        return -RT_ETIMEOUT;

    ack = !GET_SDA(ops);    /* ACK : SDA pin is pulled low */
    bit_dbg("%s\n", ack ? "ACK" : "NACK");

    SCL_L(ops);

    return ack;
}

static rt_size_t i2c_send_bytes(struct rt_i2c_bus_device *bus,
                                struct rt_i2c_msg        *msg)
{
    rt_int32_t ret;
    rt_size_t bytes = 0;
    const rt_uint8_t *ptr = msg->buf;
    rt_int32_t count = msg->len;
    rt_uint16_t ignore_nack = msg->flags & RT_I2C_IGNORE_NACK;

    while (count > 0)
    {
        ret = i2c_writeb(bus, *ptr);

        if ((ret > 0) || (ignore_nack && (ret == 0)))
        {
            count --;
            ptr ++;
            bytes ++;
        }
        else if (ret == 0)
        {
            i2c_dbg("send bytes: NACK.\n");

            return 0;
        }
        else
        {
            i2c_dbg("send bytes: error %d\n", ret);

            return ret;
        }
    }

    return bytes;
}

static rt_err_t i2c_send_ack_or_nack(struct rt_i2c_bus_device *bus, int ack)
{
    struct rt_i2c_bit_ops *ops = bus->priv;

    if (ack)
        SET_SDA(ops, 0);
    i2c_delay(ops);
    if (SCL_H(ops) < 0)
    {
        bit_dbg("ACK or NACK timeout\n");

        return -RT_ETIMEOUT;
    }
    SCL_L(ops);

    return RT_EOK;
}

static rt_size_t i2c_recv_bytes(struct rt_i2c_bus_device *bus,
                                struct rt_i2c_msg        *msg)
{
    rt_int32_t val;
    rt_int32_t bytes = 0;   /* actual bytes */
    rt_uint8_t *ptr = msg->buf;
    rt_int32_t count = msg->len;
    const rt_uint32_t flags = msg->flags;

    while (count > 0)
    {
        val = i2c_readb(bus);
        if (val >= 0)
        {
            *ptr = val;
            bytes ++;
        }
        else
        {
            break;
        }

        ptr ++;
        count --;

        if (!(flags & RT_I2C_NO_READ_ACK))
        {
            val = i2c_send_ack_or_nack(bus, count);
            if (val < 0)
                return val;
        }
    }

    return bytes;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155

三、IIC应用示例之AHT10

3.1 AHT10温湿度传感器简介

AHT10是一款高精度、完全校准、贴片封装的温湿度传感器,包括一个电容式感湿元件和一个高性能CMOS微处理器相连接。AHT10采用标准IIC通信方式,体积小、功耗低、响应快、性价比高。

该传感器输入电压范围为1.8V - 3.3V,测量温度与湿度的量程、精度如下表所示:
AHT10温湿度量程与精度
AHT10接口定义及其在潘多拉开发板上与STM32L475芯片的连接原理图如下:
AHT10与STM32L475连接原理图
从上图可知潘多拉开发板使用PC1与PD6来模拟IIC总线的SDA与SCL引脚和AHT10温湿度传感器进行通信。

AHT10上电后,MCU要通过IIC协议访问AHT10传感器,需要知道AHT10的设备地址及其支持的命令,查询AHT10 datasheet可知,AHT10的IIC设备地址为0x38,其支持的基本命令与返回状态位如下表所示:
AHT10支持的IIC基本命令及状态位说明
MCU通过IIC协议读取AHT10温湿度数据的IIC传输数据帧如下图所示:
通过IIC协议初始化AHT10的数据帧格式
通过IIC协议读取AHT10温湿度数据的数据帧格式
通过AHT10触发测量数据并读取温湿度数据的IIC传输数据时序图可以编写AHT10的驱动函数(或访问接口函数),读取出的温湿度数据需要换算成更直观的格式,换算公式如下:
AHT10温湿度数据转换公式

3.2 IIC设备应用示例

这里既然采用软件模拟IIC,RT-Thread pin设备并不需要在CubeMX中再配置GPIO引脚,因此可以直接跳过CubeMX配置。

软件模拟IIC需要配置模拟SDA与SCL使用的GPIO引脚号,这些可以通过宏定义配置,我们就在Kconfig内配置,然后在menuconfig中使能配置项。在.\board\Kconfig文件内已经有了I2C1配置项如下:

// projects\stm32l475_device_sample\board\Kconfig
	......
    menuconfig BSP_USING_I2C1
        bool "Enable I2C1 BUS (software simulation)"
        default n
        select RT_USING_I2C
        select RT_USING_I2C_BITOPS
        select RT_USING_PIN
        if BSP_USING_I2C1
            config BSP_I2C1_SCL_PIN
                int "i2c1 scl pin number"
                range 1 176
                default 15
            config BSP_I2C1_SDA_PIN
                int "I2C1 sda pin number"
                range 1 176
                default 16
        endif
    ......
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

这个配置项也不需要修改,IIC SDA与SCL使用的GPIO引脚号在menuconfig中配置即可,首先查得PC1与PD6在RT-Thread PIN设备管理中的引脚号(libraries\HAL_Drivers\drv_gpio.c)分别为33和54,也即配置BSP_I2C1_SDA_PIN为33,BSP_I2C1_SCL_PIN为54,在menuconfig中的配置界面如下:
配置软件模拟I2C1
menuconfig保存配置后,模拟IIC的引脚就配置好了,RT-Thread中的I2C1设备就可以使用了(I2C初始化与注册过程被RT-Thread自动初始化组件调用执行,用户只需完成引脚配置与依赖宏使能即可)。

用户要读取AHT10的温湿度数据,还需要按照前面介绍的AHT10 IIC通讯命令进行,RT-Thread正好提供了AHT10的驱动函数,我们直接使用即可,在menuconfig中使能AHT10驱动的配置界面如下:
AHT10软件包配置项
AHT10软件包支持多次读取输出平均值,这里重点介绍AHT10对IIC设备的访问,所以就不使能average filter了,选择最新版本,保存配置。

在env界面执行pkgs --update从github下载aht10软件包到当前工程,命令执行结果如下:
下载AHT10软件包
AHT10软件包下载到当前工程后,我们先看看其是怎么实现的,首先看AHT10设备控制块:

// projects\stm32l475_device_sample\packages\aht10-latest\aht10.h

struct aht10_device
{
    struct rt_i2c_bus_device *i2c;

#ifdef AHT10_USING_SOFT_FILTER
    filter_data_t temp_filter;
    filter_data_t humi_filter;

    rt_thread_t thread;
    rt_uint32_t period; //sample period
#endif /* AHT10_USING_SOFT_FILTER */

    rt_mutex_t lock;
};
typedef struct aht10_device *aht10_device_t;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

由于我们没有使能AHT10_USING_SOFT_FILTER,先忽略与平均值过滤器相关的成员,结构体aht10_device只有两个成员:I2C总线设备句柄aht10_device.i2c与互斥锁aht10_device.lock。

接着看AHT10设备的初始化过程:

// projects\stm32l475_device_sample\packages\aht10-latest\aht10.c

/**
 * This function initializes aht10 registered device driver
 *
 * @param dev the name of aht10 device
 *
 * @return the aht10 device.
 */
aht10_device_t aht10_init(const char *i2c_bus_name)
{
    aht10_device_t dev;

    RT_ASSERT(i2c_bus_name);

    dev = rt_calloc(1, sizeof(struct aht10_device));
    if (dev == RT_NULL)
    {
        LOG_E("Can't allocate memory for aht10 device on '%s' ", i2c_bus_name);
        return RT_NULL;
    }

    dev->i2c = rt_i2c_bus_device_find(i2c_bus_name);
    if (dev->i2c == RT_NULL)
    {
        LOG_E("Can't find aht10 device on '%s' ", i2c_bus_name);
        rt_free(dev);
        return RT_NULL;
    }

    dev->lock = rt_mutex_create("mutex_aht10", RT_IPC_FLAG_FIFO);
    if (dev->lock == RT_NULL)
    {
        LOG_E("Can't create mutex for aht10 device on '%s' ", i2c_bus_name);
        rt_free(dev);
        return RT_NULL;
    }
#ifdef AHT10_USING_SOFT_FILTER
	......
#endif /* AHT10_USING_SOFT_FILTER */
    sensor_init(dev);

    return dev;
}

static rt_err_t sensor_init(aht10_device_t dev)
{
    rt_uint8_t temp[2] = {0, 0};

    write_reg(dev->i2c, AHT10_NORMAL_CMD, temp);
    rt_thread_delay(rt_tick_from_millisecond(500)); //at least 300 ms

    temp[0] = 0x08;
    temp[1] = 0x00;
    write_reg(dev->i2c, AHT10_CALIBRATION_CMD, temp); //go into calibration
    rt_thread_delay(rt_tick_from_millisecond(450));   //at least 300 ms

    return RT_EOK;
}

#define AHT10_ADDR 0x38 //connect GND

#define AHT10_CALIBRATION_CMD 0xE1 //calibration cmd for measuring
#define AHT10_NORMAL_CMD 0xA8      //normal cmd
#define AHT10_GET_DATA 0xAC        //get data cmd
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65

我们在使用AHT10前需要先手动调用aht10_init函数完成AHT10设备的初始化,其初始化过程实际上就是通过IIC总线向AHT10模块发送初始化命令0xE1,这在前面AHT10通讯命令中介绍过。

继续看读取AHT10温湿度数据的接口函数实现过程:

// projects\stm32l475_device_sample\packages\aht10-latest\aht10.c

/**
 * This function reads temperature by aht10 sensor measurement
 *
 * @param dev the pointer of device driver structure
 *
 * @return the relative temperature converted to float data.
 */
float aht10_read_temperature(aht10_device_t dev)
{
#ifdef AHT10_USING_SOFT_FILTER
    average_measurement(dev, &dev->temp_filter);

    return dev->temp_filter.average;
#else
    return read_hw_temperature(dev);
#endif /* AHT10_USING_SOFT_FILTER */
}

/**
 * This function reads relative humidity by aht10 sensor measurement
 *
 * @param dev the pointer of device driver structure
 *
 * @return the relative humidity converted to float data.
 */
float aht10_read_humidity(aht10_device_t dev)
{
#ifdef AHT10_USING_SOFT_FILTER
    average_measurement(dev, &dev->humi_filter);

    return dev->humi_filter.average;
#else
    return read_hw_humidity(dev);
#endif /* AHT10_USING_SOFT_FILTER */
}

static float read_hw_temperature(aht10_device_t dev)
{
    rt_uint8_t temp[6];
    float cur_temp = -50.0;  //The data is error with missing measurement.  
    rt_err_t result;

    RT_ASSERT(dev);

    result = rt_mutex_take(dev->lock, RT_WAITING_FOREVER);
    if (result == RT_EOK)
    {
        rt_uint8_t cmd[2] = {0, 0};
        write_reg(dev->i2c, AHT10_GET_DATA, cmd); // sample data cmd

        result = calibration_enabled(dev);
        if (result != RT_EOK)
        {
            rt_thread_mdelay(1500);
            sensor_init(dev); // reset sensor
            LOG_E("The aht10 is under an abnormal status. Please try again");
        }
        else
        {
            read_regs(dev->i2c, 6, temp); // get data
            /*sensor temperature converse to reality */
            cur_temp = ((temp[3] & 0xf) << 16 | temp[4] << 8 | temp[5]) * 200.0 / (1 << 20) - 50;
        }
    }
    else
    {
        LOG_E("The aht10 could not respond temperature measurement at this time. Please try again");
    }
    rt_mutex_release(dev->lock);

    return cur_temp;
}

static float read_hw_humidity(aht10_device_t dev)
{
    rt_uint8_t temp[6];
    float cur_humi = 0.0;  //The data is error with missing measurement.  
    rt_err_t result;

    RT_ASSERT(dev);

    result = rt_mutex_take(dev->lock, RT_WAITING_FOREVER);
    if (result == RT_EOK)
    {
        rt_uint8_t cmd[2] = {0, 0};
        write_reg(dev->i2c, AHT10_GET_DATA, cmd); // sample data cmd

        result = calibration_enabled(dev);
        if (result != RT_EOK)
        {
            rt_thread_mdelay(1500);
            sensor_init(dev);
            LOG_E("The aht10 is under an abnormal status. Please try again");
        }
        else
        {
            read_regs(dev->i2c, 6, temp);                                                          // get data
            cur_humi = (temp[1] << 12 | temp[2] << 4 | (temp[3] & 0xf0) >> 4) * 100.0 / (1 << 20); //sensor humidity converse to reality
        }
    }
    else
    {
        LOG_E("The aht10 could not respond temperature measurement at this time. Please try again");
    }
    rt_mutex_release(dev->lock);

    return cur_humi;
}

static rt_err_t write_reg(struct rt_i2c_bus_device *bus, rt_uint8_t reg, rt_uint8_t *data)
{
    rt_uint8_t buf[3];

    buf[0] = reg; //cmd
    buf[1] = data[0];
    buf[2] = data[1];

    if (rt_i2c_master_send(bus, AHT10_ADDR, 0, buf, 3) == 3)
        return RT_EOK;
    else
        return -RT_ERROR;
}

static rt_err_t read_regs(struct rt_i2c_bus_device *bus, rt_uint8_t len, rt_uint8_t *buf)
{
    struct rt_i2c_msg msgs;

    msgs.addr = AHT10_ADDR;
    msgs.flags = RT_I2C_RD;
    msgs.buf = buf;
    msgs.len = len;

    if (rt_i2c_transfer(bus, &msgs, 1) == 1)
    {
        return RT_EOK;
    }
    else
    {
        return -RT_ERROR;
    }
}

/*check calibration enable */
static rt_uint8_t calibration_enabled(aht10_device_t dev)
{
    rt_uint8_t val = 0;

    read_regs(dev->i2c, 1, &val);

    if ((val & 0x68) == 0x08)
        return RT_EOK;
    else
        return RT_ERROR;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156

读取AHT10温湿度数据,需要先发送触发测量数据的命令0xAC,再读取温湿度数据。读取温湿度数据前需要先确认AHT10是否已完成初始化,该过程通过函数calibration_enabled实现,原理是读取AHT10状态位,Bit[3]使能校准位是否被置位,若未校准则执行初始化函数,若已校准则读取温湿度数据,并按照AHT10温湿度信号换算公式处理读取的数据。

AHT10读取温湿度数据的过程中,需要通过IIC总线与AHT10通信,在函数write_reg与read_regs中分别调用了RT-Thread I2C设备接口函数rt_i2c_master_send与rt_i2c_transfer,从上面的代码中可以看出IIC设备的访问过程,即先通过rt_device_find发现I2C设备(函数rt_i2c_bus_device_find是对rt_device_find的再封装),然后就可以通过I2C设备驱动框架层或 I / O设备管理层的接口函数访问I2C设备了。

我们使用AHT10软件包提供的接口函数编写个读取AHT10温湿度数据的示例程序,先在.\applications目录下新建i2c_sample.c文件,在其中编辑读取AHT10温湿度数据的程序代码如下:

// projects\stm32l475_device_sample\applications\i2c_sample.c

#include "rtthread.h"
#include "rtdevice.h"
#include "aht10.h"

static void i2c_aht10_sample(void)
{
    float humidity, temperature;
    aht10_device_t aht10_dev;
    const char *i2c_bus_name = "i2c1";
    int count = 0;

    aht10_dev = aht10_init(i2c_bus_name);
    if(aht10_dev == RT_NULL)
        rt_kprintf("The sensor initializes failed.\n");

    while(++count < 10)
    {
        humidity = aht10_read_humidity(aht10_dev);
        rt_kprintf("read aht10 sensor humidity   : %d.%d %%\n", (int)humidity, (int)(humidity * 10) % 10);

        temperature = aht10_read_temperature(aht10_dev);
        if( temperature >= 0 )
            rt_kprintf("read aht10 sensor temperature: %d.%d°C\n", (int)temperature, (int)(temperature * 10) % 10);
        else
            rt_kprintf("read aht10 sensor temperature: %d.%d°C\n", (int)temperature, (int)(-temperature * 10) % 10);
        
        rt_thread_mdelay(1000);
    }
}
MSH_CMD_EXPORT(i2c_aht10_sample, i2c aht10 sample);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

在env环境下执行scons --target=mdk5命令生成MDK工程,打开生成的工程文件project.uvprojx,编译报错如下:
AHT10编译报错
提示找不到sensor.h文件,我们并没有使能SENSOR传感器框架,在menuconfig中使能SENSOR传感器框架(下文再介绍SENSOR框架工作原理),配置界面如下:
使能sensor框架
保存配置后,在env中执行scons --target=mkd5重新生成工程,打开MDK工程文件project.uvprojx编译无报错,将程序烧录到我们的STM32L475潘多拉开发板中,运行结果如下:
IIC读取AHT10温湿度数据结果
本示例工程源码下载地址:https://github.com/StreamAI/RT-Thread_Projects/tree/master/projects/stm32l475_device_sample

四、Sensor框架原理及示例

Sensor(传感器)是物联网重要的一部分,“Sensor 之于物联网”就相当于“眼睛之于人类”。人类如果没有了眼睛就看不到这大千的花花世界,对于物联网来说也是一样。

如今随着物联网的发展,已经有大量的 Sensor 被开发出来供开发者选择了,如:加速度计(Accelerometer)、磁力计(Magnetometer)、陀螺仪(Gyroscope)、气压计(Barometer/pressure)、湿度计(Humidometer)等。这些传感器,世界上的各大半导体厂商都有生产,虽然增加了市场的可选择性,同时也加大了应用程序开发的难度。因为不同的传感器厂商、不同的传感器都需要配套自己独有的驱动才能运转起来,这样在开发应用程序的时候就需要针对不同的传感器做适配,自然加大了开发难度。为了降低应用开发的难度,增加传感器驱动的可复用性,RT-Thread设计了 Sensor 设备。

Sensor 设备的作用是:为上层提供统一的操作接口(也即 I / O设备管理接口)。传感器设备一般具有如下特性:

  • 接口:标准 device 接口(open/close/read/control);
  • 工作模式:支持 轮询、中断、FIFO 三种模式;
  • 电源模式:支持 掉电、普通、低功耗、高功耗 四种模式

4.1 Sensor设备描述

既然要兼容市场大部分传感器,就要对Sensor设备有恰当的描述,SENSOR框架描述传感器设备的数据结构如下:

// rt-thread-4.0.1\components\drivers\sensors\sensor.h

typedef struct rt_sensor_device *rt_sensor_t;

struct rt_sensor_device
{
    struct rt_device             parent;    /* The standard device */

    struct rt_sensor_info        info;      /* The sensor info data */
    struct rt_sensor_config      config;    /* The sensor config data */

    void                        *data_buf;  /* The buf of the data received */
    rt_size_t                    data_len;  /* The size of the data received */

    const struct rt_sensor_ops  *ops;       /* The sensor ops */

    struct rt_sensor_module     *module;    /* The sensor module */
    
    rt_err_t (*irq_handle)(rt_sensor_t sensor);             /* Called when an interrupt is generated, registered by the driver */
};

struct rt_sensor_info
{
    rt_uint8_t     type;                    /* The sensor type */
    rt_uint8_t     vendor;                  /* Vendor of sensors */
    const char    *model;                   /* model name of sensor */
    rt_uint8_t     unit;                    /* unit of measurement */
    rt_uint8_t     intf_type;               /* Communication interface type */
    rt_int32_t     range_max;               /* maximum range of this sensor's value. unit is 'unit' */
    rt_int32_t     range_min;               /* minimum range of this sensor's value. unit is 'unit' */
    rt_uint32_t    period_min;              /* Minimum measurement period,unit:ms. zero = not a constant rate */
    rt_uint8_t     fifo_max;
};

struct rt_sensor_config
{
    struct rt_sensor_intf        intf;      /* sensor interface config */
    struct rt_device_pin_mode    irq_pin;   /* Interrupt pin, The purpose of this pin is to notification read data */
    rt_uint8_t                   mode;      /* sensor work mode */
    rt_uint8_t                   power;     /* sensor power mode */
    rt_uint16_t                  odr;       /* sensor out data rate */
    rt_int32_t                   range;     /* sensor range of measurement */
};

struct rt_sensor_intf
{
    char                       *dev_name;   /* The name of the communication device */
    rt_uint8_t                  type;       /* Communication interface type */
    void                       *user_data;  /* Private data for the sensor. ex. i2c addr,spi cs,control I/O */
};

struct rt_sensor_ops
{
    rt_size_t (*fetch_data)(struct rt_sensor_device *sensor, void *buf, rt_size_t len);
    rt_err_t (*control)(struct rt_sensor_device *sensor, int cmd, void *arg);
};

struct rt_sensor_module
{
    rt_mutex_t                   lock;                      /* The module lock */

    rt_sensor_t                  sen[RT_SENSOR_MODULE_MAX]; /* The module contains a list of sensors */
    rt_uint8_t                   sen_num;                   /* Number of sensors contained in the module */
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64

描述传感器设备的结构体rt_sensor_device继承自设备基类rt_device,成员包括:保存传感器信息数据的rt_sensor_info,保存传感器配置数据的rt_sensor_config,传感器接收数据基地址及长度,传感器操作函数集合rt_sensor_ops,拥有多个传感器的模组rt_sensor_module,传感器的中断处理函数指针irq_handle等,对传感器设备的描述还是挺完备的。

sensor配置结构体rt_sensor_config中包含了传感器接口rt_sensor_intf,中断引脚rt_device_pin_mode,工作模式mode,功耗模式power,传输速率odr,测量范围range等,该结构体需要用户根据具体的传感器型号配置。

Sensor的操作函数集合rt_sensor_ops中只有两个成员:获取传感器数据的接口函数fetch_data,传感器控制接口函数control。这两个函数需要具体的传感器在使用sensor框架时实现并注册到sensor框架中。

读取不同的Sensor,数据格式是不一样的,数据单位也是不一样的,为了将获取的传感器数据以更直观的形式展示,还需要专门的数据结构描述读取的传感器数据,描述sensor数据的数据结构如下:

// rt-thread-4.0.1\components\drivers\sensors\sensor.h

struct rt_sensor_data
{
    rt_uint32_t         timestamp;          /* The timestamp when the data was received */
    rt_uint8_t          type;               /* The sensor type of the data */
    union
    {
        struct sensor_3_axis acce;          /* Accelerometer.       unit: mG          */
        struct sensor_3_axis gyro;          /* Gyroscope.           unit: mdps        */
        struct sensor_3_axis mag;           /* Magnetometer.        unit: mGauss      */
        rt_int32_t           temp;          /* Temperature.         unit: dCelsius    */
        rt_int32_t           humi;          /* Relative humidity.   unit: permillage  */
        rt_int32_t           baro;          /* Pressure.            unit: pascal (Pa) */
        rt_int32_t           light;         /* Light.               unit: lux         */
        rt_int32_t           proximity;     /* Distance.            unit: centimeters */
        rt_int32_t           hr;            /* Heart rate.          unit: bpm         */
        rt_int32_t           tvoc;          /* TVOC.                unit: permillage  */
        rt_int32_t           noise;         /* Noise Loudness.      unit: HZ          */
        rt_uint32_t          step;          /* Step sensor.         unit: 1           */
        rt_int32_t           force;         /* Force sensor.        unit: mN          */
    } data;
};

/* 3-axis Data Type */
struct sensor_3_axis
{
    rt_int32_t x;
    rt_int32_t y;
    rt_int32_t z;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

结构体rt_sensor_data包含传感器类型type,读取数据的时间戳timestamp,读取到的数据data三个成员构成,其中data是一个共用体,可以保存这13类传感器中的任意一类数据。

4.2 Sensor设备访问接口

先从sensor设备的创建与注册过程开始介绍,该过程的实现代码如下:

// rt-thread-4.0.1\components\drivers\sensors\sensor.c

/*
 * sensor register
 */
int rt_hw_sensor_register(rt_sensor_t sensor,
                          const char              *name,
                          rt_uint32_t              flag,
                          void                    *data)
{
    rt_int8_t result;
    rt_device_t device;
    RT_ASSERT(sensor != RT_NULL);

    char *sensor_name = RT_NULL, *device_name = RT_NULL;

    /* Add a type name for the sensor device */
    sensor_name = sensor_name_str[sensor->info.type];
    device_name = rt_calloc(1, rt_strlen(sensor_name) + 1 + rt_strlen(name));
    if (device_name == RT_NULL)
    {
        LOG_E("device_name calloc failed!");
        return -RT_ERROR;
    }

    rt_memcpy(device_name, sensor_name, rt_strlen(sensor_name) + 1);
    strcat(device_name, name);

    if (sensor->module != RT_NULL && sensor->module->lock == RT_NULL)
    {
        /* Create a mutex lock for the module */
        sensor->module->lock = rt_mutex_create(name, RT_IPC_FLAG_FIFO);
        if (sensor->module->lock == RT_NULL)
        {
            rt_free(device_name);
            return -RT_ERROR;
        }
    }

    device = &sensor->parent;

#ifdef RT_USING_DEVICE_OPS
    device->ops         = &rt_sensor_ops;
#else
    device->init        = rt_sensor_init;
    device->open        = rt_sensor_open;
    device->close       = rt_sensor_close;
    device->read        = rt_sensor_read;
    device->write       = RT_NULL;
    device->control     = rt_sensor_control;
#endif
    device->type        = RT_Device_Class_Sensor;
    device->rx_indicate = RT_NULL;
    device->tx_complete = RT_NULL;
    device->user_data   = data;

    result = rt_device_register(device, device_name, flag | RT_DEVICE_FLAG_STANDALONE);
    if (result != RT_EOK)
    {
        LOG_E("rt_sensor register err code: %d", result);
        return result;
    }

    LOG_I("rt_sensor init success");
    return RT_EOK;
}

static char *const sensor_name_str[] =
{
    "none",
    "acce_",     /* Accelerometer     */
    "gyro_",     /* Gyroscope         */
    "mag_",      /* Magnetometer      */
    "temp_",     /* Temperature       */
    "humi_",     /* Relative Humidity */
    "baro_",     /* Barometer         */
    "li_",       /* Ambient light     */
    "pr_",       /* Proximity         */
    "hr_",       /* Heart Rate        */
    "tvoc_",     /* TVOC Level        */
    "noi_",      /* Noise Loudness    */
    "step_"      /* Step sensor       */
    "forc_"      /* Force sensor      */
};

#ifdef RT_USING_DEVICE_OPS
const static struct rt_device_ops rt_sensor_ops =
{
    rt_sensor_init,
    rt_sensor_open,
    rt_sensor_close,
    rt_sensor_read,
    RT_NULL,
    rt_sensor_control
};
#endif
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96

传感器设备注册时,设备名自动加上了传感器类型前缀sensor_name_str,注册后的sensor设备若要访问也需要加上类型前缀,由于rt_device设备名最大长度RT_NAME_MAX默认为8,也即加上sensor type前缀后的设备名不应超过RT_NAME_MAX,若设备名长度不够可以将RT_NAME_MAX值设置大些,否则设备名会被自动截取。

Sensor框架向 I / O设备管理层注册的设备操作函数集合rt_sensor_ops是通过调用rt_sensor_ops来实现的,这里就不逐一展示实现代码了,需要注意的是从传感器读取的数据都是以rt_sensor_data为单位组织的,读取的数据长度则是rt_sensor_data数据的个数。

传感器设备向 I / O设备管理层注册后,就可以通过 I / O设备管理接口访问传感器设备了,接口函数在前面介绍过,这里不再详细介绍,只展示函数描述:
访问传感器设备接口寒素

4.3 AHT10移植到Sensor框架

前面介绍AHT10软件包时使能了Sensor框架,说明最新版本的AHT10软件包支持Sensor框架,这倒是省去了我们的移植工作,下面看看移植实现过程。

先看AHT10传感器的初始化过程实现代码:

// bsp\stm32l475_week4\packages\aht10-latest\sensor_asair_aht10.c

int rt_hw_aht10_init(const char *name, struct rt_sensor_config *cfg)
{
    rt_int8_t result;
    rt_sensor_t sensor_temp = RT_NULL, sensor_humi = RT_NULL;
    
#ifdef PKG_USING_AHT10   
    
     /* temperature sensor register */
    sensor_temp = rt_calloc(1, sizeof(struct rt_sensor_device));
    if (sensor_temp == RT_NULL)
        return -1;

    sensor_temp->info.type       = RT_SENSOR_CLASS_TEMP;
    sensor_temp->info.vendor     = RT_SENSOR_VENDOR_UNKNOWN;
    sensor_temp->info.model      = "aht10";
    sensor_temp->info.unit       = RT_SENSOR_UNIT_DCELSIUS;
    sensor_temp->info.intf_type  = RT_SENSOR_INTF_I2C;
    sensor_temp->info.range_max  = SENSOR_TEMP_RANGE_MAX;
    sensor_temp->info.range_min  = SENSOR_TEMP_RANGE_MIN;
    sensor_temp->info.period_min = 5;

    rt_memcpy(&sensor_temp->config, cfg, sizeof(struct rt_sensor_config));
    sensor_temp->ops = &sensor_ops;

    result = rt_hw_sensor_register(sensor_temp, name, RT_DEVICE_FLAG_RDONLY, RT_NULL);
    if (result != RT_EOK)
    {
        LOG_E("device register err code: %d", result);
        goto __exit;
    }
    
    /* humidity sensor register */
    sensor_humi = rt_calloc(1, sizeof(struct rt_sensor_device));
    if (sensor_humi == RT_NULL)
        return -1;

    sensor_humi->info.type       = RT_SENSOR_CLASS_HUMI;
    sensor_humi->info.vendor     = RT_SENSOR_VENDOR_UNKNOWN;
    sensor_humi->info.model      = "aht10";
    sensor_humi->info.unit       = RT_SENSOR_UNIT_PERMILLAGE;
    sensor_humi->info.intf_type  = RT_SENSOR_INTF_I2C;
    sensor_humi->info.range_max  = SENSOR_HUMI_RANGE_MAX;
    sensor_humi->info.range_min  = SENSOR_HUMI_RANGE_MIN;
    sensor_humi->info.period_min = 5;

    rt_memcpy(&sensor_humi->config, cfg, sizeof(struct rt_sensor_config));
    sensor_humi->ops = &sensor_ops;

    result = rt_hw_sensor_register(sensor_humi, name, RT_DEVICE_FLAG_RDONLY, RT_NULL);
    if (result != RT_EOK)
    {
        LOG_E("device register err code: %d", result);
        goto __exit;
    }
    
#endif
    
    _aht10_init(&cfg->intf);
    return RT_EOK;
    
__exit:
    if (sensor_temp)
        rt_free(sensor_temp);
    if (sensor_humi)
        rt_free(sensor_humi);
    if (temp_humi_dev)
        aht10_deinit(temp_humi_dev);
    return -RT_ERROR;     
}

static struct rt_sensor_ops sensor_ops =
{
    aht10_fetch_data,
    aht10_control
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77

AHT10传感器包含温度传感器与湿度传感器两个类型,因此在初始化过程中分别创建并注册了两个sensor设备sensor_temp与sensor_humi。传感器对象rt_sensor_device通过动态分配内存空间,在初始化函数中完成了传感器信息rt_sensor_info的初始化,传感器配置rt_sensor_config则需要用户配置并以参数形式传入,最后将实现的传感器操作函数集合sensor_ops通过调用函数rt_hw_sensor_register注册到Sensor框架中。完成sensor设备注册后,调用前面介绍的aht10_init函数完成AHT10设备的初始化。

由于AHT10没有中断引脚,仅支持轮询访问方式,也没有功耗控制等复杂功能,因此其操作函数集合rt_sensor_ops实现比较简单,实现代码如下:

// bsp\stm32l475_week4\packages\aht10-latest\sensor_asair_aht10.c

static rt_size_t aht10_fetch_data(struct rt_sensor_device *sensor, void *buf, rt_size_t len)
{
    RT_ASSERT(buf);

    if (sensor->config.mode == RT_SENSOR_MODE_POLLING)
    {
        return _aht10_polling_get_data(sensor, buf);
    }
    else
        return 0;
}

static rt_err_t aht10_control(struct rt_sensor_device *sensor, int cmd, void *args)
{
    rt_err_t result = RT_EOK;

    return result;
}

static rt_size_t _aht10_polling_get_data(rt_sensor_t sensor, struct rt_sensor_data *data)
{
    float temperature_x10, humidity_x10;
    
    if (sensor->info.type == RT_SENSOR_CLASS_TEMP)
    {
        temperature_x10 = 10 * aht10_read_temperature(temp_humi_dev);
        data->data.temp = (rt_int32_t)temperature_x10;
        data->timestamp = rt_sensor_get_ts();
    }    
    else if (sensor->info.type == RT_SENSOR_CLASS_HUMI)
    {
        humidity_x10    = 10 * aht10_read_humidity(temp_humi_dev);
        data->data.humi = (rt_int32_t)humidity_x10;
        data->timestamp = rt_sensor_get_ts();
    }
    return 1;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

AHT10获取传感器数据函数中,读取的温湿度值以十倍形式给出,主要是由于传感器数据结构体rt_sensor_data中的成员类型都为整数,要想精确到小数位需要另行处理,比如精确到一位小数,就把读取的值以10倍形式给出,用户对其除10得整数部分,对其取10的模数得小数部分。

4.4 Sensor框架应用示例

前面我们已经在menuconfig中使能sensor框架了,所以这里直接在.\applications\i2c_sample.c文件中新增示例程序。

要访问AHT10设备,首先需要进行初始化,前面介绍AHT10初始化函数rt_hw_aht10_init时提到,需要我们自己配置rt_sensor_config,并将其以参数形式传入,编辑的初始化代码如下:

// projects\stm32l475_device_sample\applications\i2c_sample.c

#include "rtthread.h"
#include "rtdevice.h"
#include "sensor_asair_aht10.h"

#define AHT10_I2C_BUS_NAME      "i2c1"

static int rt_hw_aht10_port(void)
{
    struct rt_sensor_config cfg;

    cfg.intf.dev_name = AHT10_I2C_BUS_NAME;
    cfg.intf.type = RT_SENSOR_INTF_I2C;
    cfg.intf.user_data = (void *)AHT10_I2C_ADDR;
    cfg.mode = RT_SENSOR_MODE_POLLING; 
    rt_hw_aht10_init("aht10", &cfg);

    return RT_EOK;
}
INIT_ENV_EXPORT(rt_hw_aht10_port);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

上面使用了RT-Thread自动初始化机制,传感器配置项rt_sensor_config.intf.user_data对于IIC设备常用来保存其设备地址(对于SPI设备常用来保存其片选引脚)。

完成AHT10传感器设备初始化后,就可以使用 I / O设备管理接口访问该传感器了,访问AHT10传感器的示例代码如下:

// projects\stm32l475_device_sample\applications\i2c_sample.c

#define SENSOR_TEMP_NAME    "temp_aht10"
#define SENSOR_HUMI_NAME    "humi_aht10"

static void sensor_aht10_sample(void)
{
    rt_device_t sensor_temp, sensor_humi;
    struct rt_sensor_data sensor_data;

    sensor_temp = rt_device_find(SENSOR_TEMP_NAME);

    rt_device_open(sensor_temp, RT_DEVICE_FLAG_RDONLY);
    if(rt_device_read(sensor_temp, 0, &sensor_data, 1) == 1)
    {
        rt_kprintf("read aht10 sensor temperature:%3d.%d°C, timestamp:%5d\n", sensor_data.data.temp / 10, sensor_data.data.temp % 10, sensor_data.timestamp);
    }
    rt_device_close(sensor_temp);

    sensor_humi = rt_device_find(SENSOR_HUMI_NAME);

    rt_device_open(sensor_humi, RT_DEVICE_FLAG_RDONLY);
    if(rt_device_read(sensor_humi, 0, &sensor_data, 1) == 1)
    {
        rt_kprintf("read aht10 sensor humidity:%3d.%d%, timestamp:%5d\n", sensor_data.data.humi / 10, sensor_data.data.humi % 10, sensor_data.timestamp);
    }
    rt_device_close(sensor_temp);
}
MSH_CMD_EXPORT_ALIAS(sensor_aht10_sample, sensor_aht10, sensor aht10 sample);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

示例程序编辑完成后保存,在env环境中执行scons --target=mdk5生成MDK工程文件,打开工程文件project.uvprojx编译无报错,将程序烧录到STM32L475潘多拉开发板中,运行结果如下:
通过sensor访问AHT10结果
sensor框架还为用户提供了部分finsh命令,方便用户通过console读取或调试传感器设备,在上图中也给出了sensor finsh命令运行示例。

本示例工程源码下载地址:https://github.com/StreamAI/RT-Thread_Projects/tree/master/projects/stm32l475_device_sample

更多文章:

  • 《RT-Thread Sample Project Source Code Based on STM32L475》
  • 《IOT-OS之RT-Thread(七)— I/O设备模型框架与PIN设备对象管理》
  • 《IOT-OS之RT-Thread(九)— SPI设备对象管理与SFUD管理框架》
注:本文转载自blog.csdn.net的流云IoT的文章"https://blog.csdn.net/m0_37621078/article/details/103115383"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top