1. 实验目的
通过软件模拟I2C,实现和24C02(EEPROM)之间的双向通信。
2. 实验流程
初始化IIC;
编写起始、停止、应答信号函数;
编写向24C02指定地址写入数据函数;
编写在24C02指定地址读取数据函数;
编写main函数
2.1 初始化IIC
这里是使用GPIOB8,GPIOB9模拟IIC,GPIOB8对应的是IIC的SCL,GPIOB9对应的是IIC的SDA。
如图所示,PB9对应的就是18位和第19位,00是输入模式,01是输出模式;两个位&(与运算),相同为本身,不同为0;两个位|(或运算),有1为1,两者0为0;
&= ~(1<<X);就是把X位置0,其他位置不变。
|=1<<X; 就是把X位置1,其他位置不变。
GPIOB->MODER&=~(3<<(9*2));(3<<(9 * 2))就是把第18位和第19位置为1,其他位置为0; ~(3<<(9 * 2))就是把第18位和第19位置为0,其他位置为1;&= ~(3<<(9 * 2));就是把第18位和第19位置为0,其他位变为原来的值。GPIOB->MODER|=0<<9 * 2;就是第18位置为0,其他位不变。(这里可以不加这个,不影响),所以就是输入模式。
GPIOB->MODER&=~(3<<(9 * 2));就是把第18位和第19位置为0,其他位变为原来的值;GPIOB->MODER|=1<<9 * 2; 就是第18位置为1,其他位不变。所以就是输出模式。
#define SDA_IN() {GPIOB->MODER&=~(3<<(9*2));GPIOB->MODER|=0<<9*2;} //PB9输入模式
#define SDA_OUT() {GPIOB->MODER&=~(3<<(9*2));GPIOB->MODER|=1<<9*2;} //PB9输出模式
#define IIC_SCL PBout(8) //SCL,可以输出0和1
#define IIC_SDA PBout(9) //SDA,可以输出0和1
#define READ_SDA PBin(9) //输入SDA
//初始化IIC
void IIC_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);//使能GPIOB时钟
//GPIOB8,B9初始化设置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//普通输出模式
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化
IIC_SCL=1; //默认拉高的,因为空闲状态就是电平拉高的
IIC_SDA=1; //默认拉高的,因为空闲状态就是电平拉高的
}
2.2 编写起始、停止、应答信号函数
2.2.1 产生IIC起始信号
产生起始信号:SDA线开始输出高电平,SCL开始输出高电平,过一段时间,SDA线输出低电平,SCL还是输出高电平。这里的IIC_SCL=0;是下一时刻准备发送或接收数据。
//产生IIC起始信号
void IIC_Start(void)
{
SDA_OUT(); //SDA线输出, PB9输出模式,PB8初始化就是输出模式
IIC_SDA=1; //拉高,代表空闲状态
IIC_SCL=1; //拉高,代表空闲状态
delay_us(4);
IIC_SDA=0; //START:when CLK is high,DATA change form high to low //变低了
delay_us(4); //延长一段时间是根据时序图来的
IIC_SCL=0; //钳住I2C总线,准备发送或接收数据
}
2.2.2 产生IIC停止信号
产生停止信号:SDA线开始输出低电平,SCL线开始输出低电平,过一小段时间,SDA线输出高电平,SCL线输出高电平。
//产生IIC停止信号
void IIC_Stop(void)
{
SDA_OUT(); //SDA线输出
IIC_SCL=0; //开始的时候就是低,然后再变为高的
IIC_SDA=0; //STOP:when CLK is high DATA change form low to high
delay_us(4);
IIC_SCL=1;
IIC_SDA=1; //发送I2C总线结束信号
delay_us(4);
}
2.2.3 等待应答信号
READ_SDA 是读取PB9输入状态的值是0还是1;如果是0则结束循环,代表数据有效,返回0;是1则循环,如果一段时间还是1,代表数据无效,返回1。
#define READ_SDA PBin(9) //输入SDA
//等待应答信号到来
//返回值:1,接收应答失败
// 0,接收应答成功
u8 IIC_Wait_Ack(void)
{
u8 ucErrTime=0;
SDA_IN(); //SDA设置为输入
IIC_SDA=1;delay_us(1);
IIC_SCL=1;delay_us(1);
while(READ_SDA) //是1(高电平)就无效
{
ucErrTime++;
if(ucErrTime>250)
{
IIC_Stop();
return 1;
}
}
IIC_SCL=0;//时钟输出0
return 0;
}
2.2.4 产生ACK应答
产生应答信号就是在SCL线由低电平——高电平——低电平,这期间SDA线都处于低电平。
//产生ACK应答
void IIC_Ack(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=0;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
}
2.2.5 不产生ACK应答
不产生应答信号就是在SCL线由低电平——高电平——低电平,这期间SDA线都处于高电平。
//不产生ACK应答
void IIC_NAck(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=1;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
}
2.2.6 IIC发送一个字节
发送一个字节,一位一位发送出去;0x80是1000 0000,txd&0x80是取其最高位, txd<<=1; 把数据左移1位,下一次就发送次高位,以此类推,把8位数据发完。注意在发送数据的时候,SCL线由低电平——高电平——低电平。
//IIC发送一个字节
//返回从机有无应答
//1,有应答
//0,无应答
void IIC_Send_Byte(u8 txd)
{
u8 t;
SDA_OUT(); //设置为输出
IIC_SCL=0; //拉低时钟开始数据传输 //取最高位,右移7位
for(t=0;t<8;t++)
{
IIC_SDA=(txd&0x80)>>7;
txd<<=1;
delay_us(2); //对TEA5767这三个延时都是必须的
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
delay_us(2);
}
}
2.2.7 IIC读取一个字节
读取一个字节,也是一个位一个位就行读,if(READ_SDA)receive++; 如果读到SDA线上是1就加1。
//读1个字节,ack=1时,发送ACK,ack=0,发送nACK
u8 IIC_Read_Byte(unsigned char ack)
{
unsigned char i,receive=0;
SDA_IN();//SDA设置为输入
for(i=0;i<8;i++ )
{
IIC_SCL=0;
delay_us(2);
IIC_SCL=1;
receive<<=1;
if(READ_SDA)receive++;
delay_us(1);
}
if (!ack)
IIC_NAck();//发送nACK
else
IIC_Ack(); //发送ACK
return receive;
}
2.3 编写向24C02指定地址写入数据函数
写的时候,EEPROM的设备地址是0XA0;读的时候,EEPROM的设备地址是0XA1。
MSB:最高位;LSB:最低位;这里的24C02的总容量是256,所以发送存储地址的时候,不用分高8位和低8位来发送。
产生起始信号;
发送从设备地址;
等待从设备应答;
发送写入数据的目的地址;
等待从设备应答;
发送要写入的数据;
等待从设备应答;
产生停止信号;
//在AT24CXX指定地址写入一个数据
void AT24CXX_WriteOneByte(u16 WriteAddr,u8 DataToWrite)
{
IIC_Start(); //产生起始信号
IIC_Send_Byte(0XA0); //发送器件地址0XA0,写数据
IIC_Wait_Ack(); //等待应答
IIC_Send_Byte(WriteAddr); //发送写入数据的目的地址
IIC_Wait_Ack(); //等待应答
IIC_Send_Byte(DataToWrite); //发送要写入的数据
IIC_Wait_Ack(); //等待应答
IIC_Stop(); //产生停止信号
delay_ms(10);
}
//在AT24C02里面的指定地址开始写入指定个数的数据
//WriteAddr :开始写入的地址 对24c02为 0~255
//pBuffer :数据数组首地址
//NumToWrite:要写入数据的个数
void AT24CXX_Write(u16 WriteAddr,u8 *pBuffer,u16 NumToWrite)
{
while(NumToWrite--)
{
AT24CXX_WriteOneByte(WriteAddr,*pBuffer);
WriteAddr++;
pBuffer++;
}
}
2.4 编写在24C02指定地址读取数据函数
产生起始信号;
发送写从设备地址(0XA0);
等待从设备应答;
发送读取数据的目的地址;
等待从设备应答;
产生起始信号;
发送读从设备地址(0XA1);
等待从设备应答;
读取数据;
产生停止信号;
//在24C02指定地址读出一个数据
//ReadAddr:开始读数的地址
//返回值 :读到的数据
u8 AT24CXX_ReadOneByte(u16 ReadAddr)
{
u8 temp=0; //接收数据变量
IIC_Start(); //产生起始信号
IIC_Send_Byte(0XA0); //发送器件地址0XA0,写数据
IIC_Wait_Ack(); //等待应答
IIC_Send_Byte(ReadAddr); //发送读取数据地址
IIC_Wait_Ack(); //等待应答
IIC_Start(); //产生起始信号
IIC_Send_Byte(0XA1); //进入接收模式
IIC_Wait_Ack(); //等待应答
temp=IIC_Read_Byte(0); //接收数据
IIC_Stop(); //产生一个停止条件
return temp; //返回读取的结果
}
//在AT24C02里面的指定地址开始读出指定个数的数据
//ReadAddr :开始读出的地址 对24c02为0~255
//pBuffer :数据数组首地址
//NumToRead:要读出数据的个数
void AT24CXX_Read(u16 ReadAddr,u8 *pBuffer,u16 NumToRead)
{
while(NumToRead)
{
*pBuffer++ = AT24CXX_ReadOneByte(ReadAddr++);
NumToRead--;
}
}
2.5 main.c函数
const u8 TEXT_Buffer[]={"Explorer STM32F4 IIC TEST"}; //要写入到24c02的字符串数组
#define SIZE sizeof(TEXT_Buffer) //写入的字符串数组大小
int main(void)
{
u8 key;
u16 i;
u8 datatemp[SIZE]; / /接收读取数据的数组
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
delay_init(168); //初始化延时函数
uart_init(115200); //初始化串口波特率为115200
LED_Init(); //初始化LED
KEY_Init(); //按键初始化
AT24CXX_Init(); //IIC初始化
AT24CXX_Write(0,(u8*)TEXT_Buffer,SIZE); //写入一个数组数据
AT24CXX_Read(0,datatemp,SIZE); //读取刚刚写入的数据
for(i = 0;i < SIZE;i++){
printf("%c",datatemp[i]); //打印接收到的数据
if(i == SIZE-1){
printf("\r\n");
}
}
while(1){}
}
3. 实验结果
读出了刚刚写入24C02的数据。