一、简介
本文主要介绍STM32F10xx系列如何使用软件模拟I2C总线读写AT24C02的EEPROM数据。
二、概述
I2C协议是一种用于同步、半双工、串行总线(由单片机时钟线、单数据交换器数据线组成)上的协议。规定了总线空闲状态、起始条件、停止条件、数据有效性、字节格式、响应确认信号、从设备地址选择、数据方向。有主从机之分,主机主控就是掌控单片机时钟信号的一方,并且起始信号和停止信号也由主机发送。现在很多的硬件、传感器等都是用到i2c协议与MCU(stm32)进行通信的。因此i2c还是必不可少的一个重要知识点。
三、I2C协议
I2C基本读写过程如下:
包括:起始信号、停止信号、应答、发送数据等。
1)起始信号
在SCL为高定平的基础上,SDA由高电平跳变为低电平为起始信号。为一次传输的开始。
2)停止信号
在SCL为高定平的基础上,SDA由低电平跳变为高电平为停止信号。为一次传输的结束。
3)数据有效性
在起始信号接收到之后,需将SCL信号拉低准备数据传输。SCL 为高电平的时候 SDA 表示的数据有效,即此时的 SDA 为高电平时表示数据“1”,为低电平时表示数据“0”。当 SCL 为低电平时,SDA的数据无效,一般在这个时候 SDA 进行电平切换,为下一次表示数据做好准备。注意每次数据传输都以字节为单位。
4)应答
I2C 的数据和地址传输都带响应。响应包括“应答 (ACK)”和“非应答 (NACK)”两种信号。作为数据接收端时,当设备 (无论主从机) 接收到 I2C 传输的一个字节数据或地址后,若希望对方继续发送数据,则需要向对方发送“应答 (ACK)”信号,发送方会继续发送下一个数据;若接收端希望结束数据传输,则向对方发送“非应答 (NACK)”信号,发送方接收到该信号后会产生一个
停止信号,结束信号传输。传输时主机产生时钟,在第9个时钟时,数据发送端会释放 SDA 的控制权,由数据接收端控制SDA,若 SDA 为高电平,表示非应答信号 (NACK),低电平表示应答信号 (ACK)。
四、驱动代码
1)IIC驱动
#include "iic_drv.h"
#include "delay.h"
#define IIC_SCL PCout(12)
#define IIC_SDA PCout(11)
#define SDA_OUT() {GPIOC->CRH &= 0xFFFF0FFF;\
GPIOC->CRH |= 0x00003000;\
};
#define SDA_IN() {GPIOC->CRH &= 0xFFFF0FFF;\
GPIOC->CRH |= 0x00008000;\
};
void IIC_Init(void)
{
RCC->APB2ENR |= 1 << 4; //enable I/O port C clock
GPIOC->CRH &= 0xFFF00FFF; //cfg sda scl as output PP speed 50MHZ
GPIOC->CRH |= 0x00033000;
GPIOC->ODR |= 0x3 << 11; //cfg sda scl output '1'
}
//start signal SCL=1 SDA change from high to low
void IIC_Start(void)
{
SDA_OUT();
IIC_SDA = 1;
IIC_SCL = 1;
delay_us(4);
IIC_SDA = 0;
delay_us(4);
//IIC_SCL = 0; //SCL change to low for ready to send data after start signal(SCL = 1,data line stable,data valid)
}
//stop signal SCL=1 SDA change from low to high
void IIC_Stop(void)
{
SDA_OUT();
IIC_SDA = 0;
IIC_SCL = 1;
delay_us(4);
IIC_SDA = 1;
delay_us(4);
}
//wait ack
//return 0:ACK
// 1:NACK
void IIC_WaitAck(void)
{
u8 ack = 0;
u16 timeout = 0;
SDA_IN();
IIC_SCL = 1;
delay_us(4);
do
{
ack = IIC_SDA;
timeout++;
if (timeout > 250)
{
IIC_Stop();
return;
}
} while (ack);
}
//send Ack
void IIC_Ack(void)
{
SDA_OUT();
IIC_SDA = 0;
IIC_SCL = 1;
delay_us(4);
IIC_SCL = 0; //release scl
delay_us(4);
}
//send NAck
void IIC_NAck(void)
{
SDA_OUT();
IIC_SDA = 1;
IIC_SCL = 1;
delay_us(4);
IIC_SCL = 0; //release scl
delay_us(4);
}
void IIC_Send_Byte(u8 dat)
{
u8 i;
SDA_OUT();
for (i = 0; i < 8; i++)
{
IIC_SCL = 1;
IIC_SDA = (dat >> 7) & 0x01;
delay_us(2);
IIC_SCL = 0;
delay_us(2);
}
}
u8 IIC_Read_Byte(u8 ack)
{
u8 i, receive = 0;
SDA_IN();
for (i = 0; i < 8; i++)
{
IIC_SCL = 1;
receive |= IIC_SDA << (7 - i);
delay_us(4);
IIC_SCL = 0;
delay_us(4);
}
(ack == 0) ? IIC_Ack() : IIC_NAck();
return receive;
}
2)AT24C02驱动
#include "AT24C02.h"
#include "iic_drv.h"
#include "delay.h"
void AT24C02_Init(void)
{
IIC_Init();
}
u8 AT24C02_ReadOneByte(u16 ReadAddr)
{
u8 temp = 0;
IIC_Start();
IIC_Send_Byte(0xA0);
IIC_WaitAck();
IIC_Send_Byte(ReadAddr%256);
IIC_WaitAck();
IIC_Start();
IIC_Send_Byte(0xA1);
IIC_WaitAck();
temp = IIC_Read_Byte(1);
IIC_Stop();
}
void AT24C02_WriteOneByte(u16 WriteAddr, u8 dat)
{
IIC_Start();
IIC_Send_Byte(0xA0);
IIC_WaitAck();
IIC_Send_Byte(WriteAddr%256);
IIC_WaitAck();
IIC_Send_Byte(dat);
IIC_WaitAck();
IIC_Stop();
delay_ms(10);
}
u8 AT24C02_Check(void)
{
u8 temp;
temp = AT24C02_ReadOneByte(0xFF);
if (temp == 0x55)
return 0;
AT24C02_WriteOneByte(0xFF, 0x55);
temp = AT24C02_ReadOneByte(0xFF);
if (temp == 0x55)
return 0;
return 1;
}
void AT24C02_Read(u16 ReadAddr, u8 *pBuf, u16 NumToRead)
{
while (NumToRead--)
{
*pBuf++ = AT24C02_ReadOneByte(ReadAddr);
ReadAddr++;
}
}
void AT24C02_Write(u16 WriteAddr, u8 *pBuf, u16 NumToWrite)
{
while (NumToWrite--)
{
AT24C02_WriteOneByte(WriteAddr, *pBuf);
WriteAddr++;
pBuf++;
}
}