本文例子參考《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檔案,點擊運行,可以看到示波器中顯示的波形為正弦波(注意示波器的調整),

轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/483698.html
標籤:嵌入式
