基于STM32---编码器测速(利用GPIO模拟脉冲信号)

一、思路分析

1、在没有实体编码器的情况下,要测试 "定时器编码器模式 + 速度计算" 的逻辑,核心是用 STM32 的 GPIO 模拟编码器的 A/B 相脉冲信号,让定时器的编码器模式 "误以为" 有真实编码器在转动,从而完成速度计算的测试。

2、核心原理:定时器的编码器模式是通过检测A/B 两相脉冲的相位差(超前 / 滞后)来判断方向、计数脉冲。因此,我们可以用两个 GPIO 引脚(模拟 A/B 相) 按固定时序翻转电平,模拟编码器的正转 / 反转脉冲,输入到定时器的编码器通道引脚,触发定时器计数。

二、STM32CubeMX的配置

1、RCC的配置

打开STM32CubeMX,新建工程,选择STM32,打开;

找到RCC;选择时钟源;

时钟源 选择的类型 作用
HSE Crystal/Ceramic Resonator 高速外部时钟(High Speed External),选择 "晶振 / 陶瓷谐振器" 作为时钟源
LSE Crystal/Ceramic Resonator 低速外部时钟(Low Speed External),同样选择晶振 / 陶瓷谐振器作为时钟源

然后在点击顶部Clock Configuration

HCLK设为 72MHz(STM32F103 最大主频),配置如下:

2、SYS的配置

Debug(调试模式)选择:Serial Wire

这是 ARM Cortex-M 系列芯片常用的调试协议,仅需 2 根引脚(SWDIO、SWCLK)即可实现调试、下载程序,是嵌入式开发中最常用的调试方式之一。

3、TIM2的配置**(测速定时中断,0.1 秒触发一次)**

用于固定周期计算速度:左侧Timers → 点击TIM2

Mode Configuration:Mode:选择Internal Clock(内部时钟);

Parameter Settings(计算:72MHz 主频→定时 0.1 秒):

其他参数默认;

NVIC Settings:优先级:与 TIM3 一致或略低(如Preemption Priority=1)。

勾选TIM2 global interruptCounter Mode Up

Counter Period (ARR):设为999(定时周期:(999+1)/10kHz=0.1 秒);

Prescaler (PSC):设为7199(分频后时钟:72MHz/(7199+1)=10kHz);

4、TIM3的配置(编码器模式)

这是核心,用于 "接收" 模拟编码器的 A/B 相脉冲:

Mode:选择Encoder Mode(编码器模式);

Encoder Mode:选择Encoder Mode TI1 and TI2(同时检测 A/B 两相);

Counter Mode:默认Up/Down(支持正反转计数);

Channel 1/2:默认Input Capture direct mode(无需修改);

Prescaler (PSC):设为0(不分频,保证计数精度);

Counter Period (ARR):设为65535(16 位最大值,减少溢出频率);

最后勾选NVIC;

5、TIM4的配置(模拟编码器脉冲,1ms 触发一次)

用于定时翻转 GPIO 电平,生成模拟 A/B 相脉冲:

Mode:选择Internal Clock

Prescaler (PSC):设为71(分频后时钟:72MHz/(71+1)=1MHz);

Counter Period (ARR):设为999(定时周期:(999+1)/1MHz=1ms);

Counter ModeUp

其他参数默认;

最后开启NVIC;

6、配置USART1(USB转串口)

Asynchronous:异步模式,是串口通信最常用的模式(无需时钟同步,仅通过 RX/TX 引脚通信);

参数项 当前值 作用
Baud Rate 115200 Bits/s 波特率,即串口通信的速率,115200 是嵌入式开发中常用的高速波特率之一
Word Length 8 Bits (including Parity) 数据位长度,此处为 8 位(含校验位),通常搭配 "无校验" 时实际为 8 位数据位
Parity None 无校验位,简化通信流程(对可靠性要求高的场景会选择奇 / 偶校验)
Stop Bits 1 1 位停止位,是串口通信的标准配置
参数项 当前值 说明
Data Direction Receive and Transmit 全双工模式,支持同时接收和发送数据
Over Sampling 16 Samples 16 倍过采样,提升串口通信的抗干扰能力(是默认且常用的配置)

