天天看點

【STM32H7教程】第74章 STM32H7的SPI總線應用之驅動DAC8563(雙通道,16bit分辨率,正負10V)

完整教程下載下傳位址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980

第74章       STM32H7的SPI總線應用之驅動DAC8563(雙通道,16bit分辨率,正負10V)

本章節為大家講解标準SPI接線方式驅動模數轉換器DAC856X,制作了中斷和DMA兩種驅動方式。

74.1 初學者重要提示

74.2 DAC結構分類

74.3 DAC技術術語

74.4 DAC856X硬體設計

74.5 DAC856X關鍵知識點整理(重要)

74.6 DAC856X驅動設計(中斷更新方式)

74.7 DAC856X驅動設計(SPI DMA更新方式)

74.8 SPI總線闆級支援包(bsp_spi_bus.c)

74.9 DAC856X支援包中斷方式(bsp_spi_dac8562.c)

74.10 DAC856X支援包DMA方式(bsp_spidma_dac8562.c)

74.11 DAC856X驅動移植和使用(中斷更新方式)

74.12 DAC856X驅動移植和使用(SPI DMA更新方式)

74.13 實驗例程設計架構

74.14 實驗例程說明(MDK)

74.15 實驗例程說明(IAR)

74.16 總結

1、  學習本章節前,務必優先學習第72章。

2、  對于DAC8562和DAC8563,教程中不做區分,因為DAC8562和DAC8563完全相容,差別僅僅在于CLR引腳有效時,DAC8562資料設定為0,  DAC8563資料設定為32767。

3、 本章涉及到的知識點比較多,需要大家掌握STM32H7的SPI , DMA,TIM,DMAMUX和DAC8563的一些細節用法。

4、  H7的SPI + DMA驅動這類外設的靈活度,絕對可以媲美FPGA去控制:

  •  H7的SPI外設比F4系列的靈活性強太多了,主要表現在兩個方面:資料的傳輸支援了4-32bit,特别是那個NSS片選引腳,超強勁,可以做各種時間插入,靈活應對了市場上這類晶片的需求。
  •  DMA這塊相比F4系列,有了質的飛躍,支援了DMAMUX,這個DMAMUX除了帶來靈活的觸發源選擇,還支援了各種觸發事件和同步觸發功能。本章配套例子的觸發周期控制就是利用了DMAMUX的同步觸發功能。

5、  本章配套了中斷和DMA兩種更新方式的案例,DMA實作方式與中斷更新方式完全不同,因為DMA方式要使用硬體SPI1 NSS片選引腳驅動DAC856X。而中斷更新方式使用公共的總線驅動檔案bsp_spi_bus.c,片選是通過通用IO方式控制,支援串行FLASH、TSC2046、VS1053、AD7705、ADS1256等SPI裝置。大家在看例子的時候要注意。

6、  對于本章教程配套例子的SPI DMA方式,這裡特别注意一點,定時器觸發一次,就會讓SPI以DMA方式傳輸24bit資料。

7、  DAC856X資料手冊,子產品原理圖和接線圖都已經放到本章教程配置例子的Doc檔案裡。

這裡将三種DAC結構為大家做個普及:R2R型MDAC,R2R型backDAC和Srting型DAC。

注,這些知識翻譯自TI的英文技術手冊。

74.2.1 R2R型MDAC

自動測試裝置或儀器通常使用R2R MDAC。MDAC型制造商能夠設計具有±1 LSB的高分辨率積分非線性(INL)和差分非線性(DNL)DAC。通過使用合适的外部放大器,MDAC能夠實作較短的建立時間(<0.3 ms)和大于10 MHz的帶寬。并且通過為MDAC的外部運算放大器提供不同電源電壓和高輸出電流可以增強DAC功能。

MDAC産生的電流與使用者設定的數字編碼,外部放大器以及RFB(在MDAC内部)将DAC的電流輸出信号轉換為可用的電壓。

這類DAC的缺點是會有穩定性問題。

【STM32H7教程】第74章 STM32H7的SPI總線應用之驅動DAC8563(雙通道,16bit分辨率,正負10V)

74.2.2 R2R型backDAC

通常在工業應用中使用R2R backDAC。其它一些應用還包括儀器和數字控制校準。使用這類DAC,每次新更新會将2R支路切換到參考電壓高(VREF-H)或參考電壓低(VREF-L)。注意R-2R梯子的布置與MDAC相比是倒置的。這就是名字backDAC的由來,這種架構很容易制造。

【STM32H7教程】第74章 STM32H7的SPI總線應用之驅動DAC8563(雙通道,16bit分辨率,正負10V)

這類DAC的缺點是毛刺脈沖問題(注,此貼有詳細解釋:連結):

74.2.3 String型DAC

String型DAC最适合便攜式儀器,閉環伺服控制和過程控制。下圖顯示了一個3bit String DAC的模型,數字輸入代碼101b被解碼為5/8 VREF。String DAC的輸出級放大器隔離了來自輸出負載的内部電阻元件。

String DAC是一種低功耗解決方案,可確定單調性在整個輸入代碼中具有良好的DNL(差分非線性)性能範圍。毛刺能量通常低于其它DAC類型。

但是,INL(積分非線性)通常較大,具體取決于電阻式片上比對,另一方面,控制回路中的DAC可減輕線性度影響。String DAC的噪聲也相對較大,因為電阻串的阻抗很高,是以該值很高。但String DAC功耗低且非常小的故障能量。

【STM32H7教程】第74章 STM32H7的SPI總線應用之驅動DAC8563(雙通道,16bit分辨率,正負10V)

一些常見的DAC技術術語需要大家見到了,大概了解是什麼意思。

74.3.1 機關ppm℃(ppm/℃)

這個參數是專門用來定義溫飄的,ppm全稱是parts per million,即百萬分之一。比如2ppm℃就是2 x 10^-6 ,反映到DAC8563上,定義如下:

Input or 2.5-V Output
4-ppm°C Temperature Drift (Typ)      

也就是說,當輸出2.5V時,每變化一度,輸出電壓的變化是2.5V x (4 x 10^-6) = 10uV

類似的定義還有很多:

【STM32H7教程】第74章 STM32H7的SPI總線應用之驅動DAC8563(雙通道,16bit分辨率,正負10V)

ppb,ppt,ppq所代表的含義:

【STM32H7教程】第74章 STM32H7的SPI總線應用之驅動DAC8563(雙通道,16bit分辨率,正負10V)

74.3.2 毛刺脈沖(Glitch impulse)

使用DAC進行設計時,您期望輸出從一個值單調移至下一個值,但實際電路并非總是如此。在某些代碼範圍内,出現過沖或下沖(量化為毛刺脈沖)并不少見。主要以下面兩種形式呈現:

【STM32H7教程】第74章 STM32H7的SPI總線應用之驅動DAC8563(雙通道,16bit分辨率,正負10V)
【STM32H7教程】第74章 STM32H7的SPI總線應用之驅動DAC8563(雙通道,16bit分辨率,正負10V)

具體原因分析在這個文章裡面進行了講解(内容較多,就不整理到教程裡面了):連結。

74.3.3 偏移誤差(Offset Error)

偏移誤差為标稱偏移點與實際偏移點之間的差。此錯誤以相同的數量影響所有代碼,通常可以通過修正來補償處理。如果無法修正,則該誤差稱為零刻度誤差。

【STM32H7教程】第74章 STM32H7的SPI總線應用之驅動DAC8563(雙通道,16bit分辨率,正負10V)

74.3.4 增益誤差(Gain Error)

增益誤差定義為傳輸時标稱增益點與實際增益點之差。

【STM32H7教程】第74章 STM32H7的SPI總線應用之驅動DAC8563(雙通道,16bit分辨率,正負10V)

74.3.5 差分非線性誤差(DNL)

DNL全稱Differential Nonlinearity。

差分非線性誤差為實際步長寬度(對于ADC)或步長高度(對于DAC)與1 LSB的理想值之間的內插補點。 是以,如果階躍寬度或高度恰好為1 LSB,則差分非線性誤差為零。 如果DNL超過1 LSB,轉換器可能變得非單調。這意味着增加了輸入的幅度但輸出的大小可能變小。

【STM32H7教程】第74章 STM32H7的SPI總線應用之驅動DAC8563(雙通道,16bit分辨率,正負10V)

74.3.6 積分非線性誤差(INL)

INL全稱Integral Nonlinearity

積分非線性誤差是從一個傳輸點到相對應的理想傳輸曲線的最大偏差距離,不考慮偏置誤差和增益誤差。 這個參數對最佳傳輸函數或端點傳輸函數有一定參考意義。

【STM32H7教程】第74章 STM32H7的SPI總線應用之驅動DAC8563(雙通道,16bit分辨率,正負10V)

74.3.7 絕對精度誤差(Absolute Accuracy Error)

絕對精度誤差是包括偏移,增益,積分線性等誤差的總體誤差。

【STM32H7教程】第74章 STM32H7的SPI總線應用之驅動DAC8563(雙通道,16bit分辨率,正負10V)

DAC的輸出量可以為0到2.5V或者0到5V,通過外置運放,實作了±10V輸出。原理圖下載下傳:

http://www.armbbs.cn/forum.php?mod=viewthread&tid=97082 。

74.4.1 DAC856X子產品規格

産品規格:

1、供電電壓 : 2.7 - 5.5V  【3.3V供電時,輸出電壓也可以到正負10V】

2、通道數: 2路  (通過1片DAC8563實作)

3、輸出電壓範圍 : -10V ~ +10V 【客戶可以自己更改為 0-10V輸出範圍。使用烙鐵切換2個焊點即可,無需更換元器件】

4、輸出驅動能力:帶運放驅動,最大輸出電流10mA,負載電阻>1K歐姆

5、分辨率: 16位

6、功耗 : 小于20mA

7、MCU接口 :高速 SPI (50M) 支援 3.3V和5V單片機

8、DAC輸出模拟帶寬:350KHz

9、DAC輸出響應: 10uS 到 0.003% FSR

産品特點:

1、輸出和供電電壓無關;子產品内帶正負12V升壓電路

2、自适應單片機的電平(2.7 - 5V 均可以)

3、輸出電壓可抵達正負10V

4、上電時預設輸出0V (在軟體未啟動時)

5、引出正負12V電源排針,友善客戶使用

重要提示:

1、DAC8562和DAC8563完全相容,差別僅僅在于CLR引腳有效時,DAC8562資料設定為0, DAC8563資料設定為32767。注意這是DAC的内部資料,不表示輸出電壓。 對于-10 ~ +10V輸出的子產品,DAC8562輸出-10V, DAC8563輸出0V。

2、無論是用DAC8562還是DAC8563晶片,隻要軟體不啟動,本子產品輸出電壓預設狀态都是0V。

3、CLR腳懸浮時,電壓在1.9V左右,容易受到幹擾導緻輸出被清零。是以即使不用CLR控制功能,這個CLR腳也需要接固定電平(推薦接GND)。CLR是邊沿觸發,僅在下降沿信号出現執行清零。

産品效果:

【STM32H7教程】第74章 STM32H7的SPI總線應用之驅動DAC8563(雙通道,16bit分辨率,正負10V)
【STM32H7教程】第74章 STM32H7的SPI總線應用之驅動DAC8563(雙通道,16bit分辨率,正負10V)

74.4.2 DAC856X硬體接口

V7闆子上DAC856X子產品的插座的原理圖如下:

【STM32H7教程】第74章 STM32H7的SPI總線應用之驅動DAC8563(雙通道,16bit分辨率,正負10V)

實際對應開發的位置如下:

【STM32H7教程】第74章 STM32H7的SPI總線應用之驅動DAC8563(雙通道,16bit分辨率,正負10V)

驅動DAC856X需要對下面這些知識點有個認識。

74.5.1 DAC856X基礎資訊

  •   雙通道DAC,軌到軌輸出,16bit分辨率,支援50MHz的SPI時脈速度。
  •   自帶2.5V的内部參考基準,典型的溫飄是4ppm/℃。使用内部2.5V參考基準的情況下,根據增益設定不同,DAC的輸出量可以為0到2.5V或者0到5V。
  •   使用者可以根據需要外接運放實作常用的±5V,±10V或者±15V輸出。
  •   相對精度誤差4LSB INL。
  •   毛刺脈沖 0.1nV-s
  •   上電複位數值0V或者中間值。
