STM32之增量式编码器电机测速

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文件编写

编码器

编码器,是一种用来测量机械旋转或位移的传感器。它能够测量机械部件在旋转或直线运动时的位移位置或速度等信息,并将其转换成一系列电信号。
.

编码器种类

按监测原理分类

光电编码器

光电编码器,是一种通过光电转换将输出轴上的机械几何位移量转换成脉冲或数字量的传感器。这是目前应用最多的传感器,光电编码器是由光源、光码盘和光敏元件组成。
STM32之增量式编码器电机测速_第1张图片

  • 光栅盘是在一定直径的圆板上等分地开通若干个长方形孔。由于光电码盘与电动机同轴,电动机旋转时,光栅盘与电动机同速旋转,经发光二极管等电子元件组成的检测装置检测输出若干脉冲信号,通过计算每秒光电编码器输出脉冲的个数就能反映当前电动机的转速。
  • 为了判断旋转方向,码盘还可提供相位相差90°的两路脉冲信号。
    .

霍尔编码器

霍尔编码器是一种通过磁电转换将输出轴上的机械几何位移量转换成脉冲或数字量的传感器。STM32之增量式编码器电机测速_第2张图片

  • 霍尔编码器是由霍尔码盘(磁环)和霍尔元件组成。

  • 霍尔码盘是在一定直径的圆板上等分地布置有不同的磁极。霍尔码盘与电动机同轴,电动机旋转时,霍尔元件检测输出若干脉冲信号,为判断转向,一般输出两组存在一定相位差的方波信号。
    .

按输出信号分类

增量式编码器

增量式编码器也称正交式编码器,是将设备运动时的位移信息变成连续的脉冲信号,脉冲个数表示位移量的大小。

特点:

  • 只有当设备运动时才会输出信号。

  • 一般会输出通道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通道的计数清零,实现对码盘绝对位置的计算。

  • 增量式编码器只输出设备的位置变化和运动方向,不会输出设备的绝对位置。
    STM32之增量式编码器电机测速_第3张图片
    .

绝对式编码器

绝对式编码器在总体结构上与增量式比较类似,都是由码盘、检测装置和放大整形电路构成,但是具体的码盘结构和输出信号含义不同。它是将设备运动时的位移信息通过二进制编码的方式(特殊的码盘)变成数字量直接输出。

特点:

  • 其码盘利用若干透光和不透光的线槽组成一套二进制编码,这些二进制码与编码器转轴的每一个不同角度是唯一对应的。

  • 绝对式编码器的码盘上有很多圈线槽,被称为码道,每一条(圈)码道内部线槽数量和长度都不同。它们共同组成一套二进制编码,一条(圈)码道对应二进制数的其中一个位(通常是码盘最外侧的码道表示最低位,最内侧的码道表示最高位)。

  • 码道的数量决定了二进制编码的位数,一个绝对式编码器有N 条码道,则它输出二进制数的总个数是2的N次方个。

  • 读取这些二进制码就能知道设备的绝对位置,所以称之为绝对式编码器。 编码方式一般采用自然二进制、格雷码或者BCD 码等。

     自然二进制的码盘易于理解,但当码盘的制造工艺有误差时,在两组信号的临界区域,所有码道的值可能不会同时变化,或因为所有传感器检测存在微小的时间差,导致读到错误的值。比如从000跨越到111,理论上应该读到111,但如果从内到外的3条码道没有完全对齐,可能会读到如001或其它异常值。
    
     格雷码的(相邻的两个2进制数只有1个位不同)码盘可以避免二进制码盘的数据读取异常,因为格雷码码盘的相邻两个信号组只会有1位的变化,就算制造工艺有误差导致信号读取有偏差,最多也只会产生1个偏差(相邻信号的偏差)
    

STM32之增量式编码器电机测速_第4张图片
.

编码器参数

分辨率

