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

【CW32无线抄表项目】W25Q+CW32程序示例

资料下载https://telesky.yuque.com/bdys8w/01/zr02y6vd0r7mnzcl?singleDoc#参考仓库:https://gitee.com/Armink/SFUD一、程序分析硬件总线映射引脚与时钟的“避坑点”#define FLASH_SPIx CW_SPI2 // 注意CW32 中 SPI1 在 APB2 总线而 SPI2 通常挂载在 APB1 总线上 #define FLASH_SPI_CLK RCC_APB1_PERIPH_SPI2 #define FLASH_SPI_APBClkENx RCC_APBPeriphClk_Enable1 // 改为 APB1 的时钟使能 //SPIx GPIO 统一修改为 GPIOB 及对应的引脚 #define FLASH_SPI_SCK_GPIO_CLK RCC_AHB_PERIPH_GPIOB #define FLASH_SPI_SCK_GPIO_PORT CW_GPIOB #define FLASH_SPI_SCK_GPIO_PIN GPIO_PIN_10 #define FLASH_SPI_MISO_GPIO_CLK RCC_AHB_PERIPH_GPIOB #define FLASH_SPI_MISO_GPIO_PORT CW_GPIOB #define FLASH_SPI_MISO_GPIO_PIN GPIO_PIN_14 #define FLASH_SPI_MOSI_GPIO_CLK RCC_AHB_PERIPH_GPIOB #define FLASH_SPI_MOSI_GPIO_PORT CW_GPIOB #define FLASH_SPI_MOSI_GPIO_PIN GPIO_PIN_15 // CS引脚修改为 PB12 #define FLASH_SPI_CS_GPIO_CLK RCC_AHB_PERIPH_GPIOB #define FLASH_SPI_CS_GPIO_PORT CW_GPIOB #define FLASH_SPI_CS_GPIO_PIN GPIO_PIN_12 //GPIO AF (引脚复用功能重映射) #define FLASH_SPI_AF_SCK PB10_AFx_SPI2SCK() #define FLASH_SPI_AF_MISO PB14_AFx_SPI2MISO() #define FLASH_SPI_AF_MOSI PB15_AFx_SPI2MOSI() //CS LOW or HIGH (片选拉低/拉高控制宏) #define FLASH_SPI_CS_LOW() PB12_SETLOW() #define FLASH_SPI_CS_HIGH() PB12_SETHIGH()注意CW32 中 SPI1 在 APB2 总线而 SPI2 通常挂载在 APB1 总线上很多新手移植代码时把 SPI1 改成 SPI2引脚也改了但 Flash 就是没反应。原因就在于没注意单片机内部的总线挂载情况把 APB1 错写成了 APB2导致时钟根本没开起来。。初始化参数用代码还原“时序图”示例程序SPI 初始化核心代码段/************************ SPI 参数配置 ***********************/ SPI_InitStructure.SPI_Direction SPI_Direction_2Lines_FullDuplex; // 双线全双工 (DI和DO两根线同时工作) SPI_InitStructure.SPI_Mode SPI_Mode_Master; // 主机模式 (单片机当老板Flash当员工) SPI_InitStructure.SPI_DataSize SPI_DataSize_8b; // 一次发 8 个 bit (一个字节) // 重点 1时钟极性与相位 (还原 Mode 3) SPI_InitStructure.SPI_CPOL SPI_CPOL_High; // 时钟空闲时为高电平 SPI_InitStructure.SPI_CPHA SPI_CPHA_2Edge; // 在第 2 个边沿 (上升沿) 抓取数据 // 重点 2片选信号软件控制 SPI_InitStructure.SPI_NSS SPI_NSS_Soft; // 放弃硬件CS改用普通GPIO软件控制 SPI_InitStructure.SPI_BaudRatePrescaler SPI_BaudRatePrescaler_8; // 速度设置分频系数 (可根据需要调整) // 重点 3高低位顺序 SPI_InitStructure.SPI_FirstBit SPI_FirstBit_MSB; // 最高有效位 (MSB) 最先发送 SPI_Init(FLASH_SPIx, SPI_InitStructure); // 把配置参数正式写入单片机寄存器 SPI_Cmd(FLASH_SPIx, ENABLE); // 启动 SPI 模块大家还记得前面我们在 W25Q64 数据手册里看到的时序图吗有一条虚线标着 Mode 3它的特点是单片机不发数据时时钟线CLK是停在高电平的。代码里的SPI_CPOL SPI_CPOL_High就是在告诉单片机‘没事干的时候把时钟线拉高’。那么什么时候读数据呢Mode 3 规定是在时钟的上升沿。大家想既然空闲是高电平那它动起来的第一个动作肯定是‘往下拉’下降沿第 1 个边沿然后才是‘往上拉’上升沿第 2 个边沿。所以我们必须把相位配置成SPI_CPHA SPI_CPHA_2Edge。这两行代码加在一起就是标准的SPI Mode 3手动包裹每一次通讯 (重点)这是软件 CS 最直观的体现。SPI_FLASH_WriteEnable函数就像做汉堡一样把发送数据的动作“夹”在拉低和拉高之间void SPI_FLASH_WriteEnable(void){ FLASH_SPI_CS_LOW(); // 1. 手动拉低老师点名“W25Q64听好了” SPI_FLASH_SendByte(FLASH_CMD_WriteEnable); // 2. 发送 0x06 指令 FLASH_SPI_CS_HIGH(); // 3. 手动拉高指令结束“去执行吧” }以后不管是发 1 个字节还是发 256 个字节格式永远是先拉低 - 中间疯狂发数据 - 最后拉高。二、 为什么放弃硬件 CS非要自己用软件写硬件 SPI 往往很“死板”。有些单片机的硬件 CS 逻辑是每发送完一个字节它就会自动把 CS 拉高一下然后再拉低发下一个字节。致命后果回忆一下我们之前的时序图如果执行Page Program(页写入) 连续写 256 个字节W25Q64 要求这期间 CS 必须全程保持低电平。如果硬件 SPI 中途把 CS 拉高了哪怕一微秒Flash 就会认为“通讯被意外打断了刚才收到的数据全部作废”软件 CS 的优势只有程序员才知道一次通讯到底多长。用代码控制哪怕发 1000 个字节只要我们不写FLASH_SPI_CS_HIGH()门就永远开着。可能会有人以为把0x06或擦除指令发给 Flash它立刻就去干活了。错原理解密Flash 内部有一个指令缓存。它一直在听直到看到CS 从低变高上升沿的那一瞬间它才知道“哦单片机的话说完了我现在立刻去执行”软件 CS 的优势通过软件代码我们能精准地确保最后一个 bit 完全从引脚上发送出去了再从容地执行PB12_SETHIGH()触发 Flash 内部的高压泵去擦写。硬件 CS 往往在时钟停止的那一瞬间就立刻抬起有时会导致最后一个比特的保持时间不够。3、SPI 的“心脏”底层收发函数/** * brief 通过 SPI 发送 1 个字节同时接收 1 个字节 */ uint8_t SPI_FLASH_SendByte(uint8_t byte) { /*1. 等待发送漏斗空出来 (TXE: Transmit Buffer Empty)单片机往外发数据是需要时间的 发送寄存器里上一个字节还没漏完马上又塞一个新字节进去新数据就会把老数据挤爆覆盖掉。 所以我们必须死等直到单片机说‘报告TXE 标志位置位了’ 我们才能执行下一步 SPI_SendData 进去。 */ while(SPI_GetFlagStatus(FLASH_SPIx, SPI_FLAG_TXE) RESET); // 2. 把数据倒进发送漏斗 SPI_SendData(FLASH_SPIx, byte); // 3. 等待接收漏斗装满 (RXNE: Receive Buffer Not Empty) /* 单片机就会触发一个严重的溢出错误OVR 标志位置位。一旦发生这个错误SPI 硬件就会强行自我锁死 拒绝再发送或接收任何数据直到你手动去清空错误标志 */ while(SPI_GetFlagStatus(FLASH_SPIx, SPI_FLAG_RXNE) RESET); // 4. 把接收漏斗里的数据拿出来返回、 /* SPI 最核心的物理机制了移位寄存器Shift Register。 SPI 的 MOSI发和 MISO收在单片机内部其实连着同一个首尾相接的环形跑道。 你每往外挤出去 1 个 bit外面就必然会挤进来 1 个 bit。 也就是说哪怕你只是想单纯地发指令给 Flash比如发 0x06当你发完这 8 个 bit 的同时Flash 也会被迫通过 MISO 给你塞回来 8 个 bit 的‘垃圾数据’。 我们如果不把这些垃圾数据从接收漏斗SPI_ReceiveData里拿走清空下次想真正收数据时系统就会报错。这就是为什么发送函数最后必须要 return 一个接收值。” */ return SPI_ReceiveData(FLASH_SPIx); }4、擦与写操作/** * brief 扇区擦除 4KB * * param SectorAddr :待擦除的扇区地址 */ void SPI_FLASH_SectorErase(uint32_t SectorAddr) { //发送 写使能 指令 SPI_FLASH_WriteEnable(); //等待写入完成 // SPI_FLASH_WaitForWriteEnd(); FLASH_SPI_CS_LOW(); //发送 扇区擦除 指令 SPI_FLASH_SendByte(FLASH_CMD_SectorErase); //发送 待擦除扇区地址 SPI_FLASH_SendByte((SectorAddr 0xFF0000) 16); // 发送高 8 位地址 SPI_FLASH_SendByte((SectorAddr 0xFF00) 8); // 发送中 8 位地址 SPI_FLASH_SendByte(SectorAddr 0xFF); // 发送低 8 位地址 FLASH_SPI_CS_HIGH(); //等待擦除完成 SPI_FLASH_WaitForWriteEnd(); }传入的SectorAddr最好是4096 的整数倍比如 0x000000, 0x001000。如果你传了个中间地址Flash 还是会暴力地把包含这个地址的整个 4KB 扇区全部抹掉这段代码极其简单就是个while循环把传进来的数组数据一个一个发出去。 但它有一个致命的物理限制——它绝对不能跨页如果你在这一页的第 250 个字节处开始写准备写 10 个字节。当写到第 256 个字节本页结尾时Flash 不会自动翻页它会像打字机卡壳一样强行把打字头拽回本页的第 1 个字节把你之前好端端的数据给覆盖掉。这就是著名的‘页卷回Page Wrap’灾难。”如果没有大容量的 RAM 做缓存就全靠这个函数来智能切分数据安全跨页。/** * brief 写入不定量数据 * * param pBuffer :待写入数据的指针 * param WriteAddr :写入地址 * param NumByteToWrite :写入数据长度 * note * -需要先擦除 */ void SPI_FLASH_BufferWrite(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite) { uint8_t NumOfPage 0, NumOfSingle 0, Addr 0, count 0, temp 0; Addr WriteAddr % SPI_FLASH_PageSize; count SPI_FLASH_PageSize - Addr; NumOfPage NumByteToWrite / SPI_FLASH_PageSize; NumOfSingle NumByteToWrite % SPI_FLASH_PageSize; if(Addr 0) /* WriteAddr 刚好按页对齐 */ { if(NumOfPage 0) /* NumByteToWrite SPI_FLASH_PageSize */ { SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite); } else /* NumByteToWrite SPI_FLASH_PageSize */ { while(NumOfPage--) { SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize); WriteAddr SPI_FLASH_PageSize; pBuffer SPI_FLASH_PageSize; } if(NumOfSingle ! 0) { SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle); } } } else /* WriteAddr 与 SPI_FLASH_PageSize 不对齐 */ { if(NumOfPage 0) /* NumByteToWrite SPI_FLASH_PageSize */ { if(NumOfSingle count) /*! (NumByteToWrite WriteAddr) SPI_FLASH_PageSize */ { temp NumOfSingle - count; //写完当前页 SPI_FLASH_PageWrite(pBuffer, WriteAddr, count); WriteAddr count; pBuffer count; //写剩余数据 SPI_FLASH_PageWrite(pBuffer, WriteAddr, temp); } else { SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite); } } else /* NumByteToWrite SPI_FLASH_PageSize */ { NumByteToWrite - count; NumOfPage NumByteToWrite / SPI_FLASH_PageSize; NumOfSingle NumByteToWrite % SPI_FLASH_PageSize; //先写完当前页以后地址将对齐 SPI_FLASH_PageWrite(pBuffer, WriteAddr, count); WriteAddr count; pBuffer count; //WriteAddr 刚好按页对齐 while(NumOfPage--) { SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize); WriteAddr SPI_FLASH_PageSize; pBuffer SPI_FLASH_PageSize; } if(NumOfSingle ! 0) { SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle); } } } }算法逻辑解剖四个关键变量Addr WriteAddr % SPI_FLASH_PageSize;翻译算一下你要写的起始位置在当前页的偏移量是多少也就是打字机现在处于这一页的第几格。如果Addr 0说明刚好在一页的开头完美对齐。count SPI_FLASH_PageSize - Addr;翻译算一下当前这一页还剩下多少空位可以写。NumOfPage NumByteToWrite / SPI_FLASH_PageSize;翻译算一下你给的数据总长能填满几个完整的整页。NumOfSingle NumByteToWrite % SPI_FLASH_PageSize;翻译算一下填满整页后最后还剩下的一条“小尾巴”是几个字节。这个函数的本质就是‘填坑与翻页’。 假设你现在身处第一页的末尾还剩 10 个空位count10但你手里有 300 个字节要写。 这个函数的逻辑是先调用PageWrite把手里的 10 个字节塞进当前的空位把这一页填满。此时地址自动对齐到了下一页的开头。手里还剩 290 个字节。算一下刚好能填满 1 个整页256字节。于是用while循环再调用一次PageWrite写入 256 字节。最后剩下一条 34 字节的尾巴NumOfSingle34再调用一次PageWrite收尾。有了这个总监把关我们在应用层只需要无脑调用BufferWrite想写多少写多少再也不用管什么 256 字节的物理边界了”示例假设你现在身处第一页的末尾还剩 10 个空位count10但你手里有 300 个字节要写。 这个函数的逻辑是先调用PageWrite把手里的 10 个字节塞进当前的空位把这一页填满。此时地址自动对齐到了下一页的开头。手里还剩 290 个字节。算一下刚好能填满 1 个整页256字节。于是用while循环再调用一次PageWrite写入 256 字节。最后剩下一条 34 字节的尾巴NumOfSingle34再调用一次PageWrite收尾。有了这个总监把关我们在应用层只需要无脑调用BufferWrite想写多少写多少再也不用管什么 256 字节的物理边界了”大家发现没有读取函数并没有像写入那样去算什么页边界、满不满的问题。 为什么因为 Flash 的物理结构对‘读操作’没有设限只要你不拉高 CS 引脚内部的地址指针就会自动加 1。哪怕你直接让NumByteToRead 83886088MB它也会顺畅地把整颗芯片从头到尾给你扫一遍。这就是‘扫描仪’的威力。void SPI_FLASH_BufferRead(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead) { // 动作 1拉下开关告诉 Flash 准备干活 FLASH_SPI_CS_LOW(); // 动作 2发送“普通读”指令 (0x03) SPI_FLASH_SendByte(FLASH_CMD_ReadData); // 动作 3发送 24 位起始地址 (从哪里开始读) SPI_FLASH_SendByte((ReadAddr 0xFF0000) 16); // 高 8 位 SPI_FLASH_SendByte((ReadAddr 0xFF00) 8); // 中 8 位 SPI_FLASH_SendByte(ReadAddr 0xFF); // 低 8 位 // 动作 4开启“吸尘器”模式疯狂吸取数据 while(NumByteToRead--) { *pBuffer SPI_FLASH_ReadByte(); // 内部在发 0xFF 哑字节换取数据 pBuffer; // 指针后移准备存下一个字节 } // 动作 5完工拉高 CS 结束通讯 FLASH_SPI_CS_HIGH(); }大家仔细对比一下我们上一节讲的BufferWrite写数据的时候代码长达几十行要算偏移量、算剩余空间一旦跨越 256 字节的页边界就得重新发地址。但是你们看读数据的代码居然只有一个简单的while循环它根本不管 256 字节的界限想读多少就读多少NumByteToRead甚至可以填几万。这是为什么呢这就是 Flash 的物理魅力写数据像用老式打字机打到纸的边缘256字节就会卡死必须手动换行重新发地址。而读数据就像拉开一幅无尽的卷轴只要你一开始告诉它一个起始地址动作 3并且只要CS引脚一直保持低电平Flash 内部的地址指针就会自动 1、跨页、跨扇区、跨块畅通无阻我们现在用的指令是0x03普通读取。在 W25Q64 的手册里普通读取的时钟频率是有上限的通常在 33MHz 甚至更低。 如果你的 CW32 单片机跑得飞快把 SPI 时钟设置到了 48MHz 极限狂飙用这个0x03指令读出来的数据可能会错位或者全是乱码解决办法把0x03换成我们在时序图章节讲过的0x0BFast Read极速读取。唯一的区别是发完 24 位地址后代码里要多发一个字节的0xFF8个 Dummy Clocks给 Flash 留出反应时间然后才能进入while循环去吸取真实数据。二、SDK分析与移植1.SDK分析原工程中没有下列程序需要自己找一个地方加进去/* 1. 针对 AC6 的禁用半主机指令 */ __asm(.global __use_no_semihosting\n\t); /* 2. 定义标准库需要的支持函数 */ #include stdio.h /* 这里的 __FILE 结构体在 AC6 下通常不需要手动定义MicroLIB 会处理 */ /* 但为了彻底重定向 printf我们需要实现底层输出函数 */ // 如果你没在其他地方定义 fputc请加上这段 int fputc(int ch, FILE *f) { // 假设你使用的是 UART1发送寄存器为 TDR // 这里的具体寄存器名根据 CW32 库文件决定通常是 CW_UART1-TDR 或类似 USART_SendData_8bit(CW_UART1, (uint8_t)ch); while (USART_GetFlagStatus(CW_UART1, USART_FLAG_TXE) RESET); return ch; } /* 3. 定义半主机依赖的底层存根函数 */ void _sys_exit(int x) { x x; while (1); // 报错后死循环 } void _ttywrch(int ch) { ch ch; }这段代码是嵌入式开发里非常经典的“printf串口重定向与半主机模式Semihosting禁用”模板。特别是当你从旧版的 Keil AC5 编译器升级到最新的AC6 编译器时这段代码是必须要有的“护身符”。如果在代码里调用了printf()但不加这段程序这段代码是嵌入式开发里非常经典的“printf串口重定向与半主机模式Semihosting禁用”模板。特别是当你从旧版的 Keil AC5 编译器升级到最新的AC6 编译器时这段代码是必须要有的“护身符”。如果在代码里调用了printf()但不加这段程序你会遇到两种极其折磨人的报错现象:现象一编译直接报错Linker Error如果你不加_sys_exit和_ttywrch这几个存根函数同时又在代码里用了标准 C 库函数没勾选 MicroLIB 的情况下点击编译时Keil 的 Build Output 窗口会爆出红色的底层链接错误常见报错长这样Error: L6218E: Undefined symbol _sys_exit (referred from ...)Error: L6218E: Undefined symbol _ttywrch (referred from ...)Error: L6218E: Undefined symbol __aeabi_assert ...为什么报错C 语言的标准库原本是给电脑Windows/Linux设计的当程序出错或者结束时它会默认去调用操作系统的退出函数exit或终端输出函数ttywrch。但我们的 CW32 单片机里根本没有操作系统编译器找不到这些底层函数就会报“未定义符号”的错误。代码里写死这几个空函数就是为了骗过编译器“行了退出函数我给你准备好了你别报错了。”现象二运行时“拔线死机”The Silent Killer这是最坑、最容易让崩溃的现象。如果你没加__asm(.global __use_no_semihosting\n\t);这句话编译可能完全通过零警告但一下载到板子上就会出现“灵异事件”插着仿真器调试代码跑得好好的printf的数据能在 Keil 的 Debug 窗口里打印出来。拔掉仿真器插充电宝独立供电板子死机了程序卡死在启动阶段LED 也不闪了所有任务罢工。为什么死机半主机模式的坑半主机模式Semihosting是一种调试机制。它会让单片机的printf试图通过 JTAG/SWD 仿真器的数据线把字符传给电脑屏幕。 如果没禁用半主机模式每次执行printf时单片机内部会触发一条特殊的硬件断点指令BKPT来呼叫电脑。当你拔掉仿真器独立运行时单片机喊破喉咙也没人理它它就会一直卡在这个断点指令上导致整个系统彻底死机。现象三printf变成“哑巴”如果不加fputc这个函数现象编译可以通过程序也不会死机但是你的电脑串口助手里收不到任何数据。为什么printf只负责把你要发送的变量转换成字符格式比如把数字123变成字符1,2,3但它不知道这些字符要从单片机的哪个引脚扔出去。fputc就是printf和 CW32 硬件之间的**“水管接头”**。你在fputc里写了USART_SendData_8bit(CW_UART1, ch)printf才知道“哦原来我要把字符塞进 UART1 的发送寄存器里啊。”2.示例程序#include flashhoufun.h #include cw32_eval_spi_flash.h uint8_t Flash_TxBuffer[] kunkun; uint8_t Flash_RxBuffer[BufferSize]; uint8_t Flash_TxBuffer2[] zhiyin; uint8_t Flash_RxBuffer2[7]; // zhiyin 长度为 6 \0 uint8_t DeviceID 0; uint16_t ManufactDeviceID 0; uint32_t JedecID 0; uint8_t UniqueID[8]; // 使用新名字 volatile FlashTestStatus TransferStatus FLASH_FAILED; // 替换返回类型和内部比较的宏 FlashTestStatus Buffercmp(uint8_t* pBuffer1, uint8_t* pBuffer2, uint16_t BufferLength) { while(BufferLength--) { if(*pBuffer1 ! *pBuffer2) { return FLASH_FAILED; } pBuffer1; pBuffer2; } return FLASH_PASSED; } //void flash_fun(void) //{ // DeviceID SPI_FLASH_DeviceID(); // ManufactDeviceID SPI_FLASH_ManufactDeviceID(); // JedecID SPI_FLASH_JedecID(); // SPI_FLASH_UniqueID(UniqueID); // // // 擦除扇区 4KB // SPI_FLASH_SectorErase(FLASH_SectorToEraseAddress); // // // 写数据 // SPI_FLASH_BufferWrite(Flash_TxBuffer, FLASH_WriteAddress, BufferSize); // printf(\r\n尝试写入的数据为: %s\r\n, Flash_TxBuffer); // // // 读数据 // SPI_FLASH_BufferRead(Flash_RxBuffer, FLASH_ReadAddress, BufferSize); // printf(\r\n实际读出的数据为: %s\r\n, Flash_RxBuffer); // // // 检查 // TransferStatus Buffercmp(Flash_TxBuffer, Flash_RxBuffer, BufferSize); // if(TransferStatus FLASH_PASSED) // { // printf(\r\nFLASH Success! kunkun 验证通过\r\n); // } // else // { // printf(\r\nFLASH Error 1! 数据不一致\r\n); // } //} void flash_fun(void) { // --- 步骤 1读取 ID 确认通信 (保持不变) --- DeviceID SPI_FLASH_DeviceID(); ManufactDeviceID SPI_FLASH_ManufactDeviceID(); JedecID SPI_FLASH_JedecID(); SPI_FLASH_UniqueID(UniqueID); // --- 步骤 2测试第一个扇区 (0-4KB) --- uint32_t addr1 0x0000; SPI_FLASH_SectorErase(addr1); // 擦除第一个 4KB SPI_FLASH_BufferWrite(Flash_TxBuffer, addr1, BufferSize); SPI_FLASH_BufferRead(Flash_RxBuffer, addr1, BufferSize); if(Buffercmp(Flash_TxBuffer, Flash_RxBuffer, BufferSize) FLASH_PASSED) { printf(\r\n[Sector 0] kunkun 验证通过正在挑战 Sector 1...); // --- 步骤 3测试第二个扇区 (4-8KB) --- // 5-8KB 的数据属于第二个 4KB 扇区起始地址为 0x1000 uint32_t addr2 0x1000; SPI_FLASH_SectorErase(addr2); // 擦除第二个 4KB 扇区 SPI_FLASH_BufferWrite(Flash_TxBuffer2, addr2, 7); printf(\r\n尝试向 0x1000 写入数据: %s, Flash_TxBuffer2); SPI_FLASH_BufferRead(Flash_RxBuffer2, addr2, 7); printf(\r\n从 0x1000 实际读出数据: %s, Flash_RxBuffer2); if(Buffercmp(Flash_TxBuffer2, Flash_RxBuffer2, 7) FLASH_PASSED) { printf(\r\n[Sector 1] zhiyin 验证通过两个区域均正常\r\n); TransferStatus FLASH_PASSED; } else { printf(\r\n[Sector 1] zhiyin 失败请检查地址 0x1000 处的写入。); TransferStatus FLASH_FAILED; } } else { printf(\r\n[Sector 0] kunkun 验证失败请检查底层驱动。); TransferStatus FLASH_FAILED; } }#include main.h #include cw32f030_gpio.h #include cw32f030_rcc.h #include init.h #include buffer.h #include fun.h #include radio.h #include delay.h #include flashhoufun.h #include cw32_eval_spi_flash.h // 全局中断标志 (fun.c 也要用) volatile uint8_t g_bIrqTriggered 0; void System_Init_Config(void); int32_t main(void) { System_Init_Config(); SPI_FLASH_Init(); flash_fun(); while (1) { } } void System_Init_Config(void) { RCC_Configuration(); GPIO_Configuration(); SPI_Configuration(); EXTI_Configuration(); ADC_Configuration(); }3.实物与效果展示注意W25Q64 是 3.3V 器件严禁接 5V方案一独立运行模式无串口打印当你完成调试准备将网关部署到 500 个节点的现场时可以撤掉串口模块以精简电路。连接设备设备引脚CW32F030 引脚说明W25Q64VCC3.3V电源W25Q64GNDGND电源地W25Q64/CSPB12软件片选 (CS)W25Q64CLKPB10SPI2 时钟 (SCK)W25Q64DO (IO1)PB14SPI2 数据输出 (MISO)W25Q64DI (IO0)PB15SPI2 数据输入 (MOSI)其他PA08 / PA09悬空串口引脚不接线代码可保留以防报错方案二开发调试模式带串口打印printf原工程就是如此可以通过串口打印出来信息。此模式下你可以通过电脑串口助手查看 Flash 的 ID 识别情况和程序运行状态。连接设备设备引脚CW32F030 引脚说明W25Q64VCC3.3V电源严禁接 5VW25Q64GNDGND电源地W25Q64/CSPB12软件片选 (CS)W25Q64CLKPB10SPI2 时钟 (SCK)W25Q64DO (IO1)PB14SPI2 数据输出 (MISO)W25Q64DI (IO0)PB15SPI2 数据输入 (MOSI)USB转TTLRXDPA08单片机发送 (TX)接模块接收USB转TTLTXDPA09单片机接收 (RX)接模块发送USB转TTLGNDGND共地通讯基础

