HAL库软件IIC读取AT24C02/AT24C32

    为了克服硬件IIC的缺点以及更好了了解IIC协议,下面让我来介绍一下HAL库环境下的软件IIC如何使用。

硬件:stm32f103RCT6、AT24C02

软件:cubemx、keil5、野火上位机

我们把程序分为三部分,第一部分是延时函数和软件IIC函数;第二部分是AT24C02读写函数;第三部分是main()测试函数。

delay函数:

delay.c:  

#include "stm32f1xx_hal.h"
#include "delay.h"

                      

void Delay_Us(uint16_t us){
	SysTick->LOAD = us*72;                 //运行频率为72MHZ就乘72,因为一个systick为 (1/运行频率)秒
	SysTick->VAL = 0x00;
	SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
	while(!(SysTick->CTRL&SysTick_CTRL_COUNTFLAG_Msk));
	SysTick->CTRL &=~ SysTick_CTRL_ENABLE_Msk;
}

void Delay_Ms(uint16_t ms){
	while(ms--){
		Delay_Us(1000);
	}
}





delay.h:   头文件

#ifndef DELAY_H
#define DELAY_H

#include "stdint.h"


void Delay_Us(uint16_t us);
void Delay_Ms(uint16_t ms);

#endif

软件IIC函数:

SCL我们选择推挽输出+上拉,默认输出高电平;

SDA我们选择开漏输出+上拉,默认输出高电平;因为SDA线既要用作输出,也要用作输入(从机应答信号),使用开漏模式则可以解决这个问题。当然我们也可以使SDA为推挽输出模式,但是这样每次SDA输出和输入模式转变时都需要重新初始化SDA的GPIO口,比较麻烦。

Sotf_IIC.c:

#include "Soft_IIC.h"
#include "delay.h"

/* iic起始信号,当SCL为高电平时,SDA从高电平变为低电平*/
void iic_start(void)
{
    /* 保持时钟线高电平,数据线产生下降沿 */
    IIC_SDA_H();
    IIC_SCL_H();
    iic_delay();
    IIC_SDA_L();
    iic_delay();

    /* 拉低时钟线,准备发送/接收数据 ,此时SCL和SDA都为低电平*/
    IIC_SCL_L();
    iic_delay();
}


/* iic停止信号,当SCL为高电平时,SDA从低电平变为高电平 */
void iic_stop(void)
{
    /* 保持时钟线高电平,数据线产生上升沿 */
    IIC_SDA_L();
    IIC_SCL_H();
    iic_delay();
    IIC_SDA_H();
    iic_delay();
	 //停止信号发送后传输结束,SDA和SCL都为高电平
}

/* 等待应答信号 */
/* return 0:fail 1:succeed*/
uint8_t iic_wait_ack (void)
{
    IIC_SDA_H();    /* 主机释放SDA线 */
    iic_delay();
    IIC_SCL_H();    /* 拉高SCL等待读取从机应答信号 */
    iic_delay();
    if (IIC_READ_SDA) /* SCL高电平读取SDA状态 */
    {
        iic_stop();     /* SDA高电平表示从机非应答 */
        return 0;
    }
		/* SDA低电平表示从机应答 */
    IIC_SCL_L();         /* SCL低电平表示结束应答检查 */
    iic_delay();
    return 1;
}

/* 应答信号 */
void iic_ack(void)
{
   IIC_SDA_L();         //拉低SDA
	 IIC_SCL_H();         //拉高SCL
	 iic_delay();
	 IIC_SCL_L();                //拉低SCL
	 IIC_SDA_H();         //拉高SDA,释放SDA线
	 iic_delay();
}

/* 非应答信号 */
void iic_nack(void)
{
    IIC_SCL_L();
    iic_delay();
    IIC_SDA_H();  /* 数据线为高电平,表示非应答 */
    iic_delay();
    IIC_SCL_H();
    iic_delay();
	  IIC_SCL_L();
	
}


