一、项目简介

本文介绍一个基于 STM32F4 的增量式编码器角度测量程序。该程序使用 TIM1 编码器接口模式采集编码器 A、B 两相信号,通过 Z 相索引信号完成每圈计数测量,并将编码器分辨率、当前角度、Z 相脉宽以及脉冲次数实时显示在 LCD 上。

程序主要实现以下功能:

  1. 使用 TIM1 读取增量式编码器 A、B 相信号;

  2. 使用 PE13 外部中断读取编码器 Z 相信号;

  3. 动态测量编码器每圈分辨率;

  4. 当分辨率接近 2880 时自动锁定;

  5. 实时计算当前机械角度;

  6. 区分正转和反转;

  7. 测量正向、反向 Z 相高电平脉宽;

  8. 通过 LCD 实时显示编码器状态信息。

二、硬件连接说明

本文程序使用的硬件资源如下表所示。

功能 STM32 引脚 外设功能
编码器 A 相 PE9 TIM1_CH1
编码器 B 相 PE11 TIM1_CH2
编码器 Z 相 PE13 EXTI13
LCD 显示 I2C 接口 RSCG12864B
5V 使能 PA1 GPIO 输出

其中:

  • A、B 相用于判断编码器旋转方向和计数;

  • Z 相用于一圈零位参考和动态分辨率测量;

  • LCD用于显示分辨率、角度和 Z 相统计信息;

  • PA1用于控制外部 5V 使能。

三、程序文件结构

本工程主要包含以下三个代码文件:

encoder.c      // 编码器采集、方向判断、分辨率测量、角度计算
encoder.h      // 编码器模块对外接口声明
main.c         // 主函数,LCD显示和周期刷新

整体功能关系如下:

编码器 A/B 相
      ↓
TIM1 编码器模式计数
      ↓
软件计算增量 diff 和累计计数 s_total_cnt
      ↓
根据动态分辨率计算角度
      ↓
LCD 显示角度和分辨率


编码器 Z 相
      ↓
EXTI13 外部中断
      ↓
锁存一圈计数
      ↓
动态测量分辨率
      ↓
判断是否锁定到 2880

四、主要全局变量说明

encoder.c 中定义了一组全局变量,用于保存编码器状态。

volatile uint32_t Encoder_DynamicResolution  = 0;
volatile uint8_t  Encoder_ResolutionMeasured = 0;
volatile int8_t   Encoder_Direction          = 0;

1. 动态分辨率变量

Encoder_DynamicResolution

用于保存当前测得的编码器每圈计数值。例如程序中目标分辨率为:

#define ENCODER_TARGET_RES 2880u

当程序测得的分辨率接近 2880 时,会自动锁定为 2880。

2. 方向变量

Encoder_Direction

用于保存当前旋转方向:

数值 含义
1 正转
-1 反转
0 未确定或静止

3. Z 相统计变量

volatile uint32_t Encoder_ZPulseWidthCnt = 0;
volatile uint32_t Encoder_ZPulseCount    = 0;
volatile uint32_t Encoder_FPulseWidthCnt = 0;
volatile uint32_t Encoder_FPulseCount    = 0;

含义如下:

变量 含义
Encoder_ZPulseWidthCnt 正向 Z 相高电平宽度
Encoder_ZPulseCount 正向 Z 相脉冲次数
Encoder_FPulseWidthCnt 反向 Z 相高电平宽度
Encoder_FPulseCount 反向 Z 相脉冲次数

这里的“宽度”不是时间单位,而是编码器计数单位 cnt

4. 分辨率锁定标志

volatile uint8_t Encoder_ResFixedFlag = 0;

含义如下:

数值 含义
0 未锁定
1 已锁定到 2880

LCD 显示中也会根据该变量改变显示格式:

未锁定:_2880
已锁定:2880_

五、TIM1 编码器模式初始化

程序使用 TIM1 的编码器接口模式采集 A、B 相。

TIM_EncoderInterfaceConfig(TIM1, TIM_EncoderMode_TI12,
                           TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);

其中:

  • TIM_EncoderMode_TI12 表示同时使用 CH1 和 CH2;

  • TIM_ICPolarity_Rising 表示采集上升沿;

  • TIM1 的 CNT 会根据 A、B 相自动加减计数;

  • 方向由 TIM1 的硬件方向位 DIR 判断。

读取方向的代码如下:

static inline int8_t Encoder_GetHwDirection(void)
{
    return (TIM1->CR1 & TIM_CR1_DIR) ? -1 : 1;
}

含义是:

TIM1 DIR 位 方向
0 正向计数
1 反向计数

六、软件累计计数逻辑

虽然 TIM1 可以自动计数,但是 TIM1 是 16 位计数器,因此需要软件进行连续累计。

核心函数为:

static void Encoder_UpdateDeltaAndTotal(void)

其基本逻辑是:

cur  = TIM1_GetCounter();
diff = (int32_t)cur - (int32_t)s_prev_cnt;

其中:

  • cur 是当前 TIM1 计数值;

  • s_prev_cnt 是上一次计数值;

  • diff 是本次增量。

然后对 diff 进行溢出修正:

if (diff > (int32_t)(span / 2u))
    diff -= (int32_t)span;
else if (diff < -(int32_t)(span / 2u))
    diff += (int32_t)span;

这样可以避免计数器从最大值跳到 0,或者从 0 跳到最大值时产生异常大差值。

最后累加到软件总计数:

s_total_cnt += diff;

s_total_cnt 是一个有符号变量,可以表示正向累计,也可以表示反向累计。

七、方向确认与反转处理

为了防止编码器在静止或抖动时方向来回切换,程序加入了方向确认机制。

#define DIR_CONFIRM_N 3u

表示连续 3 次检测到同一方向增量后,才确认方向切换。

正向确认逻辑如下:

if (diff > 0)
{
    s_pos_confirm++;
    s_neg_confirm = 0u;

    if (s_pos_confirm >= DIR_CONFIRM_N)
    {
        Encoder_Direction = 1;
    }
}

反向确认逻辑如下:

else if (diff < 0)
{
    s_neg_confirm++;
    s_pos_confirm = 0u;

    if (s_neg_confirm >= DIR_CONFIRM_N)
    {
        Encoder_Direction = -1;
    }
}

