信号源是许多电子设备必不可少的部件,可以采用模拟集成电路如8038、单片机控制D/A转换器或DDS芯片如AD9850等方法实现。对于内部具有D/A转换器的单片机,采用其自备的D/A转换器产生需要的信号是最经济的方法。内部具有D/A转换器的单片机种类很多,这里介绍采用Cygnal公司 的一款功能强大的内部具有D/A 转换器的单片机C8051F020产生任意波形的详细方法。该方法及部分程序也可以用于其他型号的单片机芯片。
1 采用C8051F020 D/A转换器产生正弦信号的基本原理
单片机指令不支持正弦函数运算,所以要产生正弦信号只能通过查正弦函数表的方法,再经过D/A转换成模拟量而输出正弦波。其波形的频率可以通过改变定时器的初值,即改变查表输出的时间来控制。由于本例中采用了C8051F020的一个12位D/A转换器产生正弦信号,所以平均值为2048幅度为0~4096(212)的64点正弦函数表可以通过如下公式计算:
输出=sin(2πj/64)×2048+2048 j=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位的。
2 硬件电路
采用C8051F020 D/A转换器产生正弦信号的硬件电路图如图1所示。
电源部分电路如图2所示。
3 软件编程
// sinwave.c
#include <c8051io20.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,具体的计算方法如前文所述。
4 产生任意波形的方法
若想产生方波、三角波或任意波形,可以简单地通过修改正弦函数表来得到。
5 结束语
本文中的程序已经在新华龙公司C8051F020仿真开发板上调试通过,若要产生高精度的信号,必须考虑四舍五入近似、系统时钟精度及程序响应延时造成的误差。