STM32快速读写AT24C16 代码 模拟I2C

本帖只适用AT24C16及以下的芯片,AT24C32及以上的芯片读写方式不一样,故不适用!!!

如果你的代码可以正常读写24C01/02,直接拿来读取24C16是可以的,但是只能读取256字节。

AT24C16与AT24C01/02/04/08 不同,它引脚的A2,A1,A0是无效的,也就是它没有自己独立的地址,总线上只能挂一个AT24C16设备。

AT24C16总共2048字节,分为128页,每页16字节,地址范围是0~2047。

128页只需要7位地址,分为高3位和低4位,高3位在设备地址中,低4位在字节地址中。

设备地址:1010+页地址高3位+读写方向(1:读  0:写)

字节地址:页地址高4位+4位页内偏移地址

例如读写地址:1864 ,首先计算该地址是多少页的多少个字节,1864/16=116(0x74)页,1864%16=8(0x08),即116页的第8个字节

其中页地址0x74=0 1 1 1 0 1 0 0,最高位忽略,分为D6、D5、D4(高3位)和D3~D0(低4位)两个部分 。

可以计算出 设备地址和字节地址:

设备地址:1010+111+0/1  (AT24C16设备地址高4位固定为1010)

扫描二维码关注公众号,回复: 4866808 查看本文章

字节地址:0100+1000(高4位是页地址低4位,低4位是页内偏移地址,即0x08)

最后,根据标准I2C读写时序来对这个地址进行读写即可!

模拟I2C.c

#include "myiic.h"
#include "delay.h"

#if SoftDelay
__asm void delay_us(u32 usec)  
{                    
 ALIGN
	PUSH.W {r1}		//2时钟周期
	MOV r1,#18		//1时钟周期
	MUL r0,r1		//1时钟周期
	SUB r0,#3		//1时钟周期
loop
	SUBS r0,#1			//1时钟周期
	BNE loop			//如果跳转则为3个周期,不跳则只有1个周期
	POP {r1}			//2时钟周期
	BX lr				//3个时钟周期
						//总共所用周期为(usec*4)-4,此处减4主要用于抵消调用此函数的消耗时钟周期(传参1时钟,BLX跳转3时钟)
  //本函数内总共所用周期为usec*(freq/4)-2 +9,调用此函数的消耗5个时钟周期(传参2时钟,BLX跳转3时钟)
} 
#endif

//初始化IIC
void IIC_Init(void)
{					     
	GPIO_InitTypeDef GPIO_InitStructure;
	RCC_APB2PeriphClockCmd(IIC_CLOCK, ENABLE );		   
	GPIO_InitStructure.GPIO_Pin = SCL_PIN|SDA_PIN;    //引脚
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ;   //推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(SCL_PORT, &GPIO_InitStructure);
 
	IIC_SCL=1;
	IIC_SDA=1;//拉高SDA和SCL

}
//产生IIC起始信号
void IIC_Start(void)
{
	SDA_OUT();     //sda线输出
	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总线,准备发送或接收数据 
}	  
//产生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);							   	
}
//等待应答信号到来
//返回值: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)
	{
		ucErrTime++;
		if(ucErrTime>250)
		{
			IIC_Stop();
			return 1;
		}
	}
	IIC_SCL=0;//时钟输出0 	   
	return 0;  
} 
//产生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;
}
//不产生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;
}					 				     
//IIC发送一个字节
//返回从机有无应答
//1,有应答
//0,无应答			  
void IIC_Send_Byte(u8 txd)
{                        
    u8 t;   
	SDA_OUT(); 	    
    IIC_SCL=0;//拉低时钟开始数据传输
    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);
    }	 
} 	    
//读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;
}



























i2c.h

#ifndef __MYIIC_H
#define __MYIIC_H
#include "stm32f10x.h"
//位段定义 方便后面直接操作IO口   Reg:寄存器地址  Bit:该寄存器的第多少位
#define BITBAND_REG(Reg,Bit) (*((uint32_t volatile*)(0x42000000u + (((uint32_t)&(Reg) - (uint32_t)0x40000000u)<<5) + (((uint32_t)(Bit))<<2))))
#define SoftDelay 0  //是否使用软件延时


//IO口定义 移植需要修改
#define SCL_PORT GPIOC
#define SDA_PORT GPIOC
#define SCL_PIN  GPIO_Pin_12
#define SDA_PIN  GPIO_Pin_11
//I2C 时钟 移植需要修改
#define IIC_CLOCK RCC_APB2Periph_GPIOC

//IO方向设置  移植需要修改
#define SDA_IN()  {SDA_PORT->CRH&=0XFFFF0FFF;SDA_PORT->CRH|=8<<12;} // SDA配置为输入
#define SDA_OUT() {SDA_PORT->CRH&=0XFFFF0FFF;SDA_PORT->CRH|=3<<12;} // SDA配置为输出

//IO操作函数	移植需要修改
#define IIC_SCL    BITBAND_REG(SCL_PORT->ODR,12) //SCL输出=?
#define IIC_SDA    BITBAND_REG(SDA_PORT->ODR,11)  //SDA输出=?	 
#define READ_SDA   (SDA_PORT->IDR&(1<<11))  //读SDA脚 

//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(unsigned char ack);//IIC读取一个字节
u8 IIC_Wait_Ack(void); 				//IIC等待ACK信号
void IIC_Ack(void);					//IIC发送ACK信号
void IIC_NAck(void);				//IIC不发送ACK信号

void IIC_Write_One_Byte(u8 daddr,u8 addr,u8 data);
u8 IIC_Read_One_Byte(u8 daddr,u8 addr);
#endif
















AT24C16.c

#include "24cxx.h"
#include "delay.h"
#include "stdio.h"

//初始化IIC接口
void AT24CXX_Init(void)
{
    IIC_Init();
}

