91欧美超碰AV自拍|国产成年人性爱视频免费看|亚洲 日韩 欧美一厂二区入|人人看人人爽人人操aV|丝袜美腿视频一区二区在线看|人人操人人爽人人爱|婷婷五月天超碰|97色色欧美亚州A√|另类A√无码精品一级av|欧美特级日韩特级

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線(xiàn)課程
  • 觀看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

【CW32無(wú)線(xiàn)抄表項(xiàng)目】W25Q+CW32程序示例

CW32生態(tài)社區(qū) ? 來(lái)源:CW32生態(tài)社區(qū) ? 作者:CW32生態(tài)社區(qū) ? 2026-03-31 21:29 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

資料下載:

https://telesky.yuque.com/bdys8w/01/zr02y6vd0r7mnzcl?singleDoc#

參考倉(cāng)庫(kù):

https://gitee.com/Armink/SFUD

chaijie_default.pngwKgZO2nLzCWAcc1XAADV0IAqfPk103.jpgchaijie_default.pngwKgZPGnLzCaAcSl3AAEpkGOVJ9I082.jpg

一、程序分析

硬件總線(xiàn)映射(引腳與時(shí)鐘的“避坑點(diǎn)”)

#define FLASH_SPIx                CW_SPI2
// 注意:CW32 中 SPI1 在 APB2 總線(xiàn),而 SPI2 通常掛載在 APB1 總線(xiàn)上!
#define FLASH_SPI_CLK             RCC_APB1_PERIPH_SPI2 
#define FLASH_SPI_APBClkENx       RCC_APBPeriphClk_Enable1 // 改為 APB1 的時(shí)鐘使能
//SPIx GPIO 統(tǒng)一修改為 GPIOB 及對(duì)應(yīng)的引腳
#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 (引腳復(fù)用功能重映射)
#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 總線(xiàn),而 SPI2 通常掛載在 APB1 總線(xiàn)上!很多新手移植代碼時(shí),把 SPI1 改成 SPI2,引腳也改了,但 Flash 就是沒(méi)反應(yīng)。原因就在于沒(méi)注意單片機(jī)內(nèi)部的總線(xiàn)掛載情況,把 APB1 錯(cuò)寫(xiě)成了 APB2,導(dǎo)致時(shí)鐘根本沒(méi)開(kāi)起來(lái)。。

初始化參數(shù):用代碼還原“時(shí)序圖”

示例程序:SPI 初始化核心代碼段

/************************ SPI 參數(shù)配置 ***********************/
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;  // 雙線(xiàn)全雙工 (DI和DO兩根線(xiàn)同時(shí)工作)
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;                       // 主機(jī)模式 (單片機(jī)當(dāng)老板,F(xiàn)lash當(dāng)員工)
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;                   // 一次發(fā) 8 個(gè) bit (一個(gè)字節(jié))
// 重點(diǎn) 1:時(shí)鐘極性與相位 (還原 Mode 3)
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;                         // 時(shí)鐘空閑時(shí)為高電平
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;                        // 在第 2 個(gè)邊沿 (上升沿) 抓取數(shù)據(jù)
// 重點(diǎn) 2:片選信號(hào)軟件控制
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;                           // 放棄硬件CS,改用普通GPIO軟件控制
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8;  // 速度設(shè)置:分頻系數(shù) (可根據(jù)需要調(diào)整)
// 重點(diǎn) 3:高低位順序
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;                  // 最高有效位 (MSB) 最先發(fā)送
SPI_Init(FLASH_SPIx, &SPI_InitStructure);                           // 把配置參數(shù)正式寫(xiě)入單片機(jī)寄存器
SPI_Cmd(FLASH_SPIx, ENABLE);                                        // 啟動(dòng) SPI 模塊

大家還記得前面我們?cè)?W25Q64 數(shù)據(jù)手冊(cè)里看到的時(shí)序圖嗎?有一條虛線(xiàn)標(biāo)著 Mode 3,它的特點(diǎn)是:單片機(jī)不發(fā)數(shù)據(jù)時(shí),時(shí)鐘線(xiàn)(CLK)是停在高電平的。 代碼里的 SPI_CPOL = SPI_CPOL_High 就是在告訴單片機(jī):‘沒(méi)事干的時(shí)候,把時(shí)鐘線(xiàn)拉高’。

那么什么時(shí)候讀數(shù)據(jù)呢?Mode 3 規(guī)定是在時(shí)鐘的上升沿。大家想,既然空閑是高電平,那它動(dòng)起來(lái)的第一個(gè)動(dòng)作肯定是‘往下拉’(下降沿,第 1 個(gè)邊沿),然后才是‘往上拉’(上升沿,第 2 個(gè)邊沿)。所以,我們必須把相位配置成 SPI_CPHA = SPI_CPHA_2Edge。

這兩行代碼加在一起,就是標(biāo)準(zhǔn)的 SPI Mode 3!

手動(dòng)包裹每一次通訊 (重點(diǎn))

這是軟件 CS 最直觀的體現(xiàn)。 SPI_FLASH_WriteEnable 函數(shù),就像做漢堡一樣,把發(fā)送數(shù)據(jù)的動(dòng)作“夾”在拉低和拉高之間:

