简易波形发生器通过单片机的Proteus仿真

利用数模转换器 PCF8591 实现简易波形发生器(可以输出正弦波、方波、三角波和锯齿波; 可以通过按键选择波形和频率;事先用 MATLAB 生成波形数据,点数越多,波形越逼真)。
PCF8951是一个单电源低功耗的8位CMOS数据采集器件,具有4路模拟输入,1路模拟输出,一个串行I2C总线接口用来与单片机通信。三个引脚A0,A1,A2,用于编程硬件地址,允许最多8个器件连接到I2C而不需要额外的电路。器件的地址,控制以及数据都是通过I2C总线来传输。其中引脚1,2,3,4是4路模拟输入,引脚5,6,7是I2C总线的硬件地址,引脚8为数字地,9脚和10脚是I2C总线的SDA和SCL。12脚是时钟选择引脚,高电平表示外部时钟输入,低电平表示使用内部时钟。14脚是基准源,15脚是DAC的模拟输出,16脚是供电电源VCC。
在实际应用中收到I2C通信本身速率的影响,输出波形的频率不能设置得太高,我在实物单片机中调试时发现设置的频率不能超过24Hz不然会程序会崩溃,仿真可以设置更高的频率。
使用方法:
按下向上键切换波形
按下回车键进入频率设置,输入数字再按回车后保存,如果不想保存按下ESC键退出设置。
起始时可能需要调整示波器才能正常显示波形。
简易波形发生器通过单片机的Proteus仿真_第1张图片
电路图画得不是很好,我当时还不知道可以简化连线的方法,这一方面可以改进。
然后是代码方面:
四个程序要放在同一个工程中,
1.简易信号发生器主函数.c

#include 
#define uchar unsigned char
#define uint unsigned int

uchar str[6];
uchar setIndex = 0;//频率设置索引
uchar addend = 0;
uchar lastaddend;//保存上一次的频率,为退出做准备
uchar code SinWave[] = {	 //正弦波波表
	127,133,139,145,151,158,164,170,175,181,
	187,192,198,203,208,212,217,221,225,229,
	233,236,239,242,245,247,249,251,252,253,
	254,254,255,254,254,253,252,251,249,247,
	245,242,239,236,233,229,225,221,217,212,
	208,203,198,192,187,181,175,170,164,158,
	151,145,139,133,127,120,114,108,102,95,
	89,83,78,72,66,61,55,50,45,41,
	36,32,28,24,20,17,14,11,8,6,
	4,2,1,1,0,0,0,0,0,1,
	1,2,4,6,8,11,14,17,20,24,
	28,32,36,41,45,50,55,61,66,72,
	78,83,89,95,102,108,114,120
};

uchar code TriWave[] = {  //三角波波表
	0,4,8,12,16,20,24,28,32,36,
	40,44,48,52,56,60,64,68,72,76,
	80,84,88,92,96,100,104,108,112,116,
	120,124,128,132,136,140,144,148,152,156,
	160,164,168,172,176,180,184,188,192,196,
	200,204,208,212,216,220,224,228,232,236,
	240,244,248,252,255,252,248,244,240,236,
	232,228,224,220,216,212,208,204,200,196,
	192,188,184,180,176,172,168,164,160,156,
	152,148,144,140,136,132,128,124,120,116,
	112,108,104,100,96,92,88,84,80,76,
	72,68,64,60,56,52,48,44,40,36,
	32,28,24,20,16,12,8,4
};

uchar code SawWave[] = {
	0,8,16,24,32,40,48,56,64,72,//锯齿波
	80,88,96,104,112,120,128,136,144,152,
	160,168,176,184,192,200,208,216,224,232,
	240,248,256,264,272,280,288,296,304,312,
	320,328,336,344,352,360,368,376,384,392,
	400,408,416,424,432,440,448,456,464,472,
	480,488,496,504,512,520,528,536,544,552,
	560,568,576,584,592,600,608,616,624,632,
	640,648,656,664,672,680,688,696,704,712,
	720,728,736,744,752,760,768,776,784,792,
	800,808,816,824,832,840,848,856,864,872,
	880,888,896,904,912,920,928,936,944,952,
	960,968,976,984,992,1000,1008,1016
};

