STM32 学习笔记_TIME定时器1

     STM32 单片机的定时器的确很强大,参考说明书中就占了一百多页,占参考手册1/4 有多了。
STM32的定时器分了好几个类别,各个类别针对功能作用都不大相同。
分有: 一、高级定时器
       二、通用定时器
       三、基本定时器
       四、看门狗定时器
       五、SysTick定时器

      其中看门狗定时器和SysTick定时器本篇笔记阐述,这里主要记下对平时使用定时器作用的计时计数器的一些自己的理解。
按照参考手册中的定义 高级定时器 通用定时器 基本定时器,这三个定时器成上下级的关系,即基本定时器有的功能通用定时器都有,而且还增加了向下、向上/向下计数器、PWM生成、输出比较、输入捕获等等功能;而高级定时器又包含了通用定时器的所有功能,另外还增加了死区互补输出、刹车信号、加入重复计数器等等。(这里等等功能请参考《STM32参考手册》)
      所以学习STM32 定时器实际就是学习一下高级定时器,然后适当的删减后就是后面的两种定时器了。
假若不涉及输出输入,定时器的最基本用法就是计数定时作用了本篇笔记主要针对这部分的理解所写下的。

高级定时器中一共有20个寄存器:

TIMx_CR1、TIMx_CR2、TIMx_SMCR、TIMx_DIER、TIMx_SR、TIMx_EGR、TIMx_CCMR1、TIMx_CCMR2、
TIMx_CCER、TIMx_CNT、TIMx_PSC、TIMx_ARR、TIMx_RCR、TIMx_CCR1、
TIMx_CCR2、TIMx_CCR3、
TIMx_CCR4、TIMx_BDTR、TIMx_DCR、TIMx_DMAR
好吧一堆寄存器光看都看到眼花缭乱了,当然不是所有寄存器都涉及到才能让定时器工作的,例如最基本的定时功能所涉及的只有几个与时基功能相关的寄存器,
TIMx_CNT(计数器寄存器)、TIMx_PSC(预分频器寄存器)、TIMx_ARR(自动装载寄存器)、TIMx_RCR(重复次数寄存器)。参考手册中有那么 衣服定时器的框图。这几个寄存器的关系如图所示的:

CK_PSC这根时钟线上的时钟源的选择,即给定时器计数计时的时钟源的输入方式,有四种方式,分别是内部时钟,外部时钟模式1,外部时钟模式2,内部触发。这部分日后再说,这里暂且使用最常用的内部时钟方式,既是当内部时钟为72MHz 的内部时钟源。
如图所示的,时钟源首先进入预分频器,然后再进入预先装入自动重装载寄存器的计数器中,当计数器溢出时产生一次中断和一次事件更新。除了多了一个PSC,其他的基本和51单片机很相似,初次看参考手册中的功能描述中出现了好多次“
更新事件(UEV)”。这究竟是怎么的一样东西呢? 在这里有个新概念叫“影子寄存器”,在上图中,可以看到PSC、ARR、REP(重复计数器中的低八位)这三个寄存器框框下都有个黑影,每次这三个寄存器就是影子寄存器,如果看到参考手册全图中还可以看到另外还有几个框框下也有阴影部分的,这几个寄存器也是影子寄存器。何谓影子寄存器呢,例如PSC寄存器可以理解为有两个,一个是用户可以访问到的寄存器,可读可写,另一部分就是客户访问不到的但其装载值和实际寄存器是密切关联的,当程序在运行中改写PSC 这时候影子寄存器的作用就体现了,因为立刻写入的值可能会大于或小于目前正在运行的寄存器中的数值,而真实在运行时候的正是这个影子寄存器中的值,而程序写入的是可访问的寄存器,只有当产生一个更新事件的时候影子寄存器才会读入访问寄存器中的值,这样就可以防止突然修改而产生的非正常中断或不会中断等异常问题。当然在控制器CR1中控制这个影子寄存器是否起作用,不起作用的话就是立即写入这个数值到寄存器中。下面两幅是参考手册中的相关时序图:

 


回头再说一下“
更新事件(UEV)” ,当计数器溢出的时候产生一次UEV事件,另外还可以在事件寄存器TMx_EGR中的UG位软件写入产生一次事件更新,当UEV事件来临的时候所有影子寄存器均载入寄存器中的值,从而实现所有带影子寄存器的更新,而不启用影子寄存器的情况下只能实现,写那个寄存器更新那个寄存器而,这可能造成相关联的寄存器产生冲突矛盾,建议还是开启此功能,在下一个溢出周期后产生事件更新。

       (既然说到了影子寄存器也说点自己的猜测,了解了点STM32单片机的都知道几乎所有寄存器都是32位的,唯独TIM寄存器是16位的,是的如果是32位的计数器我们可能还能做更宽广的定时作用。但我们也还是发现即使加入了影子寄存器而整体的寄存器地址依然保持是连续的,这我猜测一种可能性寄存器本身其实还是32位的,但高位提供了影子寄存器的载入功能,所以依然能保持地址连续性,只要设定了高位禁止访问即可。官方资料和搜索中均未有任何确认说法,纯粹本人猜测未得到官方任何证实)

     另外高级定时器中还有RCR重复次数寄存器这个,也是比较简单的事件更新(UEV) 都是在RCR为0的情况下产生计数器溢出而产生的,当RCR中不为0的时候计数器溢出只会使得重复次数寄存器递减而不会产生UEV,这样就可以使得定时器的定时情况得以延长,而相当于有16位的分频器,16位的计数器,再加入16位的重复次数,一共48位的计数定时器。详细看参考手册,这个很好理解。

    基本的基时单元就是上面提及的这几个,下面看看3.0库是如何实习的基本使用。

 TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
 
       
       TIM_DeInit(TIM2);                                           //重新将Timer设置为缺省值
       
       TIM_InternalClockConfig(TIM2);                              //采用内部时钟给TIM2提供时钟源      
       TIM_TimeBaseStructure.TIM_Prescaler = 36000 - 1;            //预分频系数为36000-1,这样计数器时钟为72MHz/36000 = 2kHz       
       TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;     //设置时钟分割      
       TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //设置计数器模式为向上计数模式       
       TIM_TimeBaseStructure.TIM_Period = 2000 - 1;           //设置计数溢出大小,每计2000个数就产生一个更新事件
       TIM_TimeBaseInit(TIM2,&TIM_TimeBaseStructure);         //将配置应用到TIM2中
       TIM_ClearFlag(TIM2, TIM_FLAG_Update);                  //清除溢出中断标志  
       
       TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);                //开启TIM2的中断

以上是一个最基本的定时器配置的代码,载自网上被转载无数次的地方……
中断函数自己按照需求写,这里不多说。
在库中的初始化函数和初始化数据类型有3类,TIM_TimeBaseInitTypeDef、TIM_OCInitTypeDef、TIM_ICInitTypeDef

与基时参数相关的数据类型是TIM_TimeBaseInitTypeDef

 typedef struct
 {
   uint16_t TIM_Prescaler;         /*!< Specifies the prescaler value used to divide the TIM clock.
                                        This parameter can be a number between 0x0000 and 0xFFFF */
 
   uint16_t TIM_CounterMode;       /*!< Specifies the counter mode.
                                        This parameter can be a value of @ref TIM_Counter_Mode */
 
   uint16_t TIM_Period;            /*!< Specifies the period value to be loaded into the active
                                        Auto-Reload Register at the next update event.
                                        This parameter must be a number between 0x0000 and 0xFFFF.  */ 
 
   uint16_t TIM_ClockDivision;     /*!< Specifies the clock division.
                                       This parameter can be a value of @ref TIM_Clock_Division_CKD */
 
   uint8_t TIM_RepetitionCounter;  /*!< Specifies the repetition counter value. Each time the RCR downcounter
                                        reaches zero, an update event is generated and counting restarts
                                        from the RCR value (N).
                                        This means in PWM mode that (N+1) corresponds to:
                                           - the number of PWM periods in edge-aligned mode
                                           - the number of half PWM period in center-aligned mode
                                       This parameter must be a number between 0x00 and 0xFF. 
                                        @note This parameter is valid only for TIM1 and TIM8. */
 } TIM_TimeBaseInitTypeDef; 
以上是从库stm32f10x_tim.h中 截取的代码,整体的数据结构可以中这段注释中得知,不懂E文的要么翻字典要么翻库函数中文翻译
版本(当然这个是2.0的库,有部分会和3.0后的版本很不相同),这部分的数据类型还是很一样的,不多说。 
接着就是TIM_TimeBaseInit()这个函数了,在stm32f10x_tim.c的224行中
void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct)
{
  uint16_t tmpcr1 = 0;

  /* Check the parameters */
  assert_param(IS_TIM_ALL_PERIPH(TIMx)); 
  assert_param(IS_TIM_COUNTER_MODE(TIM_TimeBaseInitStruct->TIM_CounterMode));
  assert_param(IS_TIM_CKD_DIV(TIM_TimeBaseInitStruct->TIM_ClockDivision));

  tmpcr1 = TIMx->CR1;  

  if((TIMx == TIM1) || (TIMx == TIM8)|| (TIMx == TIM2) || (TIMx == TIM3)||
     (TIMx == TIM4) || (TIMx == TIM5)) 
  {
    /* Select the Counter Mode */
    tmpcr1 &= (uint16_t)(~((uint16_t)(TIM_CR1_DIR | TIM_CR1_CMS)));
    tmpcr1 |= (uint32_t)TIM_TimeBaseInitStruct->TIM_CounterMode;
  }
 
  if((TIMx != TIM6) && (TIMx != TIM7))
  {
    /* Set the clock division */
    tmpcr1 &= (uint16_t)(~((uint16_t)TIM_CR1_CKD));
    tmpcr1 |= (uint32_t)TIM_TimeBaseInitStruct->TIM_ClockDivision;
  }

  TIMx->CR1 = tmpcr1;

  /* Set the Autoreload value */
  TIMx->ARR = TIM_TimeBaseInitStruct->TIM_Period ;
 
  /* Set the Prescaler value */
  TIMx->PSC = TIM_TimeBaseInitStruct->TIM_Prescaler;
    
  if ((TIMx == TIM1) || (TIMx == TIM8)|| (TIMx == TIM15)|| (TIMx == TIM16) || (TIMx == TIM17))  
  {
    /* Set the Repetition Counter value */
    TIMx->RCR = TIM_TimeBaseInitStruct->TIM_RepetitionCounter;
  }

  /* Generate an update event to reload the Prescaler and the Repetition counter
     values immediately */
  TIMx->EGR = TIM_PSCReloadMode_Immediate;           
}

可以看3.0后的函数里把所有的TIMx都加入一个函数里面做判断了,不需要和2.0的区分TIM1和TIM 两类函数,比较其基本操作都一样无非就是多了一个两个寄存器而已。
程序中可以看到这一段:

  if((TIMx == TIM1) || (TIMx == TIM8)|| (TIMx == TIM2) || (TIMx == TIM3)||
     (TIMx == TIM4) || (TIMx == TIM5))
  {
    /* Select the Counter Mode */​
    tmpcr1 &= (uint16_t)(~((uint16_t)(TIM_CR1_DIR | TIM_CR1_CMS)));
    tmpcr1 |= (uint32_t)TIM_TimeBaseInitStruct->TIM_CounterMode;
  }
if((TIMx != TIM6) && (TIMx != TIM7))
{     
    /* Set the clock division */     
    tmpcr1 &= (uint16_t)(~((uint16_t)TIM_CR1_CKD));     
    tmpcr1 |= (uint32_t)TIM_TimeBaseInitStruct->TIM_ClockDivision;   
}
TIMx->CR1 = tmpcr1;
    高级定时器和通用定时器拥有向上计数、向下技术、向上/向下模式,三种,而基本计时器只有向上计数一种,因此TIM6和TIM7木有的设置计数模式,tmpcr1首先装入了控制寄存器CR1d 值,然后把其中DIR(位4)和CMS(位6:5)清除,然后或运算上载入数据结构中的值,这里代码再跳转到stm32f10x_tim.h中第362行
#define TIM_CounterMode_Up                 ((uint16_t)0x0000)
#define TIM_CounterMode_Down               ((uint16_t)0x0010)
#define TIM_CounterMode_CenterAligned1     ((uint16_t)0x0020)
#define TIM_CounterMode_CenterAligned2     ((uint16_t)0x0040)
#define TIM_CounterMode_CenterAligned3     ((uint16_t)0x0060) 定义的是TIM_CounterMode的几种情况,即控制计数器向上
技术或向下计数,或是向上向下交替计数,当设置的是交替计数的情况DIR位为只读。
TIM_ClockDivision为时钟分配因子,其中有 0、2x、4x ,在AHB低时钟时为提高定时精度而实用的倍频器,位于CR1寄存器中的位9:8。这段程序便配置好了CR1寄存器,然后再看下面的程序。 载入了PSC寄存器和ARR寄存器值,如果是高级定时器还有RCR寄存器值,在这一步便基本配置得差不多了,但还是发现函数最后还有一句 TIMx->EGR = TIM_PSCReloadMode_Immediate; 这里在事件寄存器中做了一次软件的事件更新触发 使得其UG位置1。
#define TIM_PSCReloadMode_Immediate        ((uint16_t)0x0001)
 
基本定时器的工作原理便如本文所述,如有错误非常感谢您的指出。

 

STM32 Comments(4) 2012年7月22日 10:17

Emacs列编辑

1.先mark矩形块
------------------------------------------------
2.列编辑命令
------------------------------------------------
按键 功能
C-x r k  ;; 剪切矩形块
C-x r y  ;; 粘贴矩形块
C-x r o  ;; 插入矩形块
C-x r c  ;; 删除矩形块
C-x r d           ;; 删除矩形块,并后面的补齐空白区
C-x r t  ;; 填充矩形块
C-x C-x  ;; 交换mark点
C-x r o           ;;在选定的矩形区域插入空白

 


矩形插入的效果图

EMACS Comments(4) 2012年7月05日 13:03

EMACS再加入cscope配置

   之前一直不指定cscoope怎么的玩,一直配置索引都是失败的,最近工作也都能打上一小罐酱油。正好捣鼓捣鼓这东西,的确如果程序文件多了基本的ECB 和cedet 是不够用的,特别大型代码文件N多的情况。

如果这东西配不成真的就这么给我瘸腿了。

