乐趣区

关于嵌入式:SPI协议与GPIO模拟SPI的实现

SPI 定义

SPI(Serial Peripheral Interface, 串口外设接口),它用于 MCU 与各种外围设备以串行形式进行通信,速度最高可达 25MHz 以上。

SPI 接口次要利用在 EEPROM、FLASH、实时时钟、网络控制器、OLED 显示驱动器、AD 转换器,数字信号处理器、数字信号解码器等设施之间。

SPI 通常由四条线组成,一条主设施输入与从设施输出(Master Output Slave Input,MOSI),一条主设施输出与从设施输入(Master Input Slave Output,MISO),一条时钟信号(Serial Clock,SCLK),一条从设施使能抉择(Chip Select,CS)。与 I²C 相似,协定比较简单,也能够应用 GPIO 模仿 SPI 时序。

SPI 的数据交换

在 SCLK 时钟周期的驱动下,MOSI 和 MISO 同时进行,如下图所示,能够看作一个虚构的环形拓扑构造。主机和从机都有一个移位寄存器,主机移位寄存器数据通过 MOSI 将数据写入从机的移位寄存器,此时从机移位寄存器的数据也通过 MISO 传给了主机,实现了两个移位寄存器的数据交换。无论主机还是从机,发送和接管都是同时进行的,如同一个“环”。须要留神的是,数据的写入程序是从高地址到低地址。

如果主机只对从机进行写操作,主机只需疏忽接管的从机数据即可;如果主机要读取从机数据,须要主机发送一个空数据来引发从机发送数据。

传输模式

SPI 有四种传输模式,次要差异在于 CPOL 和 CPHA 的不同。

CPOL(Clock Polarity,时钟极性),示意 SCK 在闲暇时为高电平还是低电平。当 CPOL=0,SCK 闲暇时为低电平,当 CPOL=1,SCK 闲暇时为高电平。

CPHA(Clock Phase,时钟相位),示意 SCK 在第几个时钟边缘采样数据。当 CPHA=0,在 SCK 第一个边际采样数据(即在第二个边际输入),当 CPHA=1,在 SCK 第二个边际采样数据(即在第一个边际输入数据)。

比方:CPHA= 0 时,示意在时钟第一个时钟边际采样数据。当 CPOL=1,即闲暇时为高电平,从高电平变为低电平,第一个时钟边际(降落沿)即进行采样。当 CPOL=0,即闲暇时为低电平,从低电平变为高电平,第一个时钟边际(回升沿)即进行采样。

数据传输流程

首先主机和从机都抉择同一传输模式。而后主机片选拉低,选中从机。接着在时钟的驱动下,MOSI 发送数据,同时 MISO 读取接收数据。最初实现传输,勾销片选。

模仿 SPI

 /*
* 函数名:void SPI_WriteByte(uint8_t data)
* 输出参数:data -> 要写的数据
* 输入参数:无  
* 返回值:无
* 函数作用:模仿 SPI 写一个字节
*/ 
void SPI_WriteByte(uint8_t data) {        //SPI 写 1 Byte,循环 8 次,每次发送 1 Bit;uint8_t i = 0;  
    uint8_t temp = 0;  
    for(i=0; i<8; i++) {temp = ((data&0x80)==0x80)? 1:0;  // 将 data 最高位保留到 temp;data = data<<1;                   //data 左移一位,将次高位变为最高位,用于下次取最高位;SPI_CLK(0); //CPOL=0              // 拉低时钟,即闲暇时钟为低电平,CPOL=0;SPI_MOSI(temp);                   // 依据 temp 值,设置 MOSI 引脚的电平;SPI_Delay();                      // 简略延时,能够定时器或延时函数实现
        SPI_CLK(1); //CPHA=0              // 拉高时钟,W25Q64 只反对 SPI 模式 0 或 1,即会在时钟回升沿采样 MOSI 数据;SPI_Delay();}
     SPI_CLK(0);                          // 最初 SPI 发送完后,拉低时钟,进入闲暇状态;}

/*
* 函数名:uint8_t SPI_ReadByte(void)
* 输出参数:* 输入参数:无
* 返回值:读到的数据
* 函数作用:模仿 SPI 读一个字节
*/    
uint8_t SPI_ReadByte(void) {         //SPI 读 1 Byte,循环 8 次,每次接管 1 Bit;uint8_t i = 0;
    uint8_t read_data = 0xFF;
    for(i=0; i<8; i++) {
        read_data = read_data << 1;  //“凌空”read_data 最低位,8 次循环后,read_data 将高位在前;SPI_CLK(0);                  // 拉低时钟,即闲暇时钟为低电平;SPI_Delay();
        SPI_CLK(1);
        SPI_Delay();
        if(SPI_MISO()==1) {read_data = read_data + 1;}
    }
    SPI_CLK(0);                      // 最初 SPI 读取完后,拉低时钟,进入闲暇状态  
    return read_data;
}  

后面提到 SPI 传输能够看作一个虚构的环形拓扑构造,即输出和输入同时进行。在后面“SPI_WriteByte()”函数里,发送了 1 Byte,也应该接管 1 Byte,只是代码中疏忽了接管引脚 MISO 的状态;在后面“SPI_ReadByte()”函数里,接管了 1 Byte,也应该发送 1 Byte,只是代码中疏忽了发送引脚 MOSI 的内容。有些场景,SPI 须要同时读写,因而还须要编写 SPI 同时读写函数。

/*
* 函数名:uint8_t SPI_WriteReadByte(uint8_t data)
* 输出参数:data -> 要写的一个字节数据
* 输入参数:无
* 返回值:读到的数据
* 函数作用:模仿 SPI 读写一个字节
*/SPI 读和写 1 Byte,循环 8 次,每次发送和接管 1 Bit;uint8_t SPI_WriteReadByte(uint8_t data) {
    uint8_t i = 0;
    uint8_t temp = 0;
    uint8_t read_data = 0xFF;
    for(i=0;i<8;i++) {temp = ((data&0x80)==0x80)? 1:0; // 将 data 最高位保留到 temp;data = data<<1;                  //data 左移一位,将次高位变为最高位,用于下次取最高位;read_data = read_data<<1;        //“凌空”read_data 最低位,8 次循环后,read_data 将高位在前;SPI_CLK(0);
        SPI_MOSI(temp);
        SPI_Delay();
        SPI_CLK(1);
        SPI_Delay();
        if(SPI_MISO()==1) {             // 读取 MISO 上的数据,保留到以后 read_data 最低位,后续通过左移的形式腾挪地位;read_data = read_data + 1;
        }
    }
    SPI_CLK(0);
    return read_data;
}
退出移动版