uchar code FangWave[] = {
	255,255,255,255,255,255,255,255,255,255,//方波
	255,255,255,255,255,255,255,255,255,255,
	255,255,255,255,255,255,255,255,255,255,
	255,255,255,255,255,255,255,255,255,255,
	255,255,255,255,255,255,255,255,255,255,
	255,255,255,255,255,255,255,255,255,255,
	255,255,255,255,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0	
};

uchar code *pWave;//波表指针
uchar T0RH = 0;
uchar T0RL = 0;
uchar T1RH = 1;
uchar T1RL = 1;

void SetWaveFreq(uchar freq);
extern void KeyScan();
extern void KeyDriver();
extern void I2CStart();
extern void I2CStop();
extern bit I2CWrite(uchar dat);
extern void InitLcd1602();
extern void LcdFullClear();
extern void LcdAreaClear(uchar x,uchar y,uchar len);
extern void LcdShowStr(uchar x,uchar y,uchar *str);
extern void LcdSetCursor(uchar x, uchar y);
extern void LcdOpenCursor();
extern void LcdCloseCursor();

void main()
{
	InitLcd1602();
	EA = 1;//开总中断
	pWave = SinWave;//默认正弦波
	SetWaveFreq(10);//默认频率10Hz
	lastaddend = 10;
	LcdShowStr(0,0,"Zheng Xian Bo");
	LcdShowStr(0,1,"Ping Lv:");
	LcdShowStr(8,1,"10");
	LcdShowStr(11,1,"Hz");//位置不再改变
	TMOD &= 0xf0;
	TMOD |= 0x01;
	TH0 = 0xFC;	//为T0赋初值0xFC67,定时1ms
	TL0 = 0x67;
	ET0 = 1;	//使能T0中断
	TR0 = 1;	//启动T0
	while(1)
	{
		KeyDriver();//调用按键驱动
	}
}

void Shownumber(uchar *str, uchar dat)
{
	signed char i = 0;
	uchar buf[6];

	do {
		buf[i++] = dat % 10;
		dat /= 10;
	}  while (dat > 0);
	while(i-->0)
	{
		*str++ = buf[i] + '0';
	}
	*str = '\0';
}

//进入频率设置
void EnterFreSet()
{
	LcdSetCursor(8, 1);
	LcdOpenCursor();//打开光标
	setIndex = 2;
}

//退出时间设置函数
void ExitFreSet(bit save)
{
	static unsigned long j;
	if (save)
	{	
		if(addend <= 24)
		{
			SetWaveFreq(addend);
			lastaddend = addend;
		}
		else//如果超过范围
		{
			LcdAreaClear(8,1,8);
			LcdShowStr(8, 1, ">24");
			for(j=8000;j>0;j--);
			LcdAreaClear(8,1,8);
			LcdShowStr(11,1,"Hz");
			addend = 0;//清空数值    
			EnterFreSet();
			return;	
		}		
	}
	else//要考虑恢复到上一次的时间
	{
		Shownumber(str,lastaddend);
		LcdAreaClear(8,1,3);
		LcdShowStr(8, 1, str);
	}
	setIndex = 0;
	addend = 0;
	LcdCloseCursor();
	Shownumber(str,setIndex);
	//LcdShowStr(15, 1, str);
}

void KeyAction(uchar keycode)
{
	static uchar cnt = 0;
	static uchar index = 0;
	if ((keycode >= '0') &&(keycode<='9')&&setIndex==2)//进入设置状态
	{
		LcdSetCursor(8, 1+index);
		//LcdOpenCursor();//打开光标
		addend = addend * 10 + (keycode - '0');
		Shownumber(str,addend);
		LcdAreaClear(8,1,3);
		LcdShowStr(8, 1, str);
		index ++;
	}
	else if (keycode == 0x26)
	{
		cnt++;
		if(cnt == 10)
			cnt = 0;
		//在三种波形间循环切换
		if (cnt == 2)
		{
			LcdAreaClear(0,0,16);
			LcdShowStr(0,0,"San Jiao Bo");
			pWave = TriWave;
		}
		else if (cnt == 4)
		{
			LcdAreaClear(0,0,16);
			LcdShowStr(0,0,"Ju Chi Bo");				 
			pWave = SawWave;
		}
		else if (cnt == 6)
		{
			LcdAreaClear(0,0,16);
			LcdShowStr(0,0,"Fang Bo");
			pWave = FangWave;
		}
		else if (cnt == 8)
		{
			LcdAreaClear(0,0,16);
			LcdShowStr(0,0,"Zheng Xian Bo");
			pWave = SinWave;
		}	
	}
	else if (keycode == 0x0d)//回车键
	{
		if (setIndex == 0)//不处于设置状态时,进入设置状态
		{
			EnterFreSet();
		}
		else//已处于设置状态时,保存时间并退出设置
		{
			index = 0;
			ExitFreSet(1);
		}
	}
	else if(setIndex == 2 && keycode == 0x1b)//ESC键
	{
		index = 0;
		ExitFreSet(0);
	}
}