相关文章:

【CW32无线抄表项目】W25Q+CW32程序示例

资料下载: https://telesky.yuque.com/bdys8w/01/zr02y6vd0r7mnzcl?singleDoc# 参考仓库: https://gitee.com/Armink/SFUD 一、程序分析 硬件总线映射(引脚与时钟的“避坑点”) #define FLASH_SPIx CW_SPI2 // 注意&…...

告别慢查询:用快马ai智能生成postgresql性能优化与索引方案

告别慢查询:用快马AI智能生成PostgreSQL性能优化与索引方案 在电商系统中,订单查询是最常见的操作之一。随着业务量的增长,数据库查询性能往往会成为瓶颈。最近我在优化一个电商平台的订单查询模块时,发现几个典型的性能问题&…...

SELinux 导致 K8s 日志 logrotate 无法轮询压缩

1. 问题现象在某 Linux 环境中,Kubernetes 日志无法自动轮询、无法压缩归档,具体表现如下:/var/log/kubernetes/kubelet.log 持续增大,达到 90MB 不再切割日志压缩包停留在某一时间点,之后不再生成新归档系统日志&…...

收藏必备!小白程序员轻松入门大模型,带你理清AI核心概念全框架

AI浪潮已经刮了一年多,身边越来越多人聊AI,张口就是“agent”“skill”,听得人只能点头附和,似懂非懂?其实不是听不懂,而是没有把这些概念串起来,告诉你它们到底是什么、彼此有啥关系。 咱不聊复…...

