当前位置: 首页 > news >正文

STM32-SPI全双工同步通信

一.简介:

SPI(Serial Peripheral Interface),即串行外围设备接口,是一种全双工同步通信总线。

1.物理层

在这里插入图片描述上图为SPI通讯常用的连线方式,
SPI通讯使用四条线:3条总线SCK、MOSI、MISO;1条片选线NSS(低电平有效)

  • SCK(Serial Clock): 时钟信号线;

  • MOSI(Master Output,Slave Input): 主设备输出,从设备输入,数据的方向为主机到从机;

  • MISO(Master Input, Slave Output): 主设备输入,从设备输出,数据的方向为从机到主机;

  • NSS:从设备选择信号线,即片选信号线(CS)。有几个从设备,主机相应增加几条片选信号线。

    3条总线SCK、MOSI、MISO并联

2.协议层

- 时序图:

在这里插入图片描述   上图为主机的通讯时序。NSS、SCK、MOSI 信号都由主机控制产生,而 MISO 的信号由从机产生,主机通过该信号线读取从机的数据。MOSI 与 MISO 的信号只在 NSS 为低电平的时候才有效,在 SCK 的每个时钟周期 MOSI 和 MISO 传输一位数据。

- 起始、停止信号

NSS 信号线由高变低,是 SPI 通讯的起始信号;
NSS 信号由低变高,是 SPI 通讯的停止信号,表示本次通讯结束,从机的选中状态被取消。

- CPOL/CPHA

SPI一共四种通讯模式
在这里插入图片描述

  • CPOL:时钟极性,即SPI通讯之前,NSS线为高电平时SCK的状态.
    CPOL=0,空闲时SCK时钟为低电平;CPOL=1,空闲时SCK时钟为高电平。
  • CPHA:时钟相位,即数据的采样时刻。
    CPHA=0,MOSI 或 MISO 数据线上的信号将会在SCK 时钟线的“奇数边沿”被采样;
    CPHA=1,“偶数边沿”被采样。

二.I2C_InitTypeDef 结构体

typedef struct
{uint16_t SPI_Direction; /* 设置 SPI 的单双向模式 */uint16_t SPI_Mode; /* 设置 SPI 的主/从机端模式 */uint16_t SPI_DataSize; /* 设置 SPI 的数据帧长度,可选 8/16 位 */uint16_t SPI_CPOL; /* 设置时钟极性 CPOL,可选高/低电平 */uint16_t SPI_CPHA; /* 设置时钟相位,可选奇/偶数边沿采样 */uint16_t SPI_NSS; /* 设置 NSS 引脚由 SPI 硬件控制还是软件控制*/uint16_t SPI_BaudRatePrescaler; /* 设置时钟分频因子,fpclk/分频数 =fSCK */uint16_t SPI_FirstBit; /* 设置 MSB/LSB 先行 */uint16_t SPI_CRCPolynomial; /* 设置 CRC 校验的表达式 */
}SPI_InitTypeDef;

通过标准外设库中SPI_InitTypeDef结构体初始化函数配置SPI外设。
1)SPI_Direction:设置SPI通讯方向,双线全双工(SPI_Direction_2Lines_FullDuplex),双线只接收 (SPI_Direction_2Lines_RxOnly),单线只接收 (SPI_Direction_1Line_Rx)、单线只发送模式(SPI_Direction_1Line_Tx)。
2)SPI_Mode:一般设置为主机模式(SPI_Mode_Master)。
3)SPI_DataSize:数据帧大小是为 8 位 (SPI_DataSize_8b) 还是 16位(SPI_DataSize_16b)。
4)SPI_CPOL:时钟极性,高电平(SPI_CPOL_High)、低电平(SPI_CPOL_Low)。
5)SPI_CPHA:时钟相位,为 SPI_CPHA_1Edge(在 SCK 的 奇 数 边 沿 采 集 数 据) 或SPI_CPHA_2Edge(在 SCK 的偶数边沿采集数据)。
6)SPI_NSS:硬件模式(SPI_NSS_Hard)、软件模式(SPI_NSS_Soft),软件模式则需编写程序控制 GPIO 端口拉高或置低产生非片选和片选信号,应用较多。
7)SPI_BaudRatePrescaler:可设置为2、4(SPI_BaudRatePrescaler_4)、6、8、16、32、64、128、256分频。
8)SPI_FirstBit:MSB 先行 (高位数据在前) 还是 LSB 先行 (低位数据在前)
9)SPI_CRCPolynomial:CRCPolynomial(CRC多项式)的设置是用来配置CRC(循环冗余校验)的计算方式

