【STM32H7教程】第75章 STM32H7的SPI總線應用之驅動DAC8501(雙路輸出,16bit分辨率,0-5V)

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

第75章       STM32H7的SPI總線應用之驅動DAC8501(雙路輸出,16bit分辨率,0-5V)

本章節爲大家講解標準SPI接線方式驅動模數轉換器DAC8501,製作了中斷和DMA兩種驅動方式。

目錄

第75章       STM32H7的SPI總線應用之驅動DAC8501(雙路輸出,16bit分辨率,0-5V)

75.1 初學者重要提示

75.2 DAC結構分類和技術術語

75.3 DAC8501硬件設計

75.3.1 DAC8501模塊規格

75.3.2 DAC8501硬件接口

75.4 DAC8501關鍵知識點整理(重要)

75.4.1 DAC8501基礎信息

75.4.2 DAC8501每個引腳的作用

75.4.3 DAC8501輸出電壓計算公式

75.4.4 DAC8501時序圖

75.4.5 DAC8501寄存器配置

75.5 DAC8501驅動設計(中斷更新方式)

75.5.1 第1步:SPI總線配置

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

75.5.3 第3步:DAC8501的時鐘極性和時鐘相位配置

75.5.4 第4步:單SPI接口管理多個SPI設備的切換機制

75.5.5 第5步:DAC8501的數據更新

75.6 DAC8501驅動設計(SPI DMA更新方式)

75.6.1 第1步:SPI總線配置

75.6.2 第2步:TIM12週期性觸發配置

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

75.6.4 第4步:24bit數據的DMA傳輸解決辦法

75.6.5 第5步:DMA緩衝區的MPU配置

75.6.6 第6步:DAC8501的時鐘極性和時鐘相位配置

75.6.7 第7步:DAC8501的最高更新速度計算

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

75.7 SPI總線板級支持包(bsp_spi_bus.c)

75.7.1 函數bsp_InitSPIBus

75.7.2 函數bsp_InitSPIParam

75.7.3 函數bsp_spiTransfer

75.8 DAC8501支持包中斷方式(bsp_spi_dac8501.c)

75.8.1 函數bsp_InitDAC8501

75.8.2 函數DAC8501_SetCS1

75.8.3 函數DAC8501_SetCS2

75.8.4 函數DAC8501_SetDacData

75.8.5 函數DAC8501_DacToVoltage

75.8.6 函數DAC8501_VoltageToDac

75.9 DAC8501支持包DMA方式(bsp_spidma_dac8501.c)

75.9.1 函數bsp_InitDAC8501

75.9.2 函數DAC8501_SetDacDataDMA

75.9.3 函數DAC8501_SetDacData

75.10          DAC8501驅動移植和使用(中斷更新方式)

75.11          DAC8501驅動移植和使用(SPI DMA更新方式)

75.12          實驗例程設計框架

75.13          實驗例程說明(MDK)

75.14          實驗例程說明(IAR)

75.15   總結


 

75.1 初學者重要提示

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

2、  DAC8501模塊上帶了兩片8501,每片是單通道DAC,帶片上輸出緩衝運放,軌到軌輸出,16bit分辨率,支持30MHz的SPI時鐘速度。

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

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

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

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

6、  對於本章教程配套例子的SPI DMA方式,這裏特別注意一點,定時器觸發一次,就會讓SPI以DMA方式傳輸24bit數據。

7、  DAC8501數據手冊,模塊原理圖和接線圖都已經放到本章教程配置例子的Doc文件裏。

75.2 DAC結構分類和技術術語

在本教程的第74章進行了詳細說明。

75.3 DAC8501硬件設計

DAC的原理圖下載:

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

75.3.1 DAC8501模塊規格

產品規格:

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

2、通道數: 2路  (通過2片DAC8501E實現)。

3、輸出電壓範圍 : 0 - 5V【零位 < 0.020V, 滿位 > 4.970V】。

4、分辨率: 16位。

5、功耗 :  小於10mA。

6、MCU接口 :高速 SPI (30M) 支持 3.3V和5V單片機。

7、DAC輸出模擬帶寬:350KHz。

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

產品特點:

1、輸出和供電電壓無關;模塊內帶升壓電路和5V基準。

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

