stm32模擬iic——引腳配置、代碼


我的工程里要用到iic總線擴展rom,stm32是有硬件iic的,但是,網上有很多人說這個硬件iic有漏洞,甚至於有bug。http://bbs.21ic.com/icview-184741-1-1.html http://blog.gkong.com/more.asp?name=zjcsharp&id=112878。《例說stm32》的表述是:“非常復雜,不太好用”。那么我判斷這個硬件iic可能確實有不足,因此選擇直接用軟件模擬出iic。

在做的過程中,遇到幾個問題,記錄下來。

1、引腳的模式與配置

iic的兩個引腳SDA與SCL都要求既能輸出又能輸入。這對stm32來說問題不大,由參考手冊給出的圖來看,引腳是始終連着IDR寄存器的,另外“輸出配置”一節還特意講到,“在開漏模式時,對輸入數據寄存器的讀訪問可得到I/O狀態”。所以,模式的問題很好解決。

SDA線是由不同的器件分時控制的,這就造成一個問題:當一個器件主動置高或者置低時,若另一個器件若發出相反的電平,會短路。

這就決定將引腳配置成推挽,有很多麻煩事。alientek就是這么做的,他在主機(單片機)控制SDA線時,將其SDA引腳配置成推挽輸出;從機(EEPROM)控制SDA線時,將單片機的引腳配置成上拉/下拉輸入,用頻繁的配置切換來避免這個問題。

我覺得這么做太麻煩,stm32有一個開漏的配置,它與推挽有點像,但也不完全一樣,手冊里這么說:

開漏模式:輸出寄存器上的’0’激活N-MOS,而輸出寄存器上的’1’將端口置於高阻狀態(P-MOS從不被激活)。
推挽模式:輸出寄存器上的’0’激活N-MOS,而輸出寄存器上的’1’將激活P-MOS。

這樣一來,問題就很好解決了:當單片機的SDA引腳置低時,SDA線被拉低,當單片機的SDA引腳置高時,實際上引腳是浮空的,SDA線通過上拉電阻被VCC拉高(iic的兩條線都要通過上拉電阻接到VCC,典型接法),這樣就不會出現短路的狀況。很巧妙。

2、邏輯與時序

邏輯與時序的問題iic相關手冊,尤其是EEPROM的手冊上都有詳細的介紹,這里主要從編程的角度來討論這個問題。

先附上代碼:

void IicStart()
{

	SDA_H;
	SCL_H;
	delay_us(5);
	SDA_L;
	delay_us(5);
	SCL_L;			  //­

	delay_us(2);	  //
	SDA_H;
	delay_us(2);
}

void IicStop()
{
	SDA_L;
	SCL_L;
	delay_us(2);

	SCL_H;
	delay_us(2);
	SDA_H;			  //
	delay_us(2);
	SCL_L;			   
	
	delay_us(2);		//
}
void IicAck()  //
{
	SCL_L;
	SDA_L;
	delay_us(5);

	SCL_H;
	delay_us(5);   //
	SCL_L;

	delay_us(2);
	SDA_H;
	delay_us(2);
}

void IicNack()  //
{
	SCL_L;
	SDA_H;
	delay_us(5);

	SCL_H;
	delay_us(5);   //
	SCL_L;

	delay_us(2);
	SDA_H;
	delay_us(2);
}
void IicWaiteAck()
{	
	SCL_L;
	SDA_H;
	delay_us(5);

	SCL_H;
	delay_us(5);
	SCL_L;

	delay_us(2);
}
void IicSendByte(u8 temp)
{
	u8 i;

	for(i=0;i<8;i++)
	{
		SCL_L;
		delay_us(5);
		if(temp&0x80)           //MSB在前
  			SDA_H;
		else
			SDA_L;
		SCL_H;
		delay_us(5);
		SCL_L;
		temp<<=1;
	}
	
	delay_us(2);
	SDA_H;
	delay_us(2);
		
}

u8 IicReceiveByte()
{
	u8 i,temp=0;

	delay_us(2);
	SDA_H;      			//
	delay_us(2);           

	for(i=0;i<8;i++)
	{
		temp<<=1;
		SCL_L;
		delay_us(5);
		SCL_H;
		delay_us(2);

		if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_7))
			temp=temp|0x01;
		else
			temp=temp&0xFE;	
	}

	SCL_L;
	delay_us(2);
	return temp;	
}