分辨率是指编码器能够分辨的最小单位。

  • 对于增量式编码器,其分辨率表示为编码器转轴旋转一圈所产生的脉冲数,即脉冲数/转(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倍频。
    STM32之增量式编码器电机测速_第5张图片
    如果某个增量式编码器的分辨率为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倍频)
    .

编码器的计数方向

编码器模式下,计数器的计数方向(递增计数还是递减计数)会根据增量编码器的速度和方向自动进行修改,因此,其计数值始终表示编码器的位置。计数方向对应于所连传感器的旋转方向。STM32之增量式编码器电机测速_第6张图片
STM32 的编码器接口在计数的时候,并不是单纯采集某一通道信号的上升沿或下降沿,而是需要综合另一个通道信号的电平。
.

TI1 <–> 通道A
.
TI2 <–> 通道B

仅在TI1计数

电机正转,向上计数。

假定电机正转时,编码器的通道A的信号比通道B提前1/4个周期(也即相位提前90度),在通道A的上升沿与下降沿均计数,因为计数的方向代表的电机转动的方向,所以,在正转的情况下:

  • 通道A上升沿,通道B为低电平,向上计数,代表电机正转。
  • 通道A下降沿,通道B为高电平,向上计数,代表电机正转。
    STM32之增量式编码器电机测速_第7张图片
    .

电机反转,向下计数。

假定电机反转时,编码器的通道A的信号比通道B滞后1/4个周期(也即相位滞后90度),在通道A的上升沿与下降沿均计数,因为计数的方向代表的电机转动的方向,所以,在反转的情况下:

  • 通道A下降沿,通道B为低电平,向下计数,代表电机反转。
  • 通道A上升沿,通道B为高电平,向下计数,代表电机反转。
    STM32之增量式编码器电机测速_第8张图片
    .

仅在TI2计数

同理与仅在TI1计数,不多赘述。
.

在TI1和TI2上均计数

电机正转,向上计数。

假定电机正转时,编码器的通道A的信号比通道B提前1/4个周期(也即相位提前90度),在通道A和通道B的上升沿与下降沿均计数,因为计数的方向代表的电机转动的方向,所以在正转的情况下:

  • 通道A上升沿,通道B为低电平,向上计数,代表电机正转。
  • 通道B上升沿,通道A为高电平,向上计数,代表电机正转。
  • 通道A下降沿,通道B为高电平,向上计数,代表电机正转。
  • 通道B下降沿,通道A为高电平,向上计数,代表电机正转。
    STM32之增量式编码器电机测速_第9张图片
    .

电机反转,向下计数。

假定电机反转时,编码器的通道A的信号比通道B滞后1/4个周期(也即相位滞后90度),在通道A和通道B的上升沿与下降沿均计数,因为计数的方向代表的电机转动的方向,所以在反转的情况下:

  • 通道A上升沿,通道B为高电平,向下计数,代表电机反转。
  • 通道A下降沿,通道B为高电平,向下计数,代表电机反转。
  • 通道B上升沿,通道A为低电平,向下计数,代表电机反转。
  • 通道B下降沿,通道A为高电平,向下计数,代表电机反转。STM32之增量式编码器电机测速_第10张图片

编码器计数实例

TI1FP1和TI1FP2极性不反相:Rising

  • 电机正转时,编码器的通道A(TI1)的信号超前通道B(TI2),计数器向上计数。
  • 电机反转时,编码器的通道A(TI1)的信号滞后通道B(TI2),计数器向下计数。STM32之增量式编码器电机测速_第11张图片
    .

TI1FP1和TI1FP2极性反相:Falling

极性反相可以改变计数器计数方向。

  • 电机正转时,编码器的通道A(TI1)的信号超前通道B(TI2),计数器向下计数。
  • 电机反转时,编码器的通道A(TI1)的信号滞后通道B(TI2),计数器向上计数。STM32之增量式编码器电机测速_第12张图片

正交式(霍尔传感器测速码盘)编码器电机测速