这样处理后,可以降低编码器抖动导致的方向误判。

八、Z 相中断处理逻辑

Z 相连接到 PE13,并配置为外部中断 EXTI13,采用双边沿触发。

EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling;

因此:

  • Z 相上升沿用于测量一圈计数和分辨率;

  • Z 相下降沿用于测量 Z 相高电平宽度。

九、Z 相上升沿:测量一圈分辨率

Z 相上升沿处理中,程序会读取当前 TIM1 计数值:

raw_u16 = (uint16_t)TIM_GetCounter(TIM1);
abs_cnt = RawToAbsCount(raw_u16);
Encoder_LastRoundCount = abs_cnt;

然后将 TIM1 计数器清零:

TIM_SetCounter(TIM1, 0);
s_total_cnt = 0;
s_prev_cnt  = 0;

这一步的作用是:

每检测到一次 Z 相上升沿,就认为编码器完成了一圈,
然后锁存这一圈内 TIM1 的累计计数值。

如果连续几圈测得的计数值比较接近,程序就认为分辨率测量稳定。

十、动态分辨率确认与锁定

程序中设置了以下参数:

#define CONFIRM_COUNT       2u
#define RES_TOLERANCE       30u
#define ENCODER_TARGET_RES  2880u
#define RES_LOCK_TOLERANCE  20u

含义如下:

参数 含义
CONFIRM_COUNT 连续确认次数
RES_TOLERANCE 两次测量允许误差
ENCODER_TARGET_RES 目标分辨率 2880
RES_LOCK_TOLERANCE 锁定允许误差

当两次测量结果误差小于 RES_TOLERANCE 时,认为分辨率有效:

if (diff_res <= RES_TOLERANCE)
{
    s_confirm_cnt++;
}

当测量分辨率处于 2880 ± 20 范围内时,强制锁定为 2880:

if ((Encoder_DynamicResolution >= (ENCODER_TARGET_RES - RES_LOCK_TOLERANCE)) &&
    (Encoder_DynamicResolution <= (ENCODER_TARGET_RES + RES_LOCK_TOLERANCE)))
{
    Encoder_DynamicResolution = ENCODER_TARGET_RES;
    Encoder_ResFixedFlag = 1;
    Encoder_SetTim1ARR(Encoder_DynamicResolution - 1u);
}

锁定后:

Encoder_DynamicResolution = 2880
TIM1->ARR = 2879
Encoder_ResFixedFlag = 1

这样后续角度计算会更加稳定。

十一、Z 相下降沿:测量 Z 相高电平宽度

Z 相下降沿中,程序读取从 Z 相上升沿到下降沿之间的计数值:

raw_fall  = TIM1_GetCounter();
width_cnt = RawToAbsCount(raw_fall);

如果上升沿时判断方向为正向,则保存到正向变量:

Encoder_ZPulseWidthCnt = width_cnt;
Encoder_ZPulseCount++;

如果方向为反向,则保存到反向变量:

Encoder_FPulseWidthCnt = width_cnt;
Encoder_FPulseCount++;

因此 LCD 上显示的:

Z_Cnt:正向 Z 相高电平宽度
Z_Num:正向 Z 相脉冲次数
F_Cnt:反向 Z 相高电平宽度
F_Num:反向 Z 相脉冲次数

十二、角度计算方法

角度计算函数为:

float Encoder_GetAngle(void)
{
    pos = (uint32_t)wrap_mod_res(s_total_cnt, res);
    ang = ((float)pos * 360.0f) / (float)res;
    return ang;
}

计算公式为:

角度 = 当前计数位置 × 360° / 每圈分辨率

例如分辨率为 2880 时:

每 1 个计数对应角度 = 360 / 2880 = 0.125°

如果当前计数为 720,则:

角度 = 720 × 360 / 2880 = 90°

十三、LCD 显示内容

主函数中 LCD 显示 6 行主要数据:

print_string_5x7_xy(0x00, 0,  8, (U8*)"Res:");
print_string_5x7_xy(0x00, 0, 16, (U8*)"Ang:");
print_string_5x7_xy(0x00, 0, 24, (U8*)"Z_Cnt:");
print_string_5x7_xy(0x00, 0, 32, (U8*)"Z_Num:");
print_string_5x7_xy(0x00, 0, 40, (U8*)"F_Cnt:");
print_string_5x7_xy(0x00, 0, 48, (U8*)"F_Num:");

显示含义如下:

LCD 字段 含义
Res 当前动态分辨率
Ang 当前机械角度
Z_Cnt 正向 Z 相高电平宽度
Z_Num 正向 Z 相脉冲次数
F_Cnt 反向 Z 相高电平宽度
F_Num 反向 Z 相脉冲次数

主循环每 100ms 刷新一次 LCD:

for (i = 0; i < 100; i++)
{
    Encoder_UpdateDirection();
    delay_ms(1);
}

也就是说,程序每 1ms 更新一次编码器方向和累计计数,每 100ms 刷新一次显示。

十四、显示效果说明

程序启动后,LCD 初始显示如下:

Res:   Wait
Ang:   Wait
Z_Cnt: 0
Z_Num: 0
F_Cnt: 0
F_Num: 0

当编码器旋转并检测到 Z 相后,程序开始测量分辨率。

如果分辨率尚未锁定,显示格式类似:

Res:   _2876

如果分辨率成功锁定到 2880,则显示格式变为:

Res:   2880_

角度显示格式为一位小数,例如:

Ang:   123.4

十五、程序设计亮点

1. 使用硬件编码器模式,计数稳定

TIM1 编码器模式可以由硬件自动完成 A、B 相计数和方向判断,比普通 GPIO 中断方式更加稳定。

2. 使用 Z 相自动测量分辨率

程序不需要一开始手动写死分辨率,而是可以根据 Z 相自动测量每圈计数值。

3. 支持分辨率锁定

当测量值接近 2880 时,程序会自动锁定为 2880,避免后续测量抖动导致角度计算不稳定。

4. 软件连续计数

虽然 TIM1 是 16 位计数器,但程序通过 s_total_cnt 实现了连续计数,方便计算角度和方向。

5. 方向防抖机制

