MC68HC908GZ SPI中断机制深度解析与实战配置指南
1. 项目概述与核心价值
在嵌入式开发,尤其是涉及实时数据交换的领域,比如电机驱动、传感器数据采集或者显示屏刷新,SPI(Serial Peripheral Interface)通信的效率和实时性往往是项目成败的关键。很多新手工程师,甚至一些有经验的开发者,在处理SPI通信时,习惯性地采用“轮询”(Polling)的方式:主循环里不停地检查“数据发完了没?”、“新数据收到了没?”。这种方式在低速或简单应用中尚可,一旦系统任务变多、通信频率升高,CPU时间被大量浪费在无意义的查询上,系统响应就会变得迟钝,甚至错过关键事件。
中断机制,就是解决这个痛点的“利器”。它让外设(比如SPI模块)在“有事”的时候(比如数据收发完成、发生错误),主动“打断”CPU当前的工作,让CPU立刻来处理这个紧急任务。处理完后,CPU再回到原来的地方继续执行。这就像你在专心写代码时,手机来了个重要电话,你接完电话再继续写,而不是每隔5秒就拿起手机看一眼有没有来电。对于MC68HC908GZ这类资源有限的8位微控制器来说,用好中断是榨干其性能、实现复杂多任务系统的必修课。
MC68HC908GZ系列的SPI模块提供了相当完善的中断支持,围绕SPRF(接收满)和SPTE(发送空)这两个核心状态标志构建。但芯片手册(Datasheet)往往只告诉你每个寄存器位是干什么的,却很少告诉你“为什么要这么配置”以及“实际编程时会遇到哪些坑”。我在这类项目上摸爬滚打多年,从简单的EEPROM读写到复杂的多从机传感器网络,深刻体会到:仅仅知道位定义是远远不够的,理解中断产生的条件、清除标志的“机关”、以及不同模式下的细微差别,才是写出稳定、高效SPI驱动代码的核心。本文将结合手册内容,深入解析这些“所以然”,并分享一系列从实际项目中总结出的配置心得和避坑指南。
2. SPI中断机制深度解析
要驾驭MC68HC908GZ的SPI中断,不能孤立地看几个使能位,必须把它当作一个由状态标志、使能逻辑、硬件清除机制共同构成的系统来理解。这个系统的核心目标,是让CPU从枯燥的轮询中解放出来,只在数据真正需要处理时才被唤醒。
2.1 核心中断源与使能逻辑
SPI模块提供了四个可以触发CPU中断请求的状态标志,它们可以分为三类:发送相关、接收相关和错误相关。理解它们之间的使能关系是正确配置的第一步。
1. 发送器空中断(SPTE)
- 标志位:
SPTE(SPI Transmitter Empty)。当发送数据寄存器(SPDR)中的数据被成功转移到移位寄存器,准备发送下一个字节时,此标志被硬件自动置1。 - 使能位:
SPTIE(SPI Transmit Interrupt Enable)。仅当SPTIE=1且SPE=1(SPI模块总使能)时,SPTE标志才能产生中断请求。 - 核心逻辑:这个中断的意图是告诉CPU:“发送缓冲区空了,你可以准备下一个要发送的数据了”。它是一个典型的“生产者-消费者”模型中的“消费者就绪”信号。这里有一个关键细节:
SPE位也参与使能逻辑。这意味着,如果你在初始化时先配置了SPTIE=1,但SPE=0(SPI未开启),那么即使SPTE置位,也不会产生中断。这个设计防止了在SPI模块未激活时产生无意义的中断。
2. 接收器满中断(SPRF)
- 标志位:
SPRF(SPI Receiver Full)。当移位寄存器接收完一个完整的字节,并将其转移到接收数据寄存器时,此标志被硬件自动置1。 - 使能位:
SPRIE(SPI Receiver Interrupt Enable)。当SPRIE=1时,SPRF标志即可产生中断请求。请注意:此中断的使能不受SPE位状态影响。即使SPE=0,只要SPRIE=1且SPRF=1,中断仍会产生。 - 核心逻辑:这个中断告诉CPU:“数据收到了,快来取走”。它独立于
SPE的设计非常有用。想象一个场景:主机发送完一串命令后,暂时关闭了SPI模块以省电(SPE=0),但从机可能仍在处理并准备返回数据。如果接收中断依赖于SPE,那么返回的数据就无法及时通知CPU。独立使能保证了在任何状态下,只要收到数据,CPU都能被及时告知。
3. 错误中断(MODF & OVRF)
- 标志位:
MODF(Mode Fault):模式错误。当SPI配置为主机(SPMSTR=1)且MODFEN=1时,如果SS引脚被拉低(意味着总线上出现了另一个主机),此标志置1。当SPI配置为从机时,在传输过程中SS引脚被拉高,也会置位此标志(如果MODFEN=1)。OVRF(Overflow):溢出错误。当CPU尚未读取接收数据寄存器中的旧数据,而移位寄存器又收到了一个新字节时,此标志置1。旧数据被保留,新数据丢失。
- 使能位:
ERRIE(Error Interrupt Enable)。这是一个“总开关”,当ERRIE=1时,MODF和OVRF两个标志中的任何一个置位,都会触发同一个“接收/错误”中断请求。 - 核心逻辑:错误中断将两种不同的异常情况合并到了一个中断向量中。这就要求我们在中断服务程序(ISR)中,必须首先检查
SPSCR寄存器,明确到底是MODF还是OVRF触发了中断,然后分别处理。MODFEN位提供了一个精细控制:当MODFEN=0时,MODF标志永远不会被置位,此时即使ERRIE=1,也只有OVRF能触发错误中断。这在单主机系统中可以避免SS引脚配置不当引发的误报。
上述逻辑关系可以用一个简化的表达式来概括:
- 发送中断请求 =
SPTE & SPTIE & SPE - 接收中断请求 =
SPRF & SPRIE - 错误中断请求 = (
MODF | OVRF) &ERRIE& (MODFEN参与MODF置位逻辑)
2.2 中断标志的清除“机关”
这是最容易出错的地方!MC68HC908GZ的SPI中断标志清除不是简单的“写1清零”或“读寄存器清零”,而是有特定的序列要求。操作错了,标志清不掉,会导致中断持续触发,系统卡死。
清除
SPRF(接收满标志):- 操作序列:先读取
SPSCR寄存器(此时SPRF必须为1),然后读取SPDR(接收数据寄存器)。 - 原理剖析:这个两步操作是一种硬件互锁机制。第一步读取状态寄存器,相当于“确认”了这个接收事件;第二步读取数据寄存器,相当于“取走”了数据。只有完成“确认并取走”这个完整动作,硬件才认为这个接收事务已处理完毕,自动清除
SPRF标志。如果只读数据寄存器而不读状态寄存器,标志是不会清除的。在实际编程中,我习惯在ISR里这样写:if (SPSCR & SPRF_MASK) { // 检查是否是接收中断 volatile uint8_t dummy; dummy = SPSCR; // 第一步:读SPSCR(状态) dummy = SPDR; // 第二步:读SPDR(数据),dummy变量防止编译器优化 // 此时SPRF已自动清除 received_data = dummy; // 处理数据 } - 常见坑点:在C语言中,如果编译器优化等级较高,它可能会认为
dummy = SPSCR;这行代码没有意义(因为没使用dummy的值)而将其优化掉。因此,必须将dummy声明为volatile,或者直接使用接收到的数据。
- 操作序列:先读取
清除
SPTE(发送空标志):- 操作:写入
SPDR(发送数据寄存器)。 - 原理:这很直观。
SPTE置位意味着“发送缓冲区空,可以写新数据了”。当你向SPDR写入一个新字节时,硬件认为你响应了这个“就绪”信号,于是自动清除SPTE标志。所以,通常在发送中断的ISR中,填充下一个要发送的字节,这个动作本身就会清除中断标志。
- 操作:写入
清除
MODF(模式错误) 和OVRF(溢出错误):MODF清除序列:先读取SPSCR(此时MODF必须为1),然后写入SPCR(控制寄存器)。OVRF清除序列:先读取SPSCR(此时OVRF必须为1),然后读取SPDR。- 原理与坑点:
MODF的清除需要写SPCR,这通常意味着你可能需要在ISR中重新初始化SPI(因为发生了多主机冲突)。而OVRF的清除序列和SPRF类似,但意义不同:OVRF清除时读SPDR,读出的是那个未被及时读取而可能已过时的旧数据,新数据已经丢失了。处理OVRF时,重点应该是修复导致CPU未能及时响应接收中断的软件逻辑(例如提高接收中断优先级、优化ISR处理时间),而不是处理这个数据。
重要经验:在编写SPI中断服务程序时,第一个动作就应该是读取
SPSCR寄存器的值并保存到局部变量中。因为后续的清除操作(读SPDR或写SPCR)会改变SPSCR中的标志位。用保存的状态变量来判断中断源,才是最可靠的。
2.3 中断与SPI模块复位的关系
理解SPE(SPI使能位)对中断标志的影响,对于动态管理SPI模块至关重要。
- 部分复位:当
SPE位由1变为0时,SPI模块会进行一次“部分复位”。此时:SPTE标志被置1(因为发送逻辑停止,缓冲区视为空)。- 任何正在进行的传输被中止。
- 移位寄存器被清空。
- 但是,所有控制位(如
SPTIE,SPRIE,ERRIE,MODFEN等)以及状态标志SPRF,OVRF,MODF均保持不变。
- 系统复位:只有真正的系统复位(上电、看门狗等)才会将所有控制位和状态标志清零。
这个设计的巧妙之处在于:它允许你在两次传输之间关闭SPI模块(SPE=0)以节能,而无需重新配置所有中断使能和控制寄存器。当你再次开启SPI(SPE=1)时,之前的中断配置依然有效。同时,SPRF、OVRF、MODF这些标志在SPE=0时仍可被查询和中断响应,确保你不会错过在SPI禁用期间可能发生的错误(比如MODF)。
3. 关键寄存器配置详解与实战指南
寄存器配置不是简单地填几个魔法数字(Magic Number)。每一比特的设置都对应着硬件行为的变化。下面我们结合常见应用场景,拆解每个关键位的配置逻辑。
3.1 SPI控制寄存器(SPCR - $0010)
这个寄存器是SPI功能的“总开关”和“模式选择器”。
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | | SPRIE | R |SPMSTR| CPOL | CPHA |SPWOM | SPE | SPTIE|- SPRIE (Bit 7) & SPTIE (Bit 0):如前所述,接收和发送中断使能。我的实战建议是:在初始化阶段,通常先不开启中断(都设为0),等SPI模块、GPIO、时钟等全部配置稳定后,在开启传输前再置位它们。避免初始化过程中因状态不稳定产生误中断。
- SPMSTR (Bit 5):主/从模式选择。1=主机,0=从机。关键点:在从机模式下,
SS引脚的功能是固定的输入(用于片选),不受MODFEN控制。在主机模式下,SS引脚的功能则取决于MODFEN。 - CPOL (Bit 4) & CPHA (Bit 3):时钟极性与相位。这是SPI通信的“方言”,主从设备必须一致。
CPOL:时钟空闲状态。0=低电平,1=高电平。CPHA:数据采样时刻。0=在时钟的第一个边沿(SCK的第一个跳变)采样,1=在时钟的第二个边沿采样。- 如何选择?这完全取决于你的外设芯片数据手册。比如,常见的NOR Flash芯片多采用
CPOL=0, CPHA=0(模式0)或CPOL=1, CPHA=1(模式3)。务必核对清楚,否则数据会错位。
- SPWOM (Bit 2):有线或模式。置1时,SCK、MOSI、MISO引脚变为开漏输出。这主要用于两种场景:
- 与其他开漏输出的设备共享总线(如I2C,但需要软件模拟)。
- 电平转换。当MCU是3.3V,而外设是5V时,可以将引脚设为开漏,外加上拉电阻到5V,实现安全通信。注意:开漏输出必须外接上拉电阻才能输出高电平。
- SPE (Bit 1):SPI模块总使能。这是最后一步才打开的“闸门”。
3.2 SPI状态与控制寄存器(SPSCR - $0011)
这个寄存器包含了状态标志和额外的控制位。
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | | SPRF |ERRIE| OVRF| MODF| SPTE|MODFEN| SPR1 | SPR0|- SPRF, OVRF, MODF, SPTE (Bit 7,5,4,3):状态标志,只读。在ISR中通过特定序列清除。
- ERRIE (Bit 6):错误中断总使能。
- MODFEN (Bit 2):模式错误检测使能。这是配置的难点。
- 在主机模式(
SPMSTR=1)下:MODFEN=1:SS引脚被强制为输入功能,用于检测多主机冲突。如果检测到SS被拉低(另一个主机在驱动总线),则产生MODF错误,SPI模块自动禁用(SPE清零,MISO变为高阻),防止总线冲突。这是多主机系统或需要总线冲突保护的场景下的安全配置。MODFEN=0:SS引脚可作为普通GPIO使用,SPI模块忽略该引脚电平。这是最常见的单主机系统配置,因为通常我们不需要这个检测功能,反而希望把SS引脚用作普通IO去控制从机的片选。
- 在从机模式(
SPMSTR=0)下:SS引脚总是输入。MODFEN仅控制是否在传输中SS变高时产生MODF错误。通常从机设备保持MODFEN=0即可。
- 在主机模式(
- SPR1, SPR0 (Bit 1,0):波特率选择位(仅主机模式有效)。波特率 =
CGMOUT / (2 * BD),其中BD为分频因子(2, 8, 32, 128)。CGMOUT是时钟发生器模块的输出时钟。计算要点:首先要确定你的系统总线时钟,然后根据外设支持的最高速率和通信距离选择合适的分频。过高的速率在长线传输时容易出错。
3.3 SPI数据寄存器(SPDR - $0012)
这是一个特殊的“影子寄存器”。写入时,数据进入发送数据寄存器;读取时,数据来自接收数据寄存器。它们是两个独立的物理寄存器,只是共享同一个地址。
致命陷阱:绝对不要对
SPDR使用“读-修改-写”指令(如C语言中的|=,&=, 或汇编中的BSET/BCLRon the same address)。因为读操作取得的是接收缓冲区的值,而写操作修改的是发送缓冲区,两者毫无关系。这样的操作会导致数据混乱。正确的做法是:发送时直接赋值(SPDR = txData;),接收时直接读取(rxData = SPDR;)。
4. 中断服务程序(ISR)设计与实战流程
理解了原理和寄存器,最终要落地到代码。一个健壮的SPI中断服务程序需要处理好多任务并发、标志清除和错误处理。
4.1 基本ISR框架
以下是一个典型的SPI中断服务程序框架,以C语言为例,假设使用SPRIE和SPTIE,并处理错误中断。
// 假设的寄存器地址定义 #define SPCR (*(volatile uint8_t*)0x0010) #define SPSCR (*(volatile uint8_t*)0x0011) #define SPDR (*(volatile uint8_t*)0x0012) // 位定义 #define SPRF_MASK 0x80 #define ERRIE_MASK 0x40 #define OVRF_MASK 0x20 #define MODF_MASK 0x10 #define SPTE_MASK 0x08 // ... 其他位定义 // 全局缓冲区/状态变量 volatile uint8_t spi_tx_buffer[32]; volatile uint8_t spi_rx_buffer[32]; volatile uint8_t spi_tx_index = 0; volatile uint8_t spi_tx_count = 0; volatile uint8_t spi_rx_index = 0; volatile bool spi_transfer_complete = false; __interrupt void SPI_ISR(void) { uint8_t status = SPSCR; // 第一步:保存状态寄存器值! // 1. 检查并处理接收中断 (SPRF) if (status & SPRF_MASK) { // 清除SPRF标志的标准序列 volatile uint8_t dummy; dummy = SPSCR; // 读状态寄存器(已读,此处可省略,但为清晰保留) dummy = SPDR; // 读数据寄存器,清除标志 spi_rx_buffer[spi_rx_index++] = dummy; // 存储数据 // 这里可以添加缓冲区满检查... } // 2. 检查并处理发送中断 (SPTE) if (status & SPTE_MASK) { if (spi_tx_index < spi_tx_count) { SPDR = spi_tx_buffer[spi_tx_index++]; // 写入下一个字节,同时清除SPTE标志 } else { // 所有数据发送完毕 // 可以选择关闭发送中断 SPTIE,避免空触发 SPCR &= ~SPTIE_MASK; spi_transfer_complete = true; // 通知主程序 } } // 3. 检查并处理错误中断 (MODF 或 OVRF) if (status & (OVRF_MASK | MODF_MASK)) { // 错误中断可能由两者之一触发 if (status & MODF_MASK) { // 模式错误:多主机冲突或SS线异常 dummy = SPSCR; // 读状态寄存器 dummy = SPCR; // 写控制寄存器,清除MODF标志(此操作无实际影响,仅为清除序列) // 通常需要重新初始化SPI模块,并可能进行错误恢复 SPCR = 0; // 先关闭SPI init_spi(); // 重新初始化你的SPI配置函数 } if (status & OVRF_MASK) { // 溢出错误:CPU来不及读取数据 dummy = SPSCR; // 读状态寄存器 dummy = SPDR; // 读数据寄存器(丢弃旧数据),清除OVRF标志 // 记录错误,可能需要增加接收缓冲区或提高中断优先级 log_error("SPI Overflow!"); } } // 注意:实际芯片可能需要手动清除中断标志,此处假设为自动向量中断。 }4.2 主程序与中断的协同工作流程
中断服务程序负责“应急处理”,而主程序或后台任务负责“任务规划”。一个典型的数据块发送/接收流程如下:
初始化:
- 配置GPIO(将MISO、MOSI、SCK、SS配置为SPI功能)。
- 配置SPCR、SPSCR,设置主从模式、时钟极性相位、波特率。此时先不使能中断(
SPRIE=0, SPTIE=0, ERRIE=0)和SPI模块(SPE=0)。 - 初始化发送/接收缓冲区和索引变量。
- 配置MCU的中断控制器,将SPI中断向量指向你的
SPI_ISR函数,并全局使能中断。
启动传输:
- 将待发送数据填入
spi_tx_buffer,设置spi_tx_count和spi_tx_index=0。 - 如果需要接收数据,则重置
spi_rx_index=0。 - 清除完成标志:
spi_transfer_complete = false。 - 关键步骤:使能所需中断。如果使用“发送空”中断来驱动发送,则置位
SPTIE;如果希望接收数据时产生中断,则置位SPRIE。根据情况决定是否使能ERRIE。 - 最后,置位
SPE,使能SPI模块。 - 手动触发第一次发送:由于发送缓冲区初始为空,
SPTE可能已经是1,但此时中断还未开启。一种可靠的方法是,在开启SPTIE和SPE之后,立即向SPDR写入第一个字节。这个写入操作会启动时钟,开始传输,并且会清除SPTE标志。当这个字节从移位寄存器移出后,SPTE会再次置1,从而触发第一次发送中断,进入中断驱动的发送流程。
- 将待发送数据填入
传输进行中:
- 主程序可以继续执行其他任务。
SPI_ISR自动处理数据的搬移和标志清除。
传输完成:
- 主程序可以通过轮询
spi_transfer_complete标志,或者等待一个由ISR释放的信号量/任务通知,来获知传输完成。 - 处理接收到的数据(
spi_rx_buffer)。 - 如果需要,可以关闭SPI中断或模块以节能。
- 主程序可以通过轮询
4.3 高级话题:使用DMA与SPI中断结合
在一些高端应用中,为了进一步解放CPU,可以使用DMA(直接存储器访问)来搬运SPI数据。MC68HC908GZ系列本身可能不包含DMA控制器,但理解这个思想很重要。其原理是:
- 配置DMA通道的源地址为内存发送缓冲区,目标地址为
SPDR。 - 使能SPI的
SPTE中断,但在SPI_ISR中,不直接写SPDR,而是启动或链式DMA传输。 - 同样,接收时配置DMA从
SPDR搬数据到内存接收缓冲区,由SPRF中断或DMA完成中断来通知CPU。 - 这样,CPU只在传输开始和结束时介入,中间的数据搬运全部由DMA完成,极大提高了效率。这在需要高速、连续传输(如LCD刷屏、音频流)时非常有用。
5. 低功耗模式下的SPI中断行为
在电池供电的设备中,低功耗设计至关重要。MC68HC908GZ的WAIT和STOP模式会影响SPI模块的行为。
5.1 WAIT模式
执行WAIT指令后,CPU进入低功耗休眠状态,但外设时钟(包括SPI的时钟)通常仍在运行。
- SPI状态:SPI模块保持活动状态。这意味着,如果SPI被配置为主机并在传输中,它将继续生成时钟和数据;如果是从机,它仍能接收数据和时钟。
- 中断唤醒:这是关键特性。任何已使能的SPI中断(
SPRF、SPTE或错误中断)在触发时,都可以将MCU从WAIT模式唤醒。CPU被唤醒后,会首先执行对应的中断服务程序(ISR),ISR返回后再继续执行WAIT指令之后的代码。 - 实操建议:如果你希望在WAIT模式下完全停止SPI以节省功耗,则应在进入WAIT模式前,将
SPE位清零(禁用SPI模块)。但请注意,禁用SPI不会清除已挂起的中断标志。一个更安全的做法是:进入WAIT前,先禁用SPI中断(SPRIE=0, SPTIE=0, ERRIE=0),再检查并清除SPSCR中的可能标志位,最后再执行WAIT。
5.2 STOP模式
执行STOP指令后,MCU进入最深的低功耗模式,主时钟停止。
- SPI状态:SPI模块停止工作,因为其时钟源停止了。任何正在进行的传输都会被中止。
- 唤醒与恢复:STOP模式通常只能由外部中断、复位或特定的唤醒定时器(如低功耗定时器LPTMR)退出。SPI中断无法唤醒STOP模式,因为其模块已不工作。退出STOP模式(通过外部中断唤醒)后,SPI寄存器保持进入STOP前的状态。如果唤醒后需要继续SPI通信,软件需要重新初始化SPI模块(至少需要重新使能
SPE),并可能需要重新启动被中止的传输序列。 - 重要注意:如果SPI通信对时序连续性要求极高(例如,驱动一个不允许通信中断的显示器件),则应避免在传输过程中进入STOP模式。或者,使用外部中断唤醒后,设计一套完整的通信状态恢复机制。
6. 调试技巧与常见问题排查
调试SPI中断问题,逻辑分析仪或带SPI解码功能的示波器是必备工具。以下是一些常见问题及排查思路:
问题1:根本进不了中断服务程序(ISR)。
- 检查1:全局中断是否使能?确认MCU的全局中断屏蔽位(如I位)已清除。
- 检查2:中断向量表是否正确?确保链接器脚本和启动代码正确地将
SPI_ISR函数地址放在了SPI中断向量处。 - 检查3:中断标志是否真的置位了?在主循环中轮询
SPSCR的SPRF或SPTE,看它们是否会变化。如果不变化,说明SPI数据传输本身可能就没成功(检查接线、时钟极性相位、波特率)。 - 检查4:中断使能位开了吗?确认
SPRIE或SPTIE已置1。特别注意:SPTIE需要SPE=1才有效。 - 检查5:中断标志清除序列是否正确?如果上次中断的标志未被正确清除,硬件可能不会产生新的中断请求。在ISR开头读取
SPSCR后,用调试器或IO口输出其值,确认标志位状态。
问题2:中断只进入一次,之后再也不进了。
- 几乎可以断定是中断标志清除有问题。严格按照第2.2节所述的序列操作:对于
SPRF,必须是“读SPSCR -> 读SPDR”;对于SPTE,就是“写SPDR”。在ISR中单步调试,观察执行清除操作后SPSCR相应标志位是否被清零。
问题3:数据收发出现错位、乱码。
- 检查1:时钟极性和相位(CPOL, CPHA)。这是最常见的原因。用示波器同时抓取SCK和MOSI/MISO信号,对照外设芯片数据手册的时序图,一个边沿一个边沿地核对数据采样点。
- 检查2:波特率是否过高?过高的波特率在长导线或面包板上容易受到干扰。尝试降低波特率(增大SPR分频比)测试。
- 检查3:从机片选(SS)信号是否正常?在
CPHA=0的模式下,SS信号在每字节传输间需要产生一个高脉冲。用逻辑分析仪检查SS信号的时序是否符合图17-13的要求。 - 检查4:是否存在溢出(OVRF)?如果接收数据丢失,检查是否触发了
OVRF。这表示你的ISR处理速度跟不上数据接收速度。优化ISR代码,或者使用更大的接收缓冲区(环形缓冲区),并在主循环中处理数据,而非在ISR中处理复杂逻辑。
问题4:作为从机时,收不到主机发来的数据。
- 检查1:SS引脚连接和配置。确保主机的SS线正确连接到从机的SS引脚,并且在传输期间被主机拉低。从机的SS引脚必须被配置为输入(通常是默认的),且软件不能将其重新配置为输出。
- 检查2:从机的SPI时钟源。从机的SCK必须由主机提供。检查接线。
- 检查3:从机的中断使能。确认从机的
SPRIE已置位,以便在收到数据(SPRF置位)时产生中断。
问题5:在调试器单步执行时SPI工作正常,全速运行就出错。
- 这通常是时序竞争条件的典型表现。例如,在主程序中判断
SPTE后写入SPDR,同时又在中断中写入SPDR,如果没有良好的互斥保护(如关中断),就可能发生冲突。确保对共享资源(SPDR、发送缓冲区索引spi_tx_index等)的访问是原子操作,或者在访问前后关中断/开中断。
最后,分享一个我常用的调试初始化步骤:
- 将SPI配置为最简单的轮询模式(关闭所有中断),测试基本的字节收发功能。确保硬件连接和基础配置正确。
- 使能接收中断(
SPRIE),在ISR中只是简单地读取数据并存入一个全局变量,在主循环中打印这个变量。验证中断能正常触发和清除。 - 加入发送中断(
SPTIE),实现中断驱动的单向数据块发送。 - 最后,实现全双工的中断驱动收发,并加入错误处理。 这种由简入繁、步步为营的方法,能帮你快速定位问题所在的层次。
