I2C 总线驱动
本章将介绍 MS-RTOS I2C 总线驱动开发及测试。
I2C 是内置集成电路(Inter—Integrated Circuit)的英文缩写。I2C 是一种由 Philips 公司开发的两线式串行总线,用于连接微控制器及其外围设备。I2C 总线只有两根线分别为:时钟线 SCL(Serial Clock)和数据线 SDA(Serial Data)。SDA 传输数据是大端传输,每次传输 8bit,即一字节。I2C 总线支持多主控(multimastering),但任何时间点只能有一个主控。总线上每个设备都有自己的一个 addr(7bit/10bit),系统中可能有多个同种芯片,为此 addr 分为固定部分和可编程部份,细节视芯片而定。
1. I2C 基础知识
1.1 物理层
I2C 通讯设备之间的常用连接方式:
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)信号,只有接收到应答信号后,主机才能继续发送或接收数据。下图中,灰色:表示数据由主机传输至从机,白色:表示数据由从机传输至主机。
(2)I2C 信号和响应
I2C 的数据和地址传输都带响应。响应包括应答(ACK)和非应答(NACK)两种信号。作为数据接收端时,当设备(无论主从机)接收到 I2C 传输的一个字节数据或地址后,若希望对方继续发送数据,则需要向对方发送应答(ACK)信号,发送方会继续发送下一个数据;若接收端希望结束数据传输,则向对方发送非应答(NACK)信号,发送方接收到该信号后会产生一个停止信号,结束信号传输。传输时主机产生时钟,在第 9 个时钟时,数据发送端会释放 SDA 的控制权,由数据接收端控制 SDA,若 SDA 为高电平,表示非应答信号(NACK),低电平表示应答信号(ACK)。
1.3 STM32 的 I2C 架构
STM32 的 I2C 外设可用作通讯的主机及从机,支持 100Kbit/s 和 400Kbit/s 的速率,支持 7 位、10 位设备地址,支持 DMA 数据传输,并具有数据校验功能。它的 I2C 外设还支持 SMBus2.0 协议,SMBus 协议与 I2C 类似,主要应用于笔记本电脑的电池管理中,感兴趣的读者可参考《SMBus20》文档了解。
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 总线的过程。
上图由上到下分别为:应用层、IO 驱动层、子系统框架层、驱动适配层。不一定所有类型的驱动都有驱动框架。在注册 I2C 总线驱动时,ms_i2c_bus_t
将作为 I2C 总线控制器设备的 privinfo_t
的一员;在应用程序操作 I2C 总线时,将调用到 ms_i2c_bus_drv_ops
,实际上将调用 privinfo_t
里 ms_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 send start 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
2. FAQ
(1)stm32 的 I2C 为什么会有 bug?
确实很多人用 STM32 的 I2C 会出现问题,ST 工程师总结了很多相关实战经验,供参考。比如 STM8L I2C 程序第二次数据通信失败问题分析,STM32F4xxx 的 I2C 总线挂起异常处理,一个判断 I2C 总线通信异常原因的方法。详解知乎:https://www.zhihu.com/question/30835089