通过连续多次确认同一方向,避免编码器抖动时方向频繁跳变。

6. LCD 局部刷新

主循环中只有当数据发生变化时才刷新对应区域,可以减少 LCD 闪烁和残影。

十六、需要注意的问题

1. delay_ms_counter 必须正常递增

Z 相上升沿中使用了:

now_ms = delay_ms_counter;

如果 delay_ms_counter 没有在 SysTick 或定时器中断中正常递增,则 Z 相最小间隔判断会失效。

2. TIM5 被用作 1 MHz 微秒计时器

程序中 TIM5 用于 Z 相边沿去抖:

TIM_TimeBaseStructure.TIM_Prescaler = 84 - 1;

这里默认 APB1 定时器时钟为 84 MHz。

如果你的系统时钟配置不同,需要同步修改 TIM5 的预分频值。

3. PE9、PE11、PE13 引脚需要与硬件一致

本文程序默认:

PE9  -> A 相
PE11 -> B 相
PE13 -> Z 相

如果实际 PCB 使用的引脚不同,需要修改 GPIO 和复用配置。

4. 目标分辨率目前固定为 2880

代码中写死了目标分辨率:

#define ENCODER_TARGET_RES 2880u

如果使用其他编码器,需要根据实际分辨率修改该值。

5. Z 相中断中不要加入耗时操作

Z 相中断函数中只适合做计数锁存、状态更新等轻量操作,不建议在中断里加入 LCD 显示、串口打印等耗时函数。

十七、核心代码结构

1. 编码器初始化

void Encoder_Init(void)
{
    // 初始化 GPIOE、TIM1、TIM5、EXTI13
    // 配置 PE9/PE11 为 TIM1 编码器输入
    // 配置 PE13 为 Z 相外部中断输入
    // 初始化状态变量
    // 启动 TIM1
}

2. 编码器周期更新

void Encoder_UpdateDirection(void)
{
    Encoder_UpdateDeltaAndTotal();
}

建议在主循环中定时调用,例如每 1ms 调用一次。

3. 获取角度

float Encoder_GetAngle(void)
{
    uint32_t res;
    uint32_t pos;
    float ang;

    res = Encoder_DynamicResolution;
    if (res == 0u)
    {
        return 0.0f;
    }

    pos = (uint32_t)wrap_mod_res(s_total_cnt, res);
    ang = ((float)pos * 360.0f) / (float)res;

    return ang;
}

4. Z 相中断

void EXTI15_10_IRQHandler(void)
{
    if (EXTI_GetITStatus(EXTI_Line13) != RESET)
    {
        // 清除中断标志
        // 判断 Z 相当前电平
        // 上升沿:测量一圈计数和分辨率
        // 下降沿:测量 Z 相高电平宽度
    }
}

十八、完整代码

1. encoder.c

#include "stm32f4xx.h"

#include "encoder.h"

/* ===================== 全局变量定义 ===================== */

volatile uint32_t Encoder_DynamicResolution  = 0;

volatile uint8_t  Encoder_ResolutionMeasured = 0;

volatile int8_t   Encoder_Direction          = 0;

volatile uint32_t Encoder_LastRoundCount = 0;

volatile uint32_t g_z_irq_count          = 0;

/* Z相脉宽与计数(正向/反向)- 宽度单位cnt */

volatile uint32_t Encoder_ZPulseWidthCnt = 0;   // 正向最新脉宽(cnt)

volatile uint32_t Encoder_ZPulseCount    = 0;   // 正向脉冲次数

volatile uint32_t Encoder_FPulseWidthCnt = 0;   // 反向最新脉宽(cnt)

volatile uint32_t Encoder_FPulseCount    = 0;   // 反向脉冲次数

/* 分辨率固定标志:1=已固定到2880,0=未固定 */

volatile uint8_t  Encoder_ResFixedFlag   = 0;

/* 在delay模块中定义(1ms节拍计数) */

volatile uint32_t delay_ms_counter = 0;

/* ===================== DBG变量(Live Watch) ===================== */

volatile uint16_t Encoder_DBG_CNT       = 0;   //TIM1 计数器 CNT 的值

volatile uint16_t Encoder_DBG_ARR       = 0;   //TIM1 自动重装值 ARR

volatile int32_t  Encoder_DBG_DIFF      = 0;   //相对上次的增量差值

volatile int32_t  Encoder_DBG_TOTAL_CNT = 0;   

volatile float    Encoder_DBG_ANGLE     = 0.0f; //机械角度

volatile uint32_t Encoder_DBG_RES       = 0;    //分辨率(每圈计数)

volatile uint8_t  Encoder_DBG_REV_MODE  = 0;    //0:正转;  1:反转

/* ===================== 内部参数/状态 ===================== */

#define MIN_ROUND_MS        200u   // Z上升沿最小间隔过滤(防抖/抗干扰)

#define CONFIRM_COUNT       2u

#define RES_TOLERANCE       30u

/* 锁定目标与容差 */

#define ENCODER_TARGET_RES  2880u

#define RES_LOCK_TOLERANCE  20u    // 可改10/20/30

/* Z边沿去抖(避免一圈+2) */

#define Z_EDGE_DEBOUNCE_US  20u

/* 方向切换确认(防抖动来回切模式) */

#define DIR_CONFIRM_N 3u     // 连续3次同方向增量才确认切换

static volatile uint32_t s_last_z_ms = 0;

static volatile uint32_t s_last_valid_res = 0;

static volatile uint8_t  s_confirm_cnt    = 0;

static uint16_t s_last_raw_u16 = 0;

/* Z高电平宽度测量状态(单位cnt) */

static volatile uint8_t  s_z_high_active = 0;

static volatile int8_t   s_z_rise_dir    = 0;

/* Z边沿去抖(避免一圈+2) */

static volatile uint32_t s_last_z_edge_us = 0;

 

/* 电平状态机,避免同电平重复处理中断 */

static volatile uint8_t s_z_last_level = 0xFF;

 

/* 连续计数(用于角度计算) */

static volatile int32_t  s_total_cnt = 0;  // 连续总计数(可正可负)

static volatile uint16_t s_prev_cnt  = 0;  // 上次原始CNT

 

/******** 反转模式 ********/

static volatile uint8_t  s_reverse_mode = 0; // 0:正常 1:反转模式

static volatile int32_t  s_arr_cnt      = 0; // 反转逻辑计数

 

/* 方向切换确认(防抖动来回切模式) */

static volatile uint8_t s_pos_confirm = 0;

static volatile uint8_t s_neg_confirm = 0;

 

/* ===================== 内部函数 ===================== */

static inline uint16_t TIM1_GetCounter(void)

{

    return (uint16_t)TIM_GetCounter(TIM1);

}

 

static uint32_t RawToAbsCount(uint16_t raw)

{

    if (raw <= 32767u) return (uint32_t)raw;

    return (uint32_t)(65536u - (uint32_t)raw);

}

 

/* TIM5作为1MHz自由运行计时器(1tick=1us) */

static void Encoder_UsTimerInit(void)

{

    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;

 

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE);

 

    TIM_TimeBaseStructure.TIM_Prescaler         = 84 - 1;      // 84MHz/84=1MHz

    TIM_TimeBaseStructure.TIM_CounterMode       = TIM_CounterMode_Up;

    TIM_TimeBaseStructure.TIM_Period            = 0xFFFFFFFF;  // 32位自动重装

    TIM_TimeBaseStructure.TIM_ClockDivision     = TIM_CKD_DIV1;

    TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;

    TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure);

 

    TIM_Cmd(TIM5, ENABLE);

}

 

static inline uint32_t Encoder_GetUsTick(void)

{

    return TIM_GetCounter(TIM5);

}

 

/* 直接读取TIM1硬件方向位:DIR=0上数(正向), DIR=1下数(反向) */

static inline int8_t Encoder_GetHwDirection(void)

{

    return (TIM1->CR1 & TIM_CR1_DIR) ? -1 : 1;

}

 

/* EXTI13使能/关闭(反转时关闭,防止Z继续清零) */

static inline void Encoder_SetZExtiEnable(uint8_t en)

{

    if (en)

    {

        EXTI->IMR |= EXTI_Line13;

    }

    else

    {

        EXTI->IMR &= ~EXTI_Line13;

        EXTI_ClearITPendingBit(EXTI_Line13);

    }

}

 

/* 设置TIM1 ARR,并同步软件计数参考(会重置计数状态) */

static void Encoder_SetTim1ARR(uint32_t arr)

{

    if (arr > 0xFFFFu) arr = 0xFFFFu;

 

    TIM_Cmd(TIM1, DISABLE);

    TIM_SetAutoreload(TIM1, (uint16_t)arr);

    TIM_SetCounter(TIM1, 0);

    TIM_GenerateEvent(TIM1, TIM_EventSource_Update);

    TIM_Cmd(TIM1, ENABLE);

 

    s_prev_cnt  = 0;

    s_total_cnt = 0;

}

 

/* 运行时修改ARR:不重置s_total_cnt/s_prev_cnt */

static void Encoder_SetTim1ARR_Runtime(uint32_t arr)

{

    uint16_t cnt;

 

    if (arr > 0xFFFFu) arr = 0xFFFFu;

 

    TIM_Cmd(TIM1, DISABLE);

 

    TIM_SetAutoreload(TIM1, (uint16_t)arr);

 

    /* 若当前CNT超过新ARR,钳位到ARR */

    cnt = (uint16_t)TIM_GetCounter(TIM1);

    if (cnt > (uint16_t)arr)

        TIM_SetCounter(TIM1, (uint16_t)arr);

 

    TIM_GenerateEvent(TIM1, TIM_EventSource_Update);

    TIM_Cmd(TIM1, ENABLE);

}

 

/* 归一到[0,res) */

static inline int32_t wrap_mod_res(int32_t v, uint32_t res)

{

    if (res == 0) return 0;

    v %= (int32_t)res;

    if (v < 0) v += (int32_t)res;

    return v;

}

 

/* 按“三公式”更新:先溢出修正增量,再累计连续计数 */

static void Encoder_UpdateDeltaAndTotal(void)

{

    uint16_t cur;

    int32_t  diff;

    uint32_t res;

    uint32_t span;

 

    cur  = TIM1_GetCounter();

    diff = (int32_t)cur - (int32_t)s_prev_cnt;

    res  = Encoder_DynamicResolution;

 

    /* diff修正窗口 */

    if (res > 0u) span = res;                   /* 例如2880 */

    else          span = (uint32_t)TIM1->ARR + 1u;

 

    if (span == 0u) span = 65536u;

 

    if (diff > (int32_t)(span / 2u))

        diff -= (int32_t)span;

    else if (diff < -(int32_t)(span / 2u))

        diff += (int32_t)span;

 

    /* 连续计数 */

    s_total_cnt += diff;

 

    /* 方向确认 */

    if (diff > 0)

    {

        if (s_pos_confirm < 255u) s_pos_confirm++;

        s_neg_confirm = 0u;

 

        if (s_pos_confirm >= DIR_CONFIRM_N)

        {

            if (s_reverse_mode)

            {

                /* 反转 -> 正转:无缝衔接 */

                s_total_cnt    = s_arr_cnt;

                s_reverse_mode = 0u;

 

                if (res > 0u)

                    Encoder_SetTim1ARR_Runtime(res - 1u);  /* ARR = res - 1 */

 

                Encoder_SetZExtiEnable(1u);

                s_z_last_level = GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_13) ? 1u : 0u;

            }

 

            Encoder_Direction = 1;

            s_pos_confirm = DIR_CONFIRM_N;

        }

    }

    else if (diff < 0)

    {

        if (s_neg_confirm < 255u) s_neg_confirm++;

        s_pos_confirm = 0u;

 

        if (s_neg_confirm >= DIR_CONFIRM_N)

        {

            if (!s_reverse_mode)

            {

                /* 正转 -> 反转 */

                s_reverse_mode = 1u;

 

                if (res > 0u)

                {

                    s_arr_cnt = wrap_mod_res(s_total_cnt, res);

                    Encoder_SetTim1ARR_Runtime(res - 1u);  /* ARR = res - 1 */

                }

                else

                {

                    s_arr_cnt = 0;

                }

 

                Encoder_SetZExtiEnable(0u); /* 反转时关Z中断 */

            }

 

            Encoder_Direction = -1;

            s_neg_confirm = DIR_CONFIRM_N;

        }

    }

 

    /* 反转模式逻辑回卷 */

    if (s_reverse_mode && (res > 0u) && (diff != 0))

    {

        int32_t t = s_arr_cnt + diff;          /* diff<0时递减 */

        t %= (int32_t)res;

        if (t < 0) t += (int32_t)res;

        s_arr_cnt = t;

 

        /* 统一角度源:s_total_cnt */

        s_total_cnt = s_arr_cnt;

    }

 

    s_prev_cnt = TIM1_GetCounter();

 

    /* ===== DBG快照 ===== */

    Encoder_DBG_CNT       = s_prev_cnt;

    Encoder_DBG_ARR       = (uint16_t)TIM1->ARR;

    Encoder_DBG_DIFF      = diff;

    Encoder_DBG_TOTAL_CNT = s_total_cnt;

    Encoder_DBG_RES       = res;

    Encoder_DBG_REV_MODE  = s_reverse_mode;

}

 

/* ===================== 对外接口 ===================== */

void Encoder_Init(void)

{

    GPIO_InitTypeDef        GPIO_InitStructure;

    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;

    TIM_ICInitTypeDef       TIM_ICInitStructure;

    NVIC_InitTypeDef        NVIC_InitStructure;

    EXTI_InitTypeDef        EXTI_InitStructure;

 

    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE);

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);

    Encoder_UsTimerInit();

 

    /* AB相:PE9、PE11 -> TIM1 CH1/CH2 */

    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_9 | GPIO_Pin_11;

    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF;

    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;

    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;

    GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;

    GPIO_Init(GPIOE, &GPIO_InitStructure);

 

    GPIO_PinAFConfig(GPIOE, GPIO_PinSource9,  GPIO_AF_TIM1);

    GPIO_PinAFConfig(GPIOE, GPIO_PinSource11, GPIO_AF_TIM1);

 

    /* Z相:PE13 输入 + EXTI13 */

    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_13;

    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_IN;

    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;

    GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;

    GPIO_Init(GPIOE, &GPIO_InitStructure);

 

    /* TIM1 编码器模式 */

    TIM_TimeBaseStructure.TIM_Prescaler         = 0;

    TIM_TimeBaseStructure.TIM_CounterMode       = TIM_CounterMode_Up;

    TIM_TimeBaseStructure.TIM_Period            = 0xFFFF;

    TIM_TimeBaseStructure.TIM_ClockDivision     = TIM_CKD_DIV1;

    TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;

    TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);

 

    TIM_EncoderInterfaceConfig(TIM1, TIM_EncoderMode_TI12,

                               TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);

 

    TIM_ICInitStructure.TIM_Channel     = TIM_Channel_1;

    TIM_ICInitStructure.TIM_ICPolarity  = TIM_ICPolarity_Rising;

    TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;

    TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;

    TIM_ICInitStructure.TIM_ICFilter    = 0x0F;

    TIM_ICInit(TIM1, &TIM_ICInitStructure);

 

    TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;

    TIM_ICInit(TIM1, &TIM_ICInitStructure);

 

    TIM_SetAutoreload(TIM1, 0xFFFF);

 

    /* PE13 -> EXTI13,双边沿触发 */

    SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOE, EXTI_PinSource13);

 

    EXTI_InitStructure.EXTI_Line    = EXTI_Line13;

    EXTI_InitStructure.EXTI_Mode    = EXTI_Mode_Interrupt;

    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling;

    EXTI_InitStructure.EXTI_LineCmd = ENABLE;

    EXTI_Init(&EXTI_InitStructure);

 

    NVIC_InitStructure.NVIC_IRQChannel                   = EXTI15_10_IRQn;

    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;

    NVIC_InitStructure.NVIC_IRQChannelSubPriority        = 0;

    NVIC_InitStructure.NVIC_IRQChannelCmd                = ENABLE;

    NVIC_Init(&NVIC_InitStructure);

 

    /* 状态清零 */

    TIM_SetCounter(TIM1, 0);

    s_last_raw_u16 = 0;

    Encoder_Direction = 0;

 

    Encoder_LastRoundCount      = 0;

    Encoder_DynamicResolution   = 0;

    Encoder_ResolutionMeasured  = 0;

    Encoder_ResFixedFlag        = 0;

    g_z_irq_count               = 0;

 

    s_last_z_ms                 = 0;

    s_last_valid_res            = 0;

    s_confirm_cnt               = 0;

 

    Encoder_ZPulseWidthCnt      = 0;

    Encoder_ZPulseCount         = 0;

    Encoder_FPulseWidthCnt      = 0;

    Encoder_FPulseCount         = 0;

 

    s_z_high_active             = 0;

    s_z_rise_dir                = 0;

    s_last_z_edge_us            = 0;

    s_z_last_level              = GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_13) ? 1 : 0;

 

    s_total_cnt                 = 0;

    s_prev_cnt                  = TIM1_GetCounter();

 

    s_reverse_mode              = 0;

    s_arr_cnt                   = 0;

 

    s_pos_confirm               = 0;

    s_neg_confirm               = 0;

 

    /* DBG清零 */

    Encoder_DBG_CNT       = 0;

    Encoder_DBG_ARR       = (uint16_t)TIM1->ARR;

    Encoder_DBG_DIFF      = 0;

    Encoder_DBG_TOTAL_CNT = 0;

    Encoder_DBG_ANGLE     = 0.0f;

    Encoder_DBG_RES       = 0;

    Encoder_DBG_REV_MODE  = 0;

 

    Encoder_SetZExtiEnable(1);

    TIM_Cmd(TIM1, ENABLE);

}

 

void Encoder_ResetCount(void)

