STM32--编码器测速

1、软件准备

串口调试助手

2、硬件准备

开发板一块,由于条件有限我们就不使用编码器,考虑使用两个GPIO引脚模拟编码器输出信号

3、Cube mx配置

这回到了嵌入式系统(特别是STM32微控制器)的核心概念。在STM32中,"自动重装值" 是一个极其重要且具体 的硬件功能,主要与定时器 相关。它不是指重装整个系统,而是指定时器在计数达到某个值后,自动重新加载初始值并继续运行。

我们来深入解析这个概念。

核心定义

在STM32的定时器(TIM)模块中,自动重装值 是指定时器计数器(CNT)向上(或向下)计数到的目标值 。当计数器计数到这个值时,会产生一个更新事件 ,然后计数器自动重装到预先设置的初始值(通常是0或另一个值),并开始下一轮的计数周期。

这个值通常存储在定时器的 "自动重装寄存器" 中,对于通用定时器是 ARR 寄存器


关键原理:ARR 和 CNT 的关系

你可以把定时器的工作想象成一个水桶接水

  • 自动重装值 :就是水桶的容量

  • 计数器 :就是当前桶里的水量

  1. 设置ARR:你决定水桶的容量(例如,ARR = 999)。

  2. 开始计数:水龙头打开,水开始流入(CNT 从0开始递增)。

  3. 达到ARR:当水量(CNT)达到桶的容量(ARR = 999)时,再增加一滴水就会溢出。

  4. 溢出与重装 :溢出这一瞬间,就产生了 "更新事件" 。同时,水桶被瞬间清空(CNT 自动重装为0或其他预设值)。

  5. 循环往复:水继续流入,开始下一个周期。

这个"溢出"事件就是定时器中断或PWM周期切换的核心触发源


计算公式与影响

自动重装值ARR直接决定了两个最重要的定时参数:

  1. 定时周期/溢出频率(用于基本定时) * 公式:溢出时间 = (ARR + 1) * (定时器时钟周期) * 或:溢出频率 = 定时器时钟频率 / (ARR + 1) * 示例 :定时器时钟为72MHz,预分频器(PSC)设为7199(将时钟分频为10KHz)。若ARR设为9999,则溢出时间为 (9999+1) * (1/10KHz) = 10000 * 0.1ms = 1秒。ARR越大,定时周期越长。

  2. PWM信号的周期和占空比(用于输出PWM) * PWM周期 :由ARR决定。PWM周期 = (ARR + 1) * 时钟周期 * PWM占空比 :由捕获/比较寄存器(CCR) 决定。占空比 = CCR / (ARR + 1) * 示例:ARR=999,若CCR=300,则占空比约30%。改变CCR可以调节占空比,改变ARR则同时改变整个PWM信号的频率(周期)。


在代码中的体现(以HAL库为例)

当你配置一个定时器时,自动重装值ARR是一个必须设置的参数。

复制代码
// 示例:配置TIM3为1秒溢出中断
TIM_HandleTypeDef htim3;

htim3.Instance = TIM3;
htim3.Init.Prescaler = 7199; // 预分频,72MHz / (7199+1) = 10KHz
htim3.Init.CounterMode = TIM_COUNTERMODE_UP; // 向上计数模式
htim3.Init.Period = 9999; // !!!这就是自动重装值ARR!!!
                          // 10KHz下,计数10000次就是1秒
htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE; // 见下文"影子寄存器"

HAL_TIM_Base_Init(&htim3);
HAL_TIM_Base_Start_IT(&htim3); // 启动定时器并开启中断

CNT 计数到 Period(即ARR)的值9999,并再加1回到0时,就会触发更新中断,你可以在中断服务函数中执行任务(如翻转LED)。


重要特性:预装载与影子寄存器

这是STM32定时器非常巧妙的设计,为了解决ARR值更新时的同步和安全问题

  • 影子寄存器:用户写入的ARR值,并非直接作用于当前计数比较逻辑。实际起作用的是另一个不可见的"影子寄存器"。

  • 预装载功能 : * 启用时 (AutoReloadPreload = ENABLE):你写入ARR寄存器的新值 ,会先保存在"预装载寄存器"中。只有在下一次更新事件 发生时,这个新值才会被同步 到影子寄存器中并生效。这保证了在一个完整的定时周期内,ARR值不会中途改变,避免了产生奇怪的毛刺或半个周期的PWM。 * 禁用时 :你写入ARR的值会立即更新到影子寄存器,立刻影响当前计数周期,可能导致不可预期的输出。

