本帖最后由 51hei社區 于 2016-1-11 04:55 編輯
本文已假設你了解了STM32的IIC的基礎知識,也大概了解了IIC會出現問題。
本文只簡單介紹兩個具體例子,其他依照處理即可。
準備物品:一個邏輯分析儀,這樣才能可靠了解I2C在哪里出錯了。某寶上很便宜,100以內。
例子:1BYTE接受和2BYTE接收。
解決辦法有兩種,意思都一樣:
1:暫時提升權限到“最高”,在"最高"中斷運行花費時間在10個C指令運行時間內,保守估計在1us以內(通過邏輯分析儀也可看到)。這基本滿足了大部分人需要,也應該能滿足實時系統需求。
2:禁止中斷,也是在特定IIC操控指令段運行的時候禁止中斷,花費時間和方法1比應該少一點。
本文借鑒了 這篇文章的方法 《淺談 STM32 硬件I2C的使用 (中斷方式 無DMA無最高優先級)》,可baidu獲得
但實際使用結果是,上文并沒有完全解決硬件I2C的方法,上文在測試中沒有出現異常時因為I2C被中斷打斷的次數不夠密集,只有在外部中斷剛好擊中I2C運行的某兩個指令之間的時候,I2C通信才會出現問題。
那么在解決I2C問題之前,需要創造一個足夠強悍的中斷,使得I2C中斷在運行的時候,基本每執行一句話都被打斷,這樣才能有效驗證I2C。以下測試環境都在STM32F103(72Mhz)芯片上運行
通過TIM2定時器產生中斷,在中斷函數里面進行延時,通過設定時間設定,產生一個基本都在中斷函數里運行的狀態,兩個中斷之間的時隙在1.25us,通過邏輯分析儀觀察,在每個I2C的中斷之間,例如開始發送中斷(EV5)和收到地址響應中斷(EV6)之間會被上述中斷函數打斷幾十到上百次(由于被打斷次數太多,沒數。只是看邏輯分析儀上面密密麻麻的被不停打斷)而EV5 和EV6中斷之間運行的I2C代碼一共也只有十幾條,所以驗證了I2C沒執行一句會被打斷一次。(如果還不放心,可以對一次發送或讀取操作進行多次反復,確保I2C每條語句執行之間都會被打斷)
中斷函數為,特別注意 delay_usj(25); - void TIM2_IRQHandler(void)
- {
- if(TIM_GetITStatus(TIM2,TIM_IT_Update)!=RESET)
- {
- TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
- LED_ON;
- //nop;
- delay_usj(25);
- LED_OFF;
- }
- }
[color=rgb(51, 102, 153) !important]復制代碼
延時函數
- void delay_usj(u32 n)
- {
- u8 j;
- while(n--)
- for(j=0;j<7;j++);
- }
[color=rgb(51, 102, 153) !important]復制代碼
中斷設置語句,特別注意TIM_TimeBaseStructure.TIM_Period = 22;//自動重裝值 ,TIM_TimeBaseStructure.TIM_Prescaler = 71; //36000
- TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2 ,ENABLE);
- TIM_DeInit(TIM2);
- TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
- TIM_TimeBaseStructure.TIM_Period = 22;//自動重裝值
- TIM_TimeBaseStructure.TIM_Prescaler = 71;//36000
- TIM_TimeBaseStructure.TIM_ClockDivision =TIM_CKD_DIV1;
- TIM_TimeBaseStructure.TIM_CounterMode =TIM_CounterMode_Up;
- TIM_TimeBaseStructure.TIM_RepetitionCounter =0x0;
- TIM_TimeBaseInit(TIM2,&TIM_TimeBaseStructure);
- TIM_ClearFlag(TIM2, TIM_FLAG_Update);
- TIM_ITConfig(TIM2, TIM_IT_Update,ENABLE);
- TIM_Cmd(TIM2, ENABLE);
[color=rgb(51, 102, 153) !important]復制代碼
通過特定時間設置,即上述特別注意的三個地方,通過這幾個值,能保證在我的機器上stm32f103上產生連綿不斷的中斷,中斷之間的運行時間有1.25us,在實際測試i2c時,在連續兩個事件之間,例如ev5和ev6之間會被打斷非常多次,而ev5和ev6之間的運行代碼才寥寥十幾行,再通過反復多次運行測試,可以確保了i2c的每條語句執行完畢后都會被外部中斷打斷。從而有效驗證i2c是否編寫正確。對于你的平臺,可以通過觀看中斷中的LED_ON;LED_OFF(實際是某個GPIO電燈管教),通過檢測邏輯分析儀來查看是否中斷足夠熱烈,足夠讓I2C事件之間產生足夠多的中斷。這個延時設置需要稍微花一點時間才能找到。
說完測試環境,說如何編寫I2C
根據相關芯片referencemanual和errata sheet,例如下文
I2C eventmanagement
Description
As describedin the I2C section of the STM8S microcontroller reference manual(RM0016),
theapplication firmware has to manage several software events beforethe current byte is
transferred.If the EV7, EV7_1, EV6_1, EV6_3, EV2, EV8, and EV3 events are notmanaged
before thecurrent byte is transferred, problems may occur such as receivingan extra byte,
reading thesame data twice, or missing data.
Workaround
When the EV7,EV7_1, EV6_1, EV6_3, EV2, EV8, and EV3 events cannot bemanaged
before thecurrent byte transfer, and before the acknowledge pulse when theACK control bit
changes, itis recommended to use I2C interrupts in nested mode and to makethem
uninterruptible byincreasing their priority to the highest priority in theapplication.
No fix isplanned for this limitation.
上述意思是說 EV7,EV7_1, EV6_1, EV6_3, EV2, EV8, andEV3這幾個事件不能被打斷,除了最高中端不會被打斷外沒有其他解決方法。
那么既然不能被打斷,就是用文頭說的兩個解決發放解決。
對于1BYTE接收,用解決方法1
例子如下- switch(RxLength)
- {
- case 1:
- //I2C_StretchClockCmd(I2C1,ENABLE);//經過驗證此句無用
- I2C1->CR1 &=((uint16_t)0xFBFF);//I2C_AcknowledgeConfig(I2C1,DISABLE);
- #ifdef ENABLEHIGHIRQ //暫時I2C1權限提升到NVIC_IRQChannelPreemptionPriority = 0,NVIC_IRQChannelSubPriority =15;實際上就是不可打斷。是否用此保護塊依賴于你的使用環境,如果沒有外部中斷或者外部中斷優先級比i2c低,可以不使用此保護塊
- tmppriority = (0x700 - ((SCB->AIRCR) &(uint32_t)0x700))>> 0x08;
- tmppre = (0x4 - tmppriority);
- tmpsub = tmpsub >> tmppriority;
- tmppriority = (uint32_t)0 << tmppre;//NVIC_IRQChannelPreemptionPriority = 0
- tmppriority |= 15 & tmpsub; // NVIC_IRQChannelSubPriority =15;
- tmppriority = tmppriority << 0x04;
- NVIC->IP[I2C1_EV_IRQn] = tmppriority;//I2C1_EV_IRQn=31
- NVIC->ISER[I2C1_EV_IRQn >> 0x05] =
- (uint32_t)0x01 << (I2C1_EV_IRQn &(uint8_t)0x1F);
- #endif
- //以下兩句為必須一起執行的語句,如果讀了SR2后被外部中斷打斷,則I2C一但讀取SR2后則硬件開始傳送下一個數據,而STOP位沒有被及時賦值,則導致I2C通信異常。加上保護塊后則沒有問題。
- //在最高終端運行的時間是,上一句+本保護塊2句+收尾幾局,運行時間很短暫
- (void) I2C1->SR2;//讀SR2
- I2C1->CR1 |= ((uint16_t)0x0200); //write stop bit;
- #ifdef ENABLEHIGHIRQ //回復之前的I2C權限
- tmppriority = (0x700 - ((SCB->AIRCR) &(uint32_t)0x700))>> 0x08;
- tmppre = (0x4 - tmppriority);
- tmpsub = tmpsub >> tmppriority;
- tmppriority = (uint32_t)PreemptionPriority <<tmppre;
- tmppriority |= SubPriority & tmpsub;
- tmppriority = tmppriority << 0x04;
- NVIC->IP[I2C1_EV_IRQn] = tmppriority;
- NVIC->ISER[I2C1_EV_IRQn >> 0x05] =
- (uint32_t)0x01 << (I2C1_EV_IRQn &(uint8_t)0x1F);
- #endif
[color=rgb(51, 102, 153) !important]復制代碼
2BYTE接收,用解決方法2:
- case2:
- I2C1->CR1 |= ((uint16_t)0x0400);//I2C_AcknowledgeConfig(I2C1,ENABLE);
- I2C1->CR1 |=I2C_NACKPosition_Next;//I2C_NACKPositionConfig(I2C1,I2C_NACKPosition_Next);
- I2C_StretchClockCmd(I2C1,ENABLE);
- I2C_ITConfig(I2C1, I2C_IT_EVT ,ENABLE);
- level = rt_hw_interrupt_disable(); //保護塊開始
- (void) I2C1->SR2;// once addr is clearing .the above code isgoing to run ,and bus is busy.
- I2C1->CR1 &=((uint16_t)0xFBFF);//I2C_AcknowledgeConfig(I2C1,DISABLE);
- rt_hw_interrupt_enable(level);
- return;
[color=rgb(51, 102, 153) !important]復制代碼
rt_hw_interrupt_disable,rt_hw_interrupt_enable的意思是只允許NMI 和 hard fault異常,其他中斷/ 異常都被屏蔽(當前 CPU優先級=0)。是我在rtthread rtos里面找到的代碼
- ;
- rt_hw_interrupt_disable PROC
- EXPORT rt_hw_interrupt_disable
- MRS r0, PRIMASK
- CPSID I
- BX LR
- ENDP
- ;
- rt_hw_interrupt_enable PROC
- EXPORT rt_hw_interrupt_enable
- MSR PRIMASK, r0
- BX LR
- ENDP
[color=rgb(51, 102, 153) !important]復制代碼
代碼用匯編編寫,簡潔快速。可以參看rtthread相關代碼。
對于2byte以上,推薦dma方法,也請使用上述中斷方法測試,可以確保dma的操控也不會出錯。
實際使用效果如邏輯分析儀展示
S1是最低放大圖片,由于中斷過于頻繁,導致I2C的每傳送一個BYTE,或者說每一個中斷事件之間都被密密麻麻的中斷擁塞,導致I2C的運行時間被長時間阻隔,同時,I2C的中斷運行代碼也意味著每運行一句會被中斷打斷。
S2是最高放大圖片,對應著一個EV5開始事件,CHANEL2的低電平意味著LED中斷運行,高電平意味著LED中斷退出,CPU運行其他程序和I2C中斷。可以看到每個LED中斷之間間隔很短(實際不是1.25us,受邏輯分析數據采樣率影響,但也差不多,不影響分析)
S3可以看到在每個I2C事件之間都填充了非常多的LED中斷事件,由于事件過多,以至于圖片無法展示到下一個I2C中斷
S4對應著某個I2C事件,可以看到I2C的事件處理完后和LED中斷并行的畫面 S1 可以看到SCL和SDA 


]
|