news 2026/7/12 18:31:08

软件:STM32-F1系列-软硬件SPI介绍(2026/7/10)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
软件:STM32-F1系列-软硬件SPI介绍(2026/7/10)

目录

SPI(高位先行)

1:什么是spi

2:核心特点

3. 四根线

4. 工作原理

5.四种工作模式

模式0:

模式1:

模式2:

模式3:

6.优缺点

7.常见应用场景

8.与UART和I2C的区别

W25Q64芯片介绍

1:内存分块

2:写入操作

3:读取操作

SPI协议配置

1:硬件引脚电平变化层

①:SPI写CS引脚电平

②:SPI写SCK引脚电平

③:SPI写MOSI引脚电平

④:SPI读MISO引脚电平

2:初始化配置

3:协议层

①:起始信号

②:停止信号

③:交换数据

软件SPI读取W25Q64

1:W25Q64写使能

2:W25Q64等待忙

3:W25Q64页编程

4:W25Q64扇区擦除

5:读取厂商ID和设备ID

6:W25Q64读取数据

硬件SPI读取W25Q64

模式选择:

1:初始化

2:交换数据


SPI(高位先行)

1:什么是spi

SPI(Serial Peripheral Interface,串行外设接口)是一种高速、全双工同步时钟的串行通信总线,由摩托罗拉公司最早提出。它是目前微控制器(MCU)连接外部芯片最常用的方式之一,广泛应用于嵌入式系统中,简单来说,它就像是微控制器(主机)与各种外设(从机)之间的一条“高速公路”,用于传输数据。

2:核心特点

  • 全双工通信:数据可以同时发送和接收(两根数据线)。

  • 高速:比 I2C、UART 快得多,通常可达几十 MHz,甚至上百 MHz。

  • 同步传输:有时钟线(SCK)统一指挥,收发双方步调一致。

  • 主从架构:一个主机(通常是MCU)控制多个从机。

  • 硬件连接简单直接:相比 UART 不需要复杂的波特率匹配,相比 I2C 不需要仲裁机制。

3. 四根线

标准的 SPI 总线包含 4 根信号线:

  1. SCK (Serial Clock):时钟线。由主机产生,用来同步数据传输。

  2. MOSI (Master Out Slave In):主发从收。数据从主机发送到从机。

  3. MISO (Master In Slave Out):主收从发。数据从从机发送到主机。

  4. CS/SS (Chip Select/Slave Select):片选线。由主机控制,拉低表示选中某个从机。每个从机都需要一根独立的 CS/SS/NSS 线。

4. 工作原理

  • 选片:主机拉低对应从机的 CS 引脚,告诉它“我要和你说话了”。

  • 发时钟:主机在 SCK 线上产生时钟脉冲。

  • 交换数据

    • 主机通过 MOSI 线发送一位数据,同时从机通过 MISO 线返回一位数据。

    • 每产生一个时钟脉冲,双方就交换一位数据。

    • 因为是环形总线,发送和接收是同时进行的(全双工)。

  • 结束:数据传输完成后,主机拉高 CS 引脚,释放从机。

5.四种工作模式

时钟极性 (CPOL) 和 时钟相位 (CPHA),这是 SPI 最容易让人困惑的地方。SPI 有 4种工作模式,由 CPOL 和 CPHA 决定。

重要提示:通信前,必须查阅从机芯片的数据手册(Datasheet),确认它支持哪种模式,否则无法通信。

模式0:

模式1:

模式2:

模式3:

6.优缺点

SPI 协议本身非常灵活,它没有硬性规定一次通信(即一帧数据)必须传输多少位(Bits)。

7.常见应用场景

凡是需要高速传输数据的外设,基本都用 SPI:

  • 显示屏:OLED、TFT LCD 屏幕(刷图快)。

  • 存储卡:SD 卡(部分模式)、Flash 存储芯片(W25Qxx)。

  • 传感器:高精度 ADC/DAC、部分 IMU(惯性测量单元)传感器。

  • 无线通信:LoRa 模块、NRF24L01 无线模块。

  • 实时时钟 (RTC):部分时钟芯片。

8.与UART和I2C的区别

W25Q64芯片介绍

1:内存分块

最小擦除单位为扇(4KB),最小写入单位为页(256Byte),块区(64KB)

2:写入操作

3:读取操作

SPI协议配置

1:硬件引脚电平变化层

