ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

多摩川编码器与STM32适配方案:T-Format与增量式读取全解析

多摩川编码器与STM32适配方案:T-Format与增量式读取全解析 简介面向STM32F429的多摩川TS5668N21编码器读取程序是一套完整的嵌入式工程资源适用于工业自动化、伺服电机控制与精密定位等需要高精度角度或速度反馈的场景。程序围绕ADM485半双工协议使用UART与RS-485驱动控制完成编码器数据交互解决了旋转位置精确测量与实时传输问题。资源共263个文件以C源文件、头文件、汇编启动文件为主同时包含Keil工程配置、链接脚本、调试辅助文件及编译中间产物压缩包约4.79MB源码目录结构清晰便于在STM32F429开发板上直接编译与烧录。已有4790人学习下载配套完整性较好。工程内部给出了STM32CubeMX初始化配置、HAL库中断服务例程以及编码器数据处理示例涉及UART的RTS方向控制、波特率与奇偶校验设置、A/B相正交解码、Z相零位捕获、霍尔信号读取等关键细节还专门处理了编码器上电稳定等待和中断防丢脉冲锁存机制可帮助开发者快速理解并移植高精度编码器读取逻辑。无论是初次接触多摩川编码器还是需要快速搭建STM32测控方案都能从这套工程中获得完整参考。 接手过一个机械臂关节项目电机和减速器都定好了编码器用的是多摩川17位多圈绝对值型号T-Format协议。当时想的很简单读个编码器而已结果从信号链路到协议时序前后折腾了整整一周才跑稳。这篇文章把多摩川编码器配STM32的完整读取方案梳理一遍重点讲最常用的T-Format读取方式、增量式编码器的定时器方案以及实测中容易忽略的坑给后面做伺服驱动、机器人关节、数控改造的朋友省点时间。1. 先搞清楚手上的编码器是哪种协议多摩川编码器不是一个统一的多摩川协议。同一个品牌下面至少有三种完全不同的读取方式协议类型常见型号/系列接口形态STM32侧读取方案T-FormatTS5700N、TS5600N系列CLK/DATA差分RS422SPI主机模拟时序或GPIO位操作BiSS-C较新的绝对式编码器SCLK/SLO差分SPI主机时钟控制增量式普通A/B/Z输出A/A-、B/B-、Z/Z-定时器编码器模式这个区分非常关键。网上能找到的代码往往只针对某一特定协议套到另一种型号上基本跑不通。判断方法其实不复杂看编码器铭牌型号TS开头的大概率是T-Format资料里写了BiSS或BISS-C的就是后一种如果输出是A/B/Z或者带U/V/W相那是增量式。我的建议是拿到编码器先别急着写程序用万用表量一遍引脚定义再用示波器看一眼上电后DATA线上有没有稳态电平。这一步能快速确认拿到的是差分接口还是单端接口避免后面硬件接错浪费两三天调程序的时间。另外一个容易被忽略的点是编码器的圈数范围。同样是TS5700N系列有的只有单圈17位有的是17位单圈12位多圈帧结构差异很大。如果你只按位置和圈数加起来的方式读遇到单圈型号会读出一堆无意义的高位数据。所以第一步一定是确认型号规格书而不是搜一个代码直接编译。2. 硬件链路设计编码器信号不能直接进STM32引脚多摩川编码器绝大多数是5V供电信号电平也是5V级别部分型号还是差分输出。STM32的GPIO是3.3V IO两者直接对接会出现两个问题STM32引脚不耐受5V除非该引脚是FTP且配置为开漏否则长期使用有烧坏风险差分信号不转换成单端STM32根本读不到数据。所以在硬件上需要加一个电平转换/差分接收的环节。这里给出一个在伺服驱动板上很常见的接法差分接收CLK/CLK-、DATA/DATA-分别接AM26LS32或MAX3488这类RS422接收芯片输出端变成3.3V单端逻辑后进STM32。如果是STM32输出时钟给编码器单端引脚需要经过RS422驱动器比如AM26LS31转成差分再接进编码器。单端接收如果你的编码器型号是单端输出最简单的方法是加一颗电平转换芯片如TXS0108EPWR或者用电阻分压把5V降到3.3V再进GPIO。注意分压电阻要考虑信号速率编码器时钟通常在几百kHz到几MHz串联电阻大了会拉缓信号边沿。这种情况下务必用示波器验证边沿不要只看电平对不对。供电和共地是另一个大坑。编码器供电5V、电流几百毫安最好不要从3.3V LDO反升上去直接取自控制板上干净的5V电源并在编码器电源引脚就近放一个100nF陶瓷电容和一个10uF/25V电解电容。控制板与编码器之间必须共地否则差分接收器也会偶发出现翻转错误表现就是位置偶尔跳一个很大值。下面用表格把关键信号和STM32引脚的对应关系列出来以SPI1为例编码器信号转换后电平STM32引脚说明CLK / CLK-3.3V单端CLKSPI1_SCK (PB3)主机给编码器的时钟DATA / DATA-3.3V单端DATASPI1_MISO (PB4)编码器返回的数据CS / 使能引脚3.3V任意GPIO常用PB9控制一帧读取的起止5V / GND--编码器供电与共地注意如果STM32和编码器距离超过20cm走线建议用双绞线并在接收端靠近芯片的位置并联一个120Ω的终端电阻。差分信号在长线上没有匹配电阻时边沿反射会在高速时导致数据频繁出错这类问题最隐蔽看起来像程序bug实际是硬件问题。3. T-Format读取程序拆解本质是用SPI模拟编码器时序T-Format协议从主机侧看其实就是一个SPI主模式通信主机把CS拉低连续输出时钟编码器在时钟配合下把位置、圈数、CRC逐位返回给MISO。区别在于每帧的位宽比较特殊往往不是8位对齐所以直接按字节读SPI会很别扭。我采用的方案是SPI主机按8位一个字节发送0xFF也可以发任意dummy值同时接收MISO等到收满一帧所需字节数再把收到的bit流按协议帧格式解析。这样比GPIO逐位翻转代码简洁而且SPI硬件能保证时钟稳定。3.1 CubeMX配置要点SPI1选主模式8位数据宽度MSB先行CPOL0、CPHA1编码器在CLK下降沿更新数据、上升沿稳定所以主机应在上跳沿采样。不同型号可能相反实测如果读到全0全1就改成CPOL0/CPHA0再试波特率预分频取8或16先让SCK工作在1MHz以内把协议跑通后再提频NSS改成软件控制CS引脚用GPIO手动拉低。初始化代码HAL库void encoder_spi_init(void) { GPIO_InitTypeDef gpio {0}; SPI_InitTypeDef spi {0}; __HAL_RCC_GPIOB_CLK_ENABLE(); __HAL_RCC_SPI1_CLK_ENABLE(); // CS引脚PB9 gpio.Pin GPIO_PIN_9; gpio.Mode GPIO_MODE_OUTPUT_PP; gpio.Pull GPIO_PULLUP; gpio.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOB, gpio); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_SET); // CS默认高 // SCK/MISO配置为复用功能 gpio.Pin GPIO_PIN_3 | GPIO_PIN_4; gpio.Mode GPIO_MODE_AF_PP; gpio.Alternate GPIO_AF5_SPI1; HAL_GPIO_Init(GPIOB, gpio); hspi1.Instance SPI1; spi.Mode SPI_MODE_MASTER; spi.Direction SPI_DIRECTION_2LINES; spi.DataSize SPI_DATASIZE_8BIT; spi.CLKPolarity SPI_POLARITY_LOW; spi.CLKPhase SPI_PHASE_2EDGE; spi.NSS SPI_NSS_SOFT; spi.BaudRatePrescaler SPI_BAUDRATEPRESCALER_8; spi.FirstBit SPI_FIRSTBIT_MSB; HAL_SPI_Init(hspi1); }这里有个细节如果你的板子SPI引脚不是默认复用位置CubeMX会帮你算好Alternate功能号。F103系列如果使用重映射引脚还需要在代码里使能AFIO重映射否则SCK和MISO就是不通的。调试SPI通信前先用逻辑分析仪把SCK波形量出来确认时钟真的到了编码器端能省掉很多盲猜。3.2 读帧与解析一帧的位长度因型号而异。例如TS5700N系列常见的是起始位1位 单圈位置17位 多圈圈数12位 CRC若干位。下面这段代码把帧长和各字段位偏移做成参数移植时改宏定义即可#define ENC_FRAME_BITS 40 // 一帧总位数按规格书填 #define ENC_START_BITS 1 // 起始位 #define ENC_POS_BITS 17 // 单圈位数 #define ENC_TURN_BITS 12 // 多圈位数无多圈填0 #define ENC_CRC_BITS 8 // CRC位数 static int enc_read_bit(const uint8_t *buf, int bit_idx) { int byte bit_idx 3; int bit 7 - (bit_idx 7); return (buf[byte] bit) 0x01; } static uint32_t enc_extract_bits(const uint8_t *buf, int start_bit, int len) { uint32_t val 0; for (int i 0; i len; i) { val (val 1) | enc_read_bit(buf, start_bit i); } return val; } int encoder_read(uint32_t *pos, uint32_t *turns) { uint8_t tx[ENC_FRAME_BITS / 8 1]; uint8_t rx[ENC_FRAME_BITS / 8 1]; int start_bit ENC_START_BITS; memset(tx, 0xFF, sizeof(tx)); memset(rx, 0, sizeof(rx)); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_RESET); // 在这里加一点延时约为1~2us给编码器准备时间 HAL_SPI_TransmitReceive(hspi1, tx, rx, sizeof(tx), 100); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_SET); // 检查起始位一般为0 if (enc_read_bit(rx, 0) ! 0) { return -1; } *pos enc_extract_bits(rx, start_bit, ENC_POS_BITS); start_bit ENC_POS_BITS; if (ENC_TURN_BITS 0) { *turns enc_extract_bits(rx, start_bit, ENC_TURN_BITS); start_bit ENC_TURN_BITS; } else { *turns 0; } // CRC校验不同型号多项式不同务必按规格书实现 // crc_recv enc_extract_bits(rx, start_bit, ENC_CRC_BITS); // crc_calc enc_crc_calc(rx, start_bit - 1); // if (crc_calc ! crc_recv) return -2; return 0; }代码里我故意把CRC校验留成注释因为多摩川不同型号的CRC多项式和起始位定义差别很大。没有CRC校验就开始调试确实也能转但批量装机后大概率会在低温或大电流工况下出现偶发位置跳变而且非常难查。规格书里的CRC段落大多会给一个示例序列或者多项式照着实现就行。4. 把位置和圈数变成机械角度拼接与归一化拿到位置和圈数之后直接把这两个raw值丢给控制环是没有意义的得换算成机械角度和速度。4.1 总位置计算如果编码器是17位单圈12位多圈总位置 圈数 × 131072 位置。这里有个大坑圈数翻转时位置位是否在同一时刻归零取决于协议中圈数和位置的同步关系。有的型号圈数位和位置位在同一帧内天然一致有的型号需要你确认零位是否对齐。最稳妥的做法是让电机按固定方向匀速转几百圈同时采集圈数-位置曲线。如果出现阶梯误差说明进位不同步需要在软件里做补偿。实际伺服项目里我会在编码器驱动层多封装一层返回一个连续的uint64_t脉冲数控制层完全不用关心圈数和位置拼接的问题。uint64_t total_steps (uint64_t)(*turns) * (1UL ENC_POS_BITS) *pos; float angle_deg (float)(total_steps ((1UL (ENC_POS_BITS ENC_TURN_BITS)) - 1)) * 360.0f / (float)(1UL ENC_POS_BITS);上面的角度计算限定在单圈内输出0~360度适合直接给FOC做电角度。如果要算多圈绝对值角度去掉掩码限制即可。需要说明的是机械角度到电角度还要乘以极对数这一步通常放在电流环里做不放在编码器驱动层。4.2 零位校准绝对式编码器上电自带绝对位置但它的零点通常是编码器出厂时设定的并不等于机械结构的零点。工程上我会在装配完成后做一次找零处理手动把机械轴转到设计的零点位置保存当前编码器总位置到Flash运行时用当前值减零点值作为实际位置。如果板子上有其他通信接口也可以把这个零点值在调试模式下用串口写入。千万别把零点值写死在代码里——换一台电机就要重新编译维护起来很痛苦。我在项目里一般会预留一条串口命令类似set zero和read zero产线校准时直接发命令完成不碰代码。4.3 转速计算控制环往往同时需要速度反馈。最简单的方式是位置差分static uint64_t last_total_steps 0; static int32_t speed_steps 0; int32_t speed (int32_t)(total_steps - last_total_steps); last_total_steps total_steps; speed_steps (int32_t)(0.8f * speed_steps 0.2f * speed); // 简单一阶低通这个一阶滤波系数0.8/0.2不是定死的。控制周期1kHz、编码器分辨率较高时速度环需要更平滑可以把低通截止频率放在50~200Hz之间系数按 alpha dt / (RC dt) 计算。没有滤波直接差分速度波形会非常吵电流环跟着抖。5. 实测最容易踩的四个坑与排查思路这一节是全篇价值比较高的部分因为编码器上电时序、SPI时钟沿这些问题规格书写得比较模糊只有实测才能确认。5.1 读到全0或全1这是最典型的T-Format移植失败现象。绝大多数时候不是硬件坏了而是SPI采样沿和编码器数据输出沿错位。多摩川T-Format很多型号是CLK下降沿移出数据、上升沿稳定采样对应SPI配置CPOL0、CPHA1个别型号正好相反。快速验证办法把CPHA改一档重新跑看数据是否变得有规律。如果两个配置都不对检查时钟有没有真的到编码器端——用示波器夹在SCK和DATA上看两路波形相对关系比盲猜快得多。我在一块自制的转接板上遇到过SCK布线断了半截波形衰减严重编码器完全无法响应。5.2 首字节丢位导致位置乱跳CS拉低后编码器并不是立刻就能采样到有效数据内部需要准备时间。某些型号在CS下降沿后要等一小段时间才响应第一个时钟。解决方法是在CS拉低后加1~2us的延时或者把第一个读取字节当作dummy丢弃从第二个字节开始拼帧。这个坑很容易被忽略尤其是用逻辑分析仪抓波形时看起来数据前几位很乱但后面正常位置值就忽大忽小。我习惯在编码器读函数里保留一个带空转的SPI读取保证数据从稳定状态开始。5.3 CRC频繁失败或偶尔位置跳变优先检查三件事编码器供电是否稳定信号线是否过长SCK频率是否过高。多摩川编码器的SCK上限通常在几MHz但实际线缆和接收电路会降低可用频率。我实测一根30cm普通杜邦线SCK跑5MHz时CRC一帧错五六次降到1MHz就完全正常了。量产项目建议SCK不超过规格书上限的四分之一换双绞屏蔽线后再提频。如果一定要跑高速接收端要加施密特触发器整形或者选用带自动方向控制的RS422芯片避免数据线上的毛刺被当成有效电平。5.4 上电顺序编码器必须先供电并稳定再开始发时钟。如果STM32和编码器共用同一个电源复位瞬间SPI可能提前动作编码器还在未就绪状态返回乱码。解决方式有两种一是读取前延时50ms以上二是在硬件上加电源监控电路电压大于4.5V时才释放SPI片选。我遇到过更隐蔽的情况STM32先跑起来编码器后上电前面几次读取的CRC全错但延时100ms后又正常。这个问题不是所有批次都会出现因为不同编码器内部逻辑上电时间有差异所以驱动里最好做连续读错N帧进入重初始化的保护逻辑。6. 增量式多摩川编码器的快读方案如果你手里的型号是增量式A/B/Z输出读取程序就简单很多不需要SPI也不需要协议解析。STM32自带定时器编码器模式能直接处理A/B正交信号还能4倍频。配置思路把A相接TIMx_CH1、B相接TIMx_CH2定时器配置为Encoder Mode TI1TI2Counter按方向自动加减定时采样读取CNT即可得到位置增量。TIM_Encoder_InitTypeDef enc {0}; sEncoderHandle.Instance TIM3; sEncoderHandle.Init.Period 0xFFFF; sEncoderHandle.Init.CounterMode TIM_COUNTERMODE_UP; sEncoderHandle.Init.ClockDivision TIM_CLOCKDIVISION_DIV1; enc.EncoderMode TIM_ENCODERMODE_TI12; enc.IC1Polarity TIM_ICPOLARITY_RISING; enc.IC2Polarity TIM_ICPOLARITY_RISING; HAL_TIM_Encoder_Init(sEncoderHandle, enc); HAL_TIM_Encoder_Start(sEncoderHandle, TIM_CHANNEL_ALL);增量式编码器的最大问题是断电丢位置所以上电后需要找零位要么撞限位要么找Z信号。如果你做的是伺服类产品只要不是极端成本敏感我还是建议选绝对式多摩川编码器闭环逻辑会简单很多。多摩川编码器读取这件事硬件链路和协议时序占了80%的坑真正的SPI读写代码反而是最顺的一部分。先把型号协议确认清楚再按上面流程把信号链路搭稳读取程序基本一次就能跑通。我在实际项目中最后保留的经验是每个编码器的参数位长、CRC、零点都单独做成配置结构体而不是散落在代码宏定义里调试和换型都会省很多事。本文还有配套的精品资源点击获取
返回列表