【STM32H7教程】第74章 STM32H7的SPI總線應用之驅動DAC8563(雙通道,16bit分辨率,正負10V)

74.5.2 DAC856X每個引腳的作用

DAC856X主要有下面兩種封裝形式:

【STM32H7教程】第74章 STM32H7的SPI總線應用之驅動DAC8563(雙通道,16bit分辨率,正負10V)
  •  Avdd

供電範圍2.7-5.5V

  •   CLR

異步清除輸入,下降沿有效,觸發後,DAC8562輸出最低電壓值,DAC8563輸出中間值。使用者寫入操作的的第24個時鐘下降沿将退出清除模式,激活清除模式将終止寫操作。

  •   Din

串行時鐘輸入,每個時鐘下降沿将資料寫到的24bit的輸入移位寄存器。

  •   GND

接地端。

  •   LDAC

同步模式下,資料更新發生在第24個SCLK周期的下降沿,之後伴随着SYNC的下降沿。 這種同步更新不需要LDAC,而LDAC必須永久接地,或者将指令發送到裝置時保持低電平。異步模式下,LDAC是低電平觸發,用于同步DAC更新,可以編寫多個單通道指令進行設定,然後在LDAC引腳上産生一個下降沿将同步更新DAC輸出寄存器。

  •   SCLK

時鐘輸入端,支援50MHz。

  •   SYNC (片選)

低電平有效,當SYNC變為低電平時,它使能輸入移位寄存器,并且資料采樣在随後的時鐘下降沿。 DAC輸出在第24個時鐘下降沿之後更新。 如果SYNC在第23個時鐘沿之前變高,SYNC的上升沿将充當中斷,并且DAC756x,DAC816x和DAC856x器件将忽略寫序列。

  •   VoutA

模拟電壓輸出A。

  •   VoutB

模拟電壓輸出B。

  •   Vrefin/Vrefout

雙向電壓參考引腳,如果使用内部電壓基準,此引腳是輸出2.5V。

74.5.3 DAC856X輸出電壓計算公式

DAC856X的計算公式如下:

【STM32H7教程】第74章 STM32H7的SPI總線應用之驅動DAC8563(雙通道,16bit分辨率,正負10V)
  •   DIN

配置DAC856X資料輸出寄存器的數值,範圍0 到2^16 – 1,即0到65535。

  •   2n

對于DAC856X來說,n是16。

  •   VREF

如果使用内部參考電壓,那麼此數值是2.5V,如果使用外部參考電壓,由VREFIN引腳的輸入決定。

  •   Gain

增益設定。禁止内部電壓基準後,預設增益是1。如果使能内部電壓基準後,預設增益是2。具體增益是1還是2,可以通過DAC856X的寄存器設定。

74.5.4 DAC856X時序圖

DAC856X的時序圖如下:

【STM32H7教程】第74章 STM32H7的SPI總線應用之驅動DAC8563(雙通道,16bit分辨率,正負10V)
【STM32H7教程】第74章 STM32H7的SPI總線應用之驅動DAC8563(雙通道,16bit分辨率,正負10V)

這個時序裡面有三個參數尤其重要,後面時序配置要用到。

  •   f(SCLK)

支援最高的串行時鐘是50MHz。

  •   t(4)

每傳輸24bit資料後,SYNC要保持一段時間的高電平,DAC856X要求至少要80ns。

  •   t(5)

SYNC低電平有效到SCLK第1個下降沿信号的時間,最小值13ns。

74.5.5 DAC856X寄存器配置

DAC856X的寄存器配置看下面的圖表即可,一目了然(X表示為0或者為1均可):

【STM32H7教程】第74章 STM32H7的SPI總線應用之驅動DAC8563(雙通道,16bit分辨率,正負10V)

控制DAC856X每次要傳輸24bit資料,高8bit控制位 + 16bit資料位。

比如Power up DAC-A and DAC-B:

DAC8562_WriteCmd((4 << 19) | (0 << 16) | (3 << 0))

DAC856X的程式驅動架構設計如下:

【STM32H7教程】第74章 STM32H7的SPI總線應用之驅動DAC8563(雙通道,16bit分辨率,正負10V)

有了這個框圖,程式設計就比較好了解了。

74.6.1 第1步:SPI總線配置

spi總線配置通過如下兩個函數實作:

/*
*********************************************************************************************************
*    函 數 名: bsp_InitSPIBus
*    功能說明: 配置SPI總線。
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
void bsp_InitSPIBus(void)
{    
    g_spi_busy = 0;
    
    bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_8, SPI_PHASE_1EDGE, SPI_POLARITY_LOW);
}

/*
*********************************************************************************************************
*    函 數 名: bsp_InitSPIParam
*    功能說明: 配置SPI總線參數,時鐘分頻,時鐘相位和時鐘極性。
*    形    參: _BaudRatePrescaler  SPI總線時鐘分頻設定,支援的參數如下:
*                                 SPI_BAUDRATEPRESCALER_2    2分頻
*                                 SPI_BAUDRATEPRESCALER_4    4分頻
*                                 SPI_BAUDRATEPRESCALER_8    8分頻
*                                 SPI_BAUDRATEPRESCALER_16   16分頻
*                                 SPI_BAUDRATEPRESCALER_32   32分頻
*                                 SPI_BAUDRATEPRESCALER_64   64分頻
*                                 SPI_BAUDRATEPRESCALER_128  128分頻
*                                 SPI_BAUDRATEPRESCALER_256  256分頻
*                                                        
*             _CLKPhase           時鐘相位,支援的參數如下:
*                                 SPI_PHASE_1EDGE     SCK引腳的第1個邊沿捕獲傳輸的第1個資料
*                                 SPI_PHASE_2EDGE     SCK引腳的第2個邊沿捕獲傳輸的第1個資料
*                                 
*             _CLKPolarity        時鐘極性,支援的參數如下:
*                                 SPI_POLARITY_LOW    SCK引腳在空閑狀态處于低電平
*                                 SPI_POLARITY_HIGH   SCK引腳在空閑狀态處于高電平
*
*    返 回 值: 無
*********************************************************************************************************
*/
void bsp_InitSPIParam(uint32_t _BaudRatePrescaler, uint32_t _CLKPhase, uint32_t _CLKPolarity)
{
    /* 提高執行效率,隻有在SPI硬體參數發生變化時,才執行HAL_Init */
    if (s_BaudRatePrescaler == _BaudRatePrescaler && s_CLKPhase == _CLKPhase && s_CLKPolarity == _CLKPolarity)
    {        
        return;
    }

    s_BaudRatePrescaler = _BaudRatePrescaler;    
    s_CLKPhase = _CLKPhase;
    s_CLKPolarity = _CLKPolarity;
    
    
    /* 設定SPI參數 */
    hspi.Instance               = SPIx;                   /* 例化SPI */
    hspi.Init.BaudRatePrescaler = _BaudRatePrescaler;     /* 設定波特率 */
    hspi.Init.Direction         = SPI_DIRECTION_2LINES;   /* 全雙工 */
    hspi.Init.CLKPhase          = _CLKPhase;              /* 配置時鐘相位 */
    hspi.Init.CLKPolarity       = _CLKPolarity;           /* 配置時鐘極性 */
    hspi.Init.DataSize          = SPI_DATASIZE_8BIT;      /* 設定資料寬度 */
    hspi.Init.FirstBit          = SPI_FIRSTBIT_MSB;       /* 資料傳輸先傳高位 */
    hspi.Init.TIMode            = SPI_TIMODE_DISABLE;     /* 禁止TI模式  */
    hspi.Init.CRCCalculation    = SPI_CRCCALCULATION_DISABLE; /* 禁止CRC */
    hspi.Init.CRCPolynomial     = 7;                       /* 禁止CRC後,此位無效 */
    hspi.Init.CRCLength         = SPI_CRC_LENGTH_8BIT;     /* 禁止CRC後,此位無效 */
    hspi.Init.NSS               = SPI_NSS_SOFT;               /* 使用軟體方式管理片選引腳 */
    hspi.Init.FifoThreshold     = SPI_FIFO_THRESHOLD_01DATA;  /* 設定FIFO大小是一個資料項 */
    hspi.Init.NSSPMode          = SPI_NSS_PULSE_DISABLE;      /* 禁止脈沖輸出 */
    hspi.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_ENABLE; /* 禁止SPI後,SPI相關引腳保持目前狀态 */  
    hspi.Init.Mode             = SPI_MODE_MASTER;            /* SPI工作在主要模式 */

    /* 複位配置 */
    if (HAL_SPI_DeInit(&hspi) != HAL_OK)
    {
        Error_Handler(__FILE__, __LINE__);
    }    

    /* 初始化配置 */
    if (HAL_SPI_Init(&hspi) != HAL_OK)
    {
        Error_Handler(__FILE__, __LINE__);
    }    
}      

關于這兩個函數有以下兩點要做個說明:

  •   函數bsp_InitSPIBus裡面的配置是個初始設定。實際驅動晶片時,會通過函數bsp_InitSPIParam做再配置。
  •   函數bsp_InitSPIParam提供了時鐘分頻,時鐘相位和時鐘極性配置。驅動不同外設晶片時,基本上調整這三個參數就夠。當SPI接口上接了多個不同類型的晶片時,通過此函數可以友善的切換配置。

74.6.2 第2步:SPI總線的查詢,中斷和DMA方式設定

注:對于DAC8563,請使用查詢方式。

SPI驅動的查詢,中斷和DMA方式主要通過函數bsp_spiTransfer實作資料傳輸:

/*
*********************************************************************************************************
*                                 選擇DMA,中斷或者查詢方式
*********************************************************************************************************
*/
//#define USE_SPI_DMA    /* DMA方式  */
//#define USE_SPI_INT    /* 中斷方式 */
#define USE_SPI_POLL   /* 查詢方式 */

/* 查詢模式 */
#if defined (USE_SPI_POLL)

uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];  
uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];

/* 中斷模式 */
#elif defined (USE_SPI_INT)

uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];   
uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];

/* DMA模式使用的SRAM4 */
#elif defined (USE_SPI_DMA)
    #if defined ( __CC_ARM )    /* IAR *******/
        __attribute__((section (".RAM_D3"))) uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];   
        __attribute__((section (".RAM_D3"))) uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];
    #elif defined (__ICCARM__)   /* MDK ********/
        #pragma location = ".RAM_D3"
        uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];   
        #pragma location = ".RAM_D3"
        uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];
    #endif
#endif

/*
*********************************************************************************************************
*    函 數 名: bsp_spiTransfer
*    功能說明: 啟動資料傳輸
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
void bsp_spiTransfer(void)
{
    if (g_spiLen > SPI_BUFFER_SIZE)
    {
        return;
    }
    
    /* DMA方式傳輸 */
#ifdef USE_SPI_DMA
    wTransferState = TRANSFER_WAIT;
    
    if(HAL_SPI_TransmitReceive_DMA(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen) != HAL_OK)    
    {
        Error_Handler(__FILE__, __LINE__);
    }
    
    while (wTransferState == TRANSFER_WAIT)
    {
        ;
    }
#endif

    /* 中斷方式傳輸 */    
#ifdef USE_SPI_INT
    wTransferState = TRANSFER_WAIT;

    if(HAL_SPI_TransmitReceive_IT(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen) != HAL_OK)    
    {
        Error_Handler(__FILE__, __LINE__);
    }
    
    while (wTransferState == TRANSFER_WAIT)
    {
        ;
    }
#endif

    /* 查詢方式傳輸 */    
#ifdef USE_SPI_POLL
    if(HAL_SPI_TransmitReceive(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen, 1000000) != HAL_OK)    
    {
        Error_Handler(__FILE__, __LINE__);
    }    
#endif
}      

通過開頭宏定義可以友善的切換中斷,查詢和DMA方式。其中查詢和中斷方式比較好了解,而DMA方式要特别注意兩點:

  •   通過本手冊第26章的記憶體塊超友善使用方式,将DMA緩沖定義到SRAM4上。因為本工程是用的DTCM做的主RAM空間,這個空間無法使用通用DMA1和DMA2。
  •   由于程式裡面開啟了資料Cache,會造成DMA和CPU通路SRAM4資料不一緻的問題,特此将SRAM4空間關閉Cache。
/* 配置SRAM4的MPU屬性為Non-cacheable */
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0x38000000;
    MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
    MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    MPU_InitStruct.SubRegionDisable = 0x00;
    MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

    HAL_MPU_ConfigRegion(&MPU_InitStruct);      