ObsPy地震学工具箱:从数据采集到科学发现的完整Python解决方案

ObsPy地震学工具箱:从数据采集到科学发现的完整Python解决方案 【免费下载链接】obspy ObsPy: A Python Toolbox for seismology/seismological observatories. 项目地址: https://gitcode.com/gh_mirrors/ob/obspy ObsPy是地震学领域的Python工具箱&#xf…...

React Native Boilerplate组件库终极指南:AssetByVariant与IconByVariant高级用法

React Native Boilerplate组件库终极指南:AssetByVariant与IconByVariant高级用法 【免费下载链接】react-native-boilerplate A React Native template for building solid applications 🐙, using JavaScript 💛 or Typescript &#x1f49…...

革命性终端网站构建工具LiveTerm:5分钟打造个性化网页终端

革命性终端网站构建工具LiveTerm:5分钟打造个性化网页终端 【免费下载链接】LiveTerm 💻 Build terminal styled websites in minutes! 项目地址: https://gitcode.com/gh_mirrors/li/LiveTerm LiveTerm是一款革命性的终端网站构建工具&#xff0…...

WireGuard排除私网地址聚类表(掩码形式)

事情缘由: 玩过WireGuard的都知道,它的配置文件是如下形式的: [Interface] PrivateKey *********************** Address **********/32 DNS 8.8.8.8 MTU1420 [Peer] PublicKey ************************ Endpoint 8.8.8.8:12345 A…...

