定时器PWM输入模式配置(基于合宙AIR001,HAL)

1. PWM测量

1.1 基本流程

        在介绍PWM输入模式之前,我们先脱离开单片机,先介绍下PWM频率和占空比的概念,以及测量的思想。

  • 频率:单位时间内脉冲信号的次数。

  • 占空比:脉冲信号中高电平时间与周期时间的比例。

        对于频率的测量,只需要获取到一个周期(高电平持续时间+低电平持续时间)的时间即可。占空比呢,需要我们对高电平时间进行测量,再与周期作比即可。

        既然我们要对时间进行较为精确的测量,肯定要用到定时器了,我们可以简单设计下定时器测量的流程:

  1. 配置好定时器参数       
  2. 第一个上升沿复位计数器,开始计数
  3. 下降沿记录当前计数值(cnt1
  4. 第二个上升沿时记录当前计数值(cnt2
  5. 按以下方式计算频率和占空比即可

                        高电平计数  =  cnt1  +  1
                        周期计数 = cnt2  +  1
                        频率  =  定时器时钟频率  /  (定时器分频数 + 1) / 周期计数
                        占空比  =  高电平计数  /  周期计数 * 100 

         注:“+1”是因为计数器从0开始计数,所以经过的计数周期为计数值加1。

        这里参考air001,举个实际栗子:

                        定时器时钟频率:48MHz
                        分频数:47
                        重载值:65535

        假设下降沿计数cnt14999第二个上升沿计数cnt2为9999,那么该PWM的高电平计数就是4999+1 = 5000,周期计数是9999+1 = 10000,进而有

freq= \frac{48000000}{(47+1)*10000}=100\textup{Hz}

duty = \frac{5000}{10000}*100\% = 50\%

        细心的你会发现,如果计数溢出怎么办,对于该定时器配置,在单个重载周期(开启到溢出)内,最大的cnt2为65535,那么可以测得最小的频率是:

freq_{\textup{min}} = \frac{48000000}{(47+1)*(65535+1)}\approx 15.26\textup{Hz}

        如果测量的PWM信号频率高于该值,不会出现溢出问题(在获取到cnt2之后,复位计数了),低于该频率必然会出现溢出问题,如果知道待测PWM信号频率的大致范围,可以通过调整分频值和重载值,避免溢出。聪明的你也能想到解决溢出的方法,那就是每次都记录下周期内溢出的次数,最后计算的时候加上若干次溢出的计数即可,之后的文章中,就先不考虑溢出的问题了~


1.2 输入捕获方式

        到了这里,可以进入到实操环节了,有了上面的铺垫,使用一般输入捕获就可以解决问题啦。步骤就像下面这样:

  1. 依然是配置定时器~
  2. 配置输入捕获(IC)中断,边沿设置为上升沿
  3. 开启定时器及中断
  4. 中断函数内
    1. 清中断标志
    2. 第一次上升沿触发
      1. 计数器复位
      2. 边沿设置为下降沿
    3. 下降沿触发
      1. 记录当前计数值
      2. 边沿设置为上升沿
    4. 第二次上升沿触发
      1. 记录当前计数值
      2. 可以计算频率和占空比了

        本文重点是后面的PWM输入模式,这里就不详细展开叙述了


1.3 PWM输入模式

        输入捕获模式完全可以实现我们的PWM测量功能,但是中断中处理的内容有点多,并且还需要额外的变量标记当前捕获状态,那么还有什么其他方式吗?

        PWM输入模式就是专门为此设计的,算是输入捕获的一个特例。

1.3.1 芯片手册

        以下是air001手册中的描述(TIM3):

        简单来说,就是将输入信号分给两个通道一个负责上升沿(测频率),一个负责下降沿(测占空比),并且从模式设置为复位,这样每次上升沿都会触发复位,也就是每个周期都重新计数啦,是不是又和前面说的一样了~

        下面是其中一些定义,还可参考TIM3架构图:

                Icx->Input capture x 输入捕获通道

                Tix->Timer input x 定时器输入通道(输入引脚)

                TixFP->Timer Input x Filtered Pin 定时器输入的滤波信号

                CC->Capture/Compare 捕获/比较


 1.3.2 主要寄存器

这里主要涉及到的寄存器有如下几个:

⬇️TIM3_CCRx(TIM3捕获/比较寄存器x):输入情况下包含了ICx事件传输的计数值

💡该寄存器保存了ICx通道边沿触发时的计数值,无须手动去记录计数值,直接读该寄存器即可

⬇️TIM3_CCMR1(TIM3捕获/比较模式寄存器1):主要是配置ICx和TIx之间的映射(CCxS位),以及其他ICx配置

💡将IC1(负责周期)绑定到TI1定时器输入:CC1S = 01

    IC2(负责占空比)绑定到TI1定时器输入:CC2S = 10

⬇️TIM3_CCER(TIM3捕获/比较使能寄存器1):ICx的使能和极性设定

💡IC1上升沿:CC1NP/CC1P = 00

     IC2下降沿:CC1NP/CC1P = 00

⬇️TIM3_SMCR(TIM3从模式控制寄存器):从模式设定

💡从模式触发输入选择为TI1FP1: TS=101

    从模式设置为复位模式:SMS = 100


1.3.3 配置流程

        清楚了主要的寄存器,实际配置起来就很流畅啦,这里直接从代码看流程~

1. 定时器配置

        默认内部时钟(系统时钟调到了48MHz),重载值65536-1,分频值48-1,上升计数

(这里没写引脚的配置,TIM3对应的有PA6,复用引脚即可)

  // .h
  #define INPUT_TIM TIM3
  #define INPUT_TIM_CH_freq TIM_CHANNEL_1
  #define INPUT_TIM_CH_duty TIM_CHANNEL_2
  #define INPUT_TIM_ARR ((uint32_t)(65536 - 1))
  #define INPUT_TIM_PSC ((uint32_t)(48 - 1))

  // .c
  TIM_HandleTypeDef TIM_Input_Handle;
  TIM_Input_Handle.Instance = INPUT_TIM;
  //不用指定channel,后面IC1配置了直接连接
  //TIM_Input_Handle.Channel = INPUT_TIM_CH_freq;
  TIM_Input_Handle.Init.Period = INPUT_TIM_ARR;
  TIM_Input_Handle.Init.Prescaler = INPUT_TIM_PSC;
  TIM_Input_Handle.Init.CounterMode = TIM_COUNTERMODE_UP;
  TIM_Input_Handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  TIM_Input_Handle.Init.RepetitionCounter = 0;
  HAL_TIM_IC_Init(&TIM_Input_Handle);

2. 输入捕获IC1,IC2配置

        IC1上升沿,直接连接(TI1),不分频,不滤波(根据实际调整)

        IC2下降沿,间接连接(TI1),不分频,不滤波(根据实际调整)

  // .c
  TIM_IC_InitTypeDef TIM_IC_Config;

  TIM_IC_Config.ICPolarity = TIM_ICPOLARITY_RISING;
  TIM_IC_Config.ICSelection = TIM_ICSELECTION_DIRECTTI;
  TIM_IC_Config.ICPrescaler = TIM_ICPSC_DIV1;
  TIM_IC_Config.ICFilter = 0;

  // 实际就是对CCER和CCMR寄存器进行操作
  HAL_TIM_IC_ConfigChannel(&TIM_Input_Handle, &TIM_IC_Config,
                           INPUT_TIM_CH_freq);

  TIM_IC_Config.ICPolarity = TIM_ICPOLARITY_FALLING;
  TIM_IC_Config.ICSelection = TIM_ICSELECTION_INDIRECTTI;
  HAL_TIM_IC_ConfigChannel(&TIM_Input_Handle, &TIM_IC_Config,
                           INPUT_TIM_CH_duty);

3. 从模式配置

        从模式复位模式,触发选择TI1FP1,上升沿,不分频,不滤波(根据实际调整)

  // .c  
  TIM_SlaveConfigTypeDef TIM_Slave_Config;

  TIM_Slave_Config.SlaveMode = TIM_SLAVEMODE_RESET;
  TIM_Slave_Config.InputTrigger = TIM_TS_TI1FP1;
  TIM_Slave_Config.TriggerPolarity = TIM_ICPOLARITY_RISING;
  TIM_Slave_Config.TriggerPrescaler = TIM_ICPSC_DIV1;
  TIM_Slave_Config.TriggerFilter = 0;

  // 对smcr寄存器的操作
  HAL_TIM_SlaveConfigSynchro(&TIM_Input_Handle, &TIM_Slave_Config);

4. 中断配置

        使能TIM3中断(其他板子可能有单独的IC中断名),启动IC(函数内部包含了定时器启动)

  // .h
  #define INPUT_TIM_IRQ TIM3_IRQn
  // .c
  HAL_NVIC_SetPriority(INPUT_TIM_IRQ, 0, 0);
  HAL_NVIC_EnableIRQ(INPUT_TIM_IRQ);

  HAL_TIM_IC_Start_IT(&TIM_Input_Handle, INPUT_TIM_CH_freq);
  HAL_TIM_IC_Start_IT(&TIM_Input_Handle, INPUT_TIM_CH_duty);

5. 中断函数

        函数内读取CCR1和CCR2,并计算当前频率和占空比

// air001xx_it.c
void TIM3_IRQHandler(void){
  HAL_TIM_IRQHandler(&TIM_Input_Handle);
}

// .c
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {
  // 读CCR1和CCR2寄存器
  if (htim->Instance == INPUT_TIM && htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) {
    uint32_t period = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1) + 1;
    uint32_t pulse = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2) + 1;
    freq = (float)SystemCoreClock / (TIM_Input_Handle.Instance->PSC + 1) / period;
    duty = (float)pulse / period * 100;
  }
}

        至此freq和duty检测完毕~


