【STM庫應用】stm32 之 IIC應用
iic協議是比較簡單的雙線協議,時鐘線CLK和資料線SDA。
一般我們常見的還有spi匯流排,這種匯流排可以可以根據需要擴充套件,還有單匯流排等等
這次還以at240c2為例進行操作!
PS:這就是傳說中的iic時序圖
硬體構造我們不過多的分析,今天用到庫了!我們先從庫函式硬體iic初始化說起!
PB6 -- CLK
PB7 -- SDA
在配置管腳方面,我還是喜歡用暫存器配置,因為我的兩行程式碼可以解決庫函式的N多行程式碼的問題!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); }
還有在結構體變數命名方面也是屬於我自己的獨創吧,這樣反正我覺得是既容易識別,也少打幾個字!
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,不然,。。。後果很嚴重!