当前位置: 首页 > article >正文

rtthread学习笔记系列--28 I2C驱动

文章目录

  • 28 I2C驱动
    • 28.0 硬件电路
    • 28.1 I2C设备驱动
      • 28.1.1 I2C设备注册
      • 28.2 I2C总线设备驱动
      • 28.2.1 初始化
      • 28.2.2 transfer
    • 28.2 软件I2C
      • 28.1.1 初始化
      • 28.1.2 读写 i2c_bit_xfer
      • 28.1.3 i2c_start
      • 28.1.4 i2c_restart
      • 28.1.5 i2c_stop
      • 28.1.6 i2c_writeb
      • 28.1.7 i2c_waitack
      • 28.1.8 i2c_readb
      • 28.1.9 i2c_bit_send_address
      • 28.1.10 i2c_recv_bytes
      • 28.1.11 i2c_send_ack_or_nack
    • 28.3 STM32 HAL I2C
      • 28.3.1 阻塞方式
      • 28.3.2 非阻塞 中断方式
      • 28.3.3 非阻塞 DMA
      • 28.3.4 SEQ传输函数
    • 28.4 硬件I2C驱动

https://github.com/wdfk-prog/RT-Thread-Study

28 I2C驱动

https://www.i2c-bus.org/i2c-primer/termination-versus-capacitance/

28.0 硬件电路

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

I2C 总线使用 SDA 和 SCL 传输数据和时钟。首先要意识到:SDA 和 SCL 是开漏(在 TTL 世界中也称为开集),也就是说 I2C 主设备和从设备只能将这些线路驱动为低电平或保持开路。如果没有 I2C 设备将其拉低,终端电阻Rp 会将线路拉高至 Vcc。这允许同时操作多个 I2C 主设备(如果它们具有**多主设备功能)或拉伸(从设备可以通过按住 SCL 来减慢通信速度)等功能。

终端电阻 Rp 与线路电容 Cp 一起影响 SDA 和 SCL 上信号的时间行为。虽然 I2C 设备使用开漏驱动器或 FET 拉低线路(通常可以驱动至少约 10mA 或更多),但上拉电阻 Rp 负责将信号恢复到高电平。Rp 通常在 1 kΩ 至 10 kΩ 之间,导致典型的上拉电流约为 1 mA 或更小。这就是 I2C 信号具有锯齿状外观的原因。事实上,每个“齿”在上升沿显示线路的充电特性,在下降沿显示放电特性。

img

SDA(上)和 SCL(下)的 Rp = 10 kΩ 和 Cp = 300 pF。SCL 时钟以 100 kHz(标称值)运行。

28.1 I2C设备驱动

28.1.1 I2C设备注册

  1. 调用 rt_i2c_bus_device_device_init,将I2C设备注册到rt_device中

struct rt_i2c_bus_device_ops

{

    rt_ssize_t (*master_xfer)(struct rt_i2c_bus_device *bus,

                             struct rt_i2c_msg msgs[],

                             rt_uint32_t num);

    rt_ssize_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,

                                int cmd,

                                void *args);

};


struct rt_i2c_bus_device

{

    struct rt_device parent;

    conststruct rt_i2c_bus_device_ops *ops;

    rt_uint16_t  flags;

    struct rt_mutex lock;

    rt_uint32_t  timeout;

    rt_uint32_t  retries;

    void *priv;

};

rt_err_trt_i2c_bus_device_device_init(struct rt_i2c_bus_device *bus,

                                       constchar               *name)

{

    //注册bus设备

    device->user_data = bus;

    //挂钩读写控制函数

    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;

}

28.2 I2C总线设备驱动

28.2.1 初始化

  • 总线初始化互斥量

rt_err_trt_i2c_bus_device_register(struct rt_i2c_bus_device *bus,

                                    constchar               *bus_name)

{

    //初始化锁

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

    //默认超时时间按照一个切换时间

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

    //注册I2C设备,挂钩读写控制函数

    res = rt_i2c_bus_device_device_init(bus, bus_name);

    return res;

}

