完整教程下載地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980
第74章 STM32H7的SPI總線應用之驅動DAC8563(雙通道,16bit分辨率,正負10V)
本章節為大家講解標准SPI接線方式驅動模數轉換器DAC856X,制作了中斷和DMA兩種驅動方式。
74.1 初學者重要提示
74.2 DAC結構分類
74.3 DAC技術術語
74.4 DAC856X硬件設計
74.5 DAC856X關鍵知識點整理(重要)
74.6 DAC856X驅動設計(中斷更新方式)
74.7 DAC856X驅動設計(SPI DMA更新方式)
74.8 SPI總線板級支持包(bsp_spi_bus.c)
74.9 DAC856X支持包中斷方式(bsp_spi_dac8562.c)
74.10 DAC856X支持包DMA方式(bsp_spidma_dac8562.c)
74.11 DAC856X驅動移植和使用(中斷更新方式)
74.12 DAC856X驅動移植和使用(SPI DMA更新方式)
74.13 實驗例程設計框架
74.14 實驗例程說明(MDK)
74.15 實驗例程說明(IAR)
74.16 總結
74.1 初學者重要提示
1、 學習本章節前,務必優先學習第72章。
2、 對於DAC8562和DAC8563,教程中不做區分,因為DAC8562和DAC8563完全兼容,區別僅僅在於CLR引腳有效時,DAC8562數據設置為0, DAC8563數據設置為32767。
3、 本章涉及到的知識點比較多,需要大家掌握STM32H7的SPI , DMA,TIM,DMAMUX和DAC8563的一些細節用法。
4、 H7的SPI + DMA驅動這類外設的靈活度,絕對可以媲美FPGA去控制:
- H7的SPI外設比F4系列的靈活性強太多了,主要表現在兩個方面:數據的傳輸支持了4-32bit,特別是那個NSS片選引腳,超強勁,可以做各種時間插入,靈活應對了市場上這類芯片的需求。
- DMA這塊相比F4系列,有了質的飛躍,支持了DMAMUX,這個DMAMUX除了帶來靈活的觸發源選擇,還支持了各種觸發事件和同步觸發功能。本章配套例子的觸發周期控制就是利用了DMAMUX的同步觸發功能。
5、 本章配套了中斷和DMA兩種更新方式的案例,DMA實現方式與中斷更新方式完全不同,因為DMA方式要使用硬件SPI1 NSS片選引腳驅動DAC856X。而中斷更新方式使用公共的總線驅動文件bsp_spi_bus.c,片選是通過通用IO方式控制,支持串行FLASH、TSC2046、VS1053、AD7705、ADS1256等SPI設備。大家在看例子的時候要注意。
6、 對於本章教程配套例子的SPI DMA方式,這里特別注意一點,定時器觸發一次,就會讓SPI以DMA方式傳輸24bit數據。
7、 DAC856X數據手冊,模塊原理圖和接線圖都已經放到本章教程配置例子的Doc文件里。
74.2 DAC結構分類
這里將三種DAC結構為大家做個普及:R2R型MDAC,R2R型backDAC和Srting型DAC。
注,這些知識翻譯自TI的英文技術手冊。
74.2.1 R2R型MDAC
自動測試設備或儀器通常使用R2R MDAC。MDAC型制造商能夠設計具有±1 LSB的高分辨率積分非線性(INL)和差分非線性(DNL)DAC。通過使用合適的外部放大器,MDAC能夠實現較短的建立時間(<0.3 ms)和大於10 MHz的帶寬。並且通過為MDAC的外部運算放大器提供不同電源電壓和高輸出電流可以增強DAC功能。
MDAC產生的電流與用戶設置的數字編碼,外部放大器以及RFB(在MDAC內部)將DAC的電流輸出信號轉換為可用的電壓。
這類DAC的缺點是會有穩定性問題。
74.2.2 R2R型backDAC
通常在工業應用中使用R2R backDAC。其它一些應用還包括儀器和數字控制校准。使用這類DAC,每次新更新會將2R支路切換到參考電壓高(VREF-H)或參考電壓低(VREF-L)。注意R-2R梯子的布置與MDAC相比是倒置的。這就是名字backDAC的由來,這種架構很容易制造。
這類DAC的缺點是毛刺脈沖問題(注,此貼有詳細解釋:鏈接):
74.2.3 String型DAC
String型DAC最適合便攜式儀器,閉環伺服控制和過程控制。下圖顯示了一個3bit String DAC的模型,數字輸入代碼101b被解碼為5/8 VREF。String DAC的輸出級放大器隔離了來自輸出負載的內部電阻元件。
String DAC是一種低功耗解決方案,可確保單調性在整個輸入代碼中具有良好的DNL(差分非線性)性能范圍。毛刺能量通常低於其它DAC類型。
但是,INL(積分非線性)通常較大,具體取決於電阻式片上匹配,另一方面,控制回路中的DAC可減輕線性度影響。String DAC的噪聲也相對較大,因為電阻串的阻抗很高,所以該值很高。但String DAC功耗低且非常小的故障能量。
74.3 DAC技術術語
一些常見的DAC技術術語需要大家見到了,大概了解是什么意思。
74.3.1 單位ppm℃(ppm/℃)
這個參數是專門用來定義溫飄的,ppm全稱是parts per million,即百萬分之一。比如2ppm℃就是2 x 10^-6 ,反映到DAC8563上,定義如下:
Input or 2.5-V Output 4-ppm°C Temperature Drift (Typ)
也就是說,當輸出2.5V時,每變化一度,輸出電壓的變化是2.5V x (4 x 10^-6) = 10uV
類似的定義還有很多:
ppb,ppt,ppq所代表的含義:
74.3.2 毛刺脈沖(Glitch impulse)
使用DAC進行設計時,您期望輸出從一個值單調移至下一個值,但實際電路並非總是如此。在某些代碼范圍內,出現過沖或下沖(量化為毛刺脈沖)並不少見。主要以下面兩種形式呈現:
具體原因分析在這個帖子里面進行了講解(內容較多,就不整理到教程里面了):鏈接。
74.3.3 偏移誤差(Offset Error)
偏移誤差為標稱偏移點與實際偏移點之間的差。此錯誤以相同的數量影響所有代碼,通常可以通過修正來補償處理。如果無法修正,則該誤差稱為零刻度誤差。
74.3.4 增益誤差(Gain Error)
增益誤差定義為傳輸時標稱增益點與實際增益點之差。
74.3.5 差分非線性誤差(DNL)
DNL全稱Differential Nonlinearity。
差分非線性誤差為實際步長寬度(對於ADC)或步長高度(對於DAC)與1 LSB的理想值之間的差值。 因此,如果階躍寬度或高度恰好為1 LSB,則差分非線性誤差為零。 如果DNL超過1 LSB,轉換器可能變得非單調。這意味着增加了輸入的幅度但輸出的大小可能變小。
74.3.6 積分非線性誤差(INL)
INL全稱Integral Nonlinearity
積分非線性誤差是從一個傳輸點到相對應的理想傳輸曲線的最大偏差距離,不考慮偏置誤差和增益誤差。 這個參數對最佳傳輸函數或端點傳輸函數有一定參考意義。
74.3.7 絕對精度誤差(Absolute Accuracy Error)
絕對精度誤差是包括偏移,增益,積分線性等誤差的總體誤差。
74.4 DAC856X硬件設計
DAC的輸出量可以為0到2.5V或者0到5V,通過外置運放,實現了±10V輸出。原理圖下載:
http://www.armbbs.cn/forum.php?mod=viewthread&tid=97082 。
74.4.1 DAC856X模塊規格
產品規格:
1、供電電壓 : 2.7 - 5.5V 【3.3V供電時,輸出電壓也可以到正負10V】
2、通道數: 2路 (通過1片DAC8563實現)
3、輸出電壓范圍 : -10V ~ +10V 【客戶可以自己更改為 0-10V輸出范圍。使用烙鐵切換2個焊點即可,無需更換元器件】
4、輸出驅動能力:帶運放驅動,最大輸出電流10mA,負載電阻>1K歐姆
5、分辨率: 16位
6、功耗 : 小於20mA
7、MCU接口 :高速 SPI (50M) 支持 3.3V和5V單片機
8、DAC輸出模擬帶寬:350KHz
9、DAC輸出響應: 10uS 到 0.003% FSR
產品特點:
1、輸出和供電電壓無關;模塊內帶正負12V升壓電路
2、自適應單片機的電平(2.7 - 5V 均可以)
3、輸出電壓可抵達正負10V
4、上電時缺省輸出0V (在軟件未啟動時)
5、引出正負12V電源排針,方便客戶使用
重要提示:
1、DAC8562和DAC8563完全兼容,區別僅僅在於CLR引腳有效時,DAC8562數據設置為0, DAC8563數據設置為32767。注意這是DAC的內部數據,不表示輸出電壓。 對於-10 ~ +10V輸出的模塊,DAC8562輸出-10V, DAC8563輸出0V。
2、無論是用DAC8562還是DAC8563芯片,只要軟件不啟動,本模塊輸出電壓缺省狀態都是0V。
3、CLR腳懸浮時,電壓在1.9V左右,容易受到干擾導致輸出被清零。因此即使不用CLR控制功能,這個CLR腳也需要接固定電平(推薦接GND)。CLR是邊沿觸發,僅在下降沿信號出現執行清零。
產品效果:
74.4.2 DAC856X硬件接口
V7板子上DAC856X模塊的插座的原理圖如下:
實際對應開發的位置如下:
74.5 DAC856X關鍵知識點整理(重要)
驅動DAC856X需要對下面這些知識點有個認識。
74.5.1 DAC856X基礎信息
- 雙通道DAC,軌到軌輸出,16bit分辨率,支持50MHz的SPI時鍾速度。
- 自帶2.5V的內部參考基准,典型的溫飄是4ppm/℃。使用內部2.5V參考基准的情況下,根據增益設置不同,DAC的輸出量可以為0到2.5V或者0到5V。
- 用戶可以根據需要外接運放實現常用的±5V,±10V或者±15V輸出。
- 相對精度誤差4LSB INL。
- 毛刺脈沖 0.1nV-s
- 上電復位數值0V或者中間值。
74.5.2 DAC856X每個引腳的作用
DAC856X主要有下面兩種封裝形式:
- Avdd
供電范圍2.7-5.5V
- CLR
異步清除輸入,下降沿有效,觸發后,DAC8562輸出最低電壓值,DAC8563輸出中間值。用戶寫入操作的的第24個時鍾下降沿將退出清除模式,激活清除模式將終止寫操作。
- Din
串行時鍾輸入,每個時鍾下降沿將數據寫到的24bit的輸入移位寄存器。
- GND
接地端。
- LDAC
同步模式下,數據更新發生在第24個SCLK周期的下降沿,之后伴隨着SYNC的下降沿。 這種同步更新不需要LDAC,而LDAC必須永久接地,或者將命令發送到設備時保持低電平。異步模式下,LDAC是低電平觸發,用於同步DAC更新,可以編寫多個單通道命令進行設置,然后在LDAC引腳上產生一個下降沿將同步更新DAC輸出寄存器。
- SCLK
時鍾輸入端,支持50MHz。
- SYNC (片選)
低電平有效,當SYNC變為低電平時,它使能輸入移位寄存器,並且數據采樣在隨后的時鍾下降沿。 DAC輸出在第24個時鍾下降沿之后更新。 如果SYNC在第23個時鍾沿之前變高,SYNC的上升沿將充當中斷,並且DAC756x,DAC816x和DAC856x器件將忽略寫序列。
- VoutA
模擬電壓輸出A。
- VoutB
模擬電壓輸出B。
- Vrefin/Vrefout
雙向電壓參考引腳,如果使用內部電壓基准,此引腳是輸出2.5V。
74.5.3 DAC856X輸出電壓計算公式
DAC856X的計算公式如下:
- DIN
配置DAC856X數據輸出寄存器的數值,范圍0 到2^16 – 1,即0到65535。
- 2n
對於DAC856X來說,n是16。
- VREF
如果使用內部參考電壓,那么此數值是2.5V,如果使用外部參考電壓,由VREFIN引腳的輸入決定。
- Gain
增益設置。禁止內部電壓基准后,默認增益是1。如果使能內部電壓基准后,默認增益是2。具體增益是1還是2,可以通過DAC856X的寄存器設置。
74.5.4 DAC856X時序圖
DAC856X的時序圖如下:
這個時序里面有三個參數尤其重要,后面時序配置要用到。
- f(SCLK)
支持最高的串行時鍾是50MHz。
- t(4)
每傳輸24bit數據后,SYNC要保持一段時間的高電平,DAC856X要求至少要80ns。
- t(5)
SYNC低電平有效到SCLK第1個下降沿信號的時間,最小值13ns。
74.5.5 DAC856X寄存器配置
DAC856X的寄存器配置看下面的圖表即可,一目了然(X表示為0或者為1均可):
控制DAC856X每次要傳輸24bit數據,高8bit控制位 + 16bit數據位。
比如Power up DAC-A and DAC-B:
DAC8562_WriteCmd((4 << 19) | (0 << 16) | (3 << 0))
74.6 DAC856X驅動設計(中斷更新方式)
DAC856X的程序驅動框架設計如下:
有了這個框圖,程序設計就比較好理解了。
74.6.1 第1步:SPI總線配置
spi總線配置通過如下兩個函數實現:
/* ********************************************************************************************************* * 函 數 名: bsp_InitSPIBus * 功能說明: 配置SPI總線。 * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ void bsp_InitSPIBus(void) { g_spi_busy = 0; bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_8, SPI_PHASE_1EDGE, SPI_POLARITY_LOW); } /* ********************************************************************************************************* * 函 數 名: bsp_InitSPIParam * 功能說明: 配置SPI總線參數,時鍾分頻,時鍾相位和時鍾極性。 * 形 參: _BaudRatePrescaler SPI總線時鍾分頻設置,支持的參數如下: * SPI_BAUDRATEPRESCALER_2 2分頻 * SPI_BAUDRATEPRESCALER_4 4分頻 * SPI_BAUDRATEPRESCALER_8 8分頻 * SPI_BAUDRATEPRESCALER_16 16分頻 * SPI_BAUDRATEPRESCALER_32 32分頻 * SPI_BAUDRATEPRESCALER_64 64分頻 * SPI_BAUDRATEPRESCALER_128 128分頻 * SPI_BAUDRATEPRESCALER_256 256分頻 * * _CLKPhase 時鍾相位,支持的參數如下: * SPI_PHASE_1EDGE SCK引腳的第1個邊沿捕獲傳輸的第1個數據 * SPI_PHASE_2EDGE SCK引腳的第2個邊沿捕獲傳輸的第1個數據 * * _CLKPolarity 時鍾極性,支持的參數如下: * SPI_POLARITY_LOW SCK引腳在空閑狀態處於低電平 * SPI_POLARITY_HIGH SCK引腳在空閑狀態處於高電平 * * 返 回 值: 無 ********************************************************************************************************* */ void bsp_InitSPIParam(uint32_t _BaudRatePrescaler, uint32_t _CLKPhase, uint32_t _CLKPolarity) { /* 提高執行效率,只有在SPI硬件參數發生變化時,才執行HAL_Init */ if (s_BaudRatePrescaler == _BaudRatePrescaler && s_CLKPhase == _CLKPhase && s_CLKPolarity == _CLKPolarity) { return; } s_BaudRatePrescaler = _BaudRatePrescaler; s_CLKPhase = _CLKPhase; s_CLKPolarity = _CLKPolarity; /* 設置SPI參數 */ hspi.Instance = SPIx; /* 例化SPI */ hspi.Init.BaudRatePrescaler = _BaudRatePrescaler; /* 設置波特率 */ hspi.Init.Direction = SPI_DIRECTION_2LINES; /* 全雙工 */ hspi.Init.CLKPhase = _CLKPhase; /* 配置時鍾相位 */ hspi.Init.CLKPolarity = _CLKPolarity; /* 配置時鍾極性 */ hspi.Init.DataSize = SPI_DATASIZE_8BIT; /* 設置數據寬度 */ hspi.Init.FirstBit = SPI_FIRSTBIT_MSB; /* 數據傳輸先傳高位 */ hspi.Init.TIMode = SPI_TIMODE_DISABLE; /* 禁止TI模式 */ hspi.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; /* 禁止CRC */ hspi.Init.CRCPolynomial = 7; /* 禁止CRC后,此位無效 */ hspi.Init.CRCLength = SPI_CRC_LENGTH_8BIT; /* 禁止CRC后,此位無效 */ hspi.Init.NSS = SPI_NSS_SOFT; /* 使用軟件方式管理片選引腳 */ hspi.Init.FifoThreshold = SPI_FIFO_THRESHOLD_01DATA; /* 設置FIFO大小是一個數據項 */ hspi.Init.NSSPMode = SPI_NSS_PULSE_DISABLE; /* 禁止脈沖輸出 */ hspi.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_ENABLE; /* 禁止SPI后,SPI相關引腳保持當前狀態 */ hspi.Init.Mode = SPI_MODE_MASTER; /* SPI工作在主控模式 */ /* 復位配置 */ if (HAL_SPI_DeInit(&hspi) != HAL_OK) { Error_Handler(__FILE__, __LINE__); } /* 初始化配置 */ if (HAL_SPI_Init(&hspi) != HAL_OK) { Error_Handler(__FILE__, __LINE__); } }
關於這兩個函數有以下兩點要做個說明:
- 函數bsp_InitSPIBus里面的配置是個初始設置。實際驅動芯片時,會通過函數bsp_InitSPIParam做再配置。
- 函數bsp_InitSPIParam提供了時鍾分頻,時鍾相位和時鍾極性配置。驅動不同外設芯片時,基本上調整這三個參數就夠。當SPI接口上接了多個不同類型的芯片時,通過此函數可以方便的切換配置。
74.6.2 第2步:SPI總線的查詢,中斷和DMA方式設置
注:對於DAC8563,請使用查詢方式。
SPI驅動的查詢,中斷和DMA方式主要通過函數bsp_spiTransfer實現數據傳輸:
/* ********************************************************************************************************* * 選擇DMA,中斷或者查詢方式 ********************************************************************************************************* */ //#define USE_SPI_DMA /* DMA方式 */ //#define USE_SPI_INT /* 中斷方式 */ #define USE_SPI_POLL /* 查詢方式 */ /* 查詢模式 */ #if defined (USE_SPI_POLL) uint8_t g_spiTxBuf[SPI_BUFFER_SIZE]; uint8_t g_spiRxBuf[SPI_BUFFER_SIZE]; /* 中斷模式 */ #elif defined (USE_SPI_INT) uint8_t g_spiTxBuf[SPI_BUFFER_SIZE]; uint8_t g_spiRxBuf[SPI_BUFFER_SIZE]; /* DMA模式使用的SRAM4 */ #elif defined (USE_SPI_DMA) #if defined ( __CC_ARM ) /* IAR *******/ __attribute__((section (".RAM_D3"))) uint8_t g_spiTxBuf[SPI_BUFFER_SIZE]; __attribute__((section (".RAM_D3"))) uint8_t g_spiRxBuf[SPI_BUFFER_SIZE]; #elif defined (__ICCARM__) /* MDK ********/ #pragma location = ".RAM_D3" uint8_t g_spiTxBuf[SPI_BUFFER_SIZE]; #pragma location = ".RAM_D3" uint8_t g_spiRxBuf[SPI_BUFFER_SIZE]; #endif #endif /* ********************************************************************************************************* * 函 數 名: bsp_spiTransfer * 功能說明: 啟動數據傳輸 * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ void bsp_spiTransfer(void) { if (g_spiLen > SPI_BUFFER_SIZE) { return; } /* DMA方式傳輸 */ #ifdef USE_SPI_DMA wTransferState = TRANSFER_WAIT; if(HAL_SPI_TransmitReceive_DMA(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen) != HAL_OK) { Error_Handler(__FILE__, __LINE__); } while (wTransferState == TRANSFER_WAIT) { ; } #endif /* 中斷方式傳輸 */ #ifdef USE_SPI_INT wTransferState = TRANSFER_WAIT; if(HAL_SPI_TransmitReceive_IT(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen) != HAL_OK) { Error_Handler(__FILE__, __LINE__); } while (wTransferState == TRANSFER_WAIT) { ; } #endif /* 查詢方式傳輸 */ #ifdef USE_SPI_POLL if(HAL_SPI_TransmitReceive(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen, 1000000) != HAL_OK) { Error_Handler(__FILE__, __LINE__); } #endif }
通過開頭宏定義可以方便的切換中斷,查詢和DMA方式。其中查詢和中斷方式比較好理解,而DMA方式要特別注意兩點:
- 通過本手冊第26章的內存塊超方便使用方式,將DMA緩沖定義到SRAM4上。因為本工程是用的DTCM做的主RAM空間,這個空間無法使用通用DMA1和DMA2。
- 由於程序里面開啟了數據Cache,會造成DMA和CPU訪問SRAM4數據不一致的問題,特此將SRAM4空間關閉Cache。
/* 配置SRAM4的MPU屬性為Non-cacheable */ MPU_InitStruct.Enable = MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress = 0x38000000; MPU_InitStruct.Size = MPU_REGION_SIZE_64KB; MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS; MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE; MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE; MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE; MPU_InitStruct.Number = MPU_REGION_NUMBER2; MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1; MPU_InitStruct.SubRegionDisable = 0x00; MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct);
74.6.3 第3步:DAC856X的時鍾極性和時鍾相位配置
首先回憶下STM32H7支持的4種時序配置。
- 當CPOL = 1, CPHA = 1時
SCK引腳在空閑狀態處於高電平,SCK引腳的第2個邊沿捕獲傳輸的第1個數據。
- 當CPOL = 0, CPHA = 1時
SCK引腳在空閑狀態處於低電平,SCK引腳的第2個邊沿捕獲傳輸的第1個數據。
- 當CPOL = 1, CPHA = 0時
SCK引腳在空閑狀態處於高電平,SCK引腳的第1個邊沿捕獲傳輸的第1個數據。
- 當CPOL = 0 ,CPHA= 0時
SCK引腳在空閑狀態處於低電平,SCK引腳的第1個邊沿捕獲傳輸的第1個數據。
有了H7支持的時序配置,再來看下DAC856X的時序圖:
首先DAC856X是下降升沿做數據采集,所以STM32H7的可選的配置就是:
CHOL = 0, CPHA = 1
CHOL = 1, CPHA = 0
對於這兩種情況的主要區別是空閑狀態下SCLK時鍾選擇高電平還是低電平,根據上面的時序圖和DAC856X的數據手冊,兩種情況下都可以正常運行。經過實際測試,STM32H7使用這兩個配置確實都可以正常運行。程序里面默認是選擇CHOL = 0, CPHA = 1。
74.6.4 第4步:單SPI接口管理多個SPI設備的切換機制
單SPI接口管理多個SPI設備最麻煩的地方是不同設備的時鍾分配,時鍾極性和時鍾相位並不相同。對此的解決解決辦法是在片選階段配置切換,比如DAC856X的片選:
/* ********************************************************************************************************* * 函 數 名: DAC8562_SetCS * 功能說明: DAC8562 片選控制函數 * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ void DAC8562_SetCS(uint8_t _Level) { if (_Level == 0) { bsp_SpiBusEnter(); /* 占用SPI總線 */ bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_4, SPI_PHASE_2EDGE, SPI_POLARITY_LOW); CS_0(); } else { CS_1(); bsp_SpiBusExit(); /* 釋放SPI總線 */ } }
通過這種方式就有效的解決了單SPI接口管理多設備的問題。因為給每個設備都配了一個獨立的片選引腳,這樣就可以為每個設備都配置這么一個片選配置。
但是頻繁配置也比較繁瑣,所以函數bsp_InitSPIParam里面做了特別處理。當前配置與之前配置相同的情況下無需重復配置。
74.6.5 第5步:DAC856X的數據更新
DAC856X的雙通道數據更新通過下面的函數實現:
/* ********************************************************************************************************* * 函 數 名: DAC8562_SetDacData * 功能說明: 設置DAC輸出,並立即更新。 * 形 參: _ch, 通道, 0 , 1 * _data : 數據 * 返 回 值: 無 ********************************************************************************************************* */ void DAC8562_SetDacData(uint8_t _ch, uint16_t _dac) { if (_ch == 0) { /* Write to DAC-A input register and update DAC-A; */ DAC8562_WriteCmd((3 << 19) | (0 << 16) | (_dac << 0)); } else if (_ch == 1) { /* Write to DAC-B input register and update DAC-A; */ DAC8562_WriteCmd((3 << 19) | (1 << 16) | (_dac << 0)); } }
函數實現比較簡單,每次更新發送24bit數據即可。
74.7 DAC856X驅動設計(SPI DMA更新方式)
DAC856X的DMA驅動方式略復雜,跟中斷更新方式完全不同,要使用硬件SPI1 NSS引腳驅動DAC8562的片選,所有專門做了一個驅動文件來實現,程序驅動框架設計如下:
有了這個框圖,程序設計就比較好理解了。
74.7.1 第1步:SPI總線配置
spi總線配置通過如下兩個函數實現:
/* ********************************************************************************************************* * 函 數 名: bsp_InitDAC8562 * 功能說明: 配置GPIO並初始化DAC8562寄存器 * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ void bsp_InitDAC8562(void) { /* 配置GPIO */ GPIO_InitTypeDef GPIO_InitStruct; s_SpiDmaMode = 0; /*##-1- 配置SPI DMA ############################################################*/ bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_4, SPI_PHASE_2EDGE, SPI_POLARITY_LOW); /*##-2- 配置CLR引腳 ############################################################*/ CLR_CLK_ENABLE(); GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; /* 設置推挽輸出 */ GPIO_InitStruct.Pull = GPIO_NOPULL; /* 上下拉電阻不使能 */ GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_MEDIUM; /* GPIO速度等級 */ GPIO_InitStruct.Pin = CLR_PIN; HAL_GPIO_Init(CLR_GPIO, &GPIO_InitStruct); CLR_0(); /* CLR接GND可靠一些,CLR是下降沿觸發 */ LDAC_0(); /* 不用異步更新模式,此引腳接GND */ /*##-3- 配置DAC8562 ############################################################*/ /* Power up DAC-A and DAC-B */ DAC8562_WriteCmd((4 << 19) | (0 << 16) | (3 << 0)); /* LDAC pin inactive for DAC-B and DAC-A 不使用LDAC引腳更新數據 */ DAC8562_WriteCmd((6 << 19) | (0 << 16) | (3 << 0)); /* 復位2個DAC到中間值, 輸出0V */ DAC8562_SetDacData(0, 32767); DAC8562_SetDacData(1, 32767); /* 選擇內部參考並復位2個DAC的增益=2 (復位時,內部參考是禁止的) */ DAC8562_WriteCmd((7 << 19) | (0 << 16) | (1 << 0)); } /* ********************************************************************************************************* * 函 數 名: bsp_InitSPIParam * 功能說明: 配置SPI總線參數,時鍾分頻,時鍾相位和時鍾極性。 * 形 參: _BaudRatePrescaler SPI總線時鍾分頻設置,支持的參數如下: * SPI_BAUDRATEPRESCALER_2 2分頻 * SPI_BAUDRATEPRESCALER_4 4分頻 * SPI_BAUDRATEPRESCALER_8 8分頻 * SPI_BAUDRATEPRESCALER_16 16分頻 * SPI_BAUDRATEPRESCALER_32 32分頻 * SPI_BAUDRATEPRESCALER_64 64分頻 * SPI_BAUDRATEPRESCALER_128 128分頻 * SPI_BAUDRATEPRESCALER_256 256分頻 * * _CLKPhase 時鍾相位,支持的參數如下: * SPI_PHASE_1EDGE SCK引腳的第1個邊沿捕獲傳輸的第1個數據 * SPI_PHASE_2EDGE SCK引腳的第2個邊沿捕獲傳輸的第1個數據 * * _CLKPolarity 時鍾極性,支持的參數如下: * SPI_POLARITY_LOW SCK引腳在空閑狀態處於低電平 * SPI_POLARITY_HIGH SCK引腳在空閑狀態處於高電平 * * 返 回 值: 無 ********************************************************************************************************* */ void bsp_InitSPIParam(uint32_t _BaudRatePrescaler, uint32_t _CLKPhase, uint32_t _CLKPolarity) { /* 設置SPI參數 */ hspi.Instance = SPIx; /* 例化SPI */ hspi.Init.BaudRatePrescaler = _BaudRatePrescaler; /* 設置波特率 */ hspi.Init.Direction = SPI_DIRECTION_2LINES_TXONLY; /* 全雙工 */ hspi.Init.CLKPhase = _CLKPhase; /* 配置時鍾相位 */ hspi.Init.CLKPolarity = _CLKPolarity; /* 配置時鍾極性 */ hspi.Init.DataSize = SPI_DATASIZE_24BIT; /* 設置數據寬度 */ hspi.Init.FirstBit = SPI_FIRSTBIT_MSB; /* 數據傳輸先傳高位 */ hspi.Init.TIMode = SPI_TIMODE_DISABLE; /* 禁止TI模式 */ hspi.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; /* 禁止CRC */ hspi.Init.CRCPolynomial = 7; /* 禁止CRC后,此位無效 */ hspi.Init.CRCLength = SPI_CRC_LENGTH_8BIT; /* 禁止CRC后,此位無效 */ hspi.Init.FifoThreshold = SPI_FIFO_THRESHOLD_05DATA; /* 設置FIFO大小是一個數據項 */ hspi.Init.NSS = SPI_NSS_HARD_OUTPUT; /* 使用軟件方式管理片選引腳 */ hspi.Init.NSSPMode = SPI_NSS_PULSE_ENABLE; /* 使能脈沖輸出 */ hspi.Init.NSSPolarity = SPI_NSS_POLARITY_LOW; /* 低電平有效 */ /* MSS, 插入到NSS有效邊沿和第一個數據開始之間的額外延遲,單位SPI時鍾周期個數 */ hspi.Init.MasterSSIdleness = SPI_MASTER_SS_IDLENESS_00CYCLE; /* MIDI, 兩個連續數據幀之間插入的最小時間延遲,單位SPI時鍾周期個數 */ hspi.Init.MasterInterDataIdleness = SPI_MASTER_INTERDATA_IDLENESS_10CYCLE; hspi.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_ENABLE; /* 禁止SPI后,SPI相關引腳保持當前狀態 */ hspi.Init.Mode = SPI_MODE_MASTER; /* SPI工作在主控模式 */ /* 復位配置 */ if (HAL_SPI_DeInit(&hspi) != HAL_OK) { Error_Handler(__FILE__, __LINE__); } /* 初始化配置 */ if (HAL_SPI_Init(&hspi) != HAL_OK) { Error_Handler(__FILE__, __LINE__); } }
這兩個配置函數里面最重要的是置紅的幾個配置選項,這里依次為大家做個說明:
- SPI_DIRECTION_2LINES_TXONLY
驅動DAC856X僅需要SPI寫操作。
- SPI_DATASIZE_24BIT
STM32H7的SPI支持4-32bit數據傳輸,由於DAC856X需要24bit數據,所以這里配置為24即可。
- SPI_FIFO_THRESHOLD_05DATA
對於SPI1來說,里面的FIFO大小是16字節,那么SPI數據傳輸配置為24bit的話,FIFO最多可以存儲5個24bit,因此這個fifo閥值要設置為5。
- SPI_NSS_HARD_OUTPUT
我們這里要使用SPI的硬件片選引腳SPI_NSS。
- SPI_MASTER_SS_IDLENESS_00CYCLE
插入到NSS有效邊沿和第一個數據開始之間的額外延遲,單位SPI時鍾周期個數。
根據本章4.5.4小節里面的t(5)要求,片選有效到SCLK第1個下降沿信號的時間,最小值13ns。由於DAC856X的最高時鍾是50MHz,即20ns的分辨率,並且實際程序中,我們選擇的是第2個邊沿做數據采集,所以這里配置為0即可,也就是無需插入時間。
- SPI_MASTER_INTERDATA_IDLENESS_10CYCLE
兩個連續數據幀之間插入的最小時間延遲,單位SPI時鍾周期個數。
根據本章4.5.4小節里面的t(4)要求,每傳輸24bit數據后,片選要保持一段時間的高電平,DAC856X要求至少要80ns,也是說,如果我們以50MHz驅動DAC856X,這里至少要配置為4個時鍾周期,推薦值為5及其以上即可,我們這里直接配置為10個時鍾周期(配置為5也沒問題的)。
74.7.2 第2步:TIM12周期性觸發配置
這里特別注意一點,定時器觸發一次,就會讓SPI以DMA方式傳輸24bit輸出。
TIM12的觸發配置如下:
/* ********************************************************************************************************* * 函 數 名: TIM12_Config * 功能說明: 配置TIM12,用於觸發DMAMUX的請求發生器 * 形 參: _ulFreq 觸發頻率,推薦范圍100Hz - 1MHz * 返 回 值: 無 ********************************************************************************************************* */ TIM_HandleTypeDef htim ={0}; TIM_MasterConfigTypeDef sMasterConfig = {0}; TIM_OC_InitTypeDef sConfig = {0}; void TIM12_Config(uint32_t _ulFreq) { uint16_t usPeriod; uint16_t usPrescaler; uint32_t uiTIMxCLK; /* 使能時鍾 */ __HAL_RCC_TIM12_CLK_ENABLE(); /*----------------------------------------------------------------------- bsp.c 文件中 void SystemClock_Config(void) 函數對時鍾的配置如下: System Clock source = PLL (HSE) SYSCLK(Hz) = 400000000 (CPU Clock) HCLK(Hz) = 200000000 (AXI and AHBs Clock) AHB Prescaler = 2 D1 APB3 Prescaler = 2 (APB3 Clock 100MHz) D2 APB1 Prescaler = 2 (APB1 Clock 100MHz) D2 APB2 Prescaler = 2 (APB2 Clock 100MHz) D3 APB4 Prescaler = 2 (APB4 Clock 100MHz) 因為APB1 prescaler != 1, 所以 APB1上的TIMxCLK = APB1 x 2 = 200MHz; 不含這個總線下的LPTIM1 因為APB2 prescaler != 1, 所以 APB2上的TIMxCLK = APB2 x 2 = 200MHz; APB4上面的TIMxCLK沒有分頻,所以就是100MHz; APB1 定時器有 TIM2, TIM3 ,TIM4, TIM5, TIM6, TIM7, TIM12, TIM13, TIM14,LPTIM1 APB2 定時器有 TIM1, TIM8 , TIM15, TIM16,TIM17 APB4 定時器有 LPTIM2,LPTIM3,LPTIM4,LPTIM5 ----------------------------------------------------------------------- */ uiTIMxCLK = SystemCoreClock / 2; if (_ulFreq < 100) { usPrescaler = 10000 - 1; /* 分頻比 = 10000 */ usPeriod = (uiTIMxCLK / 10000) / _ulFreq - 1; /* 自動重裝的值 */ } else if (_ulFreq < 3000) { usPrescaler = 100 - 1; /* 分頻比 = 100 */ usPeriod = (uiTIMxCLK / 100) / _ulFreq - 1;/* 自動重裝的值 */ } else /* 大於4K的頻率,無需分頻 */ { usPrescaler = 0; /* 分頻比 = 1 */ usPeriod = uiTIMxCLK / _ulFreq - 1; /* 自動重裝的值 */ } htim.Instance = TIM12; htim.Init.Period = usPeriod; htim.Init.Prescaler = usPrescaler; htim.Init.ClockDivision = 0; htim.Init.CounterMode = TIM_COUNTERMODE_UP; htim.Init.RepetitionCounter = 0; if(HAL_TIM_Base_DeInit(&htim) != HAL_OK) { Error_Handler(__FILE__, __LINE__); } if(HAL_TIM_Base_Init(&htim) != HAL_OK) { Error_Handler(__FILE__, __LINE__); } sConfig.OCMode = TIM_OCMODE_PWM1; sConfig.OCPolarity = TIM_OCPOLARITY_LOW; sConfig.Pulse = usPeriod / 2; /* 占空比50% */ if(HAL_TIM_OC_ConfigChannel(&htim, &sConfig, TIM_CHANNEL_1) != HAL_OK) { Error_Handler(__FILE__, __LINE__); } /* 啟動OC1 */ if(HAL_TIM_OC_Start(&htim, TIM_CHANNEL_1) != HAL_OK) { Error_Handler(__FILE__, __LINE__); } /* TIM12的TRGO用於觸發DMAMUX的請求發生器 */ sMasterConfig.MasterOutputTrigger = TIM_TRGO_OC1REF; sMasterConfig.MasterOutputTrigger2 = TIM_TRGO2_RESET; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; HAL_TIMEx_MasterConfigSynchronization(&htim, &sMasterConfig); } #endif
這個函數支持的觸發頻率很寬,對於DAC856X來說,如果樣本點設置為100個的話,此函數推薦的觸發頻率是100Hz到1MHz,具體可以支持到最高觸發速度計算看本章4.7.7小節即可。
74.7.3 第3步:DMAMUX同步觸發SPI DMA傳輸
DMA和DMAMUX的配置如下:
/* ********************************************************************************************************* * 函 數 名: bsp_spiDamStart * 功能說明: 啟動SPI DMA傳輸 * 形 參: _ulFreq 范圍推薦100Hz-1MHz * 返 回 值: 無 ********************************************************************************************************* */ void bsp_spiDamStart(uint32_t _ulFreq) { /* 設置模式,要切換到DMA CIRCULAR模式 */ s_SpiDmaMode = 1; bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_4, SPI_PHASE_2EDGE, SPI_POLARITY_LOW); /* 使能DMA時鍾 */ DMAx_CLK_ENABLE(); /* SPI DMA發送配置 */ hdma_tx.Instance = SPIx_TX_DMA_STREAM; /* 例化使用的DMA數據流 */ hdma_tx.Init.FIFOMode = DMA_FIFOMODE_ENABLE; /* 使能FIFO */ hdma_tx.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL; /* 用於設置閥值 */ hdma_tx.Init.MemBurst = DMA_MBURST_SINGLE; /* 用於存儲器突發 */ hdma_tx.Init.PeriphBurst = DMA_PBURST_SINGLE; /* 用於外設突發 */ hdma_tx.Init.Request = SPIx_TX_DMA_REQUEST; /* 請求類型 */ hdma_tx.Init.Direction = DMA_MEMORY_TO_PERIPH; /* 傳輸方向是從存儲器到外設 */ hdma_tx.Init.PeriphInc = DMA_PINC_DISABLE; /* 外設地址自增禁止 */ hdma_tx.Init.MemInc = DMA_MINC_ENABLE; /* 存儲器地址自增使能 */ hdma_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD; /* 外設數據傳輸位寬選擇字節,即8bit */ hdma_tx.Init.MemDataAlignment = DMA_MDATAALIGN_WORD; /* 存儲器數據傳輸位寬選擇字節,即8bit */ hdma_tx.Init.Mode = DMA_CIRCULAR; /* 正常模式 */ hdma_tx.Init.Priority = DMA_PRIORITY_LOW; /* 優先級低 */ /* 復位DMA */ if(HAL_DMA_DeInit(&hdma_tx) != HAL_OK) { Error_Handler(__FILE__, __LINE__); } /* 初始化DMA */ if(HAL_DMA_Init(&hdma_tx) != HAL_OK) { Error_Handler(__FILE__, __LINE__); } /* 關聯DMA句柄到SPI */ __HAL_LINKDMA(&hspi, hdmatx, hdma_tx); /* 關閉DMA發送中斷 */ HAL_NVIC_SetPriority(SPIx_DMA_TX_IRQn, 1, 0); HAL_NVIC_DisableIRQ(SPIx_DMA_TX_IRQn); /* 關閉SPI中斷 */ HAL_NVIC_SetPriority(SPIx_IRQn, 1, 0); HAL_NVIC_DisableIRQ(SPIx_IRQn); /* 同步觸發配置 */ dmamux_syncParams.EventEnable = ENABLE; dmamux_syncParams.SyncPolarity = HAL_DMAMUX_SYNC_RISING; dmamux_syncParams.RequestNumber = 1; dmamux_syncParams.SyncSignalID = HAL_DMAMUX1_SYNC_TIM12_TRGO; dmamux_syncParams.SyncEnable = ENABLE; HAL_DMAEx_ConfigMuxSync(&hdma_tx, &dmamux_syncParams); //LPTIM_Config(_ulFreq); TIM12_Config(_ulFreq); /* 啟動DMA傳輸 */ if(HAL_SPI_Transmit_DMA(&hspi, (uint8_t*)g_spiTxBuf, g_spiLen/4)!= HAL_OK) { Error_Handler(__FILE__, __LINE__); } }
這段程序里面最關鍵的就是置紅的部分。作用是配置DMAMUX的同步觸發功能,觸發周期由TIM12控制。
74.7.4 第4步:24bit數據的DMA傳輸解決辦法
由於通用DMA1和DMA2僅支持8bit,16bit和32bit數據傳輸,我們這里要傳輸24bit數據,解決的關鍵就是配置DMA為傳輸寬度為32bit,並將傳輸的數據由24bit再補一個8bit的任意值組成32bit即可,實際的傳輸會由SPI完成。
/* ********************************************************************************************************* * 函 數 名: DAC8562_SetDacDataDMA * 功能說明: DAC8562數據發送,DMA方式 * 形 參: _ch 1表示通道1輸出 * 2表示通道2輸出 * 3表示通道1和2都輸出 * 4表示通道1和2都輸出,並且附加一個控制命令,有效防止傳輸錯誤時恢復。 * _pbufch1 通道1數據緩沖地址 * _pbufch2 通道2數據緩沖地址 * _sizech1 通道1數據大小 * _sizech2 通道2數據大小 * _ulFreq 觸發頻率,范圍2KB- 1MHz,注意這個參數是觸發頻率,並不是波形周期,觸發一次,SPI DMA * 傳輸一次24bit數據。 * 返 回 值: 無 ********************************************************************************************************* */ void DAC8562_SetDacDataDMA(uint8_t _ch, uint16_t *_pbufch1, uint16_t *_pbufch2, uint32_t _sizech1, uint32_t _sizech2, uint32_t _ulFreq) { uint32_t i; uint32_t _cmd; g_spiLen = 0; switch (_ch) { /* 通道1數據發送 */ case 1: for(i = 0; i < _sizech1; i++) { _cmd = (3 << 19) | (0 << 16) | (_pbufch1[i] << 0); g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd); g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 8); g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 16); g_spiTxBuf[g_spiLen++] = 0xff; } break; /* 通道2數據發送 */ case 2: for(i = 0; i < _sizech2; i++) { _cmd = (3 << 19) | (1 << 16) | (_pbufch2[i] << 0); g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd); g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 8); g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 16); g_spiTxBuf[g_spiLen++] = 0xff; } break; /* 通道1和2混合發送 */ case 3: if(_sizech1 == _sizech2) { for(i = 0; i < _sizech1; i++) { _cmd = (3 << 19) | (0 << 16) | (_pbufch1[i] << 0); g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd); g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 8); g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 16); g_spiTxBuf[g_spiLen++] = 0xff; _cmd = (3 << 19) | (1 << 16) | (_pbufch2[i] << 0); g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd); g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 8); g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 16); g_spiTxBuf[g_spiLen++] = 0xff; } } else { for(i = 0; i < _sizech1; i++) { _cmd = (3 << 19) | (0 << 16) | (_pbufch1[i] << 0); g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd); g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 8); g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 16); g_spiTxBuf[g_spiLen++] = 0xff; } for(i = 0; i < _sizech2; i++) { _cmd = (3 << 19) | (1 << 16) | (_pbufch2[i] << 0); g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd); g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 8); g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 16); g_spiTxBuf[g_spiLen++] = 0xff; } } break; /* 插入關鍵命令,防止傳輸錯誤 */ case 4: if(_sizech1 == _sizech2) { for(i = 0; i < _sizech1; i++) { _cmd = (3 << 19) | (0 << 16) | (_pbufch1[i] << 0); g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd); g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 8); g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 16); g_spiTxBuf[g_spiLen++] = 0xff; _cmd = (3 << 19) | (1 << 16) | (_pbufch2[i] << 0); g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd); g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 8); g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 16); g_spiTxBuf[g_spiLen++] = 0xff; } } else { for(i = 0; i < _sizech1; i++) { _cmd = (3 << 19) | (0 << 16) | (_pbufch1[i] << 0); g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd); g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 8); g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 16); g_spiTxBuf[g_spiLen++] = 0xff; } for(i = 0; i < _sizech2; i++) { _cmd = (3 << 19) | (1 << 16) | (_pbufch2[i] << 0); g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd); g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 8); g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 16); g_spiTxBuf[g_spiLen++] = 0xff; } } /* 數據填充完畢后,插入關鍵命令,數據輸出過程中被8256誤識別為命令處理*/ _cmd = (7 << 19) | (0 << 16) | (1 << 0); g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd); g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 8); g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 16); g_spiTxBuf[g_spiLen++] = 0xff; break; default: break; } bsp_spiDamStart(_ulFreq); }
74.7.5 第5步:DMA緩沖區的MPU配置
因為工程是用的DTCM做的主RAM空間,這個空間無法使用通用DMA1和DMA2,通過本手冊第26章的內存塊超方便使用方式,將DMA緩沖定義到SRAM4上:
#if defined ( __CC_ARM ) /* IAR *******/ __attribute__((section (".RAM_D3"))) uint8_t g_spiTxBuf[SPI_BUFFER_SIZE]; __attribute__((section (".RAM_D3"))) uint8_t g_spiRxBuf[SPI_BUFFER_SIZE]; #elif defined (__ICCARM__) /* MDK ********/ #pragma location = ".RAM_D3" uint8_t g_spiTxBuf[SPI_BUFFER_SIZE]; #pragma location = ".RAM_D3" uint8_t g_spiRxBuf[SPI_BUFFER_SIZE]; #endif
由於程序里面開啟了數據Cache,會造成DMA和CPU訪問SRAM4數據不一致的問題,特此將SRAM4空間關閉Cache。
/* 配置SRAM4的MPU屬性為Non-cacheable */ MPU_InitStruct.Enable = MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress = 0x38000000; MPU_InitStruct.Size = MPU_REGION_SIZE_64KB; MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS; MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE; MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE; MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE; MPU_InitStruct.Number = MPU_REGION_NUMBER2; MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1; MPU_InitStruct.SubRegionDisable = 0x00; MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct);
74.7.6 第6步:DAC856X的時鍾極性和時鍾相位配置
注:與本章4.6.3小節內容是一樣的。
首先回憶下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支持的時序配置,再來看下DAC856X的時序圖:
首先DAC856X是下降升沿做數據采集,所以STM32H7的可選的配置就是:
CHOL = 0, CPHA = 1
CHOL = 1, CPHA = 0
對於這兩種情況的主要區別是空閑狀態下SCLK時鍾選擇高電平還是低電平,根據上面的時序圖和DAC856X的數據手冊,兩種情況下都可以正常運行。經過實際測試,STM32H7使用這兩個配置確實都可以正常運行。程序里面默認是選擇CHOL = 0, CPHA = 1。
74.7.7 第7步:DAC856X的最高更新速度計算
這里特別注意一點,定時器觸發一次,就會讓SPI以DMA方式傳輸24bit數據。
配置條件:
- SPI時鍾是50MHz。
- SPI數據傳為24bit,每個bit需要時間20ns。
- hspi.Init.MasterSSIdleness = SPI_MASTER_SS_IDLENESS_00CYCLE 插入到NSS有效邊沿和第一個數據開始之間的額外延遲,單位SPI時鍾周期個數,即20ns。
- hspi.Init.MasterInterDataIdleness = SPI_MASTER_INTERDATA_IDLENESS_10CYCLE 兩個連續數據幀之間插入的最小時間延遲,單位SPI時鍾周期個數,即20ns。
根據上面的配置,傳輸一幀(24bit)數據需要的時間:
24bit * 20ns+ SPI_MASTER_SS_IDLENESS_00CYCLE * 20ns
+ SPI_MASTER_INTERDATA_IDLENESS_10CYCLE * 20ns
= 24bit * 20ns + 0 * 20ns + 10 * 20ns
= 680ns。
那么這種配置下,可以支持最高觸發速度是1 / 680ns = 1.47MHz,如果想速度再提升些,可以降低參數hspi.Init.MasterInterDataIdleness,推薦的最小值是5個時鍾周期,那么可以支持的最高觸發速度是1/580ns = 1.7MHz。
認識到這些后,實際輸出的波形周期也比較好算了,比如我們設置10個樣本點為一個周期,那么觸發速度為1MHz的時候,那么波形周期就是100KHz。
74.7.8 第8步:DAC值和電壓值互轉
DAC856X模塊的輸出電壓范圍是-10V到10V,對應的編碼值范圍是0到65535,為了方便大家做互轉,專門做了兩個函數:
/* ********************************************************************************************************* * 函 數 名: DAC8562_DacToVoltage * 功能說明: 將DAC值換算為電壓值,單位0.1mV * 形 參: _dac 16位DAC字 * 返 回 值: 電壓。單位0.1mV ********************************************************************************************************* */ int32_t DAC8562_DacToVoltage(uint16_t _dac) { int32_t y; /* CaculTwoPoint(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x);*/ y = CaculTwoPoint(X1, Y1, X2, Y2, _dac); return y; } /* ********************************************************************************************************* * 函 數 名: DAC8562_VoltageToDac * 功能說明: 將電壓值轉換為DAC置 * 形 參: _volt 電壓,單位0.1mV * 返 回 值: 16位DAC字 ********************************************************************************************************* */ uint32_t DAC8562_VoltageToDac(int32_t _volt) { /* CaculTwoPoint(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x);*/ return CaculTwoPoint(Y1, X1, Y2, X2, _volt); }
74.7.9 第9步:防止SPI DMA批量數據傳輸錯誤解決辦法
使用SPI DMA批量數據傳輸過程中,要防止一些數據被DAC856X錯誤識別成關鍵命令,從而造成DAC856X工作異常,其中最重要的一個關鍵命令就下面這個:
/* 選擇內部參考並復位2個DAC的增益=2 (復位時,內部參考是禁止的) */ DAC8562_WriteCmd((7 << 19) | (0 << 16) | (1 << 0));
針對這個問題,函數DAC8562_SetDacDataDMA專門做了一個傳輸方式4:
/* 插入關鍵命令,防止傳輸錯誤 */ case 4: if(_sizech1 == _sizech2) { for(i = 0; i < _sizech1; i++) { _cmd = (3 << 19) | (0 << 16) | (_pbufch1[i] << 0); g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd); g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 8); g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 16); g_spiTxBuf[g_spiLen++] = 0xff; _cmd = (3 << 19) | (1 << 16) | (_pbufch2[i] << 0); g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd); g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 8); g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 16); g_spiTxBuf[g_spiLen++] = 0xff; } } else { for(i = 0; i < _sizech1; i++) { _cmd = (3 << 19) | (0 << 16) | (_pbufch1[i] << 0); g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd); g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 8); g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 16); g_spiTxBuf[g_spiLen++] = 0xff; } for(i = 0; i < _sizech2; i++) { _cmd = (3 << 19) | (1 << 16) | (_pbufch2[i] << 0); g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd); g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 8); g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 16); g_spiTxBuf[g_spiLen++] = 0xff; } } /* 數據填充完畢后,插入關鍵命令,數據輸出過程中被8256誤識別為命令處理*/ _cmd = (7 << 19) | (0 << 16) | (1 << 0); g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd); g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 8); g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 16); g_spiTxBuf[g_spiLen++] = 0xff; break;
解決辦法是在批量數據的末尾附一個命令,通過這種方式可以有效防止DAC856X工作異常。
74.8 SPI總線板級支持包(bsp_spi_bus.c)
SPI總線驅動文件bsp_spi_bus.c主要實現了如下幾個API供用戶調用:
- bsp_InitSPIBus
- bsp_InitSPIParam
- bsp_spiTransfer
74.8.1 函數bsp_InitSPIBus
函數原型:
void bsp_InitSPIBus(void)
函數描述:
此函數主要用於SPI總線的初始化,在bsp.c文件調用一次即可。
74.8.2 函數bsp_InitSPIParam
函數原型:
void bsp_InitSPIParam(uint32_t _BaudRatePrescaler, uint32_t _CLKPhase, uint32_t _CLKPolarity)
函數描述:
此函數用於SPI總線的配置。
函數參數:
- 第1個參數SPI總線的分頻設置,支持的參數如下:
SPI_BAUDRATEPRESCALER_2 2分頻
SPI_BAUDRATEPRESCALER_4 4分頻
SPI_BAUDRATEPRESCALER_8 8分頻
SPI_BAUDRATEPRESCALER_16 16分頻
SPI_BAUDRATEPRESCALER_32 32分頻
SPI_BAUDRATEPRESCALER_64 64分頻
SPI_BAUDRATEPRESCALER_128 128分頻
SPI_BAUDRATEPRESCALER_256 256分頻
- 第2個參數用於時鍾相位配置,支持的參數如下:
SPI_PHASE_1EDGE SCK引腳的第1個邊沿捕獲傳輸的第1個數據
SPI_PHASE_2EDGE SCK引腳的第2個邊沿捕獲傳輸的第1個數據
- 第3個參數是時鍾極性配置,支持的參數如下:
SPI_POLARITY_LOW SCK引腳在空閑狀態處於低電平
SPI_POLARITY_HIGH SCK引腳在空閑狀態處於高電平
74.8.3 函數bsp_spiTransfer
函數原型:
void bsp_spiTransfer(void)
函數描述:
此函數用於啟動SPI數據傳輸,支持查詢,中斷和DMA方式傳輸。
74.9 DAC856X支持包中斷方式(bsp_spi_dac8562.c)
DAC856X驅動文件bsp_spi_dac8562.c主要實現了如下幾個API供用戶調用:
- bsp_InitDAC8562
- DAC8562_SetCS
- DAC8562_WriteCmd
- DAC8562_SetDacData
- DAC8562_DacToVoltage
- DAC8562_VoltageToDac
74.9.1 函數bsp_InitDAC8562
函數原型:
void bsp_InitDAC8562(void)
函數描述:
主要用於DAC856X的初始化,調用前務必先調用函數bsp_InitSPIBus初始化SPI外設。
74.9.2 函數DAC8562_SetCS
函數原型:
void DAC8562_SetCS(uint8_t _Level)
函數描述:
此函數用於片選DAC8562。
函數參數:
- 第1個參數為0表示選中,為1表示取消選中。
74.9.3 函數DAC8562_WriteCmd
函數原型:
void DAC8562_WriteCmd(uint32_t _cmd)
函數描述:
此函數用於向SPI總線發送24個bit數據。
函數參數:
- 第1個參數為24bit數據。
74.9.4 函數DAC8562_SetDacData
函數原型:
void DAC8562_SetDacData(uint8_t _ch, uint16_t _dac)
函數描述:
此函數用於設置DAC輸出,並立即更新。
函數參數:
- 第1個參數為0表示通道1,為1表示通道2。
- 第2個參數是DAC數值設置,范圍0到65535,0對應最小電壓值,65535對應最大電壓值。
74.9.5 函數DAC8562_DacToVoltage
函數原型:
int32_t DAC8562_DacToVoltage(uint16_t _dac)
函數描述:
此函數用於將DAC值換算為電壓值,單位0.1mV。
函數參數:
- 第1個參數DAC數值,范圍0到65535。
- 返回值,返回電壓值,單位0.1mV。
74.9.6 函數DAC8562_VoltageToDac
函數原型:
uint32_t DAC8562_VoltageToDac(int32_t _volt)
函數描述:
此函數用於將電壓值轉換為DAC值。
函數參數:
- 第1個參數是電壓值,范圍-100000到100000,單位0.1mV。
- 返回值,返回DAC值。
74.10 DAC856X支持包DMA方式(bsp_spidma_dac8562.c)
DAC856X驅動文件bsp_spidam_dac8562.c涉及到的函數比較多,我們主要介紹用到的如下幾個函數:
- bsp_InitDAC8562
- DAC8562_SetDacDataDMA
- DAC8562_WriteCmd
- DAC8562_SetDacData
74.10.1 函數bsp_InitDAC8562
函數原型:
void bsp_InitDAC8562(void)
函數描述:
主要用於DAC856X的初始化。
74.10.2 函數DAC8562_SetDacDataDMA
函數原型:
void DAC8562_SetDacDataDMA(uint8_t _ch, uint16_t *_pbufch1, uint16_t *_pbufch2, uint32_t _sizech1, uint32_t _sizech2, uint32_t _ulFreq)
函數描述:
此函數用於SPI DMA方式數據發送。
函數參數:
- 第1個參數用於選擇的通道:
- 1表示通道1輸出
- 2表示通道2輸出
- 3表示通道1和2都輸出
- 4表示通道1和2都輸出,並且附加一個控制命令,有效防止傳輸錯誤時恢復。
- 第2個參數表示通道1數據緩沖地址。
- 第3個參數表示通道2數據緩沖地址。
- 第4個參數表示通道1數據大小。
- 第5個參數表示通道2數據大小。
- 第6個參數表示觸發頻率,推薦范圍100Hz- 1MHz,注意這個參數是觸發頻率,並不是波形周期。這里觸發一次,SPI DMA傳輸一次24bit數據。
74.10.3 函數DAC8562_WriteCmd
函數原型:
void DAC8562_WriteCmd(uint32_t _cmd)
函數描述:
此函數用於向SPI總線發送24個bit數據。
函數參數:
- 第1個參數為24bit數據。
74.10.4 函數DAC8562_SetDacData
函數原型:
void DAC8562_SetDacData(uint8_t _ch, uint16_t _dac)
函數描述:
此函數用於設置DAC輸出,並立即更新。
函數參數:
- 第1個參數為0表示通道1,為1表示通道2。
- 第2個參數是DAC數值設置,范圍0到65535,0對應最小電壓值,65535對應最大電壓值。
74.11 DAC856X驅動移植和使用(中斷更新方式)
DAC856X移植步驟如下:
第1步:復制bsp_spi_bus.c,bsp_spi_bus.h,bsp_spi_dac8562.c,bsp_spi_dac8562.h到自己的工程目錄,並添加到工程里面。
第2步:根據使用的第幾個SPI,SPI時鍾,SPI引腳和DMA通道等,修改bsp_spi_bus.c文件開頭的宏定義
/* ********************************************************************************************************* * 時鍾,引腳,DMA,中斷等宏定義 ********************************************************************************************************* */ #define SPIx SPI1 #define SPIx_CLK_ENABLE() __HAL_RCC_SPI1_CLK_ENABLE() #define DMAx_CLK_ENABLE() __HAL_RCC_DMA2_CLK_ENABLE() #define SPIx_FORCE_RESET() __HAL_RCC_SPI1_FORCE_RESET() #define SPIx_RELEASE_RESET() __HAL_RCC_SPI1_RELEASE_RESET() #define SPIx_SCK_CLK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE() #define SPIx_SCK_GPIO GPIOB #define SPIx_SCK_PIN GPIO_PIN_3 #define SPIx_SCK_AF GPIO_AF5_SPI1 #define SPIx_MISO_CLK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE() #define SPIx_MISO_GPIO GPIOB #define SPIx_MISO_PIN GPIO_PIN_4 #define SPIx_MISO_AF GPIO_AF5_SPI1 #define SPIx_MOSI_CLK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE() #define SPIx_MOSI_GPIO GPIOB #define SPIx_MOSI_PIN GPIO_PIN_5 #define SPIx_MOSI_AF GPIO_AF5_SPI1 #define SPIx_TX_DMA_STREAM DMA2_Stream3 #define SPIx_RX_DMA_STREAM DMA2_Stream2 #define SPIx_TX_DMA_REQUEST DMA_REQUEST_SPI1_TX #define SPIx_RX_DMA_REQUEST DMA_REQUEST_SPI1_RX #define SPIx_DMA_TX_IRQn DMA2_Stream3_IRQn #define SPIx_DMA_RX_IRQn DMA2_Stream2_IRQn #define SPIx_DMA_TX_IRQHandler DMA2_Stream3_IRQHandler #define SPIx_DMA_RX_IRQHandler DMA2_Stream2_IRQHandler #define SPIx_IRQn SPI1_IRQn #define SPIx_IRQHandler SPI1_IRQHandler
第3步:根據芯片支持的時鍾速度,時鍾相位和時鍾極性配置函數DAC8562_SetCS。
/* ********************************************************************************************************* * 函 數 名: DAC8562_SetCS * 功能說明: DAC8562 片選控制函數 * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ void DAC8562_SetCS(uint8_t _Level) { if (_Level == 0) { bsp_SpiBusEnter(); /* 占用SPI總線 */ bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_4, SPI_PHASE_2EDGE, SPI_POLARITY_LOW); CS_0(); } else { CS_1(); bsp_SpiBusExit(); /* 釋放SPI總線 */ } }
第4步:根據使用的片選,CLR和LDAC引腳,修改bsp_spi_dac8562.c文件開頭的宏定義。
/* SYNC, 也就是CS片選 */ #define CS_CLK_ENABLE() __HAL_RCC_GPIOG_CLK_ENABLE() #define CS_GPIO GPIOG #define CS_PIN GPIO_PIN_10 #define CS_1() CS_GPIO->BSRR = CS_PIN #define CS_0() CS_GPIO->BSRR = ((uint32_t)CS_PIN << 16U) /* CLR */ #define CLR_CLK_ENABLE() __HAL_RCC_GPIOE_CLK_ENABLE() #define CLR_GPIO GPIOE #define CLR_PIN GPIO_PIN_4 #define CLR_1() CLR_GPIO->BSRR = CLR_PIN #define CLR_0() CLR_GPIO->BSRR = ((uint32_t)CLR_PIN << 16U) /* LDAC 使用擴展IO ,特別注意,我們這里是用的擴展IO控制的 */ #define LDAC_1() HC574_SetPin(NRF24L01_CE, 1); #define LDAC_0() HC574_SetPin(NRF24L01_CE, 0);
第5步:如果使用DMA方式的話,請不要使用TCM RAM,因為通用DMA1和DMA2不支持。並為了防止DMA和CPU同時訪問DMA緩沖造成的數據一致性問題,將這塊空間關閉Cache處理,比如使用的SRAM4:
/* 配置SRAM4的MPU屬性為Non-cacheable */ MPU_InitStruct.Enable = MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress = 0x38000000; MPU_InitStruct.Size = MPU_REGION_SIZE_64KB; MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS; MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE; MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE; MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE; MPU_InitStruct.Number = MPU_REGION_NUMBER2; MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1; MPU_InitStruct.SubRegionDisable = 0x00; MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct);
第6步:初始化SPI。
/* 針對不同的應用程序,添加需要的底層驅動模塊初始化函數 */ bsp_InitSPIBus(); /* 配置SPI總線 */ bsp_InitDAC8562(); /* 初始化配置DAC8562/8563 */
第7步:DAC856X驅動主要用到HAL庫的SPI驅動文件,簡單省事些可以添加所有HAL庫C源文件進來。
第8步:應用方法看本章節配套例子即可。
74.12 DAC856X驅動移植和使用(SPI DMA更新方式)
DAC856X移植步驟如下:
第1步:復制bsp_spidma_dac8562.c,bsp_spidma_dac8562.h到自己的工程目錄,並添加到工程里面。
第2步:根據使用的第幾個SPI,SPI時鍾,SPI引腳和DMA通道等,修改bsp_spidma_dac8562.c文件開頭的宏定義
/* ********************************************************************************************************* * 時鍾,引腳,DMA,中斷等宏定義 ********************************************************************************************************* */ #define SPIx SPI1 #define SPIx_CLK_ENABLE() __HAL_RCC_SPI1_CLK_ENABLE() #define DMAx_CLK_ENABLE() __HAL_RCC_DMA2_CLK_ENABLE() #define SPIx_FORCE_RESET() __HAL_RCC_SPI1_FORCE_RESET() #define SPIx_RELEASE_RESET() __HAL_RCC_SPI1_RELEASE_RESET() /* SYNC, 也就是CS片選 */ #define SPIx_NSS_CLK_ENABLE() __HAL_RCC_GPIOG_CLK_ENABLE() #define SPIx_NSS_GPIO GPIOG #define SPIx_NSS_PIN GPIO_PIN_10 #define SPIx_NSS_AF GPIO_AF5_SPI1 #define SPIx_SCK_CLK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE() #define SPIx_SCK_GPIO GPIOB #define SPIx_SCK_PIN GPIO_PIN_3 #define SPIx_SCK_AF GPIO_AF5_SPI1 #define SPIx_MISO_CLK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE() #define SPIx_MISO_GPIO GPIOB #define SPIx_MISO_PIN GPIO_PIN_4 #define SPIx_MISO_AF GPIO_AF5_SPI1 #define SPIx_MOSI_CLK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE() #define SPIx_MOSI_GPIO GPIOB #define SPIx_MOSI_PIN GPIO_PIN_5 #define SPIx_MOSI_AF GPIO_AF5_SPI1 #define SPIx_TX_DMA_STREAM DMA2_Stream3 #define SPIx_RX_DMA_STREAM DMA2_Stream2 #define SPIx_TX_DMA_REQUEST DMA_REQUEST_SPI1_TX #define SPIx_RX_DMA_REQUEST DMA_REQUEST_SPI1_RX #define SPIx_DMA_TX_IRQn DMA2_Stream3_IRQn #define SPIx_DMA_RX_IRQn DMA2_Stream2_IRQn #define SPIx_DMA_TX_IRQHandler DMA2_Stream3_IRQHandler #define SPIx_DMA_RX_IRQHandler DMA2_Stream2_IRQHandler #define SPIx_IRQn SPI1_IRQn #define SPIx_IRQHandler SPI1_IRQHandler
第3步:根據使用的CLR和LDAC引腳,修改bsp_spidma_dac8562.c文件開頭的宏定義。
/* CLR */ #define CLR_CLK_ENABLE() __HAL_RCC_GPIOE_CLK_ENABLE() #define CLR_GPIO GPIOE #define CLR_PIN GPIO_PIN_4 #define CLR_1() CLR_GPIO->BSRR = CLR_PIN #define CLR_0() CLR_GPIO->BSRR = ((uint32_t)CLR_PIN << 16U) /* LDAC 使用擴展IO ,特別注意,我們這里是用的擴展IO控制的 */ #define LDAC_1() HC574_SetPin(NRF24L01_CE, 1); #define LDAC_0() HC574_SetPin(NRF24L01_CE, 0);
第4步:如果使用DMA方式的話,請不要使用TCM RAM,因為通用DMA1和DMA2不支持。並為了防止DMA和CPU同時訪問DMA緩沖造成的數據一致性問題,將這塊空間關閉Cache處理,比如使用的SRAM4:
/* 配置SRAM4的MPU屬性為Non-cacheable */ MPU_InitStruct.Enable = MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress = 0x38000000; MPU_InitStruct.Size = MPU_REGION_SIZE_64KB; MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS; MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE; MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE; MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE; MPU_InitStruct.Number = MPU_REGION_NUMBER2; MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1; MPU_InitStruct.SubRegionDisable = 0x00; MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct);
第5步:初始化SPI。
/* 針對不同的應用程序,添加需要的底層驅動模塊初始化函數 */ bsp_InitDAC8562(); /* 初始化配置DAC8562/8563 */
第6步:DAC856X驅動主要用到HAL庫的SPI驅動文件,簡單省事些可以添加所有HAL庫C源文件進來。
第7步:應用方法看本章節配套例子即可
74.13 實驗例程設計框架
通過程序設計框架,讓大家先對配套例程有一個全面的認識,然后再理解細節,本次實驗例程的設計框架如下:
第1階段,上電啟動階段:
- 這部分在第14章進行了詳細說明。
第2階段,進入main函數:
- 第1部分,硬件初始化,主要是MPU,Cache,HAL庫,系統時鍾,滴答定時器和LED。
- 第2部分,應用程序設計部分,實現DAC856X的簡易信號發生器功能。。
74.14 實驗例程說明(MDK)
注:本章是配套了兩個例子的,這里我們以SPI DMA方式進行說明。
配套例子:
V7-052_DAC856x簡易信號發生器(雙通道SPI DMA方式,16bit分辨率, 正負10V輸出)
V7-053_DAC856x簡易信號發生器(雙通道SPI查詢方式,16bit分辨率, 正負10V輸出)
實驗目的:
- 學習SPI Flash的DAC8563的SPI DMA驅動方式實現。
實驗內容:
- 雙通道DAC,軌到軌輸出,16bit分辨率,支持50MHz的SPI時鍾速度。
- 自帶2.5V的內部參考基准,典型的溫飄是4ppm/℃,使用內部2.5V參考基准的情況下,根據增益設置不同,DAC的輸出量可以為0到2.5V或者0到5V。
- DAC8562和DAC8563完全兼容,區別僅僅在於CLR引腳有效時,DAC8562數據設置為0, DAC8563數據設置為32767,注意這是DAC的內部數據,不表示輸出電壓。 對於-10 ~ +10V輸出的模塊,DAC8562輸出-10V, DAC8563輸出0V。
- 無論是用DAC8562還是DAC8563芯片,只要軟件不啟動,本模塊輸出電壓缺省狀態都是0V。
- CLR腳懸浮時,電壓在1.9V左右,容易受到干擾導致輸出被清零。因此即使不用CLR控制功能,這個CLR腳也需要接固定電平(推薦接GND)。CLR是邊沿觸發,僅在下降沿信號出現執行清零。
實驗操作:
- 啟動一個自動重裝軟件定時器,每100ms翻轉一次LED2。
- K1鍵按下,雙通道輸出,通道1輸出方波,通道2輸出正弦波。
- K2鍵按下,雙通道輸出方波。
- K3鍵按下,雙通道輸出正弦波。
- 搖桿上鍵按下,通道1停止方波,通道2停止輸出。
- 搖桿下鍵按下,雙通道輸出直流。
- 搖桿OK鍵按下,重新初始化。
上電后串口打印的信息:
波特率 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輸出方波,通道2輸出正弦波。
- K2鍵按下,雙通道輸出方波。
- K3鍵按下,雙通道輸出正弦波。
- 搖桿上鍵按下,通道1停止方波,通道2停止輸出。
- 搖桿下鍵按下,雙通道輸出直流。
- 搖桿OK鍵按下,重新初始化。
/* ********************************************************************************************************* * 函 數 名: main * 功能說明: c程序入口 * 形 參: 無 * 返 回 值: 錯誤代碼(無需處理) ********************************************************************************************************* */ int main(void) { bsp_Init(); /* 硬件初始化 */ PrintfLogo(); /* 打印例程名稱和版本等信息 */ DemoSpiDac(); /* SPI DAC測試 */ } /* ********************************************************************************************************* * 函 數 名: DemoSpiDac * 功能說明: DAC8562測試 * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ void DemoSpiDac(void) { uint8_t i=0; uint8_t ucKeyCode; /* 按鍵代碼 */ sfDispMenu(); /* 打印命令提示 */ bsp_StartAutoTimer(0, 200); /* 啟動1個100ms的自動重裝的定時器 */ /* 生成方波數據 */ for(i =0; i< 50; i++) { ch1buf[i] = 0; } for(i =50; i< 100; i++) { ch1buf[i] = 65535; } /* 生成正弦波數據 */ MakeSinTable(ch2buf, 100, 0, 65535); /* 上電默認雙通道輸出: 第1個參數: 1 表示通道1輸出 2 表示通道2輸出 3 表示通道1和2都輸出 4 表示通道1和2都輸出,並且附加一個控制命令,有效防止傳輸錯誤時恢復,即使插拔模塊也不影響。 最后一個參數: 定時器觸發速度1MHz,觸發1次是一組24bit數據的傳輸。 推薦范圍100Hz - 1MHz。 */ DAC8562_SetDacDataDMA(4, ch1buf, ch2buf, sizeof(ch1buf)/sizeof(uint16_t), sizeof(ch2buf)/sizeof(uint16_t), 1000000); while(1) { bsp_Idle(); /* 這個函數在bsp.c文件。用戶可以修改這個函數實現CPU休眠和喂狗 */ /* 判斷定時器超時時間 */ if (bsp_CheckTimer(0)) { /* 每隔100ms 進來一次 */ bsp_LedToggle(2); } /* 按鍵濾波和檢測由后台systick中斷服務程序實現,我們只需要調用bsp_GetKey讀取鍵值即可。 */ ucKeyCode = bsp_GetKey(); /* 讀取鍵值, 無鍵按下時返回 KEY_NONE = 0 */ if (ucKeyCode != KEY_NONE) { switch (ucKeyCode) { case KEY_DOWN_K1: /* K1鍵按下,雙通道輸出,通道1輸出方波,通道2輸出正弦波 */ /* 生成方波數據 */ for(i =0; i< 50; i++) { ch1buf[i] = 0; } for(i =50; i< 100; i++) { ch1buf[i] = 65535; } /* 生成正弦波數據 */ MakeSinTable(ch2buf, 100, 0, 65535); /* 上電默認雙通道輸出,觸發速度1MHz */ DAC8562_SetDacDataDMA(3, ch1buf, ch2buf, sizeof(ch1buf)/sizeof(uint16_t), sizeof(ch2buf)/sizeof(uint16_t), 1000000); break; case KEY_DOWN_K2: /* K2鍵按下,雙通道輸出方波 */ /* 生成正弦波數據 */ MakeSinTable(ch2buf, 100, 0, 65535); /* 上電默認雙通道輸出,觸發速度1MHz */ DAC8562_SetDacDataDMA(3, ch2buf, ch2buf, sizeof(ch2buf)/sizeof(uint16_t), sizeof(ch2buf)/sizeof(uint16_t), 1000000); break; case KEY_DOWN_K3: /* K3鍵按下,雙通道輸出正弦波 */ /* 生成方波數據 */ for(i =0; i< 50; i++) { ch1buf[i] = 0; } for(i =50; i< 100; i++) { ch1buf[i] = 65535; } /* 上電默認雙通道輸出,觸發速度1MHz */ DAC8562_SetDacDataDMA(3, ch1buf, ch1buf, sizeof(ch1buf)/sizeof(uint16_t), sizeof(ch1buf)/sizeof(uint16_t), 1000000); break; case JOY_DOWN_U: /* 搖桿上鍵按下,通道1停止方波,通道2停止輸出 */ /* 通道1輸出方波 */ for(i =0; i< 50; i++) { ch1buf[i] = 0; } for(i =50; i< 100; i++) { ch1buf[i] = 65535; } /* 僅通道1輸出 */ DAC8562_SetDacDataDMA(1, ch1buf, ch2buf, sizeof(ch1buf)/sizeof(uint16_t), sizeof(ch2buf)/sizeof(uint16_t), 1000000); break; case JOY_DOWN_D: /* 搖桿下鍵按下,雙通道輸出直流 */ /* 通道1輸出負數10V */ DAC8562_SetDacData(0, 0); /* 通道2輸出正10V */ DAC8562_SetDacData(1, 65535); break; case JOY_DOWN_OK: /* 搖桿OK鍵按下,重新初始化 */ /* 初始化配置DAC8562/8563 */ //bsp_InitDAC8562(); DAC8562_WriteCmd((7 << 19) | (0 << 16) | (1 << 0)); break; default: /* 其它的鍵值不處理 */ break; } } } }
74.15 實驗例程說明(IAR)
注:本章是配套了兩個例子的,這里我們以SPI DMA方式進行說明。
配套例子:
V7-052_DAC856x簡易信號發生器(雙通道SPI DMA方式,16bit分辨率, 正負10V輸出)
V7-053_DAC856x簡易信號發生器(雙通道SPI查詢方式,16bit分辨率, 正負10V輸出)
實驗目的:
- 學習SPI Flash的DAC8563的SPI DMA驅動方式實現。
實驗內容:
- 雙通道DAC,軌到軌輸出,16bit分辨率,支持50MHz的SPI時鍾速度。
- 自帶2.5V的內部參考基准,典型的溫飄是4ppm/℃,使用內部2.5V參考基准的情況下,根據增益設置不同,DAC的輸出量可以為0到2.5V或者0到5V。
- DAC8562和DAC8563完全兼容,區別僅僅在於CLR引腳有效時,DAC8562數據設置為0, DAC8563數據設置為32767,注意這是DAC的內部數據,不表示輸出電壓。 對於-10 ~ +10V輸出的模塊,DAC8562輸出-10V, DAC8563輸出0V。
- 無論是用DAC8562還是DAC8563芯片,只要軟件不啟動,本模塊輸出電壓缺省狀態都是0V。
- CLR腳懸浮時,電壓在1.9V左右,容易受到干擾導致輸出被清零。因此即使不用CLR控制功能,這個CLR腳也需要接固定電平(推薦接GND)。CLR是邊沿觸發,僅在下降沿信號出現執行清零。
實驗操作:
- 啟動一個自動重裝軟件定時器,每100ms翻轉一次LED2。
- K1鍵按下,雙通道輸出,通道1輸出方波,通道2輸出正弦波。
- K2鍵按下,雙通道輸出方波。
- K3鍵按下,雙通道輸出正弦波。
- 搖桿上鍵按下,通道1停止方波,通道2停止輸出。
- 搖桿下鍵按下,雙通道輸出直流。
- 搖桿OK鍵按下,重新初始化。
上電后串口打印的信息:
波特率 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輸出方波,通道2輸出正弦波。
- K2鍵按下,雙通道輸出方波。
- K3鍵按下,雙通道輸出正弦波。
- 搖桿上鍵按下,通道1停止方波,通道2停止輸出。
- 搖桿下鍵按下,雙通道輸出直流。
- 搖桿OK鍵按下,重新初始化。
/* ********************************************************************************************************* * 函 數 名: main * 功能說明: c程序入口 * 形 參: 無 * 返 回 值: 錯誤代碼(無需處理) ********************************************************************************************************* */ int main(void) { bsp_Init(); /* 硬件初始化 */ PrintfLogo(); /* 打印例程名稱和版本等信息 */ DemoSpiDac(); /* SPI DAC測試 */ } /* ********************************************************************************************************* * 函 數 名: DemoSpiDac * 功能說明: DAC8562測試 * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ void DemoSpiDac(void) { uint8_t i=0; uint8_t ucKeyCode; /* 按鍵代碼 */ sfDispMenu(); /* 打印命令提示 */ bsp_StartAutoTimer(0, 200); /* 啟動1個100ms的自動重裝的定時器 */ /* 生成方波數據 */ for(i =0; i< 50; i++) { ch1buf[i] = 0; } for(i =50; i< 100; i++) { ch1buf[i] = 65535; } /* 生成正弦波數據 */ MakeSinTable(ch2buf, 100, 0, 65535); /* 上電默認雙通道輸出: 第1個參數: 1 表示通道1輸出 2 表示通道2輸出 3 表示通道1和2都輸出 4 表示通道1和2都輸出,並且附加一個控制命令,有效防止傳輸錯誤時恢復,即使插拔模塊也不影響。 最后一個參數: 定時器觸發速度1MHz,觸發1次是一組24bit數據的傳輸。 推薦范圍100Hz - 1MHz。 */ DAC8562_SetDacDataDMA(4, ch1buf, ch2buf, sizeof(ch1buf)/sizeof(uint16_t), sizeof(ch2buf)/sizeof(uint16_t), 1000000); while(1) { bsp_Idle(); /* 這個函數在bsp.c文件。用戶可以修改這個函數實現CPU休眠和喂狗 */ /* 判斷定時器超時時間 */ if (bsp_CheckTimer(0)) { /* 每隔100ms 進來一次 */ bsp_LedToggle(2); } /* 按鍵濾波和檢測由后台systick中斷服務程序實現,我們只需要調用bsp_GetKey讀取鍵值即可。 */ ucKeyCode = bsp_GetKey(); /* 讀取鍵值, 無鍵按下時返回 KEY_NONE = 0 */ if (ucKeyCode != KEY_NONE) { switch (ucKeyCode) { case KEY_DOWN_K1: /* K1鍵按下,雙通道輸出,通道1輸出方波,通道2輸出正弦波 */ /* 生成方波數據 */ for(i =0; i< 50; i++) { ch1buf[i] = 0; } for(i =50; i< 100; i++) { ch1buf[i] = 65535; } /* 生成正弦波數據 */ MakeSinTable(ch2buf, 100, 0, 65535); /* 上電默認雙通道輸出,觸發速度1MHz */ DAC8562_SetDacDataDMA(3, ch1buf, ch2buf, sizeof(ch1buf)/sizeof(uint16_t), sizeof(ch2buf)/sizeof(uint16_t), 1000000); break; case KEY_DOWN_K2: /* K2鍵按下,雙通道輸出方波 */ /* 生成正弦波數據 */ MakeSinTable(ch2buf, 100, 0, 65535); /* 上電默認雙通道輸出,觸發速度1MHz */ DAC8562_SetDacDataDMA(3, ch2buf, ch2buf, sizeof(ch2buf)/sizeof(uint16_t), sizeof(ch2buf)/sizeof(uint16_t), 1000000); break; case KEY_DOWN_K3: /* K3鍵按下,雙通道輸出正弦波 */ /* 生成方波數據 */ for(i =0; i< 50; i++) { ch1buf[i] = 0; } for(i =50; i< 100; i++) { ch1buf[i] = 65535; } /* 上電默認雙通道輸出,觸發速度1MHz */ DAC8562_SetDacDataDMA(3, ch1buf, ch1buf, sizeof(ch1buf)/sizeof(uint16_t), sizeof(ch1buf)/sizeof(uint16_t), 1000000); break; case JOY_DOWN_U: /* 搖桿上鍵按下,通道1停止方波,通道2停止輸出 */ /* 通道1輸出方波 */ for(i =0; i< 50; i++) { ch1buf[i] = 0; } for(i =50; i< 100; i++) { ch1buf[i] = 65535; } /* 僅通道1輸出 */ DAC8562_SetDacDataDMA(1, ch1buf, ch2buf, sizeof(ch1buf)/sizeof(uint16_t), sizeof(ch2buf)/sizeof(uint16_t), 1000000); break; case JOY_DOWN_D: /* 搖桿下鍵按下,雙通道輸出直流 */ /* 通道1輸出負數10V */ DAC8562_SetDacData(0, 0); /* 通道2輸出正10V */ DAC8562_SetDacData(1, 65535); break; case JOY_DOWN_OK: /* 搖桿OK鍵按下,重新初始化 */ /* 初始化配置DAC8562/8563 */ //bsp_InitDAC8562(); DAC8562_WriteCmd((7 << 19) | (0 << 16) | (1 << 0)); break; default: /* 其它的鍵值不處理 */ break; } } } }
74.16 總結
本章節涉及到的知識點非常多,特別是SPI DMA方式驅動的實現方法,需要大家稍花點精力去研究。