平台:STM32ZET6(核心板)+ST-LINK/V2+SD卡+USB串口线+外部EEPROM(不需要上拉电阻)

工程介绍:主要文件在USER组中,bsp_i2c_ee.c,bsp_i2c_ee.h,bsp_eeprom.c,bsp_eeprom.h和main.c,其中bsp_i2c_ee.c中主要时基本的模拟I2C时序,而bsp_eeprom.c中主要利用前一个文件中定义的基本操作,进行EEPROM的读写操作。其他类似I2C时序的协议,均可以保留bsp_i2c_ee.c的基础上添加新的内容。本文有些内容借鉴了其他网友的总结,在此表示感谢。

1.硬件部分:电路连接较为简单,笔者在淘宝上买的24C02N主要有四根线,两根电源线,一根SCL和一根SDA。这里我们把SCL和SDA连接到B端口的6和7引脚。如上图所示,如果需要改变引脚设置,只需要更改宏即可。

MCU和EEPROM连接好之后就像下图所示,MCU作为主机,EEPROM作为从机,从机地址不可以重复,由于STM32ZET6(核心板)上提供了上拉输入功能,我们可以很方便的将EEPROM模块的SCL和SDA直接连接到PB6和PB7上。

2.软件部分

关键在于 I2C时序的模拟,主要模拟的是起始信号,终止信号,应答信号,非应答信号,等待接收应答信号,发送一个字节,读取一个字节。

这些工作分别由以下函数模拟产生。

int I2C_Start(void);
void I2C_Stop(void);
void I2C_Ack();
void I2C_NoAck();
uint8_t I2C_GetAck(void);
void I2C_SendByte(uint8_t Data);
uint8_t I2C_ReadByte(uint8_t ack);

2.1 起始信号和终止信号

如图所示,当SCL(SCLK)为高电平,SDA(SDI)从高电平到低电平跳变,作为起始信号。反映在程序上,如下:

int I2C_Start(void)
{I2C_SDA_OUT(); //配置SDA为推挽输出SDA_H;SCL_H;//高电平有效I2C_delay();//延时//查看此时SDA是否就绪(高电平)if(!SDA_read){printf("\r\nSDA线为低电平,总线忙,退出\r\n");return DISABLE;//SDA总线忙,退出}//制造一个下降沿,下降沿是开始的标志SDA_L;I2C_delay();//查看此时SDA已经变为低电平if(SDA_read){printf("\r\nSDA线为高电平,总线出错,退出\r\n");return DISABLE;//SDA总线忙,退出}SCL_L;return ENABLE;
}

当SCL(SCLK)为高电平,SDA(SDI)从低电平到高电平跳变,作为终止信号。反映在程序上,如下:

void I2C_Stop(void)
{I2C_SDA_OUT(); //配置SDA为推挽输出SCL_L;//制造一个上升沿,上升沿是结束的标志SDA_L;  SCL_H;//高电平有效I2C_delay();//延时SDA_H;I2C_delay();
}

2.2 应答和非应答信号

//主机的应答信号,主机把第九位置高,从机将其拉低表示应答
static void I2C_Ack()
{SCL_L;I2C_SDA_OUT();   //配置SDA为推挽输出SDA_L;//置低I2C_delay();   //注意延时时间应该大于4微秒,其他位置也是如此SCL_H;I2C_delay();SCL_L;
}//主机的非应答信号,从机把第九位置高,主机将其拉低表示非应答
static void I2C_NoAck()
{SCL_L;I2C_SDA_OUT();   //配置SDA为推挽输出I2C_delay();SDA_H;//置高I2C_delay();SCL_H;I2C_delay();SCL_L;
}
//注意延时时间应该大于4微秒,其他位置也是如此SCL_H;I2C_delay();SCL_L;
}//主机的非应答信号,从机把第九位置高,主机将其拉低表示非应答
static void I2C_NoAck()
{SCL_L;I2C_SDA_OUT();   //配置SDA为推挽输出I2C_delay();SDA_H;//置高I2C_delay();SCL_H;I2C_delay();SCL_L;
}

2.3 应答位的接收