①:SPI写CS引脚电平
void MySPI_W_SS(uint8_t BitValue) { GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue); //根据BitValue,设置SS引脚的电平 }
②:SPI写SCK引脚电平
void MySPI_W_SCK(uint8_t BitValue) { GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)BitValue); //根据BitValue,设置SCK引脚的电平 }
③:SPI写MOSI引脚电平
void MySPI_W_MOSI(uint8_t BitValue) { GPIO_WriteBit(GPIOA, GPIO_Pin_7, (BitAction)BitValue); //根据BitValue,设置MOSI引脚的电平,BitValue要实现非0即1的特性 }
④:SPI读MISO引脚电平
uint8_t MySPI_R_MISO(void) { return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6); //读取MISO电平并返回 }

2:初始化配置

void MySPI_Init(void) { /*开启时钟*/ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟 /*GPIO初始化*/ GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA4、PA5和PA7引脚初始化为推挽输出 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA6引脚初始化为上拉输入 /*设置默认电平*/ MySPI_W_SS(1); //SS默认高电平 MySPI_W_SCK(0); //SCK默认低电平 }

3:协议层

①:起始信号
void MySPI_Start(void) { MySPI_W_SS(0); //拉低SS,开始时序 }
②:停止信号
void MySPI_Stop(void) { MySPI_W_SS(1); //拉高SS,终止时序 }
③:交换数据
uint8_t MySPI_SwapByte(uint8_t ByteSend) { uint8_t i, ByteReceive = 0x00; //定义接收的数据,并赋初值0x00,此处必须赋初值0x00,后面会用到 for (i = 0; i < 8; i ++) //循环8次,依次交换每一位数据 { /*两个!可以对数据进行两次逻辑取反,作用是把非0值统一转换为1,即:!!(0) = 0,!!(非0) = 1*/ MySPI_W_MOSI(!!(ByteSend & (0x80 >> i))); //使用掩码的方式取出ByteSend的指定一位数据并写入到MOSI线 MySPI_W_SCK(1); //拉高SCK,上升沿移出数据 if (MySPI_R_MISO()) { ByteReceive |= (0x80 >> i);//当MISO为1时,置变量指定位为1,当MISO为0时,不做处理,指定位为默认的初值0 } //读取MISO数据,并存储到Byte变量 MySPI_W_SCK(0); //拉低SCK,下降沿移入数据 } return ByteReceive; //返回接收到的一个字节数据 }

软件SPI读取W25Q64

1:W25Q64写使能

void W25Q64_WriteEnable(void) { MySPI_Start(); //SPI起始 MySPI_SwapByte(W25Q64_WRITE_ENABLE); //交换发送写使能的指令 MySPI_Stop(); //SPI终止 }

2:W25Q64等待忙

void W25Q64_WaitBusy(void) { uint32_t Timeout; MySPI_Start(); //SPI起始 MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1); //交换发送读状态寄存器1的指令 Timeout = 100000; //给定超时计数时间 while ((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) == 0x01) //循环等待忙标志位 { Timeout --; //等待时,计数值自减 if (Timeout == 0) //自减到0后,等待超时 { /*超时的错误处理代码,可以添加到此处*/ break; //跳出等待,不等了 } } MySPI_Stop(); //SPI终止 }

3:W25Q64页编程

void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count) { uint16_t i; W25Q64_WriteEnable(); //写使能 MySPI_Start(); //SPI起始 MySPI_SwapByte(W25Q64_PAGE_PROGRAM); //交换发送页编程的指令 MySPI_SwapByte(Address >> 16); //交换发送地址23~16位 MySPI_SwapByte(Address >> 8); //交换发送地址15~8位 MySPI_SwapByte(Address); //交换发送地址7~0位 for (i = 0; i < Count; i ++) //循环Count次 { MySPI_SwapByte(DataArray[i]); //依次在起始地址后写入数据 } MySPI_Stop(); //SPI终止 W25Q64_WaitBusy(); //等待忙 }

4:W25Q64扇区擦除

void W25Q64_SectorErase(uint32_t Address) { W25Q64_WriteEnable(); //写使能 MySPI_Start(); //SPI起始 MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB); //交换发送扇区擦除的指令 MySPI_SwapByte(Address >> 16); //交换发送地址23~16位 MySPI_SwapByte(Address >> 8); //交换发送地址15~8位 MySPI_SwapByte(Address); //交换发送地址7~0位 MySPI_Stop(); //SPI终止 W25Q64_WaitBusy(); //等待忙 }

5:读取厂商ID和设备ID

void W25Q64_ReadID(uint8_t *MID, uint16_t *DID) { MySPI_Start(); //SPI起始 MySPI_SwapByte(W25Q64_JEDEC_ID); //交换发送读取ID的指令 *MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE); //交换接收MID,通过输出参数返回 *DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE); //交换接收DID高8位 *DID <<= 8; //高8位移到高位 *DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE); //或上交换接收DID的低8位,通过输出参数返回 MySPI_Stop(); //SPI终止 }

6:W25Q64读取数据

void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count) { uint32_t i; MySPI_Start(); //SPI起始 MySPI_SwapByte(W25Q64_READ_DATA); //交换发送读取数据的指令 MySPI_SwapByte(Address >> 16); //交换发送地址23~16位 MySPI_SwapByte(Address >> 8); //交换发送地址15~8位 MySPI_SwapByte(Address); //交换发送地址7~0位 for (i = 0; i < Count; i ++) //循环Count次 { DataArray[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE); //依次在起始地址后读取数据 } MySPI_Stop(); //SPI终止 }

硬件SPI读取W25Q64

模式选择:

连续传输:CS一直保持有效(低电平),一直时钟,一直收发。

非连续传输:每传一次数据,CS都会释放(拉高),一次通信结束。

区别在于:CS(片选)是否中途释放。

1:初始化

void MySPI_Init(void) { /*开启时钟*/ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); //开启SPI1的时钟 /*GPIO初始化*/ GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA4引脚初始化为推挽输出 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA5和PA7引脚初始化为复用推挽输出 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA6引脚初始化为上拉输入 /*SPI初始化*/ SPI_InitTypeDef SPI_InitStructure; //定义结构体变量 SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //模式,选择为SPI主模式 SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //方向,选择2线全双工 SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //数据宽度,选择为8位 SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //先行位,选择高位先行 SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128; //波特率分频,选择128分频 SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; //SPI极性,选择低极性 SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; //SPI相位,选择第一个时钟边沿采样,极性和相位决定选择SPI模式0 SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS,选择由软件控制 SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC多项式,暂时用不到,给默认值7 SPI_Init(SPI1, &SPI_InitStructure); //将结构体变量交给SPI_Init,配置SPI1 /*SPI使能*/ SPI_Cmd(SPI1, ENABLE); //使能SPI1,开始运行 /*设置默认电平*/ MySPI_W_SS(1); //SS默认高电平 }

2:交换数据

uint8_t MySPI_SwapByte(uint8_t ByteSend) { while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) != SET); //轮询查询数据寄存器空,空了再写入数据让它发 SPI_I2S_SendData(SPI1, ByteSend); //写入数据到发送数据寄存器,开始产生时序 while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) != SET); //轮询查询接收数据寄存器非空,非空表示数据接受完全 return SPI_I2S_ReceiveData(SPI1); //读取接收到的数据并返回 }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/12 18:28:04

深入解析vcpkg-tool:现代化C++依赖管理的核心引擎

深入解析vcpkg-tool&#xff1a;现代化C依赖管理的核心引擎 【免费下载链接】vcpkg-tool Components of microsoft/vcpkgs binary. 项目地址: https://gitcode.com/gh_mirrors/vc/vcpkg-tool vcpkg-tool是微软维护的vcpkg生态系统的核心二进制组件&#xff0c;专为C开发…

作者头像 李华
网站建设 2026/7/12 18:25:14

大模型正确的学习顺序,终于说清楚了!

引言&#xff1a;为什么需要正确的学习顺序&#xff1f; 在人工智能浪潮席卷全球的今天&#xff0c;大型语言模型&#xff08;LLM&#xff09;已成为技术领域最炙手可热的话题之一。无论是希望转型AI的开发者、寻求技术突破的研究者&#xff0c;还是希望将AI能力融入产品的产品…

作者头像 李华
网站建设 2026/7/12 18:19:26

PaddleOCR企业级部署架构深度解析:从边缘到云端的全栈技术方案

PaddleOCR企业级部署架构深度解析&#xff1a;从边缘到云端的全栈技术方案 【免费下载链接】PaddleOCR 飞桨多语言OCR工具包&#xff08;实用超轻量OCR系统&#xff0c;支持80种语言识别&#xff0c;提供数据标注与合成工具&#xff0c;支持服务器、移动端、嵌入式及IoT设备端的…

作者头像 李华
网站建设 2026/7/12 18:17:41

AllOrigins 终极指南:如何通过 API 轻松绕过同源策略限制

AllOrigins 终极指南&#xff1a;如何通过 API 轻松绕过同源策略限制 【免费下载链接】allOrigins :alien: Pull content from any page as JSON via API 项目地址: https://gitcode.com/gh_mirrors/al/allOrigins 在当今的 Web 开发世界中&#xff0c;跨域数据获取是一…

作者头像 李华
网站建设 2026/7/12 18:17:12

Rufus终极指南:轻松制作Windows/Linux启动盘的完整教程

Rufus终极指南&#xff1a;轻松制作Windows/Linux启动盘的完整教程 【免费下载链接】rufus The Reliable USB Formatting Utility 项目地址: https://gitcode.com/GitHub_Trending/ru/rufus Rufus是一款功能强大的开源USB启动盘制作工具&#xff0c;专为Windows和Linux系…

作者头像 李华