#if EE_TYPE<=AT24C08  //24C01/02/08
//在AT24CXX指定地址读出一个数据
//ReadAddr:开始读数的地址
//返回值  :读到的数据
u8 AT24CXX_ReadOneByte(u16 ReadAddr)
{
    u8 temp=0;
    IIC_Start();
    if(EE_TYPE>AT24C16)
    {
        IIC_Send_Byte(0XA0);	   //发送写命令
        IIC_Wait_Ack();
        IIC_Send_Byte(ReadAddr>>8);//发送高地址
        IIC_Wait_Ack();
    } else IIC_Send_Byte(0XA0+((ReadAddr/256)<<1));   //发送器件地址0XA0,写数据

    IIC_Wait_Ack();
    IIC_Send_Byte(ReadAddr%256);   //发送低地址
    IIC_Wait_Ack();
    IIC_Start();
    IIC_Send_Byte(0XA1);//进入接收模式
    IIC_Wait_Ack();
    temp=IIC_Read_Byte(0);
    IIC_Stop();//产生一个停止条件
    return temp;
}
//在AT24CXX指定地址写入一个数据
//WriteAddr  :写入数据的目的地址
//DataToWrite:要写入的数据
void AT24CXX_WriteOneByte(u16 WriteAddr,u8 DataToWrite)
{
    IIC_Start();
    if(EE_TYPE>AT24C16)
    {
        IIC_Send_Byte(0XA0);//发送设备地址
        IIC_Wait_Ack();
        IIC_Send_Byte(WriteAddr>>8);//发送字节高地址
    } else
    {
        IIC_Send_Byte(0XA0+((WriteAddr/256)<<1));   //发送器件地址0XA0,写数据
    }
    IIC_Wait_Ack();
    IIC_Send_Byte(WriteAddr%256);   //发送字节低地址
    IIC_Wait_Ack();
    IIC_Send_Byte(DataToWrite);     //发送要写入的数据
    IIC_Wait_Ack();
    IIC_Stop();//产生一个停止条件
    delay_ms(10);
}
#else  //24C16
/*****************************************************************
*函数名: AT24CXX_ReadOneByte(u16 ReadAddr)
*功能:AT24CXX 读指定地址的一个字节   AT24C16使用
*调用:底层I2C读写函数
*被调用:外部调用
*形参:
			ReadAddr:要读取的地址
*返回值:返回读取的数据
*其他:每次读就启动一次I2C时序
*****************************************************************/
u8 AT24CXX_ReadOneByte(u16 ReadAddr)
{
    unsigned char Page=0,WordAddress=0,DeviceAddress=0xA0;
    u8 temp=0;
    Page=ReadAddr/AT24CXX_Page_Size;
    WordAddress=(ReadAddr%AT24CXX_Page_Size) & 0x0F;
    DeviceAddress |= (((Page<<1) & 0xE0)>>4);//High 3 bits
    WordAddress |= (Page & 0x0F)<<4;//Low 4 bits
    IIC_Start();
    IIC_Send_Byte(DeviceAddress&0xFE);//发送设备地址+写方向
    IIC_Wait_Ack();
    IIC_Send_Byte(WordAddress);//发送字节地址
    IIC_Wait_Ack();
    IIC_Start();                //起始信号
    IIC_Send_Byte(DeviceAddress|0x01);//发送设备地址+读方向
    IIC_Wait_Ack();
    temp=IIC_Read_Byte(0);
    IIC_Stop();//产生一个停止条件
    return temp;
}
/*****************************************************************
*函数名: AT24CXX_WriteOneByte(u16 WriteAddr,u8 DataToWrite)
*功能:AT24CXX 向指定地址写入一个字节   AT24C16使用
*调用:
*被调用:外部调用
*形参:
			WriteAddr:要写入的地址
			DataToWrite:写入的数据
*返回值:无
*其他:每次写就启动一次I2C时序
*****************************************************************/
void AT24CXX_WriteOneByte(u16 WriteAddr,u8 DataToWrite)
{
    unsigned char Page=0,WordAddress=0,DeviceAddress=0xA0;
    Page=WriteAddr/AT24CXX_Page_Size;
    WordAddress=(WriteAddr%AT24CXX_Page_Size) & 0x0F;
    DeviceAddress |= (((Page<<1) & 0xE0)>>4);//High 3 bits
    WordAddress |= (Page & 0x0F)<<4;//Low 4 bits
#if DEBUG	> 0
    printf("Page:%x\r\n",Page);
    printf("WordAddress:%x\r\n",WordAddress);
    printf("DeviveAddress:%x\r\n",DeviceAddress);
#endif
    IIC_Start();
    IIC_Send_Byte(DeviceAddress);//发送设备地址
    IIC_Wait_Ack();
    IIC_Send_Byte(WordAddress);//发送字节地址
    IIC_Wait_Ack();
    IIC_Send_Byte(DataToWrite);     //发送要写入的数据
    IIC_Wait_Ack();
    IIC_Stop();//产生一个停止条件
    delay_ms(10);
}
#endif



