当前位置: 首页 > article >正文

STM32F103 I2C软件模拟(AT24C02)

有关I2C通信协议我们在《通信协议-I2C》已经进行了详细的介绍,因此这一节不再重复介绍。

一、软件/硬件I2C

想要控制STM32产生I2C方式的通讯,可以采用软件模拟或硬件I2C这两种方式。

1.1 软件模拟

所谓软件模拟,即直接使用CPU内核按照I2C协议的要求控制GPIO输出高低电平。

如控制产生I2C的起始信号时, 先控制作为SCL线的GPIO引脚输出高电平, 然后控制作为SDA线的GPIO引脚在此期间完成由高电平至低电平的切换,最后再控制SCL线切换为低电平,这样就输出了一个标准的I2C起始信号。

1.2 硬件I2C

硬件I2C是指直接利用STM32芯片中的硬件I2C外设,该硬件I2C外设跟USART串口外设类似,只要配置好对应的寄存器, 外设就会产生标准串口协议的时序。

使用它的I2C外设则可以方便地通过外设寄存器产生I2C协议方式的通讯,如初始化好I2C外设后, 只需要把某寄存器位置1,那么外设就会控制对应的SCLSDA线自动产生I2C起始信号,而不需要内核直接控制引脚的电平。

相对来说,硬件I2C直接使用外设来控制引脚,可以减轻CPU的负担。不过使用硬件I2C时必须使用某些固定的引脚作为SCLSDA, 软件模拟I2C则可以使用任意GPIO引脚,相对比较灵活。

在本开发板中,由于STM32F103RCT6芯片引脚较少,资源比较紧张, 在设计硬件时不方便使用硬件I2C指定的引脚连接外部设备,所以在控制程序上使用软件模拟I2C的方式。

二、AT24C02

2.1 回顾

有关AT24C02)可以参考《通信协议-I2C》小节中的介绍,我们介绍了AT24C0x的读写命令。

后续我们又在《linux驱动移植-I2C驱动移植(OLED SSD1306)》中介绍了I2C设备驱动的编写,并以OLED SSD1306作为实现设备。

2.2 硬件接线

我所使用的STM32F103RTC6开发板上有一块数据存储器,采用的是AT24C02芯片;

AT24C02是一个2K位串行可擦可编程只读存储器(EEPRAM),内部含有2568位字节,AT24C02有一个16字节页写缓冲区,该器件通过I2C总线接口进行操作,有一个专门的写保护功能。

其中I2C_SCL连接到处理器的PC12引脚,I2C_SDA连接到处理器的PC11引脚。

三、AT24C02源码实现

3.1 I2C软件模拟
3.1.1 I2C初始化

配置PC11~PC12引脚,均配置为通用推挽输出,最大速度50MHZ,输出高电平;

//宏定义I2C端口***********************************************
#define I2C_SCL     PCout(12)
#define I2C_SDA     PCout(11)

//宏定义SDA数据方向********************************************
#define SDA_OUT()   gpio_init(PC11,GPO_PUSH_PULL_50,HIGH)  
#define SCL_OUT()   gpio_init(PC12,GPO_PUSH_PULL_50,HIGH)

//宏定义读取SDA数据*******************************************
#define Read_SDA    PCin(11)

void SDA_IN(void)   
{  
   gpio_init(PC11, GPI_DOWN,HIGH); // 输入下拉
   PCout(11)=0;
}
/**********************************************************************************
 *
 *		Description:I2C初始化
 *
 **********************************************************************************/
void I2C_Init(void)			  //I2C初始化
{
  SDA_OUT();          //SDA设置为输出       
  SCL_OUT();		  //SCL设置为输出
  I2C_SCL=1;
  I2C_SDA=1;  
}
3.1.2 起始信号

I2C通信的起始信号由主设备发起,SCL保持高电平,SDA由高电平变为低电平;只有在起始信号发送之后,其它数据才有效;

/**********************************************************************************
 *
 *		Description:I2C起始信号
 *                   SCL=1;  SDA:1->0   
 *
 **********************************************************************************/
void  I2C_Start(void)		 //I2C起始信号
{
   SDA_OUT();
   I2C_SDA =1;				   
   delay_us(5);
   I2C_SCL =1;
   delay_us(5);
   I2C_SDA =0;
   delay_us(5);
   I2C_SCL =0;
}
3.1.3 终止信号

I2C通信的终止信号由主设备发起,SCL保持高电平,SDA由低电平变为高低电平;随着终止信号的出现,所有外部操作就结束;

/**********************************************************************************
 *
 *		Description:I2C终止信号
 *                  SCL=1;  SDA:0->1   
 *
 **********************************************************************************/