{

    TIM_SetCounter(TIM1, 0);

    s_last_raw_u16    = 0;

    Encoder_Direction = 0;

 

    s_total_cnt    = 0;

    s_prev_cnt     = 0;

    s_reverse_mode = 0;

    s_arr_cnt      = 0;

 

    s_pos_confirm  = 0;

    s_neg_confirm  = 0;

 

    Encoder_DynamicResolution  = 0;

    Encoder_ResolutionMeasured = 0;

    Encoder_ResFixedFlag       = 0;

    s_last_valid_res           = 0;

    s_confirm_cnt              = 0;

 

    Encoder_SetTim1ARR(0xFFFFu);

    Encoder_SetZExtiEnable(1);

 

    /* DBG清零 */

    Encoder_DBG_CNT       = 0;

    Encoder_DBG_ARR       = (uint16_t)TIM1->ARR;

    Encoder_DBG_DIFF      = 0;

    Encoder_DBG_TOTAL_CNT = 0;

    Encoder_DBG_ANGLE     = 0.0f;

    Encoder_DBG_RES       = 0;

    Encoder_DBG_REV_MODE  = 0;

}

 

void Encoder_UpdateDirection(void)

{

    Encoder_UpdateDeltaAndTotal();

}

 

uint32_t Encoder_GetResolution(void)

{

    return Encoder_DynamicResolution;

}

 

uint32_t Encoder_GetAbsCount(void)

{

    return RawToAbsCount(TIM1_GetCounter());

}

 

float Encoder_GetAngle(void)

{

    uint32_t res;

    uint32_t pos;

    float ang;

 

    /* 不在这里更新,避免重复更新 */

    res = Encoder_DynamicResolution;

    if (res == 0u)

    {

        Encoder_DBG_ANGLE = 0.0f;

        return 0.0f;

    }

 

    pos = (uint32_t)wrap_mod_res(s_total_cnt, res);

    ang = ((float)pos * 360.0f) / (float)res;

 

    if (ang >= 360.0f) ang -= 360.0f;

    if (ang <   0.0f)  ang += 360.0f;

 

    Encoder_DBG_ANGLE = ang;

    return ang;

}

 

uint8_t Encoder_IsResFixed(void)

{

    return Encoder_ResFixedFlag;

}

 

/* ===================== Z相中断 ===================== */

void EXTI15_10_IRQHandler(void)

{

    if (EXTI_GetITStatus(EXTI_Line13) != RESET)

    {

        uint8_t  z_level;

        uint32_t now_us;

 

        EXTI_ClearITPendingBit(EXTI_Line13);

 

        z_level = GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_13) ? 1 : 0;

        now_us  = Encoder_GetUsTick();

 

        /* 边沿去抖 */

        if ((uint32_t)(now_us - s_last_z_edge_us) < Z_EDGE_DEBOUNCE_US)

            return;

        s_last_z_edge_us = now_us;

 

        /* 同电平重复中断过滤 */

        if ((s_z_last_level != 0xFF) && (z_level == s_z_last_level))

            return;

        s_z_last_level = z_level;

 

        if (z_level)  /* ---------- Z上升沿 ---------- */

        {

            uint32_t now_ms;

            uint32_t elapsed;

            uint32_t abs_cnt;

            uint16_t raw_u16;

 

            now_ms  = delay_ms_counter;

            elapsed = now_ms - s_last_z_ms;

            abs_cnt = 0;

 

            if ((s_last_z_ms != 0) && (elapsed < MIN_ROUND_MS))

                return;

 

            s_last_z_ms = now_ms;

            g_z_irq_count++;

 

            s_z_rise_dir = Encoder_GetHwDirection();

 

            /* 一圈计数锁存 + 清零 */

            TIM_Cmd(TIM1, DISABLE);

            raw_u16 = (uint16_t)TIM_GetCounter(TIM1);

            abs_cnt = RawToAbsCount(raw_u16);

            Encoder_LastRoundCount = abs_cnt;

 

            TIM_SetCounter(TIM1, 0);

            s_last_raw_u16 = 0;

 

            /* 同步软件参考 */

            s_total_cnt = 0;

            s_prev_cnt  = 0;

 

            TIM_Cmd(TIM1, ENABLE);

 

            s_z_high_active = 1;

 

            /* 已锁定后,始终保持2880,同时确保ARR=2879 */

            if (Encoder_ResFixedFlag)

            {

                Encoder_DynamicResolution = ENCODER_TARGET_RES;

                if (TIM1->ARR != (ENCODER_TARGET_RES - 1u))

                    Encoder_SetTim1ARR(ENCODER_TARGET_RES - 1u);

 

                Encoder_DBG_ARR = (uint16_t)TIM1->ARR;

                Encoder_DBG_CNT = TIM1_GetCounter();

                Encoder_DBG_RES = Encoder_DynamicResolution;

                return;

            }

 

            /* 分辨率稳定确认 */

            if (Encoder_ResolutionMeasured == 0)

            {

                Encoder_ResolutionMeasured = 1;

                s_confirm_cnt    = 0;

                s_last_valid_res = 0;

 

                if ((abs_cnt < 100) || (abs_cnt >= 100000))

                    return;

            }

 

            if ((abs_cnt > 100) && (abs_cnt < 100000))

            {

                if (s_last_valid_res == 0)

                {

                    s_last_valid_res = abs_cnt;

                    s_confirm_cnt    = 1;

                }

                else

                {

                    uint32_t diff_res;

                    diff_res = (abs_cnt > s_last_valid_res)

                             ? (abs_cnt - s_last_valid_res)

                             : (s_last_valid_res - abs_cnt);

 

                    if (diff_res <= RES_TOLERANCE)

                    {

                        s_confirm_cnt++;

                        if (s_confirm_cnt >= CONFIRM_COUNT)

                        {

                            Encoder_DynamicResolution = (abs_cnt + s_last_valid_res) / 2;

                            s_last_valid_res = Encoder_DynamicResolution;

                            s_confirm_cnt    = CONFIRM_COUNT;

 

                            /* 是否锁定到2880 */

                            if ((Encoder_DynamicResolution >= (ENCODER_TARGET_RES - RES_LOCK_TOLERANCE)) &&

                                (Encoder_DynamicResolution <= (ENCODER_TARGET_RES + RES_LOCK_TOLERANCE)))

                            {

                                Encoder_DynamicResolution = ENCODER_TARGET_RES;

                                Encoder_ResFixedFlag = 1;

 

                                /* ARR = res - 1 */

                                Encoder_SetTim1ARR(Encoder_DynamicResolution - 1u);

                            }

                            else

                            {

                                Encoder_ResFixedFlag = 0;

                            }

 

                            s_arr_cnt = (int32_t)Encoder_DynamicResolution;

                        }

                    }

                    else

                    {

                        s_confirm_cnt    = 1;

                        s_last_valid_res = abs_cnt;

                    }

                }

            }

            else

            {

                s_confirm_cnt       = 0;

                s_last_valid_res    = 0;

                Encoder_ResFixedFlag = 0;

            }

        }

        else  /* ---------- Z下降沿 ---------- */

        {

            if (s_z_high_active)

            {

                uint16_t raw_fall;

                uint32_t width_cnt;

 

                raw_fall  = TIM1_GetCounter();

                width_cnt = RawToAbsCount(raw_fall);

                s_z_high_active = 0;

 

                if (Encoder_DynamicResolution > 0)

                {

                    if ((width_cnt == 0) || (width_cnt >= Encoder_DynamicResolution))

                        return;

                }

 

                if (s_z_rise_dir > 0)

                {

                    Encoder_ZPulseWidthCnt = width_cnt;

                    Encoder_ZPulseCount++;

                }

                else if (s_z_rise_dir < 0)

                {

                    Encoder_FPulseWidthCnt = width_cnt;

                    Encoder_FPulseCount++;

                }

            }

        }

    }

}