void SPI_FLASH_WriteEnable(void){
  FLASH_SPI_CS_LOW();                     // 1. 手動(dòng)拉低:老師點(diǎn)名“W25Q64,聽(tīng)好了!”
  SPI_FLASH_SendByte(FLASH_CMD_WriteEnable); // 2. 發(fā)送 0x06 指令
  FLASH_SPI_CS_HIGH();                    // 3. 手動(dòng)拉高:指令結(jié)束,“去執(zhí)行吧!”
}

以后不管是發(fā) 1 個(gè)字節(jié),還是發(fā) 256 個(gè)字節(jié),格式永遠(yuǎn)是:先拉低 -> 中間瘋狂發(fā)數(shù)據(jù) -> 最后拉高。

二、 為什么放棄硬件 CS,非要自己用軟件寫(xiě)?

硬件 SPI 往往很“死板”。有些單片機(jī)的硬件 CS 邏輯是:每發(fā)送完一個(gè)字節(jié),它就會(huì)自動(dòng)把 CS 拉高一下,然后再拉低發(fā)下一個(gè)字節(jié)。

致命后果:回憶一下我們之前的時(shí)序圖,如果執(zhí)行 Page Program (頁(yè)寫(xiě)入) 連續(xù)寫(xiě) 256 個(gè)字節(jié),W25Q64 要求這期間 CS 必須全程保持低電平。如果硬件 SPI 中途把 CS 拉高了哪怕一微秒,F(xiàn)lash 就會(huì)認(rèn)為:“通訊被意外打斷了,剛才收到的數(shù)據(jù)全部作廢!”

軟件 CS 的優(yōu)勢(shì):只有程序員才知道一次通訊到底多長(zhǎng)。用代碼控制,哪怕發(fā) 1000 個(gè)字節(jié),只要我們不寫(xiě) FLASH_SPI_CS_HIGH(),門(mén)就永遠(yuǎn)開(kāi)著。

可能會(huì)有人以為,把 0x06 或擦除指令發(fā)給 Flash,它立刻就去干活了。錯(cuò)!

原理解密:Flash 內(nèi)部有一個(gè)指令緩存。它一直在聽(tīng),直到看到 CS 從低變高(上升沿) 的那一瞬間,它才知道:“哦,單片機(jī)的話(huà)說(shuō)完了,我現(xiàn)在立刻去執(zhí)行!”

軟件 CS 的優(yōu)勢(shì):通過(guò)軟件代碼,我們能精準(zhǔn)地確保最后一個(gè) bit 完全從引腳上發(fā)送出去了,再?gòu)娜莸貓?zhí)行 PB12_SETHIGH(),觸發(fā) Flash 內(nèi)部的高壓泵去擦寫(xiě)。硬件 CS 往往在時(shí)鐘停止的那一瞬間就立刻抬起,有時(shí)會(huì)導(dǎo)致最后一個(gè)比特的保持時(shí)間不夠。

3、SPI 的“心臟”:底層收發(fā)函數(shù)

/**
 * @brief 通過(guò) SPI 發(fā)送 1 個(gè)字節(jié),同時(shí)接收 1 個(gè)字節(jié)
 */
uint8_t SPI_FLASH_SendByte(uint8_t byte)
{
  /*1. 等待發(fā)送漏斗空出來(lái) (TXE: Transmit Buffer Empty)單片機(jī)往外發(fā)數(shù)據(jù)是需要時(shí)間的
  發(fā)送寄存器里上一個(gè)字節(jié)還沒(méi)漏完,馬上又塞一個(gè)新字節(jié)進(jìn)去,新數(shù)據(jù)就會(huì)把老數(shù)據(jù)擠爆(覆蓋掉)。
  所以我們必須死等,直到單片機(jī)說(shuō):‘報(bào)告,TXE 標(biāo)志位置位了’ 我們才能執(zhí)行下一步 SPI_SendData 進(jìn)去。
  */
  while(SPI_GetFlagStatus(FLASH_SPIx, SPI_FLAG_TXE) == RESET);
  // 2. 把數(shù)據(jù)倒進(jìn)發(fā)送漏斗
  SPI_SendData(FLASH_SPIx, byte);

  // 3. 等待接收漏斗裝滿(mǎn) (RXNE: Receive Buffer Not Empty)
  /*
  單片機(jī)就會(huì)觸發(fā)一個(gè)嚴(yán)重的溢出錯(cuò)誤(OVR 標(biāo)志位置位)。一旦發(fā)生這個(gè)錯(cuò)誤,SPI 硬件就會(huì)強(qiáng)行自我鎖死,
  拒絕再發(fā)送或接收任何數(shù)據(jù),直到你手動(dòng)去清空錯(cuò)誤標(biāo)志
  */
  while(SPI_GetFlagStatus(FLASH_SPIx, SPI_FLAG_RXNE) == RESET);
  // 4. 把接收漏斗里的數(shù)據(jù)拿出來(lái)返回、
  /*
  SPI 最核心的物理機(jī)制了:移位寄存器(Shift Register)。
  SPI 的 MOSI(發(fā))和 MISO(收)在單片機(jī)內(nèi)部其實(shí)連著同一個(gè)首尾相接的環(huán)形跑道。
  你每往外擠出去 1 個(gè) bit,外面就必然會(huì)擠進(jìn)來(lái) 1 個(gè) bit。
   也就是說(shuō),哪怕你只是想單純地發(fā)指令給 Flash(比如發(fā) 0x06),當(dāng)你發(fā)完這 8 個(gè) bit 的同時(shí),F(xiàn)lash 也會(huì)被迫通過(guò) MISO 給你塞回來(lái) 8 個(gè) bit 的‘垃圾數(shù)據(jù)’。
   我們?nèi)绻话堰@些垃圾數(shù)據(jù)從接收漏斗(SPI_ReceiveData)里拿走清空,下次想真正收數(shù)據(jù)時(shí),系統(tǒng)就會(huì)報(bào)錯(cuò)。這就是為什么發(fā)送函數(shù)最后必須要 return 一個(gè)接收值?!?  */
  return SPI_ReceiveData(FLASH_SPIx);
}