void I2C_Stop(void)		   //I2C终止信号
{
   SDA_OUT();
   I2C_SDA =0;
   delay_us(5);
   I2C_SCL =1;
   delay_us(5);
   I2C_SDA =1;
   delay_us(5);
   I2C_SCL =0;
}
3.1.4 应答信号

I2C总线在进行数据传送时,传送的字节数没有限制,但是每个字节长度必须为8位。

数据传送过程中,先传送最高位(MSB),接收端在收到有效数据后向对方相应的信号,发送端每发送一个字节数据(8位),在第9个始终周期释放数据线去接收对方的应答;因此一帧数据共有9位;

  • SDA位低电平位有效应答(ACK),表示接收端已经接收到数据;
  • SDA是高电平位无效应答(NAK),表示接收端没有接收成功;

主设备发送完数据需要等待从设备的应答,GPIO模拟:

/**********************************************************************************
 *
 *		Description:I2C(主机)等待来自从机的应答信号
 *        parameter:返回0:接受失败   1:接收成功  
 *
 **********************************************************************************/
u8 I2C_WaitAck(void)		    //等待来自从机的应答信号
{
  u16 time;
  SDA_IN();
  I2C_SCL =0;
  delay_us(5);
  I2C_SDA =1;
  delay_us(5);
  I2C_SCL =1;
  delay_us(5);
  while(Read_SDA)
  {
    time++;
	if(time>=2500)
	{
	  I2C_Stop();
	  return 0;  
	}
  }
  I2C_SCL =0;
  return 1;
}

主设备接收到从设备发送的数据后,需要向从设备发送方发送应答,GPIO模拟:

/**********************************************************************************
 *
 *		Description:I2C(主机)产生应答信号
 *        parameter: ack:0不应答  1:应答        
 *
 **********************************************************************************/
void I2C_Ack(u8 ack)			//产生应答信号
{
   SDA_OUT();
   I2C_SCL =0;
   delay_us(5);
   if(ack)
      I2C_SDA =0;
   else
      I2C_SDA =1;
   I2C_SCL =1;
   delay_us(5);
   I2C_SCL =0;
   delay_us(5);
}
3.1.5 有效数据

I2C总线进行数据传送时,时钟信号SCL为高电平期间,数据线SDA上的数据必须稳定;只有在SCL上的信号为低电平时,SDA上的高电平或低电平状态才允许变化。

因为当SCL是高电平时,数据线SDA的变化被规定为控制命令(也就是前面的起始信号和终止信号)。

主设备向从设备发送发送一个字节数据,GPIO模型模拟:

/**********************************************************************************
 *
 *		Description:I2C写一个字节数据
 *             byte:一个字节的数据   
 *
 **********************************************************************************/
void I2C_WriteData(u8 byte)			 //I2C写一个字节数据
{
  u8 i=0;
  SDA_OUT();
  I2C_SCL =0;
  for(i=0;i<8;i++)
  {
    I2C_SDA =(byte&0x80)>>7;
	byte <<=1;
	delay_us(5);
	I2C_SCL =1;
	delay_us(5);
	I2C_SCL =0;
	delay_us(5);
  }
}

主设备从从设备读取一个字节数据,GPIO模拟:

/**********************************************************************************
 *
 *		Description:I2C(主机)读取一个数据 
 *        parameter:返回读取的数据  
 *
 **********************************************************************************/
u8 I2C_ReadData(void)			   //I2C读取一个数据 
{
  u8 i=0;
  u8 data=0;
  SDA_IN();
  I2C_SCL =0;
  for(i=0;i<8;i++)
  {
	I2C_SCL =1;
	data <<=1;
	data |=(u8)Read_SDA;
	delay_us(5);
	I2C_SCL =0;
	delay_us(5);
  }
  return data;
}
3.2 AT24C02
3.2.1 按字节写
/**********************************************************************************************
 *
 *   function:向24c02的address地址中写入一字节数据  
 *    address:0~0xFF        
 *
 **********************************************************************************************/
void Write24c02(u8 address,u8 byte)
{
   I2C_Start();
   I2C_WriteData(0xA0);
   I2C_WaitAck();
   I2C_WriteData(address);
   I2C_WaitAck();
   I2C_WriteData(byte);
   I2C_WaitAck();
   I2C_Stop();
   delay_ms(10);       //这个延时一定要足够长,否则会出错。因为24c02在从sda上取得数据后,还需要一定时间的烧录过程。
}
3.2.2 随机读
/**********************************************************************************************
 *
 *   function:从24c02的地址address中读取一个字节数据
 *    address:0~0xFF 
 *
 ***********************************************************************************/
