SPI驱动非标准字长外设:硬件打包与软件模拟方案详解
1. SPI接口驱动非标准字长外设的硬件与软件实现在嵌入式系统开发中SPISerial Peripheral Interface接口因其简单、高速和全双工的特性成为连接各类外设的基石。无论是读取传感器数据、配置无线模块还是驱动显示器件SPI都扮演着关键角色。然而现实世界并不总是完美的8位对齐。当你面对一个像MC144110这样的6位D/A转换器或者一个12位的ADC时你会发现它们的数据字长与SPI硬件通常预设的8位或16位传输单元并不匹配。这种“非标准字长”外设的驱动是嵌入式工程师从“会用”到“精通”必须跨越的一道坎。这不仅仅是发送几个字节那么简单。它涉及到对SPI硬件底层工作机制的深刻理解、对数据流在时间和空间上的精确编排以及在软件灵活性与硬件效率之间的经典权衡。本文将以经典的M68HC11微控制器和MC144110六通道6位D/A转换器为例拆解两种核心的驱动策略利用硬件SPI配合数据打包以及完全用软件模拟SPI时序。我们将深入时序细节、代码实现和那些手册上不会写的“坑”让你不仅能复现更能理解背后的“为什么”。2. 核心挑战与设计思路解析2.1 非标准字长外设带来的根本问题SPI硬件如M68HC11内置的模块其数据寄存器SPDR通常是8位或16位的。每次传输硬件逻辑会自动移出8位或16位数据。但对于一个需要连续写入36位数据6个通道 x 6位的MC144110来说这就产生了矛盾。核心矛盾在于硬件传输的原子性与外设需求的数据流连续性不匹配。硬件SPI以固定的字节8位为单位进行传输而外设期待的是一个连续的、特定长度的比特流。MC144110并不关心你发送的是5个字节还是4个半字节它只认SCK时钟边沿并依次锁存出现在其数据输入引脚DIN上的比特。多余的比特对于36位有效数据若发送40位则多出4位会被它简单地“忽略”或“移过”。这就引出了两种根本性的解决思路硬件SPI 软件打包尊重硬件SPI的8位传输单元在发送前由软件将6个6位数据重新组合打包成5个8位字节。然后通过硬件SPI连续发送这5个字节。外设接收连续的40个时钟边沿但只使用最后36个有效位。纯软件模拟SPIBit-Banging放弃硬件SPI控制器将SCK、MOSI、SS等信号线当作普通的GPIO通用输入输出来操作。通过软件指令精确控制每一位的输出和时序从而可以直接输出任意长度的比特流例如精确的36位。2.2 方案权衡硬件效率 vs. 软件灵活性选择哪种方案取决于你对系统关键指标的考量。方案一利用硬件SPI控制器数据打包优点CPU占用率低一旦启动传输硬件接管时钟生成和数据移位CPU可处理其他任务或进入低功耗模式仅通过中断或查询标志位获知传输完成。时序精确且稳定SPI时钟由硬件计数器生成频率稳定不受其他中断或程序分支影响通信可靠性极高。代码简洁对于打包后数据数据传输部分代码非常简单通常是写入数据寄存器、等待标志位、循环。缺点数据预处理开销需要额外的代码和CPU时间执行复杂的位操作移位、掩码、组合将6位数据打包成8位字节。这部分开销可能不小。存在冗余传输必须发送整数倍字节可能包含无效的“填充位”浪费了少量的总线时间。灵活性受限严格受限于硬件SPI的时钟极性和相位模式。方案二软件模拟SPIBit-Banging优点极致灵活可以生成任意长度、任意时序的波形。不受硬件8位限制可以直接输出6位数据无需打包。时钟极性和相位可随意编程。无需专用引脚理论上可以用任何GPIO引脚模拟SPI功能在引脚资源紧张时非常有用。无数据预处理直接使用原始的6位数据值按位输出节省了打包的计算开销。缺点CPU占用率高整个传输过程需要CPU全程参与循环控制每一位的输出在此期间CPU几乎被独占。时序精度依赖软件时钟周期由软件指令执行时间决定容易受到中断、缓存、甚至处理器流水线变化的影响在高波特率下时序难以保证。代码复杂且移植性差需要精心编写位操作和延时代码与特定型号的MCU指令集和时钟频率紧密耦合。如何选择如果系统对实时性和CPU资源要求高且外设数据格式固定首选硬件SPI打包。预处理的开销是固定的一次优化后可长期受益于硬件传输的高效。如果外设字长非常奇特例如7位、13位或者需要动态改变通信格式或者硬件SPI引脚已被占用则选择软件模拟SPI。其灵活性可以应对各种“非标”场景。在MCU主频较低且SPI时钟要求不高时软件模拟是一个快速验证和解决问题的好方法。3. 硬件连接与基础配置3.1 MC144110与M68HC11的硬件连接无论采用哪种软件方案硬件连接是相同的。理解硬件连接是分析时序和编写驱动的基础。MC144110是一个6通道、6位精度的数字模拟转换器DAC。它通过一个串行接口接收数据其关键引脚如下DIN串行数据输入。数据在SCLK上升沿或下降沿取决于器件被锁存。SCLK串行时钟输入。EN或CS/SS使能引脚低电平有效。当EN为低时DAC响应SCLK和DIN为高时DAC忽略时钟和数据并更新其模拟输出。在M68HC11评估板EVB上我们使用Port D的部分引脚与之连接PD3/MOSI连接MC144110 DIN作为主设备的数据输出。PD4/SCK连接MC144110 SCLK提供串行时钟。PD5/SS连接MC144110 EN作为片选信号。注意这里将主机的SS从机选择引脚用作通用输出来控制从设备的使能这是一个常见的技巧。注意在标准SPI主从配置中主机的SS引脚通常配置为输出高电平。但在此处我们将其重新定义为通用输出引脚通过设置DDRD51并手动控制其电平来充当外设的使能信号。这是因为MC144110是一个简单的“哑”外设而非一个标准的SPI从设备它不需要遵循完整的SPI四线制协议只需要时钟、数据和使能三线。3.2 M68HC11 SPI模块基础配置对于使用硬件SPI的方案需要对SPI控制寄存器SPCR进行正确配置。参考手册中的示例配置为$57二进制0101 0111我们将其分解SPIE0禁止SPI中断。我们采用查询方式。SPE1使能SPI模块。DWOM0Port D引脚为常规推挽输出模式。MSTR1设置MCU为SPI主模式。CPOL0时钟极性为0表示空闲时SCK为低电平。CPHA1时钟相位为1。这是关键配置。在CPHA1模式下数据在SCK的第一个边沿此处为上升沿因为CPOL0被采样在第二个边沿下降沿切换。这种模式与许多外设包括MC144110的典型时序兼容且能避免一种潜在的“写冲突”问题。SPR1:SPR011选择最慢的时钟分频SCK E时钟 / 32。在2MHz E时钟下SCK频率62.5kHz。这是为了满足MC144110相对较慢的时序要求。关于CPHA0的“坑”手册中特别警告了CPHA0模式下的一个潜在问题当CPHA0时从设备必须在SS线变低之前就准备好数据。如果主机MCU在传输结束后未能及时拉高SS而从设备又试图写入新的数据到其SPI数据寄存器就会发生“写冲突”WCOL标志置位。因此对于从设备驱动在CPHA0时必须在写入SPDR前检查SS引脚状态。而采用CPHA1模式数据锁存边沿发生在传输周期中间通常能更安全地协调主从动作。在我们的主设备驱动外设的场景下我们完全控制SS作为EN因此选择CPHA1可以简化时序控制。4. 方案一详解硬件SPI驱动与数据打包4.1 数据打包算法解析这是本方案的核心和难点。我们需要将6个独立的6位数值DA1-DA6每个值占据一个字节的低6位高2位为0重新排列组合成5个连续的8位字节SPI1-SPI5以便通过硬件SPI发送。原始数据格式每个DAx为6位存储在内存字节中DA1: 位 [13,12,11,10,15,14] (注意手册图中标注顺序有时是反的我们按代码逻辑理解) DA2: 位 [23,22,21,20,25,24] DA3: 位 [33,32,31,30,35,34] DA4: 位 [43,42,41,40,45,44] DA5: 位 [53,52,51,50,55,54] DA6: 位 [63,62,61,60,65,64]注这里的位编号xx代表第xx个被发送的位xx越大越先被发送MSB First。例如对于DA1位15是最高位(MSB)位10是最低位(LSB)。目标打包格式5个字节共40位最后36位有效SPI1: [--, --, --, --, 65, 64, 63, 62] // 高4位未用低4位是DA6的最高4位 SPI2: [61, 60, 55, 54, 53, 52, 51, 50] // DA6的低2位 DA5的全部6位 SPI3: [45, 44, 43, 42, 41, 40, 35, 34] // DA4的高6位 DA3的低2位 SPI4: [33, 32, 31, 30, 25, 24, 23, 22] // DA3的中间4位 DA2的高4位 SPI5: [21, 20, 15, 14, 13, 12, 11, 10] // DA2的低2位 DA1的全部6位打包过程对应代码子程序REFORM的思维导图这个过程本质上是将一串36位的比特流以8位为一组进行切分并将切分点落在原始6位数据的边界上。代码通过巧妙的移位ASLA, LSRB, RORA等和逻辑操作ANDB, ORAB来实现。处理DA1和DA2生成SPI5和SPI4的中间值将DA1左移2位使其高6位对齐到字节的高6位。取DA2用掩码$3F(0011 1111) 确保只使用低6位。通过右移DA2和带进位循环右移A寄存器将DA2的低位逐步移入A寄存器即DA1数据的空缺位置。经过两次这样的操作就形成了SPI5A寄存器和SPI4的一个中间部分B寄存器。处理DA3和DA4完成SPI4和生成SPI3类似地处理DA3和DA4。将DA3左移DA4右移并循环将它们的数据位交错组合最终生成完整的SPI4与上一步的中间部分合并和SPI3。处理DA5和DA6生成SPI2和SPI1流程与第一步完全对称处理DA5和DA6生成SPI2和SPI1。这个算法非常精妙它直接在CPU的累加器A和B上进行位操作效率极高。它避免了使用耗时的内存访问和循环是嵌入式编程中空间换时间此处是代码空间换执行时间的典型范例。4.2 数据传输流程与代码剖析打包完成后数据传输流程就变得直截了当。主程序UPDAT1负责控制整个发送过程初始化与打包调用REFORM子程序完成上述数据打包结果存入SPI1到SPI5的连续内存位置。启动传输将SS(PD5) 引脚拉低使能MC144110。循环发送设置指针X指向SPI1。进入循环 a. 从X指向的地址加载一个字节到累加器A。 b. 将该字节写入SPI数据寄存器SPDR。写入SPDR这个动作会自动启动一次8位的SPI硬件传输。 c. 循环读取SPI状态寄存器SPSR等待SPIFSPI传输完成标志位变为1。 d. 指针X加1指向下一个待发送字节。 e. 判断是否已发送完5个字节SPI51若未完成跳回步骤a。结束传输将SS(PD5) 引脚拉高禁止MC144110。此时MC144110会锁存已接收的36位数据并更新其6个DAC通道的输出。关键代码段解读以等待SPIF为例WAIT1 LDAA SPSR ; 读取状态寄存器 BPL WAIT1 ; 如果最高位(SPIF)为0正数则循环等待BPLBranch if Plus指令在符号位N标志即SPSR的最高位为0时跳转。因为SPIF标志位于SPSR的最高位第7位当传输完成时该位被硬件置1读回的SPSR值就是一个负数最高位为1BPL条件不成立程序便退出循环。这是一种非常简洁的查询标志位的方法。4.3 时序分析与验证图8-13的时序分析图是确保驱动可靠性的关键。它验证了软件控制下的硬件SPI时序能否满足MC144110的数据手册要求。我们分析几个关键参数EN低电平到第一个SCK上升沿的延迟t_ENCLMC144110要求EN变低后至少需要5µs的建立时间才能出现第一个时钟。代码中在拉低SS(EN) 后直接执行LDAA 0,X和STAA SPDR。根据指令周期数在2MHz E时钟下1个周期0.5µs计算从BCLR拉低EN到STAA SPDR启动SPI传输SCK开始产生之间的指令执行时间提供了足够的延迟5.5µs满足要求。数据建立与保持时间t_SU, t_HDMC144110要求数据在SCK上升沿前至少1µs稳定建立时间并在上升沿后至少保持5µs保持时间。在CPHA1, CPOL0的配置下数据在SCK上升沿被采样。硬件SPI保证了MOSI数据在SCK边沿前后的稳定窗口。从时序图看数据在SCK上升沿前有约8µs的稳定时间远大于1µs下降沿后数据仍保持约8µs也满足5µs的保持时间。这是硬件SPI的最大优势——时序由硬件保证精确且一致。最后一个SCK边沿到EN上升沿的延迟t_CLEN传输完最后一个字节后需要等待至少5µs才能拉高EN。代码在发送循环结束后执行BSET PORTD,Y $20来拉高EN。从最后一个STAA SPDR到BSET之间的指令包括最后的等待循环、判断和跳转提供了足够的延迟约19.5-22.5µs完全满足要求。实操心得在进行任何外设驱动开发时手工绘制或详细分析关键时序图是必不可少的步骤。不能想当然地认为“代码写了就应该对”。必须将代码执行时间考虑每条指令的周期数与外设数据手册中的时序参数逐项对比。对于M68HC11这类已知指令周期的MCU可以精确计算对于现代带流水线和缓存的高性能MCU则需要留出足够的裕量或使用硬件定时器。5. 方案二详解软件模拟SPIBit-Banging5.1 实现原理与代码流程当硬件SPI的8位限制带来过多开销时软件模拟提供了最直接的解决方案。其核心思想是将SCK、MOSI、SS全部当作普通GPIO用软件指令模拟出SPI的波形。在示例代码UPDAT2中我们关闭了硬件SPISPE0并将PD3、PD4、PD5配置为输出。程序流程如下初始化设置Port D引脚方向确保SPI模块关闭。动传输拉低SS(PD5/EN)并插入两个NOP指令以提供满足t_ENCL要求的延迟。逐位发送循环 a. 设置一个位掩码初始为$20即二进制0010 0000它从最高位第5位因为我们是6位数据开始。 b. 进入内层循环NXTBIT i. 拉高SCKPD4产生上升沿。 ii. 测试当前数据字节由Y指针指向与位掩码的AND结果判断要发送的位是1还是0。 iii. 根据判断结果设置MOSIPD3为高或低。 iv. 拉低SCK产生下降沿。MC144110通常在SCK的上升沿锁存数据因此我们的数据在SCK为低时变化在SCK上升沿保持稳定这模拟了CPHA0的时序与硬件方案不同。 v. 将位掩码右移一位准备发送下一个低位。 vi. 判断位掩码是否已移出变为0若非零跳回步骤i发送下一位。切换至下一个数据一个6位数据发送完毕后Y指针减1指向下一个DA值从DA6到DA1。循环判断判断是否6个数据都已发送完Y指针是否已越过DA1。结束传输全部36位发送完毕后拉高SS(EN)。5.2 时序精控与指令周期计算软件模拟SPI的成败完全取决于对指令执行时间的精确控制。图8-15的时序分析至关重要。位周期SCK频率SCK的高低电平时间由执行BSET、BITA、BEQ/BRA、BCLR等指令的周期数决定。在2MHz E时钟下一个指令周期为0.5µs。通过计算一个完整位周期SCK低-高-低所经历的所有指令周期可以得出SCK的频率。代码路径有两条发送1或发送0但通过BRA ENDBIT和额外的BRA ENDBIT进行了平衡确保无论发送0还是1位周期时间一致。这是软件模拟中保证时钟占空比稳定的常用技巧。建立与保持时间数据MOSI的变化发生在SCK为低电平期间BCLR PORTD,X $08或BSET ...指令而SCK的上升沿发生在数据设置指令之后。因此数据在SCK上升沿前有足够长的稳定时间建立时间。SCK下降沿后数据会保持一段时间直到下次变化这个保持时间也远大于要求。通过计算BSET SCK指令执行完到BCLR SCK指令开始执行之间的指令周期可以精确得出高电平脉宽进而验证时序。延迟补偿在拉低EN后使用了两个NOP指令来精确延长等待时间以满足t_ENCL。NOP空操作是进行短延时最可靠的方式。避坑指南软件模拟SPI最大的风险来自中断。如果在你精心控制的位循环中发生了中断中断服务程序的执行会彻底破坏SCK时序导致通信失败。因此在软件模拟SPI的关键循环期间必须禁止中断使用SEI指令或其等效高级语言函数。在传输完成后再恢复中断CLI。这是很多初学者容易忽略的地方。5.3 方案对比与选择建议让我们从几个维度对比两种方案特性维度硬件SPI 数据打包软件模拟SPI (Bit-Banging)CPU占用低传输由硬件负责高CPU全程参与每一位时序精度高由硬件时钟保证中低受指令执行和中断影响最大速率高可达系统时钟分频低受限于软件循环速度代码复杂度数据打包算法复杂传输代码简单位操作循环简单但时序控制需精心设计灵活性低固定为8位倍数极高任意位长、相位、极性外设资源占用硬件SPI模块仅占用通用GPIO适用场景高速、实时性要求高、外设字长固定或可接受打包开销低速、非标字长、引脚复用、快速原型验证个人经验选择 在资源丰富的现代32位MCU如ARM Cortex-M系列上硬件SPI通常非常强大支持8位、16位甚至32位传输并且常有FIFO缓冲。对于非标准字长我优先考虑使用DMA直接内存访问配合硬件SPI。可以将打包好的数据数组放在内存中由DMA自动搬运到SPI数据寄存器完全解放CPU。如果硬件SPI实在不适用例如字长非常奇怪或引脚冲突我会使用硬件定时器Timer产生精确的SCK时钟用中断或DMA来切换MOSI数据位这比纯软件循环更可靠、更高效。纯软件Bit-Banging是我最后的选择通常只用于极低速100kHz或早期验证阶段。6. 常见问题排查与调试技巧驱动非标准字长外设时问题往往比驱动标准器件更隐蔽。以下是一些常见问题及排查思路6.1 问题一外设无响应输出不正确检查电源和基本连接确保VDD、VSS连接正确电压在额定范围内。这是所有问题排查的第一步。验证片选/使能(EN)信号用示波器或逻辑分析仪查看EN信号。是否在传输数据前被拉低传输结束后是否被拉高电平是否满足要求高/低电压一个常见的错误是片选信号极性弄反。检查时钟(SCK)和数据(MOSI)信号有无时钟SCK线上是否有脉冲如果没有检查MCU的SCK引脚配置输出模式、复用功能是否开启。时钟极性/相位(CPOL/CPHA)是否正确这是SPI通信中最容易出错的地方。用逻辑分析仪捕获波形对照外设数据手册的时序图看数据是在SCK的哪个边沿被采样。MC144110通常在上升沿采样我们的硬件方案CPHA1和软件方案先设置数据后拉高SCK都满足此条件。数据是否对齐对于硬件打包方案确认你打包后的字节顺序和位顺序与外设期望的完全一致。MSB First还是LSB First仔细阅读外设手册。我们的例子中MC144110是MSB First。数据位是否包含无效位确认你发送的总位数是否足够。对于硬件方案发送了40位外设使用后36位。确保多余的“填充位”不会意外地被外设解释为有效命令。6.2 问题二时序相关的不稳定现象症状偶尔通信成功大部分时间失败或高速时失败低速时正常。排查示波器/逻辑分析仪是关键。必须捕获完整的通信波形EN, SCK, MOSI并测量关键时间参数EN有效到第一个SCK的延迟、SCK频率、数据建立/保持时间、最后一个SCK到EN无效的延迟。与外设数据手册的“最小值”和“最大值”要求逐项对比。软件模拟方案重点检查位循环中是否被中断打断。在传输函数开头加__disable_irq()结尾加__enable_irq()以ARM Cortex-M为例。硬件SPI方案检查SPI时钟分频设置。是否太快超过外设支持的最大SCLK频率尝试降低波特率。检查MCU主频是否稳定。6.3 问题三数据内容错误但时序正确排查数据打包算法错误这是硬件方案最常见的问题。编写一个简单的测试函数给DA1-DA6赋一组容易辨认的值如0x01, 0x02, 0x04, 0x08, 0x10, 0x20然后单步调试或打印出打包后的SPI1-SPI5字节手动验证位排列是否正确。字节序/位序误解确认你对“第1位”的定义。是传输的第一位最高位MSB对应外设的最高位还是最低位LSB我们的例子中位编号65是最先发送的最高位。软件模拟的位操作错误检查位掩码初始值和移位方向。发送6位数据时掩码应从0x20(0010 0000) 开始右移还是从0x01(0000 0001) 开始左移这取决于你约定先发送高位还是低位。6.4 调试工具与技巧逻辑分析仪必备神器。不仅能看波形还能解码SPI协议。设置正确的通道SCK, MOSI, MISO, SS、阈值电压、采样率并配置SPI解码器设置位序、相位。它能直观地显示你发送的每一个字节甚至每一位数据极大提升调试效率。示波器用于精确测量时序参数特别是建立/保持时间、脉冲宽度等。IO模拟在代码关键点如拉低EN前、发送每个字节后翻转一个空闲的GPIO引脚用示波器观察可以测量代码段执行时间。仿真器/调试器单步执行代码观察寄存器、内存变量的变化验证数据打包算法。分而治之先确保你能用软件模拟SPI驱动一个简单的标准8位器件如SPI Flash的ID读取命令验证你的底层GPIO控制和基本时序是正确的。然后再套用到复杂的数据打包或非标准字长外设上。驱动非标准字长外设是对嵌入式工程师综合能力的考验。它要求你不仅理解通信协议还要精通MCU的指令集、时序分析并具备扎实的调试能力。从M68HC11的汇编代码中我们看到了在资源极度受限的时代工程师们如何通过精巧的算法和对硬件的极致掌控来解决问题。今天虽然我们拥有了更强大的工具和更丰富的资源但解决问题的核心逻辑——理解硬件、精确控制、验证时序——从未改变。掌握这两种方法硬件打包与软件模拟并学会根据实际情况进行权衡和选择将使你能够从容应对各种“非标”挑战真正驾驭SPI总线。