直接数字频率合成技术及其C++的实现

本文深入介绍了直接数字频率合成(DDFS)技术的工作原理及其在软件中的应用实例。通过详细的数学推导和源代码示例,展示了如何利用DDFS生成高质量的正弦波和其他周期性波形。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

DDFS-Direct Digital Frequency Synthesizer 直接数字频率合成技术可以用来产生任意波形的周期信号。所谓的DDFS简单的说是查表法,内部维护一个Lookup Table储存一个周期的波形。那么这个查找表其实就是给出了相位到函数值的一个映射关系:


这里相位ω又是时间t的线性函数。


而调整输出频率实际上就是调整相位函数的系数kΔ。

举个具体的例子,比如我们的查找表存储的是正弦波的一个周期。查找表的长度为1024。正弦波的相位是以2π为周期的,那么查找表相邻的两项间的相位差为。也就是说查找表的相位分别为:


需要注意的是最后一个元素对应的相位并不是2π,因为相位与相位0其实是相同的,查找表中只能出现一次。

我们还需要一个相位累加器和一个频率寄存器,相位累加器用来记录当前的相位值是多少。比如说我们用一个16位无符号整数寄存器来记录这个相位值,那么0到65537(注意这里是65537)就对应0到2π这个相位周期。相位随时间变化是线性的,所以我们每次只需将相位累加器累加一个固定的相位就可以了。累加的这个值就存在频率寄存器中,比如频率寄存器中的值为5,那么相位累加器的值依次为:0,5,10,15,20,...,65530,65535,3,8,13,...。这里超过65536的值比如65537自动溢出成为0,刚好利用到了整型变量的这种周期性。

频率寄存器的值设为多少合适呢,如果我们的采样频率为 Fs,我们希望的输出频率为f。查找表的长度为M,相位累加器的长度为N,频率寄存器的值为k。那么相位累加器的最小相位变化量为。输出波形一个周期有Fs/f 个数据点。数据点间的相位差为。那么有如下等式:


实际计算出的k值不一定是个整数,四舍五入就可以了。举个具体的例子:采样频率为1kHz,我们希望输出10Hz的正弦波,相位累加器的长度为65536。那么频率寄存器的值应为

可以看出,相位累加器的长度越长,频率分辨率就越高。因此实际使用时相位累加器通常会设计为32位或更高位数。查找表的长度通常就短很多,一般都是10位或12位。这时只需将相位累加器的最高几位作为查找表的指标就可以了。

 

直接数字频率合成技术并不仅仅是用于硬件生成特定函数波形,在数值计算领域,也可以采用这项技术。还是以生成正弦波为例。虽然大多数编程语言中都提供了sin()和cos()函数,但是这个函数的计算速度相对还是较慢的。如果你的程序中需要调用几百万次,那么花费的时间还是很可观的。另外,随着t的增加,sin(t)的计算精度也会下降。这时,采用DDS技术的优势就体现出来了。首先查表法保证它的速度比任何的现有的三角函数计算代码都要快的多,其次,所有的运算都是整数运算保证了不存在任何累计计算误差。

 

下面是实现代码,可以生成多种周期函数波形:

#ifndef DDS_H_INCLUDED
#define DDS_H_INCLUDED


class LookupTable
{
    friend class DDS;
private:
    unsigned int m_length;
    unsigned int m_shift;
    double *m_table;
public:
    LookupTable();
    LookupTable(unsigned int N);
    ~LookupTable();
    void sine(double amplitude = 1.0, double baseline = 0.0);
    void rect(double dutycircle = 0.5, double amplitude = 1.0, double baseline = 0.0);
    void sawtooth(double dutycircle = 0.5, double amplitude = 1.0, double baseline = 0.0);
    void arbitrary(double table[]);
};