28.2.2 transfer

  • 加锁形成临界区,调用 master_xfer函数,完成I2C数据传输

    err = rt_mutex_take(&bus->lock, RT_WAITING_FOREVER);

    if (err != RT_EOK)

    {

        return (rt_ssize_t)err;

    }

    ret = bus->ops->master_xfer(bus, msgs, num);

    err = rt_mutex_release(&bus->lock);

  • rt_i2c_master_recv

    msg.flags  = flags | RT_I2C_RD;

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

  • rt_i2c_master_send

    msg.flags = flags;

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

28.2 软件I2C

  • TIMING_DELAY 默认 10us, 即10kHz
  • TIMING_TIMEOUT 默认 10 tick

28.1.1 初始化

  1. 初始化I2C 引脚,设置为开漏输出,默认高电平

    rt_pin_mode(cfg->scl_pin, PIN_MODE_OUTPUT_OD);

    rt_pin_mode(cfg->sda_pin, PIN_MODE_OUTPUT_OD);

    rt_pin_write(cfg->scl_pin, PIN_HIGH);

    rt_pin_write(cfg->sda_pin, PIN_HIGH);

  • 原因:

SDA 和 SCL 是开漏,也就是说 I2C 主设备和从设备只能将这些线路驱动为低电平或保持开路。如果没有 I2C 设备将其拉低,终端电阻Rp 会将线路拉高至 Vcc。


终端电阻 Rp 与线路电容 Cp 一起影响 SDA 和 SCL 上信号的时间行为。虽然 I2C 设备使用开漏驱动器或 FET 拉低线路(通常可以驱动至少约 10mA 或更多),但上拉电阻 Rp 负责将信号恢复到高电平。Rp 通常在 1 kΩ 至 10 kΩ 之间,导致典型的上拉电流约为 1 mA 或更小。这就是 I2C 信号具有锯齿状外观的原因。事实上,每个“齿”在上升沿显示线路的充电特性,在下降沿显示放电特性。

  1. 调用 rt_i2c_bus_device_register注册设I2C总线设备,初始化总线互斥量
  • 将软件I2C操作函数挂钩到总线设备中

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 */


    void (*pin_init)(void);

    rt_bool_t i2c_pin_init_flag;

};


intrt_soft_i2c_init(void)

{

    obj->ops = soft_i2c_ops;

    obj->ops.data = cfg;

    //i2c_bug.priv用于rt_i2c_bit_ops

    obj->i2c_bus.priv = &obj->ops;

}

3.i2c_bus_unlock解锁I2C

  • 死锁

总线表现:SCL为高,SDA一直为低

原因:


正常情况下,I2C总线协议能够保证总线正常的读写操作。

但是,当I2C主设备异常复位时(看门狗动作,板上电源异常导致复位芯片动作,手动按钮复位等等)有可能导致I2C总线死锁产生。下面详细说明一下总线死锁产生的原因。在I2C主设备进行读写操作的过程中.主设备在开始信号后控制SCL产生8个时钟脉冲,然后拉低SCL信号为低电平,在这个时候,从设备输出应答信号,将SDA信号拉为低电平。

    如果这个时候主设备异常复位,SCL就会被释放为高电平。此时,如果从设备没有复位,就会继续I2C的应答,将SDA一直拉为低电平,直到SCL变为低电平,才会结束应答信号。

    而对于I2C主设备来说.复位后检测SCL和SDA信号,如果发现SDA信号为低电平,则会认为I2C总线被占用,会一直等待SCL和SDA信号变为高电平。

    这样,I2C主设备等待从设备释放SDA信号,而同时I2C从设备又在等待主设备将SCL信号拉低以释放应答信号,两者相互等待,I2C总线进入一种死锁状态。

    同样,当I2C进行读操作,I2C从设备应答后输出数据,如果在这个时刻I2C主设备异常复位而此时I2C从设备输出的数据位正好为0,也会导致I2C总线进入死锁状态。