4、擦與寫(xiě)操作

wKgZO2nLzCaANJqMAACU-gvAqYg093.jpg

/**
 * @brief 扇區(qū)擦除 4KB
 * 
 * @param SectorAddr :待擦除的扇區(qū)地址
 */
void SPI_FLASH_SectorErase(uint32_t SectorAddr)
{
  //發(fā)送 寫(xiě)使能 指令
  SPI_FLASH_WriteEnable();
  //等待寫(xiě)入完成
  // SPI_FLASH_WaitForWriteEnd();
  FLASH_SPI_CS_LOW();
  //發(fā)送 扇區(qū)擦除 指令
  SPI_FLASH_SendByte(FLASH_CMD_SectorErase);
  //發(fā)送 待擦除扇區(qū)地址
  SPI_FLASH_SendByte((SectorAddr & 0xFF0000) >> 16); // 發(fā)送高 8 位地址
  SPI_FLASH_SendByte((SectorAddr & 0xFF00) >> 8);    // 發(fā)送中 8 位地址
  SPI_FLASH_SendByte(SectorAddr & 0xFF);             // 發(fā)送低 8 位地址
  FLASH_SPI_CS_HIGH();
  //等待擦除完成
  SPI_FLASH_WaitForWriteEnd();
}

傳入的 SectorAddr 最好是 4096 的整數(shù)倍(比如 0x000000, 0x001000)。如果你傳了個(gè)中間地址,F(xiàn)lash 還是會(huì)暴力地把包含這個(gè)地址的整個(gè) 4KB 扇區(qū)全部抹掉!

wKgZPGnLzCeAZLDyAAFRg4WcmBY909.jpg

這段代碼極其簡(jiǎn)單,就是個(gè) while 循環(huán),把傳進(jìn)來(lái)的數(shù)組數(shù)據(jù)一個(gè)一個(gè)發(fā)出去。 但它有一個(gè)致命的物理限制——它絕對(duì)不能跨頁(yè)! 如果你在這一頁(yè)的第 250 個(gè)字節(jié)處開(kāi)始寫(xiě),準(zhǔn)備寫(xiě) 10 個(gè)字節(jié)。當(dāng)寫(xiě)到第 256 個(gè)字節(jié)(本頁(yè)結(jié)尾)時(shí),F(xiàn)lash 不會(huì)自動(dòng)翻頁(yè)!它會(huì)像打字機(jī)卡殼一樣,強(qiáng)行把打字頭拽回本頁(yè)的第 1 個(gè)字節(jié),把你之前好端端的數(shù)據(jù)給覆蓋掉。這就是著名的‘頁(yè)卷回(Page Wrap)’災(zāi)難?!?/p>

如果沒(méi)有大容量的 RAM 做緩存,就全靠這個(gè)函數(shù)來(lái)智能切分?jǐn)?shù)據(jù),安全跨頁(yè)。

wKgZO2nLzCeAcPhkAAG14KXdvc8347.jpg

/**
 * @brief 寫(xiě)入不定量數(shù)據(jù)
 * 
 * @param pBuffer :待寫(xiě)入數(shù)據(jù)的指針
 * @param WriteAddr :寫(xiě)入地址
 * @param NumByteToWrite :寫(xiě)入數(shù)據(jù)長(zhǎng)度
 * @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 剛好按頁(yè)對(duì)齊 */
  {
    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 不對(duì)齊  */
  {
    if(NumOfPage == 0) /* NumByteToWrite < SPI_FLASH_PageSize */
    {
      if(NumOfSingle > count) /*!< (NumByteToWrite + WriteAddr) > SPI_FLASH_PageSize */
      {
        temp = NumOfSingle - count;

        //寫(xiě)完當(dāng)前頁(yè)
        SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);
        WriteAddr +=  count;
        pBuffer += count;
        //寫(xiě)剩余數(shù)據(jù)
        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;

      //先寫(xiě)完當(dāng)前頁(yè),以后地址將對(duì)齊
      SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);
      WriteAddr +=  count;
      pBuffer += count;
      //WriteAddr 剛好按頁(yè)對(duì)齊
      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);
      }
    }
  }
}

算法邏輯解剖:四個(gè)關(guān)鍵變量:

Addr = WriteAddr % SPI_FLASH_PageSize;

翻譯:算一下你要寫(xiě)的起始位置,在當(dāng)前頁(yè)的偏移量是多少(也就是打字機(jī)現(xiàn)在處于這一頁(yè)的第幾格)。如果 Addr == 0,說(shuō)明剛好在一頁(yè)的開(kāi)頭(完美對(duì)齊)。

count = SPI_FLASH_PageSize - Addr;

翻譯:算一下當(dāng)前這一頁(yè),還剩下多少空位可以寫(xiě)。

NumOfPage = NumByteToWrite / SPI_FLASH_PageSize;

翻譯:算一下你給的數(shù)據(jù)總長(zhǎng),能填滿(mǎn)幾個(gè)完整的整頁(yè)

NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;

翻譯:算一下填滿(mǎn)整頁(yè)后,最后還剩下的一條“小尾巴”是幾個(gè)字節(jié)。

這個(gè)函數(shù)的本質(zhì)就是‘填坑與翻頁(yè)’。 假設(shè)你現(xiàn)在身處第一頁(yè)的末尾,還剩 10 個(gè)空位(count=10),但你手里有 300 個(gè)字節(jié)要寫(xiě)。 這個(gè)函數(shù)的邏輯是:

先調(diào)用 PageWrite,把手里的 10 個(gè)字節(jié)塞進(jìn)當(dāng)前的空位,把這一頁(yè)填滿(mǎn)。

此時(shí)地址自動(dòng)對(duì)齊到了下一頁(yè)的開(kāi)頭。

手里還剩 290 個(gè)字節(jié)。算一下,剛好能填滿(mǎn) 1 個(gè)整頁(yè)(256字節(jié))。于是用 while 循環(huán)再調(diào)用一次 PageWrite 寫(xiě)入 256 字節(jié)。

最后剩下一條 34 字節(jié)的尾巴(NumOfSingle=34),再調(diào)用一次 PageWrite 收尾。 有了這個(gè)總監(jiān)把關(guān),我們?cè)趹?yīng)用層只需要無(wú)腦調(diào)用 BufferWrite,想寫(xiě)多少寫(xiě)多少,再也不用管什么 256 字節(jié)的物理邊界了!

示例

假設(shè)你現(xiàn)在身處第一頁(yè)的末尾,還剩 10 個(gè)空位(count=10),但你手里有 300 個(gè)字節(jié)要寫(xiě)。 這個(gè)函數(shù)的邏輯是:

先調(diào)用 PageWrite,把手里的 10 個(gè)字節(jié)塞進(jìn)當(dāng)前的空位,把這一頁(yè)填滿(mǎn)。

此時(shí)地址自動(dòng)對(duì)齊到了下一頁(yè)的開(kāi)頭。

手里還剩 290 個(gè)字節(jié)。算一下,剛好能填滿(mǎn) 1 個(gè)整頁(yè)(256字節(jié))。于是用 while 循環(huán)再調(diào)用一次 PageWrite 寫(xiě)入 256 字節(jié)。

最后剩下一條 34 字節(jié)的尾巴(NumOfSingle=34),再調(diào)用一次 PageWrite 收尾。 有了這個(gè)總監(jiān)把關(guān),我們?cè)趹?yīng)用層只需要無(wú)腦調(diào)用 BufferWrite,想寫(xiě)多少寫(xiě)多少,再也不用管什么 256 字節(jié)的物理邊界了!

大家發(fā)現(xiàn)沒(méi)有,讀取函數(shù)并沒(méi)有像寫(xiě)入那樣去算什么頁(yè)邊界、滿(mǎn)不滿(mǎn)的問(wèn)題。 為什么?因?yàn)?Flash 的物理結(jié)構(gòu)對(duì)‘讀操作’沒(méi)有設(shè)限!只要你不拉高 CS 引腳,內(nèi)部的地址指針就會(huì)自動(dòng)加 1。哪怕你直接讓 NumByteToRead = 8388608(8MB),它也會(huì)順暢地把整顆芯片從頭到尾給你掃一遍。這就是‘掃描儀’的威力。

chaijie_default.png

void SPI_FLASH_BufferRead(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead)
{
  // 動(dòng)作 1:拉下開(kāi)關(guān),告訴 Flash 準(zhǔn)備干活
  FLASH_SPI_CS_LOW();
  // 動(dòng)作 2:發(fā)送“普通讀”指令 (0x03)
  SPI_FLASH_SendByte(FLASH_CMD_ReadData);
  // 動(dòng)作 3:發(fā)送 24 位起始地址 (從哪里開(kāi)始讀?)
  SPI_FLASH_SendByte((ReadAddr & 0xFF0000) >> 16); // 高 8 位
  SPI_FLASH_SendByte((ReadAddr& 0xFF00) >> 8);     // 中 8 位
  SPI_FLASH_SendByte(ReadAddr & 0xFF);             // 低 8 位

  // 動(dòng)作 4:開(kāi)啟“吸塵器”模式,瘋狂吸取數(shù)據(jù)
  while(NumByteToRead--)
  {
    *pBuffer = SPI_FLASH_ReadByte(); // 內(nèi)部在發(fā) 0xFF 啞字節(jié)換取數(shù)據(jù)
    pBuffer++;                       // 指針后移,準(zhǔn)備存下一個(gè)字節(jié)
  }
  // 動(dòng)作 5:完工,拉高 CS 結(jié)束通訊
  FLASH_SPI_CS_HIGH();
}

大家仔細(xì)對(duì)比一下我們上一節(jié)講的 BufferWrite,寫(xiě)數(shù)據(jù)的時(shí)候,代碼長(zhǎng)達(dá)幾十行,要算偏移量、算剩余空間,一旦跨越 256 字節(jié)的頁(yè)邊界就得重新發(fā)地址。

但是你們看讀數(shù)據(jù)的代碼,居然只有一個(gè)簡(jiǎn)單的 while 循環(huán)! 它根本不管 256 字節(jié)的界限,想讀多少就讀多少(NumByteToRead 甚至可以填幾萬(wàn))。這是為什么呢?