7、配置模拟编码器的 GPIO(PB0、PB1)

用于输出模拟 A/B 相脉冲,需连接到 TIM3 的编码器输入引脚(PA6/TIM3_CH1、PA7/TIM3_CH2):

然后将PB0和PB1配置为Output Push Pull(推挽输出)模式;

8、保存,选择MDK,生成

三、连接STM32,STLink,USB串口

STLink对应连接即可;

单片机连接USB串口是要与之相反【一方的发送端(TX)需要连接到另一方的接收端(RX)】

因此,为了让两者能正常通信:

单片机的发送端(A9) 要连到 USB 串口的接收端(RXD)(单片机发的数据,USB 串口能收到);

单片机的接收端(A10) 要连到 USB 串口的发送端(TXD)(USB 串口发的数据,单片机能收到)。

单片机的A10接USB串口的TXD;

单片机的A9接USB串口的RXD;

然后GND接GND;

A6、A7引脚与B0、B1引脚进行短接;

四、代码实现

1、明确代码保护区间

(1)多区间的核心设计逻辑

CubeMX 会根据代码的功能位置划分不同编号 / 命名的 USER CODE 区间,目的是让你把不同用途的自定义代码,精准放在对应的位置,既不破坏自动生成的代码结构,又能保证逻辑的正确性。

(2)常见的多区间类型(以 main.c 为例)

区间标记 位置 用途(该放什么代码)
USER CODE BEGIN 1 main 函数顶部(头文件后) 定义全局变量、自定义函数声明、宏定义等
USER CODE BEGIN 2 外设初始化后、主循环前 初始化类代码(如串口发送初始提示、传感器初始化)
USER CODE BEGIN WHILE 主循环while(1)内部 主循环的核心业务逻辑(如定时采集、数据处理)
USER CODE BEGIN 3 主循环while(1)结束后 极少用,一般留空(主循环是死循环,不会执行到)
USER CODE BEGIN 4 main 函数末尾 自定义函数的实现(若不想单独建文件可放这)

(3)另外的代码保护区间

/* USER CODE BEGIN PV */:仅定义文件内的私有全局变量;

/* USER CODE BEGIN PM*/: 定义仅当前文件使用的宏;

/* USER CODE BEGIN PD */: 声明仅当前文件使用的函数、自定义数据类型(结构体 / 枚举);

/* USER CODE BEGIN PTD */: 定义仅当前文件使用的结构体、枚举、类型别名(typedef);

/* USER CODE BEGIN PFP */仅当前文件(如main.cusart.c)内使用的自定义函数的实现代码;

标记 核心用途 作用域 记忆技巧
PM 私有宏定义 当前文件 M = Macros(宏)
PD 私有函数 / 类型声明 当前文件 D = Declarations(声明)
PTD 私有自定义类型(结构体 / 枚举) 当前文件 TD = Typedefs(类型定义)
PV 私有变量(之前讲过) 当前文件 V = Variables(变量)

2、代码写入

(1)在/* USER CODE BEGIN PV */中写入:

正转时,A、B 相的电平会按{1,0} → {1,1} → {0,1} → {0,0}循环变化;

反转时,电平变化顺序则相反(如{1,0} → {0,0} → {0,1} → {1,1})。

cpp 复制代码
int n = 0;
// 模拟编码器的状态与时序
uint8_t encoder_state = 0;
const uint8_t forward_seq[4][2] = {{1,0}, {1,1}, {0,1}, {0,0}}; // 正转时序

(2)在/* USER CODE BEGIN PFP */中写入:

判断正旋,反旋,计算转速;

