STM32笔记—定时器-CSDN博客

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

目录

一、TIM简介   

二、基本定时器TIM6和TIM7

1. TIM6和TIM7简介

2. TIM6和TIM7的主要特性

3. TIM6和TIM7的功能

3.1 时基单元

 3.2 计数模式

 3.3 时钟源

三、通用定时器

1. TIMx(2、3、4、5)简介

2. TIMx主要功能

3. 时钟选择

 4. 影子寄存器

 5. 定时中断程序实现

5.1 函数TIM_TimeBaseInit

 源码

 5.2 函数NVIC_Init

 5.3 定时中断初始化

5.4 TIM2中断函数

6. TIMx输出比较功能

 6.1 输出比较模式的配置步骤

6.2 PWM模式

    PWM基本结构

6.3 呼吸灯代码实现 

初始化

 STM32103C8T6的引脚定义图

pwm配置

main函数

7.  输入捕获

7.1 频率测量方法

 7.2 输入捕获通道

        TIM_ICInit函数输入捕获初始化

         STM32通用或高级定时器的从模式有如下几种

 8. PWMI模式

 注意

         PWMI模块初始化

        TIM_PWMIConfig()函数

四、高级定时器

1. TIM1和TIM8简介

2. 重复计数器

 3. 互补输出和死区插入

位操作

        左移与右移    


一、TIM简介   

        定时器可以对输入的时钟进行计数并在计数值达到设定值时触发中断

        16位计数器、预分频器、自动重装寄存器的时基单元在72MHz计数时钟下可以实现最大59.65s的定时【1/(72MHZ/65536*65536)预分频器和计数器都是16位的

        根据复杂度和应用场景分为了高级定时器、通用定时器、基本定时器三种类型  

类型

编号

总线

功能

高级定时器

TIM1TIM8

APB2

拥有通用定时器全部功能

并额外具有重复计数器、死区生成、互补输出、刹车输入等功能

通用定时器

TIM2TIM3

TIM4TIM5

APB1

拥有基本定时器全部功能

并额外具有内外时钟源选择、输入捕获、输出比较、

编码器接口、主从触发模式等功能

基本定时器

TIM6TIM7

APB1

拥有定时中断、主模式触发DAC的功能

二、基本定时器TIM6和TIM7

1. TIM6和TIM7简介

         基本定时器TIM6和TIM7各包含一个16位自动装载计数器由各自的可编程预分频器驱动。 它们可以作为通用定时器提供时间基准特别地可以为数模转换器(DAC)提供时钟。实际上它们在芯片内部直接连接到DAC并通过触发输出直接驱动DAC。 这2个定时器是互相独立的不共享任何资源。

2. TIM6和TIM7的主要特性

        TIM6和TIM7定时器的主要功能包括 16位自动重装载累加计数器 16位可编程(可实时修改)预分频器用于对输入的时钟按系数为165536之间的任意数值分频 触发DAC的同步电路  在更新事件(计数器溢出)时产生中断/DMA请求。

         上图可以简单的由下图理解基准时钟预分频计数器累加到重装载值产生中断或者事件

        

3. TIM6和TIM7的功能

3.1 时基单元

         时基单元包含 ● 计数器寄存器(TIMx_CNT) ● 预分频寄存器(TIMx_PSC) ● 自动重装载寄存器(TIMx_ARR) 

        预分频器:预分频可以以系数介于1至65536之间的任意数值对计数器时钟分频。它是通过一个16位寄存器(TIMx_PSC)的计数实现分频。

        因为TIMx_PSC控制寄存器具有缓冲可以在运行过程中改变它的数值新的预分频数值将在下一个更新事件时起作用。

        CK_PSC预分频器的输入时钟、CNT_EN计数器使能前半段从计数器使能开始定时器时钟等于计数器时钟后半段预分频系数从1变成2定时器时钟变成预分频器输入时钟的一半。当计数周期没有结束时改变预分频寄存器的值不会立即生效而是等到计数结束产生中断才会传递改变的数值到缓冲寄存器里。

        计数器计数频率CK_CNT = CK_PSC / (PSC + 1)

 3.2 计数模式

        计数器从0累加计数到自动重装载数值(TIMx_ARR寄存器)然后重新从0开始计数并产生一个计数器溢出事件。

        当发生一次更新事件时所有寄存器会被更新并(根据URS位)设置更新标志(TIMx_SR寄存器的UIF位) ● 传送预装载值(TIMx_PSC寄存器的内容)至预分频器的缓冲区。 ● 自动重装载影子寄存器被更新为预装载值(TIMx_ARR)。

 

         当计数值到了0x36后计数器溢出触发中断并更新中断标志位计数器溢出频率CK_CNT_OV = CK_CNT / (ARR + 1) = CK_PSC / (PSC + 1) / (ARR + 1)

 3.3 时钟源

        计数器的时钟由内部时钟(CK_INT)提供。

