SPI和IIC协议解析

看了半天还是不会写,只能反复看例程找到规律了。因为手上stm32的例程非常完整,还包括了对将要用到的无线通信模块和mpu6050模块的具体使用,所以就从这里入手开始学习,完了再仔细看看msp430的串口相关的例程,希望能比较顺利吧。。。

一、IIC对24cxx的读写
首先是模拟IIC的源文件,跟以前一模一样的。
然后对24cxx模块的读取的头文件是这样的:

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和模块的读写协议,实现读写功能。

1、读一个字节

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));   
	
	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;
}

24c02是空间为256个字节的存储器,读时序如下:
在这里插入图片描述
即 起始信号——发器件地址+写指令(可以理解为是一个写控制字)——发字节地址——起始信号——发器件地址+读指令(可以理解为一个读控制字)——接收数据——发送非应答——发结束信号

但是看代码,会发现有分支操作,如果器件空间大于2048,即11位可表示的地址时,会先发0xA0,再发高地址,再发低地址,如果器件地址小于等于11位,
则会发0xA+高三位地址+0/1,0/1就是读写位,再发低地址,每发一次都要等待应答,然后发起始信号,然后发0XA1,进入接收模式,等待应答,然后开始读操作并发送非应答信号,然后发终止信号,将读到的字节返回,就完了。

2、写一个字节

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));   
	}	 
	IIC_Wait_Ack();	   
    IIC_Send_Byte(WriteAddr%256);   
	IIC_Wait_Ack(); 	 										  		   
	IIC_Send_Byte(DataToWrite);   
	IIC_Wait_Ack();  		    	   
    IIC_Stop();
	delay_ms(10);	 
}

写时序如下图:
在这里插入图片描述
起始信号——器件地址+0(写控制字)——发字节地址——发数据——终止信号

相对简单一点,发地址和写指令的操作跟上面是一样的,写完地址可以进接着写数据进去,然后发送终止信号,就没了。

3、读写指定长度的字节
这个就比较简单,重复以上操作就可以,不说了

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);
	}												    
}




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;												    
}

4、进行指定个数量的字节的读写
也比较简单,就是循环调用读单个字节的函数就可以。

void AT24CXX_Read(u16 ReadAddr,u8 *pBuffer,u16 NumToRead)
{
	while(NumToRead)
	{
		*pBuffer++=AT24CXX_ReadOneByte(ReadAddr++);	
		NumToRead--;
	}
}  



void AT24CXX_Write(u16 WriteAddr,u8 *pBuffer,u16 NumToWrite)
{
	while(NumToWrite--)
	{
		AT24CXX_WriteOneByte(WriteAddr,*pBuffer);
		WriteAddr++;
		pBuffer++;
	}
}

5、在使用中,可以直接把字符串写进去,因为一个字符实际上是一个八位ascii码,保存在存储器中,读取出来利用显示模块可以直接将字符显示出来。

二、利用SPI与W25Q128进行通信
这个就比24cxx复杂一些,光指令表就一大串,虽然会用到的可能不会很多,但总之就是看起来很厉害。。。

spi部分一样,没什么内容,重点还是怎么结合通信协议编写源文件。
头文件是这样的:

void W25QXX_Init(void);
u16  W25QXX_ReadID(void);  	
u8	 W25QXX_ReadSR(void);        
void W25QXX_Write_SR(u8 sr);  		
void W25QXX_Write_Enable(void);  	
void W25QXX_Write_Disable(void);	
void W25QXX_Write_NoCheck(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite);
void W25QXX_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead);  
void W25QXX_Write(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite);
void W25QXX_Erase_Chip(void);    	 
void W25QXX_Erase_Sector(u32 Dst_Addr);	
void W25QXX_Wait_Busy(void);         
void W25QXX_PowerDown(void);     
void W25QXX_WAKEUP(void);		

看的脑壳疼。。。。看模块介绍:

每个sector是4K,也就是4096个地址, 在写任何一个地址之前,如果该地址的值不是0xFF,必须先擦除对应的sector,然后再写。

根据要写的起始地址,确定要写的起始区域的Sector号以及在起始 sector中的偏移量。

