STM32LL库编程系列第六讲——定时器编码器模式+电机驱动

第六讲——定时器编码器模式+电机驱动



前言

本章将介绍如何使用STM32读取电机编码器时序,从而计算出电机转速。同时搭配对电机的PWM渐变控制。使用的微处理器是STM32F407VET6。
编程逻辑,手机APP发送数据——>控制板蓝牙接收数据——>根据数据计算出占空比——>控制电机——>读取编码器时序——>得到转速


一、前提知识

1.编码器概述

编码器是一种将角位移或者角速度转换成一连串电数字脉冲的旋转式传感器,我们可以通过编码器测量到底位移或者速度信息。编码器从输出数据类型上分,可以分为增量式编码器和绝对式编码器。
从编码器检测原理上来分,还可以分为光学式、磁式、感应式、电容式。常见的是光电编码器(光学式)和霍尔编码器(磁式)。

2.编码器原理

光电编码器是一种通过光电转换将输出轴上的机械几何位移量转换成脉冲或数字量的传感器。光电编码器是由光码盘和光电检测装置组成。光码盘是在一定直径的圆板上等分地开通若干个长方形孔。由于光电码盘与电动机同轴,电动机旋转时,检测装置检测输出若干脉冲信号,为判断转向,一般输出两组存在一定相位差的方波信号。
霍尔编码器是一种通过磁电转换将输出轴上的机械几何位移量转换成脉冲或数字量的传感器。霍尔编码器是由霍尔码盘和霍尔元件组成。霍尔码盘是在一定直径的圆板上等分地布置有不同的磁极。霍尔码盘与电动机同轴,电动机旋转时,霍尔元件检测输出若干脉冲信号,为判断转向,一般输出两组存在一定相位差的方波信号。
可以看到两种原理的编码器目的都是获取AB 相输出的方波信号,其使用方法也是一样,下面是一个简单的示意图。
在这里插入图片描述
以下是我使用的编码器电机,如图
在这里插入图片描述
这是一款增量式输出的霍尔编码器。编码器有AB相输出,所以不仅可以测速,还可以辨别转向。根据上图的接线说明可以看到,我们只需给编码器电源5V 供电,在电机转动的时候即可通过 AB 相输出方波信号。编码器自带了上拉电阻,所以无需外部上拉,可以直接连接到单片机10读取。

3.编码器软件四倍频技术

先看编码器输出波形,如图
在这里插入图片描述
这里,我们是通过软件的方法实现四倍频。首先可以看到上图编码器输出的AB相波形,正常情况下我们使用M法测速的时候,会通过测量单位时间内A相输出的脉冲数来得到速度信息。常规的方法,我们只测量A相(或B相)的上升沿或者下降沿,也就是上图中对应的数字 1234 中的某一个,这样就只能计数 3次。而四倍频的方法是测量A相和B相编码器的上升沿和下降沿。这样在同样的时间内,可以计数12次(3个1234的循环)。这就是软件四倍频的原理。

4.定时器计时方式

如下图,当选择在TI1和TI2处均计数,此时就可以进行软件四倍频了。下图给出了定时器递增和递减的所有结果。
在这里插入图片描述
当选择,定时器向上计数,TI1和TI2处均计数,TI1FP1与TI1FP2不反向(TI1FP1 和 TI2FP2 是进行输入滤波器和极性选择后 TI1 和 TI2 的信号),此时计数器计时工作如下
在这里插入图片描述
从而读取计数器值可以换算得到转速,读取计数器技术方向可以得到电机转向。

二、使用CubeMX建立工程

这里只说明关于定时器部分的配置介绍,其他外设配置说明见往期文章
在这里插入图片描述

将定时器1的Combined Channels选择为Encoder mode。
预分频PSC为0(转载时自动加1,PSC=1表示不分频),因为此时定时器相当为输入捕获模式,定时器的频率就为编码器AB相时序的频率,所有不需要分频。
选择向上计数
重装载值为30000,在这里可以理解为定时器计数上限。只要保证在计数时间内计数不到达到这个上限即可
CKD是的AB相时序的频率与数字滤波器所使用的采样频率 之间的分频比,这里不分频,表示采样频率就是编码器AB相时序的频率。
Repetition Counter (RCR- 8 bits value)表示重复计数器,意思就是当计数器可以计数(RCR+1)个ARR,只有高级定时器才具有此功能,这里设置为0,不需要。
不使能计数自动重装载,程序控制归0。
在这里插入图片描述
Encoder Mode设置为Encoder Mode TI1 and TI2,表示在TI1和TI2处均计数,使用软件四倍频。
Polarity选择Rising Edge,这里不是字面上升沿的意思,如图,在编码器模式下Rising Edge表示不反相,Falling Edge 表示反相。
在这里插入图片描述
不反相的计数方式为1.4章节的第二张图,反相的计数方式如下图,这里选择不反相。
在这里插入图片描述
Prescaler Division Ratio 选择不分频输出