74.6.3 第3步:DAC856X的時鐘極性和時鐘相位配置

首先回憶下STM32H7支援的4種時序配置。

  •   當CPOL = 1, CPHA = 1時

SCK引腳在空閑狀态處于高電平,SCK引腳的第2個邊沿捕獲傳輸的第1個資料。

  •   當CPOL = 0, CPHA = 1時

SCK引腳在空閑狀态處于低電平,SCK引腳的第2個邊沿捕獲傳輸的第1個資料。

  •   當CPOL = 1, CPHA = 0時

SCK引腳在空閑狀态處于高電平,SCK引腳的第1個邊沿捕獲傳輸的第1個資料。

  •   當CPOL = 0 ,CPHA= 0時

SCK引腳在空閑狀态處于低電平,SCK引腳的第1個邊沿捕獲傳輸的第1個資料。

【STM32H7教程】第74章 STM32H7的SPI總線應用之驅動DAC8563(雙通道,16bit分辨率,正負10V)

有了H7支援的時序配置,再來看下DAC856X的時序圖:

【STM32H7教程】第74章 STM32H7的SPI總線應用之驅動DAC8563(雙通道,16bit分辨率,正負10V)

首先DAC856X是下降升沿做資料采集,是以STM32H7的可選的配置就是:

CHOL = 0,  CPHA = 1

CHOL = 1,  CPHA = 0

對于這兩種情況的主要差別是空閑狀态下SCLK時鐘選擇高電平還是低電平,根據上面的時序圖和DAC856X的資料手冊,兩種情況下都可以正常運作。經過實際測試,STM32H7使用這兩個配置确實都可以正常運作。程式裡面預設是選擇CHOL = 0,  CPHA = 1。

74.6.4 第4步:單SPI接口管理多個SPI裝置的切換機制

單SPI接口管理多個SPI裝置最麻煩的地方是不同裝置的時鐘配置設定,時鐘極性和時鐘相位并不相同。對此的解決解決辦法是在片選階段配置切換,比如DAC856X的片選:

/*
*********************************************************************************************************
*    函 數 名: DAC8562_SetCS
*    功能說明: DAC8562 片選控制函數
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
void DAC8562_SetCS(uint8_t _Level)
{
    if (_Level == 0)
    {
        bsp_SpiBusEnter();    /* 占用SPI總線  */    
        bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_4, SPI_PHASE_2EDGE, SPI_POLARITY_LOW);        
        CS_0();
    }
    else
    {        
        CS_1();    
        bsp_SpiBusExit();    /* 釋放SPI總線 */
    }    
}      

通過這種方式就有效的解決了單SPI接口管理多裝置的問題。因為給每個裝置都配了一個獨立的片選引腳,這樣就可以為每個裝置都配置這麼一個片選配置。

但是頻繁配置也比較繁瑣,是以函數bsp_InitSPIParam裡面做了特别處理。目前配置與之前配置相同的情況下無需重複配置。

74.6.5 第5步:DAC856X的資料更新

DAC856X的雙通道資料更新通過下面的函數實作:

/*
*********************************************************************************************************
*    函 數 名: DAC8562_SetDacData
*    功能說明: 設定DAC輸出,并立即更新。
*    形    參: _ch, 通道, 0 , 1
*             _data : 資料
*    返 回 值: 無
*********************************************************************************************************
*/
void DAC8562_SetDacData(uint8_t _ch, uint16_t _dac)
{
    if (_ch == 0)
    {
        /* Write to DAC-A input register and update DAC-A; */
        DAC8562_WriteCmd((3 << 19) | (0 << 16) | (_dac << 0));
    }
    else if (_ch == 1)
    {
        /* Write to DAC-B input register and update DAC-A; */
        DAC8562_WriteCmd((3 << 19) | (1 << 16) | (_dac << 0));
    }
}      

函數實作比較簡單,每次更新發送24bit資料即可。

DAC856X的DMA驅動方式略複雜,跟中斷更新方式完全不同,要使用硬體SPI1 NSS引腳驅動DAC8562的片選,所有專門做了一個驅動檔案來實作,程式驅動架構設計如下:

【STM32H7教程】第74章 STM32H7的SPI總線應用之驅動DAC8563(雙通道,16bit分辨率,正負10V)

74.7.1 第1步:SPI總線配置

/*
*********************************************************************************************************
*    函 數 名: bsp_InitDAC8562
*    功能說明: 配置GPIO并初始化DAC8562寄存器
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
void bsp_InitDAC8562(void)
{
    /* 配置GPIO */
    GPIO_InitTypeDef GPIO_InitStruct;

    s_SpiDmaMode = 0;  
    
    /*##-1- 配置SPI DMA ############################################################*/
    bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_4, SPI_PHASE_2EDGE, SPI_POLARITY_LOW);
    
    /*##-2- 配置CLR引腳 ############################################################*/
    CLR_CLK_ENABLE();
    
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;        /* 設定推挽輸出 */
    GPIO_InitStruct.Pull = GPIO_NOPULL;                /* 上下拉電阻不使能 */
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_MEDIUM; /* GPIO速度等級 */    

    GPIO_InitStruct.Pin = CLR_PIN;    
    HAL_GPIO_Init(CLR_GPIO, &GPIO_InitStruct);            

    CLR_0();        /* CLR接GND可靠一些,CLR是下降沿觸發 */
    LDAC_0();        /* 不用異步更新模式,此引腳接GND */
    
    /*##-3- 配置DAC8562 ############################################################*/
    /* Power up DAC-A and DAC-B */
    DAC8562_WriteCmd((4 << 19) | (0 << 16) | (3 << 0));

    /* LDAC pin inactive for DAC-B and DAC-A  不使用LDAC引腳更新資料 */
    DAC8562_WriteCmd((6 << 19) | (0 << 16) | (3 << 0));

    /* 複位2個DAC到中間值, 輸出0V */
    DAC8562_SetDacData(0, 32767);
    DAC8562_SetDacData(1, 32767);

    /* 選擇内部參考并複位2個DAC的增益=2 (複位時,内部參考是禁止的) */
    DAC8562_WriteCmd((7 << 19) | (0 << 16) | (1 << 0));
}

/*
*********************************************************************************************************
*    函 數 名: bsp_InitSPIParam
*    功能說明: 配置SPI總線參數,時鐘分頻,時鐘相位和時鐘極性。
*    形    參: _BaudRatePrescaler  SPI總線時鐘分頻設定,支援的參數如下:
*                                 SPI_BAUDRATEPRESCALER_2    2分頻
*                                 SPI_BAUDRATEPRESCALER_4    4分頻
*                                 SPI_BAUDRATEPRESCALER_8    8分頻
*                                 SPI_BAUDRATEPRESCALER_16   16分頻
*                                 SPI_BAUDRATEPRESCALER_32   32分頻
*                                 SPI_BAUDRATEPRESCALER_64   64分頻
*                                 SPI_BAUDRATEPRESCALER_128  128分頻
*                                 SPI_BAUDRATEPRESCALER_256  256分頻
*                                                        
*             _CLKPhase           時鐘相位,支援的參數如下:
*                                 SPI_PHASE_1EDGE     SCK引腳的第1個邊沿捕獲傳輸的第1個資料
*                                 SPI_PHASE_2EDGE     SCK引腳的第2個邊沿捕獲傳輸的第1個資料
*                                 
*             _CLKPolarity        時鐘極性,支援的參數如下:
*                                 SPI_POLARITY_LOW    SCK引腳在空閑狀态處于低電平
*                                 SPI_POLARITY_HIGH   SCK引腳在空閑狀态處于高電平
*
*    返 回 值: 無
*********************************************************************************************************
*/
void bsp_InitSPIParam(uint32_t _BaudRatePrescaler, uint32_t _CLKPhase, uint32_t _CLKPolarity)
{
    
    /* 設定SPI參數 */
    hspi.Instance               = SPIx;                           /* 例化SPI */
    hspi.Init.BaudRatePrescaler = _BaudRatePrescaler;             /* 設定波特率 */
    hspi.Init.Direction         = SPI_DIRECTION_2LINES_TXONLY;  /* 全雙工 */
    hspi.Init.CLKPhase          = _CLKPhase;                     /* 配置時鐘相位 */
    hspi.Init.CLKPolarity       = _CLKPolarity;                   /* 配置時鐘極性 */
    hspi.Init.DataSize          = SPI_DATASIZE_24BIT;               /* 設定資料寬度 */
    hspi.Init.FirstBit          = SPI_FIRSTBIT_MSB;             /* 資料傳輸先傳高位 */
    hspi.Init.TIMode            = SPI_TIMODE_DISABLE;             /* 禁止TI模式  */
    hspi.Init.CRCCalculation    = SPI_CRCCALCULATION_DISABLE;     /* 禁止CRC */
    hspi.Init.CRCPolynomial     = 7;                               /* 禁止CRC後,此位無效 */
    hspi.Init.CRCLength         = SPI_CRC_LENGTH_8BIT;             /* 禁止CRC後,此位無效 */
    hspi.Init.FifoThreshold     = SPI_FIFO_THRESHOLD_05DATA;    /* 設定FIFO大小是一個資料項 */
    
    hspi.Init.NSS         = SPI_NSS_HARD_OUTPUT;                 /* 使用軟體方式管理片選引腳 */
    hspi.Init.NSSPMode    = SPI_NSS_PULSE_ENABLE;                /* 使能脈沖輸出 */
    hspi.Init.NSSPolarity = SPI_NSS_POLARITY_LOW;               /* 低電平有效 */

/* MSS, 插入到NSS有效邊沿和第一個資料開始之間的額外延遲,機關SPI時鐘周期個數 */
    hspi.Init.MasterSSIdleness        = SPI_MASTER_SS_IDLENESS_00CYCLE;   

/* MIDI, 兩個連續資料幀之間插入的最小時間延遲,機關SPI時鐘周期個數 */
    hspi.Init.MasterInterDataIdleness = SPI_MASTER_INTERDATA_IDLENESS_10CYCLE; 
    
    hspi.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_ENABLE; /* 禁止SPI後,SPI相關引腳保持目前狀态 */  
    hspi.Init.Mode            = SPI_MODE_MASTER;                    /* SPI工作在主要模式 */

    /* 複位配置 */
    if (HAL_SPI_DeInit(&hspi) != HAL_OK)
    {
        Error_Handler(__FILE__, __LINE__);
    }    

    /* 初始化配置 */
    if (HAL_SPI_Init(&hspi) != HAL_OK)
    {
        Error_Handler(__FILE__, __LINE__);
    }    
}      

這兩個配置函數裡面最重要的是置紅的幾個配置選項,這裡依次為大家做個說明:

  •   SPI_DIRECTION_2LINES_TXONLY

驅動DAC856X僅需要SPI寫操作。

  •   SPI_DATASIZE_24BIT

STM32H7的SPI支援4-32bit資料傳輸,由于DAC856X需要24bit資料,是以這裡配置為24即可。

  •   SPI_FIFO_THRESHOLD_05DATA

對于SPI1來說,裡面的FIFO大小是16位元組,那麼SPI資料傳輸配置為24bit的話,FIFO最多可以存儲5個24bit,是以這個fifo閥值要設定為5。

  •   SPI_NSS_HARD_OUTPUT

我們這裡要使用SPI的硬體片選引腳SPI_NSS。

  •   SPI_MASTER_SS_IDLENESS_00CYCLE

插入到NSS有效邊沿和第一個資料開始之間的額外延遲,機關SPI時鐘周期個數。

根據本章4.5.4小節裡面的t(5)要求,片選有效到SCLK第1個下降沿信号的時間,最小值13ns。由于DAC856X的最高時鐘是50MHz,即20ns的分辨率,并且實際程式中,我們選擇的是第2個邊沿做資料采集,是以這裡配置為0即可,也就是無需插入時間。

  •   SPI_MASTER_INTERDATA_IDLENESS_10CYCLE

兩個連續資料幀之間插入的最小時間延遲,機關SPI時鐘周期個數。

根據本章4.5.4小節裡面的t(4)要求,每傳輸24bit資料後,片選要保持一段時間的高電平,DAC856X要求至少要80ns,也是說,如果我們以50MHz驅動DAC856X,這裡至少要配置為4個時鐘周期,推薦值為5及其以上即可,我們這裡直接配置為10個時鐘周期(配置為5也沒問題的)。

74.7.2 第2步:TIM12周期性觸發配置

