单片机开发之波形发生器

 因客户需求,需要使用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正弦波也是挺正常的,就是阶梯状明显了些,

猜你喜欢

转载自www.cnblogs.com/MzMxMyg/p/9886254.html