哈哈哈哈哈操欧洲电影,久草网在线,亚洲久久熟女熟妇视频,麻豆精品色,久久福利在线视频,日韩中文字幕的,淫乱毛视频一区,亚洲成人一二三,中文人妻日韩精品电影

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

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

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

【CW32無線抄表項目】W25Q+CW32程序示例

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

掃碼添加小助手

加入工程師交流群

資料下載:

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

參考倉庫:

https://gitee.com/Armink/SFUD

chaijie_default.pngwKgZO2nLzCWAcc1XAADV0IAqfPk103.jpgchaijie_default.pngwKgZPGnLzCaAcSl3AAEpkGOVJ9I082.jpg

一、程序分析

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

#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 統(tǒng)一修改為 GPIOB 及對應(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 總線,而 SPI2 通常掛載在 APB1 總線上!很多新手移植代碼時,把 SPI1 改成 SPI2,引腳也改了,但 Flash 就是沒反應(yīng)。原因就在于沒注意單片機(jī)內(nèi)部的總線掛載情況,把 APB1 錯寫成了 APB2,導(dǎo)致時鐘根本沒開起來。。

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

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

/************************ SPI 參數(shù)配置 ***********************/
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;  // 雙線全雙工 (DI和DO兩根線同時工作)
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 個 bit (一個字節(jié))
// 重點(diǎn) 1:時鐘極性與相位 (還原 Mode 3)
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;                         // 時鐘空閑時為高電平
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;                        // 在第 2 個邊沿 (上升沿) 抓取數(shù)據(jù)
// 重點(diǎn) 2:片選信號軟件控制
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ù)正式寫入單片機(jī)寄存器
SPI_Cmd(FLASH_SPIx, ENABLE);                                        // 啟動 SPI 模塊

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

4、擦與寫操作

wKgZO2nLzCaANJqMAACU-gvAqYg093.jpg

/**
 * @brief 扇區(qū)擦除 4KB
 * 
 * @param SectorAddr :待擦除的扇區(qū)地址
 */
