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,不然,。。。后果很嚴重!
- 前言
- 【菜鳥入門】stm32的第一個程序--LED
- 【菜鳥入門】stm32 之 掃描按鍵
- 【菜鳥入門】stm32 之 中斷按鍵
- 【菜鳥入門】stm32 之 USART
- 【菜鳥入門】stm32 之 iic
- 【菜鳥入門】stm32 之 eeprom
- 【菜鳥入門】stm32 之 pwm
- 【菜鳥入門】stm32 之 ADC 模數轉換
- 【菜鳥入門】stm32 之 實時時鐘
- 【菜鳥入門】stm32 之 DMA
- 【菜鳥入門】stm32 之 DAC
- 【STM庫應用】stm32 之 USART
- 中斷源去抖辦法
- stm32 啟動代碼應用技巧
- 【STM庫應用】stm32 之 IIC應用
- 【STM庫應用】stm32 之 中斷按鍵初始化(注意事項)
- 關于結構體初始化
- 【STM庫應用】stm32 之 TIM (詳解一 通用定時器)
- 【STM庫應用】stm32 之 TIM (詳解二 脈沖寬度、周期測量)
- 【stm32庫應用】SD驅動移植(基于SDIO外設)
- SD卡fat文件系統移植
- stm32 DMA初始化選項研究
- stm32 靈活靜態存儲控制器(FSMC)(NORFLASH\PSRAM)
- 【stm32+uC/OS-II】ucosii移植簡單詳細步驟
- STM32 加入調試信息來調試代碼
- NRF24L01 無線通信模塊使用