uint8_t I2C_GetAck(void)
{uint8_t time = 0;I2C_SDA_IN();    //配置SDA为上拉输入SDA_H;I2C_delay();SCL_H;I2C_delay();while(SDA_read)//从机未应答,若应答,会拉低第九位{time++;if(time > 250){//不应答时不可以发出终止信号,否则,复合读写模式下不可以进行第二阶段//SCCB_Stop();SCL_L;return DISABLE;}}SCL_L;return ENABLE;
}

2.4 读一个字节和写一个字节

//I2C写一个字节
void I2C_SendByte(uint8_t Data)
{uint8_t cnt;I2C_SDA_OUT(); //配置SDA为推挽输出for(cnt=0; cnt<8; cnt++){SCL_L;                                 //SCL低(SCL低时,变化SDA)I2C_delay();if(Data & 0x80){SDA_H;                              //SDA高,从最低位开始写起}else{SDA_L;                               //SDA低}Data <<= 1;SCL_H;                                //SCL高(发送数据)I2C_delay();}SCL_L;                                   //SCL低(等待应答信号)I2C_delay();
}//I2C读取一个字节
uint8_t I2C_ReadByte(uint8_t ack)
{uint8_t cnt;uint8_t data;I2C_SDA_IN(); //配置SDA为上拉输入for(cnt=0; cnt<8; cnt++){SCL_L;                                 //SCL低I2C_delay();SCL_H;                                //SCL高(读取数据)data <<= 1;if(SDA_read){data |= 0x01;                              //SDA高(数据有效)}I2C_delay();}//发送应答信号,为低代表应答,高代表非应答if(ack == 1){I2C_NoAck();}else{I2C_Ack();}return data;                                   //返回数据
}

2.5 GPIO的初始化,以及SDA的输入输出模式的重新配置

前面我们注意到,当在应答位的接收,以及读取一个字节的信息时,SDA需要被设置为输入模式,读取从机(EEPROM)发送来的数据。

//GPIO配置函数
void I2C_GPIO_Configuration(void)
{GPIO_InitTypeDef  GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);GPIO_InitStructure.GPIO_Pin = PIN_I2C_SCL;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;  //推挽输出GPIO_Init(PORT_I2C_SCL, &GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin = PIN_I2C_SDA;GPIO_Init(PORT_I2C_SDA, &GPIO_InitStructure);
}//重新设置SDA为上拉输入模式
void I2C_SDA_IN()
{GPIO_InitTypeDef  GPIO_InitStructure;GPIO_InitStructure.GPIO_Pin = PIN_I2C_SDA;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;  //上拉输入,使得板外部不需要接上拉电阻GPIO_Init(PORT_I2C_SDA, &GPIO_InitStructure);
}//重新设置SDA为推挽输出模式
void I2C_SDA_OUT()
{GPIO_InitTypeDef  GPIO_InitStructure;GPIO_InitStructure.GPIO_Pin = PIN_I2C_SDA;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;  //推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;         //IO口速度为50MHzGPIO_Init(PORT_I2C_SDA, &GPIO_InitStructure);
}
//I2C初始化
void I2C_Initializes(void)
{I2C_GPIO_Configuration();SCL_H;                                  //置位状态SDA_H;
}

2.6 与EEPROM相关的宏定义

#define EEPROM_DEV_ADDR           0xA0           //地址(设备地址)
#define EEPROM_WR                 0x00                     //写
#define EEPROM_RD                 0x01                     //读

2.7 调用之前定义好的I2C时序模拟函数,完成EEPROM的读写一个字节操作。(借鉴于他人)

