本文例子參考《STM32單片機開發實例——基於Proteus虛擬模擬與HAL/LL庫》 源代碼:https://github.com/LanLinnet/STM33F103R6 項目要求 在SPI匯流排通信的基礎上,使用單片機控制DAC晶元MCP4921以1秒為周期輸出正弦波,正弦波的波動範圍為0-3 ...
本文例子參考《STM32單片機開發實例——基於Proteus虛擬模擬與HAL/LL庫》
源代碼:https://github.com/LanLinnet/STM33F103R6
項目要求
在SPI匯流排通信的基礎上,使用單片機控制DAC晶元MCP4921以1秒為周期輸出正弦波,正弦波的波動範圍為0-3.3V。
硬體設計
-
在第一節的基礎上,在Proteus中添加電路如下圖所示。其中我們添加了一個DAC晶元
MCP4921
。
此外,我們還添加了兩個虛擬儀錶:一個示波器OSCILLOSCOPE
和一個SPI匯流排調試工具SPI DEBUGGER
。
-
MCP4921:
1)簡介:STM32F103R6單片機本身不自帶DAC,如果設計到數模轉換的項目,可以選擇DAC晶元MCP4921。MCP4921是美國Microchip公司的串列12位DAC晶元,相容SPI,最高通信頻率為20MHz,一次轉換時間為4.5μs,工作電壓為2.7-5.5V。
2)引腳:MCP4921引腳的功能如下表所示。
3)通信數據格式:MCP4921只有數據輸入,沒有數據輸出,單片機只需要將16位數據(12位數字量和4位配置信息)一起打包發給DAC晶元,DAC隨即開始數模轉換過程。MCP4921通信數據格式如下表所示。
- \(\overline A/B\)位:對於MCP4921,由於只有A通道,所以該位只能選0。
- BUF位:參考電壓\(V_{REF}\)輸入緩衝器控制位,設1時緩衝,設0時未緩衝。
- \(\overline{GA}\)位:輸出增益選擇位,設1時無增益,設0時兩倍增益。
- \(\overline{SHDN}\)位:待機模式設置為,設1時不進入待機模式,設0時進入待機模式。
-
正弦波形的生成:
1)存在問題:MCP4921是12位DAC晶元,因此輸入數字量的範圍是0x000-0x3FF,輸出模擬量電壓範圍為0-\(V_{REF}\),即無法輸出負電壓,那麼就無法輸出完整的正弦波形。
2)解決方案:- 通過外圍元器件搭建調理電路使電路能夠輸出負電壓。
- 將正弦波信號沿縱軸(電壓/數字量)正向移動,確保波谷也位於橫軸(時間)的上方。
3)採樣表:這裡我們選擇後一個方案,可以推出正弦波計算公式為
\(D=512\times\sin\left(2\pi\;t\right)+512\)
為了提高單片機CPU的執行效率,這裡我們使用查表法。在1秒內,每隔0.02秒計算一次採樣值,其採樣表如下表所示。
-
打開CubeMX,建立工程。STM32F103R6單片機自帶一個SPI模塊,但是為了便於移植,本項目中採用GPIO引腳模擬SPI時序。設置PA4、PA5、PA7均為
GPIO_Output
點擊“Categories”中的“GPIO”,修改GPIO各參數如下圖所示。有關SPI通信部分可以參考第17節。
-
點擊“Generator Code”生成Keil工程。
軟體編寫
-
考慮到代碼的可移植性,這裡將SPI和MCP4921的驅動代碼全部封裝成函數並分別歸入頭文件“vSPI.h”和“MCP4921.h”中。我們可以先在
...\Core\Src
文件夾中建立這兩個頭文件,此時Keil可能找不到對應文件,可以直接將文件拽入Keil中進行編輯,然後再在“main.c”文件中進行include。 -
點擊“Open Project”在Keil中打開工程,打開“vSPI.h”,添加代碼如下。
#ifndef INC_VSPI_H_ #define INC_VSPI_H_ #include "main.h" //軟體延時函數,單位為微秒 void delay_us(uint16_t n) { uint16_t i = n * 8; while(i--); } //SPI匯流排使能 void vSPI_En() { HAL_GPIO_WritePin(GPIOA, vnCS_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOA, vSCK_Pin, GPIO_PIN_RESET); delay_us(4); } //SPI匯流排禁止 void vSPI_Dis() { HAL_GPIO_WritePin(GPIOA, vSCK_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOA, vnCS_Pin, GPIO_PIN_SET); } //SPI主站發送1位元組 void vSPI_SndByte(uint8_t dat) //dat表示發送的位元組 { uint8_t i; for(i=0; i<8; i++) { HAL_GPIO_WritePin(GPIOA, vSCK_Pin, GPIO_PIN_RESET); delay_us(4); if(dat & 0x80) { HAL_GPIO_WritePin(GPIOA, vMOSI_Pin, GPIO_PIN_SET); } else HAL_GPIO_WritePin(GPIOA, vMOSI_Pin, GPIO_PIN_RESET); dat<<=1; //上升沿 HAL_GPIO_WritePin(GPIOA, vSCK_Pin, GPIO_PIN_SET); delay_us(4); } } #endif /* INC_VSPI_H_ */
打開“MCP4921.h”,添加代碼如下。
#ifndef INC_MCP4921_H_ #define INC_MCP4921_H_ #include "main.h" #include "vSPI.h" //寫入MCP4921: Cmd-指令(僅高4位) Dat-數據(12位) void MCP4921Write(uint8_t Cmd, uint16_t Dat) { uint8_t DatM, DatL; //數據高位元組、低位元組 DatL = (uint8_t)(Dat & 0x00ff); DatM = (uint8_t)((Dat>>8) & 0x00ff); vSPI_En(); //SPI匯流排使能 vSPI_SndByte(0x70|DatM); //先寫高位元組 vSPI_SndByte(DatL); //再寫低位元組 vSPI_Dis(); //SPI匯流排禁止 } #endif /* INC_MCP4921_H_ */
-
隨後我們需要在main.c文件中的最前面引入我們自定義的頭文件
/* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ #include "vSPI.h" //引入自定義頭文件 #include "MCP4921.h" /* USER CODE END Includes */
在全局中定義正弦波輸出的表
/* USER CODE BEGIN PV */ //查表法 static uint16_t tD[50] = { 512, 576, 639, 700, 759, 813, 862, 907, 944, 975, 999, 1015, 1023, 1023, 1015, 999, 975, 944, 907, 862, 813, 759, 700, 639, 576, 512, 448, 385, 324, 265, 211, 162, 117, 80, 49, 25, 9, 1, 1, 9, 25, 49, 80, 117, 162, 211, 265, 324, 385, 448 }; /* USER CODE END PV */
最後,在main函數中定義迴圈變數,並調用我們自定義的函數每隔20ms計算一次採樣值並輸出
/* USER CODE BEGIN 1 */ int i; //迴圈變數i /* USER CODE END 1 */
/* USER CODE BEGIN WHILE */ while (1) { for(i=0; i<50; i++) { MCP4921Write(0x70, tD[i]); HAL_Delay(20); //每隔20ns計算(輸出)1次採樣值 } /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */
聯合調試
- 點擊運行,生成HEX文件。
- 在Proteus中載入相應HEX文件,點擊運行。可以看到示波器中顯示的波形為正弦波(註意示波器的調整)。