這就是 Flash 的物理魅力!寫(xiě)數(shù)據(jù)像用老式打字機(jī),打到紙的邊緣(256字節(jié))就會(huì)卡死,必須手動(dòng)換行(重新發(fā)地址)。而讀數(shù)據(jù)就像拉開(kāi)一幅無(wú)盡的卷軸,只要你一開(kāi)始告訴它一個(gè)起始地址(動(dòng)作 3),并且只要 CS 引腳一直保持低電平,F(xiàn)lash 內(nèi)部的地址指針就會(huì)自動(dòng) +1、跨頁(yè)、跨扇區(qū)、跨塊,暢通無(wú)阻!

我們現(xiàn)在用的指令是 0x03(普通讀取)。在 W25Q64 的手冊(cè)里,普通讀取的時(shí)鐘頻率是有上限的(通常在 33MHz 甚至更低)。 如果你的 CW32 單片機(jī)跑得飛快,把 SPI 時(shí)鐘設(shè)置到了 48MHz 極限狂飆,用這個(gè) 0x03 指令讀出來(lái)的數(shù)據(jù)可能會(huì)錯(cuò)位或者全是亂碼!

解決辦法: 把 0x03 換成我們?cè)跁r(shí)序圖章節(jié)講過(guò)的 0x0B(Fast Read,極速讀取)。唯一的區(qū)別是,發(fā)完 24 位地址后,代碼里要多發(fā)一個(gè)字節(jié)的 0xFF(8個(gè) Dummy Clocks)給 Flash 留出反應(yīng)時(shí)間,然后才能進(jìn)入 while 循環(huán)去吸取真實(shí)數(shù)據(jù)。

二、SDK分析與移植

1.SDK分析

wKgZPGnLzCmAZ8P7AAD0rhfceQs116.jpgchaijie_default.png

原工程中沒(méi)有下列程序,需要自己找一個(gè)地方加進(jìn)去

wKgZO2nLzCqAZIQtAADcFA8CjBA087.jpg

/* 1. 針對(duì) AC6 的禁用半主機(jī)指令 */
__asm(".global __use_no_semihostingnt");
/* 2. 定義標(biāo)準(zhǔn)庫(kù)需要的支持函數(shù) */
#include 
/* 這里的 __FILE 結(jié)構(gòu)體在 AC6 下通常不需要手動(dòng)定義,MicroLIB 會(huì)處理 */
/* 但為了徹底重定向 printf,我們需要實(shí)現(xiàn)底層輸出函數(shù) */
// 如果你沒(méi)在其他地方定義 fputc,請(qǐng)加上這段:
int fputc(int ch, FILE *f) {
    // 假設(shè)你使用的是 UART1,發(fā)送寄存器為 TDR
    // 這里的具體寄存器名根據(jù) CW32 庫(kù)文件決定,通常是 CW_UART1->TDR 或類(lèi)似
    USART_SendData_8bit(CW_UART1, (uint8_t)ch); 
    while (USART_GetFlagStatus(CW_UART1, USART_FLAG_TXE) == RESET);
    return ch;
}
/* 3. 定義半主機(jī)依賴(lài)的底層存根函數(shù) */
void _sys_exit(int x) {
    x = x;
    while (1); // 報(bào)錯(cuò)后死循環(huán)
}
void _ttywrch(int ch) {
    ch = ch;
}

這段代碼是嵌入式開(kāi)發(fā)里非常經(jīng)典的 printf串口重定向與半主機(jī)模式(Semihosting)禁用” 模板。特別是當(dāng)你從舊版的 Keil AC5 編譯器升級(jí)到最新的 AC6 編譯器 時(shí),這段代碼是必須要有的“護(hù)身符”。

如果在代碼里調(diào)用了 printf(),但不加這段程序

這段代碼是嵌入式開(kāi)發(fā)里非常經(jīng)典的 printf串口重定向與半主機(jī)模式(Semihosting)禁用” 模板。特別是當(dāng)你從舊版的 Keil AC5 編譯器升級(jí)到最新的 AC6 編譯器 時(shí),這段代碼是必須要有的“護(hù)身符”。

如果在代碼里調(diào)用了 printf(),但不加這段程序,你會(huì)遇到兩種極其折磨人的報(bào)錯(cuò)現(xiàn)象:

現(xiàn)象一:編譯直接報(bào)錯(cuò)(Linker Error)

如果你不加 _sys_exit 和 _ttywrch 這幾個(gè)存根函數(shù),同時(shí)又在代碼里用了標(biāo)準(zhǔn) C 庫(kù)函數(shù)(沒(méi)勾選 MicroLIB 的情況下),點(diǎn)擊編譯時(shí),Keil 的 Build Output 窗口會(huì)爆出紅色的底層鏈接錯(cuò)誤:

常見(jiàn)報(bào)錯(cuò)長(zhǎng)這樣:

Error: L6218E: Undefined symbol _sys_exit (referred from ...)

Error: L6218E: Undefined symbol _ttywrch (referred from ...)

Error: L6218E: Undefined symbol __aeabi_assert ...