//写入一个字节
int EEPROM_WriteByte(uint16_t Addr, uint8_t Data)
{/* 1.开始 */I2C_Start();/* 2.设备地址/写 */I2C_SendByte(EEPROM_DEV_ADDR | EEPROM_WR);//读取应答位if(!I2C_GetAck()){printf("\r\n发送设备地址时非应答!\r\n");I2C_Stop();return DISABLE;}/* 3.数据地址 */#if (8 == EEPROM_WORD_ADDR_SIZE)I2C_SendByte((uint8_t)(Addr&0x00FF));         //数据地址(8位)#elseI2C_SendByte((uint8_t)(Addr>>8));             //数据地址(16位)I2C_SendByte((uint8_t)(Addr&0x00FF));#endifI2C_GetAck();//不需要判断应答位的状况/* 4.写一字节数据 */I2C_SendByte(Data);/* 5.停止 */I2C_Stop();
}//读取一个字节
int EEPROM_ReadByte(uint16_t Addr, uint8_t *Data)
{/* 1.开始 */I2C_Start();/* 2.设备地址/写 */I2C_SendByte(EEPROM_DEV_ADDR | EEPROM_WR);//读取应答位if(!I2C_GetAck()){printf("\r\n读一串数据的两相写阶段非应答!\r\n");I2C_Stop();return DISABLE;}/* 3.数据地址 */#if (8 == EEPROM_WORD_ADDR_SIZE)I2C_SendByte((uint8_t)(Addr&0x00FF));         //数据地址(8位)#elseI2C_SendByte((uint8_t)(Addr>>8));             //数据地址(16位)I2C_SendByte((uint8_t)(Addr&0x00FF));#endif/* 4.重新开始 */I2C_Start();/* 5.设备地址/读 */I2C_SendByte(EEPROM_DEV_ADDR | EEPROM_RD);//读取应答位if(!I2C_GetAck()){printf("\r\n读一串数据的两相读阶段非应答!\r\n");I2C_Stop();return DISABLE;}/* 6.读一字节数据 */*Data = I2C_ReadByte(I2C_NOACK);               //只读取1字节(产生非应答)/* 7.停止 */I2C_Stop();
}

2.8 重复调用读写一个字节函数,实现同时读写多个字节的数据。

//写入多个字节
void EEPROM_WriteNByte(uint16_t Addr, uint8_t *pData, uint16_t Length)
{uint16_t i;//每写一个字节,调用一次EEPROM_WriteBytefor(i=0;i<Length;i++){//写入数据EEPROM_WriteByte(Addr, *pData);Addr++;pData++;//延时Delay_us(10000);}
}//读取多个字节
void EEPROM_ReadNByte(uint16_t Addr, uint8_t *pData, uint16_t Length)
{uint16_t i;//每写一个字节,调用一次EEPROM_ReadBytefor(i=0;i<Length;i++){//写入数据EEPROM_ReadByte(Addr, pData);Addr++;pData++;//延时Delay_us(10000);}
}

2.9 测试读写功能

#define EEPROM_BUF_LEN            64            //测试BUF长度
void System_Initializes(void)
{//定时器配置SysTick_Init();//串口配置USART_Config();//I2C配置I2C_Initializes();
}
int main(void)
{uint8_t  cnt;uint8_t  line = 0;uint8_t  w_buf[EEPROM_BUF_LEN];uint8_t  r_buf[EEPROM_BUF_LEN];System_Initializes();/* 填充缓冲区 */for(cnt=0; cnt<EEPROM_BUF_LEN; cnt++){w_buf[cnt] = cnt;}printf("************************I2C协议读写EEPROM实验*********************\r\n");//打印读取的内容for(cnt=0; cnt<EEPROM_BUF_LEN; cnt++){printf("w_buf[%d] = %d\t", cnt, w_buf[cnt]);line++;if(line >= 4){printf("\r\n");line = 0;}}//0地址连续写EEPROM_BUF_LEN节数据EEPROM_WriteNByte(0, w_buf, EEPROM_BUF_LEN);Delay_us(100000);//0地址连续读EEPROM_BUF_LEN节数据,并打印EEPROM_ReadNByte(0, r_buf, EEPROM_BUF_LEN);//打印读取的内容for(cnt=0; cnt<EEPROM_BUF_LEN; cnt++){printf("r_buf[%d] = %d\t", cnt, r_buf[cnt]);line++;if(line >= 4){printf("\r\n");line = 0;}}
}

