I2C 总线的一些特征
? 只要求两条总线线路 一条串行数据线 SDA 一条串行时钟线 SCL
? 每个连接到总线的器件都可以通过唯一的地址和一直存在的简单的主机 从机关系软件设定地 址 主机可以作为主机发送器或主机接收器
? 它是一个真正的多主机总线 如果两个或更多主机同时初始化数据传输可以通过冲突检测和仲裁 防止数据被破坏
? 串行的 8 位双向数据传输位速率在标准模式下可达 100kbit/s 快速模式下可达 400kbit/s 高速 模式下可达 3.4Mbit/s
? 片上的滤波器可以滤去总线数据线上的毛刺波 保证数据完整
? 连接到相同总线的 IC 数量只受到总线的最大电容 400pF 限制
I2C 总线术语的定义
术语 | 描述 |
---|
发送器 | 发送数据到总线的器件 | 接收器 | 从总线接收数据的器件 | 主机 | 初始化发送 产生时钟信号和终止发送的器件 | 从机 | 被主机寻址的器件 | 多主机 | 同时有多于一个主机尝试控制总线 但不破坏报文 | 仲裁 | 是一个在有多个主机同时尝试控制总线 但只允许其中一个控制总线并使报文不被破坏的过程 | 同步 | 两个或多个器件同步时钟信号的过程 |
发送器和接收器外,器件在执行数据传输时也可以被看作是主机或从机,见上表,主机是初始化总线的数据传输并产生允许传输的时钟信号的器件,此时,任何被寻址的器件都被认为是从机
总体特征
SDA 和 SCL 都是双向线路,都通过一个电流源或上拉电阻连接到正的电源电压,见图 3 当总线空 闲时,这两条线路都是高电平,连接到总线的器件输出级必须是漏极开路 或集电极开路 才能执行线与的功能
因为连接到总线的器件输出级必须是漏极开路 或集电极开路 才能执行线与的功能 ,所以我们在配置IO模式时,配置成开漏输出
数据的有效性
SDA 线上的数据必须在时钟的高电平周期保持稳定,数据线的高或低电平状态只有在 SCL 线的时钟 信号是低电平时才能改变
.h内容如下
#ifndef _BSP_I2C_H
#define _BSP_I2C_H
#include"stm32f10x.h"
#include"stm32f10x_gpio.h"
#define I2C_CLK_FUN RCC_APB2PeriphClockCmd
#define I2C_CLK RCC_APB2Periph_GPIOB
#define I2C_SCL_PORT GPIOB
#define I2C_SCL_PIN GPIO_Pin_6
#define I2C_SCL_Hight GPIO_SetBits(I2C_SCL_PORT,I2C_SCL_PIN);
#define I2C_SCL_Low GPIO_ResetBits(I2C_SCL_PORT,I2C_SCL_PIN);
#define I2C_SDA_PORT GPIOB
#define I2C_SDA_PIN GPIO_Pin_7
#define I2C_SDA_Hight GPIO_SetBits(I2C_SDA_PORT,I2C_SDA_PIN);
#define I2C_SDA_Low GPIO_ResetBits(I2C_SDA_PORT,I2C_SDA_PIN);
#define I2C_SDA_READ() ((GPIOB->IDR & I2C_SDA_PIN) != 0)
void I2C_Start(void);
void I2C_Stop(void);
uint8_t I2C_Wait_ACK(void);
void I2C_Send_Byte(uint8_t data);
uint8_t I2C_Read_Data(void);
void I2C_Send_Ack(void);
void I2C_Send_NAck(void);
uint8_t I2C_Check_Ack(uint8_t data);
#endif
GPIO初始化
void I2C_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
I2C_CLK_FUN(I2C_CLK,ENABLE);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStruct.GPIO_Pin = I2C_SDA_PIN;
GPIO_Init(I2C_SDA_PORT,&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = I2C_SCL_PIN;
GPIO_Init(I2C_SCL_PORT,&GPIO_InitStruct);
I2C_Stop();
}
SDA与SCL都配置成开漏输出
起始和停止
起始条件:在 SCL 线是高电平时,SDA 线从高电平向低电平切换 停止条件:当 SCL 是高电平时 SDA 线由低电平向高电平切换
void I2C_Start(void)
{
I2C_SCL_Hight;
I2C_SDA_Hight;
Delay_Us(10);
I2C_SDA_Low;
Delay_Us(10);
I2C_SCL_Low;
Delay_Us(10);
}
void I2C_Stop(void)
{
I2C_SCL_Hight;
I2C_SDA_Low;
Delay_Us(10);
I2C_SDA_Hight;
}
等待响应
等待响应就好比生活中两个人打电话,拨打方打通电话之后,等待接收方回应:“喂,你好”(ACK ),然后进行下面的通话。 而在I2C 通信过程中,每次发送方传输一字节后,接收方就会回应一个ACK
响应时序图如下:
uint8_t I2C_Wait_ACK(void)
{
uint8_t ack;
I2C_SDA_Hight;
Delay_Us(10);
I2C_SCL_Hight;
Delay_Us(10);
if(I2C_SDA_READ())
ack = 1;
else
ack = 0;
I2C_SCL_Low;
Delay_Us(10);
return ack;
}
发送NACK
在24C02芯片手册中,观察Current Address Read 这个时序,会发现在数据传输结束后,不再传输时,发送方会发送一个非应答(NACK ),所以我们编写一个发送NACK的函数
void I2C_Send_NAck(void)
{
I2C_SDA_Hight;
Delay_Us(10);
I2C_SCL_Hight;
Delay_Us(10);
I2C_SCL_Low;
Delay_Us(10);
}
发送数据
由上面的数据有效性知:SDA线上的数据必须在时钟的高电平周期保持稳定,数据线的高或低电平状态只有在 SCL 线的时钟信号是低电平时才能改变
void I2C_Send_Byte(uint8_t data)
{
uint8_t mask;
for(mask=0x80;mask!=0x00;mask>>=1)
{
if(data&mask)
{
I2C_SDA_Hight;
}
else
I2C_SDA_Low;
Delay_Us(10);
I2C_SCL_Hight;
Delay_Us(10);
I2C_SCL_Low;
}
I2C_SDA_Hight;
}
发送过程中,由上图可知,高位在前,低位在后 所以定义一个变量mask,这个for循环for(mask=0x80;mask!=0x00;mask>>=1) 表示mask从高位为1,到最低为1的过程:10000000、01000000、00100000、、、、00000001 ,紧接着用data&mask ,如果结果为1,即表示data 相应位为1,反之为0
读取数据
读取数据过程中,SCL线必须保持高电平 ,当一位读取结束后,释放SCL,等待一段时间,让SDA改变数据,重复上述过程
uint8_t I2C_Read_Data(void)
{
uint8_t data=0,mask;
for(mask=0x01;mask!=0;mask>>=1)
{
I2C_SCL_Hight;
Delay_Us(10);
if(GPIO_ReadInputDataBit(I2C_SDA_PORT,I2C_SDA_PIN))
data |= mask;
else
data &= ~mask;
I2C_SCL_Low;
Delay_Us(10);
}
return data;
}
mask的用法与发送数据时的类似
寻址
由图可以得到: 一、寻址过程是通常在起始条件后的第一个字节决定了主机选择哪一个从机 二、当发送了一个地址后 系统中的每个器件都在起始条件后将头 7 位与它自己的地址比较 三、如果一样器件会任务它被主机寻址 至于是从机 接收器还是从机 发送器都由 R/ W 位决定 四、使用这个地址时 理论上所有器件都会发出一个响应
所以可以总结出寻址的步骤:发送起始信号->发送设备地址->等待ACK->发送停止信号
若是代码无误,设备地址正确,会等到一个数值:0
得到设备地址 打开开发板配套的原理图,找到EEPROM(这里是以野火霸道开发板为例) 电路,然后再在24C02芯片手册 中找到Figure 1. Device Address,由二者可以得到EEPROM的前七位为1010000 ,然后最低位使用W ,所以得到地址:0xA0 寻址代码如下
uint8_t I2C_Check_Ack(uint8_t data)
{
uint8_t Ack;
I2C_GPIO_Config();
I2C_Start();
I2C_Send_Byte(data);
Ack = I2C_Wait_ACK();
I2C_Stop();
return Ack;
}
在main.c 中调用寻址函数,传入形参为:0xA0 输出结果如下: 经结果知,驱动代码正确
|