这块我们来说一下I2C通信协议。
IIC有两根线,SCL时钟线,SDA数据线,主机可以挂载很多从机, 多个主机也可以挂载相同的从机,但是同一时刻只能有1个主机同一个从机进行通信。并且时钟线SCL是只能由主机控制,数据线SDA主机和从机都可以控制。
IIC时序
1.IIC空闲状态:SDA为高电平,SCL为高电平。
2.起始信号:当SCL为高电平时,SDA由高电平转变为低电平。
3.停止信号:当SCL为高电平时,SDA由低电平转变为高电平
4.应答或者不应答
当发送低电平时为应答,发送高电平为不应答
应答信号是在主机接收到数据后给从机的信号,一般选择不去应答,然后发送停止信号,如果应答从机可能还会继续发送数据。
5.时序
平时正常使用是写入一个字节(8个bit位),此时是向所有的从机发送,但是从机会去比对发送的数据,这个数据是从机的地址,这个地址是7位bit,但是不知道是给从机发还是从从机收到数据,所以最后一位数据是确定去发送还是去接收数据。
如图第1-7位是从机的地址位,最后第0位是控制向从机写还是从从机读数据,当第0位为0时表示主机向从机写数据,当为1时代表主机要接收从机的数据。
所以假设从机地址为1010000,此时要读数据,最后一位发送的就是1,最后的数据也就是0xA1,
当要向该从机写数据时就发送0xA0。
总体时序
在读数据和写数据的时候需要去同步传输的数据,IIC是通过SCL这根时钟线来同步传输的数据的,在写入1个bit位时,需要先将SCL拉低,然后改变此时你想传输的1位数据,再将SCL时钟线拉高,也就是从低电平转变为高电平。此时实现1位bit位的传输,再SCL为高电平时,SDA的数据需要是稳定的数据,也就是1或者0,不能一直再0和1之间跳变。
使用:当我们再实际用IIC协议时一般是先传输从机地址写数据,然后将想要在从机地址的那个寄存器的地址上写数据,第三个就是写的数据。
写1个字节数据的步骤
假设从机地址是1010000,需要向他的0x10地址写入数据0x55
起始信号 0xA0(要向该从机写数据) 等待应答 0x10(要向0x10寄存器中写入数据) 等待应答
0x55(数据) 等待应答 停止信号
读1个字节的步骤:
假设从机地址是1010000,需要向他的0x10地址读1个字节数据
起始信号 0xA0(向从机写入数据) 等待应答 0x10(要写入的地址)等待应答 起始信号 0xA1(要从从机读取数据) 等待应答 数据 主机非应答 停止信号
读取数据为什么又要写数据呢,因为此时前七位确定了从机的地址,但是不知道在从机的什么位置去读取数据,此时只能先发送需要读取的位置,再将写换成读,记住同一次不能即写又读,但是为了不去释放总线(怕被其他设备占用)所以再次在总线中使用起始信号去读取数据。
附带代码 .c文件
#include "stm32f10x.h"
#include "hal_eeprom.h"
static void I2C_delay(unsigned short t);
static void hal_I2CConfig(void);
static void I2C_Stop(void);
static void I2C_Start(void);
static void hal_I2C_SCL(unsigned char bVal);
static void hal_I2C_SDA(unsigned char bVal);
static unsigned char hal_I2C_SDA_INPUT(void);
static void hal_I2C_SDA_IO_Set(unsigned char IOMode);
static void hal_I2C_SDA_IO_Set(unsigned char IOMode);
static void I2C_Ack(void);
static void I2C_NoAck(void);
static unsigned char I2C_WaitAck(void);
static void I2C_SendByte(unsigned char SendByte) ;
static unsigned char I2C_ReceiveByte(void) ;
void hal_eepromInit(void)
{
hal_I2CConfig();
}
//GPIO初始化
static void hal_I2CConfig(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* Configure I2C2 pins: PB8->SCL and PB9->SDA */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitStructure.GPIO_Pin = I2C_SCL_PIN | I2C_SDA_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_Init(I2C_SCL_PORT, &GPIO_InitStructure);
hal_I2C_SDA(1);
hal_I2C_SCL(1);
}
static void hal_I2C_SDA(unsigned char bVal)
{
if(bVal)
{
GPIO_SetBits(I2C_SDA_PORT,I2C_SDA_PIN);
}else
{
GPIO_ResetBits(I2C_SDA_PORT,I2C_SDA_PIN);
}
}
static void hal_I2C_SCL(unsigned char bVal)
{
if(bVal)
{
GPIO_SetBits(I2C_SCL_PORT,I2C_SCL_PIN);
}else
{
GPIO_ResetBits(I2C_SCL_PORT,I2C_SCL_PIN);
}
}
static unsigned char hal_I2C_SDA_INPUT(void)
{
return GPIO_ReadInputDataBit(I2C_SDA_PORT, I2C_SDA_PIN);
}
static void I2C_delay(unsigned short t)
{
unsigned short i=50,j,c;
c = t;
for(j=0; j<c; j++)
{
while(i)
{
i--;
}
}
}
static void I2C_Start(void)
{
hal_I2C_SDA(1);
I2C_delay(1);
hal_I2C_SCL(1);
I2C_delay(1);
hal_I2C_SDA(0);
I2C_delay(1);
}
static void I2C_Stop(void)
{
hal_I2C_SDA(0);
I2C_delay(1);
hal_I2C_SCL(1);
I2C_delay(1);
hal_I2C_SDA(1);
I2C_delay(1);
}
static void hal_I2C_SDA_IO_Set(unsigned char IOMode)
{
if(IOMode == 0) //输出
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitStructure.GPIO_Pin = I2C_SDA_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_Init(I2C_SDA_PORT, &GPIO_InitStructure);
}else if(IOMode == 1) //输入
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitStructure.GPIO_Pin = I2C_SDA_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(I2C_SDA_PORT, &GPIO_InitStructure);
}
}
static void I2C_Ack(void)
{
hal_I2C_SCL(0);
I2C_delay(1);
hal_I2C_SDA(0);
I2C_delay(1);
hal_I2C_SCL(1);
I2C_delay(1);
hal_I2C_SCL(0);
I2C_delay(1);
}
static void I2C_NoAck(void)
{
hal_I2C_SCL(0);
I2C_delay(1);
hal_I2C_SDA(1);
I2C_delay(1);
hal_I2C_SCL(1);
I2C_delay(1);
hal_I2C_SCL(0);
I2C_delay(1);
}
static unsigned char I2C_WaitAck(void)
{
hal_I2C_SDA(1);
hal_I2C_SDA_IO_Set(1);
hal_I2C_SCL(1);
I2C_delay(1);
if(hal_I2C_SDA_INPUT())
{
return 0;
}
hal_I2C_SCL(0);
hal_I2C_SDA_IO_Set(0);
I2C_delay(1);
return 1;
}
static void I2C_SendByte(unsigned char SendByte)
{
unsigned char i=0;
unsigned char temp;
temp = SendByte;
for(i=0;i<8;i++)
{
hal_I2C_SCL(0);
I2C_delay(1);
if(temp&0x80)
{
hal_I2C_SDA(1);
}else
{
hal_I2C_SDA(0);
}
I2C_delay(1);
hal_I2C_SCL(1);
I2C_delay(1);
temp<<=1;
}
hal_I2C_SCL(0);
I2C_delay(1);
hal_I2C_SDA(1);
I2C_delay(1);
}
//通过I2C来写一个字节的步骤:首先开始信号,写入是发送还是写入的信号,等待应答,发送需要写入数据的地址,等待应答
//,最后在写入数据,等待应答,最后发送停止信号
void I2C_WriteByte(unsigned short address,unsigned char dat)
{
I2C_Start();
I2C_SendByte(0xA0);
I2C_WaitAck();
I2C_SendByte((address>>8)&0xFF);
I2C_WaitAck();
I2C_SendByte(address&0xFF);
I2C_WaitAck();
I2C_SendByte(dat);
I2C_WaitAck();
I2C_Stop();
//I2C_delay(1000); //延时10ms
}
//读操作同写操作差不多,但是
static unsigned char I2C_ReceiveByte(void)
{
unsigned char i;
unsigned char ReceiveByte=0;
hal_I2C_SCL(0);
I2C_delay(1);
hal_I2C_SDA(1);
hal_I2C_SDA_IO_Set(1); //SDA设置成输入
for(i=0; i<8; i++)
{
ReceiveByte <<= 1;
I2C_delay(1);
hal_I2C_SCL(1);
I2C_delay(1);
if(hal_I2C_SDA_INPUT())
{
ReceiveByte|=0x01;
}
hal_I2C_SCL(0);
}
hal_I2C_SDA_IO_Set(0); //SDA设置成输出
I2C_delay(1);
return ReceiveByte;
}
//读一个字节,操作同写差不多,主要是参数和读函数
unsigned char I2C_ReadByte(unsigned short address)
{
unsigned char dat;
I2C_Start();
I2C_SendByte(0xA0);
I2C_WaitAck();
I2C_SendByte((address>>8)&0xFF);
I2C_WaitAck();
I2C_SendByte(address&0xFF);
I2C_WaitAck();
I2C_Start();
I2C_SendByte(0xA1);
I2C_WaitAck();
dat = I2C_ReceiveByte();
I2C_NoAck();
I2C_Stop();
return dat;
}
.h文件
#ifndef __HAL_EEPROM_H__
#define __HAL_EEPROM_H__
#define I2C_SCL_PORT GPIOB
#define I2C_SCL_PIN GPIO_Pin_8
#define I2C_SDA_PORT GPIOB
#define I2C_SDA_PIN GPIO_Pin_9
#define EEPROM_PAGE_SIZE 32
void hal_eepromInit(void);
unsigned char I2C_ReadByte(unsigned short address);
void I2C_WriteByte(unsigned short address,unsigned char dat);
void I2C_PageWrite(unsigned short address,unsigned char *pDat, unsigned short num);
void I2C_Read(unsigned short address,unsigned char *pBuffer, unsigned short len);
#endif