XC8 MCC环境下PIC驱动MCP23S17的SPI新手问题求解
问题解答
- 实现思路整体方向正确,但存在时序逻辑错误,不符合MCP23S17的SPI通信要求:
- 每个独立的读/写命令都需要完整的「CS拉低→传输命令/数据→CS拉高」流程,你的代码将整个循环内的所有读写操作都放在同一个CS低电平区间内,MCP23S17无法识别连续的多命令,导致写操作完全不生效,后续读操作也得不到预期结果。
- 读操作的字节接收逻辑与MCP23S17的输出时序不匹配,导致首次读返回无效值。
- 需要dummy写入才生效的原因有两个:
- SPI是全双工协议,每发送1字节的同时会自动接收1字节,MCP23S17需要在收到「操作码+寄存器地址」两个字节之后,才会准备好要返回的寄存器数据,因此你需要再发送1个dummy字节,才能交换到有效数据。
- 首次SPI传输时,SPI外设的接收缓冲区可能残留默认值0x00,加上你没有按要求切换CS电平,导致第一次循环的读命令没有被正确解析,第二次循环时缓冲区的旧值已经被清空,加上前一次循环末尾CS拉高复位了MCP23S17的命令解析状态,才能读到正确值。
- 两个函数的核心区别:
SPI1_ExchangeByte()是全双工操作:发送1字节的同时读取SPI总线上返回的1字节,返回值就是接收到的字节,会主动读取并清空接收缓冲区的内容,适合需要读取从机返回数据的场景。SPI1_WriteByte()是只发不收操作:仅将要发送的字节写入发送缓冲区,等待发送完成后直接返回,不会读取接收缓冲区的内容。SPI硬件会在发送的同时自动接收1字节存入缓冲区,如果你连续调用SPI1_WriteByte()不读缓冲区,旧的接收数据会积压,后续调用SPI1_ExchangeByte()时会先读到这些积压的旧值,导致数据错乱。
- MCC生成函数的文档可以在两个地方找到:
- 驱动对应的
.h头文件中,每个函数的声明上方都有详细的功能、入参、返回值说明,你可以直接查看spi1.h获取函数定义。 - 项目生成的
mcc_generated_files目录下,会附带对应驱动的使用说明文档,格式通常为HTML或TXT,包含驱动的配置说明、使用示例、API参考。
修正后的代码参考
#include "mcc_generated_files/mcc.h" uint8_t receiveData; uint8_t OpCodeW = 0x40; // 硬件地址为000时的写操作码 uint8_t OpCodeR = 0x41; // 硬件地址为000时的读操作码 // 封装MCP23S17寄存器读函数 uint8_t MCP23S17_ReadReg(uint8_t regAddr) { uint8_t ret; CS1_GPIO_SetLow(); SPI1_ExchangeByte(OpCodeR); // 发送读操作码 SPI1_ExchangeByte(regAddr); // 发送寄存器地址,此时MCP23S17准备数据 ret = SPI1_ExchangeByte(0x00); // 发dummy字节,交换得到寄存器值 CS1_GPIO_SetHigh(); return ret; } // 封装MCP23S17寄存器写函数 void MCP23S17_WriteReg(uint8_t regAddr, uint8_t data) { CS1_GPIO_SetLow(); SPI1_ExchangeByte(OpCodeW); // 发送写操作码 SPI1_ExchangeByte(regAddr); // 发送寄存器地址 SPI1_ExchangeByte(data); // 发送待写入数据 CS1_GPIO_SetHigh(); __delay_us(10); // 等待寄存器写入完成 } void main(void) { SYSTEM_Initialize(); INTERRUPT_GlobalInterruptEnable(); INTERRUPT_PeripheralInterruptEnable(); Reset1_GPIO_SetLow(); __delay_ms(200); Reset1_GPIO_SetHigh(); __delay_ms(200); SPI1_Open(SPI1_DEFAULT); // SPI只需要打开一次,不需要循环开关 printf("Initalised \r\n"); while (1) { // 读初始IODIRA值 receiveData = MCP23S17_ReadReg(0x00); printf("Read IODIRA: 0x%02x \r\n", receiveData); // 写入IODIRA寄存器 MCP23S17_WriteReg(0x00, 0xF0); // 回读验证 receiveData = MCP23S17_ReadReg(0x00); printf("Wrote to IODIRA and read back: 0x%02x \r\n", receiveData); printf(" ----- \r\n\n"); __delay_ms(5000); } }
内容的提问来源于stack exchange,提问作者Dan Twining
相关产品推荐
相关产品推荐

