江协科技/江科大-STM32入门教程-18.示例程序(编码器接口测速)


0. 江协科技/江科大-STM32入门教程-各章节详细笔记-查阅传送门-STM32标准库开发_stm32江协大 csdn-CSDN博客文章浏览阅读3.4k次,点赞47次,收藏143次。江协科技/江科大-STM32标准库开发-各章节详细笔记-传送门至各个章节笔记。基本上课程讲的每句都详细记录,方便回顾。_stm32江协大 csdnhttps://blog.csdn.net/m0_61712829/article/details/132434192?spm=1001.2014.3001.5501

main.c

这段代码主要作用是通过定时器定时执行 `TIM2_IRQHandler` 中断服务程序,在定时器中断处理函数中读取编码器的值,并将其存储在 `speed` 变量中,然后在主循环中利用 OLED 显示器显示速度值。

值得注意的是,在 `main` 函数中需要调用 `OLED_Init()` 进行 OLED 显示器的初始化,并在使用 OLED 显示的地方调用 `OLED_ShowString` 和 `OLED_ShowSignedNum` 函数进行显示。

除了这些代码片段外,程序的其他部分,例如 `OLED_Init`、`Timer_Init`、`Encoder_init` 等函数的定义以及头文件的包含内容,以及关于定时器和编码器的配置,也是非常重要的。

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


uint16_t speed;

int main(void)
{
	
	OLED_Init();	//初始化OLED
	Timer_Init();    //初始化定时器
	Encoder_init();
	
	OLED_ShowString(1,1,"speed:");

	while(1)
	{
		OLED_ShowSignedNum(1,7,speed,5);//每隔一段时间读取一次;用定时中断
	}

}


//定时器2中断函数放在使用中断的main.c文件中;在startup文件中;定时中断每隔1s执行一次
void TIM2_IRQHandler(void) //当定时器产生更新中断时,这个函数就会自动被执行
{
	//检查中断标志位
	if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET)
	{
	//执行相应的用户代码
		speed = Encoder_Get();   //定时器每隔1s读取一下速度,存在speed变量里
		TIM_ClearITPendingBit(TIM2,TIM_IT_Update);//清除标志位
	}

}

Encoder.c

这段代码是针对 STM32 微控制器的编码器初始化和测速函数的实现,用于读取编码器的旋转位置和速度。以下是对代码的简要说明:

1. `Encoder_init` 函数是编码器的初始化函数,主要包括了以下几个步骤:
   - 开启时钟,选择内部时钟,以及初始化 GPIO 接口。
   - 配置时基单元 TIM3,设置预分频器、计数器模式和周期等参数。
   - 配置输入捕获单元(通道)TIM_ICInit,设置滤波器和极性。
   - 最后调用 `TIM_EncoderInterfaceConfig` 函数配置编码器接口。
   - 启动定时器 TIM3。

2. `Encoder_Get` 函数是用于测速的函数,主要实现了在固定的时间间隔内读取一次计数器的值,并清零计数器。函数返回了测量得到的编码器的变化值,即速度信息。

#include "stm32f10x.h"                  // Device header

//编码器旋转控制CNT自增自减	
//编码器初始化函数,编码器电路初始化后,CNT就会随着编码器旋转而自增自减;直接读出CNT值就能测量编码器的位置;测量编码器的速度和方向就需要每隔一段固定的闸门时间取出一次CNT然后再把CNT清零这就是测频法测量速度了
/*
第一步,RCC开启时钟,开启GPIO和定时器的时钟
第二步,配置GPIO,需将PA6和PA7配置成输入模式
第三步,配置时基单元,预分频器一般选择不分频,ARR一般给最大值655535,只需要CNT执行计数就行了
第四步,配置输入捕获单元,这里只有滤波器和极性两个参数有用,后面的参数没有用到,与编码器无关
第五步,配置编码器接口模式,直接调用一个库函数
最后,调用TIM_Cmd,启动定时器
*/

void Encoder_init(void)
{
	//1.打开时钟,选择内部时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	//2.初始化GPIO
	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);
	
	//3.配置时基单元 
	/*
	公式:
	PWM 频 率:Freq = CK_PSC / (PSC + 1) / (ARR + 1)
	PWM占空比:Duty = CCR / (ARR + 1)
	PWM分辨率:Reso = 1 / (ARR + 1)
	*/
	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);	//初始化TIM3
	
	//4.配置输入捕获单元(通道),编码器接口只使用了通道1和2的滤波器和极性选择
	//首先定义结构体变量,然后StructInit给结构体赋一个初始值,再部分修改我们想要的参数,调用ICInit配置一遍电路,结构体变量的配置在调用ICInit函数之后就写入到硬件的寄存器了,所以ICInit之后这个结构体我们可以换个值继续使用、不需要重新定义新的结构体
	TIM_ICInitTypeDef TIM_ICInitStructure;
	TIM_ICStructInit(&TIM_ICInitStructure);//结构体初始化,防止结构体中出现不确定值可能造成问题,最好用StructInit给结构体赋一个初始值
	TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;	//通道1
	TIM_ICInitStructure.TIM_ICFilter = 0xF;	//滤波器为0xF
	//TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;	//电平极性为上升沿,这里的上升沿参数代表的是高低电平极性不反转;等会配置编码器接口的时候也有极性配置,属于重复配置,这个其实可以删掉;这里的上升沿并不代表上升沿有效,因为编码器接口始终都是上升沿、下降沿都有效
	TIM_ICInit(TIM3, &TIM_ICInitStructure);
	TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;	//通道2
	TIM_ICInitStructure.TIM_ICFilter = 0xF;	//滤波器为0xF
	//TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;	//电平极性为上升沿,这里的上升沿参数代表的是高低电平极性不反转;等会配置编码器接口的时候也有极性配置,属于重复配置,这个其实可以删掉;这里的上升沿并不代表上升沿有效,因为编码器接口始终都是上升沿、下降沿都有效
	TIM_ICInit(TIM3, &TIM_ICInitStructure);
	
	//5.配置编码器接口,只需调用一个函数就行了;;需保证TIM_EncoderInterfaceConfig在TIM_ICInit函数之后,否则TIM_ICInit覆盖TIM_EncoderInterfaceConfig函数的配置
	TIM_EncoderInterfaceConfig(TIM3,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);//选择Rising是通道不反相,选择Falling是通道反相;重复配置TIM_ICPolarity_Rising,后面的参数会覆盖前面的参数配置
	
	//6.启动定时器
	TIM_Cmd(TIM3,ENABLE);
}

int16_t Encoder_Get(void)
{
	//测速,在固定的匝门时间读一次CNT然后把CNT清零
	int16_t temp;//因为要先读取CNT再清零,所以要用temp缓存一下
	temp = TIM_GetCounter(TIM3);//读取CNT
	TIM_SetCounter(TIM3,0);//CNT清零	
	return temp;

}


 

Encoder.h

这段代码是编码器模块的头文件 "Encoder.h" 的内容。它使用了条件编译指令,防止头文件的重复包含。

在头文件中,声明了两个函数的原型:
- `Encoder_init`:编码器的初始化函数。
- `Encoder_Get`:获取编码器的值(位置或速度)的函数。

同时,使用了预处理指令 `#ifndef`、`#define`、`#endif`,确保头文件只包含一次,以避免重复定义的错误。

通过包含这个头文件,其他源文件就可以使用 `Encoder_init` 和 `Encoder_Get` 函数进行编码器的初始化和获取编码器的值。

#ifndef __ENCODER_H
#define __ENCODER_H

void Encoder_init(void);
int16_t Encoder_Get(void);

#endif
  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿齐Archie

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值