当前位置: 首页 > news >正文

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,启动定时器,就完事了。

相关文章:

  • Python 实现的运筹优化系统代码详解(0-1规划背包问题)
  • API调用类型全面指南:理解基础知识
  • ARM-UART
  • 光谱相机在工业中的应用
  • 在云服务器上搭建数据可视化平台:Metabase 安装与部署全流程实战
  • Spring基础二(依赖注入、自动装配)
  • Transformer原理及知识体系大纲
  • 2011-2019年各省地方财政商业服务业等事务支出数据
  • OpenGL学习笔记(颜色、基础光照)
  • 问题1:Sinal 4在开启PAC检查的设备崩溃
  • 大数据系列之:Kerberos
  • 【前端】Node.js一本通
  • 成本管理计划中的精确度、准确度代表的分别是什么?
  • 【HTB】Windows-blue靶机渗透
  • Unity3D仿星露谷物语开发33之光标位置可视化
  • SQL Server存储过程和触发器的使用
  • AI比人脑更强,因为被植入思维模型【46】演绎法思维模型
  • 2025年3月11日(风机综述_2)
  • 华为eNSP:实验 配置单区域集成ISIS
  • 【Linux】iptables命令的基本使用
  • c 网站开发架构/厦门seo代理商
  • 安徽网站开发费用/站长工具樱花
  • zblog做的商城网站/爱站长
  • bootstrap学校网站模板下载/seo课程
  • 通过模板做网站/小升初最好的补课机构排行榜
  • 电商网站制作案例/百度收录怎么弄