专栏:STM32F103入门教程
环境:Keil‑MDK + STM32F103 标准库3.5版本
一、SPI
SPI:Serial Peripheral Interface,串行外设接口,高速同步串行通信,常用于 MCU 和外设:Flash、OLED、ADC、DAC、传感器、之间通信。
同步:有单独时钟线,收发双方时钟保持一致,不需要波特率配置(对比 UART 异步)。
四根信号线(标准 SPI)
SCLK:串行时钟,由主机输出,时钟信号,决定通信速度
MOSI:主机输出,从机输入 → 主机发数据给外设
MISO:主机输入,从机输出 → 外设发数据给主机
CS / NSS:片选,片选信号,一般低电平有效,选中某一个从设备
主机:控制时钟的设备(一般是 MCU)
从机:被控制的外设(Flash、传感器)
接线记忆
MOSI:Master Out Slave In 主机出‑从机进
MISO:Master In Slave Out 主机进‑从机出
多设备连接方式
1)独立片选(最常用)
SCLK、MOSI、MISO 全部并联到所有从机;每个从机使用独立 CS 引脚。
主机想和哪个设备通信,就把对应 CS 拉低,其余 CS 保持高。
SPI 4 种模式(CPOL、CPHA)
两个参数,决定时钟空闲电平、在哪一个边沿采样数据
CPOL:时钟极性
CPOL=0:空闲时 SCLK = 低电平
CPOL=1:空闲时 SCLK = 高电平
CPHA:时钟相位,数据采样边沿
CPHA=0:第一个时钟边沿采样数据
CPHA=1:第二个时钟边沿采样数据
组合 4 种模式:
Mode0:CPOL=0, CPHA=0 → 最常用(Flash、OLED 大多用模式 0)
Mode1:CPOL=0, CPHA=1
Mode2:CPOL=1, CPHA=0
Mode3:CPOL=1, CPHA=1
⚠️主机和从机必须模式一致,否则收到乱码。
通信过程(Mode0 举例)
主机把对应从机 CS 拉低,选中设备
主机产生 SCLK 时钟;在每个时钟周期,MOSI 发送 1bit,同时 MISO 接收 1bit
SPI 是全双工:发数据的同时也在收数据,8bit/16bit 为一组交换;你即使只想读数据,主机也要输出 dummy(填充)字节。
收发完成,CS 拉高释放从机。
关键点:SPI 没有应答 ACK,没有校验;你发出去,不知道对方有没有收到。对比 I2C 有应答。
工程常见坑
模式不对,数据全乱,Mode0 最多;买外设模块优先确认 SPI 模式。
SPI 全双工:读寄存器时,主机必须发送 dummy 字节,才会收到从机返回数据。
CS 时序:必须通信前拉低,通信结束再拉高;不要一直拉低。
高速 SPI,布线要短,远离开关电源噪声,否则丢数据。
SPI 没有硬件 CRC 校验,可靠性要求高时软件加校验。
SPI 对比 I2C、UART
| 对比项 | SPI | I2C | UART |
|---|---|---|---|
| 全称 | Serial Peripheral Interface | Inter-Integrated Circuit | Universal Asynchronous Receiver/Transmitter |
| 信号线 | 4 根(SCK、MOSI、MISO、CS),多从机时 CS 需增加 | 2 根(SDA 数据线、SCL 时钟线) | 2 根(TX 发送、RX 接收),可共用 GND |
| 同步/异步 | 同步(依赖 SCK 时钟) | 同步(依赖 SCL 时钟) | 异步(无时钟线,靠双方约定波特率) |
| 双工模式 | 全双工(同时收发) | 半双工(SDA 线分时收发) | 全双工(TX/RX 独立,可同时收发) |
| 典型速率 | 最快,可达 几十 MHz | 中等,标准 100 kHz,快速 400 kHz,高速 3.4 MHz | 较慢,通常 9600 ~ 921600 bps(少数可达 Mbps) |
| 主从关系 | 一主多从,主机控制时钟和片选 | 多主多从,支持总线仲裁 | 无主从,点对点平等通信 |
| 设备寻址 | 硬件片选线(CS/SS)选中从机 | 软件 7 位或 10 位设备地址 | 无寻址,直接点对点连接 |
| 应答机制 | 无硬件 ACK(通常需软件确认) | 有 ACK/NACK(每字节都有应答) | 无硬件 ACK(可选软件校验,如校验和) |
| 物理层电平 | TTL/CMOS(通常 3.3V/5V) | 开漏输出,需外接上拉电阻 | TTL/CMOS(通常 3.3V/5V)或 RS-232 电平 |
| 传输距离 | 最短(板级通信,<30 cm) | 较短(板级或短距离,<1 m) | 较长(TTL 几米;RS-232 可达 15 m;RS-485 可达 1 km+) |
| 协议复杂度 | 简单(无起始/停止位,纯数据流) | 较复杂(起始/停止/应答/仲裁) | 最简单(只需约定波特率、数据位、校验、停止位) |
| 多设备连接 | 需多根 CS 线,扩展性一般 | 总线型,扩展性好(最多 127/1023 个设备) | 只能点对点,多设备需额外切换电路 |
| 数据帧格式 | 无固定帧格式,连续时钟移位 | 起始位 + 地址 + 数据 + ACK + 停止位 | 起始位 + 数据位 + 校验位 + 停止位 |
| 硬件开销 | 引脚多,但无需上拉电阻 | 引脚少,但需外接上拉电阻 | 引脚少,最简单 |
| 典型应用 | Flash、显示屏、SD 卡、高速传感器 | EEPROM、RTC、温湿度传感器、OLED | 蓝牙模块、GPS、调试串口、RS-485 工业总线 |
二、W25QXX
W25QXX 系列 SPI‑NOR Flash 简介
W25QXX 是华邦 Winbond 的串行 NOR Flash,广泛用于单片机外挂存储,保存配置、日志、字库、固件镜像;充电桩、工业设备大量使用。
注意:单位 Mbit(兆比特),除以 8 才是 MB(兆字节)。
1 基础参数
供电:2.7‑3.6V(3.3V 系统),部分子系列支持 1.8V
接口:标准 SPI(Mode0/Mode3)、Dual‑SPI 双路、Quad‑SPI 四路;标准 SPI 最高 80‑104MHz华邦电子
擦写寿命:每个扇区典型 10 万次;断电数据保存 20 年电子工程世…
封装:SOIC‑8、WSON、PDIP;最常用 SOIC‑8(如 W25Q80DVSIG)
功耗:工作电流几 mA.
2 存储组织结构(全系列统一)
页 Page:256 字节:编程写入最小单元,一次页编程最多写 256 字节;超过 256 会页回绕覆盖开头。
扇区 Sector:4KB:最小擦除单元,只能按扇区擦除(指令 0x20)。
块 Block:32KB / 64KB:大块擦除(0x52、0xD8)。
⚠️NOR Flash 特性:bit 只能 1→0;要把 0 变回 1,必须执行擦除,擦完该区域全部变成 0xFF,不能字节直接擦除
W25Q80 关键参数
| 参数 | 数值 |
|---|---|
| 容量 | 8M-bit = 1M-Byte |
| 页大小 | 256 Bytes |
| 扇区(Sector) | 4 KB(16 页) |
| 块(Block) | 32 KB / 64 KB |
| SPI 模式 | 支持 Mode 0 (0,0) 和 Mode 3 (1,1),Mode 0 最常用 |
| 时钟频率 | 标准读(03h) ≤50MHz;Fast Read 等 ≤80MHz (2.7V) / ≤104MHz (3.0V) |
| 供电 | 2.7V ~ 3.6V |
硬件连接(STM32F103 ↔ W25Q80)
| W25Q80 引脚 | 功能 | 接 STM32 |
|---|---|---|
| /CS (1) | 片选,低有效 | 任意 GPIO(软件控制,如 PA4) |
| DO/IO1 (2) | 数据输出 | SPI1_MISO (PA6) 或 SPI2_MISO (PB14) |
| /WP/IO2 (3) | 写保护 | 接 VCC(3.3V)或 GPIO |
| GND (4) | 地 | GND |
| DI/IO0 (5) | 数据输入 | SPI1_MOSI (PA7) 或 SPI2_MOSI (PB15) |
| CLK (6) | 时钟 | SPI1_SCK (PA5) 或 SPI2_SCK (PB13) |
| /HOLD/IO3 (7) | 暂停 | 接 VCC(3.3V)或 GPIO |
| VCC (8) | 电源 | 3.3V |

