STM32F10x时钟相关分析

 //======================================================//
**基于STM32的时钟树和通用定时器的使用实例详解
**内容1:时钟树的相关分析
**内容2:通用定时器的使用
**内容3:定时器复用功能重映射的相关分析
**内容3:PWM脉宽调节输出
//======================================================//

================================华丽的分界线===============================
==================================时钟树===============================
说到时钟树,要从时钟源说起。在STM32中有三种高速时钟源可以被用来驱动系统时钟(SYSTEM),分别为HSI振荡时钟、HSE振荡时钟、PLL时钟。还有两个低速时钟源,分别为约40kHz的LSI低速内部RC时钟、32.768kHz的LSE低速外部晶振作为时钟源。
如下图:
图片
时钟源详细:
①、HSI是高速内部时钟,RC振荡器,频率为8MHz,精度不高。  ②、HSE是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz~16MHz。  ③、LSI是低速内部时钟,RC振荡器,频率为40kHz,提供低功耗时钟。WDG  ④、LSE是低速外部时钟,接频率为32.768kHz的石英晶体。RTC  ⑤、PLL为锁相环倍频输出,其时钟输入源可选择为HSI/2、HSE或者HSE/2。倍频可选择为2~16倍,但是其输出频率最大不得超过72MHz。

其中很重要的是:在任何一个外设在使用之前,必须首先使能其相应的时钟。
下面简单的说一下整个的线路,整个线路呢,其实可以分成三部分来看,一个是输入端是那个时钟源,其二,根据频率要求进行了哪些的分频及倍频这个的“通道”,其三就是输出端的各种外设。

1、输入端就是五个时钟源,HSI、HSE、LSI、LSE、PLL。
2、这个“通道”指AHB预分频器、APB1预分频器、APB2预分频器、ADC预分频器、还有就是USB预分频器。
3、输出端就是指加载在AHB预分频器、APB1预分频器、APB2预分频器、ADC预分频器、还有就是USB预分频器等后面的各种外设,如HCLK、 Cortex系统时钟、PCLK1、TIMxCLK、PCLK2等各种外设。

其中也可以从中看出一个很重要的问题那就是:
1、在任何一个外设在使用之前,就是先使能相应的时钟。
2、在设置定时器或者说是其他的时钟,其实就是通过设置多个预分频器配置AHB、高速APB(APB2)和低速APB(APB1)域的频率,另一种意思就是说,通过设置相应预分频的寄存器来设定时钟的频率。

RCC通过AHB时钟(HCLK)8分频后作为Cortex系统定时器(SysTick)的外部时钟。通过对SysTick控制与状态寄存器的设置,可选择上述时钟或Cortex(HCLK)时钟作为SysTick时钟。ADC时钟由高速APB2时钟经2、4、6或8分频后获得。

定时器时钟频率分配由硬件按以下2种情况自动设置:
1. 如果相应的APB预分频系数是1,定时器的时钟频率与所在APB总线频率一致。
2. 否则,定时器的时钟频率被设为与其相连的APB总线频率的2倍。
=====================================时钟详细========================================
HSE时钟
高速外部时钟信号(HSE)由以下两种时钟源产生:
● HSE外部晶体/陶瓷谐振器
● HSE用户外部时钟
HSE晶体可以通过设置时钟控制寄存器(RCC_CR)中的HSEON位被启动和关闭。

HSI时钟
HSI时钟信号由内部8MHz的RC振荡器产生,可直接作为系统时钟或在2分频后作为PLL输入。HSI  RC振荡器能够在不需要任何外部器件的条件下提供系统时钟。它的启动时间比HSE晶体振荡器短。然而,即使在校准之后它的时钟频率精度仍较差

PLL
PLL以下述时钟源之一为输入,产生倍频的输出:
● HSI时钟除以2
● HSE或通过一个可配置分频器的PLL2时钟
PLL2和PLL3由HSE通过一个可配置的分频器提供时钟。时钟配置寄存器2(RCC_CFGR2)。
必须在使能每个PLL之前完成PLL的配置(选择时钟源、预分频系数和倍频系数等),同时应该在
它们的输入时钟稳定(就绪位)后才能使能。一旦使能了PLL,这些参数将不能再被改变。
当改变主PLL的输入时钟源时,必须在选中了新的时钟源(通过的PLLSRC位)之后才能关闭原来的时钟源。
如果使能了时钟中断寄存器(RCC_CIR),可以在PLL就绪时产生一个中断。  