绿联 安装SeaTable在线协同表格

绿联 安装SeaTable在线协同表格 1、镜像 seatable/seatable-developer:latest 2、安装 2.1、基础设置 重启策略:容器退出时总是重启容器。 2.2、网络 网络选择桥接(bridge)。 2.3、存储空间 装载路径/shared不可变更。 2.4、端口设置 容器端口固定80&#x…...

Pi0机器人控制实战:从模型下载到Web演示完整流程

Pi0机器人控制实战:从模型下载到Web演示完整流程 1. 项目概述与核心价值 Pi0是一个创新的视觉-语言-动作流模型,专为通用机器人控制设计。这个开源项目将深度学习与机器人技术相结合,通过自然语言指令和视觉输入来生成精确的机器人动作。项…...

DeepSeek架构深度解析:从原理到实践的完整指南

一、引言 2025年1月,DeepSeek-R1的发布在全球AI领域引发巨大震动——一个开源模型以远低于主流闭源模型的训练成本,实现了与之相匹敌的推理性能,直接导致英伟达股价单日下跌17%。在随后的时间里,DeepSeek团队持续迭代&#xff0c…...

数字记忆守护者:GetQzonehistory全攻略

数字记忆守护者:GetQzonehistory全攻略 【免费下载链接】GetQzonehistory 获取QQ空间发布的历史说说 项目地址: https://gitcode.com/GitHub_Trending/ge/GetQzonehistory 一、数字记忆危机:我们正在失去什么? 在这个信息爆炸的时代&…...