这样主从进入一个相互等待的死锁过程。

解决方式:


死锁解决方法

    在I2C主设备中增加I2C总线恢复程序。每次I2C主设备复位后,如果检测到SDA数据线被拉低,则控制I2C中的SCL时钟线产生9个时钟脉冲(针对8位数据的情况),这样I2C从设备就可以完成被挂起的读操作,从死锁状态中恢复过来。

    这种方法有很大的局限性,因为大部分主设备的I2C模块由内置的硬件电路来实现,软件并不能够直接控制SCL信号模拟产生需要时钟脉冲。


/**

* if i2c is locked, this function will unlock it

*

* @parami2c config class.

*

* @return RT_EOK indicates successful unlock.

*/

staticrt_err_ti2c_bus_unlock(conststruct soft_i2c_config *cfg)

{

    rt_ubase_t i = 0;

    //复位后发现SDA为低电平,则产生9个时钟脉冲

    if(PIN_LOW == rt_pin_read(cfg->sda_pin))

    {

        while(i++ < 9)

        {

            rt_pin_write(cfg->scl_pin, PIN_HIGH);

            rt_hw_us_delay(cfg->timing_delay);

            rt_pin_write(cfg->scl_pin, PIN_LOW);

            rt_hw_us_delay(cfg->timing_delay);

        }

    }

    //还是低电平死锁状态,则返回错误

    if(PIN_LOW == rt_pin_read(cfg->sda_pin))

    {

        return -RT_ERROR;

    }


    return RT_EOK;

}

28.1.2 读写 i2c_bit_xfer


#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)     /* 不发送结束位 */


staticrt_ssize_ti2c_bit_xfer(struct rt_i2c_bus_device *bus,

                              struct rt_i2c_msg         msgs[],

                              rt_uint32_t               num)

{

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

            }

            else

            {

                LOG_D("send start condition");

                i2c_start(ops);

            }

            //发送地址

            ret = i2c_bit_send_address(bus, msg);

            if ((ret != RT_EOK) && !ignore_nack)

            {

                LOG_D("receive NACK from device addr 0x%02x msg %d",

                        msgs[i].addr, i);

                goto out;

            }

        }

        //读取数据

        if (msg->flags & RT_I2C_RD)

        {

            ret = i2c_recv_bytes(bus, msg);

            if (ret >= 1)

            {

                LOG_D("read %d byte%s", 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)

            {

                LOG_D("write %d byte%s", ret, ret == 1 ? "" : "s");

            }

            if (ret < msg->len)

            {

                if (ret >= 0)

                    ret = -RT_ERROR;

                goto out;

            }

        }

    }

    ret = i;


out:

    if (!(msg->flags & RT_I2C_NO_STOP))

    {

        LOG_D("send stop condition");

        //发送停止条件

        i2c_stop(ops);

    }


    return ret;

}

28.1.3 i2c_start


staticvoidi2c_start(struct rt_i2c_bit_ops *ops)

{

    //开始前,I2C总线应该为空闲状态,即SDA和SCL都为高电平

#ifdef RT_I2C_BITOPS_DEBUG

    if (ops->get_scl &&!GET_SCL(ops))

    {

        LOG_E("I2C bus error, SCL line low");

    }

    if (ops->get_sda &&!GET_SDA(ops))

    {

        LOG_E("I2C bus error, SDA line low");

    }

#endif

    //SCL 线是高电平时,SDA 线从高电平向低电平切换表示起始条件

    SDA_L(ops);

    i2c_delay(ops);

    SCL_L(ops);

}

28.1.4 i2c_restart


staticvoidi2c_restart(struct rt_i2c_bit_ops *ops)

{

    SDA_H(ops);

    SCL_H(ops);

    i2c_delay(ops);

    SDA_L(ops);

    i2c_delay(ops);

    SCL_L(ops);

}

28.1.5 i2c_stop


staticvoidi2c_stop(struct rt_i2c_bit_ops *ops)