2. encoder.h

#ifndef __ENCODER_H

#define __ENCODER_H

 

 

#include "stm32f4xx.h"

 

/* 动态分辨率(每圈计数值),例如2880 */

extern volatile uint32_t Encoder_DynamicResolution;

 

/* 分辨率是否已进入测量/已测得(由实现逻辑定义):

 * 0 = 未测量

 * 1 = 已开始测量或已得到有效测量流程

 */

extern volatile uint8_t  Encoder_ResolutionMeasured;

 

/* 当前旋转方向:

 *  1 = 正方向

 * -1 = 反方向

 *  0 = 未确定/静止(视实现)

 */

extern volatile int8_t   Encoder_Direction;

 

/* 最近一圈计数(通常在Z相上升沿锁存) */

extern volatile uint32_t Encoder_LastRoundCount;

 

/* Z相有效中断次数统计(调试/监控用) */

extern volatile uint32_t g_z_irq_count;

 

/* Z相(正向)高电平脉宽(单位:编码器计数cnt) */

extern volatile uint32_t Encoder_ZPulseWidthCnt;

 

/* Z相(正向)脉冲累计次数 */

extern volatile uint32_t Encoder_ZPulseCount;

 

/* Z相(反向)高电平脉宽(单位:编码器计数cnt) */

extern volatile uint32_t Encoder_FPulseWidthCnt;

 

/* Z相(反向)脉冲累计次数 */

extern volatile uint32_t Encoder_FPulseCount;

 

/* 分辨率是否已固定到目标值2880:

 * 1 = 已固定(锁定)

 * 0 = 未固定(动态测量中或未达锁定条件)

 */

extern volatile uint8_t  Encoder_ResFixedFlag;

 

/* ===================== 对外函数接口 ===================== */

 

/* 编码器模块初始化:

 * - 初始化GPIO/TIM/EXTI等外设

 * - 初始化内部状态变量

 * - 启动编码器计数与Z相中断逻辑

 */

void     Encoder_Init(void);

 

/* 获取当前绝对计数值(由实现决定转换方式) */

uint32_t Encoder_GetAbsCount(void);

 

/* 复位编码器相关计数与状态:

 * - 清零计数

 * - 重置分辨率/方向/锁定标志等

 */

void     Encoder_ResetCount(void);

 

/* 获取当前动态分辨率(每圈计数) */

uint32_t Encoder_GetResolution(void);

 

/* 周期更新方向与累计计数(建议在主循环定时调用) */

void     Encoder_UpdateDirection(void);

 

/* 获取当前机械角度(通常范围0~360°) */

float    Encoder_GetAngle(void);

 

/* 查询分辨率是否已锁定到2880:

 * 返回1表示已锁定,0表示未锁定

 */

uint8_t  Encoder_IsResFixed(void);

 

#endif


3. main.c

#include "stm32f4xx.h"

#include "RSCG12864B.h"

#include "delay.h"

#include "encoder.h"

 

extern volatile uint32_t Encoder_ZPulseWidthCnt;

extern volatile uint32_t Encoder_ZPulseCount;

extern volatile uint32_t Encoder_FPulseWidthCnt;

extern volatile uint32_t Encoder_FPulseCount;

 

/* 1ms节拍计数(由delay模块维护) */

extern volatile uint32_t delay_ms_counter;

 

/* UI刷新周期(ms) */

#define UI_REFRESH_MS   100u

 

/* 打印 x10 角度 */

static void lcd_print_ang_x10(U8 addr, U8 x, U8 y, uint32_t ang_x10)

{

    if (ang_x10 == 0xFFFFFFFF)

    {

        print_string_5x7_xy(addr, x, y, (U8*)"Wait");

        return;

    }

 

    {

        uint32_t ang_int  = ang_x10 / 10;

        uint32_t ang_frac = ang_x10 % 10;

        uint8_t int_digits;

        uint8_t x_dot;

        uint8_t x_frac;

 

        if      (ang_int >= 100) int_digits = 3;

        else if (ang_int >= 10)  int_digits = 2;

        else                     int_digits = 1;

 

        x_dot  = x + int_digits * 6;

        x_frac = x_dot + 6;

 

        print_U32_5x7_xy(addr, x, y, ang_int);

        print_string_5x7_xy(addr, x_dot, y, (U8*)".");

        print_U32_5x7_xy(addr, x_frac, y, ang_frac);

    }

}

 

/* 显示 Res 锁定状态:

 * fixed=1 -> "2880_"

 * fixed=0 -> "_2880"

 */

static void lcd_print_res_lock_style(U8 addr, U8 x, U8 y, uint32_t res, uint8_t fixed)

