1. 程式人生 > 其它 >18-CubeMx+Keil+Proteus模擬STM32 - DAC

18-CubeMx+Keil+Proteus模擬STM32 - DAC

本文例子參考《STM32微控制器開發例項——基於Proteus虛擬模擬與HAL/LL庫》
原始碼:https://github.com/LanLinnet/STM33F103R6

專案要求

在SPI匯流排通訊的基礎上,使用微控制器控制DAC晶片MCP4921以1秒為週期輸出正弦波,正弦波的波動範圍為0-3.3V。

硬體設計

  1. 第一節的基礎上,在Proteus中新增電路如下圖所示。其中我們添加了一個DAC晶片MCP4921

    此外,我們還添加了兩個虛擬儀表:一個示波器OSCILLOSCOPE和一個SPI匯流排除錯工具SPI DEBUGGER

  2. 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時進入待機模式。
  3. 正弦波形的生成:
    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秒計算一次取樣值,其取樣表如下表所示。

  4. 開啟CubeMX,建立工程。STM32F103R6微控制器自帶一個SPI模組,但是為了便於移植,本專案中採用GPIO引腳模擬SPI時序。設定PA4、PA5、PA7均為GPIO_Output點選“Categories”中的“GPIO”,修改GPIO各引數如下圖所示。有關SPI通訊部分可以參考

    第17節

  5. 點選“Generator Code”生成Keil工程。

軟體編寫

  1. 考慮到程式碼的可移植性,這裡將SPI和MCP4921的驅動程式碼全部封裝成函式並分別歸入標頭檔案“vSPI.h”和“MCP4921.h”中。我們可以先在...\Core\Src資料夾中建立這兩個標頭檔案,此時Keil可能找不到對應檔案,可以直接將檔案拽入Keil中進行編輯,然後再在“main.c”檔案中進行include。

  2. 點選“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_ */
    
  3. 隨後我們需要在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 */
    

聯合除錯

  1. 點選執行,生成HEX檔案。
  2. 在Proteus中載入相應HEX檔案,點選執行。可以看到示波器中顯示的波形為正弦波(注意示波器的調整)。