LSE时钟
LSE晶体是一个32.768kHz的低速外部晶体或陶瓷谐振器。它为实时时钟或者其他定时功能提供一个低功耗且精确的时钟源。
LSE晶体通过在备份域控制寄存器(RCC_BDCR)里的LSEON位启动和关闭。

LSI时钟
LSI RC担当一个低功耗时钟源的角色,它可以在停机和待机模式下保持运行,为独立看门狗和
自动唤醒单元提供时钟。LSI时钟频率大约40kHz(在30kHz和60kHz之间)。进一步信息请参考数
据手册中有关电气特性部分。
LSI RC可以通过控制/状态寄存器(RCC_CSR)里的LSION位来启动或关闭。

讲到了时钟树呢,就要搞清楚,系统时钟的设置了,因为系统时钟的设置就是通过对时钟树的理解写的,换句话说就是只要理解了时钟树的脉络,那么系统时钟的配置也就相当容易了。下面是一个系统时钟的初始化函数,选用的时钟源是HSE外部时钟,外部晶振接的是8Mhz的外部晶振,选择HSE时钟源的理由是第一系统时钟要求就是要比较精确,首先先选择高速时钟源,然后HSI时钟源的信号是由内部RC振荡器提供的约为8Mhz的时钟信号,因为RC振荡器产生的信号不够稳定,作为系统时钟作为严格的系统时钟可能不是一个很好的选择,这时就选择了HSE时钟源。

系统时钟初始化函数步骤简单描述为5步:
1、复位向量表。
2、选择HSE时钟源
3、设定预分频器AHB、APB1 APB2的分频系数及PLL的倍频系数。
4、使能PLL时钟源
5、使能PLL时钟

可能会对以上步骤的先后顺序有点疑惑,为什么会先配置PLL的相关系数,然后打开呢?
答案:(来自手册)PLL的设置(选择HIS振荡器除2或HSE振荡器为PLL的输入时钟,和选择倍频因子)必须在其被**前完成。一旦PLL被**,这些参数就不能被改动。