三、通用定时器

1. TIMx(2、3、4、5)简介

        通用定时器是一个通过可编程预分频器驱动的16位自动装载计数器构成。 它适用于多种场合包括测量输入信号的脉冲长度(输入捕获)或者产生输出波形(输出比较和PWM)。 使用定时器预分频器和RCC时钟控制器预分频器脉冲长度和波形周期可以在几个微秒到几个毫秒间调整。

2. TIMx主要功能

        通用TIMx (TIM2、TIM3、TIM4和TIM5)定时器功能包括

        ● 16位向上、向下、向上/向下自动装载计数器 与基本定时器的区别之一● 16位可编程(可以实时修改)预分频器计数器时钟频率的分频系数为165536之间的任意数值 ● 4个独立通道 ─ 输入捕获 ─ 输出比较 ─ PWM生成(边缘或中间对齐模式) ─ 单脉冲模式输出 ● 使用外部信号控制定时器和定时器互连的同步电路

        ● 如下事件发生时产生中断/DMA ─ 更新计数器向上溢出/向下溢出计数器初始化(通过软件或者内部/外部触发) ─ 触发事件(计数器启动、停止、初始化或者由内部/外部触发计数) ─ 输入捕获 ─ 输出比较

        ● 支持针对定位的增量(正交)编码器和霍尔传感器电路 ● 触发输入作为外部时钟或者按周期的电流管理      

3. 时钟选择

        计数器时钟可由下列时钟源提供

        ● 内部时钟(CK_INT)

        ● 外部时钟模式1外部输入脚(TIx)

        ● 外部时钟模式2外部触发输入(ETR)

        ● 内部触发输入(ITRx)使用一个定时器作为另一个定时器的预分频器如可以配置一个定时器Timer1而作为另一个定时器Timer2的预分频器。

        对于基本定时器只能选择72MHz的内部时钟而通用定时器可以选择如下时钟外部时钟+内部时钟[ITR接别的定时器]
  

 4. 影子寄存器

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

        下图给出一些例子当TIMx_ARR=0x36时计数器在ARPE=0/1时的动作。

        当没有预装入时写入新数值至TIMx_ARR寄存器计数器会直接开始自增到36再发生溢出触发中断。

        而当ARPE=1,真正起作用的变成了自动加载影子寄存器计数器会先计数到F5等到计数周期结束才开始重新计数到36如果没有影子寄存器就会出现计数器先从F1自增到0xFFFF再从0计数到36的现象计数时间增加。

 5. 定时中断程序实现

5.1 函数TIM_TimeBaseInit

TIM_TimeBaseInitTypeDef structure
TIM_TimeBaseInitTypeDef定义于文件“stm32f10x_tim.h”
typedef struct
{
u16 TIM_Period;
u16 TIM_Prescaler;
u8 TIM_ClockDivision;
u16 TIM_CounterMode;
} TIM_TimeBaseInitTypeDef;

TIM_Period
        TIM_Period设置了在下一个更新事件装入活动的自动重装载寄存器周期的值。它的取值必须在0x0000和0xFFFF之间。
TIM_Prescaler
        TIM_Prescaler设置了用来作为TIMx时钟频率除数的预分频值。它的取值必须在0x0000和0xFFFF之间。
TIM_ClockDivision
        TIM_ClockDivision设置了时钟分割。该参数取值见下表。

TIM_ClockDivision描述
TIM_CKD_DIV1TDTS = Tck_tim
TIM_CKD_DIV2TDTS = 2Tck_tim
TIM_CKD_DIV4TDTS = 4Tck_tim

TIM_CounterMode
        TIM_CounterMode选择了计数器模式。该参数取值见下表。

