日B视频 亚洲,啪啪啪网站一区二区,91色情精品久久,日日噜狠狠色综合久,超碰人妻少妇97在线,999青青视频,亚洲一区二卡,让本一区二区视频,日韩网站推荐

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

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

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

【CW32無線抄表項目】單片機SPI+DMA讀寫Flash(W25Q)保姆級避坑指南

CW32生態(tài)社區(qū) ? 來源:CW32生態(tài)社區(qū) ? 2026-04-02 16:35 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

在用 SPI 讀寫 Flash(比如 W25Q 系列)時,往往會覺得用 CPU 一個字節(jié)一個字節(jié)地收發(fā)太慢了。于是大家都會想到用 DMA(直接內(nèi)存訪問) 這個“搬運工”來代勞。

但是!當(dāng)你滿懷信心地配置好 DMA,一跑程序,往往會絕望地卡死在 while(dma_done == 0); 里面。今天,我們就用一段極簡的測試代碼(往 Flash 里寫一個 "kunkun" 并讀出來),手把手教你如何完美打通 SPI 和 DMA 的任督二脈!

核心思維預(yù)警:SPI 和 DMA 是怎么配合的?

SPI 的全雙工脾氣:SPI 就像一個雙向傳送帶。你發(fā)一個字節(jié)出去,必然會同時收一個字節(jié)回來。必須有發(fā)才有收。

DMA 的搬運工角色:我們通常需要雇傭兩個 DMA 搬運工。一個叫 TX(發(fā)送通道),負責(zé)把內(nèi)存里的數(shù)據(jù)瘋狂塞給 SPI;另一個叫 RX(接收通道),負責(zé)把 SPI 收到的數(shù)據(jù)搬回內(nèi)存。

第一步:準(zhǔn)備好你的“停車場”(內(nèi)存對齊)

// 【關(guān)鍵】:定義真正的內(nèi)存空間,并強制 4 字節(jié)對齊
__attribute__((aligned(4))) uint8_t CW_DMA_TxBuf1[256] = "kunkun"; 
__attribute__((aligned(4))) uint8_t CW_DMA_RxBuf1[256];

注意: DMA 搬運數(shù)據(jù)速度極快,但它有個小怪癖——喜歡整齊的地址。加上 attribute((aligned(4))) 就是告訴編譯器:“請把這兩個數(shù)組放在能被 4 整除的內(nèi)存地址上”。如果不加,有時候硬件在尋址時可能會報錯或者發(fā)生數(shù)據(jù)偏移。

C 語言中內(nèi)存對齊(結(jié)構(gòu)體)

struct MyData {
    int a;    // 4 字節(jié)
    int b;    // 4 字節(jié)
    char c;   // 1 字節(jié)
};

它的內(nèi)存布局就像這樣:

第 0-3 字節(jié):放 int a,完美填滿一排。

第 4-7 字節(jié):放 int b,完美填滿第二排。

第 8 字節(jié):放 char c,它只占了第三排的第一個座位。

第 9-11 字節(jié): CPU 是個“強迫癥”,它要求下一個結(jié)構(gòu)體(如果你定義一個數(shù)組的話)必須從新的一排(4 的倍數(shù)地址)開始。為了保證這種整齊,它在 char c 后面塞了 3 個字節(jié)的廢話(Padding)。

所以:9 (有效)+ 3 (墊片) = 12 字節(jié)。

#pragma pack(1)
struct MyData {
    int a;
    int b;
    char c;
};
#pragma pack() // 用完記得關(guān)掉,否則會影響后面的代碼
//缺點:CPU 訪問 a 和 b 可能會變慢一點點,
//因為地址可能不再是 4 的倍數(shù),CPU 甚至需要分兩次讀取再拼接(這叫非對齊訪問)。

第二步:配置 DMA 搬運工的“打卡機”(中斷配置)