根据要写的起始地址和字节数,确定要写的数据是否跨sector。 确定好要操作的sector以及sector的地址范围。

对每一个sector,先遍历要写的地址区域保存的数据是不是0xff,如果都是,就不用擦除。如果有不是0xff的区域,先读出里面的数据,保存在缓存W25QXX_BUFFER,然后擦除里面的内容。然后把这个sector要操作的数据,写到缓存。最后一次性吧缓存W25QXX_BUFFER的数据写到这个对应的sector。

1、先readID,就是读器件型号

u16 W25QXX_ReadID(void)
{
	u16 Temp = 0;	  
	W25QXX_CS=0;				    
	SPI2_ReadWriteByte(0x90);          //发送读取ID命令
	SPI2_ReadWriteByte(0x00); 	    
	SPI2_ReadWriteByte(0x00); 	    
	SPI2_ReadWriteByte(0x00); 	 			   
	Temp|=SPI2_ReadWriteByte(0xFF)<<8;  
	Temp|=SPI2_ReadWriteByte(0xFF);	 
	W25QXX_CS=1;				    
	return Temp;
}   		    

发送读取ID的控制字,然后交换三个字节之后进行数据读取。

2、
读取器件的状态寄存器

u8 W25QXX_ReadSR(void)   
{  
	u8 byte=0;   
	W25QXX_CS=0;                       
	SPI2_ReadWriteByte(W25X_ReadStatusReg);   //读取状态寄存器控制字
	byte=SPI2_ReadWriteByte(0Xff);   
	W25QXX_CS=1;                
	return byte;   
} 

这个却不需要交换三个空字节,可能这个比较特殊。

写状态寄存器

void W25QXX_Write_SR(u8 sr)   
{   
	W25QXX_CS=0;                  
	SPI2_ReadWriteByte(W25X_WriteStatusReg);  //相应的控制字
	SPI2_ReadWriteByte(sr);             
	W25QXX_CS=1;                         
}   

也不需要交换空字节。

3、
w25Qxx写使能:

void W25QXX_Write_Enable(void)   
{
	W25QXX_CS=0;                      
    SPI2_ReadWriteByte(W25X_WriteEnable); 	 //相应的控制字
	W25QXX_CS=1;                   
} 

写禁止:

void W25QXX_Write_Disable(void)   
{  
	W25QXX_CS=0;                
    SPI2_ReadWriteByte(W25X_WriteDisable);  
	W25QXX_CS=1;                        
} 		

仍然不需要交换空字节,难道第一个才比较特殊?。。。

4、从指定地址读取指定长度的字节

void W25QXX_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead)   
{ 
 	u16 i;   										    
	W25QXX_CS=0;                       
    SPI2_ReadWriteByte(W25X_ReadData);        //发送读取命令
    SPI2_ReadWriteByte((u8)((ReadAddr)>>16));  	   //高8位地址
    SPI2_ReadWriteByte((u8)((ReadAddr)>>8));     //中8位地址
    SPI2_ReadWriteByte((u8)ReadAddr);     //低8位地址,总共发了24位地址
    for(i=0;i<NumByteToRead;i++)
	{ 
        pBuffer[i]=SPI2_ReadWriteByte(0XFF);   
    }
	W25QXX_CS=1;  				    	      
}  

发完控制字,接着三次发送是地址,再接着是数据。器件内部数据指针会自动++。

5、SPI在一页(0~65536)内写入少于256个字节的数据

void W25QXX_Write_Page(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)
{
 	u16 i;  
    W25QXX_Write_Enable();               
	W25QXX_CS=0;                      
    SPI2_ReadWriteByte(W25X_PageProgram);    
    SPI2_ReadWriteByte((u8)((WriteAddr)>>16)); 
    SPI2_ReadWriteByte((u8)((WriteAddr)>>8));   
    SPI2_ReadWriteByte((u8)WriteAddr);   
    for(i=0;i<NumByteToWrite;i++)SPI2_ReadWriteByte(pBuffer[i]);
	W25QXX_CS=1;                         
	W25QXX_Wait_Busy();					   
} 