/* 发送一个字节数据 */
void iic_send_byte(uint8_t data)
{
    for (uint8_t t=0; t<8; t++)
    {
        /* 高位先发 */
        IIC_SDA((data & 0x80) >> 7);
        iic_delay();
        /* 拉高时钟线,稳定数据接收 */
        IIC_SCL_H();
        iic_delay();
        
        IIC_SCL_L();
        data <<= 1;     /* 左移1位, 用于下一次发送 */
    }
    IIC_SDA_H();      /* 发送完成,主机释放SDA线 */ 
}

/* 读取1字节数据 */
/* ack:通知从机是否继续发送。
   0,停止发送;1,继续发送
*/
uint8_t iic_read_byte(uint8_t ack)
{ 
    uint8_t receive = 0 ;
    for (uint8_t t=0; t<8; t++)
    {
        /* 高位先输出,先收到的数据位要左移 */ 
        receive <<= 1;
        IIC_SCL_H();
        iic_delay();
        if (IIC_READ_SDA) 
        {
            receive++;
        }
        IIC_SCL_L();
        iic_delay();
    }
    /* 判断是否继续读取从机数据 */
    if ( !ack ) 
    {
        iic_nack();
    }
    else 
    {
        iic_ack();
    }
    return receive;
}


Sotf_IIC.h: 头文件

#ifndef _SOFT_IIC_H_
#define _SOFT_IIC_H_

#include "main.h"


#define IIC_DELAY_TIME 5   //延时时间根据芯片手册的时序设置,这里选择5us
#define  iic_delay()  Delay_Us(IIC_DELAY_TIME)


#define IIC_SCL_H()     HAL_GPIO_WritePin(IIC_SCL_GPIO_Port, IIC_SCL_Pin, GPIO_PIN_SET) 
#define IIC_SCL_L()     HAL_GPIO_WritePin(IIC_SCL_GPIO_Port, IIC_SCL_Pin, GPIO_PIN_RESET) 
#define IIC_SDA_H()     HAL_GPIO_WritePin(IIC_SDA_GPIO_Port, IIC_SDA_Pin, GPIO_PIN_SET) 
#define IIC_SDA_L()     HAL_GPIO_WritePin(IIC_SDA_GPIO_Port, IIC_SDA_Pin, GPIO_PIN_RESET) 
#define IIC_READ_SDA    HAL_GPIO_ReadPin(IIC_SDA_GPIO_Port, IIC_SDA_Pin) 
/* SDA引脚设置宏定义 */
#define IIC_SDA(x) do{x ? \
                      HAL_GPIO_WritePin(IIC_SDA_GPIO_Port, IIC_SDA_Pin, GPIO_PIN_SET) : \
                      HAL_GPIO_WritePin(IIC_SDA_GPIO_Port, IIC_SDA_Pin, GPIO_PIN_RESET); \
                     }while(0)


void iic_start(void);
void iic_stop(void);
uint8_t iic_wait_ack (void);
void iic_ack(void);
void iic_nack(void);
void iic_send_byte(uint8_t data);
uint8_t iic_read_byte(uint8_t ack);
								
#endif


AT24C02读写函数,分为随机地址单字节读写和连续字节读取以及按页写入

AT24C02.c:

#include "stm32f1xx_hal.h"
#include "24c02.h"
#include "main.h"
#include "string.h"
#include "i2c.h"
#include "Soft_IIC.h"
#include "delay.h"

/* 写入一个字节到从机AT24C02中 */
void at24c02_write_one_byte(uint8_t addr, uint8_t data)
{
    /* 1、发送起始信号 */
    iic_start();
    
    /* 2、发送通讯地址(写操作地址) */
    iic_send_byte(0xA0);
    
    /* 3、等待应答信号 */
    iic_wait_ack();
    
    /* 4、发送内存地址:0~255 */
    iic_send_byte(addr);
    
    /* 5、等待应答信号 */
    iic_wait_ack();
    
    /* 6、发送写入数据 */
    iic_send_byte(data);
    
    /* 7、等待应答信号 */
    iic_wait_ack();
    
    /* 8、发送停止信号 */
    iic_stop();
    
    /* 等待EEPROM写入完成 */
    Delay_Ms(10);
}