這裡特别注意一點,定時器觸發一次,就會讓SPI以DMA方式傳輸24bit輸出。

TIM12的觸發配置如下:

/*
*********************************************************************************************************
*    函 數 名: TIM12_Config
*    功能說明: 配置TIM12,用于觸發DMAMUX的請求發生器
*    形    參: _ulFreq  觸發頻率,推薦範圍100Hz - 1MHz                              
*    返 回 值: 無
*********************************************************************************************************
*/   
TIM_HandleTypeDef  htim ={0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_OC_InitTypeDef sConfig = {0};
void TIM12_Config(uint32_t _ulFreq)
{
    uint16_t usPeriod;
    uint16_t usPrescaler;
    uint32_t uiTIMxCLK;
    
    
      /* 使能時鐘 */  
      __HAL_RCC_TIM12_CLK_ENABLE();
      
    /*-----------------------------------------------------------------------
        bsp.c 檔案中 void SystemClock_Config(void) 函數對時鐘的配置如下: 

        System Clock source       = PLL (HSE)
        SYSCLK(Hz)                = 400000000 (CPU Clock)
        HCLK(Hz)                  = 200000000 (AXI and AHBs Clock)
        AHB Prescaler             = 2
        D1 APB3 Prescaler         = 2 (APB3 Clock  100MHz)
        D2 APB1 Prescaler         = 2 (APB1 Clock  100MHz)
        D2 APB2 Prescaler         = 2 (APB2 Clock  100MHz)
        D3 APB4 Prescaler         = 2 (APB4 Clock  100MHz)

        因為APB1 prescaler != 1, 是以 APB1上的TIMxCLK = APB1 x 2 = 200MHz; 不含這個總線下的LPTIM1
        因為APB2 prescaler != 1, 是以 APB2上的TIMxCLK = APB2 x 2 = 200MHz;
        APB4上面的TIMxCLK沒有分頻,是以就是100MHz;

        APB1 定時器有 TIM2, TIM3 ,TIM4, TIM5, TIM6, TIM7, TIM12, TIM13, TIM14,LPTIM1
        APB2 定時器有 TIM1, TIM8 , TIM15, TIM16,TIM17

        APB4 定時器有 LPTIM2,LPTIM3,LPTIM4,LPTIM5
    ----------------------------------------------------------------------- */
    uiTIMxCLK = SystemCoreClock / 2;
    
    if (_ulFreq < 100)
    {
        usPrescaler = 10000 - 1;                    /* 分頻比 = 10000 */
        usPeriod =  (uiTIMxCLK / 10000) / _ulFreq  - 1; /* 自動重裝的值 */
    }
    else if (_ulFreq < 3000)
    {
        usPrescaler = 100 - 1;                    /* 分頻比 = 100 */
        usPeriod =  (uiTIMxCLK / 100) / _ulFreq  - 1;/* 自動重裝的值 */
    }
    else    /* 大于4K的頻率,無需分頻 */
    {
        usPrescaler = 0;                    /* 分頻比 = 1 */
        usPeriod = uiTIMxCLK / _ulFreq - 1;    /* 自動重裝的值 */
    }
    
    htim.Instance = TIM12;
    htim.Init.Period            = usPeriod;
    htim.Init.Prescaler         = usPrescaler;
    htim.Init.ClockDivision     = 0;
    htim.Init.CounterMode       = TIM_COUNTERMODE_UP;
    htim.Init.RepetitionCounter = 0;

    if(HAL_TIM_Base_DeInit(&htim) != HAL_OK)
    {
        Error_Handler(__FILE__, __LINE__);        
    }
    
    if(HAL_TIM_Base_Init(&htim) != HAL_OK)
    {
        Error_Handler(__FILE__, __LINE__);        
    }
 
    sConfig.OCMode     = TIM_OCMODE_PWM1;
    sConfig.OCPolarity = TIM_OCPOLARITY_LOW;
    sConfig.Pulse = usPeriod / 2;     /* 占空比50% */
    if(HAL_TIM_OC_ConfigChannel(&htim, &sConfig, TIM_CHANNEL_1) != HAL_OK)
    {
        Error_Handler(__FILE__, __LINE__);
    }

    /* 啟動OC1 */
    if(HAL_TIM_OC_Start(&htim, TIM_CHANNEL_1) != HAL_OK)
    {
        Error_Handler(__FILE__, __LINE__);
    }
 
    /* TIM12的TRGO用于觸發DMAMUX的請求發生器 */
    sMasterConfig.MasterOutputTrigger = TIM_TRGO_OC1REF;
    sMasterConfig.MasterOutputTrigger2 = TIM_TRGO2_RESET;
    sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
    
    HAL_TIMEx_MasterConfigSynchronization(&htim, &sMasterConfig);
}
#endif      

這個函數支援的觸發頻率很寬,對于DAC856X來說,如果樣本點設定為100個的話,此函數推薦的觸發頻率是100Hz到1MHz,具體可以支援到最高觸發速度計算看本章4.7.7小節即可。

74.7.3 第3步:DMAMUX同步觸發SPI DMA傳輸

DMA和DMAMUX的配置如下:

/*
*********************************************************************************************************
*    函 數 名: bsp_spiDamStart
*    功能說明: 啟動SPI DMA傳輸
*    形    參: _ulFreq 範圍推薦100Hz-1MHz
*    返 回 值: 無
*********************************************************************************************************
*/
void bsp_spiDamStart(uint32_t _ulFreq)
{
    /* 設定模式,要切換到DMA CIRCULAR模式 */
    s_SpiDmaMode = 1;
    
    bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_4, SPI_PHASE_2EDGE, SPI_POLARITY_LOW);
    
    /* 使能DMA時鐘 */
    DMAx_CLK_ENABLE();      

    /* SPI DMA發送配置 */        
    hdma_tx.Instance                 = SPIx_TX_DMA_STREAM;      /* 例化使用的DMA資料流 */
    hdma_tx.Init.FIFOMode            = DMA_FIFOMODE_ENABLE;     /* 使能FIFO */
    hdma_tx.Init.FIFOThreshold       = DMA_FIFO_THRESHOLD_FULL; /* 用于設定閥值 */
    hdma_tx.Init.MemBurst            = DMA_MBURST_SINGLE;        /* 用于存儲器突發 */
    hdma_tx.Init.PeriphBurst         = DMA_PBURST_SINGLE;        /* 用于外設突發 */
    hdma_tx.Init.Request             = SPIx_TX_DMA_REQUEST;     /* 請求類型 */  
    hdma_tx.Init.Direction           = DMA_MEMORY_TO_PERIPH;    /* 傳輸方向是從存儲器到外設 */  
    hdma_tx.Init.PeriphInc           = DMA_PINC_DISABLE;        /* 外設位址自增禁止 */ 
    hdma_tx.Init.MemInc              = DMA_MINC_ENABLE;         /* 存儲器位址自增使能 */  
    hdma_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;     /* 外設資料傳輸位寬選擇位元組,即8bit */ 
    hdma_tx.Init.MemDataAlignment    = DMA_MDATAALIGN_WORD;     /* 存儲器資料傳輸位寬選擇位元組,即8bit */    
    hdma_tx.Init.Mode                = DMA_CIRCULAR;            /* 正常模式 */
    hdma_tx.Init.Priority            = DMA_PRIORITY_LOW;        /* 優先級低 */

     /* 複位DMA */
    if(HAL_DMA_DeInit(&hdma_tx) != HAL_OK)
    {
        Error_Handler(__FILE__, __LINE__);     
    }
    
     /* 初始化DMA */
    if(HAL_DMA_Init(&hdma_tx) != HAL_OK)
    {
        Error_Handler(__FILE__, __LINE__);     
    }

    /* 關聯DMA句柄到SPI */
    __HAL_LINKDMA(&hspi, hdmatx, hdma_tx);    


    /* 關閉DMA發送中斷 */
    HAL_NVIC_SetPriority(SPIx_DMA_TX_IRQn, 1, 0);
    HAL_NVIC_DisableIRQ(SPIx_DMA_TX_IRQn);
    
    /* 關閉SPI中斷 */
    HAL_NVIC_SetPriority(SPIx_IRQn, 1, 0);
    HAL_NVIC_DisableIRQ(SPIx_IRQn);

    /* 同步觸發配置 */
    dmamux_syncParams.EventEnable   = ENABLE;                             
    dmamux_syncParams.SyncPolarity  = HAL_DMAMUX_SYNC_RISING;          
    dmamux_syncParams.RequestNumber = 1;                   
    dmamux_syncParams.SyncSignalID  = HAL_DMAMUX1_SYNC_TIM12_TRGO; 
    dmamux_syncParams.SyncEnable    = ENABLE;    
    
    HAL_DMAEx_ConfigMuxSync(&hdma_tx, &dmamux_syncParams);
    
    //LPTIM_Config(_ulFreq);
    
    TIM12_Config(_ulFreq);
    
    /* 啟動DMA傳輸 */
    if(HAL_SPI_Transmit_DMA(&hspi, (uint8_t*)g_spiTxBuf, g_spiLen/4)!= HAL_OK)    
    {
        Error_Handler(__FILE__, __LINE__);
    }
}      

這段程式裡面最關鍵的就是置紅的部分。作用是配置DMAMUX的同步觸發功能,觸發周期由TIM12控制。

74.7.4 第4步:24bit資料的DMA傳輸解決辦法

由于通用DMA1和DMA2僅支援8bit,16bit和32bit資料傳輸,我們這裡要傳輸24bit資料,解決的關鍵就是配置DMA為傳輸寬度為32bit,并将傳輸的資料由24bit再補一個8bit的任意值組成32bit即可,實際的傳輸會由SPI完成。

/*
*********************************************************************************************************
*    函 數 名: DAC8562_SetDacDataDMA
*    功能說明: DAC8562資料發送,DMA方式
*    形    參: _ch         1表示通道1輸出
*                         2表示通道2輸出
*                         3表示通道1和2都輸出
*                         4表示通道1和2都輸出,并且附加一個控制指令,有效防止傳輸錯誤時恢複。
*             _pbufch1    通道1資料緩沖位址
*             _pbufch2    通道2資料緩沖位址
*             _sizech1    通道1資料大小
*             _sizech2    通道2資料大小
*             _ulFreq     觸發頻率,範圍2KB- 1MHz,注意這個參數是觸發頻率,并不是波形周期,觸發一次,SPI DMA
*                         傳輸一次24bit資料。
*    返 回 值: 無
*********************************************************************************************************
*/
void DAC8562_SetDacDataDMA(uint8_t _ch, uint16_t *_pbufch1, uint16_t *_pbufch2, uint32_t _sizech1, uint32_t _sizech2, uint32_t _ulFreq)
{
    uint32_t i;
    uint32_t _cmd;
    
    g_spiLen = 0;
    
    switch (_ch)
    {
        /* 通道1資料發送 */
        case 1:
            for(i = 0; i < _sizech1; i++)
            {
                _cmd = (3 << 19) | (0 << 16) | (_pbufch1[i] << 0);
                
                g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd);
                g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 8);
                g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 16);
                g_spiTxBuf[g_spiLen++] = 0xff;
            }
            break;
    
        /* 通道2資料發送 */
        case 2:
            for(i = 0; i < _sizech2; i++)
            {
                _cmd = (3 << 19) | (1 << 16) | (_pbufch2[i] << 0);
                
                g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd);
                g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 8);
                g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 16);
                g_spiTxBuf[g_spiLen++] = 0xff;
            }
            break;

        /* 通道1和2混合發送 */            
        case 3:
            if(_sizech1 == _sizech2)
            {
                
                for(i = 0; i < _sizech1; i++)
                {
                    _cmd = (3 << 19) | (0 << 16) | (_pbufch1[i] << 0);
                    
                    g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd);
                    g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 8);
                    g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 16);
                    g_spiTxBuf[g_spiLen++] = 0xff;
                    
                    _cmd = (3 << 19) | (1 << 16) | (_pbufch2[i] << 0);
                    
                    g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd);
                    g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 8);
                    g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 16);
                    g_spiTxBuf[g_spiLen++] = 0xff;
                }            
            }
            else
            {
                for(i = 0; i < _sizech1; i++)
                {
                    _cmd = (3 << 19) | (0 << 16) | (_pbufch1[i] << 0);
                    
                    g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd);
                    g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 8);
                    g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 16);
                    g_spiTxBuf[g_spiLen++] = 0xff;
                }
                for(i = 0; i < _sizech2; i++)
                {
                    _cmd = (3 << 19) | (1 << 16) | (_pbufch2[i] << 0);
                    
                    g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd);
                    g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 8);
                    g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 16);
                    g_spiTxBuf[g_spiLen++] = 0xff;
                }
            }
            break;
            
        /* 插入關鍵指令,防止傳輸錯誤 */            
        case 4:
            if(_sizech1 == _sizech2)
            {
                
                for(i = 0; i < _sizech1; i++)
                {
                    _cmd = (3 << 19) | (0 << 16) | (_pbufch1[i] << 0);
                    
                    g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd);
                    g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 8);
                    g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 16);
                    g_spiTxBuf[g_spiLen++] = 0xff;
                    
                    _cmd = (3 << 19) | (1 << 16) | (_pbufch2[i] << 0);
                    
                    g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd);
                    g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 8);
                    g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 16);
                    g_spiTxBuf[g_spiLen++] = 0xff;
                }            
            }
            else
            {
                for(i = 0; i < _sizech1; i++)
                {
                    _cmd = (3 << 19) | (0 << 16) | (_pbufch1[i] << 0);
                    
                    g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd);
                    g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 8);
                    g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 16);
                    g_spiTxBuf[g_spiLen++] = 0xff;
                }
                for(i = 0; i < _sizech2; i++)
                {
                    _cmd = (3 << 19) | (1 << 16) | (_pbufch2[i] << 0);
                    
                    g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd);
                    g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 8);
                    g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 16);
                    g_spiTxBuf[g_spiLen++] = 0xff;
                }
            }
            
            /* 資料填充完畢後,插入關鍵指令,資料輸出過程中被8256誤識别為指令處理*/
            _cmd = (7 << 19) | (0 << 16) | (1 << 0);
            
            g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd);
            g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 8);
            g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 16);
            g_spiTxBuf[g_spiLen++] = 0xff;
            break;
        
        default:
            break;

    }
    
    bsp_spiDamStart(_ulFreq);
}      