TIM_CounterMode描述
TIM_CounterMode_UpTIM向上计数模式
TIM_CounterMode_DownTIM向下计数模式
TIM_CounterMode_CenterAligned1TIM中央对齐模式1计数模式
TIM_CounterMode_CenterAligned2TIM中央对齐模式2计数模式
TIM_CounterMode_CenterAligned3TIM中央对齐模式3计数模式
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_TimeBaseStructure.TIM_Period = 0xFFFF;
TIM_TimeBaseStructure.TIM_Prescaler = 0xF;
TIM_TimeBaseStructure.TIM_ClockDivision = 0x0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, & TIM_TimeBaseStructure);
 源码

        注意TIM_TimeBaseInit函数末尾手动产生了更新事件若不清除此标志位则开启中断后会立刻进入一次中断如果不介意此问题则不清除此标志位也可。

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;           
}

 5.2 函数NVIC_Init

 5.3 定时中断初始化

        1. 开启时钟 2. 配置时钟源 3. 配置时基单元初始化 4. 触发中断开启中断、中断优先级、调用NVIC

/**
  * 函    数定时中断初始化
  * 参    数无
  * 返 回 值无
  */
void Timer_Init(void)
{
	/*开启时钟*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);			//开启TIM2的时钟
	
	/*配置时钟源*/
	TIM_InternalClockConfig(TIM2);		//选择TIM2为内部时钟若不调用此函数TIM默认也为内部时钟
	
	/*时基单元初始化*/
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;				//定义结构体变量
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;		//时钟分频选择不分频此参数用于配置滤波器时钟不影响时基单元功能
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;	//计数器模式选择向上计数
	TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1;				//计数周期即ARR的值
	TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1;				//预分频器即PSC的值
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;			//重复计数器高级定时器才会用到
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);				//将结构体变量交给TIM_TimeBaseInit配置TIM2的时基单元	
	
	/*中断输出配置*/
	TIM_ClearFlag(TIM2, TIM_FLAG_Update);						//清除定时器更新标志位
																//TIM_TimeBaseInit函数末尾手动产生了更新事件
																//若不清除此标志位则开启中断后会立刻进入一次中断
																//如果不介意此问题则不清除此标志位也可
	
	TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);					//开启TIM2的更新中断
	
	/*NVIC中断分组*/
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);				//配置NVIC为分组2
																//即抢占优先级范围0~3响应优先级范围0~3
																//此分组配置在整个工程中仅需调用一次
																//若有多个中断可以把此代码放在main函数内while循环之前
																//若调用多次配置分组的代码则后执行的配置会覆盖先执行的配置
	
	/*NVIC配置*/
	NVIC_InitTypeDef NVIC_InitStructure;						//定义结构体变量
	NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;				//选择配置NVIC的TIM2线
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;				//指定NVIC线路使能
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;	//指定NVIC线路的抢占优先级为2
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;			//指定NVIC线路的响应优先级为1
	NVIC_Init(&NVIC_InitStructure);								//将结构体变量交给NVIC_Init配置NVIC外设
	
	/*TIM使能*/
	TIM_Cmd(TIM2, ENABLE);			//使能TIM2定时器开始运行
}
5.4 TIM2中断函数
  * 函    数TIM2中断函数
  * 参    数无
  * 返 回 值无
  * 注意事项此函数为中断函数无需调用中断触发后自动执行
  *           函数名为预留的指定名称可以从启动文件复制
  *           请确保函数名正确不能有任何差异否则中断函数将不能进入
  */
void TIM2_IRQHandler(void)
{
	if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)		//判断是否是TIM2的更新事件触发的中断
	{
		Num ++;												//Num变量自增用于测试定时中断
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);			//清除TIM2更新事件的中断标志位
													//中断标志位必须清除
													//否则中断将连续不断地触发导致主程序卡死
	}
}

6. TIMx输出比较功能

     输出比较可以通过比较CNT与CCR寄存器值的关系来对输出电平进行置1、置0或翻转的操作用于输出一定频率和占空比的PWM波形;每个高级定时器和通用定时器都拥有4个输出比较通道;高级定时器的前3个通道额外拥有死区生成和互补输出的功能。

        即通过比较CNT计数器和捕获/比较寄存器输出电平。

        上图CCIP极性选择此外输出模式控制器模式如下

模式

描述

冻结

CNT=CCRREF保持为原状态

匹配时置有效电平

CNT=CCRREF置有效电平

匹配时置无效电平

CNT=CCRREF置无效电平

匹配时电平翻转

CNT=CCRREF电平翻转

强制为无效电平

CNTCCR无效REF强制为无效电平

强制为有效电平

CNTCCR无效REF强制为有效电平

PWM模式1

向上计数CNT<CCRREF置有效电平CNTCCRREF置无效电平

向下计数CNT>CCRREF置无效电平CNTCCRREF置有效电平

PWM模式2

向上计数CNT<CCRREF置无效电平CNTCCRREF置有效电平

