资讯动态

PIC单片机SPI外设详解:从MSSP寄存器配置到W25Q64实战

发布时间:2026/9/16 20:33:40 来源:尧图企业网站定制
简介SPI是一种应用广泛的同步串行通信协议常用于微控制器与外围设备之间的高速数据交换这是一份面向PIC微控制器的SPI通信示例程序包用C语言编写主要解决PIC主机与各类SPI从设备之间的数据通信问题。对于需要驱动Flash存储器、传感器、显示屏等外设或正在学习PIC内部MSSP模块SPI模式的开发者可直接参考使用。资源包仅包含1个C源文件整体压缩后约947B代码体量虽小却围绕SCK、MOSI、MISO、SS四线机制覆盖了接口初始化、参数配置与数据收发核心流程并给出了清晰的函数划分。程序实现了SPI接口初始化、四种工作模式模式03切换、时钟频率设定、从设备片选控制以及单字节读写等关键功能开发者对照实际硬件调整引脚映射与时序参数即可移植到Proteus仿真或实物开发板中用于连接Flash、传感器、显示屏等常见SPI外设也可辅助排查SCK/MOSI/MISO/SS信号线上的时序问题节省从零编写底层驱动的时间。该资源目前已有127人学习下载适合正在研究PIC单片机SPI协议、调试通信时序或需要一套最小可运行代码的开发者参考。1. 从SPI.rar_PIC_PIC spi_spi pic说起PIC 与 SPI 的真正距离SPI.rar_PIC_PIC spi_spi pic这类压缩包名在各大单片机论坛和网盘里并不少见。文件名把芯片型号、接口名、工程关键词全部堆在一起解压后往往是一堆.c、.h文件和 Proteus 仿真工程。对刚接触 PIC 的开发者来说难题不在于 SPI 是什么而在于PIC 的数据手册怎么看、MSSP 模块怎么配、为什么别人贴的代码在自己板子上读回来全是0xFF。STM32 有 CubeMX 图形化配置PIC 虽然有 MCC 代码生成器但你拿到的旧工程或同事发来的源码却未必带着完整配置最后还是得回到寄存器级别。这篇文章就顺着 PIC 的 SPI 外设展开模块结构、寄存器映射、XC8 驱动代码、W25Q64 读 ID 实战最后收在软件模拟 SPI 和片选管理的技巧上。适合手里有一块 PIC16/18 系列开发板、想驱动 Flash、OLED、传感器之类 SPI 从设备的工程师。2. PIC 的 SPI 外设从 MSSP 模块到寄存器映射2.1 SPI 物理层四线、主从和极相位的概念先对齐SPI 是同步串行接口PIC 的 MSSPMaster Synchronous Serial Port模块承担了它的大部分硬件工作。四根线分别是 SCK时钟、SDO主出从入、SDI主入从出、SS片选从模式时使用。主设备产生时钟数据在时钟边沿移位从设备靠片选信号决定是否参与总线。相比 I2CSPI 没有应答机制也没有地址帧主设备想和哪个从设备通信就把哪个从设备的片选拉低。这个特性决定了 SPI 的速度可以做得比较高但也意味着片选管理、时钟极性和相位必须由程序员自己控制好。时钟极性和相位对应 SPI 的四种模式PIC 的数据手册里用 CKP时钟极性和 CKE时钟边沿两个位来描述外部从设备的数据手册则用 CPOL/CPHA 描述。绝大多数 SPI Flash 和 OLED 驱动芯片默认工作在 Mode 0即 CPOL0、CPHA0时钟空闲为低数据在上升沿采样。在 PIC 的 MSSP 上Mode 0 对应 CKP0、CKE1。这里有一个常见误区有人把 CKP 和 CKE 当成独立的开关随意尝试却不理解它们在时间轴上对应哪个边沿结果波形看起来有数据但读回来全错位。Mode 0: 空闲低电平上升沿采样下降沿切换数据 Mode 3: 空闲高电平上升沿采样下降沿切换数据注意极性相反与 I2C 相比SPI 没有标准定义的总线速率分级SCK 上限完全由主设备分频和从设备最高时钟参数共同决定。PIC16F877A 的 MSSP 在 20 MHz 主频下最高可以跑出 5 MHz 的 SCKFosc/4但实际使用中要考虑从设备的最高时钟、PCB 走线长度和 MISO 建立时间。建议第一版驱动先跑低频如 Fosc/64逻辑分析仪确认波形没问题后再往上加速。2.2 把 MSSP 拆开SSPCON1、SSPSTAT、SSPBUF 的职责划分PIC16F877A 这类经典芯片的 MSSP 模块核心寄存器只有三个SSPCON1 负责模式选择和使能SSPSTAT 负责状态位和时钟边沿配置SSPBUF 是数据缓冲。注意这里没有独立的波特率寄存器SPI 主模式的时钟分频直接由 SSPCON1 里的 SSPM 位决定SSPADD 是 I2C 模式才用的不少初学 PIC 的人会在 SSPADD 上浪费时间。SSPCON1 的各 bit 分布如下Bit 7 WCOL写冲突标志发送过程中又写 SSPBUF 时置 1需要软件清除Bit 6 SSPOV接收溢出标志SSPBUF 未读又收到新数据时置 1Bit 5 SSPENMSSP 模块使能置 1 后 SCK/SDO/SDI 才被模块接管Bit 4 CKP时钟极性0 为空闲低电平1 为空闲高电平Bit 3:0 SSPM模式选择SPI 主模式时选0100Fosc/64或0000Fosc/4SSPSTAT 的关键位是 CKE时钟边沿选择、SMP采样点选择和 BF缓冲满标志。CKE 决定了数据在时钟的哪个边沿变化SMP 决定在哪个位置采样输入数据。这两个位配合 CKP 一起才能拼出完整的 Mode 0 到 Mode 3。SSPBUF 是一个双重缓冲寄存器。写入 SSPBUF 时数据被送入内部的移位寄存器同时硬件开始产生 SCK接收完成后移位寄存器里的数据被搬运到 SSPBUF。所以说一次SSPBUF data;加一次while(!SSPSTATbits.BF);加一次result SSPBUF;就完成了一字节的全双工交换。这里要特别注意读 SSPBUF 会清除 BF 标志所以轮询和读取的顺序不能颠倒。// XC8 中常见的寄存器写法PIC16F877A SSPCON1 0b00100010; // SPI 主模式, Fosc/64, CKP0, SSPEN1 SSPSTAT 0b01000000; // CKE1, SMP0对应 Mode 0这段配置的含义是SSPM 位为0010SCK 频率为主时钟的 1/64CKP 为 0SCK 空闲为低CKE 为 1数据在 SCK 上升沿采样。这是驱动 W25Q64、OLED、nRF24L01 这类从设备时最常用的起点配置。2.3 时钟频率怎么算Fosc/4 到 Fosc/64 的选择逻辑PIC16 系列 SPI 主模式的时钟来源是系统时钟分频各档位由 SSPM 的低四位决定。常见配置如下表SSPM 值分频4 MHz 主频下 SCK20 MHz 主频下 SCK0000Fosc/41 MHz5 MHz0001Fosc/16250 kHz1.25 MHz0010Fosc/6462.5 kHz312.5 kHz0011定时器2输出/2取决于 TMR2取决于 TMR2选择分频档位时先看从设备数据手册里Maximum SCK Frequency一项W25Q64 在标准 SPI 模式最多支持 50 MHz 时钟远不是瓶颈真正限制的是引脚驱动能力、飞线的信号质量、以及你的示波器/逻辑分析仪能不能看清波形。开发期用 Fosc/64 最稳妥跑通逻辑后再逐步提升比如把 SSPM 改成0001或0000。有一种特殊情况值得提某些从设备要求 SCK 低于某个值比如早期的射频模块或者长线传输场景这种情况下 Fosc/64 仍然太快那就只能用软件模拟 SPI 或降低主频。片上硬件 SPI 的效率优势在这种场景会被极限拉低反而 bit-bang 更灵活这也是后面第 5 章单独讲软模拟的原因。3. 用 XC8 写一个可复用的 PIC SPI 驱动3.1 引脚规划公共引脚、重映射和上下拉的预处理PIC16F877A 的 MSSP 引脚是固定的RC3 对应 SCKRC4 对应 SDIRC5 对应 SDO。这三个脚的 TRIS 位必须设置正确方向反了数据要么出不去要么读不到。RC3SCK设为输出RC5SDO设为输出RC4SDI设为输入。片选脚建议自己选一个普通 GPIO比如 RC0不要用 MSSP 的 SS 脚。#define SCK RC3 #define SDO RC5 #define SDI RC4 #define CS RC0 TRISC3 0; // SCK 输出 TRISC5 0; // SDO 输出 TRISC4 1; // SDI 输入 TRISC0 0; // 片选输出 CS 1; // 默认不选中从设备在 Proteus 仿真里也要注意RC4 同时是 I2C 的 SDA 引脚仿真模型中如果 I2C 外设处在激活状态可能影响 SDI 的电平读取。这在真实芯片上不会发生但仿真时却是常见坑点。SDO 引脚在空闲状态下如果从设备没有驱动 MISO读到的电平不确定处理办法是在 PCB 上给 SDI 加一个 10k 上拉到 VCC这样悬空时读到的是高电平至少不会出现随机跳变。对 W25Q64 这种从设备来说未选中时 MISO 是高阻态上拉很有必要。3.2 最小驱动代码初始化、单字节收发、片选控制下面这段代码可以在 XC8 编译器下直接使用接口兼容 8 位 PIC 的 MSSP 模块。spi_init()放在主程序开始时调用spi_xfer()每次完成一字节的全双工交换读指令时传0x00或其他任意占位字节。void spi_init(void) { TRISC3 0; TRISC5 0; TRISC4 1; TRISC0 0; CS 1; SSPCON1 0b00100010; // SPI 主模式, Fosc/64, CKP0, SSPEN1 SSPSTAT 0b01000000; // CKE1, SMP0 (Mode 0) } unsigned char spi_xfer(unsigned char data) { SSPBUF data; // 写入要发送的数据触发 SCK 移位 while (!SSPSTATbits.BF); // 等待接收缓冲满 return SSPBUF; // 读取接收到的数据同时清 BF 标志 }这段代码的逻辑是写SSPBUF之后硬件自动把数据一位一位移出 SDO同时从 SDI 移入数据。BF 位置 1 代表接收完成此时SSPBUF里就是从设备回复的字节。读操作本身会清除 BF 位所以下一次循环不会读到旧数据。注意每次spi_xfer()的返回值都必须读走哪怕你不关心它否则溢出标志 SSPOV 会置位后续通信数据就乱了。实际操作中片选拉低应该在发送第一个字节之前拉高要在读完全部字节之后中间不能松开。常见的错误是在循环里每次调用spi_xfer()都重新拉低拉高 CS这样从设备会被反复复位读出来的数据自然不对。// 读 W25Q64 JEDEC ID 的调用方式 CS 0; spi_xfer(0x9F); id_byte1 spi_xfer(0x00); id_byte2 spi_xfer(0x00); id_byte3 spi_xfer(0x00); CS 1;3.3 波特率与模式选择对照表默认 Mode 0 怎么读MSSP 的边沿配置可以通过 CKP 和 CKE 自由组合但并不是所有组合都对应外部通用的模式编号。我这里给出一张实用对照表方便你直接查询和验证外部模式CPOLCPHAPIC 建议配置CKP, CKE典型从设备Mode 000CKP0, CKE1W25Q64, OLED, nRF24L01Mode 101CKP0, CKE0部分 ADC 芯片Mode 210CKP1, CKE1部分传感器Mode 311CKP1, CKE0部分 Flash 的 QPI 模式周边如果从设备数据手册只写了 CPOL/CPHA没有直接给 Mode 号就按这个表换算成 PIC 的 CKP/CKE。要注意的是老版本 Microchip 数据手册对 CKE 的描述出现过表述不清的情况不同系列芯片PIC16 和 PIC18在极相位的对应关系上是一致的但如果你用的是 PIC24/dsPIC寄存器名和位定义都变了必须重新查手册。调试的时候如果波形看起来对但数据错乱不要只盯着 SCK 频率把 CKP 和 CKE 的四种组合各试一遍配合逻辑分析仪看哪组能解出正确的字节。这个排查法看似笨但效率比反复读手册高得多因为有时候从设备的时序图和文字描述并不完全一致实测才是最终标准。4. 实战SPI 读写 W25Q64验证驱动是否成立4.1 W25Q64 的命令协议如何和 PIC 驱动对接W25Q64 是华邦 8 MB 串行 FlashSPI 接口时序简单、价格便宜是验证 SPI 驱动的理想从设备。它识别自己是靠 JEDEC ID发送0x9F命令后从设备会回复 3 个字节厂商 ID、容量 ID、版本 ID。W25Q64 的典型回复是0xEF 0x40 0x17其中0x40表示容量为 8 MB0x17是设备版本。GD25Q64 等国产兼容芯片会回复0xC8开头说明总线通信是通的只是厂商不同。除了 JEDEC ID最常用的命令是读状态寄存器0x05它返回 1 个字节Bit 0 是 BUSY 位。写入操作前要等 BUSY 清零才能进行这是 Flash 编程的基本握手。写使能命令0x06必须在每次页编程或擦除前发送否则命令会被忽略这个细节在处理写入时会反复碰到。4.2 完整例程读 JEDEC ID 与读状态寄存器下面的函数把读 JEDEC ID 封装好返回一个uint32_t低 24 位依次是厂商、容量、版本 ID。因为 PIC16 的unsigned long是 32 位可以直接放。unsigned long w25q64_read_jedec_id(void) { unsigned long id 0; CS 0; spi_xfer(0x9F); // 发送 JEDEC ID 读命令 id (unsigned long)spi_xfer(0x00) 16; id | (unsigned long)spi_xfer(0x00) 8; id | (unsigned long)spi_xfer(0x00); CS 1; return id; } unsigned char w25q64_read_status(void) { unsigned char status; CS 0; spi_xfer(0x05); // 读状态寄存器命令 status spi_xfer(0x00); // 占位发送接收状态字节 CS 1; return status; }函数里把0x00作为占位字节发送是因为 SPI 是全双工协议每发送一个字节就会收到一个字节。即使你只关心从设备发来的数据也必须输出时钟硬件才会移位。CS 0在命令发送前执行CS 1在整个事务完成后才拉高中途不能释放否则 W25Q64 会把当前指令当作完成而终止。// 主程序验证 unsigned long jedec; jedec w25q64_read_jedec_id(); if ((jedec 0xFFFFFF) 0xEF4017) { // W25Q64 识别成功 } else { // 检查硬件连接或极相配置 }4.3 出现全 0xFF 时先查这三个地方SPI 调试最典型的故障现象就是读回来的数据全是0xFF这说明总线上一半以上的环节出了问题。按优先级排查第一是片选用万用表量 CS 引脚电压确认在事务期间确实被拉低。很多人会把片选误接到 MISO 的邻近引脚或者飞线接触不良导致从设备从未被选中此时 MISO 一直处于高阻态被上拉拉高读回0xFF就顺理成章了。第二是时钟极性和相位不匹配。W25Q64 支持 Mode 0 和 Mode 3但如果你把 CKP 配成了 1而数据手册里默认是 Mode 0从设备会对边沿判断错位解码出的数据就可能是0xFF。这种问题看波形最容易发现SCK 空闲电平应该为低逻辑分析仪上一眼就能看出来。第三是 SDI 引脚方向。TRISC4 如果是输出模式PIC 内部的输出驱动器会强制驱动引脚外部从设备根本拉不动它数据就进不来。这个问题常见于复用别人工程时顺带把引脚配置一起拷过来偏偏没留意方向位的赋值。把这三处逐一核对百分之八十的0xFF问题都能解决剩下的才需要怀疑芯片损坏或焊接异常。5. 进阶软件模拟 SPI、多从机片选与调试技巧5.1 什么时候需要软件模拟 SPIbit-bang 最小实现硬件 SPI 不是万能药碰到这两种情况就得用软件模拟一是 MSSP 被 I2C 占用PIC16 的 MSSP 在同一时刻只能选一种模式二是需要把 SCK 拉到硬件外设不支持的低频。用 GPIO 逐位翻转就是常见的 bit-bang 方式。PIC 引脚翻转速度不快但配合内部 4/8 MHz 时钟驱动 W25Q64 这类设备绰绰有余。void spi_soft_init(void) { SCK 0; // 空闲低电平对应 Mode 0 SDO 0; CS 1; } unsigned char spi_soft_xfer(unsigned char tx) { unsigned char i, rx 0; for (i 0; i 8; i) { SDO (tx 0x80) ? 1 : 0; tx 1; SCK 1; // 上升沿从设备采样 rx 1; if (SDI) rx | 0x01; // 读取 MISO SCK 0; // 拉低准备下一次上升沿 } return rx; }代码中每一个 SCK 高电平都模拟了硬件 SPI 的采样边沿时序逻辑上和 MSSP 的 Mode 0 完全一致。注意这个写法是先置数据再抬时钟顺序不能反。软件模拟 SPI 的波特率由每条语句的执行时间决定无法精确用公式计算但这在低频场景下反而成了优势不用配分频直接把延时循环加在SCK 1和SCK 0之间就行适配长线或慢速从设备非常灵活。5.2 多从机与硬件片选/软件片选的取舍PIC 的 MSSP 主模式下SS 引脚默认不作为片选输出使用。很多开发者第一次接触 PIC 时会下意识把 SS 脚接从设备的片选结果发现控制不了这就是没搞清硬件片选和软件片选的区别。片选方式引脚占用切换速度适用场景硬件片选仅从模式1 引脚由 MSSP 控制由硬件自动控制单主单从且从设备 SS 必须接 MCU软件片选GPIO 手动拉每从机 1 个 GPIO靠代码控制速度偏慢多从机或从机片选极性特殊PIC 主模式本身没有硬件片选自动拉低机制所以最常见的做法就是软件片选每个从设备一个 GPIO。管理多个 Flash、OLED 和传感器时写一个spi_select(uint8_t cs_pin)和spi_deselect()会比逐个拉电平更可靠。另外要注意如果某个从设备的片选是高电平有效控制逻辑要反过来这类芯片在 SPI 总线里不太常见但遇到时直接套用低有效逻辑就会一直处于未选中状态。5.3 验证逻辑分析仪抓时序与频率调整硬件 SPI 驱动写完不要急着接真实从设备先用一个 8 通道逻辑分析仪抓一下 CS、SCK、SDO、SDI 四条线的时序。在屏幕上确认三个特征CS 拉低期间SCK 有完整 8 个脉冲SCK 空闲电平为低Mode 0 时SDO 的数据在 SCK 上升沿之前已稳定。任意一条不满足都能定位到对应的寄存器配置或引脚设置。逻辑分析仪的 SPI 解码器通常会要求你指定 CPOL/CPHA 和位序这里填 Mode 0 和 MSB First。如果解码结果和发送的数据一致硬件 SPI 通路基本就没问题了。之后再接上 W25Q64 验证 JEDEC ID。这个过程比直接闷头调从设备快得多因为你能区分问题是出在 PIC 侧还是从设备侧。最后一个实用技巧当怀疑 SCK 太快导致 MISO 采样点不对时不一定要降主频可以只把 SSPCON1 的分频从 Fosc/4 改成 Fosc/16 或 Fosc/64再比较逻辑分析仪的解码结果。多数情况下问题会从波形乱跳变成数据完全正确这比逐条改 CKE/CKP 组合更快。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价