当前位置: 代码迷 >> 综合 >> STM32基础(12)IIC读写AT24C02(EEPROM)
  详细解决方案

STM32基础(12)IIC读写AT24C02(EEPROM)

热度:89   发布时间:2023-11-15 05:39:03.0

原理

IIC介绍

I2C(Inter-Integrated Circuit)总线是由 PHILIPS 公司开发的两线式串行总线,用于连接微控制器及其外围设备。它是同步通信的一种特殊形式,由于管脚少、硬件实现简单、可扩展性强等特点,被广泛的使用在各大集成芯片内。

主机:启动数据传送并产生时钟信号的设备

从机:被主机寻址的器件

主模式:用 I2CNDAT 支持自动字节计数的模式;位 I2CRM,I2CSTT,I2CSTP控制数据的接收和发送

从模式:发送和接收操作都是由 I2C 模块自动控制的

发送器:发送数据到总线的器件

接收器:从总线接收数据的器件

IIC协议层:

  1. 数据有效性规定:时钟信号为高电平期间,数据线上的数据必须保持稳定,IIC发送数据以字节为单位,从高位开始发送

  1. 起始停止信号:SCL 线为高电平期间,SDA 线由高向低电平的变化表示起始信号,SDA 线由低向高电平的变化表示终止信号。

  1. 应答信号:响应包括“应答(ACK)”和“非应答(NACK)”两种信号。当设备(无论主从机)接收到 I2C 传输的一个字节数据或地址后,若希望对方继续发送数据,则需要向对方发送“应答(ACK)”信号即特定的低电平脉冲,发送方会继续发送下一个数据;若接收端希望结束数据传输,则向对方发送“非应答(NACK)”信号即特定的高电平脉冲,发送方接收到该信号后会产生一个停止信号,结束信号传输。

  1. 总线的寻址方式:从机的地址由固定部分4位和可编程部分3位组成,D7~D1 位组成从机的地址。D0 位是数据传送方向位,为“0”时表示主机向从机写数据,为“1”时表示主机由从机读数据。

  1. 数据传输:有阴影部分表示数据由主机向从机传送,无阴影部分则表示数据由从机向主机传送。A 表示应答,A 非表示非应答(高电平)。S 表示起始信号,P表示终止信号。
    1. 主机向从机发送数据

    1. 主机向从机读数据

    1. 改变方向


AT24C02芯片

AT24C02是一个2K串行CMOS,AT24C02(EEPROM)芯片具有 C 通信接口,芯片内保存的数据在掉电情况下都不丢失,所以

通常用于存放一些比较重要的数据等。

A0、A1、A2:地址输入引脚,开发板已接地,即地址为000

SDA:串行数据输入输出

SCL:串行时钟输入

WP:写保护,1只读0读写,开发板已接地

GND:地

VCC:正电源

步骤

  1. 编写USART驱动程序(STM32F1系列通用)
    1. 将固件库文件stm32f10x_usart.c添加至工程
    2. 编写头文件:函数声明
    3. 编写驱动文件:
      1. 初始化函数:
        1. 使能端口时钟,串口时钟:RCC_APB2PeriphClockCmd
        2. 配置GPIO口Tx、Rx引脚:GPIO_InitTypeDef
        3. 配置串口:波特率、字长、停止位、校验位
        4. 使能串口:USART_Cmd(USART1, ENABLE)
      2. fputc函数覆写:printf中循环调用fputc
  2. 编写IIC驱动程序
    1. 编写头文件:
      1. IIC_SCL端口、引脚、时钟定义
      2. IIC_SDA端口、引脚、时钟定义
      3. IIC_SCL位带操作定义:输出
      4. IIC_SDA位带操作定义:输入输出
      5. 函数声明
    2. 编写驱动文件:
      1. IIC初始化函数:开启端口时钟,配置SCL、SDA为推挽输出,SCL、SDA电平上拉
      2. SDA方向IN函数:工作模式为推挽输出
      3. SDA方向OUT函数:工作模式为上拉输入
      4. 产生起始信号函数
      5. 产生停止信号函数
      6. 产生应答信号函数
      7. 产生非应答信号函数
      8. 等待应答信号函数
      9. 发送一个字节函数
      10. 读取一个字节函数
  3. 编写EEPROM驱动程序
    1. 编写头文件:包含IIC头文件,函数声明
    2. 编写驱动文件:
      1. AT24C02初始化函数:即IIC初始化
      2. 从指定位置读取一个字节函数
      3. 向指定位置写一个字节函数
      4. 从指定位置读取N个字节函数
      5. 向指定位置写N个字节函数
  4. 主函数:初始化串口,初始化滴答定时器,初始化AT24C02,检测AT24C02,读写AT24C02