cpp 复制代码
//记录溢出数量
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
    if (htim->Instance == TIM3) {
    // 获取编码器旋转方向:0=正旋(上溢),1=反旋(下溢)
    uint32_t direction = __HAL_TIM_DIRECTION_STATUS(htim);
    if (direction == 0) {       // 根据方向更新溢出计数n
      n++; // 正旋/上溢,n+1
    } else {
      n--; // 反旋/下溢,n-1
    }
	if (direction == 0) {
	printf("ARR direction = %d   Forward\r\n", direction);
    printf("n = %d\r\n", n); }
	else {
	printf("ARR direction = %d   Reverse\r\n", direction);
	printf("n = %d\r\n", n); }
}
	// 新增:TIM4触发模拟编码器脉冲
    else if (htim->Instance == TIM4) {
    // 按正转时序翻转PB0(A相)、PB1(B相)
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, forward_seq[encoder_state][0]);
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, forward_seq[encoder_state][1]);
    encoder_state = (encoder_state + 1) % 4;
  }
    else if(htim->Instance == TIM2){
        //printf("TIM2\r\n");
        uint16_t x = TIM3->CNT;		
        uint16_t total = 0;
        //判断正反
        if(n >= 0){
            total = x + n*10;
        }else{
            total = (10-x)-(n+1)*10;
        }
        printf("speed%d = %lf\r\n",n>=0?1:-1,total*1.0/0.1);
        TIM3->CNT = 0;
        n = 0;
    }
}

(3)在 /* USER CODE BEGIN 2 */中开启定时器

cpp 复制代码
 //要手动始能中断,是否需要手动始能,看手册中的中断控制寄存器的初始化的值
  __HAL_TIM_ENABLE_IT(&htim3, TIM_IT_UPDATE);
  //以中断方式开启定时器
  HAL_TIM_Encoder_Start_IT(&htim3, TIM_CHANNEL_ALL);
  // 启动TIM2/TIM4定时中断
  HAL_TIM_Base_Start_IT(&htim2);
  HAL_TIM_Base_Start_IT(&htim4);

(4)在 while (1)循环里写入模拟信号

cpp 复制代码
	 HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);
     HAL_Delay(10);                                          // 控制脉冲频率(数值越小,计数越快,越易溢出)
     HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_SET);    // 模拟B相(PA9)高电平(滞后A相,形成90°相位差)
     HAL_Delay(10);
     HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);  // 模拟A相低电平
     HAL_Delay(10);
     HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET);  // 模拟B相低电平
     HAL_Delay(10);
	 

(5)最后在/* USER CODE BEGIN 4 */中重定向**fputc**

原本fputc会将字符输出到终端(如电脑控制台);

重定义后,字符会通过HAL_UART_Transmit函数发送到 USART1 串口,实现 "用printf打印数据到串口" 的功能。

cpp 复制代码
int fputc(int ch, FILE *f)
{
    HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
    return ch;
}

五、进行烧录测试

完成代码编写以后,编译一下,然后利用STLink烧录到STM32;

接着打开串口助手;

打开串口,按下单片机上面的复位按键;

B1接A7;

B0接A6;

n=0 出现时对应的方向是 Forward,这是因为 TIM2 中断会重置 n=0;

属于代码中 "重置计数" 的预期行为,并非异常。

相关推荐
Mintopia2 小时前
🪄 生成式应用的 **前端 orchestration 层(编排层)指南**
人工智能·llm·aigc
雍凉明月夜2 小时前
深度学习之常用归一化(Normalization)
人工智能·深度学习·计算机视觉
沃达德软件2 小时前
视频标注技术全解析
人工智能·目标检测·计算机视觉·视觉检测·音视频·实时音视频·视频编解码
Buxxxxxx2 小时前
DAY 44 简单CNN
人工智能·神经网络·cnn
GEO AI搜索优化助手2 小时前
从传统SEO到生成式AI搜索优化的战略转型
人工智能·搜索引擎·生成式引擎优化·ai优化·geo搜索优化
颜颜yan_2 小时前
从 0 到 1 搭建一个「塔罗感」AI 智能体 —— 微光运势实践记录
人工智能·ai·智能体·modelengine
灰灰勇闯IT2 小时前
鸿蒙 ArkUI 声明式 UI 核心:状态管理(@State/@Prop/@Link)实战解析
人工智能·计算机视觉·harmonyos
cyyt2 小时前
深度学习周报(12.22~12.28)
人工智能·算法·机器学习
质变科技AI就绪数据云2 小时前
AI记忆架构三大路线
人工智能·ai·ai agent·智能体·记忆体