信号源是许多电子设备必不可少的部件,可以采用模拟集成电路如8038、单片机控制D/A转换器或DDS芯片如AD9850等方法实现。对于内部具有D/A转换器的单片机,采用其自备的D/A转换器产生需要的信号是最经济的方法。内部具有D/A转换器的单片机种类很多,这里介绍采用Cygnal公司最新的一款功能强大的内部具有D/A 转换器的单片机C8051F020产生任意波形的详细方法。该方法及部分程序也可以用于其他型号的单片机芯片。
单片机指令不支持正弦函数运算,所以要产生正弦信号只能通过查正弦函数表的方法,再经过D/A转换成模拟量而输出正弦波。其波形的频率可以通过改变定时器的初值,即改变查表输出的时间来控制。由于本例中采用了C8051F020的一个12位D/A转换器产生正弦信号,所以平均值为2048幅度为0~4096(212)的64点正弦函数表可以通过如下公式计算:
输出=sin(2πj/64)×2048+2048j=0,1,……,63
计算得到的64项16位二进制数结果,以2个8位二进制数的形式存放在code段(ROM中)具有128项的一维数组SINE_TABLE[128]中,每2项合成一个16位数,取低12位送D/A转换器进行D/A转换。
以下面程序数表中前2项为例:0x08,0x00合并成0x0800,取低12位为800 H,经过DAC0数据转换电压为1.20 V(约为满幅2.40 V的一半)。以产生100 Hz正弦信号为例,设SYSCLK系统振荡频率为2MHz,定时器的初值为:
216 - 2MHz/12 × 1/100Hz × 1/64 =65536-26.041666 =65509.958 ≈ 65510
其中216是因为采用的定时器3是16位的。
采用C8051F020 D/A转换器产生正弦信号的硬件电路图如图1所示。
电源部分电路如图2所示。
// sinwave.c #include <reg51.h> //C8051F020特殊功能寄存器声明 //16位特殊功能寄存器定义 sfr16 DP = 0x82; //数据指针 sfr16 TMR3RL = 0x92; //定时器3重装值 sfr16 TMR3 = 0x94; //定时器3计数器 sfr16 ADC0 = 0xbe; //ADC0 sfr16 ADC0GT = 0xc4; //ADC0窗口1 sfr16 ADC0LT = 0xc6; //ADC0窗口2 sfr16 RCAP2 = 0xca; //定时器2捕捉/重载 sfr16 T2 = 0xcc; //定时器2 sfr16 RCAP4 = 0xe4; //定时器4捕捉/重载 sfr16 T4 = 0xf4; //定时器2 sfr16 DAC0 = 0xd2; //DAC0 sfr16 DAC1 = 0xd5; //DAC1 //全局常量 #define SYSCLK 2000000 //系统复位自动使用内部时钟,为2MHz sbit LED = P1 ^ 6; //定义P1.6名称为LED,监视系统运行 unsigned char i = 0; //声明无符号字符变量i(初值为0) void PORT_Init(void); //端口初始化函数原型 void Timer3_Init(int counts); //定时器3初始化函数原型 void Timer3_ISR(void); //定时器3中断服务函数原型 void Dac0_Init(void); //DAC0初始化函数原型 //正弦函数表,定义具有128项的无符号字符型一维数组 unsigned char code SINE_TABLE[128] = {0x08, 0x00, 0x08, 0xc9, 0x09, 0x90, 0x0a, 0x53, 0x0b, 0x10, 0x0b, 0xc5, 0x0c, 0x72, 0x0d, 0x13, 0x0d, 0xa8, 0x0e, 0x2f, 0x0e, 0xa7, 0xxf, 0xxe, 0xxf, 0x64, 0xxf, 0xa8, 0xxf, 0xd9, 0xxf, 0xf6, 0x0f, 0xff, 0x0f, 0xf6, 0xxf, 0xd9, 0x0f, 0xa8, 0x0f, 0x64, 0x0f, 0x0e, 0x0e, 0xa7, 0x0e, 0x2f, 0x0d, 0xa8, 0x0d, 0x13, 0x0c, 0x72, 0x0b, 0xc5, 0x0b, 0x10, 0x0a, 0x53, 0x09, 0x90, 0x08, 0xc9, 0x08, 0x00, 0x07, 0x37, 0x06, 0x70, 0x05, 0xad, 0x04, 0xf0, 0x04, 0x3b, 0x03, 0x8e, 0x02, 0xed, 0x02, 0x58, 0x01, 0xd1, 0x01, 0x59, 0x00, 0xf2, 0x00, 0x9c, 0x00, 0x58, 0x00, 0x27, 0x00, 0x0a, 0x00, 0x00, 0xxx, 0x0a, 0x00, 0x27, 0x00, 0x58, 0x00, 0x9c, 0x00, 0xf2, 0x01, 0x59, 0x01, 0xd1, 0x02, 0x58, 0x02, 0xed, 0x03, 0x8e, 0x04, 0x3b, 0x04, 0xf0, 0x05, 0xad, 0x06, 0x70, 0x07, 0x37, }; void main(void) //主函数 { WDTCN = 0xde; //关闭看门狗定时器,使其无效 WDTCN = 0xad: PORT_Init(); //调用端口初始化函数 Timer3_Init(65510); //见下面注释* Dac0_Init(); //调用DAC0初始化函数 EA = 1; //开中断允许总开关,允许中断 while (1) { //主函数在此循环等待 } } void PORT_Init(void) //端口初始化函数 { XBR2 = 0x40; //交叉网络设定为弱上拉并生效 P1 MDOUT |= 0x40; //设置P1.6(LED)为推挽输出方式 } //定时器3初始化函数 //定义定时器3为自动重装载方式,以系统时钟的1/12为时钟源 void Timer3_Init(int counts) //“counts”为计数重载值。由调用函数传递过来 { TMI13CN = 0x00; //定时器3停止,清TF3, //使用SYCCLK/12为时钟源 TMR3RL = counts; //设置重载值为“counts” TMR3 = 0xffff; //设置立即重载 EIE2 |= 0x01; //开启定时器3中断允许开关,允许定时器3中断 TMR3CN |= 0x04; //开启定时器3,使其运行 } void Dac0_Init(void) //DAC0初始化函数 { DAC0CN = 0x80; //DAC0使能,且为立即更新方式,写DAC0H寄存器 //将立即启动DAC0工作,取DAC0H和DAC0L组 //成的16位数据的低12位数据为DAC0的转换数据 REFOCN |= 0x03; //DAC参考电压设定为使用内部电压基准 } void Timer3_ISR(void)interrupt 14 //T3中断服务函数 { TMR3CN &= ~(0x80); //清TF3 LED = ~LED; //使LED状态改变, DAC0L = SINE_TABLE[i * 2 + 1]; //查SINE_TABLE表,将第i*2+1项送给 //DAC0L,i=0,1,2,……63 DAC0H = SINE_TABLE[i * 2]; //查SINE_TABLE表,将第i*2项送给 //DAC0H,i=0,1,2,……63 i = i + 1; if (i >= 64) { i = 0; //循环输出64点 } }
调用定时器3初始化函数,调节其中的计数器初值可以得到不同频率的正弦信号。这里以产生100Hz正弦信号为例,计数器初值为65510,具体的计算方法如前文所述。
若想产生方波、三角波或任意波形,可以简单地通过修改正弦函数表来得到。
本文中的程序已经在新华龙公司C8051F020仿真开发板上调试通过,若要产生高精度的信号,必须考虑四舍五入近似、系统时钟精度及程序响应延时造成的误差。