微控制器開發之波形發生器
因客戶需求,需要使用stm32微控制器做一個40HZ~150HZ的低頻正弦波,方波發生器。這篇文章記錄思路及遇到問題的解決,不再做微控制器外設配置的說明,並且力求程式能夠方便移植到其他微控制器。
上網查看了許多例程大多是仿照野火哥(不知道原子哥怎麼實現)利用matlab生成好一段正弦波的陣列,配置好DMA及定時器觸發將這段陣列寫到暫存器,讓DAC引腳按照固定的資料進行模擬量輸出以達到輸出正弦波的目的。這種方式如果要更改峰值和輸出頻率就變得不那麼方便了。按照我一貫的程式設計習慣希望軟體和具體硬體分離的越開越好。對於這次專案開發希望硬體提供定時器,及獲取ADC(電位器設定頻率峰值),輸出DAC三個主要函式。基於這個要求DAC配置為不適用觸發方式,而且呼叫函式寫入希望的DAC值才輸出相應電壓。
考慮到方便頻率和峰值的調節,以及波形的變換,希望能有一個函式輸入頻率,波形等設定引數能形成一個數組用於波形輸出。再使用定時器按固定的頻率依次輸出陣列內的ADC值。那麼對這個陣列有一個要求頻率不同,輸出點數不同。
例如DAC輸出兩個不同電壓值的間隔時間為50us那麼如果10HZ和100HZ都只是輸出36次不同電壓值那麼100HZ的波形肯定比10HZ的波形要平滑一些。所以輸出間隔相同那麼輸出的點數應該頻率越低輸出越多次(頻率越低,一次波形輸出的完成需要更多時間)。
這樣只要有了這個陣列,那麼無論什麼頻率的波形都能有一樣的平滑。接下來以正弦波為例記錄整個波形輸出的流程:
1.正弦波sin值的獲取:
不使用已經算好的陣列資料那麼只能使用math庫提供的sin函式來獲取sin的資料了。按照我的習慣是x = 角度度數。y=sin(x)來獲取sin值。數學庫提供的SIN是傳入弧度,那麼這裡需要做一個轉換
double get_sin(double angle)//輸入角度返回sin值
{
const double pi=acos(-1.0);
double r;
double ret;
r=angle*pi/180;
ret=sin(r);
return ret;
}
使用get_sin函式輸入0~360(浮點數),即可獲得對應的sin值。
2.計算不同頻率下需要的輸出點數(dac引腳的電壓改變次數)
在計算點數之前需要確定頻率freq,和兩次DAC輸出的間隔interval
1次波形需要的時間
T1=1000000us /freq 得出一次波形輸出需要多少微妙
需要輸出多少次才能完成一個波形的輸出:(輸出點數)
times = T1 / interval
兩次輸出的角度差:
incre = 360.0/times 輸出浮點數 可直接傳入get_sin獲得sin值
void set_wave(Wave *wave,u32 freq,u32 interval)
{
const u32 _1s = 1000000;//(us)
wave->Data = wavedat; //快取地址
wave->freq = freq; //頻率
wave->out_interval =interval; //輸出間隔
wave->out_times = _1s/wave->freq /wave->out_interval;//輸出次數
wave->incre_angle = 360.0/wave->out_times; //每次輸出角度增量
}
3.獲得陣列
到這一步已經知道一個頻率下需要輸出n次,每次輸出對應增加x角度,那麼只需要將x增加n次每次呼叫get_sin函式就能獲取所有的sin點了
void set_sin_wave_data(Wave *wave)
{
u32 count =0;
double r = 0;
for(count =0;count < wave->out_times;count++ )
{
r += wave->incre_angle;
wave->Data[count] = (get_sin(r)+1);//將sin返回全部變成正值
}
}
4.封裝DAC輸出函式
DAC輸出向暫存器寫入16位的資料最大為4095對應參考電壓VEF的值。為了方便使用將封裝函式:傳入電壓值單位為mv,DAC引腳輸出對應電壓。
整個函式的意思就是傳入電壓vol是最大電壓VEF的百分幾,那麼就向暫存器寫入4095的百分之幾。
#define VEF 3300
void set_ch1_vol(u32 vol)
{
float dac_value =0;
if(vol > VEF ) vol = VEF;
dac_value = ((float)vol/VEF) * 4095;
DAC_SetChannel1Data(DAC_Align_12b_R,(u16)dac_value);
DAC_SoftwareTriggerCmd(DAC_Channel_1,ENABLE);
}
5.輸出波形
來到這一步已經是萬事俱備只欠輸出了。這裡需要配置微控制器的定時器,將定時時間配置成與輸出間隔相等。之前獲得的陣列
wave->Data[count] = (get_sin(r)+1);
是0~2的資料並不能直接輸出到DAC,如果不+Data陣列最大是1最小是-1,+1之後這段資料最大時2對應峰值,最小是0(如果不+1會產生伏電壓微控制器不能輸出負電壓)。這時候只要峰值電壓V/2 乘上Data數組裡的值便可以將這個值填入DAC暫存器。那麼定時器每中斷一次按順序填入V/2 *Data[n]即可輸出波形。本次設定50um為輸出間隔,下面是50um定時器的中斷函式
void t2_irq()//50us
{
short Data = 0;
static u32 count=0;
Data = (max_wave_vol/2)*wave.Data[count];
set_ch1_vol(Data);
if(++count >= wave.out_times)
count=0;
}
程式到這裡波形已經輸出了,如下圖。40HZ 峰值2000mv
但是這時候出現了問題,在負半軸最低點是平的。
使用串列埠示波器打印出輸出的DAC值卻沒有這種情況,最後用示波器測得最低點有100多個mv,再檢視串列埠輸出資料最低點ADC=0那麼應該沒有電壓輸出。這時猜想stm32微控制器的dac不能輸出絕對的0V電壓。
那麼既然不能輸出絕對的0V那麼把整個波形擡高100mv那麼這樣波形就能完整了吧,於是改定時器中斷函式為
void t2_irq()//50us
{
short Data = 0;
static u32 count=0;
Data = (max_wave_vol/2)*wave.Data[count];
/*base_vol =100將整體電壓提高100mv*/
set_ch1_vol(Data+base_vol);
if(++count >= wave.out_times)
count=0;
}
這樣一來,40HZ~50HZ 頻率峰值可調正弦波就出來了。
最後嘗試設定了1KH,輸出間隔為10us,嘗試過低於8us就不正常了。1KH正弦波也是挺正常的,就是階梯狀明顯了些,