搜索
bottom↓
回复: 48

STM32 IIC 硬件通信解决关键点,已验证通过

  [复制链接]

出0入0汤圆

发表于 2015-2-16 17:21:56 | 显示全部楼层 |阅读模式
本帖最后由 iguesser 于 2015-2-16 23:32 编辑

本文已假设你了解了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);
  1. void TIM2_IRQHandler(void)
  2. {
  3.          if(TIM_GetITStatus(TIM2,TIM_IT_Update)!=RESET)
  4.         {
  5.                 TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
  6.        
  7.          LED_ON;
  8.          //nop;
  9.          delay_usj(25);
  10.          
  11.          LED_OFF;
  12.          }
  13. }
复制代码


延时函数
  1. void delay_usj(u32 n)
  2. {
  3.         u8 j;
  4.         while(n--)
  5.         for(j=0;j<7;j++);                    
  6. }
复制代码


中断设置语句,特别注意TIM_TimeBaseStructure.TIM_Period = 22; //自动重装值  , TIM_TimeBaseStructure.TIM_Prescaler = 71;       //36000
  1.        
  2.   TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;

  3.   RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2 , ENABLE);
  4.        
  5.   TIM_DeInit(TIM2);   

  6.   TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
  7.        
  8.   TIM_TimeBaseStructure.TIM_Period = 22; //自动重装值         
  9.   TIM_TimeBaseStructure.TIM_Prescaler = 71;       //36000  
  10.   TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;      
  11.   TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  
  12.         TIM_TimeBaseStructure.TIM_RepetitionCounter = 0x0;
  13.   TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
  14.   
  15.         TIM_ClearFlag(TIM2, TIM_FLAG_Update);
  16.         TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);

  17.   TIM_Cmd(TIM2, ENABLE);
复制代码


通过特定时间设置,即上述特别注意的三个地方,通过这几个值,能保证在我的机器上stm32f103上产生连绵不断的中断,中断之间的运行时间有1.25us,在实际测试i2c时,在连续两个事件之间,例如ev5和ev6之间会被打断非常多次,而ev5和ev6之间的运行代码才寥寥十几行,再通过反复多次运行测试,可以确保了i2c的每条语句执行完毕后都会被外部中断打断。从而有效验证i2c是否编写正确。对于你的平台,可以通过观看中断中的LED_ON;LED_OFF(实际是某个GPIO电灯管教),通过检测逻辑分析仪来查看是否中断足够热烈,足够让I2C事件之间产生足够多的中断。这个延时设置需要稍微花一点时间才能找到。

说完测试环境,说如何编写I2C
根据相关芯片reference manual和errata sheet,例如下文
I2C event management
Description
As described in the I2C section of the STM8S microcontroller reference manual (RM0016),
the application firmware has to manage several software events before the current byte is
transferred. If the EV7, EV7_1, EV6_1, EV6_3, EV2, EV8, and EV3 events are not managed
before the current byte is transferred, problems may occur such as receiving an extra byte,
reading the same data twice, or missing data.
Workaround
When the EV7, EV7_1, EV6_1, EV6_3, EV2, EV8, and EV3 events cannot be managed
before the current byte transfer, and before the acknowledge pulse when the ACK control bit
changes, it is recommended to use I2C interrupts in nested mode and to make them
uninterruptible by increasing their priority to the highest priority in the application.
No fix is planned for this limitation.
上述意思是说 EV7, EV7_1, EV6_1, EV6_3, EV2, EV8, and EV3这几个事件不能被打断,除了最高中端不会被打断外没有其他解决方法。

