引言
串行外围设备(SPI)接口是由Motorola公司开发的,用来在微控制器MCU和外围设备芯片之间以串行方式实现数据交换的,低成本、易使用的接口。其中,外围设备包括显示驱动器、网络控制器、A/D转换器、内存及微控制器MCU(如DSP、ARM、加密芯片等)等。与标准的串行接口不同,SPI是一个同步协议接口,全双工通信,所有的传输都参照一个共同的时钟,这个同步时钟信号由主端(Master)产生。接收数据的外设使用时钟对串行比特流的接收进行同步化[12]。
SPI通信采用主从方式进行,一般情况下,通过4根线完成通信,分别为两根数据线(主输出从输入MOSI和主输入从输出MISO)、一根时钟线(SCK)和一根片选线(CS)。其中,时钟信号SCK由主端发出,片选信号用于由主设备控制外设芯片使能。
1 SPI通信机制优缺点的分析
SPI通信示意图如图1所示。微控制器MCU采用SPI通信时,片选信号CS(一般情况下为低电平)使能外围设备。主端提供时钟脉冲SCK,数据以串行方式传输,主端数据输出通过MOSI,而数据输入通过MISO,从端数据输出通过MISO,而数据输入通过MOSI,传输一位数据需要一个时钟信号SCK,这样传输一个字节数据至少需要8个时钟信号SCK。

图1 SPI通信示意图
由以上SPI通信过程的描述可知,时钟信号SCK完全由主端设备控制。标准的串行通信一次连续传输至少8位数据,而SPI通信因为时钟信号SCK完全由主端设备控制,所以通信过程中数据是可以一位一位传输的,若没有主端设备的时钟跳变SCK,通信就会暂停,这样主端设备就可以通过控制时钟信号SCK进而控制整个数据的通信,这是SPI通信的一个优点[3]。SPI通信还有个优点,即数据输出和数据输入彼此独立,采用不同的信号线,可以同时完成数据的输入与输出,实现全双工高速通信。此外,相对于ISO7816通信和I2C总线通信等其他接口通信方式,SPI通信实现起来较为简单,就是主端和从端的8位SPI寄存器进行数据的传输。
由于SPI通信有着诸多的优点,所以工业嵌入式系统开发中用到的很多芯片都支持SPI接口。但是,SPI通信的缺点也很明显,通信过程中没有握手,没有应答,没有数据校验。SPI主端设备向从端设备发出数据后,从端设备有没有收到数据?SPI从端设备收到的数据是否与SPI主端设备发出的数据完全一致?如果不一致,SPI主端设备是否应该向从端重传数据?这些都是不得而知的。
通信可靠性对于工业嵌入式系统来说至关重要,直接决定工业嵌入式系统是否有应用价值,一个可靠性欠缺的工业嵌入式系统可能会给企业、社会乃至国家带来莫大的损失。由此可知,SPI通信机制完全有必要改进。
2 改进型SPI通信机制的设计
SPI通信过程有必要加入握手、应答和数据校验等环节,以保证SPI主端和从端通信的可靠性。一般来说,对任何机制的改进都不会是凭空想象,都是建立在原型的基础之上。在SPI通信机制改进时,笔者参考了TCP连接的三次握手机制[4]和ISO7816协议的应答机制[56]。
2.1 通信流程
改进型SPI通信的流程如图2所示,SPI通信由主端控制,主端通过片选引脚使能从端并向从端发出时钟脉冲,从端以应答的方式与主端进行交互,过程如下:
① SPI主端向从端发出命令头,该命令头有5个字节;
② SPI从端向主端发出应答数据,该数据为之前收到的命令头的第二个字节(标志字节);
③ SPI主端向从端发出数据帧,该数据帧由帧头、实际数据、校验值及帧尾表示;
④ SPI从端向主端发出应答数据,该数据为标志字节、数据长度、实际数据、校验值及状态字(两个字节)组成。

图2 SPI通信流程(无数据重传环节)
以上过程中,步骤①和②可以看作是SPI通信主端对从端就绪状态的探测,步骤③和④才是真正的数据交互。步骤①、②中,当SPI从端未就绪时,SPI主端无法收到从端的应答,那么SPI主端就需要进行超时重传,重新传送之前的命令头,重传次数达到上限后就不再重传,否则会浪费系统资源。步骤③、④中,SPI主端会对从端发过来应答数据中的实际数据进行校验并得出一个校验值,将该校验值与从端发过来的应答数据中的校验值进行比对,如果不一致,那么主端有必要进行重传,如图3所示。

图3 SPI通信流程(有数据重传环节)
2.2 通信数据格式
SPI通信过程中,主端发给从端的数据分为两类,即命令头和数据帧:
① 命令头。由ISO7816协议中的APDU(Application Protocol Data Unit)命令格式组成,即CLA、INS、P1、P2、Len[56]。
② 数据帧。一个完整的数据帧由帧头、实际数据、校验值及帧尾组成,如表1所列。
表1 SPI通信数据帧的组成

SPI通信过程中,从端给主端发出的应答有两种:一种是主端探测从端时从端响应给主端的标志,另一种就是实际通信中的应答,这个完整的应答报文由标志字节、数据长度、实际数据、校验值及状态字组成,如表2所列。
表2 SPI通信应答报文的组成

3 改进型SPI通信机制的实现
按照上述的流程,SPI通信过程中主端的代码如下所示,读者可以依此写出SPI从端的代码。
int SpiMaster(unsigned char *rxbuf, unsigned char *in, int len, unsigned char *out){
int i = 0, cnt = 0;
unsigned char cmd[5] = {0xA0, 0xE0, 0x80, 0x00, len};
CMD_RESEND1:
SendCmdHeader(cmd, rxbuf);//发送命令头
RcvINS(rxbuf,cmd[1]);//接收标志字节
if(errorTimeout){//超时全局变量超时重传,超过3次则结束
errorTimeout = 0;
if(cnt<3){
cnt++;
goto CMD_RESEND1;
}
else{
printf("Timeout ERROR!\\n");
return -1;
}
}
cnt = 0;
CMD_RESEND2:
SendData(cmd, rxbuf+1, in, len);//发送实际数据及校验码
RcvINS(cmd,rxbuf,cmd[1]);//接收标志字节
RcvLenL(cmd,rxbuf+1, cmd[3]*0x0100+cmd[4]+1);//接收长度
RcvData(cmd, rxbuf+3);//接收实际数据及校验码
RcvSW(cmd, rxbuf+3+len+1, 0x90);//接收状态字计算接收到数据的CRC校验值
if(get_crc7(rxbuf+3, rxbuf[1]*0x100+rxbuf[2]-1) != rxbuf[rxbuf[1]*0x100+rxbuf[2]+1+1]){//CRC校验值比对,不一致,则命令重传,超过3次,结束
if(cnt<3){
cnt++;
goto CMD_RESEND2;
}
else{
printf("CRC check ERROR!\\n");
return -1;
}
}
for(i=0; i< len; i++){
out[i]=rxbuf[i+3];
}
return 0;
}
结语
本文首先分析SPI通信机制的优缺点,然后阐述了对SPI通信机制的改进,在原有的SPI通信机制中加入了握手、应答、数据校验及超时重传等环节以提高SPI通信的可靠性,并通过长期的系统应用得到了验证。以上改进设计具有很好的扩展性,完全可以应用于其他系统的双机通信,具有较高的实用价值。