向下计数CNT>CCRREF置有效电平CNTCCRREF置无效电平

 6.1 输出比较模式的配置步骤

        1. 选择计数器时钟(内部外部预分频器)

        2. 将相应的数据写入TIMx_ARR和TIMx_CCRx寄存器中

        3. 如果要产生一个中断请求和/或一个DMA请求设置CCxIE位和/或CCxDE位。

        4. 选择输出模式(往往设置为PWM1模式)

        例如当计数器CNT与CCRx匹配时翻转OCx的输出引脚CCRx预装载未用开启OCx输出且高电平有效则必须设置OCxM=’011’、OCxPE=’0’、CCxP=’0’和CCxE=’1’。怎么理解

        CCxP = 0///CCxE = 1(捕获/比较使能寄存器(TIMx_CCER))开启OCx输出且高电平有效

         OCxM=’011’、OCxPE=’0翻转OCx的输出引脚CCRx预装载未用

捕获/比较模式寄存器1(TIMx_CCMR1):

      

       5. 设置TIMx_CR1寄存器的CEN位启动计数器

        TIMx_CCRx寄存器能够在任何时候通过软件进行更新以控制输出波形条件是未使用预装载寄存器(OCxPE=’0’否则TIMx_CCRx影子寄存器只能在发生下一次更新事件时被更新)。下图给出了一个例子。

        计数器的值和输出比较寄存器的值一直在比较待计数器的值达到CCR的003A因为设置的模式时翻转电平所以通过输出模式控制器后到达的OC1电平从低电平变成高电平但是由于CNT的值还没有到重装载寄存器的值计数器接着自增。而此时在CC1R寄存器写入B201h那么由于没有使用预装载那么待到B201再次马上翻转电平。 

6.2 PWM模式

        脉冲宽度调制模式可以产生一个由TIMx_ARR寄存器确定频率、由TIMx_CCRx寄存器确定占空比的信号。

PWM模式1

向上计数CNT<CCRREF置有效电平CNTCCRREF置无效电平

向下计数CNT>CCRREF置无效电平CNTCCRREF置有效电平

PWM模式2

向上计数CNT<CCRREF置无效电平CNTCCRREF置有效电平

向下计数CNT>CCRREF置有效电平CNTCCRREF置无效电平

        仅当发生一个更新事件的时候预装载寄存器才能被传送到影子寄存器因此在计数器开始计数之前必须通过设置TIMx_EGR寄存器中的UG位来初始化所有的寄存器。


          下面是一个PWM模式1的例子。当TIMx_CNT<TIMx_CCRx时PWM信号参考OCxREF为高否则为低。如果TIMx_CCRx中的比较值大于自动重装载值(TIMx_ARR)则OCxREF保持为’1’。如果比较值为0则OCxREF保持为’0’。 下图为TIMx_ARR=8时边沿对齐的PWM波形实例。

            注意CCxIF中断标志当TIMx_CNT与TIMx_CCR1匹配时置1

    PWM基本结构

   30相当于CCR的值(捕获比较寄存器)99相当于自动重装载值

        PWM频率  Freq = CK_PSC / (PSC + 1) / (ARR + 1)
        PWM占空比  Duty = CCR / (ARR + 1)
        PWM分辨率  Reso = 1 / (ARR + 1)
6.3 呼吸灯代码实现 
初始化

        需要注意的是GPIO的配置要配置成复用推挽输出。

        将PA0引脚初始化为复用推挽输出受外设控制的引脚均需要配置为复用模式 。      

/*开启时钟*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);			//开启TIM2的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);			//开启GPIOA的时钟
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;		//GPIO_Pin_15;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);				//将PA0引脚初始化为复用推挽输出	
										  //受外设控制的引脚均需要配置为复用模式		
	
	/*配置时钟源*/
	TIM_InternalClockConfig(TIM2);	//选择TIM2为内部时钟若不调用此函数TIM默认也为内部时钟
	
	/*时基单元初始化*/
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;				//定义结构体变量
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;     //时钟分频选择不分频此参数用于配置滤波器时钟不影响时基单元功能
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式选择向上计数
	TIM_TimeBaseInitStructure.TIM_Period = 100 - 1;					//计数周期即ARR的值
	TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1;				//预分频器即PSC的值
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;            //重复计数器高级定时器才会用到
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);             //将结构体变量交给TIM_TimeBaseInit配置TIM2的时基单元
	
	/*输出比较初始化*/
	TIM_OCInitTypeDef TIM_OCInitStructure;							//定义结构体变量
	TIM_OCStructInit(&TIM_OCInitStructure);							//结构体初始化若结构体没有完整赋值
																	//则最好执行此函数给结构体所有成员都赋一个默认值
																	//避免结构体初值不确定的问题
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;				//输出比较模式选择PWM模式1
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;		//输出极性选择为高若选择极性为低则输出高低电平取反
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;	//输出使能
	TIM_OCInitStructure.TIM_Pulse = 0;								//初始的CCR值
	TIM_OC1Init(TIM2, &TIM_OCInitStructure);						//将结构体变量交给TIM_OC1Init配置TIM2的输出比较通道1
	
	/*TIM使能*/
	TIM_Cmd(TIM2, ENABLE);			//使能TIM2定时器开始运行
}

 注意其中对于GPIO的初始化还可以使用I/O口重映射

