七、TIM定时器

五、TIM定时器

TIM简介

  • TIM(Timer)定时器
  • 定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断
  • 16位计数器、预分频器、自动重装寄存器组成时基单元,在72MHz计数时钟下可以实现最大59.65s的定时
  • 不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能
  • 根据复杂度和应用场景分为了高级定时器、通用定时器、基本定时器三种类型

定时器类型

类型 编号 总线 功能
高级定时器 TIM1、TIM8 APB2 拥有通用定时器全部功能,并额外具有重复计数器、死区生成、互补输出、刹车输入等功能
通用定时器 TIM2、TIM3、TIM4、TIM5 APB1 拥有基本定时器全部功能,并额外具有内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等功能
基本定时器 TIM6、TIM7 APB1 拥有定时中断、主模式触发DAC功能
  • STM32F103C8T6的定时器资源:TIM1、TIM2、TIM3、TIM4

基本定时器

image

  • 基本定时器只能使用内部时钟,所以通向时基单元的计数基准频率就是72MHz

  • 预分频器:对72MHz的计数时钟进行预分频

    该寄存器是16位的,可写的范围为0~65535

    该寄存器如果写0,那就是不分频,或者说叫1分频,输出频率=输入频率=72MHz

    如果写1,就是2分频,输出频率=输入频率/2=36MHz

    写2就是3分频,输出频率=输入频率/3=24MHz

    。。。以此类推,直到65535,就是65536分频

  • 计数器:对预分频后的计数时钟进行计数

    16位寄存器,计数时钟每来一个上升沿计数器就加一

    里面的值向上计数从0加到65535,如果超过65535则重新归0

  • 自动重装载寄存器:存放的是我们写入的计数目标

    16位寄存器,里面存放的是要计数的目标

    当计数器里面的值等于该寄存器里面的值,就会产生更新中断和更新事件,然后计数器清零开始下一次记数

  • 主模式触发DAC:把更新事件通过主模式映射到TRGO,然后TRGO就会直接去触发DAC

    定时器触发更新事件后,把他映射到触发输出TRGO的位置,TRGO直接接到DAC的触发转换引脚上

通用定时器

image

  • 计数器模式:向上计数模式、向下计数模式、中央对齐模式

    通用和高级定时器支持这三种模式,基础定时器只支持向上计数

    • 向上计数模式:从0开始自增,直到等于重装寄存器的值触发中断,如此循环往复

    • 向上计数模式:从等于重装寄存器的值开始自减,直到等于0触发中断,如此循环往复

    • 中央对齐模式:从0开始自增,直到等于重装寄存器的值触发中断,再开始自减直到0触发中断,如此循环往复

image

时钟源选择:

  • 内部时钟(CK_INT):72MHz

  • 外部时钟模式2:

    TIMx_ETR引脚(PA0),输入方波信号,经过输入滤波、ETRF、触发控制器,就可以作为时基单元的时钟了

  • 外部时钟模式1:

    1. 从ETR引脚输入方波信号,经过输入滤波、RTGI、从模式控制器,就可以作为时基单元的时钟了
    2. IRT信号,这一部分时钟信号来自其他定时器。其他定时器从TRGO输出时钟信号,发送到IRT引脚
    3. CH1引脚的边沿(上升沿和下降沿)
    4. CH1引脚
    5. CH2引脚

常用的时钟源为:内部时钟和ETR

输入捕获和输出比较

暂时了解

image

高级定时器

image

image

  • 重复次数计数器:之前的通用和基础定时器最大计数时间是59秒多,就要触发一次中断或事件,现在高级定时器多了一个16位的重复计数器,就可以实现几个周期再更新一次中断或事件,最大可以等65535个周期了再触发,也就是59秒多乘65535
  • 刹车输入:如果外部引脚BKIN产生了刹车信号,或者内部时钟失效,产生了故障,控制电路就会自动切断电机的输入,防止意外的发生
  • 死区生成电路:防止互补输出的PWM驱动桥臂时,由于器件不理想,在开关切换的瞬间造成短暂的直通现象,所以在前面加上死区生成电路,在开关切换的瞬间产生一定时长的死区,让桥臂的上下管全部关断,防止直通现象

定时中断基本结构

image

时序图

预分频器时序

当预分频器的参数从1变到2时,计数器的时序图

image

缓冲寄存器

如果我们在一个计数器计数到一半时改变了分频系数,就会导致这个周期的前后部分的频率不一样。

虽然这样做一般不会有什么太大影响,但是STM32的定时器比较严谨,设计了缓冲寄存器,这样当我们在计数寄存器记到一半的时候改变分频系数,这个变化不会立刻生效,而是会等到本次计数周期结束后才改变

预分频寄存器中的存的值与实际分频系数有一个数的偏移

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

CK_CNT:计数器计数频率

CK_PSC:预分频前的时钟频率

PSC:预分频寄存器的值

计数器时序

CK_PSC:预分频前的时钟频率

CNT_EN:使能标志位

CK_CNT:预分频后的时钟频率

分频因子就是分频系数

image

更新中断标志位置1后需要手动置0

计数器溢出频率:CK_CNT_OV = CK_CNT / (ARR + 1)= CK_PSC / (PSC + 1) / (ARR + 1)

CK_CNT_OV:计数器溢出频率

CK_CNT:计数器计数频率

ARR:重装载寄存器的值

CK_PSC:预分频前的时钟频率

PSC:预分频寄存器的值

计数器缓冲寄存器

为了防止计数中途更改数值造成错误,设计了计数器缓冲寄存器,和预分频缓冲寄存器是相同的功能

可以通过ARPE位,来选择是否启用计数器缓冲寄存器

计数器不启用缓冲寄存器

image

计数器启用缓冲寄存器

image

RCC时钟树

image

从SYSCLK分开,左边是时钟产生电路,右边是时钟分配电路

  • 共有四个震荡源:

    • 8MHz HSI RC:内部的8MHz高速震荡器

    • 4-16MHz HSE OSC:外部的4-16MHz高速石英晶体震荡器。也就是晶振,一般接8MHz的

    • LSC OSC 32.768KHz:外部的32.768KHz低速晶振。一般给RTC提供时钟

    • LSI RC 40KHz:内部的40KHz低速RC振荡器。给看门狗提供时钟

    上面的两个高速晶振是提供系统时钟的,AHB、APB2、APB1的时钟来自这里。外部石英晶振的稳定性要比内部的RC振荡器要好一点

ST配置系统时钟的流程

先选择内部8MHz的振荡器为系统时钟,暂时使用8MHz的时钟运行,再启用外部时钟。

外部时钟经过PLL XTPRE、PLL SRC,进入PLL MUL锁相环进行倍频,8MHz倍频9倍得到72MHz,等锁相环输出稳定后,选择锁相环输出为系统时钟

TIM相关库函数

用于初始化的结构体

时基单元

TIM_TimeBaseInitTypeDef结构体

typedef struct
{
  uint16_t TIM_Prescaler;         /*!< 预分频寄存器的值,注意:不是预分频系数 */
  uint16_t TIM_CounterMode;       /*!< 计数模式 */
  uint16_t TIM_Period;            /*!< 重装载寄存器的值  */ 
  uint16_t TIM_ClockDivision;     /*!< 时钟分频 */
  uint8_t TIM_RepetitionCounter;  /*!< 重复计数器,高级定时器才有该寄存器。用不到的情况直接给0即可 */
} TIM_TimeBaseInitTypeDef;

// TIM_Prescaler可写的值
0~65535

// TIM_CounterMode可写的值
TIM_CounterMode_Up				// 向上计数
TIM_CounterMode_Down			// 向下计数
TIM_CounterMode_CenterAligned1	// 中央对齐模式1
TIM_CounterMode_CenterAligned2	// 中央对齐模式2
TIM_CounterMode_CenterAligned3	// 中央对齐模式3
    
// TIM_Period可写的值
0~65535
    
// TIM_ClockDivision可写的值
/*
对于外部输入引脚,一般都会连接一个滤波器,可以滤掉信号的抖动干扰

工作方式:在一个固定的时钟频率f下进行采样,如果连续的N个采样点都为相同的电平,那就代表输入的信号稳定了,就把采样值输出出去;如果这N个采样点不完全相同,那就说明信号有抖动,此时就保持上一次的输出,或者直接输出低电平

这里的采样频率f和采样点数N都是滤波器的参数,频率越低,采样点数越多,那滤波效果就越好,响应的延迟就更大

采样频率f可以是由内部时钟直接而来,也可以是内部时钟分频后而来。而分频多少就是由TIM_ClockDivision参数决定的。这个参数跟时基单元的初始化没有太大关系,没有特殊要求随便给给就行了
*/
TIM_CKD_DIV1	// 1分频,也就是不分频
TIM_CKD_DIV2	// 2分频
TIM_CKD_DIV4	// 4分频

// TIM_RepetitionCounter可写的值
取值范围为0x00 ~ 0xFF之间的数字。
@说明:该参数仅对TIM1和TIM8有效。也就是高级定时器

输出比较

TIM_OCInitTypeDef结构体

