一、I2C总线协议
定义:两线式串行总线
两线式:说明处理器和外设之间只需要两根信号线,分别是SCL时钟控制信号线和SDA 数据线
SCL:时钟控制信号线,永远只能由CPU控制,用于实现数据的同步,就四个字:低放高取
SCL为低电平时将数据放在SDA数据线上
SCL为高电平时从数据线SDA上获取数据
SDA:数据线,用于传输数据,双方都可以控制
如果处理器给外设发送数据,SDA由处理器控制
如果外设给处理器发送数据,SDA由外设控制
SCL和SDA必须要分别连接一个上拉电阻,所以他们默认的电平都是高电平
I2C数据传输从高位开始,I2C数据传输一次传输一个字节,如果传输多个字节,需要分拆着来传
SCL和SDA上可以连接多个外设,也可以连接多个CPU,(理论上可以连接多个CPU)
I2C协议规定:总线上数据的传输必须以一个起始信号作为开始条件,以一个结束信号作
为传输的停止条件。起始和结束信号总是由主设备产生。总线在空闲状态时,SCL和SDA
都保持着高电平。
起始信号:当SCL为高电平而SDA由高到低的跳变,表示产生一个起始条件
结束信号:当SCL为高而SDA由低到高的跳变,表示产生一个停止条件
数据传输:数据传输以字节为单位,主设备在SCL线上产生每个时钟脉冲的过程中将在SDA线上传输一个数据位,数据在时钟的高电平被采样,一个字节按数据位从高位到低位的顺序进行传输
主设备在传输有效数据之前要先指定从设备的地址,一般为7位,然后再发生数据传输的方向位,0表示主设备向从设备写数据,1表示主设备向从设备读数据
应答信号:接收数据的器件在接收到8bit数据后,向发送数据的器件发出低电平的应答信号,表示已收到数据。这个信号可以是主控器件发出,也可以是从动器件发出。总之,由接收数据的器件发出。
二、I2C总线读写操作
主设备往从设备写数据
主设备读从设备数据
主设备读从设备的某个寄存器
软件模拟I2C时序
//iic.h
#ifndef __IIC_H
#define __IIC_H
//包含总头文件
#include "stm32f10x.h"
#include "system.h"
//定义SCL引脚的信息
#define IIC_SCL_PORT GPIOB
#define IIC_SCL_PIN GPIO_Pin_6
#define IIC_SCL_PORT_RCC RCC_APB2Periph_GPIOB
//定义SDA引脚的信息
#define IIC_SDA_PORT GPIOB
#define IIC_SDA_PIN GPIO_Pin_7
#define IIC_SDA_PORT_RCC RCC_APB2Periph_GPIOB
//定义输入输出的位带操作信息
#define IIC_SCL PBout(6)
#define IIC_SDA PBout(7)
#define READ_SDA PBin(7)
extern void IIC_Init(void); //IIC初始化
extern void IIC_Start(void); //发送START信号
extern void IIC_Stop(void); //发送STOP信号
extern void IIC_Send_Byte(u8 TxData); //发送1字节数据
extern u8 IIC_Read_Byte(u8 Ack); //读1字节数据
extern u8 IIC_Wait_Ack(void); //等待ACK信号
extern void IIC_Ack(void); //CPU发送ACK信号
extern void IIC_NAck(void); //CPU发送NACK信号
#endif
//iic.c
#include "iic.h"
#include "systick.h"
//定义IIC初始化函数
void IIC_Init(void) {
GPIO_InitTypeDef GPIO_Config;
//使能SCL和SDA引脚的GPIO时钟
RCC_APB2PeriphClockCmd(IIC_SCL_PORT_RCC|IIC_SDA_PORT_RCC, ENABLE);
//初始化SCL引脚的工作参数
GPIO_Config.GPIO_Pin = IIC_SCL_PIN;
GPIO_Config.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Config.GPIO_Mode = GPIO_Mode_Out_PP; //GPIO模拟I2C时序,采用推挽输出
GPIO_Init(IIC_SCL_PORT, &GPIO_Config);
//初始化SDA引脚的工作参数
GPIO_Config.GPIO_Pin = IIC_SDA_PIN;
GPIO_Config.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Config.GPIO_Mode = GPIO_Mode_Out_PP; //GPIO模拟I2C时序,采用推挽输出
GPIO_Init(IIC_SDA_PORT, &GPIO_Config);
//默认拉高SCL和SDA
IIC_SCL = 1;
IIC_SDA = 1;
}
//定义配置SDA为输出的函数,CPU发送数据的时候需要配置为输出
void SDA_OUT(void) {
GPIO_InitTypeDef GPIO_Config;
GPIO_Config.GPIO_Pin = IIC_SDA_PIN;
GPIO_Config.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Config.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(IIC_SDA_PORT, &GPIO_Config);
}
//定义配置SDA为输入的函数,当CPU从外设读取数据时,需要配置为输入
void SDA_IN(void) {
GPIO_InitTypeDef GPIO_Config;
GPIO_Config.GPIO_Pin = IIC_SDA_PIN;
GPIO_Config.GPIO_Mode = GPIO_Mode_IPU; //输入上拉
GPIO_Init(IIC_SDA_PORT, &GPIO_Config);
}
//定义发送START信号函数
/*
SCL:
1 -----------------------------
0
SDA: tsu:sta>4.7us
1 --------------
| thd:sta>4us
0 ---------------
*/
void IIC_Start(void) {
SDA_OUT(); //由于要发送START信号,所有要将SDA配置为输出
IIC_SCL = 1; //时钟拉高
IIC_SDA = 1; //数据线也拉高
delay_us(5); //SCL和SDA都会延时5us的高电平
IIC_SDA = 0; //SDA拉低,SCL还是高
delay_us(6); //SDA拉低6us,SCL还是拉高6us
IIC_SCL = 0; //准备开始传输设备地址信息了
}
//定义发送STOP信号函数
/*
SCL:
1 ---------------------------------
0
SDA: tbuf>4us
1 ------------------
tsu:sto>4.7us |
0 ---------------
*/
void IIC_Stop(void) {
SDA_OUT(); //将SDA配置为输出
IIC_SDA = 0; //SDA拉低
IIC_SCL = 1; //SCL拉高
delay_us(6); //延时>4.7us
IIC_SDA = 1; //SDA拉高
delay_us(6); //延时>4us
}
//定义等待ACK信号函数
u8 IIC_Wait_Ack(void) {
u8 tempTime = 0; //判断接收ACK信号是否超时
IIC_SCL = 0; //为了让外设在SCL为低电平的时候将ACK信号放到SDA上
delay_us(6); //在此期间外设将ACK信号放到数据线SDA上
SDA_IN(); //SDA配置为输入,为了获取到外设发送的ACK
IIC_SCL = 1; //拉高SCL
delay_us(6);
while(READ_SDA) {
tempTime++;
if(tempTime > 250) {
IIC_Stop(); //没有收到有效ACK超时,停止后续的数据传输
return 1;
}
}
IIC_SCL = 0; //既然接收到了ACK信号了,可以继续传输数据了
return 0;
}
//定义CPU发送ACK信号函数
void IIC_Ack(void) {
IIC_SCL = 0; //拉低的目的是将ACK数据放到SDA上
SDA_OUT();
IIC_SDA = 0; //发送一个有效的低电平ACK信号
delay_us(6);
IIC_SCL = 1; //让外设在SCL为高电平获取ACK信号
delay_us(6);
IIC_SCL = 0;
}
//定义CPU发送NACK信号函数
void IIC_NAck(void) {
IIC_SCL = 0; //拉低的目的是将ACK数据放到SDA上
SDA_OUT();
IIC_SDA = 1; //发送一个无有效的ACK信号,高电平
delay_us(6);
IIC_SCL = 1; //让外设在SCL为高电平获取ACK信号
delay_us(6);
IIC_SCL = 0;
}
//定义发送1字节数据的函数
void IIC_Send_Byte(u8 TxData) {
u8 i;
SDA_OUT(); //为了发送数据
IIC_SCL = 0; //为了将数据放到SDA上
for(i = 0; i < 8; i++) { //发送8个bit位,一位一位的传输
if(TxData & 0x80) //从高位开始传输,此时需要判断高位的bit位值到底是1还是0
IIC_SDA = 1; //发送1
else
IIC_SDA = 0; //发送0
TxData <<= 1; //将低位往bit7挪动
delay_us(6);
IIC_SCL = 1; //为了让外设获取数据
delay_us(6);
IIC_SCL = 0; //开始传输下一个bit位
}
}
//定义读取1字节数据的函数
u8 IIC_Read_Byte(u8 ack) {
u8 i, data = 0;
SDA_IN(); //首先将SDA配置为输入,为了获取数据
for(i = 0; i < 8; i++) { //读取8个bit位
IIC_SCL = 0; //将SCL拉低目的是让外设将数据放到SDA上
delay_us(6);
IIC_SCL = 1; //将SCL拉高目的是让CPU开始获取数据
data |= READ_SDA << (7 - i); //由于I2C数据传输从高位开始,所以这么干
delay_us(6);
}
//读取1字节数据完毕,判断是否需要让CPU发送ACK还是NACK
if(!ack)
IIC_NAck();
else
IIC_Ack();
return data; //返回读取到的数据
}