/* 往从机AT24C02中读取一个字节 */
uint8_t at24c02_read_one_byte(uint8_t addr)
{
    uint8_t read = 0;
    
    /* 1、发送起始信号 */
    iic_start();
    
    /* 2、发送通讯地址(写操作地址) */
    iic_send_byte(0xA0);
    
    /* 3、等待应答信号 */
    if(iic_wait_ack() != 1)  return 1;
    
    /* 4、发送内存地址:0~255 */
    iic_send_byte(addr);
    
    /* 5、等待应答信号 */
    if(iic_wait_ack() != 1)  return 2;
    
    /* 6、发送起始信号 */
    iic_start();
    
    /* 7、发送通讯地址(读操作地址) */
    iic_send_byte(0xA1);
    
    /* 8、等待应答信号 */
    if(iic_wait_ack() != 1)  return 3;
    
    /* 9、接收数据并发送非应答(获取该地址即可) */
    read = iic_read_byte(0);
    
    /* 10、发送停止信号 */
    iic_stop();
    
    return read;
}

/*---------------------------------------------------------*/
/*函数名:AT24C02读取数据                                    */
/*参  数:addr:地址  rdata:接收缓冲区 datalen:读取长度   */
/*返回值:0:正确 其他:错误                               */
/*---------------------------------------------------------*/
uint8_t AT24C02_ReadData(uint8_t addr, uint8_t *rdata, uint16_t datalen){
	
	uint16_t i;                                  //用于for循环
	iic_start();
    
	/* 2、发送通讯地址(写操作地址) */
	iic_send_byte(0xA0);
	
	/* 3、等待应答信号 */
	if(iic_wait_ack() != 1)  return 1;
	
	/* 4、发送内存地址:0~255 */
	iic_send_byte(addr);
	
	/* 5、等待应答信号 */
	if(iic_wait_ack() != 1)  return 2;
	
	/* 6、发送起始信号 */
	iic_start();
	
	/* 7、发送通讯地址(读操作地址) */
	iic_send_byte(0xA1);
	
	/* 8、等待应答信号 */
	if(iic_wait_ack() != 1)  return 3;
	
	for(i = 0;i<datalen - 1;i++)
	
	{
	 rdata[i] = iic_read_byte(1);
	}
	rdata[datalen - 1] = iic_read_byte(0);
	iic_stop();
	
	return 0;		                            //正确,返回0
}


/*-------------------------------------------------*/
/*函数名:    AT24C02写入一页(8字节)数据        */
/*参  数:addr:地址  wdata:需要写入的数据指针    */
/*返回值:0:正确 其他:错误                       */
/*-------------------------------------------------*/
uint8_t AT24C02_WritePage(uint8_t addr, uint8_t *wdata){
	
	uint8_t i;                                  //用于for循环
	
	iic_start();                             //发送起始信号
	iic_send_byte(0xA0);            //发送24C02写操作器件地址
	 if(iic_wait_ack() != 1)  return 1;        //等待应答,错误的话,返回1
	iic_send_byte(addr);             //发送内部存储空间地址
	 if(iic_wait_ack() != 1)  return 2;       //等待应答,错误的话,返回2
	for(i=0;i<8;i++){                          //循环8次写入一页
		 iic_send_byte(wdata[i]);                //发送数据
		  if(iic_wait_ack() != 1)  return 3+i;    //等待应答,错误的话,返回3+i
	}
	iic_stop();                               //发送停止信号
	/* 等待EEPROM写入完成 */
  Delay_Ms(10);
	return 0;	                                //正确,返回0
}


AT24c02.h:   头文件

#ifndef M24C02_H
#define M24C02_H

#include "stdint.h"