使用方法(例)

void INIT_SPI_CONFIG()
{SPI_InitTypeDef SPI_InitStruct;//开启SPI时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);//结构体赋值SPI_InitStruct.SPI_CPHA = SPI_CPHA_2Edge;//奇/偶数边沿采样SPI_InitStruct.SPI_CPOL = SPI_CPOL_High;//空闲时,CS高低电平SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b;//数据帧长度:8位SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//双向全双工SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;SPI_InitStruct.SPI_Mode = SPI_Mode_Master;SPI_InitStruct.SPI_NSS = SPI_NSS_Soft;//通过软件控制csSPI_InitStruct.SPI_CRCPolynomial = 7;//CRCPolynomial(CRC多项式)的设置是用来配置CRC(循环冗余校验)的计算方式SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;//波特率分频因子//初始化函数配置SPI外设SPI_Init(SPI1,&SPI_InitStruct);//使能SPISPI_Cmd(SPI1,ENABLE);
}

三.收发数据原理

1.发送单字节数据

//发送字节
uint8_t SPI_FLASH_SendByte(uint8_t byte)
{//等待发送缓冲区为空while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE) == RESET){}SPI_I2S_SendData(SPI1,byte);//等待接收缓冲区非空while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE) == RESET){}return SPI_I2S_ReceiveData(SPI1);
}
  • 发送数据过程:
    SPI_I2S_SendData()发送数据–>SPI数据寄存器DR–>发送缓冲区->SPI外设发出数据

2.读取单字节数据

//接收字节  必须先发送一个任意数字后,才可使用SPI_I2S_ReceiveData()读取数据
uint8_t SPI_FLASH_ReadByte()
{return SPI_FLASH_SendByte(Dummy_Byte);
}
  • 接收数据过程:
    SPI外设接收新数据–>接收缓冲区->SPI数据寄存器DR->SPI_I2C_ReceiveData()接收新数据;

复杂的数据通讯都是由单个字节数据收发组成的。

3.注意事项:

在SPI中,读操作和写操作是同步的。即使主设备只需要进行写操作,它也会收到从设备返回的数据;如果主设备只需要读取数据,它也必须发送一个空字节来触发从设备的数据传输。

  • 写操作:主设备向从设备发送数据时,从设备会在相同的时钟周期中返回数据。主设备将发送的数据通过MOSI线传送到从设备,同时从设备通过MISO线将数据发送回主设备。如果主设备只进行写操作,它将忽略从设备返回的数据。
  • 读操作:主设备要读取从设备的数据时,主设备必须向从设备发送一个数据字节(这可以是任意的空字节),从设备在接收到这个数据字节后,开始向主设备发送数据。

四.读写串行FLASH

1.简介

在这里插入图片描述
  上图FLASH 芯片 (型号:W25Q64) 是一种使用 SPI 通讯协议的存储器,它的 CS/CLK/DIO/DO 引脚分别连接到了 STM32 对应的 SPI 引脚 NSS/SCK/MOSI/MISO 上。它与 EEPROM 都是掉电后数据不丢失的存储器,但 FLASH 存储器容量普遍大于 EEPROM。FLASH 芯片只能一大片一大片地擦写,而EEPROM可以单个字节擦写。

1)指令表

通过指令,实现FLASH读写操作。
在这里插入图片描述
  上述指令表中,带括号的字节参数,方向为FLASH向主机传输;不带括号的字节参数,方向为主机向FLASH传输。

2)获取DeviceID

Device ID:标记不同的设备:

//获取Device ID
uint8_t SPI_FLASH_ReadDeviceID(void)
{//发送指令:0xAB  dummy  dummy dummy;接收:DeviceIDuint32_t DeviceID = 0;uint32_t temp = 0;//低电平 启动通讯SPI_FLASH_CS_LOW();temp = SPI_FLASH_SendByte(W25X_DeviceID_COMMAND);printf("temp1-1:%x0X\r\n",temp);temp = SPI_FLASH_SendByte(Dummy_Byte);printf("temp1-2:%x0X\r\n",temp);temp  = SPI_FLASH_SendByte(Dummy_Byte);printf("temp1-3:%x0X\r\n",temp);temp = SPI_FLASH_SendByte(Dummy_Byte);printf("temp1-4:%x0X\r\n",temp);DeviceID = SPI_FLASH_ReadByte();//高电平 关闭通讯SPI_FLASH_CS_HIGH();return DeviceID;
}