74.7.5 第5步:DMA緩沖區的MPU配置

因為工程是用的DTCM做的主RAM空間,這個空間無法使用通用DMA1和DMA2,通過本手冊第26章的記憶體塊超友善使用方式,将DMA緩沖定義到SRAM4上:

#if defined ( __CC_ARM )    /* IAR *******/
    __attribute__((section (".RAM_D3"))) uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];   
    __attribute__((section (".RAM_D3"))) uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];
#elif defined (__ICCARM__)  /* MDK ********/
    #pragma location = ".RAM_D3"
    uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];   
    #pragma location = ".RAM_D3"
    uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];
#endif      

由于程式裡面開啟了資料Cache,會造成DMA和CPU通路SRAM4資料不一緻的問題,特此将SRAM4空間關閉Cache。

/* 配置SRAM4的MPU屬性為Non-cacheable */
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0x38000000;
    MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
    MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    MPU_InitStruct.SubRegionDisable = 0x00;
    MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

    HAL_MPU_ConfigRegion(&MPU_InitStruct);      

74.7.6 第6步:DAC856X的時鐘極性和時鐘相位配置

注:與本章4.6.3小節内容是一樣的。

  •  當CPOL = 1, CPHA = 0時
【STM32H7教程】第74章 STM32H7的SPI總線應用之驅動DAC8563(雙通道,16bit分辨率,正負10V)
【STM32H7教程】第74章 STM32H7的SPI總線應用之驅動DAC8563(雙通道,16bit分辨率,正負10V)

74.7.7 第7步:DAC856X的最高更新速度計算

這裡特别注意一點,定時器觸發一次,就會讓SPI以DMA方式傳輸24bit資料。

配置條件:

  •   SPI時鐘是50MHz。
  •   SPI資料傳為24bit,每個bit需要時間20ns。
  •   hspi.Init.MasterSSIdleness = SPI_MASTER_SS_IDLENESS_00CYCLE      插入到NSS有效邊沿和第一個資料開始之間的額外延遲,機關SPI時鐘周期個數,即20ns。
  •   hspi.Init.MasterInterDataIdleness = SPI_MASTER_INTERDATA_IDLENESS_10CYCLE     兩個連續資料幀之間插入的最小時間延遲,機關SPI時鐘周期個數,即20ns。

根據上面的配置,傳輸一幀(24bit)資料需要的時間:

24bit * 20ns+ SPI_MASTER_SS_IDLENESS_00CYCLE * 20ns

+ SPI_MASTER_INTERDATA_IDLENESS_10CYCLE * 20ns

= 24bit * 20ns + 0 * 20ns + 10 * 20ns

= 680ns。

那麼這種配置下,可以支援最高觸發速度是1 / 680ns = 1.47MHz,如果想速度再提升些,可以降低參數hspi.Init.MasterInterDataIdleness,推薦的最小值是5個時鐘周期,那麼可以支援的最高觸發速度是1/580ns = 1.7MHz。

認識到這些後,實際輸出的波形周期也比較好算了,比如我們設定10個樣本點為一個周期,那麼觸發速度為1MHz的時候,那麼波形周期就是100KHz。

74.7.8 第8步:DAC值和電壓值互轉

DAC856X子產品的輸出電壓範圍是-10V到10V,對應的編碼值範圍是0到65535,為了友善大家做互轉,專門做了兩個函數:

/*
*********************************************************************************************************
*    函 數 名: DAC8562_DacToVoltage
*    功能說明: 将DAC值換算為電壓值,機關0.1mV
*    形    參: _dac  16位DAC字
*    返 回 值: 電壓。機關0.1mV
*********************************************************************************************************
*/
int32_t DAC8562_DacToVoltage(uint16_t _dac)
{
    int32_t y;

    /* CaculTwoPoint(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x);*/
    y =  CaculTwoPoint(X1, Y1, X2, Y2, _dac);
    return y;
}

/*
*********************************************************************************************************
*    函 數 名: DAC8562_VoltageToDac
*    功能說明: 将電壓值轉換為DAC置
*    形    參: _volt 電壓,機關0.1mV
*    返 回 值: 16位DAC字
*********************************************************************************************************
*/
uint32_t DAC8562_VoltageToDac(int32_t _volt)
{
    /* CaculTwoPoint(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x);*/
    return CaculTwoPoint(Y1, X1, Y2, X2, _volt);
}      

74.7.9 第9步:防止SPI DMA批量資料傳輸錯誤解決辦法

使用SPI DMA批量資料傳輸過程中,要防止一些資料被DAC856X錯誤識别成關鍵指令,進而造成DAC856X工作異常,其中最重要的一個關鍵指令就下面這個:

/* 選擇内部參考并複位2個DAC的增益=2 (複位時,内部參考是禁止的) */
DAC8562_WriteCmd((7 << 19) | (0 << 16) | (1 << 0));      

針對這個問題,函數DAC8562_SetDacDataDMA專門做了一個傳輸方式4:

/* 插入關鍵指令,防止傳輸錯誤 */            
case 4:
    if(_sizech1 == _sizech2)
    {
        
        for(i = 0; i < _sizech1; i++)
        {
            _cmd = (3 << 19) | (0 << 16) | (_pbufch1[i] << 0);
            
            g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd);
            g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 8);
            g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 16);
            g_spiTxBuf[g_spiLen++] = 0xff;
            
            _cmd = (3 << 19) | (1 << 16) | (_pbufch2[i] << 0);
            
            g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd);
            g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 8);
            g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 16);
            g_spiTxBuf[g_spiLen++] = 0xff;
        }            
    }
    else
    {
        for(i = 0; i < _sizech1; i++)
        {
            _cmd = (3 << 19) | (0 << 16) | (_pbufch1[i] << 0);
            
            g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd);
            g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 8);
            g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 16);
            g_spiTxBuf[g_spiLen++] = 0xff;
        }
        for(i = 0; i < _sizech2; i++)
        {
            _cmd = (3 << 19) | (1 << 16) | (_pbufch2[i] << 0);
            
            g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd);
            g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 8);
            g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 16);
            g_spiTxBuf[g_spiLen++] = 0xff;
        }
    }
    
    /* 資料填充完畢後,插入關鍵指令,資料輸出過程中被8256誤識别為指令處理*/
    _cmd = (7 << 19) | (0 << 16) | (1 << 0);
    
    g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd);
    g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 8);
    g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 16);
    g_spiTxBuf[g_spiLen++] = 0xff;
    break;      

解決辦法是在批量資料的末尾附一個指令,通過這種方式可以有效防止DAC856X工作異常。

SPI總線驅動檔案bsp_spi_bus.c主要實作了如下幾個API供使用者調用:

  •   bsp_InitSPIBus
  •   bsp_InitSPIParam
  •   bsp_spiTransfer

74.8.1 函數bsp_InitSPIBus

函數原型:

void bsp_InitSPIBus(void)

函數描述:

此函數主要用于SPI總線的初始化,在bsp.c檔案調用一次即可。

74.8.2 函數bsp_InitSPIParam

void bsp_InitSPIParam(uint32_t _BaudRatePrescaler, uint32_t _CLKPhase, uint32_t _CLKPolarity)

此函數用于SPI總線的配置。

函數參數:

  •   第1個參數SPI總線的分頻設定,支援的參數如下:

SPI_BAUDRATEPRESCALER_2    2分頻

SPI_BAUDRATEPRESCALER_4    4分頻

SPI_BAUDRATEPRESCALER_8    8分頻

SPI_BAUDRATEPRESCALER_16   16分頻

SPI_BAUDRATEPRESCALER_32   32分頻

SPI_BAUDRATEPRESCALER_64   64分頻

SPI_BAUDRATEPRESCALER_128  128分頻

SPI_BAUDRATEPRESCALER_256  256分頻

  •   第2個參數用于時鐘相位配置,支援的參數如下:

SPI_PHASE_1EDGE     SCK引腳的第1個邊沿捕獲傳輸的第1個資料

SPI_PHASE_2EDGE     SCK引腳的第2個邊沿捕獲傳輸的第1個資料

  •   第3個參數是時鐘極性配置,支援的參數如下:

SPI_POLARITY_LOW   SCK引腳在空閑狀态處于低電平

SPI_POLARITY_HIGH   SCK引腳在空閑狀态處于高電平

74.8.3 函數bsp_spiTransfer

void bsp_spiTransfer(void)

此函數用于啟動SPI資料傳輸,支援查詢,中斷和DMA方式傳輸。

DAC856X驅動檔案bsp_spi_dac8562.c主要實作了如下幾個API供使用者調用:

  •   bsp_InitDAC8562
  •   DAC8562_SetCS
  •   DAC8562_WriteCmd
  •   DAC8562_SetDacData
  •   DAC8562_DacToVoltage
  •   DAC8562_VoltageToDac

74.9.1 函數bsp_InitDAC8562

void bsp_InitDAC8562(void)

主要用于DAC856X的初始化,調用前務必先調用函數bsp_InitSPIBus初始化SPI外設。

74.9.2 函數DAC8562_SetCS

void DAC8562_SetCS(uint8_t _Level)

此函數用于片選DAC8562。

  •   第1個參數為0表示選中,為1表示取消選中。

74.9.3 函數DAC8562_WriteCmd

void DAC8562_WriteCmd(uint32_t _cmd)

此函數用于向SPI總線發送24個bit資料。

  •   第1個參數為24bit資料。

74.9.4 函數DAC8562_SetDacData

void DAC8562_SetDacData(uint8_t _ch, uint16_t _dac)

此函數用于設定DAC輸出,并立即更新。

  •   第1個參數為0表示通道1,為1表示通道2。
  •   第2個參數是DAC數值設定,範圍0到65535,0對應最小電壓值,65535對應最大電壓值。

74.9.5 函數DAC8562_DacToVoltage

int32_t DAC8562_DacToVoltage(uint16_t _dac)

此函數用于将DAC值換算為電壓值,機關0.1mV。

  •   第1個參數DAC數值,範圍0到65535。
  •   傳回值,傳回電壓值,機關0.1mV。

74.9.6 函數DAC8562_VoltageToDac

uint32_t DAC8562_VoltageToDac(int32_t _volt)

此函數用于将電壓值轉換為DAC值。

  •   第1個參數是電壓值,範圍-100000到100000,機關0.1mV。
  •   傳回值,傳回DAC值。

74.10          DAC856X支援包DMA方式(bsp_spidma_dac8562.c)

