# MS-RTOS I2C 驱动开发

本章将介绍 MS-RTOS I2C 总线驱动开发及测试。

I2C 是内置集成电路(Inter—Integrate Circuit)的英文缩写。I2C是一种由Philips公司开发的两线式串行总线,用于连接微控制器及其外围设备。I2C总线只有两根线分别为:时钟线SCL(Serial Clock)和数据线SDA(Serial Data)。SDA传输数据是大端传输,每次传输8bit,即一字节。I2C总线支持多主控(multimastering),但任何时间点只能有一个主控。总线上每个设备都有自己的一个addr(7bit/10bit),系统中可能有多个同种芯片,为此addr分为固定部分和可编程部份,细节视芯片而定。

# 1. I2C 基础知识

# 1.1 物理层

I2C 通讯设备之间的常用连接方式:

i2c_interface

I2C 具有三种传输模式:标准模式传输速率为100kbit/s ,快速模式为400kbit/s ,高速模式下可达 3.4Mbit/s,但目前大多I2C设备尚不支持高速模式。 连接到相同总线的 IC 数量受到总线的最大电容 400pF 限制 。

# 1.2 协议层

I2C的协议定义了通讯的起始和停止信号、数据有效性、响应、仲裁、时钟同步和地址广播等环节。

(1)I2C 基本读写过程

起始信号产生后,所有从机就开始等待主机紧接下来广播的从机地址信号 (SLAVE_ADDRESS)。在I2C总线上,每个设备的地址都是唯一的,当主机广播的地址与某个设备地址相同时,这个设备就被选中了,没被选中的设备将会忽略之后的数据信号。根据I2C协议,这个从机地址可以是7位或10位。在地址位之后,是传输方向的选择位,该位为0时,表示后面的数据传输方向是由主机传输至从机,即主机向从机写数据。该位为1时,则相反,即主机由从机读数据。从机接收到匹配的地址后,主机或从机会返回一个应答(ACK)或非应答(NACK)信号,只有接收到应答信号后,主机才能继续发送或接收数据。下图中,灰色:表示数据由主机传输至从机,白色:表示数据由从机传输至主机。

i2c_protocol

(2)I2C 信号和响应

I2C的数据和地址传输都带响应。响应包括"应答(ACK)"和"非应答(NACK)"两种信号。作为数据接收端时,当设备(无论主从机)接收到I2C传输的一个字节数据或地址后,若希望对方继续发送数据,则需要向对方发送"应答(ACK)"信号,发送方会继续发送下一个数据;若接收端希望结束数据传输,则向对方发送"非应答(NACK)"信号,发送方接收到该信号后会产生一个停止信号,结束信号传输。传输时主机产生时钟,在第9个时钟时,数据发送端会释放SDA的控制权,由数据接收端控制SDA,若SDA为高电平,表示非应答信号(NACK),低电平表示应答信号(ACK)。

i2c_signal

# 1.3 STM32 的 I2C 架构

STM32的I2C外设可用作通讯的主机及从机,支持100Kbit/s和400Kbit/s的速率,支持7位、10位设备地址,支持DMA数据传输,并具有数据校验功能。它的I2C外设还支持SMBus2.0协议,SMBus协议与I2C类似,主要应用于笔记本电脑的电池管理中,感兴趣的读者可参考《SMBus20》文档了解。

i2c_arch

# 2. I2C 驱动框架

# 2.1 模拟 I2C

MS-RTOS 已经实现了通用的 GPIO 模拟 I2C 的库。在构建 MS-RTOS 的 SDK 时,勾选 libmsdriver;或者自己手动从 GitHub 上下载最新的 libmsdriver,并添加到 SDK 中。

(1)相关数据结构

在基于 libmsdriver 来开发 GPIO 模拟的 I2C 驱动时,只需要实现(填充)好 ms_i2c_bitbang_io_t ,然后调用 ms_i2c_bitbang_bus_dev_create 即可。在实际开发时,需要包含头文件 "i2c/ms_drv_i2c_bitdang.h" 并添加链接选项 -lmsdriver