同样,传入控制字,然后三次地址传输,接着不断向缓冲器写入数据,期间内部地址指针会自动++的。

6、无检验写flash,在指定地址开始写入数据,具有自动换页功能。
必须确保所写地址内全部为0xFF,否则会写入失败。

void W25QXX_Write_NoCheck(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)   
{ 			 		 
	u16 pageremain;	   
	pageremain=256-WriteAddr%256;
	if(NumByteToWrite<=pageremain)pageremain=NumByteToWrite;
	while(1)
	{	   
		W25QXX_Write_Page(pBuffer,WriteAddr,pageremain);
		if(NumByteToWrite==pageremain)break;
	 	else
		{
			pBuffer+=pageremain;
			WriteAddr+=pageremain;	

			NumByteToWrite-=pageremain;		
			if(NumByteToWrite>256)pageremain=256; 
			else pageremain=NumByteToWrite; 	 
		}
	};	    
} 

逻辑不是很直接,但多看两遍就看明白了,不说了。。。

7、擦除
擦除整个芯片:

void W25QXX_Erase_Chip(void)   
{                                   
    W25QXX_Write_Enable();            
    W25QXX_Wait_Busy();   
  	W25QXX_CS=0;                 
    SPI2_ReadWriteByte(W25X_ChipErase);        
	W25QXX_CS=1;                          
	W25QXX_Wait_Busy();   				   	
}   

擦除一个扇区:

void W25QXX_Erase_Sector(u32 Dst_Addr)   
{  
	
 	printf("fe:%x\r\n",Dst_Addr);	  
 	Dst_Addr*=4096;
    W25QXX_Write_Enable();                  	
    W25QXX_Wait_Busy();   
  	W25QXX_CS=0;                          
    SPI2_ReadWriteByte(W25X_SectorErase);      
    SPI2_ReadWriteByte((u8)((Dst_Addr)>>16));  
    SPI2_ReadWriteByte((u8)((Dst_Addr)>>8));   
    SPI2_ReadWriteByte((u8)Dst_Addr);  
	W25QXX_CS=1;                           
    W25QXX_Wait_Busy();   				   		
}  

写控制字,写地址(三个),然后就自动擦了。。。

8、写入指定长度的数据

u8 W25QXX_BUFFER[4096];		 
void W25QXX_Write(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)   
{ 
	u32 secpos;
	u16 secoff;
	u16 secremain;	   
 	u16 i;    
	u8 * W25QXX_BUF;	  
   	W25QXX_BUF=W25QXX_BUFFER;	     
 	secpos=WriteAddr/4096;
	secoff=WriteAddr%4096;
	secremain=4096-secoff;
 	//printf("ad:%X,nb:%X\r\n",WriteAddr,NumByteToWrite);
 	if(NumByteToWrite<=secremain)secremain=NumByteToWrite;
	while(1) 
	{	
		W25QXX_Read(W25QXX_BUF,secpos*4096,4096);
		for(i=0;i<secremain;i++)
		{
			if(W25QXX_BUF[secoff+i]!=0XFF)break;  	  
		}
		if(i<secremain)
		{
			W25QXX_Erase_Sector(secpos);		
			for(i=0;i<secremain;i++)	  
			{
				W25QXX_BUF[i+secoff]=pBuffer[i];	  
			}
			W25QXX_Write_NoCheck(W25QXX_BUF,secpos*4096,4096);

		}else W25QXX_Write_NoCheck(pBuffer,WriteAddr,secremain);
		
		if(NumByteToWrite==secremain)break;
		else
		{
			secpos++;
			secoff=0;	 

		   	pBuffer+=secremain;  				
			WriteAddr+=secremain;			
		   	NumByteToWrite-=secremain;			
			if(NumByteToWrite>4096)secremain=4096;
			else secremain=NumByteToWrite;		
		}	 
	};	 
}

这个是最头大的。。。大概就是把要写的区域读出来,有非0xFF的就把这个扇区清空,然后写入数据,最后就是把该写的都写进去。。。不说了,很头大。

先到这里,晚上看那两个更加让人头大的模块。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值