本模块采用PCF8591,它是一款AD-DA集成芯片。所以本节对iic通信协议不做过多的介绍,重心放在iic的rtl建模,本次通过iic控制PCF8591实现DAC输出功能。及输出波形 将数字量转为模拟量通过矩阵按键 可以进行调节模式 设定频率的大小,数值可以在lcd屏幕上进行显示。
附控制程序
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
};
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*3.6); //这才是频率为10HZ的频率图
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;
}
}
#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;
}
#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);//清屏操作
}
#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;
}
}