STM32F4硬件\內部I2C驅動(主模式)
此篇文章將帶領你理解並學習STM32F4硬件\內部的I2C主模式去驅動slave I2C設備
基礎知識
I2C模式
i2c一般有四個模式:
● 從發送器–|一般用於利用i2c進行設備通信
● 從接收器–|
● 主發送器------|
● 主接收器------|一般用於驅動一個i2c設備(傳感器類)
I2C通信流程
在主模式下, I2C 接口會啓動數據傳輸並生成時鐘信號。串行數據傳輸始終是在出現起始位時開始,在出現停止位時結束。起始位和停止位均在主模式下由軟件生成。
在從模式下,該接口能夠識別其自身地址( 7 或 10 位)以及廣播呼叫地址。廣播呼叫地址檢測可由軟件使能或禁止。
通信數據格式爲數據和地址均以 8 位字節傳輸, MSB 在前。起始位後緊隨地址字節(7 位地址佔據一個字節;10 位地址佔據兩個字節)。地址始終在主模式下傳送。
在字節傳輸 8 個時鐘週期後是第 9 個時鐘脈衝,在此期間接收器必須向發送器發送一個應答位
總結一下就是:在通信時在主設備發送起始信號過後,串行數據開始傳輸,在第九個時鐘時從設備要向主設備發送一個應答位。在串行數據傳輸時均是MSB在前
I2C地址
對於I2C地址很多人都分不清,i2c的地址組成一般是7+1,其中最後1位爲讀寫位(一般0爲寫、1爲讀),注意這裏7位很重要,一般數據手冊中給出的都是這7位地址,換句話說也就是7前7位表示8位的一個數值。舉個例子:mpu6050陀螺儀模塊在數據手冊中說7位設備地址爲0X68(AD0接GND時,AD0接VCC時爲0X68)。可能會有很多人這樣說mpu的寫地址爲0X68、讀地址爲0X69,就是改最後一位嘛,0就是寫、1就是讀。很顯然這是錯的,因爲它指的是7位地址,不加最後一位。換成二進制解釋0X68地址: 110 1000讀寫位,這裏的0x68指不加讀寫位的1101000,那麼實際對mpu寫操作的地址是1101 0000 即0xD0,讀地址爲0xD1。同理對於0X69時也一樣
總結一下:關於i2c地址,一定要看清數據手冊所給的是7位地址(不包含讀寫位的,需要在最後加上一位表示讀寫位)還是已經包含了讀寫位的地址(如果包含一般給出的是寫地址即+1就是讀地址)
I2C起始位與停止位
在i2c工作中,有效數據即SDA只允許在SCLK低電平期間變化,在SCLK高電平期間不允許改變。所以,對於兩類特殊的命令信號都是在SCLK在高電平期間SDA發生改變。
起始位如下圖:在SCLK高電平期間SDA由高到低變化將被理解爲起始位。
停止位如下圖:在SCLK高電平期間SDA由低到高變化將被理解爲起始位。
使用STM32F407系列硬件/內部I2C
首先我們先看主發送
單字節發送
對於內部外設的開發,我建議我們要習慣看開發手冊,這點很重要。下面截取自開發手冊中的一個圖:
看到上圖我們很好理解想要發送數據一般步驟爲:
生成起始位->等待EV5事件->發送設備地址(寫地址)->等待EV6事件->發送數據->等待EV8事件->…->等待EV8_2事件->發送停止位。那麼轉化爲流程圖就是下圖:
注意:在主發送時,注意停止位發送時刻,數據手冊上對於主發送數據的結束通信有這麼一句話
對於這句話我個人覺得可能會誤導我們在發送最後一個數據時,在寫DR後就發送停止位,這樣理解是錯誤的,對於這樣的不好理解的我們可以直接看英文原版數據手冊可能有所幫助,如下圖對應英文原版:
看注意我們就可以知道停止位應當在EV8_2期間產生,也就是說在寫DR後要等待一個TxE或者是BTF時其後纔可以生成停止位,而非寫DR後就生成停止位。總結一點就是對於主發送我們可以直接按照上述流程進行,無需多慮。
多字節發送
對於多字節發送,完全按照上述流程走即可。
然後我們看主接收
對於主接收可能有點麻煩,一般而言對於主I2C接收操作會有一個“僞寫”操作,所謂的“僞寫”就是先告訴我將要操作i2c設備中的哪個內部寄存器或者說哪一部分,即定位過程。不明白的的話我們可以看兩個例子:
例1,AT24C256 EEPROM的隨機讀操作時序見下圖,可以看到隨機讀寫需要一個DUMMY Write。
例2,MPU6050陀螺儀在讀過程中也是需要一個“僞寫”過程,如下圖
從上述兩個設備來看,想要讀取時都需要一個定位過程,換句話說也就是先告訴設備我準備讀取你的哪個寄存器或者哪一部分。此時我們再看手冊中關於主I2C讀取框圖(流程)如下圖:
同過上面的流程我們可以很明顯的看出,此讀取序列流程,並沒有“僞寫”過程,因此真正的主接收流程圖應該是主發送部分加主接收這個序列圖,即下圖:
當然了在第一部分的寫過程,不僅僅可以寫一個數據可以寫多個,根據不同設備確定,和真正的寫唯一區別就是不用生成停止位僅此而已。有了以上了解我們開始進行代碼段設計,因爲涉及到硬件規則緣故以及效率問題,我們在讀過程中,一般分爲單字節讀取、兩字節讀取以及多字節讀取,之所以區分這三個是因爲它們結束通信方式是不一樣的,以下我將分別講述這三個操作過程中結束通信方式以及注意點。
單字節讀取
在單字節讀取過程中,結束通信過程如下:應當在EV6期間(圖244裏的)在 ADDR 標誌清零之前,禁止應答並在EV6 之後生成停止位。有細心的同志,就可以發現如果我們使用庫函數開發,在調用I2C_CheckEvent函數檢查事件時,查完後就會清除ADDR,那麼是不是不行啊!針對這個問題我測試了,如果按這樣理解就是在I2C_Send7bitAddress函數後就關閉自動應答,這種操作有時會帶來問題,爲了安全起見,我做法是等待EV6事件後在生成停止條件,畢竟中間時間也想差不了多少。所以如果在規定時間內有EV6事件,就先執行關閉自動應答,在生成停止位,最後等待EV7事件後讀取一個字節的數據。
兩字節讀取
在手冊中對於兩字節讀取建議操作步驟如下圖:
那麼我們就直接按照這個圖來理解。這裏涉及到一個POS位,先了解下POS位,
在我們理解中當關閉自動應答過後,當接收到一個字節後就不應答了,言外之意就是值可以接收到一個字節數據,想要接受兩個字節數據就必須在最後一個字節時關閉自動相應,有人可能就會有這個想法我直接在單字節讀取過程中,在第一個字節接收後再生成停止位不可以嗎,不可以會丟失第二個數據,這裏涉及到一個關鍵點就是移位寄存器,對於移位寄存器在多字節接收會有更好的理解,這裏接單理解下就是,當我們接收>1個字節的數據時,當檢測到EV7事件時(此前未生成停止條件),第一個數據在DR中第二個數據已經在與移位寄存器中,如果在讀DR後生成停止條件的話移位寄存器中的值面臨丟失或者說總線錯誤的問題,爲解決此問題便有了POS位,對於POS位我可以簡單理解成,在接收到第一個數據時即使ACK爲0也不產生NACK,只有在接收第二個字節後在生成NACK,由硬件幫我們決定在接收第二個字節後給與NACK,保證時序準確定。
這裏有個注意點:按照圖中第四步在等待了BTF標誌位後,我們不能在讀取第一個字節後立馬讀第二個字節,因爲第二個字節還在移位寄存器中,我們需要等待一個時鐘週期在讀取就可以了,也就是在讀取兩個字節間隨便加一個操作就可以了,比如賦值語句;當然了也可以監測RxNE標誌。
多字節讀取
下面到了最後一個,多字節讀取,也就是至少接收三個數據。在理解這個後你將會對移位寄存器有個很好的理解。
同理中間出來一個BTF我們先查看下對此位的描述,
結合兩者我相信應該都可以明白如何寫對應的代碼了。這裏在最後一步讀取第N-1數據和第N數據時注意下留一個時鐘週期或者檢測RxNE操作,理由在於給與移位寄存器數據到DR中一個時間間隔。
總結:關於上述讀取,尤其看了第三個關於>2字節讀取時,進一步明白了移位寄存器後,都會對兩字節接收存在疑問,按照同樣的思路設計2字節讀取的就是了。我也有疑問,我也想直接測試下,奈何代碼都寫好了,懶得動了。又想了想,既然手冊上都區分,我們就按照他們規定的來,以免出現不必要的錯誤,畢竟這是硬件幫我們產生的時序,不按照規定的來最終出現啥問題也是不得而知的。不過有興趣的可以都測試下看看,這樣對於進一步理解這個I2C肯定會有很大的幫助。
代碼設計
通過上面的講述可能還比較迷,那麼直接結合代碼看比較好。
這裏會有個getI2C1TimeLimit函數,主要用來等數據時的一個最大時間限制,以免程序卡死,此函數是獲取一個延時函數的數值,可以根據自身的判斷設置。
/**
* @brief: 獲取i2c超時值獲取-時間大約值不一定準確
* @args:None
* @return:超時值(us)
* @add:原理1、是一般i2c最遲響應時間在10ms內。
* 2、一個while(--i)語句時間大約是0.3us。
* 在使用過程中先獲取此值,然後直接用while(--i),就是i*0.3 us了。
*/
static uint32_t getI2C1TimeLimit(void)
{
RCC_ClocksTypeDef RCC_Clocks;
RCC_GetClocksFreq(&RCC_Clocks);
return RCC_Clocks.SYSCLK_Frequency/10000;//168000000/10000=16800 5.40ms
}
寫數據
寫字節代碼
/**
* @brief: 利用硬件i2c1向目標寄存器寫入一個數據
* @args:三個參數:寄存器地址,寄存器地址是否是16位,需要寫入的數據
* @arg1:目標設備地址(寫地址就好了)
* @arg2:目標寄存器地址
* @arg3:寄存器地址是否是16位的
* @arg4:一個字節數據
* @return:
* >0 成功
* <0 失敗
* -1 i2c busy
* -2 i2c send start fail
* -3 i2c send address failed
* -4 i2c high regAdder failed
* -5 i2c send low regAdder failed
* -6 i2c send data failed
*/
int32_t i2c1WriteByte(uint8_t devAdder, uint16_t regAdder, bool is16BitRegAdder, uint8_t num)
{
uint32_t timeLimit = getI2C1TimeLimit();
uint32_t tmp = timeLimit;
//--判斷當前i2c是否忙
while((--tmp)&&(I2C_GetFlagStatus(I2C1_NAME, I2C_FLAG_BUSY)));
if(tmp == 0)
return -1;//i2c busy
//--生成起始位(SB未清零-I2C_CheckEvent讀了SR1,在後續發送地址時I2C_Send7bitAddress寫了DR後清除SB位)
I2C_GenerateSTART(I2C1_NAME, ENABLE);//發送起始位後,自動轉換爲主設備
tmp = timeLimit;
while((--tmp)&&(!I2C_CheckEvent(I2C1_NAME,I2C_EVENT_MASTER_MODE_SELECT)));//EV5事件
if(tmp == 0)
return -2;//i2c send start fail
//--發送7位設備地址.調用I2C_CheckEvent會清除EV6事件(先讀SR1再讀SR2)
I2C_Send7bitAddress(I2C1_NAME, devAdder, I2C_Direction_Transmitter);//七位地址,非十位地址
tmp = timeLimit;
while((--tmp)&&(!I2C_CheckEvent(I2C1_NAME,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)));//EV6事件
if(tmp == 0)
return -3;//i2c send address failed
//--發送要寫入的目標寄存器
if(is16BitRegAdder)
{
I2C_SendData(I2C1_NAME, regAdder>>8);//發送高8位地址
tmp = timeLimit;
while((--tmp)&&(!I2C_CheckEvent(I2C1_NAME,I2C_EVENT_MASTER_BYTE_TRANSMITTING)));//EV8事件-寫DR清除
if(tmp == 0)
return -4;//i2c high regAdder failed
}
I2C_SendData(I2C1_NAME, regAdder%256);//發送低8位地址
tmp = timeLimit;
while((--tmp)&&(!I2C_CheckEvent(I2C1_NAME,I2C_EVENT_MASTER_BYTE_TRANSMITTING)));//EV8事件-寫DR清除
if(tmp == 0)
return -5;//i2c send low regAdder failed
//--發送數據
I2C_SendData(I2C1_NAME, num);
tmp = timeLimit;
while((--tmp)&&(!I2C_CheckEvent(I2C1_NAME,I2C_EVENT_MASTER_BYTE_TRANSMITTED)));//EV8_2事件
if(tmp == 0)
return -6;//i2c send data failed
//--發送結束信號
I2C_GenerateSTOP(I2C1_NAME, ENABLE);
return 1;
}
寫多字節代碼
/**
* @brief: 利用硬件i2c1向目標起始寄存器連續寫入多個數據
* @args:5個參數:開始寄存器地址,寄存器地址是否是16位,需要寫入的數據數組,數據數組大小,是否允許跨頁(如果不是eerpom不用管)
* @arg1:目標設備地址(寫地址就好了)
* @arg2:目標寄存器地址
* @arg3:寄存器地址是否是16位的
* @arg4:一個要寫入數據的數組
* @arg5:寫入數據長度
* @return:
* >0 成功發送的字節數
* <=0 失敗
* 0 buf大小爲0
* -1 i2c busy
* -2 i2c send start fail
* -3 i2c send address failed
* -4 i2c high regAdder failed
* -5 i2c send low regAdder failed
*/
static int32_t i2c1WriteMoreByte(uint8_t devAdder, uint16_t startRegAdder, bool is16BitRegAdder,uint8_t *buf,uint8_t bufSize)
{
uint32_t timeLimit = getI2C1TimeLimit();
uint32_t tmp = timeLimit;
uint32_t index = 0;
if(bufSize <= 0)
{
return 0;
}
//--判斷當前i2c是否忙
while((--tmp)&&(I2C_GetFlagStatus(I2C1_NAME, I2C_FLAG_BUSY)));
if(tmp == 0)
return -1;//i2c busy
//--生成起始位(SB未清零-I2C_CheckEvent讀了SR1,在後續發送地址時I2C_Send7bitAddress寫了DR後清除SB位)
I2C_GenerateSTART(I2C1_NAME, ENABLE);//發送起始位後,自動轉換爲主設備
tmp = timeLimit;
while((--tmp)&&(!I2C_CheckEvent(I2C1_NAME,I2C_EVENT_MASTER_MODE_SELECT)));//EV5事件
if(tmp == 0)
return -2;//i2c send start fail
//--發送7位設備地址.調用I2C_CheckEvent會清除EV6事件(先讀SR1再讀SR2)
I2C_Send7bitAddress(I2C1_NAME, devAdder, I2C_Direction_Transmitter);//七位地址,非十位地址
tmp = timeLimit;
while((--tmp)&&(!I2C_CheckEvent(I2C1_NAME,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)));//EV6事件
if(tmp == 0)
return -3;//i2c send address failed
//--發送要寫入的目標寄存器
if(is16BitRegAdder)
{
I2C_SendData(I2C1_NAME, startRegAdder>>8);//發送高8位地址
tmp = timeLimit;
while((--tmp)&&(!I2C_CheckEvent(I2C1_NAME,I2C_EVENT_MASTER_BYTE_TRANSMITTING)));//EV8事件-寫DR清除
if(tmp == 0)
return -4;//i2c high regAdder failed
}
I2C_SendData(I2C1_NAME, startRegAdder%256);//發送低8位地址
tmp = timeLimit;
while((--tmp)&&(!I2C_CheckEvent(I2C1_NAME,I2C_EVENT_MASTER_BYTE_TRANSMITTING)));//EV8事件-寫DR清除
if(tmp == 0)
return -5;//i2c send low regAdder failed
//--發送數據
while(index < bufSize)
{
if(index == (bufSize - 1))
{
//已經是最後一個字節了,在寫入DR後發送停止位了
I2C_SendData(I2C1_NAME, buf[index]);
tmp = timeLimit;
while((--tmp)&&(!I2C_CheckEvent(I2C1_NAME,I2C_EVENT_MASTER_BYTE_TRANSMITTED)));//EV8_2事件
I2C_GenerateSTOP(I2C1_NAME, ENABLE);
if(tmp == 0)
return index;//返回已發送的字節數量
}else{
I2C_SendData(I2C1_NAME, buf[index]);
tmp = timeLimit;
while((--tmp)&&(!I2C_CheckEvent(I2C1_NAME,I2C_EVENT_MASTER_BYTE_TRANSMITTING)));//EV8事件
if(tmp == 0)
return index;//返回已發送的字節數量
}
index++;
}
return index;
}
讀/接收數據
讀/接收字節代碼
/**
* @brief: 利用硬件i2c1讀取目標寄存器數據
* @args:兩個參數:寄存器地址,寄存器地址是否是16位
* @arg1:目標設備地址(寫地址就好了)
* @arg2:目標8位寄存器地址
* @arg3:寄存器地址是否是16位的
* @arg4:讀取結果指針()
* @return:
* >0 成功
* <0 失敗
* -1 i2c busy
* -2 i2c send start fail
* -3 i2c send address failed
* -4 i2c send high regAdder failed
* -5 i2c send low regAdder failed
* -6 i2c reSend start fail
* -7 i2c reSend address failed
* -8 i2c no received
*/
int32_t i2c1ReadByte(uint8_t devAdder, uint16_t regAdder, bool is16BitRegAdder, uint8_t *num)
{
uint32_t timeLimit = getI2C1TimeLimit();
uint32_t tmp = timeLimit;
/*第一步:先將讀指針移到指定的地址寄存器上*/
//--判斷當前i2c是否忙
while((--tmp)&&(I2C_GetFlagStatus(I2C1_NAME, I2C_FLAG_BUSY)));
if(tmp == 0)
return -1;//i2c busy
//--生成起始位(SB未清零-I2C_CheckEvent讀了SR1,在後續發送地址時I2C_Send7bitAddress寫了DR後清除SB位)
I2C_GenerateSTART(I2C1_NAME, ENABLE);//發送起始位後,自動轉換爲主設備
tmp = timeLimit;
while((--tmp)&&(!I2C_CheckEvent(I2C1_NAME,I2C_EVENT_MASTER_MODE_SELECT)));//EV5事件
if(tmp == 0)
return -2;//i2c send start fail
//--發送7位設備地址
I2C_Send7bitAddress(I2C1_NAME, devAdder, I2C_Direction_Transmitter);//七位地址,非十位地址
tmp = timeLimit;
while((--tmp)&&(!I2C_CheckEvent(I2C1_NAME,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)));
if(tmp == 0)
return -3;//i2c send address failed
//清零事件EV6 ADDR位(先讀SR1再讀SR2)
(void)(I2C1_NAME->SR1);
(void)(I2C1_NAME->SR2);//清除EV6事件
//--發送要寫入的目標寄存器
if(is16BitRegAdder)
{
I2C_SendData(I2C1_NAME, regAdder>>8);//發送高8位地址
tmp = timeLimit;
while((--tmp)&&(!I2C_CheckEvent(I2C1_NAME,I2C_EVENT_MASTER_BYTE_TRANSMITTING)));
if(tmp == 0)
return -4;//i2c high regAdder failed
}
I2C_SendData(I2C1_NAME, regAdder%256);//發送低8位地址
tmp = timeLimit;
while((--tmp)&&(!I2C_CheckEvent(I2C1_NAME,I2C_EVENT_MASTER_BYTE_TRANSMITTING)));
if(tmp == 0)
return -5;//i2c send low regAdder failed
/*第二步:開始讀取*/
//--再次生成起始位
I2C_GenerateSTART(I2C1_NAME, ENABLE);//發送起始位後,自動轉換爲主設備
tmp = timeLimit;
while((--tmp)&&(!I2C_CheckEvent(I2C1_NAME,I2C_EVENT_MASTER_MODE_SELECT)));
if(tmp == 0)
return -6;//i2c reSend start fail
//--再次發送7位設備地址(接收/讀取模式).調用I2C_CheckEvent會清除EV6事件(先讀SR1再讀SR2)
I2C_Send7bitAddress(I2C1_NAME, devAdder, I2C_Direction_Receiver);//七位地址,非十位地址。設置接收時會默認地址最後一位取反即爲1
tmp = timeLimit;
while((--tmp)&&(!I2C_CheckEvent(I2C1_NAME,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)));//EV6事件
if(tmp == 0)
return -7;//i2c reSend address failed
//--準備接收數據
I2C_AcknowledgeConfig(I2C1_NAME, DISABLE);//因爲只接收一個字節數據,所以不需要響應
I2C_GenerateSTOP(I2C1_NAME, ENABLE);//將在接收後產生停止信號-在此書寫原因在手冊
tmp = timeLimit;
while((--tmp)&&(!I2C_CheckEvent(I2C1_NAME,I2C_EVENT_MASTER_BYTE_RECEIVED)));//EV7事件
if(tmp == 0)
return -8;//i2c no received
//--接收數據
*num = I2C_ReceiveData(I2C1_NAME);//保存數據
//--恢復響應
I2C_AcknowledgeConfig(I2C1_NAME, ENABLE);
return 1;
}
讀/接收兩字節代碼
/**
* @brief: 利用硬件i2c1讀取目標寄存器開始以後的兩個數據
* @args:兩個參數:寄存器地址,寄存器地址是否是16位
* @arg1:目標設備地址(寫地址就好了)
* @arg2:目標8位寄存器地址
* @arg3:寄存器地址是否是16位的
* @arg4:讀取結果指針
* @arg5:是否允許跨頁
* @return:
* >0 成功(成功讀取的字節數)
* <=0 失敗
* -1 i2c busy
* -2 i2c send start fail
* -3 i2c send address failed
* -4 i2c send high regAdder failed
* -5 i2c send low regAdder failed
* -6 i2c reSend start fail
* -7 i2c reSend address failed
* -8 i2c no received
*/
int32_t i2c1ReadTwoByte(uint8_t devAdder, uint16_t startRegAdder, bool is16BitRegAdder, uint8_t *buf, bool isAllowOverPage)
{
uint32_t timeLimit = getI2C1TimeLimit();
uint32_t tmp = timeLimit;
#if I2C1_DEVICE_IS_EEPROM
//跨頁問題
uint16_t pageRemain = 0;
uint16_t newAdder = 0;
if(getI2C1DeviceCurrentPageRemain(startRegAdder, &pageRemain) < 0)
{
return 0;
}
if(pageRemain < 2)
{
//需要跨頁
if(i2c1ReadByte(devAdder, startRegAdder, true, &buf[0]) < 0)//讀取第一個數據
{
return 0;
}
if(!isAllowOverPage)
{
//不允許,直接退出,返回已經讀取的一個數量
return 1;//只存在一個數據
}
if(getI2C1DeviceNewPage(startRegAdder, &newAdder) < 0)//移動到下一頁
{
return 1;//只存在一個數據
}
if(i2c1ReadByte(devAdder, newAdder, true, &buf[1]) < 0)//讀取第二個數據
{
return 0;//只存在一個數據
}
return 2;
}
#endif
if(buf == NULL)
{
return 0;
}
/*第一步:先將讀指針移到指定的地址寄存器上*/
//--判斷當前i2c是否忙
while((--tmp)&&(I2C_GetFlagStatus(I2C1_NAME, I2C_FLAG_BUSY)));
if(tmp == 0)
return -1;//i2c busy
//--生成起始位(SB未清零-I2C_CheckEvent讀了SR1,在後續發送地址時I2C_Send7bitAddress寫了DR後清除SB位)
I2C_GenerateSTART(I2C1_NAME, ENABLE);//發送起始位後,自動轉換爲主設備
tmp = timeLimit;
while((--tmp)&&(!I2C_CheckEvent(I2C1_NAME,I2C_EVENT_MASTER_MODE_SELECT)));//EV5事件
if(tmp == 0)
return -2;//i2c send start fail
//--發送7位設備地址
I2C_Send7bitAddress(I2C1_NAME, devAdder, I2C_Direction_Transmitter);//七位地址,非十位地址
tmp = timeLimit;
while((--tmp)&&(!I2C_CheckEvent(I2C1_NAME,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)));
if(tmp == 0)
return -3;//i2c send address failed
//清零事件EV6 ADDR位(先讀SR1再讀SR2)
(void)(I2C1_NAME->SR1);
(void)(I2C1_NAME->SR2);//清除EV6事件
//--發送要寫入的目標寄存器
if(is16BitRegAdder)
{
I2C_SendData(I2C1_NAME, startRegAdder>>8);//發送高8位地址
tmp = timeLimit;
while((--tmp)&&(!I2C_CheckEvent(I2C1_NAME,I2C_EVENT_MASTER_BYTE_TRANSMITTING)));
if(tmp == 0)
return -4;//i2c high regAdder failed
}
I2C_SendData(I2C1_NAME, startRegAdder%256);//發送低8位地址
tmp = timeLimit;
while((--tmp)&&(!I2C_CheckEvent(I2C1_NAME,I2C_EVENT_MASTER_BYTE_TRANSMITTING)));
if(tmp == 0)
return -5;//i2c send low regAdder failed
/*第二步:開始讀取*/
//--再次生成起始位
I2C_GenerateSTART(I2C1_NAME, ENABLE);//發送起始位後,自動轉換爲主設備
tmp = timeLimit;
while((--tmp)&&(!I2C_CheckEvent(I2C1_NAME,I2C_EVENT_MASTER_MODE_SELECT)));
if(tmp == 0)
return -6;//i2c reSend start fail
//--再次發送7位設備地址(接收/讀取模式).調用I2C_CheckEvent會清除EV6事件(先讀SR1再讀SR2)
I2C_Send7bitAddress(I2C1_NAME, devAdder, I2C_Direction_Receiver);//七位地址,非十位地址。設置接收時會默認地址最後一位取反即爲1
tmp = timeLimit;
while((--tmp)&&(!I2C_CheckEvent(I2C1_NAME,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)));//EV6事件
if(tmp == 0)
return -7;//i2c reSend address failed
//--準備接收數據
I2C_NACKPositionConfig(I2C1_NAME, I2C_NACKPosition_Next);//POS位置位,在接收的下一個字節產生NACK
tmp = timeLimit;
while((--tmp)&&(!I2C_GetFlagStatus(I2C1_NAME,I2C_FLAG_BTF)));//等待BTF置位
if(tmp == 0)
return -8;//i2c no received
//--發送結束信號
I2C_GenerateSTOP(I2C1_NAME, ENABLE);
//--接收兩個字節數據.讀取第一個數據後第二個數據將會從移位寄存器轉到DR中
buf[0] = (uint8_t)I2C_ReceiveData(I2C1_NAME);//保存第一個字節
tmp = timeLimit;//給移位寄存器轉到DR中時間,相當於nop
buf[1] = (uint8_t)I2C_ReceiveData(I2C1_NAME);//保存第二個字節
return 2;//成功讀取兩個字節
}
讀/接收多字節代碼
/**
* @brief: 利用硬件i2c1讀取從目標寄存器開始的bufsize個數據
* @args:四個參數:寄存器起始地址,寄存器地址是否是16位,保存數據的buf,保存數據數量
* @arg1:目標設備地址(寫地址就好了)
* @arg2:目標8位寄存器地址
* @arg3:寄存器地址是否是16位的
* @arg4:讀取結果指針
* @arg5:要讀取的大小,調用此函數最小讀取爲3個字節
* @return:
* >0 成功 表示已經接收的字節數量
* <0 失敗
* -1 i2c busy
* -2 i2c send start fail
* -3 i2c send address failed
* -4 i2c send high regAdder failed
* -5 i2c send low regAdder failed
* -6 i2c reSend start fail
* -7 i2c reSend address failed
*/
static int32_t i2c1ReadMoreByte(uint8_t devAdder, uint16_t startRegAdder, bool is16BitRegAdder,uint8_t *buf,uint8_t bufSize)
{
uint32_t timeLimit = getI2C1TimeLimit();
uint32_t tmp = timeLimit;
uint32_t index = 0;
if(bufSize < 3)
{
return 0;
}
/*第一步:先將讀指針移到指定的地址寄存器上*/
//--判斷當前i2c是否忙
while((--tmp)&&(I2C_GetFlagStatus(I2C1_NAME, I2C_FLAG_BUSY)));
if(tmp == 0)
return -1;//i2c busy
//--生成起始位(SB未清零-I2C_CheckEvent讀了SR1,在後續發送地址時I2C_Send7bitAddress寫了DR後清除SB位)
I2C_GenerateSTART(I2C1_NAME, ENABLE);//發送起始位後,自動轉換爲主設備
tmp = timeLimit;
while((--tmp)&&(!I2C_CheckEvent(I2C1_NAME,I2C_EVENT_MASTER_MODE_SELECT)));//EV5事件
if(tmp == 0)
return -2;//i2c send start fail
//--發送7位設備地址
I2C_Send7bitAddress(I2C1_NAME, devAdder, I2C_Direction_Transmitter);//七位地址,非十位地址
tmp = timeLimit;
while((--tmp)&&(!I2C_CheckEvent(I2C1_NAME,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)));
if(tmp == 0)
return -3;//i2c send address failed
//清零事件EV6 ADDR位(先讀SR1再讀SR2)
(void)(I2C1_NAME->SR1);
(void)(I2C1_NAME->SR2);//清除EV6事件
//--發送要寫入的目標寄存器
if(is16BitRegAdder)
{
I2C_SendData(I2C1_NAME, startRegAdder>>8);//發送高8位地址
tmp = timeLimit;
while((--tmp)&&(!I2C_CheckEvent(I2C1_NAME,I2C_EVENT_MASTER_BYTE_TRANSMITTING)));
if(tmp == 0)
return -4;//i2c high regAdder failed
}
I2C_SendData(I2C1_NAME, startRegAdder%256);//發送低8位地址
tmp = timeLimit;
while((--tmp)&&(!I2C_CheckEvent(I2C1_NAME,I2C_EVENT_MASTER_BYTE_TRANSMITTING)));
if(tmp == 0)
return -5;//i2c send low regAdder failed
/*第二步:開始讀取*/
//--再次生成起始位
I2C_GenerateSTART(I2C1_NAME, ENABLE);//發送起始位後,自動轉換爲主設備
tmp = timeLimit;
while((--tmp)&&(!I2C_CheckEvent(I2C1_NAME,I2C_EVENT_MASTER_MODE_SELECT)));
if(tmp == 0)
return -6;//i2c reSend start fail
//--再次發送7位設備地址(接收/讀取模式).調用I2C_CheckEvent會清除EV6事件(先讀SR1再讀SR2)
I2C_Send7bitAddress(I2C1_NAME, devAdder, I2C_Direction_Receiver);//七位地址,非十位地址。設置接收時會默認地址最後一位取反即爲1
tmp = timeLimit;
while((--tmp)&&(!I2C_CheckEvent(I2C1_NAME,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)));//EV6事件
if(tmp == 0)
return -7;//i2c reSend address failed
//--開始接收數據
while(index < bufSize)
{
if(index == bufSize-3)
{
//已經是倒數第三個了
tmp = timeLimit;
while((--tmp)&&(!I2C_GetFlagStatus(I2C1_NAME, I2C_FLAG_BTF)));//step1等待BTF置位(讀DR復位)
if(tmp == 0)
return index;//返回已經接收的字節數
I2C_AcknowledgeConfig(I2C1_NAME,DISABLE);//step2復位ACK
buf[index] = I2C_ReceiveData(I2C1_NAME);//step3讀取n-2數據
index++;
tmp = timeLimit;
while((--tmp)&&(!I2C_GetFlagStatus(I2C1_NAME, I2C_FLAG_BTF)));//step4再次等待BTF置位(讀DR復位)
if(tmp == 0)
return index;//返回已經接收的字節數
I2C_GenerateSTOP(I2C1_NAME, ENABLE);//step5生成停止位
buf[index++] = I2C_ReceiveData(I2C1_NAME);//step6讀取第n-1數據
tmp = timeLimit;//此操作就是爲了等待移位寄存器中的數據移入DR,相當於一個nop
buf[index++] = I2C_ReceiveData(I2C1_NAME);//step7讀取第n數據
//接收完成
I2C_AcknowledgeConfig(I2C1_NAME,ENABLE);//恢復響應
return index;//返回已經接收數量
}
tmp = timeLimit;
while((--tmp)&&(!I2C_CheckEvent(I2C1_NAME,I2C_EVENT_MASTER_BYTE_RECEIVED)));//EV7事件
if(tmp == 0)
return index;//返回已經接收的字節數
buf[index] = I2C_ReceiveData(I2C1_NAME);
index++;//接收下一字節
}
return 0;
}
參閱
STM32F407xx開發手冊中英文版
其中延時函數思路是借鑑了一位網友的,時間緣故(接近一個月了),一時找不到了。所以無法註明,特此指出。