我们代入参数后,深入了解一下时钟的相关系数的值为多少。
假如Stm32_Clock_Init(9); //系统时钟设置
那么相应的时钟配置如下图所示
图片
============================================================================
//系统时钟初始化函数
//pll:选择的倍频数,从2开始,最大值为16  
void Stm32_Clock_Init(u8 PLL)
{
unsigned char temp=0;   
MYRCC_DeInit();   //复位并配置向量表
  RCC->CR|=0x00010000;  //外部高速时钟使能HSEON
while(!(RCC->CR>>17));//等待外部时钟就绪
RCC->CFGR=0X00000400; //APB1=DIV2;APB2=DIV1;AHB=DIV1;
PLL-=2;   //抵消2个单位(因为是从2开始的,设置0就是2)
RCC->CFGR|=PLL<<18;   //设置PLL值 2~16
RCC->CFGR|=1<<16;   //PLLSRC ON 
FLASH->ACR|=0x32;   //FLASH 2个延时周期
RCC->CR|=0x01000000;  //PLLON
while(!(RCC->CR>>25));//等待PLL锁定
RCC->CFGR|=0x00000002;//PLL作为系统时钟  
while(temp!=0x02)     //等待PLL作为系统时钟设置成功
{   
temp=RCC->CFGR>>2;
temp&=0x03;
}    
}
======================================================================================
PS:复位并配置向量表函数具体参数必须与芯片手册相结合,可以不用了解太深,如果要了解具体参数的含义请到STM32f103系列芯片手册查阅。
===============================复位并配置向量表函数==================================
//不能在这里执行所有外设复位!否则至少引起串口不工作.     
//把所有时钟寄存器复位   
void MYRCC_DeInit(void)
{
  RCC->APB1RSTR = 0x00000000;//复位结束  
RCC->APB2RSTR = 0x00000000; 
  
   RCC->AHBENR = 0x00000014;  //睡眠模式闪存和SRAM时钟使能.其他关闭.   
   RCC->APB2ENR = 0x00000000; //外设时钟关闭.    
   RCC->APB1ENR = 0x00000000;   
RCC->CR |= 0x00000001;     //使能内部高速时钟HSION    
RCC->CFGR &= 0xF8FF0000;   //复位SW[1:0],HPRE[3:0],PPRE1[2:0],PPRE2[2:0],ADCPRE[1:0],MCO[2:0]  
RCC->CR &= 0xFEF6FFFF;     //复位HSEON,CSSON,PLLON
RCC->CR &= 0xFFFBFFFF;     //复位HSEBYP       
RCC->CFGR &= 0xFF80FFFF;   //复位PLLSRC, PLLXTPRE, PLLMUL[3:0] and USBPRE 
RCC->CIR = 0x00000000;     //关闭所有中断  
//配置向量表   
#ifdef  VECT_TAB_RAM
MY_NVIC_SetVectorTable(0x20000000, 0x0);
#else   
MY_NVIC_SetVectorTable(0x08000000,0x0);
#
============================================================================
下面是相关寄存器的截图
 
 
图片

图片

图片

图片

图片

图片

图片

图片


==================================华丽的分界线===============================
==================================通用定时器3的使用================================
/**********************************************************************************
知识点:
1个定时器只有1个计数器,但是可以做很多个通道.共享一个计数器。

***********************************************************************************/
STM32的定时器分为高级定时器、通用定时器和基本定时器,高级控制定时器是指精度较为精准的TIM1和TIM8,而通用定时器是指TIM2~5,不过高级控制定时器(TIM1和TIM8)和通用定时器(TIMx)是完全独立的,他们的不共享任何资源,支持同步操作。他们之间只是说功能不一样,想通用定时器和基本定时器之间,他们的区别是基本定时器没有PWM测量输入输出脉宽的功能。具体区别见《STM32中文参考手册》。

现在呢,先讲讲通用定时器的使用,后面使用到了相关定时器,再更新相关内容。
首先先说一下通用定时器的大概框图,信号输入口主要有:外部信号(TIMx_ETR)、内部时钟(CK_INT)。其中内部时钟来自RCC的TIMxCLK时钟,也就是有来自输入为APB1的倍频器(详细内容涉及到时钟树),但是一定要区分出来CK_INT的时钟不是直接来自APB1或APB2,而是来自输入为APB1或者APB2的倍频器(这个可以从时钟树的图中看出,当APB1的预分频系数为1时,这个倍频器不起作用,定时器的时钟频率等于APB1的频率;当APB1的预分频系数为其它数值(即预分频系数为2、4、8或16)时,这个倍频器起作用,定时器的时钟频率等于APB1的频率两倍。)。
其实从通用定时器的框图就可以大概知道配置一个定时器需要配置哪些参数。从内部时钟(CK_INT)出来,输入信号进入触发控制器,在触发控制器中要设置的参数是定时器的使能、计数器的开启及计数的模式选择(向上/向下/中央对齐)。信号出来后进入PSC预分频器,在PSC预分频器中药配置相关的分频系数以得到相应的频率。处理过后的到CK_CNT信号,进入CNT计数器。计数器开始计数,并在相应的寄存器进行置位,(如果要进行数据的捕获那么就需要定时器通道的介入,在计数器计数的时候,可以通过设置相应的通道进行数据的捕获,从而实现需要的功能。比如,你要实现计数器计数到366时溢出,并在溢出前计数到300的时候,做一个操作,计数器溢出做另外一个操作,那么就可以通过配置捕获/比较寄存器得以实现。)然后信号输出。
在图中TIMx_CH1、TIMx_CH2、TIMx_CH3、TIMx_CH4、指的是4个通道,在PWM脉宽调制实例中会又相应配置,这了只是做简单的介绍。

/***********************************补充内容***************************************
关于框图还有以下几点要注意:
1、 影子寄存器。
有阴影的寄存器,表示在物理上这个寄存器对应2个寄存器,一个是程序员可以写入或读出的寄存器,称为preload register(预装载寄存器),
另一个是程序员看不见的、但在操作中真正起作用的寄存器,称为shadow register(影子寄存器);
2、 输入滤波机制
在ETR何TIx输入端有个输入滤波器,它的作用是以采样频率Fdts来采样N次进行滤波的。
3、 输入引脚和输出引脚是相同的。

定时器相关
时基单元 
时基单元有三个部分:CNT、PSC、ARR。CNT的计数方式分三种:向上、向下、中央对齐。通俗的说就是0—ARR、ARR—0、0—(ARR-1)—ARR—1.


*********************************************************************************/
==============================摘自中文参考手册==================================
时钟的选择
计数器时钟可由下列时钟源提供:
● 内部时钟(CK_INT)
● 外部时钟模式1:外部输入脚(TIx)
● 外部时钟模式2:外部触发输入(ETR)
● 内部触发输入(ITRx):使用一个定时器作为另一个定时器的预分频器,如可以配置一个定时
器Timer1而作为另一个定时器Timer2的预分频器。

内部时钟源(CK_INT)
如果禁止了从模式控制器(TIMx_SMCR寄存器的SMS=000),则CEN、DIR(TIMx_CR1寄存器)
和UG位(TIMx_EGR寄存器)是事实上的控制位,并且只能被软件修改(UG位仍被自动清除)。只
要CEN位被写成’1’,预分频器的时钟就由内部时钟CK_INT提供。

外部时钟源模式1
当TIMx_SMCR寄存器的SMS=111时,此模式被选中。计数器可以在选定输入端的每个上升沿
或下降沿计数。

外部时钟源模式2
选定此模式的方法为:令TIMx_SMCR寄存器中的ECE=1
计数器能够在外部触发ETR的每一个上升沿或下降沿计数。

例如,要配置在ETR下每2个上升沿计数一次的向上计数器,使用下列步骤:
1. 本例中不需要滤波器,置TIMx_SMCR寄存器中的ETF[3:0]=0000
2. 设置预分频器,置TIMx_SMCR寄存器中的ETPS[1:0]=01
3. 设置在ETR的上升沿检测,置TIMx_SMCR寄存器中的ETP=0
4. 开启外部时钟模式2,置TIMx_SMCR寄存器中的ECE=1
5. 启动计数器,置TIMx_CR1寄存器中的CEN=1
计数器在每2个ETR上升沿计数一次。
在ETR的上升沿和计数器实际时钟之间的延时取决于在ETRP信号端的重新同步电路。
=======================================================================================
定时器的初始化:
1)TIM3时钟使能。
2)允许TIM3工作。
3)设置TIM3_DIER允许更新中断。
4)设置TIM3_ARR和TIM3_PSC的值
5)TIM3中断分组设置
6)编写中断服务函数。
我们这里使用的是更新(溢出)中断,所以在状态寄存器 SR 的最低位。在处理完中断之后应
该向 TIM3_SR 的最低位写 0,来清除该中断标志