3、輸出電壓軌到軌,最高電壓可以到 4.970V 以上。

產品效果:

 

75.3.2 DAC8501硬件接口

V7板子上DAC8501模塊的插座的原理圖如下:

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

75.4 DAC8501關鍵知識點整理(重要)

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

75.4.1 DAC8501基礎信息

  •   單通道DAC,帶片上輸出緩衝運放,軌到軌輸出,16bit分辨率,支持30MHz的SPI時鐘速度。
  •   模擬輸出帶寬350KHz。
  •   供電範圍2.7V到5.5V。
  •   具有低功耗特性。
  •   上電覆位輸出0V。

75.4.2 DAC8501每個引腳的作用

DAC8501的封裝形式:

  •   Vdd

供電範圍2.7-5.5V。

  •   Vref

穩壓基準輸入。

  •   Vfb

輸出運放的反饋。

  •   Vout

模擬輸出電壓,輸出運放具有軌到軌特性。

  •   SYNC (片選)

低電平有效,當SYNC變爲低電平時,它使能輸入移位寄存器,並且數據採樣在隨後的時鐘下降沿。 DAC輸出在第24個時鐘下降沿之後更新。 如果SYNC在第23個時鐘沿之前變高,SYNC的上升沿將充當中斷,並且DAC8501將忽略寫序列。

  •   SCLK

時鐘輸入端,支持30MHz。

  •   Din

串行時鐘輸入,每個時鐘下降沿將數據寫到的24bit的輸入移位寄存器。

  •   GND

接地端。

75.4.3 DAC8501輸出電壓計算公式

DAC8501的計算公式如下:

  •   D

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

  •   VREF

使用外部參考電壓,由VREFIN引腳的輸入決定。

  •   Vout

輸出電壓。

75.4.4 DAC8501時序圖

DAC8501的時序圖如下:

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

  •   f(1)

供電2.7到3.6V時,最高時鐘20MHz。

供電3.6到5.5V時,最高時鐘30MHz。

  •   t(4)

SYNC低電平有效到SCLK第1個上降沿信號的時間沒有最小值限制,可以爲0。

  •   t(8)

每傳輸24bit數據後,SYNC要保持一段時間的高電平。

供電2.7到3.6V時,最小要求50ns。

供電3.6到5.5V時,最小要求33ns。

75.4.5 DAC8501寄存器配置

DAC8501的寄存器配置是24bit格式:

 

控制DAC8501每次要傳輸24bit數據,高8bit控制位 + 16bit數據位。

控制位的PD1和PD0定義如下:

PD1 PD0 決定4種工作模式

0   0  ---> 正常工作模式

0   1  ---> 輸出接1K歐到GND

1   0  ---> 輸出100K歐到GND

1   1  ---> 輸出高阻

75.5 DAC8501驅動設計(中斷更新方式)

DAC8501的程序驅動框架設計如下:

 

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

75.5.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接口上接了多個不同類型的芯片時,通過此函數可以方便的切換配置。

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

注:對於DAC8501,請使用查詢方式。

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);

 

75.5.3 第3步:DAC8501的時鐘極性和時鐘相位配置

首先回憶下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個數據。

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

首先DAC8501是下降升沿做數據採集,所以STM32H7的可選的配置就是:

CHOL = 0,  CPHA = 1

CHOL = 1,  CPHA = 0

對於這兩種情況的主要區別是空閒狀態下SCLK時鐘選擇高電平還是低電平,根據上面的時序圖和DAC8501的數據手冊,兩種情況下都可以正常運行。經過實際測試,STM32H7使用這兩個配置確實都可以正常運行。程序裏面默認是選擇CHOL = 0,  CPHA = 1。

75.5.4 第4步:單SPI接口管理多個SPI設備的切換機制

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

/*
*********************************************************************************************************
*    函 數 名: DAC8501_SetCS1
*    功能說明: DAC8501 片選控制函數
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
void DAC8501_SetCS1(uint8_t _Level)
{
    if (_Level == 0)
    {
        bsp_SpiBusEnter();    /* 佔用SPI總線  */    
        bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_8, SPI_PHASE_2EDGE, SPI_POLARITY_LOW);        
        CS1_0();
    }
    else
    {        
        CS1_1();    
        bsp_SpiBusExit();    /* 釋放SPI總線 */
    }    
}

