资讯动态

STM32多圈绝对值编码器测试:从SSI数据解析到掉电记忆

发布时间:2026/9/9 18:47:54 来源:尧图企业网站定制
简介面向STM32开发者的多圈绝对值编码器测试程序基于串行同步接口SSI协议实现位置与圈数同步读取适用于工业自动化、机器人定位、数控设备等需要精确角度检测的场景。整个工程以RAR压缩包形式发布共计一百六十三个文件其中四十六个C源文件与四十六个H头文件构成核心代码配套Keil工程配置、编译生成的HEX与AXF、链接映射文件、调试配置文件等压缩包大小仅为三点二兆字节便于快速下载与导入开发环境。目前已有三千一百零四人学习。工程结构清晰从初始化代码、SSI通信收发、编码器数据解析到错误处理与日志输出均有完整实现并预留串口打印接口可直接编译运行开发者既能对照代码理解硬件时序也能基于现有模块做功能扩展或平台移植是掌握绝对式编码器及STM32外设应用的实用参考。 第一次把多圈绝对值编码器接到STM32开发板上我习惯性地去找Z相脉冲中断翻完整个接线表才发现根本没有这根信号线。这个小小的不适应恰恰说明多圈绝对值编码器和增量编码器在工程思路上的分水岭你要测的不再是频率和脉冲数而是一份断电后依然记得自己转到第几圈的位置数据。这篇文章记录的是我基于STM32给多圈绝对值编码器写测试程序的完整过程覆盖的范围包括接口协议确认、硬件接线、数据解析、稳定性判断以及圈数溢出、掉电记忆这些多圈器件特有的现象。文章里给的代码和排查思路我按HAL库体系整理过适合刚入手绝对值编码器、第一次用STM32读SSI或SPI数据的开发者参考。如果你需要给这类编码器做导入检验或者做产线工装直接拿走改参数也能用。1. 为什么测试多圈绝对值编码器需要单独写程序很多做运动控制的工程师第一次接触绝对值编码器潜意识里还是会沿用增量编码器的思路去测试接上A相B相开个定时器数脉冲。这套方法放到多圈绝对值编码器上完全行不通因为它的输出根本不是一个脉冲序列而是一串带位置信息的数字量。1.1 三种编码器的本质差异先把概念理清楚。增量编码器输出的是相位差90度的两路脉冲位置靠外部计数器累加得到断电之后计数清零重新上电必须找原点。单圈绝对值编码器可以直接读出当前角度但这个角度只覆盖0到360度一旦转出这一圈它就不知道自己在第几圈了。多圈绝对值编码器在单圈绝对值的基础上内部多维护了一套圈数记录读出来的数据直接就是“第几圈加圈内角度”的完整绝对位置。项增量编码器单圈绝对值编码器多圈绝对值编码器输出内容A/B脉冲需计数一圈内的绝对角度圈数一圈内的绝对角度断电后位置丢失角度保留圈数丢失圈数和角度都保留上电是否找零需要同一圈内不需要不需要典型场景电机测速、手轮转台、机械臂关节垂直升降轴、堆垛机、旋转分度台我做设备调试时遇到过一种很典型的情况设备装了垂直升降轴用的增量编码器每次断电重启都要先回到原点否则位置等于零。后来换成了多圈绝对值编码器上电直接读圈数和角度设备省掉了一次回零动作整个开机流程缩短了好几秒。这就是这类编码器的核心价值——位置数据本身就是完整的不需要依赖外部计数。1.2 测试程序到底要回答什么问题既然器件本身的价值在“断电记忆”和“位置即得”那测试程序就不能只把数据读出来看个热闹。我给自己定的测试目标是回答下面五个问题少一个都不算测完硬件链路是否通畅时钟线、数据线、片选线的时序配合是否正确能不能稳定读到数据。数据解析是否正确多圈字段和单圈字段的拼接顺序对不对掩码有没有做对。静态稳定性如何转轴静止时连续读取是否跳变跳变幅度是否在分辨率以内。断电记忆是否生效切断供电再上电圈数和角度是否保持原值。方向定义是否一致按安装方向正转时读数应该增大还是减小程序里需不需要做方向取反。后面所有章节的内容本质上都是在回答这五个问题。带着这些目标去写测试程序就不会写完一段读取代码就以为万事大吉。2. 动手前先把接口协议和数据格式敲定这块是测试程序最容易翻车的地方。很多人拿到编码器就急着接STM32结果读回来的数据要么是全零要么是乱跳排查半天发现是协议没搞对。2.1 SSI、SPI还是RS485先分清接口类型多圈绝对值编码器常见的数字接口有三种SSI、SPI、RS485。它们用起来差别很大测试程序的写法也完全不同。SSI是工业绝对值编码器最常见的接口。主机发一串时钟脉冲从机在时钟驱动下把数据位同步输出一次性把整包数据推完。它的数据位长比较随意13位、17位、25位、29位都有完全由具体型号决定。SPI接口的读取逻辑和SSI类似但工业编码器上的SPI时序不一定和STM32标准SPI外设完全一致片选信号的行为、时钟空闲电平、数据采样边沿都需要对着数据手册确认。RS485接口多走Modbus RTU协议主机发读寄存器帧从机回数据帧测试程序的核心就变成了串口收发和CRC16校验而不是SPI时序。我在选型阶段一般先看编码器标签上的接口标识再翻数据手册的时序图。SSI和SPI容易搞混因为它们都是主机提供时钟、从机吐数据。最简单的区分办法是看手册里数据位长如果写的是“25位SSI”那就是连续时钟输出一整包如果写的是“SPI兼容”通常意味着可以按字节读但要注意片选和时钟极性的参数。2.2 数据位长和拼接顺序决定了解析代码怎么写以常见的配置举个例子单圈分辨率17位多圈12位总数据位长就是29位。意思是单圈角度被量化成2^17等于131072份圈数范围是2^12等于4096圈。读取到的原始数据里有一段代表圈数有一段代表角度测试程序要做的就是正确地把这两个字段拆出来。不同厂家的字段排列顺序不一样最常见的是多圈在前、单圈在后但也有一小部分厂家反过来。判断方法很土但很有效用手缓慢旋转编码器轴观察读回来哪个字段在变。如果转一圈圈数位没有任何变化而低位角度在走满整个范围那顺序就没搞错。反过来如果变化量出现在高位的圈数字段说明拼接顺序反了。代码里务必用掩码把圈数和角度分离开不要直接把整个uint32_t当作位置值去用。两个字段一起变化的时候不做掩码很容易解读出莫名其妙的数字。2.3 电平、差分与接线的几个硬性注意点编码器供电常见5VSTM32的IO是3.3V信号电平能不能直接对接要看编码器的输出形式。推挽输出的3.3V兼容型号可以直接接集电极开路输出的需要外部上拉5V TTL输出则要考虑分压或电平转换。另一个容易踩的坑是差分信号。一部分工业SSI编码器为了抗干扰数据线走RS422差分输出A和A-两个引脚。这种编码器不能直接接到STM32的GPIO上中间必须加差分接收芯片比如AM26LV32这类TTL转换芯片。如果手头只有单端输入的MCU硬接的结果就是数据完全读不到。接线时还有一个经验编码器供电不要和电机驱动共用一个电源尤其是测试阶段电机一启动电源毛刺就可能让读到的数值跳一个分辨率。编码器线的屏蔽层建议单端接地测试用的杜邦线尽量短线越长数据线受到干扰的概率越大。3. STM32测试程序的代码骨架HAL库版我用的是STM32F103和STM32F407两块板子做过同样的测试代码主体基本通用。下面这套基于HAL库的框架核心思想是把读取、解析、判稳三个动作拆开方便单独调。3.1 CubeMX里的基础配置-CubeMX里把SPI配成主机模式速率先压在500kHz到1MHz之间等时序确认稳定了再尝试拉高。片选信号用普通GPIO控制不要在SPI外设里自动管理NSS这样时序可控也方便后面改成软件模拟。串口开一个用于打印观测数据波特率115200重定向printf到UART即可。时钟极性和相位的选择先按数据手册推荐值来。如果手册没写测试阶段优先试CPOL0、CPHA0不行再换CPOL0、CPHA1。相位不对最典型的症状是数据全是0xAA、0x55这类规律性错位数据。3.2 一次完整的读取与解析下面的代码用一个数组接收原始字节再把字节拼成uint32_t最后按预定位数拆出圈数和角度。不同型号的编码器只需要修改开头的三个宏。#include main.h #define ENC_ANGLE_BITS 17 /* 单圈分辨率位数按数据手册修改 */ #define ENC_TURN_BITS 12 /* 多圈范围位数按数据手册修改 */ #define ENC_TOTAL_BITS (ENC_ANGLE_BITS ENC_TURN_BITS) #define ENC_BYTES ((ENC_TOTAL_BITS 7) / 8) /* 读取一次原始数据 */ uint32_t read_encoder_raw(void) { uint8_t buf[ENC_BYTES] {0}; uint32_t raw 0; int i; HAL_GPIO_WritePin(ENC_CS_GPIO_Port, ENC_CS_Pin, GPIO_PIN_RESET); HAL_SPI_Receive(hspi1, buf, ENC_BYTES, 10); HAL_GPIO_WritePin(ENC_CS_GPIO_Port, ENC_CS_Pin, GPIO_PIN_SET); for (i 0; i ENC_BYTES; i) { raw (raw 8) | buf[i]; } /* 如果数据左对齐需要右移掉多余的填充位 */ raw (ENC_BYTES * 8 - ENC_TOTAL_BITS); return raw; } /* 从原始数据里拆出圈数和角度 */ void parse_encoder_data(uint32_t raw, uint32_t *turns, uint32_t *angle) { *turns (raw ENC_ANGLE_BITS) ((1UL ENC_TURN_BITS) - 1); *angle raw ((1UL ENC_ANGLE_BITS) - 1); }如果SPI外设怎么调都读不到数据不要死磕直接改成GPIO模拟时序。工业SSI的时序和标准SPI未必兼容GPIO模拟反而更灵活测试早期的排错效率更高。uint32_t read_encoder_gpio_bitbang(void) { uint32_t raw 0; int i; HAL_GPIO_WritePin(ENC_CS_GPIO_Port, ENC_CS_Pin, GPIO_PIN_RESET); for (int d 0; d 100; d); /* 片选稳定等待 */ for (i ENC_TOTAL_BITS - 1; i 0; i--) { HAL_GPIO_WritePin(ENC_SCK_GPIO_Port, ENC_SCK_Pin, GPIO_PIN_SET); for (int d 0; d 100; d); if (HAL_GPIO_ReadPin(ENC_DATA_GPIO_Port, ENC_DATA_Pin) GPIO_PIN_SET) { raw | (1UL i); } HAL_GPIO_WritePin(ENC_SCK_GPIO_Port, ENC_SCK_Pin, GPIO_PIN_RESET); for (int d 0; d 100; d); } HAL_GPIO_WritePin(ENC_CS_GPIO_Port, ENC_CS_Pin, GPIO_PIN_SET); return raw; }GPIO模拟版的好处是能看到每一位的读取过程配合示波器很容易定位问题。测试程序跑通之后如果需要更高的采样率再回到SPI外设模式也不迟。3.3 判稳逻辑与串口打印静态测试时编码器轴不动连续读取值理论上应该完全一致。我习惯在循环里连续读5次如果5次结果都一样才认为当前数据有效否则标记一次不稳定计数。uint8_t is_enc_data_stable(uint32_t *stable_val, uint8_t sample_times) { uint32_t last 0, cur 0; uint8_t i, same_count 0; for (i 0; i sample_times; i) { HAL_Delay(2); cur read_encoder_raw(); if (i 0 cur last) { same_count; } last cur; } if (same_count sample_times - 1) { *stable_val cur; return 1; } return 0; }主循环里的打印格式建议把原始值和解析值都输出。原始值用于排查字节拼接问题解析值用于观察位置变化。格式类似raw0x1A2B3C turn128 angle8912一眼就能看出数据有没有异常。注意别在定时器中断里调用HAL_Delay做判稳中断里延时会导致系统卡顿我踩过这个坑后来把判稳逻辑全放到了主循环。4. 读数不对时的排查链路如果说前面是测试程序的正常流程那这一章就是测试程序存在的意义。多圈绝对值编码器第一次上电读数不对几乎是必然经历。4.1 现象与优先怀疑点读到的现象优先怀疑点处理方式读数固定不变但电压正常时钟没发出来或片选没拉低示波器抓SCK和CS确认波形读数随机乱跳电平不匹配、接触不良、SPI速率过高降速到500kHz以下重新压线数据高低位整体错位字段顺序或数据位长理解错误缓慢转轴观察变化的是高位还是低位上电第一次读数异常之后正常上电稳定时序不足初始化后先丢弃一帧延时50ms再读其中最隐蔽的就是上电第一次读数异常。有些编码器内部有自检流程上电后需要一段时间才能稳定输出这时候去读读回来的可能是中间态。处理办法很粗暴初始化完成后延时一段时间或者直接丢弃第一次读取结果。4.2 多读几帧区分干扰和真实位置我在前面加了判稳函数实际使用中有个体会当转轴静止时如果读数频繁跳变多半是硬件问题而非软件问题。此时不要急着改代码先看电源纹波再看信号线屏蔽最后考虑时钟极性。软件上能做的只是多读几帧取众数但这治标不治本。当转轴匀速转动时读数应该按照分辨率步进递增或递减比如17位编码器每圈131072个码值。如果发现读数不是逐步变化而是偶尔跳变几百甚至几千那很大概率是时钟频率太高导致采样落在数据位的边沿附近把SPI速率调低一档往往就解决了。4.3 校验位与CRC的处理策略部分编码器的数据包里带奇偶校验位或CRC校验码这类编码器在测试程序里反而好办读到数据先过一遍校验校验失败直接算无效帧。没有校验位的编码器只能靠多读判稳来兜底。比较麻烦的是那种校验逻辑不公开的型号。遇到这种情况我的处理方式是把原始值和解析值都记录下来观察稳定状态下原始值是否恒定。如果原始值恒定但解析值异常那问题一定出在解析代码如果原始值本身就不稳定那是传输层面的问题先处理硬件链路。5. 多圈特有的行为溢出、掉电、零位与方向测试多圈绝对值编码器和测试普通传感器最大的不同在于要主动去验证那些增量编码器永远不会出现的行为。这一节里的每一条都是我在实际项目中真正遇到过的。5.1 圈数溢出不是程序bug12位多圈对应4096圈16位多圈对应65536圈。如果你一直朝一个方向转转到最大圈数附近时有些型号的圈数会回绕到0重新开始有些会饱和在最大值不动还有些会置一个溢出标志位。这三种行为都是设计使然不是编码器坏了也不是测试程序写错了。测试时我的做法是查手册确认最大圈数然后故意转到边界去把回绕或饱和的行为记录在测试结果里。这一条看起来简单但不少设备调试时因为圈数溢出导致数据突变现场排查半天才发现是应用层没考虑边界。5.2 断电记忆上电读值必须保持多圈绝对值编码器之所以值钱核心就是断电记忆。测试方法很简单记录当前圈数和角度切断编码器供电等一秒重新上电比较前后读值。如果一致记忆正常。如果不一致先排查供电引脚是否真的断开了再确认型号确实支持多圈记忆而不是被商家拿单圈编码器冒充了多圈。这里要提一句多圈记忆的实现方式。老式产品用机械齿轮记忆圈数纯物理结构不需要电池。后来出现了电池备份型断电后靠电池维持计数状态这种型号电池没电就会丢圈数。现在还有基于韦根传感器的电子式多圈不用电池也能记录圈数。拿到编码器时先搞清楚属于哪种测试方法略有差异机械式和韦根式直接断电验证即可电池备份型的还要检查电池接没接。5.3 零位写入和方向取反的处理很多多圈绝对值编码器支持零位写入功能把当前机械位置标记为坐标原点。测试程序里应该留一个按键触发零位写入的入口按下后发送写零位指令再把读数清零作为验证条件。方向问题则是另一个高频坑。顺时针转动时如果读到的数值在减小可能是编码器安装方向反了也可能是数据输出采用了补码格式。测试程序里预留一个方向取反的宏比如#define ENC_DIR_REVERSE 1在解析后做一次raw MAX_CODE - raw的处理。软件取反只适合临时验证最终项目里还是建议通过安装方式统一方向否则以后维护会非常头大。6. 测试到这一步怎么才算验收通过测试程序既然叫“测试程序”最终要能回答一个结论性的问题这颗编码器能不能用测试过程中有没有异常。我在做导入验证时会按下面的标准判断。重复精度验证固定一个机械位置断电上电重复10次读值极差应该为0。如果每次都差一点要么是供电不稳要么是编码器本身有问题。连续转动验证用手匀速转轴一整圈串口打印的角度值应该连续单调变化不允许跳变。静态稳定性验证转轴静止连续读1000次记录跳变次数和最大跳变量。方向一致性验证正转读值递增反转读值递减不能出现忽增忽减。掉电记忆验证按第5章的方法断电上电读值保持一致。这些结果建议整理成一张简单的测试记录表包含型号、接口类型、数据位长、供电电压、SPI速率、测试时间、结论这几列。后面如果编码器在设备上出了问题这张表能帮你快速排查到底是批次差异还是程序问题。我从测试程序切到正式工程程序时一般会做三处调整把GPIO模拟时序替换成SPI外设以提升读取速率把轮询读取改成定时器触发并加入错误重试机制把解析后的位置数据封装成结构体方便给运动控制逻辑调用。测试程序的定位是验证链路和器件正式程序的定位是满足控制周期两者目标不同代码不必强行共用。最后再说一个实测心得程序第一次能读到数据的时候先不要急着把编码器装到设备上也不要拿伺服电机驱动它转。我习惯拿在手里缓速转轴盯着串口助手看数值。转到圈数边界时确认溢出行为反向转确认方向逻辑断电再上电确认记忆保持。把这些人工测试全部验证通过后再考虑装机和上高速采样否则排查问题的难度会成倍上升。一套好的测试程序不只是把数据读出来更是在替你把整个链路的质量提前问一遍。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价