typedef struct
{
  uint16_t TIM_OCMode;        /*!< 选择TIM输出比较的模式 */
  uint16_t TIM_OutputState;   /*!< 是否使能TIM输出比较 */
  uint16_t TIM_OutputNState;  /*!< 是否使能TIM互补输出比较,高级定时器有效 */
  uint16_t TIM_Pulse;         /*!< 指定CCR的值 */
  uint16_t TIM_OCPolarity;    /*!< 指定输出极性 */
  uint16_t TIM_OCNPolarity;   /*!< 指定互补输出极性,高级定时器有效 */
  uint16_t TIM_OCIdleState;   /*!< 指定空闲状态时的TIM输出比较引脚状态 */
  uint16_t TIM_OCNIdleState;  /*!< 指定空闲状态时的TIM互补输出比较引脚状态,高级定时器有效 */
} TIM_OCInitTypeDef;

// TIM_OCMode可写的值
TIM_OCMode_Timing		// 冻结模式
TIM_OCMode_Active		// 相等时置有效电平
TIM_OCMode_Inactive		// 相等时置无效电平
TIM_OCMode_Toggle		// 相等时电平翻转
TIM_OCMode_PWM1			// PWM模式1
TIM_OCMode_PWM2			// PWM模式2
    
// TIM_OutputState可写的值
TIM_OutputState_Disable		// TIM输出比较失能
TIM_OutputState_Enable		// TIM输出比较使能
    
// TIM_OutputNState可写的值
TIM_OutputNState_Disable	// TIM互补输出比较失能
TIM_OutputNState_Enable		// TIM互补输出比较使能
    
// TIM_Pulse可写的值
0~65535
    
// TIM_OCPolarity可写的值
TIM_OCPolarity_High			// 输出的电平不取反
TIM_OCPolarity_Low			// 输出的电平取反
    
// TIM_OCNPolarity可写的值
TIM_OCNPolarity_High		// 输出的电平不取反
TIM_OCNPolarity_Low			// 输出的电平取反
    
// TIM_OCIdleState可写的值
TIM_OCIdleState_Set			// 置高电平
TIM_OCIdleState_Reset		// 置低电平
    
// TIM_OCNIdleState可写的值
TIM_OCNIdleState_Set		// 置高电平
TIM_OCNIdleState_Reset		// 置低电平

输入捕获

TIM_ICInitTypeDef结构体

typedef struct
{

  uint16_t TIM_Channel;      /*!< 指定输入捕获的通道 */
  uint16_t TIM_ICPolarity;   /*!< 指定触发输入捕获事件的信号边沿。 */
  uint16_t TIM_ICSelection;  /*!< 指定输入信号的线路 */
  uint16_t TIM_ICPrescaler;  /*!< 对输入捕获的信号分频 */
  uint16_t TIM_ICFilter;     /*!< 滤波器采样频率 */
} TIM_ICInitTypeDef;

// TIM_Channel可写的值
TIM_Channel_1	// 通道1(IC1)
TIM_Channel_2	// 通道2(IC2)
TIM_Channel_3	// 通道3(IC3)
TIM_Channel_4	// 通道4(IC4)

// TIM_ICPolarity可写的值
TIM_ICPolarity_Rising		// 上升沿
TIM_ICPolarity_Falling		// 下降沿
TIM_ICPolarity_BothEdge		// 上升沿和下降沿

// TIM_ICSelection可写的值
// 选择输入为TI1、TI2、TI3、TI4,分别连接通道IC1、IC2、IC3、IC4
TIM_ICSelection_DirectTI		// 选择直连输入
// 选择输入为TI1、TI2、TI3、TI4,分别连接通道IC2、IC1、IC4、IC3
TIM_ICSelection_IndirectTI		// 选择交叉输入             
TIM_ICSelection_TRC				// TRC,来自其他定时器的信号

// TIM_ICPrescaler可写的值
TIM_ICPSC_DIV1		// 输入捕获的信号不分频
TIM_ICPSC_DIV2		// 输入捕获的信号预分频系数2
TIM_ICPSC_DIV4		// 输入捕获的信号预分频系数4
TIM_ICPSC_DIV8		// 输入捕获的信号预分频系数8

// TIM_ICFilter可写的值
0x00~0x0f

函数

// 将TIMx外设寄存器初始化为其默认重置值。
void TIM_DeInit(TIM_TypeDef* TIMx);

// 时基单元初始化
void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);

// 使用结构体初始化OC1通道(OC:输出比较)
void TIM_OC1Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
// 使用结构体初始化OC2通道(OC:输出比较)
void TIM_OC2Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
// 使用结构体初始化OC3通道(OC:输出比较)
void TIM_OC3Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
// 使用结构体初始化OC4通道(OC:输出比较)
void TIM_OC4Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);

// 使用结构体初始化一个输入捕获通道
void TIM_ICInit(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct);

// 使用结构体初始化两个可以交叉输入的输入捕获通道,用于PWMI模式
void TIM_PWMIConfig(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct);

void TIM_BDTRConfig(TIM_TypeDef* TIMx, TIM_BDTRInitTypeDef *TIM_BDTRInitStruct);

// 用默认值填充每个TIM_TimeBaseInitTypeDef结构体的成员。时基单元
void TIM_TimeBaseStructInit(TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);

// 用默认值填充每个TIM_OCInitTypeDef结构体的成员。输出比较
void TIM_OCStructInit(TIM_OCInitTypeDef* TIM_OCInitStruct);

// 用默认值填充每个TIM_ICInitTypeDef结构体的成员。输入捕获
void TIM_ICStructInit(TIM_ICInitTypeDef* TIM_ICInitStruct);

// 用默认值填充每个TIM_BDTRInitTypeDef结构体的成员。
void TIM_BDTRStructInit(TIM_BDTRInitTypeDef* TIM_BDTRInitStruct);

// 使能计数器
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);

// 高级定时器输出PWM时调用,使能主输出,否则PWM将不能正常输出
void TIM_CtrlPWMOutputs(TIM_TypeDef* TIMx, FunctionalState NewState);

// 使能中断输出信号
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);

void TIM_GenerateEvent(TIM_TypeDef* TIMx, uint16_t TIM_EventSource);

void TIM_DMAConfig(TIM_TypeDef* TIMx, uint16_t TIM_DMABase, uint16_t TIM_DMABurstLength);

void TIM_DMACmd(TIM_TypeDef* TIMx, uint16_t TIM_DMASource, FunctionalState NewState);

// 选择内部时钟
void TIM_InternalClockConfig(TIM_TypeDef* TIMx);

// 选择ITR其他定时器的时钟
void TIM_ITRxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);

// 选择输入捕获的信号为外部时钟
void TIM_TIxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_TIxExternalCLKSource,
                                uint16_t TIM_ICPolarity, uint16_t ICFilter);

// 选择ETR通过外部时钟模式1输入的时钟
void TIM_ETRClockMode1Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,
                             uint16_t ExtTRGFilter);

// 选择ETR通过外部时钟模式2输入的时钟
void TIM_ETRClockMode2Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, 
                             uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);

// 单独用来配置ETR引脚的预分频器、极性、滤波器
void TIM_ETRConfig(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,
                   uint16_t ExtTRGFilter);

// 单独配置预分频值
void TIM_PrescalerConfig(TIM_TypeDef* TIMx, uint16_t Prescaler, uint16_t TIM_PSCReloadMode);

// 改变计数器的计数模式
void TIM_CounterModeConfig(TIM_TypeDef* TIMx, uint16_t TIM_CounterMode);

// 选择从模式的触发源
void TIM_SelectInputTrigger(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);

// 配置编码器接口
void TIM_EncoderInterfaceConfig(TIM_TypeDef* TIMx, uint16_t TIM_EncoderMode,
                                uint16_t TIM_IC1Polarity, uint16_t TIM_IC2Polarity);

// 输出比较通道1强制输出高电平或低电平
void TIM_ForcedOC1Config(TIM_TypeDef* TIMx, uint16_t TIM_ForcedAction);
// 输出比较通道2强制输出高电平或低电平
void TIM_ForcedOC2Config(TIM_TypeDef* TIMx, uint16_t TIM_ForcedAction);
// 输出比较通道3强制输出高电平或低电平
void TIM_ForcedOC3Config(TIM_TypeDef* TIMx, uint16_t TIM_ForcedAction);
// 输出比较通道4强制输出高电平或低电平
void TIM_ForcedOC4Config(TIM_TypeDef* TIMx, uint16_t TIM_ForcedAction);

// 自动重装载寄存器是否启用缓冲寄存器(自动重装载寄存器预装)
void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);

void TIM_SelectCOM(TIM_TypeDef* TIMx, FunctionalState NewState);

void TIM_SelectCCDMA(TIM_TypeDef* TIMx, FunctionalState NewState);

void TIM_CCPreloadControl(TIM_TypeDef* TIMx, FunctionalState NewState);

// 启用或禁用CCR1上的缓冲寄存器。(CCR:输入捕获/输出比较寄存器)
void TIM_OC1PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
// 启用或禁用CCR2上的缓冲寄存器。(CCR:输入捕获/输出比较寄存器)
void TIM_OC2PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
// 启用或禁用CCR3上的缓冲寄存器。(CCR:输入捕获/输出比较寄存器)
void TIM_OC3PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
// 启用或禁用CCR4上的缓冲寄存器。(CCR:输入捕获/输出比较寄存器)
void TIM_OC4PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);

// 配置OC1快速使能。(OC:输出比较)
void TIM_OC1FastConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCFast);
// 配置OC2快速使能。(OC:输出比较)
void TIM_OC2FastConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCFast);
// 配置OC3快速使能。(OC:输出比较)
void TIM_OC3FastConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCFast);
// 配置OC4快速使能。(OC:输出比较)
void TIM_OC4FastConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCFast);

// OC1清除REF信号
void TIM_ClearOC1Ref(TIM_TypeDef* TIMx, uint16_t TIM_OCClear);
// OC2清除REF信号
void TIM_ClearOC2Ref(TIM_TypeDef* TIMx, uint16_t TIM_OCClear);
// OC3清除REF信号
void TIM_ClearOC3Ref(TIM_TypeDef* TIMx, uint16_t TIM_OCClear);
// OC4清除REF信号
void TIM_ClearOC4Ref(TIM_TypeDef* TIMx, uint16_t TIM_OCClear);

// 单独配置OC1的极性
void TIM_OC1PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
// 单独配置OC1N的极性,高级定时器才有,OC1与OC1N是互补输出
void TIM_OC1NPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCNPolarity);
// 单独配置OC2的极性
void TIM_OC2PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
// 单独配置OC2N的极性,高级定时器才有,OC2与OC2N是互补输出
void TIM_OC2NPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCNPolarity);
// 单独配置OC3的极性
void TIM_OC3PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
// 单独配置OC3N的极性,高级定时器才有,OC3与OC3N是互补输出
void TIM_OC3NPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCNPolarity);
// 单独配置OC4的极性
void TIM_OC4PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);

// 单独配置OC的输出使能
void TIM_CCxCmd(TIM_TypeDef* TIMx, uint16_t TIM_Channel, uint16_t TIM_CCx);
// 单独配置OCN的输出使能
void TIM_CCxNCmd(TIM_TypeDef* TIMx, uint16_t TIM_Channel, uint16_t TIM_CCxN);

// 单独配置输出比较的模式
void TIM_SelectOCxM(TIM_TypeDef* TIMx, uint16_t TIM_Channel, uint16_t TIM_OCMode);

void TIM_UpdateDisableConfig(TIM_TypeDef* TIMx, FunctionalState NewState);

void TIM_UpdateRequestConfig(TIM_TypeDef* TIMx, uint16_t TIM_UpdateSource);

void TIM_SelectHallSensor(TIM_TypeDef* TIMx, FunctionalState NewState);

void TIM_SelectOnePulseMode(TIM_TypeDef* TIMx, uint16_t TIM_OPMode);

// 选择主模式的输出触发源。TRGO
void TIM_SelectOutputTrigger(TIM_TypeDef* TIMx, uint16_t TIM_TRGOSource);

// 选择从模式
void TIM_SelectSlaveMode(TIM_TypeDef* TIMx, uint16_t TIM_SlaveMode);

void TIM_SelectMasterSlaveMode(TIM_TypeDef* TIMx, uint16_t TIM_MasterSlaveMode);

// 手动的给计数器一个值
void TIM_SetCounter(TIM_TypeDef* TIMx, uint16_t Counter);

// 手动的给自动重装载寄存器一个值
void TIM_SetAutoreload(TIM_TypeDef* TIMx, uint16_t Autoreload);

// 单独更改CCR1的值(CCR:输出比较/输入捕获寄存器)
void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare1);
// 单独更改CCR2的值(CCR:输出比较/输入捕获寄存器)
void TIM_SetCompare2(TIM_TypeDef* TIMx, uint16_t Compare2);
// 单独更改CCR3的值(CCR:输出比较/输入捕获寄存器)
void TIM_SetCompare3(TIM_TypeDef* TIMx, uint16_t Compare3);
// 单独更改CCR4的值(CCR:输出比较/输入捕获寄存器)
void TIM_SetCompare4(TIM_TypeDef* TIMx, uint16_t Compare4);

// 单独配置输入捕获通道1的分频器
void TIM_SetIC1Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC);
// 单独配置输入捕获通道2的分频器
void TIM_SetIC2Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC);
// 单独配置输入捕获通道3的分频器
void TIM_SetIC3Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC);
// 单独配置输入捕获通道4的分频器
void TIM_SetIC4Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC);

void TIM_SetClockDivision(TIM_TypeDef* TIMx, uint16_t TIM_CKD);

// 读取通道1的CCR1(输入捕获/输出比较寄存器)的值
uint16_t TIM_GetCapture1(TIM_TypeDef* TIMx);
// 读取通道2的CCR2(输入捕获/输出比较寄存器)的值
uint16_t TIM_GetCapture2(TIM_TypeDef* TIMx);
// 读取通道3的CCR3(输入捕获/输出比较寄存器)的值
uint16_t TIM_GetCapture3(TIM_TypeDef* TIMx);
// 读取通道4的CCR4(输入捕获/输出比较寄存器)的值
uint16_t TIM_GetCapture4(TIM_TypeDef* TIMx);

// 获取当前计数器的值
uint16_t TIM_GetCounter(TIM_TypeDef* TIMx);

// 获取当前预分频器的值
uint16_t TIM_GetPrescaler(TIM_TypeDef* TIMx);

// 检查指定的TIM是否产生了中断标志位。返回值为:SET或RESET。在中断函数外的地方使用
FlagStatus TIM_GetFlagStatus(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);

// 将指定的TIM中断标志位清除。在中断函数外使用
void TIM_ClearFlag(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);

// 检查指定的TIM是否产生了中断标志位。返回值为:SET或RESET。在中断函数内使用
ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT);

// 将指定的TIM中断标志位清除。在中断函数内使用
void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT);

滤波器采样

对于外部输入引脚,一般都会连接一个滤波器,可以滤掉信号的抖动干扰

工作方式:在一个固定的时钟频率f下进行采样,如果连续的N个采样点都为相同的电平,那就代表输入的信号稳定了,就把采样值输出出去;如果这N个采样点不完全相同,那就说明信号有抖动,此时就保持上一次的输出,或者直接输出低电平

这里的采样频率f和采样点数N都是滤波器的参数,频率越低,采样点数越多,那滤波效果就越好,响应的延迟就更大

采样频率f可以是由内部时钟直接而来,也可以是内部时钟分频后而来。

image

/*
当使用外部时钟进行中断时,可以配置ExtTRGFilter参数来选择采样频率,取值范围为:0x00~0x0F,对应上图
*/
// 选择ETR通过外部时钟模式1输入的时钟
void TIM_ETRClockMode1Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,
                             uint16_t ExtTRGFilter);

// 选择ETR通过外部时钟模式2输入的时钟
void TIM_ETRClockMode2Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, 
                             uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);

小节1的案例

定时中断计数

使用到的库函数

// 时基单元初始化
void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);

// 使能计数器
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);

// 使能中断输出信号
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);

// 选择内部时钟
void TIM_InternalClockConfig(TIM_TypeDef* TIMx);

// 将指定的TIM中断标志位清除。在中断函数外使用
void TIM_ClearFlag(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);

// 检查指定的TIM是否产生了中断标志位。返回值为:SET或RESET。在中断函数内使用
ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT);

// 将指定的TIM中断标志位清除。在中断函数内使用
void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT);

在Systems组中添加Timer_TIM2的c文件和h文件

image

Timer_TIM2.c

#include "stm32f10x.h"                  // Device header

/*
@作用 初始化TIM2,并设置预分频值和自动重装寄存器的值
@参数 Prescaler:设置预分频值
@参数 Perios:设置自动重装寄存器的值
@返回值 无
*/
void Timer_TIM2_Init(uint16_t Prescaler, uint16_t Period)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);			// 开启TIM2的时钟
	TIM_InternalClockConfig(TIM2);									// TIM2定时器使用内部时钟
	
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;					// 创建一个TIM_TimeBaseInitTypeDef类型的结构体
	TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;		// 时钟分频1,不分频。该分频用于滤波器
	TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;	// 计数器向上计数
	TIM_TimeBaseInitStruct.TIM_Period = Period;						// 设置重装寄存器的值
	TIM_TimeBaseInitStruct.TIM_Prescaler = Prescaler;				// 设置预分频寄存器的值
	TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;				// 设置重复计数器的值,高级定时器才有效,其他写0即可
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStruct);				// 根据TIM_TimeBaseInitTypeDef结构体中的属性初始TIM2的时基单元
	
    /*
    当调用TIM_TimeBaseInit时会写入预分频值,而预分频寄存器有一个缓冲寄存器,只有在更新事件时我们写入的值才会生效
    所以调用TIM_TimeBaseInit函数时内部会触发一次更新事件,而更新事件和更新中断是一起触发的,因此在每次复位或启动前都会触发一次更新中断
    所以要再调用TIM_TimeBaseInit函数后立马清除一下更新中断标志位,不要产生中断
    */
	TIM_ClearFlag(TIM2, TIM_FLAG_Update);
	
	TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);						// 中断输出信号使能,选择更新中断
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);					// 选择优先级分组2
	
	NVIC_InitTypeDef NVIC_InitStruct;
	NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn;					// 选择NVIC通道
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;					// 允许中断
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;			// 抢占优先级2
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;					// 响应优先级1
	NVIC_Init(&NVIC_InitStruct);									// 根据NVIC_InitTypeDef结构体的属性初始化NVIC
	
	TIM_Cmd(TIM2, ENABLE);											// TIM2定时器的计数器开始工作
}

