STM32单片机入门学习——第20节: [6-8]编码器接口测速
写这个文章是用来学习的,记录一下我的学习过程。希望我能一直坚持下去,我只是一个小白,只是想好好学习,我知道这会很难,但我还是想去做!
本文写于:2025.04.06
STM32开发板学习——第20节: [6-8]编码器接口测速
- 前言
- 开发板说明
- 引用
- 解答和科普
- 一、编码器接口测速
- 问题
- 总结
前言
本次笔记是用来记录我的学习过程,同时把我需要的困难和思考记下来,有助于我的学习,同时也作为一种习惯,可以督促我学习,是一个激励自己的过程,让我们开始32单片机的学习之路。
欢迎大家给我提意见,能给我的嵌入式之旅提供方向和路线,现在作为小白,我就先学习32单片机了,就跟着B站上的江协科技开始学习了.
在这里会记录下江协科技32单片机开发板的配套视频教程所作的实验和学习笔记内容,因为我之前有一个开发板,我大概率会用我的板子模仿着来做.让我们一起加油!
另外为了增强我的学习效果:每次笔记把我不知道或者问题在后面提出来,再下一篇开头作为解答!
开发板说明
本人采用的是慧净的开发板,因为这个板子是我N年前就买的板子,索性就拿来用了。另外我也购买了江科大的学习套间。
原理图如下
1、开发板原理图
2、STM32F103C6和51对比
3、STM32F103C6核心板
视频中的都用这个开发板来实现,如果有资源就利用起来。另外也计划实现江协科技的套件。
下图是实物图
引用
【STM32入门教程-2023版 细致讲解 中文字幕】
还参考了下图中的书籍:
STM32库开发实战指南:基于STM32F103(第2版)
数据手册
解答和科普
一、编码器接口测速
A相接PA6,B相输出接到PA7引脚,因为用的是TIM3.
第一步,RCC开启时钟,开启GPIO和定时器的时钟;
第二步,配置GPIO,这里需要把PA6和PA7配置成输入模式;
第三步,配置时基单元,这里预分频器我们一般选择不分频,自动重装,一般给最大65535,只需要个CNT执行计数就行了;
第四步,配置输入捕获单元,不过这里输入捕获单元只有滤波器和极性这两个参数有用,后面的参数没有用到,与编码器无关;
第五步,配置编码器接口模式,这个直接调用一个库函数就可以了;
最后调用TIM_Cmd,启动定时器,就完事了。
CNT就会随着编码器旋转而自增自减,如果想要测量编码器的位置,那直接读出CNT的值就行了,如果想测量编码器的速度和方向,那就需要每隔一段固定的闸门时间,取出一次CNT,然后再把CNT清零,这样就是测频法测量速度了。
void TIM_EncoderInterfaceConfig(TIM_TypeDef* TIMx, uint16_t TIM_EncoderMode,
uint16_t TIM_IC1Polarity, uint16_t TIM_IC2Polarity);
定时器编码器接口配置:第一个参数选择定时器,第二个参数选择编码器模式,后面的参数分别选择通道1和通道2的电平极性;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU; // 上拉输入
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_6|GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
这里的GPIO模式,可以选择上拉、下拉、或者浮空,我们一般可以看一下接在这个引脚的外部模块输出的默认电平,如果外部模块空闲默认输出高电平,我们就选择上拉输入,默认为低电平,我们就配置下拉输入,默认输出低电平。和外部模块保持默认状态一致,防止默认电平打架,这是上拉和下拉的选择原则。不过一般来说,默认高电平,这是一个习惯的状态。所以一般上拉输入用的比较多。不确定外部模块的默认状态,或者外部信号输出功率非常小,这时就尽量选择浮空输入,浮空输入,没有上拉电阻和下拉电阻去影响外部信号,但是缺点就是当引脚悬空时,没有默认的电平了,输入就会受到噪声干扰,来回不断底跳变。
时基单元配置
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1; //滤波的分频关系不大
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up ; //向上计数
TIM_TimeBaseInitStructure.TIM_Period=65536-1; /* ARR 计数最大值,同时方便转化为负数*/
TIM_TimeBaseInitStructure.TIM_Prescaler=1-1; /* PSC编码器时钟直接驱动计数器*/
TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);
输入捕获单元
TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_ICStructInit(&TIM_ICInitStructure);
TIM_ICInitStructure.TIM_Channel=TIM_Channel_1 ; //选择通道
TIM_ICInitStructure.TIM_ICFilter=0xF; //滤波
TIM_ICInitStructure.TIM_ICPolarity=TIM_ICPolarity_Rising; //配置编码器接口的时候也有//上升沿触发代表的是高低电平不翻转(也就是是否反相)
TIM_ICInit(TIM3,&TIM_ICInitStructure); //写入寄存器
TIM_ICInitStructure.TIM_Channel=TIM_Channel_2 ; //选择通道
TIM_ICInitStructure.TIM_ICFilter=0xF; //滤波
TIM_ICInitStructure.TIM_ICPolarity=TIM_ICPolarity_Rising; //配置编码器接口的时候也有//上升沿触发代表的是高低电平不翻转(也就是是否反相)
TIM_ICInit(TIM3,&TIM_ICInitStructure);
配置编码器接口
TIM_EncoderInterfaceConfig(TIM3,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);
模式3、通道都不反相,极性不变;
TIM_ICInitStructure.TIM_ICPolarity=TIM_ICPolarity_Rising; //配置编码器接口的时候也有//上升沿触发代表的是高低电平不翻转(也就是是否反相)
这种极性祈祷的效果是一样的,其实配置的都是同一个寄存器;后配置的会覆盖前面配置的参数;
启动定时器
TIM_Cmd(TIM3,ENABLE);
#include "stm32f10x.h" // Device header
void Encoder_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU; // 上拉输入
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_6|GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
// TIM_InternalClockConfig(TIM3); //编码器会托管时钟,没有用了
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1; //滤波的分频关系不大
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up ; //向上计数
TIM_TimeBaseInitStructure.TIM_Period=65536-1; /* ARR 计数最大值,同时方便转化为负数*/
TIM_TimeBaseInitStructure.TIM_Prescaler=1-1; /* PSC编码器时钟直接驱动计数器*/
TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);
TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_ICStructInit(&TIM_ICInitStructure);
TIM_ICInitStructure.TIM_Channel=TIM_Channel_1 ; //选择通道
TIM_ICInitStructure.TIM_ICFilter=0xF; //滤波
TIM_ICInit(TIM3,&TIM_ICInitStructure); //写入寄存器
TIM_ICInitStructure.TIM_Channel=TIM_Channel_2 ; //选择通道
TIM_ICInitStructure.TIM_ICFilter=0xF; //滤波
TIM_ICInit(TIM3,&TIM_ICInitStructure);
TIM_EncoderInterfaceConfig(TIM3,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);
TIM_Cmd(TIM3, ENABLE);
}
int16_t Encoder_Get(void)
{
return TIM_GetCounter(TIM3);
}
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "Timer.h"
#include "OLED.h"
#include "Encoder.h"
//uint16_t Num;
int main(void)
{
OLED_Init();
//Timer_Init();
Encoder_Init();
OLED_ShowString(1,2,"Hello STM32 MCU");
OLED_ShowString(2,1,"CNT:");
while(1)
{
OLED_ShowNum(2,5,Encoder_Get(),5);
}
}
//void TIM2_IRQHandler(void)
//{
// if(TIM_GetITStatus(TIM2,TIM_IT_Update)== SET)
// {
// Num++;
// TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
// }
//}
这个时候每转动一格编码器就会增加4,如果你是电机的编码器就不会有这个段落感了;
0之后左转为65536如何想要变成负数,怎么做呢:就是直接把uint16_t转化为int16_t就行了
借用补码的特性快速完成负数转换的小技巧。
实验现象1
左转不是负数
int16_t Encoder_Get(void)
{
return TIM_GetCounter(TIM3);
}
while(1)
{
OLED_ShowSignedNum(2,5,Encoder_Get(),5);
}
实验现象2
调整后显示负数
极性问题
1、A、B相两根线换一下,增减方向相反;
向右转是减,向左转是增;
2、可以修改两个输入通道的极性
把任意一个反转一下,方向就会反过来,如果两个极性都反转,那极性还是保持不变。
实现现象
软件极性反转
目前是测量为止,还要测速度的话,还要进行改进,
测速的话可以在固定的闸门时间,读一次CNT,然后CNT清零,我们修改一下这个Get参数,要求读完后清零CNT;
int16_t Encoder_Get(void) //先读取后请求 TEMP中间缓存一下
{
int16_t Temp;
Temp=TIM_GetCounter(TIM3);
TIM_SetCounter(TIM3,0);
return Temp;
}
while(1)
{
OLED_ShowSignedNum(2,5,Encoder_Get(),5);
Delay_ms(1000); //实际电机的话要小点,防止计数器溢出;
}
Delay 实验现象
主程序Delayis测速
如果主程序没有其他东西可以这样做,但是如果有其他东西的话,最好就不要再主循环加入过长的Delay();这样会阻塞主循环的执行,可以用定时中断来完成;
1.main
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "Timer.h"
#include "OLED.h"
#include "Encoder.h"
int16_t Speed;
int main(void)
{
OLED_Init();
Timer_Init();
Encoder_Init();
OLED_ShowString(1,2,"Hello STM32 MCU");
OLED_ShowString(2,1,"Speed:");
while(1)
{
OLED_ShowSignedNum(2,7,Speed,5);
}
}
void TIM2_IRQHandler(void) //目前配置是1S进入中断
{
if(TIM_GetITStatus(TIM2,TIM_IT_Update)== SET)
{
Speed=Encoder_Get();
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
}
2、Encoder.CH
#include "stm32f10x.h" // Device header
void Encoder_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU; // 上拉输入
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_6|GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
// TIM_InternalClockConfig(TIM3); //编码器会托管时钟,没有用了
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1; //滤波的分频关系不大
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up ; //向上计数
TIM_TimeBaseInitStructure.TIM_Period=65536-1; /* ARR 计数最大值,同时方便转化为负数*/
TIM_TimeBaseInitStructure.TIM_Prescaler=1-1; /* PSC编码器时钟直接驱动计数器*/
TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);
TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_ICStructInit(&TIM_ICInitStructure);
TIM_ICInitStructure.TIM_Channel=TIM_Channel_1 ; //选择通道
TIM_ICInitStructure.TIM_ICFilter=0xF; //滤波
TIM_ICInit(TIM3,&TIM_ICInitStructure); //写入寄存器
TIM_ICInitStructure.TIM_Channel=TIM_Channel_2 ; //选择通道
TIM_ICInitStructure.TIM_ICFilter=0xF; //滤波
TIM_ICInit(TIM3,&TIM_ICInitStructure);
TIM_EncoderInterfaceConfig(TIM3,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);
TIM_Cmd(TIM3, ENABLE);
}
int16_t Encoder_Get(void) //先读取后请求 TEMP中间缓存一下
{
int16_t Temp;
Temp=TIM_GetCounter(TIM3);
TIM_SetCounter(TIM3,0);
return Temp;
}
#ifndef __ENCODER_H
#define __ENCODER_H
void Encoder_Init(void);
int16_t Encoder_Get(void);
#endif
3、TImer
#include "stm32f10x.h" // Device header
extern uint16_t Num;
void Timer_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
TIM_InternalClockConfig(TIM2); //好多人不写,默认的是内部时钟
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1; //滤波的分频关系不大
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up ; //向上计数
TIM_TimeBaseInitStructure.TIM_Period=7200-1; //ARR
TIM_TimeBaseInitStructure.TIM_Prescaler=10000-1; //PSC
TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
TIM_ClearFlag(TIM2,TIM_FLAG_Update); //手动把更新标志位清除一下,就能避免刚初始化完就进中断的问题了
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE); //更新中断到NVIC的通路
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
NVIC_Init(&NVIC_InitStructure);
TIM_Cmd(TIM2,ENABLE);
}
/*
void TIM2_IRQHandler(void)
{
if(TIM_GetITStatus(TIM2,TIM_IT_Update)== SET)
{
Num++;
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
}
*/
#ifndef __TIMER_H
#define __TIMER_H
void Timer_Init(void);
#endif
实验现象
定时器1s进入中断,获取速度
问题
1、正交信号示波器的波形
正转:
2、反转
总结
本节课主要学习了配置编码器接口,同样的引脚配置,时基单元,然后进入到输入比较配置,最后进行编码器配置,只需要一个函数,然后就完成了获取位置的代码,后来有在一定的时间内,变化的位置就是速度,完成了编码器接口测速度的功能。第一步,RCC开启时钟,开启GPIO和定时器的时钟;
第二步,配置GPIO,这里需要把PA6和PA7配置成输入模式;
第三步,配置时基单元,这里预分频器我们一般选择不分频,自动重装,一般给最大65535,只需要个CNT执行计数就行了;
第四步,配置输入捕获单元,不过这里输入捕获单元只有滤波器和极性这两个参数有用,后面的参数没有用到,与编码器无关;
第五步,配置编码器接口模式,这个直接调用一个库函数就可以了;
最后调用TIM_Cmd,启动定时器,就完事了。