通信接口

SPI模块官方手册

字号+ 作者:duino123.com 来源:未知 2016-05-31 15:40 我要评论( )

SPI模块官方手册

1.串行外围设备接口入门(Introduction to the Serial Peripheral Interface)

Serial Peripheral Interface (SPI)是一种同步串行数据传输协议,用于近距离时,微控制器(Microcontrollers),如Arduino,与其他外围设备的快速通信。他也可以用于2个微控制器的通讯。
SPI通讯通常有一个主设备(通常是Microcontrollers),用于控制外围设备。通常会有3种线路通用于各类设备的方法。
-Master In Slave Out(MISO)- Slave line,用于Slave向Master发送数据
-Master Out Slave In(MOSI)- Master line,用于Master向Slave发送数据
-Serial Clock(SCK)- 时钟脉冲,主设备用于同步数据传输
-Slave Select pin- 分配给所有的设备,用于enable/disable指定的设备,同时用于避免由于线路忙导致的错误传输。
 
SPI最大的问题在于它的标准太不严格了,这导致各个设备在实现它的时候都有一些不同,这就意味着当我们在编写接口代码的时候必须仔细阅读设备数据参数。通常来说有编号为0-3的3种传输模式(不是4种么?)这些模式控制数据是在时钟信号的高电平还是低电平传入或传出,以及在高或低电平时时钟无效。
所有的SPI设置都由Arduino SPI控制寄存器(SPCR)来决定。这个寄存器就是微控制器内存的一个字节,它是可读写的。寄存器提供的服务通常有3类:控制、数据和状态。
 
控制寄存器(SPCR)编码设置控制多种微控制器的功能。通常控制寄存器中的一个位影响某个特定的设置,比如速度和极性(这个是啥?)
 
数据寄存器(SPDR)仅仅hold住了一个字节。比如,SPI数据寄存器hold住了要发往MOSI线的一个字节,或者这个数据是要从MISO线传入的。
 
状态寄存器(SPSR)根据多种微控制器的条件改变其状态。比如,SPI状态寄存器(SPSR)的第七位被设置为1表示有数据从SPI传入或传出。
 
SPI控制寄存器(SPCR)共有8位,每一个都控制了一种特定的SPI设置。

SPCR
| 7    | 6    | 5    | 4    | 3    | 2    | 1    | 0    |
| SPIE | SPE  | DORD | MSTR | CPOL | CPHA | SPR1 | SPR0 |

SPIE - Enables the SPI interrupt when 1
SPE - Enables the SPI when 1
DORD - Sends data least Significant Bit First when 1, most Significant Bit first when 0
MSTR - Sets the Arduino in master mode when 1, slave mode when 0
CPOL - Sets the data clock to be idle when high if set to 1, idle when low if set to 0
CPHA - Samples data on the falling edge of the data clock when 1, rising edge when 0
SPR1 and SPR0 - Sets the SPI speed, 00 is fastest (4MHz) 11 is slowest (250KHz)

SPIE:置为1时,表示enable SPI的中断
SPE:置为1时,表示enable SPI
DORD:发送数据时,设置为1表示最低有效位,0表示最高有效位。请各自脑补最低有效位和最高有效位。。。
MSTR:设置为1表示Arduino为master模式,0为slave模式
CPOL:设置为1时,数据时钟在高时无效,设置为0时,在低时无效
CPHA:设置为1时,时钟低电平时是Samples data(样本数据?),0时时钟高电平是Sampledata
SPR1和SPR0:设置SPI的速度,00是最快的(4MHz),11是最慢的(250KHz)
 
这些意味着当对一个新的SPI设备编码的时候,我们需要注意一些事情并根据如下设置SPCR:
- 数据传入是最高有效位(MSB)还是最低有效位(LSB)?
- 数据时钟无效是在高还是低?
- samples是在时钟脉冲上升沿还是下降沿?
- SPI 运行的速度是多少?
你还要感受一下feel一下你的芯片,在你设置好之后需要暂停多久才能继续?Let‘s go !

2.串行EEPROM简介



AT25HP512是一个65536字节串行EEPROM。它支持SPI的模式0-3,在10MHz,5V的环境下运行,同时也可以在1.8v的低速下运行。他的内存被组织成512个页,每个页有128字节。他每次只能写入128字节,但是可以同时读出1-128字节的数据,这个设备同时提供了多种程度的写保护,但这里不涉及这个部分。
 
enable这个设备只需要让片选信号CS为低即可。指令使用8位的opcodes来发送,同时在时钟上升沿传入数据,大概使用10微秒写入1个页的数据,所以在每个EEPROM的的写程序后面都应该等待10ms。
 

3.面包板的准备