class DDS
{
private:
    LookupTable *m_pLut;
    unsigned int m_iter;
    unsigned int m_stride;
    unsigned int m_phase;
public:
    DDS(LookupTable *pLut, unsigned int stride = 1, unsigned int phase = 0);
    void setPhase(double phase = 0.0);
    void setFreq(double freq);
    void setFreq(double freq, double fs);
    inline double value();
};
inline double DDS::value(void)
{
    unsigned int index = (m_iter + m_phase);
    index = index >> m_pLut->m_shift;
    double value = m_pLut->m_table[index];
    m_iter += m_stride;
    return value;
}
#endif // DDS_H_INCLUDED

#include "dds.h"
#include <cstdlib>
#include <cmath>
LookupTable::LookupTable()
{
    m_length = 0;
    m_table = NULL;
}
LookupTable::~LookupTable()
{
    delete[] m_table;
}
LookupTable::LookupTable(unsigned int N)
{
    if(N < 1)
    {
        N = 1;
    }
    if(N > 16)
    {
        N = 16;
    }
    m_length = 0x01 << N;
    m_shift = 32 - N;
    m_table = new double[m_length];
}

void LookupTable::sine(double amplitude, double baseline)
{
    if(m_length != 0)
    {
        for(unsigned int i = 0; i < m_length; i++)
        {
            m_table[i] = amplitude * sin(2 * M_PI / m_length * i) + baseline;
        }
    }
}

void LookupTable::rect(double dutycircle, double amplitude, double baseline)
{
    if(m_length != 0)
    {
        for(unsigned int i = 0; i < m_length; i++)
        {
             double value = (1.0 * i / m_length < dutycircle) ? 0 : 1;
             m_table[i] = amplitude * value + baseline;
        }
    }
}

void LookupTable::sawtooth(double dutycircle, double amplitude, double baseline)
{
    double pos, value;
    if(m_length != 0)
    {
        for(unsigned int i = 0; i < m_length; i++)
        {
            pos = 1.0 * i / m_length;
            if(pos < dutycircle)
            {
                value = pos / dutycircle;
            }
            else
            {
                value = (1.0 - pos) / (1.0 - dutycircle);
            }
            m_table[i] = amplitude * value + baseline;
        }
    }
}
void LookupTable::arbitrary(double table[])
{
    if(m_length != 0)
    {
        for(unsigned int i = 0; i < m_length; i++)
        {
            m_table[i] = table[i];
        }
    }
}
DDS::DDS(LookupTable *pLut, unsigned int stride, unsigned int phase)
{
    m_iter = 0;
    m_pLut = pLut;
    m_stride = stride;
    m_phase = phase;
}
void DDS::setPhase(double phase)
{
    m_phase = round(phase /(2 * M_PI) * 4294967296.00);
}
void DDS::setFreq(double omega)
{
    double stride = omega / (2 *M_PI) * 4294967296.00;
    m_stride = round(stride);
}
void DDS::setFreq(double freq, double fs)
{
    double omega = 2 *M_PI *freq / fs;
    setFreq(omega);
}

#include <iostream>
#include "dds.h"
using namespace std;

int main()
{
    LookupTable sincos(10);
    LookupTable sawtooth(10);
    sincos.sine(2, 0);
    sawtooth.sawtooth(0.3, 1, 0);
    DDS dds1(&sincos);
    DDS dds2(&sincos);
    DDS dds3(&sawtooth);
    dds1.setFreq(10, 1000);
    dds1.setPhase(0.0);
    dds2.setPhase(3.1415926/2);
    dds2.setFreq(10, 1000);
    dds3.setFreq(10, 1000);
    for(int i =0; i <200; i++)
    {
        cout << i << ", " << dds1.value() << ", " ;
        cout << dds2.value() << ", " << dds3.value() << endl;
    }
    return 0;
}

将输出结果绘出图形如下:


希望这个代码对大家有用。