/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;		//GPIO_Pin_15;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);				//将PA0引脚初始化为复用推挽输出	
										  //受外设控制的引脚均需要配置为复用模式		
	
 STM32103C8T6的引脚定义图

         查手册可以直到复用的引脚为PA15,如下表

         使用下图函数进行配置改变指定引脚的映射。

 

         但是PA15正好是主功能是JTDI的特殊引脚故在配置io口时需要注意让该io口变成普通的io再配置复用。

         具体配置代码如下

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//开启AFIO时钟
	GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2,ENABLE);
	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);
	
pwm配置
/**
  * 函    数PWM设置CCR
  * 参    数Compare 要写入的CCR的值范围0~100
  * 返 回 值无
  * 注意事项CCR和ARR共同决定占空比此函数仅设置CCR的值并不直接是占空比
  *           占空比Duty = CCR / (ARR + 1)
  */
void PWM_SetCompare1(uint16_t Compare)
{
	TIM_SetCompare1(TIM2, Compare);		//设置CCR1的值
}
main函数
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"

uint8_t i;			//定义for循环的变量

int main(void)
{
	/*模块初始化*/
	OLED_Init();		//OLED初始化
	PWM_Init();			//PWM初始化
	
	while (1)
	{
		for (i = 0; i <= 100; i++)
		{
			PWM_SetCompare1(i);			//依次将定时器的CCR寄存器设置为0~100PWM占空比逐渐增大LED逐渐变亮
			Delay_ms(10);				//延时10ms
		}
		for (i = 0; i <= 100; i++)
		{
			PWM_SetCompare1(100 - i);	//依次将定时器的CCR寄存器设置为100~0PWM占空比逐渐减小LED逐渐变暗
			Delay_ms(10);				//延时10ms
		}
	}
}

7.  输入捕获

        输入捕获模式下当通道输入引脚出现指定电平跳变时当前CNT的值将被锁存到CCR中可用于测量PWM波形的频率、占空比、脉冲间隔、电平持续时间等参数每个高级定时器和通用定时器都拥有4个输入捕获通道可配置为PWMI模式同时测量频率和占空比。

7.1 频率测量方法

        测频法在闸门时间T内对上升沿计次得到N则频率f = N/T

        测周法两个上升沿内以标准频率fc计次得到N 则频率f = fc/N。

        频率的定义是1s内出现多少个重复的周期。那么测频法如果T=1s,那么在闸门时间内计数次数刚好等于频率。但是如果待测信号的频率非常低1s内出现的上升沿将非常少甚至没有但是频率肯定不是0故当计数次数少时即上升沿出现次数少时对于频率测量的误差会很大故往往测频法适用于测量高频信号有助于减少误差。计数次数都希望越大越好以减少误差但是注意计数均存在±1的误差因为当周期只出现了一半的时候若闸门时间就到了那么就会只取次数出现误差

        而测周法恰恰相反对于低频信号而言周期更长计数次数会增加误差减小。假如有一待测信号为500KHz而fc = 1MHz那么一个周期内仅仅能计数一两次那么当待测信号频率更高时计次次数将更小甚至为0那么对于待测信号频率的判断误差会很大。

        那什么是高频信号什么是低频信号中界频率测频法与测周法误差相等的频率点。

 7.2 输入捕获通道
        TIM_ICInit函数输入捕获初始化

        具体代码如下

