打印

关于定时器的使用

[复制链接]
楼主: 511
手机看帖
扫描二维码
随时随地手机跟帖
21
511|  楼主 | 2009-7-13 21:57 | 只看该作者 回帖奖励 |倒序浏览

谢谢

使用特权

评论回复
22
511|  楼主 | 2009-7-13 22:00 | 只看该作者

希望香主认认真真看我写的疑问

使用特权

评论回复
23
香水城| | 2009-7-14 15:43 | 只看该作者

经过实际测试和分析,结论是STM32功能正常

先来看看为什么楼主的下面程序,增加蓝色的这2行会很快产生更新中断:

第1行  TIM_UpdateDisableConfig(TIM4,ENABLE);
第2行  TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure); 
第3行  TIM_UpdateDisableConfig(TIM4,DISABLE);

上面第2行的作用就是把之前设置的那些参数,TIM_Period、TIM_Prescaler、TIM_ClockDivision、TIM_CounterMode等写入对应的寄存器。这里关键的是TIM_Period和TIM_Prescaler这2个参数,它们分别对应自动重装载寄存器(TIMx_ARR)和预分频器(TIMx_PSC)。

从定时器的功能框图中我们看到TIMx_ARR和TIMx_PSC,都分别有对应的影子寄存器;在实际操作中,影子寄存器的内容参与操作,只有在产生更新事件时,可以把软件操作的寄存器内容传送到影子寄存器中,这样保证正常操作的任何一个周期不受软件操作的影响,同时允许在某个时刻通过更新事件的触发,同时刷新所有操作参数。

因为不能通过写入TIMx_ARR和TIMx_PSC,直接把需要的数值传送到真正起作用的影子寄存器,所以在TIM_TimeBaseInit()函数中通过软件产生一个更新事件(TIMx_EGR的UG位),强迫地把TIMx_ARR和TIMx_PSC的内容传送到对应的影子寄存器中。

但是上述程序段中的第1行,恰恰是要求不产生更新事件,也就是说不允许把TIMx_ARR和TIMx_PSC的内容传送到对应的影子寄存器中,这样造成的结果是:TIMx_PSC对应的影子寄存器没有被写入正确的数值,仍然是复位状态"0",即分频值为1。最终的结果就是你看到的,第一次的更新中断很快就来了,从第二次开始才正常,这是因为第一次的更新中断时,TIMx_PSC对应的影子寄存器被刷新为设置的数值。

这里还有一个问题要解释,就是为什么第一次的更新中断不是马上到来,而是过一会儿才有?这是因为TIMx_CR1寄存器中的自动装载预装载使能位(ARPE)为"0"时,允许写入TIMx_ARR的同时立即传送到它的影子寄存器,而不必等到更新事件,ARPE的复位默认值刚好是"0",这样TIMx_ARR的正确数值参与了操作,所以第一次的更新中断是过了一会儿才出现。

总结一下:增加了上述程序段中的第1行,关闭了更新事件的产生,使得预分频器的设置被延后到产生第一次更新中断的时候,上述程序段中的第3行又重新允许更新事件的产生,导致第一次更新中断之后一切回复正常。



这是TIM_TimeBaseInit()函数的源码,红色标出的那行就是软件产生更新事件。

void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct)
{
  /* Check the parameters */
  assert_param(IS_TIM_123458_PERIPH(TIMx)); 
  assert_param(IS_TIM_COUNTER_MODE(TIM_TimeBaseInitStruct->TIM_CounterMode));
  assert_param(IS_TIM_CKD_DIV(TIM_TimeBaseInitStruct->TIM_ClockDivision));

  /* Select the Counter Mode and set the clock division */
  TIMx->CR1 &= CR1_CKD_Mask & CR1_CounterMode_Mask;
  TIMx->CR1 |= (u32)TIM_TimeBaseInitStruct->TIM_ClockDivision |
                TIM_TimeBaseInitStruct->TIM_CounterMode;
  /* Set the Autoreload value */
  TIMx->ARR = TIM_TimeBaseInitStruct->TIM_Period ;

  /* Set the Prescaler value */
  TIMx->PSC = TIM_TimeBaseInitStruct->TIM_Prescaler;

  /* Generate an update event to reload the Prescaler value immediatly */
  TIMx->EGR = TIM_PSCReloadMode_Immediate;
    
  if (((*(u32*)&TIMx) == TIM1_BASE) || ((*(u32*)&TIMx) == TIM8_BASE))  
  {
    /* Set the Repetition Counter value */
    TIMx->RCR = TIM_TimeBaseInitStruct->TIM_RepetitionCounter;
  }        
}

使用特权

评论回复
24
香水城| | 2009-7-14 16:02 | 只看该作者

关于同事B的TIM3问题

这个问题的原帖抄录如下:

程序再初始化时执行如下语句,就会进一次TIM3中断,但这是TIM3并没有开启。
    TIM_TimeBaseInitTypeDef   TIM_TimeBaseStructure;  
   
    /* Time Base configuration */    
    TIM_TimeBaseStructure.TIM_Period = 0xFFFF;
    TIM_TimeBaseStructure.TIM_Prescaler = 0x00;
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);    
    //下降沿计数值加1,
    TIM_ETRClockMode2Config(TIM3, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_Inverted, 0);    
      
   TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE );


通过前面(23楼)的分析,可以清楚地看到,为了能够立刻使预分频器TIMx_PSC的内容生效(进入对应的影子寄存器),在TIM_TimeBaseInit()函数中通过软件产生了一个更新事件,如果在退出这个函数后,打开更新中断之前,没有清除更新事件标记,即使没有使能定时器,也会产生更新中断。

所以在使能更新中断之前,程序中的这句话是很关键的:
  TIM_ClearFlag(TIM4, TIM_FLAG_Update);

使用特权

评论回复
25
lut1lut| | 2009-7-14 16:46 | 只看该作者

瓦噻,终于真相大白了!

看来STM32的Timer真是灵活得很哪!

不过要把各个环节、细节都捋一遍,弄清楚,还真要费一番工夫。

经过这个帖子的学习,四点心得:

1)PSC的赋值是缓冲"buffered"的;必须有更新事件到来,才会真正赋上值;

2) ARR的赋值可以是直接见效的;也可以是缓冲的,取决于APRE

3) TIM_TimeBaseInit() 初始化事件基这个函数里面,通过设置UG,人为产生了一个更新事件,来确保PSC和ARR能够赋上值。

4)因此,为了后面不必要的中断,需要在3)时间基初始化后清除标志,TIM_ClearFlag(TIMx, TIM_FLAG_Update) 再打开相应中断TIM_ITConfig(TIMx, TIM_IT_Update , ENABLE)。否则即使没有开始运行timer,一样要进中断。

好贴!学习了,收藏了~~~

使用特权

评论回复
26
511|  楼主 | 2009-7-14 18:22 | 只看该作者

谢谢香主的解答,感觉又清晰许多,等有空再细看一遍

第三个问题的工程文件在13楼
我希望按键启动定时器的时候不进更新中断,问题是一按键启动就进一次更新中断~~~

使用特权

评论回复
27
香水城| | 2009-7-14 18:54 | 只看该作者

希望按键启动定时器的时候不进更新中断,为什么要使能更

直接关闭这个中断就好了。

使用特权

评论回复
28
511|  楼主 | 2009-7-14 20:40 | 只看该作者

我想要溢出更新中断啊,香主

不使能,溢出更新中断不也没了么?

使用特权

评论回复
29
香水城| | 2009-7-14 20:55 | 只看该作者

能否说清楚一些你得到的结果,我没有看出第3点错哪里了

这是你在15楼的提问:
第三个原因:为什么即使不加这2行代码,如果用触发模式启动定时器还是会进更新中断?即使把URS位置一也无效,这也是疑问3,也是最大的疑问。
  TIM_TimeBaseStructure.TIM_Period = 6000;  
  TIM_TimeBaseStructure.TIM_Prescaler = 65535;    
  TIM_TimeBaseStructure.TIM_ClockDivision = 0;    
  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; 
  TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);   

  TIM_ICStructInit(&TIM_ICInitStructure);
  TIM_ICInitStructure.TIM_ICMode = TIM_ICMode_ICAP;            
  TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;           
  TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Falling;         
  TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;       
  TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;       
  TIM_ICInitStructure.TIM_ICFilter = 0;  
  TIM_ICInit(TIM4, &TIM_ICInitStructure);
  
  TIM_ClearFlag(TIM4, TIM_FLAG_Update);
  TIM_ITConfig(TIM4, TIM_IT_Update , ENABLE); 
    /* Input Trigger selection */
  TIM_SelectInputTrigger(TIM4, TIM_TS_TI1FP1);
    /* Slave Mode selection: Trigger Mode */
  TIM_SelectSlaveMode(TIM4, TIM_SlaveMode_Trigger);
  while (1);


我的问题是:何时进的中断,你怎么知道那不是你要的?


另外,给你一个诊断的思路:

1)定义2个全局变量:
  vu32 count1, count2;

2)在while循环中对这2个变量不断递加:
  count1 = count2 = 0;
  while (1) {
    count1++;
    if (count1==0)
      count2++;
  }

3)在更新中断中放置调试断点,并通过调试器设置"调试时停止定时器计数"位,这个位的作用是程序在断点处停止时,定时器的计数器会暂停。

4)每次停在上述断点时,记录下count1和count2的数值,通过比较可以大致知道,每次进入中断的时间,借次可以判断(猜测)更新中断产生的原因。

