STM32之增量式编码器电机测速
- 编码器
-
- STM32的编码器模式
- 编码器模式
- 编码器的计数方向
- 仅在TI1计数
-
- 仅在TI2计数
- 在TI1和TI2上均计数
-
- 编码器计数实例
- TI1FP1和TI1FP2极性不反相:Rising
- TI1FP1和TI1FP2极性反相:Falling
- 正交式(霍尔传感器测速码盘)编码器电机测速
- 模块介绍
- TB6612FNG电机驱动模块
-
- JGB37-520编码器电机
- 接线
- TB6612FNG模块与STM32板子接线
- TB6612FNG模块与520电机接线
- 520电机与STM32板子接线
- STM32CubeMX相关配置
- 配置SYS
- 配置RCC
- 配置GPIO
- 配置USART1
- 配置定时器2、3、4
- 配置NVIC
- 使用Micro库
- 文件编写
- 添加文件motor.c
- 添加文件motor.h
- 修改文件usart.c
- 修改文件usart.h
- 修改文件tim.c
- main.c文件编写
编码器
编码器,是一种用来测量机械旋转或位移的传感器。它能够测量机械部件在旋转或直线运动时的位移位置或速度等信息,并将其转换成一系列电信号。
.
编码器种类
按监测原理分类
光电编码器
光电编码器,是一种通过光电转换将输出轴上的机械几何位移量转换成脉冲或数字量的传感器。这是目前应用最多的传感器,光电编码器是由光源、光码盘和光敏元件组成。
![在这里插入图片描述](https://img-blog.csdnimg.cn/7dd8482baf7544ffa200dcea77c1c78f.png)
- 光栅盘是在一定直径的圆板上等分地开通若干个长方形孔。由于光电码盘与电动机同轴,电动机旋转时,光栅盘与电动机同速旋转,经发光二极管等电子元件组成的检测装置检测输出若干脉冲信号,通过计算每秒光电编码器输出脉冲的个数就能反映当前电动机的转速。
- 为了判断旋转方向,码盘还可提供相位相差90°的两路脉冲信号。
.
霍尔编码器
霍尔编码器是一种通过磁电转换将输出轴上的机械几何位移量转换成脉冲或数字量的传感器。![在这里插入图片描述](https://img-blog.csdnimg.cn/c0878a0071c043838c4a99a11b2292ef.png)
按输出信号分类
增量式编码器
增量式编码器也称正交式编码器,是将设备运动时的位移信息变成连续的脉冲信号,脉冲个数表示位移量的大小。
特点:
-
只有当设备运动时才会输出信号。
-
一般会输出通道A和通道B 两组信号,并且有90° 的相位差(1/4个周期),同时采集这两组信号就可以计算设备的运动速度和方向。
-
如下图,通道A和通道B的信号的周期相同,且相位相差1/4个周期,结合两相的信号值:
当B相和A相先是都读到高电平(1 1),再B读到高电平,A读到低电平(1 0),则为顺时针转
当B相和A相先是都读到低电平(0 0),再B读到高电平,A读到低电平(1 0),则为逆时针转
-
除通道A、通道B 以外,还会设置一个额外的通道Z 信号,表示编码器特定的参考位置
-
如下图,传感器转一圈后Z 轴信号才会输出一个脉冲,在Z轴输出时,可以通过将AB通道的计数清零,实现对码盘绝对位置的计算。
-
增量式编码器只输出设备的位置变化和运动方向,不会输出设备的绝对位置。
![在这里插入图片描述](https://img-blog.csdnimg.cn/73ad586de1c04b3cac3a3ce973cc94fa.png)
.
绝对式编码器
绝对式编码器在总体结构上与增量式比较类似,都是由码盘、检测装置和放大整形电路构成,但是具体的码盘结构和输出信号含义不同。它是将设备运动时的位移信息通过二进制编码的方式(特殊的码盘)变成数字量直接输出。
特点:
-
其码盘利用若干透光和不透光的线槽组成一套二进制编码,这些二进制码与编码器转轴的每一个不同角度是唯一对应的。
-
绝对式编码器的码盘上有很多圈线槽,被称为码道,每一条(圈)码道内部线槽数量和长度都不同。它们共同组成一套二进制编码,一条(圈)码道对应二进制数的其中一个位(通常是码盘最外侧的码道表示最低位,最内侧的码道表示最高位)。
-
码道的数量决定了二进制编码的位数,一个绝对式编码器有N 条码道,则它输出二进制数的总个数是2的N次方个。
-
读取这些二进制码就能知道设备的绝对位置,所以称之为绝对式编码器。 编码方式一般采用自然二进制、格雷码或者BCD 码等。
自然二进制的码盘易于理解,但当码盘的制造工艺有误差时,在两组信号的临界区域,所有码道的值可能不会同时变化,或因为所有传感器检测存在微小的时间差,导致读到错误的值。比如从000跨越到111,理论上应该读到111,但如果从内到外的3条码道没有完全对齐,可能会读到如001或其它异常值。
格雷码的(相邻的两个2进制数只有1个位不同)码盘可以避免二进制码盘的数据读取异常,因为格雷码码盘的相邻两个信号组只会有1位的变化,就算制造工艺有误差导致信号读取有偏差,最多也只会产生1个偏差(相邻信号的偏差)
![在这里插入图片描述](https://img-blog.csdnimg.cn/877d570ab3824580bf3ab02e919b2ea0.png)
.
编码器参数
分辨率
分辨率是指编码器能够分辨的最小单位。
- 对于增量式编码器,其分辨率表示为编码器转轴旋转一圈所产生的脉冲数,即脉冲数/转(Pulse Per Revolution或PPR)。码盘上透光线槽的数目其实就等于分辨率,也叫多少线。
- 对于绝对式编码器,内部码盘所用的位数就是它的分辨率,单位是位(bit),具体还分单圈分辨率和多圈分辨率。
.
精度
精度是指编码器每个读数与转轴实际位置间的最大误差,通常用角度、角分或角秒来表示。
- 精度由码盘刻线加工精度、转轴同心度、材料的温度特性、电路的响应时间等各方面因素共同决定。
- 例如有些绝对式编码器参数表里会写±20′′,这个就表示编码器输出的读数与转轴实际位置之间存在正负20 角秒的误差。
.
最大响应频率
最大响应频率是指编码器每秒输出的脉冲数,单位是Hz。
- 计算公式:最大响应频率 = 分辨率 * 轴转速 / 60
.
信号输出形式
- 对于增量式编码器,每个通道的信号独立输出,输出电路形式通常有集电极开路输出、推挽输出、差分输出等。
- 对于绝对式编码器,由于是直接输出几十位的二进制数,为了确保传输速率和信号质量,一般采用串行输出或总线型输出,例如同步串行接口(SSI)、RS485、CANopen或EtherCAT 等,也有一部分是并行输出,输出电路形式与增量式编码器相同。
.
编码器倍频
增量式编码器输出的脉冲波形一般为占空比50% 的方波,通道A 和B 相位差为90°。
- 如果只使用通道A(或通道B)计数,并且只捕获通道A(或通道B)的上升沿或下降沿,即为1倍频(没有倍频)。
- 如果只使用通道A(或通道B)计数,并且捕获了通道A(或通道B)的上升沿和下降沿,则编码器转一圈的计数值翻倍,实现2倍频。
- 如果既使用通道A计数,又使用通道B计数,而且都捕获了上升沿和下降沿,则实现了4倍频。
![在这里插入图片描述](https://img-blog.csdnimg.cn/ed6a16ac131544a996f9a1611db9bfd3.png)
如果某个增量式编码器的分辨率为11PPR,能分辨的最小角度为0.6°,那么经过4倍频后它的分辨率能提高到44PPR,能分辨的最小角度为0.15°,即编码器进行倍频能提高其精度。
.
STM32的编码器模式
在STM32中,高级定时器TIM1、TIM8和通用定时器TIM2、TIM3、TIM4、TIM5都提供编码器接口模式,不过该模式是适用于增量式编码器的。
.
编码器模式
- 模式1:计数器仅在TI1的边沿处计数。(2倍频)
- 模式2:计数器仅在TI2的边沿处计数。(2倍频)
- 模式3:计数器既在TI1的边沿处计数,又在TI1的边沿处计数。(4倍频)
.
编码器的计数方向
编码器模式下,计数器的计数方向(递增计数还是递减计数)会根据增量编码器的速度和方向自动进行修改,因此,其计数值始终表示编码器的位置。计数方向对应于所连传感器的旋转方向。![在这里插入图片描述](https://img-blog.csdnimg.cn/c7b6862cd8a3489eb5af3afaccf22c18.png)
STM32 的编码器接口在计数的时候,并不是单纯采集某一通道信号的上升沿或下降沿,而是需要综合另一个通道信号的电平。
.
TI1 <–> 通道A
.
TI2 <–> 通道B
仅在TI1计数
电机正转,向上计数。
假定电机正转时,编码器的通道A的信号比通道B提前1/4个周期(也即相位提前90度),在通道A的上升沿与下降沿均计数,因为计数的方向代表的电机转动的方向,所以,在正转的情况下:
- 通道A上升沿,通道B为低电平,向上计数,代表电机正转。
- 通道A下降沿,通道B为高电平,向上计数,代表电机正转。
![在这里插入图片描述](https://img-blog.csdnimg.cn/41be73cb953741fe9aa82739f67969c8.png)
.
电机反转,向下计数。
假定电机反转时,编码器的通道A的信号比通道B滞后1/4个周期(也即相位滞后90度),在通道A的上升沿与下降沿均计数,因为计数的方向代表的电机转动的方向,所以,在反转的情况下:
- 通道A下降沿,通道B为低电平,向下计数,代表电机反转。
- 通道A上升沿,通道B为高电平,向下计数,代表电机反转。
![在这里插入图片描述](https://img-blog.csdnimg.cn/f935678380b84f4f93ad8fecaab2b0b8.png)
.
仅在TI2计数
同理与仅在TI1计数,不多赘述。
.
在TI1和TI2上均计数
电机正转,向上计数。
假定电机正转时,编码器的通道A的信号比通道B提前1/4个周期(也即相位提前90度),在通道A和通道B的上升沿与下降沿均计数,因为计数的方向代表的电机转动的方向,所以在正转的情况下:
- 通道A上升沿,通道B为低电平,向上计数,代表电机正转。
- 通道B上升沿,通道A为高电平,向上计数,代表电机正转。
- 通道A下降沿,通道B为高电平,向上计数,代表电机正转。
- 通道B下降沿,通道A为高电平,向上计数,代表电机正转。
![在这里插入图片描述](https://img-blog.csdnimg.cn/c58dcf45873f49b9942878510c6a92c3.png)
.
电机反转,向下计数。
假定电机反转时,编码器的通道A的信号比通道B滞后1/4个周期(也即相位滞后90度),在通道A和通道B的上升沿与下降沿均计数,因为计数的方向代表的电机转动的方向,所以在反转的情况下:
- 通道A上升沿,通道B为高电平,向下计数,代表电机反转。
- 通道A下降沿,通道B为高电平,向下计数,代表电机反转。
- 通道B上升沿,通道A为低电平,向下计数,代表电机反转。
- 通道B下降沿,通道A为高电平,向下计数,代表电机反转。
![在这里插入图片描述](https://img-blog.csdnimg.cn/f2fac4ecd3e74ab6b130f7a14e538963.png)
编码器计数实例
TI1FP1和TI1FP2极性不反相:Rising
- 电机正转时,编码器的通道A(TI1)的信号超前通道B(TI2),计数器向上计数。
- 电机反转时,编码器的通道A(TI1)的信号滞后通道B(TI2),计数器向下计数。
![在这里插入图片描述](https://img-blog.csdnimg.cn/7bbf3b2418a645b3a3fa987e3d2e58cb.png)
.
TI1FP1和TI1FP2极性反相:Falling
极性反相可以改变计数器计数方向。
- 电机正转时,编码器的通道A(TI1)的信号超前通道B(TI2),计数器向下计数。
- 电机反转时,编码器的通道A(TI1)的信号滞后通道B(TI2),计数器向上计数。
![在这里插入图片描述](https://img-blog.csdnimg.cn/daf1cac5025843ee8f95cf611002921a.png)
正交式(霍尔传感器测速码盘)编码器电机测速
使用TB6612FNG电机驱动模块来驱动电机,控制电机的正转、反转、制动、停止等功能,利用STM32的定时器作为编码器模式来获取电机在单位时间内产生的脉冲数来达到电机测速的目的。
.
模块介绍
TB6612FNG电机驱动模块
![在这里插入图片描述](https://img-blog.csdnimg.cn/39d55b8d2e484553b7430dee985b4a29.png)
TB6612FNG是双驱动,也就是可以驱动两个电机,VM外接12V左右电源,GND连接一个即可。
.
真值表
输入 | 输出 |
---|
IN1 | IN2 | PWM | STBY | O1 | O2 | 电机状态 |
High | Low | High | High | High | Low | 正转 |
Low | High | High | High | Low | High | 反转 |
High | High | x | High | Low | Low | 刹车 |
Low | High | Low | High | Low | Low |
High | Low | Low | High | Low | Low |
Low | Low | High | High | OFF | 自由刹车 |
x | x | x | Low | OFF | 待机 |
- x:可以是High,也可以是Low。
- 刹车:电机停止时,通过提供电源来将电机锁定在当前位置。这样可以防止电机继续旋转,也可以提供一定的力矩来抵消负载。
- 自由刹车:将电机锁定在当前位置,不允许电机旋转,但也不提供额外的电源以保持电机的当前位置。
- 待机:该模块不工作。
.
电机控制
- 如果PWM引脚给予一个高电平,那么通过IN1和IN2引脚就能控制电机的四个状态(电机旋转时为全速状态)。
- 如果PWM引脚给予PWM波(有效电平为高电平,实测80kHz好用),那么可以通过PWM波的占空比大小来控制电机旋转时的速度,同样通过IN1和IN2引脚就能控制电机的四个状态。
.
JGB37-520编码器电机
![在这里插入图片描述](https://img-blog.csdnimg.cn/9c19d9a48a1d404fa20d1da651619ab3.png)
JGB37-520编码器电机(AB相增量式霍尔编码器)是一款直流减速电机,使用的是一款霍尔传感器测速码盘,配有 11 线强磁码盘(即编码器线数为11ppr),减速比为1:30(即减速前转速为330rpm)。在电机旋转一圈单相输出11个脉冲,在四倍频情况下电机旋转一圈输出11 * 30 * 4 = 1320个计数。
.
接线
TB6612FNG模块与STM32板子接线
- PB3 <-> STBY
- PB4 <-> AIN1
- PB5 <-> AIN2
- PA6(定时器3通道1输出PWM波) <-> PWMA
- 12V左右外接电源 <-> VM
- 单片机5V或3.3V <-> VCC
- GND <-> GND(连接一个即可)
.
TB6612FNG模块与520电机接线
- M+(M1) <-> AO1
- M-(M2) <-> AO2
.
520电机与STM32板子接线
- PA0(定时器2通道1,编码器模式) <-> A相
- PA1(定时器2通道2,编码器模式) <-> B相
- 单片机5V或3.3V <-> VCC
- GND <-> GND
.
STM32CubeMX相关配置
配置SYS
![在这里插入图片描述](https://img-blog.csdnimg.cn/49f4bf59669d46f08c658cbc681cf7c9.png)
.
配置RCC
![在这里插入图片描述](https://img-blog.csdnimg.cn/0fb6927ebbf74d068dc1ba70cd0cb58e.png)
![在这里插入图片描述](https://img-blog.csdnimg.cn/5d017ea6d3034a7b8aeb5ff17c3faf97.png)
.
配置GPIO
- PB3引脚配置成输出高电平模式。
- PB4引脚配置成输出高电平模式。
- PB5引脚配置成输出高电平模式。
![在这里插入图片描述](https://img-blog.csdnimg.cn/d03af0104f0047b9a058d999232f7e83.png)
.
配置USART1
![在这里插入图片描述](https://img-blog.csdnimg.cn/d4dac6f9e35945528e38244417561118.png)
.
配置定时器2、3、4
- 配置定时器2为编码器模式。
- 配置定时器3通道1输出PWM波(80kHZ好用)。
- 配置定时器4定时1s产生溢出中断。
.
配置定时器2为编码器模式。![在这里插入图片描述](https://img-blog.csdnimg.cn/7d38b320517b47d788eff2e2e1b289cc.png)
![在这里插入图片描述](https://img-blog.csdnimg.cn/1ca8eed6e3f04b4a834c3d88b12d8395.png)
.
.
配置定时器3通道1输出PWM波(80kHZ好用)。
![在这里插入图片描述](https://img-blog.csdnimg.cn/0325f9804b80402ba0651db9911192d0.png)
![在这里插入图片描述](https://img-blog.csdnimg.cn/9d9f4c90af4e49cf8fb50b864ee455e8.png)
.
.
配置定时器4定时1s产生溢出中断。![在这里插入图片描述](https://img-blog.csdnimg.cn/0780cbc331ec4c37b804d5724742b71f.png)
.
配置NVIC
打开定时器4中断和USART1中断。
![在这里插入图片描述](https://img-blog.csdnimg.cn/abb7fb3c5f3140a39e03829ebd848d80.png)
.
使用Micro库
只要映射了printf用来发送数据去串口都要使用这个库。
![在这里插入图片描述](https://img-blog.csdnimg.cn/66db4ef2c1b4423c8cf87b3b9e5cc713.png)
.
文件编写
添加文件motor.c
#include "gpio.h"
#include "motor.h"
#define STBY_pin GPIO_PIN_3
#define AIN1_pin GPIO_PIN_4
#define AIN2_pin GPIO_PIN_5
#define STBY(x) do{ x ? HAL_GPIO_WritePin(GPIOB, STBY_pin, GPIO_PIN_SET) : HAL_GPIO_WritePin(GPIOB, STBY_pin, GPIO_PIN_RESET); }while(0)
#define AIN1(x) do{ x ? HAL_GPIO_WritePin(GPIOB, AIN1_pin, GPIO_PIN_SET) : HAL_GPIO_WritePin(GPIOB, AIN1_pin, GPIO_PIN_RESET); }while(0)
#define AIN2(x) do{ x ? HAL_GPIO_WritePin(GPIOB, AIN2_pin, GPIO_PIN_SET) : HAL_GPIO_WritePin(GPIOB, AIN2_pin, GPIO_PIN_RESET); }while(0)
void motor_left_forward()
{
STBY(1);
AIN1(1);
AIN2(0);
}
void motor_left_back()
{
STBY(1);
AIN1(0);
AIN2(1);
}
void motor_left_stop()
{
STBY(1);
AIN1(0);
AIN2(0);
}
.
添加文件motor.h
void motor_left_forward(void);
void motor_left_back(void);
void motor_left_stop(void);
.
修改文件usart.c
#include "usart.h"
#include <stdio.h>
#include <string.h>
#define USART_REC_LEN 200
uint8_t buf = 0;
uint8_t UART1_RX_Buffer[USART_REC_LEN];
uint16_t UART1_RX_STA = 0;
UART_HandleTypeDef huart1;
void MX_USART1_UART_Init(void)
{
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart1) != HAL_OK)
{
Error_Handler();
}
HAL_UART_Receive_IT(&huart1, &buf, 1);
printf("usart1 is ok\r\n");
}
void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(uartHandle->Instance==USART1)
{
__HAL_RCC_USART1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_10;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(USART1_IRQn);
}
}
void HAL_UART_MspDeInit(UART_HandleTypeDef* uartHandle)
{
if(uartHandle->Instance==USART1)
{
__HAL_RCC_USART1_CLK_DISABLE();
HAL_GPIO_DeInit(GPIOA, GPIO_PIN_9|GPIO_PIN_10);
HAL_NVIC_DisableIRQ(USART1_IRQn);
}
}
int fputc(int my_data, FILE *p)
{
unsigned char temp = my_data;
HAL_UART_Transmit(&huart1, &temp, 1, 0xffff);
return my_data;
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART1)
{
if (!(UART1_RX_STA & 0x8000))
{
if (UART1_RX_STA & 0x4000)
{
if (buf == 0x0a)
{
UART1_RX_STA |= 0x8000;
}
else
{
UART1_RX_STA = 0;
}
}
else
{
if (buf == 0x0d)
{
UART1_RX_STA |= 0x4000;
}
else
{
UART1_RX_Buffer[UART1_RX_STA & 0x3ffff] = buf;
UART1_RX_STA++;
if (UART1_RX_STA > USART_REC_LEN - 1)
{
UART1_RX_STA = 0;
}
}
}
}
HAL_UART_Receive_IT(&huart1, &buf, 1);
}
}
void usart1_receive_data_handle()
{
if (UART1_RX_STA & 0x8000)
{
printf("接收完成\r\n");
if (!strcmp((const char *)UART1_RX_Buffer, "haozige"))
{
printf("浩子哥\r\n");
}
else
{
printf("%s\r\n", "输入错误,请重新输入");
}
memset(UART1_RX_Buffer, 0, USART_REC_LEN);
UART1_RX_STA = 0;
}
}
.
修改文件usart.h
.
#ifndef __USART_H__
#define __USART_H__
#ifdef __cplusplus
extern "C" {
#endif
#include "main.h"
extern UART_HandleTypeDef huart1;
void MX_USART1_UART_Init(void);
void usart1_receive_data_handle(void);
#ifdef __cplusplus
}
#endif
#endif
修改文件tim.c
#include "tim.h"
TIM_HandleTypeDef htim2;
TIM_HandleTypeDef htim3;
TIM_HandleTypeDef htim4;
void MX_TIM2_Init(void)
{
TIM_Encoder_InitTypeDef sConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
htim2.Instance = TIM2;
htim2.Init.Prescaler = 0;
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 65535;
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
sConfig.EncoderMode = TIM_ENCODERMODE_TI12;
sConfig.IC1Polarity = TIM_ICPOLARITY_RISING;
sConfig.IC1Selection = TIM_ICSELECTION_DIRECTTI;
sConfig.IC1Prescaler = TIM_ICPSC_DIV1;
sConfig.IC1Filter = 6;
sConfig.IC2Polarity = TIM_ICPOLARITY_RISING;
sConfig.IC2Selection = TIM_ICSELECTION_DIRECTTI;
sConfig.IC2Prescaler = TIM_ICPSC_DIV1;
sConfig.IC2Filter = 6;
if (HAL_TIM_Encoder_Init(&htim2, &sConfig) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
__HAL_TIM_SetCounter(&htim2,0);
__HAL_TIM_ENABLE(&htim2);
HAL_TIM_Encoder_Start(&htim2,TIM_CHANNEL_ALL);
}
void MX_TIM3_Init(void)
{
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_OC_InitTypeDef sConfigOC = {0};
htim3.Instance = TIM3;
htim3.Init.Prescaler = 8;
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 99;
htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
if (HAL_TIM_Base_Init(&htim3) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_PWM_Init(&htim3) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 0;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
if (HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
{
Error_Handler();
}
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
HAL_TIM_MspPostInit(&htim3);
}
void MX_TIM4_Init(void)
{
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
htim4.Instance = TIM4;
htim4.Init.Prescaler = 7199;
htim4.Init.CounterMode = TIM_COUNTERMODE_UP;
htim4.Init.Period = 9999;
htim4.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim4.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
if (HAL_TIM_Base_Init(&htim4) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim4, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim4, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
HAL_TIM_Base_Start_IT(&htim4);
}
void HAL_TIM_Encoder_MspInit(TIM_HandleTypeDef* tim_encoderHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(tim_encoderHandle->Instance==TIM2)
{
__HAL_RCC_TIM2_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
}
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle)
{
if(tim_baseHandle->Instance==TIM3)
{
__HAL_RCC_TIM3_CLK_ENABLE();
}
else if(tim_baseHandle->Instance==TIM4)
{
__HAL_RCC_TIM4_CLK_ENABLE();
HAL_NVIC_SetPriority(TIM4_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(TIM4_IRQn);
}
}
void HAL_TIM_MspPostInit(TIM_HandleTypeDef* timHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(timHandle->Instance==TIM3)
{
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_6;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
}
void HAL_TIM_Encoder_MspDeInit(TIM_HandleTypeDef* tim_encoderHandle)
{
if(tim_encoderHandle->Instance==TIM2)
{
__HAL_RCC_TIM2_CLK_DISABLE();
HAL_GPIO_DeInit(GPIOA, GPIO_PIN_0|GPIO_PIN_1);
}
}
void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef* tim_baseHandle)
{
if(tim_baseHandle->Instance==TIM3)
{
__HAL_RCC_TIM3_CLK_DISABLE();
}
else if(tim_baseHandle->Instance==TIM4)
{
__HAL_RCC_TIM4_CLK_DISABLE();
HAL_NVIC_DisableIRQ(TIM4_IRQn);
}
}
.
main.c文件编写
#include "main.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"
#include <stdio.h>
#include "motor.h"
void SystemClock_Config(void);
short encoder;
short pulse;
float speed;
short get_left_encoder(void)
{
short encoder_cnt;
encoder_cnt = (short)__HAL_TIM_GetCounter(&htim2);
__HAL_TIM_SetCounter(&htim2,0);
return encoder_cnt;
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim4.Instance == TIM4)
{
encoder = get_left_encoder();
pulse = encoder / 4;
speed = (float)encoder / 1320;
printf("encoder: %d\r\n",encoder);
printf("pulse: %d\r\n",pulse);
printf("speed: %0.2f c/s\r\n",speed);
}
}
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_TIM2_Init();
MX_TIM3_Init();
MX_TIM4_Init();
MX_USART1_UART_Init();
__HAL_TIM_SetCompare(&htim3, TIM_CHANNEL_1, 22);
motor_left_back();
while (1)
{
usart1_receive_data_handle();
}
}
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
void Error_Handler(void)
{
__disable_irq();
while (1)
{
}
}
#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t *file, uint32_t line)
{
}
#endif
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)