/*输入捕获初始化*/
	TIM_ICInitTypeDef TIM_ICInitStructure;							//定义结构体变量
	TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;				//选择配置定时器通道1
	TIM_ICInitStructure.TIM_ICFilter = 0xF;							//输入滤波器参数可以过滤信号抖动
	TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;		//极性选择为上升沿触发捕获
	TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;			//捕获预分频选择不分频每次信号都触发捕获
	TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;	//输入信号交叉选择直通不交叉
	TIM_ICInit(TIM3, &TIM_ICInitStructure);							//将结构体变量交给TIM_ICInit配置TIM3的输入捕获通道

         需要注意下图部分每次捕获后计数器清零。

         STM32通用或高级定时器的从模式有如下几种

        1、复位模式 【Reset mode】

        2、触发模式 【Trigger mode】

        3、门控模式 【Gate mode】

        4、外部时钟模式1 【External clock mode 1】

        5、编码器模式 【encode mode】

        计数器开始依据内部时钟计数然后正常运转直到TI1出现一个上升沿此时计数器被清零然后从0重新开始计数。同时触发标志(TIMx_SR寄存器中的TIF位)被设置根据TIMx_DIER寄存器中TIE(中断使能)位和TDE(DMA使能)位的设置产生一个中断请求或一个DMA请求。下图显示当自动重装载寄存器TIMx_ARR=0x36时的动作。在TI1上升沿和计数器的实际复位之间的延时取决于TI1输入端的重同步电路。

        当有效触发输入信号出现时计数器将会被复位同时还会产生更新事件和触发事件。如果计数器向上计数或中央对齐模式的话复位后计数器从0开始计数如果向下计数模式复位后计数器从ARR值开始计数。不妨以计数器向上计数为例将它配置在复位从模式。比方说当计数器计数到某个数据的时候来了个触发信号计数器不再继续往上计数而是重新归0后开始计数。当然计数器的实际复位操作与触发沿之间往往会有个小的延时这是由于触发信号作为有效触发脉冲的话还需要经过定时器内的同步电路确认。

        下面列出了相关寄存器

         清零逻辑如下图触发源选择器选择TI1FP1TRGI触发从模式reset。但是自动清除只能选择通道1和通道2

        即当检测到上升沿后该触发信号将CNT计数器的值转运到捕获寄存器中即CCR1 = CNT之后CNT置零CNT计数继续++只要没有达到自动重装载值当边沿检测到下一次上升沿再次发生转运CCR1 = CNT这样刚好得到了一个周期的计数值故其实输入捕获使用的是测周法获取频率值。

 这里再简单介绍一下从模式控制寄存器(TIMx_SMCR)

        滤波的配置

         从模式清零

         TIM_SelectInputTrigger():

         即TI1产生上升沿时会触发CNT归零。实现代码

/*选择触发源及从模式*/
	TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);					//触发源选择TI1FP1
	TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);					//从模式选择复位
																	//即TI1产生上升沿时会触发CNT归零

 8. PWMI模式

        两个通道一个上升沿触发一个下降沿触发当上升沿触发时CCR1 = CNT之后从模式控制寄存器使得CNT = 0待下次下降沿到来CCR2 = CNT(该计数值刚好一个高电平的计数值。)之后依次循环。

 注意

        GPIO的配置需要配置成浮空输入我这里配置成了上拉输入可以参考这篇文章

        输入捕获时GPIO引脚的输入方式如何设置-CSDN博客

        引申什么时候可以用浮空输入
        当捕获方波信号的时候应该用浮空输入不能用下拉输入。因为方波信号本身既有高电平也有低电平强制下拉为低电平会影响方波信号。捕获发生在上升沿或者下降沿如果没有上升沿和下降沿就无法发生捕获。被捕捉的信号本身没有上升沿或下降沿要通过IO口输入设置造出上升沿或下降沿被捕捉信号本身有上升沿或下降沿的IO口设置为浮空输入即可。

/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);							//将PA6引脚初始化为上拉输入
	

        为什么初始化PA6?

         PWMI模块初始化
/*PWMI模式初始化*/
	TIM_ICInitTypeDef TIM_ICInitStructure;							//定义结构体变量
	TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;				//选择配置定时器通道1
	TIM_ICInitStructure.TIM_ICFilter = 0xF;							//输入滤波器参数可以过滤信号抖动
	TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;		//极性选择为上升沿触发捕获
	TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;			//捕获预分频选择不分频每次信号都触发捕获
	TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;	//输入信号交叉选择直通不交叉
	TIM_PWMIConfig(TIM3, &TIM_ICInitStructure);						//将结构体变量交给TIM_PWMIConfig配置TIM3的输入捕获通道
																	//此函数同时会把
        TIM_PWMIConfig()函数
