一、MySPI.c最底层 SPI 驱动#include stm32f10x.h /************************* 硬件引脚操作层 *************************/ // 控制CS片选引脚PA4 void MySPI_W_SS(uint8_t BitValue) { GPIO_WriteBit(GPIOA, GPIO_Pin_4, BitValue); } // 控制SCK时钟引脚PA5 void MySPI_W_SCK(uint8_t BitValue) { GPIO_WriteBit(GPIOA, GPIO_Pin_5, BitValue); } // 控制MOSI引脚PA7主机输出从机输入 void MySPI_W_MOSI(uint8_t BitValue) { GPIO_WriteBit(GPIOA, GPIO_Pin_7, BitValue); } // 读取MISO引脚PA6主机输入从机输出 uint8_t MySPI_R_MISO(void) { return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6); } /************************* SPI初始化函数 *************************/ // 上电只调用一次配置SPI引脚工作模式 void MySPI_Init(void) { // 开启GPIOA时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; // 配置SS、SCK、MOSI为推挽输出 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); // 配置MISO为上拉输入 GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPU; GPIO_InitStructure.GPIO_Pin GPIO_Pin_6; GPIO_Init(GPIOA, GPIO_InitStructure); // SPI模式0默认状态SS拉高SCK拉低 MySPI_W_SS(1); MySPI_W_SCK(0); } /************************* SPI通信控制函数 *************************/ // 开始SPI通信拉低CS选中从机 void MySPI_Start(void) { MySPI_W_SS(0); } // 结束SPI通信拉高CS取消选中从机 void MySPI_Stop(void) { MySPI_W_SS(1); } /************************* 核心交换一个字节 *************************/ // SPI全双工通信发1个字节同时收1个字节 uint8_t MySPI_SwapByte(uint8_t ByteSend) { uint8_t i, ByteReceive 0x00; // 循环8次逐位收发 for(i 0; i 8; i) { // 发送当前位从最高位开始 MySPI_W_MOSI(!!(ByteSend (0x80 i))); // SCK拉高产生上升沿 MySPI_W_SCK(1); // 读取当前位 if(MySPI_R_MISO() 1) { ByteReceive | (0x80 i); } // SCK拉低产生下降沿 MySPI_W_SCK(0); } return ByteReceive; }二、W25Q64.cFlash 芯片驱动#include stm32f10x.h #include MySPI.h #include W25Q64_Ins.h /************************* W25Q64初始化 *************************/ void W25Q64_Init(void) { MySPI_Init(); } /************************* 读取芯片ID *************************/ // 验证芯片是否正常连接 void W25Q64_ReadID(uint8_t *MID, uint16_t *DID) { MySPI_Start(); // 发送读ID指令 MySPI_SwapByte(W25Q64_JEDEC_ID); // 接收3个字节的ID *MID MySPI_SwapByte(W25Q64_DUMMY_BYTE); *DID MySPI_SwapByte(W25Q64_DUMMY_BYTE); *DID 8; *DID | MySPI_SwapByte(W25Q64_DUMMY_BYTE); MySPI_Stop(); } /************************* 写使能 *************************/ // 所有写入/擦除操作前必须调用 void W25Q64_WriteEnable(void) { MySPI_Start(); MySPI_SwapByte(W25Q64_WRITE_ENABLE); MySPI_Stop(); } /************************* 等待忙 *************************/ // 所有写入/擦除操作后必须调用 void W25Q64_WaitBusy(void) { uint32_t Timeout; MySPI_Start(); MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1); Timeout 100000; // 循环等待忙标志位清零 while((MySPI_SwapByte(W25Q64_DUMMY_BYTE) 0x01) 0x01) { Timeout--; if(Timeout 0) break; } MySPI_Stop(); } /************************* 页编程写入数据 *************************/ // 最多写入256字节1页 void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count) { uint16_t i; // 先写使能 W25Q64_WriteEnable(); MySPI_Start(); // 发送页编程指令 MySPI_SwapByte(W25Q64_PAGE_PROGRAM); // 发送24位地址 MySPI_SwapByte(Address 16); MySPI_SwapByte(Address 8); MySPI_SwapByte(Address); // 循环发送数据 for(i 0; i Count; i) { MySPI_SwapByte(DataArray[i]); } MySPI_Stop(); // 等待写入完成 W25Q64_WaitBusy(); } /************************* 扇区擦除4KB *************************/ // 擦除指定地址所在的整个4KB扇区 void W25Q64_SectorErase(uint32_t Address) { W25Q64_WriteEnable(); MySPI_Start(); // 发送扇区擦除指令 MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB); // 发送24位地址 MySPI_SwapByte(Address 16); MySPI_SwapByte(Address 8); MySPI_SwapByte(Address); MySPI_Stop(); // 等待擦除完成 W25Q64_WaitBusy(); } /************************* 读取数据 *************************/ void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count) { uint32_t i; MySPI_Start(); // 发送读数据指令 MySPI_SwapByte(W25Q64_READ_DATA); // 发送24位地址 MySPI_SwapByte(Address 16); MySPI_SwapByte(Address 8); MySPI_SwapByte(Address); // 循环读取数据 for(i 0; i Count; i) { DataArray[i] MySPI_SwapByte(W25Q64_DUMMY_BYTE); } MySPI_Stop(); }三、main.c应用层主函数#include stm32f10x.h #include OLED.h #include Delay.h #include W25Q64.h // 全局变量 uint8_t MID; // 制造商ID uint16_t DID; // 设备ID uint8_t ArrayWrite[] {0x01, 0x02, 0x03, 0x04}; // 待写入数据 uint8_t ArrayRead[4]; // 读取数据缓存 int main(void) { // 初始化外设 OLED_Init(); W25Q64_Init(); // 显示固定标题 OLED_ShowString(1, 1, MID: DID:); OLED_ShowString(2, 1, W:); OLED_ShowString(3, 1, R:); // 读取并显示芯片ID W25Q64_ReadID(MID, DID); OLED_ShowHexNum(1, 5, MID, 2); OLED_ShowHexNum(1, 12, DID, 4); // Flash读写流程擦除→写入→读取 W25Q64_SectorErase(0x000000); W25Q64_PageProgram(0x000000, ArrayWrite, 4); W25Q64_ReadData(0x000000, ArrayRead, 4); // 主循环刷新显示 while(1) { // 显示写入的数据 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); } }四、我踩过的所有坑血泪教训汇总1. 硬件接线坑90% 的问题出在这里错误现象错误原因正确做法读 ID 全是FFFFMISO 和 MOSI 接反了SPI 同名字相接MISO 接 MISOMOSI 接 MOSI不要交叉读 ID 全是FFFF接了 5V 电源W25Q64 是 3.3V 芯片绝对不能接 5V会直接烧坏读 ID 全是FFFF没有共地必须接 GND两个设备电平参考要一致读 ID 全是FFFFCS 引脚接错CS 必须接 PA4不能接其他引脚偶尔能读到 ID偶尔读不到杜邦线接触不良换一根杜邦线或者重新拔插2. MySPI.c 代码坑错误现象错误原因正确做法读 ID 全是00忘记开 GPIOA 时钟初始化时必须先开时钟读 ID 全是FFMISO 引脚配置成了输出 / 下拉 / 浮空MISO 必须配置成上拉输入读 ID 全是FFSCK 默认电平是高电平SPI 模式 0 要求 SCK 默认低电平读 ID 乱码MySPI_SwapByte里少加了括号必须写成(ByteSend (0x80 i))优先级比高读 ID 乱码少加了!!必须加!!把非 0 值统一变成 1保证电平标准读 ID 乱码从最低位开始发送必须从最高位第 7 位开始发送3. W25Q64.c 代码坑错误现象错误原因正确做法写不进去数据读出来全是FF写使能和MySPI_Start()顺序反了必须先写使能再拉 CS程序一直卡着不动WaitBusy和MySPI_Stop()顺序反了必须先拉 CS再等待忙ID 读出来只有低 8 位ReadID里少了 必须写成 *DID MySPI_SwapByte(...)编译报错 “未定义的函数”函数名写错了注意下划线W25Q64_ReadData不是W25Q64ReadData读出来的数据和写入的不一样没有先擦除就写入Flash 只能把 1 变成 0写之前必须先擦除写入超过 256 字节数据出错页编程最多写 256 字节超过 256 字节要分多次写入4. main.c 代码坑错误现象错误原因正确做法显示乱码显示代码只执行了一次把显示代码放到while(1)里循环刷新写入的数据断电后丢失没有等待忙就断电写入 / 擦除后必须等待忙完成五.创建 W25Q64_Ins.h 头文件#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 #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 #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六.总结遇到全是 FFFF先查接线再改代码SPI 交换字节括号和一个都不能少写擦操作顺序先使能再拉 CS最后等忙Flash 铁律写前必须擦只能 1 变 0函数名别写错下划线不能少
STM32 软件 SPI 驱动 W25Q64 全套精简注释代码
一、MySPI.c最底层 SPI 驱动#include stm32f10x.h /************************* 硬件引脚操作层 *************************/ // 控制CS片选引脚PA4 void MySPI_W_SS(uint8_t BitValue) { GPIO_WriteBit(GPIOA, GPIO_Pin_4, BitValue); } // 控制SCK时钟引脚PA5 void MySPI_W_SCK(uint8_t BitValue) { GPIO_WriteBit(GPIOA, GPIO_Pin_5, BitValue); } // 控制MOSI引脚PA7主机输出从机输入 void MySPI_W_MOSI(uint8_t BitValue) { GPIO_WriteBit(GPIOA, GPIO_Pin_7, BitValue); } // 读取MISO引脚PA6主机输入从机输出 uint8_t MySPI_R_MISO(void) { return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6); } /************************* SPI初始化函数 *************************/ // 上电只调用一次配置SPI引脚工作模式 void MySPI_Init(void) { // 开启GPIOA时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; // 配置SS、SCK、MOSI为推挽输出 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); // 配置MISO为上拉输入 GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPU; GPIO_InitStructure.GPIO_Pin GPIO_Pin_6; GPIO_Init(GPIOA, GPIO_InitStructure); // SPI模式0默认状态SS拉高SCK拉低 MySPI_W_SS(1); MySPI_W_SCK(0); } /************************* SPI通信控制函数 *************************/ // 开始SPI通信拉低CS选中从机 void MySPI_Start(void) { MySPI_W_SS(0); } // 结束SPI通信拉高CS取消选中从机 void MySPI_Stop(void) { MySPI_W_SS(1); } /************************* 核心交换一个字节 *************************/ // SPI全双工通信发1个字节同时收1个字节 uint8_t MySPI_SwapByte(uint8_t ByteSend) { uint8_t i, ByteReceive 0x00; // 循环8次逐位收发 for(i 0; i 8; i) { // 发送当前位从最高位开始 MySPI_W_MOSI(!!(ByteSend (0x80 i))); // SCK拉高产生上升沿 MySPI_W_SCK(1); // 读取当前位 if(MySPI_R_MISO() 1) { ByteReceive | (0x80 i); } // SCK拉低产生下降沿 MySPI_W_SCK(0); } return ByteReceive; }二、W25Q64.cFlash 芯片驱动#include stm32f10x.h #include MySPI.h #include W25Q64_Ins.h /************************* W25Q64初始化 *************************/ void W25Q64_Init(void) { MySPI_Init(); } /************************* 读取芯片ID *************************/ // 验证芯片是否正常连接 void W25Q64_ReadID(uint8_t *MID, uint16_t *DID) { MySPI_Start(); // 发送读ID指令 MySPI_SwapByte(W25Q64_JEDEC_ID); // 接收3个字节的ID *MID MySPI_SwapByte(W25Q64_DUMMY_BYTE); *DID MySPI_SwapByte(W25Q64_DUMMY_BYTE); *DID 8; *DID | MySPI_SwapByte(W25Q64_DUMMY_BYTE); MySPI_Stop(); } /************************* 写使能 *************************/ // 所有写入/擦除操作前必须调用 void W25Q64_WriteEnable(void) { MySPI_Start(); MySPI_SwapByte(W25Q64_WRITE_ENABLE); MySPI_Stop(); } /************************* 等待忙 *************************/ // 所有写入/擦除操作后必须调用 void W25Q64_WaitBusy(void) { uint32_t Timeout; MySPI_Start(); MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1); Timeout 100000; // 循环等待忙标志位清零 while((MySPI_SwapByte(W25Q64_DUMMY_BYTE) 0x01) 0x01) { Timeout--; if(Timeout 0) break; } MySPI_Stop(); } /************************* 页编程写入数据 *************************/ // 最多写入256字节1页 void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count) { uint16_t i; // 先写使能 W25Q64_WriteEnable(); MySPI_Start(); // 发送页编程指令 MySPI_SwapByte(W25Q64_PAGE_PROGRAM); // 发送24位地址 MySPI_SwapByte(Address 16); MySPI_SwapByte(Address 8); MySPI_SwapByte(Address); // 循环发送数据 for(i 0; i Count; i) { MySPI_SwapByte(DataArray[i]); } MySPI_Stop(); // 等待写入完成 W25Q64_WaitBusy(); } /************************* 扇区擦除4KB *************************/ // 擦除指定地址所在的整个4KB扇区 void W25Q64_SectorErase(uint32_t Address) { W25Q64_WriteEnable(); MySPI_Start(); // 发送扇区擦除指令 MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB); // 发送24位地址 MySPI_SwapByte(Address 16); MySPI_SwapByte(Address 8); MySPI_SwapByte(Address); MySPI_Stop(); // 等待擦除完成 W25Q64_WaitBusy(); } /************************* 读取数据 *************************/ void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count) { uint32_t i; MySPI_Start(); // 发送读数据指令 MySPI_SwapByte(W25Q64_READ_DATA); // 发送24位地址 MySPI_SwapByte(Address 16); MySPI_SwapByte(Address 8); MySPI_SwapByte(Address); // 循环读取数据 for(i 0; i Count; i) { DataArray[i] MySPI_SwapByte(W25Q64_DUMMY_BYTE); } MySPI_Stop(); }三、main.c应用层主函数#include stm32f10x.h #include OLED.h #include Delay.h #include W25Q64.h // 全局变量 uint8_t MID; // 制造商ID uint16_t DID; // 设备ID uint8_t ArrayWrite[] {0x01, 0x02, 0x03, 0x04}; // 待写入数据 uint8_t ArrayRead[4]; // 读取数据缓存 int main(void) { // 初始化外设 OLED_Init(); W25Q64_Init(); // 显示固定标题 OLED_ShowString(1, 1, MID: DID:); OLED_ShowString(2, 1, W:); OLED_ShowString(3, 1, R:); // 读取并显示芯片ID W25Q64_ReadID(MID, DID); OLED_ShowHexNum(1, 5, MID, 2); OLED_ShowHexNum(1, 12, DID, 4); // Flash读写流程擦除→写入→读取 W25Q64_SectorErase(0x000000); W25Q64_PageProgram(0x000000, ArrayWrite, 4); W25Q64_ReadData(0x000000, ArrayRead, 4); // 主循环刷新显示 while(1) { // 显示写入的数据 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); } }四、我踩过的所有坑血泪教训汇总1. 硬件接线坑90% 的问题出在这里错误现象错误原因正确做法读 ID 全是FFFFMISO 和 MOSI 接反了SPI 同名字相接MISO 接 MISOMOSI 接 MOSI不要交叉读 ID 全是FFFF接了 5V 电源W25Q64 是 3.3V 芯片绝对不能接 5V会直接烧坏读 ID 全是FFFF没有共地必须接 GND两个设备电平参考要一致读 ID 全是FFFFCS 引脚接错CS 必须接 PA4不能接其他引脚偶尔能读到 ID偶尔读不到杜邦线接触不良换一根杜邦线或者重新拔插2. MySPI.c 代码坑错误现象错误原因正确做法读 ID 全是00忘记开 GPIOA 时钟初始化时必须先开时钟读 ID 全是FFMISO 引脚配置成了输出 / 下拉 / 浮空MISO 必须配置成上拉输入读 ID 全是FFSCK 默认电平是高电平SPI 模式 0 要求 SCK 默认低电平读 ID 乱码MySPI_SwapByte里少加了括号必须写成(ByteSend (0x80 i))优先级比高读 ID 乱码少加了!!必须加!!把非 0 值统一变成 1保证电平标准读 ID 乱码从最低位开始发送必须从最高位第 7 位开始发送3. W25Q64.c 代码坑错误现象错误原因正确做法写不进去数据读出来全是FF写使能和MySPI_Start()顺序反了必须先写使能再拉 CS程序一直卡着不动WaitBusy和MySPI_Stop()顺序反了必须先拉 CS再等待忙ID 读出来只有低 8 位ReadID里少了 必须写成 *DID MySPI_SwapByte(...)编译报错 “未定义的函数”函数名写错了注意下划线W25Q64_ReadData不是W25Q64ReadData读出来的数据和写入的不一样没有先擦除就写入Flash 只能把 1 变成 0写之前必须先擦除写入超过 256 字节数据出错页编程最多写 256 字节超过 256 字节要分多次写入4. main.c 代码坑错误现象错误原因正确做法显示乱码显示代码只执行了一次把显示代码放到while(1)里循环刷新写入的数据断电后丢失没有等待忙就断电写入 / 擦除后必须等待忙完成五.创建 W25Q64_Ins.h 头文件#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 #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 #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六.总结遇到全是 FFFF先查接线再改代码SPI 交换字节括号和一个都不能少写擦操作顺序先使能再拉 CS最后等忙Flash 铁律写前必须擦只能 1 变 0函数名别写错下划线不能少