最后发现原来除了本身配置cscope 中加入(require 'xcscope) 在windows 下的emacs 还需要在bin中加入cscope.exe和sort.exe 

加入这两个文件后便可以实现cscope的索引功能了。

 

EMACS Comments(2) 2012年6月29日 19:45

USB转串口转接器:FT232BL方案

      自己本身用的是以前买的一个条PL2303 的USB转串口,一直没发现,原来只能半双工,自交无法接受数据,数据量大一点点就出现错误或死掉的感觉,拔拔电源从新接倒好了。之前一直没发现这个问题,一直以为自己代码写得有问题或是硬件、芯片的问题。后来发毛了,无聊的拿镊子乱戳,戳到转接器就无聊的给它自交了一下,没想到是这个狗东西的问题。买个做个都差不多了,干脆自己做个吧。发现FT232BL的效果都不错,公司里用也是这个,自交5K次失败也就20次出头,效果还比较好。

      原理图什么的都很现成,FT232BL+MAX3245

 

一段时间没维护过自己做的PCB 原件库了,还是得弄得完善点好玩,基本3D模型也按比例弄上去了上个3D的PCB图

缺了个晶振,还有个东西也做错了,不过整体效果还是比较满意的。

 

硬件 Comments(0) 2012年6月14日 17:06

STM32 学习笔记 中断优先级理解

    Cortex-M3的中断嵌套可真让我理解得云里雾里的。一般的中断优先级就算了,还搞个什么的亚优先级。查找资料中查找到这个亚优先级还有好多别名'副优先级'、'响应优先级'……

     不过从'响应优先级' 和'抢占优先级' 这两个就可以看出这两种优先级的区别。

      抢占优先级就是假如当前情况是在运行着某个中断程序的情况先,触发了一个中断信号,而且比当前的中断等级要高,那么当前的中断程序会被挂起,直接跳到高抢占优先级的中断程序去。一般说法就是:具有高抢占式优先级的中断可以在具有低抢占式优先级的中断处理过程中被响应,即中断嵌套,或者说高抢占式优先级的中断可以嵌套低抢占式优先级的中断。

    响应优先级就是来一个中断运行一个中断程序,如果两个中断信号来到,并且抢占优先级相同,那么判断响应优先级高的先运行,结束后再运行优先级低的。而这运行中断程序当中再来同抢占优先级,不同响应优先级,是不会打断当前运行的程序,也只会等到当前中断程序运行完后再运行。即这两个中断没有任何嵌套关系。

 

STM32把指定中断优先级的寄存器位减少到4位,这4个寄存器位的分组方式如下: 
第0组:所有4位用于指定响应优先级 
第1组:最高1位用于指定抢占式优先级,最低3位用于指定响应优先级 
第2组:最高2位用于指定抢占式优先级,最低2位用于指定响应优先级 
第3组:最高3位用于指定抢占式优先级,最低1位用于指定响应优先级 
第4组:所有4位用于指定抢占式优先级 
可以通过调用STM32的固件库中的函数NVIC_PriorityGroupConfig()选择使用哪种优先级分组方式,这个函数的参数有下列5种: 
NVIC_PriorityGroup_0 => 选择第0组 
NVIC_PriorityGroup_1 => 选择第1组 
NVIC_PriorityGroup_2 => 选择第2组 
NVIC_PriorityGroup_3 => 选择第3组 
NVIC_PriorityGroup_4 => 选择第4组 

 

一个简单的例子

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); 
//USART1 中断
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // 指定抢占式优先级别1 
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; // 指定响应优先级别0 
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; 
NVIC_Init(&NVIC_InitStructure); 
// USART2 中断
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; // 指定抢占式优先级别0 
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; // 指定响应优先级别1 
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; 
NVIC_Init(&NVIC_InitStructure); 

 

这样串口2的优先级就比串口1要高,优先处理串口2的数据,当然这样写程序是有问题的,串口被打断意味这数据传输必定会出现失败的事情,这些就考虑整体程序时候要考虑的了,毕竟这就是个例子看看而已

STM32 Comments(3) 2012年5月30日 14:49

STM32 学习感悟

      在学习stm32 之前,编程风格可以说是非常烂的,大量的使用全局变量,函数即没返回也没输入,注释混乱,命名无根据。在它的固件库中有着许多规范的命名,调用等方式,已经很多C语言巧妙的使用。

      毕竟自己做了款学习板所以程序也要亲力亲为了,思前想后的,最后决定不能像市场的货那样,一款一个单,一个模块一个程序,实在是只能让人知道怎么读读数据而已。要为我后面的设计打基础,必须要有像样点的小规模级别上的程序才能训练我,最后还决定,把模块程序一个一个的集合到一起,形成一个大集合,像操作系统一样,给一个命令就做那事情,不需要,要那个功能就烦人的又烧一次程序。

      关于功能实现先与状态机模式实现,并行的情况后面深入了之后必须要再做一次研究,以超级终端为上位机做控制,当然没有超级终端的win7 可以用SecureCRT 这个软件,同样的终端功能,设置好串口相应设置后即可做通讯。单片机与其的通讯只能采用字符的形式出现,不支持直接看8位数据。

现在构造了两级菜单,总体感觉还行,但对这控制模式还说不清,毕竟自己设计的很难说缺点,查错误。

主级控制:

第二级控制菜单1:

第二级控制菜单1:

在非循环型工作中也并非要做到一个大循环体中,如主页和时钟选择,只要等待接收缓存发生变化后检查缓存值便可以做先关动作了。

 int main(void)
{ ………………// 省略初始化数据 和寄存器
     while (1)
     { switch(Sys_State)
	{
     /*------进入时钟选择-----------*/
	case '1':   
	  TempState='\0';
	  Receive_buffer='\0';
	  RCCUart_Display();
	  while(Receive_buffer!='0')
	    {  
	      if(TempState!='\0')
		{ 
		  RCC_ConfigControl(TempState);RCCUart_Display();Receive_buffer='\0';
		}
    /*---------动作后再做缓存检查----------------------*/
	      TempState=Receive_buffer;
	    }
	  break;

	  /*************************************************************/
	case '2':
	  TempState='0';
	  LedState='1';
	  Receive_buffer='1';
	  //  LEDUart_Display(LedState);
	  while(Receive_buffer!='0')
	    {
	      LedState=Receive_buffer; 		
      
	      if(TempState!=LedState)
		{TempState=LedState;LEDUart_Display(LedState);}	//数据变化,每次循环变化一次,实现跑马灯移动      

	      LED_Display(LedState,&LedData);
	      GPIO_Write(GPIOE,LedData);
	      delay_MS(1000);
	    }
	  break;

	  /*************************************************************/
	case '3':

	  break;

	  /*************************************************************/	  
	default :
	  
	  MAIN_Display();
	  while(Receive_buffer=='0')
	    {}
	  
	  break;
	}

	Sys_State=Receive_buffer;	  
    }

 

两个菜单均以状态机模式循环,并检查到退出控制命令符才得以跳出至上一级菜单。如果要做多级,还真有点复杂,判断太多了,得抽个时间看看操作系统的思路

 

 

 

STM32 Comments(1) 2012年4月02日 00:29

无刷电机初调

 最近还做了个小实验,无刷电机驱动,这个电机的的驱动方法还真的比较复杂,不想直流电机 步进电机,给电平就能转,既要驱动电路保护驱动,还要程序上的事实电平转换驱动,结合起来还是不容易的。只是一直比较懒每次都是弄一半一半的, 这回终于把小实验做了。但还是开环的驱动程序。

 

为了做准确的做PWM 输出,PWM 端口控制必须要在程序中断中做出,而且要及时,指令过多 或判断过多都会影响pwm 的准确控制,

贴出51 做的一部分代码,虽然pwm 的输出效果还是比较不错的,占空比都能很好的控制准确,但示波器看出换相还是 存在很大的问题,看来也需要做入中断或另一种控制方式

继续阅读

系统 Comments(0) 2012年3月25日 10:14

coterx-M3 学习开发板

    买过两次板子,这想再也不买了,全都自己做从硬件到软件,更加熟悉这个东西,去年做了一版很是有问题,今年过年后马上把新做了一版送去做样板回来了,这里非常感谢sky.Wang的帮助。

    

    正面

 

反面

焊好的一部分

 

总归有点像样,简单的程序也调通了一部分,不过没打算仔细调太多,主要是熟悉片子的使用,后面有设计计划用上这个片子,最近乱七八糟的事都很多,工作也比较忙的,真希望有功夫好好的研究一下自己的成果,呵呵。

 

软件开发的不多说,STM32 的片子,网上多到飞起,在想是否有需要转到飞思卡尔的单片机嵌入式系统上学习呢? 换个平台又是一个挺费时间的事情。以后再说吧。今早还要继续调试代码。真希望多点这样的时间啊

硬件 Comments(3) 2012年3月25日 09:58