3)获取Flash ID

Flash ID:专用于存储芯片的标识编码(如SPI NOR Flash),遵循JEDEC标准,包含厂商ID、容量和版本信息‌

//获取Flash ID
uint32_t SPI_FLASH_ReadFlashID(void)
{uint32_t FlashID,Temp1,Temp2,Temp3;SPI_FLASH_CS_LOW();//发送指令:0x9F;接收:生产厂商、存储类型、容量SPI_FLASH_SendByte(W25X_JEDEC_COMMAND);Temp1 = SPI_FLASH_ReadByte();Temp2 = SPI_FLASH_ReadByte();Temp3 = SPI_FLASH_ReadByte();SPI_FLASH_CS_HIGH();FlashID = (Temp1<<16)| (Temp2<<8) | Temp3;return FlashID;
}

主机端通过读取Flash ID 来测试硬件是否连接正常,或用于识别设备。

2.写操作

1)擦除扇区

//擦除扇区数据(重要) 一个扇区为4KB
void SPI_FLASH_SectorErase()
{//写使能SPI_FLASH_WriteEnableCommand();//等待FLASH写数据完成SPI_FLASH_WaitForEnd();SPI_FLASH_CS_LOW();//发送扇区擦除指令 0x20SPI_FLASH_SendByte(W25X_SectorErase_COMMAND);//发送扇区擦除地址 高位、中位、低位SPI_FLASH_SendByte((FLASH_SectorEraseAddress & 0xFF0000)>>16);SPI_FLASH_SendByte((FLASH_SectorEraseAddress & 0x00FF00)>>8);SPI_FLASH_SendByte(FLASH_SectorEraseAddress & 0x0000FF);SPI_FLASH_CS_HIGH();//等待擦除数据完成SPI_FLASH_WaitForEnd();
}//等待FLASH写数据完成
void SPI_FLASH_WaitForEnd()
{uint8_t Flag;SPI_FLASH_CS_LOW();SPI_FLASH_SendByte(W25X_ReadStatusReg_COMMAND);//发送 读取状态寄存器 指令do{Flag = SPI_FLASH_SendByte(Dummy_Byte);}while(Flag & WIP_STATUS == 0x01);SPI_FLASH_CS_HIGH();
}//发送FLASH 写使能 操作指令
void SPI_FLASH_WriteEnableCommand()
{SPI_FLASH_CS_LOW();SPI_FLASH_SendByte(W25X_WriteEnable_COMMAND);SPI_FLASH_CS_HIGH();
}

注意事项:

  • 擦除数据之间,要先发送写数据指令到FLASH;
  • FLASH 存储器的特性决定了它只能把原来为“1”的数据位改写成“0”,而原来为“0”的
    数据位不能直接改写为“1”。所以这里涉及到数据“擦除”的概念,在写入前,必须要对目标存储矩阵进行擦除操作,把矩阵中的数据位擦除为“1”

2)写入数据

//向Flash写入一页数据256Byte(DataSize < SPI_FLASH_PageSize)
void SPI_FLASH_WritePageData(uint8_t *SendDataAddress,uint32_t FlashAddress,uint8_t DataSize)
{//写使能SPI_FLASH_WriteEnableCommand();SPI_FLASH_CS_LOW();//写入命令SPI_FLASH_SendByte(W25X_WritePage_COMMAND);//写入地址 高位、中位、低位SPI_FLASH_SendByte((FlashAddress & 0xFF0000)>>16);SPI_FLASH_SendByte((FlashAddress & 0x00FF00)>>8);SPI_FLASH_SendByte(FlashAddress & 0x0000FF);//写入数据  一次写一个字节while(DataSize--){SPI_FLASH_SendByte(*SendDataAddress);//指向下一个字节数据SendDataAddress++;}SPI_FLASH_CS_HIGH();//等待写入完成SPI_FLASH_WaitForEnd();
}//向Flash写入全部数据 DataSize:字节数
void SPI_FLASH_WriteData(uint8_t *SendDataAddress,uint32_t FlashAddress,uint8_t DataSize)
{uint8_t PageNum = DataSize/SPI_FLASH_PageSize;//整数页数uint8_t modNum = DataSize % SPI_FLASH_PageSize;//剩余字节数if(PageNum == 0){//写入一页 (DataSize<SPI_FLASH_PageSize)SPI_FLASH_WritePageData(SendDataAddress,FlashAddress,DataSize);}else{ //写入多页 (DataSize>SPI_FLASH_PageSize)while(PageNum--){SPI_FLASH_WritePageData(SendDataAddress,FlashAddress,SPI_FLASH_PageSize);SendDataAddress += SPI_FLASH_PageSize;FlashAddress += SPI_FLASH_PageSize;}SPI_FLASH_WritePageData(SendDataAddress,FlashAddress,modNum);}
}
  • 原理:
    1)使能写操作;
    2)读取FLASH芯片状态寄存器的内容(确定其是否空闲);
    3)扇区擦除 (第一个字节为指令编码,紧跟着3个字节–要擦除的存储矩阵的地址);
    4)页写入 (发送写指令,发送紧跟3个字节-写地址)。

3.读操作

//从Flash读取数据
void SPI_FLASH_ReadData(uint8_t *RecvDataAddress,uint32_t FlashAddress,uint8_t DataSize)
{SPI_FLASH_CS_LOW();//发送命令SPI_FLASH_SendByte(W25X_ReadData_COMMAND);//发送地址 高位、中位、低位SPI_FLASH_SendByte((FlashAddress & 0xFF0000)>>16);SPI_FLASH_SendByte((FlashAddress & 0x00FF00)>>8);SPI_FLASH_SendByte(FlashAddress & 0x0000FF);//读取数据 一次读一个字节while(DataSize--){*RecvDataAddress = SPI_FLASH_ReadByte();RecvDataAddress++;}SPI_FLASH_CS_HIGH();
}
  • 原理:
    1)发送读指令(0x03);
    2)发送读地址(三个字节,地址高位、地址中位、地址地位);
    3)SPI_I2C_ReceiveData()读取数据。

4.发送和接收数据对比

//对比发送和接收的数据
STATUS CompareData(uint8_t *SendData,uint8_t *RecvData,uint8_t DataSize)
{while(DataSize--){if(*SendData != *RecvData)return FAILED;SendData++;RecvData++;}return PASSED;
}

4.工程源代码

完整工程免费下载链接:STM32-SPI-FLASH 数据读写

6.运行结果

使用串口调试助手sscom33显示打印的数据:
在这里插入图片描述

http://www.xdnf.cn/news/1169371.html

相关文章:

  • LWIP学习记录2——MAC内核
  • mybatis多对一一对多的关联及拼接操作以及缓存处理
  • 【学习路线】Python全栈开发攻略:从编程入门到AI应用实战
  • Custom SRP - Draw Calls
  • Claude Code Kimi K2 环境配置指南 (Windows/macOS/Ubuntu)
  • python小工具:测内网服务器网速和延迟
  • Qt资源系统:如何有效管理图片和文件
  • Canmv k230 DAC案例——TLV5638
  • 104.二叉树的最大深度
  • API是什么,如何保障API安全?
  • 刀客doc:Netflix与YouTube开始在广告战场正面交锋
  • [学习] 笛卡尔坐标系的任意移动与旋转详解
  • 洛谷 B3939:[GESP样题 四级] 绝对素数 ← 素数判定+逆序整数
  • 深入解析 Pandas:Python 数据分析的强大工具
  • Jenkins接口自动化测试(构建)平台搭建
  • Kafka监控体系搭建:基于Prometheus+JMX+Grafana的全方位性能观测方案
  • NLP自然语言处理的一些疑点整理
  • JavaScript AJAX 实现,演示如何将 Token 添加到 Authorization
  • 怎么在Mac系统中使用不坑盒子?
  • 交叉编译opencv(Cpp)于arm64架构开发板上
  • .NET使用EPPlus导出EXCEL的接口中,文件流缺少文件名信息
  • 【n8n教程笔记——工作流Workflow】文本课程(第一阶段)——1、导航编辑器界面(Navigating the editor UI)介绍
  • numpy库的基础知识(二)
  • 理解后端开发中的API设计原则
  • 达梦数据库表字段增加时报错[-2106]:无效的表或视图名,[-2116]:列[IS_REPEAT]已存在
  • [3-02-02].第04节:开发应用 - RequestMapping注解的属性2
  • 支付网关系统前后端鉴权方案
  • 网络原理 HTTP 和 HTTPS
  • 代码检测SonarQube+Git安装和规范
  • Uni-App:跨平台开发的终极解决方案