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

文章目录[隐藏]

原理

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 high
	delay_us(5);		
}

//等待应答信号,收到应答信号返回0,非应答返回1
u8 IIC_Wait_Ack(void)
{
	u8 timeout=0;
	
	SDA_IN();//SDA设置为输入模式  	 
  
	IIC_SCL=1;//时钟线输出1
	delay_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;
		else
			IIC_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;//左移,最低位填充0
		if(READ_SDA)
		{
			receive++;//如果收到1,最低位置1  
		}			 
		delay_us(1); 
  }
	
  if(ack)
		IIC_Ack();//应答
	else
		IIC_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个字节,往最后一个地址写入一个字节0xFF
	u8 temp = AT24C02_ReadOneByte(255);	  
	if(temp==0XFF)
		return 0;
	else
		return 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();//初始化AT24C02
	
	while(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);
	}
}

版权声明:本文为CSDN博主「神奇的大喵」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_37443229/article/details/121499067

生成海报
点赞 0

神奇的大喵

我还没有学会写个人说明!

暂无评论

发表评论

相关推荐

STM32F2————配置时钟延迟不准的问题

STM32F2配置时钟问题 笔者在本科毕业设计使用STM32F207芯片,但是在配置时钟时出现了问题。 问题 我按照F1写代码的延时函数放在F2竟然不准了 换个办法 使用Systick时钟也是不准,原因是笔者代

为什么重写printf函数没有用?

以前在网上找了无数方法去重写printf函数,但发现都没效果,今天偶然发现重写printf函数可以了,原因是以前没有勾选微库(Use MicroLlB)! 这里