nfs-subdir-external-provisioner核心配置参数详解:onDelete、archiveOnDelete、pathPattern

nfs-subdir-external-provisioner核心配置参数详解:onDelete、archiveOnDelete、pathPattern 【免费下载链接】nfs-subdir-external-provisioner Dynamic sub-dir volume provisioner on a remote NFS server. 项目地址: https://gitcode.com/gh_mirrors/nf/nfs-s…...

OpenClaw+Phi-3-vision-128k-instruct数据标注:半自动生成图像标签训练集

OpenClawPhi-3-vision-128k-instruct数据标注:半自动生成图像标签训练集 1. 为什么需要半自动数据标注 去年我在做一个宠物品种识别项目时,最头疼的就是数据标注环节。手动给5000多张猫狗图片打标签,不仅耗时耗力,还容易因为疲劳…...

Git-Credential-Manager-for-Windows安全存储机制深度解析:如何保护你的Git凭证安全 [特殊字符]

Git-Credential-Manager-for-Windows安全存储机制深度解析:如何保护你的Git凭证安全 🔐 【免费下载链接】Git-Credential-Manager-for-Windows Secure Git credential storage for Windows with support for Visual Studio Team Services, GitHub, and B…...

WebGLStudio.js虚拟文件系统完全指南:如何高效管理3D资源

