剛接觸STM32的時候,第一個學(xué)習(xí)的就是I2C,當(dāng)時去網(wǎng)上學(xué)習(xí)別人寫得I2C代碼,雖然能用,但是當(dāng)時并不理解為什么要這么配置,特別希望有人把代碼掰碎了講講看,今天突然想起來,就把以前寫的I2C代碼拿出來掰碎了捋捋,希望對新手有些幫助。
先說說STM32的I2C:
ST的M3系列還有M4系列的I2C基本上是一致的,但是到M0系列以后,I2C的設(shè)計是重新修改過的,所以用起來會比M3和M4系列的好用很多,前面的文章有詳細(xì)描述過STM32F103的I2C的硬件缺陷,有興趣的可以看看,接下來講講M0系列的硬件I2C。
從我的使用體驗上來說,M0系列的I2C用起來比STM32F103的體驗感強(qiáng)太多了,少了很多紛繁復(fù)雜的EVENT,通信的流程簡化了很多,先看看初始化的代碼:
void IIC_Init(void)
{
I2C_InitTypeDef I2C_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
//I2C時鐘使能
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOC,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5|GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //復(fù)用模式
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;// GPIO_PuPd_NOPULL
GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOC,GPIO_PinSource5,GPIO_AF_0);//SDA
GPIO_PinAFConfig(GPIOC,GPIO_PinSource6,GPIO_AF_0);//SCL
//IIC配置
I2C_InitStructure.I2C_Timing=0x00200002;//0x00210507
I2C_InitStructure.I2C_AnalogFilter=I2C_AnalogFilter_Enable;
I2C_InitStructure.I2C_DigitalFilter=4;
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C; //I2C模式
I2C_InitStructure.I2C_OwnAddress1 =0; //指定自己的地址為七位地址,和從器件不同即可
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable ;//啟用應(yīng)答
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;//七位地址模式
I2C_Init(I2C1, &I2C_InitStructure);
/* 使能 I2C */
I2C_Cmd(I2C1, ENABLE);
}
IO配置
IO配置為開漏輸出,選擇復(fù)用功能。
TIMING配置
I2C外設(shè)寄存器的配置,第一個參數(shù)I2C_Timing很多新手朋友可能不知道該怎么設(shè)置,原來的時候ST官方有個execl的小工具,選擇I2C頻率,上升時間和下降時間后,點(diǎn)擊計算就可以得到一個值,復(fù)制過來填進(jìn)去就行,如下圖(可以在官網(wǎng)找到,名字:I2C_Timing_Configuration_V1.0.1)
后面我發(fā)現(xiàn)一個更簡單的辦法,直接在cubemx上面選擇M0的芯片的I2C,配置好對應(yīng)的設(shè)置后會自動生成配置值:
濾波器配置
timing配置完成后是濾波器的配置,模擬濾波器和數(shù)字濾波器的作用:
去除總線上的一些噪聲干擾。
開啟后對總線的影響:
在總線上沒有掛負(fù)載時,I2C的速度可以根據(jù)時鐘源選擇,超過I2C協(xié)議規(guī)定的3.4M/s(如此速度下通信,前提是從機(jī)能支持),但是當(dāng)開啟數(shù)字濾波和模擬濾波后,速度會下降很多。
其他的配置沒什么特別要注意的地方,不詳細(xì)展開。
讀EEPROM流程詳述
I2C作為主機(jī)讀寫從機(jī)數(shù)據(jù),從機(jī)是EEPROM,只做了簡單的讀數(shù)據(jù)操作,當(dāng)時為了偷懶沒有做翻頁讀的處理(由于當(dāng)時有很多種型號的EEPROM,讀寫函數(shù)里做了條件編譯來兼容不同系列),
EE_TYPE在頭文件中的定義如下:
#define A24C01 0x01
#define A24C02 0x02
#define A24C04 0x03
#define A24C08 0x04
#define A24C16 0x05
#define A24C32 0x06
#define A24C64 0x07
#define A24C128 0x08
#define A24C256 0x09
#define A24C512 0x0a
#define A24C1024 0x0b
#define EE_TYPE A24C02
//IIC讀取數(shù)據(jù)函數(shù)
void IIC_Read(uint16_t SalveAddr,uint8_t startaddr,uint8_t *buffer,uint8_t Length)
{
uint8_t i;
while(I2C_GetFlagStatus(I2C1,I2C_FLAG_BUSY));//檢查總線是否繁忙
#if (EE_TYPE > A24C16)
I2C_TransferHandling(I2C1,SalveAddr,2,I2C_SoftEnd_Mode,I2C_Generate_Start_Write);
while(I2C_GetFlagStatus(I2C1,I2C_FLAG_TXIS)==RESET);//檢查TXDR寄存器是否為空
I2C_SendData(I2C1, startaddr>>8);//向總線發(fā)送從器件地址
while(I2C_GetFlagStatus(I2C1,I2C_FLAG_TXIS)==RESET);//檢查發(fā)送是否完成
I2C_SendData(I2C1,startaddr);
while(I2C_GetFlagStatus(I2C1,I2C_FLAG_TC)==RESET);
#else
I2C_TransferHandling(I2C1,SalveAddr,1,I2C_SoftEnd_Mode,I2C_Generate_Start_Write);
while(I2C_GetFlagStatus(I2C1,I2C_FLAG_TXIS)==RESET);//檢查TXDR寄存器是否為空
I2C_SendData(I2C1,startaddr);
while(I2C_GetFlagStatus(I2C1,I2C_FLAG_TC)==RESET);
#endif
I2C_TransferHandling(I2C1,SalveAddr,Length,I2C_AutoEnd_Mode, I2C_Generate_Start_Read);//產(chǎn)生一個讀的起始信號
for(i=0;i<Length;i++)
{
while(I2C_GetFlagStatus(I2C1,I2C_FLAG_RXNE)==RESET);//等待接收完成
buffer[i]=I2C_ReceiveData(I2C1);
}
while(I2C_GetFlagStatus(I2C1,I2C_FLAG_STOPF)==RESET);//作為主機(jī)檢測由硬件自己產(chǎn)生的停止信號
I2C_ClearFlag(I2C1,I2C_FLAG_STOPF);
}
讀數(shù)據(jù)前先對總線進(jìn)行BUSY判斷,總線電平有變化時,說明處于busy狀態(tài),暫時無法操作。
如果總線處于空閑狀態(tài),就可以接手總線的控制權(quán),調(diào)用I2C_TransferHandling函數(shù)向總線上的器件發(fā)起尋址,參數(shù)中需要確定從器件地址,發(fā)送字節(jié)數(shù),及達(dá)到發(fā)送字節(jié)數(shù)后的結(jié)束方式,是否生成起始條件及讀寫方向。
其中結(jié)束方式有三個選項:
軟件結(jié)束模式:在既定傳輸字節(jié)完成后,TC標(biāo)志將置位且SCL線低電平將被延展,此時可以執(zhí)行的操作有兩個,一是不放棄總線控制權(quán)重新發(fā)起start信號,二是通過軟件操作手動發(fā)送停止位。
自動結(jié)束模式:在既定傳輸字節(jié)完成后,硬件自動發(fā)送停止位。
重加載模式:當(dāng)要傳輸?shù)淖止?jié)數(shù)大于255時,可以選擇重加載模式,完成既定傳輸字節(jié)數(shù)后,TCR標(biāo)志將置1,SCL會被延長,向NBYTES中寫入一個非0值可以繼續(xù)傳輸數(shù)據(jù),但是在向NBYTES中寫入最后一次傳輸?shù)淖止?jié)數(shù)之前,必須關(guān)閉重加載模式。
由于從器件是EEPROM,在器件地址發(fā)送完成后還要發(fā)送一次讀寫地址,此時如果用的從機(jī)器件是24c16以下的型號,從機(jī)的讀寫地址就是8位的,如果是24c32以上的,就要發(fā)送16位讀寫地址。讀寫地址發(fā)送完成后判斷TC標(biāo)志表示發(fā)送完成,16位地址時,需要在高8位地址發(fā)送完成后判斷TXIS標(biāo)志,我理解的TXIS標(biāo)志置起狀態(tài)是TXDR寄存器中數(shù)據(jù)傳輸?shù)揭莆患拇嫫髦校ù思拇嫫鳑]有公開到用戶手冊),并未轉(zhuǎn)化成高低電平到總線時的狀態(tài)(TC標(biāo)志未置位),TXIS置起后,可以繼續(xù)發(fā)送低八位地址。
到這里為止,如果通信過程順利,接下來的操作就是對eeprom具體區(qū)域讀數(shù),由于上一次調(diào)用I2C_TransferHandling選擇軟件結(jié)束模式,且并未發(fā)送停止信號,接下來的操作就是調(diào)用I2C_TransferHandling,再次發(fā)起起始信號,發(fā)送從器件地址,將傳輸方向從寫改為讀,且確定傳輸字節(jié)數(shù),接下來就是根據(jù)既定字節(jié)數(shù),由硬件自動發(fā)送時鐘出去讀取數(shù)據(jù),根據(jù)RXNE標(biāo)志來接收數(shù)據(jù),如果想讀取的字節(jié)數(shù)小于255,可以選擇自動結(jié)束模式,在傳輸完成后,自動發(fā)出停止信號,也可以選擇軟件結(jié)束(需要在傳輸完成后配置寄存器發(fā)出停止信號),如果接收數(shù)據(jù)大于255,選擇重加載模式,操作方法上文有描述。
接收完成后,需要讀取狀態(tài)寄存器判斷主機(jī)是否已經(jīng)發(fā)送停止位,此時,一次完整的I2C作為主機(jī)讀取EEPROM數(shù)據(jù)的通信過程結(jié)束。文章來源:http://www.zghlxwxcb.cn/news/detail-716187.html
寫EEPROM流程詳述
void IIC_Write(uint16_t SalveAddr,uint8_t startaddr,uint8_t *buffer, uint8_t Length)
{
uint8_t i;
while(I2C_GetFlagStatus(I2C1,I2C_FLAG_BUSY));
#if (EE_TYPE > A24C16)
I2C_TransferHandling(I2C1,SalveAddr,2,I2C_Reload_Mode,I2C_Generate_Start_Write);
while(I2C_GetFlagStatus(I2C1,I2C_FLAG_TXIS)==RESET);//檢查TXDR寄存器是否為空
I2C_SendData(I2C1, startaddr>>8);
while(I2C_GetFlagStatus(I2C1,I2C_FLAG_TXIS)==RESET);//該位在Reload=1時NBYTES個數(shù)數(shù)據(jù)發(fā)送完成后被置1,向NBYTES寫入非0數(shù)值時被清0
I2C_SendData(I2C1,startaddr);
while(I2C_GetFlagStatus(I2C1,I2C_FLAG_TCR)==RESET);
#else
I2C_TransferHandling(I2C1,SalveAddr,1,I2C_Reload_Mode,I2C_Generate_Start_Write);
while(I2C_GetFlagStatus(I2C1,I2C_FLAG_TXIS)==RESET);//檢查TXDR寄存器是否為空
I2C_SendData(I2C1,startaddr);
while(I2C_GetFlagStatus(I2C1,I2C_FLAG_TCR)==RESET);
#endif
I2C_TransferHandling(I2C1,SalveAddr,Length,I2C_AutoEnd_Mode,I2C_No_StartStop);
for(i=0;i<Length;i++)
{
while(I2C_GetFlagStatus(I2C1,I2C_FLAG_TXIS)==RESET);//等待發(fā)送寄存器為空
I2C_SendData(I2C1,buffer[i]);
}
while(I2C_GetFlagStatus(I2C1,I2C_FLAG_STOPF)==RESET);//作為主機(jī)檢測由硬件自己產(chǎn)生的停止信號
I2C_ClearFlag(I2C1,I2C_FLAG_STOPF);
}
作為主機(jī)寫EEPROM的操作流程類似,但是不需要修改傳輸方向,所以在第一次尋址時使用重加載模式,在8位或16位讀寫地址發(fā)送完成后,不重新生成起始信號,直接再次寫入NBYTES,開始寫入數(shù)據(jù)操作,由于該函數(shù)中我既定的傳輸字節(jié)數(shù)小于255,所以在該次傳輸開始前將模式修改成自動結(jié)束模式。
然后就是開始發(fā)送數(shù)據(jù),后續(xù)結(jié)束流程和讀EEPROM操作時類似。文章來源地址http://www.zghlxwxcb.cn/news/detail-716187.html
到了這里,關(guān)于STM32F030硬件I2C代碼及解析的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!