/**
 * @brief Functions for setting and getting the state of the I2C lines.
 *
 * These need to be implemented by the user of this library.
 */
typedef struct {
    /*
     * Set the state of the SCL line (zero/non-zero value)
     */
    void (*set_scl)(ms_ptr_t io_ctx, ms_uint8_t state);

    /*
     * Set the state of the SDA line (zero/non-zero value)
     */
    void (*set_sda)(ms_ptr_t io_ctx, ms_uint8_t state);

    /*
     * Return the state of the SDA line (zero/non-zero value)
     */
    ms_uint8_t (*get_sda)(ms_ptr_t io_ctx);

    /*
     * Delay ns
     */
    void(*delay)(ms_ptr_t io_ctx, ms_uint32_t ns);
} const ms_i2c_bitbang_io_t;

ms_err_t ms_i2c_bitbang_bus_dev_create(const char *bus_name, const char *path,
                                       ms_i2c_bitbang_io_t *io, ms_ptr_t io_ctx);

(2)驱动示例

#include "ms_config.h"
#include "ms_rtos.h"
#include "includes.h"

#include "i2c/ms_drv_i2c_bitdang.h"

extern void bsp_delay_us(ms_uint32_t nus);

/*
 * GPIO port and pin
 */
#define SCL_GPIO_PORT       GPIOB
#define SCL_GPIO_PIN        GPIO_PIN_6
#define SCL_GPIO_CLK_TYPE   RCU_GPIOB

#define SDA_GPIO_PORT       GPIOB
#define SDA_GPIO_PIN        GPIO_PIN_7
#define SDA_GPIO_CLK_TYPE   RCU_GPIOB

/*
 * GPIO DIR CONTROL
 */
#define SDA_IN()      {gpio_mode_set(SDA_GPIO_PORT, GPIO_MODE_INPUT, \
                                     GPIO_PUPD_NONE, SDA_GPIO_PIN);}
#define SDA_OUT()     {gpio_mode_set(SDA_GPIO_PORT, GPIO_MODE_OUTPUT, \
                                     GPIO_PUPD_NONE, SDA_GPIO_PIN);}

/*
 * Set the state of the SCL line (zero/non-zero value)
 */
static void __i2c_io_set_scl(ms_ptr_t io_ctx, ms_uint8_t state)
{
    if (state != 0) {
        gpio_bit_set(SCL_GPIO_PORT, SCL_GPIO_PIN);
    } else {
        gpio_bit_reset(SCL_GPIO_PORT, SCL_GPIO_PIN);
    }
}

/*
 * Set the state of the SDA line (zero/non-zero value)
 */
static void __i2c_io_set_sda(ms_ptr_t io_ctx, ms_uint8_t state)
{
    SDA_OUT();

    if (state != 0) {
        gpio_bit_set(SDA_GPIO_PORT, SDA_GPIO_PIN);
    } else {
        gpio_bit_reset(SDA_GPIO_PORT, SDA_GPIO_PIN);
    }
}

/*
 * Return the state of the SDA line (zero/non-zero value)
 */
static ms_uint8_t __i2c_io_get_sda(ms_ptr_t io_ctx)
{
    SDA_IN();

    if (gpio_input_bit_get(SDA_GPIO_PORT, SDA_GPIO_PIN) == RESET) {
        return 0;
    } else {
        return 1;
    }
}

/*
 * Delay ns
 */
static void __i2c_io_delay(ms_ptr_t io_ctx, ms_uint32_t ns)
{
    bsp_delay_us((ns + (1000 - 1)) / 1000);
}

/*
 * ms_i2c_bitbang_io_t
 */
static ms_i2c_bitbang_io_t __i2c_io = {
    .set_scl = __i2c_io_set_scl,
    .set_sda = __i2c_io_set_sda,
    .get_sda = __i2c_io_get_sda,
    .delay   = __i2c_io_delay,
};

/*
 * I2C GPIO initialization function
 */
void bsp_i2c_emulator_init(const char *bus_name, const char *path)
{
    /* GPIO clock enable */
    rcu_periph_clock_enable(SCL_GPIO_CLK_TYPE);
    rcu_periph_clock_enable(SDA_GPIO_CLK_TYPE);

    /* I2C0_SCL (PB6) */
    gpio_mode_set(SCL_GPIO_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, SCL_GPIO_PIN);
    gpio_output_options_set(SCL_GPIO_PORT, GPIO_OTYPE_OD, 
                            GPIO_OSPEED_50MHZ, SCL_GPIO_PIN);
    gpio_bit_set(SCL_GPIO_PORT, SCL_GPIO_PIN);

    /* I2C0_SDA (PB7) */
    gpio_mode_set(SDA_GPIO_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, SDA_GPIO_PIN);
    gpio_output_options_set(SDA_GPIO_PORT, GPIO_OTYPE_OD, 
                            GPIO_OSPEED_50MHZ, SDA_GPIO_PIN);
    gpio_bit_set(SDA_GPIO_PORT, SDA_GPIO_PIN);

    ms_i2c_bitbang_bus_dev_create(bus_name, path, &__i2c_io, MS_NULL);
}

# 2.2 硬件 I2C

MS-RTOS 为 I2C 总线驱动封装了一层简单易用的驱动框架。同时,MS-RTOS 也已经支持了部分系列的 EEPROM 驱动,这部分驱动放到了 libmsdriver 中。

(1)相关数据结构

MS-RTOS I2C 驱动框架相关的数据结构和接口可以在头文件 sdk/src/driver/ms_drv_i2c.h 中找到。

  • ms_i2c_bus_t

    该结构体用于描述一条 I2C 总线,并包含操作总线控制器的接口。

    /*
     * ms_i2c_bus_ops_t
     */
    typedef struct {
        ms_ssize_t  (*trans)(ms_ptr_t bus_ctx, const ms_i2c_msg_t *msg, ms_size_t n_msg);
        int         (*ioctl)(ms_ptr_t bus_ctx, int cmd, ms_ptr_t arg);
    } const ms_i2c_bus_ops_t;
    
    /*
     * ms_i2c_bus_t
     */
    typedef struct {
        ms_io_name_node_t   nnode;
        ms_i2c_bus_ops_t   *ops;
        ms_handle_t         lockid;
        ms_list_head_t      dev_list;
        ms_ptr_t            ctx;
    } ms_i2c_bus_t;
    
  • ms_i2c_device_t

    该结构体用于描述一个 I2C 总线上的设备。通常实际 I2C 设备需要包含一个该类型的成员,在进行 I2C 通信时,需要将 ms_i2c_device_t attach 到一条 ms_i2c_bus_t ,然后调用相应的接口进行通信,如:ms_i2c_device_trans

    /*
     * ms_i2c_device_t
     */
    typedef struct {
        ms_io_name_node_t   nnode;
        ms_i2c_bus_t       *bus;
        ms_ptr_t            ctx;
        ms_uint32_t         clk_speed;    /* I2C frequency                      */
        ms_uint16_t         addr;         /* Slave device 7bit/10bit address    */
        ms_uint8_t          addrlen;      /* Slave device length (7 or 10 bits) */
    } ms_i2c_device_t;
    

    I2C 设备可以使用 API 接口包括:

    接口 说明
    ms_i2c_device_attach 将 ms_i2c_device_t 附加/绑定 到 一条 I2C Bus 上 (bus_name)
    ms_i2c_device_detach 取消 ms_i2c_device_t 和 I2C Bus 的 绑定
    ms_i2c_device_trans 传输消息
    ms_i2c_device_read 从总线上读取数据
    ms_i2c_device_write 向总线发送数据
    ms_i2c_device_writeread 向总线写数据,然后从总线读数据
    ms_i2c_device_ioctl 通过 ioctl 发送总线控制命令
    ms_i2c_device_lock_bus 锁住 I2C Bus (非硬件上的锁定)
    ms_i2c_device_unlock_bus 解锁 I2C Bus (非硬件上的解锁)

(2)I2C 框架

下图主要描绘了 I2C 总线驱动、I2C 设备驱动 和 应用层操作 I2C 总线 的过程。

i2c_bus_arch

上图由上到下分别为:应用层、IO 驱动层、子系统框架层、驱动适配层。不一定所有类型的驱动都有驱动框架。在注册 I2C 总线驱动时,ms_i2c_bus_t 将作为 I2C 总线控制器设备的 privinfo_t 的一员;在应用程序操作 I2C 总线时,将调用到 ms_i2c_bus_drv_ops ,实际上将调用 privinfo_tms_i2c_bus_t 的操作函数;在注册 I2C 设备驱动时,需要指定 i2c_bus_name 等 I2C 设备信息,在创建 xx24xx_dev 时将绑定 i2c_dev 到对应的 i2c_bus , EEPROM 的读写操作将通过 I2C 来完成,而使用 i2c_dev 就可以调用到 I2C 相关的 API。

(3)驱动示例

I2C 驱动示例,仅作为参考。

#define __MS_IO
#include "ms_config.h"
#include "ms_rtos.h"
#include "ms_io_core.h"
#include <string.h>
#include "includes.h"

/*
 * Private info
 */
typedef struct {
    ms_uint8_t      channel;
    ms_addr_t       base;
    ms_uint8_t      event_irq;
    ms_uint8_t      error_irq;
    ms_i2c_param_t  param;
    ms_handle_t     trans_sem;
} privinfo_t;

/*
 * i2c trans
 */