DAC856X驅動檔案bsp_spidam_dac8562.c涉及到的函數比較多,我們主要介紹用到的如下幾個函數:

  •   DAC8562_SetDacDataDMA

74.10.1   函數bsp_InitDAC8562

主要用于DAC856X的初始化。

74.10.2   函數DAC8562_SetDacDataDMA

void DAC8562_SetDacDataDMA(uint8_t _ch, uint16_t *_pbufch1, uint16_t *_pbufch2, uint32_t _sizech1, uint32_t _sizech2, uint32_t _ulFreq)

此函數用于SPI DMA方式資料發送。

  •   第1個參數用于選擇的通道:
    •   1表示通道1輸出
    •   2表示通道2輸出
    •   3表示通道1和2都輸出
    •   4表示通道1和2都輸出,并且附加一個控制指令,有效防止傳輸錯誤時恢複。
  •   第2個參數表示通道1資料緩沖位址。
  •   第3個參數表示通道2資料緩沖位址。
  •   第4個參數表示通道1資料大小。
  •   第5個參數表示通道2資料大小。
  •   第6個參數表示觸發頻率,推薦範圍100Hz- 1MHz,注意這個參數是觸發頻率,并不是波形周期。這裡觸發一次,SPI DMA傳輸一次24bit資料。

74.10.3   函數DAC8562_WriteCmd

  • 第1個參數為24bit資料。

74.10.4   函數DAC8562_SetDacData

  • 第1個參數為0表示通道1,為1表示通道2。
  •  第2個參數是DAC數值設定,範圍0到65535,0對應最小電壓值,65535對應最大電壓值。

74.11          DAC856X驅動移植和使用(中斷更新方式)

DAC856X移植步驟如下:

第1步:複制bsp_spi_bus.c,bsp_spi_bus.h,bsp_spi_dac8562.c,bsp_spi_dac8562.h到自己的工程目錄,并添加到工程裡面。

第2步:根據使用的第幾個SPI,SPI時鐘,SPI引腳和DMA通道等,修改bsp_spi_bus.c檔案開頭的宏定義

/*
*********************************************************************************************************
*                                時鐘,引腳,DMA,中斷等宏定義
*********************************************************************************************************
*/
#define SPIx                            SPI1
#define SPIx_CLK_ENABLE()                __HAL_RCC_SPI1_CLK_ENABLE()
#define DMAx_CLK_ENABLE()                __HAL_RCC_DMA2_CLK_ENABLE()

#define SPIx_FORCE_RESET()                __HAL_RCC_SPI1_FORCE_RESET()
#define SPIx_RELEASE_RESET()            __HAL_RCC_SPI1_RELEASE_RESET()

#define SPIx_SCK_CLK_ENABLE()            __HAL_RCC_GPIOB_CLK_ENABLE()
#define SPIx_SCK_GPIO                    GPIOB
#define SPIx_SCK_PIN                    GPIO_PIN_3
#define SPIx_SCK_AF                        GPIO_AF5_SPI1

#define SPIx_MISO_CLK_ENABLE()            __HAL_RCC_GPIOB_CLK_ENABLE()
#define SPIx_MISO_GPIO                    GPIOB
#define SPIx_MISO_PIN                     GPIO_PIN_4
#define SPIx_MISO_AF                    GPIO_AF5_SPI1

#define SPIx_MOSI_CLK_ENABLE()            __HAL_RCC_GPIOB_CLK_ENABLE()
#define SPIx_MOSI_GPIO                    GPIOB
#define SPIx_MOSI_PIN                     GPIO_PIN_5
#define SPIx_MOSI_AF                    GPIO_AF5_SPI1

#define SPIx_TX_DMA_STREAM               DMA2_Stream3
#define SPIx_RX_DMA_STREAM               DMA2_Stream2

#define SPIx_TX_DMA_REQUEST              DMA_REQUEST_SPI1_TX
#define SPIx_RX_DMA_REQUEST              DMA_REQUEST_SPI1_RX

#define SPIx_DMA_TX_IRQn                 DMA2_Stream3_IRQn
#define SPIx_DMA_RX_IRQn                 DMA2_Stream2_IRQn

#define SPIx_DMA_TX_IRQHandler           DMA2_Stream3_IRQHandler
#define SPIx_DMA_RX_IRQHandler           DMA2_Stream2_IRQHandler

#define SPIx_IRQn                        SPI1_IRQn
#define SPIx_IRQHandler                  SPI1_IRQHandler      

第3步:根據晶片支援的時脈速度,時鐘相位和時鐘極性配置函數DAC8562_SetCS。

/*
*********************************************************************************************************
*    函 數 名: DAC8562_SetCS
*    功能說明: DAC8562 片選控制函數
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
void DAC8562_SetCS(uint8_t _Level)
{
    if (_Level == 0)
    {
        bsp_SpiBusEnter();    /* 占用SPI總線  */    
        bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_4, SPI_PHASE_2EDGE, SPI_POLARITY_LOW);        
        CS_0();
    }
    else
    {        
        CS_1();    
        bsp_SpiBusExit();    /* 釋放SPI總線 */
    }    
}      

第4步:根據使用的片選,CLR和LDAC引腳,修改bsp_spi_dac8562.c檔案開頭的宏定義。

/* SYNC, 也就是CS片選 */    
#define CS_CLK_ENABLE()          __HAL_RCC_GPIOG_CLK_ENABLE()
#define CS_GPIO                GPIOG
#define CS_PIN                GPIO_PIN_10
#define CS_1()                CS_GPIO->BSRR = CS_PIN
#define CS_0()                CS_GPIO->BSRR = ((uint32_t)CS_PIN << 16U)

/* CLR */    
#define CLR_CLK_ENABLE()          __HAL_RCC_GPIOE_CLK_ENABLE()
#define CLR_GPIO                 GPIOE
#define CLR_PIN                GPIO_PIN_4
#define CLR_1()                CLR_GPIO->BSRR = CLR_PIN
#define CLR_0()                CLR_GPIO->BSRR = ((uint32_t)CLR_PIN << 16U)

/* LDAC 使用擴充IO ,特别注意,我們這裡是用的擴充IO控制的 */    
#define LDAC_1()            HC574_SetPin(NRF24L01_CE, 1);
#define LDAC_0()            HC574_SetPin(NRF24L01_CE, 0);      

第5步:如果使用DMA方式的話,請不要使用TCM RAM,因為通用DMA1和DMA2不支援。并為了防止DMA和CPU同時通路DMA緩沖造成的資料一緻性問題,将這塊空間關閉Cache處理,比如使用的SRAM4:

/* 配置SRAM4的MPU屬性為Non-cacheable */
MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress      = 0x38000000;
MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

HAL_MPU_ConfigRegion(&MPU_InitStruct);      

第6步:初始化SPI。

/* 針對不同的應用程式,添加需要的底層驅動子產品初始化函數 */
bsp_InitSPIBus();    /* 配置SPI總線 */        
bsp_InitDAC8562();    /* 初始化配置DAC8562/8563 */      

第7步:DAC856X驅動主要用到HAL庫的SPI驅動檔案,簡單省事些可以添加所有HAL庫C源檔案進來。

第8步:應用方法看本章節配套例子即可。

74.12          DAC856X驅動移植和使用(SPI DMA更新方式)

第1步:複制bsp_spidma_dac8562.c,bsp_spidma_dac8562.h到自己的工程目錄,并添加到工程裡面。

第2步:根據使用的第幾個SPI,SPI時鐘,SPI引腳和DMA通道等,修改bsp_spidma_dac8562.c檔案開頭的宏定義

/*
*********************************************************************************************************
*                                時鐘,引腳,DMA,中斷等宏定義
*********************************************************************************************************
*/
#define SPIx                            SPI1
#define SPIx_CLK_ENABLE()                __HAL_RCC_SPI1_CLK_ENABLE()
#define DMAx_CLK_ENABLE()                __HAL_RCC_DMA2_CLK_ENABLE()

#define SPIx_FORCE_RESET()                __HAL_RCC_SPI1_FORCE_RESET()
#define SPIx_RELEASE_RESET()            __HAL_RCC_SPI1_RELEASE_RESET()

/* SYNC, 也就是CS片選 */    
#define SPIx_NSS_CLK_ENABLE()             __HAL_RCC_GPIOG_CLK_ENABLE()
#define SPIx_NSS_GPIO                    GPIOG
#define SPIx_NSS_PIN                    GPIO_PIN_10
#define SPIx_NSS_AF                    GPIO_AF5_SPI1

#define SPIx_SCK_CLK_ENABLE()            __HAL_RCC_GPIOB_CLK_ENABLE()
#define SPIx_SCK_GPIO                    GPIOB
#define SPIx_SCK_PIN                    GPIO_PIN_3
#define SPIx_SCK_AF                    GPIO_AF5_SPI1

#define SPIx_MISO_CLK_ENABLE()            __HAL_RCC_GPIOB_CLK_ENABLE()
#define SPIx_MISO_GPIO                    GPIOB
#define SPIx_MISO_PIN                     GPIO_PIN_4
#define SPIx_MISO_AF                    GPIO_AF5_SPI1

#define SPIx_MOSI_CLK_ENABLE()          __HAL_RCC_GPIOB_CLK_ENABLE()
#define SPIx_MOSI_GPIO                  GPIOB
#define SPIx_MOSI_PIN                   GPIO_PIN_5
#define SPIx_MOSI_AF                  GPIO_AF5_SPI1

#define SPIx_TX_DMA_STREAM              DMA2_Stream3
#define SPIx_RX_DMA_STREAM              DMA2_Stream2

#define SPIx_TX_DMA_REQUEST             DMA_REQUEST_SPI1_TX
#define SPIx_RX_DMA_REQUEST             DMA_REQUEST_SPI1_RX

#define SPIx_DMA_TX_IRQn                DMA2_Stream3_IRQn
#define SPIx_DMA_RX_IRQn                DMA2_Stream2_IRQn

#define SPIx_DMA_TX_IRQHandler          DMA2_Stream3_IRQHandler
#define SPIx_DMA_RX_IRQHandler          DMA2_Stream2_IRQHandler

#define SPIx_IRQn                       SPI1_IRQn
#define SPIx_IRQHandler                 SPI1_IRQHandler      

第3步:根據使用的CLR和LDAC引腳,修改bsp_spidma_dac8562.c檔案開頭的宏定義。

/* CLR */    
#define CLR_CLK_ENABLE()     __HAL_RCC_GPIOE_CLK_ENABLE()
#define CLR_GPIO            GPIOE
#define CLR_PIN                GPIO_PIN_4
#define CLR_1()                CLR_GPIO->BSRR = CLR_PIN
#define CLR_0()                CLR_GPIO->BSRR = ((uint32_t)CLR_PIN << 16U)

/* LDAC 使用擴充IO ,特别注意,我們這裡是用的擴充IO控制的 */    
#define LDAC_1()            HC574_SetPin(NRF24L01_CE, 1);
#define LDAC_0()            HC574_SetPin(NRF24L01_CE, 0);      

第4步:如果使用DMA方式的話,請不要使用TCM RAM,因為通用DMA1和DMA2不支援。并為了防止DMA和CPU同時通路DMA緩沖造成的資料一緻性問題,将這塊空間關閉Cache處理,比如使用的SRAM4:

/* 配置SRAM4的MPU屬性為Non-cacheable */
MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress      = 0x38000000;
MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

HAL_MPU_ConfigRegion(&MPU_InitStruct);      

第5步:初始化SPI。

/* 針對不同的應用程式,添加需要的底層驅動子產品初始化函數 */
bsp_InitDAC8562();    /* 初始化配置DAC8562/8563 */      

 第6步:DAC856X驅動主要用到HAL庫的SPI驅動檔案,簡單省事些可以添加所有HAL庫C源檔案進來。

 第7步:應用方法看本章節配套例子即可

74.13          實驗例程設計架構

通過程式設計架構,讓大家先對配套例程有一個全面的認識,然後再了解細節,本次實驗例程的設計架構如下:

【STM32H7教程】第74章 STM32H7的SPI總線應用之驅動DAC8563(雙通道,16bit分辨率,正負10V)

  第1階段,上電啟動階段:

  • 這部分在第14章進行了詳細說明。

  第2階段,進入main函數:

  •  第1部分,硬體初始化,主要是MPU,Cache,HAL庫,系統時鐘,滴答定時器和LED。
  •  第2部分,應用程式設計部分,實作DAC856X的簡易信号發生器功能。。