核心指令集
指令码 名称 关键说明
0x06 WREN 写使能 写 / 擦除 / 写状态寄存器前必须发,WEL 置 1
0x04 WRDI 写禁止
0x05 RDSR1 读状态寄存器 1 bit0=BUSY 忙位;bit1=WEL 写使能锁存
0x35 RDSR2 读状态寄存器 2 包含 QE (Quad 使能位)
0x01 写状态寄存器 BV/DV 版本:后面跟 SR1、SR2 两字节;RV 版本分开 0x01 和 0x31
0x03 READ 读数据 24bit 地址,连续读,无需写使能
0x02 PAGE PROG 页编程 最多写 256 字节;写前必须擦除
0x20 SE 扇区擦除 4KB,擦完全部 0xFF
0xD8 BE64K 块擦除 64KB
0xC7 CE 全片擦除 耗时很长,数秒,尽量少用
0x9F RDID 读 JEDEC ID 识别芯片型号,驱动初始化必做
三、STM32F103 SPI 设置相关
3.1 SPI 初始化结构体 SPI_InitTypeDef
typedef struct
{
uint16_t SPI_Direction; /*!< Specifies the SPI unidirectional or bidirectional data mode.
This parameter can be a value of @ref SPI_data_direction */
uint16_t SPI_Mode; /*!< Specifies the SPI operating mode.
This parameter can be a value of @ref SPI_mode */
uint16_t SPI_DataSize; /*!< Specifies the SPI data size.
This parameter can be a value of @ref SPI_data_size */
uint16_t SPI_CPOL; /*!< Specifies the serial clock steady state.
This parameter can be a value of @ref SPI_Clock_Polarity */
uint16_t SPI_CPHA; /*!< Specifies the clock active edge for the bit capture.
This parameter can be a value of @ref SPI_Clock_Phase */
uint16_t SPI_NSS; /*!< Specifies whether the NSS signal is managed by
hardware (NSS pin) or by software using the SSI bit.
This parameter can be a value of @ref SPI_Slave_Select_management */
uint16_t SPI_BaudRatePrescaler; /*!< Specifies the Baud Rate prescaler value which will be
used to configure the transmit and receive SCK clock.
This parameter can be a value of @ref SPI_BaudRate_Prescaler.
@note The communication clock is derived from the master
clock. The slave clock does not need to be set. */
uint16_t SPI_FirstBit; /*!< Specifies whether data transfers start from MSB or LSB bit.
This parameter can be a value of @ref SPI_MSB_LSB_transmission */
uint16_t SPI_CRCPolynomial; /*!< Specifies the polynomial used for the CRC calculation. */
}SPI_InitTypeDef;
重要参数选项:
SPI_Direction 通信方向:全双工/单工
SPI_Direction_2Lines_FullDuplex 双线全双工(最常用)
SPI_Direction_2Lines_RxOnly 双线只接收
SPI_Direction_1Line_Tx 单线发送
SPI_Direction_1Line_Rx 单线接收
SPI_Mode SPI主机 / SPI从机
SPI_Mode_Master 主机模式 ✅
SPI_Mode_Slave 从机模式
SPI_DataSize 数据宽度 8bit /16bit
SPI_DataSize_8b 8 位(绝大多数外设)
SPI_DataSize_16b 16 位
SPI_CPOL CPOL 时钟极性
SPI_CPOL_Low CPOL=0,空闲 SCLK 低
SPI_CPOL_High CPOL=1,空闲 SCLK 高
SPI_CPHA CPHA 时钟相位
SPI_CPHA_1Edge CPHA=0,第一个边沿采样(Mode0)
SPI_CPHA_2Edge CPHA=1,第二个边沿采样
SPI Mode0:CPOL_Low + CPHA_1Edge
SPI_NSS NSS:硬件NSS / 软件NSS
SPI_NSS_Soft 软件 NSS(推荐):NSS 引脚作为普通 GPIO,软件控制 CS,内部 NSS 信号由软件管理
SPI_NSS_Hard 硬件 NSS:由硬件引脚自动管理 NSS,主机模式下很少用,容易踩坑。
SPI_BaudRatePrescaler 波特率预分频,SCLK = PCLK / 预分频
SPI1 时钟来自 APB2 (72M):
SPI_BaudRatePrescaler_2 36M;
Prescaler_4 18M;
Prescaler_8 9M;
Prescaler_16 4.5M;
Prescaler_32 2.25M;
Prescaler_64 1.125M;
Prescaler_128 562.5K;
Prescaler_256 281.2K.
外设速度不能太高,Flash 一般用 9M/4.5M,如果用杜邦线接使用最低频率,256分频
SPI_FirstBit MSB先行 / LSB先行
SPI_FirstBit_MSB 高位先发(绝大多数器件)
SPI_FirstBit_LSB 低位先发
SPI_CRCPolynomial CRC 多项式(通常不用)
核心库函数列表
1)初始化函数
void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct)
功能:按照结构体配置 SPI 寄存器。
2)使能关闭 SPI 外设
void SPI_Cmd(SPI_TypeDef* SPIx, FunctionalState NewState);
SPI_Cmd(SPI1, ENABLE); 打开 SPI;DISABLE关闭。
3)数据读写函数(最核心)
void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data)
向 SPI DR 寄存器写入数据,开始移位发送。SPI 是全双工,发送同时也会接收。
uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx)
读取 DR 寄存器里面已经收到的数据。
⚠️F1 标准库没有封装 “同时收发” 函数,需要自己判断标志位实现。
4)状态标志位读取
FlagStatus SPI_I2S_GetFlagStatus(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG)
常用标志:
SPI_I2S_FLAG_TXE:发送寄存器为空,可以发送下一个数据 ✅
SPI_I2S_FLAG_RXNE:接收寄存器非空,已经收到数据 ✅
SPI_I2S_FLAG_BSY:SPI 忙标志!非常重要,BSY=1 代表通信还在进行,不能关闭 SPI,不能 CS 拉高。
void SPI_I2S_ClearFlag(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG)
清除标志位;TXE/RXNE 硬件自动清零,不需要手动清。
四、工程与程序编写
4.1工程复制
复制I2C工程文件夹为SPI文件夹,I2C.uvprojx复制为SPI.uvprojx。
4.2 添加驱动文件
- Hardware文件夹下新建Spi和W25qxx文件夹。分别添加spi.h,spi.c,w25qxx.h,w25qxx.c文件。
spi.h
#ifndef __SPI_H
#define __SPI_H
#include "stm32f10x.h"
// SPI1 软件NSS引脚 PA4
#define SPI1_CS_PIN GPIO_Pin_4
#define SPI1_CS_PORT GPIOA
// 软件CS控制
#define SPI1_CS_LOW() GPIO_ResetBits(SPI1_CS_PORT, SPI1_CS_PIN)
#define SPI1_CS_HIGH() GPIO_SetBits(SPI1_CS_PORT, SPI1_CS_PIN)
void spi1_init(void);
uint8_t spi1_read_write_byte(uint8_t dat);
#endif
spi.c
#include "spi.h"
/**
* @brief SPI1初始化 软件NSS Mode0 8bit主机
*/
void spi1_init(void)
{
GPIO_InitTypeDef gpio_init_struct;
SPI_InitTypeDef spi_init_struct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE);
// PA5 SCK AF_PP ; PA7 MOSI AF_PP
gpio_init_struct.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
gpio_init_struct.GPIO_Mode = GPIO_Mode_AF_PP;
gpio_init_struct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &gpio_init_struct);
// PA6 MISO 浮空输入
gpio_init_struct.GPIO_Pin = GPIO_Pin_6;
gpio_init_struct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &gpio_init_struct);
// PA4 CS 软件NSS 普通推挽输出
gpio_init_struct.GPIO_Pin = SPI1_CS_PIN;
gpio_init_struct.GPIO_Mode = GPIO_Mode_Out_PP;
gpio_init_struct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(SPI1_CS_PORT, &gpio_init_struct);
SPI1_CS_HIGH(); // 默认片选拉高
// SPI参数配置
spi_init_struct.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
spi_init_struct.SPI_Mode = SPI_Mode_Master;
spi_init_struct.SPI_DataSize = SPI_DataSize_8b;
spi_init_struct.SPI_CPOL = SPI_CPOL_Low; // Mode0 空闲 SCLK 低,
spi_init_struct.SPI_CPHA = SPI_CPHA_1Edge; // 第一个边沿采样
spi_init_struct.SPI_NSS = SPI_NSS_Soft; // 软件NSS,不用硬件NSS
spi_init_struct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; //72M/256
spi_init_struct.SPI_FirstBit = SPI_FirstBit_MSB;
spi_init_struct.SPI_CRCPolynomial = 7;
SPI_Init(SPI1, &spi_init_struct);
SPI_Cmd(SPI1, ENABLE);
}
/**
* @brief SPI1读写1字节,全双工
* @param dat:要发送的数据
* @retval 收到的数据
*/
uint8_t spi1_read_write_byte(uint8_t dat)
{
u16 count = 0;
while((SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) ) == RESET){
count++;
if(count>1000){//防止程序卡死
return 0;
}
}
SPI_I2S_SendData(SPI1, dat);
count = 0;
while((SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) ) == RESET){
if(count>1000){//防止程序卡死
return 0;
}
}
return SPI_I2S_ReceiveData(SPI1);
}
w25qxx.h
#ifndef __W25QXX_H
#define __W25QXX_H
#include "stm32f10x.h"
// W25Qxx 指令
#define W25QXX_CMD_WREN 0x06 //写使能
#define W25QXX_CMD_WRDI 0x04 //写禁止
#define W25QXX_CMD_RDSR1 0x05 //读状态寄存器1
#define W25QXX_CMD_RDSR2 0x35 //读状态寄存器2
#define W25QXX_CMD_WRSR 0x01 //写状态寄存器(BV/DV:后接SR1,SR2两字节)
#define W25QXX_CMD_READ 0x03 //读数据
#define W25QXX_CMD_FASTREAD 0x0B //快速读
#define W25QXX_CMD_PAGEPROG 0x02 //页编程
#define W25QXX_CMD_SECTORERASE 0x20 //4K扇区擦除
#define W25QXX_CMD_BLK64ERASE 0xD8 //64K块擦除
#define W25QXX_CMD_CHIPERASE 0xC7 //整片擦除
#define W25QXX_CMD_JEDECID 0x9F //读JEDEC ID
// W25Q80 参数 8Mbit = 1MByte
#define W25Q80_PAGE_SIZE 256U
#define W25Q80_SECTOR_SIZE 4096U
#define W25Q80_SIZE (1024U*1024U)
uint16_t w25qxx_read_jedec_id(void);
void w25qxx_wait_busy(void);
void w25qxx_write_enable(void);
void w25qxx_sector_erase(uint32_t addr);
void w25qxx_page_write(uint32_t addr, uint8_t *pbuf, uint16_t len);
void w25qxx_read(uint32_t addr, uint8_t *pbuf, uint16_t len);
#endif
w25qxx.c
#include "w25qxx.h"
#include "spi.h"
#include "delay.h" //需要系统延时
/**
* @brief 等待芯片BUSY位清零
*/
void w25qxx_wait_busy(void)
{
uint8_t sr;
while(1)
{
SPI1_CS_LOW();
spi1_read_write_byte(W25QXX_CMD_RDSR1);
sr = spi1_read_write_byte(0xFF);
SPI1_CS_HIGH();
if( (sr & 0x01) == 0 )
break;
delay_ms(1);
}
}
/**
* @brief 写使能
*/
void w25qxx_write_enable(void)
{
SPI1_CS_LOW();
spi1_read_write_byte(W25QXX_CMD_WREN);
SPI1_CS_HIGH();
}
/**
* @brief 读JEDEC ID
* @retval 返回高字节+低字节,W25Q80返回0x4014
*/
uint16_t w25qxx_read_jedec_id(void)
{
uint16_t id;
uint8_t mfr,memtype,cap;
SPI1_CS_LOW();
spi1_read_write_byte(W25QXX_CMD_JEDECID);
mfr = spi1_read_write_byte(0xFF);
memtype= spi1_read_write_byte(0xFF);
cap = spi1_read_write_byte(0xFF);
SPI1_CS_HIGH();
id = (memtype<<8) | cap;
return id;
}
/**
* @brief 扇区擦除 4K
* @param addr:扇区内任意地址
*/
void w25qxx_sector_erase(uint32_t addr)
{
addr = addr & (~0xFFFU); // 强制对齐到4KB扇区首地址,低12位清零
w25qxx_wait_busy();
w25qxx_write_enable();
SPI1_CS_LOW();
spi1_read_write_byte(W25QXX_CMD_SECTORERASE);
spi1_read_write_byte( (addr>>16) & 0xFF );
spi1_read_write_byte( (addr>>8) & 0xFF );
spi1_read_write_byte( addr & 0xFF );
SPI1_CS_HIGH();
w25qxx_wait_busy();
}
/**
* @brief 页写,一页最多256字节,不要跨页
* @param addr 起始地址
* @param pbuf 数据源
* @param len 1~256
*/
void w25qxx_page_write(uint32_t addr, uint8_t *pbuf, uint16_t len)
{
uint16_t i;
if(len ==0 || len>256) return;
w25qxx_wait_busy();
w25qxx_write_enable();
SPI1_CS_LOW();
spi1_read_write_byte(W25QXX_CMD_PAGEPROG);
spi1_read_write_byte( (addr>>16)&0xFF );
spi1_read_write_byte( (addr>>8)&0xFF );
spi1_read_write_byte( addr&0xFF );
for(i=0;i<len;i++)
{
spi1_read_write_byte(pbuf[i]);
}
SPI1_CS_HIGH();
w25qxx_wait_busy();
}
/**
* @brief 连续读数据
* @param addr 起始地址
* @param pbuf 接收缓存
* @param len 读取字节数
*/
void w25qxx_read(uint32_t addr, uint8_t *pbuf, uint16_t len)
{
uint16_t i;
SPI1_CS_LOW();
spi1_read_write_byte(W25QXX_CMD_READ);
spi1_read_write_byte( (addr>>16)&0xFF );
spi1_read_write_byte( (addr>>8)&0xFF );
spi1_read_write_byte( addr&0xFF );
for(i=0;i<len;i++)
{
pbuf[i] = spi1_read_write_byte(0xFF);
}
SPI1_CS_HIGH();
}
main.c
#include "stm32f10x.h"
#include "led.h"
#include "delay.h"
#include "key.h"
#include "usart.h"
#include "exti.h"
#include "time.h"
#include <stdio.h>
#include "i2c_sim.h"
#include "at24c02.h"
#include "spi.h"
#include "w25qxx.h"
u8 flash_read_buffer[16]={0};
u8 flash_write_buffer[16]={"w25q80 test!"};
int main(void)
{
u16 data16,i=0;
u32 add =0;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);// 设置中断优先级分组4
led_init();
delay_init();
usart1_init(115200);
//i2c_sim_init();
//at24c02_test();
spi1_init();
printf("w25q80 test \r\n");
data16 = w25qxx_read_jedec_id();
printf("0x %04x %d \r\n",data16,data16);
w25qxx_read(add,flash_read_buffer,16);
printf("写之前读 %s\r\n",flash_read_buffer);
while(1)
{
delay_ms(1000);
i++;
if(i==10){
//w25qxx_sector_erase(add);//第一次写可以,写过一次后要先擦除才能写
w25qxx_page_write(add,flash_write_buffer,sizeof(flash_write_buffer));
w25qxx_read(add,flash_read_buffer,sizeof(flash_read_buffer));
printf("写之后读 %s\r\n",flash_read_buffer);
return 0;
}
}
}
/*
w25q80 test
0x 4014 16404
写之前读 ????????????????
写之后读 w25q80 test!
*/
4.3 添加库文件,工程文件,和文件包含路径



4.4 编译烧录



串口助手设置的参数要和程序里面一致,选择串口要正确(插拨一下看电脑里面的设置管理器里面的端口)。
启动会打印W25Q80的id号,判断出型号确实是对的。下面是读和写的测试,可以得出写入和读出一致,写入成功了!
关于spi和w25qxx的内容还是很多的,上面只是简要介绍,还是要看芯片手册!
资料下载
参考资料网盘 :
链接 : https://pan.baidu.com/s/1BrH0JbUY6z7j7gAihTa4Dw
提取码: qnws
本节课完整工程例程网盘:
链接: https://pan.baidu.com/s/1rl6EIqi9m6kQ1JFjuSqINg
提取码: bumc

1万+

被折叠的 条评论
为什么被折叠?



