基于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;

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

相关推荐
奔跑中的小象1 天前
统信UOS + 天数AI卡部署SGLang服务手册
人工智能·uos·sglang·天数智芯
DevSecOps选型指南1 天前
中国版Mythos,为何是悬镜安全灵脉CodeAI?
人工智能·安全
Shockang1 天前
LangGraph 状态机实战
人工智能
刹那芳华19921 天前
循环神经网络的从零开始实现(RNN)
人工智能·rnn·深度学习
阿童木写作1 天前
跨境图片翻译工具多合一,批量图片视频字幕翻译加智能抠图
人工智能·python·音视频·语音识别
科技之门1 天前
AI3D从建模到贴图、绑骨和动画的完整流程怎么做?V2Fun完整工作流指南
人工智能·3d·贴图
宇宙第一小趴菜1 天前
二、机器学习的应用领域和发展史
人工智能·机器学习
前端开发江鸟1 天前
我写过 MCP Server,却一直以为 MCP 只有 Tool
人工智能
天国梦1 天前
自习室智能化升级避坑指南:天学网AI智习室方案实测与选型建议
大数据·人工智能
阿里云云原生1 天前
可用性从 99.9% 跃升至 99.995%:畅捷通如何用 AI 重塑运维底座?
运维·网络·人工智能