74.14          實驗例程說明(MDK)

注:本章是配套了兩個例子的,這裡我們以SPI DMA方式進行說明。

配套例子:

V7-052_DAC856x簡易信号發生器(雙通道SPI DMA方式,16bit分辨率, 正負10V輸出)

V7-053_DAC856x簡易信号發生器(雙通道SPI查詢方式,16bit分辨率, 正負10V輸出)

實驗目的:

  1. 學習SPI Flash的DAC8563的SPI DMA驅動方式實作。

實驗内容:

  1. 雙通道DAC,軌到軌輸出,16bit分辨率,支援50MHz的SPI時脈速度。
  2. 自帶2.5V的内部參考基準,典型的溫飄是4ppm/℃,使用内部2.5V參考基準的情況下,根據增益設定不同,DAC的輸出量可以為0到2.5V或者0到5V。
  3. DAC8562和DAC8563完全相容,差別僅僅在于CLR引腳有效時,DAC8562資料設定為0, DAC8563資料設定為32767,注意這是DAC的内部資料,不表示輸出電壓。 對于-10 ~ +10V輸出的子產品,DAC8562輸出-10V, DAC8563輸出0V。
  4. 無論是用DAC8562還是DAC8563晶片,隻要軟體不啟動,本子產品輸出電壓預設狀态都是0V。
  5. CLR腳懸浮時,電壓在1.9V左右,容易受到幹擾導緻輸出被清零。是以即使不用CLR控制功能,這個CLR腳也需要接固定電平(推薦接GND)。CLR是邊沿觸發,僅在下降沿信号出現執行清零。

實驗操作:

  1. 啟動一個自動重裝軟體定時器,每100ms翻轉一次LED2。
  2. K1鍵按下,雙通道輸出,通道1輸出方波,通道2輸出正弦波。
  3. K2鍵按下,雙通道輸出方波。
  4. K3鍵按下,雙通道輸出正弦波。
  5. 搖杆上鍵按下,通道1停止方波,通道2停止輸出。
  6. 搖杆下鍵按下,雙通道輸出直流。
  7. 搖杆OK鍵按下,重新初始化。

上電後序列槽列印的資訊:

波特率 115200,資料位 8,奇偶校驗位無,停止位 1。

【STM32H7教程】第74章 STM32H7的SPI總線應用之驅動DAC8563(雙通道,16bit分辨率,正負10V)

波形效果:

【STM32H7教程】第74章 STM32H7的SPI總線應用之驅動DAC8563(雙通道,16bit分辨率,正負10V)

子產品插入位置:

【STM32H7教程】第74章 STM32H7的SPI總線應用之驅動DAC8563(雙通道,16bit分辨率,正負10V)

程式設計:

  系統棧大小配置設定:

【STM32H7教程】第74章 STM32H7的SPI總線應用之驅動DAC8563(雙通道,16bit分辨率,正負10V)

  RAM空間用的DTCM:

【STM32H7教程】第74章 STM32H7的SPI總線應用之驅動DAC8563(雙通道,16bit分辨率,正負10V)

  硬體外設初始化

硬體外設的初始化是在 bsp.c 檔案實作:

/*
*********************************************************************************************************
*    函 數 名: bsp_Init
*    功能說明: 初始化所有的硬體裝置。該函數配置CPU寄存器和外設的寄存器并初始化一些全局變量。隻需要調用一次
*    形    參:無
*    返 回 值: 無
*********************************************************************************************************
*/
void bsp_Init(void)
{
    /* 配置MPU */
    MPU_Config();
    
    /* 使能L1 Cache */
    CPU_CACHE_Enable();

    /* 
       STM32H7xx HAL 庫初始化,此時系統用的還是H7自帶的64MHz,HSI時鐘:
       - 調用函數HAL_InitTick,初始化滴答時鐘中斷1ms。
       - 設定NVIV優先級分組為4。
     */
    HAL_Init();

    /* 
       配置系統時鐘到400MHz
       - 切換使用HSE。
       - 此函數會更新全局變量SystemCoreClock,并重新配置HAL_InitTick。
    */
    SystemClock_Config();

    /* 
       Event Recorder:
       - 可用于代碼執行時間測量,MDK5.25及其以上版本才支援,IAR不支援。
       - 預設不開啟,如果要使能此選項,務必看V7開發闆使用者手冊第xx章
    */    
#if Enable_EventRecorder == 1  
    /* 初始化EventRecorder并開啟 */
    EventRecorderInitialize(EventRecordAll, 1U);
    EventRecorderStart();
#endif
    
bsp_InitDWT();      /* 初始化DWT時鐘周期計數器 */       
    bsp_InitKey();         /* 按鍵初始化,要放在滴答定時器之前,因為按鈕檢測是通過滴答定時器掃描 */
    bsp_InitTimer();       /* 初始化滴答定時器 */
    bsp_InitLPUart();     /* 初始化序列槽 */
    bsp_InitExtIO();     /* 初始化FMC總線74HC574擴充IO. 必須在 bsp_InitLed()前執行 */    
    bsp_InitLed();         /* 初始化LED */    
bsp_InitExtSDRAM(); /* 初始化SDRAM */

    /* 針對不同的應用程式,添加需要的底層驅動子產品初始化函數 */    
    bsp_InitDAC8562();    /* 初始化配置DAC8562/8563 */
}      

  MPU配置和Cache配置:

資料Cache和指令Cache都開啟。配置了AXI SRAM區(本例子未用到AXI SRAM)和FMC的擴充IO區以及SRAM4

/*
*********************************************************************************************************
*    函 數 名: MPU_Config
*    功能說明: 配置MPU
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
static void MPU_Config( void )
{
    MPU_Region_InitTypeDef MPU_InitStruct;

    /* 禁止 MPU */
    HAL_MPU_Disable();

    /* 配置AXI SRAM的MPU屬性為Write back, Read allocate,Write allocate */
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0x24000000;
    MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
    MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    MPU_InitStruct.SubRegionDisable = 0x00;
    MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

    HAL_MPU_ConfigRegion(&MPU_InitStruct);
    
    
    /* 配置FMC擴充IO的MPU屬性為Device或者Strongly Ordered */
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0x60000000;
    MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;    
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;    
    MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER1;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
    MPU_InitStruct.SubRegionDisable = 0x00;
    MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    
    HAL_MPU_ConfigRegion(&MPU_InitStruct);

    /* 配置SRAM4的MPU屬性為Non-cacheable */
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0x38000000;
    MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
    MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    MPU_InitStruct.SubRegionDisable = 0x00;
    MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

    HAL_MPU_ConfigRegion(&MPU_InitStruct);

    /*使能 MPU */
    HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}

/*
*********************************************************************************************************
*    函 數 名: CPU_CACHE_Enable
*    功能說明: 使能L1 Cache
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
static void CPU_CACHE_Enable(void)
{
    /* 使能 I-Cache */
    SCB_EnableICache();

    /* 使能 D-Cache */
    SCB_EnableDCache();
}      

  每10ms調用一次按鍵處理:

按鍵處理是在滴答定時器中斷裡面實作,每10ms執行一次檢測。

/*
*********************************************************************************************************
*    函 數 名: bsp_RunPer10ms
*    功能說明: 該函數每隔10ms被Systick中斷調用1次。詳見 bsp_timer.c的定時中斷服務程式。一些處理時間要求
*              不嚴格的任務可以放在此函數。比如:按鍵掃描、蜂鳴器鳴叫控制等。
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
void bsp_RunPer10ms(void)
{
    bsp_KeyScan10ms();
}      

  主功能:

主程式實作如下操作:

  •   啟動一個自動重裝軟體定時器,每100ms翻轉一次LED2。
  •   K1鍵按下,雙通道輸出,通道1輸出方波,通道2輸出正弦波。
  •   K2鍵按下,雙通道輸出方波。
  •   K3鍵按下,雙通道輸出正弦波。
  •   搖杆上鍵按下,通道1停止方波,通道2停止輸出。
  •   搖杆下鍵按下,雙通道輸出直流。
  •   搖杆OK鍵按下,重新初始化。
/*
*********************************************************************************************************
*    函 數 名: main
*    功能說明: c程式入口
*    形    參: 無
*    返 回 值: 錯誤代碼(無需處理)
*********************************************************************************************************
*/
int main(void)
{
    bsp_Init();        /* 硬體初始化 */
    
    PrintfLogo();    /* 列印例程名稱和版本等資訊 */

    DemoSpiDac();   /* SPI DAC測試 */
}

/*
*********************************************************************************************************
*    函 數 名: DemoSpiDac
*    功能說明: DAC8562測試
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
void DemoSpiDac(void)
{
    uint8_t i=0;
    uint8_t ucKeyCode;    /* 按鍵代碼 */
    
    sfDispMenu();        /* 列印指令提示 */
    
    bsp_StartAutoTimer(0, 200);    /* 啟動1個100ms的自動重裝的定時器 */
    
    
    /* 生成方波資料 */
    for(i =0; i< 50; i++)
    {
        ch1buf[i] = 0;
    }
    
    for(i =50; i< 100; i++)
    {
        ch1buf[i] = 65535;
    }

    /* 生成正弦波資料 */    
    MakeSinTable(ch2buf, 100, 0, 65535);
    
    /* 
       上電預設雙通道輸出:
        第1個參數:
          1 表示通道1輸出
          2 表示通道2輸出
          3 表示通道1和2都輸出
          4 表示通道1和2都輸出,并且附加一個控制指令,有效防止傳輸錯誤時恢複,即使插拔子產品也不影響。
        最後一個參數:
          定時器觸發速度1MHz,觸發1次是一組24bit資料的傳輸。
          推薦範圍100Hz - 1MHz。
    */
    DAC8562_SetDacDataDMA(4, ch1buf, ch2buf, sizeof(ch1buf)/sizeof(uint16_t),
 sizeof(ch2buf)/sizeof(uint16_t), 1000000);
    
    while(1)
    {
        bsp_Idle();        /* 這個函數在bsp.c檔案。使用者可以修改這個函數實作CPU休眠和喂狗 */
        
        /* 判斷定時器逾時時間 */
        if (bsp_CheckTimer(0))    
        {
            /* 每隔100ms 進來一次 */  
            bsp_LedToggle(2);
        }
        
        /* 按鍵濾波和檢測由背景systick中斷服務程式實作,我們隻需要調用bsp_GetKey讀取鍵值即可。 */
        ucKeyCode = bsp_GetKey();    /* 讀取鍵值, 無鍵按下時傳回 KEY_NONE = 0 */
        if (ucKeyCode != KEY_NONE)
        {
            switch (ucKeyCode)
            {
                case KEY_DOWN_K1:            /* K1鍵按下,雙通道輸出,通道1輸出方波,通道2輸出正弦波 */
                    /* 生成方波資料 */
                    for(i =0; i< 50; i++)
                    {
                        ch1buf[i] = 0;
                    }
                    
                    for(i =50; i< 100; i++)
                    {
                        ch1buf[i] = 65535;
                    }

                    /* 生成正弦波資料 */    
                    MakeSinTable(ch2buf, 100, 0, 65535);
                    
                    /* 上電預設雙通道輸出,觸發速度1MHz */
                    DAC8562_SetDacDataDMA(3, ch1buf, ch2buf, sizeof(ch1buf)/sizeof(uint16_t),
 sizeof(ch2buf)/sizeof(uint16_t), 1000000);
                    break;

                case KEY_DOWN_K2:            /* K2鍵按下,雙通道輸出方波 */
                    /* 生成正弦波資料 */    
                    MakeSinTable(ch2buf, 100, 0, 65535);
                    
                    /* 上電預設雙通道輸出,觸發速度1MHz */
                    DAC8562_SetDacDataDMA(3, ch2buf, ch2buf, sizeof(ch2buf)/sizeof(uint16_t),
 sizeof(ch2buf)/sizeof(uint16_t), 1000000);
                    break;

                case KEY_DOWN_K3:            /* K3鍵按下,雙通道輸出正弦波 */
                    /* 生成方波資料 */
                    for(i =0; i< 50; i++)
                    {
                        ch1buf[i] = 0;
                    }
                    
                    for(i =50; i< 100; i++)
                    {
                        ch1buf[i] = 65535;
                    }
                    
                    /* 上電預設雙通道輸出,觸發速度1MHz */
                    DAC8562_SetDacDataDMA(3, ch1buf, ch1buf, sizeof(ch1buf)/sizeof(uint16_t),
 sizeof(ch1buf)/sizeof(uint16_t), 1000000);    
                    break;
                
                case JOY_DOWN_U:            /* 搖杆上鍵按下,通道1停止方波,通道2停止輸出 */
                    /* 通道1輸出方波 */
                    for(i =0; i< 50; i++)
                    {
                        ch1buf[i] = 0;
                    }
                    
                    for(i =50; i< 100; i++)
                    {
                        ch1buf[i] = 65535;
                    }
                    
                    /* 僅通道1輸出 */
                    DAC8562_SetDacDataDMA(1, ch1buf, ch2buf, sizeof(ch1buf)/sizeof(uint16_t),
 sizeof(ch2buf)/sizeof(uint16_t), 1000000);    
                    break;
                
                case JOY_DOWN_D:            /* 搖杆下鍵按下,雙通道輸出直流 */
                    /* 通道1輸出負數10V */
                    DAC8562_SetDacData(0, 0);    

                    /* 通道2輸出正10V */
                    DAC8562_SetDacData(1, 65535);        
                    break;
                
                case JOY_DOWN_OK:            /* 搖杆OK鍵按下,重新初始化  */
                    /* 初始化配置DAC8562/8563 */
                    //bsp_InitDAC8562();    
                    DAC8562_WriteCmd((7 << 19) | (0 << 16) | (1 << 0));
                    break;

                default:
                    /* 其它的鍵值不處理 */
                    break;
            }
        }
    }
}      