1.3.4 测试一下

        之前用TIM1生成了一个带死区刹车的互补pwm,正好用来测试下,配置如下:

  // .h
  #define OUTPUT_TIM TIM1
  #define OUTPUT_TIM_ARR ((uint32_t)(10000 - 1))
  #define OUTPUT_TIM_PSC ((uint32_t)(48 - 1))

  // .c
  TIM_Output_Handle.Instance = OUTPUT_TIM;
  TIM_Output_Handle.Init.CounterMode = TIM_COUNTERMODE_UP;
  TIM_Output_Handle.Init.Period = OUTPUT_TIM_ARR;
  TIM_Output_Handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  TIM_Output_Handle.Init.Prescaler = OUTPUT_TIM_PSC;

  HAL_TIM_PWM_Init(&TIM_Output_Handle);

        可知周期为100Hz,设定比较值为5000,占空比正好50%,将TIM1输出解到TIM3的CH1输入引脚即可,通过串口查看一下

  

        纳尼,为什么CCR1没问题,但是CCR2差一些呢,原因是忘了还有死区时间,关了死区就没问题啦~


2. 代码

tim3.h

#ifndef __TIM3_H__
#define __TIM3_H__

#ifdef __cplusplus
extern "C" {
#endif

#include "main.h"

#define INPUT_TIM TIM3
#define INPUT_TIM_IRQ TIM3_IRQn
#define INPUT_TIM_CLK_ENABLE() __HAL_RCC_TIM3_CLK_ENABLE()
#define INPUT_TIM_CH_freq TIM_CHANNEL_1
#define INPUT_TIM_CH_duty TIM_CHANNEL_2

#define INPUT_TIM_CHx_CLK() __HAL_RCC_GPIOA_CLK_ENABLE()
#define INPUT_TIM_CHx_PORT GPIOA
#define INPUT_TIM_CHx_PIN GPIO_PIN_6
#define INPUT_TIM_CHx_PIN_AF GPIO_AF1_TIM3

#define INPUT_TIM_ARR ((uint32_t)(65536 - 1))
#define INPUT_TIM_PSC ((uint32_t)(48 - 1))

extern TIM_HandleTypeDef TIM_Input_Handle;
extern TIM_IC_InitTypeDef TIM_IC_Config;
extern uint16_t freq;
extern uint16_t duty;

void TIM_Input_Init(void);
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim);