使用TB6612FNG电机驱动模块来驱动电机,控制电机的正转、反转、制动、停止等功能,利用STM32的定时器作为编码器模式来获取电机在单位时间内产生的脉冲数来达到电机测速的目的。
.

模块介绍

TB6612FNG电机驱动模块

STM32之增量式编码器电机测速_第13张图片
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编码器电机

STM32之增量式编码器电机测速_第14张图片
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

STM32之增量式编码器电机测速_第15张图片
.

配置RCC

STM32之增量式编码器电机测速_第16张图片
STM32之增量式编码器电机测速_第17张图片
.

配置GPIO

  • PB3引脚配置成输出高电平模式。
  • PB4引脚配置成输出高电平模式。
  • PB5引脚配置成输出高电平模式。
    STM32之增量式编码器电机测速_第18张图片
    .

配置USART1

STM32之增量式编码器电机测速_第19张图片
.

配置定时器2、3、4

  • 配置定时器2为编码器模式。
  • 配置定时器3通道1输出PWM波(80kHZ好用)。
  • 配置定时器4定时1s产生溢出中断。
    .

配置定时器2为编码器模式。STM32之增量式编码器电机测速_第20张图片STM32之增量式编码器电机测速_第21张图片
.
.
配置定时器3通道1输出PWM波(80kHZ好用)。
STM32之增量式编码器电机测速_第22张图片
STM32之增量式编码器电机测速_第23张图片
.
.
配置定时器4定时1s产生溢出中断。STM32之增量式编码器电机测速_第24张图片
.

配置NVIC

打开定时器4中断和USART1中断。
STM32之增量式编码器电机测速_第25张图片
.

使用Micro库

只要映射了printf用来发送数据去串口都要使用这个库。
STM32之增量式编码器电机测速_第26张图片

.

文件编写

添加文件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

/* USER CODE BEGIN Header */
/**
 ******************************************************************************
 * @file    usart.c
 * @brief   This file provides code for the configuration
 *          of the USART instances.
 ******************************************************************************
 * @attention
 *
 * Copyright (c) 2023 STMicroelectronics.
 * All rights reserved.
 *
 * This software is licensed under terms that can be found in the LICENSE file
 * in the root directory of this software component.
 * If no LICENSE file comes with this software, it is provided AS-IS.
 *
 ******************************************************************************
 */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "usart.h"

/* USER CODE BEGIN 0 */
 
#include 
#include 
 
#define USART_REC_LEN 200
 
// 串口接收缓存(1字节)
uint8_t buf = 0;
 
uint8_t UART1_RX_Buffer[USART_REC_LEN]; // 接收缓冲,串口接收的数据存放地点
 
// 串口接收状态,16位
uint16_t UART1_RX_STA = 0;
// bit15: 如果是1表示接收完成
// bit14: 如果是1表示接收到回车(0x0d)
// bit13~bit0: 接收到的有效字节数目
 
/* USER CODE END 0 */

UART_HandleTypeDef huart1;

/* USART1 init function */

void MX_USART1_UART_Init(void)
{

  /* USER CODE BEGIN USART1_Init 0 */
 
  /* USER CODE END USART1_Init 0 */

  /* USER CODE BEGIN USART1_Init 1 */
 
  /* USER CODE END USART1_Init 1 */
  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();
  }
  /* USER CODE BEGIN USART1_Init 2 */
 
    /* 开启串口1的接收中断 */
    HAL_UART_Receive_IT(&huart1, &buf, 1); /* 每接收一个串口数据调用一次串口接收完成回调函数 */
		printf("usart1 is ok\r\n");
  /* USER CODE END USART1_Init 2 */

}

void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(uartHandle->Instance==USART1)
  {
  /* USER CODE BEGIN USART1_MspInit 0 */
 
  /* USER CODE END USART1_MspInit 0 */
    /* USART1 clock enable */
    __HAL_RCC_USART1_CLK_ENABLE();

    __HAL_RCC_GPIOA_CLK_ENABLE();
    /**USART1 GPIO Configuration
    PA9     ------> USART1_TX
    PA10     ------> USART1_RX
    */
    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);

    /* USART1 interrupt Init */
    HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(USART1_IRQn);
  /* USER CODE BEGIN USART1_MspInit 1 */
 
  /* USER CODE END USART1_MspInit 1 */
  }
}

