一:硬件接口
1.1:功能引脚
1.2:IIC总线通信注意事项
二:通信协议
(1)空闲状态:
(2)起始位:
(3)有效数据位
(4)应答(非应答)
(5)停止位
IIC模拟代码
一:硬件接口
1.1:功能引脚
IIC一般由两根线组成,一根时钟线SCL,一根数据线SDA。
SCL: 提供数据交互的时钟
SDA: 进行数据的交互
当然在进行数据交互的时候,两个芯片需要共地。
1.2:IIC总线通信注意事项
(1)对于IIC总线来说,它是可以同时接入多个从机进行通信的,但是注意只能是主机(单片机)进行唯一的数据读写操作。
(2)为了进行通信,每一个可以进行IIC通信的从机的芯片手册上会标出该自己用于IIC通信的地址。(这里我们以AT24c128为例)
可以看到,基本的前面的bit位数据是固定的,但是留出了A1和A0两个bit的控制引脚,我们在电路板中将其对应的引脚置高,那么对应的bit数据就为1,反之则为0。最后一个LSB位,0代表需要对其进行写操作,1代表需要对其进行读操作。
(3)我们一般使用的IIC协议都是模拟的IIc协议,也就是我们通过时序图来进行对应电平切换模拟出的IIc通信代码。这里需要注意的是:
1:在不进行数据通信时,SCL和SDA引脚必须保持高电平。
2:使用的模拟IIC引脚必须接入上拉电阻,所以SDA和SCL一般配置为开漏输出。
3:SDA引脚既需要输入也需要输出,在读写操作切换时,记得修改该引脚的模式。
二:通信协议
IIC通信由:空闲状态+起始位+有效数据位+应答(非应答)+停止位 组成
(1)空闲状态:
这一时刻SDA和SCL一般都是保持高电平输出。所以我们在进行IIC模拟通信的初 始化时,SDA和SCL引脚都会被配置为高电平。
(2)起始位:
这个时刻,SCL引脚保持高电平不变,SDA引脚电平从高电平跳变为低电平。
(3)有效数据位
这里在发送有效数据位时,有一个规范,在SCL引脚为高电平时,SDA的电平不能被修改,这是数据的采样时刻,数据交互时,会在SCL引脚高电平时进行数据的采样。只有在SCL保持低电平时,才能对SDA引脚的电平进行修改(当然也不一定要修改,比如连续发送多个bit1或者bit0)
根据代码也能很好的看出来,每次在对SDA引脚进行电平修改时,都会将SCL引脚点哦置0,然后修改好SDA引脚电平后,再将SCL引脚电平置高。
(4)应答(非应答)
这里就比较简单了,有应答信号就是SDA引脚有一个低电平,没有的话就是高电平。
(5)停止位
IIC模拟代码
注意如果IIC读写输出出现问题,可以将延时适当调大一些。
// 该代码用于stm32f103c8t6测试没问题
#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 OUTPUT 0
#define INPUT 1
static void I2C_Tail(void); //发送结束标志
static void I2C_Head(unsigned short Addrs); // 发送起始标志
static void hal_I2C_Config(void); // 模拟I2C配置函数
static void hal_I2C_SCL_SET(unsigned char i); // 设置时钟引脚
static void hal_I2C_SDA_SET(unsigned char i); // 设置数据引脚
static void hal_I2C_Start(void); // 起始标志
static void hal_I2C_Stop(void); // 结束标志
static void I2C_Delay(unsigned short t);
static void I2C_ACK(void); // 有ACK应答
static void I2C_NOACK(void); // 无ACK应答
static void I2C_SendByte(unsigned char data); // 写一个byte数据时序
static void I2C_WriteByte(unsigned short Addr, unsigned char data); // 发送一个byte
static unsigned char I2C_RecvByte(void); // 读一个字节时序
static unsigned char I2C_WaitAck(void); // 返回0,无应答数据 。返回1,有应答数据
void hal_I2C_SDA_IO_SET(unsigned char IOMode); // OUTPUT 或者 INPUT
unsigned char hal_IC_SDA_INPUT(void);
unsigned char I2C_ReadByte(unsigned short Addr); // 读一个字节数据
void hal_eeprom_Init(void)
{
hal_I2C_Config();
}
unsigned char I2C_ReadByte(unsigned short Addr)
{
unsigned char dat;
hal_I2C_Start();
I2C_SendByte(0xA0);
I2C_WaitAck();
I2C_SendByte((Addr>>8)&0xFF);
I2C_WaitAck();
I2C_SendByte(Addr&0xFF);
I2C_WaitAck();
hal_I2C_Start();
I2C_SendByte(0xA1); // 发送 1010 0001 最低位的1代表从该芯片读取数据
I2C_WaitAck();
dat = I2C_RecvByte();
I2C_NOACK();
hal_I2C_Stop();
return dat;
}
static unsigned char I2C_RecvByte(void)
{
unsigned char i = 0;
unsigned char RecvByte = 0;
hal_I2C_SCL_SET(0);
I2C_Delay(1);
hal_I2C_SDA_SET(1);
hal_I2C_SDA_IO_SET(INPUT);
for(; i < 8; i++)
{
RecvByte<<=1;
I2C_Delay(1);
hal_I2C_SCL_SET(1);
I2C_Delay(1);
if(hal_IC_SDA_INPUT()) {
RecvByte |= 0x01;
}
hal_I2C_SCL_SET(0);
}
hal_I2C_SDA_IO_SET(OUTPUT);
I2C_Delay(1);
return RecvByte;
}
void hal_I2C_Read( unsigned short Addrs, unsigned char *pBuf, unsigned short Len)
{
unsigned short i = 0;
I2C_Head(Addrs);
hal_I2C_Start();
I2C_SendByte(0xA1); // 发送 1010 0001 最低位的1代表从该芯片读取数据
I2C_WaitAck();
for( i = 0; i < Len; i++)
{
pBuf[i] = I2C_RecvByte();
if(i == Len - 1) I2C_NOACK();
else I2C_ACK();
}
hal_I2C_Stop();
}
static void I2C_WriteByte(unsigned short Addr, unsigned char data)
{
hal_I2C_Start(); // 发送有效数据起始标志
// 该芯片最低位的一个bit位代表读/写:0 代表写入数据 || 1 代表读出数据
I2C_SendByte(0xA0); // 发送需要通信的硬件地址(对应的硬件手册上有!!!!!)
I2C_WaitAck();
I2C_SendByte((Addr>>8)&0xFF); // 发送写入的地址(高字节地址)
I2C_WaitAck();
I2C_SendByte(Addr&0xFF); // 发送写入的地址(低字节地址)
I2C_WaitAck();
I2C_SendByte(data);
I2C_WaitAck();
hal_I2C_Stop(); // 发送有效数据结束标志
I2C_Delay(20000);
}
static unsigned char I2C_WaitAck(void)
{
hal_I2C_SDA_SET(1);
hal_I2C_SDA_IO_SET(INPUT); // 设置SDA引脚为输入引脚
hal_I2C_SCL_SET(1);
I2C_Delay(1);
if(hal_IC_SDA_INPUT()) { // 该引脚为高电平,代表无应答
hal_I2C_SDA_IO_SET(OUTPUT); // 设置SDA引脚为输出引脚
return 0;
}
hal_I2C_SCL_SET(0);
hal_I2C_SDA_IO_SET(OUTPUT);
I2C_Delay(1);
return 1;
}
static void I2C_Head(unsigned short Addrs)
{
hal_I2C_Start(); // 发送有效数据起始标志
// 该芯片最低位的一个bit位代表读/写:0 代表写入数据 || 1 代表读出数据
I2C_SendByte(0xA0); // 发送需要通信的硬件地址(对应的硬件手册上有!!!!!)
I2C_WaitAck();
I2C_SendByte((Addrs>>8)&0xFF); // 发送写入的地址(高字节地址)
I2C_WaitAck();
I2C_SendByte(Addrs&0xFF); // 发送写入的地址(低字节地址)
I2C_WaitAck();
}
static void I2C_Tail(void)
{
hal_I2C_Stop(); // 发送有效数据结束标志
I2C_Delay(20000);
}
void hal_I2C_Write(unsigned short Addrs, unsigned char* pBuf, unsigned short Len)
{
unsigned short tmp = 0;
unsigned short i = 0, j = 0;
unsigned short page = 0;
unsigned short size;
if(Addrs%EEPROM_PAGE_SIZE)
{
tmp = EEPROM_PAGE_SIZE-(Addrs%EEPROM_PAGE_SIZE); // 拿到当前页还有多少字节可写入
if(tmp > Len)
{
tmp = Len; // 后续写入操作,需要tmp来做循环因子
}
}
// 先将数据写入当前页
if(tmp)
{
I2C_Head(Addrs);
// 正式写入数据
for(i = 0; i < tmp; i++)
{
I2C_SendByte(pBuf[i]);
I2C_WaitAck();
}
I2C_Tail();
}
Len -= tmp; // 将已经发送过的数据个数减掉
Addrs += tmp; // 将写入的地址更新
page = Len/EEPROM_PAGE_SIZE; // int整形除法,只会得到整数
size = Len%EEPROM_PAGE_SIZE; // 不足一页还有几个字节
for(i = 0; i < page; i++)
{
I2C_Head(Addrs);
for(j = 0; j < EEPROM_PAGE_SIZE; j++)
{
I2C_SendByte(pBuf[tmp + j]);
I2C_WaitAck();
}
I2C_Tail();
Addrs += EEPROM_PAGE_SIZE;
tmp += EEPROM_PAGE_SIZE;
}
if(size)
{
I2C_Head(Addrs);
for(i = 0; i < size; i++)
{
I2C_SendByte(pBuf[tmp + i]);
I2C_WaitAck();
}
I2C_Tail();
}
}
static void I2C_SendByte(unsigned char data)
{
unsigned char i = 0;
for(; i < 8; i++)
{
hal_I2C_SCL_SET(0);
I2C_Delay(1);
if(data&0x80){
hal_I2C_SDA_SET(1);
} else {
hal_I2C_SDA_SET(0);
}
I2C_Delay(1);
hal_I2C_SCL_SET(1);
I2C_Delay(1);
data<<=1;
}
hal_I2C_SCL_SET(0);
I2C_Delay(1);
hal_I2C_SDA_SET(1);
I2C_Delay(1);
}
static void I2C_ACK(void)
{
hal_I2C_SCL_SET(0);
I2C_Delay(1);
hal_I2C_SDA_SET(0); // SDA数据线置低,进行数据位应答。
I2C_Delay(1);
hal_I2C_SCL_SET(1);
I2C_Delay(1);
hal_I2C_SCL_SET(0);
I2C_Delay(1);
}
static void I2C_NOACK(void)
{
hal_I2C_SCL_SET(0);
I2C_Delay(1);
hal_I2C_SDA_SET(1); // SDA数据线置高,不进行数据应答。
I2C_Delay(1);
hal_I2C_SCL_SET(1);
I2C_Delay(1);
hal_I2C_SCL_SET(0);
I2C_Delay(1);
}
static void hal_I2C_Start(void)
{
hal_I2C_SDA_SET(1);
I2C_Delay(1);
hal_I2C_SCL_SET(1);
I2C_Delay(1);
hal_I2C_SDA_SET(0);
I2C_Delay(1);
}
static void hal_I2C_Stop(void)
{
hal_I2C_SDA_SET(0);
I2C_Delay(1);
hal_I2C_SCL_SET(1);
I2C_Delay(1);
hal_I2C_SDA_SET(1);
I2C_Delay(1);
}
static void hal_I2C_Config(void)
{
GPIO_InitTypeDef GPIO_Struct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_Struct.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_Struct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Struct.GPIO_Pin = I2C_SCL_Pin | I2C_SDA_Pin;
GPIO_Init(GPIOB, &GPIO_Struct);
hal_I2C_SDA_SET(1);
hal_I2C_SCL_SET(1);
}
void hal_I2C_SDA_IO_SET(unsigned char IOMode)
{
if(0 == IOMode) {
GPIO_InitTypeDef GPIO_Struct;
GPIO_Struct.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_Struct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Struct.GPIO_Pin = I2C_SDA_Pin;
GPIO_Init(GPIOB, &GPIO_Struct);
}
if(1 == IOMode) {
GPIO_InitTypeDef GPIO_Struct;
GPIO_Struct.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Struct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Struct.GPIO_Pin = I2C_SDA_Pin;
GPIO_Init(GPIOB, &GPIO_Struct);
}
}
static void hal_I2C_SDA_SET(unsigned char i)
{
if(i) {
GPIO_SetBits(I2C_SDA_PORT,I2C_SDA_Pin);
} else {
GPIO_ResetBits(I2C_SDA_PORT,I2C_SDA_Pin);
}
}
static void hal_I2C_SCL_SET(unsigned char i)
{
if(i) {
GPIO_SetBits(I2C_SCL_PORT,I2C_SCL_Pin);
} else {
GPIO_ResetBits(I2C_SCL_PORT,I2C_SCL_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--;
}
}
}
unsigned char hal_IC_SDA_INPUT(void)
{
return GPIO_ReadInputDataBit(I2C_SDA_PORT, I2C_SDA_Pin);
}