P89LPC93x单片机SPI接口配置与驱动开发实战指南

P89LPC93x单片机SPI接口配置与驱动开发实战指南 1. 项目概述与SPI核心价值如果你正在捣鼓一块基于NXP P89LPC933/934/935/936系列的老牌51内核单片机想要驱动一个SPI Flash、TFT屏幕或者数字传感器却发现官方用户手册UM10116里那几十页关于SPI的章节读起来像天书寄存器位描述看得人头大那么这篇笔记可能就是为你准备的。我最近刚好在用一个P89LPC935做个小玩意需要和一颗SPI接口的温湿度传感器通信把手册翻来覆去啃了好几遍又实际调通了代码这里就把SPI接口从硬件原理到软件配置再到实际调试中踩过的坑系统地梳理一遍。SPI全称Serial Peripheral Interface中文叫串行外设接口。它不像UART那样需要事先约定好波特率也不像I2C那样需要复杂的起始、停止和应答信号。SPI的核心就是一个“同步时钟”加几根数据线主设备产生时钟从设备跟着时钟节拍收发数据简单粗暴效率极高。在P89LPC93x这类资源有限的8位MCU上SPI是连接外部世界的高速通道官方标称最高支持3 Mbit/s的速率对于大多数传感器、存储器和显示模块来说这个速度已经绰绰有余。它的技术价值就在于用最少的引脚通常4根线和最简单的协议实现了全双工的高速数据流极大简化了PCB布线和软件驱动开发的复杂度。2. SPI硬件架构与引脚功能解析要玩转P89LPC93x的SPI首先得把它当成一个独立的硬件模块来理解而不是一堆零散的寄存器位。这个模块内部有一个8位的移位寄存器、一个时钟生成逻辑、以及配套的控制和状态逻辑。对我们程序员来说最关键的就是那四根物理引脚和三个特殊功能寄存器SFR。2.1 四线制引脚定义与角色这四根线是SPI通信的物理基础每一根都有明确的角色接错了通信肯定失败。SPICLK (P2.5): 串行时钟线由主设备产生从设备接收。它是一切数据收发的节拍器。时钟的极性CPOL和相位CPHA决定了数据在时钟的哪个边沿被采样和更新这是SPI模式配置的核心后面会细说。MOSI (P2.2): 主设备输出从设备输入。顾名思义主设备通过这根线发送数据给从设备。MISO (P2.3): 主设备输入从设备输出。从设备通过这根线将数据发回给主设备。这里有个关键点当一个SPI从设备未被选中时SS为高它的MISO引脚必须处于高阻态Hi-Z否则会与总线上其他设备的数据线冲突导致通信紊乱。P89LPC93x的硬件会自动处理这一点。SS (P2.4): 从设备选择线低电平有效。这是SPI主从架构的关键。在一个多从设备的系统中主设备通过控制不同的GPIO引脚拉低对应从设备的SS引脚来选中与之通信的从机。对于从设备而言只有当自己的SS引脚被拉低时它才会响应SPI时钟和数据。注意当SPI功能被禁用SPEN0时这四个引脚P2.2, P2.3, P2.4, P2.5会恢复为普通的I/O口功能。在初始化SPI前务必确保已经通过SPCTL寄存器开启了SPI功能否则你操作的可能只是一个普通的GPIO。2.2 核心寄存器拆解SPCTL, SPSTAT, SPDAT手册里表格很多但抓住这三个寄存器就抓住了SPI的命脉。SPI控制寄存器 (SPCTL - 地址 E2h)这是SPI模块的“大脑”所有的模式、速率、时序配置都在这里。位符号描述复位值实操解读7SSIGSS引脚忽略控制00SS引脚功能有效用于模式选择主/从。1忽略SS引脚由MSTR位单独决定主从模式。通常在多主机或固定主从系统中设置为1以简化设计。6SPENSPI使能01开启SPI功能相关引脚被SPI模块占用。0关闭SPI引脚作普通I/O。上电后第一步就是将此位置1。5DORD数据位顺序00高位MSB先发送。1低位LSB先发送。必须与外设器件的数据格式严格匹配否则读到的数据全是乱的。大部分SPI器件默认是MSB在先。4MSTR主/从模式选择01主机模式。0从机模式。当SSIG0时此位可能被硬件自动清零见模式切换部分。3CPOL时钟极性00时钟空闲时为低电平。1时钟空闲时为高电平。决定了SPICLK引脚在无数据传输时的稳态电平。2CPHA时钟相位10/1与CPOL组合定义数据采样的时钟边沿。共形成4种SPI模式Mode 0-3。这是SPI通信中最容易配错的地方。1SPR1时钟速率选择0SPR1和SPR0共同决定SPI时钟相对于系统时钟CCLK的分频系数从而设置通信波特率。0SPR0时钟速率选择000: CCLK/4; 01: CCLK/16; 10: CCLK/64; 11: CCLK/128。SPI状态寄存器 (SPSTAT - 地址 E1h)这是SPI模块的“眼睛”用来判断一次传输是否完成或者是否发生了错误。位符号描述复位值实操解读7SPIF传输完成标志0硬件置1当一次完整的8位数据传输完成时该位由硬件自动置1。软件清0必须通过向该位写1来清除。这是判断一次SPI收发是否结束的唯一标志。6WCOL写冲突标志0硬件置1当SPI数据寄存器SPDAT在数据传输过程中被写入时该位置1。软件清0通过向该位写1清除。表明你试图在SPI忙的时候写数据这次写操作无效。5:0-保留x读为未定义值应写0。SPI数据寄存器 (SPDAT - 地址 E3h)这是SPI模块的“嘴巴”和“耳朵”8位宽读写都通过它。写入SPDAT在主机模式下向此寄存器写入一个字节会立即启动一次SPI发送同时也会接收一个字节。在从机模式下写入的数据会被放入发送缓冲区等待主机时钟来移出。读取SPDAT读取此寄存器得到的是接收缓冲区里的数据即最近一次SPI交换主机发送/从机接收或从机发送/主机接收所获取到的字节。这里有一个非常重要的硬件机制SPI的发送是单缓冲的而接收是双缓冲的。这意味着在上一字节还没发送完之前你不能写入下一个字节否则会触发WCOL。但是在接收时硬件会将移位寄存器里的数据自动转移到独立的接收缓冲区这样移位寄存器可以立即准备接收下一个字节只要你在下一个字节接收完成前把当前接收缓冲区的数据读走就行。3. SPI主从模式配置与实战代码理解了寄存器我们来动手配置。根据你的系统架构配置方法截然不同。3.1 经典单主单从配置这是最常见的场景比如MCU作为主机连接一个SPI Flash作为从机。主机配置步骤初始化引脚虽然SPEN使能后引脚功能会自动切换但良好的习惯是先确保P2.2 (MOSI), P2.5 (SPICLK) 初始化为推挽输出模式P2.3 (MISO) 初始化为输入模式。SS引脚P2.4在此模式下可以配置为普通GPIO输出用于控制从机的片选。配置SPCTL寄存器SSIG 1我们忽略SS引脚功能用GPIO控制从机片选。SPEN 1使能SPI模块。DORD 0假设外设是MSB在先。MSTR 1设置为主机模式。CPOL和CPHA根据外设手册选择。例如对于大多数SPI Flash常用Mode 0 (CPOL0, CPHA0) 或 Mode 3 (CPOL1, CPHA1)。这里以Mode 0为例设置CPOL0,CPHA0。SPR1, SPR0设置SPI时钟分频。假设系统CCLK为12MHz想要1.5Mbit/s的速率则分频系数应为812M/81.5M。查表可知CCLK/8这个分频不存在最接近的是CCLK/4 (3M) 或 CCLK/16 (750k)。我们选择CCLK/16即SPR10, SPR01。编写收发函数主机发起通信的流程是拉低从机SS - 写入SPDAT启动传输 - 等待SPIF置位 - 读取SPDAT获取从机返回数据 - 拉高从机SS。// 假设SS控制引脚连接在P0.0 sbit SPI_SS P0^0; void SPI_MasterInit(void) { // 1. 配置SPI相关引脚模式 (以准双向口为例实际可根据驱动能力调整) P2M1 ~((12) | (15)); // P2.2(MOSI), P2.5(SCLK) 推挽输出 P2M2 | ((12) | (15)); P2M1 ~(13); // P2.3(MISO) 仅输入 P2M2 ~(13); // 2. 配置SPI控制寄存器: Mode 0, Master, CCLK/16, SS忽略 SPCTL (17) | // SSIG 1 (16) | // SPEN 1 (05) | // DORD 0 (MSB first) (14) | // MSTR 1 (Master) (03) | // CPOL 0 (02) | // CPHA 0 (01) | // SPR1 0 (10); // SPR0 1 (CCLK/16) SPI_SS 1; // 默认不选中从机 } unsigned char SPI_MasterTransfer(unsigned char dat) { unsigned char recv_data; SPSTAT 0xC0; // 清除SPIF和WCOL标志位写1清零 SPDAT dat; // 写入数据启动传输 while (!(SPSTAT 0x80)); // 等待传输完成标志SPIF置位 recv_data SPDAT; // 读取接收到的数据 return recv_data; } // 使用示例向从机发送一个命令字节并读取一个字节的响应 unsigned char SPI_SendCommand(unsigned char cmd) { unsigned char response; SPI_SS 0; // 选中从设备 response SPI_MasterTransfer(cmd); SPI_SS 1; // 释放从设备 return response; }从机配置步骤初始化引脚确保P2.3 (MISO) 初始化为输出模式P2.2 (MOSI), P2.5 (SPICLK), P2.4 (SS) 初始化为输入模式。配置SPCTL寄存器SSIG 0使用SS引脚功能。SPEN 1使能SPI。MSTR 0设置为从机模式。CPOL和CPHA必须与主机严格一致。中断或轮询处理从机通信由主机时钟驱动。从机需要预先将要发送的数据写入SPDAT。当主机发起传输时从机会自动接收数据并置位SPIF。从机应在SPIF置位后读取收到的数据并准备下一个要发送的数据。void SPI_SlaveInit(void) { // 配置SPI相关引脚模式 P2M1 ~(13); // P2.3(MISO) 推挽输出 P2M2 | (13); P2M1 | ((12) | (14) | (15)); // P2.2(MOSI), P2.4(SS), P2.5(SCLK) 高阻输入 P2M2 ~((12) | (14) | (15)); // 配置SPI控制寄存器: Mode 0, Slave, 使用SS引脚 SPCTL (07) | // SSIG 0 (16) | // SPEN 1 (05) | // DORD 0 (04) | // MSTR 0 (Slave) (03) | // CPOL 0 (02) | // CPHA 0 (01) | // SPR1 0 (从机忽略时钟分频) (00); // SPR0 0 // 使能SPI中断可选 IEN1 | 0x08; // 设置ESPI (IEN1.3) 1 EA 1; // 全局中断使能 // 预先写入一个初始数据例如设备ID SPDAT 0xA5; } // SPI中断服务函数 void SPI_ISR(void) interrupt 9 { // SPI中断向量号需查手册确定 unsigned char recv_cmd, send_data; if (SPSTAT 0x80) { // 检查SPIF标志 SPSTAT 0x80; // 清除SPIF标志写1清零 recv_cmd SPDAT; // 读取主机发来的命令 // 根据命令准备要返回的数据 send_data ProcessCommand(recv_cmd); // 将待发送数据写入SPDAT准备下一次传输 SPDAT send_data; } // 注意从机模式下WCOL也可能发生如果SPDAT在传输中被写 if (SPSTAT 0x40) { SPSTAT 0x40; // 清除WCOL标志 // 处理写冲突错误 } }3.2 多从机系统与SS片选管理当单个主机需要控制多个SPI从设备时硬件连接上所有从机的SPICLK、MOSI、MISO分别并联到主机的对应引脚。关键在于SS片选线的管理。每个从机需要独立的SS线。主机通过控制不同的GPIO引脚在同一时刻只拉低一个从机的SS确保总线不会冲突。配置要点主机配置与单主单从相同SSIG1MSTR1。使用多个普通GPIO口如P0.0, P0.1, P0.2分别连接到各从机的SS引脚。从机每个从机的配置也与前述相同SSIG0MSTR0。每个从机只响应自己SS引脚的低电平信号。软件流程主机在与某个从机通信前先将其对应的SS线拉低通信结束后再拉高。在切换从机时必须确保前一个从机的SS已拉高再拉低下一个从机的SS中间最好有短暂延时避免信号毛刺导致误触发。3.3 主从动态切换与“模式改变”机制P89LPC93x的SPI支持一个有趣的功能通过SS引脚动态改变主从模式。这在两个对等设备需要互为主从的场合例如通过SPI互连的两块MCU非常有用。机制原理 当设备配置为主机模式SPEN1,SSIG0,MSTR1且其SS引脚P2.4被配置为输入或准双向模式时如果另一个主机将该SS引脚拉低本设备会被强制切换为从机模式硬件自动将MSTR位清零。同时SPIF标志会被置位如果中断使能还会产生SPI中断。应用场景与配置 假设Device A和Device B通过SPI直连MOSI-MOSI, MISO-MISO, SPICLK-SPICLK, SS-SS。两者初始都配置为“潜在主机”SPEN 1SSIG 0// 使用SS引脚功能MSTR 1// 初始设为主机P2.4 (SS)配置为准双向口P2M2.4, P2M1.4 01当Device A想要发送数据时它执行以下操作将自己的SS引脚P2.4配置为输出模式P2M2.4, P2M1.4 00? 注意准双向口本身可输出低电平这里更准确的是直接操作端口寄存器拉低。驱动自己的SS引脚输出低电平。这个低电平信号会作用于Device B的SS引脚导致Device B的MSTR位被硬件清零变为从机。此时Device A是唯一的主机可以开始SPI通信。软件注意事项在作为“潜在主机”的设备中软件必须定期检查MSTR位。如果发现MSTR被意外清零即自己被选为了从机应进入从机接收状态。通信结束后发起方Device A应释放SS线拉高并可能将SS引脚配置回输入/准双向模式等待下一次通信。这个机制对时序要求严格软件需要妥善处理竞争和状态切换否则容易导致总线冲突或死锁。4. SPI时钟与数据模式深度解析SPI通信的时序由CPOL和CPHA两位精确控制形成了四种标准模式。理解这四种模式的波形差异是调试SPI通信的基石。4.1 CPOL与CPHA定义四种SPI模式CPOL (Clock Polarity): 时钟极性。决定了SCLK线在空闲时的状态。CPOL0: 时钟空闲时为低电平。CPOL1: 时钟空闲时为高电平。CPHA (Clock Phase): 时钟相位。决定了数据是在时钟的第一个边沿还是第二个边沿被采样捕获。CPHA0: 数据在第一个时钟边沿被采样。对于CPOL0第一个边沿是上升沿对于CPOL1第一个边沿是下降沿。CPHA1: 数据在第二个时钟边沿被采样。组合起来就是以下四种模式主从设备的模式必须完全一致模式CPOLCPHA时钟空闲电平数据采样边沿数据变化边沿Mode 000低电平第一个上升沿下降沿Mode 101低电平第二个下降沿上升沿Mode 210高电平第一个下降沿上升沿Mode 311高电平第二个上升沿下降沿关键记忆点对于P89LPC93x当CPHA0时数据在时钟的前沿Leading Edge被采样在后沿Trailing Edge更新。当CPHA1时则相反。这个“前沿”和“后沿”的具体是上升沿还是下降沿则由CPOL决定。4.2 模式0 (CPOL0, CPHA0) 详解与波形这是最常用的模式。我们结合手册中的图45从机格式和图47主机格式来分析。时序特点空闲时SCLK为低电平。当主机启动传输写入SPDATSCLK在短暂延时后产生第一个脉冲。数据采样时刻在SCLK的第一个上升沿主从双方同时采样对方的数据线主机采样MISO从机采样MOSI。这意味着在上升沿到来之前数据必须已经稳定在数据线上。数据更新时刻在SCLK的下降沿双方将下一个要发送的数据位放到数据线上。对软件的影响主机在写入SPDAT启动传输后数据会在约半个到一个SPI位时间后出现在MOSI线上确保在第一个上升沿前稳定。从机必须在自己的SS引脚变低且第一个SCLK上升沿到来之前将第一个要发送的数据位准备好。这就要求从机的响应速度要足够快通常需要在SS下降沿触发的中断中或通过预先写入SPDAT来准备数据。4.3 模式3 (CPOL1, CPHA1) 及其他模式Mode 3是另一种常见模式尤其在一些存储器中用到。时序特点空闲时SCLK为高电平。数据采样时刻在SCLK的第二个上升沿即第一个下降沿之后的那个上升沿。数据更新时刻在SCLK的下降沿。Mode 1和Mode 2相对较少使用但原理相通。选择哪种模式完全取决于你的外设器件的数据手册要求绝对不能自己想当然。4.4 时钟预分频器SPR1, SPR0配置与速率计算SPI的通信速率由系统主时钟CCLK和SPCTL寄存器中的SPR1、SPR0分频位共同决定。P89LPC93x提供4种分频选择。速率计算公式SPI_BaudRate CCLK / (4 * (2^(2*SPR1 SPR0)) )更直观地看下表SPR1SPR0分频系数SPI时钟频率 (当CCLK12MHz)0043.0 MHz0116750 kHz1064187.5 kHz1112893.75 kHz选择策略不超过外设极限首先查看外设器件手册的最大支持SCLK频率确保配置的速率低于此值。考虑信号完整性在长导线或干扰较大的环境中过高频率会导致信号畸变应适当降低速率。匹配从机速度如果从机是低速设备如某些传感器主机速率过高可能导致从机来不及响应。系统负载更高的SPI速率意味着更频繁的中断或DMA请求会占用更多CPU时间或总线带宽。一个常见的做法是在初始化时设置一个较低的、可靠的速率如CCLK/64在确认通信正常后如果需要高性能再尝试提高速率。5. 高级主题与疑难杂症排查在实际项目中仅仅完成基础通信往往不够还会遇到一些棘手的边界情况和错误。5.1 写冲突Write Collision的产生与处理写冲突标志WCOL是SPI状态寄存器里一个非常重要的错误指示位。它的触发条件是在SPI数据传输正在进行的过程中即SPIF0期间软件向SPDAT数据寄存器执行了写操作。为什么会发生主机端相对少见因为主机通常能控制发送节奏。但如果程序bug导致在未检查SPIF标志时就连续写入SPDAT就会触发。从机端非常常见从机无法预知主机何时发起传输。如果从机在主机时钟正在移出数据的中间时刻尝试更新SPDAT中的数据就会发生写冲突。硬件行为当写冲突发生时WCOL位被硬件置1。重要的是这次冲突的写入操作是无效的SPDAT中准备发送的数据不会被加载到移位寄存器当前正在进行的传输不受影响继续完成。软件处理流程在每次写入SPDAT之前尤其是从机在中断服务程序中准备下一个发送数据时必须检查SPIF标志确保上一次传输已完成。在SPI中断服务程序或轮询处理中读取SPSTAT后应检查WCOL位。如果WCOL为1说明发生了冲突。软件需要向WCOL位写1来清除该标志。然后根据应用逻辑决定是重发丢失的数据还是忽略这次错误。// 安全的从机数据更新函数在中断中使用 void SPI_SlaveUpdateTxData(unsigned char new_data) { if (SPSTAT 0x40) { // 先检查是否有未处理的写冲突 SPSTAT 0x40; // 清除WCOL标志 } // 确保当前没有正在进行的传输SPIF为0或者等待其完成 // 对于从机在SPI中断中当SPIF置位表示一次传输结束此时是更新数据的安全窗口 SPDAT new_data; }5.2 从机模式下的SS引脚行为与CPHA0的陷阱手册第13.2节特别强调了一个重要限制当CPHA 0时SSIG必须为0即必须使用SS引脚功能并且SS引脚必须在连续的字节传输之间被取消选中拉高再重新选中拉低。原因分析 在CPHA0模式下数据采样的时刻第一个时钟边沿与SS引脚的激活密切相关。如果SS在两个字节之间保持低电平从机硬件可能无法正确区分字节边界导致数据错位。因此主机在发送多字节数据时如果从机配置为CPHA0必须对每个字节都执行“拉低SS - 发送字节 - 拉高SS”的操作或者至少在每个字节发送间隙短暂拉高SS。这对于一些需要连续传输的命令-数据序列的外设如Flash的读操作来说会降低效率。解决方案改用CPHA1模式如果外设支持优先使用CPHA1。在此模式下SS引脚可以在整个多字节传输期间保持低电平效率更高。严格遵守字节间SS切换如果外设只支持CPHA0则主机软件必须严格管理SS信号确保字节间有足够的SS高电平时间。5.3 SPI中断的应用与优化使能SPI中断设置ESPI (IEN1.3)和EA可以让CPU在数据收发完成时被及时通知避免轮询等待提高系统效率。中断服务程序ISR要点进入ISR后首先读取SPSTAT。这个操作本身不会清除标志但可以获取状态。判断中断源检查SPIF和WCOL位。清除标志通过向SPIF和WCOL位写1来清除它们。这是必须的步骤否则会连续触发中断。处理数据读取SPDAT获取接收到的数据写入下一个要发送的数据如果需要全双工通信。注意从机的写冲突在从机ISR中如果需要在SPDAT中放置下一个响应数据务必确保在SPIF置位后的安全窗口内操作或做好冲突检测和处理。一个典型的主机中断驱动发送流程volatile unsigned char spi_tx_buffer[10]; volatile unsigned char spi_rx_buffer[10]; volatile unsigned char spi_index 0; volatile bit spi_busy 0; void SPI_MasterSendArray(unsigned char *data, unsigned char len) { // 等待上一次传输完成 while(spi_busy); // 复制数据到发送缓冲区 for (spi_index0; spi_indexlen; spi_index) { spi_tx_buffer[spi_index] data[spi_index]; } spi_index 0; spi_busy 1; SPI_SS 0; // 选中从机 SPSTAT 0xC0; // 清除标志 SPDAT spi_tx_buffer[spi_index]; // 启动第一次传输 } void SPI_ISR(void) interrupt 9 { if (SPSTAT 0x80) { // SPIF SPSTAT 0x80; // 清除SPIF spi_rx_buffer[spi_index-1] SPDAT; // 保存刚接收到的数据 if (spi_index sizeof(spi_tx_buffer)) { // 发送下一个字节 SPDAT spi_tx_buffer[spi_index]; } else { // 所有字节发送完毕 SPI_SS 1; // 释放从机 spi_busy 0; // 标记传输结束 } } if (SPSTAT 0x40) { // WCOL SPSTAT 0x40; // 清除WCOL这里可以添加错误处理 } }5.4 低功耗模式下的SPI行为当P89LPC93x进入空闲Idle模式时CPU停止执行指令但外设包括SPI的时钟通常还在运行取决于具体配置。这意味着如果SPI被配置为从机它仍然可以响应主机发起的通信并在数据接收完成后产生SPI中断从而将CPU从空闲模式唤醒。这是一个非常有用的特性可以用于构建低功耗的传感器节点。然而在掉电Power-down模式下主振荡器停止SPI模块由于失去时钟源而完全停止工作无法进行通信或产生中断。如果系统需要在深度睡眠下仍能通过SPI被唤醒则需要考虑使用外部中断或其他有独立时钟源的外设如看门狗定时器或比较器作为唤醒源。6. 实战案例驱动SPI Flash存储器我们以一个具体的例子——驱动W25Q162MB SPI Flash——来串联前面所有的知识点。这款Flash支持标准SPI模式0和模式3。硬件连接MCU MOSI (P2.2) - Flash DI (数据输入)MCU MISO (P2.3) - Flash DO (数据输出)MCU SPICLK (P2.5) - Flash CLKMCU P0.0 (GPIO) - Flash CS (片选低有效)Flash WP# 和 HOLD# 接高电平。软件驱动关键点初始化配置SPI为模式0主机速率设为CCLK/16保证稳定性。将CS引脚P0.0配置为推挽输出并拉高。实现基础收发函数就是前面SPI_MasterTransfer函数它封装了单字节交换。实现Flash专用命令函数每个Flash操作都以一个命令字节开始。#define CMD_READ_ID 0x9F #define CMD_READ_DATA 0x03 #define CMD_WRITE_ENABLE 0x06 unsigned long SPI_FlashReadID(void) { unsigned long id 0; FLASH_CS 0; // 选中Flash SPI_MasterTransfer(CMD_READ_ID); id | (unsigned long)SPI_MasterTransfer(0xFF) 16; id | (unsigned long)SPI_MasterTransfer(0xFF) 8; id | SPI_MasterTransfer(0xFF); FLASH_CS 1; // 释放Flash return id; } void SPI_FlashReadData(unsigned long addr, unsigned char *buffer, unsigned int len) { unsigned int i; FLASH_CS 0; SPI_MasterTransfer(CMD_READ_DATA); SPI_MasterTransfer((addr 16) 0xFF); // 发送24位地址 SPI_MasterTransfer((addr 8) 0xFF); SPI_MasterTransfer(addr 0xFF); for (i0; ilen; i) { buffer[i] SPI_MasterTransfer(0xFF); // 读数据时主机发送哑元数据 } FLASH_CS 1; }处理Flash的“忙”状态Flash在写或擦除操作期间会置位状态寄存器中的“忙”位。在写入前需要发送WRITE_ENABLE命令在写入后需要轮询状态寄存器直到“忙”位清除。void SPI_FlashWaitBusy(void) { FLASH_CS 0; SPI_MasterTransfer(CMD_READ_STATUS_REG1); while (SPI_MasterTransfer(0xFF) 0x01); // 检查BUSY位 FLASH_CS 1; }调试心得第一步永远是读ID上电后先尝试读取器件ID0x9F命令这是验证SPI硬件连接和基本时序是否正确的最快方法。如果读出的ID不对检查模式CPOL/CPHA、时钟极性、片选信号和电源。注意字节序SPI Flash通常是MSB在先与P89LPC93x的默认设置一致。片选时序Flash对CS的下降沿和上升沿非常敏感。确保在发送命令字节之前拉低CS在整个命令序列包括地址、数据完成之后再拉高CS。两个命令之间CS必须有一个短暂的高电平时间具体看Flash数据手册的tSHSL参数。上拉电阻如果导线较长在SPI总线上特别是MISO加上4.7k-10k的上拉电阻可以增强抗干扰能力。通过这个完整的案例你应该能够将P89LPC93x的SPI模块从寄存器配置到实际应用融会贯通。记住数据手册是你的第一参考资料当通信出现问题时第一件事就是用逻辑分析仪或示波器抓取SPI四根线的实际波形与数据手册的时序图逐一对齐这是定位硬件和软件问题最直接有效的方法。