1、什么是SPI通信协议

 SPI(Serial Peripheral Interface,串行外设接口)是一种由摩托罗拉(现NXP)开发的同步全双工串行通信协议,专为短距离高速数据传输设计。SPI通信协议采用主从架构,通常由一个主设备(如微控制器)和一个或多个从设备(如传感器、存储器等)组成。

 

2、SPI通信的设计目的

(1)为了满足高速数据传输,适合对速度要求较高的应用场景

         SPI协议相比I2C协议更快的原因:

      ① SPI协议支持全双工,比I2C的半双工快;

      ② SPI的最大时钟频率为系统时钟频率的1/2,属于MHz级别。而I2C传输速率基本在KHz级别;

          I2C总线不同的模式:标准模式(100Kbit/s)、低速模式(10Kbit/s)、快速模式(400Kbit/s)、高速模式(3.4Mbit/s)

      ③ I²C协议需要在每次传输中添加起始位、停止位以及应答信号,SPI通信不需要;

(2)简化硬件设计,SPI从设备不需要复杂的地址编码,进一步降低了硬件复杂度

 

3、常见采用SPI通信的模块:

     W25Q64 Flash 存储器、  OLED屏幕、 2.4G无线通信模块NRF24L01、 Micro SD卡

     

4、SPI的硬件电路

SCK  (时钟线): 由主机控制SCK总线

MOSI(数据线):主机输出、从机输入

MISO(数据线):主机输入,从机输出

输出引脚设置为推挽输出,输入引脚为浮空或者浮空/上拉输入。(I2C协议的SDA引脚输出为开漏输出模式,导致上升沿慢)

 SS(片选总线):每根SS线接1个从机

SS线为1表示未被选择,未被选择的从机MISO被设置为高阻态

 

5、SPI的移位示意图 ( 模式1,CPOL=0,CPHA=1 )

主机输出,从机输入:抛玉引砖

主机输入,从机输出:抛砖引玉

(玉=数据,砖=0XFF)

 

6、SPI的时序单元

(1)起始条件:SS从1--->0

(2)终止条件:SS从0--->1

         SS为0时表示正在通信

(3)交换字节:      

模式0 CPOL=0 CPHA=0
模式1 CPOL=0 CPHA=1
模式2 CPOL=1 CPHA=0
模式3 CPOL=1 CPHA=1

 

  模式1的时序电平变化:

 CPOL=0 表示空闲状态,SCK为低电平;CPHA=1 表示SCK第一个边沿移出数据,第二个边沿移入数据

 

 

6、SPI的时序波形

(1)发送0X06指令(例如0X06表示写使能)

(2)指定地址写  0X02 + 0X12 + 0X34 + 0X56 + 0X55

     0X02表示写操作,0X12+0X34+0X56 表示地址, 0X55为写入数据

(3)指定地址读 0X03 + 0X12 + 0X34 + 0X56 + 0XFF

     0X03表示读操作,0X12+0X34+0X56 表示地址, 0XFF表示“砖”

 

7、软件SPI读写W25Q64 flash存储器实验

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "W25Q64.h"
#include "W25Q64_INS.h"

uint8_t uiMID = 0x00;
uint16_t uiDID = 0x00;
uint8_t auiArrayWrite[] = {0x00, 0x11, 0x22, 0x33};
uint8_t auiArrayRead[4];

int main(void)
{
	OLED_Init();	
    W25Q64_Init();

    /* 读取厂商ID和设备ID */
    W25Q64_ReadID(&uiMID, &uiDID);
    OLED_ShowHexNum(1, 1, uiMID, 4);
    OLED_ShowHexNum(1, 6, uiDID, 4);

    /* 写入数据 */
    W25Q64_SectorClear(0x001000);
    W25Q64_PageProgram(0x001000, auiArrayWrite, 4);
	OLED_ShowHexNum(3, 1, auiArrayWrite[0], 2);
	OLED_ShowHexNum(3, 4, auiArrayWrite[1], 2);
	OLED_ShowHexNum(3, 7, auiArrayWrite[2], 2);
	OLED_ShowHexNum(3, 11, auiArrayWrite[3], 2);
	
    /* 读取数据 */
    W25Q64_ReadData(0x001000, auiArrayRead, 4);
	OLED_ShowHexNum(4, 1, auiArrayRead[0], 2);
	OLED_ShowHexNum(4, 4, auiArrayRead[1], 2);
	OLED_ShowHexNum(4, 7, auiArrayRead[2], 2);
	OLED_ShowHexNum(4, 11, auiArrayRead[3], 2);

	while(1)
	{

	}	
}

 

W25Q64.c

#include "stm32f10x.h"
#include "MySPI.h"
#include "W25Q64_INS.h"

void W25Q64_Init(void)
{
    MySPI_Init();
}

void W25Q64_ReadID(uint8_t *uiMID, uint16_t *uiDID)
{
    MySPI_Start();
    MySPI_SwapByteMask(W25Q64_JEDEC_ID);
    
    *uiMID = MySPI_SwapByteMove(0xFF);
    
    *uiDID = MySPI_SwapByteMove(0xFF);
    *uiDID <<= 8;
    *uiDID |= MySPI_SwapByteMove(0xFF);
    
    MySPI_Stop();
}

void W25Q64_WriteEnable(void)
{
    MySPI_Start();
    MySPI_SwapByteMask(W25Q64_WRITE_ENABLE);
    MySPI_Stop();
}

void W25Q64_WaitBusy(void)
{
    MySPI_Start();
    MySPI_SwapByteMask(W25Q64_READ_STATUS_REGISTER_1);
    while( (MySPI_SwapByteMask(0xFF) & 0x01) == 0x01 );
    MySPI_Stop();
}