74.15          實驗例程說明(IAR)

【STM32H7教程】第74章 STM32H7的SPI總線應用之驅動DAC8563(雙通道,16bit分辨率,正負10V)
【STM32H7教程】第74章 STM32H7的SPI總線應用之驅動DAC8563(雙通道,16bit分辨率,正負10V)
【STM32H7教程】第74章 STM32H7的SPI總線應用之驅動DAC8563(雙通道,16bit分辨率,正負10V)
【STM32H7教程】第74章 STM32H7的SPI總線應用之驅動DAC8563(雙通道,16bit分辨率,正負10V)
【STM32H7教程】第74章 STM32H7的SPI總線應用之驅動DAC8563(雙通道,16bit分辨率,正負10V)
/*
*********************************************************************************************************
*    函 數 名: bsp_Init
*    功能說明: 初始化所有的硬體裝置。該函數配置CPU寄存器和外設的寄存器并初始化一些全局變量。隻需要調用一次
*    形    參:無
*    返 回 值: 無
*********************************************************************************************************
*/
void bsp_Init(void)
{
    /* 配置MPU */
    MPU_Config();
    
    /* 使能L1 Cache */
    CPU_CACHE_Enable();

    /* 
       STM32H7xx HAL 庫初始化,此時系統用的還是H7自帶的64MHz,HSI時鐘:
       - 調用函數HAL_InitTick,初始化滴答時鐘中斷1ms。
       - 設定NVIV優先級分組為4。
     */
    HAL_Init();

    /* 
       配置系統時鐘到400MHz
       - 切換使用HSE。
       - 此函數會更新全局變量SystemCoreClock,并重新配置HAL_InitTick。
    */
    SystemClock_Config();

    /* 
       Event Recorder:
       - 可用于代碼執行時間測量,MDK5.25及其以上版本才支援,IAR不支援。
       - 預設不開啟,如果要使能此選項,務必看V7開發闆使用者手冊第xx章
    */    
#if Enable_EventRecorder == 1  
    /* 初始化EventRecorder并開啟 */
    EventRecorderInitialize(EventRecordAll, 1U);
    EventRecorderStart();
#endif
    
bsp_InitDWT();      /* 初始化DWT時鐘周期計數器 */       
    bsp_InitKey();         /* 按鍵初始化,要放在滴答定時器之前,因為按鈕檢測是通過滴答定時器掃描 */
    bsp_InitTimer();       /* 初始化滴答定時器 */
    bsp_InitLPUart();     /* 初始化序列槽 */
    bsp_InitExtIO();     /* 初始化FMC總線74HC574擴充IO. 必須在 bsp_InitLed()前執行 */    
    bsp_InitLed();         /* 初始化LED */    
bsp_InitExtSDRAM(); /* 初始化SDRAM */

    /* 針對不同的應用程式,添加需要的底層驅動子產品初始化函數 */    
    bsp_InitDAC8562();    /* 初始化配置DAC8562/8563 */
}      
/*
*********************************************************************************************************
*    函 數 名: MPU_Config
*    功能說明: 配置MPU
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
static void MPU_Config( void )
{
    MPU_Region_InitTypeDef MPU_InitStruct;

    /* 禁止 MPU */
    HAL_MPU_Disable();

    /* 配置AXI SRAM的MPU屬性為Write back, Read allocate,Write allocate */
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0x24000000;
    MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
    MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    MPU_InitStruct.SubRegionDisable = 0x00;
    MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

    HAL_MPU_ConfigRegion(&MPU_InitStruct);
    
    
    /* 配置FMC擴充IO的MPU屬性為Device或者Strongly Ordered */
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0x60000000;
    MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;    
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;    
    MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER1;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
    MPU_InitStruct.SubRegionDisable = 0x00;
    MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    
    HAL_MPU_ConfigRegion(&MPU_InitStruct);

    /* 配置SRAM4的MPU屬性為Non-cacheable */
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0x38000000;
    MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
    MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    MPU_InitStruct.SubRegionDisable = 0x00;
    MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

    HAL_MPU_ConfigRegion(&MPU_InitStruct);

    /*使能 MPU */
    HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}

/*
*********************************************************************************************************
*    函 數 名: CPU_CACHE_Enable
*    功能說明: 使能L1 Cache
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
static void CPU_CACHE_Enable(void)
{
    /* 使能 I-Cache */
    SCB_EnableICache();

    /* 使能 D-Cache */
    SCB_EnableDCache();
}      
/*
*********************************************************************************************************
*    函 數 名: bsp_RunPer10ms
*    功能說明: 該函數每隔10ms被Systick中斷調用1次。詳見 bsp_timer.c的定時中斷服務程式。一些處理時間要求
*              不嚴格的任務可以放在此函數。比如:按鍵掃描、蜂鳴器鳴叫控制等。
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
void bsp_RunPer10ms(void)
{
    bsp_KeyScan10ms();
}      
/*
*********************************************************************************************************
*    函 數 名: main
*    功能說明: c程式入口
*    形    參: 無
*    返 回 值: 錯誤代碼(無需處理)
*********************************************************************************************************
*/
int main(void)
{
    bsp_Init();        /* 硬體初始化 */
    
    PrintfLogo();    /* 列印例程名稱和版本等資訊 */

    DemoSpiDac();   /* SPI DAC測試 */
}

/*
*********************************************************************************************************
*    函 數 名: DemoSpiDac
*    功能說明: DAC8562測試
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
void DemoSpiDac(void)
{
    uint8_t i=0;
    uint8_t ucKeyCode;    /* 按鍵代碼 */
    
    sfDispMenu();        /* 列印指令提示 */
    
    bsp_StartAutoTimer(0, 200);    /* 啟動1個100ms的自動重裝的定時器 */
    
    
    /* 生成方波資料 */
    for(i =0; i< 50; i++)
    {
        ch1buf[i] = 0;
    }
    
    for(i =50; i< 100; i++)
    {
        ch1buf[i] = 65535;
    }

    /* 生成正弦波資料 */    
    MakeSinTable(ch2buf, 100, 0, 65535);
    
    /* 
       上電預設雙通道輸出:
        第1個參數:
          1 表示通道1輸出
          2 表示通道2輸出
          3 表示通道1和2都輸出
          4 表示通道1和2都輸出,并且附加一個控制指令,有效防止傳輸錯誤時恢複,即使插拔子產品也不影響。
        最後一個參數:
          定時器觸發速度1MHz,觸發1次是一組24bit資料的傳輸。
          推薦範圍100Hz - 1MHz。
    */
    DAC8562_SetDacDataDMA(4, ch1buf, ch2buf, sizeof(ch1buf)/sizeof(uint16_t),
 sizeof(ch2buf)/sizeof(uint16_t), 1000000);
    
    while(1)
    {
        bsp_Idle();        /* 這個函數在bsp.c檔案。使用者可以修改這個函數實作CPU休眠和喂狗 */
        
        /* 判斷定時器逾時時間 */
        if (bsp_CheckTimer(0))    
        {
            /* 每隔100ms 進來一次 */  
            bsp_LedToggle(2);
        }
        
        /* 按鍵濾波和檢測由背景systick中斷服務程式實作,我們隻需要調用bsp_GetKey讀取鍵值即可。 */
        ucKeyCode = bsp_GetKey();    /* 讀取鍵值, 無鍵按下時傳回 KEY_NONE = 0 */
        if (ucKeyCode != KEY_NONE)
        {
            switch (ucKeyCode)
            {
                case KEY_DOWN_K1:            /* K1鍵按下,雙通道輸出,通道1輸出方波,通道2輸出正弦波 */
                    /* 生成方波資料 */
                    for(i =0; i< 50; i++)
                    {
                        ch1buf[i] = 0;
                    }
                    
                    for(i =50; i< 100; i++)
                    {
                        ch1buf[i] = 65535;
                    }

                    /* 生成正弦波資料 */    
                    MakeSinTable(ch2buf, 100, 0, 65535);
                    
                    /* 上電預設雙通道輸出,觸發速度1MHz */
                    DAC8562_SetDacDataDMA(3, ch1buf, ch2buf, sizeof(ch1buf)/sizeof(uint16_t),
 sizeof(ch2buf)/sizeof(uint16_t), 1000000);
                    break;

                case KEY_DOWN_K2:            /* K2鍵按下,雙通道輸出方波 */
                    /* 生成正弦波資料 */    
                    MakeSinTable(ch2buf, 100, 0, 65535);
                    
                    /* 上電預設雙通道輸出,觸發速度1MHz */
                    DAC8562_SetDacDataDMA(3, ch2buf, ch2buf, sizeof(ch2buf)/sizeof(uint16_t),
 sizeof(ch2buf)/sizeof(uint16_t), 1000000);
                    break;

                case KEY_DOWN_K3:            /* K3鍵按下,雙通道輸出正弦波 */
                    /* 生成方波資料 */
                    for(i =0; i< 50; i++)
                    {
                        ch1buf[i] = 0;
                    }
                    
                    for(i =50; i< 100; i++)
                    {
                        ch1buf[i] = 65535;
                    }
                    
                    /* 上電預設雙通道輸出,觸發速度1MHz */
                    DAC8562_SetDacDataDMA(3, ch1buf, ch1buf, sizeof(ch1buf)/sizeof(uint16_t),
 sizeof(ch1buf)/sizeof(uint16_t), 1000000);    
                    break;
                
                case JOY_DOWN_U:            /* 搖杆上鍵按下,通道1停止方波,通道2停止輸出 */
                    /* 通道1輸出方波 */
                    for(i =0; i< 50; i++)
                    {
                        ch1buf[i] = 0;
                    }
                    
                    for(i =50; i< 100; i++)
                    {
                        ch1buf[i] = 65535;
                    }
                    
                    /* 僅通道1輸出 */
                    DAC8562_SetDacDataDMA(1, ch1buf, ch2buf, sizeof(ch1buf)/sizeof(uint16_t),
 sizeof(ch2buf)/sizeof(uint16_t), 1000000);    
                    break;
                
                case JOY_DOWN_D:            /* 搖杆下鍵按下,雙通道輸出直流 */
                    /* 通道1輸出負數10V */
                    DAC8562_SetDacData(0, 0);    

                    /* 通道2輸出正10V */
                    DAC8562_SetDacData(1, 65535);        
                    break;
                
                case JOY_DOWN_OK:            /* 搖杆OK鍵按下,重新初始化  */
                    /* 初始化配置DAC8562/8563 */
                    //bsp_InitDAC8562();    
                    DAC8562_WriteCmd((7 << 19) | (0 << 16) | (1 << 0));
                    break;

                default:
                    /* 其它的鍵值不處理 */
                    break;
            }
        }
    }
}      

74.16   總結

本章節涉及到的知識點非常多,特别是SPI DMA方式驅動的實作方法,需要大家稍花點精力去研究。

微信公衆号:armfly_com

安富萊論壇:www.armbbs.cn

安富萊淘寶:https://armfly.taobao.com