在使用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基础

    Stm32基础 目录 常用功能函数 跑马灯实验 蜂鸣器实验 按键实验 端口复用与重映射 常用功能函数 初始化gpio函数 作用:初始化一个或者多个io口(同一组)的工作方式和速度该函数主要是操作GPI ...

  2. STM32启动代码分析 IAR 比较好

    stm32启动代码分析 (2012-06-12 09:43:31) 转载▼     最近开始使用ST的stm32w108芯片(也是一款zigbee芯片).开始看他的启动代码看的晕晕呼呼呼的. 还好在c ...

  3. myBatis 基础测试 表关联关系配置 集合 测试

    myBatis 基础测试 表关联关系配置 集合 测试 测试myelipse项目源码 sql 下载 http://download.csdn.net/detail/liangrui1988/599388 ...

  4. Azure WAF防火墙工作原理分析和配置向导

    Azure WAF工作原理分析和配置向导 本文博客地址为:http://www.cnblogs.com/taosha/p/6716434.html ,转载请保留出处,多谢! 本地数据中心往云端迁移的的 ...

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

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

  6. Spring Cloud Alibaba基础教程:Nacos配置的多文件加载与共享配置

    前情回顾: <Spring Cloud Alibaba基础教程:使用Nacos实现服务注册与发现> <Spring Cloud Alibaba基础教程:支持的几种服务消费方式> ...

  7. Spring Cloud Alibaba基础教程:Nacos配置的多环境管理

    前情回顾: <Spring Cloud Alibaba基础教程:使用Nacos实现服务注册与发现> <Spring Cloud Alibaba基础教程:支持的几种服务消费方式> ...

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

    MySQL基础环境_安装配置教程(Windows7 64或Centos7.2 64.MySQL5.7) 安装包版本 1)     VMawre-workstation版本包 地址: https://m ...

  9. Windows基础环境_安装配置教程(Windows7 64、JDK1.8、Android SDK23.0、TortoiseSVN 1.9.5)

    Windows基础环境_安装配置教程(Windows7 64.JDK1.8.Android SDK23.0.TortoiseSVN 1.9.5) 安装包版本 1)     JDK版本包 地址: htt ...

随机推荐

  1. 人工智能 tensorflow框架-->简介及安装01

    简介:Tensorflow是google于2015年11月开源的第二代机器学习框架. Tensorflow名字理解:图形边中流动的数据叫张量(Tensor),因此叫Tensorflow 既 张量流动 ...

  2. win10 uwp MVVM入门

    MVVM 是一个强大的架构,基本从 WPF 开始,wr(我说的就是微软)就提倡使用 MVVM.它可以将界面和后台分离,让开发人员可以不关心界面是怎样,全心投入到后台代码编写中. 然后在编写完后台代码后 ...

  3. 面向接口编程实现不改代码实现Redis单机/集群之间的切换

    开发中一般使用Redis单机,线上使用Redis集群,因此需要实现单机和集群之间的灵活切换 pom配置: <!-- Redis客户端 --> <dependency> < ...

  4. JVM菜鸟进阶高手之路十三(等你来战!!!)

    转载请注明原创出处,谢谢! 前几天有个朋友问了我个问题,下面给大家分享下,希望大家积极在评论区进行评论留言,等你来战!!! 先来个趣味题,热身下,引出后面的jvm题目. 地上的影子是那个人的? 地上的 ...

  5. Eclipse中Hibernate插件的安装

    在使用Hibernate开发时,大多数情况下涉及到其XML配置文件的编辑,尤其是.cfg.xml(配置文件)和hbm.xml(关系映射文件)这两种.为了更方便的使用此框架,其插件的安装是很有必要的. ...

  6. java的集合框架set 和map的深入理解

    Java的集合框架之Map的用法详解 Map有两种比较常用的实现:HashMap 和 TreeMap. HashMap: HashMap 也是无序的,也是按照哈希编码来排序的,允许使用null 值和n ...

  7. 读书笔记-你不知道的JS上-混入与原型

    继承 mixin混合继承 function mixin(obj1, obj2) { for (var key in obj2) { //重复不复制 if (!(key in obj1)) { obj1 ...

  8. [Scikit-learn] 2.1 Clustering - Variational Bayesian Gaussian Mixture

    最重要的一点是:Bayesian GMM为什么拟合的更好? PRML 这段文字做了解释: Ref: http://freemind.pluskid.org/machine-learning/decid ...

  9. javascript高性能写法

    看到一篇不错的博文,如果想写出比较高性能的代码,可参看这个链接http://developer.51cto.com/art/200906/131335.htm

  10. C++ primer第五版随笔--2015年1月6日

    记录自己看这本书时的一些内容. 一.引用(reference) 引用为对象起了另外一个名字.例如: int ival=1024: int &relVal1=ival;//对,注意尽量不要用这方 ...