//设置DAC输出值,val-设定值
void SetDACOut(uchar val)
{
	I2CStart();
	if (!I2CWrite(0x48<<1))
	{
		I2CStop();
		return;
	}
	I2CWrite(0x40);
	I2CWrite(val);
	I2CStop();
}
//设置输出波形的频率,freq-设定频率
void SetWaveFreq(uchar freq)
{
	unsigned long tmp;

	tmp = (11059200/12) / (freq*32);
	tmp = 65536 - tmp;
	T1RH = (uchar)(tmp>>8);
	T1RL = (uchar) tmp;
	TMOD &= 0x0f;
	TMOD |= 0x10;
	TH1 = T1RH;
	TL1 = T1RL;
	ET1 = 1;
	PT1 = 1;//设置为高优先级
	TR1 = 1;//启动T1	
}

//T1中断服务函数,执行波形输出
void InterruptTimer1() interrupt 3 
{
	static uchar i = 0;

	TH1 = T1RH;
	TL1 = T1RL;
	//循环输出波形中的数据
	SetDACOut(pWave[i]);
	i++;
	if (i >= 128)
	{
		i=0;
	}
}

2.I2C.c:用于I2C通信

#include 
#include 

#define uchar unsigned char
#define I2CDelay() {_nop_();_nop_();_nop_();_nop_();}
sbit I2C_SCL = P3^7;
sbit I2C_SDA = P3^6;

extern void LcdShowStr(uchar x, uchar y, uchar *str);

//产生总线起始信号
void I2CStart()
{
	I2C_SDA = 1;//首先确保SDA, SCL都是高电平
	I2C_SCL = 1;
	I2CDelay();
	I2C_SDA = 0;//先拉低SDA
	//起始信号的条件是SCL为高电平期间,SDA由高电平向低电平产生一个下降沿
	I2CDelay();
	I2C_SCL = 0;//再拉低SCL
}

//产生总线停止信号
void I2CStop()
{
	I2C_SCL = 0;//首先要确保SDA, SCL都是低电平
	I2C_SDA = 0;
	I2CDelay();
	I2C_SCL = 1;//先拉高SCL
	I2CDelay();
	I2C_SDA = 1;//再拉高SDA
	//停止信号的条件是SCL为高电平期间,SDA由低电平向高电平产生一个上降沿
	I2CDelay();
}

//I2C总线写操作,dat-待写入字节,返回值-从机应答位的值
bit I2CWrite(uchar dat)
{
	bit ack;
	uchar mask;//用于探测字节内某一位值的掩码变量
	//mask=1000 0000向右进1,即为1100 0000
	for (mask = 0x80; mask!=0; mask>>=1)//从高位到低位依次进行
	{
		if ((mask&dat) == 0)//该为输出到SDA上
			I2C_SDA = 0;
		else
			I2C_SDA = 1;
		I2CDelay();
		I2C_SCL = 1;
		I2CDelay();
		I2C_SCL = 0;
	}
	I2C_SDA = 1;//8位数据发送完后,主机释放SDA,以检测从机应答
	I2CDelay();
	I2C_SCL = 1;//拉高SCL
	ack = I2C_SDA;//读取此时的SDA值,即为从机的应答值
	I2CDelay();
	I2C_SCL = 0;//再拉低SCL完成应答位,并保持住总线
	return (~ack);//返回从机应答值
}


//I2C总线读操作,并发送非应答信号,返回值-读到的字节
uchar I2CReadNAK()
{
    uchar mask;
    uchar dat;

    I2C_SDA = 1;//首先确保主机释放SDA
    for (mask=0x80;mask!=0;mask>>=1)//从高位到低位依次进行    
	{
        I2CDelay();
        I2C_SCL = 1;//拉高SCL
        if (I2C_SDA == 0)//读取SDA的值          
			dat &= ~mask;//为0时,dat对应位清零
        else
            dat |= mask;//为1时,dat对应位置1
        I2CDelay();
        I2C_SCL = 0;//再拉低SCL以使从机发出下一位
    }
    I2C_SDA = 1;//8位数据发送完以后,拉高SDA,产生非应答信号
    I2CDelay();
    I2C_SCL = 1;//拉高SCL
    I2CDelay();
    I2C_SCL = 0;//再拉低SCL完成非应答为位,并保持住总线

    return dat;
}