STM32模拟I2C时序读写EEPROM精简版相关推荐

  1. STM32模拟I2C协议获取MLX90615红外温度传感器测温数据(Open Drain管脚配置)

    STM32模拟I2C协议获取MLX90615红外温度传感器测温数据(Open Drain管脚配置) STM32的GPIO管脚可以配置为Open Drain输出模式,并且有两个功能: 可以设置内部上拉, ...

  2. STM32模拟SPI时序

      STM32模拟SPI时序的代码如下: #define MOSI_H GPIO_SetBits ( GPIOA, GPIO_Pin_7 ) #define MOSI_L GPIO_ResetBits ...

  3. STM32系统学习——I2C (读写EEPROM)

    I2C 通讯协议(Inter-Integrated Circuit)引脚少,硬件实现简单,可扩展性强,不需要 USART.CAN 等通讯协议的外部收发设备,现在被广泛地使用在系统内多个集成电路(IC) ...

  4. S5PV210之GPIO模拟I2c时序之pcf8591与at24xx linux3.0.8驱动

    目录:一. 说明 二. 驱动程序说明及问题 三. 案例一       四. 案例二 一. 说明 mini210开发板上带了at24c08, 看了linux内核自带的at24.c的驱动程序,编译下载到看 ...

  5. I2C学习——读写eeprom

    一.理论学习 I2C 通讯协议(Inter-Integrated Circuit)是由Philips公司开发的一种简单.双向二线制同步串行总线,只需要两根线即可在连接于总线上的器件之间传送信息. I2 ...

  6. STM32模拟I2C程序

    修改自cleanflight /*******************************************************************************测试平台: ...

  7. STM32 Cubemax(三)——时序读写完成称重传感器+HX711的使用

    STM32称重传感器+HX711的使用--HAL库 文章目录 STM32称重传感器+HX711的使用--HAL库 前言 一.接线 二.CubeMax配置 三.代码编写 注意点 前言 因为在一个项目中使 ...

  8. 初学24CXX系列EEPROM使用详解STM32库函数I2C总线

    24CXX系列芯片属于EEPROM(Electrically Erasable Programmable read only memory)即电可擦可编程只读存储器,是一种掉电后数据不丢失(不挥发)存 ...

  9. linux源码gpio模拟i2c,linux内核gpio模拟i2c实例.doc

    linux内核gpio模拟i2c实例.doc linux内核GPIO模拟I2C实例2010-10-11作者:cvip302814来源:cvip302814的blog前言:在许多情况下,我们并没有足够的 ...

最新文章

  1. python乘法口诀代码-python---九九乘法表代码
  2. RSA体系 c++/java相互进行加签验签--转
  3. 全球及中国商用壁挂式浴镜行业投资决策与需求前景预测报告2022版
  4. php 二维数组字母排序,PHP二维数组获取第一个中文首字母并排序 筋斗云网络
  5. power bi_如何将Power BI模型的尺寸减少90%!
  6. 女生转行IT与男生有什么不一样?
  7. layui datetimepicker 只日期范围到当前时间的前一天_浪琴手表如何正确调整日期?手表调日期的方法...
  8. Eclipse中web项目的默认发布路径改为外部Tomcat中webapp路径
  9. 变更数据推送java_idea 团队成员修改工程后push推送
  10. 注解形式控制器 数据验证,类型转换
  11. 公有云关闭潮或显端倪,企业如何选择?
  12. php网站模板怎么修改,自己做网站如何用好并自主修改网上的免费模板
  13. 常用Physionet命令整理
  14. java中的冒泡排序和交换排序
  15. 简单sql存储过程实例、储过程实战
  16. Qgis 3.18 的安装步骤
  17. 收评:5月24日资金流向(摘自益盟)
  18. ⑦企业级zabbix监控 微信报警、邮箱报警、钉钉报警、全网最细
  19. 关于未来趋势的几点预测:
  20. android语音输入文字,盘点好用的语音输入APP,懒得打字的时候就说话吧!

热门文章

  1. 径向基神经网络(RBF)回归预测MATLAB实现超详细
  2. oracle批量删除表空间,批量删除oracle用户表空间
  3. Python异常 #IndentationError: unindent does not match any outer indentation level
  4. 【FPGA】Mint20.3系统安装VCS2018环境
  5. layui时间怎么设置年月日时分秒_layui.laydate--日期与时间选择模块文档介绍
  6. 从程序员到项目经理(22):以德服人才能口服心服 - 兼谈华为公司狼性管理
  7. 多测师肖sir_高级讲师_第二个月第2讲python之基本操作(002)
  8. 高考400分计算机专业,高考400分 计算机专业
  9. 男人二十五岁前该懂的
  10. python-16-python并行计算程序multiprocessing