在编码器模式下,通过两根输入信号线,可以完成对旋转角度的检测等任务。本文章采用增量式旋转编码器。
增量型旋转编码器原理
在增量型旋转编码器下,一般有A和B两相输出信号,当顺时针旋转时,A相和B相均输出方波,不过B相的方波比A相方波提前,则A相上升沿时对应B相高电平,A相下降沿时对应B相低电平;而逆时针旋转时,B相比A相落后,因此A相上升沿时对应B相低电平,A相下降沿时对应B相高电平。这样便可以得知当前的旋转方向,再结合A相(或者B相)的上升沿数量,得知当前旋转角度。
NOTEA相和B相谁先谁后并不绝对,也可能根据具体原件的型号反转。此时便要在芯片配置界面中进行极性翻转等修改。

虽然可以用A相上升沿触发中断再检测B相电平信号实现计数,但高频的中断会占用过多计算资源,其他的任务反而无法正常运行。
因此,通用/高级定时器中为增量型编码器准备了专用的编码器接口。将A相和B相的信号接入到TI1和TI2中,引伸的TI1FP1和TI2FP2便接入到编码器接口,根据A相B相的信号控制计数器的增减。

目的:旋转旋转编码器时,舵机同步旋转;顺时针旋转使LED亮度增加,逆时针旋转使LED亮度减小。亮度限制在0-100之间,并将亮度实时显示在屏幕上。按下按钮可以切换LED。
芯片配置
- 在左侧Timers中选择TIM1,在Combined Channels下拉选择Encoder Mode即可启用定时器的编码器模式;
- 在右侧的芯片引脚处,可以看到PA8和PA9已经分别被设定为TIM1_CH1和TIM1_CH2;
- 在下侧的设置中,我们可以修改更多编码器模式的属性。
-
Encoder Mode:使用哪一通道的输入信号作为计数的标准,选择TI1则在TI1的上升沿/下降沿进行计数,当然也可以选择二者都计数;
-
Polarity:极性翻转,若需翻转上升沿/下降沿(高电平/低电平),则可更改此项,在其中一个通道启用后便可翻转增减;
-
Input Filter:输入滤波,写入15即可;
-
由于无法单独进行上升沿或下降沿的检测,因此我们将预分频器设置为二分频,即将Prescaler设置为2-1。
- 为了实现按键切换LED,再为PB15启用外部中断并添加用户标签
KEY,并在NVIC中启用EXTI line[15<10>10>] interrupts,并调节优先级使其优先级次于Time basetick timer。
代码配置
在main.c中声明宏与变量,要注意部分变量需要在main函数之外声明,之后会在stm32f1xx_it.c中使用:
#define BASE 32767 //使计数器以32767为原点,防止计数器从0减一到65535#define INITIAL_BRIGHTNESS 50-BRIGHTNESS_STEP //初始数据和实际的INITIAL_BRIGHTNESS相差BRIGHTNESS_STEP,使程序开始进行时屏幕上显示出的信息是实际的INITIAL_BRIGHTNESS#define INITIAL_DUTY 150-DUTY_STEP //初始数据和实际的INITIAL_DUTY相差DUTY_STEP,使程序开始进行时屏幕上显示出的信息是实际的INITIAL_BRIGHTNESS#define MIN_BRIGHTNESS 0#define MAX_BRIGHTNESS 255#define MIN_DUTY 50#define MAX_DUTY 250#define BRIGHTNESS_STEP 1 //每个编码器脉冲时,brightness增加多少#define DUTY_STEP -1*20 //每个编码器脉冲时,duty增加多少#define CHANNEL_RED 2#define CHANNEL_GREEN 1#define CHANNEL_BLUE 0uint16_t counter=0;uint8_t duty=INITIAL_DUTY;//占空比转换:counter每加1,duty-=20uint8_t brightness=INITIAL_BRIGHTNESS;//亮度转换:counter-BASE+50uint8_t channel_index=CHANNEL_BLUE;uint32_t channels[3]={TIM_CHANNEL_1,TIM_CHANNEL_2,TIM_CHANNEL_3};用于启用编码器的函数:
HAL_TIM_Encoder_Start(&htim1, TIM_CHANNEL_ALL);与之前一致:
&htim1:操作的定时器的句柄;TIM_CHANNEL_ALL:启用的通道,TIM_CHANNEL_ALL则表示启用所有的通道。
完成对定时器的启动和初始化:
HAL_Delay(20);OLED_Init();HAL_TIM_Encoder_Start(&htim1, TIM_CHANNEL_ALL);HAL_TIM_PWM_Start(&htim3, channels[channel_index]);HAL_TIM_PWM_Start(&htim4, TIM_CHANNEL_3);__HAL_TIM_SET_COUNTER(&htim1, BASE+1);//初始数据和BASE相差1,用于触发更新,使程序开始进行时屏幕上就显示出信息__HAL_TIM_SET_COMPARE(&htim3, channels[channel_index], brightness);__HAL_TIM_SET_COMPARE(&htim4, TIM_CHANNEL_3, duty);在while(1)中写入代码,用于在计数器发生变化时更新OLED屏幕和PWM输出:
- 首先,检测计数器的变化,只有计数器值产生变化时才更新LED和舵机的占空比;
- 检测到计数器的变化后,与原点值
BASE:32767进行对比与计算即可知道有几个脉冲,以及是顺时针还是逆时针; - 接着,利用
counter与BASE作差得到的diff,计算出期望的占空比。若占空比超出了合法范围(MIN_DUTY-MAX_DUTY),则需将其约束回范围内; - 然后,更新OLED屏幕的显示状况;
- 最后,将计数器锚定回到原点值
BASE,防止计数器数值越界。至此,完成了一次检测与修改。
具体代码如下:
while (1){ uint16_t counter=__HAL_TIM_GET_COUNTER(&htim1); int16_t diff=(int16_t)(counter-BASE); if(diff){//计数器值产生变化时: //设置duty int16_t newduty=duty+DUTY_STEP*diff; if((newduty>=MIN_DUTY) && (newduty<=MAX_DUTY)){//若newduty还在MIN_DUTY(50)到MAX_DUTY(250)之间,则直接更改 duty+=DUTY_STEP*diff; } else if(newduty<MIN_DUTY){//若newduty已经超出了MIN_DUTY(50)的范围,说明已经达到下限,则将其设为MIN_DUTY(50) duty=MIN_DUTY; } else if(newduty>MAX_DUTY){//若newduty已经超出了MAX_DUTY(250)的范围,说明已经达到下限,则将其设为MAX_DUTY(250) duty=MAX_DUTY; } __HAL_TIM_SET_COMPARE(&htim4, TIM_CHANNEL_3,duty);
//设置brightness int16_t newbrightness=brightness+BRIGHTNESS_STEP*diff; if((newbrightness>=0) && (newbrightness<=255)){//若newbrightness还在MIN_BRIGHTNESS(0)到MAX_BRIGHTNESS(255)之间,则直接更改 brightness+=BRIGHTNESS_STEP*diff; } else if(newbrightness<MIN_BRIGHTNESS){//若newbrightness已经超出了MIN_BRIGHTNESS(0)的范围,说明已经达到下限,则将其设为MIN_BRIGHTNESS(0) brightness=MIN_BRIGHTNESS; } else if(newbrightness>MAX_BRIGHTNESS){//若newbrightness已经超出了MAX_BRIGHTNESS(255)的范围,说明已经达到下限,则将其设为MAX_BRIGHTNESS(255) brightness=MAX_BRIGHTNESS; } __HAL_TIM_SET_COMPARE(&htim3, channels[channel_index],brightness);
//重新锚定计数器为BASE(32767),防止计数器越界 __HAL_TIM_SET_COUNTER(&htim1, BASE);
//OLED屏幕显示 OLED_NewFrame(); char message[20]=""; sprintf(message, "brightness:%d",brightness); OLED_PrintString(0, 0, message, &font16x16, OLED_COLOR_NORMAL); sprintf(message, "duty:%d(%d.%d%%)",duty,(int)(2.5+(((float)duty-MIN_DUTY)/(MAX_DUTY-MIN_DUTY)*(12.5-2.5))),(int)(10*(2.5+(((float)duty-MIN_DUTY)/(MAX_DUTY-MIN_DUTY)*(12.5-2.5))))%10);//50-250 -> 2.5-12.5 OLED_PrintString(0, 16, message, &font16x16, OLED_COLOR_NORMAL); OLED_ShowFrame(); }}要实现按下按钮切换LED,需要用到之前讲过的外部中断。打开文件stm32f1xx_it.c,在这里修改中断函数EXTI15_10_IRQHandler(void)以实现使用外部中断切换LED。
但是在修改之前,还需要使用extern从main.c中引入外部变量以及引入tim.h:
/* Private includes ----------------------------------------------------------*//* USER CODE BEGIN Includes */#include "tim.h"/* USER CODE END Includes *//* Private variables ---------------------------------------------------------*//* USER CODE BEGIN PV */extern uint8_t brightness;extern uint8_t channel_index;extern uint32_t channels[3];/* USER CODE END PV */然后,修改中断回调函数实现按键中断切换LED灯:
void EXTI15_10_IRQHandler(void){ /* USER CODE BEGIN EXTI15_10_IRQn 0 */ if(HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin)==GPIO_PIN_RESET){ HAL_Delay(10); if(HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin)==GPIO_PIN_RESET){ HAL_TIM_PWM_Stop(&htim3, channels[channel_index]); channel_index=(channel_index+1)%3; HAL_TIM_PWM_Start(&htim3, channels[channel_index]); __HAL_TIM_SET_COMPARE(&htim3, channels[channel_index], brightness); } } /* USER CODE END EXTI15_10_IRQn 0 */ HAL_GPIO_EXTI_IRQHandler(KEY_Pin); /* USER CODE BEGIN EXTI15_10_IRQn 1 */
/* USER CODE END EXTI15_10_IRQn 1 */}总结
相关函数
HAL_TIM_Encoder_Start(TIM_HandleTypeDef *htim, uint32_t Channel);//启用编码器模式旋转编码器原理
通过A相和B相两根输入信号线,并借助定时器的编码器模式,可以得知当前旋转角度。
部分信息可能已经过时





