1. 程式人生 > >【STM庫應用】stm32 之 IIC應用

【STM庫應用】stm32 之 IIC應用

iic協議是比較簡單的雙線協議,時鐘線CLK和資料線SDA。

一般我們常見的還有spi匯流排,這種匯流排可以可以根據需要擴充套件,還有單匯流排等等

這次還以at240c2為例進行操作!


PS:這就是傳說中的iic時序圖

硬體構造我們不過多的分析,今天用到庫了!我們先從庫函式硬體iic初始化說起!

PB6   --   CLK

PB7   --   SDA

void i2c_init(u8 addr,u32 clock)
{
	I2C_InitTypeDef i2c;
	RCC->APB2ENR |= 1<<3;
	GPIOB->CRL |= (u32)0xff<<(6*4);
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);
	i2c.I2C_Ack = I2C_Ack_Enable;
	i2c.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
	i2c.I2C_ClockSpeed = clock*1000;
	i2c.I2C_DutyCycle = I2C_DutyCycle_2;
	i2c.I2C_Mode = I2C_Mode_I2C;
	i2c.I2C_OwnAddress1 = addr;

	I2C_Cmd(I2C1,ENABLE);
	I2C_Init(I2C1,&i2c);
}
在配置管腳方面,我還是喜歡用暫存器配置,因為我的兩行程式碼可以解決庫函式的N多行程式碼的問題!

還有在結構體變數命名方面也是屬於我自己的獨創吧,這樣反正我覺得是既容易識別,也少打幾個字!

typedef struct
{
  uint32_t I2C_ClockSpeed; //I2C時鐘頻率設定
  uint16_t I2C_Mode;             //I2C模式設定
  uint16_t I2C_DutyCycle;     //高低電平時間之比
  uint16_t I2C_OwnAddress1;      //主裝置地址設定,也就是自己的地址
  uint16_t I2C_Ack;                 //Check
  uint16_t I2C_AcknowledgedAddress; //地址長度,可以為7bit的也可以為10bit的
}I2C_InitTypeDef;

IIC初始化完之後,我們開始來研究eeprom


看完這個寫一個位元組的協議之後,我們應該對這個寫已經沒有什麼問題了,很簡單的。


這個是寫一個page

注:在eeprom裡面寫資料時,一次最多隻能寫一個page,一個page為8byte,同時這個也有位元組對齊的要求!


比如我們從Address = 4開始寫,那麼我們最多一次性可寫4個byte,如果我們從8開始寫的話,我們就可以8個byte,最後偏移到15。

void eeprom_write_byte(u8 wt_addr,u8 data)
{

	I2C_GenerateSTART(I2C1,ENABLE);delay(5);
	I2C_Send7bitAddress(I2C1,EEPROM_ADDR,I2C_Direction_Transmitter);
	while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); 
	I2C_SendData(I2C1,wt_addr);delay(5);
	I2C_SendData(I2C1,data);delay(5);
	I2C_GenerateSTOP(I2C1, ENABLE);
	delay(20);
}

由於stm32的i2c確實做的不怎麼樣,標著暫存器太多,也不容易識別,我們就不要檢測這些標誌暫存器,用延時了把他們隔離了。不過在把地址傳送出去之後,要檢測裝置是否被選中,這個在我們的模擬的i2c裡面也是必須檢測的!可以認為是必不可少的!
void eeprom_write_page(u8 wt_addr,u8 *buff,u32 length)
{
	int i = 0;
	I2C_GenerateSTART(I2C1,ENABLE);delay(5);
	I2C_Send7bitAddress(I2C1,EEPROM_ADDR,I2C_Direction_Transmitter);
	while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); 
	I2C_SendData(I2C1,wt_addr);delay(5);
	for(i=0; i<length; i++)
	{
		I2C_SendData(I2C1,buff[i]);delay(5);
	}
	I2C_GenerateSTOP(I2C1, ENABLE);
}

這個是寫一個page的函式,如果大家想看比較靈活的寫函式,去我看我前面發表的部落格裡面找找,那個無論你寫幾個都無所謂,只要不超過eeprom的大小!


讀資料是稍稍複雜一點點的,我們首先要選中裝置,然後選擇我們要操作的地址,這時候不要stop,如果stop訊號一發出,匯流排就被釋放掉了,裝置也就跟處理器斷開,所以這裡需要一個RSTART,跟START不一樣,多了個R,這個可以理解為重新開始,這個訊號不會選中其他裝置,也不會丟失當前裝置。

然後還有個注意點是,在讀完第N個位元組後,不要返回迴應,直接stop,不然裝置會以為你沒有結束,會一直佔據匯流排,等待下一個資料的傳送,這樣等你下一次來訪問他的時候,他就不讓你訪問了,因為他還停留在給你傳資料的狀態,所以這裡一定不要返回acK直接stop訊號發出哦!

unsigned char eeprom_read_byte(u8 rd_addr)
{
	u8 temp = 0;

	I2C_GenerateSTART(I2C1,ENABLE);delay(5);
	
	I2C_Send7bitAddress(I2C1,EEPROM_ADDR,I2C_Direction_Transmitter);
	while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
	
	I2C_Cmd(I2C1, ENABLE);
	I2C_SendData(I2C1,rd_addr);delay(10);
	I2C_GenerateSTART(I2C1,ENABLE);delay(5);
	
	I2C_Send7bitAddress(I2C1,EEPROM_ADDR,I2C_Direction_Receiver);
	while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));
	
	I2C_AcknowledgeConfig(I2C1, DISABLE);
	while(!(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED)));
	temp = I2C_ReceiveData(I2C1);delay(5);
	
	I2C_GenerateSTOP(I2C1, ENABLE);
	delay(20);
	I2C_AcknowledgeConfig(I2C1, ENABLE);

	return temp;
}

還是有幾個訊號是必須確認的,裝置地址傳送出去,看裝置是否有迴應!

這裡最後一個NOACK必須在傳送最後一個位元組前使能,在stop訊號發出後,記得吧ACK訊號重新使能,因為我們剛剛開始是需要ack的,只是最後有時候不需要!


對於資料的讀,在所讀資料長度上,是沒有要求的,也沒有page限制,想讀多少,讀多少!

void eeprom_read(u8 rd_addr,u8 *buff,u32 length)
{
	int i = 0;
	I2C_GenerateSTART(I2C1,ENABLE);delay(5);
	I2C_Send7bitAddress(I2C1,EEPROM_ADDR,I2C_Direction_Transmitter);
	while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
	I2C_Cmd(I2C1, ENABLE);
	I2C_SendData(I2C1,rd_addr);delay(20);
	I2C_GenerateSTART(I2C1,ENABLE);delay(5);
	I2C_Send7bitAddress(I2C1,EEPROM_ADDR,I2C_Direction_Receiver);
	while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));
	for(i=0; i<length; i++)
	{
		if(i == length-1)
			I2C_AcknowledgeConfig(I2C1, DISABLE);
		while(!(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED)));
		buff[i] = I2C_ReceiveData(I2C1);delay(5);
	}
	I2C_GenerateSTOP(I2C1, ENABLE);
	I2C_AcknowledgeConfig(I2C1, ENABLE);
}


PS:在提一下,在接收最後一個數據之前,必須關閉ACK,不然,。。。後果很嚴重!