{

    uint8_t digits = 1;

    U8 x_num = x + 6;   /* 左边预留1字符 '_' */

    U8 x_tail;

 

    /* 清空区域,避免残影 */

    print_string_5x7_xy(addr, x, y, (U8*)"              ");

 

    if (res == 0)

    {

        print_string_5x7_xy(addr, x, y, (U8*)"Wait");

        return;

    }

 

    if      (res >= 1000000000UL) digits = 10;

    else if (res >= 100000000UL)  digits = 9;

    else if (res >= 10000000UL)   digits = 8;

    else if (res >= 1000000UL)    digits = 7;

    else if (res >= 100000UL)     digits = 6;

    else if (res >= 10000UL)      digits = 5;

    else if (res >= 1000UL)       digits = 4;

    else if (res >= 100UL)        digits = 3;

    else if (res >= 10UL)         digits = 2;

 

    x_tail = x_num + digits * 6;

 

    if (fixed)

    {

        /* 固定成功:2880_ */

        print_U32_5x7_xy(addr, x_num, y, res);

        print_string_5x7_xy(addr, x_tail, y, (U8*)"_");

    }

    else

    {

        /* 未固定:_2880 */

        print_string_5x7_xy(addr, x, y, (U8*)"_");

        print_U32_5x7_xy(addr, x_num, y, res);

    }

}

 

int main(void)

{

    uint32_t last_res, last_ang10, last_zcnt, last_zn, last_fcnt, last_fn;

    uint8_t  last_fixed;

    uint32_t last_ui_ms;

 

    SystemInit();

    delay_init(168);

 

    /* 5V使能 PA1 */

    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);

    {

        GPIO_InitTypeDef g;

        g.GPIO_Pin   = GPIO_Pin_1;

        g.GPIO_Mode  = GPIO_Mode_OUT;

        g.GPIO_Speed = GPIO_Speed_50MHz;

        g.GPIO_OType = GPIO_OType_PP;

        g.GPIO_PuPd  = GPIO_PuPd_NOPULL;

        GPIO_Init(GPIOA, &g);

    }

    GPIO_SetBits(GPIOA, GPIO_Pin_1);

    delay_ms(200);

 

    Encoder_Init();

 

    I2C_Initial();

    delay_ms(1000);

    brightness(0x00, 0xFF);

    clear(0x00);

 

    /* 7行显示(5x7字体,行距8像素) */

    print_string_5x7_xy(0x00, 0,  8, (U8*)"Res:");

    print_string_5x7_xy(0x00, 0, 16, (U8*)"Ang:");

    print_string_5x7_xy(0x00, 0, 24, (U8*)"Z_Cnt:");

    print_string_5x7_xy(0x00, 0, 32, (U8*)"Z_Num:");

    print_string_5x7_xy(0x00, 0, 40, (U8*)"F_Cnt:");

    print_string_5x7_xy(0x00, 0, 48, (U8*)"F_Num:");

 

    last_res   = 0xFFFFFFFF;

    last_fixed = 0xFF;

    last_ang10 = 0xFFFFFFFF;

    last_zcnt  = 0xFFFFFFFF;

    last_zn    = 0xFFFFFFFF;

    last_fcnt  = 0xFFFFFFFF;

    last_fn    = 0xFFFFFFFF;

 

    last_ui_ms = delay_ms_counter;

 

    while (1)

    {

        uint32_t res, zp_cnt, zn, fp_cnt, fn, ang_x10;

        uint8_t  fixed;

        float    angle;

        uint8_t  i;

 

        /* 100ms窗口:每1ms更新一次方向/计数 */

        for (i = 0; i < 100; i++)

        {

            Encoder_UpdateDirection();

            delay_ms(1);

        }

 

        /* ---- 以下每100ms刷新一次显示 ---- */

        res   = Encoder_GetResolution();

        fixed = Encoder_IsResFixed();

        angle = Encoder_GetAngle();

 

        zp_cnt = Encoder_ZPulseWidthCnt;

        zn     = Encoder_ZPulseCount;

        fp_cnt = Encoder_FPulseWidthCnt;

        fn     = Encoder_FPulseCount;

 

        if ((res != last_res) || (fixed != last_fixed))

        {

            lcd_print_res_lock_style(0x00, 54, 8, res, fixed);

            last_res   = res;

            last_fixed = fixed;

        }

 

        if (res == 0)

        {

            ang_x10 = 0xFFFFFFFF;

        }

        else

        {

            ang_x10 = (uint32_t)(angle * 10.0f + 0.5f);

            if (ang_x10 > 3600) ang_x10 = 0;

        }

 

        if (ang_x10 != last_ang10)

        {

            print_string_5x7_xy(0x00, 54, 16, (U8*)"          ");

            lcd_print_ang_x10(0x00, 54, 16, ang_x10);

            last_ang10 = ang_x10;

        }

 

        if (zp_cnt != last_zcnt)

        {

            print_string_5x7_xy(0x00, 54, 24, (U8*)"          ");

            print_U32_5x7_xy(0x00, 54, 24, zp_cnt);

            last_zcnt = zp_cnt;

        }

 

        if (zn != last_zn)

        {

            print_string_5x7_xy(0x00, 54, 32, (U8*)"          ");

            print_U32_5x7_xy(0x00, 54, 32, zn);

            last_zn = zn;

        }

 

        if (fp_cnt != last_fcnt)

        {

            print_string_5x7_xy(0x00, 54, 40, (U8*)"          ");

            print_U32_5x7_xy(0x00, 54, 40, fp_cnt);

            last_fcnt = fp_cnt;

        }

 

        if (fn != last_fn)

        {

            print_string_5x7_xy(0x00, 54, 48, (U8*)"          ");

            print_U32_5x7_xy(0x00, 54, 48, fn);

            last_fn = fn;

        }

    }

}


十九、总结

本文实现了一个基于 STM32F4 的增量式编码器角度测量与 LCD 显示程序。程序利用 TIM1 编码器接口模式完成 A、B 相计数,通过 Z 相中断实现分辨率自动测量,并在检测到目标分辨率 2880 后自动锁定。同时,程序还能实时计算机械角度,并在 LCD 上显示当前分辨率、角度、Z 相脉宽和脉冲次数。

该程序适用于以下场景:

  • 增量式编码器调试;

  • 电机转角检测;

  • 转台角度显示;

  • 编码器分辨率自动识别;

  • 机械位置反馈系统;

  • 运动控制实验平台。

 

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