注意:
当发生一个更新事件时,所有的寄存器都被更新,硬件同时(依据URS位)设置更新标志位
(TIMx_SR寄存器中的UIF位)。
● 预分频器的缓冲区被置入预装载寄存器的值(TIMx_PSC寄存器的内容)。
● 自动装载影子寄存器被重新置入预装载寄存器的值(TIMx_ARR)。

下面的代码中有一个要注意的是系统时钟的设置,如果主函数缺少系统时钟的话,(即Stm32_Clock_Init(9);)那么相当于缺少时钟信号来源,所以这还是要注意一下的。

**********************************************************************************
知识点:
STM32的定时器具有计数功能,在实际应用中可以用来对引脚上的输入信号进行统计。其输入信号作为计数时钟,输入引脚为ETR引脚。

解释:
“其输入信号作为计数时钟,输入引脚为ETR引脚。”这句话的意思是:这个时候定时器是一个计数器,ETR引脚的信号就是被计数的对象,ETR引脚来一个信号,计数器就数一下,来一个就数一下,就是这个意思。但是计数器数的时候也要“吃饭啊”要不然它没有力气数的,于是“那为什么程序中还要RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);,这不是为TIM2开启了两个时钟吗”就作为计数器的“饭”了。

以上的内容呢很有趣,也很实用,是在查资料的时候无意查到的。觉得很有用!
***********************************************************************************/
=====================================time.c====================================
#include "sys.h"