WebGLStudio.js虚拟文件系统完全指南:如何高效管理3D资源 【免费下载链接】webglstudio.js A full open source 3D graphics editor in the browser, with scene editor, coding pad, graph editor, virtual file system, and many features more. 项目地址: http…...

文字的编码方式————不同UTF之间的区别

目录 1. 编码与字体 A. ASCII(American Standard Code for Information Interchange) B. ANSI C. UNICODE 2 . UNICODE 编码实现 (1)UTF-16 a. UTF-16 LE b. UTF-16 BE (2)UTF-8 (3&#xff…...

Protocol

在Python的世界里,Protocol这个概念,其实挺有意思的。它不是那种一上来就让人眼前一亮的语法糖,也不是什么解决具体问题的现成工具。它更像是一种约定,一种让代码“说清楚自己”的方式。如果你写过一段时间Python,尤其…...

TypeVar

## 关于Python里的TypeVar,你可能想知道的 最近在整理一些旧代码,翻到几年前写的一个通用缓存工具类,里面用到了TypeVar。当时注释里只简单写了一句“用于类型提示”,现在回头看,觉得可以展开聊聊这个东西。 TypeVar是…...

如何用Venera打造个性化漫画阅读体验?

如何用Venera打造个性化漫画阅读体验? 【免费下载链接】venera A comic app 项目地址: https://gitcode.com/gh_mirrors/ve/venera 你是否曾经感到市面上的漫画阅读应用千篇一律,界面设计缺乏个性?或者希望在深夜阅读时,应…...

全方位解析GBFR Logs:《碧蓝幻想:Relink》战斗数据分析平台

全方位解析GBFR Logs:《碧蓝幻想:Relink》战斗数据分析平台 【免费下载链接】gbfr-logs GBFR Logs lets you track damage statistics with a nice overlay DPS meter for Granblue Fantasy: Relink. 项目地址: https://gitcode.com/gh_mirrors/gb/gbf…...

性能测试中的“假阳性”:如何识别与避免?

在软件性能测试领域,“假阳性”是一个令测试团队既头疼又难以回避的挑战。它指的是测试报告或监控工具错误地发出性能警报,声称系统存在性能瓶颈或缺陷,但经过深入分析或在实际环境中验证,发现系统运行状态良好,并不存…...

Node.js企业级应用部署与运维完整方案:Google Cloud Platform实战指南

Node.js企业级应用部署与运维完整方案:Google Cloud Platform实战指南 【免费下载链接】nodejs-docs-samples Node.js samples for Google Cloud Platform products. 项目地址: https://gitcode.com/gh_mirrors/no/nodejs-docs-samples 想要构建稳定可靠的No…...

hello-uniapp自定义组件开发:打造属于你的UniApp组件库

hello-uniapp自定义组件开发:打造属于你的UniApp组件库 【免费下载链接】hello-uniapp uni-app框架演示示例 项目地址: https://gitcode.com/gh_mirrors/he/hello-uniapp UniApp作为一款优秀的跨平台开发框架,让开发者能够使用Vue.js语法编写一次…...

3个维度解析PhpWebStudy新版本:打造更稳定安全的本地开发环境

3个维度解析PhpWebStudy新版本:打造更稳定安全的本地开发环境 【免费下载链接】PhpWebStudy Lightweight Native Local Dev Toolbox for Windows, macOS & Linux. Run OpenClaw/n8n/Apache/Nginx/Caddy/Tomcat/PHP/Node.js/Bun/Deno/Python/Java/Go/Ruby/Perl/R…...

突破性能瓶颈:Rust如何重塑数据科学与AI的未来

突破性能瓶颈:Rust如何重塑数据科学与AI的未来 在当今数据驱动的时代,数据科学与AI领域正面临着前所未有的性能挑战。随着数据集规模的爆炸式增长和模型复杂度的不断提升,传统编程语言在处理高并发、大规模数据时逐渐显露出性能瓶颈。而Rust…...

Awesome Rust核心库精选:异步编程与网络开发

Awesome Rust核心库精选:异步编程与网络开发 本文深入探讨了Rust生态系统中的核心库,重点分析了异步运行时(Tokio与async-std)、网络编程库、HTTP客户端/服务器框架、数据序列化工具链以及密码学与安全相关库。通过对比分析各库的…...

RyTuneX:WinUI3驱动的Windows性能优化引擎

RyTuneX:WinUI3驱动的Windows性能优化引擎 【免费下载链接】RyTuneX RyTuneX is a cutting-edge optimizer built with the WinUI 3 framework, designed to amplify the performance of Windows devices. Crafted for both Windows 10 and 11. 项目地址: https:/…...

从数据小白到战斗大师:GBFR Logs如何帮你玩转《碧蓝幻想:Relink》

从数据小白到战斗大师:GBFR Logs如何帮你玩转《碧蓝幻想:Relink》 【免费下载链接】gbfr-logs GBFR Logs lets you track damage statistics with a nice overlay DPS meter for Granblue Fantasy: Relink. 项目地址: https://gitcode.com/gh_mirrors/…...

4大维度全面掌控Cyber Engine Tweaks:打造专属赛博朋克2077体验

4大维度全面掌控Cyber Engine Tweaks:打造专属赛博朋克2077体验 【免费下载链接】CyberEngineTweaks Cyberpunk 2077 tweaks, hacks and scripting framework 项目地址: https://gitcode.com/gh_mirrors/cy/CyberEngineTweaks 🌟 引擎核心&#x…...