u8 Read24c02(u8 address)
{
   u8 byte;
   I2C_Start();
   I2C_WriteData(0xA0);
   I2C_WaitAck(); 
   I2C_WriteData(address);
   I2C_WaitAck();
   I2C_Start();
   I2C_WriteData(0xA1);
   I2C_WaitAck();
   byte=I2C_ReadData();
   //I2C_Ack(0);
   I2C_Stop();
   delay_ms(10);
   return  byte;
}
3.2.3 连续写
/**********************************************************************************************
 *
 *   function:向24c02的address地址中写入字符串
 *    address:24C02的起始地址    0~0xFF 
 *        str:字符串的指针    
 *
 **********************************************************************************************/
void Write_24c02Buffer(u8 address,const u8 *str)
{
     u8 i=0;
	 u8 length=0;
	 length= strlen((char *)str);
	 for(i=0;i<length;i++)
	 {
	   Write24c02(address++,str[i]);
	 }
}
3.2.3 连续读
/**********************************************************************************************
 *
 *   function:从24c02的address地址中读取字符串
 *    address:24c02的起始地址    0~0xFF 
 *        str: 写入的字符串指针	      
 *     length: 写入的数据长度不包含'\0'              
 *
 **********************************************************************************************/
void  Read_24c02Buffer(u8 address,u8 *str,u8 length)
{
  u8 i=0;
  for(i=0;i<length;i++)
  {
   str[i]=Read24c02(address++);
  }
  str[i]='\0';
}
3.3 实现功能

这里我们通过向AT24C02写入并读取来测试I2C功能。

3.3.1 main函数实现
int main()
{ 
   u8 *time;
   char temp[256];
   STM32_Clock_Init(9);         	                //系统时钟初始化

   STM32_NVIC_Init(2,USART1_IRQn,0,1);		        //串口中断优先级初始化,其中包括中断使能
   usart_init(USART_1,115200);				        //串口1初始化,波特率115200 映射到PA9 PA10

   STM32_NVIC_Init(2,RTC_IRQn,0,1);		            //RTC中断优先级初始化,其中包括中断使能
   while(RTC_Init());                               //RTC初始化

   OLED12864_GPIO_Init();                           //GPIO初始化
   OLED12864_Init();                                //OLED初始化
   OLED_P16x8Str(45,0,"OLED");	                    //调用LCD_P8x16Str字符串显示函数,在第0页即第一行的第45列开始,显示字符串“OLED"

   I2C_Init();                                     //I2C初始化

   OLED_P8x16Chi(16,6,"安徽理工大学");
   Write_24c02Buffer(10,"郑洋是好人");             //连续写入
   while(1)
   {   
       Read_24c02Buffer(10,temp,10);
	   printf("打印-----%s\n",temp);
	   time = RTCTime();
       OLED_P8x6Str(8,4,time);      //显示当前时间
	   delay_ms(1000);
   } 
}
3.3.2 测试

编译程序并下载测试,打开串口查看;

下图是使用逻辑分析器捕捉到的模拟I2C信号;

参考文章

[1] I2C—读写EEPROM


http://www.kler.cn/a/459926.html

相关文章:

  • springmvc--请求参数的绑定
  • 让私域用户付费的三个关键要素
  • 电商Google广告:2025年提升转化率的5种策略
  • 代码随想录算法训练营第五十天|图论基础|深度优先搜索理论基础|KM98.所有可达路径|广度优先搜索理论基础
  • 远程主机执行脚本1、无脚本内容外协。
  • 车载通信架构 --- 智能汽车通信前沿技术
  • 如何用Python爬取网站数据:基础教程与实战
  • 【AIGC-ChatGPT职业提示词指令】智能职业规划助手:基于SVG可视化的职业发展指南系统
  • JVM实战—3.JVM垃圾回收的算法和全流程
  • ubuntu18.04使用ndk编译protobuf库
  • Kafka数据迁移全解析:同集群和跨集群
  • 记一次 .NET某电商医药网站 CPU爆高分析
  • MySQL 可重复读隔离级别,完全解决幻读了吗?
  • uniapp 微信小程序开发使用高德地图、腾讯地图
  • Excel基础知识
  • 命令行之巅:Linux Shell编程的至高艺术(中)
  • 加强版十六章视频读写
  • Oracle SqlPlus常用命令简介
  • SDL2音视频播放的常用API库
  • Redis字符串底层结构对数值型的支持常用数据结构和使用场景
  • 安装torch-geometric库
  • 正则表达式:高级应用与性能优化
  • uniapp使用ucharts组件
  • 21天掌握JavaWeb - 第17天:前端页面开发与集成测试
  • leetcode 热题100(78. 子集)dfs回溯 c++
  • #渗透测试#红蓝攻防#红队打点web服务突破口总结02