SPI接口通过总线进行数据交换,主器件产生移位时钟,驱动总线上的数据按位传输。SPI总线可实现全双工通信,数据传输速率可达数兆bps,其总线由四条线构成,即:
·SDO线:主器件数据输出(接从器件数据输入)。
·SDI线:主器件数据输入(接从器件数据输出)。
·SCK线:时钟信号输出(时钟信号由主器件产生)。
·
在一个基于SPI总线的通信系统中,需要至少有一个主控器件,时钟线SCK只能由主器件控制,从器件不能控制SCK线。
13.1.2
SPI模块的功能
ATmega32单片机的SPI模块使用4个引脚进行串行通信,具有7种可编程的比特率控制方式,可以根据需要配置成主机或从机模式。由于SPI模块既可以配置成主器件,又可配置成从器件,因此其引脚的标识也有着自身的特点,具体功能如下:
·MOSI引脚:SPI总线主机输出/从机输入(Master Output/Slave Input)。
·MISO引脚:SPI总线主机输入/从机输出(Master Input/Slave Output)。
·SCK引脚:SPI总线主机时钟信号输出/从机输入。
·
ATmega32单片机的SPI模块内部结构如图13-2所示。
从图13-2中我们可以看出,模块内部的8位移位寄存器和数据缓冲器实现了模块的移位发送和接收功能。引脚控制逻辑用于在SPI模式下对引脚功能的配置。当SPI工作在主机模式时,串行通信时钟来自于系统时钟,可编程的分频器提供了对系统时钟的2-128级分频。分频后的时钟信号在时钟逻辑的控制下驱动串行通信模块进行通信。
在任何时候,只要将数据写入主机的数据缓冲器SPDR中,SPI模块立即在SCK引脚上产生时钟信号,在时钟的驱动下,数据可按高位在前或低位在前(可设定)的顺序在总线上按位传输。数据传输结束后,主器件的SPI时钟停止,传输结束中断标志位SPIF置位,如果此中断得到允许,单片机会跳转到与之对应的中断向量处开始执行中断服务程序。
SPI主从通信原理如图13-3所示。图中左侧单片机为主器件,右侧单片机为从器件,主从器件的串行移位寄存器,通过各自的MISO和MOSI引脚相连接。在主器件时钟发生器产生的串行时钟的驱动下,8位的数据由主器件的MOSI引脚移出,同时从主器件的MISO引脚移入来自从器件的另一个8位的数据。从以上的通信方式我们可以看出,SPI通信其实是主从器件的一个数据交换的过程。
13.2
SPI模块的设置
13.2.1
SPI模块的引脚配置
SPI模块使能后,按照主从配置不同,MOSI、MISO、SCK和
在表13-1中,标有“自动配置”的引脚数据方向无需用户软件指定,但标有“用户定义”的引脚必须为其指定数据方向。此外需要特别注意的是
1)清零SPCR寄存器的MSTR位,使SPI成为从机,MOSI和SCK引脚变为输入状态。
2)SPSR的SPIF置位,并向CPU发送中断申请。
当SPI模块被配置为从机时,从机选择引脚
13.2.2
SPI模块的控制寄存器
1.SPCR寄存器
该寄存器是SPI模块的控制寄存器,包含了多个SPI模块的相关控制位。
SPCR寄存器:SPI的控制寄存器
bit 7SPIE:SPI中断使能位。该位置1时允许SPI中断,清零时则禁止SPI中断。
bit 6SPE:SPI模块使能位。该位置1时使能SPI模块,任何关于SPI的操作之前都要将SPE位置1。
bit 5DORD:数据次序选择位。该位置1时数据的最低位(LSB)首先发送,清零时数据的最高位(MSB)首先发送。
bit 4MSTR:主/从模式选择位。该位置1时SPI模块被配置成主机模式,清零时则为从机模式。
bit 3CPOL:时钟极性选择位。该位置1时SCK线在空闲时为高电平,对应的时钟信号起始沿为下降沿。CPOL位清零时SCK线在空闲时为低电平,对应的时钟信号起始沿为上升沿。具体设置详见表13-3。
bit 2CPHA:时钟相位选择位。该位置1时,SPI模块对所输入的数据在SCK时钟的结束沿采样,清零时在SCK的开始沿采样。具体时序状态如图13-4所示。
bit 1-0SPR1:SPR0:SPI时钟速率选择位。这两位与SPI2X位共同作用,决定了工作在主机模式下的SPI通信速率,对从机模式没有影响。SPI通信速率设置详见表13-2。
注意:当MSTR位为1时,如果
2.SPSR寄存器
该寄存器是SPI模块的状态寄存器,包含了SPI模块的相关状态位。
SPSR寄存器:SPI的状态寄存器
bit 7SPIF:SPI中断标志位。该位置1时表示串行发送结束,如果此中断得到允许,单片机将跳转到相应的中断向量处并执行中断服务程序。进入中断服务程序后,SPIF位自动清零,也可以先软件查询SPIF位的状态,SPIF位置1后,将接收到的数据从SPDR寄存器中读回,此操作同样会使SPIF位清零。
bit 6WCOL:写冲突标志位。在SPI发送数据时,将数据写入寄存器SPDR将会产生写冲突,从而使WCOL位置1。通过软件查询SPSR寄存器的WCOL位可以检测到写冲突的产生,写冲突产生后,对SPDR寄存器的读操作可以将WCOL位清零。
bit 5-1保留。
bit 0SPI2X:SPI倍速模式选择位。该位置1时SPI通信速度加倍,具体设置详见表13-2。
3.SPDR寄存器
该寄存器是SPI模块的数据寄存器,用于保存接收或待发送的数据,对该寄存器的写操作将会启动SPI的数据传输。
SPDR寄存器:SPI数据寄存器
13.2.3
SPI的数据传输时序
SPI串行通信的时钟信号相位和极性有4种组合,这些组合决定了SPI在时钟的哪一个相位发送或接收数据。一般来说,我们要根据从器件的性能要求来更改主器件的时钟相位设置,以实现SPI数据的正确传输。在前面的寄存器介绍中,我们已经知道SPCR寄存器的CPOL位决定空闲时钟极性,而CPHA位决定发送和接收数据是在时钟的开始沿还是结束沿。CPOL位的CPHA位之间的4种组合详见表13-3,相应的通信时序如图13-4所示。
13.2.4
SPI模块设置向导
SPI通信的标准是不严格的,不同的设备在通信时所采取的时序也略有不同,当你在编写代码驱动SPI设备时,必须要注意器件间的这些差异。在SPI通信设置中,要注意以下几个方面:
1)数据传输时是高位(MSB)在前,还是低位(LSB)在前。编程SPCR寄存器的DORD位,设定数据次序。
2)时钟SCK线在空闲时,是高电平还是低电平。编程SPCR寄存器的CPOL位,设置时钟极性。
3)数据采样是在时钟的开始沿还是结束沿。这一点对于数据的接收和发送使用不同时钟沿的器件尤为重要,编程SPCR寄存器的CPHA位,确定时钟相位。
4)合理设置SPI通信速度。一般来说,SPI的通信速度都在2MHz以下,高于此值数据传输可能无法完成,编程SPCR寄存器的SPR1:SPR0位,正确设置SPI时钟速率。
13.3
存储器93C46
93C46是串行E 13.3.1
93C46的引脚功能
93C46共有8个引脚,其中CS引脚是芯片的片选控制端,高电平有效;SK引脚是SPI通信的时钟输入端,接主器件的时钟输出;DI端是SPI通信的数据输入端,接主器件的数据输出;DO端是SPI通信的数据输出端,接主器件的数据输入;ORG端是存储器结构选择端,当ORG接Vcc时,存储器为16位结构;当ORG接GND时,存储器为8位结构。93C46的引脚功能如表13-4所示。
ATmega32单片机与93C46的通信方式如图13-5所示。
13.3.2
93C46的操作指令
对93C46的操控是通过一系列的指令来实现的。当存储器的结构为8位时,93C46的操作指令由10位构成,即一位高电平“1”的开始位、2位的操作码和7位的地址位。如果需要,后面还可以跟随8位的数据位。当存储器的结构为16位时,93C46的操作指令由9位构成,即1位高电平“1”的开始位,2位的操作码及6位的地址位构成,后面也可以跟随16位的数据位。93C46的操作指令集详见表13-5。
下面我们以8位的存储器结构为例来分析一下这些指令。
1)读操作指令(READ):当主器件发送110XXXXXXX指令后,7位地址XXXXXXX处的存储数据会由DO引脚输出。在接收到读操作指令之前,93C46的DO引脚是高阻态的,当接收到读操作指令后,DO引脚先输出一个虚拟的低电平,然后开始输出数据。93C46的读操作时序如图13-6所示。
2)写操作指令(WRITE):在写入数据时,主器件先发送写指令101XXXXXXX,之后发送8位的写入数据。在CS引脚的下降沿,93C46会使用自动时钟把数据写入地址(XXXXXXX)处,写入过程中DO引脚为低电平,写入完成后DO引脚会转为高电平。93C46的写操作时序如图13-7所示。
3)擦除操作指令(ERASE):主器件发送擦除指令111XXXXXXX后,在CS引脚的下降沿,93C46会使用自动时钟将地址(XXXXXXX)的数据擦除。擦除指令完成后,所有存储位都回到逻辑“1”的状态。
4)写允许指令(EWEN):主器件发送写允许指令10011XXXXX后,才可以进行写(WRITE)操作。写允许命令发送后会持续有效,直到断电或发送一条写禁止指令。
5)写禁止指令(EWDS):主器件发送指令10000XXXXX后,会禁止对93C46的写入和擦除操作,这也可以防止意外地对器件进行写入和擦除。
6)擦除全部指令(ERAL):主器件发送擦除全部指令10010XXXXX后,在CS引脚的下降沿,93C46使用自动时钟擦除存储器的全部内容,擦除完毕后,所有存储位都恢复到逻辑“1”的状态。
7)写入全部指令(WRAL):主器件先发送写入全部指令10001XXXXX,之后发送要写入的数据,在CS引脚的下降沿,93C46使用自动时钟把数据写入到全部存储单元中。
13.3.3
93C46的数据传输时序
93C46的数据传输同步时序如图13-8所示。从图中我们可以看出,93C46会在SK引脚时钟的上升沿采样DI引脚的输入数据,因此要求主器件要在时钟的上升沿发送数据。另外,93C46会将保存的数据在时钟的下降沿从DO引脚输出,这同样要求主器件要在时钟的末端采样输入的数据。为了实现正确的SPI通信,我们需要针对上述两种情况分别设置主器件的收发时序。
注意:在ATmega32单片机SPI模块的4种模式下,收发都是在同一个时钟沿上进行的,而93C46要求主器件要在时钟的开始沿(上升沿)发送,而在时钟的结束沿(下降沿)接收,所以在程序中单片机的SPI模块必须根据收发两种状态调整SPI方式,发送数据时对应方式0,接收数据时对应方式1。
13.4
SPI模块的编程应用
利用ATmega32单片机的SPI硬件接口,可以实现与存储器93C46的通信。使用硬件接口的好处是不用再软件模拟SPI时序,可以使代码变得清晰简洁。完成这个实验我们需要一片DIP8封装的93C46存储器和一小块多孔电路板(俗称洞洞板),以及一些杜邦线,这些在电子市场上都很容易买到。材料准备好以后,按照图13-5所示的方式,将93C46与系统板上ATmega32单片机的相应引脚连接起来。在AVR系统板上,由于PB4-PB7引脚用来作为四位数码管的位驱动端,这会与SPI通信产生冲突,所以需要使用杜邦线将数码管的位驱动端转移至PD4-PD7引脚上。
硬件连接好以后,打开Atmel Studio 6.1软件,新建名为“SPI1”的项目,保存在chapter13文件夹下,软件自动添加名为SPI1.c的源文件到新建的项目中。编辑SPI1.c源文件,具体代码详见代码清单13-1。
代码清单13-1
SPI模块与93C46通信(数码管断电接力显示)
/*
*
SPI1.c *
SPI 模块与93C46
通信
*
Created: 2013/9/27
8:13:10
*
Author: GAO */
/*
SPI 方式读写93C46, 数码管显示读出数据, 断电可接力。
写用SPI 模式0, 读用SPI 模式,SS 引脚连接93C46CS 端。
*/
#include
//
包含AVR 头文件
#define F_CPU 16000000UL //
定义系统时钟
#include
//
包含延时函数头文件
#define SS_SET (PORTB=PORTB|0x10)
//
置位SS 引脚
#define SS_CLR (PORTB=PORTB&0xEF)
//
清零SS 引脚
unsigned char table0[]={0x3f,0x06,0x5b,0x4f,0x66, 0x6d,0x7d,0x07,0x7f,0x6f }; //
共阴无点
unsigned char table1[]={0xbf,0x86,0xdb,0xcf,0xe6, 0xed,0xfd,0x87,0xff,0xef }; //
共阴有点
unsigned int NUM; //
定义全局变量NUM 用于显示
unsigned char TEMP; //
定义全局变量TEMP 用于保存从93C46
中读出的值
void display(unsigned int NUM); //
数码管显示函数声明
void SPI_init(void); //SPI 初始化函数声明
void transfor_spi(unsigned char TDATA); //SPI 数据交换函数声明
void write_onechar(unsigned char ADD,unsigned char SA_DATA); //
向93C46
某一地址写一个字节函数声明
void write_en(void); //93C46
写允许函数声明
unsigned char read_onechar(unsigned char ADD); //
从93C46
某一地址读一个字节函数声明
/**********
主函数**********/
int main(void)
{
DDRA=0xFF; //
设数码管段驱动端为输出
DDRD=0xF0; //
设数码管位驱动端为输出
SPI_init(); //SPI 模式初始化
write_en(); //
打开93C46
写允许
while(1)
{
NUM=read_onechar(3); //
从“3
”存储单元读出一个字节
NUM++; write_onechar(3,NUM); //
将NUM 值写入“3
”存储单元
display(NUM); //
扫描数码管
}
}
/**********
数码管显示函数**********/
void display(unsigned int NUM)
{
unsigned char NUM4,NUM3,NUM2,NUM1; NUM1=NUM%10; NUM2=NUM%100/10; NUM3=NUM%1000/100; NUM4=NUM/1000; PORTA=table0[NUM1]; PORTD=0x10; _delay_ms(2); //
延时2ms PORTA=0x00; PORTD=0x00; _delay_ms(1); //
延时1ms PORTA=table0[NUM2]; PORTD=0x20; _delay_ms(2); //
延时2ms PORTA=0x00; PORTD=0x00; _delay_ms(1); //
延时1ms PORTA=table0[NUM3]; PORTD=0x40; _delay_ms(2); //
延时2ms PORTA=0x00; PORTD=0x00; _delay_ms(1); //
延时1ms PORTA=table0[NUM4]; PORTD=0x80; _delay_ms(2); //
延时2ms PORTA=0x00; PORTD=0x00; _delay_ms(1); //
延时1ms }
/**********SPI 初始化函数**********/
void SPI_init(void)
{
DDRB|=0xF0; //
将PB4-PB7
置为输出,MISO 在主机模式下会自动配置成输入
SPCR=0x53; //
高位在先,时钟速率
1/128
FOSC ,SPI 模式0
SPSR=0x00; }
/**********SPI 数据交换函数**********/
void transfor_spi(unsigned char TDATA)
{
SPDR=TDATA; //
将值写入SPDR while((SPSR&0x80)==0); //
等待SPIF 置1
TEMP=SPDR; //
读回SPDR 中收到的数据,读操作会清零SPIF 位
}
/**********
向93C46
某一地址写一个字节函数**********/
void write_onechar(unsigned char ADD,unsigned char SA_DATA)
{
SS_SET; //
拉高片选端
_delay_us(2); //
延时2
μs transfor_spi(0x02); //
写命令101+ADD transfor_spi(0x80|ADD); //10
位的命令,要分两次写入
transfor_spi(SA_DATA); //
写入数据
SS_CLR; //
拉低片选端,芯片内部开始烧写过程
_delay_ms(5); //
延时等待
}
/**********93C46
写允许函数**********/
void write_en(void)
{
SS_SET; //
拉高片选端
_delay_us(2); //
延时2
μs transfor_spi(0x02); //
写命令10011
00000
transfor_spi(0x60); //10
位的命令,要分两次写入
_delay_us(2); //
延时2
μs SS_CLR; //
拉低片选端
}
/**********
从93C46
某一地址读一个字节函数**********/
unsigned char read_onechar(unsigned char ADD)
{
SS_SET; //
拉高片选端
_delay_us(2); //
延时2
μs transfor_spi(0x03); //
写命令110+ADD transfor_spi(ADD); //10
位的命令,要分两次写入
SPCR=0x57; //
切换到SPI 模式1
transfor_spi(0x00); //
写入空数据,目的是接收数据
SPCR=0x53; //
恢复到SPI 模式0
SS_CLR; //
拉低片选端
return TEMP; //
返回READTEMP 值
}
/**********
结束**********/
成功编译以上代码后,将其下载到AVR系统板中,程序运行后的效果如图13-9所示。数码管显示数值从0~255开始不断循环累加,在累加过程中如中途断电,再次加电后数码管会从断电时的数值继续累加,显然这是E2PROM存储器的存储功能所致。