简单比喻

  • 有预装载 :像编辑一个文档,你全部改完后,统一按"保存"键才会生效。

  • 无预装载 :像直接编辑正在播放的幻灯片,改一个字就立刻显示一个字,可能导致页面混乱。


总结

在STM32中,自动重装值 是定时器模块的核心配置参数,它:

  1. 物理实体:是ARR寄存器的值。

  2. 核心作用 :定义了定时器的计数上限 ,从而决定了定时周期PWM周期

  3. 工作过程:计数器CNT达到ARR后,产生更新事件,并自动重装,开始新周期。

  4. 高级特性 :通过预装载影子寄存器机制,保证了在运行中修改ARR时的稳定性和安全性。

理解ARR是掌握STM32所有定时器功能(基本定时、输入捕获、输出比较、PWM生成)的基石。

开启中断

建议选择生成所有

这里推荐选择这个

4、编写代码和解析

复制代码
  /* USER CODE BEGIN 2 */
  // 1. 启动TIM3编码器模式(带溢出中断)
  __HAL_TIM_ENABLE_IT(&htim3, TIM_IT_UPDATE);  // 使能TIM3溢出中断
  HAL_TIM_Encoder_Start_IT(&htim3, TIM_CHANNEL_ALL);  // 启动编码器中断模式

  // 2. 启动TIM2定时中断(100ms采样)
  HAL_TIM_Base_Start_IT(&htim2);
  /* USER CODE END 2 */

由于缺少器件;我们使用两个GPIO引脚生成一个正交波,来模拟编码器

复制代码
        HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);
        HAL_Delay(200);
        
        // 状态2: A=1, B=1
        HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_SET);
        HAL_Delay(100);
        
        // 状态3: A=0, B=1
        HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);
        HAL_Delay(100);
        
        // 状态4: A=0, B=0
        HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET);
        HAL_Delay(200);

/* USER CODE BEGIN 4 */
int fputc(int ch, FILE *f) //printf重定向函数
{
    HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
    return ch;
}

int n = 0;

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){
    if(htim->Instance == TIM3){
        //1为下溢
        uint32_t direction = __HAL_TIM_DIRECTION_STATUS(htim);
        if(direction == 0){
            n++;
        }else{
            n--;
        }
    //  printf("ARR direction = %d\r\n",direction);
    }
    
    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;
    }
}
/* USER CODE END 4 */

**为什么要printf重定向?**因为printf是面向屏幕打印,不是面向串口,所以需要重定向

代码解释

HAL_TIM_PeriodElapsedCallback 是 STM32 HAL 库中定时器周期溢出中断的统一回调函数,所有配置为 "周期溢出中断" 的定时器(如 TIM2、TIM3)触发中断时,都会进入该函数。需通过定时器实例(Instance)区分不同定时器,实现差异化逻辑

复制代码
    // -------------------------- 第一部分:TIM3编码器溢出中断处理 --------------------------
    if(htim->Instance == TIM3) // 判断是否为TIM3的溢出中断
    {
        // 获取编码器当前计数方向:__HAL_TIM_DIRECTION_STATUS是HAL库方向判断宏
        // 返回值说明(对应TIM3编码器模式):
        //  0 = TIM_COUNTERMODE_UP   :上计数(编码器反转,计数器从0→9,触发上溢)
        //  1 = TIM_COUNTERMODE_DOWN :下计数(编码器正转,计数器从9→0,触发下溢)
        // 注:原注释"1为下溢"是对该返回值的备注
        uint32_t direction = __HAL_TIM_DIRECTION_STATUS(htim);
        
        // 根据方向更新溢出次数:正转(下溢)n累加,反转(上溢)n递减
        if(direction == 0)      // 方向0 → 反转(上溢)
        {
            n++;                // 反转溢出,n递减(此处逻辑与注释匹配:正转n++,反转n--)
        }
        else                    // 方向1 → 正转(下溢)
        {
            n--;
        }
        
        // 串口打印当前编码器旋转方向(0=反转,1=正转),用于调试方向判断是否正确
        printf("ARR direction = %d\r\n",direction);
    }

HAL_TIM_DIRECTION_STATUS 宏完全解析