#ifdef __cplusplus
}
#endif
#endif

tim3.c

#include "tim3.h"

TIM_HandleTypeDef TIM_Input_Handle;
TIM_IC_InitTypeDef TIM_IC_Config;
uint16_t freq = 0;
uint16_t duty = 0;

void TIM_Input_Init(void) {
  TIMx_GPIO_Input_Configuration();
  TIMx_Input_NVIC_Configuration();
  TIMx_Input_Configuration();
}

static void TIMx_GPIO_Input_Configuration(void) {
  INPUT_TIM_CHx_CLK();
  GPIO_InitTypeDef GPIO_InitStruct;
  GPIO_InitStruct.Pin = INPUT_TIM_CHx_PIN;
  GPIO_InitStruct.Alternate = INPUT_TIM_CHx_PIN_AF;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
  HAL_GPIO_Init(INPUT_TIM_CHx_PORT, &GPIO_InitStruct);
}

static void TIMx_Input_NVIC_Configuration(void) {
  HAL_NVIC_SetPriority(INPUT_TIM_IRQ, 0, 0);
  HAL_NVIC_EnableIRQ(INPUT_TIM_IRQ);
}

static void TIMx_Input_Configuration(void) {
  INPUT_TIM_CLK_ENABLE();
  TIM_Input_Handle.Instance = INPUT_TIM;
  // TIM_Input_Handle.Channel = INPUT_TIM_CH_freq;
  TIM_Input_Handle.Init.Period = INPUT_TIM_ARR;
  TIM_Input_Handle.Init.Prescaler = INPUT_TIM_PSC;
  TIM_Input_Handle.Init.CounterMode = TIM_COUNTERMODE_UP;
  TIM_Input_Handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  TIM_Input_Handle.Init.RepetitionCounter = 0;
  HAL_TIM_IC_Init(&TIM_Input_Handle);

  TIM_IC_Config.ICPolarity = TIM_ICPOLARITY_RISING;
  TIM_IC_Config.ICSelection = TIM_ICSELECTION_DIRECTTI;
  TIM_IC_Config.ICPrescaler = TIM_ICPSC_DIV1;
  TIM_IC_Config.ICFilter = 0;

  HAL_TIM_IC_ConfigChannel(&TIM_Input_Handle, &TIM_IC_Config,
                           INPUT_TIM_CH_freq);

  TIM_IC_Config.ICPolarity = TIM_ICPOLARITY_FALLING;
  TIM_IC_Config.ICSelection = TIM_ICSELECTION_INDIRECTTI;
  HAL_TIM_IC_ConfigChannel(&TIM_Input_Handle, &TIM_IC_Config,
                           INPUT_TIM_CH_duty);

  TIM_SlaveConfigTypeDef TIM_Slave_Config;
  TIM_Slave_Config.SlaveMode = TIM_SLAVEMODE_RESET;
  TIM_Slave_Config.InputTrigger = TIM_TS_TI1FP1;
  TIM_Slave_Config.TriggerPolarity = TIM_ICPOLARITY_RISING;
  TIM_Slave_Config.TriggerPrescaler = TIM_ICPSC_DIV1;
  TIM_Slave_Config.TriggerFilter = 0;
  HAL_TIM_SlaveConfigSynchro(&TIM_Input_Handle, &TIM_Slave_Config);

  HAL_TIM_IC_Start_IT(&TIM_Input_Handle, INPUT_TIM_CH_freq);
  HAL_TIM_IC_Start_IT(&TIM_Input_Handle, INPUT_TIM_CH_duty);
}


void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {
  if (htim->Instance == INPUT_TIM && htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) {
    // 通道1捕获周期(上升沿)
    uint32_t period = HAL_TIM_ReadCapturedValue(htim, INPUT_TIM_CH_freq) + 1;
    // 通道2捕获占空比(下降沿)
    uint32_t pulse = HAL_TIM_ReadCapturedValue(htim, INPUT_TIM_CH_duty) + 1;

    freq = (float)SystemCoreClock / (TIM_Input_Handle.Instance->PSC + 1) / period;
    duty = (float)pulse / period * 100;
  }
}

        实际项目最好还是把配置的函数写成这样,方便调试:

if (HAL_TIM_IC_Start_IT(&TimHandle, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值