/**
  * @brief  Configures the TIM peripheral according to the specified
  *         parameters in the TIM_ICInitStruct to measure an external PWM signal.
  * @param  TIMx: where x can be  1, 2, 3, 4, 5, 8, 9, 12 or 15 to select the TIM peripheral.
  * @param  TIM_ICInitStruct: pointer to a TIM_ICInitTypeDef structure
  *         that contains the configuration information for the specified TIM peripheral.
  * @retval None
  */
void TIM_PWMIConfig(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct)
{
  uint16_t icoppositepolarity = TIM_ICPolarity_Rising;
  uint16_t icoppositeselection = TIM_ICSelection_DirectTI;
  /* Check the parameters */
  assert_param(IS_TIM_LIST6_PERIPH(TIMx));
  /* Select the Opposite Input Polarity */
  if (TIM_ICInitStruct->TIM_ICPolarity == TIM_ICPolarity_Rising)
  {
    icoppositepolarity = TIM_ICPolarity_Falling;
  }
  else
  {
    icoppositepolarity = TIM_ICPolarity_Rising;
  }
  /* Select the Opposite Input */
  if (TIM_ICInitStruct->TIM_ICSelection == TIM_ICSelection_DirectTI)
  {
    icoppositeselection = TIM_ICSelection_IndirectTI;
  }
  else
  {
    icoppositeselection = TIM_ICSelection_DirectTI;
  }
  if (TIM_ICInitStruct->TIM_Channel == TIM_Channel_1)
  {
    /* TI1 Configuration */
    TI1_Config(TIMx, TIM_ICInitStruct->TIM_ICPolarity, TIM_ICInitStruct->TIM_ICSelection,
               TIM_ICInitStruct->TIM_ICFilter);
    /* Set the Input Capture Prescaler value */
    TIM_SetIC1Prescaler(TIMx, TIM_ICInitStruct->TIM_ICPrescaler);
    /* TI2 Configuration */
    TI2_Config(TIMx, icoppositepolarity, icoppositeselection, TIM_ICInitStruct->TIM_ICFilter);
    /* Set the Input Capture Prescaler value */
    TIM_SetIC2Prescaler(TIMx, TIM_ICInitStruct->TIM_ICPrescaler);
  }
  else
  { 
    /* TI2 Configuration */
    TI2_Config(TIMx, TIM_ICInitStruct->TIM_ICPolarity, TIM_ICInitStruct->TIM_ICSelection,
               TIM_ICInitStruct->TIM_ICFilter);
    /* Set the Input Capture Prescaler value */
    TIM_SetIC2Prescaler(TIMx, TIM_ICInitStruct->TIM_ICPrescaler);
    /* TI1 Configuration */
    TI1_Config(TIMx, icoppositepolarity, icoppositeselection, TIM_ICInitStruct->TIM_ICFilter);
    /* Set the Input Capture Prescaler value */
    TIM_SetIC1Prescaler(TIMx, TIM_ICInitStruct->TIM_ICPrescaler);
  }
}

四、高级定时器

1. TIM1和TIM8简介

        高级控制定时器(TIM1和TIM8)由一个16位的自动装载计数器组成它由一个可编程的预分频器驱动。 它支持针对定位的增量(正交)编码器和霍尔传感器电路它适合多种用途包含测量输入信号的脉冲宽度(输入捕获)或者产生输出波形(输出比较、PWM、嵌入死区时间的互补PWM死区时间可编程的互补输出等)。 使用定时器预分频器和RCC时钟控制预分频器可以实现脉冲宽度和波形周期从几个微秒到几个毫秒的调节。

        和高级定时器最大的两处区别

2. 重复计数器

         关于高级定时器 重复计数值寄存器的使用介绍 - ZaiLi - 博客园 (cnblogs.com)

 3. 互补输出和死区插入

          驱动电机的时候常用防止mos管短暂的直接导通。

        高级控制定时器(TIM1和TIM8)能够输出两路互补信号并且能够管理输出的瞬时关断和接通。 这段时间通常被称为死区用户应该根据连接的输出器件和它们的特性(电平转换的延时、电源开关的延时等)来调整死区时间。

        如果OCx和OCxN为高有效

        ● OCx输出信号与参考信号相同只是它的上升沿相对于参考信号的上升沿有一个延迟。

        ● OCxN输出信号与参考信号相反只是它的上升沿相对于参考信号的下降沿有一个延迟。         如果延迟大于当前有效的输出宽度(OCx或者OCxN)则不会产生相应的脉冲。 下列几张图显示了死区发生器的输出信号和当前参考信号OCxREF之间的关系。

           逻辑门图解—与门、或门、非门、与非门、或非门、异或门、同或门_小小本科生debug的博客-CSDN博客

         当写入0时与门有0则0全1为1