為什么報(bào)錯(cuò)? C 語(yǔ)言的標(biāo)準(zhǔn)庫(kù)原本是給電腦(Windows/Linux)設(shè)計(jì)的,當(dāng)程序出錯(cuò)或者結(jié)束時(shí),它會(huì)默認(rèn)去調(diào)用操作系統(tǒng)的退出函數(shù)(exit)或終端輸出函數(shù)(ttywrch)。但我們的 CW32 單片機(jī)里根本沒(méi)有操作系統(tǒng)!編譯器找不到這些底層函數(shù),就會(huì)報(bào)“未定義符號(hào)”的錯(cuò)誤。代碼里寫(xiě)死這幾個(gè)空函數(shù),就是為了騙過(guò)編譯器:“行了,退出函數(shù)我給你準(zhǔn)備好了,你別報(bào)錯(cuò)了?!?/p>

現(xiàn)象二:運(yùn)行時(shí)“拔線(xiàn)死機(jī)”(The Silent Killer)

這是最坑、最容易讓崩潰的現(xiàn)象。如果你沒(méi)加 __asm(".global __use_no_semihostingnt"); 這句話(huà),編譯可能完全通過(guò),零警告,但一下載到板子上就會(huì)出現(xiàn)“靈異事件”:

插著仿真器調(diào)試: 代碼跑得好好的,printf 的數(shù)據(jù)能在 Keil 的 Debug 窗口里打印出來(lái)。

拔掉仿真器,插充電寶獨(dú)立供電: 板子死機(jī)了!程序卡死在啟動(dòng)階段,LED 也不閃了,所有任務(wù)罷工。

為什么死機(jī)?(半主機(jī)模式的坑) 半主機(jī)模式(Semihosting)是一種調(diào)試機(jī)制。它會(huì)讓單片機(jī)的 printf 試圖通過(guò) JTAG/SWD 仿真器的數(shù)據(jù)線(xiàn),把字符傳給電腦屏幕。 如果沒(méi)禁用半主機(jī)模式,每次執(zhí)行 printf 時(shí),單片機(jī)內(nèi)部會(huì)觸發(fā)一條特殊的硬件斷點(diǎn)指令(BKPT)來(lái)呼叫電腦。當(dāng)你拔掉仿真器獨(dú)立運(yùn)行時(shí),單片機(jī)喊破喉嚨也沒(méi)人理它,它就會(huì)一直卡在這個(gè)斷點(diǎn)指令上,導(dǎo)致整個(gè)系統(tǒng)徹底死機(jī)。

現(xiàn)象三:printf 變成“啞巴”

如果不加 fputc 這個(gè)函數(shù):

現(xiàn)象: 編譯可以通過(guò),程序也不會(huì)死機(jī),但是你的電腦串口助手里收不到任何數(shù)據(jù)。

為什么?printf 只負(fù)責(zé)把你要發(fā)送的變量轉(zhuǎn)換成字符格式(比如把數(shù)字 123 變成字符 '1', '2', '3'),但它不知道這些字符要從單片機(jī)的哪個(gè)引腳扔出去。 fputc 就是 printf 和 CW32 硬件之間的**“水管接頭”**。你在 fputc 里寫(xiě)了 USART_SendData_8bit(CW_UART1, ch),printf 才知道:“哦!原來(lái)我要把字符塞進(jìn) UART1 的發(fā)送寄存器里啊。”

2.示例程序

chaijie_default.png