/*---------------读写方式选择-----------------*/
#if  QuickWR == 0
//在AT24CXX里面的指定地址开始读出指定个数的数据
//ReadAddr :开始读出的地址 对24c02为0~255
//pBuffer  :数据数组首地址
//NumToRead:要读出数据的个数
void AT24CXX_Read(u16 ReadAddr,u8 *pBuffer,u16 NumToRead)
{
    while(NumToRead)
    {
        *pBuffer++=AT24CXX_ReadOneByte(ReadAddr++);
        NumToRead--;
    }
}
//在AT24CXX里面的指定地址开始写入指定个数的数据
//WriteAddr :开始写入的地址 对24c02为0~255
//pBuffer   :数据数组首地址
//NumToWrite:要写入数据的个数
void AT24CXX_Write(u16 WriteAddr,u8 *pBuffer,u16 NumToWrite)
{
    while(NumToWrite--)
    {
        AT24CXX_WriteOneByte(WriteAddr,*pBuffer);
        WriteAddr++;
        pBuffer++;
    }
}
#else  //快速读写方式
/*****************************************************************
*函数名: AT24CXX_Write_Bytes(u8 *pBuffer,u16 WriteAddress,u8 Len)
*功能: 页写函数 最多写入一页(16字节)
*调用: 底层I2C写函数
*被调用:外部调用
*形参:
      *pBuffer:指向写入缓存区
			WriteAddr:要写入的地址
			Len:写入数据长度
*返回值:无
*其他:启动一次I2C时序最多写入一页(16Bytes)数据,明显快于按字节写入
*****************************************************************/
void AT24CXX_Write_Bytes(u8 *pBuffer,u16 WriteAddress,u8 Len)
{
    unsigned char Page=0,WordAddress=0,DeviceAddress=0xA0;
    u8 i=0;
    Page=WriteAddress/AT24CXX_Page_Size;
    WordAddress=(WriteAddress%AT24CXX_Page_Size) & 0x0F;
    DeviceAddress |= (((Page<<1) & 0xE0)>>4);//High 3 bits
    WordAddress |= (Page & 0x0F)<<4;//Low 4 bits
    IIC_Start();
    IIC_Send_Byte(DeviceAddress);//发送设备地址
    IIC_Wait_Ack();
    IIC_Send_Byte(WordAddress);//发送字节地址
    IIC_Wait_Ack();
    for(i=0; i<Len; i++)
    {
        IIC_Send_Byte(*pBuffer++);//发送字节地址
        IIC_Wait_Ack();
    }
    IIC_Stop();//产生一个停止条件
    delay_ms(10);
}
/*****************************************************************
*函数名: AT24CXX_Write(u16 WriteAddr,u8 *pBuffer,u16 NumToWrite)
*功能:AT24CXX 快速写入不定量字节
*调用:
*被调用:外部调用
*形参:
			WriteAddr:要写入的首地址
			*pBuffer:指向写入缓存区
			NumToWrite:写入的字节数
*返回值:无
*其他:快速模式 不用每次写一个字节就启动一次I2C时序
*****************************************************************/
void AT24CXX_Write(u16 WriteAddr,u8 *pBuffer,u16 NumToWrite)
{
    unsigned char NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0;
    Addr=WriteAddr%AT24CXX_Page_Size;//地址正好是16字节对齐
    count=AT24CXX_Page_Size-Addr;//不对齐字节数
    NumOfPage=NumToWrite/AT24CXX_Page_Size;//需要写入多少页
    NumOfSingle=NumToWrite%AT24CXX_Page_Size;//剩余需写入的字节数
    if(0==Addr)//如果地址对齐
    {
        if(NumToWrite<=AT24CXX_Page_Size)//写入字节<=1页
        {
            AT24CXX_Write_Bytes(pBuffer,WriteAddr,NumToWrite);
        }
        else
        {
            while(NumOfPage--)//按页写入
            {
                AT24CXX_Write_Bytes(pBuffer,WriteAddr,AT24CXX_Page_Size);
                pBuffer+=AT24CXX_Page_Size;
                WriteAddr+=AT24CXX_Page_Size;
            }
            if(NumOfSingle != 0)//如果还剩下字节
            {
                AT24CXX_Write_Bytes(pBuffer,WriteAddr,NumOfSingle);//把剩下的字节写入
            }
        }
    }
    else//地址不对齐
    {
        if(NumToWrite<=count)// 要写入的字节数<=count
        {
            AT24CXX_Write_Bytes(pBuffer,WriteAddr,NumToWrite);//写入实际字节数
        }
        else//要写入字节数大于count
        {
            AT24CXX_Write_Bytes(pBuffer,WriteAddr,count);//现将count个字节写入 写入后 地址刚好对齐
            NumToWrite-=count;//计算剩余字节数
            pBuffer+=count;//写入内容偏移count
            WriteAddr+=count;//写入地址偏移count

            NumOfPage=NumToWrite/AT24CXX_Page_Size;//需要写入多少页
            NumOfSingle=NumToWrite%AT24CXX_Page_Size;//剩余需写入的字节数

            while(NumOfPage--)//先按页写入
            {
                AT24CXX_Write_Bytes(pBuffer,WriteAddr,AT24CXX_Page_Size);
                pBuffer+=AT24CXX_Page_Size;
                WriteAddr+=AT24CXX_Page_Size;
            }
            if(NumOfSingle != 0)//还剩余字节
            {
                AT24CXX_Write_Bytes(pBuffer,WriteAddr,NumOfSingle);//把剩下的字节写入
            }
        }
    }
}