/*
// TIM2的中断函数模板
void TIM2_IRQHandler()
{
	// 判断TIM2的更新中断的中断标志位是否置1
	if(TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
	{
		
		// 清除TIM2更新中断的中断标志位
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
	}
}
*/

Timer_TIM2.h

#ifndef __Timer_TIM2_H
#define __Timer_TIM2_H

// 初始化TIM2,并设置预分频值和自动重装寄存器的值
void Timer_TIM2_Init(uint16_t Prescaler, uint16_t Period);

#endif

接线图

image

main.c

#include "stm32f10x.h"                  // Device header
#include "OLED.h"
#include "Timer_TIM2.h"

uint16_t count;

int main()
{
    // 预分频系数为7200,自动重载的值为10000,定时1s
	/*计算公式如下:
	定时器时钟频率:72MHz / 7200 = 10KHz = 0.1ms
	定时器溢出时间:10000 * 0.1ms = 1s
	*/
	Timer_TIM2_Init(7200-1, 10000-1);
	OLED_Init();
	OLED_ShowString(1,1,"Count:");
	while(1)
	{
		OLED_ShowNum(1,7,count,5);
	}
}
void TIM2_IRQHandler()
{
	// 判断TIM2的更新中断的中断标志位是否置1
	if(TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
	{
		count++;
		// 清除TIM2更新中断的中断标志位
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
	}
}

定时器外部时钟

使用到的库函数

// 选择ETR通过外部时钟模式2输入的时钟
void TIM_ETRClockMode2Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, 
                             uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);

// 获取当前计数器的值
uint16_t TIM_GetCounter(TIM_TypeDef* TIMx);

// 获取当前预分频器的值
uint16_t TIM_GetPrescaler(TIM_TypeDef* TIMx);

接线图

image

#include "stm32f10x.h"                  // Device header
#include "OLED.h"

uint16_t num;

int main()
{
	// 开启TIM2和GPIOA的时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	/* 配置GPIOA外设 */
	// 初始化PA0引脚
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;		// 参考文档的建议是使用浮空输入,但是用上拉或下拉输入也可以
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;			// 引脚0
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;	// 速度50MHz
	GPIO_Init(GPIOA, &GPIO_InitStruct);
	
	/* 配置NVIC外设 */
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);				// 优先级分组2
	// 初始化NVIC
	NVIC_InitTypeDef NVIC_InitStruct;
	NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn;				// 选择TIM2的中断通道
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;				// 开启中断通道
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;		// 抢占优先级
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2;				// 响应优先级
	NVIC_Init(&NVIC_InitStruct);
	
	/* 配置TIM2外设 */
	// 选择ETR外部时钟模式2
	// 参数1:选择定时器。参数2:是否对外部时钟分频。参数3:上升沿/高电平还是下降沿/低电平触发。参数4:滤波频率
	TIM_ETRClockMode2Config(TIM2, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_Inverted, 0x0f);
	// 初始化TIM2
	TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct;
	TIM_TimeBaseStruct.TIM_ClockDivision = TIM_CKD_DIV1;		// 对滤波时钟的分频系数
	TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up;	// 向上计数模式
	TIM_TimeBaseStruct.TIM_Period = 10 - 1;						// 自动重装寄存器的值
	TIM_TimeBaseStruct.TIM_Prescaler = 1 - 1;					// 预分频寄存器的值
	TIM_TimeBaseStruct.TIM_RepetitionCounter = 0;				// 重复计数器的值
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStruct);				// 根据TIM_TimeBaseStruct结构体的参数初始化TIM2
	/*
    当调用TIM_TimeBaseInit时会写入预分频值,而预分频寄存器有一个缓冲寄存器,只有在更新事件时我们写入的值才会生效
    所以调用TIM_TimeBaseInit函数时内部会触发一次更新事件,而更新事件和更新中断是一起触发的,因此在每次复位或启动前都会触发一次更新中断
    所以要再调用TIM_TimeBaseInit函数后立马清除一下更新中断标志位,不要产生中断
    */
	TIM_ClearFlag(TIM2, TIM_FLAG_Update);						// 清除中断标志位
	// 中断输出信号使能
	TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
	// TIM2计数器使能
	TIM_Cmd(TIM2, ENABLE);
	
	OLED_Init();
	OLED_ShowString(1,1,"Num:");
	OLED_ShowString(2,1,"Counter:");
	OLED_ShowString(3,1,"Prescaler:");
	while(1)
	{
		OLED_ShowNum(1,7, num, 5);
		OLED_ShowNum(2,9, TIM_GetCounter(TIM2), 5);		// 显示当前计数器的值
		OLED_ShowNum(3,11, TIM_GetPrescaler(TIM2), 5);	// 显示当前预分频的值
	}
}
void TIM2_IRQHandler()
{
	// 判断更新中断标志位是否置1
	if(TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
	{
		num++;
		// 清除更新中断标志位
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
	}
}

输出比较

输出比较简介

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

image

输出比较通道

通用定时器

image

将CNT(计数器)的值与CCR(输出比较/输入捕获寄存器)的值比较,当CNT大于或等于CCR时,输出模式控制器改变输出的OC1REF的电平

REF信号可以前往主模式控制器,映射到TRGO输出上去

REF的主要路线还是前往CC1P极性选择,当CC1P寄存器写0时,REF的电平不反转,该是什么就是什么。当CC1P寄存器写1时,REF的电平取反

最后通过输出使能电路,选择是否要输出该REF信号

高级定时器

image

OC1和OC1N是互补输出,OC1导通时OC1N就需要关闭,OC1N导通时OC1需要关闭。

由于器件不理想,可能在一方导通时,另一方没有来得及关闭,导致两方都导通从而断路

所以加上死区发生器,在导通的一方关闭后延迟一段时间,另一方再导通

输出比较模式

下面是输出模式控制器的比较逻辑,该模式控制器的输入是CNT和CCR的大小关系,输出是REF的高低电平

有效电平表示高电平,无效电平表示低电平

image

// TIM_OCMode可写的值
TIM_OCMode_Timing // 冻结模式
TIM_OCMode_Active // 相等时置有效电平
TIM_OCMode_Inactive // 相等时置无效电平
TIM_OCMode_Toggle // 相等时电平翻转
TIM_OCMode_PWM1 // PWM模式1
TIM_OCMode_PWM2 // PWM模式2

PWM

PWM简介

  • PWM代表脉宽调制(Pulse Width Modulation),是一种用于控制模拟电子设备的数字技术。通过调整信号的脉冲宽度,PWM可以模拟出模拟信号的效果,常见的应用包括电机控制、LED亮度调节、音频处理等。

  • PWM的基本原理是通过改变脉冲信号的占空比来控制输出信号的平均功率。脉冲信号由一个周期性的方波组成,每个周期包括一个高电平(表示“开”状态)和一个低电平(表示“关”状态)。通过调整高电平和低电平的持续时间比例,可以改变脉冲信号的占空比,从而控制输出信号的平均功率。

  • 在电机控制中,PWM可以用来调节电机的转速和转矩,通过改变电机驱动器的PWM信号的占空比,可以实现对电机的精确控制。在LED亮度调节中,PWM可以控制LED灯的亮度,通过改变PWM信号的占空比,可以实现LED的调光效果。在音频处理中,PWM可以用来模拟出不同频率的声音信号,通过调整PWM信号的频率和占空比,可以实现音频信号的数字合成和处理。

总的来说,PWM是一种非常灵活和高效的控制技术,广泛应用于各种电子设备和系统中。

PWM重要参数

周期 = TS 频率 = 1 / TS 占空比 = TON / TS 精度 = 占空比变化步距

  1. 周期:即高电平和低电平的持续时间之和。频率越高,脉冲信号的周期越短,控制精度和响应速度越高,但同时也会增加系统的噪声和功耗。
  2. 频率: 是指1秒钟内信号从高电平到低电平再回到高电平的次数(一个周期);也就是一秒钟PWM有多少个周期
  3. 占空比:PWM信号的占空比指的是高电平的持续时间占整个周期的比例。占空比越高,输出信号的平均功率越大,控制效果越强,但同时也会增加系统的热损耗和电磁干扰。
  4. 精度:PWM信号的精度指的是控制系统对PWM信号频率和占空比的控制精度。精度越高,系统对PWM信号的控制越精确,输出信号的稳定性和可靠性也越高。

image

PWM基本结构

image

PWM参数计算

PWM频率: Freq = CK_PSC / (PSC + 1) / (ARR + 1)
PWM占空比: Duty = CCR / (ARR + 1)
PWM分辨率: Reso = 1 / (ARR + 1)

CK_PSC:预分频前的时钟

PSC:预分频值

ARR:自动重装载的值

CCR:输入捕获/输出比较寄存器的值

舵机与直流电机

舵机介绍

舵机是一种根据输入PWM信号占空比来控制输出角度的装置

  • 输入PWM信号要求:周期为20ms,高电平宽度为0.5ms~2.5ms

image

舵机硬件电路

image

直流电机及驱动芯片

  • 直流电机是一种将电能转换为机械能的装置,有两个电极,当电极正接时,电机正转,当电极反接时,电机反转
  • 直流电机属于大功率器件,GPIO口无法直接驱动,需要配合电机驱动电路来操作
  • TB6612是一款双路H桥型的直流电机驱动芯片,可以驱动两个直流电机并且控制其转速和方向

image

TB6612硬件电路及使用

image

PWMA、AIN1、AIN2共同控制AO1和AO2的输出

PWMB、BIN1、BIN2共同控制BO1和BO2的输出

STBY:接VCC则允许电机工作,接GND则电机待机

VM:驱动电压输入,根据电机型号输入相应的电压

VCC:逻辑电压输入,根据单片机的电压输入相应的电压

image

H:高电平

L:低电平

IN1输入低电平,IN2输入高电平,电机反转

IN1输入高电平,IN2输入低电平,电机正转

PWM输入高电平电机旋转,输入低电平电机处于制动(停止)状态

STBY输入低电平,IN1、IN2、PWM不管输入什么电机都不转

小节2的案例

呼吸灯

使用到的函数

// 使用结构体初始化OC1通道(OC:输出比较)
void TIM_OC1Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);