void HAL_UART_MspDeInit(UART_HandleTypeDef* uartHandle)
{

  if(uartHandle->Instance==USART1)
  {
  /* USER CODE BEGIN USART1_MspDeInit 0 */
 
  /* USER CODE END USART1_MspDeInit 0 */
    /* Peripheral clock disable */
    __HAL_RCC_USART1_CLK_DISABLE();

    /**USART1 GPIO Configuration
    PA9     ------> USART1_TX
    PA10     ------> USART1_RX
    */
    HAL_GPIO_DeInit(GPIOA, GPIO_PIN_9|GPIO_PIN_10);

    /* USART1 interrupt Deinit */
    HAL_NVIC_DisableIRQ(USART1_IRQn);
  /* USER CODE BEGIN USART1_MspDeInit 1 */
 
  /* USER CODE END USART1_MspDeInit 1 */
  }
}

/* USER CODE BEGIN 1 */
 
/* 重写stdio.h文件中的prinft()里的fputc()函数 */
int fputc(int my_data, FILE *p)
{
    unsigned char temp = my_data;
    // 改写后,使用printf()函数会将数据通过串口一发送出去
    HAL_UART_Transmit(&huart1, &temp, 1, 0xffff); // 0xfffff为最大超时时间
    return my_data;
}
 
/* 串口接收完成回调函数 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    // 判断中断是哪个串口触发的
    if (huart->Instance == USART1)
    {
 
        // 判断接收是否完成,即判断UART1_RX_STA的bit15是否为1
        if (!(UART1_RX_STA & 0x8000))
        { // 如果没接收完成就进入接收流程
 
            // 判断是否接收到回车0x0d
            if (UART1_RX_STA & 0x4000)
            {
 
                // 判断是否接收到换行0x0a
                if (buf == 0x0a)
                {
 
                    // 如果回车和换行都接收到了,则表示接收完成,即把bit15拉高
                    UART1_RX_STA |= 0x8000;
                }
                else
                { // 如果接收到回车0x0d没有接收到换行0x0a
 
                    // 则认为接收错误,重新开始接收
                    UART1_RX_STA = 0;
                }
            }
            else
            { // 如果没有接收到回车0x0d
 
                // 则判断收到的这个字符是否是回车0x0d
                if (buf == 0x0d)
                {
 
                    // 如果这个字符是回车,则将将bit14拉高,表示接收到回车
                    UART1_RX_STA |= 0x4000;
                }
                else
                { // 如果不是回车
 
                    // 则将这个字符存放到缓存数组中
                    UART1_RX_Buffer[UART1_RX_STA & 0x3ffff] = buf;
                    UART1_RX_STA++;
 
                    // 如果接收数据大于UART1_REC_LEN(200字节),则重新开始接收
                    if (UART1_RX_STA > USART_REC_LEN - 1)
                    {
                        UART1_RX_STA = 0;
                    }
                }
            }
        }
        // 如果接收完成则重新开启串口1的接收中断
        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;
    }
}
 
/* USER CODE END 1 */

.

修改文件usart.h

.

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file    usart.h
  * @brief   This file contains all the function prototypes for
  *          the usart.c file
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2023 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __USART_H__
#define __USART_H__

#ifdef __cplusplus
extern "C" {
#endif

/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

extern UART_HandleTypeDef huart1;

/* USER CODE BEGIN Private defines */

/* USER CODE END Private defines */

void MX_USART1_UART_Init(void);

/* USER CODE BEGIN Prototypes */

void usart1_receive_data_handle(void);

/* USER CODE END Prototypes */

#ifdef __cplusplus
}
#endif

#endif /* __USART_H__ */