__HAL_TIM_DIRECTION_STATUS 是 STM32 HAL 库中用于读取定时器计数方向 的核心宏,在编码器测速场景中,它直接决定旋转方向(正 / 反转)的判断,该宏本质是直接操作定时器的控制寄存器 1(TIMx_CR1) ,读取其中的 DIR 位(Direction,方向位)

复制代码
  // -------------------------- 第二部分:TIM2定时采样中断处理 --------------------------
    if(htim->Instance == TIM2) // 判断是否为TIM2的定时中断(配置为100ms触发一次)
    {
        // 调试用:打印TIM2中断触发标记(注释后不影响功能,可取消注释验证中断是否生效)
        //printf("TIM2\r\n");
        
        uint16_t x = TIM3->CNT; // 读取TIM3编码器当前计数值(范围0~9,未溢出的"剩余脉冲数")
        uint16_t total = 0;     // 存储100ms采样周期内编码器的总脉冲数
        
        // 根据溢出次数n的正负,区分整体旋转方向,计算总脉冲数
        // 核心逻辑:总脉冲数 = 当期计数值 + 溢出次数×每溢出一次的脉冲数(ARR+1=10)
        if(n >= 0)              // n≥0 → 整体为正转(包括无溢出的正转)
        {
            // 正转总脉冲:当前计数值(x) + 溢出次数(n)×10(每溢出一次对应10个脉冲)
            total = x + n*10;
        }
        else                    // n<0 → 整体为反转
        {
            // 反转总脉冲计算逻辑:
            // (10-x) :反转时计数器从0→9,剩余未溢出的脉冲数为10-x
            // -(n+1)*10 :n为负数,抵消负溢出的基数(例如n=-1时,-(n+1)*10=0,仅计算10-x)
            total = (10-x)-(n+1)*10;
        }
        
        // 串口打印转速:
        // speed1 = 正转转速,speed-1 = 反转转速;
        // 转速计算:total(100ms总脉冲) / 0.1(采样时间,单位秒) → 脉冲/秒
        // total*1.0 是为了将整数转为浮点数,避免除法取整
        printf("speed%d = %lf\r\n",n>=0?1:-1,total*1.0/0.1);
        
        TIM3->CNT = 0;          // 重置TIM3计数值为0,为下一次采样周期做准备
        n = 0;                  // 重置溢出次数为0,为下一次采样周期做准备
    }

编译下载

打开串口调试助手

正接---正向旋转

反接-反像旋转

数据解析

为什么速度始终是10和0

推测:

1,GPIO模拟正交波的问题?与编码器产生不同

编码器工作原理 脉冲产生码盘旋转时,A、B 两相会输出连续的方波脉冲,且两相脉冲相位差 90°。 正转时:A 相超前 B 相 90°; 反转时:B 相超前 A 相 90°。 这一特性是 STM32 判断电机转向的核心依据。

2、自动重装值

上述两个问题第二问题本人以实践过,依旧和上述数据一样没有发生改变

相关推荐
bu_shuo2 小时前
STM32 X-CUBE-MCSDK软件安装
stm32·单片机·嵌入式硬件
曾浩轩2 小时前
跟着江协科技学STM32之4-1OLED调试工具
科技·stm32·单片机·学习
yuezhilangniao8 小时前
信创问题:从CPU到外设的统一- 拥抱 RISC-V
嵌入式硬件·risc-v
逼子格13 小时前
初入职场亦深耕创作:我的2025硬件工程师成长与分享之旅
嵌入式硬件·嵌入式·硬件工程师·硬件·博客之星·硬件工程师成长之路·硬件学习
v先v关v住v获v取13 小时前
番茄收获机切割与分离装置结构设计cad5张 +三维图+设计说明书
科技·单片机·51单片机
Zeku13 小时前
20251228 - Linux 驱动文件 (.ko) 深度解析笔记
stm32·freertos·linux驱动开发·linux应用开发
￴ㅤ￴￴ㅤ9527超级帅14 小时前
4、stm32异常与中断
stm32·单片机·嵌入式硬件
BreezeJuvenile15 小时前
通用定时器_测量PWM方波的周期和占空比案例
stm32·单片机·学习·通用定时器·pwm输入·测量占空比
进阶的猪15 小时前
stm32f407 RCC时钟配置
stm32·单片机·嵌入式硬件