DDS正弦信号发生器

DDS的基本工作流程就是通过相位的累加,得到当前的相位值,然后用当前的相位值(转换为ROM地址)进行查表,得到相应的幅值,然后输出

 

 

通过一些计算得出一个公式:

 

 

 

DDS正弦发生器的输出频率计算表达式:

fo = (fclk/2^N)*K

fo:输出正弦信号频率;fclk:输入脉冲频率(比如定时期中断频率);N:定时器里面的累加器的位数;比如32位、19位;N其实包含了DA位数,或者叫正弦波数据的数据位宽(256个数的正弦就会占N中的8位)。K:频率控制字,个人理解为累加器的累加因子,也就是每次进入定时器加多少。比如32位累加器,da值数据位宽占8位,则定时进入后累加K,一直累加到累加器的最高8位有数据变化,则输出AD值。

 

举例:

 

其实1.9HZ既是步进,也是能输出的最低频率,这时频率控制字=1,已经低于要求的100HZ。当要输出100HZ时,K=100*2^19/1e6约等于52.

 

程序例子:

用excel或者python算出256个点的sin值:

volatile unsigned char waveform[256] = {128, 131, 134, 137, 140, 143, 146, 149,
    152, 156, 159, 162, 165, 168, 171, 174, 176, 179, 182, 185, 188, 191, 193,
    196, 199, 201, 204, 206, 209, 211, 213, 216, 218, 220, 222, 224, 226, 228,
    230, 232, 234, 236, 237, 239, 240, 242, 243, 245, 246, 247, 248, 249, 250,
    251, 252, 252, 253, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255,
    255, 255, 254, 254, 253, 252, 252, 251, 250, 249, 248, 247, 246, 245, 243,
    242, 240, 239, 237, 236, 234, 232, 230, 228, 226, 224, 222, 220, 218, 216,
    213, 211, 209, 206, 204, 201, 199, 196, 193, 191, 188, 185, 182, 179, 176,
    174, 171, 168, 165, 162, 159, 156, 152, 149, 146, 143, 140, 137, 134, 131,
    127, 124, 121, 118, 115, 112, 109, 106, 103, 99, 96, 93, 90, 87, 84, 81, 79,
    76, 73, 70, 67, 64, 62, 59, 56, 54, 51, 49, 46, 44, 42, 39, 37, 35, 33, 31,
    29, 27, 25, 23, 21, 19, 18, 16, 15, 13, 12, 10, 9, 8, 7, 6, 5, 4, 3, 3, 2,
    1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 3, 4, 5, 6, 7, 8, 9, 10,
    12, 13, 15, 16, 18, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 42, 44, 46,
    49, 51, 54, 56, 59, 62, 64, 67, 70, 73, 76, 79, 81, 84, 87, 90, 93, 96, 99,
    103, 106, 109, 112, 115, 118, 121, 124};

注意, 放在RAM里, 比放在FLASH里的好处是读取时少用一个时钟周期.

累加器很简单:

void cloop(uint16_t step)
{
    register uint16_t counter = 0;

    while(1) {
        counter += step;
        PORTC = waveform[counter >> 8];
    }

    return;
}

执行这个循环体需要11个时钟周期. 假如AVR的主频是20MHz, 则输出单个相位的频率是20 / 11 = 1.82MHz. 当step=1时, 每累加256次, counter的高位进1, 输出相位变化一次; 输出相位变化256次完成一个周期, 因此这时的频率分辨率是1.82 / 256 / 256 = 27.8Hz.

看起来已经不错了, 能不能再给力一点呢? 可以把counter换成32位unsigned long型, 然后右移16位后再取低8位作为索引, 这样能把频率分辨率提高到0.1Hz左右, 不过这时循环需要的时钟周期稍多一点, 输出相位更新频率相应会低一点.

它能输出的最高频率是多少呢? 理论上最高可以达到相位更新频率的一半左右, 不过这时波形没法看了. 如果输出频率是相位更新频率的1/10, 这时一个周期能输出10个相位, 波形稍微好看一点.

能不能再快一点呢? 看看这个循环体是否能再优化一下. 改成汇编, 可以再节约几个周期:

asmloop: 
    clr     I
    ldi     r30, lo8(sine) 
    ldi     r31, hi8(sine)
    add     r31, r20

; only works if lo8(waveform) == 0
loop:
    add     r28, r22                ; 1 clock
    adc     r29, r23                ; 1 clock
    adc     r30, r24                ; 1 clock
    lpm                             ; 3 clocks
    out     _SFR_IO_ADDR(PORTD), r0 ; 1 clock
    rjmp    loop                    ; 2 clocks
    nop
    nop
    ret

这是把波形数据放进FLASH的情况, 24位累加需要三个时钟周期, 之后用LPM指令读取相位数据再三个周期, 输出相位一个周期, 跳转回去两个周期, 总共9个, 是不是又快了一点. 如果只用16位的counter, 把相位数据放在ram用ld指令读取, 7个时钟周期就够了, 这样相位更新频率可以达到20 / 7 = 2.86MHz.

如果换成STM32呢? STM32F103的主频是72MHz, 不用汇编的话大致也需要12个周期输出一个相位, 于是相位更新频率可以达到6MHz. 或者, STM32有些型号有DAC, 就不用一堆电阻搭R-2R DAC了. 不过STM32的DAC输出更新频率只有1MHz.

以上做法的缺点也很明显, 单片机的全部CPU资源都用在这个死循环上了, 完全干不了别的事, AVR还好, STM32这么用就太浪费了. 能不能让它一边输出波形一边还能干别的呢? 这就需要用到DMA这个神器了, 可以在后台把数据从DMA写到DAC, 输出频率用TIM控制到1M, 这样CPU同时还能干别的事. DMA写完若干数据, 在完成中断里计算出新的一批相位数据写到缓冲区. 不过这样衔接时会有点问题, 更好的办法是打开DMA的完成和半完成两个中断, 就可以用双缓冲的方式了, 大概这样:

void DDS_RefreshTable(int tc)
{
    unsigned long step = g_freq * TABLE_SIZE * 72 / (72000000UL / TABLE_SIZE);
    static unsigned long counter;
    int pos;

    if(tc)
        pos = TABLE_SIZE / 2;
    else
        pos = 0;
    for(int i = 0; i < TABLE_SIZE / 2; i++) {
        counter += step;
        dds_table[i + pos] = sine_table[(counter / TABLE_SIZE) % TABLE_SIZE];
    }
}

提问:

DDS输出过程中,从哪个频率开始,就不再是256个点在输出了呢?换句话说,从什么时候开始,DA的输出开始跳点?

 

posted @ 2022-10-19 11:48  Zurro  阅读(701)  评论(0编辑  收藏  举报