1. SPI总线概述
图2示出SPI总线工作的四种方式,其中使用的最为广泛的是SPI0和SPI3方式(实线表示):
图2 SPI总线四种工作方式
SPI总线接口及时序
SPI 模块为了和外设进行数据交换,根据外设工作要求,其输出串行同步时钟极性和相位可以进行配置,时钟极性(CPOL)对传输协议没有重大的影响。如果 CPOL="0",串行同步时钟的空闲状态为低电平;如果CPOL=1,串行同步时钟的空闲状态为高电平。时钟相位(CPHA)能够配置用于选择两种不同的传输协议之一进行数据传输。如果CPHA=0,在串行同步时钟的第一个跳变沿(上升或下降)数据被采样;如果CPHA=1,在串行同步时钟的第二个跳变沿(上升或下降)数据被采样。SPI主模块和与之通信的外设音时钟相位和极性应该一致。SPI总线接口时序如图所示。
SPI,是英语Serial Peripheral interface的缩写,顾名思义就是串行外围设备接口。SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,现在越来越多的芯片集成了这种通信协议。
其工作模式有两种:主模式和从模式,无论那种模式,都支持3Mbit/s的速率,并且还具有传输完成标志和写冲突保护标志。
跟SPI密切相关的两个概念是时钟极性和时钟相位。
下面以矩力的7022B芯片为例子,分析SPI工作模式的设置。
在7022B的数据手册中,指出:在SCK的上升沿放数据,在下降沿取数据。在SCK的下降沿将DIN的数据采样到7022B中,在SCK的上升沿将7022B的数据放置在DOUT上面输出。
下面是模拟SPI总线对7022B进行读取的例子,这个例子生动表示了何时放数据,何时采样数据。
unsigned long Read_reg3(unsigned char cmd)
{
int i;
unsigned long data;
set_bit(SPI_PORT,SPI_SS);
clr_bit(SPI_PORT,SPI_SCK);
clr_bit(SPI_PORT,SPI_SS);
for(i=0;i<8;i++)
{
set_bit(SPI_PORT,SPI_SCK);
if(cmd&0x80)
{
set_bit(SPI_PORT,SPI_MOSI);
}
else
{
clr_bit(SPI_PORT,SPI_MOSI);
}
cmd="cmd"<<1;
nop;
nop;
clr_bit(SPI_PORT,SPI_SCK);nop;
}
clr_bit(SPI_PORT,SPI_SCK);
_delay_us(3);
data="0";
for(i=0;i<24;i++)
{
set_bit(SPI_PORT,SPI_SCK);
nop;
nop;
nop;
if(PINB&(1
{
data+=1;
}else
{
data+=0;
}
data="data"<<1;
nop;
clr_bit(SPI_PORT,SPI_SCK);
}
set_bit(SPI_PORT,SPI_SS);
return data;
}
如果采用硬件SPI,则需要对单片机(ATMEGA16)SPI寄存器进行设置,本例子中,需要设置
SPCR=0x57;//MSB在先01010011
时钟极性为0,因为空闲时CLK电平为低。
时钟相位设置为1,因为是下降沿采样数据,上升沿放数据。
我对SPI的理解错在采样的概念上。比如,下降沿采样,当主机接收时,我觉得应该是先有下降沿,再有采样。其实,正确的理解是:采样是对主机从机都一致的概念,采样之前必须把数据准备好。当主机接收数据时,主机也是下降沿采样,但是在下降沿发生之前,必须准备好数据,换句话说,从机在上升沿发生后,就要把数据放出来,为下降沿采样做好准备。
3. SPI模块的时序
SPI是在同步信号SPSCK的控制下完成数据的传输,根据控制位CPHA和CPOL的不同,数据线和时钟线产生4种可能的时序。图10-2、图10-3、图10-4和图10-5给出了数据线与SPSCK之间的关系。从时序图上可以看出,CPOL为1或0时,时钟信号SPSCK的出现正好是反相关系,CPHA用来选择两种不同的定时协议中的一种,以对允许SPI系统与各种同步外设通信。主机和从机必须使用同样的时序模式,才能正常通信。
从图10-2可以看出,当CPHA=0,CPOL=0时,MISO引脚上的数据在第一个SPSCK沿跳变之前已经上线了,而为了保证正确传输,MOSI引脚的MSB位必须与SPSCK的第一个边沿同步,在SPI传输过程中,首先将数据上线,然后在同步时钟信号的上升沿时,SPI的接收方捕捉位信号,在时钟信号的一个周期结束时(下降沿),下一位数据信号上线,再重复上述过程,直到一个字节的8位信号传输结束。
图10-3是当CPHA=0,CPOL=1时的数据线和时钟线的时序,与图10-2唯一不同之处只是在同步时钟信号的下降沿时捕捉位信号,上升沿时下一位数据上线。
从图10-4可以看出,在CPHA=1,CPOL=0时,MISO引脚和MOSI引脚上的数据的MSB位必须与SPSCK的第一个边沿同步,在SPI传输过程中,在同步时钟信号周期开始时(上升沿)数据上线,然后在同步时钟信号的下降沿时,SPI的接收方捕捉位信号,在时钟信号的一个周期结束时(上升沿),下一位数据信号上线,再重复上述过程,直到一个字节的8位信号传输结束。
图10-5是当CPHA=1,CPOL=1时的数据线和时钟线的时序,与图10-4唯一不同之处只是在同步时钟信号的上升沿时捕捉位信号,下降沿时下一位数据上线。
单片机(MCU)系统为了与SPI标准外围接口器件进行通信,必须使用SPI( Serial Peripheral Interface,串行外设接口)总线。SPI总线系统是Motorola提出的一种同步串行外设接口,有信号线少、协议简单、传输速度快的特点,因此有不少外围器件都采用SPI总线,如Flash RAM、A/ D转换器、LED显示器、MCU以及计算机网络等。MCU中的SPI接口通过配置可与各个厂家生产的多种标准外围器件直接连接。
对于那些没有SPI接口功能的MCU来说,SPI接口的功能靠软件控制MCU的I/O口的方法来模拟。不过,用软件来模拟SPI接口的功能,工作速度非常慢,并且需要主从器件的软件之间配合得非常好。如果在单片机芯片内部用硬件电路来完成SPI接口功能,在硬件增加不多的情况下,能够极大地提高传输速度(最高频率可达主器件的频率的1/4),减轻软件的负担,使用极为方便。
SPI接口工作的时候,没有应答信号,并且数据在发送的时候无需校验位,所以,要求主从器件的软件必须完全符合SPI的时序要求,否则数据传输很容易出现错误。本文通过MCU中SPI接口模块的设计,分析数据传输的各种出错情况,并针对各种情况,增强SPI接口的错误处理能力。
1) SPR设定错误
在从器件时钟频率小于主器件时钟频率时,如果SCK的速率设得太快,将导致接收到的数据不正确(SPI接口本身难以判断收到的数据是否正确,要在软件中处理)。
整个系统的速度受三个因素影响:主器件时钟CLK主、从器件时钟CLK从和同步串行时钟SCK,其中SCK是对CLK主的分频,CLK从和CLK主是异步的。要使SCK无差错无遗漏地被从器件所检测到,从器件的时钟CLK从必须要足够快。下面以SCK设置为CLK主的4分频的波形为例,分析同步串行时钟、主时钟和从时钟之间的关系。
如图1所示,当T从<Tsck/2,即T从<2T主时,无论主时钟和从时钟之间的相位关系如何,在从器件CLK从的上升沿必然能够检测到SCK的低电平,即SCK=0的范围内至少包含一个CLK从的上升沿。
图2中,当T从≥TSCK/2=2T主时,在clk_s的两个上升沿都检测不到SCK的低电平,这样从器件就会漏掉一个SCK。在某些相位条件下,即使CLK从侥幸能检测到SCK的低电平,也不能保证可以继续检测到下一个SCK。只要遗漏了一个SCK,就相当于串行数据漏掉了一个位,后面继续接收/发送的数据就都是错误的了。
根据以上的分析,SPR和主从时钟比的关系如表1所列。
表1 SPR的设置和主从时钟周期比值之间的关系
在发送数据之前按照表1对SPR进行设置,SPR设定错误可以完全避免。
2) 模式错误(MODF)
模式错误表示的是主从模式选择的设置和引脚SS的连接不一致。
器件工作在主模式的时候(MSTR=1),它的片选信号SS引脚必须接高电平。在发送数据的过程中,如果它的SS从高电平跳至低电平,在SS的下降沿,SPI模块将检测到模式错误,对MODF位置1,强制器件从主模式转入从模式(即令MSTR=0),清空内部计数器counter,并结束正在进行的数据传输,如图3(a)所示。
对从模式(MSTR=0),在没有数据传送的时候,SS高电平表示从器件未被选中,从器件不工作,MISO输出高阻;在数据传输过程中,片选信号SS必须接低电平,且SS不允许跳变。如果SS从低电平跳到高电平,在SS的上跳沿,SPI模块也将检测到模式错误,清空内部计数器counter,并结束正在进行的数据传输。直到SS恢复为低电平,重新使SPEN=1时,才重新开始工作,如图3(b)所示。
3 )溢出错误(OVR)
溢出错误表示连续传输多个数据时,后一个数据覆盖了前一个数据而产生的错误。
状态标志SPIF表示的是数据传输正在进行中,它对数据的传输有较大的影响。主器件的SPIF有效由数据寄存器的空标志SPTE=0产生,而从器件的SPIF有效则只能由收到的第一个SCK的跳变产生,且又由于从器件的SPIF和主器件发出的SCK是异步的,因此从器件的传输标志SPIF从相对于主器件的传输标志SPIF主有一定的滞后。如图4所示,在主器件连续发送两个数据的时候将有可能导致从器件的传输标志和主器件下一个数据的传输标志相重叠(图4中虚线和阴影部分),第一个收到的数据必然被覆盖,第二个数据的收/发也必然出错,产生溢出错误。
通过对从器件的波形分析发现,counter=8后的第一个时钟周期,数据最后一位的传输已经完成。在数据已经收/发完毕的情况下,counter=8状态的长短对数据的正确性没有影响,因此可以缩短counter=8的状态,以避免前一个SPIF和后一个SPIF相重叠。这样,从硬件上避免了这一阶段的溢出错误。
但是,如果从器件工作速度不够快或者软件正在处理其他事情,在SPI接口接收到的数据尚未被读取的情况下,又接收到一个新的数据,溢出错误还是会发生的。此时,SPI接口保护前一个数据不被覆盖,舍弃新收到的数据,置溢出标志OVR=1;另外发出中断信号(如果该中断允许),通知从器件及时读取数据。
4) 偏移错误(OFST)
SPI接口一般要求从器件先工作,然后主器件才开始发送数据。有时在主器件往外发送数据的过程中,从器件才开始工作,或者SCK受到外界干扰,从器件未能准确地接收到8个SCK。如图5所示,从器件接收到的8个SCK其实是属于主器件发送相邻的两个数据的SCK主。这时,主器件的SPIF和从器件的SPIF会发生重叠,数据发生了错位,从器件如果不对此进行纠正的话,数据的接收/发送便一直地错下去。
在一个数据的传输过程中,SPR是不允许改变的,即SCK是均匀的,而从图5可以看出,从器件接收到的8个SCK并不均匀,它们是分别属于两个数据的,因此可以计算SCK的占空时间来判断是否发生了偏移错误。经分析,正常时候SCK=1时的时钟周期数n的取值满足如下关系:
但由于主从时钟之间是异步的,并且经过了取整,所以正常时候SCK=1时的时钟周期计数值COUNT应满足:
比如在图5中,COUNT的最大值COUNT(max)=2或者1,都可认为是正常的。但当出现COUNT(max)=8时,可以判定出现了偏移错误。在实际设计中,先记录下第一个COUNT(max)的值,如果后面又出现与记录值相差1以上的COUNT(max)出现,可知有偏移错误OFST发生。SPI接口在“不均匀”的地方令SPIF=1,然后准备等待下一个数据的第一个SCK。其中COUNT的位数固定为8位,为了避免溢出时重新从00H开始计数,当计数达到ffH时停止计数。
5) 其他错误
设定不当,或者受到外界干扰,数据传输难免会发生错误,或者有时软件对错误的种类判断不清,必须要有一种方法强制SPI接口从错误状态中恢复过来。在SPI不工作,即SPEN=0的时候,清除SPI模块内部几乎所有的状态(专用寄存器除外)。如果软件在接收数据的时候,能够发现数据有错误,无论是什么错误,都可以强制停止SPI的工作,重新进行数据传输。例如,在偏移错误(OFST)中,如果SPR2、SPR1和SPR0的设置适当,也可以使SCK显得比较“均匀”。SPI接口硬件本身不可能检测到有错误,若用户软件能够发现错误,这时就可以强制停止SPI的传输工作,这样就可以避免错误一直持续下去。
结语
本文对SPI接口之间数据传输中各种出错情况进行分析,并对SPI接口处理错误的能力进行增强。对一些传输错误,SPI接口可以检测出来,通过对各种错误状态寄存器进行置位,并做相应的处理解决。但是有些错误由硬件本身造成,是检测不到的。因此,在应用中,如果对数据的正确性要求较高,除了要在软件上满足SPI接口的时序要求外,还需要在软件上作适当的处理。
5. 串行接口SPI接口应用设计
使用的同步串行三线SPI接口,可以方便的连接采用SPI通信协议的外围或另一片AVR单片机,实现在短距离内的高速同步通信。ATmega128的 SPI采用硬件方式实现面向字节的全双工3线同步通信,支持主机、从机和2种不同极性的SPI时序,通信速率有7种选择,主机方式的最高速率为1/2系统时钟,从机方式最高速率为1/4系统时钟。 ATmega128单片机内部的SPI接口也被用于程序存储器和数据E2PROM的编程下载和上传。但特别需要注意的是,此时SPI的MOSI和MISO接口不再对应PB2、PB3引脚,而是转换到PE0、PE1引脚上(PDI、PDO),其详见第二章中关于程序存储器的串行编程和校验部分的内容。 ATmega128的SPI为硬件接口和传输完成中断申请,所以使用SPI传输数据的有效方法是采用中断方式+数据缓存器的设计方法。在对SPI初始化时,应注意以下几点: .正确选择和设置主机或从机,以及工作模式(极性),数据传输率; .注意传送字节的顺序,是低位优先(LSB First)还是高位优先(MSB Frist); .正确设置MOSI和MISO接口的输入输出方向,输入引脚使用上拉电阻,可以节省总线上的吊高电阻。 下面一段是SPI主机方式连续发送(接收)字节的例程: #define SIZE 100 unsigned char SPI_rx_buff[SIZE]; unsigned char SPI_tx_buff[SIZE]; unsigned char rx_wr_index,rx_rd_index,rx_counter,rx_buffer_overflow; unsigned char tx_wr_index,tx_rd_index,tx_counter; #pragma interrupt_handler spi_stc_isr:18 void spi_stc_isr(void) { SPI_rx_buff[rx_wr_index] = SPDR; //从ISP口读出收到的字节 if (++rx_wr_index == SIZE) rx_wr_index = 0; //放入接收缓冲区,并调整队列指针 if (++rx_counter == SIZE) { rx_counter = 0; rx_buffer_overflow = 1; } if (tx_counter) //如果发送缓冲区中有待发的数据 { --tx_counter; SPDR = SPI_tx_buff[tx_rd_index]; //发送一个字节数据,并调整指针 if (++tx_rd_index == SIZE) tx_rd_index = 0; } } unsigned char getSPIchar(void) { unsigned char data; while (rx_counter == 0); //无接收数据,等待 data = SPI_rx_buff[rx_rd_index]; //从接收缓冲区取出一个SPI收到的数据 if (++rx_rd_index == SIZE) rx_rd_index = 0; //调整指针 CLI(); --rx_counter; SEI(); return data; } void putSPIchar(char c) { while (tx_counter == SIZE);//发送缓冲区满,等待 CLI(); if (tx_counter || ((SPSR & 0x80) == 0))//发送缓冲区已中有待发数据 { //或SPI正在发送数据时 SPI_tx_buffer[tx_wr_index] = c; //将数据放入发送缓冲区排队 if (++tx_wr_index == SIZE) tx_wr_index = 0; //调整指针 ++tx_counter; } else SPDR = c; //发送缓冲区中空且SPI口空闲,直接放入SPDR由SIP口发送 SEI(); } void spi_init(void) { unsigned chat temp; DDRB |= 0x080; //MISO=input and MOSI,SCK,SS = output PORTB |= 0x80; //MISO上拉电阻有效 SPCR = 0xD5; //SPI允许,主机模式,MSB,允许SPI中断,极性方式01,1/16系统时钟速率 SPSR = 0x00; temp = SPSR; temp = SPDR; //清空SPI,和中断标志,使SPI空闲 } void main(void) { unsigned char I; CLI(); //关中断 spi_init(); //初始化SPI接口 SEI(); //开中断 while() { putSPIchat(i); //发送一个字节 i++; getSPIchar(); //接收一个字节(第一个字节为空字节) ……… } } 这个典型的SPI例程比较简单,主程序中首先对ATmega128的硬件SPI进行初始化。在初始化过程中,将PORTB的MOSI、SCLK和SS引脚作为输出,同时将MISO作为输入引脚,并打开上拉电阻。接着对SPI的寄存器进行初始化设置,并空读一次SPSR、SPDR寄存器(读SPSR后再对 SPDR操作将自动清零SPI中断标志自动清零),使ISP空闲等待发送数据。 AVR的SPI由一个16位的循环移位寄存器构成,当数据从主机方移出时,从机的数据同时也被移入,因此SPI的发送和接收在一个中断服务中完成。在SPI中断服务程序中,先从SPDR中读一个接收的字节存入接收数据缓冲器中,再从发送数据缓冲器取出一个字节写入SPDR中,由ISP发送到从机。数据一旦写入SPDR,ISP硬件开始发送数据。下一次ISP中断时,表示发送完成,并同时收到一个数据。类似本章介绍的USART接口的使用,程序中putSPIchar()和getSPIchar()为应用程序的底层接口函数(SPI驱动程序是SPI中断服务程序),同时也使用了两个数据缓冲器,分别构成循环队列。这种程序设计的思路,不但程序的结构性完整,同时也适当的解决了高速MCU和低速串口之间的矛盾,实现程序中任务的并行运行,提高了MCU的运行效率。 本例程是通过SPI批量输出、输入数据的示例,用户可以使用一片ATmega128,将其MOSI和MISO两个引脚连接起来,构成一个ISP接口自发自收的系统,对程序进行演示验证。需要注意,实际接收到的字节为上一次中断时发出的数据,即第一个收到的字节是空字节。 读懂和了解程序的处理思想,读者可以根据需要对程序进行改动,适合实际系统的使用。如在实际应用中外接的从机是一片SPI接口的温度芯片,协议规程为:主机先要连续发送3个字节的命令,然后从机才返回一个字节的数据。那么用户程序可以先循环调用putSPIchar()函数4次,将3个字节的命令和一个字节的空数据发送到从机,然后等待一段时间,或处理一些其它的操作后,再循环调用getSPIchar()函数4次,从接收数据缓冲器中连续读取4个字节,放弃前3个空字节,第4个字节即为从机的返回数据了。 |
文章评论(0条评论)
登录后参与讨论