/*
*********************************************************************************************************
*    函 數 名: DAC8501_SetCS2(0)
*    功能說明: 設置CS2。 用於運行中SPI共享。
*    形    參: 無
    返 回 值: 無
*********************************************************************************************************
*/
void DAC8501_SetCS2(uint8_t _level)
{
    if (_level == 0)
    {
        bsp_SpiBusEnter();    /* 佔用SPI總線  */
        bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_8, SPI_PHASE_2EDGE, SPI_POLARITY_LOW);        
        CS2_0();
    }
    else
    {
        CS2_1();
        bsp_SpiBusExit();    /* 釋放SPI總線 */
    }
}

 

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

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

75.5.5 第5步:DAC8501的數據更新

DAC8501的雙通道數據更新通過下面的函數實現:

/*
*********************************************************************************************************
*    函 數 名: DAC8501_SetDacData
*    功能說明: 設置DAC數據
*    形    參: _ch, 通道,
*             _data : 數據
*    返 回 值: 無
*********************************************************************************************************
*/
void DAC8501_SetDacData(uint8_t _ch, uint16_t _dac)
{
    uint32_t data;

    /*
        DAC8501.pdf page 12 有24bit定義

        DB24:18 = xxxxx 保留
        DB17: PD1
        DB16: PD0

        DB15:0  16位數據

        其中 PD1 PD0 決定4種工作模式
              0   0  ---> 正常工作模式
              0   1  ---> 輸出接1K歐到GND
              1   0  ---> 輸出100K歐到GND
              1   1  ---> 輸出高阻
    */

    data = _dac; /* PD1 PD0 = 00 正常模式 */

    if (_ch == 0)
    {
        DAC8501_SetCS1(0);
    }
    else
    {
        DAC8501_SetCS2(0);
    }

    /* DAC8501 SCLK時鐘高達30M,因此可以不延遲 */
    g_spiLen = 0;
    g_spiTxBuf[g_spiLen++] = (data >> 16);
    g_spiTxBuf[g_spiLen++] = (data >> 8);
    g_spiTxBuf[g_spiLen++] = (data);
    bsp_spiTransfer();    

    if (_ch == 0)
    {
        DAC8501_SetCS1(1);
    }
    else
    {
        DAC8501_SetCS2(1);
    }
}

 

函數實現比較簡單,每次更新發送24bit數據即可。

75.6 DAC8501驅動設計(SPI DMA更新方式)

DAC8501的DMA驅動方式略複雜,跟中斷更新方式完全不同,要使用硬件SPI1 NSS引腳驅動DAC8501的片選,所有專門做了一個驅動文件來實現,程序驅動框架設計如下:

 

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

75.6.1 第1步:SPI總線配置

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

