电子线路专题实验Ⅱ
一、实验要求:
1. 认真阅读学习系统线路及相关资料
2. 将键盘阵列定义为0. 1. 2------ E. F,编程实现将键盘输入内容显示在LCD显示器上。
3. 编程实现将日历、时钟显示在LED显示屏上(注意仔细阅读PCF8563资料),日历、时钟轮回显示。
4. 利用D/A转换通道(下行通道)实现锯齿波发生器;输出(1~5V)固定电压转换成(4~20mA)电流。
5. 利用A/D转换通道(上行通道)实现数据采集,将采集信号显示在LED屏上。程序要求分别具有平均值滤波、中值滤波和滑动滤波功能。
6. 将按键阵列定义成与16个语音段对应,编写程序,实现按键播放不同的语音段。
二、实验设计思路:
本次实验用c语言实现,主要包括LCD,LED,AD,DA,日历芯片,测温传感芯片。受到嵌入式系统实验的启发,将LCD,LED,I2C总线协议,键盘扫描模块接口写成一个文件库(放在library文件夹下),尽量做到调用时与底层硬件无关。通过调用库文件中的函数,实现代码的重用性。键盘,LCD的代码由于与嵌入式实验具有相通之处,因此可将高层的函数(与底层硬件无关的函数)方便地移植过来。
三、实验设计:
1.矩阵键盘扫描模块
4×4的矩阵键盘,通过扫描可得到按下键的行列值,将行列值转换为相应的对应数字0~F。函数GetKey()实现获得按键的键值。对于键盘模块对于对按键的键值识别主要是通过两次扫描而取得。对于第一次扫描,给四行键全部赋予1,然后读回键盘值,对于第二次扫描,逐行为键盘送1,每次送1后再读回键盘值,若非零,说明此行有键按下,最终确定键值。
通过调用GetKey函数构造GetChar()函数,实现获取键盘字符(’0’~’F’)的功能。
通过调用GetChar()函数构造GetDec()函数,实现获取键盘输入整数的功能,整数范围在0~99999。有按’C’键回退一格,按’E’清空当前未完输入,按’F’键结束输入的功能。
程序代码:
//键盘初始化,将标志位置1;
void Key_Init(void)
{
bKeyUp_Flag=1;//标志(全局变量)位置1
}
//键盘扫描函数,得到键的行列位置;
unsigned char GetScanKey(void)
{
unsigned char key, i, temp;
unsigned char xdata * ptr;
key=0xff;
for (i=1; i<0x10; i<<=1) //i的低4位为行数位,行依次检测循环4次 {
ptr=0x8fff;
* ptr =i;
temp = * ptr; //取键盘IO口的值
temp &= 0x0f; //屏蔽高四位
if (temp!=0x00) //是否有有效键值
{
key = i<<4; //取行数位的值并将其放入返回值高4位 key|=temp; //列数位的值放入返回值低4位 break;
}
}
return key; //返回行位(高四)和列位(低四) }
//取键值,长按无效
unsigned char GetKey(void)
{
unsigned char key, temp;
if (!bKeyUp_Flag) //判断标志,是0执行
/***按键程序执行一次后会将bKeyUp_Flag标志位清零,执行此段程序, 长按键无效返回无效值,直至按键无效返回无效按键值,
置"1"标志位。按键输入恢复有效。屏蔽这部分则长按键有效***/ {
key=GetScanKey();
if (key==0xff) //没有按键,置标志位
bKeyUp_Flag=1;
else //保持按键
return 0xff; //因为0xff大于15,故为无效键值,实现长按键无效 }
key=GetScanKey();
if (key==0xff) //没有按键
return key;
else //有按键有效
temp=key; //取键值
Delay_ms(20); //延时20ms 消抖
key=GetScanKey(); //键盘扫描
if(key!=temp) //判断两次键值是否相同,排除干扰信号影响确认有效信号
{
key=0xff;
return key;
}
else //取键值
{
/*这部分主要作用是软件抗干扰*/
temp=Key_Value_Table[key>>4]; //见说明
/*行值有效位(键盘的4个行SEL返回的值含有的有效位"1")有且只有一位键值才有效否则返回无效键值*/
if (temp==0xff)
{
key=0xff;
return key;
}
temp=Key_Value_Table[key&0x0f];
/*列值有效位(键盘的4个列RL返回的值含有的有效位"1")有且只有一位键值才有效否则返回无效键值*/
if (temp==0xff)
{
key=0xff;
return key;
}
key=Key_Value_Table[key>>4]*4+Key_Value_Table[key&0x0f];//行对应的中间值的四倍与列对应的中间值之和即为按键编号0~15
/*行列组合后的值大于15无效*/
if (key>15)
{
key=0xff;
return key;
}
bKeyUp_Flag=0;
return key;
}
}
//获得键盘输入字符
int GetChar(void)
{
unsigned char key=0xff;
while(key==0xff)key=GetKey();/* wait input fix */
return ((int)key_arrenge[key]);
/* change to ASCII code and return */
}
//获得键盘输入整数
long GetDec(void)
{
long out_dec = 0; /* result(decimal number) 数值范围0~99999 */
unsigned char i,j; /* variable for character count */ int temp = 0;
int key;
for(i = 0;i<6 ; )
{
key =GetChar(); /* 获取键值’0’-’9’ */
if((key<='9')&&(key >= '0'))
{
temp = key - '0'; /* 获取数值0-9 */
out_dec = out_dec * 10 + (long)temp;
if(out_dec == 0)
{
continue; /* 首位数据输入为0时,显示不变 */
}
i++;
LCD__putchar(key); /* 显示当前输入的数据 */
continue;
}
if('E'== key) /* 当前输入清零 */
{
out_dec = 0;
back_cursol(i); /* 清显示区 */
for(j=0;j<i;j++)
LCD__putchar(' ');
back_cursol(i); /* 清显示区 */
continue;
}
if('F' == key) /* ENTER键,数值确认 */ {
return out_dec;
}
if('C' == key) /* 撤销最近一个输入数字*/
{
out_dec = out_dec / 10;
back_cursol(1);
LCD__putchar(' ');
back_cursol(1);
continue;
}
}
}
2. LED模块
通过送字形码和字位码可以点亮对应的一个8段LED。8个LED的同时显示通过循环扫描显示实现,即每次在一个LED上显示设定的数字,延时显示一段时间(延时越长,亮度越好),然后切换显示下一个,依次轮换。
程序代码:
unsigned char Led_table[16]={0x3f,0x06,0x5b,0x4f,0x66,
0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71
};
void Delay_us1(int t)
{
while(t-->=0);
}
//函数功能描述:给显示数组赋初始值;
void Led_Init(unsigned char *show)
{
unsigned char i;
for (i=0;i<8;i++)
{
//在此处给显示数组赋值
*show= 0x00;
show++;
}
}
//函数功能描述:在LED上显示;
void display(unsigned char *show)
{
unsigned char i=1,j;
unsigned char xdata *ptr;
for (j=0; j<8; j++)//八个数码管全部显示
{
ptr=0x8fff;//段选地址
*ptr=i;
ptr=0x9fff;//位选地址
//在此给*ptr赋值送字形码
*ptr=Led_table[show[j]];
Delay_us1(30);
*ptr=0x00;
i<<=1;//下一位
}
}
//数字转led字形码
unsigned char asc2led(unsigned char a)
{
return *(Led_table+a);
}
3. LCD显示模块
设计思路:
LCD的显示通过给LCD写命令和数据实现。写数据与写指令时各控制引脚的电平不同,对应了不同的地址。先构造写数据和写命令函数(与硬件相关操作),已经检测LCD是否忙的函数(当处于忙状态时不能写数据,否则硬件会工作不正常),在此接口的基础上构造库(与硬件无关操作,通过调用写数据和写命令函数实现),实现各种不同的功能,包括初始化设定工作方式,显示字符,显示字符串,显示数字(0~99999),清屏,设定光标的位置等。这里的显示字符,显示整数和键盘检测模块的读入字符,读入整数对应起来,可以合作使用。
使用时先调用初始化函数初始化LCD,设定工作方式,然后可以调用不同的函数实现不同的显示。
程序代码:
/*===== define variable =====*/
unsigned char cursol_x; /* cursor position (horizontal) */ unsigned char cursol_y; /* cursor positon (vertical) */
unsigned char xdata *ptr;//指向XDATA的指针(访问片外地址)
unsigned char code ASC2_Value_Table[16]=
{0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,
0x38,0x39,0x41,0x42,0x43,0x44,0x45,0x46};
//===========向1602指令寄存器写指令=============
void WriteW(unsigned char a)
{
ptr=0xAFF0; //RS=0,R/W=0
*ptr=a;
}
//==============向1602指令寄存器写指数据============
void WriteD(unsigned char key_asc2)
{
CheckBF();
ptr=0xAF02;
*ptr=key_asc2;
}
//===========检查忙标志位BF=============
void CheckBF(void)
{
unsigned int i;
while(1)
{
ptr=0xAFF1;//RS=0,R/W=1
i=*ptr;
i&= 0x80;
if(i==0)
break;
}
}
//==========LCD初始化==========
void LCD_Init(void)
{
CheckBF();
WriteW(0x38);
CheckBF();
WriteW(0x01);//Clear display 清显示
CheckBF();
WriteW(0x06);//Entry mode set:I/D=1,S=0 [I:Increase,D:Decline,S:Shift] CheckBF();
WriteW(0x0F);//Dislpay on/off control D=1,C=1,B=1[D:Display,C:Cursor,B:Blink] CheckBF();
WriteW(0x80);//Dislpay on/off control D=0,C=0,B=0[D:Display,C:Cursor,B:Blink] }
//======设定光标所在位置==========
void LCD__setcursol(unsigned char x, unsigned char y)
{
cursol_x = x; /* cursor position (horizontal) */ cursol_y = y; /* cursor positon (vertical) */ CheckBF(); /* wait LCD process */ WriteW(0x80 | y*0x40 + x); /* address of the second line 0x40-0x4f */
}
//======后移光标=======
void move_cursol(void)
{
if(++cursol_x > 0x0f)
{ /* move cursor to right, if get to end of the line */
cursol_x = 0; /* move to begin of the line */ if(++cursol_y >= 2)
{ /* return, if over the second line */ cursol_y = 0; /* return to begin of the line */ }
LCD__setcursol(cursol_x, cursol_y); /* set cursor */
}
}
//======前移光标=======
void back_cursol(int i)
{
cursol_x -= i;
LCD__setcursol(cursol_x, cursol_y); /* set cursor */
}
//======清屏=======
void LCD__clear(void)
{
CheckBF(); /* wait LCD process */ WriteW(0x01); /* clear display */
}
//=======写字符==========
void LCD__putchar(int c)
{
if((c == '\n')||(c == '\r')) /* line feed code */
{
cursol_x = 0;
cursol_y ^= 1;
LCD__setcursol(cursol_x, cursol_y); /* set cursor */
}
CheckBF(); /* wait LCD process */
WriteD(c); /* write Data-Register */
CheckBF(); /* wait LCD process */
move_cursol(); /* move cursor position */ }
//=========写字符串========
void LCD__puts(const unsigned char *s)
{
for(; '\0' != *s; s++)
{ /* loop before null code */ LCD__putchar(*s); /* display character on LCD */
}
}
//=========写整数=======
void LCD__putdec(long dec) /* 可打印的数据小于99999 */
{
long num ; /* number to be displayed */ int i; /* loop counter */ unsigned char str_buf[5]; /* buffer for data display */
num = dec; /* save number to be displayed */
for(i = 0 ;num != 0; i++)
{ /* confirm digit */ str_buf[i] = num % 10; /* get every digit number and save in buffer*/
num = num / 10;
}
if(i == 0)
{ /* if number is 0 */ LCD__putchar('0'); /* display '0' in LCD */ }
else
{ /* if number is not 0 */
for(--i; i >= 0; i--)
{ /* loop times of digits */
LCD__putchar(str_buf[i] + '0'); /* after transfered into ASCII code */
} /* display in LCD */ }
}
//十六进制码转换为ascii码
unsigned char Key_ASC2(unsigned char key)
{
unsigned char key_asc2;
key_asc2=ASC2_Value_Table[key];
return key_asc2;
}
4.I2C总线协议
I2C总线是由数据线SDA和时钟SCL构成的串行总线,可发送和接收数据。I2C总线在传送数据过程中共有三种类型信号,它们分别是:开始信号、结束信号和应答信号。
开始信号:SCL为高电平时,SDA由高电平向低电平跳变,开始传送数据。 结束信号:SCL为低电平时,SDA由低电平向高电平跳变,结束传送数据。 应答信号:接收数据的IC在接收到8bit数据后,向发送数据的IC发出特定的低电平脉冲,表示已收到数据。CPU向受控单元发出一个信号后,等待受控单元发出一个应答信号,CPU接收到应答信号后,根据实际情况作出是否继续传递信号的判断。若未收到应答信号,由判断为受控单元出现故障。
协议代码:
sbit SCL=P1^0;
sbit SDA=P1^1;
//***************************************
//*功能:启动I2C总线
//****************************************
void i2c_start()
{
SDA=1;
SCL=1;
_nop_();
_nop_();
SDA=0;
_nop_();
_nop_();
SCL=0;
}
//*************************************************************************
//*功能:停止I2C总线
//**************************************************************************
void i2c_stop()
{
SDA=0;
SCL=0;
_nop_();
_nop_();
SCL=1;
_nop_();
_nop_();
SDA=1;
}
//************************************************* //*功能:应答I2C总线 //************************************************* bit i2c_rec_ack()
{
bit ack_flag;
SDA=1;
_nop_();
_nop_();
SCL=1;
_nop_();
_nop_();
if(SDA==0)
{
ack_flag=1;
}
else
{
ack_flag=0;
}
SCL=0;
return ack_flag;
}
void i2c_send_ack(void)
{
SDA=0;
_nop_();
_nop_();
SCL=1;
_nop_();
_nop_();
SCL=0;
_nop_();
_nop_();
SDA=1;
}
//******************************************
//*功能:无应答I2C总线
//******************************************
void i2c_send_noack()
{
SDA=1;
_nop_();
_nop_();
SCL=1;
_nop_();
_nop_();
SCL=0;
}
//************************************************************** //*功能:往I2C总线发送数据
//************************************************************** void i2c_send_byte(unsigned char data_byte)
{
unsigned char i;
for(i=0;i<8;i++)
{
SDA=(bit)(data_byte&0x80);
_nop_();
_nop_();
SCL=1;
_nop_();
_nop_();
SCL=0;
data_byte<<=1;
}
}
//************************************************************* //*功能:从I2C总线接受数据
//*************************************************************** unsigned char i2c_rec_byte()
{
unsigned char i, temp;
temp=0;
for(i=0;i<8;i++)
{
temp<<=1;
SCL=1;
_nop_();
_nop_();
if(SDA==1)
{
temp|=0x01;
}
SCL=0;
}
return(temp);
}
//************************************************
//*功能:往I2C总线上器件指定地址写字节的数据 //************************************************
bit i2c_write_byte(unsigned char sla,unsigned char data_addr,unsigned char data_byte)
{
i2c_start(); // 发送I2C START信号 i2c_send_byte(sla); // 发送器件从地址 if(i2c_rec_ack()==0) // 检测应答信号
{
return 0;
}
i2c_send_byte(data_addr); // 发送数据地址 if(i2c_rec_ack()==0) // 检测应答信号
{
return 0;
}
i2c_send_byte(data_byte);
if(i2c_rec_ack()==0)
{
return 0;
}
i2c_stop(); // 发送I2C STOP信号 return 1;
}
//**************************************
//*功能:往I2C总线上器件指定地址开始写指定长度的数据 //***************************************
bit i2c_write_nbyte(unsigned char sla,unsigned char data_addr,unsigned char *ptr,unsigned char len)
{
i2c_start(); // 发送I2C START信号 i2c_send_byte(sla); // 发送器件从地址 if(i2c_rec_ack()==0) // 检测应答信号
{
return 0;
}
i2c_send_byte(data_addr); // 发送数据地址 if(i2c_rec_ack()==0) // 检测应答信号
{
return 0;
}
for(;len>0;len--) // 连续发送LEN长度的字节数据
{
i2c_send_byte(*ptr++);
if(i2c_rec_ack()==0)
{
return 0;
}
}
i2c_stop(); // 发送I2C STOP信号 return 1;
}
//***********************************
//*功能:从I2C总线上器件指定地址接收一个字节的数据 //*************************************
unsigned char i2c_read_byte(unsigned char sla,unsigned char data_addr) {
unsigned int temp;
i2c_start(); // 发送I2C START信号 i2c_send_byte(sla); // 发送从器件写命令 if(i2c_rec_ack()==0) // 检测应答信号
{
return 0;
}
i2c_send_byte(data_addr); // 发送数据地址 if(i2c_rec_ack()==0) // 检测应答信号
{
return 0;
}
i2c_start(); // 重新发送I2C START信号 i2c_send_byte(sla|0x01); // 发送器件从器读命令 if(i2c_rec_ack()==0) // 检测应答信号
{
return 0;
}
temp=i2c_rec_byte();
i2c_send_noack(); // 接收最后一个字节后发送NOACK信号
i2c_stop(); // 发送I2C STOP信号
return temp;
}
//**************************************
//*功能:从I2C总线上器件指定地址接收一个字节的数据 //***************************************
bit i2c_read_nbyte(unsigned char sla,unsigned char data_addr,unsigned char *ptr,unsigned char len)
{
i2c_start(); // 发送I2C START信号 i2c_send_byte(sla); // 发送从器件写命令 if(i2c_rec_ack()==0) // 检测应答信号
{
return 0;
}
i2c_send_byte(data_addr); // 发送数据地址 if(i2c_rec_ack()==0) // 检测应答信号
{
return 0;
}
i2c_start(); // 重新发送I2C START信号 i2c_send_byte(sla|0x01); // 发送器件从器读命令 if(i2c_rec_ack()==0) // 检测应答信号
{
return 0;
}
for(;len>1;len--) // 连续接收LEN-1长度的字节数据
{
*ptr++=i2c_rec_byte();
i2c_send_ack(); // 发送ACK信号 }
*ptr=i2c_rec_byte(); // 接收最后一个字节 i2c_send_noack(); // 发送NOACK信号 i2c_stop(); // 发送I2C STOP信号 return 1;
}
5. AD模块(SPI总线)
利用TLC1549实现,TLC1549可以将输入的模拟量转换成数字量,并且是10位数字量,然后也是利用SPI总线一位一位的发送,因此需要传两字节的数据,高地址中的低两位有效,低地址中的八位都有效,传送过程结合时序图进行编程。
模块使用的函数是模块驱动程序参考中的函数
sbit SCLK=P1^2;
sbit DIO=P1^3;
sbit ADCS=P1^4;
sbit DACS=P1^5;
void Before_Once_AD(void);//预采集
uint adc_1549(void) ;//读取AD输出的10bit数据
6. DA模块(SPI总线)
利用TLC5615实现的,TLC5615也是利用SPI总线一位一位的传送,并且要求传送的数据是12位,因此需要传两字节的数据,分别放在HIGHD和LOWD中,12位数据是HIGHD中的八位和LOWD中的高四位,但是12为数据中的低两位又是无效的,所以只有10位有效数字。模块使用的函数是模块驱动程序参考中的函数
//函数功能描述:驱动TLC5615开始DA转换
//dat为被转换的数字量
void tlc5615(uint dat) ;
7.日历芯片
PCF8563 是低功耗的CMOS 实时时钟/日历芯片,它提供一个可编程时钟输出,一个中断输出和掉电检测器,所有的地址和数据通过I2 C 总线接口串行传递。每次读写数据后,内嵌的字地址寄存器会自动产生增量。按I2C 总线协议规约PCF8563 有唯一的器件地址0A2H。因此,可以由CPU把初始化的日历时钟通过总线接口串行传递写入PCF8563,由PCF8563以此为起点自动计时,再通过I2C总线串行接口从PCF8563读出内部的日历/时钟。
这部分主要是在I2C总线协议的基础上进行通信,向芯片指定地址写入控制字,写入时间数据或读出时间数据。由于芯片中的实际数据都是以BCD码形式存储,因此通信时需要进行十六进制数和BCD码的互相转换。
具体设计函数包括向芯片写入时间,从芯片读出时间,码制转换。
程序代码:
//8bit 十六进制转bcd码
unsigned char NUM2BCD(unsigned char x)
{return ((((x)/10)<<4)|(x%10));
}
//8bit bcd码转十六进制
unsigned char BCD2NUM(unsigned char x)
{
return (((x)>>4)*10+((x)&0x0f));
}
/*-----------------------------------------------
函数说明:写入时间:秒,分,时,日,星期,月,年,BCD码
-----------------------------------------------*/
void wt_time(unsigned char *t)
{
i2c_write_byte(0xA2,0x00,0x00);//开始计时
i2c_write_nbyte(0xA2,0x02,t,7);//写入时间
}
/*-----------------------------------------------
读时间
-----------------------------------------------------*/
void rd_time(unsigned char *time)
{
int i;
unsigned char addr=0x02;
unsigned char temp;
for(i=0;i<7;i++)
{
temp=i2c_read_byte(0xA2,addr);
if(i==0||i==1) //去掉无效位 //秒,分
temp&=0x7f;
if(i==2||i==3) //时,日
temp&=0x3f;
if(i==4) //星期
temp&=0x07;
if(i==5) //月
temp&=0x1f;
time[i]=temp;
//Delay_ms(50);
Delay_us(100);
addr=addr+1; //页读须软件累加地址
}
}
8.语音芯片
这部分直接使用了模块驱动程序参考中的函数,ISD_WR_APC2(uchar voiceValue)函数可以调节音量,0xa8对应最大音量,0xaf对应最小音量。play_open(unsigned int AddST,unsigned int AddEN)函数可以根据播放的首尾地址播放不同的音乐。
9.库文件定义和使用
一个模块的文件包括头文件(*.h)和源文件(*.c)。在头文件中进行函数的声明,在对应源文件中将头文件首先包含进来,然后对函数进行具体定义实现。使用时用到了某个模块,先将头文件include进来,然后在工程中添加对应的C文件。
千万不要忽略了头文件中的#ifndef,这是一个很关键的东西。比如你有两个C文件,这两个C文件都include了同一个头文件。而编译时,这两个C文件要一同编译成一个可运行文件,于是问题来了,大量的声明冲突。
还是把头文件的内容都放在#ifndef和#endif中吧。不管你的头文件会不会被多个文件引用,你都要加上这个。一般格式是这样的:
#ifndef <标识>
#define <标识>
……….
#endif
四、实验过程:
1.键盘和LCD的测试
功能描述:
初始状态:LCD上第一行显示"0:get char",第二行显示"1:get decimal"。按0进入获取字符并显示的状态,按字符’F’退出返回初始状态,按1进入获取整数并显示状态(整数范围:0~99999),当键入99999时退出返回初始状态。 主要代码:
###[lcdtest.c]###
void main(void)
{
unsigned char key,flag;
long dec;
LCD_Init();//lcd初始化
Key_Init();
LCD__clear();
while(1)
{
LCD__setcursol(0,0);
LCD__puts("0:get char");
LCD__setcursol(0,1);
LCD__puts("1:get decimal");
flag=GetChar();
if(flag=='0'||flag=='1')break;
}
LCD__clear();
LCD__setcursol(0,0);
while(1)
{
if(flag=='0')
{
key=GetChar();
LCD__putchar(key);
if(key=='F')break;//当按F时跳出
}
if(flag=='1')
{
LCD__setcursol(0,0);
dec=GetDec();
LCD__clear();
LCD__putdec(dec);
if(dec==99999)break;//当输入整数为99999时跳出
}
}
}
2.LCD和LED显示日历
功能描述:
初始状态LCD显示欢迎界面,提示按F键进入,按F后进入提示菜单,按0选择程序中默认的日期设定,按1选择手动设定日期,依次按照提示键入日期。日期设定后在LCD上显示全部日期信息,LED上显示时分秒。
主要代码:
###[calendar.c]###
unsigned char show[8]={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};//led显示数值表
unsigned char time[7]={0x01};//秒,分,时,日,星期,月,年(BCD码) /*-----------------------------------------------
函数说明:软件设定时间:秒,分,时,日,星期,月,年,BCD码
-----------------------------------------------*/
void set_time(unsigned char *time)
{
LCD__clear();
LCD__setcursol(0,0);
LCD__puts("Set year(0~99):");
LCD__setcursol(0,1);
time[6]=NUM2BCD((unsigned char)(GetDec()));
LCD__clear();
LCD__setcursol(0,0);
LCD__puts("Set month(1~12):");
LCD__setcursol(0,1);
time[5]=NUM2BCD((unsigned char)(GetDec()));
LCD__clear();
LCD__setcursol(0,0);
LCD__puts("Set day:");
LCD__setcursol(0,1);
time[3]=NUM2BCD((unsigned char)(GetDec()));
LCD__clear();
LCD__setcursol(0,0);
LCD__puts("Set weekday(0~6):");
LCD__setcursol(0,1);
time[4]=NUM2BCD((unsigned char)(GetDec()));
LCD__clear();
LCD__setcursol(0,0);
LCD__puts("Set hour(0~23):");
LCD__setcursol(0,1);
time[2]=NUM2BCD((unsigned char)(GetDec()));
LCD__clear();
LCD__setcursol(0,0);
LCD__puts("Set minute(0~59):");
LCD__setcursol(0,1);
time[1]=NUM2BCD((unsigned char)(GetDec()));
LCD__clear();
LCD__setcursol(0,0);
LCD__puts("Set second(0~59):");
LCD__setcursol(0,1);
time[0]=NUM2BCD((unsigned char)(GetDec()));
}
/*-----------------------------------------------
函数说明:软件设定缺省时间:秒,分,时,日,星期,月,年,BCD码 -----------------------------------------------*/
//将2011 年6 月13 日星期一下午3 点(15 点)59 分30 秒的时间写入PCF8563 void set_default_time(unsigned char *t)
{
t[0]=0x30;
t[1]=0x59;
t[2]=0x15;
t[3]=0x13;
t[4]=0x01;
t[5]=0x06;
t[6]=0x11;
}
//--------------日历显示--------------
void CalendarDisp(unsigned char * time)
{
//---------------LCD显示-----------------------
LCD__setcursol(0,0);
//year
LCD__putchar('2');
LCD__putchar('0');
LCD__putchar(Key_ASC2((time[6]>>4)&0x0f));
LCD__putchar(Key_ASC2(time[6]&0x0f));
LCD__putchar('-');
//month
LCD__putchar(Key_ASC2((time[5]>>4)&0x0f));
LCD__putchar(Key_ASC2(time[5]&0x0f));
LCD__putchar('-');
//day
LCD__putchar(Key_ASC2((time[3]>>4)&0x0f)); LCD__putchar(Key_ASC2(time[3]&0x0f)); LCD__putchar(' ');
//weekday
switch(time[4])
{
case 0:LCD__puts("Sun");break;
case 1:LCD__puts("Mon");break;
case 2:LCD__puts("Tue");break;
case 3:LCD__puts("Wed");break;
case 4:LCD__puts("Thu");break;
case 5:LCD__puts("Fri");break;
case 6:LCD__puts("Sat");break;
}
LCD__setcursol(0,1);
//hour
LCD__putchar(Key_ASC2((time[2]>>4)&0x0f)); LCD__putchar(Key_ASC2(time[2]&0x0f)); LCD__putchar(':');
//minute
LCD__putchar(Key_ASC2((time[1]>>4)&0x0f)); LCD__putchar(Key_ASC2(time[1]&0x0f)); LCD__putchar(':');
//second
LCD__putchar(Key_ASC2((time[0]>>4)&0x0f)); LCD__putchar(Key_ASC2(time[0]&0x0f)); //---------------LCD显示_end----------------------- //---------------LED显示----------------------- show[7]=show[6]=0;
show[5]=time[2]>>4;
show[4]=time[2]&0x0f;
show[3]=time[1]>>4;
show[2]=time[1]&0x0f;
show[1]=time[0]>>4;
show[0]=time[0]&0x0f;
display(show);
//Delay_ms(20);
//---------------LED显示_end----------------------- }
//*********main***************** void main(void)
{
unsigned char key,i;
LCD_Init();//lcd初始化
Key_Init();
LCD__clear();
LCD__setcursol(0,0);
LCD__puts("----Calendar----");
LCD__setcursol(0,1);
LCD__puts("press F to start");
while(GetChar()!='F');
LCD__clear();
LCD__setcursol(0,0);
LCD__puts("0 autoset");
LCD__setcursol(0,1);
LCD__puts("1 manualset");
while(1)
{
key=GetChar();
if(key=='0')
{
set_default_time(time);
break;
}
if(key=='1')
{
set_time(time);
break;
}
}
wt_time(time);
LCD__clear();
while(1)
{
rd_time(time);
CalendarDisp(time);
if(GetKey()!=0xff)break;//当按任意键时跳出
}
}
//*********main_end*****************
3.D/A通道锯齿波发生器
功能描述:
初始状态LCD显示界面,按1产生方波,按2产生锯齿波。调节示波器扫描周期和幅值可以观察到DA的输出端口的输出的波形。
主要代码:
###[da.c]###
void main(void)
{
uint dat=0x0000;
uchar key;
LCD_Init();
LCD__setcursol(0,0);
LCD__puts("1square"); LCD__setcursol(0,1);
LCD__puts("2sawtooth"); while(1)
{
key=GetChar();
if(key=='1'||key=='2')break; }
LCD__clear();
while(1)
{
if(key=='1')//方波 {
LCD__setcursol(0,0); LCD__puts("square"); tlc5615(0);
Delay_us(100); tlc5615(512); Delay_us(100); }
if(key=='2')//锯齿波 {
LCD__setcursol(0,0); LCD__puts("sawtooth"); if(dat<512)
dat=dat+10; //步进 else dat=0;
tlc5615(dat); Delay_us(5); }
if((GetKey())!=0xff){break;} }
}
4.A/D通道读取电压显示在LCD上 功能描述:
将J3口的1,2用导线短接,调节电位器R20,使电压从0~5v改变。初始
状态下LCD显示界面,按1进行均值滤波,按2键进行中值滤波,按3键进行滑动平均滤波。假设采样数据为N个,均值滤波是将N个数据求取平均值,中值滤波是先将N个数据排序,然后取中值,滑动平均滤波是每次用新采样的m个数据替换最老的m个数据(m<N),然后将N个数据进行均值滤波。每次按一个键更新一次电压显示值,按’F’键返回主界面。
主要代码:
###[ad.c]###
#define NUM 4//滤波样本数据个数
//函数功能描述;预先采集;
void Before_Once_AD(void)
{
uchar i;
SCLK=DIO=0;
ADCS=0; //开启控制电路,使能DATA OUT和I/O CLOCK
for(i=1;i<=10;i++)
{
SCLK=1;
_nop_();
SCLK=0;
_nop_();
}
ADCS=1;
Delay_us(25);//两次转换间隔大于21us
}
//排序函数
void sort(uint *Array)
{
uchar i,j;
uint temp;
for(i=0;i<NUM-1;i++)
{
for(j=i+1;j<NUM;j++)
{
if(Array[i]>Array[j])
{
temp=Array[i];
Array[i]=Array[j];
Array[j]=temp;
}
}
}
}
//平均值滤波
uint ad_FilterAverage(uint *Array)
{
uchar i;
uint sum=0;
for (i=0;i<NUM;i++)sum+=Array[i]; return sum/NUM;
}
//中值滤波
uint ad_FilterMedia(uint *Array) {
sort(Array);
return Array[NUM/2+1];
}
//滑动平均滤波
uint ad_FilterSlide(uint *Array)
{
uchar i;
for(i=0;i<NUM-1;i++)Array[i]=Array[i+1]; Array[NUM-1]=adc_1549();
return ad_FilterAverage(Array); }
void main(void)
{
uint ad,da;
uchar temp,i,key;
uint Array[NUM];
float fad;
uint lad;
LCD_Init();
LCD__setcursol(0,0);
LCD__puts("1average 2media");
LCD__setcursol(0,1);
LCD__puts("3slide");
while(1)
{
key=GetChar();
if(key=='1'||key=='2'||key=='3')break; }
Before_Once_AD();
for(i=0;i<NUM;i++)Array[i]=adc_1549(); LCD__clear();
while(1)
{
Before_Once_AD();
switch (key)
{
case '1'://平均值滤波
for(i=0;i<NUM;i++)Array[i]=adc_1549();
ad=ad_FilterAverage(Array);
LCD__setcursol(0,0);
LCD__puts("Average filter");
break;
case '2'://中值滤波
for(i=0;i<NUM;i++)Array[i]=adc_1549();
ad=ad_FilterMedia(Array);
LCD__setcursol(0,0);
LCD__puts("Medium filter");
break;
case '3'://滑动平均滤波
ad=ad_FilterSlide(Array);
LCD__setcursol(0,0);
LCD__puts("Slide filter");
break;
}
fad=(float)(ad)/1024.0*5.0; //转化为模拟电压值
lad=(uint)(fad*10000);//放大
LCD__setcursol(0,1);
LCD__putdec(lad);
if(GetChar()=='F')break;
}
}
5.语音芯片模块
功能描述:
按0~B键选择不同声音播放,按C键减音量,按D键加音量。 主要代码:
###[voice.c]###
//--------------主函数,可选择录音,调节音量------------------------------------ void main(void)
{
uchar key=0xff;
LCD_Init();
ISD_Init();
ISD_WR_APC2(voice);
while(1){
LCD__clear();
LCD__setcursol(0,0);
LCD__puts("voice1-13");
LCD__setcursol(0,1);
LCD__puts("chvo:down13/up14");
select_voice(key);
key=0xff;
while(key==0xff)
{
key=GetKey();
}
}
}
//根据键值选择播放的音乐
void select_voice(uchar key)
{
switch(key)
{
case 13:changevoice(0); //调低音量
LCD__clear();
LCD__setcursol(0,0);
LCD__puts("voice down");break;
case 14:changevoice(1);//调高音量
LCD__clear();
LCD__setcursol(0,0);
LCD__puts("voice up");break;
case 15:break;
default:play_open(Audio_Addr[key],Audio_Addr[key+1]);break;//选择歌曲 }
}
//音量调节函数
void changevoice(uchar flag)
{
if (flag==0&&voice<0xaf)//调小音量
{
voice++;
}
else if(flag==1&&voice>0xa8)//调大音量
{
voice--;
}
ISD_WR_APC2(voice);
}
五、心得体会:
本次实验主要学习了一些单片机与CPU与外设通信和控制协议,包括I2C总
线,SPI总线,DS18B20的单总线通信等。通过实验,让我认识到实验板的强大功能,也让我看到了单片机等嵌入式系统的广泛应用。
最后感谢老师在实验中的耐心指导和帮助!