// 用默认值填充每个TIM_OCInitTypeDef结构体的成员。
void TIM_OCStructInit(TIM_OCInitTypeDef* TIM_OCInitStruct);

// 单独更改CCR1的值(CCR:输出比较/输入捕获寄存器)
void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare1);

接线图

image

示例代码

PWM的频率为10KHz,根据PWM参数公式可知,可以将ARR设置为100-1,PSC设置为72-1

那么CRR的取值范围为0100,也就是刚好对应占空比的0%100%

#include "stm32f10x.h"                  // Device header
#include "Delay.h"


int main()
{
	// 开启GPIOA和TIM2的时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	/* 初始化PA0 */
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;	// 复用推挽输出。PWM是由片上外设TIM输出,控制权要交给TIM
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;			// 引脚0
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;	// 速度50MHz
	GPIO_Init(GPIOA, &GPIO_InitStruct);				// 初始化
	
	// 使用内部时钟源
	TIM_InternalClockConfig(TIM2);
	/* 初始化时基单元 */
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
	TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;		// 滤波时钟f不分频
	TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;	// 向上计数
	TIM_TimeBaseInitStruct.TIM_Period = 100 - 1;					// ARR自动重装的值
	TIM_TimeBaseInitStruct.TIM_Prescaler = 72 - 1;					// PSC预分频值
	TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;				// 重复计数器,高级定时器才有
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStruct);				// 初始化时基单元
	TIM_ClearFlag(TIM2, TIM_FLAG_Update);							// 清除更新中断标志
	// 开启计数器
	TIM_Cmd(TIM2, ENABLE);
	
	/* 初始化输出比较 */
	TIM_OCInitTypeDef TIM_OCInitStruct;
	/* 
	因为TIM_OCInitTypeDef结构体中的属性不需要全部配置,有些属性是高级定时器的
	所以先给TIM_OCInitTypeDef结构体的属性赋一个默认值,再修改需要配置的属性即可
	*/
	TIM_OCStructInit(&TIM_OCInitStruct);						// 给TIM_OCInitTypeDef结构体的每一个属性赋一个默认值
	TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;				// 输出比较模式选择:PWM模式1
	TIM_OCInitStruct.TIM_OCPolarity = TIM_OCNPolarity_High;		// 最后输出的电平不取反
	TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;	// 允许输出
	TIM_OCInitStruct.TIM_Pulse = 0;								// CCR的值
	TIM_OC1Init(TIM2, &TIM_OCInitStruct);						// 初始化OC1
	
	while(1)
	{
		for(uint8_t i = 0;i<100;i++)
		{
			TIM_SetCompare1(TIM2, i);	// 单独配置CCR的值
			Delay_ms(10);				// 延迟10ms
		}
		
		for(uint8_t i = 100;i>0;--i)
		{
			TIM_SetCompare1(TIM2, i);	// 单独配置CCR的值
			Delay_ms(10);				// 延迟10ms
		}
	}
}


PWM驱动舵机旋转

接线图

image

示例代码

舵机对输入PWM的信号要求:周期为20ms,高电平宽度为0.5ms~2.5ms。0.5ms选择角度为0度,2.5ms的旋转角度为180度

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"


int main()
{
	// 开启TIM2、GPIOA、GPIOB的时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE);
	
	/* 初始化输出PWM的引脚 */
	GPIO_InitTypeDef GPIO_InitPWM;
	GPIO_InitPWM.GPIO_Mode = GPIO_Mode_AF_PP;	// 复用推挽输出
	GPIO_InitPWM.GPIO_Pin = GPIO_Pin_1;			// 选择PA1,根据STM32的引脚定义可知,CCR2寄存器的输出引脚为PA1
	GPIO_InitPWM.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitPWM);
	
	/* 初始化按键的GPIO引脚 */
	GPIO_InitTypeDef GPIO_InitKey;
	GPIO_InitKey.GPIO_Mode = GPIO_Mode_IPU;		// 上拉输入
	GPIO_InitKey.GPIO_Pin = GPIO_Pin_1;			// 引脚PB1
	GPIO_InitKey.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitKey);
	
	// 选择内部时钟源
	TIM_InternalClockConfig(TIM2);
	
	/* 初始化时基单元 */
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
	TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;		// 滤波器时钟不分频
	TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;	// 向上计数
	TIM_TimeBaseInitStruct.TIM_Period = 20000 - 1;					// ARR,自动重装
	TIM_TimeBaseInitStruct.TIM_Prescaler = 72 - 1;					// PSC,预分频
	TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;				// 重复计数器
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStruct);
	// 清除更新中断标志位
	TIM_ClearFlag(TIM2, TIM_FLAG_Update);
	// 开启计数器
	TIM_Cmd(TIM2, ENABLE);
	
	/* 初始化输出比较通道2 */
	TIM_OCInitTypeDef TIM_OCInitStruct;
	TIM_OCStructInit(&TIM_OCInitStruct);						// 用默认值填充TIM_OCInitTypeDef结构体的每一个属性
	TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;				// 输出比较模式,PWM模式2
	TIM_OCInitStruct.TIM_Pulse = 0;								// CCR,输出比较寄存器
	TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;	// 开启通道
	TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High;		// 输出的电平不翻转
	TIM_OC2Init(TIM2, &TIM_OCInitStruct);
	
	// 舵机的旋转角度
	float angle;
	OLED_Init();
	OLED_ShowString(1,1,"Angle:");
	OLED_ShowNum(1, 7, 0, 3);
	// 每次复位后舵机的初始角度为0度,也就是0.5ms的高电平,0.025%的占空比
	TIM_SetCompare2(TIM2, 500);
	while(1)
	{
		// 当检测到按键的引脚输入电平时,说明按键被按下
		if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)
		{
			Delay_ms(20);
			while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0);
			Delay_ms(20);
			
			// 旋转角度自增30度
			angle+=30;
			// 舵机的旋转范围是0~180度
			if(angle > 180)
			{
				angle = 0;
			}	
			// CCR值为500时,舵机旋转角度为0度;CCR的值为2500时,舵机的旋转角度为180度
			TIM_SetCompare2(TIM2, angle / 180 * 2000 + 500);
		}
		// 显示舵机旋转角度
		OLED_ShowNum(1, 7, angle, 3);
	}
}


PWM驱动直流电机

接线图

image

示例代码

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"