#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 長(zhǎng)度為 6 + ''
uint8_t DeviceID = 0;
uint16_t ManufactDeviceID = 0;
uint32_t JedecID = 0;
uint8_t UniqueID[8];
// 使用新名字
volatile FlashTestStatus TransferStatus = FLASH_FAILED; 
// 替換返回類(lèi)型和內(nèi)部比較的宏
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);
//    
//    // 擦除扇區(qū) 4KB
//    SPI_FLASH_SectorErase(FLASH_SectorToEraseAddress);         
//        
//    // 寫(xiě)數(shù)據(jù)
//    SPI_FLASH_BufferWrite(Flash_TxBuffer, FLASH_WriteAddress, BufferSize);        
//    printf("rn嘗試寫(xiě)入的數(shù)據(jù)為: %srn", Flash_TxBuffer);
//        
//    // 讀數(shù)據(jù)
//    SPI_FLASH_BufferRead(Flash_RxBuffer, FLASH_ReadAddress, BufferSize);
//    printf("rn實(shí)際讀出的數(shù)據(jù)為: %srn", Flash_RxBuffer);
//        
//    // 檢查
//    TransferStatus = Buffercmp(Flash_TxBuffer, Flash_RxBuffer, BufferSize);
//    if(TransferStatus == FLASH_PASSED)
//    { 
//        printf("rnFLASH Success! kunkun 驗(yàn)證通過(guò)!rn");
//    }
//    else
//    {        
//        printf("rnFLASH Error 1! 數(shù)據(jù)不一致!rn");
//    }
//}
void flash_fun(void)
{
    // --- 步驟 1:讀取 ID 確認(rèn)通信 (保持不變) ---
    DeviceID = SPI_FLASH_DeviceID();
    ManufactDeviceID = SPI_FLASH_ManufactDeviceID();
    JedecID = SPI_FLASH_JedecID();    
    SPI_FLASH_UniqueID(UniqueID);

    // --- 步驟 2:測(cè)試第一個(gè)扇區(qū) (0-4KB) ---
    uint32_t addr1 = 0x0000;
    SPI_FLASH_SectorErase(addr1);  // 擦除第一個(gè) 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("rn[Sector 0] kunkun 驗(yàn)證通過(guò)!正在挑戰(zhàn) Sector 1...");
        // --- 步驟 3:測(cè)試第二個(gè)扇區(qū) (4-8KB) ---
        // 5-8KB 的數(shù)據(jù)屬于第二個(gè) 4KB 扇區(qū),起始地址為 0x1000
        uint32_t addr2 = 0x1000; 

        SPI_FLASH_SectorErase(addr2);  // 擦除第二個(gè) 4KB 扇區(qū)
        SPI_FLASH_BufferWrite(Flash_TxBuffer2, addr2, 7); 
        printf("rn嘗試向 0x1000 寫(xiě)入數(shù)據(jù): %s", Flash_TxBuffer2);

        SPI_FLASH_BufferRead(Flash_RxBuffer2, addr2, 7);
        printf("rn從 0x1000 實(shí)際讀出數(shù)據(jù): %s", Flash_RxBuffer2);

        if(Buffercmp(Flash_TxBuffer2, Flash_RxBuffer2, 7) == FLASH_PASSED)
        {
            printf("rn[Sector 1] zhiyin 驗(yàn)證通過(guò)!兩個(gè)區(qū)域均正常!rn");
            TransferStatus = FLASH_PASSED;
        }
        else
        {
            printf("rn[Sector 1] zhiyin 失敗,請(qǐng)檢查地址 0x1000 處的寫(xiě)入。");
            TransferStatus = FLASH_FAILED;
        }
    }
    else
    {
        printf("rn[Sector 0] kunkun 驗(yàn)證失敗,請(qǐng)檢查底層驅(qū)動(dòng)。");
        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"
// 全局中斷標(biāo)志 (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.實(shí)物與效果展示

注意:W25Q64 是 3.3V 器件,嚴(yán)禁接 5V

方案一:獨(dú)立運(yùn)行模式(無(wú)串口打?。?/strong>

當(dāng)你完成調(diào)試,準(zhǔn)備將網(wǎng)關(guān)部署到 500 個(gè)節(jié)點(diǎn)的現(xiàn)場(chǎng)時(shí),可以撤掉串口模塊以精簡(jiǎn)電路。

連接設(shè)備 設(shè)備引腳 CW32F030 引腳 說(shuō)明
W25Q64 VCC 3.3V 電源
W25Q64 GND GND 電源地
W25Q64 /CS PB12 軟件片選 (CS)
W25Q64 CLK PB10 SPI2 時(shí)鐘 (SCK)
W25Q64 DO (IO1) PB14 SPI2 數(shù)據(jù)輸出 (MISO)
W25Q64 DI (IO0) PB15 SPI2 數(shù)據(jù)輸入 (MOSI)
其他 PA08 / PA09 懸空 串口引腳不接線(xiàn),代碼可保留以防報(bào)錯(cuò)

chaijie_default.png

wKgZPGnLzCyAYocrAABSrE9W0hw780.jpg

方案二:開(kāi)發(fā)調(diào)試模式(帶串口打印 printf

原工程就是如此,可以通過(guò)串口打印出來(lái)信息。

此模式下,你可以通過(guò)電腦串口助手查看 Flash 的 ID 識(shí)別情況和程序運(yùn)行狀態(tài)。

連接設(shè)備 設(shè)備引腳 CW32F030 引腳 說(shuō)明
W25Q64 VCC 3.3V 電源(嚴(yán)禁接 5V)
W25Q64 GND GND 電源地
W25Q64 /CS PB12 軟件片選 (CS)
W25Q64 CLK PB10 SPI2 時(shí)鐘 (SCK)
W25Q64 DO (IO1) PB14 SPI2 數(shù)據(jù)輸出 (MISO)
W25Q64 DI (IO0) PB15 SPI2 數(shù)據(jù)輸入 (MOSI)
USB轉(zhuǎn)TTL RXD PA08 單片機(jī)發(fā)送 (TX),接模塊接收
USB轉(zhuǎn)TTL TXD PA09 單片機(jī)接收 (RX),接模塊發(fā)送
USB轉(zhuǎn)TTL GND GND 共地(通訊基礎(chǔ))

chaijie_default.pngwKgZO2nLzC2ATk2HAAAwEjHI5-o554.jpg


審核編輯 黃宇

聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 無(wú)線(xiàn)抄表
    +關(guān)注

    關(guān)注

    0

    文章

    40

    瀏覽量

    17435
  • CW32
    +關(guān)注

    關(guān)注

    1

    文章

    323

    瀏覽量

    1935
收藏 人收藏
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

    評(píng)論

    相關(guān)推薦
    熱點(diǎn)推薦

    CW32無(wú)線(xiàn)表項(xiàng)目】主控及射頻芯片介紹

    對(duì)于開(kāi)發(fā)者而言,CW32F030 的硬件設(shè)計(jì)在同類(lèi) M0+ 芯片中具有極強(qiáng)的競(jìng)爭(zhēng)力。
    的頭像 發(fā)表于 04-01 17:06 ?3806次閱讀
    【<b class='flag-5'>CW32</b><b class='flag-5'>無(wú)線(xiàn)</b><b class='flag-5'>抄</b><b class='flag-5'>表項(xiàng)目</b>】主控及射頻芯片介紹

    CW32移植Free-RTOS】CW32開(kāi)發(fā)者扶持計(jì)劃

    CW32配置Free-RTOS全過(guò)程,CW32開(kāi)發(fā)者扶持計(jì)劃
    的頭像 發(fā)表于 04-18 09:38 ?7503次閱讀
    【<b class='flag-5'>CW32</b>移植Free-RTOS】<b class='flag-5'>CW32</b>開(kāi)發(fā)者扶持計(jì)劃

    CW32快速開(kāi)發(fā)入門(mén)

    CW32快速開(kāi)發(fā)入門(mén)
    的頭像 發(fā)表于 04-24 18:56 ?3769次閱讀
    <b class='flag-5'>CW32</b>快速開(kāi)發(fā)入門(mén)

    CW32量產(chǎn)燒錄工具

    本節(jié)主要介紹CW32微控制器的燒錄器CW-Writer,以及與之配合的軟件CW-Programmer的使用方法。燒錄器CW-Writer通過(guò)ISP協(xié)議,可實(shí)現(xiàn)對(duì)
    的頭像 發(fā)表于 04-25 15:22 ?2991次閱讀
    <b class='flag-5'>CW32</b>量產(chǎn)燒錄工具

    CW32 MCU有哪些系列?

    目前CW32 MCU有通用高性能MCU、安全低功耗MCU、無(wú)線(xiàn)射頻MCU等3個(gè)系列。其中射頻MCU集成了無(wú)線(xiàn)收發(fā)器,主要包括CW32R031(2.4GHz BLE-Lite)系列和
    發(fā)表于 11-12 07:34

    CW32開(kāi)發(fā)者扶持計(jì)劃#CW32 #芯片

    CW32
    CW32生態(tài)社區(qū)
    發(fā)布于 :2023年05月24日 16:56:14

    cw32和stm32的區(qū)別

    cw32和stm32的區(qū)別 CW32和STM32是兩種常見(jiàn)的單片機(jī),被廣泛應(yīng)用于各種電子設(shè)備中。在本文中,我們將深入探討CW32和STM32之間的區(qū)別和優(yōu)劣勢(shì)。 1. 硬件性能 硬件性能是衡量單片機(jī)
    的頭像 發(fā)表于 08-16 11:15 ?6491次閱讀

    cw32和gd32的區(qū)別

    cw32和gd32的區(qū)別 CW32和GD32是兩種不同的芯片系列,分別由WCH和GigaDevice公司推出,兩者有很多不同之處,下面我們來(lái)詳細(xì)介紹。 首先從CW32系列開(kāi)始,CW32
    的頭像 發(fā)表于 08-16 11:15 ?3348次閱讀

    基于CW32熱敏電阻采集溫度應(yīng)用

    基于CW32熱敏電阻采集溫度應(yīng)用
    的頭像 發(fā)表于 10-25 16:45 ?1401次閱讀
    基于<b class='flag-5'>CW32</b>熱敏電阻采集溫度應(yīng)用

    CW32 PWM輸出功能介紹

    CW32 PWM輸出功能介紹
    的頭像 發(fā)表于 09-27 16:12 ?2320次閱讀
    <b class='flag-5'>CW32</b> PWM輸出功能介紹

    CW32實(shí)時(shí)時(shí)鐘(RTC)介紹

    CW32實(shí)時(shí)時(shí)鐘(RTC)介紹
    的頭像 發(fā)表于 10-24 15:36 ?2329次閱讀
    <b class='flag-5'>CW32</b>實(shí)時(shí)時(shí)鐘(RTC)介紹

    應(yīng)用筆記-CW32 自舉程序中使用的 ISP 協(xié)議

    CW32自舉程序中使用的ISP協(xié)議CW32微控制器片上FLASH存儲(chǔ)器有一部分區(qū)域用于存儲(chǔ)BootLoader啟動(dòng)程序,在芯片出廠時(shí)已編程,用戶(hù)可利用BootLoader啟動(dòng)
    發(fā)表于 06-06 13:37 ?7次下載

    基于CW32的物聯(lián)網(wǎng)應(yīng)用

    CW32】基于CW32的物聯(lián)網(wǎng)應(yīng)用
    的頭像 發(fā)表于 11-02 15:55 ?2064次閱讀
    基于<b class='flag-5'>CW32</b>的物聯(lián)網(wǎng)應(yīng)用

    CW32無(wú)線(xiàn)表項(xiàng)目示例通信程序講解

    一、整體功能概述 這套程序模擬了一個(gè)真實(shí)的無(wú)線(xiàn)傳感器網(wǎng)絡(luò)(如表系統(tǒng))的通信流程: 主機(jī)(Master / 采集端): 負(fù)責(zé)發(fā)起通信。它首先發(fā)送“暗號(hào)”( kunkun )尋找從機(jī),收到從機(jī)的確
    的頭像 發(fā)表于 03-31 21:16 ?251次閱讀

    CW32無(wú)線(xiàn)表項(xiàng)目W25Q_CW32_DMA簡(jiǎn)介

    ‘專(zhuān)業(yè)的搬運(yùn)工’。 你只要告訴它:‘從哪搬(源)、搬到哪(目的)、搬多少(數(shù)量)’,然后 CPU 就可以去喝咖啡了,搬運(yùn)工作全交給 DMA 硬件電路自動(dòng)完成。等搬完了,它會(huì)敲敲門(mén)告訴 CPU:‘老板,活干完了?。ㄖ袛啵薄?階段一:CW32F030—DMA框圖 一共三件事:
    的頭像 發(fā)表于 03-31 21:41 ?320次閱讀
    【<b class='flag-5'>CW32</b><b class='flag-5'>無(wú)線(xiàn)</b><b class='flag-5'>抄</b><b class='flag-5'>表項(xiàng)目</b>】<b class='flag-5'>W25Q_CW</b>32_DMA簡(jiǎn)介