S32K144开发笔记6 - 软件SPI驱动MCP2515的CAN总线数据收发实战

S32K144开发笔记6 - 软件SPI驱动MCP2515的CAN总线数据收发实战
1. 硬件连接与引脚配置在开始软件SPI驱动MCP2515之前首先要确保硬件连接正确。S32K144与MCP2515的典型连接方式如下PTB13 — INT接收数据中断引脚PTB14 — CLK时钟PTB15 — MISO主设备输入从设备输出PTB16 — MOSI主设备输出从设备输入PTB17 — CSN片选在实际项目中我遇到过因为引脚配置错误导致通信失败的情况。比如有一次把MISO和MOSI接反了调试了半天才发现问题。所以建议大家在焊接前先仔细检查原理图避免这种低级错误。在S32 Design Studio中配置引脚功能时需要特别注意以下几点所有SPI相关引脚都应配置为GPIO输出模式除了MISO是输入中断引脚PTB13需要配置为输入模式并启用中断功能片选引脚PTB17初始状态应该保持高电平这里有个小技巧在初始化代码中可以先把所有引脚状态设置为高然后再进行SPI操作。这样可以避免MCP2515在上电时收到错误信号。2. 软件SPI实现细节2.1 GPIO模拟SPI时序由于使用的是软件SPI我们需要用GPIO来模拟SPI的时序。SPI的基本操作包括发送和接收一个字节// SPI发送一个字节 void SPI_SendByte(unsigned char dt) { unsigned char i; for(i0;i8;i) { MCP2515_CLK(0); // 时钟拉低 if((dti)0x80) // 判断最高位 MCP2515_MOSI(1); else MCP2515_MOSI(0); MCP2515_CLK(1); // 时钟拉高 } MCP2515_CLK(0); // 最后保持时钟低电平 } // SPI接收一个字节 unsigned char SPI_ReadByte(void) { unsigned char i,rByte0; MCP2515_CLK(0); // 初始时钟低电平 for(i0;i8;i) { MCP2515_CLK(1); // 时钟上升沿采样 rByte1; rByte|MCP2515_MISO; MCP2515_CLK(0); // 时钟下降沿 } return rByte; }在实际调试中我发现时钟信号的稳定性非常重要。如果时钟速度太快比如小于1us的延时可能会导致MCP2515无法正确识别信号。建议在初期调试时可以在每个时钟边沿后加入适当的延时比如1-5us等通信稳定后再考虑优化速度。2.2 MCP2515基本操作函数基于上面的SPI函数我们可以实现MCP2515的读写操作// 向MCP2515写入一个字节 void MCP2515_WriteByte(unsigned char addr, unsigned char dat) { MCP2515_CS(0); // 片选拉低 SPI_SendByte(CAN_WRITE); // 发送写命令 SPI_SendByte(addr); // 发送寄存器地址 SPI_SendByte(dat); // 发送数据 MCP2515_CS(1); // 片选拉高 } // 从MCP2515读取一个字节 unsigned char MCP2515_ReadByte(unsigned char addr) { unsigned char rByte; MCP2515_CS(0); // 片选拉低 SPI_SendByte(CAN_READ); // 发送读命令 SPI_SendByte(addr); // 发送寄存器地址 rByteSPI_ReadByte(); // 读取数据 MCP2515_CS(1); // 片选拉高 return rByte; } // MCP2515复位 void MCP2515_Reset(void) { MCP2515_CS(0); // 片选拉低 SPI_SendByte(CAN_RESET); // 发送复位命令 delay_ms(100); // 适当延时 MCP2515_CS(1); // 片选拉高 }在调试这些基础函数时建议先用逻辑分析仪抓取SPI波形确认时序是否正确。我曾经遇到过因为片选信号释放太快导致写入失败的情况后来在写操作后增加了1ms的延时解决了问题。3. MCP2515初始化与配置3.1 初始化流程MCP2515的初始化主要包括以下几个步骤硬件复位设置CAN总线波特率配置接收滤波器和屏蔽寄存器设置中断使能切换到正常工作模式void MCP2515_Init(void) { unsigned char temp0; MCP2515_Reset(); // 发送复位指令 delay_ms(1); // 短暂延时 // 设置波特率为500Kbps MCP2515_WriteByte(CNF1, CAN_500Kbps); MCP2515_WriteByte(CNF2, 0x80|PHSEG1_3TQ|PRSEG_1TQ); MCP2515_WriteByte(CNF3, PHSEG2_3TQ); // 清空接收缓冲区 MCP2515_WriteByte(RXB0SIDH, 0x00); MCP2515_WriteByte(RXB0SIDL, 0x00); MCP2515_WriteByte(RXB0CTRL, 0x20); // 仅接收标准帧 MCP2515_WriteByte(RXB0DLC, DLC_8); // 接收数据长度为8字节 // 配置滤波器和屏蔽寄存器这里设置为接收所有报文 MCP2515_WriteByte(RXF0SIDH, 0x00); MCP2515_WriteByte(RXF0SIDL, 0x00); MCP2515_WriteByte(RXM0SIDH, 0x00); MCP2515_WriteByte(RXM0SIDL, 0x00); // 清空中断标志使能接收中断 MCP2515_WriteByte(CANINTF, 0x00); MCP2515_WriteByte(CANINTE, 0x01); // 使能RX0中断 // 切换到正常模式 MCP2515_WriteByte(CANCTRL, REQOP_NORMAL|CLKOUT_ENABLED); // 确认是否进入正常模式 tempMCP2515_ReadByte(CANSTAT); if(OPMODE_NORMAL!(temp0xE0)) { MCP2515_WriteByte(CANCTRL, REQOP_NORMAL|CLKOUT_ENABLED); } }3.2 波特率配置技巧MCP2515的波特率配置相对复杂需要设置三个寄存器CNF1、CNF2、CNF3。这里我分享一个实际项目中的经验在车载应用中500Kbps是最常用的波特率但在工业控制中可能会用到125Kbps或250Kbps。波特率计算公式为 CAN波特率 Fosc / (2 × (BRP 1) × TQ总数)其中Fosc是MCP2515的时钟频率通常为8MHz或16MHzBRP是CNF1中的波特率预分频值TQ总数 (同步段 传播段 相位缓冲段1 相位缓冲段2)在我的一个项目中使用8MHz晶振配置500Kbps时CNF10x00CNF20x90CNF30x02。这个配置经过实际测试非常稳定。4. CAN数据收发实现4.1 发送数据CAN数据发送需要考虑以下几点等待发送缓冲区就绪设置报文ID标准帧或扩展帧填充数据触发发送void CANSendData(unsigned int id, unsigned char *CAN_TX_Buf, unsigned char len) { unsigned char j,dly,count0; while(countlen) { dly0; // 等待发送缓冲区就绪 while((MCP2515_ReadByte(TXB0CTRL)0x08) (dly50)) { delay_ms(1); dly; } // 设置标准帧ID unsigned char DH (unsigned char)(id 3); unsigned char DL (unsigned char)((id 0x07) 5); MCP2515_WriteByte(TXB0SIDH, DH); MCP2515_WriteByte(TXB0SIDL, DL); // 填充数据 for(j0;j8;) { MCP2515_WriteByte(TXB0D0j, CAN_TX_Buf[count]); j; if(countlen) break; } // 设置数据长度并触发发送 MCP2515_WriteByte(TXB0DLC, j); MCP2515_CS(0); MCP2515_WriteByte(TXB0CTRL, 0x08); // 请求发送 MCP2515_CS(1); } }在实际应用中我发现发送超时检测非常重要。上面的代码中设置了50ms的超时等待避免因为硬件故障导致程序死等。此外对于关键数据建议实现重发机制比如连续发送3次确保数据可靠传输。4.2 接收数据中断方式为了提高系统效率我们采用中断方式接收CAN数据// 中断服务函数 void MCP2515_RECEIVE_IRQ(void) { uint32_t receive PINS_DRV_GetPortIntFlag(PORTB) (1 13); if(receive ! 0) { if(((receive 13) 0x01) 1) { debug_flag 1; // 设置接收标志 } } PINS_DRV_ClearPortIntFlagCmd(PORTB); // 清除中断标志 } // 主循环中处理接收 unsigned char CANReceiveBuffer(unsigned int *id, unsigned char *CAN_RX_Buf) { unsigned char i0,len0,temp0; temp MCP2515_ReadByte(CANINTF); if(temp 0x01) { // 检查RX0IF标志 *id get_standar_id(); // 获取报文ID lenMCP2515_ReadByte(RXB0DLC); // 获取数据长度 while(ilen) { CAN_RX_Buf[i]MCP2515_ReadByte(RXB0D0i); // 读取数据 i; } } MCP2515_WriteByte(CANINTF,0); // 清除中断标志 return len; }在调试中断接收时有几点需要注意确保中断引脚配置正确下降沿或低电平触发中断服务函数要尽可能简短避免影响其他中断在主循环中处理接收数据时要考虑数据缓冲区的大小限制记得清除中断标志否则会一直触发中断我曾经遇到过一个棘手的问题中断频繁触发但实际没有新数据。后来发现是忘记清除中断标志了。所以现在每次写中断服务函数我都会特别注意标志位的清除。5. 调试技巧与常见问题5.1 调试工具推荐在开发CAN通信时以下工具非常有用逻辑分析仪用于观察SPI信号时序CAN分析仪如PCAN、周立功CAN卡用于监控CAN总线数据示波器检查信号质量和干扰万用表检查电源和接地5.2 常见问题及解决方案SPI通信失败检查硬件连接是否正确确认时钟极性CPOL和相位CPHA设置降低SPI时钟速度测试用逻辑分析仪抓取波形分析CAN报文发送不成功检查MCP2515是否进入正常模式确认波特率设置与总线其他节点一致检查终端电阻通常需要120Ω接收不到数据确认滤波器和屏蔽寄存器配置检查中断引脚连接和配置用CAN分析仪确认总线上确实有数据通信不稳定检查电源是否干净建议增加滤波电容检查PCB布局避免高频干扰适当降低通信速率测试在最近的一个车载项目中我们遇到了CAN通信偶尔丢帧的问题。经过排查发现是电源纹波太大导致的。后来在MCP2515的电源引脚增加了10μF和0.1μF的去耦电容问题得到了解决。这个经验告诉我们硬件设计同样重要不能只关注软件。