首先,写模拟I2C的需要知道的一点基础知识,让你能够知道有哪些模拟的信号,是干嘛用的。
-
起始信号:时钟线SCL为高时,数据线SDA由高到低。
-
停止信号:时钟线SCL为高时,数据线SDA由低到高。
-
只有在SCL为低电平时,SDA才允许进行高低电平的转换
-
应答信号:应答信号分为2类,一类是发送的应答信号,一类是接收的应答信号。发送的应答信号是在读数据结束后紧跟着应答或非应答信号来表示是否要继续读数据。接收的应答信号是在没发送数据后都需要接收一个应答信号来进行接下来的步骤。
-
发送数据:向目标器件发送一个字节的数据。
-
读取数据:向目标器件读取一个字节的数据。
其次,讲一下I2C写和读的相关操作顺序
写:
- 发送起始信号。
- 发送器件地址+写命令(器件地址为7位,写为0,读为1,占一位,所以加起来为8位)。
- 接收应答信号。
- 发送寄存器地址。
- 接收应答信号。
- 发送需要写入的数据。
- 接收应答信号。
- 如果需要连续写数据,则重复6,7步骤。
- 发送停止信号,结束。
读:
- 发送起始信号。
- 发送器件地址+写命令(器件地址为7位,写为0,读为1,占一位,所以加起来为8位)。
- 接收应答信号。
- 发送寄存器地址。
- 接收应答信号。
- 发送起始信号。
- 发送器件地址+读命令。
- 接收应答信号。
- 读取数据
- 若需要继续读数据,则发送应答信号。重复9的操作
- 若不需要继续读数据,则发送非应答信号
- 发送停止信号,结束。
接下来就是紧张刺激的代码环节
I2C_gpio.h
#ifndef _I2C_GPIO_H
#define _I2C_GPIO_H
#include "stm32f10x.h"
#include "delay.h"
#define SDA GPIO_Pin_6 //根据自己的引脚进行修改
#define SCL GPIO_Pin_7 //根据自己的引脚进行修改
#define I2C_port GPIOA //根据自己的引脚进行修改
#define SDA_H GPIO_SetBits(I2C_port,SDA) //SDA高电平
#define SCL_H GPIO_SetBits(I2C_port,SCL) //SCL高电平
#define SDA_L GPIO_ResetBits(I2C_port,SDA)//SDA低电平
#define SCL_L GPIO_ResetBits(I2C_port,SCL)//SCL低电平
void I2C_GPIO_Config(void); //GPIO模拟I2C的引脚初始化
void I2C_Start(void); //起始信号
void I2C_Stop(void); //停止信号
uint8_t I2C_Wait_Ack(void); //接收应答信号
void I2C_Ack(void); //发送应答信号
void I2C_nAck(void); //发送非应答信号
void I2C_Send_Byte(uint8_t Dat); //发送一个字节
uint8_t I2C_Read_Byte(unsigned char Ack); //读一个字节
uint8_t I2C_Write(uint8_t Add,uint8_t Reg,uint8_t Dat); //I2C写一个数据
uint8_t I2C_Write_Len(uint8_t Add,uint8_t Reg,uint8_t Len,uint8_t *Buf); //I2C写多个数据
uint8_t I2C_Read(uint8_t Add,uint8_t Reg); //I2C读一个数据
uint8_t I2C_Read_Len(uint8_t Add,uint8_t Reg,uint8_t Len,uint8_t *Buf); //I2C读多个数据
#endif
I2C_gpio.c
开漏输出的引脚不能独立输出高电平,需要配合上拉电阻来完成输出高电平,他可以读取引脚状态。并且在I2C电路中本来就需要上拉电阻,所以简单来说,在I2C电路中,开漏输出既可以输出信号,又可以读取输入信号。不需要频繁切换GPIO的模式来进行相关操作,是最佳的引脚模式。
#include "I2C_gpio.h"
void I2C_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStruct; //设置GPIO结构体
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//使能外设GPIOA时钟(GPIO口不是A的需要换)
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD; //设置GPIO口为开漏输出
GPIO_InitStruct.GPIO_Pin = SDA|SCL; //设置需要配置的引脚
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; //设置GPIO的速率为50Mhz
GPIO_Init(I2C_port,&GPIO_InitStruct); //初始化GPIO
GPIO_SetBits(I2C_port,SDA|SCL); //初始化SDA和SCL为高电平
}
void I2C_Start(void) //I2C的起始信号:当SCL为高期间,SDA由高到低的跳变
{ //间隔时间为大于4.7us,所以保险起见为5us。
SDA_H;
delay_us(5); //延时5微秒
SCL_H;
delay_us(5);
SDA_L;
delay_us(5);
SCL_L; //钳住I2C总线,准备发送或接收数据
}
void I2C_Stop(void) //I2C的停止信号:当SCL为高期间,SDA由低到高的跳变
{
SCL_L; //只有SCL为低电平是,SDA才允许改变改变。不先把SCL线拉低,SDA的低电平容易误判为起始信号。
SDA_L;
delay_us(5);
SCL_H; //SCL高电平
delay_us(5);
SDA_H; //SDA从低到高,触发终止信号
delay_us(5);
}
uint8_t I2C_Wait_Ack(void) //等待I2C的应答
{
uint8_t Time = 0;
SDA_H;
delay_us(1);
SCL_H;
delay_us(1);
while(GPIO_ReadInputDataBit(I2C_port,SDA))//如果SDA收到高电平信号
{
Time++;
if(Time > 250)
{
I2C_Stop();
return 1;//如果计数大于250,则发送停止信号,并返回1,表示接收应答信号失败
}
}
//如果SDA收到低电平信号
SCL_L; //SCL拉低,结束第9位的脉冲
//SDA_OUT();
return 0; //接收应答信号成功,返回0
}
void I2C_Ack(void) //SDA发送低电平来表示继续读,应答信号
{
SCL_L;
SDA_L;
delay_us(2);
SCL_H;
delay_us(2);
SCL_L;
}
void I2C_nAck(void) //SDA发送高电平来表示停止读,非应答信号
{
SCL_L;
SDA_H;
delay_us(2);
SCL_H;
delay_us(2);
SCL_L;
}
void I2C_Send_Byte(uint8_t Dat)//发送一个字节
{
uint8_t i;
SCL_L;
for(i=0;i<8;i++)
{
if((Dat&0x80)>>7 == 1) //dat与10000000,再右移7位,来判断最高位是否为1
{
SDA_H; //如果最高位是1,则SDA发送高电平
}
else //否则相反
SDA_L;
Dat <<= 1; //发送信号后,dat左移,进行下一位的数据发送
delay_us(2);
SCL_H;
delay_us(2);
SCL_L;
delay_us(2);
}
}
uint8_t I2C_Read_Byte(unsigned char Ack)//读一个字节
{
unsigned char i,receive=0;
for(i=0;i<8;i++)
{
SCL_L;
delay_us(2);
SCL_H;
receive<<=1; //receive左移一位
if(GPIO_ReadInputDataBit(I2C_port,SDA) == 1)//如果得到了高电平信号
{
receive++; //则+1
}
delay_us(1);
}
if(Ack == 1) //如果ack为1,则发送应答信号
{
I2C_Ack();
}
else
I2C_nAck(); //否则发送非应大信号
return receive; //返回得到的数据
}
uint8_t I2C_Write(uint8_t Add,uint8_t Reg,uint8_t Dat)
{
I2C_Start();//在起始信号后必须发送一个7位从机地址+1位方向位,用“0”表示主机发送数据,“1”表示主机接收数据。
I2C_Send_Byte((Add << 1) | 0); //发送 器件地址+写的命令
if(I2C_Wait_Ack() == 1) //等待响应,如果失败,发送停止信号,返回1
{
I2C_Stop();
return 1;
}
I2C_Send_Byte(Reg); //发送 寄存器地址
I2C_Wait_Ack(); //等待响应,如果失败,发送停止信号,返回1
I2C_Send_Byte(Dat); //发送8位数据的命令
if(I2C_Wait_Ack() == 1) //等待响应,如果失败,发送停止信号,返回1
{
I2C_Stop();
return 1;
}
I2C_Stop(); //发送结束,发送停止信号
return 0; //返回 0,确定发送成功
}
uint8_t I2C_Write_Len(uint8_t Add,uint8_t Reg,uint8_t Len,uint8_t *Buf)//I2C的写数据
{
uint8_t i;
I2C_Start();//在起始信号后必须发送一个7位从机地址+1位方向位,用“0”表示主机发送数据,“1”表示主机接收数据。
I2C_Send_Byte((Add << 1) | 0); //发送 器件地址+写的命令
if(I2C_Wait_Ack() == 1) //等待响应,如果失败,发送停止信号,返回1
{
I2C_Stop();
return 1;
}
I2C_Send_Byte(Reg); //发送 寄存器地址
if(I2C_Wait_Ack() == 1) //等待响应,如果失败,发送停止信号,返回1
{
I2C_Stop();
return 1;
}
for(i =0;i<Len;i++) //循环 len 次写数据
{
I2C_Send_Byte(Buf[i]); //发送第i位的8位数据
if(I2C_Wait_Ack() == 1) //等待响应,如果失败,发送停止信号,返回1
{
I2C_Stop();
return 1;
}
}
I2C_Stop(); //发送结束,发送停止信号
return 0; //返回 0,确定发送成功
}
uint8_t I2C_Read(uint8_t Add,uint8_t Reg)
{
uint8_t Dat;
I2C_Start();//在起始信号后必须发送一个7位从机地址+1位方向位,用“0”表示主机发送数据,“1”表示主机接收数据。
I2C_Send_Byte((Add << 1) | 0);
I2C_Wait_Ack(); //等待响应,如果失败,发送停止信号,返回1
I2C_Send_Byte(Reg); //发送 寄存器地址
I2C_Wait_Ack(); //等待响应,如果失败,发送停止信号,返回1
I2C_Start(); //发送开始信号
I2C_Send_Byte((Add << 1) | 1); //发送 器件地址+读的命令
I2C_Wait_Ack(); //等待响应,如果失败,发送停止信号,返回1
Dat = I2C_Read_Byte(0);
I2C_Stop(); //发送停止信号
return Dat; //读取成功,返回0
}
uint8_t I2C_Read_Len(uint8_t Add,uint8_t Reg,uint8_t Len,uint8_t *Buf)//I2C读数据
{
uint8_t i;
I2C_Start();//在起始信号后必须发送一个7位从机地址+1位方向位,用“0”表示主机发送数据,“1”表示主机接收数据。
I2C_Send_Byte((Add << 1) | 0); //发送 器件地址+写的命令
if(I2C_Wait_Ack() == 1) //等待响应,如果失败,发送停止信号,返回1
{
I2C_Stop();
return 1;
}
I2C_Send_Byte(Reg); //发送 寄存器地址
if(I2C_Wait_Ack() == 1) //等待响应,如果失败,发送停止信号,返回1
{
I2C_Stop();
return 1;
}
I2C_Start(); //发送开始信号
I2C_Send_Byte((Add << 1) | 1); //发送 器件地址+读的命令
if(I2C_Wait_Ack() == 1) //等待响应,如果失败,发送停止信号,返回1
{
I2C_Stop();
return 1;
}
for(i=0;i<Len;i++) //for循环 Len 次
{
if(i != Len-1) //如果不是读到最后一次
{
Buf[i] = I2C_Read_Byte(1); //保存第i次读到的数据到数组的第i位,并发送应答信号
}
else
Buf[i] = I2C_Read_Byte(0); //保存第i次读到的数据到数组的第i位,并发送非应答信号
}
I2C_Stop(); //发送停止信号
return 0; //读取成功,返回0
}