在此之前我并没有看过库函数的代码,我就是用这个方法,发现你的程序在加了那2行后,第1次进更新中断与以后进更新中断的时间有某种倍数关系,从而判断预分频数值在第1次没有生效,最后进到相应函数找到根源。

你可以试试用这个方法,追踪一下看看能否找到问题。

使用特权

评论回复
30
511|  楼主 | 2009-7-14 21:08 | 只看该作者

通过调试器设置"调试时停止定时器计数"位在哪啊?

香主哥哥能给个图片吗?
我原以为这个功能是要自己写DEBUG代码实现呢,原来调试器都可以啊
PS:我设定的时间是好多秒溢出一次,但是一按下按键就进中断(灯亮),肯定不是溢出中断啦~~~
除了这个不正常以外,其他都看起来正常,每隔设定秒数溢出一次(灯闪),我掐了秒表的,挺准

使用特权

评论回复
31
511|  楼主 | 2009-7-14 21:20 | 只看该作者

详细:

使用特权

评论回复
32
511|  楼主 | 2009-7-14 21:21 | 只看该作者

图2

使用特权

评论回复
33
香水城| | 2009-7-14 21:30 | 只看该作者

用我给出的办法,可以初步判断出按下按键是马上进中断还

这很重要,如果是马上进中断,表示定时器还没有开始工作,此时count1和count2应该接近于0;如果是过一会儿进中断,表示定时器已经开始工作,可能是某个参数设置有问题,此时count1和count2应该是一个比较小的数。

我这可是授人以渔啊,如果你只想要鱼,就慢慢等我去吊了再送给你,呵呵~~~~


通过调试器设置"调试时停止定时器计数"位,这个操作很简单,你只需要打开外设寄存器窗口,找到DBG那组寄存器,改变对应的位就可以了,随时改随时生效,很方便哦。

使用特权

评论回复
34
511|  楼主 | 2009-7-14 22:04 | 只看该作者

有错,重新编辑

使用特权

评论回复
35
McuIsp| | 2009-7-14 22:27 | 只看该作者

stm32的定时器相当复杂。

目前缺乏一篇好的**(简明透彻)。
老实说,我也用过不少时间的stm32了,真没从头到尾看明白过Manual里定时器一节。
用stm32 timer做过pwm语音/pwm升压控制.......

使用特权

评论回复
36
香水城| | 2009-7-15 08:30 | 只看该作者

哈哈,36楼的经历正说明STM32的功能丰富灵活

相信您使用C语言的历史比使用STM32的时间长很多,但您能说C语言的所有功能都熟悉吗?


针对STM32的功能非常丰富和灵活的特点,我们目前正在编写一些深入浅出的培训材料,希望能够帮助大家更好地理解和使用STM32的各种功能。

使用特权

评论回复
37
lut1lut| | 2009-7-15 11:08 | 只看该作者

仍然使用香主变量++的办法

那你可不可以在按键产生的时候,比如导致的中断里,设置一个flag。主循环中,检查到这个flag再开始对两个变量++呢。

使用特权

评论回复
38
lut1lut| | 2009-7-15 13:55 | 只看该作者

请更新固件库到至少2.0.3。你还在用1.0

从你的第三个项目中的代码中看到,LZ还是在用最最老的固件库。
V1.0中TIM_TimeBaseInit()没有对UG的设置;因此用户对ARR和PSC赋的新值,不能马上起作用。

PSC 还是初始值,估计0的样子,即TIM的时钟没有经过分频。

请更新到新的固件库。
需要注意的是:在调用了TIM_TimeBaseInit()后,为了避免不必要的中断,在开TIM的各个中断源前,先清标志。即24楼的最后一句话。

使用特权

评论回复
39
香水城| | 2009-7-15 14:03 | 只看该作者

我们都太粗心了——现在才发现楼主同时使用了不同的固件

在23楼讨论第1个问题时是假定、也是按照使用STM32固件库2.03版本,下载了楼主13楼的项目,测试下来发现你这个项目居然使用的是固件库1.0版本。

根本问题还是23楼指出的,预分频器的初值没有传送到影子寄存器。在固件库1.0版本的TIM_TimeBaseInit()函数中,缺少关键的那一行(23楼中红色语句)。

请把你的代码(我在30楼抄录的部分)拷贝到你的第1个问题所用的项目中,问题应该解决。


再有,我仍然是使用了30楼介绍的方法,在while(1)语句前通过设置另一个I/O口模拟按键的动作,解决了你说的按键时间不定的问题。这样很快发现问题根源还是“预分频数值在第1次没有生效”。

使用特权

评论回复
40
violet520| | 2009-7-15 14:38 | 只看该作者

~~~~~~

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则