位操作

【精选】【STM32】位操作、按位与、按位或、按位异或、取反、左移、右移等基础 C 语言知识补充_按位与操作是什么意思-CSDN博客

运算符含义
&按位与
按位或
^按位异或
~取反
<<左移
>>右移

        总结对于原二进制数来说&0是屏蔽&1是不变。0&x = 0,1&x = x;0与任何数等于01与任何数等于本身不改变。进行清0操作

        举个例子usart.c里面的

void USART_SendData(USART_TypeDef* USARTx, uint16_t Data)
{
  /* Check the parameters */
  assert_param(IS_USART_ALL_PERIPH(USARTx));
  assert_param(IS_USART_DATA(Data)); 
    
  /* Transmit Data */
  USARTx->DR = (Data & (uint16_t)0x01FF);
}

        对于usart_DR寄存器0-8位有效 USARTx->DR = (Data & (uint16_t)0x01FF);可以将无关的高位清0。剩下的都是1 1111 1111与上任何数都等于任何数本身。

        总结对于原二进制数来说|0是不变|1是置1。0|x = x,1|x = 1;0或任何数等于本身1或任何数等于1。

总结对于原二进制数来说^0是不变^1是反转。即0 ^ 0 = 0 ,0 ^ 1 = 1,1 ^ 0 = 1 ,1 ^ 1 = 0 ,
按位异或的3个特点1. 0 ^ 0 = 0 , 0 ^ 1 = 1, 0异或任何数=任何数。2.1 ^ 0 = 1 , 1 ^ 1 = 0 , 1异或任何数=任何数取反。

       而对于取反需要注意对一个二进制数进行取反。1变00变1。

        唯一需要注意的一点是~的优先级是逻辑运算符中最高的必须优先计算。

        不改变其他位时对某几个位设定值。比如要改变 GPIOA 的状态可以先对寄存器的值进行 & 清零操作GPIOA -> CRL &= 0XFFFFFF0F;  将第 4-7 位清 0然后再与需要设置的值进行 或运算GPIOA -> CRL |= 0X00000040;设置相应位的值且不改变其他位的值。


        左移与右移    

        对于无符号数左移时右侧补0相当于逻辑移位
        对于无符号数右移时左侧补0相当于逻辑移位

        左移规则左移运算是将一个二进制位的操作数按指定移动的位数向左移动移出位被丢弃右边移出的空位一律补0。

        简单说就是左边丢弃右边补0

        https://blog.csdn.net/m0_64280701/article/details/123064976

        右移运算是将一个二进制位的操作数按指定移动的位数向右移动移出位被丢弃左边移出的空位一律补0或者补符号位这由不同的机器而定。在使用补码作为机器数的机器中正数的符号位为 0 负数的符号位为 1 。简单说就是分为 2 种1. 逻辑右移左边用0填充右边丢弃。2. 算术右移左边用原该值的符号位填充右边丢弃。到底是逻辑右移还是算术右移取决于编译器我当前使用的编译器它采用的是算术右移。

   单片机的位操作知识_单片机位操作-CSDN博客   

        位运算实战演练1
        回顾要置1用|用清零用&要取反用^~和<< >>用来构建特定二进制数。
        1.给定一个整型数a设置a的bit3保证其他位不变。
                a = a | (1<<3)或者 a |= (1<<3)

        2.给定一个整形数a设置a的bit3~bit7保持其他位不变。
                a = a | (0b11111<<3)或者 a |= (0x1f<<3);

        3.给定一个整型数a清除a的bit15保证其他位不变。
                a = a & (~(1<<15));或者 a &= (~(1<<15));

        4.给定一个整形数a清除a的bit15~bit23保持其他位不变。
                a = a & (~(0x1ff<<15));或者 a &= (~(0x1ff<<15));

        5.给定一个整形数a取出a的bit3~bit8。
        思路
                第一步先将这个数bit3bit8不变其余位全部清零。
                第二步再将其右移3位得到结果。
                第三步想明白了上面的2步算法再将其转为C语言实现即可。
                a &= (0x3f<<3);
                a >>= 3;

        6.用C语言给一个寄存器的bit7bit17赋值937其余位不受影响。
        关键点第一不能影响其他位第二你并不知道原来bit7bit17中装的值。
思路

        第一步先将bit7bit17全部清零当然不能影响其他位。

        第二步再将937写入bit7bit17即可当然不能影响其他位。

        a &= ~(0x7ff<<7);

        a |= (937<<7);

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6