//I2C总线读操作,并发送应答信号,返回值-读到的字节
uchar I2CReadACK()
{
    uchar mask;
    uchar dat;

    I2C_SDA = 1;//首先确保主机释放SDA
    for (mask=0x80;mask!=0;mask>>=1)//从高位到低位依次进行    
	{
        I2CDelay();
        I2C_SCL = 1;//拉高SCL
        if (I2C_SDA == 0)//读取SDA的值          
			dat &= ~mask;//为0时,dat对应位清零
        else
            dat |= mask;//为1时,dat对应位置1
        I2CDelay();
        I2C_SCL = 0;//再拉低SCL以使从机发出下一位
    }
    I2C_SDA = 0;//8位数据发送完以后,拉高SDA,产生应答信号
    I2CDelay();
    I2C_SCL = 1;//拉高SCL
    I2CDelay();
    I2C_SCL = 0;//再拉低SCL完成非应答为位,并保持住总线

    return dat;
}

3.LCD1602.c:用于LCD1602显示驱动

#include 

#define uchar unsigned char
#define lcd1602_db P0

sbit lcd1602_rs = P1^0;
sbit lcd1602_rw = P1^1;
sbit lcd1602_e = P1^5;

void LcdWaitReady()
{
	uchar sta;
	lcd1602_db = 0xff;
	lcd1602_rs = 0;
	lcd1602_rw = 1;
	do {
		lcd1602_e = 1;
		sta = lcd1602_db;
		lcd1602_e = 0;
	} while(sta & 0x80);//只是检测是否忙没有执行任何操作,1表示在忙,0表示空闲
}

//向液晶写入一行命令,cmd-待写入命令值
void LcdWriteCmd(uchar cmd)
{
	LcdWaitReady();
	lcd1602_rs = 0;//0为命令
	lcd1602_rw = 0;//0表示写入
	lcd1602_db = cmd;
	lcd1602_e = 1;//产生高脉冲
	lcd1602_e = 0;
}

//向液晶写入一字节数据,dat-待写入数据值
void LcdWriteDat(uchar dat)
{
	LcdWaitReady();
	lcd1602_rs = 1;//1为数据
	lcd1602_rw = 0;//0表示写入
	lcd1602_db = dat;
	lcd1602_e = 1;//产生高脉冲
	lcd1602_e = 0;
}

//设置显示RAM起始地址,亦即光标位置,(x,y)-对应屏幕上的字符坐标
void LcdSetCursor(uchar x, uchar y)
{
	uchar addr;
	if(y==0)
		addr = 0x00 + x;//这意味着第一个显示字符x=0
	else
		addr = 0x40 + x;
	LcdWriteCmd(addr | 0x80);//设置RAM地址
}

/*
//在液晶上显示字符串,和设置函数的功能部分重复
void LcdShowStr(uchar x,uchar y,uchar *str,uchar len)
{
	LcdSetCursor(x, y);	
	while(len--)//通过长度来改变,而静态显示时是通过判断最后一个字符是否为/0,while(*str!='\0')
	{
		LcdWriteDat(*str++);
	}
}
*/

void LcdShowStr(uchar x,uchar y,uchar *str)
{
	LcdSetCursor(x, y);	
	while(*str != '\0')//通过长度来改变,而静态显示时是通过判断最后一个字符是否为/0,while(*str!='\0')
	{
		LcdWriteDat(*str++);
	}
}

//打开光标闪烁效果
void LcdOpenCursor()
{
	LcdWriteCmd(0x0f);
}

//关闭光标显示
void LcdCloseCursor()
{
	LcdWriteCmd(0x0c);
}

//区域清除,清除从(x,y)坐标起始的len个字符位
void LcdAreaClear(uchar x,uchar y,uchar len)
{
	LcdSetCursor(x, y);
	while(len--)
	{
		LcdWriteDat(' ');
	}
} 


//整屏清除
void LcdFullClear()
{
	LcdWriteCmd(0x01);
}