Input Filter 选择不滤波,我前面关于滤波器的解释不够容易理解,这里重新说明一下,设置Input Filter会改变采样频率,具体如图所示。
在这里插入图片描述
Fsampling 为采样频率,Fck_int 为时钟频率,Fdts为经过分频后的Fck_int ,当CKD为0时,Fdts=Fck_int
假如当Input Filter设置为2时,表示在采样频率为Fck_int下,连续4次采样都为高电平(低电平)才输出高电平(低电平),如果连续4次采样中有高有低,则保持上一次的输出,不改变输出状态。
使用的话,好处就是可以消抖,比如当Input Filter设置为2时,高电平持续时间小于4/Fck_int,也就是小于4个采样周期时间,则会被滤除,故达到消抖。坏处就是可能会滤除需要的信息,比如我需要采集的时序高电平持续时间极短,小于4/Fck_int,就会被滤除。所以要根据实际情况选择,这里可以设置为1,小小滤波一下。

我这里使用了两个电机,TIM4用于读取第二个电机编码器时序,所以TIM4与TIM1同样配置,如果只有一个电机TIM4可以不设置。
将TIM3的CH3和CH4设置为PWM输出模式,分别对应两个电机的PWM通道。关于PWM输出的详细见第五讲。这里只给出图片,不进行解释。
在这里插入图片描述
在这里插入图片描述
需要注意的一点就是,PSC和ARR的取值,电机手册上会写它的脉宽调制频率范围,我这里设置为21KHz,我把ARR设置为100是为了便于计算占空比。
接着设置TIM5,设置成25ms中断一次
在这里插入图片描述
定时器的设置就完成了,我这里还设置了USART串口,是为了蓝牙通讯和上位机通讯,往期文章有过介绍,这里不再重复介绍。


keil工程代码编写

tim.c

使能定时器1的CH1和CH2通道的比较捕获功能和定时器计数使能

  /* USER CODE BEGIN TIM1_Init 2 */
	LL_TIM_CC_EnableChannel(TIM1,LL_TIM_CHANNEL_CH1);
	LL_TIM_CC_EnableChannel(TIM1,LL_TIM_CHANNEL_CH2);
	LL_TIM_EnableCounter(TIM1);

  /* USER CODE END TIM1_Init 2 */

TIM4与TIM1同样初始化

  /* USER CODE BEGIN TIM4_Init 2 */
	LL_TIM_CC_EnableChannel(TIM4,LL_TIM_CHANNEL_CH1);
	LL_TIM_CC_EnableChannel(TIM4,LL_TIM_CHANNEL_CH2);
	LL_TIM_EnableCounter(TIM4);
  /* USER CODE END TIM4_Init 2 */

初始化TIM3

  /* USER CODE BEGIN TIM3_Init 2 */
  LL_TIM_EnableAllOutputs(TIM3);//使能定时器输出
  LL_TIM_EnableCounter(TIM3);   //使能计数
  LL_TIM_CC_EnableChannel(TIM3,LL_TIM_CHANNEL_CH3);  //使能输出比较通道
	LL_TIM_CC_EnableChannel(TIM3,LL_TIM_CHANNEL_CH4);  //使能输出比较通道
  /* USER CODE END TIM3_Init 2 */

初始化TIM5

  /* USER CODE BEGIN TIM5_Init 2 */
	LL_TIM_EnableCounter(TIM5);  //使能计数
	LL_TIM_EnableIT_UPDATE(TIM5);  //使能更新中断


  /* USER CODE END TIM5_Init 2 */

注意LL库生成的定时器初始化会警告,处理见第五讲

usart.c

开启UART5的数据非空中断

  /* USER CODE BEGIN UART5_Init 2 */
	LL_USART_EnableIT_RXNE(UART5);

  /* USER CODE END UART5_Init 2 */

stm32f4xx.c

