工程環境:
正弦波曲線的函數公式是:y=sin(x)
y的範圍區間是[-1:1]
x的取值範圍是任意實數
週期為2π
如下圖所示的藍色函數曲線:
使用DAC生成正弦波比較方便的方法是預先生成一個正弦波的資料點表,為了能夠快速設定到DAC上所有會使用到DMA,然後通過定時器控制DAC的出樣頻率就達到了生成正弦波的效果。
那麼這個正弦波資料點表是怎麼生成的呢?下面就來講解一下。
將這個y=sin(x)函數對映成我們現在的這個正弦波,那麼y就是代表的電壓,x代表的週期。
由於y=sin(x)的值範圍在[-1:1]之間,DAC設定的時候不存在負數,所以就需要加1讓公式生成的值都在正數範圍內,公式就變成了y=six(x) + 1,現在值範圍就成了[0:2],但是這樣最高能表示到2V,而DAC是能輸出到3.3V的,也就是說y=six(x) + 1輸出2V的時候代表3.3V,所以就需要對y=six(x) + 1進行擴大,比值關係就是3.3V:2V,所以公式又變成了y=(sin(x) + 1)*(3.3/2),這樣值範圍就變為了[0:3.3],然後再將電壓轉為DAC數值就可以了。
因為正弦波每週期的波形都是一樣的,所以我們生成一個週期的資料表即可。
經試驗所得,一個週期內滿足32個點就能近似逼近正弦波的效果,這裡為了波形更好看,我選擇了100個點。
週期為2π,一共100個點,那麼每兩個點的間距就是2π/100。
/**
* 生成正弦波資料點函數
* @param NPoints 一個週期內的點數
* @param VMaxRange 輸出的電壓最大值,取值範圍0~3.3V
* @param SineWaveTable 存放生成的資料點
*/
void SineWaveGen(uint32_t NPoints, float VMaxRange, uint16_t* SineWaveTable)
{
#ifndef PI
#define PI 3.14159265358979323846
#endif
int i = 0;
double radian = 0; // 弧度
double setup = 0; // 弧度和弧度之間的大小
double voltage = 0; // 輸出電壓
setup = (2 * PI) / NPoints; // 兩點之間的間距
while (i < NPoints)
{
voltage = VMaxRange / 2.0 * (sin(radian) + 1.0); // 計算電壓
SineWaveTable[i] = (uint16_t)(voltage * 4096 / 3.3); // 電壓轉為DAC數值
radian += setup; // 下一個點的弧度
i++;
}
}
說明:
Output Buffer:輸出快取
DAC 整合了 2 個輸出快取,可以用來減少輸出阻抗,無需外部運放即可直接驅動外部負載。每個 DAC 通道輸出快取可以通過設定 DAC_CR 暫存器的 BOFFx 位來使能或者關閉。如果帶載能力還不行,後面就接一個電壓跟隨器,選擇運放一定要選擇電流大的型號。
使能輸出緩衝後,DAC 輸出的最小電壓為 0.2V,最大電壓為 VREF±0.2,而未使能輸出緩衝則輸出可達到0V。
Tigger:觸發方式
選擇DAC的觸發方式,可以選擇為定時器觸發、外部中斷觸發和軟體觸發。這裡我選擇了定時器6來觸發DAC,因為通過設定定時器的頻率就可以很方便的控制DAC輸出的正弦波頻率。
Wave generation mode:波形發生器
我這裡沒有使用。
定時器6的時鐘主頻為72MHz,我這裡沒有分頻,那麼把過載值設定為72,這樣就得到了72M/72=1MHz的觸發頻率。
上面說到過我的設定是一個週期內100個點,定時器觸發頻率為1MHz,觸發一百次才能完成一個週期的波形,所以生成的波形頻率就是1MHz/100個點=10KHz。
最後啟動定時器和DMA傳輸即可:
HAL_TIM_Base_Start(&htim6);
HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_2, (uint32_t *)SineWaveTable, POINTS, DAC_ALIGN_12B_R);
生成的波形用示波器檢視如下: