cortex_m3_stm32嵌入式学习笔记(二十):IIC实验(I2C串行总线)

ARM 532浏览
IIC(Inter- Integrated Circuit)总线是一种由 PHILIPS 公司开发的两线式串行总线,用于连接微控制器及其外围设备。它是由数据线SDA 和时钟SCL构成的串行总线,可发送和接收数据。在 CPU 与被控 IC 之间、 IC 与 IC 之间进行双向传送, 高速 IIC 总线一般可达 400kbps 以上。

ALIENTEK MiniSTM32 开发板板载的 EEPROM 芯片型号为 24C02。该芯片的总容量是 256个字节,该芯片通过 IIC 总线与外部连接,我们本章就通过 STM32 来实现 24C02 的读写。目前大部分 MCU 都带有 IIC 总线接口, STM32 也不例外。但是这里我们不使用 STM32的硬件 IIC 来读写 24C02,而是通过软件模拟。STM32 的硬件 IIC 非常复杂,更重要的是不稳定,故不推荐使用。所以我们这里就通过模拟来实现了。有兴趣的读者可以研究一下 STM32的硬件 IIC。

科普EEPROM,或写作E2PROM,全称电子抹除式可复写只读存储器 (英语:Electrically-Erasable Programmable Read-Only Memory),是一种可以通过电子方式多次复写的半导体存储设备,可以在电脑上或专用设备上擦除已有信息,重新编程。相比EPROM,EEPROM不需要用紫外线照射,也不需取下,就可以用特定的电压,来抹除芯片上的信息,以便写入新的数据。

I2C 总线在传送数据过程中共有三种类型信号, 它们分别是:开始信号、结束信号和应答信号。
开始信号: SCL 为高电平时, SDA 由高电平向低电平跳变,开始传送数据。
结束信号: SCL 为高电平时, SDA 由低电平向高电平跳变,结束传送数据。
应答信号:接收数据的 IC 在接收到 8bit 数据后,向发送数据的 IC 发出特定的低电平脉冲,表示已收到数据。 CPU 向受控单元发出一个信号后,等待受控单元发出一个应答信号, CPU 接收到应答信号后,根据实际情况作出是否继续传递信号的判断。若未收到应答信号,由判断为受控单元出现故障。

这些信号中,起始信号是必需的,结束信号和应答信号,都可以不要。

24C02 的 SCL 和 SDA 分别连在 STM32 的 PC12 和 PC11 上的,连接关系如图24.2.1 所示:

     
在工程中添加两个源文件分别是 myiic.c 和 24cxx.c,myiic.c 文件存放 iic 驱动代码, 24cxx.c 文件存放 24C02 驱动代码

myiic.c

#include "24cxx.h" //初始化IIC接口 void AT24CXX_Init(void) { 	IIC_Init(); } //在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);	  } //在AT24CXX里面的指定地址开始写入长度为Len的数据 //该函数用于写入16bit或者32bit的数据. //WriteAddr  :开始写入的地址   //DataToWrite:数据数组首地址 //Len        :要写入数据的长度2,4 void AT24CXX_WriteLenByte(u16 WriteAddr,u32 DataToWrite,u8 Len) {  	 	u8 t; 	for(t=0;t<Len;t++) 	{ 		AT24CXX_WriteOneByte(WriteAddr+t,(DataToWrite>>(8*t))&0xff); 	}												     }  //在AT24CXX里面的指定地址开始读出长度为Len的数据 //该函数用于读出16bit或者32bit的数据. //ReadAddr   :开始读出的地址  //返回值     :数据 //Len        :要读出数据的长度2,4 u32 AT24CXX_ReadLenByte(u16 ReadAddr,u8 Len) {  	 	u8 t; 	u32 temp=0; 	for(t=0;t<Len;t++) 	{ 		temp<<=8; 		temp+=AT24CXX_ReadOneByte(ReadAddr+Len-t-1); 	 				    	} 	return temp;												     } //检查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;											   }  //在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++; 	} }

myiic.h

#ifndef __MYIIC_H #define __MYIIC_H #include "sys.h" #include "delay.h" //IO方向设置 #define SDA_IN()  {GPIOC->CRH&=0XFFFF0FFF;GPIOC->CRH|=8<<12;} #define SDA_OUT() {GPIOC->CRH&=0XFFFF0FFF;GPIOC->CRH|=3<<12;} //IO操作函数	  #define IIC_SCL    PCout(12)//SCL #define IIC_SDA    PCout(11)//SDA	  #define READ_SDA   PCin(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

24cxx.c