每次进入TIM5的更新中断就表示过了25ms,在中断中设置的占空比与电机本身的占空比做比较,然后每25ms该变化1%,使电机本身的占空比渐变的成为设置的占空比,如果直接将设置的占空比写入,会因为与电机本身的占空比相差过大,导致突变,对电机损害极大。
同时开始每4次计数开启编码器读取,则100ms内读取一次,读取前先关闭定时器计数,然后读取两个电机的编码器计数和方向,读取完将计数贵0,最后重新开启定时器计数。

/* USER CODE BEGIN TD */
unsigned char UART5_RXNE = 0,BLE_RX;
unsigned char pwm_left_new,pwm_left_now;
unsigned char pwm_right_new,pwm_right_now;
unsigned char coder_t = 0;
unsigned int tim1_cnt,tim4_cnt;
unsigned char tim1_cnt_direction,tim4_cnt_direction;
/* USER CODE END TD */
void TIM5_IRQHandler(void)
{
  /* USER CODE BEGIN TIM5_IRQn 0 */
	static u8 coder_t_i=0;
	if(LL_TIM_IsActiveFlag_UPDATE(TIM5)==SET)//25ms
	{
		LL_TIM_ClearFlag_UPDATE(TIM5);
		coder_t_i++;
		if(coder_t_i==4)//编码器每100ms读取一次
		{
			coder_t_i = 0;
			LL_TIM_DisableCounter(TIM1);
			LL_TIM_DisableCounter(TIM4);
			
			coder_t = 1;
			tim1_cnt = LL_TIM_GetCounter(TIM1);//获取编码器计数器值
			tim1_cnt_direction = LL_TIM_GetDirection(TIM1);//获取编码器计数方向
			tim4_cnt = LL_TIM_GetCounter(TIM4);
			tim4_cnt_direction = LL_TIM_GetDirection(TIM4);
			LL_TIM_SetCounter(TIM1,0);//清空定时器计数值
			LL_TIM_SetCounter(TIM4,0);
			
			LL_TIM_EnableCounter(TIM1);
			LL_TIM_EnableCounter(TIM4);
		}
		if(pwm_left_new>pwm_left_now)
		{
			pwm_left_now++;
			LL_TIM_OC_SetCompareCH3(TIM3,pwm_left_now);
		}
		else if(pwm_left_new<pwm_left_now)
		{
			pwm_left_now--;
			LL_TIM_OC_SetCompareCH3(TIM3,pwm_left_now);
		}
		
		if(pwm_right_new>pwm_right_now)
		{
			pwm_right_now++;
			LL_TIM_OC_SetCompareCH4(TIM3,pwm_right_now);
		}
		else if(pwm_right_new<pwm_right_now)
		{
			pwm_right_now--;
			LL_TIM_OC_SetCompareCH4(TIM3,pwm_right_now);
		}
	}

  /* USER CODE END TIM5_IRQn 0 */
  /* USER CODE BEGIN TIM5_IRQn 1 */

  /* USER CODE END TIM5_IRQn 1 */
}

当接收到蓝牙指令后,在中断将指令保存在BLE_RX 中,并打开UART5_RXNE 标志位

void UART5_IRQHandler(void)
{
  /* USER CODE BEGIN UART5_IRQn 0 */
	if(LL_USART_IsActiveFlag_RXNE(UART5))
	{
		LL_USART_ClearFlag_RXNE(UART5);
		BLE_RX = LL_USART_ReceiveData8(UART5);
		UART5_RXNE = 1;
	}
  /* USER CODE END UART5_IRQn 0 */
  /* USER CODE BEGIN UART5_IRQn 1 */

  /* USER CODE END UART5_IRQn 1 */
}

电机运行逻辑如图所示
在这里插入图片描述

main.c

首先先拉低控制引脚

  /* USER CODE BEGIN 2 */
	LL_GPIO_ResetOutputPin(motor_ina1_GPIO_Port,motor_ina1_Pin);
	LL_GPIO_ResetOutputPin(motor_inb1_GPIO_Port,motor_inb1_Pin);
	LL_GPIO_ResetOutputPin(motor_ina2_GPIO_Port,motor_ina2_Pin);
	LL_GPIO_ResetOutputPin(motor_inb2_GPIO_Port,motor_inb2_Pin);
  /* USER CODE END 2 */

接着在while里面针对蓝牙输入的指令BLE_RX去改变电机的运行状态,并且每次以10%的占空比为基准进行加减,然后对占空比设置一个上限。
如果是正转,将编码器定时器计数的值10就是1s内计数器脉冲个数,再/(134)就是1s内码盘转圈个数,再/5.18就是1s内电机转圈个数,从而得到电机r/s。
如果是反转,要用定时器重装载值减去编码器定时器计数的值,得到的才为编码器脉冲个数。
代码如下

