IOT-OS之RT-Thread(八)--- IIC设备对象管理与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 */

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;
};

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;
}

从上面的实现代码可以看出,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 */
};

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

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

  • 9
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流云IoT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值