static ms_ssize_t __i2c_bus_trans(ms_ptr_t bus_ctx, 
                                  const ms_i2c_msg_t *msg, ms_size_t n_msg)
{
    privinfo_t *priv = bus_ctx;
    ms_err_t    err;
    ms_size_t   i;
    ms_addr_t   i2cx;

    i2cx = priv->base;
    for (i = 0; i < n_msg; i++, msg++) {

        if (!(msg->flags & MS_I2C_M_NOSTART)) {

            if (msg->flags & MS_I2C_M_READ) {
                /* needn't wait while in read mode. */
            } else {
                /* [step1] wait until I2C bus is idle */
                err = __i2c_get_flag_wait(i2cx, I2C_I2CBSY, RESET);
                if (err != MS_ERR_NONE) {
                    ms_thread_set_errno(EBUSY);
                    break;
                }
            }

            /* [step2] send a start condition to I2C bus */
            i2c_start_on_bus(i2cx);

            /* wait until SBSEND bit is set */
            err = __i2c_get_flag_wait(i2cx, I2C_SBSEND, SET);
            if (err != MS_ERR_NONE) {
                ms_thread_set_errno(EIO);
                break;
            }

            /* send 7bit/10bit slave address to I2C bus */
            if (msg->flags & MS_I2C_M_TEN) {
                ms_uint8_t addr_head = 0x78; /* 11110xx */
                addr_head |= ((msg->addr >> 8) & 0x3);

                /* address first byte */
                I2C_STAT0(i2cx);
                i2c_transmit_data(i2cx, addr_head << 1);

                /* wait until ADD10SEND bit is set */
                err = __i2c_get_flag_wait(i2cx, I2C_ADD10SEND, SET);
                if (err != MS_ERR_NONE) {
                    ms_thread_set_errno(EIO);
                    break;
                }

                /* ### send second byte of 10bit address ### */
                i2c_transmit_data(i2cx, msg->addr & 0xff);

                /* wait until ADDSEND bit is set */
                err = __i2c_get_flag_wait(i2cx, I2C_ADDSEND, SET);
                if (err != MS_ERR_NONE) {
                    ms_thread_set_errno(EIO);
                    break;
                }

                /* [step4] clear the ADDSEND bit */
                i2c_flag_clear(i2cx, I2C_STAT0_ADDSEND);

                /* read/write transaction */
                if (msg->flags & MS_I2C_M_READ) {

                    /* [step4] send a start condition to I2C bus */
                    i2c_start_on_bus(i2cx);

                    /* wait until SBSEND bit is set */
                    err = __i2c_get_flag_wait(i2cx, I2C_SBSEND, SET);
                    if (err != MS_ERR_NONE) {
                        ms_thread_set_errno(EIO);
                        break;
                    }

                    /* address first byte */
                    I2C_STAT0(i2cx);
                    i2c_master_addressing(i2cx, addr_head << 1, I2C_RECEIVER);

                    /* wait until ADDSEND bit is set */
                    err = __i2c_get_flag_wait(i2cx, I2C_ADDSEND, SET);
                    if (err != MS_ERR_NONE) {
                        ms_thread_set_errno(EIO);
                        break;
                    }

                    /* [step4] clear the ADDSEND bit */
                    i2c_flag_clear(i2cx, I2C_STAT0_ADDSEND);

                    /* read N byte and send STOP */
                } else {
                    /* write N byte and send STOP */
                }

            } else {
                /* read/write transaction */
                I2C_STAT0(i2cx);
                if (msg->flags & MS_I2C_M_READ) {
                    i2c_master_addressing(i2cx, msg->addr << 1, I2C_RECEIVER);
                } else {
                    i2c_master_addressing(i2cx, msg->addr << 1, I2C_TRANSMITTER);
                }

                /* wait until ADDSEND bit is set */
                err = __i2c_get_flag_wait(i2cx, I2C_ADDSEND, SET);
                if (err != MS_ERR_NONE) {
                    ms_thread_set_errno(EIO);
                    break;
                }

                /* clear the ADDSEND bit */
                i2c_flag_clear(i2cx, I2C_STAT0_ADDSEND);

                /* read/write N byte and send STOP */
            }
        }

        /* [step-x] read or write data */
        if (msg->flags & MS_I2C_M_READ) {
            err = __i2c_bus_data_read(priv, msg);
        } else {
            err = __i2c_bus_data_write(priv, msg);
        }
        
        if (err != MS_ERR_NONE) {
            ms_thread_set_errno(EIO);
            break;
        }

        /* i2c bus stop signal */
        if (msg->flags & MS_I2C_M_READ) {
            if (!(msg->flags & MS_I2C_M_NOSTOP)) {

                /* wait until the stop condition is finished */
                err = __i2c_wait_stop_finished(i2cx);
                if (err != MS_ERR_NONE) {
                    ms_thread_set_errno(EIO);
                    break;
                }
            }

        } else {
            if (!(msg->flags & MS_I2C_M_NOSTOP)) {

                /* send a stop condition to I2C bus */
                i2c_stop_on_bus(i2cx);

                /* wait until the stop condition is finished */
                err = __i2c_wait_stop_finished(i2cx);
                if (err != MS_ERR_NONE) {
                    ms_thread_set_errno(EIO);
                    break;
                }
            }

        }
    }

    return i;
}

/*
 * i2c ioctl
 */
static int __i2c_bus_ioctl(ms_ptr_t bus_ctx, int cmd, ms_ptr_t arg)
{
    privinfo_t *priv = bus_ctx;
    int         ret;

    switch (cmd) {
        case MS_I2C_CMD_GET_PARAM:
            if (ms_access_ok(arg, sizeof(ms_i2c_param_t), MS_ACCESS_W)) {
                *(ms_i2c_param_t *)arg = priv->param;
                ret = 0;
            } else {
                ms_thread_set_errno(EFAULT);
                ret = -1;
            }
            break;

        case MS_I2C_CMD_SET_PARAM:
            if (ms_access_ok(arg, sizeof(ms_i2c_param_t), MS_ACCESS_R)) {
                priv->param = *(ms_i2c_param_t *)arg;
                __i2c_bus_config(priv, &priv->param);

            } else {
                ms_thread_set_errno(EFAULT);
                ret = -1;
            }
            break;

        default:
            ms_thread_set_errno(EINVAL);
            ret = -1;
            break;
    }

    return ret;
}

