完整教程下載地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980
第75章 STM32H7的SPI總線應用之驅動DAC8501(雙路輸出,16bit分辨率,0-5V)
本章節為大家講解標准SPI接線方式驅動模數轉換器DAC8501,制作了中斷和DMA兩種驅動方式。
75.1 初學者重要提示
75.2 DAC結構分類和技術術語
75.3 DAC8501硬件設計
75.4 DAC8501關鍵知識點整理(重要)
75.5 DAC8501驅動設計(中斷更新方式)
75.5 DAC8501驅動設計(SPI DMA更新方式)
75.6 SPI總線板級支持包(bsp_spi_bus.c)
75.7 DAC8501支持包中斷方式(bsp_spi_dac8501.c)
75.8 DAC8501支持包DMA方式(bsp_spidma_dac8501.c)
75.9 DAC8501驅動移植和使用(中斷更新方式)
75.10 DAC8501驅動移植和使用(SPI DMA更新方式)
75.11 實驗例程設計框架
75.12 實驗例程說明(MDK)
75.13 實驗例程說明(IAR)
75.14 總結
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輸出)
實驗目的:
- 學習DAC8501的SPI DMA驅動方式實現。
實驗內容:
- DAC8501模塊上帶了兩片8501,每片是單通道DAC,片上輸出緩沖運放,軌到軌輸出,16bit分辨率,支持30MHz的SPI時鍾速度。
- DAC8501本身僅支持一路輸出,而模塊上是帶了兩片DAC8501,其中只有一路的片選可以支持SPI NSS復用, 所以只有一路支持SPI DMA。
- DAC8501供電電壓2.7-5.5V,模擬輸出帶寬350KHz。
實驗操作:
- 啟動一個自動重裝軟件定時器,每100ms翻轉一次LED2。
- K1鍵按下,通道1輸出方波。
- K2鍵按下,通道1輸出正弦波。
- 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輸出)
實驗目的:
- 學習DAC8501的SPI DMA驅動方式實現。
實驗內容:
- DAC8501模塊上帶了兩片8501,每片是單通道DAC,片上輸出緩沖運放,軌到軌輸出,16bit分辨率,支持30MHz的SPI時鍾速度。
- DAC8501本身僅支持一路輸出,而模塊上是帶了兩片DAC8501,其中只有一路的片選可以支持SPI NSS復用, 所以只有一路支持SPI DMA。
- DAC8501供電電壓2.7-5.5V,模擬輸出帶寬350KHz。
實驗操作:
- 啟動一個自動重裝軟件定時器,每100ms翻轉一次LED2。
- K1鍵按下,通道1輸出方波。
- K2鍵按下,通道1輸出正弦波。
- 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方式驅動的實現方法,需要大家稍花點精力去研究。