#define M24C02_WADDR  0xA0     //24C02写操作器件地址
#define M24C02_RADDR  0xA1     //24C02读操作器件地址


void at24c02_write_one_byte(uint8_t addr, uint8_t data);
uint8_t at24c02_read_one_byte(uint8_t addr);

uint8_t AT24C02_ReadData(uint8_t addr, uint8_t *rdata, uint16_t datalen);
uint8_t AT24C02_WritePage(uint8_t addr, uint8_t *wdata);
#endif

main测试函数以及测试结果:(友情提醒,测试软件IIC时可以先从单字节写入读取开始,这样出问题了可以缩小查找范围)

main.c测试部分代码

uint8_t date[16];
uint8_t write[10] ={0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A};


AT24C02_WritePage(0, write);
AT24C02_WritePage(8, write);
AT24C02_ReadData(0, date, 16);
for(int i = 0;i<16;i++){
  myprintf("date:%x\r\n",date[i]);
}

AT24C32读写函数,由于其存储单元大于256个,因此寻址的时候需要输入16位地址,这也是它和AT24C02在驱动代码上唯二的区别,AT24C32地址要分两次输入,先输入高八位后输入低八位;其次就是AT24C32一页有32个字节,所以按页写入时应该一次写32个而不是8个字节。

AT24C32.c:

   /* 4、发送内存地址:0~65536 */
		if(EE_SIZE > 256)     //   >AT24C02,此时无法用8位寻址,得用16位寻址,先发高八位后发低八位
		{
			iic_send_byte(addr>>8);//发送高地址 
			iic_wait_ack();
		}		
		iic_send_byte(addr%256);//发送低地址



AT24C32.h:

#define EE_SIZE        4095     //24C32有4096个字节,地址最大为4095
#define PAGE_SIZE			32						//页面大小(字节)

参考链接:STM32软件模拟实现IIC写入和读取AT24C02(STM32CubeMx配置)_怎么用stm32cubemx驱动软件 iic-CSDN博客AT24C01/AT24C02系列EEPROM芯片单片机读写驱动程序-CSDN博客AT24C01C/AT24C02C Data Sheet (microchip.com)

AT24C32/64 (microchip.com)

  • 7
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
在使用HAL库进行STM32IIC读写AT24C02时,你可以参考以下步骤: 1. 首先,你需要在你的工程中包含AT24CXX.c文件,并在代码中引用该文件。该文件中定义了一些常量和函数,用于初始化AT24CXX芯片、写入数据和读取数据等操作。 2. 在AT24CXX.c文件中,你可以看到定义了一些常量,如AT24C02的地址为255。这些常量可以根据你所使用的芯片型号进行修改。 3. 在AT24CXX.c文件中,还定义了一些函数,如AT24CXX_Init()用于初始化AT24CXX芯片,AT24CXX_Write()用于写入数据,AT24CXX_Read()用于读取数据,AT24CXX_Check()用于检查AT24CXX芯片是否正常工作。你可以根据需要调用这些函数来实现对AT24C02的读写操作。 4. 在配置STM32的引脚时,你需要将IIC的引脚与AT24C02芯片的引脚相连接。具体的引脚配置可以参考AT24CXX.c文件中的注释。 5. 在配置串口时,你可以选择使用串口进行数据查看,以便调试和验证读写操作的结果。 6. 最后,根据你的需求选择适当的时钟频率,生成Keil工程代码。 综上所述,你可以使用HAL库的函数和AT24CXX.c文件中定义的函数来实现对AT24C02芯片的读写操作。 #### 引用[.reference_title] - *1* *3* [STM32 (基于HAL库) 硬件IIC读写任意AT24CXX芯片](https://blog.csdn.net/weixin_56565733/article/details/124401443)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [STM32系列(HAL库)——F103C8T6通过IIC/I2C方式读写AT24C02—(EEPROM 存储模块)](https://blog.csdn.net/lwb450921/article/details/124394615)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值