static ms_i2c_bus_ops_t i2c_bus_ops = {
        .trans = __i2c_bus_trans,
        .ioctl = __i2c_bus_ioctl,
};

static privinfo_t i2c_bus_privinfo[__I2C_MAX_NUMBER] = {
    {
            .base      = I2C0,
            .event_irq = I2C0_EV_IRQn,
            .error_irq = I2C0_ER_IRQn,
    },
};

static ms_i2c_bus_t __i2c_bus[__I2C_MAX_NUMBER] = {
    {
        /* I2C1 device data structure defined here */
        .nnode = {
            .name = "i2c0",
        },
        .ops = &i2c_bus_ops,
        .ctx = &i2c_bus_privinfo[0],
    },
};

/*
 * Create I2C device file
 */
ms_err_t i2c_bus_dev_create(const char *path, ms_uint8_t channel)
{
    ms_i2c_bus_t *i2c_bus;
    privinfo_t   *priv;
    ms_err_t      err;

    if ((path == MS_NULL) || (channel >= __I2C_MAX_NUMBER)) {
        return MS_ERR;
    }

    i2c_bus = &__i2c_bus[channel];
    priv    = (privinfo_t *)i2c_bus->ctx;
    priv->channel = channel;

    if (ms_semb_create("i2c_trans_sem", MS_FALSE, 
                       MS_WAIT_TYPE_PRIO, &priv->trans_sem) == MS_ERR_NONE) {

        bsp_i2c_low_level_init(channel);

        priv->param.clk_speed         = MS_I2C_CLK_SPEED_FAST;
        priv->param.duty_cycle        = MS_I2C_DUTY_CYCLE_2;
        priv->param.own_address1      = 0U;
        priv->param.addressing_mode   = MS_I2C_ADDRESSING_MODE_7B;
        priv->param.dual_address_mode = MS_I2C_DUAL_ADDRESS_DISABLE;
        priv->param.own_address2      = 0U;

        __i2c_bus_config(priv, &priv->param);

        err = ms_i2c_bus_register(i2c_bus);
        if (err == MS_ERR_NONE) {
            err = ms_i2c_bus_dev_create(path, i2c_bus);
        }
    } else {
        err = MS_ERR;
    }

    return err;
}

# 2.3 ioctl 命令

以下仅列出几个最基本的命令,可以在 sdk/src/driver/ms_drv_i2c.h 文件中找到所有命令的定义。

命令 描述 参数
MS_I2C_CMD_SET_PARAM 设置 I2C 总线工作参数 ms_i2c_param_t 指针
MS_I2C_CMD_GET_PARAM 获取 I2C 总线工作参数 ms_i2c_param_t 指针

(1)ms_i2c_param_t

typedef struct {
    ms_uint32_t     clk_speed;			// Specifies the SCL clock speed
    ms_uint16_t     own_address1;       // Specifies the device own address
    ms_uint16_t     own_address2; 		// second address if dual addressing mode
    ms_uint8_t      own_address2_mask;  // Specifies the second device own address
    ms_uint8_t      duty_cycle;         // Specifies the I2C fast mode duty cycle 
    ms_uint8_t      addressing_mode;    // Specifies 7bit/10bit addressing mode     
    ms_uint8_t      dual_address_mode;  // Specifies if dual addressing mode        
    ms_uint8_t      general_call_mode;  // Specifies if general call mode           
    ms_uint8_t      no_stretch_mode;    // Specifies if nostretch mode is selected  
} ms_i2c_param_t;
  • I2C SCL 引脚时钟频率 (clk_speed)
可选配置 描述
MS_I2C_CLK_SPEED_STANDARD Standard Speed (100kHz)
MS_I2C_CLK_SPEED_FAST Fast Speed (400kHz)
MS_I2C_CLK_SPEED_FAST_PLUS Fast+ Speed ( 1MHz)
MS_I2C_CLK_SPEED_HIGH High Speed (3.4MHz)
  • I2C 快速模式下的占空周期(duty_cycle)