發送8個字節:

	IicStart();
	IicSendByte(0xA0);            //尋址,發送寫命令
   	IicWaiteAck();
	IicSendByte(0x00);		   //字節地址,24LC02里首先寫入的是指針,指向0x00字節單元
	IicWaiteAck();
	for(i=0;i<8;i++)
	{
		IicSendByte(0xa0+i);			//寫完了指針就是按指針的指向寫數據
		IicWaiteAck();
	}
	IicStop();
	delay_ms(4);


接收字節:

	IicStart();
	IicSendByte(0xA0);        //尋址,發送寫命令(先要寫指針)          
	IicWaiteAck();
	IicSendByte(0x00);       //寫指針
	IicWaiteAck();
	IicStart();		//再次發出起始條件		 
	IicSendByte(0xA1);	//這次是尋址和讀命令
	for(i=0;i<2;i++)        //24LC會根據寫好的指針,將相應數據發送在SDA上
	{
		IicAck();
		*((u8 *)(&lxj)+i)=IicReceiveByte();
	}
	IicNack();
	IicStop();

 

在發送START,ACK,NACK,STOP,發送一個字節時,以及接收ACK,接收字節時,每個函數返回,都保證SCL為低,即一個信號位結束;並且SDA為高(浮空),以保證SDA線可以被其他器件控制。

另外還要注意電平高低之間要有適當的延時。我這里微秒級的延時是為了產生有效的iic信號,這些延時是我隨意加進去的,可以用,但不知道能不能減少一點;毫秒級的延時是為了等待24LC02將數據從緩沖區寫入ROM,這個時間與寫入的字節數無關,至少要3ms,否則出錯。

24LC02每次最多寫入8個字節,讀出沒有限制。

3、EEPROM的頁

24LC02有頁的概念,在寫時,先寫入緩沖區,緩沖區大小是一頁(8字節,64位),在頁寫時,先寫入緩沖區,待總線上出現STOP信號時,將緩沖區寫到相應的ROM中。這就要求:不能跨頁寫。以下是網上摘錄的一點,我覺得自己對這個東西的理解還不全面,網上的資料不多。

AT24CXX系列的EEPROM為了提高寫效率,提供了頁寫功能,內部有個一頁大小的寫緩沖RAM,地址范圍當然就是從00到一頁大小,發生寫操作時,開始送入的地址對應的頁被選中,並將其內容映像到緩沖RAM,數據從低端地址對應的緩沖RAM地址開始修改,超過這個地址范圍就回到00,寫完后,就會把開始確定的EEPROM頁擦除,再把一整頁RAM數據寫入。所有寫數據都發生在開始寫地址時確定的頁上。
如頁容量為128,一頁都是從00開始按128字節分成一個個的頁,0頁就是0~7F,1頁就是80~FF,類推,邊界就是128字節的整數倍地址。頁RAM的地址范圍為7位00~7F,寫入時高端地址就是頁號。發生寫操作,開始送入的地址對應的頁被鎖存,后續不論寫多少,都在這個頁中,只是一個頁內的地址進行加一,超過就歸零開始。從F0開始寫32個字節,那么開始送入的地址為F0,就會鎖定在1號頁(第2個頁)上,底端7位頁內部地址開始從70H開始寫,到達7F時回到00再到10H,也就是寫在了F0~FF,80~8F。也就是,從01開始寫也只能到7F,再往80寫就跑到00上去了,這就是寫操作的翻卷,datasheet上都有說明。就是從邊界前寫兩個字節也要分兩次寫。頁是絕對的,按整頁大小排列,不是從開始寫入的地址開始算。
讀沒有頁的問題,可以從任意地址開始讀取任意大小數據,只是超過整個存儲器容量時地址才回卷。但一次性訪問的數據長度也不要太大。
所以分頁的存儲器要做好存儲器管理,盡量同時讀寫的數據放在一個頁上。

4、其他

在我這個項目里,iic是不常用的,當電網發生故障時,才做記錄。所以,引腳無需長期配置在開漏輸出,根據st的官方推薦,在不使用iic時,將引腳配置成浮空輸入。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM