STM32基础问题分析——PWM配置

在使用STM32F103产生固定频率、固定占空比的PWM波时,虽然有官方以及众多开发板提供的例程,但是关于有点问题并没有说的很清晰,并且《STM32F10X参考手册》的中文翻译可能容易造成歧义,所以一开始并没有理解,这里就梳理一下我的理解,如果有误解的情况,希望交流指正。

1. 遇到的问题

先直接上段配置代码,这段代码是产生一个20kHz固定频率,50%固定占空比的方波信号,典型的配置过程,一般来说也不会有什么太多的疑问。但是我逐步了解背后的定时器工作逻辑的时候,就产生了一些疑问,也没有找到合理、清晰的解答。先说明一下,只有通用定时器和高级定时器才有PWM模式,基本定时器没有。

static void PWM_Mode_Config(void)
{
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
    TIM_OCInitTypeDef  TIM_OCInitStructure; 

    // 基本定时器配置
    TIM_DeInit(TIM3);

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);            // 开启定时器时钟,即内部时钟CK_INT=72M
    TIM_TimeBaseStructure.TIM_Period = 49;                          // 自动重装载寄存器的值,累计TIM_Period+1个频率后产生一个更新或者中断
    TIM_TimeBaseStructure.TIM_Prescaler = 71;                       // 时钟预分频数为
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;         // 设置时钟分频系数:不分频(这里用不到)
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;     // 向上计数模式
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);                 // 初始化定时器
    TIM_ClearFlag(TIM3, TIM_FLAG_Update);                           // 清除计数器更新标志位

    // PWM模式配置
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;               // 配置为PWM模式1
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;   // 使能输出
    TIM_OCInitStructure.TIM_Pulse = 25;                             // 设置初始PWM脉冲宽度为25,实际上就是配置占空比(捕获比较寄存器1,CCR1)
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;        // 当定时器计数值小于CCR1_Val时为低电平
    TIM_OC2Init(TIM3, &TIM_OCInitStructure );                       // 使能通道2
    TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable );              // 使能预装载(使能CCR1的预装载)
    TIM_ARRPreloadConfig(TIM3, ENABLE);                             // 使能自动重载寄存器ARR的预装载

    // 开启TIM3
    TIM_Cmd(TIM3, DISABLE);                                         // 使能定时器
    TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);                      // 使能定时器中断
    BASIC_TIM_NVIC_Config();
}

1.1 什么是清除标志位

TIM_ClearFlag(TIM3, TIM_FLAG_Update);                           // 清除计数器更新标志位

代码中有这样一条,有人会问函数TIM_ClearFlag()和函数TIM_ClearITPendingBit()有什么区别?其实重点在Flag和IT,前者是外设的状态标志,而后者是外设的中断标志。状态标志就是一个外设它有自身的一些标志位(Flag),来表明它处于什么状态,下图就是定时器的状态标记。中断标志就是使能外设的中断后,每次发生一次中断,它会表明发生了什么样的中断,同样中断也有相应的标记。两者分别靠函数TIM_GetFlagStatus()和函数TIM_GetITStatus()来获取。

没有使能中断时,是可以读取该外设的状态标志的。同理,串口外设也有此区别。

1.2 模式1和模式2的区别

    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;               // 配置为PWM模式1

模式1

在向上计数时,一旦TIMx_CNT < TIMx_CCR1时通道1为有效电平,否则为无效电平;在向下计数时,一旦TIMx_CNT>TIMx_CCR1时通道1为无效电平(OC1REF=0),否则为有效电平(OC1REF=1)。

模式2

在向上计数时,一旦TIMx_CNT < TIMx_CCR1时通道1为无效电平,否则为有效电平;在向下计数时,一旦TIMx_CNT>TIMx_CCR1时通道1为有效电平,否则为无效电平。

实际上可以看到,模式1和模式2并无本质的区别,只是在同样的有效电平情况下,输出的波形电平相反而已。那么什么又是有效电平?后面有机会单独说明。

1.3 什么是自动重装载和预装载寄存器?

    TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable );           // 使能预装载(使能CCR1的预装载)
    TIM_ARRPreloadConfig(TIM3, ENABLE);                          // 使能自动重载寄存器ARR的预装载

这个问题才是我写这篇博客的源动力,网上有很多人问,但是回答者没能详细说明,《STM32F10X参考手册》中的描述也有歧义。我只能从别人回答的只言片语中摸索,然后根据自己的理解来解释。下面就看第二节的基本知识。

2. PWM波产生的过程

《STM32F10X参考手册》对此的描述如下:

自动装载寄存器是预先装载的,写或读自动重装载寄存器将访问预装载寄存器。根据在TIMx_CR1寄存器中的自动装载预装载使能位(ARPE)的设置,预装载寄存器的内容被立即或在每次的更新事件UEV时传送到影子寄存器。当计数器达到溢出条件(向下计数时的下溢条件)并当TIMx_CR1寄存器中的UDIS位等于’0’时,产生更新事件。更新事件也可以由软件产生。随后会详细描述每一种配置下更新事件的产生。

我在这个通用定时器框图里面找不到预装载寄存器,这就更增加疑惑了。后来翻了很多问答帖,大概明白了其中的意思,以下是我的总结以及理解。

从框图里面看到自动重装载寄存器(Auto Reload Register,ARR)和捕获/比较寄存器组(Capture/Compare Register,CCR)的框下面有阴影,这就说明这两种寄存器含有影子寄存器(Shadow Register)。影子寄存器实际上才是真正直接参与定时器工作的寄存器,具有即时性。我们配置的ARR和CCR相当于都是上层的寄存器,芯片在工作中,自动将这些上层寄存器的值传递到各自的影子寄存器,从而参与计数或者比较过程。

那么,预装载寄存器在哪里呢?

在参考文献《关于STM32影子寄存器和预装载寄存器和TIM_ARRPreloadConfig》中发现实际上ARR和CCR(可能)在物理底层上是两个寄存器的组合,即预装载寄存器(Preload Register)和影子寄存器。实际上,我们在代码层面,对ARR和CCR赋值,最终传递到直接参与工作的影子寄存器,中间还要经历一个预装载寄存器的传递,它在这里相当于缓存的作用。但是在手册中并没找到确实再物理硬件上存在预装载寄存器的蛛丝马迹(只查到缓存器一说),那么也可以这样理解。所谓的预装载寄存器实际上只是ARR和CCR在他们需要向影子寄存器传递值的时候的一种“功能化”的别称,也就是说在需要传值的时候,ARR和CCR就是各自影子寄存器的预装载寄存器。

预装载寄存器的概念应该是相对于影子寄存器来说的。影子寄存器是即时其作用的,而预装载寄存器的值只有传递到影子寄存器才能起作用,你可以把它理解为一个缓存。就程序员的角度观察,两者共用一个地址,无法直接区别访问,只能通过另外的办法来设置对该地址操作的具体行为。

作为类比,你可以把预装载寄存器理解为内存中的cache,数据写入了cache,却是不一定写入内存的。当然你可以通过MMU设置为写穿(write through)或写回(write back)模式。

那么ARR和CCR各自的影子寄存器的值在什么时候更新呢?

可以立即将值传入或者每次事件更新的时传入,依赖于相关的控制位。

以ARR为例,控制寄存器TIMx_CR1的位7——ARPE(自动重装载预装载允许位 ,Auto-reload preload enable),写“0”时,TIMx_ARR寄存器没有缓冲;写“1”时,TIMx_ARR寄存器被装入缓冲器。也就是说ARPE写1,则影子寄存器立即被更新,否则只有等到每次事件发生时,才更新,这就是TIM_ARRPreloadConfig(TIM3, ENABLE);的含义。

void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState)
{
  /* Check the parameters */
  assert_param(IS_TIM_ALL_PERIPH(TIMx));
  assert_param(IS_FUNCTIONAL_STATE(NewState));
  if (NewState != DISABLE)
  {
    /* Set the ARR Preload Bit */
    TIMx->CR1 |= TIM_CR1_ARPE;
  }
  else
  {
    /* Reset the ARR Preload Bit */
    TIMx->CR1 &= (uint16_t)~((uint16_t)TIM_CR1_ARPE);
  }
}

同理,CCR的影子寄存器也有相应操作。我选择的是TIM3的通道2,其控制寄存器CCMR1的OC2PE位(CCMR1控制通道1和通道2),相应的操作可以对照库函数来了解。

看到有人回复“如果不改变频率和占空比,纯粹输出PWM波,则可以不需要使能预装载”,那是不是意味着以下两句代码可以不写?这需要试一下……

    TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable );           // 使能预装载(使能CCR1的预装载)
    TIM_ARRPreloadConfig(TIM3, ENABLE);                          // 使能自动重载寄存器ARR的预装载

那这样设计预装载和影子寄存器有什么好处呢?

参考如下,我的简单理解就是将定时器的赋值过程和工作过程独立开。

设计preload register和shadow register的好处是,所有真正需要起作用的寄存器(shadow register)可以在同一个时间(发生更新事件时)被更新为所对应的preload register的内容,这样可以保证多个通道的操作能够准确地同步。如果没有shadow register,或者preload register和shadow register是直通的,即软件更新preload register时,同时更新了shadow register,因为软件不可能在一个相同的时刻同时更新多个寄存器,结果造成多个通道的时序不能同步,如果再加上其它因素(例如中断),多个通道的时序关系有可能是不可预知的。



天呐,终于稍微理顺了,虽然不一定对,而且定时器需要死磕的细节太多,精力有限,慢慢来~~

参考文献:

  1. 《STM32F10X参考手册》
  2. 《32位基于ARM微控制器STM32F101xx与STM32F103xx 固件函数库》
  3. 《关于STM32影子寄存器和预装载寄存器和TIM_ARRPreloadConfig》
  4. 《为什么TIM_ARRPreloadConfig在我的程序中没有作用 他到底有什么作用?》
  5. 《STM32定时器预装载寄存器的理解?》
时间: 2024-10-14 11:36:17

STM32基础问题分析——PWM配置的相关文章

STM32 PWM配置

