一、简介
本文主要介绍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 clockGPIOC->CRH &= 0xFFF00FFF; //cfg sda scl as output PP speed 50MHZGPIOC->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 scldelay_us(4);
}//send NAck
void IIC_NAck(void)
{SDA_OUT();IIC_SDA = 1;IIC_SCL = 1;delay_us(4);IIC_SCL = 0; //release scldelay_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++;}
}