可选配置 描述
MS_I2C_DUTY_CYCLE_2 2 duty cycle
MS_I2C_DUTY_CYCLE_16_9 9/16 duty cycle

设置I2C的SCL线时钟的占空比。该配置有两个选择,分别为低电平时间比高电平时间为2:1 ( I2C_DutyCycle_2)和16:9 (I2C_DutyCycle_16_9),用来设置I2C_CCR的DUTY位。注意该参数只有I2C工作在快速模式下(即SCK时钟频率高于100kHz)下才有意义。

  • I2C 地址模式(addressing_mode)
可选配置 描述
MS_I2C_ADDRESSING_MODE_7B i2c 7bit address mode
MS_I2C_ADDRESSING_MODE_10B i2c 10bit address mode
  • I2C 双地址模式(dual_address_mode)
可选配置 描述
MS_I2C_DUAL_ADDRESS_DISABLE disable dual address mode
MS_I2C_DUAL_ADDRESS_ENABLE enable dual address mode
  • I2C 通用调用模式(general_call_mode)
可选配置 描述
MS_I2C_GENERAL_CALL_DISABLE disable general call
MS_I2C_GENERAL_CALL_ENABLE enable general call

通用广播地址是用来同时寻址所有连接到 I2C 总线上的设备。如果一个设备在广播地址时不需要数据,它可以不产生应答来忽略。如果一个设备从通用广播地址请求数据,它可以应答并当作一个从-接收器。当一个或多个设备响应时主机并不知道有多少个设备应答了。每一个可以处理这个数据的从-接收器可以响应第二个字节。从机不处理这些字节的话,可以响应NA。如果一个或多个从机响应,主机就无法看到NA。通用广播地址的含义一般在第二个字节中指明。详细解释请产看相应设备的datasheet。

  • I2C 无伸展模式(no_stretch_mode)
可选配置 描述
MS_I2C_NO_STRETCH_DISABLE disable NO_STRETCH mode
MS_I2C_NO_STRETCH_ENABLE enable NO_STRETCH mode

clock stretching 通过将SCL线拉低来暂停一个传输。直到释放SCL线为高电平,传输才继续进行。clock stretching是可选的,实际上大多数从设备不包括SCL驱动,所以它们不能stretch时钟。

(2)ms_i2c_msg_t

typedef struct {
    ms_uint32_t     clk_speed;  // SCL 时钟速度
    ms_uint16_t     addr;   	// 7bit/10bit从机地址 
    ms_uint16_t     flags;		// 传输控制标记
    ms_ptr_t        buf;		// 缓冲区地址
    ms_size_t       len;		// 缓冲区长度
} ms_i2c_msg_t;
  • I2C 传输控制标记(flags)
可选配置 描述
MS_I2C_M_TEN Ten-bit chip address
MS_I2C_M_READ Read data, from slave to master
MS_I2C_M_NOSTOP No send stop after this message
MS_I2C_M_NOSTART No sendstart before this message

# 3. I2C 的应用

# 3.1 使用 I2C 读写 EEPROM

//NOTE: 请自行参考 libmsdriver 库中的 eeprom/ms_drv_xx24xx.c 

# 3.2 使用 I2C 读取触摸屏

//NOTE: 请自行参考 libmsdriver 库中的 touch/ms_drv_gt9xx.c 

# 附录(Appendix)

1. Reference

STM32 I2C:https://blog.csdn.net/zxh1592000/article/details/78732129

I2C 读写 EEPROM:https://blog.csdn.net/flyleaf91/article/details/52325512

2. FAQ

(1)stm32 的 I2C 为什么会有 bug?

确实很多人用STM32的I2C会出现问题,ST工程师总结了很多相关实战经验,供参考。比如STM8L I2C程序第二次数据通信失败问题分析,STM32F4xxx的I2C总线挂起异常处理,一个判断I2C总线通信异常原因的方法。详解知乎:https://www.zhihu.com/question/30835089 (opens new window)