/*
*********************************************************************************************************
*    函 數 名: bsp_InitDAC8501
*    功能說明: 配置GPIO並初始化DAC8501寄存器
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
void bsp_InitDAC8501(void)
{
    s_SpiDmaMode = 0;  
    
    /*##-1- 配置SPI DMA ############################################################*/
    bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_8, SPI_PHASE_2EDGE, SPI_POLARITY_LOW);
    
    /*##-2- 默認輸出0V ############################################################*/
    DAC8501_SetDacData(0, 0);    /* CH1輸出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_02CYCLE; 
    
    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時鐘週期個數。

根據本章75.4.4小節裏面的t(4)要求,片選有效到SCLK第1個下降沿信號的時間,最小值爲0。所以這裏配置爲0即可,也就是無需插入時間。

  •   SPI_MASTER_INTERDATA_IDLENESS_10CYCLE

兩個連續數據幀之間插入的最小時間延遲,單位SPI時鐘週期個數。

根據本章75.4.4小節裏面的t(5)要求,每傳輸24bit數據後,片選要保持一段時間的高電平,DAC856X要求至少要33ns(供電3.6到5.5V時),也是說,如果我們以25MHz驅動DAC856X,這裏至少要配置爲1個時鐘週期,推薦值爲2及其以上即可,我們這裏直接配置爲2個時鐘週期(配置爲1也沒問題的)。

75.6.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小節即可。

75.6.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控制。

75.6.4 第4步:24bit數據的DMA傳輸解決辦法

由於通用DMA1和DMA2僅支持8bit,16bit和32bit數據傳輸,我們這裏要傳輸24bit數據,解決的關鍵就是配置DMA爲傳輸寬度爲32bit,並將傳輸的數據由24bit再補一個8bit的任意值組成32bit即可,實際的傳輸會由SPI完成。

/*
*********************************************************************************************************
*    函 數 名: DAC8501_SetDacDataDMA
*    功能說明: DAC8501數據發送,DMA方式
*    形    參: _ch         1表示通道1輸出
*             _pbufch1    通道1數據緩衝地址
*             _sizech1    通道1數據大小
*             _ulFreq     觸發頻率,推薦範圍100Hz- 1MHz,注意這個參數是觸發頻率,並不是波形週期。
*                         這裏觸發一次,SPI DMA傳輸一次24bit數據。
*    返 回 值: 無
*********************************************************************************************************
*/
void DAC8501_SetDacDataDMA(uint8_t _ch, uint16_t *_pbufch1, uint32_t _sizech1, uint32_t _ulFreq)
{
    uint32_t i;
    uint32_t _cmd;
    
    g_spiLen = 0;
    
    switch (_ch)
    {
        
        /*
            DAC8501.pdf page 12 有24bit定義

            DB24:18 = xxxxx 保留
            DB17: PD1
            DB16: PD0

            DB15:0  16位數據

            其中 PD1 PD0 決定4種工作模式
                  0   0  ---> 正常工作模式
                  0   1  ---> 輸出接1K歐到GND
                  1   0  ---> 輸出100K歐到GND
                  1   1  ---> 輸出高阻
        */
        
        /* 通道1數據發送 */
        case 1:
            for(i = 0; i < _sizech1; i++)
            {
                /* 更新需要配置PD1和PD0,當前是選擇的正常工作模式 */
                _cmd = (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++] = 0;
            }
            break;
        
        default:
            break;

    }
    
    bsp_spiDamStart(_ulFreq);
}

 

75.6.5 第5步:DMA緩衝區的MPU配置

因爲工程是用的DTCM做的主RAM空間,這個空間無法使用通用DMA1和DMA2,通過本手冊第26章的內存塊超方便使用方式,將DMA緩衝定義到SRAM4上:

#if defined ( __CC_ARM )    /* MDK *******/
    __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__)  /* IAR ********/
    #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);

 

由於程序裏面開啓了數據Cache,會造成DMA和CPU訪問SRAM4數據不一致的問題,特此將SRAM4空間關閉Cache。

 

75.6.6 第6步:DAC8501的時鐘極性和時鐘相位配置

注:與本章74.5.3小節內容是一樣的。

首先回憶下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個數據。

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

首先DAC8501是下降升沿做數據採集,所以STM32H7的可選的配置就是:

CHOL = 0,  CPHA = 1

CHOL = 1,  CPHA = 0

對於這兩種情況的主要區別是空閒狀態下SCLK時鐘選擇高電平還是低電平,根據上面的時序圖和DAC8501的數據手冊,兩種情況下都可以正常運行。經過實際測試,STM32H7使用這兩個配置確實都可以正常運行。程序裏面默認是選擇CHOL = 0,  CPHA = 1。

75.6.7 第7步:DAC8501的最高更新速度計算

這裏特別注意一點,定時器觸發一次,就會讓SPI以DMA方式傳輸24bit數據。

配置條件:

  •   SPI時鐘是25MHz,SPI數據傳爲24bit,每個bit需要時間40ns。
  •   hspi.Init.MasterSSIdleness = SPI_MASTER_SS_IDLENESS_00CYCLE

插入到NSS有效邊沿和第一個數據開始之間的額外延遲,單位SPI時鐘週期個數,即40ns。

  •   hspi.Init.MasterInterDataIdleness = SPI_MASTER_INTERDATA_IDLENESS_10CYCLE

兩個連續數據幀之間插入的最小時間延遲,單位SPI時鐘週期個數,即40ns。

 

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