void W25Q64_PageProgram(uint32_t uiAddr, uint8_t *puiDataArray, uint16_t uiCount)
{
    uint16_t uiLoop = 0x00;
    
    W25Q64_WriteEnable();
    MySPI_Start();
    MySPI_SwapByteMask(W25Q64_PAGE_PROGRAM);
    MySPI_SwapByteMask(uiAddr >> 16); //发送寄存器高8位地址
    MySPI_SwapByteMask(uiAddr >> 8); //发送寄存器中8位地址
    MySPI_SwapByteMask(uiAddr); //发送寄存器低8位地址

    for(uiLoop=0; uiLoop<uiCount; uiLoop++)
    {
        MySPI_SwapByteMask(puiDataArray[uiLoop]);
    }
    MySPI_Stop();
    
    W25Q64_WaitBusy();
}

void W25Q64_SectorClear(uint32_t uiAddr)
{
    W25Q64_WriteEnable();

    MySPI_Start();
    MySPI_SwapByteMask(W25Q64_SECTOR_ERASE_4KB);
    MySPI_SwapByteMask(uiAddr >> 16); //发送寄存器高8位地址
    MySPI_SwapByteMask(uiAddr >> 8); //发送寄存器中8位地址
    MySPI_SwapByteMask(uiAddr); //发送寄存器低8位地址
    MySPI_Stop();
    W25Q64_WaitBusy();
}

void W25Q64_ReadData(uint32_t uiAddr, uint8_t *puiDataArray, uint32_t uiCount)
{
    uint32_t uiLoop = 0x00;
    MySPI_Start();
    MySPI_SwapByteMask(W25Q64_READ_DATA);
    MySPI_SwapByteMask(uiAddr >> 16);        //发送寄存器高8位地址
    MySPI_SwapByteMask(uiAddr >> 8);        //发送寄存器中8位地址
    MySPI_SwapByteMask(uiAddr);            //发送寄存器低8位地址

    for(uiLoop=0; uiLoop<uiCount; uiLoop++)
    {
        puiDataArray[uiLoop] = MySPI_SwapByteMask(0xFF);
    }

    MySPI_Stop();
}

 

MySPI.c

#include "stm32f10x.h"

void MySPI_W_SS(uint8_t uiBit)
{
    GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)uiBit);
}

void MySPI_W_SCK(uint8_t uiBit)
{
    GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)uiBit);
}

void MySPI_W_MOSI(uint8_t uiBit)
{
    GPIO_WriteBit(GPIOA, GPIO_Pin_7, (BitAction)uiBit);
}

uint8_t MySPI_R_MISO(void)
{
    return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6);
}

void MySPI_Init(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

	GPIO_InitTypeDef GPIO_InitStructure;
    
    // PA6引脚是主机输入,从机输出引脚,设置为上拉输入模式
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; 
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
 	GPIO_Init(GPIOA, &GPIO_InitStructure);

    // PA4是SS引脚,PA5是时钟线引脚,PA7是主机输出,从机输入引脚,设置为推挽输出模式
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7;
 	GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    /* 置引脚默认电平 */
    MySPI_W_SS(1);
    MySPI_W_SCK(0);
}

void MySPI_Start(void)
{
    MySPI_W_SS(0);
}

void MySPI_Stop(void)
{
    MySPI_W_SS(1);
}

uint8_t MySPI_SwapByteMask(uint8_t uiByteSend)
{
    uint8_t uiRecvByte = 0;
    uint8_t uiLoop = 0;

    for(uiLoop=0; uiLoop<8; uiLoop++)
    {
        /* 主机向MOSI写数据,从机自动写入MISO数据不需要考虑 */
        MySPI_W_MOSI(uiByteSend & (0x80>>uiLoop)); 

        /* SCK电平抬升 */
        MySPI_W_SCK(1);

        /* 主机从MISO读数据,从机自动读取数据不用管 */
        if( Bit_SET ==  MySPI_R_MISO()  )
        {
            uiRecvByte |=  (0x80>>uiLoop);
        }

        /* SCK电平下降 */
        MySPI_W_SCK(0);  
    }
    
    return uiRecvByte;
}

uint8_t MySPI_SwapByteMove(uint8_t uiByteSend)
{
    uint8_t uiLoop = 0;

    for(uiLoop=0; uiLoop<8;uiLoop++)
    {
        /*主机向MOSI写数据,从机自动写入数据不需要考虑*/
        MySPI_W_MOSI(uiByteSend & (0x80>>uiLoop)); 
        
        /* SCK电平抬升 */
        MySPI_W_SCK(1);

        /*左移一上位*/
        uiByteSend <<= 1;
        
        /* 主机从MISO读数据*/
        if( Bit_SET ==  MySPI_R_MISO()  )
        {
            uiByteSend |=   0x01;
        }
        
        /* SCK电平下降 */
        MySPI_W_SCK(0); 

    }
    
    return uiByteSend;
}

   W25Q64_INS.h

/********************************************************************************
  * Copyright (C), Xuanmiao Tech. Co., Ltd.
********************************************************************************
  * @file        W25Q64_INS.h
  * @version     v1.0.0
  * @author      chuzhuyuan
  * @date        2025/02/15
********************************************************************************/

#ifndef __W25Q64_INS_H__
#define __W25Q64_INS_H__

#define W25Q64_PAGE_PROGRAM 0x02
#define W25Q64_READ_DATA 0x03
#define W25Q64_READ_STATUS_REGISTER_1 0x05
#define W25Q64_WRITE_ENABLE 0x06
#define W25Q64_SECTOR_ERASE_4KB 0x20
#define W25Q64_JEDEC_ID 0x9F


#endif

 

posted on 2025-02-14 12:51  轩~邈  阅读(82)  评论(0)    收藏  举报