完整教程下載下傳位址: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 總結
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檔案裡。
在本教程的第74章進行了詳細說明。
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子產品的插座的原理圖如下:
實際對應開發的位置如下:
驅動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引腳的輸入決定。
輸出電壓。
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 ---> 輸出高阻
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總線配置
/*
*********************************************************************************************************
* 函 數 名: 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小節内容是一樣的。
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涉及到的函數比較多,我們主要介紹用到的如下幾個函數:
- DAC8501_SetDacDataDMA
75.9.1 函數bsp_InitDAC8501
主要用于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
- 第1個參數為0表示通道1,為1表示通道2(對于SPI DMA方式,僅支援通道1)。
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更新方式)
- 第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)
/*
*********************************************************************************************************
* 函 數 名: bsp_Init
* 功能說明: 初始化所有的硬體裝置。該函數配置CPU寄存器和外設的寄存器并初始化一些全局變量。隻需要調用一次
* 形 參:無
* 返 回 值: 無
*********************************************************************************************************
*/
void bsp_Init(void)
{
/* 配置MPU */
MPU_Config();
/* 使能L1 Cache */
CPU_CACHE_Enable();
/*
STM32H7xx HAL 庫初始化,此時系統用的還是H7自帶的64MHz,HSI時鐘:
- 調用函數HAL_InitTick,初始化滴答時鐘中斷1ms。
- 設定NVIV優先級分組為4。
*/
HAL_Init();
/*
配置系統時鐘到400MHz
- 切換使用HSE。
- 此函數會更新全局變量SystemCoreClock,并重新配置HAL_InitTick。
*/
SystemClock_Config();
/*
Event Recorder:
- 可用于代碼執行時間測量,MDK5.25及其以上版本才支援,IAR不支援。
- 預設不開啟,如果要使能此選項,務必看V7開發闆使用者手冊第xx章
*/
#if Enable_EventRecorder == 1
/* 初始化EventRecorder并開啟 */
EventRecorderInitialize(EventRecordAll, 1U);
EventRecorderStart();
#endif
bsp_InitDWT(); /* 初始化DWT時鐘周期計數器 */
bsp_InitKey(); /* 按鍵初始化,要放在滴答定時器之前,因為按鈕檢測是通過滴答定時器掃描 */
bsp_InitTimer(); /* 初始化滴答定時器 */
bsp_InitLPUart(); /* 初始化序列槽 */
bsp_InitExtIO(); /* 初始化FMC總線74HC574擴充IO. 必須在 bsp_InitLed()前執行 */
bsp_InitLed(); /* 初始化LED */
bsp_InitExtSDRAM(); /* 初始化SDRAM */
/* 針對不同的應用程式,添加需要的底層驅動子產品初始化函數 */
bsp_InitDAC8562(); /* 初始化配置DAC8562/8563 */
}
/*
*********************************************************************************************************
* 函 數 名: MPU_Config
* 功能說明: 配置MPU
* 形 參: 無
* 返 回 值: 無
*********************************************************************************************************
*/
static void MPU_Config( void )
{
MPU_Region_InitTypeDef MPU_InitStruct;
/* 禁止 MPU */
HAL_MPU_Disable();
/* 配置AXI SRAM的MPU屬性為Write back, Read allocate,Write allocate */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x24000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_512KB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER0;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
/* 配置FMC擴充IO的MPU屬性為Device或者Strongly Ordered */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x60000000;
MPU_InitStruct.Size = ARM_MPU_REGION_SIZE_64KB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER1;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
/* 配置SRAM4的MPU屬性為Non-cacheable */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x38000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_64KB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER2;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
/*使能 MPU */
HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}
/*
*********************************************************************************************************
* 函 數 名: CPU_CACHE_Enable
* 功能說明: 使能L1 Cache
* 形 參: 無
* 返 回 值: 無
*********************************************************************************************************
*/
static void CPU_CACHE_Enable(void)
{
/* 使能 I-Cache */
SCB_EnableICache();
/* 使能 D-Cache */
SCB_EnableDCache();
}
/*
*********************************************************************************************************
* 函 數 名: bsp_RunPer10ms
* 功能說明: 該函數每隔10ms被Systick中斷調用1次。詳見 bsp_timer.c的定時中斷服務程式。一些處理時間要求
* 不嚴格的任務可以放在此函數。比如:按鍵掃描、蜂鳴器鳴叫控制等。
* 形 參: 無
* 返 回 值: 無
*********************************************************************************************************
*/
void bsp_RunPer10ms(void)
{
bsp_KeyScan10ms();
}
/*
*********************************************************************************************************
* 函 數 名: main
* 功能說明: c程式入口
* 形 參: 無
* 返 回 值: 錯誤代碼(無需處理)
*********************************************************************************************************
*/
int main(void)
{
bsp_Init(); /* 硬體初始化 */
PrintfLogo(); /* 列印例程名稱和版本等資訊 */
DemoSpiDac(); /* SPI DAC測試 */
}
/*
*********************************************************************************************************
* 函 數 名: DemoSpiDac
* 功能說明: 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方式驅動的實作方法,需要大家稍花點精力去研究。
微信公衆号:armfly_com
安富萊論壇:www.armbbs.cn
安富萊淘寶:https://armfly.taobao.com