24bit * 20ns+ SPI_MASTER_SS_IDLENESS_00CYCLE * 20ns

+ SPI_MASTER_INTERDATA_IDLENESS_02CYCLE * 20ns

= 24bit * 40ns + 0 * 40ns + 2 * 40ns

= 1040ns。

 

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

 

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

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

DAC8501模塊的輸出電壓範圍是0V到5V,對應的編碼值範圍是0到65535,爲了方便大家做互轉,專門做了兩個函數:

/*
*********************************************************************************************************
*    函 數 名: DAC8501_DacToVoltage
*    功能說明: 將DAC值換算爲電壓值,單位0.1mV
*    形    參: _dac  16位DAC字
*    返 回 值: 電壓,單位0.1mV
*********************************************************************************************************
*/
int32_t DAC8501_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);
    if (y < 0)
    {
        y = 0;
    }
    return y;
}

/*
*********************************************************************************************************
*    函 數 名: DAC8501_DacToVoltage
*    功能說明: 將DAC值換算爲電壓值,單位 0.1mV
*    形    參: _volt 電壓,單位0.1mV
*    返 回 值: 16位DAC字
*********************************************************************************************************
*/
uint32_t DAC8501_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);
}

 

75.7 SPI總線板級支持包(bsp_spi_bus.c)

SPI總線驅動文件bsp_spi_bus.c主要實現瞭如下幾個API供用戶調用:

  •   bsp_InitSPIBus
  •   bsp_InitSPIParam
  •   bsp_spiTransfer

75.7.1 函數bsp_InitSPIBus

函數原型:

void bsp_InitSPIBus(void)

函數描述:

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

75.7.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引腳在空閒狀態處於高電平

75.7.3 函數bsp_spiTransfer

函數原型:

void bsp_spiTransfer(void)

函數描述:

此函數用於啓動SPI數據傳輸,支持查詢,中斷和DMA方式傳輸。

75.8 DAC8501支持包中斷方式(bsp_spi_dac8501.c)

DAC8501驅動文件bsp_spi_dac8501.c主要實現瞭如下幾個API供用戶調用:

  •   bsp_InitDAC8501
  •   DAC8501_SetCS1
  •   DAC8501_SetCS2
  •   DAC8501_SetDacData
  •   DAC8501_DacToVoltage
  •   DAC8501_VoltageToDac

75.8.1 函數bsp_InitDAC8501

函數原型:

void bsp_InitDAC8501(void)

函數描述:

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

75.8.2 函數DAC8501_SetCS1

函數原型:

void DAC8501_SetCS1(uint8_t _Level)

函數描述:

此函數用於片選DAC8501模塊上的第1片8501。

函數參數:

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

75.8.3 函數DAC8501_SetCS2

函數原型:

void DAC8501_SetCS2(uint8_t _Level)

函數描述:

此函數用於片選DAC8501模塊上的第2片8501。

函數參數:

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

75.8.4 函數DAC8501_SetDacData

函數原型:

void DAC8501_SetDacData(uint8_t _ch, uint16_t _dac)

函數描述:

此函數用於設置DAC輸出,並立即更新。

函數參數:

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

75.8.5 函數DAC8501_DacToVoltage

函數原型:

int32_t DAC8501_DacToVoltage(uint16_t _dac)

函數描述:

此函數用於將DAC值換算爲電壓值,單位0.1mV。

函數參數:

  •   第1個參數DAC數值,範圍0到65535。
  •   返回值,返回電壓值,單位0.1mV。

75.8.6 函數DAC8501_VoltageToDac

函數原型:

uint32_t DAC8501_VoltageToDac(int32_t _volt)

函數描述:

此函數用於將電壓值轉換爲DAC值。

函數參數:

  •   第1個參數是電壓值,範圍0到50000,單位0.1mV。
  •   返回值,返回DAC值。

75.9 DAC8501支持包DMA方式(bsp_spidma_dac8501.c)

DAC8501驅動文件bsp_spidma_dac8501.c涉及到的函數比較多,我們主要介紹用到的如下幾個函數:

  •   bsp_InitDAC8501
  •   DAC8501_SetDacDataDMA
  •   DAC8501_SetDacData

75.9.1 函數bsp_InitDAC8501

