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
同步全双工
浙公网安备 33010602011771号