{

    //当SCL 是高电平时,SDA 线由低电平向高电平切换表示停止条件。

    SDA_L(ops);

    i2c_delay(ops);

    SCL_H(ops);

    i2c_delay(ops);

    SDA_H(ops);

    i2c_delay2(ops);

}

28.1.6 i2c_writeb

img

  • 从高位到低位发送数据,发送一个字节数据,等待ACK
  • 每个bit由SDA发送,低电平表示0,高电平表示1;每次发送一个bit,等待SCL高电平,发送下一个bit

    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)

        {

            LOG_D("i2c_writeb: 0x%02x, "

                    "wait scl pin high timeout at bit %d",

                    data, i);


            return -RT_ETIMEOUT;

        }

    }

    SCL_L(ops);

    i2c_delay(ops);


    returni2c_waitack(ops);

28.1.7 i2c_waitack

  • 等待ACK;在SCL高电平时,读取SDA电平,ACK为低电平,NACK为高电平

rt_inline rt_bool_ti2c_waitack(struct rt_i2c_bit_ops *ops)

{

    rt_bool_t ack;


    SDA_H(ops);

    i2c_delay(ops);


    if (SCL_H(ops) < 0)

    {

        LOG_W("wait ack timeout");


        return -RT_ETIMEOUT;

    }


    ack = !GET_SDA(ops);    /* ACK : SDA pin is pulled low */

    LOG_D("%s", ack ? "ACK" : "NACK");


    SCL_L(ops);


    return ack;

}

28.1.8 i2c_readb

  • 从高位到低位接收数据,接收一个字节数据
  • 必须在SCL高电平期间读取SDA电平

staticrt_int32_ti2c_readb(struct rt_i2c_bus_device *bus)

{

    rt_uint8_t i;

    rt_uint8_t data = 0;

    struct rt_i2c_bit_ops *ops = (struct rt_i2c_bit_ops *)bus->priv;


    SDA_H(ops);

    i2c_delay(ops);

    for (i = 0; i < 8; i++)

    {

        data <<= 1;


        if (SCL_H(ops) < 0)

        {

            LOG_D("i2c_readb: wait scl pin high "

                    "timeout at bit %d", 7 - i);


            return -RT_ETIMEOUT;

        }


        if (GET_SDA(ops))

            data |= 1;

        SCL_L(ops);

        i2c_delay2(ops);

    }


    return data;

}

28.1.9 i2c_bit_send_address


    /* 7-bit addr */

    addr1 = msg->addr << 1;

    if (flags & RT_I2C_RD)

        addr1 |= 1;


    for (i = 0; i <= retries; i++)

    {

        ret = i2c_writeb(bus, addr);

        if (ret == 1 || i == retries)

            break;

        LOG_D("send stop condition");

        i2c_stop(ops);

        i2c_delay2(ops);

        LOG_D("send start condition");

        i2c_start(ops);

    }

28.1.10 i2c_recv_bytes



while (count > 0)

{

    val = i2c_readb(bus);

    if (val >= 0)

    {

        *ptr = val;

        bytes ++;

    }

    else

    {

        break;

    }


    ptr ++;

    count --;


    LOG_D("recieve bytes: 0x%02x, %s",

            val, (flags & RT_I2C_NO_READ_ACK) ?

            "(No ACK/NACK)" : (count ? "ACK" : "NACK"));


    if (!(flags & RT_I2C_NO_READ_ACK))

    {

        val = i2c_send_ack_or_nack(bus, count);

        if (val < 0)

            return val;

    }

}

28.1.11 i2c_send_ack_or_nack

  • 在SCL高电平时,发送ACK或NACK

staticrt_err_ti2c_send_ack_or_nack(struct rt_i2c_bus_device *bus, intack)

{

    struct rt_i2c_bit_ops *ops = (struct rt_i2c_bit_ops *)bus->priv;


    if (ack)

        SET_SDA(ops, 0);

    i2c_delay(ops);

    if (SCL_H(ops) < 0)

    {

        LOG_E("ACK or NACK timeout.");


        return -RT_ETIMEOUT;

    }

    SCL_L(ops);


    return RT_EOK;

}

