收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人
都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
入口参量:(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising ,TIM_ICPolarity_Rising);
通过这样配置,我们就完成了编码器软件的初始化。通过以上配置,我们完成了TIM3输入捕获的初始化以及编码器接口模式的相关配置,每来一个跳变沿,计数值变化一次。
至此,为了避免我们忘了初衷,现在我们来回顾一下我们最开始用编码器的初衷,前面我们说要用编码器来干嘛来着?获取电机转动角度、转速。
现在的处境是,我们通过STM32的TIM3的编码器模式,能够测出任意时刻的脉冲值了。现在要解决的就是如何将这个脉冲值转换成我们所要得到的信息(即转动角度、转速)。
首先,根据带编码器的直流减速电机的原理,显然无论电机的转速如何,每转产生的脉冲数是固定的。这里假设电机每转产生260个脉冲(具体数据各位自行查看自己的电机参数啦),那么只要我们用‘电机已产生的脉冲数’除以‘260个/转’,就可以得到电机转了多少圈,一圈即为360度,由此便可将脉冲数和转动角度联系起来。
这里要注意一点,由于我们用的是编码器模式3(也就是TIM_EncoderMode_TI12),我们得到的脉冲数是电机实际产生的脉冲数的四倍。则电机实际产生的脉冲数应为‘得到的计数值’除以4。于是,电机转动圈数为脉冲数除以260再除以4。
转动角度算出来了,转速呢?则是根据计数方向(递增计数或递减计数)来判断的,所以我们接下来的工作就是要搞清楚计数方向和编码器信号的关系。
让我们来看一下STM32的中文参考手册:
网上几乎在这张表的讲解上都是一带而过,真正能把这张表讲清楚的几乎没有。后来看到一个人在表格上做了类似上图的注释,我才终于搞懂了。
大家还记得我们前面提到的TIM_EncoderInterfaceConfig函数吗,里面有个参量就是选择模式的,对吧。让我们再来回顾一下:
注意到各模式后面的“xx edge depending on xx level”了吗?这就是上面这个表里的第二纵列所提到的“相反信号的电平”。
TIM_EncoderMode_TI1: Counter counts on TI1FP1 edge depending on TI2FP2 level,意思就是当选择模式1时,计数器根据TI2的电平高低来记录TI1的边沿信号。
这样说可能不好理解,我们来举个具体的例子:
如上图,电机转动时产生A、B两相信号通过TI1、TI2输入到TIM3。
假设我们选择的是模式1,即计数器仅在TI1的边沿处计数。我们观察TI1的上升沿,若此时对应的TI2信号处于低电平(下图红框),于是根据表格我们可以得到计数方向为递增,假设计数器递增时电机正向转动,则可判断此时电机正转。
再譬如,假设我们选择模式2,即计数器仅在TI2的边沿处计数。我们观察TI2的下降沿,若此时对应的TI1信号为高电平(下图蓝框),于是根据表格我们可以得到计数方向为递减,假设计数器递减时电机反向转动,则可判断此时电机反转。
也就是说,当以TI1为计数信号时,需要根据TI2的电平(也就是第二纵列的“相对信号的电平”)来进行判断是向上计数还是向下计数。TI2也同理。
e.g.如果“仅在TI1计数”,当TI1为“上升”沿(表中第三纵列),此时要根据TI2的电平来做判断。根据下图,可知当TI1为上升沿的时候TI2为低电平,则根据上表可得知计数器为向上计数。这在下图得到了验证。
再例如,如果“仅在TI2计数”时,当TI2为下降沿时,如果TI1为低电平,则为向上计数(上图左侧);当TI2为下降沿时,如果TI1为高电平,则为向下计数(上图中部);当TI2为上升沿时,如果TI1为高电平,则为向上计数(上图左部);当TI2为上升沿时,如果TI1为低电平,则为向下计数(图中中部);
至此为止,我们便实现了这样的操作:通过使用带编码器的直流减速电机产生正交脉冲,通过STM32的TIM的编码器模式对脉冲进行计数,根据计数方向和编码器信号的关系来判断电机转动方向,利用脉冲计数值来计算电机转动位移。
接下来是代码部分,有些容易让人迷惑的地方(至少是让我迷惑了很久):
输入捕获初始化时Period和Prescaler的作用:
这里的period即为计数器的重装载值,prescaler即为预分频系数。注意在编码器模式时,要把TIM理解为计数器,而不是定时器,这样的话,时钟信号就不是系统内部产生的,而是通过PA6、PA7输入到TIM3的TI1和TI2的。
有了这么个理解,再来看Period和Prescaler就不难理解了。Period就是计数器每一次能检测脉冲的最大值,每来一个脉冲计数值就加一,当计数值超过period就溢出中断。Prescaler就是对电机产生的脉冲信号进行分频的分频系数。比如当分频系数为2时,每当电机产生两个脉冲,stm32才认为接收到一个有效脉冲,计数值才加一。
以下为部分代码
//思路:初始化TIM的编码器模式后,在main函数里死循环不断的读取CNT的值,从而来获得电机的脉冲数数据,以此来计算电机的转速、所转圈数等参数
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "pwm.h"
#include "encode.h"
#include "exti.h"
#include "key.h"
u32 pwm = 50000;
int main(void)
{
uint32_t cnt_temp; //用于暂存TIM的计数值,即TIM检测到的脉冲的数量
float pulse; //电机产生的实际脉冲值
float round; //电机转的圈数
encoder_init(); //TIM3编码器模式初始化,A6、A7分别作为A相和B相的脉冲输入
exti_init(); //外部中断,用于通过按键修改数据(比如PWM)
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置系统中断优先级分组2
delay_init(168); //初始化延时函数
uart_init(115200); //初始化串口波特率为115200
TIM4_PWM_Init(50000-1,84-1); //TIM4初始化,用于产生PWM供给直流减速电机
//84M/84=1Mhz的计数频率,重装载值50000,所以PWM频率为100000/50000=2hz,即整个周期为500ms
while(1)
{
TIM_SetCompare1(TIM4,pwm); //设置供给电机的PWM值
cnt_temp = read_cnt(); //得到脉冲计数值
pulse = cnt_temp/4.0f; //由于是TIM_EncoderMode_TI12,所以要四分频,即除以四,得到实际的脉冲值
round = cnt_temp/4.0f/260.0f; //假设电机每转产生260个脉冲,则通过该公式可求出电机转了几圈
printf("cnt_temp:%d\r\n", cnt_temp); //向串口打印脉冲计数值
printf("pulse:%f\r\n", pulse); //向串口打印实际脉冲值
printf("round:%f\r\n", round); //向串口打印电机转了几圈
delay_ms(1000); //每1s循环更新一次
// if(TIM3->CR1&1<<4) printf("forward");
// else printf("backward");
}
}
//把TIM3理解为一个计数器而不是一个定时器,则没有了时序信号。
//这里TIM3的时钟信号(或者说是计数信号)将由电机编码器输出的脉冲代替,也就是说电机脉冲信号成为TIM3的信号,电机每产生一个脉冲被TIM3检测到,则计数器CNT加一(类比于时序信号时每隔一个时间段计数值加一)
//这样的话,输入捕获的自动重装载值period则影响着脉冲值计数到多少之后就溢出,比如65535的话,则接收到65535个脉冲信号之后计数值置零溢出
//这样的话,输入捕获的预分频系数prescaler的作用是,当我不分频时,来一个电机脉冲信号我计数值就加一,当我二分频时,只有接收到两个脉冲信号我才认为是一个有效脉冲,计数值才加一,简单来说就是计数值总体除以二了
//这样我们就把输入捕获初始化完成了,接下来是编码器模式的初始化
//设为TIM_EncoderMode_TI12模式,即计数器在TI1和TI2上升沿处均计数,再根据设置的极性是TIM_ICPolarity_Rising,也就是在TI1和TI2的上升沿计数器累加(或累减)-->那么到时候要除以二
//这样,编码器的初始化就完成了,接下来我们只要通过函数得出它的计数值,就可以知道电机产生的脉冲数,再根据电机的参数(每转产生多少个脉冲)就可以得到电机转了几圈
#include "encode.h"
#include "sys.h"
//初始化编码器
void encoder_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_ICInitTypeDef TIM_ICInitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource6, GPIO_AF_TIM3);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_TIM3);
//Specifies the prescaler value used to divide the TIM clock.
//也就是说,这里的TIM3的时钟信号其实是由A/B相的频率来决定的,类似于外部时钟,然后分频就是对这个脉冲频率分频,比如二分频就是把两个脉冲记为一个脉冲。
TIM_TimeBaseStructure.TIM_Prescaler = 1-1; //这里我们把它设为1,即不分频
TIM_TimeBaseStructure.TIM_Period = 65535; //每来一个脉冲信号的上升沿(下面有设置)计数值就累加(或累减),65535则为最大计数值,就溢出了
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
// TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //这里按理来说应该不起作用,因为计数方向是受TI1和TI2信号的影响的
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
TIM_ICStructInit(&TIM_ICInitStructure); //Fills each TIM_ICInitStruct member with its default value
**收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。**
![img](https://img-blog.csdnimg.cn/img_convert/28d32105f61e0e4d09140ba29d9c5472.png)
![img](https://img-blog.csdnimg.cn/img_convert/705db463e28e1e2eb705a71e89c290bd.png)
**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618679757)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人**
**都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**
s://bbs.csdn.net/topics/618679757)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人**
**都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**