I2C是一种串行通讯总线,由于只有串行数据线SDA和串行时钟线SCL两个总线而被广泛使用。
I2C软件模拟通信的本质是用芯片上任意两个引脚模拟I2C通信,也就是说通过控制任意两个引脚电平的高低变化来模拟I2C时序。写代码之前,需要非常熟悉I2C时序图。
准备基础知识:
如下I2C时序图,I2C总线在空闲状态下SCL和SDA都保持高电平。
I2C总线在传送数据过程中共有三种类型信号:开始信号、结束信号和应答信号。
开始信号:SCL为高电平时,SDA 由高向低时,开始传送数据。
结束信号:SCL为高电平时,SDA 由低向高时,结束传送数据。
应答信号:在一个字节传输的8个时钟后的第9个时钟期间,接收器必须回送一个应答位(ACK)给发送器。
本项目所用芯片STM32F103RCT6,最小系统自带I2C存储器AT24C02,原理图如下:
由于I2C可以接多个主设备,多个从设备(外围设备),因此进行通讯是需要进行器件寻址,以此分辨是于哪个设备进行通讯,每个设备地址唯一。
EEPROM器件寻址:起始条件使能芯片读写操作后,EEPROM都要求有8位的器件地址信息。该器件地址信息的LSB为读/写操作选择位,高为读操作,低为写操作。若比较器件地址一致,EEPRON输出应答立答“0”。如果不一致,则返向待机状态。
AT24C系列E2PROM芯片地址的固定部分为1010。设备地址需要看芯片引脚的具体连接情况,本项目中1,2,3引脚均接地,则A2,A1,A0三位均为0,因此写地址为10100000,即0xA0;读地址为10100001,即0xA1。
要点:
模拟I2C是GPIO应设置为通用开漏输出或通用推挽输出,电平每跳变一次,需要加延时函数。
设备地址需要看芯片引脚的具体连接情况,数据地址的长度根据芯片不同而不同。
SCL时钟电平为低时,才可以改换SDA数据线的电平,SCL为高时SDA必须保持稳定。SDA数据在SCL时钟为低电平时准备好,在SCL上升沿的过程发送出去。
在写完8位数据之后,将要读取应答信号,这里也就是要SDA将从输出状态变为输入状态。
接收器拉低SDA线表示应答,并在应答脉冲期间保持稳定的低电平。当主器件作接收器时,必须发出数据传输结束的信号给发送器,即它在最后一个字节之后的应答脉冲期间不会产生应答信号(不拉低SDA)。这种情况下,发送器必须释放SDA线为高以便主器件产生停止条件。
在写读写程序的时候根据SDA时序及要求进行。也就是说必须看明白下面两个图。
主器件在EEPROM收到每个数据后都应答“0”。最后由主器件发送停止条件,终止写序列。
EEPROM接收器件地址和数据字地址并产生应答ACK,主器件就产生一个重复的起始条件。主器件发送器件地址(读/写选择位为“1”,读操作),EEPROM应答ACK,并随时钟发送数据,主器件不发送“0”时,产生停止条件。
时序混乱会导致程序无法运行,多写或者少写数据,因此每一步的高低电平时序都要谨慎思考。
模拟I2C传送的数据很慢,比硬件I2C要慢很多,而且占用CPU资源,而硬件IIC速度效率高,但用法复杂,不够稳定,容易产生卡死,各有利弊,但工程中常用硬件I2C。
I2C软件模拟EEPROM步骤:
1、配置所需要的宏
2、配置时钟
3、相关GPIO初始化
4、SDA输入输出方向配置
5、I2C起始信号及I2C结束信号
6、应答信号及非应答信号的产生
7、设置等待应答信号
8、主机发送数据
9、主机接收数据
10、EEPROM写入一个数据
11、EEPROM读出一个数据
12、EEPROM写入定长数据
13、EEPROM读出定长数据
14、EEPROM写入数组数据
15、EEPROM读出数组数据
16、EEPROM检查是否正常
代码如下:项目I2C模拟引脚用PB6,PB7。
//main.h 宏定义
#ifndef _I2C_H_
#define _I2C_H_
#include "stm32f10x.h"
#define I2C_SCL GPIO_Pin_6
#define I2C_SDA GPIO_Pin_7
#define GPIO_I2C GPIOB
#define I2C_SCL_H GPIO_SetBits(GPIO_I2C, I2C_SCL)
#define I2C_SCL_L GPIO_ResetBits(GPIO_I2C, I2C_SCL)
#define I2C_SDA_H GPIO_SetBits(GPIO_I2C, I2C_SDA)
#define I2C_SDA_L GPIO_ResetBits(GPIO_I2C, I2C_SDA)
#define AT24C02 255
#define EE_TYPE AT24C02
void RCC_Init(void);
void I2C_GPIO_Config(void);
void I2C_OUT(void);
void I2C_IN(void);
void I2C_Start(void);
void I2C_Stop(void);
void Send_Byte(uint8_t data);
uint8_t Read_Byte(uint8_t ack);
uint8_t I2C_Wait_ACK(void);
void I2C_ACK(void);
void I2C_NACK(void);
uint8_t AT24C02_ReadOneByte(uint16_t ReadAddr);
void AT24C02_WriteOneByte(u16 WriteAddr,u8 DataToWrite);
void AT24C02_WriteLenByte(uint16_t WriteAddr,uint32_t DataToWrite,uint8_t Len);
uint32_t AT24C02_ReadLenByte(uint16_t ReadAddr,uint8_t Len);
void AT24C02_Write(uint16_t WriteAddr,uint8_t *pBuffer,uint16_t NumToWrite);
void AT24C02_Read(uint16_t ReadAddr,uint8_t *pBuffer,uint16_t NumToRead);
uint8_t AT24C02_Check(void);
#endif
//main.c
#include "sys.h"
#include "stm32f10x.h"
#include "string.h"
#include "stdio.h"
#include "main.h"
#include "tools.h"
//时钟配置
void RCC_Init()
{
ErrorStatus HSEStartUpStatus;
RCC_DeInit();
RCC_HSEConfig(RCC_HSE_ON);
HSEStartUpStatus=RCC_WaitForHSEStartUp();
if(HSEStartUpStatus==SUCCESS)
{
FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);
FLASH_SetLatency(FLASH_Latency_2);
RCC_HCLKConfig(RCC_SYSCLK_Div1);
RCC_PCLK1Config(RCC_HCLK_Div2);
RCC_PCLK2Config(RCC_HCLK_Div1);
RCC_PLLConfig(RCC_PLLSource_HSE_Div1,RCC_PLLMul_9);
}
else
{
/*do nothing*/
}
RCC_PLLCmd(ENABLE);
while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY)==RESET)
{
}
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
while (RCC_GetSYSCLKSource()!=0x08)
{
}
}
//GPIO初始化
void I2C_GPIO_Config()
{
GPIO_InitTypeDef GPIO_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitStruct.GPIO_Pin=I2C_SCL|I2C_SDA;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_OD;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOC,&GPIO_InitStruct);
I2C_SCL_H;
I2C_SDA_H;
}
//SDA输出
void I2C_OUT()
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin=I2C_SDA;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_OD;
GPIO_Init(GPIOC,&GPIO_InitStructure);
I2C_SCL_H;
I2C_SDA_H;
}
//SDA输入
void I2C_IN()
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin=I2C_SDA;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;
GPIO_Init(GPIOC,&GPIO_InitStructure);
}
//起始信号
void I2C_Start(void)
{
I2C_OUT();
I2C_SDA_H;
I2C_SCL_H;
DelayUS(4);
I2C_SDA_L;
DelayUS(4);
I2C_SCL_L;
}
//停止信号
void I2C_Stop(void)
{
I2C_OUT();
I2C_SCL_L;
I2C_SDA_L;
DelayUS(4);
I2C_SCL_H;
I2C_SDA_H;
DelayUS(4);
}
//产生应答信号
void I2C_ACK(void)
{
I2C_SCL_L;
I2C_OUT();
I2C_SDA_L;
DelayUS(2);
I2C_SCL_H;
DelayUS(2);
I2C_SCL_L;
}
//产生非应答信号
void I2C_NACK(void)
{
I2C_SCL_L;
I2C_OUT();
I2C_SDA_H;
DelayUS(2);
I2C_SCL_H;
DelayUS(2);
I2C_SCL_L;
}
//等待应答信号
uint8_t I2C_Wait_ACK()
{
int time=0;
I2C_IN();
I2C_SDA_H;
DelayUS(4);
I2C_SCL_H;
DelayUS(4);
while(GPIO_ReadInputDataBit(GPIO_I2C,I2C_SDA))
{
time++;
if(time>250)
{
I2C_Stop();
return 1;
}
}
I2C_SCL_L;
I2C_OUT();
return 0;
}
//发送数据
void Send_Byte(uint8_t data)
{
uint8_t i;
I2C_OUT();
I2C_SCL_L;
DelayUS(2);
for(i=0;i<8;i++)
{
if((data&0x80)>>7)
{
I2C_SDA_H;
}
else
{
I2C_SDA_L;
data <<= 1;
DelayUS(2);
I2C_SCL_H;
DelayUS(2);
}
I2C_SCL_L;
DelayUS(2);
}
}
//接收数据
uint8_t Read_Byte(uint8_t ack)
{
uint8_t cnt;
uint8_t rev_Data;
I2C_IN();
I2C_SCL_L;
DelayUS(2);
I2C_SDA_H;
for(cnt=0;cnt<8;cnt++)
{
I2C_SCL_H;
DelayUS(2);
rev_Data<<=1;
if(GPIO_ReadInputDataBit(GPIO_I2C,I2C_SDA))
{
rev_Data++;
I2C_SCL_L;
DelayUS(2);
}
}
if(ack==0)
{
I2C_NACK();
}
else
{
I2C_ACK();
}
return rev_Data;
}
//读单个字节
uint8_t AT24C02_ReadOneByte(uint16_t ReadAddr)
{
uint8_t temp=0;
I2C_Start();
if(EE_TYPE>2047)
{
Send_Byte(0XA0);
I2C_Wait_ACK();
Send_Byte(ReadAddr>>8);
//I2C_Wait_ACK();
}
else
{
Send_Byte(0XA0+((ReadAddr/256)<<1));
}
I2C_Wait_ACK();
Send_Byte(ReadAddr%256);
I2C_Wait_ACK();
I2C_Start();
Send_Byte(0XA1);
I2C_Wait_ACK();
temp=Read_Byte(0);
I2C_Stop();
return temp;
}
//写单个字节
void AT24C02_WriteOneByte(uint16_t WriteAddr,uint8_t DataToWrite)
{
I2C_Start();
if(EE_TYPE>2047)
{
Send_Byte(0XA0);
I2C_Wait_ACK();
Send_Byte(WriteAddr>>8);
}
else
{
Send_Byte(0XA0+((WriteAddr/256)<<1));
}
I2C_Wait_ACK();
Send_Byte(WriteAddr%256);
I2C_Wait_ACK();
Send_Byte(DataToWrite);
I2C_Wait_ACK();
I2C_Stop();
DelayMS(10);
}
//写入定长数据
void AT24C02_WriteLenByte(uint16_t WriteAddr,uint32_t DataToWrite,uint8_t Len)
{
uint8_t t;
for(t=0;t<Len;t++)
{
AT24C02_WriteOneByte(WriteAddr+t,(DataToWrite>>(8*t))&0xff);
}
}
//读取定长数据
uint32_t AT24C02_ReadLenByte(uint16_t ReadAddr,uint8_t Len)
{
uint8_t t;
uint32_t temp=0;
for(t=0;t<Len;t++)
{
temp<<=8;
temp+=AT24C02_ReadOneByte(ReadAddr+Len-t-1);
}
return temp;
}
//读数组数据
void AT24C02_Read(uint16_t ReadAddr,uint8_t *pBuffer,uint16_t NumToRead)
{
while(NumToRead)
{
*pBuffer++=AT24C02_ReadOneByte(ReadAddr++);
NumToRead--;
}
}
//写入数组数据
void AT24C02_Write(uint16_t WriteAddr,uint8_t *pBuffer,uint16_t NumToWrite)
{
while(NumToWrite--)
{
AT24C02_WriteOneByte(WriteAddr,*pBuffer);
WriteAddr++;
pBuffer++;
}
}
//检查AT24C02是否正常
uint8_t AT24C02_Check(void)
{
uint8_t temp;
temp=AT24C02_ReadOneByte(255);
if(temp==0X55)
{
return 0;
}
else
{
AT24C02_WriteOneByte(255,0X55);
temp=AT24C02_ReadOneByte(255);
if(temp==0X55)
{
return 0;
}
}
return 1;
}
uint8_t readDate[10]={0};
uint8_t writeDate[8]={1,2,3,4,5,6,7,8};
int main()
{
uint8_t i=0;
RCC_Init();
I2C_GPIO_Config();
AT24C02_Check();
AT24C02_Write(0,writeDate,10);
for(i=0;i<10;i++)
{
readDate[i]=writeDate[i];
}
AT24C02_Read(0,readDate,10);
DelayMS(500);
while(1)
{
}
}
实验结果:
通过程序模拟输出数组的数据,输出正确。
版权声明:本文为CSDN博主「Artemis_yuer」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Artemis_yuer/article/details/122312885
暂无评论