函數原型:

void bsp_InitDAC8501(void)

函數描述:

主要用於DAC8501的初始化。

75.9.2 函數DAC8501_SetDacDataDMA

函數原型:

void DAC8501_SetDacDataDMA(uint8_t _ch, uint16_t *_pbufch1, uint32_t _sizech1, uint32_t _ulFreq)

函數描述:

此函數用於SPI DMA方式數據發送。

函數參數:

  •   第1個參數用於選擇的通道: 1表示通道1輸出
  •   第2個參數表示通道1數據緩衝地址。
  •   第3個參數表示通道1數據大小。
  •   第4個參數表示觸發頻率,推薦範圍100Hz- 1MHz,注意這個參數是觸發頻率,並不是波形週期。這裏觸發一次,SPI DMA傳輸一次24bit數據。

75.9.3 函數DAC8501_SetDacData

函數原型:

void DAC8501_SetDacData(uint8_t _ch, uint16_t _dac)

函數描述:

此函數用於設置DAC輸出,並立即更新。

函數參數:

  •   第1個參數爲0表示通道1,爲1表示通道2(對於SPI DMA方式,僅支持通道1)。
  •   第2個參數是DAC數值設置,範圍0到65535,0對應最小電壓值,65535對應最大電壓值。

75.10          DAC8501驅動移植和使用(中斷更新方式)

DAC8501移植步驟如下:

  •   第1步:複製bsp_spi_bus.c,bsp_spi_bus.h,bsp_spi_dac8501.c,bsp_spi_dac8501.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步:根據芯片支持的時鐘速度,時鐘相位和時鐘極性配置函數DAC8501_SetCS1和DAC8501_SetCS2。
DAC8501_SetCS1和DAC8501_SetCS2。
/*
*********************************************************************************************************
*    函 數 名: DAC8501_SetCS1
*    功能說明: DAC8501 片選控制函數
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
void DAC8501_SetCS1(uint8_t _Level)
{
    if (_Level == 0)
    {
        bsp_SpiBusEnter();    /* 佔用SPI總線  */    
        bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_8, SPI_PHASE_2EDGE, SPI_POLARITY_LOW);        
        CS1_0();
    }
    else
    {        
        CS1_1();    
        bsp_SpiBusExit();    /* 釋放SPI總線 */
    }    
}

/*
*********************************************************************************************************
*    函 數 名: DAC8501_SetCS2(0)
*    功能說明: 設置CS2。 用於運行中SPI共享。
*    形    參: 無
    返 回 值: 無
*********************************************************************************************************
*/
void DAC8501_SetCS2(uint8_t _level)
{
    if (_level == 0)
    {
        bsp_SpiBusEnter();    /* 佔用SPI總線  */
        bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_8, SPI_PHASE_2EDGE, SPI_POLARITY_LOW);        
        CS2_0();
    }
    else
    {
        CS2_1();
        bsp_SpiBusExit();    /* 釋放SPI總線 */
    }
}

 

  •   第4步:根據使用的片選引腳,修改bsp_spi_dac8562.c文件開頭的宏定義。
#define CS1_CLK_ENABLE()     __HAL_RCC_GPIOG_CLK_ENABLE()
#define CS1_GPIO            GPIOG
#define CS1_PIN            GPIO_PIN_10

#define CS1_1()            CS1_GPIO->BSRR = CS1_PIN
#define CS1_0()            CS1_GPIO->BSRR = ((uint32_t)CS1_PIN << 16U)

/*特別注意,我們這裏是用的擴展IO控制的 */    
#define CS2_1()            HC574_SetPin(NRF24L01_CE, 1);
#define CS2_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_InitDAC8501(); /* 初始化配置DAC8501 */

 

  •   第7步:DAC8501驅動主要用到HAL庫的SPI驅動文件,簡單省事些可以添加所有HAL庫C源文件進來。
  •   第8步:應用方法看本章節配套例子即可。

75.11          DAC8501驅動移植和使用(SPI DMA更新方式)

DAC8501移植步驟如下:

  •   第1步:複製bsp_spidma_dac8501.c,bsp_spidma_dac8501.h到自己的工程目錄,並添加到工程裏面。
  •   第2步:根據使用的第幾個SPI,SPI時鐘,SPI引腳和DMA通道等,修改bsp_spidma_dac8501.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步:如果使用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);

 

  •   第4步:初始化SPI。
