1. 项目概述:从手册到代码,打通MPC860 SPI的任督二脉
搞嵌入式开发,尤其是跟PowerPC架构的MPC860这类老牌通信处理器打交道,SPI(Serial Peripheral Interface)绝对是个绕不开的坎。手册翻来覆去,寄存器位定义看得眼花缭乱,但真到了写代码驱动外设的时候,还是感觉隔了一层纱——知道每个寄存器是干嘛的,但不知道怎么把它们串起来,让数据老老实实地在主从设备之间跑起来。我当年啃MPC860的SPI控制器时,没少在数据手册的图30-6和那一堆SPMODE、SPIE、SPCOM寄存器之间反复横跳。今天,我就结合手册里的核心内容,把MPC860 SPI从寄存器配置到编程实现的整个链路,掰开了揉碎了讲清楚。这不是照本宣科,而是基于实际调板子、抓波形踩过坑之后,总结出的“保姆级”实操指南。无论你是刚接触MPC860的新手,还是想深入理解其SPI控制器工作机制的老鸟,这篇文章都能帮你把理论落地成代码。
SPI的本质是一种同步、全双工、主从式的串行通信接口。它的优势在于协议简单、速率高、引脚少(通常4线),非常适合与Flash、ADC、DAC、传感器等外设通信。MPC860内部的CPM(通信处理器模块)集成了一个高度可配置的SPI控制器,支持主从模式、多种数据格式、中断驱动以及基于BD(Buffer Descriptor,缓冲区描述符)的DMA传输,功能相当强大,但复杂度也随之而来。核心难点就在于如何正确初始化那一大票寄存器,并理解BD机制如何与CPM的SDMA(Serial DMA)协同工作,实现“设置好就能跑,数据来了自动收”的自动化流程。接下来,我们就一步步拆解。
2. SPI核心寄存器深度解析与配置逻辑
手册里寄存器很多,但抓主干,最关键的就是控制数据传输格式的SPMODE、掌管事件与中断的SPIE/SPIM、以及发号施令的SPCOM。理解它们,就理解了SPI控制器的工作逻辑。
2.1 SPMODE寄存器:定义通信的“宪法”
SPMODE寄存器是SPI的配置核心,它决定了通信的基本规则。手册中的图30-6和一系列关于SPMODE[LEN]的例子,其实是在讲同一件事:数据在总线上如何排列。
关键字段解读:
SPMODE[LEN](字符长度): 这个字段定义了一次传输的比特数,其值为数据长度 - 1。这是最容易混淆的点。手册例子中,LEN=4表示数据长度是5比特,LEN=7表示8比特,LEN=0xC表示13比特。它直接影响CPU需要准备的数据内存布局。SPMODE[REV](数据反转): 控制数据位的传输顺序。REV=0时,先传输最高位(MSB);REV=1时,先传输最低位(LSB)。这必须与外设的数据格式严格匹配。很多SPI Flash默认是MSB first,而某些ADC可能是LSB first。SPMODE[CP](时钟极性)与SPMODE[CI](时钟相位): 这两者共同定义了SPI的四种工作模式(Mode 0, 1, 2, 3)。CP决定时钟空闲状态(0=低电平,1=高电平),CI决定数据在时钟的哪个边沿采样(0=第一个边沿,1=第二个边沿)。图30-6清晰地展示了CP=1时的时序。务必与外设数据手册的时序图核对一致,否则数据必然错乱。SPMODE[MSTR](主从模式): 1为主机,0为从机。主机产生时钟SPICLK,并控制片选SPISEL(如果使用)。SPMODE[EN](SPI使能): 这是SPI控制器的总开关,必须在配置好其他参数后才能置位。
实操心得:
LEN与内存对齐的坑手册里轻描淡写的一句“如果字符长度超过8位,数据长度应为偶数”,在实际编程中是个大坑。假设你要传输3个12位的数据(LEN=0xB),你不能简单地准备一个3字节的数组。因为12位是1.5个字节,CPM的DMA是以字节为单位访问内存的。你需要准备一个6字节(3 * 2)的缓冲区,并确保每个12位数据都按手册要求的格式(通常是左对齐或右对齐在16位半字中)存放。同样,TxBD[Data Length]字段应该填6,而不是3。如果这里搞错,轻则数据错位,重则总线挂死。
2.2 SPIE与SPIM寄存器:系统的“眼睛”和“耳朵”
SPIE(事件寄存器)和SPIM(中断屏蔽寄存器)是SPI与CPU交互的桥梁。SPIE负责报告事件(比如发送完成、接收完成、出错),SPIM则决定哪些事件能触发中断。
关键事件位解析:
SPIE[TXB](发送缓冲区事件): 当最后一个字符的数据从发送缓冲区写入Tx FIFO后置位。注意,这不意味着数据已经全部在总线上发送完毕!手册明确提示需要“等待两个字符时间”以确保数据完全发出。在中断服务程序里,如果你立刻释放或复用发送缓冲区,可能会截断最后几个比特的传输。SPIE[RXB](接收缓冲区事件): 当接收缓冲区已满(达到MRBLR或遇到帧结束),且对应的RxBD被关闭时置位。这是读取接收数据的标志。SPIE[TXE](发送错误)&SPIE[MME](多主错误): 错误处理的关键。TXE在传输过程中发生错误时置位(如从机未应答)。MME在主机模式下,外部却拉低了SPISEL(片选)时置位,表明总线仲裁出现问题(多主竞争)。SPIE[BSY](忙状态): 当第一个字符已被接收,但因为无可用RxBD(即所有RxBD[E]=0)而被丢弃时置位。这表明你的接收缓冲区链没有及时准备好,数据丢失了。
中断使能策略:通常,我们会通过SPIM使能TXB、RXB和错误事件的中断。初始化时,先向SPIE写入0xFF来清除所有可能残留的事件位,然后向SPIM写入0x37(二进制0011 0111),即同时使能TXB、RXB、TXE、MME和BSY的中断。这样,任何重要的状态变化都能及时通知CPU。
2.3 SPCOM寄存器:扣动扳机的手
SPCOM寄存器非常简单,只有一个有效位STR(Start Transmit)。但它启动传输的逻辑在主从模式下略有不同:
- 主机模式: 设置
STR后,如果TxBD和RxBD已准备就绪,SPI立即开始传输。 - 从机模式: 设置
STR后,SPI进入空闲状态,将发送数据从Tx缓冲区加载到数据寄存器,然后等待主机拉低SPISEL并提供时钟,才开始发送。
STR位会在一个系统时钟周期后自动清零,所以你不需要手动清除它。每次启动一次新的传输序列(可能包含多个BD的数据块),都需要重新置位STR。
3. 参数RAM与缓冲区描述符(BD)机制:DMA传输的引擎
MPC860 SPI的强大之处在于其基于BD的DMA传输。CPU只需设置好BD链表和缓冲区,CPM的SDMA通道就会自动完成数据在内存和SPI移位寄存器之间的搬运,极大减轻了CPU负担。
3.1 参数RAM初始化:搭建工作台
SPI参数RAM位于双端口RAM中一个固定的基地址(SPI_BASE)。初始化时,我们必须设置几个关键参数:
RBASE/TBASE: 分别指向接收和发送BD表在双端口RAM中的起始地址。必须8字节对齐。手册例子中假设一个RxBD后跟一个TxBD,所以RBASE=0x0000,TBASE=0x0008(一个BD占8字节)。RFCR/TFCR: 功能代码寄存器,主要控制字节序(BO位)。对于大多数大端格式的PowerPC,通常设置为0x10(二进制0001 0000),即大端模式,AT[1-3]根据具体内存访问需求设置。MRBLR: 最大接收缓冲区长度。它定义了每个RxBD关联的缓冲区至少需要多大。CPM接收数据时,不会向一个缓冲区写入超过MRBLR字节的数据。发送缓冲区长度不受此限制,由TxBD[Data Length]单独指定。MRBLR也建议设置为偶数,尤其是在字符长度>8位时。
注意事项:参数RAM的初始化顺序手册的编程示例步骤清晰,但隐含了一个关键顺序:必须先设置
RBASE/TBASE,再执行INIT RX AND TX PARAMETERS命令(写0x0051到CPCR)。这个命令会将SPI参数RAM中所有CPM维护的指针(如RBPTR,TBPTR)重置为RBASE/TBASE。如果你先执行初始化命令,后设置BASE地址,那么CPM的指针会被初始化为一个不确定的值(很可能是0),导致BD表找不到,DMA无法工作。这是一个经典的顺序坑。
3.2 缓冲区描述符(BD)详解:数据包的“快递单”
BD是CPM和CPU之间关于数据缓冲区的“契约”。每个BD对应一个数据缓冲区,包含状态控制、数据长度和缓冲区指针。
接收BD(RxBD)关键位:
E(Empty):1表示缓冲区为空,归CPM所有,CPM可以往里写数据;0表示缓冲区已满或出错,归CPU所有,CPU可以读取数据。初始化时,所有准备接收数据的RxBD,其E位必须置1。W(Wrap):1表示这是BD表中的最后一个BD。CPM处理完此BD后,会跳回RBASE指向的第一个BD,形成环形队列。这是构建BD链表的关键。I(Interrupt):1表示当此BD被关闭(填满)时,触发SPIE[RXB]事件(如果中断使能)。L(Last): 由CPM设置。在从机模式下,当SPISEL被取消断言(拉高)时,CPM会关闭当前RxBD并设置L=1,表示这是主机本次传输的最后一个字符所在的缓冲区。CM(Continuous Mode):仅主机模式有效。1启用连续模式。在此模式下,CPM在关闭此BD后不会清除E位,而是直接复用同一个缓冲区接收后续数据。这用于需要持续扫描从设备(如ADC)的场景,避免频繁切换BD的开销。
发送BD(TxBD)关键位:
R(Ready):1表示缓冲区数据已准备好,CPM可以发送;0表示缓冲区未就绪,CPU可以修改。初始化后,将第一个要发送的TxBD的R位置1。W,I: 功能同RxBD。L(Last):1表示此缓冲区包含消息的最后一个字符。发送完此BD的数据后,传输停止,即使后面还有R=1的BD。需要再次设置SPCOM[STR]来重启传输。CM(Continuous Mode):仅主机模式有效。1启用连续发送模式。CPM发送完此BD后不清除R位,从而自动重复发送此缓冲区内容。常用于产生周期性的测试信号。
BD初始化示例解析:手册示例中,RxBD状态字写0xB000,TxBD状态字写0xB800。我们拆开看:
RxBD: 0xB000=1011 0000 0000 0000b- Bit 15 (
E): 1 (空,CPM可写) - Bit 13 (
W): 0 (非最后一个BD,假设有多个BD时) - Bit 12 (
I): 1 (完成后产生中断) - Bit 11 (
L): 0 (初始值,由CPM设置) - Bit 9 (
CM): 1 (主机连续模式?这里手册示例可能为特定场景,通常从机或普通主机模式设为0) - 注意: 手册示例的这个值可能是个笔误或特定配置。对于常规单次传输,
CM通常为0。一个更通用的初始值是0x9000(E=1, I=1)。
- Bit 15 (
TxBD: 0xB800=1011 1000 0000 0000b- Bit 15 (
R): 1 (就绪) - Bit 13 (
W): 0 - Bit 12 (
I): 1 (完成后产生中断) - Bit 11 (
L): 1 (这是关键!表示发送完这个BD的数据就停止) - Bit 9 (
CM): 0 (普通模式)
- Bit 15 (
4. 主从模式编程实例与代码实现
理解了寄存器、参数RAM和BD,我们就可以把手册里的步骤翻译成实际的C代码。这里以主机模式为例,展示一个完整的初始化序列。
4.1 主机模式初始化与传输流程
以下是基于手册第30.8节,并补充了详细注释和实际编程细节的步骤:
/* 假设必要的寄存器地址映射已定义,例如通过volatile指针 */ #define IMMR (*(volatile unsigned long *)0xF0000000) /* 示例地址 */ #define SPI_BASE (IMMR + 0x3D80) #define SPIMODE (*(volatile unsigned short *)(SPI_BASE + 0x00)) #define SPIE (*(volatile unsigned short *)(SPI_BASE + 0x06)) #define SPIM (*(volatile unsigned short *)(SPI_BASE + 0x0A)) #define SPCOM (*(volatile unsigned short *)(SPI_BASE + 0x0D)) #define RFCR (*(volatile unsigned char *)(SPI_BASE + 0x04)) #define TFCR (*(volatile unsigned char *)(SPI_BASE + 0x05)) #define MRBLR (*(volatile unsigned short *)(SPI_BASE + 0x06)) #define RBASE (*(volatile unsigned short *)(SPI_BASE + 0x00)) #define TBASE (*(volatile unsigned short *)(SPI_BASE + 0x02)) /* BD结构体定义(对齐到8字节) */ typedef struct { volatile unsigned short status; /* 状态控制位 */ volatile unsigned short length; /* 数据长度(字节) */ volatile unsigned char *buffer; /* 缓冲区指针(32位地址) */ } spi_bd_t; /* 1. 配置Port B引脚功能 */ /* 使能SPIMISO (PB28), SPIMOSI (PB29), SPICLK (PB30) */ /* PBPAR, PBDIR, PBODR 为Port B相关寄存器 */ PBPAR |= (1 << 28) | (1 << 29) | (1 << 30); /* 引脚功能设为SPI */ PBDIR |= (1 << 29) | (1 << 30); /* MOSI和CLK为输出 */ PBDIR &= ~(1 << 28); /* MISO为输入 */ PBODR &= ~((1 << 28) | (1 << 29) | (1 << 30)); /* 开漏输出?通常SPI推挽,这里清0 */ /* 2. 配置片选引脚(例如PB15)为通用输出 */ PBPAR &= ~(1 << 15); /* 引脚功能为GPIO */ PBDIR |= (1 << 15); /* 方向为输出 */ PBDAT &= ~(1 << 15); /* 输出低电平,选中从设备 */ /* 3. 在双端口RAM中定义BD表 */ /* 假设DPRAM起始地址为0x2000 */ spi_bd_t *rx_bd_table = (spi_bd_t *)0x2000; spi_bd_t *tx_bd_table = (spi_bd_t *)0x2008; /* 4. 设置参数RAM中的BD表基址 */ RBASE = (unsigned short)((unsigned long)rx_bd_table & 0xFFFF); /* 取低16位 */ TBASE = (unsigned short)((unsigned long)tx_bd_table & 0xFFFF); /* 5. 执行初始化参数命令 */ CPCR = 0x0051; /* INIT RX AND TX PARAMETERS */ /* 6. 初始化SDMA配置寄存器(通常使用默认值) */ SDCR = 0x0001; /* 7. 设置功能代码寄存器(大端模式)和最大接收缓冲长度 */ RFCR = 0x10; TFCR = 0x10; MRBLR = 16; /* 每个接收缓冲区最大16字节 */ /* 8. 初始化接收BD */ /* 假设接收缓冲区在0x00001000 */ rx_bd_table[0].status = 0x9000; /* E=1, I=1, 其他位0 */ rx_bd_table[0].length = 0; /* 初始为0,CPM接收后会更新 */ rx_bd_table[0].buffer = (unsigned char *)0x00001000; /* 9. 初始化发送BD */ /* 假设发送缓冲区在0x00002000,内容为5个字节数据 */ unsigned char tx_data[5] = {0x01, 0x02, 0x03, 0x04, 0x05}; tx_bd_table[0].status = 0xB800; /* R=1, I=1, L=1 */ tx_bd_table[0].length = 5; tx_bd_table[0].buffer = tx_data; /* 10. 清除SPI事件寄存器并设置中断屏蔽 */ SPIE = 0x00FF; /* 写1清除所有事件位 */ SPIM = 0x0037; /* 使能TXB, RXB, TXE, MME, BSY中断 */ /* 11. 配置CPM中断控制器,使能SPI中断 */ CIMR |= 0x00000020; /* 设置CIMR[SPI]位 */ /* 还需要配置CICR(中断控制器配置寄存器)指定中断优先级和向量等 */ /* 12. 配置SPI模式寄存器 */ /* 假设:主机模式,使能SPI,CPOL=0, CPHA=0 (Mode 0),8位数据,时钟分频最快 */ unsigned short spmode_val = 0; spmode_val |= (0 << 15); /* MSTR = 1 (主机),但注意位定义,可能需查手册确认位 */ spmode_val |= (1 << 14); /* EN = 1 (使能) */ spmode_val |= (0 << 13); /* REV = 0 (MSB first) */ spmode_val |= (7 << 8); /* LEN = 7 (8位数据) */ /* ... 设置时钟分频位,假设为最小值以获得最快时钟 */ SPMODE = spmode_val; /* 例如 0x0370,需根据手册位域计算 */ /* 13. 启动传输 */ SPCOM |= 0x0001; /* 设置STR位 */4.2 从机模式关键差异点
从机模式的初始化流程与主机高度相似,主要区别在于:
- 引脚配置: 必须配置
SPISEL引脚(PB31)为输入,以接收主机的片选信号。 - SPMODE配置:
MSTR位设为0(从机模式)。从机模式下,波特率发生器设置被忽略,时钟由外部主机提供。 - 传输启动: 从机设置
SPCOM[STR]后,只是进入“就绪”状态,加载发送数据到寄存器。真正的传输启动,要等待主机拉低SPISEL并开始提供时钟。 - 缓冲区关闭条件: 从机的
RxBD和TxBD何时关闭,不仅取决于数据长度,还受SPISEL信号控制。手册第30.9节的NOTE部分详细描述了三种情况,这是理解从机行为的关键。
5. 中断服务程序(ISR)设计与故障排查
SPI通信是异步的,依赖中断及时处理数据传输完成事件和错误,否则会导致数据丢失或总线阻塞。
5.1 中断服务程序标准流程
当中断发生时,CPU跳转到SPI的ISR,应遵循以下步骤:
void SPI_ISR(void) { /* 1. 读取SPIE,判断中断源 */ unsigned short spie_status = SPIE; /* 2. 处理发送完成事件 */ if (spie_status & 0x0040) { /* TXB位 */ /* 当前TxBD已发送完毕 */ /* 检查TxBD[L]位,如果为1,则本次传输序列结束 */ /* 将已发送完成的TxBD的R位清零,以便CPU填充新数据 */ /* 如果还有后续数据要发送,设置下一个TxBD的R=1 */ } /* 3. 处理接收完成事件 */ if (spie_status & 0x0080) { /* RXB位 */ /* 当前RxBD已接收满 */ /* 读取RxBD[Data Length]获取实际接收字节数 */ /* 从RxBD[Buffer Pointer]指向的缓冲区读取数据 */ /* 将处理完的RxBD的E位置1,交还给CPM用于下次接收 */ } /* 4. 处理错误事件 */ if (spie_status & 0x0008) { /* TXE */ /* 发送错误,可能从机无应答 */ /* 记录错误,可能需要重发或进行错误恢复 */ } if (spie_status & 0x0004) { /* MME */ /* 多主错误,总线冲突 */ /* 通常需要重新初始化SPI或进行总线仲裁恢复 */ } if (spie_status & 0x0020) { /* BSY */ /* 接收忙错误,无可用RxBD导致数据丢失 */ /* 检查并确保RxBD链中有足够的空缓冲区(E=1) */ } /* 5. 清除SPIE中的事件位(写1清除) */ SPIE = spie_status; /* 将读出的值写回,对应位为1的即被清除 */ /* 6. 清除CPM中断状态寄存器中的SPI中断标志位 */ CISR |= 0x00000020; /* 写1清除CISR[SPI] */ /* 7. 执行中断返回指令(如rfi) */ }5.2 常见问题排查速查表
在实际调试中,SPI不通是常态。下面这个表格整理了典型症状和排查思路:
| 问题现象 | 可能原因 | 排查步骤与解决方法 |
|---|---|---|
| 完全无数据波形 | 1. SPI未使能 (SPMODE[EN]=0)。2. 引脚功能未配置(PBPAR/PBDIR)。 3. 主模式时钟未输出(分频设置极端)。 4. 片选信号未正确拉低。 | 1. 检查SPMODE寄存器值,确认EN=1,MSTR正确。2. 用示波器或逻辑分析仪检查 SPICLK,SPIMOSI,SPISEL引脚是否有输出。3. 检查Port B相关配置寄存器的设置。 4. 主模式下,确认软件或硬件拉低了片选引脚。 |
| 有时钟,但数据线无变化 | 1. 发送缓冲区未就绪 (TxBD[R]=0)。2. SPCOM[STR]未启动。3. 从机模式,主机未拉低 SPISEL。 | 1. 检查当前TxBD的R位是否为1。2. 检查 SPCOM寄存器是否已写入启动命令。3. 从机模式下,确认主机片选信号已到来。 |
| 能发送,不能接收 | 1. 接收缓冲区未准备好 (RxBD[E]=0)。2. MRBLR设置过小或为0。3. 接收中断未使能或未处理。 | 1. 检查RxBD链中是否有E=1的缓冲区。2. 确认 MRBLR设置为正数且足够大。3. 检查 SPIM是否使能了RXB中断,以及ISR是否正确将已满RxBD的E位置1。 |
| 数据错位或字节顺序错误 | 1.SPMODE[REV]设置错误(MSB/LSB)。2. RFCR/TFCR[BO]字节序设置错误。3. 数据长度 LEN与缓冲区长度不匹配。 | 1. 核对从设备数据手册的位顺序,调整REV。2. 根据CPU端序(大端)和内存数据布局,调整 BO位。3. 确认 LEN、TxBD[Data Length]和实际数据缓冲区大小关系(特别是>8位时需2字节对齐)。 |
| 传输中途停止,不触发完成中断 | 1.TxBD[L]位被设置,发送完当前BD后停止。2. 从机模式下,主机提前拉高了 SPISEL。3. 发生了错误(如 TXE)但未处理。 | 1. 检查当前发送的TxBD的L位,若为1则是一次性传输。2. 检查从机 RxBD的L位,可能因SPISEL变高而提前关闭。3. 读取 SPIE寄存器,检查是否有错误标志置位。 |
| 多主冲突或总线锁死 | 1. 多个主机同时驱动总线。 2. 从设备故障,持续拉低SCL(时钟拉伸超时)。 | 1. 检查SPIE[MME]位,实现软件仲裁机制。2. 增加软件看门狗,监测SCL低电平时间,超时后复位SPI控制器。 |
5.3 调试技巧与心得
- 善用仿真器与内存查看: 在初始化后和中断发生时,通过调试器查看
SPI_BASE开始的寄存器区域、参数RAM以及BD表的内存内容。确认每一个配置值都与预期相符,尤其是BD的状态字和指针。 - 逻辑分析仪是必需品: 抓取
SPICLK,SPIMOSI,SPIMISO,SPISEL四根线的波形。对照SPMODE设置的时钟极性和相位,检查数据在时钟的哪个边沿采样和输出,这是排查通信物理层问题最直接的手段。 - 从简入繁: 先调通回环测试(Loopback)。将MPC860的
SPIMOSI和SPIMISO短接,自己发自己收。这样可以排除外设问题,聚焦于MPC860自身的SPI控制器配置是否正确。 - 注意BD链的维护: 在ISR中处理完一个BD后,一定要及时更新它的状态(发送BD清
R,接收BD置E),并将其指针指向下一个BD(如果W位不是1)。确保BD链形成一个完整的环,避免DMA跑飞。 - 时钟分频计算:
SPMODE中的DIV16和PM位用于时钟分频。计算公式为:SPI Baud Rate = BRGCLK / (2 * (PM + 1) * (2 ^ DIV16))。确保计算出的波特率在从设备支持的范围内,且不超过MPC860 SPI控制器的最高速率(通常为系统时钟的几分之一)。
MPC860的SPI控制器是一个功能完备但稍显复杂的模块。它的设计哲学是“配置好,自动跑”,把CPU从繁重的字节搬运中解放出来。掌握它的关键在于理解寄存器配置定义规则,参数RAM与BD表建立通道,以及中断服务程序维护状态这三者之间的闭环。当你成功驱动第一个SPI设备后,这套模式会变得非常清晰。希望这篇结合手册与实战的解析,能帮你少走弯路,顺利拿下MPC860的SPI。