void AT24CXX_Read(u16 ReadAddr,u8 *pBuffer,u16 NumToRead)
{
    unsigned char Page=0,WordAddress=0,DeviceAddress=0x50;
    Page=ReadAddr/AT24CXX_Page_Size;
    WordAddress=(ReadAddr%AT24CXX_Page_Size) & 0x0F;
    DeviceAddress |= (((Page<<1) & 0xE0)>>4);//High 3 bits
    WordAddress |= (Page & 0x0F)<<4;//Low 4 bits
    while(NumToRead)
    {
        *pBuffer++=AT24CXX_ReadOneByte(ReadAddr++);
        NumToRead--;
    }
}

#endif  //快速读写方式

//检查AT24CXX是否正常
//这里用了24XX的最后一个地址(255)来存储标志字.
//如果用其他24C系列,这个地址要修改
//返回1:检测失败
//返回0:检测成功
u8 AT24CXX_Check(void)
{
    u8 temp;
    temp=AT24CXX_ReadOneByte(255);//避免每次开机都写AT24CXX
    if(temp==0X55)return 0;
    else//排除第一次初始化的情况
    {
        AT24CXX_WriteOneByte(255,0X55);
        temp=AT24CXX_ReadOneByte(255);
        if(temp==0X55)return 0;
    }
    return 1;
}


AT24C16.h

#ifndef __24CXX_H
#define __24CXX_H
#include "myiic.h"
//Mini STM32开发板
//24CXX驱动函数(适合24C01~24C16,24C32~256未经过测试!有待验证!)
//正点原子@ALIENTEK
//2010/6/10
//V1.2
#define AT24C01		127
#define AT24C02		255
#define AT24C04		511
#define AT24C08		1023
#define AT24C16		2047
#define AT24C32		4095
#define AT24C64	    8191
#define AT24C128	16383
#define AT24C256	32767


/*----------------EEPROM相关配置--------------------*/
#define EE_TYPE AT24C16  //EEPROM类型
#define AT24CXX_Page_Size 16 //AT24C16每页有16个字节
#define DEBUG   0  //串口调试开关
#define QuickWR 0 //快速读写开关


u8 AT24CXX_ReadOneByte(u16 ReadAddr);							//指定地址读取一个字节
void AT24CXX_WriteOneByte(u16 WriteAddr,u8 DataToWrite);		//指定地址写入一个字节
void AT24CXX_Write(u16 WriteAddr,u8 *pBuffer,u16 NumToWrite);	//从指定地址开始写入指定长度的数据
void AT24CXX_Read(u16 ReadAddr,u8 *pBuffer,u16 NumToRead);   	//从指定地址开始读出指定长度的数据

u8 AT24CXX_Check(void);  //检查器件
void AT24CXX_Init(void); //初始化IIC
#endif
















main.c

#include "led.h"
#include "delay.h"
#include "sys.h"
#include "usart.h"
#include "24cxx.h" 
#include "myiic.h"
#include "stdio.h"
//要写入到24c16的字符串数组
const u8 TEXT_Buffer[]={"C++ is the best language!"};//要写入的内容
#define SIZE sizeof(TEXT_Buffer)	//写入内容的大小
#define ADDRESS 2020	//读写地址
 int main(void)
 { 
	u8 datatemp[SIZE];
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);// 设置中断优先级分组2
	delay_init();	    	 //延时函数初始化	  
	uart_init(9600);	 	//串口初始化为9600
	LED_Init();		  		//初始化与LED连接的硬件接口		 	
	AT24CXX_Init();			//IIC初始化 	
 	while(AT24CXX_Check())//检测不到24c16
	{		
		delay_ms(500);
		LED0=!LED0;//DS0闪烁
	}	  
	while(1)
	{			
			   		
			AT24CXX_Write(ADDRESS,(u8*)TEXT_Buffer,SIZE);
			printf("Write:%s\r\n",TEXT_Buffer); //显示写入内容		
		    delay_ms(1000);		
			AT24CXX_Read(ADDRESS,datatemp,SIZE);
			printf("Read:%s\r\n",datatemp);//显示读取内容 						
	}
}

 演示结果:

注意:上面的代码可以支持24C01/02 ,将AT24CXX.h中的宏定义EE_TYPE 改为AT24C01/02即可

猜你喜欢

转载自blog.csdn.net/qq_24835087/article/details/83745914
I2C