文章目录

  • 前言
  • 一、AT24C64
  • 二、驱动步骤
    • 1.宏定义
    • 2.代码实现
  • 前言
    在嵌入式设计中,E2PROM存储芯片常被应用于需要掉电存储,且容量不大的场合。对比flash存储芯片,E2PROM具有擦写次数多,允许字节编程的特点,因此,更适合于数据量存储不大,且更需要频率擦写的应用场景。今天,就以HC32L136/HC32L176为平台,简单介绍下如何驱动E2PROM存储芯片AT24C64

一、AT24C64

从芯片手册我们可以找到关于芯片的引脚图和引脚说明如下

其中,A0A1A2这三个脚为设备地址输入脚,当有多个E2PROM设备级联时,通过此引脚的定义来实现对不同设备的寻址。SDA和SDL为标志的IIC接口,WP为写保护脚(默认接地即可)。
AT24C64典型的应用电路原理图如下:

二、驱动步骤

1、驱动原理

本项目中微控制器华大半导体HC32L136/HC32L176驱动AT24C64基于I2C通信协议,I2C 是双线双向的同步串行总线,它利用一根时钟线和一根数据线在连接总线的两个器件之间进行信息的传递,为设备之间数据交换提供了一种简单高效的方法。每个连接到总线上的器件都有唯一的地址,任何器件既可以作为主机也可以作为从机,但同一时刻只允许有一个主机。I2C 标准是一个具有冲突检测机制和仲裁机制的真正意义上的多主机总线,它能在多个主机同时请求控制总线时利用仲裁机制避免数据冲突并保护数据。I2C 总线控制器,能满足 I2C 总线的各种规格并支持所有与 I2C 总线通信的传输模式
通常标准 I2C 传输协议包含四个部分:起始(S)或重复起始信号(Sr),从机地址及读写位,传输数据,停止信号(P)。I2C 传输协议图如下所示:

当总线处于空闲状态下(SCL 和 SDA 线同时为高),SDA 线上出现由高到低的信号,表明总线上产生了起始信号。当两个起始信号之间没有停止信号时,即产生了重复起始信号。主机采用这种方法与另一个从机或相同的从机以不同传输方向进行通信(例如:从写入设备到从设备读出)而不释放总线。当 SCL 线为高时,SDA 线上出现由低到高的信号,被定义为停止信号。主机向总线发出停止信号结束数据传送。 START和STOP 条件图如下所示:

当起始信号产生后,主机立即传输数据的第一字节:7 位从机地址 + 读写位,读写位控制从机的数据传输方向(0:写;1:读)。被主机寻址的从机会通过在第 9 个 SCL时钟周期将 SDA 置为低电平作为应答。

数据传输过程中,一个 SCL 时钟脉冲传输一个数据位,且 SDA 线只有在 SCL 为低时才可以改变。I2C 总线上位传输图如下所示:

华大半导体I2C 组件可实现 8 位的双向数据传输,传输速率在标准模式下可达到 100Kbps 而在高速模式下可达 400Kbps,在超高速模式下可达 1Mbps,并且可以在四种模式下工作:主机发送模式、主机接收模式、从机接收模式、从机发送模式。 还有一种特殊模式广播呼叫模式,其操作方式与从机接收模式类似。

华大半导体HC32L136/HC32L176 I2C 控制器支持以下特性:

支持主机发送/接收,从机发送/接收四种工作模式
支持标准(100Kbps) / 快速(400Kbps) / 高速(1Mbps) 三种工作速率
支持 7 位寻址功能
支持噪声过滤功能
支持广播地址
支持中断状态查询功能
I2C功能模块如下图所示:

本项目中采用主机模式实现数据收、发通信,主收、发送模式数据同步图如下图所示(更详细说明可参见用户手册):

2、驱动实现

第1步:明确从机地址(AT24C64地址),AT24C64 的 I2C 设备地址如下表所示

第2步:配置HC32L136/HC32L176 I2C功能模块,这里需要留意AT24c64 的 I2C通讯引脚的电性特性,如下表所示:

I2C GPIO配置代码如下所示

///< IO端口配置
void App_PortCfg(void)
{stc_gpio_cfg_t stcGpioCfg;DDL_ZERO_STRUCT(stcGpioCfg);Sysctrl_SetPeripheralGate(SysctrlPeripheralGpio,TRUE);   //开启GPIO时钟门控 stcGpioCfg.enDir = GpioDirOut;                           ///< 端口方向配置->输出    stcGpioCfg.enOD = GpioOdEnable;                          ///< 开漏输出stcGpioCfg.enPu = GpioPuEnable;                          ///< 端口上拉配置->使能stcGpioCfg.enPd = GpioPdDisable;                         ///< 端口下拉配置->禁止Gpio_Init(GpioPortB,GpioPin8,&stcGpioCfg);               ///< 端口初始化Gpio_Init(GpioPortB,GpioPin9,&stcGpioCfg);Gpio_SetAfMode(GpioPortB,GpioPin8,GpioAf1);              ///< 配置PB08为SCLGpio_SetAfMode(GpioPortB,GpioPin9,GpioAf1);              ///< 配置PB09为SDA
}

I2C配置代码如下所示

///< I2C 模块配置
void App_I2cCfg(void)
{stc_i2c_cfg_t stcI2cCfg;DDL_ZERO_STRUCT(stcI2cCfg);                            ///< 初始化结构体变量的值为0Sysctrl_SetPeripheralGate(SysctrlPeripheralI2c0,TRUE); ///< 开启I2C0时钟门控stcI2cCfg.u32Pclk = Sysctrl_GetPClkFreq();             ///< 获取PCLK时钟stcI2cCfg.u32Baud = 100000;                            ///< 100KstcI2cCfg.enMode = I2cMasterMode;                      ///< 主机模式stcI2cCfg.bGc = FALSE;                                 ///< 广播地址应答使能关闭I2C_Init(M0P_I2C0,&stcI2cCfg);                         ///< 模块初始化
}

第3步:编写接收驱动函数,接收驱动代码如下所示:

/********************************************************************************** \brief  主机接收函数**** \param u8Addr从机内存地址,pu8Data读数据存放缓存,u32Len读数据长度**** \retval 读数据是否成功********************************************************************************/en_result_t I2C_MasterReadData(M0P_I2C_TypeDef* I2CX,uint16_t u8Addr,uint8_t *pu8Data,uint32_t u32Len)
{en_result_t enRet = Error;uint8_t u8i=0,u8State;uint8_t u16i=0;I2C_SetFunc(I2CX,I2cStart_En);while(1){while(0 == I2C_GetIrq(I2CX)){}u8State = I2C_GetState(I2CX);switch(u8State){case 0x08:                                 ///< 已发送起始条件,将发送SLA+WI2C_ClearFunc(I2CX,I2cStart_En);I2C_WriteByte(I2CX,I2C_SLAVEADDR); break;case 0x18:                                 ///< 已发送SLA+W,并接收到ACKI2C_WriteByte(I2CX,u8Addr<<8);        ///<高8位地址 发送从机内存地址break;case 0x28:                                 ///< 已发送数据,接收到ACK, 此处是已发送从机内存地址u8Addr并接收到ACKu16i++;I2C_WriteByte(I2CX,u8Addr&0xff);       ///<低8位地址 发送从机内存地址if(u16i>1)I2C_SetFunc(I2CX,I2cStart_En);         ///< 发送重复起始条件break;case 0x10:                                 ///< 已发送重复起始条件I2C_ClearFunc(I2CX,I2cStart_En);I2C_WriteByte(I2CX,I2C_SLAVEADDR|0x01);///< 发送SLA+R,开始从从机读取数据break;case 0x40:                                 ///< 已发送SLA+R,并接收到ACKif(u32Len>1){I2C_SetFunc(I2CX,I2cAck_En);       ///< 使能主机应答功能}break;case 0x50:                                 ///< 已接收数据字节,并已返回ACK信号pu8Data[u8i++] = I2C_ReadByte(I2CX);if(u8i==u32Len-1){I2C_ClearFunc(I2CX,I2cAck_En);     ///< 已接收到倒数第二个字节,关闭ACK应答功能}break;case 0x58:                                 ///< 已接收到最后一个数据,NACK已返回pu8Data[u8i++] = I2C_ReadByte(I2CX);I2C_SetFunc(I2CX,I2cStop_En);          ///< 发送停止条件break;case 0x38:                                 ///< 在发送地址或数据时,仲裁丢失I2C_SetFunc(I2CX,I2cStart_En);         ///< 当总线空闲时发起起始条件break;case 0x48:                                 ///< 发送SLA+R后,收到一个NACKI2C_SetFunc(I2CX,I2cStop_En);          ///< 发送停止条件I2C_SetFunc(I2CX,I2cStart_En);         ///< 发送起始条件break;default:I2C_SetFunc(I2CX,I2cStart_En);         ///< 其他错误状态,重新发送起始条件break;}I2C_ClearIrq(I2CX);                            ///< 清除中断状态标志位if(u8i==u32Len)                                ///< 数据全部读取完成,跳出while循环{break;}}enRet = Ok;return enRet;
}

第4步:编写发送驱动函数,主机发送状态图如下所示:

/********************************************************************************** \brief  主机发送函数**** \param u8Addr从机内存地址,pu8Data写数据,u32Len写数据长度**** \retval 写数据是否成功********************************************************************************/
en_result_t I2C_MasterWriteData(M0P_I2C_TypeDef* I2CX,uint16_t u8Addr,uint8_t *pu8Data,uint32_t u32Len)
{en_result_t enRet = Error;uint8_t u8i=0,u8State;uint8_t u16i=0;I2C_SetFunc(I2CX,I2cStart_En);while(1){while(0 == I2C_GetIrq(I2CX)){;} u8State = I2C_GetState(I2CX);switch(u8State){case 0x08:                               ///< 已发送起始条件I2C_ClearFunc(I2CX,I2cStart_En);I2C_WriteByte(I2CX,I2C_SLAVEADDR);   ///< 从设备地址发送break;case 0x18:                               ///< 已发送SLA+W,并接收到ACKI2C_WriteByte(I2CX,u8Addr<<8);       ///<高8位地址 从设备内存地址发送break;case 0x28:                               ///< 上一次发送数据后接收到ACKu16i++;I2C_WriteByte(I2CX,u8Addr&0xff);    ///<低8位地址 从设备内存地址发送if(u16i>1)I2C_WriteByte(I2CX,pu8Data[u8i++]);  ///< 继续发送数据break;case 0x20:                               ///< 上一次发送SLA+W后,收到NACKcase 0x38:                               ///< 上一次在SLA+读或写时丢失仲裁I2C_SetFunc(I2CX,I2cStart_En);       ///< 当I2C总线空闲时发送起始条件break;case 0x30:                               ///< 已发送I2Cx_DATA中的数据,收到NACK,将传输一个STOP条件I2C_SetFunc(I2CX,I2cStop_En);        ///< 发送停止条件break;default:break;}if(u8i>u32Len){I2C_SetFunc(I2CX,I2cStop_En);            ///< 此顺序不能调换,出停止条件I2C_ClearIrq(I2CX);break;}I2C_ClearIrq(I2CX);                          ///< 清除中断状态标志位}enRet = Ok;return enRet;
}

验证示例代码如下所示:

uint8_t u8Senddata[10] = {0x11,0x34,0x77,0x66,0x55,0x44,0x33,0x22,0x11,0x99};
uint8_t u8Recdata[10]={0x00};int32_t main(void)
{App_AT24C64Init(); App_LpUartInit();//< eeprom写数据I2C_MasterWriteData(M0P_I2C0,0x00,u8Senddata,10);  ///< eeprom读数据I2C_MasterReadData(M0P_I2C0,0x01,u8Recdata,10);     App_LPUartSendbuff(M0P_LPUART1,u8Recdata,10);while(1){}

总结

烧录编译,串口打印读写信息

写一页

void x24c64_writeOnePage(unsigned char *buffer,uint16_t addr,unsigned char len){unsigned char i=0; if(len>32)len=32; I2C_MasterWriteData(M0P_I2C0,addr,buffer,len);delay10us(500);
}

随机写入

void app_x24c64_writebuff(uint8_t *buffer, uint16_t addr, int num)
{ uint8_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;Addr = addr % 32;/*不满一页的开始写的地址*/count = 32 - Addr;/*不满一页的地址剩余容量*/NumOfPage =  num / 32;/*写了完整的页数*/  NumOfSingle = num % 32;/*写完完整页剩余的容量*//* 写进的地址是在页的首地址  */if(Addr == 0){ /*写进的字节数不足一页*/if(NumOfPage == 0) x24c64_writeOnePage(buffer, addr, NumOfSingle);/*写进的字节数大于一页*/else{while(NumOfPage--){//    printf("NumOfPage=%d\r\n",NumOfPage);x24c64_writeOnePage(buffer, addr, 32 ); /*写一页*/addr +=  32 ;buffer+= 32 ;}/*写完整页*///    printf("NumOfSingle=%d\r\n",NumOfSingle);if(NumOfSingle!=0){/*写尾数*/x24c64_writeOnePage(buffer, addr, NumOfSingle);}}} /* 假如写进的地址不在页的首地址*/else { if (NumOfPage == 0) {/*写进的字节数不足一页 */if (NumOfSingle > count){/*要写完完整页剩余的容量大于不满一页的地址剩余容量*/temp = NumOfSingle - count;x24c64_writeOnePage(buffer, addr, count);/*把当前页的地址写完*/addr +=  count;buffer += count;x24c64_writeOnePage(buffer, addr, temp);/*在新的一页写剩余的字节*/}else{x24c64_writeOnePage(buffer, addr, num);}}else{ /*写进的字节数大于一页*/num -= count;NumOfPage =  num / 32;NumOfSingle = num % 32;x24c64_writeOnePage(buffer, addr, count);/*把当前页的地址写完*/addr +=  count;buffer += count;while (NumOfPage--){x24c64_writeOnePage(buffer, addr, 32);addr +=  32;buffer += 32;}if (NumOfSingle != 0){x24c64_writeOnePage(buffer, addr, NumOfSingle);}}}}

随机读取

uint8_t app_x24c64_readbuff(uint8_t *rbuff,uint16_t Waddr,int len){return I2C_MasterReadData(M0P_I2C0,Waddr,rbuff, len);
}

关于E2PROM的驱动部分就暂时介绍到这里了,若是上述有误,欢迎大家在评论区指出。

HC32L136/HC32L176开发之硬件IIC驱动AT24C64相关推荐

  1. HC32F460开发之硬件IIC驱动AT24C64

    文章目录 前言 一.AT24C64 二.驱动步骤 1.宏定义 2.代码实现 总结 前言 在嵌入式设计中,E2PROM存储芯片常被应用于需要掉电存储,且容量不大的场合.对比flash存储芯片,E2PRO ...

  2. 基于STM32F103RC硬件IIC驱动18位AD MCP3421驱动开发

    基于STM32 HAL库硬件IIC 驱动18位AD MCP3421 最近用到小信号采集,发现关于该芯片的STM32 HAL 库驱动比较少.就写了一个基于STM32F103RCT6测试Demo .在此分 ...

  3. RA4M2开发(2)----基于IIC驱动OLED

    概述 在e2studio中创建新的工程并导入必要的文件,包括I2C驱动代码和SSD1306 OLED显示驱动代码. 配置RA4M2的I2C接口,使其作为I2C master进行通信. 初始化SSD13 ...

  4. MSP430杂谈--AD7745硬件IIC驱动与模拟IIC驱动

    和上一篇AD7793类似,项目中也涉及到利用AD7745读取电容值,来测环境湿度.编写了基于MSP430的AD7745的硬件IIC驱动和模拟IIC驱动,分享给大家. AD7745硬件IIC驱动完整版下 ...

  5. 基于Stm32f103硬件iic驱动LM75A温度传感器

    这是LM75A温度传感器的概述,本文主要介绍基于Stm32f103的硬件iic驱动LM75A温度传感器. 这是我所使用的硬件电路,很简单. 对于该传感器的使用,主要是读取温度值,查看数据手册我们知道需 ...

  6. STM32 驱动 GY-302 光照传感器 BH1750 模块(软件IIC与硬件IIC驱动)

    1.特别说明 ​ 要是不想看原理和过程,直接下拉找代码吧,都是测试过的,很稳定,有硬件I2C驱动的,也有软件模拟I2C驱动的,基于STM32F103系列和STM32F4系列实现,基于标准库实现,条理清 ...

  7. 12. STM32——硬件IIC驱动OLED屏幕显示

    STM32--硬件IIC驱动OLED屏幕显示 OLED屏幕 OLED屏幕特点 OLED屏幕接线说明 OLED屏幕显存 OLED屏幕原理 OLED屏幕常用指令 OLED屏幕字模软件的使用 写命令 写数据 ...

  8. 【STM32】 硬件IIC 驱动SSD1302(0.96 OLED模块) -- 3/4 OLED的命令表 学习

    书接上回 文章1:[STM32] 硬件IIC 驱动SSD1306(0.96 OLED模块) – 1/4 库函数 学习 文章2:[STM32] 硬件IIC 驱动SSD1302(0.96 OLED模块) ...

  9. 嵌入式开发-STM32硬件SPI驱动TFT屏

    嵌入式开发-STM32硬件SPI驱动TFT屏 这次用到的TFT屏 CubeMX设置 代码编写 增加的内容 需要注意问题 代码下载 这次用到的TFT屏 现在的TFT屏幕已经很便宜了,65536色屏幕,2 ...

最新文章

  1. Deep learning:二十二(linear decoder练习)
  2. Discuz! 出现“您当前的访问请求当中含有非法字符“解决方法
  3. LeetCode:完全平方数【279】【DP】
  4. IntelliJ IDEA 12 与 Tomcat7 配置
  5. 实现一个基础的spelling corrector
  6. 常用的排序算法(java版)
  7. 菜鸟晋级必修 智能手机越狱/解锁/刷机完全教程
  8. 傅里叶变换【1】:傅里叶变换及逆变换
  9. 2019 JAVA面试题附答案
  10. QPS、TPS、吞吐量含义
  11. 函数参数三种传递方式的区别
  12. Mac上的QQ字体大小和颜色如何设置
  13. 2022前端面试需要掌握的面试题
  14. python爬取证券数据并存入数据库
  15. 从0开始学股票第四课之量能的基本知识之成交量
  16. javascript解析印象笔记导出enex文件(javascript解析xml,javascript监听手机端手指滑动事件)
  17. 企业付款到零钱操作步骤
  18. 自采集壁纸网站源码 - 小韩美化版
  19. 试用期工作总结(个人版)
  20. 三电平变频调速系统matlab仿真,矢量变频调速系统仿真研究

热门文章

  1. JS之解构( Destructuring)
  2. 原创教程:下载和安装“图形化积木Python编程”海龟编辑器
  3. 汉诺塔系列问题: 汉诺塔II、汉诺塔III、汉诺塔IV、汉诺塔V、汉诺塔VI
  4. 按理说机械硬盘和固态硬盘使用得当寿命几乎一样长,但为什么网上传言机械硬盘更容易坏?
  5. Linux网络配置常见问题
  6. Linux计划任务介绍
  7. PHP - Laravel 视图模板(blade.php) 循环便利
  8. OpenHarmony如何拨打电话
  9. android如何拨打电话
  10. 82055-94-5|N3-PEG-N3|Azide-PEG-Azide|叠氮PEG叠氮可修饰蛋白质