u8 flag=0;


void TIM3_Int_Init(u16 arr,u16 psc)
{
RCC->APB1ENR |=1 << 1;//使能TIM3时钟
TIM3->CR1 |=0x01;//使能计数器
TIM3->CR1 &=~(1 << 4);//向上计数模式
TIM3->DIER |=0x01;//更新中断使能
TIM3->ARR =arr;
TIM3->PSC =psc;
MY_NVIC_Init(1,3,TIM3_IRQn,2);//抢占1,子优先级3,组2
}

//中断处理函数
void TIM3_IRQHandler(void)
{
if(TIM3->SR&0X0001)//溢出中断
{
flag++;
if(flag==3)
{
flag=0;
}
}    
TIM3->SR&=~(1<<0);//清除中断标志位    
}

=============================================================================
=================================time.h====================================
#ifndef __TIME_H__
#define  __TIME_H__

#include "stm32f10x.h"

void TIM3_Int_Init(u16 arr,u16 psc);
#endif
=============================================================================
=====================================main.c====================================
/**************************************************************
**功能:通过通用定时器3计数器的溢出中断来实现一个简单的跑马灯效果
**灯的相关引脚为PE5跟PB5
**************************************************************/
#include "sys.h"
#include "led.h"
#include "time.h"
extern u8 flag;

