1. 程式人生 > >微控制器開發之波形發生器

微控制器開發之波形發生器

 因客戶需求,需要使用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正弦波也是挺正常的,就是階梯狀明顯了些,