int main()
{
	// 开启TIM2、GPIOA、GPIOB的时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE);
	
	/* 初始化输出PWM的引脚 */
	GPIO_InitTypeDef GPIO_InitPWM;
	GPIO_InitPWM.GPIO_Mode = GPIO_Mode_AF_PP;	// 复用推挽输出
	GPIO_InitPWM.GPIO_Pin = GPIO_Pin_2;			// 选择PA1,根据STM32的引脚定义可知,CCR2寄存器的输出引脚为PA1
	GPIO_InitPWM.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitPWM);
	
	/* 初始化控制TB6612驱动芯片驱动电机正反转的引脚 */
	GPIO_InitTypeDef GPIO_InitTB;
	GPIO_InitTB.GPIO_Mode = GPIO_Mode_Out_PP;		// 推挽输出
	GPIO_InitTB.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5;	// 引脚PA4和PA5
	GPIO_InitTB.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitTB);
	
	/* 初始化按键的GPIO引脚 */
	GPIO_InitTypeDef GPIO_InitKey;
	GPIO_InitKey.GPIO_Mode = GPIO_Mode_IPU;		// 上拉输入
	GPIO_InitKey.GPIO_Pin = GPIO_Pin_1;			// 引脚PB1
	GPIO_InitKey.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitKey);
	
	// 选择内部时钟源
	TIM_InternalClockConfig(TIM2);
	
	/* 初始化时基单元 */
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
	TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;		// 滤波器时钟不分频
	TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;	// 向上计数
	TIM_TimeBaseInitStruct.TIM_Period = 100 - 1;					// ARR,自动重装
	TIM_TimeBaseInitStruct.TIM_Prescaler = 72 - 1;					// PSC,预分频
	TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;				// 重复计数器
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStruct);
	// 清除更新中断标志位
	TIM_ClearFlag(TIM2, TIM_FLAG_Update);
	// 开启计数器
	TIM_Cmd(TIM2, ENABLE);
	
	/* 初始化输出比较通道3 */
	TIM_OCInitTypeDef TIM_OCInitStruct;
	TIM_OCStructInit(&TIM_OCInitStruct);						// 用默认值填充TIM_OCInitTypeDef结构体的每一个属性
	TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;				// 输出比较模式,PWM模式2
	TIM_OCInitStruct.TIM_Pulse = 0;								// CCR,输出比较寄存器
	TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;	// 开启通道
	TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High;		// 输出的电平不翻转
	TIM_OC3Init(TIM2, &TIM_OCInitStruct);
	
	// 电机转动的速度,-100~100
	int8_t speed;
	OLED_Init();
	OLED_ShowString(1,1,"Speed:");
	OLED_ShowNum(1, 7, 0, 3);
	while(1)
	{
		// 当检测到按键的引脚输入电平时,说明按键被按下
		if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)
		{
			Delay_ms(20);
			while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0);
			Delay_ms(20);
			
			// 旋转速度加25
			speed+=25;
			if(speed > 100)
			{
				speed = -100;
			}
		}
		
		// 当speed大于0时,电机正转,否则电机反转
		if(speed>=0)
		{
			GPIO_WriteBit(GPIOA, GPIO_Pin_4, Bit_SET);
			GPIO_WriteBit(GPIOA, GPIO_Pin_5, Bit_RESET);
			TIM_SetCompare3(TIM2, speed);
		}
		else
		{
			GPIO_WriteBit(GPIOA, GPIO_Pin_5, Bit_SET);
			GPIO_WriteBit(GPIOA, GPIO_Pin_4, Bit_RESET);
			TIM_SetCompare3(TIM2, -speed);
		}
		OLED_ShowSignedNum(1,7,speed,3);
	}
}


输入捕获

  • IC(Input Capture)输入捕获
  • 输入捕获模式下,当通道输入引脚出现指定电平跳变时,当前CNT的值将被锁存到CCR中,可用于测量PWM波形的频率、占空比、脉冲间隔、电平持续时间等参数
  • 每个高级定时器和通用定时器都拥有4个输入捕获通道
  • 可配置为PWMI模式,同时测量频率和占空比
  • 可配合主从触发模式,实现硬件全自动测量

测量频率

image

测频法:适合测量高频信号

设置一个闸门时间T(一般为1s,因为频率就是1s内有多少个周期),记录这个时间里来了多少个上升沿,每来一个上升沿计数器CNT就自增一次,最终这个测出来的频率:fx = N/T

测周法:适合测量低频信号

当来第一个上升沿时,计数器在标准频率fc下进行计数,直到第二个上升沿到了,计数器停止计数,得到N,最终测出来的频率为:fx = fc/N

中界频率:

测频法与测周法误差相等的频率点,fm = √ (fc/T)

输入捕获电路

通用定时器和高级定时的输入捕获电路是一样的

image

红字有误,重新解释一下

CH1输入通过两套滤波器和边沿检测电路,分别输出TI1FP1和TI1FP2,TI1FP1输入给通道1,TI1FP2输入给通道2

CH2输入通过两套滤波器和边沿检测电路,分别输出TI2FP1和TI2FP2,TI2FP1输入给通道1,TI2FP2输入给通道2

这里的信号可以各走各的,CH1引脚输入给通道1,CH2引脚输入给通道2
也可以交叉输入,CH1引脚输入给通道2,CH2引脚输入给通道1

这样设计的目的1:可以灵活切换后续捕获电路的输入。目的2:可以把一个引脚的输入同时映射到两个捕获单元,是PWMI的经典模式

通道3和通道4也是同样的结构

TRC信号:来自其他定时器

信号经过滤波器和极性选择后,来到预分频器,可以选择对信号进行分频,分频后的信号触发捕获电路进行工作,每来一个触发信号,CCR就保存一次CNT当前的值,与此同时,会发生一个捕获事件,这个事件会在状态寄存器置标志位,可以用来产生中断

输入捕获通道

image

主从触发模式

image

主模式可映射的内部信号

image

从模式可执行的操作

image

输入捕获基本结构

通过输入捕获使用测周法测量频率

image

时钟源选择内部时钟72MHz,不分频,边沿检测选择上升沿触发,ARR一般给最大值65535,从模式触发源选择TI1FP1,从模式选择复位操作

当第一个上升沿到来时,将计数器CNT的值赋值给CCR1,然后触发从模式的复位操作,将计数器CNT清零,等到下一个上升沿来时,再将CNT的值赋值个CCR1,最后用测量时钟72MHz除以CCR1中的值,就得到了要测量的波形的频率

注意:只有TI1FP1和TI2FP2可以触发从模式自动复位计数器,通道3和通道4需要开启捕获中断,在中断函数中清0计数器

PWMI基本结构

可以同时测量频率和占空比

image

该结构只比下面多了一个通道2,其他一致

测量频率的过程与上面一致,TI1FP1配置上升沿触发,触发捕获和清零CNT,正常的捕获周期。与此同时再来一个TI1FP2,配置为下降沿触发,通过交叉通道,去触发通道2的捕获单元,将CNT中的值存到CCR2中,但是CNT不清零继续计数,直到再来一个上升沿,把CNT的值存到CCR1中,再触发从模式清零CNT。此时CCR2中的值就是高电平期间的计数值,CCR1的值就是整个周期的计数值,使用CCR2/CCR1就是占空比了

小节3的案例

输入捕获测频率

先用TIM2产生1000Hz的PWM波形,再用TIM3的输入捕获测量该频率

使用测周法测量频率

用到的函数

// 使用结构体初始化一个输入捕获通道
void TIM_ICInit(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct);

// 选择从模式的触发源
void TIM_SelectInputTrigger(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);

// 选择从模式
void TIM_SelectSlaveMode(TIM_TypeDef* TIMx, uint16_t TIM_SlaveMode);
// 读取通道1的CCR1(输入捕获/输出比较寄存器)的值
uint16_t TIM_GetCapture1(TIM_TypeDef* TIMx);

// 获取当前预分频器的值
uint16_t TIM_GetPrescaler(TIM_TypeDef* TIMx);

接线图

image

代码示例

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"