#include "24cxx.h" //初始化IIC接口 void AT24CXX_Init(void) { 	IIC_Init(); } //在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);	  } //在AT24CXX里面的指定地址开始写入长度为Len的数据 //该函数用于写入16bit或者32bit的数据. //WriteAddr  :开始写入的地址   //DataToWrite:数据数组首地址 //Len        :要写入数据的长度2,4 void AT24CXX_WriteLenByte(u16 WriteAddr,u32 DataToWrite,u8 Len) {  	 	u8 t; 	for(t=0;t<Len;t++) 	{ 		AT24CXX_WriteOneByte(WriteAddr+t,(DataToWrite>>(8*t))&0xff); 	}												     }  //在AT24CXX里面的指定地址开始读出长度为Len的数据 //该函数用于读出16bit或者32bit的数据. //ReadAddr   :开始读出的地址  //返回值     :数据 //Len        :要读出数据的长度2,4 u32 AT24CXX_ReadLenByte(u16 ReadAddr,u8 Len) {  	 	u8 t; 	u32 temp=0; 	for(t=0;t<Len;t++) 	{ 		temp<<=8; 		temp+=AT24CXX_ReadOneByte(ReadAddr+Len-t-1); 	 				    	} 	return temp;												     } //检查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;											   }  //在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++; 	} }

24cxx.h

#ifndef __24CXX_H #define __24CXX_H #include "myiic.h"   #include "delay.h" #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   #define EE_TYPE AT24C02 u8 AT24CXX_ReadOneByte(u16 ReadAddr);//指定地址读取一个字节 void AT24CXX_WriteOneByte(u16 WriteAddr,u8 DataToWrite);//指定地址写入一个字节 void AT24CXX_WriteLenByte(u16 WriteAddr,u32 DataToWrite,u8 Len);//指定地址开始写入指定长度的数据 u32 AT24CXX_ReadLenByte(u16 ReadAddr,u8 Len);//指定地址开始读取指定长度数据 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

代码有点长。。但其实配好了用到一般只有读和写两个函数如下:

//在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++; 	} }

参数解释的很明白也很好理解。。

主函数

#include "sys.h" #include "delay.h" #include "usart.h" #include "led.h" #include "lcd.h" #include "key.h" #include "myiic.h" #include "24cxx.h" const u8 TEXT_Buffer[]={"MY IIC TEST"}; #define SIZE sizeof(TEXT_Buffer) void init(void) { 	NVIC_Configuration(); 	delay_init(); 	uart_init(9600); 	LED_Init(); 	KEY_Init(); 	LCD_Init(); 	AT24CXX_Init(); 	POINT_COLOR=RED;//设置字体为红色  	LCD_ShowString(60,40,200,24,24,"IIC TEST");	 	LCD_ShowString(60,70,200,16,16,"~~~~~~~~~");	 	LCD_ShowString(60,90,200,16,16,"BY---yh"); 	LCD_ShowString(60,110,200,16,16,"2015/1/26");	 	LCD_ShowString(60,130,200,16,16,"WK_UP:Write  KEY0:Read");	 	while(AT24CXX_Check())//检测不到24c02 	{ 		LCD_ShowString(60,150,200,16,16,"24C02 Check Failed!"); 		delay_ms(500); 		LCD_ShowString(60,150,200,16,16,"Please Check!      "); 		delay_ms(500); 		LED0=!LED0;//DS0闪烁 	} 	LCD_ShowString(60,150,200,16,16,"24C02 Ready!"); 	POINT_COLOR=BLUE; } int main(void) { 	u8 key; 	u16 i=0; 	u8 datatemp[SIZE]; 	init(); 	while(1) 	{ 		key=KEY_Scan(0); 		if(key==WK_UP_PRES)//写 		{ 			LCD_Fill(0,170,239,319,WHITE);//清除半屏      			LCD_ShowString(60,170,200,16,16,"Start Write 24C02...."); 			AT24CXX_Write(0,(u8*)TEXT_Buffer,SIZE); 			LCD_ShowString(60,170,200,16,16,"24C02 Write Finished!");//提示传送完成 		} 		if(key==KEY0_PRES)//KEY0 按下,读取字符串并显示 		{  			LCD_ShowString(60,170,200,16,16,"Start Read 24C02.... "); 			AT24CXX_Read(0,datatemp,SIZE); 			LCD_ShowString(60,170,200,16,16,"The Data Readed Is:  ");//提示传送完成 			LCD_ShowString(60,190,200,16,16,datatemp);//显示读到的字符串 		} 		i++; 		delay_ms(10); 		if(i==20) 		{ 			LED0=!LED0; 			i=0; 		} 	} }