代码

//usart.h#ifndef _usart_H
#define _usart_H#include "system.h"void USART1_Init(u32 bound);#endif//usart.c#include "usart.h"
#include "stdio.h"//printf重定向,printf中循环调用fputc
int fputc(int ch, FILE* p)
{USART_SendData(USART1, (u8)ch);while(USART_GetFlagStatus(USART1, USART_FLAG_TXE)==RESET);//发送数据寄存器为空,已传至发送移位寄存器,但不一定发送完成return ch;
}void USART1_Init(u32 bound)
{GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;//开启端口时钟、串口时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);//配置Tx引脚GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//复用推挽输出GPIO_Init(GPIOA, &GPIO_InitStructure);//配置Rx引脚GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入GPIO_Init(GPIOA, &GPIO_InitStructure);//配置串口USART_InitStructure.USART_BaudRate = bound;//波特率USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长USART_InitStructure.USART_StopBits = USART_StopBits_1;//停止位USART_InitStructure.USART_Parity = USART_Parity_No;//校验位USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//硬件流控制USART_InitStructure.USART_Mode = USART_Mode_Rx|USART_Mode_Tx;//工作模式USART_Init(USART1, &USART_InitStructure);USART_Cmd(USART1, ENABLE);//使能串口}

//iic.h
#ifndef _iic_H
#define _iic_H#include "system.h"/*  IIC_SCL时钟端口、引脚定义 */
#define IIC_SCL_PORT 			GPIOB   
#define IIC_SCL_PIN 			(GPIO_Pin_6)
#define IIC_SCL_PORT_RCC		RCC_APB2Periph_GPIOB/*  IIC_SDA时钟端口、引脚定义 */
#define IIC_SDA_PORT 			GPIOB  
#define IIC_SDA_PIN 			(GPIO_Pin_7)
#define IIC_SDA_PORT_RCC		RCC_APB2Periph_GPIOB//IO位带操作	 
#define IIC_SCL    PBout(6) //SCL
#define IIC_SDA    PBout(7) //SDA,GPIOx_ODR寄存器 
#define READ_SDA   PBin(7)  //输入SDA,GPIOx_IDR寄存器//IIC操作函数
void IIC_Init(void);         //初始化IIC的IO口				 
void IIC_Start(void);				//发送IIC开始信号
void IIC_Stop(void);	  			//发送IIC停止信号
void IIC_Send_Byte(u8 txd);			//IIC发送一个字节
u8 IIC_Read_Byte(u8 ack);			//IIC读取一个字节
u8 IIC_Wait_Ack(void); 				//IIC等待ACK信号
void IIC_Ack(void);					//IIC发送ACK信号
void IIC_NAck(void);				//IIC不发送ACK信号#endif//iic.c
#include "iic.h"
#include "SysTick.h"void IIC_Init(void)
{GPIO_InitTypeDef  GPIO_InitStructure;//开启端口时钟RCC_APB2PeriphClockCmd(IIC_SCL_PORT_RCC|IIC_SDA_PORT_RCC,ENABLE);//配置SCL、SDA引脚GPIO_InitStructure.GPIO_Pin=IIC_SCL_PIN;GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;//推挽输出GPIO_Init(IIC_SCL_PORT,&GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin=IIC_SDA_PIN;GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;//推挽输出GPIO_Init(IIC_SDA_PORT,&GPIO_InitStructure);IIC_SCL=1;	IIC_SDA=1;	//所有设备空闲,总线拉高电平
}//切换SDA引脚工作模式为推挽输出
void SDA_OUT(void)
{GPIO_InitTypeDef  GPIO_InitStructure;GPIO_InitStructure.GPIO_Pin=IIC_SDA_PIN;GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;//推挽输出GPIO_Init(IIC_SDA_PORT,&GPIO_InitStructure);
}//切换SDA引脚工作模式为上拉输入
void SDA_IN(void)
{GPIO_InitTypeDef  GPIO_InitStructure;GPIO_InitStructure.GPIO_Pin=IIC_SDA_PIN;GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;//上拉输入GPIO_Init(IIC_SDA_PORT,&GPIO_InitStructure);
}//产生起始信号
void IIC_Start(void)
{SDA_OUT();//SDA线输出模式IIC_SDA=1;	  	  IIC_SCL=1;delay_us(5);IIC_SDA=0;//START:when CLK is high,DATA change form high to low delay_us(5);IIC_SCL=0;//钳住I2C总线,准备发送或接收数据 
}	//产生停止信号
void IIC_Stop(void)
{SDA_OUT();//SDA线输出模式IIC_SDA=0;	  	  IIC_SCL=1;delay_us(5);IIC_SDA=1;//STOP:when CLK is high,DATA change form low to highdelay_us(5);		
}//等待应答信号,收到应答信号返回0,非应答返回1
u8 IIC_Wait_Ack(void)
{u8 timeout=0;SDA_IN();//SDA设置为输入模式  	 IIC_SCL=1;//时钟线输出1delay_us(1);	for(READ_SDA=1; timeout<250; timeout++)//等待应答信号,即低电平0{if(READ_SDA==0){IIC_SCL=0;//时钟线输出0	   return 0;}}IIC_Stop();//超时未收到应答信号,即收到非应答信号,发送方则发送停止信号return 1;
} //产生应答信号
void IIC_Ack(void)
{ SDA_OUT();//SDA设置为输出模式IIC_SCL=0;IIC_SDA=0;//发送低电平delay_us(2);IIC_SCL=1;delay_us(5);IIC_SCL=0;
}//不产生应答	    
void IIC_NAck(void)
{SDA_OUT();//SDA设置为输出模式IIC_SCL=0;IIC_SDA=1;//发送高电平delay_us(2);IIC_SCL=1;delay_us(5);IIC_SCL=0;
}	//发送一个字节(IIC发送数据以字节为单位,从高位开始发送)
void IIC_Send_Byte(u8 txd)
{                        SDA_OUT();//SDA设置为输出模式IIC_SCL=0;//拉低时钟开始数据传输for(u8 i=0; i<8; i++){              if(txd&0x80) //比较最高位,结果1000 0000(非0值为真)或 0000 0000(0)IIC_SDA=1;elseIIC_SDA=0;txd<<=1;//左移 	  delay_us(2);  IIC_SCL=1;//拉高时钟线delay_us(2); IIC_SCL=0;//拉低时钟线	delay_us(2);}	 
} //读取一个字节,参数传入1为应答,0为非应答
u8 IIC_Read_Byte(u8 ack)
{u8 receive=0;//存放读取的字节SDA_IN();//SDA设置为输入模式for(u8 i=0; i<8; i++){IIC_SCL=0; delay_us(2);IIC_SCL=1;receive<<=1;//左移,最低位填充0if(READ_SDA){receive++;//如果收到1,最低位置1  }			 delay_us(1); }if(ack)IIC_Ack();//应答elseIIC_NAck(); //非应答  return receive;
}
//eeprom.h
#ifndef _eeprom_H
#define _eeprom_H#include "system.h"
#include "iic.h"void AT24C02_Init(void); //初始化IIC
u8 AT24C02_Check(void);  //检查器件是否可读写u8 AT24C02_ReadOneByte(u8 ReadAddr);							//指定地址读取一个字节
void AT24C02_WriteOneByte(u8 WriteAddr,u8 DataToWrite);		//指定地址写入一个字节
void AT24C02_Write(u8 WriteAddr,u8 *pBuffer,u8 NumToWrite);	//从指定地址开始写入指定长度的数据
void AT24C02_Read(u8 ReadAddr,u8 *pBuffer,u8 NumToRead);   	//从指定地址开始读出指定长度的数据#endif//eeprom.c
#include "eeprom.h"
#include "SysTick.h"//初始化
void AT24C02_Init(void)
{IIC_Init();//IIC初始化
}//检查AT24C02是否可读写
u8 AT24C02_Check(void)
{AT24C02_WriteOneByte(255,0XFF);//AT24C02存储容量为256个字节,往最后一个地址写入一个字节0xFFu8 temp = AT24C02_ReadOneByte(255);	  if(temp==0XFF)return 0;elsereturn 1;											  
}//从指定位置读一个字节
u8 AT24C02_ReadOneByte(u8 ReadAddr)
{				  u8 receive=0;IIC_Start(); IIC_Send_Byte(0XA0);	//总线寻址:向总线发送IIC设备地址0XA0,读写方向为0写数据	   IIC_Wait_Ack();	//等待应答,建立通信连接 IIC_Send_Byte(ReadAddr);	//向设备发送要操作的地址IIC_Wait_Ack();	    IIC_Start();	//读写方向改变时,需要重新发送起始信号  	 	   IIC_Send_Byte(0XA1);	//读写方向为1读数据			   IIC_Wait_Ack();receive=IIC_Read_Byte(0);	//接收一个字节,发送非应答信号	   IIC_Stop();    return receive;
}//向指定位置写一个字节
void AT24C02_WriteOneByte(u8 WriteAddr,u8 DataToWrite)
{				   	  	    																 IIC_Start();	IIC_Send_Byte(0XA0);   //总线寻址:向总线发送IIC设备地址0XA0,读写方向为0写数据IIC_Wait_Ack();	//等待应答,建立通信连接	   IIC_Send_Byte(WriteAddr);	//向设备发送要操作的地址IIC_Wait_Ack(); 	 										  		   IIC_Send_Byte(DataToWrite);     //发送一个字节							   IIC_Wait_Ack();  		    	   IIC_Stop();  
}//从指定位置读NumToRead个字节
void AT24C02_Read(u8 ReadAddr,u8 *pBuffer,u8 NumToRead)
{while(NumToRead){*pBuffer++=AT24C02_ReadOneByte(ReadAddr++);	NumToRead--;}
} //向指定位置写NumToWrite个字节
void AT24C02_Write(u8 WriteAddr,u8 *pBuffer,u8 NumToWrite)
{while(NumToWrite--){AT24C02_WriteOneByte(WriteAddr,*pBuffer);WriteAddr++;pBuffer++;}
}
//main.c#include <stdlib.h> 
#include <stdio.h> 
#include "usart.h"
#include "Systick.h"
#include "eeprom.h"int main()
{u8 writedat;u8 readdat;USART1_Init(115200);//初始化串口SysTick_Init(72);//初始化滴答定时器AT24C02_Init();//初始化AT24C02while(AT24C02_Check())//判断AT24C02可否读写,阻塞{printf("AT24C02读写异常!\r\n");delay_ms(500);}printf("AT24C02读写正常!\r\n");while(1){writedat = rand();AT24C02_WriteOneByte(0,writedat);printf("地址0写入 = %d\r\n",writedat);delay_ms(1000);readdat = AT24C02_ReadOneByte(0);printf("地址0读取 = %d\r\n",readdat);delay_ms(1000);}
}