while (1)
  {
		if(UART5_RXNE)
		{
			UART5_RXNE = 0;
			switch(BLE_RX)
			{
				case 0x31: 
				{
					LL_GPIO_ResetOutputPin(motor_ina1_GPIO_Port,motor_ina1_Pin);
					LL_GPIO_SetOutputPin(motor_inb1_GPIO_Port,motor_inb1_Pin);
					
					LL_GPIO_ResetOutputPin(motor_ina2_GPIO_Port,motor_ina2_Pin);
					LL_GPIO_SetOutputPin(motor_inb2_GPIO_Port,motor_inb2_Pin);
					pwm_left_new += 10;
					pwm_right_new += 10;
					printf("前进\r\n"); 
				}; break;
				case 0x32: 
				{
					pwm_left_new = 0;
					pwm_right_new = 0;
					printf("停止\r\n"); 
				}; break;
				case 0x33: 
				{
					LL_GPIO_SetOutputPin(motor_ina1_GPIO_Port,motor_ina1_Pin);
					LL_GPIO_ResetOutputPin(motor_inb1_GPIO_Port,motor_inb1_Pin);
					
					LL_GPIO_SetOutputPin(motor_ina2_GPIO_Port,motor_ina2_Pin);
					LL_GPIO_ResetOutputPin(motor_inb2_GPIO_Port,motor_inb2_Pin);
					pwm_left_new += 10;
					pwm_right_new += 10;
					printf("后退\r\n"); 
				}; break;
				default:
					break;
			}
			BLE_RX=0;
			printf("左轮PWM:%d   ",pwm_left_new);
			printf("右轮PWM:%d \n",pwm_right_new);
			if(pwm_left_new>=100)
			{
				pwm_left_new = 100;
				printf("左轮已最大转速 \n");
			}
			if(pwm_right_new>=100)
			{
				pwm_right_new = 100;
				printf("右轮已最大转速 \n");
			}
			if(tim1_cnt_direction==0)
			{
				printf("左轮正转,转速:%.2f R/s  ",tim1_speed);
			}
			else if(tim1_cnt_direction==1)
			{
				printf("左轮反转,转速:%.2f R/s  ",tim1_speed);
			}
			
			if(tim4_cnt_direction==0)
			{
				printf("右轮正转,转速:%.2f R/s\n",tim4_speed);
			}
			else if(tim4_cnt_direction==1)
			{
				printf("右轮反转,转速:%.2f R/s\n",tim4_speed);
			}
		}
		if(coder_t)
		{
			coder_t = 0;
			if(tim1_cnt_direction==LL_TIM_COUNTERDIRECTION_UP)
			{
				tim1_speed = tim1_cnt*10/(13*5.18*4);//计算转速,单位为转每秒
				printf("左轮正转,转速:%.2f R/s  ",tim1_speed);
			}
			else if(tim1_cnt_direction==LL_TIM_COUNTERDIRECTION_DOWN)
			{
				tim1_speed = (30000-tim1_cnt)*10/(13*5.18*4);//计算转速,单位为转每秒
				printf("左轮反转,转速:%.2f R/s  ",tim1_speed);
			}
			
			if(tim4_cnt_direction==LL_TIM_COUNTERDIRECTION_UP)
			{
				tim4_speed = tim4_cnt*10/(13*5.18*4);
				printf("右轮正转,转速:%.2f R/s\n",tim4_speed);
			}
			else if(tim4_cnt_direction==LL_TIM_COUNTERDIRECTION_DOWN)
			{
				tim4_speed = (30000-tim4_cnt)*10/(13*5.18*4);
				printf("右轮反转,转速:%.2f R/s\n",tim4_speed);
			}
		}
		
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }

四、效果展示

定时器编码器模式+电机控制


此效果与本文代码实现效果有些差别,仅供参考

五、工程下载

链接:https://pan.baidu.com/s/1sSsGl7viutpDtkUsIqhMuQ?pwd=1234
提取码:1234

结言