一本好书,研究dds数字频率合成必读! 内容简介 《直接数字频率合成》共6章,比较全面、深入地讨论了DDS的理论与应用。主要内容包括DDS的基本概念、相位累加器、正弦查表、D/A变换器的噪声分析;拟周期脉冲删除;级数展开、连分式展开;DDS相位噪声和杂散产生的机理及其降低;DDS与PLL的组合;分数-N频率合成器原理;低噪声微波频率合成器的设计原理;新的DDS结构等。 《直接数字频率合成》的特点是:内容新,反映了现在的研究和发展水平;抓住问题的主要方面,把理论与应用结合在一起;可供无线电通信领域中的研究者和工程技术人员学习参考,也可作为工作在其他领域中的有关人员学习参考。 3目录 序言 第1章 直接数字频率合成原理 1.1 DDS的基本概念 1.2 相位累加器 1.3 正弦查表 1.4 D/A变换器 1.4.1 数字编码 1.4.2 输出波形 1.5 具有调制能力的DDS系统 1.6 逼近频率合成 第2章 DDS中的相位和杂散噪声 2.1 引言 2.2 矩形波输出 2.2.1 拟周期脉冲删除 2.2.2 基于修正的恩格尔级数展开的系统 2.2.3 基于连分式展开的系统 2.2.4 基于展开组合的系统 2.2.5 杂散信号 2.3 正弦波输出 2.3.1 量化输出正弦波的傅里叶分析 2.3.2 相位截断正弦波的频谱分析 2.3.3 正弦字的截断 2.3.4 背景杂散信号电平的估计 2.3.5 W和S之间的关系 2.4 D/A变换器的噪声分析 2.4.1 量化引起的信噪比 2.4.2 D/A变换器引起的非线性杂散信号 2.4.3 突发性尖脉冲 2.5 脉冲速率频率合成器的频谱 第3章 DDS中相位噪声和杂散信号的降低 3.1 DDS的噪声特性 3.1.1 不同电路的噪声特性 3.1.2 DDS的相位噪声 3.2 DDS中接近载波的噪声 3.2.1 DDS输出噪声的计算 3.2.2 接近载波噪声的理论基础 3.2.3 杂散频谱的估计 3.2.4 实验结果及讨论 3.3 输出滤波器 3.4 改进DDS电路的设计 3.4.1 降低ROM的容量 3.4.2 降低突发性尖脉冲的方法 3.5 DDS频谱性能的改进 3.6 DDS与PLL的组合 3.6.1 DDS与PLL组合合成器 3.6.2 十进制DDS的设计 第4章 分数-N频率合成器原理 4.1 FNPLL环路 4.1.1 FNPLL环路的组成 4.1.2 FNPLL环路的工作原理 4.2 FNPLL环路简化频率合成 4.3 使用FNPLL环路的频率合成器 4.4 DDS控制吞脉冲分数-N频率合成原理 4.5 DDS控制吞脉冲分数-N环路的杂散相位调制 4.6 双模式分频器 4.7 多级调制分数分频器 4.7.1 分数分频的新方法 4.7.2 具有∑-△结构的分数-N频率合成中的杂散信号 4.7.3 分数分频器的实现 第5章 低噪声微波频率合成器的设计原理 5.1 微波环路的基本框图 5.2 微波环路中的加性噪声 5.3 用环路滤波器改善输出噪声 5.4 微波频率合成举例 5.4.1 超低噪声微波频率合成器 5.4.2 雷达和通信系统中的低噪声频率合成器 第6章 新的DDS结构 6.1 混合DDS 6.1.1 混合DDS结构 6.1.2 800MHz混合DDS 6.2 DDS后接重复分频和混频器 6.2.1 总的要求 6.2.2 5100结构作为偏移合成器 6.2.3 混频和分频链的前后端 6.3 综合技术结构 6.4 IIR滤波方法 6.4.1 IIR谐振器 6.4.2 用TMS320C30产生正弦波 6.5 复位方法 6.5.1 无稳定性控制的IIR滤波器 6.5.2 有稳定性控制的IIR滤波器 6.5.3 有稳定性控制和小□值的IIR滤波器 6.5.4 DCSW方法 6.5.5 IIR-ALT方法 6.6 实现与试验结果 6.6.1 数值输出 6.6.2 模拟输出 附录 附录A:拉普拉斯变换 附录B:z变换 附录C:DDS输出的傅里叶变换 附录D:正交调制器相位误差的数字相位预矫正
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值