修改文件tim.c

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file    tim.c
  * @brief   This file provides code for the configuration
  *          of the TIM instances.
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2023 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "tim.h"

/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

TIM_HandleTypeDef htim2;
TIM_HandleTypeDef htim3;
TIM_HandleTypeDef htim4;

/* TIM2 init function */
void MX_TIM2_Init(void)
{

  /* USER CODE BEGIN TIM2_Init 0 */
	
	//编码器
	
  /* USER CODE END TIM2_Init 0 */

  TIM_Encoder_InitTypeDef sConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};

  /* USER CODE BEGIN TIM2_Init 1 */

  /* USER CODE END TIM2_Init 1 */
  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();
  }
  /* USER CODE BEGIN TIM2_Init 2 */
	
	__HAL_TIM_SetCounter(&htim2,0);	 //设置定时器2的计数值为0
	__HAL_TIM_ENABLE(&htim2);  //使能定时器2
	HAL_TIM_Encoder_Start(&htim2,TIM_CHANNEL_ALL);  //开启TIM2的编码器模式
	
  /* USER CODE END TIM2_Init 2 */

}
/* TIM3 init function */
void MX_TIM3_Init(void)
{

  /* USER CODE BEGIN TIM3_Init 0 */

	//输出PWM
	
  /* USER CODE END TIM3_Init 0 */

  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};
  TIM_OC_InitTypeDef sConfigOC = {0};

  /* USER CODE BEGIN TIM3_Init 1 */

  /* USER CODE END TIM3_Init 1 */
  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();
  }
  /* USER CODE BEGIN TIM3_Init 2 */
	
	HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);  开启定时器3的通道1的PWM
	
  /* USER CODE END TIM3_Init 2 */
  HAL_TIM_MspPostInit(&htim3);

}
/* TIM4 init function */
void MX_TIM4_Init(void)
{

  /* USER CODE BEGIN TIM4_Init 0 */

	//定时1s
	
  /* USER CODE END TIM4_Init 0 */

  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};

  /* USER CODE BEGIN TIM4_Init 1 */

  /* USER CODE END TIM4_Init 1 */
  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();
  }
  /* USER CODE BEGIN TIM4_Init 2 */

	HAL_TIM_Base_Start_IT(&htim4);  //启动定时器4,并使能中断
	
  /* USER CODE END TIM4_Init 2 */

}

void HAL_TIM_Encoder_MspInit(TIM_HandleTypeDef* tim_encoderHandle)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(tim_encoderHandle->Instance==TIM2)
  {
  /* USER CODE BEGIN TIM2_MspInit 0 */

  /* USER CODE END TIM2_MspInit 0 */
    /* TIM2 clock enable */
    __HAL_RCC_TIM2_CLK_ENABLE();

    __HAL_RCC_GPIOA_CLK_ENABLE();
    /**TIM2 GPIO Configuration
    PA0-WKUP     ------> TIM2_CH1
    PA1     ------> TIM2_CH2
    */
    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);

  /* USER CODE BEGIN TIM2_MspInit 1 */

  /* USER CODE END TIM2_MspInit 1 */
  }
}

void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle)
{

  if(tim_baseHandle->Instance==TIM3)
  {
  /* USER CODE BEGIN TIM3_MspInit 0 */

  /* USER CODE END TIM3_MspInit 0 */
    /* TIM3 clock enable */
    __HAL_RCC_TIM3_CLK_ENABLE();
  /* USER CODE BEGIN TIM3_MspInit 1 */

  /* USER CODE END TIM3_MspInit 1 */
  }
  else if(tim_baseHandle->Instance==TIM4)
  {
  /* USER CODE BEGIN TIM4_MspInit 0 */

  /* USER CODE END TIM4_MspInit 0 */
    /* TIM4 clock enable */
    __HAL_RCC_TIM4_CLK_ENABLE();

    /* TIM4 interrupt Init */
    HAL_NVIC_SetPriority(TIM4_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(TIM4_IRQn);
  /* USER CODE BEGIN TIM4_MspInit 1 */

  /* USER CODE END TIM4_MspInit 1 */
  }
}
void HAL_TIM_MspPostInit(TIM_HandleTypeDef* timHandle)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(timHandle->Instance==TIM3)
  {
  /* USER CODE BEGIN TIM3_MspPostInit 0 */

  /* USER CODE END TIM3_MspPostInit 0 */

    __HAL_RCC_GPIOA_CLK_ENABLE();
    /**TIM3 GPIO Configuration
    PA6     ------> TIM3_CH1
    */
    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);

  /* USER CODE BEGIN TIM3_MspPostInit 1 */

  /* USER CODE END TIM3_MspPostInit 1 */
  }

}