28.3 STM32 HAL I2C

1.HAL_I2C_Mem_Write : 发送设备地址,也发送寄存器地址,再发送数据

2.HAL_I2C_Master_Transmit: 发送设备地址,再发送数据

3.HAL_I2C_Master_Seq_Transmit_IT: 通信的序列(Seq)传输函数

28.3.1 阻塞方式

  • HAL_I2C_Master_Transmit
  1. 等待 I2C_FLAG_BUSY不被设置,超时时间25ms

ISR->BUSY 此标志表示在总线上正在进行通信。当检测到启动条件时,它由硬件设置。当检测到STOP条件或PE=0时,硬件清除。

  1. 根据发送长度执行不同的发送方式

->255 ,使用 I2C_RELOAD_MODE

  • <=255,使用 I2C_AUTOEND_MODE
  1. 调用 I2C_TransferConfig ,执行 I2C_GENERATE_START_WRITE写入
  2. 等待 TXIS标志设置,才能写入数据

发送传输状态:当I2C_TXDR寄存器为空,要传输的数据必须写入I2C_TXDR寄存器时,该位由硬件设置。当下一个要发送的数据被写入I2C_TXDR寄存器时,它将被清除。

  1. 如果是>255数据,需要多次写入,等待 I2C_FLAG_TCR标志设置,再次执行发送
  2. 结束后等待 STOPF标志设置,发送停止条件
  • HAL_I2C_Master_Receive

28.3.2 非阻塞 中断方式

  • HAL_I2C_Master_Transmit_IT
  1. 设置ISR回调函数 I2C_Master_ISR_IT
  2. 发送设备地址
  3. 使能 使能 ERR、TC、STOP、NACK、TXI 中断
  4. 中断服务函数 HAL_I2C_EV_IRQHandler -> I2C_Master_ISR_IT

由中断中判断还未发送完数据,继续发送;

发送完成后调用 I2C_ITMasterSeqCplt

  1. 触发回调 HAL_I2C_MasterTxCpltCallback

28.3.3 非阻塞 DMA

28.3.4 SEQ传输函数

https://blog.csdn.net/NeoZng/article/details/128496694

28.4 硬件I2C驱动

  1. IT方式和DMA启用完成量进行通信;且TX和RX使用同一个的完成量

原因:对于主机来说,对于需要接收的数据,也需要先发送命令在执行接收;所以使用同一个完成量,并不会存在冲突;对于主机收发来说,非阻塞方式并无意义,所以使用完成量来进行通信


http://www.kler.cn/a/504576.html

相关文章:

  • 【C语言】【C++】Curl库的安装
  • 基于Python机器学习、深度学习技术提升气象、海洋、水文领域实践应用-以ENSO预测为例讲解
  • 机组存储系统
  • HarmonyOS NEXT开发进阶(六):HarmonyOS NEXT实现嵌套 H5 及双向通信
  • 【跟着官网学技术系列之MySQL】第6天之输入查询
  • 改进萤火虫算法之八:量子萤火虫算法(Quantum-behaved Firfly Algorithm,QFA)
  • 开疆智能Profient转DeviceNET主网关连接发那科机器人配置案例
  • excel 整理表格,分割一列变成多列数据
  • Mac远程控制电脑Windows怎么弄?
  • 走出实验室的人形机器人,将复刻ChatGPT之路?
  • centos 7 Samba服务器的配置
  • 【BLE】CC2541之使用自定义128bit的UUID
  • uniapp 绘制五星评分 精确到半星
  • Linux C 使用ZBar库解析二维码和条形码
  • 数据结构——线性表和顺序表
  • 在 Debian 上安装 Docker
  • UML系列之Rational Rose笔记九:组件图
  • intel x99主板设置上电服务器自动启动
  • 机器学习之DBSCAN算法自动分类
  • python识别图片中指定颜色的图案并保存为图片