int main()
{
	/* 开启TIM2、TIM3、GPIOA的时钟 */
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2 | RCC_APB1Periph_TIM3, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	/* 初始化输出PWM波形的引脚 */
	GPIO_InitTypeDef GPIO_OC1InitStruct;
	GPIO_OC1InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;		// 复用推挽输出
	GPIO_OC1InitStruct.GPIO_Pin = GPIO_Pin_0;			// 根据引脚定义图可知,TIM2的CH1复用引脚为PA0
	GPIO_OC1InitStruct.GPIO_Speed = GPIO_Speed_50MHz;	// 速度
	GPIO_Init(GPIOA, &GPIO_OC1InitStruct);
	
	/* 初始化输入捕获的引脚 */
	GPIO_InitTypeDef GPIO_IC1InitStruct;
	GPIO_IC1InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;	// 根据STM32F1的用户手册中的GPIO外设配置可知,输入捕获选浮空输入(上拉输入也没问题)
	GPIO_IC1InitStruct.GPIO_Pin = GPIO_Pin_6;				// 根据引脚定义图可知,TIM3的CH1复用引脚为PA6
	GPIO_IC1InitStruct.GPIO_Speed = GPIO_Speed_50MHz;		// 速度
	GPIO_Init(GPIOA, &GPIO_IC1InitStruct);
	
	/* 输出频率为1000Hz的PWM波形 */
	// 选择TIM2的时钟源为内部时钟
	TIM_InternalClockConfig(TIM2);
	/* 初始化TIM2的时基单元 */
	TIM_TimeBaseInitTypeDef TIM2_TimeBaseInitStruct;
	TIM2_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;		// 滤波器的采样频率时钟源不分频
	TIM2_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;	// 向上计数
	TIM2_TimeBaseInitStruct.TIM_Period = 100-1;						// ARR 自动重装寄存器的值
	TIM2_TimeBaseInitStruct.TIM_Prescaler = 720-1;					// PSC 预分频器的值
	TIM2_TimeBaseInitStruct.TIM_RepetitionCounter = 0;				// 重复计数器的值
	TIM_TimeBaseInit(TIM2, &TIM2_TimeBaseInitStruct);
	// 清除更新中断标志位
	TIM_ClearFlag(TIM2, TIM_FLAG_Update);
	// 开启TIM2的计数器,使能TIM2
	TIM_Cmd(TIM2, ENABLE);
	/* 初始化输出比较通道1,OC1 */
	TIM_OCInitTypeDef TIM_OC1InitStruct;
	// 使用默认值填充TIM_OCInitTypeDef结构体的全部属性
	TIM_OCStructInit(&TIM_OC1InitStruct);
	TIM_OC1InitStruct.TIM_OCMode = TIM_OCMode_PWM1;				// 输入比较模式:PWM1
	TIM_OC1InitStruct.TIM_OCPolarity = TIM_OCPolarity_High;		// 极性不翻转
	TIM_OC1InitStruct.TIM_OutputState = TIM_OutputState_Enable;	// 使能通道
	TIM_OC1InitStruct.TIM_Pulse = 50;							// CCR 输入/输出寄存器的值
	TIM_OC1Init(TIM2, &TIM_OC1InitStruct);
	
	/* 利用输入捕获使用测周法测量波形 */
	TIM_InternalClockConfig(TIM3);
	/* 初始化TIM3的时基单元 */
	TIM_TimeBaseInitTypeDef TIM3_TimeBaseInitStruct;
	TIM3_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;		// 滤波器的采样频率时钟源不分频
	TIM3_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;	// 向上计数
	TIM3_TimeBaseInitStruct.TIM_Period = 65535;						// ARR 自动重装寄存器的值,一般设置为最大值
	TIM3_TimeBaseInitStruct.TIM_Prescaler = 72-1;					// PSC 预分频器的值
	TIM3_TimeBaseInitStruct.TIM_RepetitionCounter = 0;				// 重复计数器的值
	TIM_TimeBaseInit(TIM3, &TIM3_TimeBaseInitStruct);
	// 清除更新中断标志位
	TIM_ClearFlag(TIM3, TIM_FLAG_Update);
	// 开启TIM2的计数器,使能TIM2
	TIM_Cmd(TIM3, ENABLE);
	/* 初始化输入捕获通道1,IC1 */
	TIM_ICInitTypeDef TIM_IC1InitStruct;
	TIM_IC1InitStruct.TIM_Channel = TIM_Channel_1;					// 选择捕获通道
	TIM_IC1InitStruct.TIM_ICFilter = 0x0f;							// 滤波频率
	TIM_IC1InitStruct.TIM_ICPolarity = TIM_ICPolarity_Rising;		// 选择触发沿为上升沿触发
	TIM_IC1InitStruct.TIM_ICPrescaler = TIM_ICPSC_DIV1;				// 对捕获的信号不分频
	TIM_IC1InitStruct.TIM_ICSelection = TIM_ICSelection_DirectTI;	// 对应着输入信号。TI1的信号输入IC1通道、TI2信号输入IC2通道
	TIM_ICInit(TIM3, &TIM_IC1InitStruct);
	
	// 选择从模式的触发源为TI1FP1
	TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);
	
	// 从模式选择复位操作。把CNT清零
	TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);
	
	OLED_Init();
	OLED_ShowString(1,1,"00000Hz");
	while(1)
	{
		/*
		TIM_GetPrescaler:获取预分频寄存器的值
		TIM_GetCapture1:获取CCR1的值
		测周法:用于计数的时钟频率除以计数器计的次数
		*/
		OLED_ShowNum(
            1,
            1,
            72000000/(TIM_GetPrescaler(TIM3)+1)/TIM_GetCapture1(TIM3),
            5
        );
	}
}

PWMI模式测频率和占空比

用到的函数

// 使用结构体初始化两个可以交叉输入的输入捕获通道,用于PWMI模式
void TIM_PWMIConfig(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct);

接线图

image

示例代码

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"

int main()
{
	/* 开启TIM2、TIM3、GPIOA的时钟 */
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2 | RCC_APB1Periph_TIM3, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	/* 初始化输出PWM波形的引脚 */
	GPIO_InitTypeDef GPIO_OC1InitStruct;
	GPIO_OC1InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;		// 复用推挽输出
	GPIO_OC1InitStruct.GPIO_Pin = GPIO_Pin_0;			// 根据引脚定义图可知,TIM2的CH1复用引脚为PA0
	GPIO_OC1InitStruct.GPIO_Speed = GPIO_Speed_50MHz;	// 速度
	GPIO_Init(GPIOA, &GPIO_OC1InitStruct);
	
	/* 初始化输入捕获的引脚 */
	GPIO_InitTypeDef GPIO_IC1InitStruct;
	GPIO_IC1InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;	// 根据STM32F1的用户手册中的GPIO外设配置可知,输入捕获选浮空输入(上拉输入也没问题)
	GPIO_IC1InitStruct.GPIO_Pin = GPIO_Pin_6;				// 根据引脚定义图可知,TIM3的CH1复用引脚为PA6
	GPIO_IC1InitStruct.GPIO_Speed = GPIO_Speed_50MHz;		// 速度
	GPIO_Init(GPIOA, &GPIO_IC1InitStruct);
	
	/* 输出频率为1000Hz的PWM波形 */
	// 选择TIM2的时钟源为内部时钟
	TIM_InternalClockConfig(TIM2);
	/* 初始化TIM2的时基单元 */
	TIM_TimeBaseInitTypeDef TIM2_TimeBaseInitStruct;
	TIM2_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;		// 滤波器的采样频率时钟源不分频
	TIM2_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;	// 向上计数
	TIM2_TimeBaseInitStruct.TIM_Period = 1000-1;					// ARR 自动重装寄存器的值
	TIM2_TimeBaseInitStruct.TIM_Prescaler = 72-1;					// PSC 预分频器的值
	TIM2_TimeBaseInitStruct.TIM_RepetitionCounter = 0;				// 重复计数器的值
	TIM_TimeBaseInit(TIM2, &TIM2_TimeBaseInitStruct);
	// 清除更新中断标志位
	TIM_ClearFlag(TIM2, TIM_FLAG_Update);
	// 开启TIM2的计数器,使能TIM2
	TIM_Cmd(TIM2, ENABLE);
	/* 初始化输出比较通道1,OC1 */
	TIM_OCInitTypeDef TIM_OC1InitStruct;
	// 使用默认值填充TIM_OCInitTypeDef结构体的全部属性
	TIM_OCStructInit(&TIM_OC1InitStruct);
	TIM_OC1InitStruct.TIM_OCMode = TIM_OCMode_PWM1;				// 输入比较模式:PWM1
	TIM_OC1InitStruct.TIM_OCPolarity = TIM_OCPolarity_High;		// 极性不翻转
	TIM_OC1InitStruct.TIM_OutputState = TIM_OutputState_Enable;	// 使能通道
	TIM_OC1InitStruct.TIM_Pulse = 300;							// CCR 输入/输出寄存器的值
	TIM_OC1Init(TIM2, &TIM_OC1InitStruct);
	
	/* 利用输入捕获使用测周法测量波形 */
	TIM_InternalClockConfig(TIM3);
	/* 初始化TIM3的时基单元 */
	TIM_TimeBaseInitTypeDef TIM3_TimeBaseInitStruct;
	TIM3_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;		// 滤波器的采样频率时钟源不分频
	TIM3_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;	// 向上计数
	TIM3_TimeBaseInitStruct.TIM_Period = 65535;						// ARR 自动重装寄存器的值,一般设置为最大值
	TIM3_TimeBaseInitStruct.TIM_Prescaler = 72-1;					// PSC 预分频器的值
	TIM3_TimeBaseInitStruct.TIM_RepetitionCounter = 0;				// 重复计数器的值
	TIM_TimeBaseInit(TIM3, &TIM3_TimeBaseInitStruct);
	// 清除更新中断标志位
	TIM_ClearFlag(TIM3, TIM_FLAG_Update);
	// 开启TIM2的计数器,使能TIM2
	TIM_Cmd(TIM3, ENABLE);
	
	/* 下面这样开启PWMI模式有点麻烦,可以使用TIM_CtrlPWMOutputs函数来一次性初始化通道1和通道2 */
//	/* 初始化输入捕获通道1,IC1,用于测量频率 */
//	TIM_ICInitTypeDef TIM_IC1InitStruct;
//	TIM_IC1InitStruct.TIM_Channel = TIM_Channel_1;					// 选择捕获通道1,IC1
//	TIM_IC1InitStruct.TIM_ICFilter = 0x0f;							// 滤波频率
//	TIM_IC1InitStruct.TIM_ICPolarity = TIM_ICPolarity_Rising;		// 选择触发沿为上升沿触发
//	TIM_IC1InitStruct.TIM_ICPrescaler = TIM_ICPSC_DIV1;				// 对捕获的信号不分频
//	TIM_IC1InitStruct.TIM_ICSelection = TIM_ICSelection_DirectTI;	// 选择直连输入信号。IC1接收TI1输入的信号
//	TIM_ICInit(TIM3, &TIM_IC1InitStruct);
//	/* 初始化输入捕获通道2,IC2,用于测量占空比 */
//	TIM_ICInitTypeDef TIM_IC2InitStruct;
//	TIM_IC2InitStruct.TIM_Channel = TIM_Channel_2;					// 选择捕获通道2,IC2
//	TIM_IC2InitStruct.TIM_ICFilter = 0x0f;							// 滤波频率
//	TIM_IC2InitStruct.TIM_ICPolarity = TIM_ICPolarity_Falling;		// 选择触发沿为下降沿触发
//	TIM_IC2InitStruct.TIM_ICPrescaler = TIM_ICPSC_DIV1;				// 对捕获的信号不分频
//	TIM_IC2InitStruct.TIM_ICSelection = TIM_ICSelection_IndirectTI;	// 选择交叉输入信号。IC2接收TI1输入的信号
	/* PWMI模式 */
	TIM_ICInitTypeDef TIM_ICPWMIInitStruct;
	TIM_ICPWMIInitStruct.TIM_Channel = TIM_Channel_1;					// 选择捕获通道1,IC1
	TIM_ICPWMIInitStruct.TIM_ICFilter = 0x0f;							// 滤波频率
	TIM_ICPWMIInitStruct.TIM_ICPolarity = TIM_ICPolarity_Rising;		// 选择触发沿为上升沿触发
	TIM_ICPWMIInitStruct.TIM_ICPrescaler = TIM_ICPSC_DIV1;				// 对捕获的信号不分频
	TIM_ICPWMIInitStruct.TIM_ICSelection = TIM_ICSelection_DirectTI;	// 选择直连输入信号。IC1接收TI1输入的信号
	/* 该函数先按照上面结构体的参数初始化通道1
	再把通道选择参数赋值成IC2的,触发沿选择上升沿,信号输入线路选择交叉输入,最后把新赋值的结构体用来初始化通道2
	这样一个函数就初始化了两个通道,配置成了PWMI模式。注意只有通道1和通道2可以使用该函数 */
	TIM_PWMIConfig(TIM3, &TIM_ICPWMIInitStruct);
	
	// 选择从模式的触发源为TI1FP1
	TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);
	
	// 从模式选择复位操作。把CNT清零
	TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);
	
	OLED_Init();
	OLED_ShowString(1,1,"00000Hz");
	OLED_ShowString(2,1,"00%");
	while(1)
	{
		/*
		TIM_GetPrescaler:获取预分频寄存器的值
		TIM_GetCapture1:获取CCR1的值
		测周法:用于计数的时钟频率除以计数器计的次数
		*/
		OLED_ShowNum(1,1,72000000/(TIM_GetPrescaler(TIM3)+1)/TIM_GetCapture1(TIM3),5);
		OLED_ShowNum(2,1,TIM_GetCapture2(TIM3) * 100 / TIM_GetCapture1(TIM3),2);
	}
}