int main(void)
{  
Stm32_Clock_Init(9); //系统时钟设置
led_Init();
TIM3_Int_Init(4999, 7199);
    while(1)
{
if(flag==1)
{

GPIOE ->ODR   &=~ (1 << 5);//PE.5 输出低
}
else if(flag==2)
{

GPIOB ->ODR   &=~ (1 << 5);//PB.5 输出低
}
else
{
GPIOB ->ODR   |= 1 << 5;//PB.5 输出高
GPIOE ->ODR   |= 1 << 5;//PE.5 输出高
}
}  
=============================================================================
下面是相关寄存器的截图

图片

图片

图片

图片

图片



==================================华丽的分界线===============================
============================定时器通道复用功能I/O重映射======================
/*****************************************************************************
知识点:
端口才重映射的时候,
1、配置的先后问题:首先是应该开启AFIO时钟,然后才能配置AFIO相关的寄存器的,不然不会有作用的。
2、SPI1重映射的时候JTAG一定要关闭,否则不会有作用。
(理由:因为JTAG占用了PB3/PB4两个引脚,而这两个引脚恰好是SPI1映射后的引脚,所以我们要关闭JTAG-DP,关闭SW-DP(系统复位后的默认状态是启用SWJ但没有跟踪功能))
******************************************************************************/

IO复用功能重映射其实是基于优化64脚或100脚封装的外设数目,可以把一些复用功能重新映射到其他引脚上。设置复用重映射和调试I/O配置寄存器(AFIO_MAPR)实现引脚的重新映射。这时,复用功能不再映射到它们的原始分配上。
注意: 对寄存器AFIO_EVCR,AFIO_MAPR和AFIO_EXTICRX进行读写操作前,应当首先打开AFIO的时钟

定时器4的通道1到通道4可以从端口B重映射到端口D。其他定时器的重映射列在表2~表4。
参见复用重映射和调试I/O配置寄存器(AFIO_MAPR)。
图片
TIM4复用功能重映像
==========================================
复用功能  TIM4_REMAP = 0   TIM4_REMAP = 1     ||
TIM4_CH1   PB6   PD12 ||
TIM4_CH2   PB7   PD13               ||
TIM4_CH3   PB8   PD14 ||
TIM4_CH4   PB9   PD15 ||
==========================================

图片

图片

图片

图片


==================================华丽的分界线===============================
首先先简单说一下PWM脉宽调制的原理。脉宽调制(PWM)是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术,广泛应用在从测量、通信到功率控制与变换的许多领域中。
其实PWM就是对占空比的调制,用来对一个模拟信号的电平的编码。
图片
图中,我们假定定时器工作在向上计数PWM模式,且当CNT<CCRx时,输出0,当CNT>=CCRx时输出1。那么就可以得到如上的PWM示意图:当CNT值小于CCRx的时候,IO输出低电平(0),当CNT值大于等于CCRx的时候,IO输出高电平(1),当CNT达到ARR值的时候,重新归零,然后重新向上计数,依次循环。改变CCRx的值,就可以改变PWM输出的占空比,改变ARR的值,就可以改变PWM输出的频率,这就是PWM 输出的原理。

首先要弄懂PWM的原理,弄懂原理了就比较容易去写寄存器的相关配置,简单说说自己的理解,一般比较容易理解或者说比较容易看出效果的是LED的PWM调光实验,本人觉得用这个例子去理解PWM脉宽调制最适合不过了,因为实验本身可以通过LED的亮度来体现一定的占空比,而PWM就是通过调制占空比的比值,也就是高低电平在整个周期的比值。
LED的PWM的实验呢,主要是要通过设置一个高值来确定周期的长度,即ARR的值(高值这里主要是向上计数模式为例),还有比较值,也就是用来判断输出高低电平的值。也可以这么理解,就是假如计数周期为一个已知长度的绳子,那么比较值呢,就是要剪的绳子长度,占空比其实就是剪下来的绳子与剩下长度的绳子的比值,所以简单的理解呢,PWM调制其实就是设置一定长度的绳子后,再设定一个比较值,从而用比较值来改变占空比,从而来实现脉宽的调制。
具体要怎么设置呢,第一先看PWM的初始化步骤,第二再结合寄存器来理解初始化的代码。

PWM工作过程图:(通道1为例,通道1~2寄存器为TIMx_CCMR1,两个通道功能设置集成在一起,如通道3~4位,TIMx_CCMR2)
图片

PWM的初始化也就如图所示一样,对以上TIMx_CCMR1的OC2M[2:0]位、TIMx_CCER的CC2P位、TIMx_CCER的CC2E位,进行相应配置。
/*******************************************************************************
CCR1:捕获比较(值)寄存器(x=1,2,3,4):设置比较值。
CCMR1: OC1M[2:0]位:
              对于PWM方式下,用于设置PWM模式1【110】或者PWM模式2【111】
CCER:CC1P位:输入/捕获1输出极性。0:高电平有效,1:低电平有效。
CCER:CC1E位:输入/捕获1输出使能。0:关闭,1:打开。
*******************************************************************************/
PWM的相关初始化
1)开启TIM3时钟,配置PB5为复用输出。
2)设置 TIM3_CH2重映射到PB5上 
3)设置 TIM3 的 的 ARR 和 和 PSC 
4)设置 TIM3_CH2 的 的 PWM  
5)使能 TIM3 的 的 CH2  输出,使能 TIM3 。
6)修改 TIM3_CCR2  来控制占空比。
=======================================time.c=====================================
#include "sys.h"

//TIM3 PWM部分初始化 
//PWM输出初始化
//arr:自动重装值
//psc:时钟预分频数
void TIM3_PWM_Init(u16 arr,u16 psc)
{    
//此部分需手动修改IO口设置
RCC->APB1ENR|=1<<1;  //TIM3时钟使能    
RCC->APB2ENR|=1<<3;     //使能PORTB时钟
GPIOB->CRL&=0XFF0FFFFF; //PB5输出
GPIOB->CRL|=0X00B00000; //复用功能输出      
   
RCC->APB2ENR|=1<<0;     //开启辅助时钟    
AFIO->MAPR&=0XFFFFF3FF; //清除MAPR的[11:10]
AFIO->MAPR|=1<<11;      //部分重映像,TIM3_CH2->PB5

TIM3->ARR=arr; //设定计数器自动重装值 
TIM3->PSC=psc; //预分频器不分频
TIM3->CCMR1|=7<<12;   //CH2 PWM2模式  
TIM3->CCMR1|=1<<11;  //CH2预装载使能    
TIM3->CCER|=1<<4;    //OC2 输出使能    
TIM3->CR1=0x0080;    //ARPE使能 
TIM3->CR1|=0x01;     //使能定时器3    
============================================================================
=================================time.h====================================
#ifndef __TIME_H__
#define  __TIME_H__

#include "stm32f10x.h"

void TIM3_PWM_Init(u16 arr,u16 psc);
#endif
============================================================================
============================================================================
#include "sys.h"
#include "delay.h" 
#include "led.h" 
#include "timer.h"

int main(void)
{    
u16 led0pwmval=0;    
u8 dir=1;
Stm32_Clock_Init(9); //系统时钟设置
delay_init(72);       //延时初始化 
LED_Init();    //初始化与LED连接的硬件接口
TIM3_PWM_Init(899,0); //不分频。PWM频率=72000/(899+1)=80Khz
while(1)
{
  delay_ms(10);  
if(dir)led0pwmval++;
else led0pwmval--;  
  if(led0pwmval>300)dir=0;
if(led0pwmval==0)dir=1;      
LED0_PWM_VAL=led0pwmval;    
}
}
============================================================================
下面来具体说一下初始化函数的具体意思。
整个过程可以简单描述为:
脉冲宽度调制模式可以产生一个由TIMx_ARR寄存器确定频率、由TIMx_CCRx寄存器确定占空
比的信号。
在TIMx_CCMRx寄存器中的OCxM位写入’110’(PWM模式1)或’111’(PWM模式2),能够独立地设
置每个OCx输出通道产生一路PWM。必须通过设置TIMx_CCMRx寄存器的OCxPE位使能相应
的预装载寄存器,最后还要设置TIMx_CR1寄存器的ARPE位,(在向上计数或中心对称模式中)
使能自动重装载的预装载寄存器。
/***************************************************************************/
void TIM3_PWM_Init(u16 arr,u16 psc)
{    
//此部分需手动修改IO口设置
1 RCC->APB1ENR|=1<<1;  //TIM3时钟使能    
2 RCC->APB2ENR|=1<<3;     //使能PORTB时钟
3 GPIOB->CRL&=0XFF0FFFFF; //PB5输出
4 GPIOB->CRL|=0X00B00000; //复用功能输出      
   
5 RCC->APB2ENR|=1<<0;     //开启辅助时钟    
6 AFIO->MAPR&=0XFFFFF3FF; //清除MAPR的[11:10]
7 AFIO->MAPR|=1<<11;      //部分重映像,TIM3_CH2->PB5

8 TIM3->ARR=arr; //设定计数器自动重装值 
9 TIM3->PSC=psc; //预分频器不分频
10 TIM3->CCMR1|=7<<12;   //CH2 PWM2模式  
11 TIM3->CCMR1|=1<<11;  //CH2预装载使能    
12 TIM3->CCER|=1<<4;    //OC2 输出使能    
13 TIM3->CR1=0x0080;    //ARPE使能 
14 TIM3->CR1|=0x01;     //使能定时器3    
}
/*****************************************************************************/
首先1~4行中,还是要重复说明的简单却很重要的,那就是开启相应的外设时钟,要用到TIM3的来产生信号,来进行PWM的调制,所以TIM3的时钟必不可少,然后第2句则是开启B端口的时钟,因为LED是PB5,那么B端口的时钟也少不了。这些还是比较简单的,还有一个需要说出来的是第4句,LED的功能要修改成复用功能输出,这是因为这是一个定时器通道的一个输出端,通道输出的端口一般是有规定的,相应寄存器且看定时器3的通道表,

5~7句呢,是设置定时器3的通道2的重映射。(为什么呢?因为所有的通道映射的端口都是一开始规定好的,如果需要重映射,必须进行开启辅助时钟后进行重映射操作,重映射操作呢,分为三种不同情况,一种呢是默认情况下,第二种呢是部分重映射,第三种呢是完全重映射,在本文的初始化代码中选用的是部分重映射,为什么呢,因为LED灯在PB5,那么这也是选择通道2的理由。)
其中第6行是把AFIO_MAPR寄存器的第11跟第10位清为0,然后再进行设置。

10~14句呢,其实就是对一些模式,或则功能的配置,这里就不在多说。
图片

//=======================================================================//
**注释:如有错误,欢迎大神指错。
//=======================================================================//