//初始化1602液晶
void InitLcd1602()
{
	LcdWriteCmd(0x38);//写入
	LcdWriteCmd(0x0c);//显示器开,光标关闭
	LcdWriteCmd(0x06);//文字不动,地址自动+1
	LcdWriteCmd(0x01);//清屏操作
}

4.keyboard.c:键盘驱动程序

#include 

sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;

sbit KEY_IN_1 = P2^3;
sbit KEY_IN_2 = P2^2;
sbit KEY_IN_3 = P2^1;
sbit KEY_IN_4 = P2^0;

sbit KEY_OUT_1 = P2^4;
sbit KEY_OUT_2 = P2^5;
sbit KEY_OUT_3 = P2^6;
sbit KEY_OUT_4 = P2^7;

unsigned char code KeyCodeMap[4][4] = {
	{'1', '2', '3', 0x26},//向上键
	{'4', '5', '6', 0x25},//向左键
	{'7', '8', '9', 0x28},//向下键
	{'0',0x1B, 0x0D, 0x27}//ESC键,回车键,向右键
};

unsigned char KeySta[4][4] = {	//全部矩阵按键的当前状态
	{1,1,1,1},{1,1,1,1},{1,1,1,1},{1,1,1,1}
};

extern void KeyAction(unsigned char keycode);

void KeyDriver()
{
	unsigned char i, j;
	static unsigned char backup[4][4] = {	//按键值备份,保存前一次的值
	{1,1,1,1},{1,1,1,1},{1,1,1,1},{1,1,1,1}
	};
	for(i=0; i<4; i++)
	{
		for(j=0; j<4; j++)
		{
			if(backup[i][j] != KeySta[i][j])//如果当前值和备份值不同
			{
				if(backup[i][j] != 0)//如果backup[i][j]!=0即当前为按下状态
				{
					//P0 = LedChar[i*4 + j];//将编号显示到数码管
					KeyAction(KeyCodeMap[i][j]);
				}
				backup[i][j] = KeySta[i][j];//更新下一次的备份值
			}
		}
	}	
}

void InterruptTimer0() interrupt 1
{
	unsigned char i;
	static unsigned char keyout = 0;
	static unsigned char keybuf[4][4] = {
	{0xFF,0xFF,0xFF,0xFF}, {0xFF,0xFF,0xFF,0xFF},
	{0xFF,0xFF,0xFF,0xFF},{0xFF,0xFF,0xFF,0xFF}
	};//初始时全为1

	TH0 = 0xFC;
	TL0 = 0x67;
	//按行扫描,keyout为行数的索引
	keybuf[keyout][0] = (keybuf[keyout][0] << 1) | KEY_IN_1;
	keybuf[keyout][1] = (keybuf[keyout][1] << 1) | KEY_IN_2;
	keybuf[keyout][2] = (keybuf[keyout][2] << 1) | KEY_IN_3;
	keybuf[keyout][3] = (keybuf[keyout][3] << 1) | KEY_IN_4;

	for(i=0; i<4; i++)
	{
		if ((keybuf[keyout][i] & 0x0F) == 0x00)
		{
			KeySta[keyout][i]=0;
		}
		else if ((keybuf[keyout][i] & 0x0F) == 0x0F)
		{
			KeySta[keyout][i] = 1;
		}
	}
	keyout++;//到下一行
	keyout = keyout & 0x03;//如果行数索引到4则清零,比if更好
	switch(keyout)
	{
		case 0:KEY_OUT_4 = 1; KEY_OUT_1 = 0; break;//为下一次扫描的行做好准备
		case 1:KEY_OUT_1 = 1; KEY_OUT_2 = 0; break;//因为其余两个值为1,因此只需将上一次被扫描的行值赋1
		case 2:KEY_OUT_2 = 1; KEY_OUT_3 = 0; break;
		case 3:KEY_OUT_3 = 1; KEY_OUT_4 = 0; break;
		default: break;
	}
}

运行结果:

10Hz正弦波简易波形发生器通过单片机的Proteus仿真_第2张图片
10Hz三角波
简易波形发生器通过单片机的Proteus仿真_第3张图片
10Hz锯齿波简易波形发生器通过单片机的Proteus仿真_第4张图片
10Hz方波:注意方波要使用DC模式不然波形不理想。简易波形发生器通过单片机的Proteus仿真_第5张图片
可以调整频率:
频率调为20Hz后三角波:
简易波形发生器通过单片机的Proteus仿真_第6张图片

你可能感兴趣的:(单片机,Proteus仿真)