编码器接口

编码器接口简介

  • Encoder Interface 编码器接口
  • 编码器接口可接收增量(正交)编码器的信号,根据编码器旋转产生的正交信号脉冲,自动控制CNT自增或自减,从而指示编码器的位置、旋转方向和旋转速度
  • 每个高级定时器和通用定时器都拥有1个编码器接口
  • 两个输入引脚借用了输入捕获的通道1和通道2,通道3和通道4不能接入编码器

正交编码器

可以测量位置,或带有方向的速度值,一般有两个信号输出引脚,A相和B相

正交信号的好处

  • 正交信号精度高,A相、B相都可以计次

  • 正交信号可以抗噪声,因为两个信号必须是交替跳变的

image

正转

边沿 另一相的状态
A相上升沿 B相低电平
A相下降沿 B相高电平
B相上升沿 A相高电平
B相下降沿 A相低电平

反转

边沿 另一相的状态
A相上升沿 B相高电平
A相下降沿 B相低电平
B相上升沿 A相低电平
B相下降沿 A相高电平

定时器的编码器接口

高级定时器和通用定时器的编码器接口功能是一样的,也都只有一个,这里拿通用定时器举例

image

定时器使用编码器接口时,时基单元的计数时钟、计数器的计数方向都由编码器接口托管

根据A相信号和B相信号的边沿及电平确定编码器的正反转

  • 编码器正转时,控制CNT计数器向上计数,自增
  • 编码器反转时,控制CNT计数器向下计数,自减

编码器接口基本结构

image

编码器的工作模式

image

模式3实例

A相和B相的信号均不反相

image

A相反相(TI1反相)

编码器输出的正交信号通过TH1和TH2引脚后,会经过滤波器、边沿检测等,这里的边沿检测实际上是进行极性选择的,选择高电平则输入的信号取反,也就是反相。选择低电平则对输入的信号取反

当只对一相取反时,与两相皆不取反的计数模式相反。两相皆取反与两相皆不取反的计数模式相同

image

小节4的案例

编码器接口测速

使用到的函数

/**
  * @作用  配置编码器接口。
  * @参数  TIMx:其中x可以为1、2、3、4、5或8,选择TIM外设。
  * @参数  TIM_EncoderMode: 编码器的工作模式。
  *   该参数可以是以下值之一:
  *     @arg TIM_EncoderMode_TI1: 只有TI1获取到边沿信号后计数器才记一次数
  *     @arg TIM_EncoderMode_TI2: 只有TI2获取到边沿信号后计数器才记一次数
  *     @arg TIM_EncoderMode_TI12: TI1或TI2获取到边沿信号后计数器记一次数
  * @参数  TIM_IC1Polarity: TI1的极性
  *   该参数可以是以下值之一:
  *     @arg TIM_ICPolarity_Falling: 对获取到的边沿信号取反
  *     @arg TIM_ICPolarity_Rising: 对获取到的边沿信号不取反
  * @参数  TIM_IC2Polarity: TI2的极性
  *   该参数可以是以下值之一:
  *     @arg TIM_ICPolarity_Falling: 对获取到的边沿信号取反
  *     @arg TIM_ICPolarity_Rising: 对获取到的边沿信号不取反
  * @返回值 无
  */
void TIM_EncoderInterfaceConfig(TIM_TypeDef* TIMx, uint16_t TIM_EncoderMode,
                                uint16_t TIM_IC1Polarity, uint16_t TIM_IC2Polarity);

接线图

image

示例代码

#include "stm32f10x.h"                  // Device header
#include "OLED.h"

int16_t Speed;

int main()
{
	/* 开启TIM2、TIM3、GPIOA的时钟 */
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	/* 初始化PA6、PA7,TIM3的输入捕获通道1、2分别连接旋转编码器的A相输出和B相输出 */
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStruct);
	
	/* 初始化TIM3的时基单元,时钟源由编码器接口接管不用配置 */
	TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct;
	TIM_TimeBaseStruct.TIM_ClockDivision = TIM_CKD_DIV1;		// 滤波器采样时钟源分频系数
	TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up;	// 计数器的计数模式也由编码器接口托管,可以随便写
	TIM_TimeBaseStruct.TIM_Period = 65535;						// ARR一般给最大值
	TIM_TimeBaseStruct.TIM_Prescaler = 1-1;						// PSC不分频
	TIM_TimeBaseStruct.TIM_RepetitionCounter = 0;				// 高级定时器才有
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStruct);				// 初始化TIM3,接收编码器的信号
	
	/* 初始化TIM2,用于定时1s。测量1s内旋转编码器的旋转频率 */
	TIM_InternalClockConfig(TIM2);
	TIM_TimeBaseStruct.TIM_Period = 10000 - 1;		// ARR
	TIM_TimeBaseStruct.TIM_Prescaler = 7200 - 1;	// PSC
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStruct);	// 初始化TIM2,定时器1s
	TIM_ClearFlag(TIM2, TIM_FLAG_Update);			// 清除更新中断标志位
	
	// 开启TIM2的时钟源为内部时钟,72MHz
	TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
	
	// NVIC优先级分组,分组2
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	// 配置NVIC
	NVIC_InitTypeDef NVIC_InitStruct;
	NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn;			// 选择中断通道TIM2
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;			// 使能
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;	// 抢占优先级2
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2;			// 响应优先级2
	NVIC_Init(&NVIC_InitStruct);
	
	/* 初始化输入捕获通道1、2 */
	TIM_ICInitTypeDef TIM_ICInitStruct;
	// 定时器使用编码器接口时,输入捕获只需要配置滤波器采样频率、选择通道,其他默认即可
	TIM_ICStructInit(&TIM_ICInitStruct);
	TIM_ICInitStruct.TIM_Channel = TIM_Channel_1;	// 选择捕获通道1
	TIM_ICInitStruct.TIM_ICFilter = 0x0f;			// 采样频率
	TIM_ICInit(TIM3, &TIM_ICInitStruct);			// 初始化通道1
	
	TIM_ICInitStruct.TIM_Channel = TIM_Channel_2;	// 选择捕获通道2
	TIM_ICInit(TIM3, &TIM_ICInitStruct);			// 初始化通道2
	
	/* 配置定时器的编码器接口 */
	TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);
	
	/* 开启TIM2、TIM3的计数器 */
	TIM_Cmd(TIM2, ENABLE);
	TIM_Cmd(TIM3, ENABLE);
	
	OLED_Init();
	OLED_ShowString(1,1,"Speed:");
	while(1)
	{
		OLED_ShowSignedNum(1,7,Speed,5);
	}
}

void TIM2_IRQHandler()
{
	// 判断TIM2的更新中断的中断标志位是否置1
	if(TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
	{
		// 获取TIM3计数器中的值
		Speed = TIM_GetCounter(TIM3);
		// 将TIM3计数器的值清零
		TIM_SetCounter(TIM3, 0);
		// 清除TIM2更新中断的中断标志位
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
	}
}

posted @ 2024-01-24 01:18  7七柒  阅读(1039)  评论(0)    收藏  举报