void HAL_TIM_Encoder_MspDeInit(TIM_HandleTypeDef* tim_encoderHandle)
{

  if(tim_encoderHandle->Instance==TIM2)
  {
  /* USER CODE BEGIN TIM2_MspDeInit 0 */

  /* USER CODE END TIM2_MspDeInit 0 */
    /* Peripheral clock disable */
    __HAL_RCC_TIM2_CLK_DISABLE();

    /**TIM2 GPIO Configuration
    PA0-WKUP     ------> TIM2_CH1
    PA1     ------> TIM2_CH2
    */
    HAL_GPIO_DeInit(GPIOA, GPIO_PIN_0|GPIO_PIN_1);

  /* USER CODE BEGIN TIM2_MspDeInit 1 */

  /* USER CODE END TIM2_MspDeInit 1 */
  }
}

void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef* tim_baseHandle)
{

  if(tim_baseHandle->Instance==TIM3)
  {
  /* USER CODE BEGIN TIM3_MspDeInit 0 */

  /* USER CODE END TIM3_MspDeInit 0 */
    /* Peripheral clock disable */
    __HAL_RCC_TIM3_CLK_DISABLE();
  /* USER CODE BEGIN TIM3_MspDeInit 1 */

  /* USER CODE END TIM3_MspDeInit 1 */
  }
  else if(tim_baseHandle->Instance==TIM4)
  {
  /* USER CODE BEGIN TIM4_MspDeInit 0 */

  /* USER CODE END TIM4_MspDeInit 0 */
    /* Peripheral clock disable */
    __HAL_RCC_TIM4_CLK_DISABLE();

    /* TIM4 interrupt Deinit */
    HAL_NVIC_DisableIRQ(TIM4_IRQn);
  /* USER CODE BEGIN TIM4_MspDeInit 1 */

  /* USER CODE END TIM4_MspDeInit 1 */
  }
}

/* USER CODE BEGIN 1 */

/* USER CODE END 1 */

.

main.c文件编写

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2023 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include 
#include "motor.h"

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

short encoder; //定义编码值
short pulse;	  //定义电机实际脉冲值
float speed;

/* 获取定时器2计数值 */
short get_left_encoder(void)  //使用计数值  
{
	short encoder_cnt;
	
	encoder_cnt = (short)__HAL_TIM_GetCounter(&htim2);		//获取定时器2的脉冲值
	
	__HAL_TIM_SetCounter(&htim2,0);	  //将定时器2计数值清0
	return encoder_cnt;	
}

/* 定时器中断的回调函数 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    // 如果是定时器4产生的中断(1s产生一次中断)
    if (htim4.Instance == TIM4)
    {
			encoder = get_left_encoder();
			
			pulse = encoder / 4;  //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);
		}
}

	

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_TIM2_Init();
  MX_TIM3_Init();
  MX_TIM4_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */

	__HAL_TIM_SetCompare(&htim3, TIM_CHANNEL_1, 22);  //修改PWM占空比

	motor_left_back();
	//motor_left_forward();
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
		usart1_receive_data_handle();
		
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  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();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  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();
  }
}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

你可能感兴趣的:(STM32,stm32,嵌入式硬件,单片机)