DSP28335与AD2S1210 SPI时序配置详解:位置采集避坑指南
简介:一份面向电机控制开发者的技术资料,讲解如何利用DSP28335的SPIA模块配置高精度旋变解码芯片AD2S1210,完成电机转子位置与速度采集。内容涵盖SPI接口初始化(含时钟极性/相位设置)、GPIO引脚控制、SPI收发函数编写、AD2S1210复位流程、读写操作及寄存器配置等完整步骤,并附有具体代码示例和简化流程图,适合刚接触旋变采集或数字信号处理外设调试的人员参考。压缩包内仅含1个docx文档,大小约1.79MB,重点呈现从硬件连线到固件实现的排错思路与配置细节。已有1054人学习这套资料,通过文档可快速掌握SPI通信时序配置、模式切换逻辑以及关键寄存器取值方法,从而减少开发中反复查阅数据手册的时间。
1. 从 SPI 时序翻车说起:DSP28335 和 AD2S1210 位置采集到底难在哪
做电机控制的人,大概率迟早会和旋转变压器(Resolver)打交道。AD2S1210 这颗芯片常用在伺服电机的角度、速度检测上,和 DSP28335 的 SPIA 模块是很经典的一套组合。但这套组合的坑几乎都藏在 SPI 的时钟极性和相位里——AD2S1210 要求“无相位延迟的上升沿”时序,也就是 SPICLK 低电平有效、上升沿发送、下降沿接收,一旦把相位配成了 1、1 模式,你读回来的数据会整字节错位,而且无论怎么调延时都救不回来。整个搭建过程会从 SPIA 时钟使能、GPIO 复用、SPI 寄存器参数(0x07、0x0006、0x0012)、AD2S1210 复位与读写函数一直写到最后的验证方法,全部步骤都按实际可运行代码给出,适合正卡在位置信息采集上的开发者照着落地。
2. 先铺外设时钟和 GPIO:少了这一步后面全是黑匣子
2.1 使能 SPIA 时钟:不同工程模板下最容易漏的一步
很多人拿到 DSP28335 的工程模板,上来就写SpiaRegs.SPICCR,结果寄存器写不进去、SPI 引脚上也没有时钟输出,第一反应是寄存器地址写错了,其实大多数情况是外设时钟根本没开。DSP28335 的外设时钟默认很多是关闭的,SPIA 模块挂在 PCLKCR0 寄存器上,需要先把对应的位打开,否则后面所有 SPI 配置都不会生效。
void InitSpiaGpio(void) { EALLOW; SysCtrlRegs.PCLKCR0.bit.SPIAENCLK = 1; // 使能 SPIA 外设时钟 EDIS; // 继续配置 SPI 引脚复用,见 2.2 节 }这段代码放在main函数最前面,InitSysCtrl()之后立刻调用。PCLKCR0属于受保护寄存器,赋值语句必须包在EALLOW和EDIS之间,否则写入会被硬件忽略,这个细节在仿真的 Debug 窗口里不容易看出来,因为寄存器的值看起来根本没变化。
如果你的工程是从网上找的例程或者从其他项目移植的,InitSpiaGpio这个函数名可能对应不上,常见变体有Spia_Init()、InitSpia(),还有直接把 GPIO 复用写在main里的写法。移植时不要只拷贝寄存器赋值,要把时钟使能、引脚复用和寄存器配置三块一起搬过来。
2.2 复用引脚确认:SPISIMOA、SPISOMIA、SPICLKA 与片选的关系
时钟使能之后,要把引脚复用为 SPI 功能。DSP28335 的 SPIA 默认引脚是 SPISIMOA(数据发送)、SPISOMIA(数据接收)、SPICLKA(时钟)、SPISTEA(硬件片选),但这几个引脚默认是普通 GPIO,需要设置 Mux 寄存器才能切换到 SPI 功能。
GpioMuxRegs.GPFMUX.bit.SPISIMOA_GPIOF0 = 1; // GPIOF0 复用为 SPISIMOA GpioMuxRegs.GPFMUX.bit.SPISOMIA_GPIOF1 = 1; // GPIOF1 复用为 SPISOMIA GpioMuxRegs.GPFMUX.bit.SPICLKA_GPIOF2 = 1; // GPIOF2 复用为 SPICLKA GpioMuxRegs.GPFMUX.bit.SPISTEA_GPIOF3 = 1; // GPIOF3 复用为 SPISTEA这里有个容易想当然的地方:SPISTEA 复用成硬件片选后由模块自动控制,看起来省事,但 AD2S1210 的 CS 更适合用普通 GPIO 手动拉低拉高。原因是读写函数里需要在地址和数据之间反复操作 WR 和 CS,硬件自动片选反而碍事。我的习惯是 SPISTEA 不用,CS 单独接到一个普通 GPIO,控制更直接。
另外注意,老版本头文件库用GpioMuxRegs,新版本有的改成了GpioCtrlRegs,字段名也跟着变。如果编译报错找不到SPISIMOA_GPIOF0,就去查工程里的DSP2833x_Gpio.h,按头文件里的实际字段名重新写。
2.3 AD2S1210 控制引脚分配:CS、RESET、RD、A0/A1、SAMPLE、WR、SOE
AD2S1210 除了 SPI 三根线之外,还有一堆控制引脚需要 DSP 用 GPIO 操作。根据项目里的实际用法,需要初始化的引脚是 CS、RESET、RD、A0、A1、SAMPLE、WR、SOE 这 8 个。具体接到 DSP 的哪个 GPIO 由板子原理图决定,我这边以 GPIOB0~GPIOB6 为例,你在移植时按实际连接修改引脚编号。
| 功能引脚 | 方向 | 说明 |
|---|---|---|
| RESET | 输出 | 低电平复位,初始化时拉低 |
| SAMPLE | 输出 | 采样触发,低电平复位期间要保持有效状态 |
| RD | 输出 | 读信号,串行模式下保持高电平 |
| WR | 输出 | 写信号,上升沿锁存地址和数据 |
| CS | 输出 | 片选,低电平有效 |
| A0 / A1 | 输出 | 模式选择,组合决定配置/位置/速度 |
| SOE | 输出 | 串行输出使能,低电平选择串行接口 |
#define CLR_RESET() GpioDataRegs.GPBCLEAR.bit.GPIOB0 = 1 #define SET_RESET() GpioDataRegs.GPBSET.bit.GPIOB0 = 1 #define SET_SAMPLE() GpioDataRegs.GPBCLEAR.bit.GPIOB1 = 1 #define CLR_SAMPLE() GpioDataRegs.GPBSET.bit.GPIOB1 = 1 #define SET_RD() GpioDataRegs.GPBSET.bit.GPIOB2 = 1 #define CLR_RD() GpioDataRegs.GPBCLEAR.bit.GPIOB2 = 1 #define SET_WR() GpioDataRegs.GPBSET.bit.GPIOB3 = 1 #define CLR_WR() GpioDataRegs.GPBCLEAR.bit.GPIOB3 = 1 #define SET_CS() GpioDataRegs.GPBSET.bit.GPIOB4 = 1 #define CLR_CS() GpioDataRegs.GPBCLEAR.bit.GPIOB4 = 1 #define SET_A0() GpioDataRegs.GPBSET.bit.GPIOB5 = 1 #define CLR_A0() GpioDataRegs.GPBCLEAR.bit.GPIOB5 = 1 #define SET_A1() GpioDataRegs.GPBSET.bit.GPIOB6 = 1 #define CLR_A1() GpioDataRegs.GPBCLEAR.bit.GPIOB6 = 1这段宏定义里有玄学:SET_SAMPLE()和CLR_SAMPLE()的物理电平和名字不一定对应,原始工程的命名按功能语义来,SET指的是让引脚进入复位流程需要的状态,而不是无脑拉高。我在第一次调试时被SET这个词误导过,SAMPLE 时序整个反了,导致采样数据永远是滞后的。建议你在自己的工程里改成SAMPLE_LOW()、SAMPLE_HIGH()这种明确电平的命名,避免后面越调越晕。
除了宏定义,还要把这几个 GPIO 的方向寄存器统一设为输出。方向配置一般在GpioMuxRegs.GPxDIR里,不同库写法不一样,老库用GPBDIR,新库用GpioCtrlRegs.GPBDIR,照着现有工程模板写即可,这里不展开。
3. 配好 SPIA 寄存器:0x07、0x0006、0x0012 背后的时序逻辑
3.1 SPICCR=0x07:8 位数据、无相位延迟、主模式的合成含义
SPICCR 是 SPI 配置寄存器,项目里写入0x07,二进制是0000 0111。低 3 位全 1,表示 SPICHAR=7,也就是一次收发 8 位数据,这是和 AD2S1210 字节协议对应的长度。寄存器里的时钟极性位保持 0,表示 SPICLK 低电平有效,配合后面的相位设置,组成了 AD2S1210 需要的 0、0 时序。
这里有一个坑:很多 DSP 例程里 SPI 用的都是 1、1 模式,尤其从网上下载的通用 SPI 驱动,极性和相位常常和你手上的从机对不上。AD2S1210 的串行接口时序要求是上升沿发送、下降沿接收、无相位延迟,也就是 SPICLK 空闲时为低,第一个边沿开始送数据。如果直接抄通用 SPI 例程,读回来的每一字节都是错位的,而且错误模式很规律,看上去像数据被整体左移或右移了一位。
3.2 SPICTL=0x0006:上升沿发送、下降沿接收与中断标志
SPICTL 写入0x0006,项目里注释为“无相位延迟主模式”。这个组合值在不同版本的头文件库里对应的位定义不完全一样,它的核心作用是配置时钟相位和使能发送通路,最终效果是:SPICLK 低电平有效,数据在上升沿发送、下降沿接收。
这 0x0006 是这份工程里经过验证的标准写法。移植到自己的工程时,不要只抄值,要在头文件里查一下SPICTL的位定义,确认 CLOCK_PHASE 和主从模式相关的位有没有因为库版本不同而产生偏移。如果读回来的数据全是 0xFF,或者第一个字节对、后面的字节错位,优先怀疑就是这一段的位配置被移植丢了。
这里没有开 SPI 中断,用轮询SPISTS.bit.INT_FLAG判断收发完成就够了。电机控制里位置更新频率通常不高,轮询不会造成性能瓶颈,反而省去中断服务函数里标志位清理的麻烦。
3.3 SPIBRR=0x0012:波特率 1.974MHz 的计算与上下限取舍
SPIBRR 写入0x0012,十进制是 18。波特率 = LSPCLK / (SPIBRR + 1)。DSP28335 默认 LSPCLK = 系统时钟 150MHz / 4 = 37.5MHz,算下来 37.5MHz / 19 = 1.974MHz。
为什么选这个值而不是把 SPI 拉满?AD2S1210 的从机接口频率有上限,虽然手册标称值看着比 2MHz 高,但实际走线、电平转换、光耦隔离都会吃掉余量。1.974MHz 留了足够裕量,又能保证 16 位位置数据在三字节传输中的耗时控制在几微秒内,对电机控制里的角度采样频率来说完全够用。如果你的板子改了 LSPCLK 分频,实际波特率会跟着变,公式不变,自己代进去重算。
| 配置项 | 值 | 效果 |
|---|---|---|
| SPICCR | 0x07 | 8 位数据,时钟极性低有效 |
| SPICTL | 0x0006 | 无相位延迟,上升沿发送 |
| SPIBRR | 0x0012 (18) | 波特率约 1.974MHz |
| SPIPRI.FREE | 1 | 仿真暂停时 SPI 继续跑 |
| SPISWRESET | 1 | 结束复位,开始收发 |
3.4 SPI_Byte 收发函数:为什么发送前要先移 8 位
SPI 是全双工通信,DSP28335 的 SPITXBUF 是 16 位寄存器,但 AD2S1210 是 8 位从机。数据必须放在高 8 位发送,所以SPI_Byte先把 byte 左移 8 位,SPI 模块才会从高位开始逐位把数据送出去。完整初始化和收发函数放一起看:
void SPIA_Init(void) { InitSpiaGpio(); // 时钟使能 + GPIO 复用 SpiaRegs.SPICCR.all = 0x07; // 8 位数据,无相位延迟主模式 SpiaRegs.SPICTL.all = 0x0006; // 上升沿发送,下降沿接收 SpiaRegs.SPIBRR = 0x0012; // 波特率 = 37.5MHz/(18+1) = 1.974MHz SpiaRegs.SPIPRI.bit.FREE = 1; // 自由运行模式 SpiaRegs.SPICCR.bit.SPILBK = 0; // 自测模式禁止,复位后默认值 SpiaRegs.SPICCR.bit.SPISWRESET = 1; // 结束复位,开始收发 } unsigned char SPI_Byte(unsigned char byte) { SpiaRegs.SPITXBUF = (byte << 8); // 发送数据放高 8 位 while (SpiaRegs.SPISTS.bit.INT_FLAG != 1); // 等待本次收发完成 return ((unsigned char)SpiaRegs.SPIRXBUF); // 取接收缓冲区低 8 位 }SPI_Byte是一个字节级全双工函数:发送一个字节的同时,硬件会收到从机返回的字节,INT_FLAG置位后说明发送完成且接收数据已就绪。原始工程里返回数据写的是SpiaRegs.SPI,那是简写,实际头文件里的接收缓冲寄存器字段是SPIRXBUF,强转unsigned char取的正是低 8 位接收数据,和发送时左移 8 位的操作对应上了。
这个函数是整个 AD2S1210 读写操作的最底层依赖,值得单独验证。调通后可以用逻辑分析仪抓 SPICLK 和 SPISIMOA 信号,确认时钟空闲电平、第一个沿的方向、数据位宽度是否符合预期,这一步检查能省掉后面大量的联调时间。
4. AD2S1210 复位与模式切换:先过时序门槛再谈采集精度
4.1 ad2s1210_Init() 复位流程:RESET 和 SAMPLE 的先后顺序
AD2S1210 上电后必须先执行复位初始化,否则内部寄存器处于未知状态,后续写入配置可能被芯片忽略。项目里的复位函数控制顺序非常讲究,直接看代码:
void ad2s1210_Init(void) { CLR_RESET(); // RESET 拉低,进入复位状态 SET_SAMPLE(); // SAMPLE 进入复位阶段的有效电平 delay_us(15); // 保持至少 10us,这里留了裕量 SET_RESET(); // RESET 拉高,退出复位 delay_ms(60); // 等待时间与分辨率相关,60ms 足够 CLR_SAMPLE(); // SAMPLE 切换电平,触发一次采样 delay_us(15); // 等待采样锁存完成 SET_SAMPLE(); // SAMPLE 回到初始状态 CLR_SOE(); // SOE 拉低,选择串行接口输出 SET_RD(); // RD 保持高电平 CLR_CS(); // CS 拉低,片选使能 }要害在第 5 行:SET_RESET()之后必须等足够长时间,项目给了 60ms,这不是随手写的。AD2S1210 的复位完成时间与后续分辨率设置有关,分辨率越高需要的越久,16 位分辨率下 60ms 是比较稳妥的做法。如果你把延时砍到 10ms,寄存器会写不进去,或者读回全是 0x00。
SAMPLE 引脚在复位过程中被切换两次,目的是让芯片退出复位后立即锁存一次有效的采样数据。SOE 拉低代表选择串行接口,RD 保持高电平是为了配合 SOE 状态——串行模式下 RD 不该被拉低,否则数据输出引脚的行为会变。CS 最后拉低,表示准备进入 SPI 通信状态。
4.2 A0/A1 模式切换:CONFIG、POSITION、VELOCITY 怎么切
AD2S1210 的工作模式由 A0 和 A1 两个引脚的组合决定,配置模式下可以访问寄存器,位置模式输出角度数据,速度模式输出转速数据。项目里的 ModeSelect 函数按模式表切换:
#define CONFIG 0x00 // A1=0, A0=0,配置模式 #define POSITION 0x01 // A1=0, A0=1,位置模式 #define VELOCITY 0x02 // A1=1, A0=0,速度模式 void AD2S1210_ModeSelect(unsigned char mode) { if (mode == CONFIG) { CLR_A0(); CLR_A1(); } else if (mode == POSITION) { SET_A0(); CLR_A1(); } else if (mode == VELOCITY) { CLR_A0(); SET_A1(); } delay_us(2); // 模式切换后留稳定时间 }模式切换后加 2us 延时是我自己的习惯,数据手册对 A0/A1 建立时间有要求,实际调试中不加延时,紧接着的 SPI 传输偶尔会失败。还有一个隐蔽问题:位置或速度模式下不能往寄存器地址写数据,芯片会忽略这些命令。所以每次写完配置,一定要把模式切回 POSITION,再开始读角度数据。
4.3 WriteToAD2S1210 与 ReadFromAD2S1210:读三字节的秘密
写入函数逻辑直接:先切配置模式,拉低 WR,发送寄存器地址,拉高 WR,再拉低 WR,发送数据,最后拉高。AD2S1210 用 WR 的上升沿锁存地址和数据,所以每次发送前后都要操作 WR。
void WriteToAD2S1210(unsigned char address, unsigned char data) { AD2S1210_ModeSelect(CONFIG); // 写入只能在配置模式下进行 CLR_WR(); SPI_Byte(address); // 发送寄存器地址 SET_WR(); CLR_WR(); SPI_Byte(data); // 发送要写入的数据 SET_WR(); }读取函数比写入复杂,因为不同模式下返回的字节数不同。配置模式下读寄存器需要先写地址再读数据;位置和速度模式下,芯片会连续输出三字节,依次对应 16 位数据的最高字节、中间字节、最低字节:
void ReadFromAD2S1210(unsigned char mode, unsigned char address, unsigned char *buf) { AD2S1210_ModeSelect(mode); if (mode == CONFIG) { buf[0] = address; CLR_WR(); buf[1] = (unsigned char)SPI_Byte(address) & 0xFF; SET_WR(); delay_us(2); CLR_WR(); buf[2] = (unsigned char)SPI_Byte(address) & 0xFF; SET_WR(); } else if (mode == POSITION || mode == VELOCITY) { SET_SAMPLE(); // 停止内部跟踪环路 delay_us(1); CLR_SAMPLE(); // 重新触发采样 delay_us(2); CLR_WR(); buf[2] = (unsigned char)SPI_Byte(address) & 0xFF; // 最高字节 buf[1] = (unsigned char)SPI_Byte(address) & 0xFF; // 中间字节 buf[0] = (unsigned char)SPI_Byte(address) & 0xFF; // 最低字节 SET_WR(); } }位置模式先动 SAMPLE 再连读三字节,这是由 AD2S1210 的跟踪环路机制决定的。SAMPLE 拉低时内部环路停止,把当前角度锁存到输出寄存器,拉高后开始转换,三字节连续读出来的就是锁存后的角度数据。这里的address参数在位置模式下没有实际意义,只是维持 SPI 时序,传0x00就可以。
读回来的三字节怎么拼成角度?buf[2] 是高字节,buf[1] 是低字节,组成 16 位原始位置数据。如果顺序拼反,角度值会完全不连续,在 0 度附近来回跳变,这一点在避坑章节还会再展开。
4.4 AD2S1210_Config:寄存器初值按什么逻辑填
复位完成、读写函数就绪后,就可以配置 AD2S1210 的工作参数。项目里的配置函数写了六个寄存器:
void AD2S1210_Config(void) { WriteToAD2S1210(0x91, 0x08); // 激励频率相关参数 WriteToAD2S1210(0x92, 0x7f); // 控制寄存器,后两位 11 表示 16 位分辨率 WriteToAD2S1210(0x8c, 0x01); // DOS 复位最小阈值 WriteToAD2S1210(0x88, 0x01); // LOS 阈值 WriteToAD2S1210(0x89, 0x7f); // DOS 超量程阈值 WriteToAD2S1210(0x8a, 0x7f); // DOS 失配阈值 }0x92 控制寄存器最低两位决定分辨率:11 对应 16 位,10 对应 14 位,01 对应 12 位。分辨率直接影响复位等待时间、数据更新率以及角度计算时的量程。16 位分辨率下位置数据范围 0~65535 对应 0~360 度,换算关系是angle = raw * 360 / 65536.0。
寄存器 0x8c、0x88、0x89、0x8a 是故障检测阈值,默认值也能工作,但项目里显式配置了更合适的阈值,避免电机启动瞬间电流冲击导致误报故障。如果对故障检测要求不高,这些可以保持默认,真正必须配的是 0x91 和 0x92。注意 AD2S1210 的寄存器是易失性的,每次上电都要重新执行ad2s1210_Init()和AD2S1210_Config(),不要把配置代码放在 while 循环里反复执行,否则频繁的写操作会打断正常的数据采集。
5. 避坑与排查:位置数据乱跳、写不进寄存器、字节错位
5.1 现象:位置数据一直在跳,静止时也不稳定
电机不动时角度值还在几百甚至几千的范围内来回跳,这种现象在伺服调试里最常见也最让人头疼。
可能原因有三个:SPI 时钟线受到干扰,SAMPLE 保持时间不够导致锁存了未稳定的数据,或者波特率超过了 AD2S1210 从机接口的实际承受能力。
解决步骤:先用逻辑分析仪抓 SPICLK,确认频率确实是 1.974MHz 而不是因为 LSPCLK 配置不同变成了别的值。再检查 SAMPLE 引脚拉低到拉高的时间是否满足手册要求,项目里的 15us 是经验值,如果还跳就加大到 20us 左右。最后确认时钟极性和相位确实是 0、0 模式,相位错了数据整字节错位,看起来也像乱跳。
5.2 现象:寄存器写不进去,读回来全是默认值
调用WriteToAD2S1210写 0x92 寄存器,读回来还是默认的 0x00 或 0xFF,配置完全没生效。
最常见的原因是 RESET 拉高后等待时间不足,芯片还没完全退出复位就开始配置。另一个可能是 WR 的上升沿时序不对,地址和数据没有在 WR 上升沿之前稳定下来。
解决:把复位函数里的delay_ms(60)当底线,不要缩短。每次写数据前,在SPI_Byte发送完成后加 1us 左右的延时再拉高 WR,确保数据在 WR 上升沿前建立稳定。还有一个容易忽略的点:CS 必须保持拉低状态,CS 在高电平时发 SPI 命令,芯片不会响应。
5.3 现象:角度值不连续,在 0 度和 360 度附近来回跳变
位置模式读回来的数据,用手缓慢转动电机,角度值按递增方向走到 65535 后直接跳到 0,这是正常的回绕,但如果从 0 附近反向转动时跳到了 32768 这种中间值,那就不是回绕,是字节顺序拼反了。
位置模式下ReadFromAD2S1210返回的 buf[2] 是最高字节,buf[1] 是中间字节。如果拼装时用(buf[0] << 8) | buf[1],数据会在角度接近 0 度时出现严重的数字回绕。
解决:统一用((unsigned int)buf[2] << 8) | buf[1]拼装。还有一个相关坑是发送字节时必须左移 8 位,如果漏掉这步,数据会发在低 8 位,从机收到的数据整体错位,读回来的值同样不对。
5.4 现象:速度模式正常,位置模式异常
同样的读函数,切换到速度模式读数正常,切回位置模式读数就不对,或者时好时坏。
原因是速度模式和位置模式的 A0/A1 组合不同,从速度模式切回位置模式时 A1 没有正确复位,芯片还停在别的状态。另一种情况是 SAMPLE 引脚在位置模式下的初始电平没有恢复到正确状态。
解决:每次调用ReadFromAD2S1210之前,先调用AD2S1210_ModeSelect(mode)强制设置 A0/A1,不要依赖上一次调用后的残留状态。位置模式读取前,SAMPLE 引脚要先进入有效电平、再切换、再恢复,这个三步序列必须完整执行,顺序不能反。
5.5 现象:断电重启后配置丢失,角度换算全错
只要重新上电,之前配置的分辨率、阈值全部回到默认值,角度换算用 16 位分辨率算出来完全是错的。
原因是 AD2S1210 的寄存器是易失性的,每次上电复位后都恢复默认。如果配置代码只在 main 函数里执行一次,且放在电机初始化之后,可能因为上电时序问题被跳过,或者被 while 循环里的重复执行干扰。
解决:确保ad2s1210_Init()和AD2S1210_Config()在 main 函数最前面完成,配置完成后立即读回 0x92 验证,读到 0x7f 说明配置生效,读到 0x00 说明芯片还在默认状态,需要回头查复位时序。每次重新上电调试,都强制走一遍完整的复位、配置、读回流程。
6. 装完怎么验证跑通:从寄存器读回开始到角度换算闭环
6.1 先把配置读回来:验证 SPI 读写通路
配置完寄存器后,不要直接去读位置数据。先切回配置模式,用ReadFromAD2S1210把刚才写入的六个寄存器依次读回来。比如写 0x92 = 0x7f,读回来的应该是 0x7f;写 0x88 = 0x01,读回来应该是 0x01。这一步能同时验证 SPI 收发函数、模式切换函数和读写函数三部分,任何一环有问题都会在这里暴露。
读回来是 0xFF,基本是 SPI 时序或 CS 控制问题;读回来是 0x00,大概率是复位没完成。只有六个寄存器全部读回一致,才继续向下验证。
6.2 给一个已知角度,验证位置数据的换算关系
把电机或手轮转到某个固定刻度,比如 90 度,读回位置数据换算成角度,看看和实际是否一致。原始数据可能类似1101 0011 1100 0000这样的 16 位二进制流,对应十六进制 0xD3C0,十进制 54208,换算角度约 297.8 度。验证时我的判断标准是:缓慢旋转一整圈,角度值从 0 平滑增长到 65535 再回绕到 0,回绕点曲线连续,没有跳变。
unsigned char buf[3]; unsigned int raw_position; float angle_deg; ReadFromAD2S1210(POSITION, 0x00, buf); raw_position = ((unsigned int)buf[2] << 8) | buf[1]; angle_deg = (float)raw_position * 360.0f / 65536.0f;串口打印三字节原始值时,正常节奏应该是:最高字节变化最慢,中间字节较快,最低字节最快。如果观察到的变化节奏反了过来,就是 buf 数组顺序用错了。
6.3 把验证固化成习惯
这套流程我吃过亏的位置主要有两个,一是 SPI 相位配成了 1、1 模式,二是位置模式三字节的接收顺序颠倒。从那以后每次排查 AD2S1210 相关问题,都强制走一遍“读回 0x92 → 旋转一圈看连续性 → 检查字节变化节奏”的验证流程,十分钟内基本能定位问题。希望帮到你。
本文还有配套的精品资源,点击获取