那么既然不能被打断,就是用文头说的两个解决发放解决。
对于1BYTE接收,用解决方法1
例子如下
  1.                 switch(RxLength)
  2.                 {
  3.                            
  4.                         case 1:
  5.                         //I2C_StretchClockCmd(I2C1,ENABLE);//经过验证此句无用
  6.                         I2C1->CR1 &= ((uint16_t)0xFBFF);//I2C_AcknowledgeConfig(I2C1, DISABLE);
  7. #ifdef         ENABLEHIGHIRQ   //暂时I2C1权限提升到 NVIC_IRQChannelPreemptionPriority = 0,NVIC_IRQChannelSubPriority = 15;实际上就是不可打断。是否用此保护块依赖于你的使用环境,如果没有外部中断或者外部中断优先级比i2c低,可以不使用此保护块

  8.     tmppriority = (0x700 - ((SCB->AIRCR) & (uint32_t)0x700))>> 0x08;
  9.     tmppre = (0x4 - tmppriority);
  10.     tmpsub = tmpsub >> tmppriority;

  11.     tmppriority = (uint32_t)0 << tmppre;   //NVIC_IRQChannelPreemptionPriority = 0
  12.     tmppriority |=  15 & tmpsub; //                NVIC_IRQChannelSubPriority = 15;
  13.     tmppriority = tmppriority << 0x04;
  14.         
  15.     NVIC->IP[I2C1_EV_IRQn] = tmppriority;        //I2C1_EV_IRQn=31
  16.    
  17.     /* Enable the Selected IRQ Channels --------------------------------------*/
  18.     NVIC->ISER[I2C1_EV_IRQn >> 0x05] =
  19.       (uint32_t)0x01 << (I2C1_EV_IRQn & (uint8_t)0x1F);
  20. #endif
  21.                         //以下两句为必须一起执行的语句,如果读了SR2 后被外部中断打断,则I2C一但读取SR2后则硬件开始传送下一个数据,而STOP位没有被及时赋值,则导致I2C通信异常。加上保护块后则没有问题。
  22.                         //在最高终端运行的时间是,上一句+本保护块2句+收尾几局,运行时间很短暂
  23.                           (void) I2C1->SR2;//读SR2

  24.                         I2C1->CR1 |= ((uint16_t)0x0200);  //write stop bit;

  25. #ifdef         ENABLEHIGHIRQ //回复之前的I2C权限
  26.    tmppriority = (0x700 - ((SCB->AIRCR) & (uint32_t)0x700))>> 0x08;
  27.     tmppre = (0x4 - tmppriority);
  28.     tmpsub = tmpsub >> tmppriority;

  29.     tmppriority = (uint32_t)PreemptionPriority << tmppre;
  30.     tmppriority |=  SubPriority & tmpsub;
  31.     tmppriority = tmppriority << 0x04;
  32.         
  33.     NVIC->IP[I2C1_EV_IRQn] = tmppriority;
  34.    
  35.     /* Enable the Selected IRQ Channels --------------------------------------*/
  36.     NVIC->ISER[I2C1_EV_IRQn >> 0x05] =
  37.       (uint32_t)0x01 << (I2C1_EV_IRQn & (uint8_t)0x1F);

  38. #endif
复制代码


2BYTE接收,用解决方法2:
  1.                 case 2:
  2.                         I2C1->CR1 |= ((uint16_t)0x0400);//I2C_AcknowledgeConfig(I2C1, ENABLE);
  3.                         I2C1->CR1 |= I2C_NACKPosition_Next;//I2C_NACKPositionConfig(I2C1,I2C_NACKPosition_Next);       
  4.                         I2C_StretchClockCmd(I2C1,ENABLE);
  5.                         I2C_ITConfig(I2C1, I2C_IT_EVT , ENABLE);        

  6.         level = rt_hw_interrupt_disable(); //保护块开始
  7.                         (void) I2C1->SR2;// once addr is clearing .the above code is going to run ,and bus is busy.

  8.                         I2C1->CR1 &= ((uint16_t)0xFBFF);//I2C_AcknowledgeConfig(I2C1, DISABLE);

  9.         rt_hw_interrupt_enable(level);

  10.                         return;
复制代码


rt_hw_interrupt_disable,rt_hw_interrupt_enable的意思是只允许 NMI 和 hard  fault 异常,其他中断/  异常都被屏蔽(当前 CPU 优先级=0)。是我在rtthread rtos里面找到的代码
  1. ;/*
  2. ; * rt_base_t rt_hw_interrupt_disable();
  3. ; */
  4. rt_hw_interrupt_disable    PROC
  5.     EXPORT  rt_hw_interrupt_disable
  6.     MRS     r0, PRIMASK
  7.     CPSID   I
  8.     BX      LR
  9.     ENDP

  10. ;/*
  11. ; * void rt_hw_interrupt_enable(rt_base_t level);
  12. ; */
  13. rt_hw_interrupt_enable    PROC
  14.     EXPORT  rt_hw_interrupt_enable
  15.     MSR     PRIMASK, r0
  16.     BX      LR
  17.     ENDP
复制代码


代码用汇编编写,简洁快速。可以参看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中断并行的画面

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

发表于 2015-2-16 17:29:01 | 显示全部楼层
收藏了,谢谢!

出0入0汤圆

发表于 2015-2-16 20:33:27 | 显示全部楼层
那么在解决I2C问题之前,需要创造一个足够强悍的中断,使得I2C中断在运行的时候,基本每执行一句话都被打断,这样才能有效验证I2C。


Reading ST's errata and CPAL drivers would let you to believe that, when in fact it is incorrect.

The issue is with poorly written documents and poorly written library.

Here is from STM8S' reference manual. The key is to clear the EV6 flag, highlighted in red, - the standard library calls do not do that.

Once it is done, the i2c works just as expected.

BTW, the same works for STM32F chips that suffer from the same problem. Except that you clear it by reading SR1 and SR2 (SR1 and SR3 on STM8S chips).

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

发表于 2015-2-16 20:51:15 | 显示全部楼层
学习一下。
非常感谢!

出0入0汤圆

发表于 2015-2-16 21:31:28 来自手机 | 显示全部楼层
学习了。谢谢

出5入42汤圆

发表于 2015-2-16 22:07:08 | 显示全部楼层
stm32的I2C比较难用,还是端口模拟简单。据说是为了规避飞利浦的专利。

出0入0汤圆

 楼主| 发表于 2015-2-16 22:09:39 | 显示全部楼层
millwood0 发表于 2015-2-16 20:33
Reading ST's errata and CPAL drivers would let you to believe that, when in fact it is incorrect.
...

what's your point?
in fact the software sequence is reading sr1 ,do something.
then reading sr2 or sr3 independent but which  will put the i2c in the runaway mode if an external interrupt hit the code after reading sr2 or sr3

出0入0汤圆

 楼主| 发表于 2015-2-16 22:12:03 | 显示全部楼层
确实不好弄。但是此问的目的在于说明其实完全能用。在普遍理解只能使用最高中断的误区下。
在特定条件下,例如上了操作系统以后,或者为了优化功耗,优化cpu 负载下,还是需要这种方式的。

出0入0汤圆

 楼主| 发表于 2015-2-16 22:15:51 | 显示全部楼层
millwood0 发表于 2015-2-16 20:33
Reading ST's errata and CPAL drivers would let you to believe that, when in fact it is incorrect.
...

实际上原文引用的errata就是stm8s的errata。
不管你是否严格按照reference manual的操作顺序,在读取sr2或者sr3后一刹那都会出现临界风险点。

出0入0汤圆

发表于 2015-2-16 23:28:11 | 显示全部楼层
给力,分析到点上,IIC是STM32败笔,

出0入0汤圆

 楼主| 发表于 2015-2-16 23:38:41 | 显示全部楼层
所有问题的根结在于一旦读取SR2后,I2C将自动开始下一次数据传输,问题在于SR2读取以后 我们还需要做其他设置动作,一旦此时被外部中断打断,则后续动作无法按时完成操作并延后操作,导致出现异常。
这个问题是否会出现在于是否恰好有一个中断刚好击中读取SR2之后,这个很隐晦,一般很难中奖。所以扑朔迷离,但是加上保护块以后就可以放心用了。

出0入0汤圆

发表于 2015-2-17 08:19:47 | 显示全部楼层
不知道这个问题在F2到F4系列里有所改善吗?

出0入8汤圆

发表于 2015-2-17 18:12:57 | 显示全部楼层
   谢谢分享,以前用过STM32F1的IIC,没遇到问题.

出0入0汤圆

发表于 2015-2-18 06:33:07 来自手机 | 显示全部楼层
ii2c紧记1

出0入0汤圆

发表于 2015-2-18 15:08:50 来自手机 | 显示全部楼层
楼主分析的透彻

出0入0汤圆

发表于 2015-2-18 22:41:40 | 显示全部楼层
用CPAL库

出0入0汤圆

发表于 2015-2-18 22:52:48 | 显示全部楼层
当系统架构清晰的情况下。大多数系统直接使用io模拟就好了。很多时候用硬件或者CPAL库都是洁癖。比较IIC的总线速率就放在那边。

出0入0汤圆

发表于 2015-2-26 21:29:28 | 显示全部楼层
前两天玩hmc磁力计  要用i2c 然后用cube 无脑图形配置 然后就能用了  不仅无阻赛读取写入而且有硬件错误自动回复和完成通讯结束函数接口
还有i2c这么脆弱 给给最高级中断有什么不可的

出0入0汤圆

发表于 2015-5-14 15:14:29 | 显示全部楼层
MARK   楼主分析的很到位   学习了

出0入0汤圆

发表于 2015-5-14 17:46:09 | 显示全部楼层
STM32硬件I2C问题,收藏了,谢谢分享!

出0入0汤圆

发表于 2015-5-14 22:37:52 | 显示全部楼层
stm32的iic,我直接用c51的io端口模拟程序,直接移植过来,没有发现问题.

出0入0汤圆

发表于 2015-5-15 06:00:03 | 显示全部楼层
谢谢, 学习了.

出0入0汤圆

发表于 2015-5-15 14:16:14 | 显示全部楼层
很不错,学习,mark下

出0入0汤圆

发表于 2015-5-16 08:51:39 | 显示全部楼层
谢谢, 学习了.

出0入0汤圆

发表于 2015-5-28 21:37:50 | 显示全部楼层
  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;

这能成功设置吗,???访问钥匙:任何对该寄存器的写操作,都必须
同时把0x05FA 写入此段,否则写操作被忽略。
若读取此半字,则0xFA05  

出0入0汤圆

发表于 2015-5-29 11:29:13 | 显示全部楼层
楼主完整的程序可以发上来吗??

出0入0汤圆

发表于 2015-5-30 13:31:50 | 显示全部楼层
帅,mark,求完整代码

出0入0汤圆

发表于 2015-10-8 15:30:16 | 显示全部楼层
stm32 iic  收藏了,谢谢

出0入0汤圆

发表于 2015-10-9 06:12:50 | 显示全部楼层
一直在用模拟I2C, 有空试下, 谢谢楼主.

出0入0汤圆

发表于 2015-10-9 09:24:21 | 显示全部楼层
这个不错,赞一个

出0入0汤圆

发表于 2015-10-17 17:00:38 | 显示全部楼层
师兄使用过硬件IIC,据说官方给出了解决的方案,我反正一直用的是模拟的

出0入0汤圆

发表于 2015-10-18 21:46:08 | 显示全部楼层
有机会验证一下

出0入0汤圆

发表于 2015-11-12 13:27:04 | 显示全部楼层
被IIC折腾的体无完肤的表示要果断收藏

出0入0汤圆

发表于 2015-11-12 13:44:41 | 显示全部楼层
我看PX4Flow代码中使用的就是硬件IIC运行没有啥问题

出0入0汤圆

发表于 2015-12-3 18:47:42 | 显示全部楼层
最近正在看看F4的硬件IIC是否有问题,先收藏,多谢楼主的分享

出0入0汤圆

发表于 2015-12-3 20:35:50 | 显示全部楼层
收藏,多谢楼主的分享

出0入0汤圆

发表于 2015-12-27 21:57:11 | 显示全部楼层
收藏,以后肯定是有用的!

出0入0汤圆

发表于 2015-12-29 02:33:38 | 显示全部楼层
感谢分享!收藏!

出0入0汤圆

发表于 2016-1-7 11:13:28 | 显示全部楼层
MARK   楼主分析的很到位   
硬件I2C的确非常头疼。

出0入0汤圆

发表于 2016-2-3 21:40:09 | 显示全部楼层
EEPROM在ucos中就不好用,正在琢磨中!

出0入0汤圆

发表于 2016-2-3 22:28:20 | 显示全部楼层
iguesser 发表于 2015-2-16 22:15
实际上原文引用的errata就是stm8s的errata。
不管你是否严格按照reference manual的操作顺序,在读取sr2 ...

这么迟才看见这篇文章,距离写《浅谈 STM32 硬件I2C的使用 (中断方式 无DMA 无最高优先级)》这篇文章已经3年多了,刚刚检查了一下当年的代码,发现读SR2的代码都在软件产生的时钟延展期间执行,这也许是我没有遇上异常的原因。我已经在原文中增加相关说明,谢谢楼主的分享!

出0入0汤圆

发表于 2016-2-15 13:30:09 | 显示全部楼层
racede 发表于 2016-2-3 22:28
这么迟才看见这篇文章,距离写《浅谈 STM32 硬件I2C的使用 (中断方式 无DMA 无最高优先级)》这篇文章已经 ...


前辈,居然能在这里遇到您。昨天晚上再次翻阅了您的博客,发现您对文章做了修改,所以跟踪到了这里.....


楼主iguesser 说的:
所有问题的根结在于一旦读取SR2后,I2C将自动开始下一次数据传输,问题在于SR2读取以后 我们还需要做其他设置动作,一旦此时被外部中断打断,则后续动作无法按时完成操作并延后操作,导致出现异常。
这个问题是否会出现在于是否恰好有一个中断刚好击中读取SR2之后,这个很隐晦,一般很难中奖。所以扑朔迷离,但是加上保护块以后就可以放心用了。


“这个问题是否会出现在于是否恰好有一个中断刚好击中读取SR2之后”
问题出在SR2后是否还会有一个中断正好击中在SR2和下一条语句之中
例如:

(void)I2Cx->SR2;
正好击中在中间位置,而且抢占时间很长,时间长到I2C已经进行下一个操作了
I2Cx->xxx;


所以我们需要模拟正好击中这两句话之间的环境,测试方法为在这两句话之间加入一个小delay,这样就确保外部中断可以百分百击中到这两句话之间

(void)I2Cx->SR2;
delay(xxx);          // 外部中断足够猛烈,并且delay足够长,可以百分百确保一击命中
I2Cx->xxx;

感谢racede和iguesser前辈的经验分享,thx

出0入0汤圆

发表于 2016-2-15 13:55:56 | 显示全部楼层
不错,或许用得上。

出0入0汤圆

发表于 2016-2-15 14:27:38 | 显示全部楼层
mark一下

出0入0汤圆

发表于 2016-2-15 22:35:24 | 显示全部楼层
这个分析的比较彻底

出0入0汤圆

发表于 2017-1-9 08:49:29 | 显示全部楼层
谢谢楼主,收藏备用了!

出190入0汤圆

发表于 2017-2-19 08:13:00 来自手机 | 显示全部楼层
分析的非常好

出0入0汤圆

发表于 2017-2-19 21:08:43 | 显示全部楼层
收藏了,谢谢~

出0入0汤圆

发表于 2017-3-7 18:14:48 | 显示全部楼层
学习!收藏了~
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-4-20 07:22

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表