一:配置TIM3的ARR寄存器和PSC寄存器,确定PWM频率. 这里配置的这两个定时器确定了PWM的频率,我的理解是:PWM的周期(频率)就是ARR寄存器值与PSC寄存器值相乘得来,但不是简单意义上的相乘,例如要设置PWM的频率参考上次通用定时器中设置溢出时间的算法,例如输出100HZ频率的PWM,首先,确定TIMx的时钟,除非APB1的时钟分频数设置为1,否则通用定时器TIMx的时钟是APB1时钟的2倍,这时的TIMx时钟为72MHz,用这个TIMx时钟72MHz除以(PSC+1),得到定时器

中部六省产业转移与创新的社会基础条件分析:结论与建议

中部六省产业转移与创新的社会基础条件分析:结论与建议 李 侠 中部六省(安徽省.江西省.湖南省.湖北省.山西省.河南省)的人口占全国总人口的27%,国土面积占全国的10.7%,GDP占全国的19.3%(2010年国家统计局数据).粗略看来,中部用占全国四分之一还多的人口,仅仅生产了不到全国五分之一的GDP,由此,不难看出,中部崛起对于我国整体经济格局的改变具有重大意义.为了实现崛起与腾飞,目前中西部地区热衷于产业转移的模式,问题是产业转移的实现是需要严格约束条件的,本研究认为决定一个地区产业转移

dsp2812 pwm配置

肚子疼了好几天,今天稍微好点,简单写点东西. 好几个月前做的项目,有些地方已经记不清楚了,但是突然客户又来问关于代码配置的情况,重新查看了代码,把相关的知识也整理一下. dsp2812中有好几个时钟相关的配置.首先是系统时钟SYSCLKOUT=15MHZ*PLL(PLL可以在代码中进行修改). 我的代码中将PLL设置成7,所以SYSCLKOUT=105MHZ 还有两个时钟是外设时钟,分别是高速外设时钟和低速外设时钟,它们分别由HISPCP和LOSPCP这两个寄存器来控制. 高速外设时钟用于事件管

stm32基础入门

1.开发工具,初学者建议MDK,后期ivr 2.寄存器开发or库 版本开发:先寄存器开发,后期两者结合: 3.软件仿真or开发板,先软件仿真,后期两者结合: 建立工程: 1.包含三部分:start.user.lib.output.outlist文件夹:并设置: outlist目录:用来存放生成的中间文件: output目录:用来存放生成的目标文件: 2.设置头文件路径,库文件路径: 3.编译: stm32基础入门

stm32编译前为什么要配置keil中C/C++中的define 和include Paths?

这是Keil与编译器的一个相互通信的过程,准确的来说,是编译器读取Keil的配置 ARM系列的有一些公司的库编译器,是与Keil的一些配置通信的. 比如你说的那个 Define,include path 一般来说,我们用Keil做51或者STR710等等一些单片机的程序时候,不需要配置刚才的两个选项,为什么? 因为C51和ARM7的编译器不去读取上述的配置. 而Cortex-M3编译器,则读取上述的配置,并转换成自己的内编译器配置 比如:你在Define里面写:THIS_MY_DEF 那么,编译

STL源码分析--空间配置器的底层实现 (二)

STL源码分析-空间配置器 空间配置器中门道 在STL中的容器里都是使用统一的空间配置器,空间配置器就是管理分配内存和销毁内存的.在STL将在heap空间创建一个对象分为两个步骤,第一是申请一块内存,第二是在这块内存中初始化一个对象.首先申请空间是由malloc提供,初始化一个对象时由constructor管理.销毁一个对象也是由两步骤完成,第一是销毁空间上的对象,第二是释放这块内存. 同时,STL的空间配置器分为两级内存,如果申请的内存空间大于128KB,那么就使用第一级空间配置,如果小于,那

MySQL基础环境_安装配置教程(Windows7 64或Centos7.2 64、MySQL5.7)

MySQL基础环境_安装配置教程(Windows7 64或Centos7.2 64.MySQL5.7) 安装包版本 1)     VMawre-workstation版本包 地址: https://my.vmware.com/web/vmware/details?downloadGroup=WKST-1411-WIN&productId=686&rPId=20814 包名:VMware-workstation-full-12.5.7.20721.exe 2)     Windows版本包

Linux基础环境_安装配置教程(CentOS7.2 64、JDK1.8、Tomcat8)

Linux基础环境_安装配置教程 (CentOS7.2 64.JDK1.8.Tomcat8) 安装包版本 1)     VMawre-workstation版本包 地址: https://my.vmware.com/web/vmware/details?downloadGroup=WKST-1411-WIN&productId=686&rPId=20814 包名:VMware-workstation-full-12.5.7.20721.exe 2)     CentOS版本包 地址:htt

从头编写 asp.net core 2.0 web api 基础框架 (4) EF配置

原文:从头编写 asp.net core 2.0 web api 基础框架 (4) EF配置 第1部分:http://www.cnblogs.com/cgzl/p/7637250.html 第2部分:http://www.cnblogs.com/cgzl/p/7640077.html 第3部分:http://www.cnblogs.com/cgzl/p/7652413.html Github源码地址:https://github.com/solenovex/Building-asp.net-co