目录
基础知识回顾:
SPI外设简介
SPI框图
主模式全双工连续传输
非连续传输
初始化SPI外设
核心代码 - 交换一个字节
硬件接线图
Code
程序配置过程
MySPI.c
MySPI.h
W25Q64.c
W25Q64.h
W25Q64_Ins.h
main.c
【STM32】SPI通信
【STM32】软件SPI读写W25Q64芯片
学习视频:【STM32入门教程-2023版 细致讲解 中文字幕】 https://www.bilibili.com/video/BV1th411z7sn/?p=39&share_source=copy_web&vd_source=8af85e60c2df9af1f0fd23935753a933
简化结构
TDR、RDR、TXE、RXNE几个关键词
非连续传输的整体步骤:
第一个字节
- 等待TXE为1
- 写入发送的数据至TDR
- 等待RXNE为1
- 读取RDR接收的数据
- 交换下一个字节,重复上述4步骤!!!
第二个字节
- 等待TXE为1
- 写入发送的数据至TDR
- 等待RXNE为1
- 读取RDR接收的数据
- 交换下一个字节,重复上述4步骤!!!
第...个字节
……
我们可以将标红的4步骤封装成一个函数,每调用一次,就交换一个字节!妙哉~妙哉~
讲解看注释即可
/**
* 函 数:SPI初始化
* 参 数:无
* 返 回 值:无
* 注意事项:此函数需要用户实现内容,实现SS、SCK、MOSI和MISO引脚的初始化
*/
void MySPI_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); //开启SPI1的时钟
/*GPIO初始化*/
//SS = PA4 从机选择引脚
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引脚初始化为推挽输出
//SCK = GPIO_Pin_5、MOSI = GPIO_Pin_7;
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引脚初始化为复用推挽输出
//MISO = PA6 上拉输入
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_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //双线全双工模式
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //8位数据帧
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //高位先行
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128; //波特率预分频器,目前时钟频率 = 72MHz/128 = 562.5KHz,如果是SPI2的外设,就是用36M/128
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; //模式0,CPOL时钟极性,空闲默认低电平
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;//时钟相位,选择1Edge就是CPHA=0,第一个边沿采样
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //外设的NSS引脚,一般用不到,所以就选软件模式
SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC校验的多项式,我们用不到,就填默认值7
SPI_Init(SPI1, &SPI_InitStructure);
//使能SPI外设
SPI_Cmd(SPI1, ENABLE);
// /*设置默认电平*/
MySPI_W_SS(1); // SS置高, 默认不选中从机
// MySPI_W_SCK(0); // 计划使用模式0, 默认低电平
// MOSI 没有明确规定,MISO是输入引脚,不用输出电平状态
}
/**
* 函 数:SPI交换传输一个字节,使用SPI模式0
* 参 数:ByteSend 要发送的一个字节
* 返 回 值:接收的一个字节
*/
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
while ( SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) != SET ); //等待TXE为1
SPI_I2S_SendData(SPI1, ByteSend); //ByteSend写入到TDR寄存器,之后自动转入移位寄存器,一旦寄存器有数据了,时序波形自动产生
while ( SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) != SET ); //等待RXNE=1,表示收到一个字节,也表示发送时序产生完成
return SPI_I2S_ReceiveData(SPI1); //读取RDR
}
硬件接线和上一篇文章相同
SPI相关库函数
void SPI_I2S_DeInit(SPI_TypeDef* SPIx);
void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct);
void SPI_Cmd(SPI_TypeDef* SPIx, FunctionalState NewState);
void SPI_I2S_ITConfig(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT, FunctionalState NewState);
void SPI_I2S_DMACmd(SPI_TypeDef* SPIx, uint16_t SPI_I2S_DMAReq, FunctionalState NewState);
void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);
uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx);
void SPI_DataSizeConfig(SPI_TypeDef* SPIx, uint16_t SPI_DataSize);
FlagStatus SPI_I2S_GetFlagStatus(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG);
void SPI_I2S_ClearFlag(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLyinAG);
ITStatus SPI_I2S_GetITStatus(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT);
void SPI_I2S_ClearITPendingBit(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT);
代码只需要再上一篇文章稍作修改即可
W25Q64的C文件和H文件内容都没有变,只修改了MySPI.c文件
根据上述非连续传输的步骤,编写代码,废话就不讲了,直接看代码注释
①配置相关引脚的复用功能,使能SPIx时钟
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);
②初始化SPIx,设置SPIx工作模式
void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct);
③使能SPIx
void SPI_Cmd(SPI_TypeDef* SPIx, FunctionalState NewState);
④SPI传输数据
void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);
uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx) ;
⑤查看SPI传输状态
SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE)
————————————————版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/weixin_53762042/article/details/117134887
#include "MySPI.h"
/*引脚配置层*/
/*硬件SPI*/
/**
* 函 数:SPI写SS引脚电平
* 参 数:BitValue 协议层传入的当前需要写入SS的电平,范围0~1
* 返 回 值:无
* 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SS为低电平,当BitValue为1时,需要置SS为高电平
*/
void MySPI_W_SS(uint8_t BitValue) //CS引脚(SS引脚)PA4,从机选择引脚,还是用软件模拟的
{
GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue); //根据BitValue,设置SS引脚的电平
}
// SPI速度非常快,操作完引脚,就不需要加延时了
/*
输出引脚配置为推挽输出,输入引脚配置为浮空或上拉输入
对主机来说,时钟、主机输出、片选都是输出引脚---推挽输出
主机输入MISO---输出引脚---选择上拉输入
从机(W25Q64)的DO输出,是主机输入---PA6
*/
/**
* 函 数:SPI初始化
* 参 数:无
* 返 回 值:无
* 注意事项:此函数需要用户实现内容,实现SS、SCK、MOSI和MISO引脚的初始化
*/
void MySPI_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); //开启SPI1的时钟
/*GPIO初始化*/
//SS = PA4 从机选择引脚
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引脚初始化为推挽输出
//SCK = GPIO_Pin_5、MOSI = GPIO_Pin_7;
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引脚初始化为复用推挽输出
//MISO = PA6 上拉输入
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_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //双线全双工模式
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //8位数据帧
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //高位先行
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128; //波特率预分频器,目前时钟频率 = 72MHz/128 = 562.5KHz,如果是SPI2的外设,就是用36M/128
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; //模式0,CPOL时钟极性,空闲默认低电平
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;//时钟相位,选择1Edge就是CPHA=0,第一个边沿采样
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //外设的NSS引脚,一般用不到,所以就选软件模式
SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC校验的多项式,我们用不到,就填默认值7
SPI_Init(SPI1, &SPI_InitStructure);
//使能SPI外设
SPI_Cmd(SPI1, ENABLE);
// /*设置默认电平*/
MySPI_W_SS(1); // SS置高, 默认不选中从机
// MySPI_W_SCK(0); // 计划使用模式0, 默认低电平
// MOSI 没有明确规定,MISO是输入引脚,不用输出电平状态
}
/*协议层*/
/**
* 函 数:SPI起始
* 参 数:无
* 返 回 值:无
*/
void MySPI_Start(void)
{
MySPI_W_SS(0); //拉低SS,开始时序
}
/**
* 函 数:SPI终止
* 参 数:无
* 返 回 值:无
*/
void MySPI_Stop(void)
{
MySPI_W_SS(1); //拉高SS,终止时序
}
//交换一个字节(读写一个字节),W25Q64系列,支持模式0和模式3
//这里选择模式0
//ByteSend是传进来的参数,通过交换一个字节发送出去,接受
//硬件SPI - 发送同时接收
/**
* 函 数:SPI交换传输一个字节,使用SPI模式0
* 参 数:ByteSend 要发送的一个字节
* 返 回 值:接收的一个字节
*/
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
while ( SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) != SET ); //等待TXE为1
SPI_I2S_SendData(SPI1, ByteSend); //ByteSend写入到TDR寄存器,之后自动转入移位寄存器,一旦寄存器有数据了,时序波形自动产生
while ( SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) != SET ); //等待RXNE=1,表示收到一个字节,也表示发送时序产生完成
return SPI_I2S_ReceiveData(SPI1); //读取RDR
}
#ifndef __MYSPI_H__
#define __MYSPI_H__
#include "stm32f10x.h" // Device header
void MySPI_Init(void);
void MySPI_Start(void);
void MySPI_Stop(void);
uint8_t MySPI_SwapByte(uint8_t ByteSend);
#endif
#include "W25Q64.h"
void W25Q64_Init(void)
{
MySPI_Init();
}
/*读取ID,第一个字节:厂商ID。设备ID:第二个字节:存储器类型;第三个字节:容量*/
/**
* 函 数:W25Q64读取ID号
* 参 数:MID 工厂ID,使用输出参数的形式返回
* 参 数:DID 设备ID,使用输出参数的形式返回
* 返 回 值:无
*/
void W25Q64_ReadID(uint8_t *MID, uint16_t *DID)
{
MySPI_Start();
MySPI_SwapByte(W25Q64_JEDEC_ID); // 0x9F, 读取ID号码指令,这里的返回值没有意义,就不需要了
*MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE); // 这次交换,把数据给主机,接收的数据是厂商ID变量*MID,发送的数据任意给,一般给0xFF
// 这里是在通信,通信是有时序的,不同时间调用相同的函数,意义就是不一样的
*DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE); // 设备ID的高八位(第三次交换)
*DID <<= 8; // 高八位移到左边
*DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE); // 设备ID的低八位(用或运算,整合数据)
MySPI_Stop();
}
/**
* 函 数:W25Q64写使能
* 参 数:无
* 返 回 值:无
*/
void W25Q64_WriteEnable(void)
{
MySPI_Start(); //SPI起始
MySPI_SwapByte(W25Q64_WRITE_ENABLE); //交换发送 写使能的指令
MySPI_Stop(); //SPI终止
}
// 发送指令码05,发完指令码,读取状态寄存器,查看是否是忙状态,最低位BUSY,1是忙,0是不忙
/**
* 函 数:W25Q64等待忙
* 参 数:无
* 返 回 值:无
*/
void W25Q64_WaitBusy(void) // 等待busy位为0
{
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终止
}
/*注意:
W25Q64_WaitBusy,事前等待&事后等待
事后等待只需要再写入操作前调用;
事前等待在写入操作和读取操作之前都得调用
*/
/**
* 函 数:W25Q64页编程
* 参 数:Address 页编程的起始地址,范围:0x000000~0x7FFFFF
* 参 数:DataArray 用于写入数据的数组(指针传递数组)
* 参 数:Count 要写入数据的数量,范围:0~256
* 返 回 值:无
* 注意事项:写入的地址范围不能跨页
*/
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(); //等待忙,事后等待比较保险
}
/**
* 函 数:W25Q64扇区擦除(4KB)
* 参 数:Address 指定扇区的地址,范围:0x000000~0x7FFFFF
* 返 回 值:无
*/
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(); //等待忙,不忙就退出这个函数了
}
/**
* 函 数:W25Q64读取数据
* 参 数:Address 读取数据的起始地址,范围:0x000000~0x7FFFFF
* 参 数:DataArray 用于接收读取数据的数组,通过输出参数返回
* 参 数:Count 要读取数据的数量,范围:0~0x800000
* 返 回 值:无
*/
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终止
}
#ifndef __W25Q64_H__
#define __W25Q64_H__
#include "stm32f10x.h" // Device header
#include "MySPI.h"
#include "W25Q64_Ins.h" //指令的头文件
void W25Q64_Init(void);
void W25Q64_ReadID(uint8_t *MID, uint16_t *DID);
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count);
void W25Q64_SectorErase(uint32_t Address);
void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count);
#endif
#ifndef __W25Q64_INS_H
#define __W25Q64_INS_H
#define W25Q64_WRITE_ENABLE 0x06 //写使能
#define W25Q64_WRITE_DISABLE 0x04 //写失能
#define W25Q64_READ_STATUS_REGISTER_1 0x05 //读状态寄存器1
#define W25Q64_READ_STATUS_REGISTER_2 0x35
#define W25Q64_WRITE_STATUS_REGISTER 0x01
#define W25Q64_PAGE_PROGRAM 0x02 //页编程
#define W25Q64_QUAD_PAGE_PROGRAM 0x32
#define W25Q64_BLOCK_ERASE_64KB 0xD8
#define W25Q64_BLOCK_ERASE_32KB 0x52
#define W25Q64_SECTOR_ERASE_4KB 0x20 //扇区擦除
#define W25Q64_CHIP_ERASE 0xC7
#define W25Q64_ERASE_SUSPEND 0x75
#define W25Q64_ERASE_RESUME 0x7A
#define W25Q64_POWER_DOWN 0xB9
#define W25Q64_HIGH_PERFORMANCE_MODE 0xA3
#define W25Q64_CONTINUOUS_READ_MODE_RESET 0xFF
#define W25Q64_RELEASE_POWER_DOWN_HPM_DEVICE_ID 0xAB
#define W25Q64_MANUFACTURER_DEVICE_ID 0x90
#define W25Q64_READ_UNIQUE_ID 0x4B
#define W25Q64_JEDEC_ID 0x9F //读取ID号
#define W25Q64_READ_DATA 0x03
#define W25Q64_FAST_READ 0x0B
#define W25Q64_FAST_READ_DUAL_OUTPUT 0x3B
#define W25Q64_FAST_READ_DUAL_IO 0xBB
#define W25Q64_FAST_READ_QUAD_OUTPUT 0x6B
#define W25Q64_FAST_READ_QUAD_IO 0xEB
#define W25Q64_OCTAL_WORD_READ_QUAD_IO 0xE3
#define W25Q64_DUMMY_BYTE 0xFF //无用数据
#endif
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "W25Q64.h"
uint8_t MID;
uint16_t DID;
uint8_t ArrayWrite[] = {0x01, 0x02, 0x03, 0x04}; //定义要写入数据的测试数组
//uint8_t ArrayWrite[] = {0x11, 0x22, 0x33, 0x44}; //定义要写入数据的测试数组
uint8_t ArrayRead[4]; //定义要读取数据的测试数组
int main(void)
{
/*模块初始化*/
OLED_Init(); //OLED初始化
W25Q64_Init(); //W25Q64初始化
/*显示静态字符串*/
OLED_ShowString(1, 1, "MID: DID:");
OLED_ShowString(2, 1, "W:");
OLED_ShowString(3, 1, "R:");
/*显示ID号*/
W25Q64_ReadID(&MID, &DID); //获取W25Q64的ID号,指针 返回输出参数
OLED_ShowHexNum(1, 5, MID, 2); //显示MID,显示厂商ID
OLED_ShowHexNum(1, 12, DID, 4); //显示DID,显示设备ID
/*W25Q64功能函数测试*/
W25Q64_SectorErase(0x000000); //扇区擦除(写之前先进行扇区擦除操作)
W25Q64_PageProgram(0x000000, ArrayWrite, 4); //将写入数据的测试数组写入到W25Q64中
W25Q64_ReadData(0x000000, ArrayRead, 4); //读取刚写入的测试数据到读取数据的测试数组中
/*显示数据*/
OLED_ShowHexNum(2, 3, ArrayWrite[0], 2); //显示写入数据的测试数组
OLED_ShowHexNum(2, 6, ArrayWrite[1], 2);
OLED_ShowHexNum(2, 9, ArrayWrite[2], 2);
OLED_ShowHexNum(2, 12, ArrayWrite[3], 2);
OLED_ShowHexNum(3, 3, ArrayRead[0], 2); //显示读取数据的测试数组
OLED_ShowHexNum(3, 6, ArrayRead[1], 2);
OLED_ShowHexNum(3, 9, ArrayRead[2], 2);
OLED_ShowHexNum(3, 12, ArrayRead[3], 2);
while (1)
{
}
}
烧录代码,测试,OLED显示写入数据和读出数据一致~
还可以参考上一篇文章的测试步骤,一一尝试
参考测试的文章:
【STM32】软件SPI读写W25Q64芯片
原创笔记,码字不易,欢迎点赞,收藏~