SPI:一种3线(有的没有SS线(选择主或从机模式))或4线的通信方式:
MOSI:主机出从机进数据线
MISO:主机进从机从数据线
SCK: 时钟线
SS: 主从模式选择线
工作过程的理解:设置好相应的配置后就可以发送或接收数据,当放送或接收完成后就会是SPIF置位,此时如果开了中断就会就入中断服务程序。如果没开中断就可以通过查询该标志位来做相应的事。进入中段服务程序后,或者读(写)SPDR寄存器都可以清零SPIF标志位。从而可以开始下一次的传输
相应的寄存器
1:SCPR:控制寄存器:SPI的大部分设置
2:SPSR:标志寄存器:(查看一些标志(主要是SPIF位)和设置SPI速度)
3:SPDR:数据寄存器:
SS引脚的说明:(实质就是当配置为输入时:是主机还是从机由输入的电平来决定)
从机模式: 当 SPI 配置为从机时,从机选择引脚 SS 总是为输入。SS 为低将激活 SPI 接口, MISO
成为输出 ( 用户必须进行相应的端口配置 ) 引脚,其他引脚成为输入引脚。当 SS 为高时
所有的引脚成为输入, SPI 逻辑复位,不再接收数据。
SS引脚对于数据包/字节的同步非常有用,可以使从机的位计数器与主机的时钟发生器同
步。当SS 拉高时SPI从机立即复位接收和发送逻辑,并丢弃移位寄存器里不完整的数据。
主机模式:当 SPI 配置为主机时 (SPCR 的 MSTR置位 ),用户可以决定 SS 引脚的方向。
若 SS 配置为输出,则此引脚可以用作普通的 I/O 口而不影响 SPI 系统。典型应用是用来
驱动从机的 SS 引脚。
如果 SS 配置为输入,必须保持为高以保证 SPI 的正常工作。若系统配置为主机,SS 为
输入,但被外设拉低,则 SPI 系统会将此低电平解释为有一个外部主机将自己选择为从
机。为了防止总线冲突, SPI 系统将实现如下动作:
- 清零 SPCR 的 MSTR 位,使 SPI 成为从机,从而 MOSI 和 SCK变为输入。
- SPSR 的 SPIF 置位。若 SPI 中断和全局中断开放,则中断服务程序将得到执行。
因此,使用中断方式处理 SPI 主机的数据传输,并且存在 SS 被拉低的可能性时,中断服
务程序应该检查 MSTR 是否为 "1”。若被清零,用户必须将其置位,以重新使能 SPI 主机
模式。
管脚配置
一:SCPR:控制寄存器:(SPI的大部分设置都在该寄存器)
二SPSR:标志寄存器:
(查看一些标志和设置SPI速度,主要是用到SPIF这个标志位,接收完成和发送完成都会置位这个标志,进入中断服务程序或访问SPDR寄存 器都可以清零这个标志)
三SPDR:数据寄存器:
应用步骤:
1先根据是主还是从设置端口的模式(输入还是输出)
主机:SS MOSI SCK 为出 MISO为入
从机:SS MOSI SCK 为入 MISO 为出
2配置相应的寄存器(就三个寄存器,注意用到中断时要先开总中断)
3用中断或查询法发送或接收数据
注意:主机或者从机都可以发送或接收。发送完之后就可以马上发下一个数据了。接收也是只要接收完了也可以马上接收下一个数据了
一:发送:(一般用查询方式发送比较好:此时只要查询发送完毕的标志位SPIF是否置位)
常用: while(!(SPSR & (1<<SPIF))); //等待发送完毕或接收完毕
查询法:实质就是查询SPSR的SPIF位是否置位,接收完和发送完都可以置位,(如果开了中断的话就会进入相应的中断)
例子:
1.主机发送(查询方式)(只要发送完毕了就可以继续发送了)
//主机模式下发送1-255
#include <avr/io.h>
#define uint unsigned int
#define uchar unsigned char
void spi_init(void)
{
PORTB |= (1<<PB4) | (1<<PB5) | (1<<PB6) | (1<<PB7); //
DDRB |= (1<<DDB5) | (1<<DDB7) | (1<<DDB4); //Set MOSI, SCK AND SS as outputs
SPCR = 0x73; //SPI使能,低位首发,主机模式,时钟模式00,上升沿采样;时钟速率Fosc/128
SPSR = 0x00; //SPI2x=0
}
void SPI_send_data(char cData)
{
PORTB &=~ (1<<PB4); //强制接收方进入从模式(在接收方的NSS引脚没有接地时)
// SPCR |= (1<<MSTR); // MSTR有时会被清零,这里强制进入主机模式
SPDR =cData;
while(!(SPSR & (1<<SPIF))); //等待发送完毕
// PORTB |= (1<<PB4); //相当于释放总线(这样可以让别的主机控制从机)
}
void Delay(uint z) //
{
unsigned int i,j;
for(i=z;i>0;i–)
for(j=2000;j>0;j–);
}
int main(void)
{
unsigned int i=0;
spi_init();
while(1)
{
for(i=255;i>0;i–)
{
SPI_send_data(i);
Delay(100);
}
}
}
2从机接收(查询方式)
#include <avr/io.h>
#include <avr/delay.h>
#define uchar unsigned char
#define uint unsigned int
void port_init(void)
{
PORTA = 0x00;
DDRA = 0xFF;
PORTB = 0xFF;
}
void spi_init(void)
{
DDRB = (1<<5) |(1<<7);
DDRB&=~(1<<4)|(1<<6);
SPCR = 0x63; // SPI允许。从机方式
SPSR = 0x00; // SPI倍速.频率为系统时钟
}
void init_devices(void)
{
port_init();
spi_init();
}
//主函数
int main(void)
{
init_devices();
while(1)
{
while(!(SPSR & (1<<SPIF)));//等待数据接收完成
PORTA=SPDR;//把接收到的数据赋给A口
_delay_loop_2(900);
}
}
3从机中断方式接收数据(注意:当进入中断服务程序后SPIF会自动清零)
/中断方式接收数据/
#include <avr/io.h>
#include <avr/interrupt.h>
#define uchar unsigned char
#define uint unsigned int
//端口初始化
void port_init(void)
{
PORTA = 0xFF;
DDRA = 0xFF;
}
void spi_init(void)
{
PORTB |= (1<<4) | (1<<5) | (1<<6) | (1<<7); //
DDRB |= (1<<5) | (1<<7) | (1<<4); //Set MOSI, SCK AND SS as outputs
SPCR = 0xE3; //SPI中断使能,SPI使能,低位首发,从机模式,时钟模式00,上升沿采样;时钟速率Fosc/128
SPSR = 0x00; //SPI2x=0
}
void init_devices(void)
{
port_init();
spi_init();
sei();//开全局中断
}
//主函;
int main(void)
{
init_devices();
while(1);
}
//SPI中断函数
SIGNAL(SIG_SPI)
{
PORTA=SPDR;
}