时间匆忙,有错误之处还望大家指出,(可能有些错别字,不影响阅读就好)

  • 38
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: stm32f407是一款强大的ARM Cortex-M4处理器,可以用来驱动四路编码器电机。驱动四路编码器电机需要控制接口和编码器读取接口。 在stm32f407上,我们可以使用GPIO端口配置来控制电机的方向和速度。通过配置GPIO的电平可以选择电机的正反转,通过PWM信号控制电机的速度。我们可以使用4个GPIO端口来控制四个电机的运行方向,另外4个PWM输出通道来控制电机的速度。 另外,stm32f407上有多个定时器(TIM)模块,可以用来统计编码器的脉冲以获取电机的转速和位置信息。我们可以选择其中一个定时器来计数编码器脉冲,并通过定时器的中断来获取编码器读取的结果。 为了实现对四路编码器电机的驱动,我们可以进行如下步骤: 1. 配置GPIO端口:将4个GPIO端口设置为输出模式,用于控制电机的方向。 2. 配置PWM输出:使用4个PWM输出通道作为电机的速度控制信号。 3. 初始化定时器:选择一个定时器模块,并配置为编码器模式。设置编码器的计数方向和分辨率。 4. 启动定时器:开始计数器计数和编码器脉冲的统计。 5. 设置速度和方向:根据需要,通过改变GPIO端口的电平来控制电机的方向,通过改变PWM输出的占空比来控制电机的转速。 6. 读取编码器数据:在定时器中断中,读取编码器的计数值。根据计数值的变化来确定电机的转速和位置。 通过以上步骤,我们可以实现对stm32f407上四路编码器电机的驱动。这样,我们可以通过控制GPIO端口和PWM输出来控制电机的方向和速度,并通过定时器模块读取编码器的数据来获取电机的转速和位置信息。 ### 回答2: 要驱动四路编码器电机,可以使用STM32F407单片机来实现。首先,确保STM32F407的引脚数量足够连接四个编码器电机,通常会选择具有多个定时器通道和编码器模式支持的引脚。 步骤如下: 1. 确定需要连接的四个编码器电机的引脚和它们的功能,例如编码器A相、B相和电机的PWM控制引脚。 2. 配置STM32F407的GPIO引脚为输入或输出。通过设置相应的寄存器来实现这一点,必要时可以使用外部中断或者输入捕捉功能来检测编码器电机的旋转变化。 3. 配置STM32F407的定时器通道来生成PWM信号。使用TIM_Init函数初始化定时器,并设置PWM模式以及相关的计时周期和占空比。 4. 在主循环中,读取编码器的A相和B相输入,并根据其状态进行旋转方向判断和计数器增减相应的值。可以使用GPIO_ReadInputDataBit函数来获取引脚的状态。 5. 使用Pulse Width Modulation (PWM) 控制引脚输出对应的PWM信号。通过改变PWM的占空比可以改变电机馈电控制信号的强度和方向。 通过实现以上步骤,就可以使用STM32F407来驱动四路编码器电机了。根据具体的编码器和电机参数,还可以进一步优化和调整驱动电路,以满足具体应用的要求。 ### 回答3: 要驱动STM32F407上的四路编码器电机,首先需要连接编码器STM32F407的GPIO引脚。每个编码器通常有两个引脚(A相和B相),用于测量电机转动的方向和速度。这些引脚需要被配置为输入模式。 在代码中,我们可以使用STM32 HAL来初始化GPIO引脚,并为编码器配置中断。中断可以被触发以检测A相和B相引脚的状态变化。 接下来,我们需要编写中断处理函数来处理编码器状态变化时的事件。我们可以通过检测A相和B相引脚的状态来确定电机转动的方向,并根据编码器的脉冲数来计算电机的速度。 在主循环中,我们可以根据需要控制四个电机的速度和方向。我们可以使用PWM信号来控制电机的转速。通过调整PWM的占空比,可以改变电机的转速。同时,我们可以使用另外两个GPIO引脚来控制电机的方向,使其正转或反转。 在编写代码时,需要考虑到编码器输入的稳定性和抗干扰能力。我们可以使用软件去抖动技术来稳定编码器输入,并使用滤波器来降低噪音干扰。 最后,为了避免电机超载和保护电路,我们可以使用限流器和过载保护电路来监测电机的电流和温度。在电机超载时,我们可以采取适当的措施,如降低电机速度或停止电机转动,以防止电机损坏。 总之,驱动STM32F407上的四路编码器电机需要连接编码器到GPIO引脚,配置中断和编写相关的中断处理函数,使用PWM信号控制电机速度,使用GPIO引脚控制电机方向,并实施稳定性和保护措施来确保电机的正常运行。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值