在使用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, ENABLE); // 使能定时器
TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE); // 使能定时器中断
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定时器预装载寄存器的理解?》

STM32基础分析——PWM配置的更多相关文章

  1. STM32之PWM波形输出配置总结

    一.   TIMER分类: STM32中一共有11个定时器,其中TIM6.TIM7是基本定时器:TIM2.TIM3.TIM4.TIM5是通用定时器:TIM1和TIM8是高级定时器,以及2个看门狗定时器 ...

  2. 【转载】 stm32之PWM

    发现这位博主的博客被大量的转发,我也转载一篇,谁叫人家写的好呢. 原文地址:http://blog.sina.com.cn/s/blog_49cb42490100s6uh.html 脉冲宽度调制(PW ...

  3. STM32的PWM输入模式设置并用DMA接收数据

    参考 :STM32输入捕获模式设置并用DMA接收数据 PWM input mode This mode is a particular case of input capture mode. The ...

  4. stm32之PWM博客好文收藏

    https://www.cnblogs.com/jiwangbujiu/p/5616376.html STM32F103 使用TIM3产生四路PWM https://www.cnblogs.com/c ...

  5. STM32之PWM君

    PWM..英语好的人估计又知道这三个大写字母代表哪三个英语单词了.小弟不才,就说中文意思好了:脉冲宽度调制,玩过飞思卡尔的人估计对PWM非常的不陌生吧.电机驱动需要PWM,控制舵机的转向需要PWM,总 ...

  6. stm32之PWM

    PWM是pulse width modulation的缩写,即脉冲宽度调制.其通过对一系列脉冲的宽度进行调制,来等效地获得所需要波形: 1.PWM是一种对模拟信号电平进行数字编码的方法.通过高分辨率计 ...

  7. wiringPi库的pwm配置及使用说明

    本文介绍树莓派(raspberry pi)在linux c 环境下的硬件pwm配置及使用方法. 1. 下载安装wiringPi 此步骤建议参考官网指南,wiringPi提供了对树莓派的硬件IO访问,包 ...

  8. 【STM32】PWM DAC基本原理(实验:PWM实现DAC)

    虽然STM32F103ZET6具有内部DAC,但是也仅仅只有两条DAC通道,并且STM32还有其他的很多型号是没有DAC的.通常情况下,采用专用的D/A芯片来实现,但是这样就会带来成本的增加. 不过S ...

  9. stm32之PWM学习

    下图是一个STM32普通PWM形成的图形原理说明 自动重装载寄存器(ARR)用于确定波形的频率(即周期).捕获比较寄存器(CCRx)(用于确定占空比的) PWM的工作过程如下:首先ARR寄存器里面的值 ...

随机推荐

  1. CAD2015 安装出错

    Autodesk安装失败后回滚连带把在D盘创建的安装目录都给删除掉了. 把.net 4.6卸载干净之后就可以成功安装CAD2015了.只安装.net 4.5就行了.

  2. libevent源码分析

    这两天没事,看了一下Memcached和libevent的源码,做个小总结. 1.入门 1.1.概述Libevent是一个用于开发可扩展性网络服务器的基于事件驱动(event-driven)模型的网络 ...

  3. CSS 内容生成

    原文地址:http://www.zhangxinxu.com/wordpress/?p=739 一.哗啦哗啦的简介 zxx://这里“哗啦哗啦”的作用是为了渲染一种氛围.content属性早在CSS2 ...

  4. EF添加和修改

    (1)//添加操作 public bool addDate() { try { //声明上下文 a_context = new AEntities(); //声明数据模型实体 //执行代码时候会先验证 ...

  5. 1-element.src.match("bulbon")

    element.src.match("bulbon")这里的math里面”bulbon“是什么意思? 原代码中 if (element.src.match("bulbon ...

  6. PCL点云库中的坐标系(CoordinateSystem)

    博客转载自:https://blog.csdn.net/qq_33624918/article/details/80488590 引言 世上本没有坐标系,用的人多了,便定义了坐标系统用来定位.地理坐标 ...

  7. Logistic Regression 用于预测马是否生病

    1.利用Logistic regression 进行分类的主要思想 根据现有数据对分类边界线建立回归公式,即寻找最佳拟合参数集,然后进行分类. 2.利用梯度下降找出最佳拟合参数 3.代码实现 # -* ...

  8. Django----解决跨域

    cors(跨域资源共享): 本质设置响应头 定制中间件 cors.py 后在settings.py中间件中配置 from django.utils.deprecation import Middlew ...

  9. 4、MemorySubSystem

    1.概述 S3C6410X存储器子系统包括7个存储器控制器,SROM控制器,两个OneNAND控制器,一个NAND Flash控制器,一个CF控制器,一个DRAM控制器.静态存储器控制器,oneNAN ...

  10. SVN下载地址及注意事项

    SVN下载地址:VisualSVN:http://www.visualsvn.com/server/download     服务器端(添加仓库和用户)TortoiseSVN:http://torto ...