STM32正交编码器接口指南
·
STM32正交编码器解决方案,包含硬件连接、定时器配置、代码实现和速度计算,适用于F1/F4/G0等主流系列。
一、硬件连接与原理
1.1 正交编码器工作原理
编码器输出信号(A相和B相):
A相: ___|‾‾‾|___|‾‾‾|___|‾‾‾|___
B相: ___|‾‾‾|___|‾‾‾|___|‾‾‾|___
↑ ↑ ↑ ↑ ↑ ↑
位置计数: +1 +1 +1 +1 +1
正转时:A相超前B相90°
反转时:B相超前A相90°
Z相(可选):每转一个脉冲,用于零点校准
1.2 硬件连接
STM32F103C8T6 增量式编码器
PA0(TIM2_CH1) ←--- A相输出
PA1(TIM2_CH2) ←--- B相输出
PA2(TIM2_CH3) ←--- Z相输出(可选)
GND ←--- GND
+5V/+3.3V ←--- VCC(根据编码器规格)
注意:
1. 编码器电压要与STM32 IO电平匹配
2. 长线传输建议加100nF滤波电容
3. 开漏输出编码器需要上拉电阻
二、STM32CubeMX配置
2.1 定时器配置(以TIM2为例)
TIM2 Configuration:
├── Clock Source: Internal Clock
├── Channel 1: Encoder Mode
├── Channel 2: Encoder Mode
├── Channel 3: Encoder Mode (Z相,可选)
└── Parameter Settings:
├── Counter Settings:
│ ├── Prescaler: 0
│ ├── Counter Mode: Up
│ ├── Counter Period: 65535 (16位最大值)
│ └── Repetition Counter: 0
└── Encoder Mode:
├── Encoder Mode: Encoder Mode TI1 and TI2
├── Encoder Polarity: Rising Edge
└── Input Filter: 6 (根据噪声情况调整,0-15)
2.2 GPIO配置
PA0: Alternate Function Push Pull, No pull-up/pull-down
PA1: Alternate Function Push Pull, No pull-up/pull-down
PA2: Alternate Function Push Pull, No pull-up/pull-down
三、完整代码实现
3.1 编码器驱动头文件 (encoder.h)
#ifndef __ENCODER_H
#define __ENCODER_H
#include "stm32f1xx_hal.h"
// 编码器参数定义
#define ENCODER_RESOLUTION 1000 // 编码器线数(每转脉冲数)
#define ENCODER_REDUCTION 4 // 4倍频(正交编码)
#define ENCODER_TOTAL_RESOLUTION (ENCODER_RESOLUTION * ENCODER_REDUCTION)
#define ENCODER_TIMER_MAX 65535 // 16位定时器最大值
#define ENCODER_OVERFLOW 32768 // 溢出阈值
// 编码器数据结构体
typedef struct {
TIM_HandleTypeDef *htim; // 定时器句柄
int32_t position; // 当前位置(累计计数)
int32_t last_position; // 上次位置
int32_t speed; // 当前速度(计数/秒)
int32_t last_timer_cnt; // 上次定时器计数值
uint32_t last_time; // 上次采样时间(ms)
float rpm; // 转速(RPM)
uint8_t direction; // 旋转方向:0=停止,1=正转,2=反转
} Encoder_HandleTypeDef;
// 函数声明
HAL_StatusTypeDef Encoder_Init(Encoder_HandleTypeDef *hencoder, TIM_HandleTypeDef *htim);
void Encoder_Start(Encoder_HandleTypeDef *hencoder);
void Encoder_Stop(Encoder_HandleTypeDef *hencoder);
void Encoder_Reset(Encoder_HandleTypeDef *hencoder);
int32_t Encoder_GetPosition(Encoder_HandleTypeDef *hencoder);
float Encoder_GetAngle(Encoder_HandleTypeDef *hencoder, float gear_ratio);
int32_t Encoder_GetSpeed(Encoder_HandleTypeDef *hencoder);
float Encoder_GetRPM(Encoder_HandleTypeDef *hencoder);
void Encoder_Update(Encoder_HandleTypeDef *hencoder);
void Encoder_IRQHandler(Encoder_HandleTypeDef *hencoder);
#endif /* __ENCODER_H */
3.2 编码器驱动实现 (encoder.c)
#include "encoder.h"
#include "math.h"
// 编码器初始化
HAL_StatusTypeDef Encoder_Init(Encoder_HandleTypeDef *hencoder, TIM_HandleTypeDef *htim)
{
hencoder->htim = htim;
hencoder->position = 0;
hencoder->last_position = 0;
hencoder->speed = 0;
hencoder->last_timer_cnt = 0;
hencoder->last_time = HAL_GetTick();
hencoder->rpm = 0.0f;
hencoder->direction = 0;
// 配置定时器为编码器模式
TIM_Encoder_InitTypeDef encoder_config = {0};
encoder_config.EncoderMode = TIM_ENCODERMODE_TI12; // 使用TI1和TI2
encoder_config.IC1Polarity = TIM_ICPOLARITY_RISING;
encoder_config.IC1Selection = TIM_ICSELECTION_DIRECTTI;
encoder_config.IC1Prescaler = TIM_ICPSC_DIV1;
encoder_config.IC1Filter = 6; // 输入滤波器
encoder_config.IC2Polarity = TIM_ICPOLARITY_RISING;
encoder_config.IC2Selection = TIM_ICSELECTION_DIRECTTI;
encoder_config.IC2Prescaler = TIM_ICPSC_DIV1;
encoder_config.IC2Filter = 6;
if (HAL_TIM_Encoder_Init(htim, &encoder_config) != HAL_OK) {
return HAL_ERROR;
}
// 配置Z相(如果存在)
if (htim->Instance == TIM2 && htim->Channel == HAL_TIM_ACTIVE_CHANNEL_3) {
TIM_IC_InitTypeDef sConfigIC = {0};
sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
sConfigIC.ICFilter = 0;
HAL_TIM_IC_ConfigChannel(htim, &sConfigIC, TIM_CHANNEL_3);
}
return HAL_OK;
}
// 启动编码器
void Encoder_Start(Encoder_HandleTypeDef *hencoder)
{
HAL_TIM_Encoder_Start(hencoder->htim, TIM_CHANNEL_ALL);
// 启动Z相输入捕获(如果存在)
if (hencoder->htim->Instance == TIM2) {
HAL_TIM_IC_Start(hencoder->htim, TIM_CHANNEL_3);
}
// 初始化计数器
__HAL_TIM_SET_COUNTER(hencoder->htim, 0);
hencoder->last_timer_cnt = 0;
hencoder->last_time = HAL_GetTick();
}
// 停止编码器
void Encoder_Stop(Encoder_HandleTypeDef *hencoder)
{
HAL_TIM_Encoder_Stop(hencoder->htim, TIM_CHANNEL_ALL);
if (hencoder->htim->Instance == TIM2) {
HAL_TIM_IC_Stop(hencoder->htim, TIM_CHANNEL_3);
}
}
// 复位编码器
void Encoder_Reset(Encoder_HandleTypeDef *hencoder)
{
__HAL_TIM_SET_COUNTER(hencoder->htim, 0);
hencoder->position = 0;
hencoder->last_position = 0;
hencoder->speed = 0;
hencoder->last_timer_cnt = 0;
hencoder->rpm = 0.0f;
}
// 获取当前位置(处理溢出)
int32_t Encoder_GetPosition(Encoder_HandleTypeDef *hencoder)
{
uint16_t current_cnt = __HAL_TIM_GET_COUNTER(hencoder->htim);
int16_t diff = (int16_t)(current_cnt - hencoder->last_timer_cnt);
// 处理溢出
if (diff > ENCODER_OVERFLOW) {
// 向下溢出
hencoder->position -= (ENCODER_TIMER_MAX - current_cnt + hencoder->last_timer_cnt + 1);
} else if (diff < -ENCODER_OVERFLOW) {
// 向上溢出
hencoder->position += (current_cnt + ENCODER_TIMER_MAX - hencoder->last_timer_cnt + 1);
} else {
// 正常情况
hencoder->position += diff;
}
hencoder->last_timer_cnt = current_cnt;
return hencoder->position;
}
// 获取角度(度)
float Encoder_GetAngle(Encoder_HandleTypeDef *hencoder, float gear_ratio)
{
int32_t position = Encoder_GetPosition(hencoder);
float total_counts_per_rev = ENCODER_TOTAL_RESOLUTION * gear_ratio;
return (float)position * 360.0f / total_counts_per_rev;
}
// 获取速度(计数/秒)
int32_t Encoder_GetSpeed(Encoder_HandleTypeDef *hencoder)
{
uint32_t current_time = HAL_GetTick();
uint32_t time_diff = current_time - hencoder->last_time;
if (time_diff > 0) {
int32_t position_diff = Encoder_GetPosition(hencoder) - hencoder->last_position;
hencoder->speed = (position_diff * 1000) / time_diff; // 转换为计数/秒
hencoder->last_position = Encoder_GetPosition(hencoder);
hencoder->last_time = current_time;
}
return hencoder->speed;
}
// 获取转速(RPM)
float Encoder_GetRPM(Encoder_HandleTypeDef *hencoder)
{
int32_t speed = Encoder_GetSpeed(hencoder);
hencoder->rpm = (float)speed * 60.0f / ENCODER_TOTAL_RESOLUTION;
// 判断方向
if (hencoder->rpm > 0.1f) {
hencoder->direction = 1; // 正转
} else if (hencoder->rpm < -0.1f) {
hencoder->direction = 2; // 反转
} else {
hencoder->direction = 0; // 停止
}
return hencoder->rpm;
}
// 更新编码器状态(在主循环中定期调用)
void Encoder_Update(Encoder_HandleTypeDef *hencoder)
{
static uint32_t last_update_time = 0;
uint32_t current_time = HAL_GetTick();
// 每10ms更新一次
if (current_time - last_update_time >= 10) {
Encoder_GetRPM(hencoder);
last_update_time = current_time;
}
}
// Z相中断回调函数
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM2 && htim->Channel == HAL_TIM_ACTIVE_CHANNEL_3) {
// Z相触发,重置位置计数
// 这里可以通过全局变量通知主程序
__HAL_TIM_SET_COUNTER(htim, 0);
}
}
3.3 主程序集成 (main.c)
#include "main.h"
#include "encoder.h"
// 全局变量
TIM_HandleTypeDef htim2;
Encoder_HandleTypeDef encoder1;
// 函数声明
void SystemClock_Config(void);
static void MX_TIM2_Init(void);
static void MX_GPIO_Init(void);
int main(void)
{
// HAL初始化
HAL_Init();
SystemClock_Config();
// GPIO初始化
MX_GPIO_Init();
// 定时器初始化
MX_TIM2_Init();
// 编码器初始化
if (Encoder_Init(&encoder1, &htim2) == HAL_OK) {
printf("编码器初始化成功!\n");
} else {
printf("编码器初始化失败!\n");
Error_Handler();
}
// 启动编码器
Encoder_Start(&encoder1);
// 主循环
while (1) {
// 更新编码器数据
Encoder_Update(&encoder1);
// 获取编码器数据
int32_t position = Encoder_GetPosition(&encoder1);
float angle = Encoder_GetAngle(&encoder1, 1.0f); // 无减速比
float rpm = Encoder_GetRPM(&encoder1);
// 每500ms打印一次数据
static uint32_t last_print_time = 0;
if (HAL_GetTick() - last_print_time >= 500) {
printf("位置: %ld, 角度: %.2f°, 转速: %.2f RPM, 方向: %s\n",
position, angle, rpm,
encoder1.direction == 0 ? "停止" :
encoder1.direction == 1 ? "正转" : "反转");
last_print_time = HAL_GetTick();
}
HAL_Delay(10);
}
}
// TIM2初始化函数
static void MX_TIM2_Init(void)
{
__HAL_RCC_TIM2_CLK_ENABLE();
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;
if (HAL_TIM_Base_Init(&htim2) != HAL_OK) {
Error_Handler();
}
}
// GPIO初始化
static void MX_GPIO_Init(void)
{
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitTypeDef GPIO_InitStruct = {0};
// PA0, PA1, PA2 配置为复用功能
GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF1_TIM2;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
// 系统时钟配置(根据实际晶振调整)
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
// 使能HSE
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; // 8MHz * 9 = 72MHz
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) {
Error_Handler();
}
// 配置系统时钟
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK |
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();
}
}
四、高级功能扩展
4.1 多编码器支持 (multi_encoder.c)
// 支持多个编码器
#define MAX_ENCODERS 2
typedef struct {
Encoder_HandleTypeDef encoders[MAX_ENCODERS];
uint8_t active_count;
} MultiEncoder_HandleTypeDef;
MultiEncoder_HandleTypeDef multi_encoder;
// 初始化多个编码器
HAL_StatusTypeDef MultiEncoder_Init(void)
{
// 编码器1:TIM2
if (Encoder_Init(&multi_encoder.encoders[0], &htim2) != HAL_OK) {
return HAL_ERROR;
}
// 编码器2:TIM3(如果有第二个编码器)
if (Encoder_Init(&multi_encoder.encoders[1], &htim3) != HAL_OK) {
return HAL_ERROR;
}
multi_encoder.active_count = 2;
return HAL_OK;
}
// 更新所有编码器
void MultiEncoder_Update(void)
{
for (int i = 0; i < multi_encoder.active_count; i++) {
Encoder_Update(&multi_encoder.encoders[i]);
}
}
4.2 速度滤波 (encoder_filter.c)
// 低通滤波器,平滑速度数据
typedef struct {
float alpha; // 滤波系数 (0-1)
float filtered_speed; // 滤波后的速度
float raw_speed; // 原始速度
} LowPassFilter;
// 初始化滤波器
void Filter_Init(LowPassFilter *filter, float cutoff_freq, float sample_rate)
{
float RC = 1.0f / (2.0f * 3.1415926f * cutoff_freq);
float dt = 1.0f / sample_rate;
filter->alpha = dt / (RC + dt);
filter->filtered_speed = 0.0f;
filter->raw_speed = 0.0f;
}
// 应用滤波
float Filter_Apply(LowPassFilter *filter, float input)
{
filter->raw_speed = input;
filter->filtered_speed = filter->alpha * input +
(1.0f - filter->alpha) * filter->filtered_speed;
return filter->filtered_speed;
}
参考代码 STM32正交编码器 www.youwenfan.com/contentcsv/135914.html
五、调试与故障排除
5.1 常见问题及解决方案
| 问题 | 原因 | 解决方法 |
|---|---|---|
| 计数不稳定 | 接触不良或噪声干扰 | 1. 检查接线 2. 增加输入滤波器 3. 添加去耦电容 |
| 方向错误 | A/B相接反 | 交换A/B相接线或反转计数方向 |
| 计数丢失 | 转速过快 | 1. 提高定时器时钟频率 2. 减少输入滤波器 |
| 位置漂移 | 未处理溢出 | 使用32位变量累计计数,正确处理溢出 |
| Z相不触发 | 电平不匹配 | 检查Z相输出电压和STM32 IO电平 |
5.2 调试工具函数
// 调试函数:打印编码器状态
void Encoder_PrintDebug(Encoder_HandleTypeDef *hencoder)
{
printf("=== 编码器调试信息 ===\n");
printf("定时器: TIM%d\n", hencoder->htim->Instance == TIM2 ? 2 : 3);
printf("当前计数: %d\n", __HAL_TIM_GET_COUNTER(hencoder->htim));
printf("累计位置: %ld\n", hencoder->position);
printf("速度: %ld 计数/秒\n", hencoder->speed);
printf("转速: %.2f RPM\n", hencoder->rpm);
printf("方向: %s\n", hencoder->direction == 0 ? "停止" :
hencoder->direction == 1 ? "正转" : "反转");
printf("=====================\n");
}
// 测试编码器响应
void Encoder_TestResponse(Encoder_HandleTypeDef *hencoder)
{
printf("手动旋转编码器,观察计数变化...\n");
int32_t last_pos = Encoder_GetPosition(hencoder);
for (int i = 0; i < 10; i++) {
HAL_Delay(500);
int32_t current_pos = Encoder_GetPosition(hencoder);
printf("位置变化: %ld -> %ld (Δ=%ld)\n",
last_pos, current_pos, current_pos - last_pos);
last_pos = current_pos;
}
}
六、性能优化建议
6.1 定时器配置优化
// 提高定时器时钟频率
__HAL_RCC_TIM2_CLK_ENABLE();
__HAL_TIM_SET_CLOCKPRESCALER(&htim2, TIM_CLOCKPRESCALER_DIV1);
// 使用32位定时器(如果可用)
#if defined(TIM2) && defined(STM32F4xx)
// STM32F4的TIM2是32位定时器,可以减少溢出处理
#endif
6.2 中断方式更新速度
// 使用定时器中断定期更新速度,避免主循环延迟
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM6) { // 使用基本定时器作为时基
Encoder_Update(&encoder1);
}
}
更多推荐

所有评论(0)