void SPI_FLASH_SectorErase(uint32_t SectorAddr)
{
  //發(fā)送 寫使能 指令
  SPI_FLASH_WriteEnable();
  //等待寫入完成
  // 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)。如果你傳了個中間地址,F(xiàn)lash 還是會暴力地把包含這個地址的整個 4KB 扇區(qū)全部抹掉!

wKgZPGnLzCeAZLDyAAFRg4WcmBY909.jpg

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

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

wKgZO2nLzCeAcPhkAAG14KXdvc8347.jpg

/**
 * @brief 寫入不定量數(shù)據(jù)
 * 
 * @param pBuffer :待寫入數(shù)據(jù)的指針
 * @param WriteAddr :寫入地址
 * @param NumByteToWrite :寫入數(shù)據(jù)長度
 * @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;

        //寫完當(dāng)前頁
        SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);
        WriteAddr +=  count;
        pBuffer += count;
        //寫剩余數(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;

      //先寫完當(dāng)前頁,以后地址將對齊
      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);
      }
    }
  }
}

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

Addr = WriteAddr % SPI_FLASH_PageSize;

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

count = SPI_FLASH_PageSize - Addr;

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

NumOfPage = NumByteToWrite / SPI_FLASH_PageSize;

翻譯:算一下你給的數(shù)據(jù)總長,能填滿幾個完整的整頁。

NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;

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

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

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

此時地址自動對齊到了下一頁的開頭。

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

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

示例

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

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

此時地址自動對齊到了下一頁的開頭。

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

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

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

chaijie_default.png

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

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

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

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

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

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

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

二、SDK分析與移植

1.SDK分析

wKgZPGnLzCmAZ8P7AAD0rhfceQs116.jpgchaijie_default.png

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

wKgZO2nLzCqAZIQtAADcFA8CjBA087.jpg

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

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

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

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

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

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

如果你不加 _sys_exit 和 _ttywrch 這幾個存根函數(shù),同時又在代碼里用了標(biāo)準(zhǔn) C 庫函數(shù)(沒勾選 MicroLIB 的情況下),點(diǎn)擊編譯時,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 語言的標(biāo)準(zhǔn)庫原本是給電腦(Windows/Linux)設(shè)計的,當(dāng)程序出錯或者結(jié)束時,它會默認(rèn)去調(diào)用操作系統(tǒng)的退出函數(shù)(exit)或終端輸出函數(shù)(ttywrch)。但我們的 CW32 單片機(jī)里根本沒有操作系統(tǒng)!編譯器找不到這些底層函數(shù),就會報“未定義符號”的錯誤。代碼里寫死這幾個空函數(shù),就是為了騙過編譯器:“行了,退出函數(shù)我給你準(zhǔn)備好了,你別報錯了?!?/p>

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

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

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

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

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

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

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

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

為什么?printf 只負(fù)責(zé)把你要發(fā)送的變量轉(zhuǎn)換成字符格式(比如把數(shù)字 123 變成字符 '1', '2', '3'),但它不知道這些字符要從單片機(jī)的哪個引腳扔出去。 fputc 就是 printf 和 CW32 硬件之間的**“水管接頭”**。你在 fputc 里寫了 USART_SendData_8bit(CW_UART1, ch),printf 才知道:“哦!原來我要把字符塞進(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 長度為 6 + ''
uint8_t DeviceID = 0;
uint16_t ManufactDeviceID = 0;
uint32_t JedecID = 0;
uint8_t UniqueID[8];
// 使用新名字
volatile FlashTestStatus TransferStatus = FLASH_FAILED; 
// 替換返回類型和內(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);         
//        
//    // 寫數(shù)據(jù)
//    SPI_FLASH_BufferWrite(Flash_TxBuffer, FLASH_WriteAddress, BufferSize);        
//    printf("rn嘗試寫入的數(shù)據(jù)為: %srn", Flash_TxBuffer);
//        
//    // 讀數(shù)據(jù)
//    SPI_FLASH_BufferRead(Flash_RxBuffer, FLASH_ReadAddress, BufferSize);
//    printf("rn實際讀出的數(shù)據(jù)為: %srn", Flash_RxBuffer);
//        
//    // 檢查
//    TransferStatus = Buffercmp(Flash_TxBuffer, Flash_RxBuffer, BufferSize);
//    if(TransferStatus == FLASH_PASSED)
//    { 
//        printf("rnFLASH Success! kunkun 驗證通過!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:測試第一個扇區(qū) (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("rn[Sector 0] kunkun 驗證通過!正在挑戰(zhàn) Sector 1...");
        // --- 步驟 3:測試第二個扇區(qū) (4-8KB) ---
        // 5-8KB 的數(shù)據(jù)屬于第二個 4KB 扇區(qū),起始地址為 0x1000
        uint32_t addr2 = 0x1000; 

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

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

        if(Buffercmp(Flash_TxBuffer2, Flash_RxBuffer2, 7) == FLASH_PASSED)
        {
            printf("rn[Sector 1] zhiyin 驗證通過!兩個區(qū)域均正常!rn");
            TransferStatus = FLASH_PASSED;
        }
        else
        {
            printf("rn[Sector 1] zhiyin 失敗,請檢查地址 0x1000 處的寫入。");
            TransferStatus = FLASH_FAILED;
        }
    }
    else
    {
        printf("rn[Sector 0] kunkun 驗證失敗,請檢查底層驅(qū)動。");
        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.實物與效果展示

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

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

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

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

chaijie_default.png

wKgZPGnLzCyAYocrAABSrE9W0hw780.jpg

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

原工程就是如此,可以通過串口打印出來信息。

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

連接設(shè)備 設(shè)備引腳 CW32F030 引腳 說明
W25Q64 VCC 3.3V 電源(嚴(yán)禁接 5V)
W25Q64 GND GND 電源地
W25Q64 /CS PB12 軟件片選 (CS)
W25Q64 CLK PB10 SPI2 時鐘 (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)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 無線抄表
    +關(guān)注

    關(guān)注

    0

    文章

    40

    瀏覽量

    17435
  • CW32
    +關(guān)注

    關(guān)注

    1

    文章

    323

    瀏覽量

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

掃碼添加小助手

加入工程師交流群

    評論

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

    CW32無線表項目】主控及射頻芯片介紹

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

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

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

    CW32快速開發(fā)入門

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

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

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

    CW32 MCU有哪些系列?

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

    CW32開發(fā)者扶持計劃#CW32 #芯片

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

    cw32和stm32的區(qū)別

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

    cw32和gd32的區(qū)別

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

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

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

    CW32 PWM輸出功能介紹

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

    CW32實時時鐘(RTC)介紹

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

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

    CW32自舉程序中使用的ISP協(xié)議CW32微控制器片上FLASH存儲器有一部分區(qū)域用于存儲BootLoader啟動程序,在芯片出廠時已編程,用戶可利用BootLoader啟動
    發(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 ?2080次閱讀
    基于<b class='flag-5'>CW32</b>的物聯(lián)網(wǎng)應(yīng)用

    CW32無線表項目示例通信程序講解

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

    CW32無線表項目W25Q_CW32_DMA簡介

    ‘專業(yè)的搬運(yùn)工’。 你只要告訴它:‘從哪搬(源)、搬到哪(目的)、搬多少(數(shù)量)’,然后 CPU 就可以去喝咖啡了,搬運(yùn)工作全交給 DMA 硬件電路自動完成。等搬完了,它會敲敲門告訴 CPU:‘老板,活干完了!(中斷)”。 階段一:CW32F030—DMA框圖 一共三件事:
    的頭像 發(fā)表于 03-31 21:41 ?727次閱讀
    【<b class='flag-5'>CW32</b><b class='flag-5'>無線</b><b class='flag-5'>抄</b><b class='flag-5'>表項目</b>】<b class='flag-5'>W25Q_CW</b>32_DMA簡介
    长垣县| 长乐市| 瑞安市| 浑源县| 尖扎县| 安陆市| 平谷区| 珠海市| 元江| 潢川县| 涿州市| 隆回县| 巴马| 华安县| 雷山县| 涞水县| 菏泽市| 大关县| 鄂尔多斯市| 琼中| 石首市| 白河县| 抚松县| 当阳市| 白玉县| 皮山县| 鹿泉市| 集安市| 嵊州市| 鲁山县| 桂平市| 保山市| 和田县| 玛纳斯县| 云和县| 铅山县| 枝江市| 衡阳市| 恩平市| 蒙山县| 攀枝花市|