spi协议详解(51入门系列教程协议协议)
上一贴中咱简单聊了一下通信协议,特别介绍了一下IIC。这贴咱来聊聊SPI,SPI(Serial Peripheral Interface,串行外设接口),来源于Motorola。最早在其MC86HC系列的处理器上定义使用,和IIC一样,也是系出名门。不过这货从协议层面上比IIC简单多了。飞利浦对IIC的协议和硬件接口做出了严格的要求,制定出完善的标准。而摩托罗拉对SPI则没有做出这么多的约束,似乎一下子就把这种接口开源出去了。所以现在流行的SPI接口,有很多类变种,有的甚至无比奇葩,下面简单来聊聊:
先说说硬件结构,看这个图
标准的SPI总线接口一般会有4根通信线路,一条总线上,也可以挂多个SPI从设备
(1)MOSI(master out slave in)
主器件输出从器件输入,简单点理解就是主设备向从设备发送数据,也有些器件称之为SDO,就是输出的意思。
(2)MISO(master in slave out)
主器件输入从器件输出,也就是主设备读取从设备的数据,另外一种称法是SDI,也就是输入的意思。
只用到了SCLK、IO和/RST三个端口,其实就是省略了标准接口中只能够单向传输的MISO和MOSI。直接做成了能够双向传输的I/O,其实细细看DS1302的时序。和SPI一样一样的,变种二、奇葩三线SPI、五线SPI。还有很多记不起来的芯片和一些LCD屏幕,譬如一些小尺寸LCD,只需要写数据,就可能会用到/SS、SCLK、MOSI;譬如一些多受控管脚的IC,除了/SS、SCLK、MOSI、MISO外。可能还会添加DCx管脚用于受控指令的传输,这里百家争鸣,朵朵奇葩,啥都有。变种三、Quad SPI,这个是非常有针对性的变种。就是为了数据传输的速度,其实Quad SPI就是标准SPI硬件接口,只不过把数据发送模式分成了多种模式。
这些模式中,可以将原本只能够单向传输的MOSI和MISO都调整成双向传输,就能达到数据传输速度加倍的效果。甚至把某些控制管脚也用于数据传输,那速度就是杠杠滴了。譬如一些Quad SPI Nor Flash之类的芯片,无论哪种SPI,其实它们的基本结构都是类似的。主从设备内部都是一个移位寄存器,连接起来构成一个环形的拓扑。
再来看看通信的协议,需要发送/接收的数据,都在每个SCLK的边沿进行传输。传输的数据为8位,按位传输,高位在前,低位在后,在主器件产生的从器件使能信号作用下,两个双向移位寄存器进行数据交换。假设主机的buff=0xaa,从机的buff=0x55,上升沿发送、下降沿接收、高位先发送。每个时钟的上升沿移动数据
是不是很简单呀?通信时序很简单。但是moto对时钟SCLK做了一些比较有意思的约束,通过CPOL,CPHA对SCLK的限定,实现SPI的四种不同模式。CPOL(Clock Polarity,时钟极性),用来设置时钟的空闲状态:
为1时,时钟空闲时为高电平
为0时,时钟空闲时为低电平
CPHA(Clock Phase,时钟相位),用来配置数据传输的时钟边沿
为0时,在时钟周期的第一边沿传输数据(或者理解为前一边沿)
为1时,在时钟周期的第二边沿传输数据(或者理解为后一边沿)
模式 | 配置 |
Mode 0 | CPOL=0,CPHA=0 |
Mode 1 | CPOL=0,CPHA=1 |
Mode 2 | CPOL=1,CPHA=0 |
Mode 3 | CPOL=1,CPHA=1 |
这里值得引起注意的地方是,对第一边沿和第二边沿的理解。当CPOL=0,也就是时钟空闲为低电平时,第一边沿应该是时钟由低电平变成高电平的边沿,也就是第一个上升沿,那么第二边沿就是下降沿了。同理,当CPOL=1,也就是时钟空闲为高电平时,第一边沿应该是时钟由高电平变成低电平的边沿,也就是第一个下降沿。那么第二边沿就是上升沿了,
下面把四种模式的示意图bia出来
大家可以对比文字和图片,瞧瞧这四种模式,好了,啰嗦了半天。来点好玩的吧!手头上SPI的器件不多,专门跑去ADI官网申请了一篇SPI接口的AD。AD7685,SPI接口的16位AD转换器,就是这货
电路图
随手画的,勿喷啊!简便起见,没有使用压随器,直接分压接入。不过要注意AD7685的输入阻抗要求,数据端口SDI直接连到高电平。其实是一个三线制的SPI接口,看看AD7685的时序吧!
一次传输16位AD数据,在第一个上升沿进行第一位数据的传输。所以是Mode 0的SPI器件,也就是 CPOL=0,CPHA=0。51无需去理会CPOL和CPHA,只需要知道AD7685的要求即可,上代码:
#include <reg51.h>
#define Vref 5.0
sbit smgbit1 = P3^7;
sbit smgbit2 = P3^6;
sbit SCK=P2^2;
sbit SDO=P2^1;
sbit CNV=P2^0;
sbit dp = P1^7;
smgdisp_cache[] = {0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xf8,0x80,0x90};
unsigned int timer_count,Vol,count;
unsigned char temp=0;
float Voltage;
void delay_ms(unsigned int xms)
{
unsigned int i,j;
for(i=xms;i>0;i--)
{
for(j=124;j>0;j--);
}
}
unsigned int adconvert(void)
{
int result;
unsigned char i;
CNV=0;
delay_ms(2);
CNV=1;
delay_ms(2);
for(i=0;i<16;i ) //SPI的数据传输哟
{
SCK=1;
CNV=0;
result=result<<1;
if(SDO)
result=result|0x01;
SCK=0;
}
return(result);
}
void init()
{
smgbit1 = 0;
smgbit2 = 1;
TMOD=0x01;//设置定时器0为工作方式1
TH0=0xfc;
TL0=0X66;
EA=1;//开总中断
ET0=1;//开定时器0中断
TR0=1;//启动定时器0
}
void SMG_Dis()
{
if(smgbit2){
P1 = smgdisp_cache[temp];
}
if(smgbit1){
P1 = smgdisp_cache[temp/10];
dp = 0;
}
}
void time0() interrupt 1
{ P1 = 0xff; //消除鬼影
timer_count ;
if(timer_count == 100)
{ count ;
Vol = adconvert();
Voltage = Vref*Vol/65535;
temp = (unsigned char)(Voltage*10.0);
timer_count = 0;
}
smgbit1 = ~smgbit1;
smgbit2 = ~smgbit2;
SMG_Dis();
TF0 = 0;
TH0 = 0xfc;
TL0 = 0x66;
}
void main()
{ init();
while(1){
}
}
采集AD值,通过数码管显示,数码管用定时器扫描
GIF显示从0-2.5v的采样,突然想起手上还有个点阵LED模块。
这货好像也是个SPI接口呢
MAX7219LED驱动IC,赶紧看看数据手册
果然,看看时序
依然还是模式0啊,写好代码
#include <reg51.h>
#include <intrins.h>
#define uchar unsigned char
#define uint unsigned int
//定义Max7219端口
sbit Max7219_pinCLK = P2^2;
sbit Max7219_pinCS = P2^1;
sbit Max7219_pinDIN = P2^0;
void Delay_xms(uint x)
{
uint i,j;
for(i=0;i<x;i )
for(j=0;j<112;j );
}
//--------------------------------------------
//功能:向MAX7219(U3)写入字节SPI
//入口参数:DATA
//出口参数:无
//说明:
void Write_Max7219_byte(uchar DATA)
{
uchar i;
Max7219_pinCS=0;
for(i=8;i>=1;i--)
{
Max7219_pinCLK=0;
Max7219_pinDIN=DATA&0x80;
DATA=DATA<<1;
Max7219_pinCLK=1;
}
}
//-------------------------------------------
//功能:向MAX7219写入数据
//入口参数:address、dat
//出口参数:无
//说明:
void Write_Max7219(uchar address,uchar dat)
{
Max7219_pinCS=0;
Write_Max7219_byte(address); //写入地址,即数码管编号
Write_Max7219_byte(dat); //写入数据,即数码管显示数字
Max7219_pinCS=1;
}
void Init_MAX7219(void)
{
Write_Max7219(0x09, 0x00); //译码方式:BCD码
Write_Max7219(0x0a, 0x00); //亮度
Write_Max7219(0x0b, 0x07); //扫描界限;8个数码管显示
Write_Max7219(0x0c, 0x01); //掉电模式:0,普通模式:1
Write_Max7219(0x0f, 0x00); //显示测试:1;测试结束,正常显示:0
}
void main(void)
{
uchar i,j;
uchar temp[]={0x00,0x66,0x99,0x81,0x42,0x24,0x18,0x00};//LED点阵取模,❤型
Init_MAX7219();
while(1)
{
for(i=1;i<9;i )
Write_Max7219(i,temp[i-1]);
for(i=1;i<16;i ){
Write_Max7219(0x0a, i);
Delay_xms(50);}
for(i=15;i>0;i--){
Write_Max7219(0x0a, i);
Delay_xms(50);}
}
}
上个图
其实代码还是可以闪烁的,只需要向MAX7219相关寄存器通过SPI写入值即可
SPI相比IIC而言的话简单不少啊,今天就先到这里。
了解更多51系列教程,请关注“云汉电子社区”官方微信公众号ickeybbs,或者登录云汉电子社区官方网站(bbs.ickey.cn)
,
免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com