将AT25HP512芯片插入面包板,EEPROM的3,7,8引脚接到5V,引脚4接地。



红色的线接+5V,GND线是黑色
 
EERPOM的引脚1连接Arduino的引脚10(Slave 片选),EEPROM的引脚2连接Arduino的引脚12(Master In Slave Out - MISO),EEPROM的引脚5连接Arduino的引脚11(Master Out Slave In - MOSI),同时EEPROM的引脚6连接Arduino的引脚13(串口时钟 SCK)。



4.Arduino SPI 编程

现在我们要编写能让Arduino和EEPROM进行SPI通信的代码了。在启动代码中,这个程序填充128字节,或者一个EEPROM页。在main loop中他将数据读出来,每次读一个字节并通过串口打印出来。我们下面用一小节来过一下代码。
 
第一步是启动我们的预处理指令(其实就是#define啦),预处理指令是在真正的编译开始前处理的。他们以#开头并且不以分号(;)结尾。反正前面一段就是说,我们接下来要开始使用#define了。
 
下面定义在我们的SPI通讯中要用到的pins引脚,DATAOUT, DATAIN, SPICLOCK和SLAVESELECT。然后定义EEPROM的控制指令(opcodes):

#define DATAOUT 11//MOSI
#define DATAIN  12//MISO 
#define SPICLOCK  13//sck
#define SLAVESELECT 10//ss

//opcodes
#define WREN  6
#define WRDI  4
#define RDSR  5
#define WRSR  1
#define READ  3
#define WRITE 2
接下来分配程序会用到的全局变量。我们将用char buffer[128]来保存要传输到EEPROM的数据:

byte eeprom_output_data;
byte eeprom_input_data=0;
byte clr;
int address=0;
//data buffer
char buffer [128];
首先我们初始化我们的串口连接,设置我们的input和output模式并设置SLAVESELECT线为高时开始。这个设置可以使设备暂时失效,这样可以防止由于线路噪声引起的传输错误。
void setup()
{
  Serial.begin(9600);
  pinMode(DATAOUT, OUTPUT);
  pinMode(DATAIN, INPUT);
  pinMode(SPICLOCK,OUTPUT);
  pinMode(SLAVESELECT,OUTPUT);
  digitalWrite(SLAVESELECT,HIGH); //disable device
现在我们设置SPI控制寄存器(SPCR)为二进制数据01010000.在控制寄存器中,每一位的设置都表示不同的功能。第8位关闭SPI中断,第七位enable SPI,第六位选择数据传输是最高有效位有线,第五位设置Arduino为master模式,第四位设置数据时钟低时无效,第三位代表SPI在数据时钟的上升沿阶段抽样数据,第二位和第一位设置SPI和系统的通讯速度,/4有4个级别。当设置了控制寄存后,我们接下来从垃圾回收变量中读取SPI的状态寄存器(SPSR)和数据寄存器(SPDR),以清除以前运行的脏数据:
  // SPCR = 01010000
  //interrupt disabled,spi enabled,msb 1st,master,clk low when idle,
  //sample on leading edge of clk,system clock/4 rate (fastest)
  SPCR = (1<<SPE)|(1<<MSTR);
  clr=SPSR;
  clr=SPDR;
  delay(10);
这里我们用数字来填充要发送的数组,并向EEPROM写入一个enable指令。这个enable指令必须在任何一个写指令之前完成。为了发送这个指令,我们将SLAVESELECT线置为低,enable这个设备后,使用spi_transfer函数发送指令。注意到我们使用了程序开始定义的WREN opcode。最后我们设置SLAVESELECT线为高来释放它。
  //fill buffer with data
  fill_buffer();
  //fill eeprom w/ buffer
  digitalWrite(SLAVESELECT,LOW);
  spi_transfer(WREN); //write enable
  digitalWrite(SLAVESELECT,HIGH);
在短暂的delay(10)之后,我们将SLAVESELECT线置为低再次选中EEPROM设备。我们发送一个写指令来告诉EEPROM我们将发送数据到内存中。首先发送16位,也就是2个字节地址来开始,最高有效位。接下来发送buffer中的128字节数据,一个字节接着一个字节,不需要pause暂停。最后我们设置SLAVESELECT引脚为高来释放设备,同时等待一段时间以保证EEPROM写入数据:
  delay(10);
  digitalWrite(SLAVESELECT,LOW);
  spi_transfer(WRITE); //write instruction
  address=0;
  spi_transfer((char)(address>>8));   //send MSByte address first
  spi_transfer((char)(address));      //send LSByte address
  //write 128 bytes
  for (int I=0;I<128;I++)
  {
    spi_transfer(buffer[I]); //write data byte
  }
  digitalWrite(SLAVESELECT,HIGH); //release chip
  //wait for eeprom to finish writing
  delay(3000);
我们在setup函数结束时,通过串口发送hi来表示setup结束。
  Serial.print('h',BYTE);
  Serial.print('i',BYTE);
  Serial.print('\n',BYTE);//debug
  delay(1000);
}
在我们的主函数loop中我们每次从EEPROM中读取一个字节并通过串口将它打印出来。为了可读性我们增加一个打印以及等待。每一次loop我们都增加EEPROM的一个地址去读,当地址增加到128后,我们重新回到0开始读,原因很简单,因为开始我们只写入了128字节数据:
void loop()
{
  eeprom_output_data = read_eeprom(address);
  Serial.print(eeprom_output_data,DEC);
  Serial.print('\n',BYTE);
  address++;
  delay(500); //pause for readability
}
 
fill_buffer函数仅仅将我们的数组用0-127这128个数字来填充。这个函数很容易就可以改写为你应用程序需要的数据:
void fill_buffer()
{
  for (int I=0;I<128;I++)
  {
    buffer[I]=I;
  }
}
spi_transfer函数将要传出的数据放入数据传输寄存器,然后就开始SPI传输了哈。可以通过SPI状态寄存器(SPSR)的某个位(SPIF)来查看数据传输是否结束了。关于位掩码(bit mask)可以参考这里:http://www.arduino.cc/en/Tutorial/。最后返回写入EEPROM的数据。
char spi_transfer(volatile char data)
{
  SPDR = data;                    // Start the transmission
  while (!(SPSR & (1<<SPIF)))     // Wait for the end of the transmission
  {
  };
  return SPDR;                    // return the received byte
}
read_eeprom函数允许我们从EEPROM中读入数据,首先设置SLAVESELECT为低来enable设备。接下来送入一个读指定,接下来送入要读的16位地址,最高有效位有限。接下来我们发送一个假数据到EEPROM中以将数据传出。最后我们在读入一个字节后,再次设置SLAVESELECT线为高来释放设备,并返回数据,如果我们想要一次读入多个数据,那么当我们重复data=spi_transfer(0XFF)时,需要将SLAVESELECT一直设置为低,这样来回128次后读出整个页的数据:
byte read_eeprom(int EEPROM_address)
{
  //READ EEPROM
  int data;
  digitalWrite(SLAVESELECT,LOW);
  spi_transfer(READ); //transmit read opcode
  spi_transfer((char)(EEPROM_address>>8));   //send MSByte address first
  spi_transfer((char)(EEPROM_address));      //send LSByte address
  data = spi_transfer(0xFF); //get data byte
  digitalWrite(SLAVESELECT,HIGH); //release chip, signal end transfer
  return data;
}
为了方便大家CTRL+c、  CTRL+v,下面是整个手册的源码:
#define DATAOUT 11//MOSI
#define DATAIN  12//MISO 
#define SPICLOCK  13//sck
#define SLAVESELECT 10//ss

//opcodes
#define WREN  6
#define WRDI  4
#define RDSR  5
#define WRSR  1
#define READ  3
#define WRITE 2

byte eeprom_output_data;
byte eeprom_input_data=0;
byte clr;
int address=0;
//data buffer
char buffer [128];

void fill_buffer()
{
  for (int I=0;I<128;I++)
  {
    buffer[I]=I;
  }
}

char spi_transfer(volatile char data)
{
  SPDR = data;                    // Start the transmission
  while (!(SPSR & (1<<SPIF)))     // Wait the end of the transmission
  {
  };
  return SPDR;                    // return the received byte
}

void setup()
{
  Serial.begin(9600);

  pinMode(DATAOUT, OUTPUT);
  pinMode(DATAIN, INPUT);
  pinMode(SPICLOCK,OUTPUT);
  pinMode(SLAVESELECT,OUTPUT);
  digitalWrite(SLAVESELECT,HIGH); //disable device
  // SPCR = 01010000
  //interrupt disabled,spi enabled,msb 1st,master,clk low when idle,
  //sample on leading edge of clk,system clock/4 rate (fastest)
  SPCR = (1<<SPE)|(1<<MSTR);
  clr=SPSR;
  clr=SPDR;
  delay(10);
  //fill buffer with data
  fill_buffer();
  //fill eeprom w/ buffer
  digitalWrite(SLAVESELECT,LOW);
  spi_transfer(WREN); //write enable
  digitalWrite(SLAVESELECT,HIGH);
  delay(10);
  digitalWrite(SLAVESELECT,LOW);
  spi_transfer(WRITE); //write instruction
  address=0;
  spi_transfer((char)(address>>8));   //send MSByte address first
  spi_transfer((char)(address));      //send LSByte address
  //write 128 bytes
  for (int I=0;I<128;I++)
  {
    spi_transfer(buffer[I]); //write data byte
  }
  digitalWrite(SLAVESELECT,HIGH); //release chip
  //wait for eeprom to finish writing
  delay(3000);
  Serial.print('h',BYTE);
  Serial.print('i',BYTE);
  Serial.print('\n',BYTE);//debug
  delay(1000);
}

byte read_eeprom(int EEPROM_address)
{
  //READ EEPROM
  int data;
  digitalWrite(SLAVESELECT,LOW);
  spi_transfer(READ); //transmit read opcode
  spi_transfer((char)(EEPROM_address>>8));   //send MSByte address first
  spi_transfer((char)(EEPROM_address));      //send LSByte address
  data = spi_transfer(0xFF); //get data byte
  digitalWrite(SLAVESELECT,HIGH); //release chip, signal end transfer
  return data;
}

void loop()
{
  eeprom_output_data = read_eeprom(address);
  Serial.print(eeprom_output_data,DEC);
  Serial.print('\n',BYTE);
  address++;
  if (address == 128)
    address = 0;
  delay(500); //pause for readability
}

一定要记得,其实整个programing有2个部分,一个部分是操作Arduino中的SPI,一个部分是EEPROM板子哪些操作是操作Arduino,哪些是操作EEPROM,自己一定要清楚。

https://www.arduino.cc/en/Tutorial/SPIEEPROM

 

begin()

 

说明

初始化SPI总线,将SCK(Pin13),MOSI(Pin11)和SS(Pin10)管脚设置为输出模式,将SCK和MOSI设置为低电平,SS为高电平。

语法

SPI.begin()

参数

返回


 

end()

 

说明

停止SPI总线的使用(保持引脚的模式不改变)。

语法

SPI.end()

参数

返回


 

setBitOrder()

 

说明

设置串行数据传输时,先传输高位还是低位,有LSBFIRST(最低位在前)和MSBFIRST(最高位在前)两种类型可选。

语法

SPI.setBitOrder(order)

参数

order:LSBFIRST(最低位在前)或MSBFIRST(最高位在前)

返回


 

setClockDivider()

 

说明

设置SPI串行通信的时钟。通信时钟是由系统时钟分频而得到,分频值有2,4,8,16,32,64或128。默认设置是SPI_CLOCK_DIV4,设置SPI串行通信时钟系统时钟的四分之一。

语法

SPI.setClockDivider(divider)

参数

divider:

  • SPI_CLOCK_DIV2
  • SPI_CLOCK_DIV4
  • SPI_CLOCK_DIV8
  • SPI_CLOCK_DIV16
  • SPI_CLOCK_DIV32
  • SPI_CLOCK_DIV64
  • SPI_CLOCK_DIV128

返回


 

setDataMode()

 

说明

设置SPI的数据模式,即:时钟极性和时钟相位。

时钟极性:表示时钟信号在空闲时是高电平还是低电平;

时钟相位:决定数据是在SCK的上升沿采样还是在SCK的下降沿采样。

包含四种数据模式(详见参数),采样时,应先准备好数据,再进行采样。看到 维基百科的文章对SPI 的细节。

语法

SPI.setDataMode(mode)

参数

mode:

  • SPI_MODE0(上升沿采样,下降沿置位,SCK闲置时为0),
  • SPI_MODE1(上升沿置位,下降沿采样,SCK闲置时为0),
  • SPI_MODE2(下降沿采样,上升沿置位,SCK闲置时为1),
  • SPI_MODE3(下降沿置位,上升沿采样,SCK闲置时为1)。

返回


 

transfer()

 

说明

用于在SPI总线上传输一个数据,包括发送与接收。

语法

SPI.transfer(val)

参数

val:向SPI总线发送的数据值

返回

从SPI总线上接收的数据值


如需更详细资料,如有好资源分享,请加入QQ交流群:214461008

欢迎阅读,欢迎转载,不用注明出处!本站只提供入门和进阶资料,做您学习的小伙伴!

老司机也欢迎光临指导!有好内容请加群:214461008 不要看声明了,看内容吧!

相关文章
  • 两块Arduino之间通过SPI通信

    两块Arduino之间通过SPI通信

    2016-06-01 13:02

  • [2016.04.21]ESP8266 wifi模块在Arduino上的应用

    [2016.04.21]ESP8266 wifi模块在Arduino上的应用

    2016-04-26 15:33

  • 如何Arduino通过SPI控制数字电位计

    如何Arduino通过SPI控制数字电位计

    2016-04-15 08:57

  • SPI通讯简介 SPI Serial Peripheral Interface

    SPI通讯简介 SPI Serial Peripheral Interface

    2016-04-14 23:07

网友点评
Arduino