文章目录
前言
上期学习了GPIO的相关操作和中断,这次要学习IIC的相关知识。通过对ESP32C3的IIC的学习,要达到深入了解IIC协议、深入了解ESP IIC驱动、提高ESP32系列应用程序开发的水平的目的。其实我在STM32的开发中也学习和使用过IIC,这里再借这个机会进行“温故知新”。
一、IIC总线协议
1.IIC总线的概念
I2C 总线是一个多主机的串行总线,由两线组成――串行数据(SDA)和串行时钟(SCL)线在连接到总线的器件间传递信息。总线上可以挂接多个器件,每个器件都有一个唯一的地址识别(无论是微控制器、LCD驱动器存储器或键盘接口),而且都可以作为一个发送器或接收器(由器件的功能决定)。除了发送器和接收器外,器件在执行数据传输时也可以被看作是主机或从机。主机是初始化总线的数据传输并产生允许传输的时钟信号的器件。此时,任何被寻址的器件都被认为是从机。
总线的结构如下图:
SDA 和 SCL 是双向线路,都通过一个电流源或上拉电阻(1kΩ~10kΩ,需要根据不同的频率需要选择不同的上拉电阻,通常来说,所选择的频率越高,需要的上拉电阻越小(但是不要小于 1K 欧姆))连接到正的电源电压。当总线空闲时,这两条线路都是高电平,连接到总线的器件输出级必须是漏极开路或集电极开路才能执行线与的功能 。
传输速率
• 标准模式100kbit/s
• 快速模式400kbit/s
• 高速模式3.4Mbit/s
连接到总线的接口数量只由总线电容是 400pF 的限制决定。
位传输
由于连接到 I2C 总线的器件有不同种类的工艺(CMOS NMOS 双极性)逻辑 ‘0’(低) 和 ‘1’(高)的电平不是固定的,它由 VDD 的相关电平决定。每传输一个数据位就产生一个时钟脉冲。
数据的有效性
SDA 线上的数据必须在时钟的高电平周期保持稳定。数据线的高或低电平状态只有在 SCL 线的时钟
信号是低电平时才能改变,如下图:
字节格式
发送到 SDA 线上的每个字节必须为 8 位。每次传输可以发送的字节数量不受限制。每个字节后必须跟一个响应位。首先传输的是数据的最高位(MSB)。如果从机要完成一些其他功能后(例如一个内部中断服务程序)才能接收或发送下一个完整的数据字节,可以使时钟线 SCL 保持低电平迫使主机进入等待状态。当从机准备好接收下一个数据字节并释放时钟线 SCL 后,数据传输继续。
7 位的地址格式
数据的传输遵循如图所示的格式。在起始条件(S)后,发送了一个从机地址。这个地址共有 7 位,紧接着的第 8 位是数据方向位(R/ W)——‘0’表示发送(写),‘1’表示请求数据(读)数据传输一般由主机产生的停止位(P)终止。但是,如果主机仍希望在总线上通讯,它可以产生重复起始条件(Sr)和寻址另一个从机,而不是首先产生一个停止条件。在这种传输中,可能有不同的读/写格式结合。
7 位寻址:
总线的寻址过程是通常在起始条件后的第一个字节决定了主机选择哪一个从机。例外的情况是可以寻址所有器件的“广播呼叫”地址。
第一个字节的头 7 位组成了从机地址如图。最低位LSB是第 8 位。它决定了报文的方向。第一个字节的最低位是‘0’表示主机会写信息到被选中的从机 。‘1’表示主机会向从机读信息。
当发送了一个地址后,系统中的每个器件都在起始条件后将头 7 位与它自己的地址比较。如果一样,器件会任务它被主机寻址,至于是从机——接收器还是从机——发送器都由 R/ W 位决定。
2.IIC协议
IIC协议的规定,主机访问从机的一般过程如下:
主机想要发送数据给一个从机:
(1)主机发送一个START条件,并向从机发送地址。
(2)主机将数据发送到从机。
(3)主机以STOP条件终止传输。
主机从从机接收/读取数据:
(1)主机发送一个START条件,并向从机发送地址 。
(2)主机将请求的寄存器读给从机。
(3)主机从从机接收数据 。
(4)主机使用STOP条件终止传输 。
2.1起始和停止条件
起始(S)和停止(P)条件一般由主机产生,总线在起始条件后被认为处于忙的状态。在停止条件的某段时间后,总线被认为再次处于空闲状态。
起始条件:在 SCL 线是高电平时,SDA 线从高电平向低电平切换
停止条件:当 SCL 是高电平时,SDA 线由低电平向高电平切换
起始(S)和停止(P)条件时序图如下:
起始条件表示开始交互,停止条件表示交互结束,主机释放总线,在一次交互中主机可以发起多次起始条件(如读数据)。
2.2应答(ACK)和不应答(NACK)
应答:
数据传输必须带响应,相关的响应时钟脉冲由主机产生,在响应的时钟脉冲期间,发送器释放 SDA 线(高),接收器必须将 SDA 线拉低,使它在这个时钟脉冲的高电平期间保持稳定的低电平 。
数据的每个字节(包括地址字节)后面跟着一个来自接收端的ACK位。ACK位允许接收端向发送端发送已成功接收的字节和可以发送的另一个字节。
不应答:
当SDA线在ACK/NACK相关的时钟周期内保持高电平时,这被解释为不应答(NACK)。
有以下几种情况会产生不应答(NACK):
当从机不能响应从机地址时(例如它正在执行一些实时函数不能接收或发送)从机必须使数据线保持高电平。主机然后产生一个停止条件终止传输或者产生重复起始条件开始新的传输。
如果从机-接收器响应了从机地址但是在传输了一段时间后不能接收更多数据字节,主机必须再一次终止传输。这个情况用从机在第一个字节后没有产生响应来表示。从机使数据线保持高电平,主机产生一个停止或重复起始条件。
如果传输中有主机接收器,它必须通过在从机不产生时钟的最后一个字节不产生一个响应,向从机发送器通知数据结束。从机-发送器必须释放数据线,允许主机产生一个停止或重复起始条件。
总结为:
(1)从机无法接收或发送,因为它正在执行一些实时功能,并没有准备好开始与主机通信。
(2)在传输过程中,从机无法接收更多的数据字节。
(3)主机机完成了读取数据并通过NACK指示从机。
(4)在传输过程中,主机获取了它不理解的数据或命令。
应答(ACK)和不应答(NACK)时序图:
2.3写数据
给从机写数据,主机将在总线先发送起始条件,再发送一个带有写标志的从机地址,在最后一个位(R/W位)设置为0,表示写。 在从机发送应答位后,主机将发送它希望写入的寄存器的寄存器地址。 从机会再次应答,让主人知道它准备好了。 在此之后,主机将开始向从机发送寄存器数据,直到主机发送了它需要的所有数据(有时只是一个字节),并且主机将用STOP条件终止传输,如图所示:
2.4读数据
在第一个字节后,主机立即读从机如图,在第一次响应时,主机——发送器变成主机——接收器,从机——接收器变成从机——发送器。第一次响应仍由从机产生。之前发送了一个不响应信号的主机产生停止条件。
2.5读/写数据
读/写操作(复合格式)如图。传输改变方向的时侯,起始条件和从机地址都会被重复。但 R/ W 位取反。如果主机接收器发送一个重复起始条件,它之前应该发送了一个不响应信号。
二、ESP32C3的IIC
1.概述
ESP32-C3 只有一个 I2C 控制器(也称为端口),负责处理在 I2C 总线上的通信。每个控制器都可以设置为主机或从机。使用IIC就是对这个个 I2C 控制器的操作,作为嵌入式开发人员应该知道IIC协议还可以直接用GPIO来模拟。
2.主要特性
• 支持主机模式和从机模式
• 支持多从机通信
• 支持标准模式 (100 Kbit/s)
• 支持快速模式 (400 Kbit/s)
• 支持 7 位以及10位地址寻址
• 支持拉低 SCL时钟实现连续数据传输
• 支持可编程数字噪声滤波功能
• 支持从机地址和从机内存或寄存器地址的双寻址模式
3.架构
I2C 控制器可以工作于主机模式或者从机模式, I2C_MS_MODE 用于模式选择。图 27-1 为 I2C 主机基本架构图,图27-2 为 I2C 从机基本架构图。 I2C 控制器内部包括的模块主要有:
• 接收和发送存储器 TX/RX RAM
• 命令控制器 CMD_Controller
• SCL 时钟控制器 SCL_FSM
• SDA 数据控制器 SCL_MAIN_FSM
• 串并转换器 DATA_Shifter
• SCL 滤波器 SCL_Filter
• SDA 滤波器 SDA_Filter
3.功能描述
I2C 控制器涉及的功能比较丰富,朋友们请参阅《esp32-c3_technical_reference_manual_cn》手册(我是去看了很多遍),篇幅有限就不贴上来了,功能描述目录如下图:
4.ESP32C3 IIC中断
ESP32-C3的IIC可以产生多种中断,可以根据需要开启相应的中断功能。请参阅《esp32-c3_technical_reference_manual_cn》手册中IIC的中断介绍。
三、ESP32C3 IIC驱动MPU6500
1.MPU6500介绍
1.1概述
MPU- 60X0是全球首例9轴运动处理传感器。它集成了3轴MEMS陀螺仪,3轴MEMS加速度计,以及一个可扩展的数字运动处理器DMP(Digital MotionProcessor),可用I²C接口连接一个第三方的数字传感器,比如磁力计。扩展之后就可以通过其I²C或SPI接口输出一个9轴的信号(SPI接口仅在MPU- 6000可用)。MPU- 60X0也可以通过其I²C接口连接非惯性的数字传感器,比如压力传感器。
MPU- 60X0对陀螺仪和加速度计分别用了三个16位的ADC,将其测量的模拟量转化为可输出的数字量。为了精确跟踪快速和慢速的运动,传感器的测量范围都是用户可控的,陀螺仪可测范围为±250,±500,±1000,±2000/秒 (dps),加速度计可测范围为±2,±4,±8,±16g。 一个片上1024字节的FIFO,有助于降低系统功耗。和所有设备寄存器之间的通信采用400kHz的I²C接口或1MHz的SPI接口(SPI仅MPU-6000可用)对于需要高速传输的应用,对寄存器的读取和中断可用20MHz的SPI。另外,片上还内嵌了一个温度传感器和在工作环境下仅有±1%变动的振荡器。
1.2产品参数
供电电源:3-5v (内部低压差稳压)
通信方式:标准IIC/SPI通信协议
芯片内置16bitAD转换器,16位数据输出
陀螺仪范围:±250 500 1000 2000° /s
加速度范围:±2 ±4 ±8 ±16g
引脚间距: 2. 54mm
模块尺寸: 25*15mm
1.3引脚说明
VCC:电源正
GND:地
SCL:I2C串 行时钟线/SPI串行时钟端口
SDA: I2C串行数据线/SPI串行数据输入
EDA:连接其他I2C设备的主机数据口
ECL:给I2C设备 提供主时钟
ADO/SD0:I2C器 件地址选择位/SPI串行数据输出
INT:中断引脚
NCS:片选
FSYNC:数字同步输入帧, 不用请接地
2.ESP32C3驱动MPU6500
MPU6500地址:
设置为0xD0(主机寻址时要左移一位,最低位用于控制读写)
当AD0悬空时为低电平,这里我让该引脚悬空,所以地址为0xD0。
测试读取的寄存器地址:
设置ESP32C3映射IIC的引脚:
通过meunconfig的Example Configuration进行设置,因为工程有针对IIC配置了Kconfig.projbuild文件,如下图:选择GPIO5、6
电路连接:
如图:
在ESP32C3的IIC库文件已经实现了IIC协议操作的API函数我们只要调用和传参即可,如图:
代码:
#include <stdio.h>
#include "esp_log.h"
#include "driver/i2c.h"
static const char *TAG = "i2c-MPU6500";
#define I2C_MASTER_SCL_IO CONFIG_I2C_MASTER_SCL /*!< GPIO number used for I2C master clock */
#define I2C_MASTER_SDA_IO CONFIG_I2C_MASTER_SDA /*!< GPIO number used for I2C master data */
#define I2C_MASTER_NUM 0 /*!< I2C master i2c port number, the number of i2c peripheral interfaces available will depend on the chip */
#define I2C_MASTER_FREQ_HZ 400000 /*!< I2C master clock frequency */
#define I2C_MASTER_TX_BUF_DISABLE 0 /*!< I2C master doesn't need buffer */
#define I2C_MASTER_RX_BUF_DISABLE 0 /*!< I2C master doesn't need buffer */
#define I2C_MASTER_TIMEOUT_MS 1000
#define MPU6500_SENSOR_ADDR 0xD0 /*!< Slave address of the MPU6500 sensor */
#define MPU6500_WHO_AM_I_REG_ADDR 0x75 /*!< Register addresses of the "who am I" register */
#define MPU6500_PWR_MGMT_1_REG_ADDR 0x6B /*!< Register addresses of the power managment register */
#define MPU6500_RESET_BIT 7
/**
* @brief Read a sequence of bytes from a MPU6500 sensor registers
*/
static esp_err_t mpu6500_register_read(uint8_t reg_addr, uint8_t *data, size_t len)
{
return i2c_master_write_read_device(I2C_MASTER_NUM, MPU6500_SENSOR_ADDR, ®_addr, 1, data, len, I2C_MASTER_TIMEOUT_MS / portTICK_RATE_MS);
}
/**
* @brief Write a byte to a MPU6500 sensor register
*/
static esp_err_t mpu6500_register_write_byte(uint8_t reg_addr, uint8_t data)
{
int ret;
uint8_t write_buf[2] = {reg_addr, data};
ret = i2c_master_write_to_device(I2C_MASTER_NUM, MPU6500_SENSOR_ADDR, write_buf, sizeof(write_buf), I2C_MASTER_TIMEOUT_MS / portTICK_RATE_MS);
return ret;
}
/**
* @brief i2c master initialization
*/
static esp_err_t i2c_master_init(void)
{
int i2c_master_port = I2C_MASTER_NUM;
i2c_config_t conf = {
.mode = I2C_MODE_MASTER,
.sda_io_num = I2C_MASTER_SDA_IO,
.scl_io_num = I2C_MASTER_SCL_IO,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = I2C_MASTER_FREQ_HZ,
};
i2c_param_config(i2c_master_port, &conf);
return i2c_driver_install(i2c_master_port, conf.mode, I2C_MASTER_RX_BUF_DISABLE, I2C_MASTER_TX_BUF_DISABLE, 0);
}
void app_main(void)
{
uint8_t data[2];
ESP_ERROR_CHECK(i2c_master_init());
ESP_LOGI(TAG, "I2C initialized successfully");
/* Read the MPU6500 WHO_AM_I register, on power up the register should have the value 0x71 */
ESP_ERROR_CHECK(mpu6500_register_read(MPU6500_WHO_AM_I_REG_ADDR, data, 1));
ESP_LOGI(TAG, "WHO_AM_I = %X", data[0]);
/* Demonstrate writing by reseting the MPU6500 电源“DEVICE_RSET” */
ESP_ERROR_CHECK(mpu6500_register_write_byte(MPU6500_PWR_MGMT_1_REG_ADDR, 1 << MPU6500_RESET_BIT));
ESP_ERROR_CHECK(i2c_driver_delete(I2C_MASTER_NUM));
ESP_LOGI(TAG, "I2C unitialized successfully");
}
3.调试结果
I (328) I2c-MPU6500: I2C initialized successfully
I (338) I2c-MPU6500: WHO_AM_I = 77
I (338) I2c-MPU6500: I2C unitialized successfully
四、总结
重温并深度的学习了一遍IIC协议,学习了ESP32C3的IIC控制器,使用ESP32C3的IIC简单的驱动了MPU6500进行数据的读写。以后再用ESP32C3的GPIO模拟IIC协议,这才是学习IIC协议的意义,STM32系列单片机也有IIC的外设,只是我们一般选择采用GPIO模拟IIC协议。