/* 針對不同的應用程序,添加需要的底層驅動模塊初始化函數 */
bsp_InitDAC8501();    /* 初始化配置DAC8501 */

 

  •   第5步:DAC8501驅動主要用到HAL庫的SPI驅動文件,簡單省事些可以添加所有HAL庫C源文件進來。
  •   第6步:應用方法看本章節配套例子即可

75.12          實驗例程設計框架

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

 

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

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

  第2階段,進入main函數:

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

75.13          實驗例程說明(MDK)

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

配套例子:

V7-054_DAC8501簡易信號發生器(單通道SPI DMA方式,16bit分辨率, 0-5V輸出)

V7-055_DAC8501簡易信號發生器(雙通道SPI查詢方式,16bit分辨率, 0-5V輸出)

實驗目的:

  1. 學習DAC8501的SPI DMA驅動方式實現。

實驗內容:

  1. DAC8501模塊上帶了兩片8501,每片是單通道DAC,片上輸出緩衝運放,軌到軌輸出,16bit分辨率,支持30MHz的SPI時鐘速度。
  2. DAC8501本身僅支持一路輸出,而模塊上是帶了兩片DAC8501,其中只有一路的片選可以支持SPI NSS複用, 所以只有一路支持SPI DMA。
  3. DAC8501供電電壓2.7-5.5V,模擬輸出帶寬350KHz。

實驗操作:

  1. 啓動一個自動重裝軟件定時器,每100ms翻轉一次LED2。
  2. K1鍵按下,通道1輸出方波。
  3. K2鍵按下,通道1輸出正弦波。
  4. K3鍵按下,通道1輸出直流。

上電後串口打印的信息:

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

波形效果:

模塊插入位置:

程序設計:

  系統棧大小分配:

  RAM空間用的DTCM:

  硬件外設初始化

硬件外設的初始化是在 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_InitDAC8501();  /* 初始化配置DAC8501 */
}

 

  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輸出方波。
  •   K2鍵按下,通道1輸出正弦波。
  •   K3鍵按下,通道1輸出直流。