void NVIC_Configuration(void){
    __disable_irq(); 
    NVIC_ClearPendingIRQ(DMACH23_IRQn);
    NVIC_SetPriority(DMACH23_IRQn, 1); // 建議設(shè)個優(yōu)先級
    NVIC_EnableIRQ(DMACH23_IRQn); 
    __enable_irq();  
}

/* 定義一個全局標(biāo)志位,告訴主程序:搬完了! */
volatile uint8_t g_dma_done = 0; // 全局標(biāo)志位
void DMACH23_IRQHandler(void)
{
    // 檢查通道 2 (RX) 是否完成(通常以 RX 完成為準(zhǔn),因為 RX 結(jié)束代表總線時鐘已全部跑完)
    if (DMA_GetITStatus(DMA_IT_TC2))
    {
        DMA_ClearITPendingBit(DMA_IT_TC2);
        g_dma_done = 1; // 豎起旗子
    }
    // 清理通道 3 (TX) 標(biāo)志位
    if (DMA_GetITStatus(DMA_IT_TC3))
    {
        DMA_ClearITPendingBit(DMA_IT_TC3);
    }

    // 錯誤處理
    if (DMA_GetITStatus(DMA_IT_TE2) || DMA_GetITStatus(DMA_IT_TE3))
    {
        DMA_ClearITPendingBit(DMA_IT_TE2 | DMA_IT_TE3);
        Error_Handle();
    }
}

注意:搬運工(DMA)干完活總得跟老板(CPU)匯報一下吧?這段代碼就是給系統(tǒng)注冊了一個“微信提示音”。當(dāng) DMA 搬完 6 個字節(jié)的 "kunkun" 時,它會觸發(fā)中斷,把我們代碼里的 g_dma_done 標(biāo)志位置為 1,這樣我們的 while 死循環(huán)就能沖過去了。

第三步:重頭戲!初始化 SPI 和 DMA

這段 SPI2_DMA_Init 初始化代碼里,藏著幾個最容易讓人抓狂的致命地雷,我們已經(jīng)全部掃清了:

void SPI2_DMA_Init(void){
    // ... (變量聲明省略) ...//  避坑 1:一定要給外設(shè)通電!
    __RCC_SPI2_CLK_ENABLE();
    RCC_AHBPeriphClk_Enable(RCC_AHB_PERIPH_DMA, ENABLE);

如果不打開 SPI 的時鐘,SPI 就等于沒插電,你后面寫的所有寄存器配置都會像扔進黑洞一樣毫無反應(yīng)。

  //  避坑 2:找對收發(fā)貨的“物理地址”// 【RX 接收通道配置】
    DMA_InitStruct.DMA_SrcAddress = (uint32_t)&CW_SPI2->DR; // 收貨地:SPI 的數(shù)據(jù)寄存器
    DMA_InitStruct.DMA_DstAddress = (uint32_t)CW_DMA_RxBuf1;   // 卸貨地:我們的內(nèi)存數(shù)組
    DMA_InitStruct.DMA_DstInc = DMA_DstAddress_Increase;       // 卸貨時地址要遞增,依次排好排滿
    DMA_InitStruct.HardTrigSource = 33; // 告訴搬運工,聽 SPI2_RX 的哨聲// 【TX 發(fā)送通道配置】
    DMA_InitStruct.DMA_SrcAddress = (uint32_t)CW_DMA_TxBuf1;   // 收貨地:我們的 "kunkun" 數(shù)組
    DMA_InitStruct.DMA_SrcInc = DMA_SrcAddress_Increase;       // 拿貨時挨個字母拿
    DMA_InitStruct.DMA_DstAddress = (uint32_t)&CW_SPI2->DR; // 卸貨地:SPI 的數(shù)據(jù)寄存器
    DMA_InitStruct.HardTrigSource = 37; // 告訴搬運工,聽 SPI2_TX 的哨聲

wKgZO2nLz0SAPLKVAAC8kmHbP0g098.jpg

圖片

圖片

圖片

找到“店名”(觸發(fā)源編號 Index)

看你第一張圖:

001000:這是二進制的 8。手冊規(guī)定,這是 SPI2 接收店的“店號”。

001001:這是二進制的 9。手冊規(guī)定,這是 SPI2 發(fā)送店的“店號”。

找到“打卡方式”(位域分配)

看你第二張圖(DMA 觸發(fā)寄存器位域描述):

第 0 位 (TYPE):設(shè)置為 1 才能開啟“硬件觸發(fā)模式”。如果是 0,搬運工就不聽 SPI 的哨聲了。

第 5 ~ 2 位 (HARDSRC):手冊規(guī)定,這 4 位是用來填“店號”的。

現(xiàn)場算賬(公式推導(dǎo))

因為“店號”要填在從 第 2 位 開始的地方,所以我們需要把店號 左移 2 位(相當(dāng)于乘以 4),然后把 第 0 位 設(shè)為 1。

對于 SPI2_RX (接收):

店號:8(二進制 1000)。

填位:把 1000 往左挪兩位,變成 1000xx。

加上開關(guān):最后一位(TYPE)填 1,變成 100001。

轉(zhuǎn)換:二進制 100001 就是十進制的 33!

$$8 times 4 + 1 = 33$$

對于 SPI2_TX (發(fā)送):

店號:9(二進制 1001)。

填位:把 1001 往左挪兩位,變成 1001xx。

加上開關(guān):最后一位(TYPE)填 1,變成 100101。

轉(zhuǎn)換:二進制 100101 就是十進制的 37!

$$9 times 4 + 1 = 37$$

信號名稱 原始編號 (Index) 寄存器填法 (二進制) 最終數(shù)值
SPI2_RX 8 (1000) 10 0001 33
SPI2_TX 9 (1001) 10 0101 37

很多朋友喜歡自己手算地址,比如寫個 0x4000380C。一旦算錯哪怕一個字節(jié),DMA 就會把數(shù)據(jù)搬到錯誤的地方導(dǎo)致崩潰。用 &CW_SPI2->DR 讓編譯器去抓取絕對正確的地址,最穩(wěn)妥!

 //  避坑 3:安全地撥動開關(guān)// 先關(guān)閉 SPI (SPE=0),確保寄存器可寫,防止被硬件鎖死
    CW_SPI2->CR1 &= ~(uint32_t)(1 CR1 |= (uint32_t)(0x03 CR1 |= (uint32_t)(1 

SPE=0(熄火):你必須先按下停止鍵,讓機器停下來。否則,為了安全,機器的換擋桿(寄存器)是鎖死拔不動的。

設(shè)置 DMA(換擋):機器停穩(wěn)后,你才能把檔位撥到“全自動模式(DMA模式)”。

SPE=1(重新啟動):接好線、換好擋后,再次合上電源。這時候,機器就會按照你設(shè)定的“全自動模式”狂奔了。

如果你跳過第一步直接改,表面上代碼寫進去了,但實際上機器內(nèi)部的檔位根本沒動,這就是為什么很多人程序卡死在 while 里的“靈異”原因。

第四步:寫入數(shù)據(jù),千萬別忘了“清腸胃”!

看 W25Q_DMA_Write_Kunkun 這個寫函數(shù),注意中間那段極其特殊的代碼:

 // 1. CPU 手動發(fā)送指令和地址 (比如 0x02, 還有 24位地址)// ... 省略 ...// 

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

    關(guān)注

    68

    文章

    11332

    瀏覽量

    226005
  • 智能水表
    +關(guān)注

    關(guān)注

    4

    文章

    218

    瀏覽量

    24415
  • CW32
    +關(guān)注

    關(guān)注

    1

    文章

    326

    瀏覽量

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

掃碼添加小助手

加入工程師交流群

    評論

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

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

    /Armink/SFUD 一、程序分析 硬件總線映射(引腳與時鐘的“點”) ? #define FLASH_SPIx CW_SPI2// 注意:
    的頭像 發(fā)表于 03-31 21:29 ?4992次閱讀
    【<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程序示例

    CW32單片機如何讓生活更便捷

    智能調(diào)節(jié)電量,根據(jù)電量情況進行節(jié)能策略的調(diào)整。例如,降低加熱功率或減少沖洗時間等,以此提高電能的使用效率并節(jié)約能源。 CW32單片機是一種非常強大的工具,它適用于對FLASH、RAM、GPIO等資源需求
    發(fā)表于 12-11 06:11

    STM32F10x_SPI (硬件接口 + 軟件模擬)讀寫Flash25Q16)

    STM32F10x_SPI(硬件接口 + 軟件模擬)讀寫Flash25Q16)
    的頭像 發(fā)表于 03-25 13:59 ?1.3w次閱讀
    STM32F10x_<b class='flag-5'>SPI</b> (硬件接口 + 軟件模擬)<b class='flag-5'>讀寫</b><b class='flag-5'>Flash</b>(<b class='flag-5'>25Q</b>16)

    單片機匯編讀寫SPI FLASH的詳細資料說明

    本文檔的主要內(nèi)容詳細介紹的是單片機匯編讀寫SPI FLASH的詳細資料說明。
    發(fā)表于 08-14 10:45 ?20次下載

    STC8單片機硬件SPI通信例程W25Q16

    。 本篇講的是使用硬件SPI單片機W25Q16進行通信,模擬SPI通信將會在下一篇講。使用W25Q16的步驟如下: 1.配置
    發(fā)表于 11-18 13:36 ?71次下載
    STC8<b class='flag-5'>單片機</b>硬件<b class='flag-5'>SPI</b>通信例程<b class='flag-5'>W25Q</b>16

    單片機學(xué)習(xí)筆記————STM32使用SPI讀寫串行Flash(二)

    第一步:STM32與Flash的硬件連接單片機型號:STM32F103ZET6Flash型號:W25Q64第二步:配置相關(guān)的宏/**************************
    發(fā)表于 11-30 17:21 ?12次下載
    <b class='flag-5'>單片機</b>學(xué)習(xí)筆記————STM32使用<b class='flag-5'>SPI</b><b class='flag-5'>讀寫</b>串行<b class='flag-5'>Flash</b>(二)

    STM32入門開發(fā): 介紹SPI總線、讀寫W25Q64(FLASH)(硬件+模擬時序)

    時序,本文示例代碼里同時采用模擬時序和硬件時序兩種方式讀寫W25Q64。模擬時序更加方便移植到其他單片機,更加方便學(xué)習(xí)理解SPI時序,通用性更高,不分MCU;硬件時序效率更高,每個MC
    發(fā)表于 12-02 09:06 ?41次下載
    STM32入門開發(fā): 介紹<b class='flag-5'>SPI</b>總線、<b class='flag-5'>讀寫</b><b class='flag-5'>W25Q</b>64(<b class='flag-5'>FLASH</b>)(硬件+模擬時序)

    STM32單片機基礎(chǔ)18——使用硬件QSPI讀寫SPI FlashW25Q64)

    本篇詳細的記錄了如何使用STM32CubeMX配置STM32L431RCT6的硬件QSPI外設(shè)與 SPI Flash 通信(W25Q64)。1. 準(zhǔn)備工作硬件準(zhǔn)備開發(fā)板首先需要準(zhǔn)備一個開發(fā)板,這里我
    發(fā)表于 12-02 10:21 ?23次下載
    STM32<b class='flag-5'>單片機</b>基礎(chǔ)18——使用硬件QSPI<b class='flag-5'>讀寫</b><b class='flag-5'>SPI</b> <b class='flag-5'>Flash</b>(<b class='flag-5'>W25Q</b>64)

    stm32 cubemx usb spi flash w25q128 u盤調(diào)試筆記

    基本代碼確定使用需求 USB SPIusb以下配置保持默認配置即可,切記不要胡亂修改參數(shù)。spi調(diào)試spi flash我使用的flashw25q
    發(fā)表于 12-14 18:52 ?34次下載
    stm32 cubemx usb <b class='flag-5'>spi</b> <b class='flag-5'>flash</b> <b class='flag-5'>w25q</b>128 u盤調(diào)試筆記

    STM32 SPI讀寫W25Q64(三)

    GPIO口模擬SPI讀寫W25Q64的基本內(nèi)容已經(jīng)跟大家介紹完了,今天跟大家介紹下如何通過串口接收文件并保存到W25Q64中。
    發(fā)表于 07-22 11:11 ?3230次閱讀
    STM32 <b class='flag-5'>SPI</b><b class='flag-5'>讀寫</b><b class='flag-5'>W25Q</b>64(三)

    CW32單片機低電壓檢測器的使用介紹

    CW32單片機低電壓檢測器的使用介紹
    的頭像 發(fā)表于 09-18 10:56 ?2241次閱讀
    <b class='flag-5'>CW32</b><b class='flag-5'>單片機</b>低電壓檢測器的使用介紹

    CW32單片機I2C接口讀寫EEPROM芯片介紹

    CW32單片機I2C接口讀寫EEPROM芯片介紹
    的頭像 發(fā)表于 11-09 17:42 ?3165次閱讀
    <b class='flag-5'>CW32</b><b class='flag-5'>單片機</b>I2C接口<b class='flag-5'>讀寫</b>EEPROM芯片介紹

    基于CW32單片機做的軟硬件開源項目

    今天就再給大家分享一個基于CW32單片機做的軟硬件開源項目,其中包括RTOS、GUI、藍牙、電源管理等眾多常用功能。
    的頭像 發(fā)表于 10-19 10:17 ?2405次閱讀
    基于<b class='flag-5'>CW32</b><b class='flag-5'>單片機</b>做的軟硬件開源<b class='flag-5'>項目</b>

    CW32單片機在智能馬桶的應(yīng)用介紹

    和調(diào)節(jié)。本文將介紹CW32單片機在智能馬桶的詳細應(yīng)用。圖:CW32的智能馬桶控制板CW32單片機在智能馬桶的應(yīng)用介紹1.溫度感應(yīng)與控制智能馬
    的頭像 發(fā)表于 12-20 10:09 ?1640次閱讀
    <b class='flag-5'>CW32</b><b class='flag-5'>單片機</b>在智能馬桶的應(yīng)用介紹

    CW32無線表項目W25Q_CW32_DMA簡介

    以前單片機搬運數(shù)據(jù)(比如把串口收到的 100 個字節(jié)存進數(shù)組),必須由 CPU 親自動手:讀一個字節(jié)、存一個字節(jié)。搬磚的時候,CPU 沒法去算水表的流量,也沒法去管 4G 模塊。 DMA 就是一個
    的頭像 發(fā)表于 03-31 21:41 ?851次閱讀
    【<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_CW32_DMA</b>簡介
    德惠市| 达拉特旗| 双江| 资阳市| 阿瓦提县| 武夷山市| 和政县| 巴彦淖尔市| 泰和县| 马尔康县| 惠来县| 乌兰浩特市| 延边| 武安市| 田林县| 五寨县| 怀来县| 洱源县| 迁西县| 永吉县| 荆州市| 彭山县| 扬州市| 湛江市| 棋牌| 石城县| 普定县| 成都市| 大港区| 宣武区| 南郑县| 芮城县| 疏附县| 仁布县| 固阳县| 荔波县| 铁岭市| 丰都县| 广水市| 大安市| 长葛市|