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

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

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

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

【CW32無(wú)線(xiàn)抄表項(xiàng)目】單片機(jī)SPI+DMA讀寫(xiě)Flash(W25Q)保姆級(jí)避坑指南

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

掃碼添加小助手

加入工程師交流群

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

#pragma pack(1)
struct MyData {
    int a;
    int b;
    char c;
};
#pragma pack() // 用完記得關(guān)掉,否則會(huì)影響后面的代碼
//缺點(diǎn):CPU 訪(fǎng)問(wèn) a 和 b 可能會(huì)變慢一點(diǎn)點(diǎn),
//因?yàn)榈刂房赡懿辉偈?4 的倍數(shù),CPU 甚至需要分兩次讀取再拼接(這叫非對(duì)齊訪(fǎng)問(wèn))。

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

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

/* 定義一個(gè)全局標(biāo)志位,告訴主程序:搬完了! */
volatile uint8_t g_dma_done = 0; // 全局標(biāo)志位
void DMACH23_IRQHandler(void)
{
    // 檢查通道 2 (RX) 是否完成(通常以 RX 完成為準(zhǔn),因?yàn)?RX 結(jié)束代表總線(xiàn)時(shí)鐘已全部跑完)
    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);
    }

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

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

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

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

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

如果不打開(kāi) SPI 的時(shí)鐘,SPI 就等于沒(méi)插電,你后面寫(xiě)的所有寄存器配置都會(huì)像扔進(jìn)黑洞一樣毫無(wú)反應(yīng)。

  //  避坑 2:找對(duì)收發(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;       // 卸貨時(shí)地址要遞增,依次排好排滿(mǎn)
    DMA_InitStruct.HardTrigSource = 33; // 告訴搬運(yùn)工,聽(tīng) SPI2_RX 的哨聲// 【TX 發(fā)送通道配置】
    DMA_InitStruct.DMA_SrcAddress = (uint32_t)CW_DMA_TxBuf1;   // 收貨地:我們的 "kunkun" 數(shù)組
    DMA_InitStruct.DMA_SrcInc = DMA_SrcAddress_Increase;       // 拿貨時(shí)挨個(gè)字母拿
    DMA_InitStruct.DMA_DstAddress = (uint32_t)&CW_SPI2->DR; // 卸貨地:SPI 的數(shù)據(jù)寄存器
    DMA_InitStruct.HardTrigSource = 37; // 告訴搬運(yùn)工,聽(tīng) SPI2_TX 的哨聲

wKgZO2nLz0SAPLKVAAC8kmHbP0g098.jpg

圖片

圖片

圖片

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

看你第一張圖:

001000:這是二進(jìn)制的 8。手冊(cè)規(guī)定,這是 SPI2 接收店的“店號(hào)”。

001001:這是二進(jìn)制的 9。手冊(cè)規(guī)定,這是 SPI2 發(fā)送店的“店號(hào)”。

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

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

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

第 5 ~ 2 位 (HARDSRC):手冊(cè)規(guī)定,這 4 位是用來(lái)填“店號(hào)”的。

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

因?yàn)椤暗晏?hào)”要填在從 第 2 位 開(kāi)始的地方,所以我們需要把店號(hào) 左移 2 位(相當(dāng)于乘以 4),然后把 第 0 位 設(shè)為 1。

對(duì)于 SPI2_RX (接收):

店號(hào):8(二進(jìn)制 1000)。

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

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

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

$$8 times 4 + 1 = 33$$

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

店號(hào):9(二進(jìn)制 1001)。

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

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

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

$$9 times 4 + 1 = 37$$

信號(hào)名稱(chēng) 原始編號(hào) (Index) 寄存器填法 (二進(jìn)制) 最終數(shù)值
SPI2_RX 8 (1000) 10 0001 33
SPI2_TX 9 (1001) 10 0101 37

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

 //  避坑 3:安全地?fù)軇?dòng)開(kāi)關(guān)// 先關(guān)閉 SPI (SPE=0),確保寄存器可寫(xiě),防止被硬件鎖死
    CW_SPI2->CR1 &= ~(uint32_t)(1 CR1 |= (uint32_t)(0x03 CR1 |= (uint32_t)(1 

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

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

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

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

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

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

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

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

    關(guān)注

    68

    文章

    11308

    瀏覽量

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

    關(guān)注

    4

    文章

    217

    瀏覽量

    24383
  • CW32
    +關(guān)注

    關(guān)注

    1

    文章

    323

    瀏覽量

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

掃碼添加小助手

加入工程師交流群

    評(píng)論

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

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

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

    CW32單片機(jī)如何讓生活更便捷

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

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

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

    單片機(jī)匯編讀寫(xiě)SPI FLASH的詳細(xì)資料說(shuō)明

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

    STC8單片機(jī)硬件SPI通信例程W25Q16

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

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

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

    STM32入門(mén)開(kāi)發(fā): 介紹SPI總線(xiàn)、讀寫(xiě)W25Q64(FLASH)(硬件+模擬時(shí)序)

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

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

    本篇詳細(xì)的記錄了如何使用STM32CubeMX配置STM32L431RCT6的硬件QSPI外設(shè)與 SPI Flash 通信(W25Q64)。1. 準(zhǔn)備工作硬件準(zhǔn)備開(kāi)發(fā)板首先需要準(zhǔn)備一個(gè)開(kāi)發(fā)板,這里我
    發(fā)表于 12-02 10:21 ?23次下載
    STM32<b class='flag-5'>單片機(jī)</b>基礎(chǔ)18——使用硬件QSPI<b class='flag-5'>讀寫(xiě)</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盤(pán)調(diào)試筆記

    基本代碼確定使用需求 USB SPIusb以下配置保持默認(rèn)配置即可,切記不要胡亂修改參數(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盤(pán)調(diào)試筆記

    STM32 SPI讀寫(xiě)W25Q64(三)

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

    CW32單片機(jī)低電壓檢測(cè)器的使用介紹

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

    CW32單片機(jī)I2C接口讀寫(xiě)EEPROM芯片介紹

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

    基于CW32單片機(jī)做的軟硬件開(kāi)源項(xiàng)目

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

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

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

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

    以前單片機(jī)搬運(yùn)數(shù)據(jù)(比如把串口收到的 100 個(gè)字節(jié)存進(jìn)數(shù)組),必須由 CPU 親自動(dòng)手:讀一個(gè)字節(jié)、存一個(gè)字節(jié)。搬磚的時(shí)候,CPU 沒(méi)法去算水表的流量,也沒(méi)法去管 4G 模塊。 DMA 就是一個(gè)
    的頭像 發(fā)表于 03-31 21:41 ?74次閱讀
    【<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_CW32_DMA</b>簡(jiǎn)介