/*
*********************************************************************************************************
*    函 數 名: main
*    功能說明: c程序入口
*    形    參: 無
*    返 回 值: 錯誤代碼(無需處理)
*********************************************************************************************************
*/
int main(void)
{
    bsp_Init();        /* 硬件初始化 */
    
    PrintfLogo();    /* 打印例程名稱和版本等信息 */

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

/*
*********************************************************************************************************
*    函 數 名: DemoSpiDac
*    功能說明: DAC8501測試
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
void DemoSpiDac(void)
{
    uint8_t i=0;
    uint8_t ucKeyCode;    /* 按鍵代碼 */
    
    sfDispMenu();        /* 打印命令提示 */
    
    bsp_StartAutoTimer(0, 200);    /* 啓動1個100ms的自動重裝的定時器 */
    
    
    /* 生成方波數據 */
    MakeSinTable(ch1buf, 100, 0, 65535);
    
    DAC8501_SetDacDataDMA(1, ch1buf, sizeof(ch1buf)/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輸出方波 */
                    /* 生成方波數據 */
                    for(i =0; i< 50; i++)
                    {
                        ch1buf[i] = 0;
                    }
                    
                    for(i =50; i< 100; i++)
                    {
                        ch1buf[i] = 65535;
                    }

                    /* 觸發速度1MHz */
                    DAC8501_SetDacDataDMA(1, ch1buf, sizeof(ch1buf)/sizeof(uint16_t), 1000000);
                    break;

                case KEY_DOWN_K2:            /* K2鍵按下,通道1輸出正弦波 */
                    /* 生成正弦波數據 */    
                    MakeSinTable(ch1buf, 100, 0, 65535);
                    
                    /* 觸發速度1MHz */
                    DAC8501_SetDacDataDMA(1, ch1buf, sizeof(ch1buf)/sizeof(uint16_t), 1000000);
                    break;

                case KEY_DOWN_K3:            /* K3鍵按下,通道1輸出直流 */
                    
                    /* 生成方波數據 */
                    for(i =0; i< 100; i++)
                    {
                        ch1buf[i] = 65535;
                    }

                    /* 觸發速度1MHz */
                    DAC8501_SetDacDataDMA(1, ch1buf, sizeof(ch1buf)/sizeof(uint16_t), 1000);
                    break;

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

 

75.14          實驗例程說明(IAR)

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

配套例子:

V7-054_DAC8501簡易信號發生器(單通道SPI DMA方式,16bit分辨率, 0-5V輸出)

V7-055_DAC8501簡易信號發生器(雙通道SPI查詢方式,16bit分辨率, 0-5V輸出)

實驗目的:

  1. 學習DAC8501的SPI DMA驅動方式實現。

實驗內容:

  1. DAC8501模塊上帶了兩片8501,每片是單通道DAC,片上輸出緩衝運放,軌到軌輸出,16bit分辨率,支持30MHz的SPI時鐘速度。
  2. DAC8501本身僅支持一路輸出,而模塊上是帶了兩片DAC8501,其中只有一路的片選可以支持SPI NSS複用, 所以只有一路支持SPI DMA。
  3. DAC8501供電電壓2.7-5.5V,模擬輸出帶寬350KHz。

實驗操作:

  1. 啓動一個自動重裝軟件定時器,每100ms翻轉一次LED2。
  2. K1鍵按下,通道1輸出方波。
  3. K2鍵按下,通道1輸出正弦波。
  4. K3鍵按下,通道1輸出直流。

上電後串口打印的信息:

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

波形效果:

模塊插入位置:

程序設計:

  系統棧大小分配:

  RAM空間用的DTCM:

  硬件外設初始化

硬件外設的初始化是在 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輸出方波。
  •   K2鍵按下,通道1輸出正弦波。
  •   K3鍵按下,通道1輸出直流。
/*
*********************************************************************************************************
*    函 數 名: main
*    功能說明: c程序入口
*    形    參: 無
*    返 回 值: 錯誤代碼(無需處理)
*********************************************************************************************************
*/
int main(void)
{
    bsp_Init();        /* 硬件初始化 */
    
    PrintfLogo();    /* 打印例程名稱和版本等信息 */

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

/*
*********************************************************************************************************
*    函 數 名: DemoSpiDac
*    功能說明: DAC8501測試
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
void DemoSpiDac(void)
{
    uint8_t i=0;
    uint8_t ucKeyCode;    /* 按鍵代碼 */
    
    sfDispMenu();        /* 打印命令提示 */
    
    bsp_StartAutoTimer(0, 200);    /* 啓動1個100ms的自動重裝的定時器 */
    
    
    /* 生成方波數據 */
    MakeSinTable(ch1buf, 100, 0, 65535);
    
    DAC8501_SetDacDataDMA(1, ch1buf, sizeof(ch1buf)/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輸出方波 */
                    /* 生成方波數據 */
                    for(i =0; i< 50; i++)
                    {
                        ch1buf[i] = 0;
                    }
                    
                    for(i =50; i< 100; i++)
                    {
                        ch1buf[i] = 65535;
                    }

                    /* 觸發速度1MHz */
                    DAC8501_SetDacDataDMA(1, ch1buf, sizeof(ch1buf)/sizeof(uint16_t), 1000000);
                    break;

                case KEY_DOWN_K2:            /* K2鍵按下,通道1輸出正弦波 */
                    /* 生成正弦波數據 */    
                    MakeSinTable(ch1buf, 100, 0, 65535);
                    
                    /* 觸發速度1MHz */
                    DAC8501_SetDacDataDMA(1, ch1buf, sizeof(ch1buf)/sizeof(uint16_t), 1000000);
                    break;

                case KEY_DOWN_K3:            /* K3鍵按下,通道1輸出直流 */
                    
                    /* 生成方波數據 */
                    for(i =0; i< 100; i++)
                    {
                        ch1buf[i] = 65535;
                    }

                    /* 觸發速度1MHz */
                    DAC8501_SetDacDataDMA(1, ch1buf, sizeof(ch1buf)/sizeof(uint16_t), 1000);
                    break;

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

 

75.15   總結

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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章