电子信息课程设计报告
2014-6-5
目录
一、题目及要求 ......................................................................................................................... 1
二、程序设计思想以及流程 ..................................................................................................... 1
1. 程序设计思想 ............................................................................................................................ 1 2. 系统初始化程序设计 ................................................................................................................ 1 3. 按键扫描及处理设计 ................................................................................................................ 2 4. 数码管显示代码设计 ................................................................................................................ 7 5. DAC输出代码设计 .................................................................................................................... 8 6. PCA模块时钟输出代码设计 ..................................................................................................... 9
三、设计电路的工作原理 ....................................................................................................... 10
1. 稳压电路设计 .......................................................................................................................... 10 2. 滤波电路设计 .......................................................................................................................... 11 3. 功放电路设计 .......................................................................................................................... 11 4. 完整电路 .................................................................................................................................. 12
四、焊接与测试 ....................................................................................................................... 12
五、体会与心得 ....................................................................................................................... 15
六、附 录................................................................................................................................ 16
1
一、 题目及要求
设计名称:数字合成简易信号发生器的设计
任务要求:自行利用C8051F020实验开发板设计编程,完成正弦信号的程控输出。
二、 程序设计思想以及流程
1.
程序设计思想 本系统主要基于以C8051F020单片机为核心的实验板,使用单片机的DAC模块完成对正弦的采样输出,经外接滤波、放大电路获得可调频的正弦信号。而我们的设计目标是使输出精度尽量高,输出频率范围尽量广。
在输出精度方面,要减小经滤波放大以后的信号的失真程度,在硬件上我们选取截至频率可程控的八阶椭圆滤波器芯片Max7400,在软件设计上可以从两方面上入手:一、提高采样精度,二、减少杂波干扰。为了提高采样精度,我们在定时器的选取方式上选取16位可自动重装的定时器3,这样可以使较为精确的控制采样频率,此外优化简化代码,使单片机CPU尽可能的不影响DAC输出;为了减少杂波的干扰,经理论计算表明,单片机DAC输出信号
∞频谱函数为:?? ω =A ?=?∞?? ???2 ?(?????),其中?(?)为预得信号频谱,A为常数。
从表达式可以看出,杂波分量为信号与???(n>0)的和频与差频,从低通滤波器的角度来看,采样频率与信号频率相差越远越好,即采样点数N=???越大越好。但在单片机的工作方面,
采样频率一高,就要求中断时间要短,而当中断时间短到一定程度时,就好比你在工作时一直有电话骚扰,单片机的主程序执行会受到阻碍,按键得不到响应,数码管显示有明显闪烁。因此我们将采样频率弱限定在48KHZ(24KHz~96KHz)附近,之所以说是弱限定,是因为对于低频(<188Hz)和高频(>12KHz),还是可以超出此范围。为了尽量使滤波效果好,我们采用变点采样,采样点数为4~256点,对于低频采样点数多,对于高频采样点数少。
基于以上两点,我们对程序进行规划设计,具体设计如下。
2. 系统初始化程序设计
2.1 系统初始化主要工作
(1) 关闭看门狗、等待晶振正常工作
(2) IO口配置及初始化
1
(3) 定时器初始化
(4) DAC初始化
(5) PCA初始化
(6) 数码管初始化显示
2.2 初始化核心代码
/****************************************************/
//程序初始化函数
/****************************************************/
void init()
{
} EA=0; //关中断 WDTCN = 0xDE; WDTCN = 0xAD; Osc_Init_24M(); io_config(); io_init(); timer_init(); dac_init(); PCA0_Init(); frequency_show(); //初始化显示频率 //初始化晶振 //IO口配置 //初始化io口 //初始化定时器 //初始化数模转换器 //关看门狗
3. 按键扫描及处理设计
3.1 编程思想
为了减少主函数周期运行时间,我们对按键的扫描及响应采用状态机的结构,对按键确认及长按判定,摒弃传统的延时方式,而使用利用定时器中断的自设累加器进行对按键的计时处理。
按键函数主要包含按键主函数、按键扫描函数、按键响应函数及一些相关处理函数。按键主函数采用状态机的结构完成按键扫描、按键确认、按键响应、按键释放与长按处理,具有占用主函数时序短、结构清晰的特点;按键扫描函数采用列反转法的方法对按键进行扫描,相较于逐行、逐列扫描它占用主函数时序较短;按键响应函数主要对各个按键的功能进行分配处理,结构清晰、便于修改。
2
3.2 核心代码
/****************************************************/ //按键主函数,使用状态机的方式完成对按键的扫描处理 /****************************************************/ void key() {
static unsigned char mode=0; static char key_num_now=-1; switch(mode) {
case 0:
//检测按键
//扫描按键并获取键值
//当键值有效进入下一状态 //5ms累加器清0
key_num_former=key_scan(); { } break;
//确认按键
//扫描按键并获取键值
key_num_now=key_scan(); { } else { } break;
//单按按键处理
3
//状态机状态标识 //前一次扫描键值 //当前扫描键值 //状态机定义
static char key_num_former=-1;
if(key_num_former>-1&&key_num_former<16)
mode=1;
key_5ms_count=0;
case 1:
if(key_num_now==key_num_former)
//当扫描键值前后相同时
//按键超过20ms,进入下一状态
if(key_5ms_count>4)
mode=2;
//当前后键值不同时回到状态0
key_num_now=-1; mode=0;
case 2:
} }
key_respond(key_num_now); mode=3; break;
//按键响应 //进入下一状态
case 3:
{ } else { }
//等待按键释放,若长时间不释放,视为长按
//当扫描键值前后相同时
//按键持续超过0.5s,判为长按 //此后每过0.1s进行一次按键响 //应即进入状态2
if(key_num_now==key_scan())
if(key_5ms_count>100) { }
key_5ms_count=50; mode=2;
//当扫描键值前后不同时 //回到状态0
mode=0;
break;
/****************************************************/ //按键扫描
//返回键值,-1为无按键
/****************************************************/ char key_scan() {
//用线反转法获取键值
//定义临时变量,用于记录KEY //临时变量,用于记录键值 //将行IO口全拉低
char key; KEY=0xff; KEY=0x0f; key=KEY;
if((key&=0x0f)!=0x0f) {
//如果列IO口有拉低位
4
char key_num=-1;
int i,j=0; //i:列标,j:行标
for(i=0;i<4&&(key!=key_row_num[i]);i++);//与码表比对获取列标 KEY=0xf0; //将列IO口拉低 key=KEY;
for(j=0;j<4&&(key!=key_line_num[j]);j++);//与码表比对获取行标 if(i<4&&j<4)
key_num=j*4+i; //计算键值
}
return key_num; //返回键值
}
/****************************************************/
//按键响应分配函数
/****************************************************/
void key_respond(char key_num)
{
switch(key_num) //按键名
{
case 0: //1
case 1: //2
case 2: //3
input_num(key_num+1); //输入数字
break;
case 4: //4
case 5: //5
case 6: //6
input_num(key_num); //输入数字
break;
case 8: //7
case 9: //8
case 10: //9
input_num(key_num-1); //输入数字
break;
case 13: //0
5
)
input_num(0); break;
//输入数字0
case 14: //00
//输入数字0 //输入数字0
input_num(0); input_num(0); break;
case 3: //Del //+
//当前显示不为空时
//当显示最高位为9时进位操作
//删除响应 //清除确认键标识
delete_num(); first_ok=0; break;
case 7:
{ } else { }
{ }
if(Fre_Dis_Point!=Frequency_Length)
if(Fre_Dis_Point>0&&(Frequency_Display[Fre_Dis_Point]==9))
Frequency_Display[Fre_Dis_Point--]=0; Frequency_Display[Fre_Dis_Point]=1;
//一般情况当前位加1
else
Frequency_Display[Fre_Dis_Point]++;
//数码管显示为空,令最低位为1
Frequency_Display[--Fre_Dis_Point]=1;
//执行确认键操作
key_enter(); break;
case 11:
{
//-
//当显示不为空时
if(Fre_Dis_Point!=Frequency_Length)
if(Fre_Dis_Point<Frequency_Length&&(Frequency_Display[Fre_Dis_Point]==1)
Frequency_Display[Fre_Dis_Point++]=10;
6
} }
}
else
Frequency_Display[Fre_Dis_Point]--;
key_enter();
//执行确认键操作
break;
//select
//清零确认键标识 //光标向高位移动
first_ok=0;
case 12:
Glitter_Point--; if(Glitter_Point==-1)
Glitter_Point=Frequency_Length;
//Enter
//执行确认键操作
break; key_enter(); break; break;
case 15:
default:
//************************************************
4. 数码管显示代码设计
4.1 编程思想
由于硬件结构的限制,数码管显示只能采用逐位扫描的方式,根据人眼视觉特性,人眼对光最小区分时间为0.1s,为此数码管扫描周期应小于100ms。
为了使数码管持续有效的显示,我们采用中断显示,每5ms进行一次数码管的扫描显示,这样扫描周期为30ms,符合人眼视觉特性。在数码管显示函数中,我们还增设了光标指向显示功能,被光标指向位将每隔0.64s变为光标或该位原有数字,实现人机友好交互。 4.2 核心代码
/****************************************************/ //数码管显示
/****************************************************/ void display()
7
{ }
static char select=0; P1=0xff;
P2=DIGI_EN[select];
P1=DIGI[11];
//数码管显示位标标识位 //位选前消隐显示 //位选数码管
//每0.64s在光标闪烁一次
if(select==Glitter_Point&&Glitter_Count<128) else
P1=DIGI[Frequency_Display[select]];
select=0;
select=select+1; if(select==6)
5. DAC输出代码设计
5.1 编程思想
DAC采样输出主要由DAC更新输出函数和更新输出配置函数组成。由于初始化我们设定DAC输出更新发生在定时器3溢出时,因此我们在定时器3中断函数中完成对DAC寄存器的赋值,以便下次DAC输出能更新。
我们选用余弦信号码表长度为256主要是因为unsigned char型数的取值范围为0~255,这样既可以获得足够的采样精度,又可以简化中断代码,优化程序执行效率。在DAC更新配置中,我们先以初始采样频率48KHz估算采样点数n,为了能从示波器上观察到较好的DAC输出波形,我们以此为基础将采样点数选为不大于n的能写为2?的形式的整数N,即真实采样点数,最后依此计算出采样频率,对DAC更新时间及时钟输出进行配置。 5.2 核心代码
/****************************************************/ //DAC输出更新,重余弦码表以output_divide的 //步进值取值输出
/****************************************************/ void update_dac() { }
8
DAC0H=Signal_Cos[Signal_Out]; Signal_Out+=output_divide;
//从余弦码表以output_divide为步进 //Signal_Out为0~255故不用清0
/****************************************************/ //更新DAC采样频率,更新PCA的CEX0的时钟输出 /****************************************************/ void update_timer_dac() { }
unsigned int n=Fs_Init/Frequency; if(n<3) do {
N*=2; n/=2;
//使N成为不大于n的2的k次幂 //计算采样步进值 //重新计算采样频率 //重设定时器3的中断时间
n=3;
n=Signal_Capacity;
//估算以频率Fs_Init采样时采样点数 //限制n=3~256
else if(n>Signal_Capacity) N=1;
}while(n!=0); N/=2;
output_divide=Signal_Capacity/N; Fs=Frequency*N;
TMR3RLL=(65536-2000000/Fs)%256; TMR3RLH=(65536-2000000/Fs)/256; TMR3L=TMR3H=0;
set_pca_clock(Frequency/2+Frequency); //配置PCA计数器CEX0频率输出
6. PCA模块时钟输出代码设计
6.1 编程思想
对于八阶椭圆低通滤波器Max7400,它要求时钟频率为中心频率的100倍,对于高频这是一个相当可观的频率值,若采用一般定时器中断方式输出,势必影响单片机执行性能及DAC的输出,为此我们采用单片机CPU干预极小的PCA计数器模块,该模块通过配置相关寄存器可对系统时钟直接进行分频工作,是时钟输出的极佳选择。
9
6.2 核心代码
/****************************************************/ //重新配置PCA0的模块CEX0的频率输出
/****************************************************/ void set_pca_clock(int fc) { }
CR=0; { } else { } CR=1;
//开启PCA0的计数
//当所设频率小于1M
// 采用系统时钟12分频,禁止中断 //=(24000000/12)/(2*fc*100);
PCA0MD = 0x00; PCA0CPH0=10000/fc;
//停止PCA0的计数 //当所设频率大于1M // 采用系统时钟,禁止中断 //=24000000/(2*fc*100);
if(fc>10000)
PCA0MD = 0x08; PCA0CPH0=120000/fc;
三、 设计电路的工作原理
1. 稳压电路设计
稳压电路输入端输入12V电源,而滤波电路和放大电路的供电电源均需5V,因此我们结合LM7805稳压芯片设计了稳压电路,用于系统供电。电路设计原理图如图1所示.
图1 稳压电路原理图
10
2. 滤波电路设计
由于我们利用单片机输出的采样信号含有高频成分,输出的波形并不理想,经过滤波后滤除高频成分,得到较理想的正弦波输出。经过查阅相关资料,我们选用max7400这款集成滤波器,利用它设计的椭圆滤波器具有较好的幅频特性以及通带和阻带纹波系数。通过外部时钟输入管脚我们可以通过单片机实时改变截止频率,得到不同采样频率下的输出波形。通过查阅max7400的技术手册,我们设计了不带频率补偿的椭圆滤波器电路。电路如图2所示。
图2 椭圆滤波器电路原理图
3. 功放电路设计
单片机的输出电压约3.7V左右,为了满足技术指标,同时提高输出电压幅度,我们利用LM358设计了放大电路。通过查阅技术手册,LM358内部有两集运放,放大倍数可达100倍,但是增益过高容易造成平顶失真,于是我们使用单级同向放大,放大倍数1到4倍左右,同时考虑到增益可调,我们使用电位器作为比例电阻,这样通过改变电位器的阻值即可达到调节增益的目的。电路原理图如图三所示。
图3 放大电路原理图
11
4.
完整电路 将各部分级联,组成功能完整的电路。如图4所示。
图4 完整电路原理图
四、 焊接与测试
将设计好的电路用Multisim仿真,结果不理想,Max7400进行仿真时始终没有输出,接下来我们先在一块万用板上焊接了一块测试电路,测试滤波器的特性,经测试所设计滤波器工作特性良好。于是我们在与实验板配套的PCB板上进行了完整的电路焊接,一步一步仔仔细细,模块化焊接,每焊接完成一个模块便进行检测调试,包括连通测试,芯片阻抗检测,无问题再进行下一步焊接。各模块焊接完成后,通过跳槽的方式将模块进行连接构成系统,最后单片机相连,测试我们的结果。图5是我们焊接的电路图片。
12
图5 实际电路图片
测试:
频率为1Hz时的波形
13
频率为1khz时的输出波形
频率为21.8Khz时的波形
14
经测试,我们设计的简易数字信号发生器有满足如下功能:
(1) 可变采样点数,采样点数4到256点。输出频率范围在1Hz~21.8khz。
(2) 输出幅度峰峰值3.7V~12V,后级放大电路增益可调,范围为1~4倍。
(3) 频率步进可调,步进值为1Hz.
(4) 7段码是扫描显示,可按数位调整频率值,光标可动。长按加减数值可连续变化。
(5) 低频(1Hz~5KHz)误差几乎为0,高频(>5KHz)最大误差1.14%。
五、 体会与心得
七周的课程设计过的飞快,转瞬即逝。在这七周的时间里,我和队友一起自己真正意义上制作了我们自己的产品。从题目的确定到编程思想的讨论及确定,再到软件程序的编写、原理图的设计,电路图的设计,到焊接,测试分析,一步一步,走的是那么的不容易。这个过程也让我知道了一件让人用的舒心的产品的设计不是一件简单的事。在开始的头几周,我们通过查阅大量的资料文献,初步确定了我们的程序方案,经过3周的程序编写,修改,调试,我们也编写了一个具有完整功能的程序。用开发班调试,虽然失败了很多次,但是不断的修改,纠错,版本化推进,一步一步完善程序,最终,看到波形输出的那一刻,我们欣喜若狂。接下来便是焊接,可靠的焊接对系统的成功还是十分必要的。由于原来参加过相关的电子工艺实习,在电路焊接这块我们没有遇到太多的问题。经过繁忙的7周,我们的产品终于新鲜出炉。看到自己的产品新鲜出炉,喜悦之情溢于言表。七周的实习,我们不仅学习运用了我们在原来课本上学到的知识,模拟电路,电路分析基础,单片机原理,同时通过这次课程设计,我们实现了从理论到实际的飞跃。俗话说,实践出真知,纸上得来终觉浅,真正在开发一个产品的时候,书本上遇不到的问问层出不穷,通过查阅技术手册,请教老师,仔细分析,一个一个困难我们解决。课本上的知识我们认识更加深刻,同时更在自己动手操作的过程中收获到了许多意想不到的知识。感谢学校给我们提供这次难得的机会,感谢由老师给予我们的耐心的教导!。
15
六、 附录
程序代码
///////////////////////////////////////////////////////////////////////////////////////////// //主函数文件
//////////////////////////////////////////////////////////////////////////////////////////// //version 3
//version 4
//version 5
//version 6
//version 7
//version 8
//version 9
//version 10
//version 10.1
//version 11
//version 12 优化算法,算出采样码表,解决码表更新赶不上输出,采样点波形连续 允许采样频率微调,理论上误差仅与量化误差与系统时钟有关 增加按键按位设置功能 完善显示_更新dac输出方式 完成pca输出代码 dac输出改为timer4 完成dac输出 完成pca输出 完善显示初始化代码 完善按键代码_增加余弦码表为256_增加时钟输出频率 完成注释 //version 10.2 完善显示_按键_初始化代码
#include "driver\io_config.h"
#include "driver\osc.h"
#include "driver\key.h"
#include "driver\seg_led.h"
void init();
void timer_init();
/****************************************************/ //主函数
/****************************************************/ void main(void)
{
init(); while(1){ //初始化 16 key(); //按键扫描及处理
}
}
/****************************************************/
//程序初始化函数
/****************************************************/
void init()
{
EA=0; //关中断
WDTCN = 0xDE; //关看门狗
WDTCN = 0xAD;
Osc_Init_24M(); //初始化晶振
io_config(); //IO口配置
io_init(); //初始化io口
timer_init(); //初始化定时器
dac_init(); //初始化数模转换器
PCA0_Init();
frequency_show(); //初始化显示频率
}
/****************************************************/
//定时器初始化
/****************************************************/
void timer_init()
{
//设置定时器0
CKCON=0xe0; //timer0,timer1选用系统时钟12分频 TL0=(65536-10000)%256; //设置中断时间为5ms
TH0=(65536-10000)/256;
TMOD=0x01; //设置工作方式1,16位计数
//设置定时器3
TMR3CN=0x00; //设置定时器3为使用系统时钟12分频 update_timer_dac();
17
//开启定时器中断并开始计数
//EIP2|=0x01; //PT3=1;
EA=1;
ET0=1;
EIE2|=0x01; //ET3=1
TR0=1;
TMR3CN|=0x04; //TR3=1;
}
/****************************************************/ //定时器0中断函数
/****************************************************/ void timer0_interrupt() interrupt 1
{
TL0=(65536-10000)%256; //设置中断时间为5ms TH0=(65536-10000)/256;
key_5ms_count++;
Glitter_Count++;
display(); //数码管显示
}
/****************************************************/ //定时器3中断函数
/****************************************************/ void timer3_interrupt() interrupt 14
{
TMR3CN&=0x0f; //TF3=0
update_dac(); //更新DAC输出
}
///////////////////////////////////////////////////////////
//文件io_config.h
///////////////////////////////////////////////////////////
#ifndef __IO_CONFIG_H
18
#define __IO_CONFIG_H
/*------------------------------------------------------------------------------*/
/* IO口配置 /*------------------------------------------------------------------------------*/
#include <c8051f020.h>
////////////////////////////函数申明////////////////////////////////////////
void io_config(void);
void io_init(void);
////////////////////////////函数定义////////////////////////////////////////
/****************************************************/
//IO口配置函数
/****************************************************/
void io_config(void)
{
XBR0=0x09; //连接 CEX0 到 P0.2
XBR1=0x00;
XBR2=0xc0; //开弱上拉,使能交叉开关
P0MDOUT=0xFF; //P0输出方式均设为推挽输出
P1MDOUT=0xFF; //P1输出方式均设为推挽输出 P2MDOUT=0xFF; //P2输出方式均设为推挽输出
P3MDOUT=0xFF; //P3输出方式均设为推挽输出
}
/*****************************************************/
//IO初始化函数
/****************************************************/
void io_init(void)
{
P7=0xFF;
P6=0xFF;
P5=0xFF;
P4=0xFF;
P3=0xFF;
P2=0xFF;
P1=0xFF; */
19
P0=0xFF;
}
#endif
///////////////////////////////////////////////////////////
//文件osc.h
///////////////////////////////////////////////////////////
#ifndef __OSC_H
#define __OSC_H
/*------------------------------------------------------------------------------*/ /* 晶振相关代码
/*------------------------------------------------------------------------------*/ void Osc_Init_24M();
/****************************************************/ //晶振初始化函数
/****************************************************/ void Osc_Init_24M()
{
unsigned int i,j;
OSCXCN = 0x67; //启动24MHz外部晶振 while (!(OSCXCN & 0x80));
OSCICN= 0x88;
while (!(OSCXCN & 0x80));
OSCICN= 0x08;
for(i=0; i<100; i++) //延时100ms
for(j=0;j<1000;j++);
}
#endif
///////////////////////////////////////////////////////////
//文件key.h
///////////////////////////////////////////////////////////
#ifndef _KEY_H */
20
#define _KEY_H
/*------------------------------------------------------------------------------*/ /* 按键扫描及响应 #include <c8051f020.h> #include "dac.h" #define #define
Frequency_Length 6 KEY
P3
//数码管显示位数
*/
/*------------------------------------------------------------------------------*/
////////////////////////////全局变量定义////////////////////////////////////// unsigned char code key_row_num[]= {0x0e,0x0d,0x0b,0x07};
//按键列扫描码表 //按键行扫描码表 //5ms累加器,每5ms加1
//数码管显示内容数字,左侧为高位, //value=0~11
//数码管光标闪烁指针,指向闪烁位 //初始化输出频率1KHZ //定义全局循环变量 //确认键按下标识位 //按键扫描函数 //输入数字函数 //删除响应函数 //清除显示函数 //更新Frequency的值 //向数码管显示Frequency //按键响应函数,分配各按键功能 //确认键响应函数
//按键主函数,采取状态机的方式完成按 //外部函数,用于更新dac输出
21
unsigned char code key_line_num[]= {0x70,0xb0,0xd0,0xe0}; unsigned char key_5ms_count=0;
unsigned char Frequency_Display[Frequency_Length]={10,10,10,10,10,10};
unsigned char Fre_Dis_Point=Frequency_Length; //数码管指针,指向当前显示位 char Glitter_Point=Frequency_Length; int Frequency=1000; char loop_var; bit first_ok=1; char key_scan();
//////////////////////////////函数申明//////////////////////////////////////// void input_num(char num); void delete_num(); void clear_display();
void update_frequency(); void frequency_show(); void key_enter(); void key();
void key_respond(char key_num);
extern void update_signal();
//////////////////////////////函数定义///////////////////////////////////////// /****************************************************/ //按键主函数,使用状态机的方式完成对按键的扫描处理 /****************************************************/ void key() {
static unsigned char mode=0; static char key_num_now=-1; switch(mode) {
case 0:
//检测按键
//扫描按键并获取键值
//当键值有效进入下一状态 //5ms累加器清0
key_num_former=key_scan(); { } break;
//确认按键
//扫描按键并获取键值
key_num_now=key_scan(); { } else { } break;
//单按按键处理
22
//状态机状态标识 //前一次扫描键值 //当前扫描键值 //状态机定义
static char key_num_former=-1;
if(key_num_former>-1&&key_num_former<16)
mode=1;
key_5ms_count=0;
case 1:
if(key_num_now==key_num_former)
//当扫描键值前后相同时
//按键持续超过20ms,进入下一状态
if(key_5ms_count>4)
mode=2;
//当前后键值不同时回到状态0
key_num_now=-1; mode=0;
case 2:
key_respond(key_num_now); //按键响应
mode=3; //进入下一状态
break;
case 3: //等待按键释放,若长时间不释放,视为长按 if(key_num_now==key_scan()) //当扫描键值前后相同时
{
if(key_5ms_count>100)
{ //按键持续超过0.5s,判为长按 key_5ms_count=50; //此后每过0.1s进行一次按键响应 mode=2; //即进入状态2
}
}
else
{ //当扫描键值前后不同时
mode=0; //回到状态0
}
break;
}
}
/****************************************************/
//按键扫描
//返回键值,-1为无按键
/****************************************************/
char key_scan()
{ //用线反转法获取键值
char key; //定义临时变量,用于记录KEY char key_num=-1; //临时变量,用于记录键值 KEY=0xff;
KEY=0x0f; //将行IO口全拉低
key=KEY;
if((key&=0x0f)!=0x0f)
{ //如果列IO口有拉低位
23
}
}
int i,j=0;
//i:列标,j:行标 //将列IO口拉低
for(i=0;i<4&&(key!=key_row_num[i]);i++);//与码表比对获取列标 KEY=0xf0; key=KEY;
for(j=0;j<4&&(key!=key_line_num[j]);j++);//与码表比对获取行标 if(i<4&&j<4)
key_num=j*4+i;
//计算键值 //返回键值
return key_num;
/****************************************************/ //向数码管输入数字函数
/****************************************************/ void input_num(char num) {
if(Glitter_Point<Frequency_Length) { } else {
{ }
clear_display(); first_ok=0;
//当光标有效时,在光标处输入
//光标指向位比当前数码管最高位高时 //在之间填0
while(Glitter_Point<Fre_Dis_Point) { }
Fre_Dis_Point--;
Frequency_Display[Fre_Dis_Point]=0;
Frequency_Display[Glitter_Point]=num; //在光标指向位输入数字 Glitter_Point++;
//光标自动向低位移动
//默认输入方式
//当确认键按下后输入数字 //清空显示 //确认键标识清零
if(first_ok)
if(Fre_Dis_Point>0&&(!(num==0&&Fre_Dis_Point==Frequency_Length)))
24
}
} { } //移位输入,赋值语句执行效率较高 Frequency_Display[0]=Frequency_Display[1]; Frequency_Display[1]=Frequency_Display[2]; Frequency_Display[2]=Frequency_Display[3]; Frequency_Display[3]=Frequency_Display[4]; Frequency_Display[4]=Frequency_Display[5]; Frequency_Display[5]=num; Fre_Dis_Point--;
/****************************************************/ //删除数码管上的数字
/****************************************************/
{
void delete_num() if(Glitter_Point<Frequency_Length&&Glitter_Point>=Fre_Dis_Point) { } else if(Fre_Dis_Point<Frequency_Length) { //默认方式,删除最低位,高位右移一位 Frequency_Display[5]=Frequency_Display[4]; Frequency_Display[4]=Frequency_Display[3]; Frequency_Display[3]=Frequency_Display[2]; Frequency_Display[2]=Frequency_Display[1]; Frequency_Display[1]=Frequency_Display[0]; Frequency_Display[0]=10; 25 { } //当光标有效并且指向已输入位时 //删除当前位,将高位右移一位 for(loop_var=Glitter_Point;loop_var>Fre_Dis_Point;loop_var--) Frequency_Display[loop_var]=Frequency_Display[loop_var-1]; Frequency_Display[Fre_Dis_Point++]=10;
}
} Fre_Dis_Point++;
/****************************************************/ //更新Frequency的数值
/****************************************************/ void update_frequency()
{
}
/****************************************************/ //清空数码管的显示
/****************************************************/ void clear_display()
{
}
/****************************************************/ //将Frequency的值更新至数码管
/****************************************************/
26
Frequency=0; { } //频率归零 //将当前数码管显示转化为频率数值 for(loop_var=Fre_Dis_Point;loop_var<Frequency_Length;loop_var++) Frequency*=10; Frequency+=Frequency_Display[loop_var]; Fre_Dis_Point= Frequency_Length; Frequency_Display[0]=10; Frequency_Display[1]=10; Frequency_Display[2]=10; Frequency_Display[3]=10; Frequency_Display[4]=10; Frequency_Display[5]=10; //数码管指针复位
void frequency_show()
{
int temp=Frequency; //用于记录频率的临时变量 clear_display(); //清除数码管显示
while(temp!=0) //将频率逐位存于数码管显示数组 {
Fre_Dis_Point--;
Frequency_Display[Fre_Dis_Point]=temp%10;
temp/=10;
}
}
/****************************************************/
//按键响应分配函数
/****************************************************/
void key_respond(char key_num)
{
switch(key_num) //按键名
{
case 0: //1
case 1: //2
case 2: //3
input_num(key_num+1); //输入数字
break;
case 4: //4
case 5: //5
case 6: //6
input_num(key_num); //输入数字
break;
case 8: //7
case 9: //8
case 10: //9
input_num(key_num-1); //输入数字
break;
27
case 13: //0
input_num(0); //输入数字0
break;
case 14: //00
input_num(0); //输入数字0
input_num(0); //输入数字0
break;
case 3: //Del
delete_num(); //删除响应
first_ok=0; //清除确认键标识
break;
case 7: //+
if(Fre_Dis_Point!=Frequency_Length)
{ //当前显示不为空时
if(Fre_Dis_Point>0&&(Frequency_Display[Fre_Dis_Point]==9)) { //当显示最高位为9时进位操作 Frequency_Display[Fre_Dis_Point--]=0;
Frequency_Display[Fre_Dis_Point]=1;
}
else //一般情况当前位加1
Frequency_Display[Fre_Dis_Point]++;
}
else
{ //数码管显示为空时,令最低位为1 Frequency_Display[--Fre_Dis_Point]=1;
}
key_enter(); //执行确认键操作
break;
case 11: //-
if(Fre_Dis_Point!=Frequency_Length)
{ //当显示不为空时
if(Fre_Dis_Point<Frequency_Length&&(Frequency_Display[Fre_Dis_Point]==1)) Frequency_Display[Fre_Dis_Point++]=10;
28
else
Frequency_Display[Fre_Dis_Point]--;
key_enter(); //执行确认键操作
}
break;
case 12: //select
first_ok=0; //清零确认键标识
Glitter_Point--; //光标向高位移动
if(Glitter_Point==-1)
Glitter_Point=Frequency_Length;
break;
case 15: //Enter
key_enter(); //执行确认键操作
break;
default:
break;
}
}
/****************************************************/
//确认键响应函数
/****************************************************/
void key_enter()
{
TMR3CN&=~0x04; //TR3=0,禁止定时器3计数 update_frequency(); //更新Frequency的值 if(Frequency==0) //禁止0频率
Frequency=1;
frequency_show(); //更新数码管显示
update_timer_dac(); //更新DAC配置
TMR3CN|=0x04; //TR3=1,允许定时器3计数 first_ok=1; //置位确认键标识
}
//************************************************
#endif
29
///////////////////////////////////////////////////////////
//文件dac.h
///////////////////////////////////////////////////////////
#ifndef _DAC_H
#define _DAC_H
/*------------------------------------------------------------------------------*/
/* DAC输出相关代码
#include "sin_table.h"
#include "pca_clock.h"
////////////////////////////////宏定义///////////////////////////////////////////
#define Fs_Init 48000
/////////////////////////////全局变量定义///////////////////////////////////////
//采样频率 //采样点数 //指向下个取出信号点位置 //输出步进 //输出频率 //更新DAC输出函数 int Fs= Fs_Init; int N=2; //初始采样频率单位HZ //采样信号长度 #define Signal_Capacity 256 */ /*------------------------------------------------------------------------------*/ unsigned char Signal_Out=0; extern int Frequency; void update_dac(); void dac_init();
unsigned char output_divide=1; //DAC初始化函数 //更新DAC时钟配置函数 void update_timer_dac();
//////////////////////////////函数定义/////////////////////////////////////////
/****************************************************/
//DAC输出更新,重余弦码表以output_divide的
//步进值取值输出
/****************************************************/
void update_dac()
{
DAC0H=Signal_Cos[Signal_Out]; //从余弦码表以output_divide的步进值取
30
数
Signal_Out+=output_divide; //因为Signal_Out取值为0~255故不用清0 // if(Signal_Out>=Signal_Capacity)
// Signal_Out-=Signal_Capacity;
}
/****************************************************/
//更新DAC采样频率,更新PCA的CEX0的时钟输出
/****************************************************/
void update_timer_dac()
{
unsigned int n=Fs_Init/Frequency; //估算以频率Fs_Init采样时采样点数 if(n<3) //限制n=3~256
n=3;
else if(n>Signal_Capacity)
n=Signal_Capacity;
N=1;
do
{
N*=2;
n/=2;
}while(n!=0);
N/=2; //使N成为不大于n的2的k次幂 output_divide=Signal_Capacity/N; //计算采样步进值
Fs=Frequency*N; //重新计算采样频率
TMR3RLL=(65536-2000000/Fs)%256; //重设定时器3的中断时间 TMR3RLH=(65536-2000000/Fs)/256;
TMR3L=TMR3H=0;
set_pca_clock(Frequency/2+Frequency); //配置PCA计数器CEX0频率输出 }
/****************************************************/
//DAC初始化配置
/****************************************************/
31
void dac_init()
{
}
//////////////////////////////////////////////////////////
#endif
///////////////////////////////////////////////////////////
//文件sin_table.h
///////////////////////////////////////////////////////////
#ifndef _SIN_TABLE_H_
#define _SIN_TABLE_H_
/*------------------------------------------------------------------------------*/
/* 余弦码表 */ /*------------------------------------------------------------------------------*/
unsigned char code Signal_Cos[]={
0xFF,0xFF,0xFF,0xFF,0xFE,0xFE,0xFE,0xFD,0xFD,0xFC,0xFB,0xFA,0xFA,0xF9,0xF8,0xF6, 0xF5,0xF4,0xF3,0xF1,0xF0,0xEE,0xED,0xEB,0xEA,0xE8,0xE6,0xE4,0xE2,0xE0,0xDE,0xDC, 0xDA,0xD7,0xD5,0xD3,0xD0,0xCE,0xCB,0xC9,0xC6,0xC4,0xC1,0xBE,0xBC,0xB9,0xB6,0xB3, 0xB0,0xAD,0xAA,0xA7,0xA5,0xA2,0x9E,0x9B,0x98,0x95,0x92,0x8F,0x8C,0x89,0x86,0x83, 0x80,0x7C,0x79,0x76,0x73,0x70,0x6D,0x6A,0x67,0x64,0x61,0x5D,0x5A,0x58,0x55,0x52, 0x4F,0x4C,0x49,0x46,0x43,0x41,0x3E,0x3B,0x39,0x36,0x34,0x31,0x2F,0x2C,0x2A,0x28, 0x25,0x23,0x21,0x1F,0x1D,0x1B,0x19,0x17,0x15,0x14,0x12,0x11,0x0F,0x0E,0x0C,0x0B, 0x0A,0x09,0x07,0x06,0x05,0x05,0x04,0x03,0x02,0x02,0x01,0x01,0x01,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x01,0x01,0x01,0x02,0x02,0x03,0x04,0x05,0x05,0x06,0x07,0x09, 0x0A,0x0B,0x0C,0x0E,0x0F,0x11,0x12,0x14,0x15,0x17,0x19,0x1B,0x1D,0x1F,0x21,0x23, 0x25,0x28,0x2A,0x2C,0x2F,0x31,0x34,0x36,0x39,0x3B,0x3E,0x41,0x43,0x46,0x49,0x4C,
32
DAC0CN=0x04; //DAC 输出更新发生在定时器3 溢出时 //000:DAC0 数据字的高4 位在DAC0H[3:0], //低字节在DAC0L 中。 //ADC/DAC 偏压发生器使能;内部电压基准缓冲器使能 //初始化DAC输出 //使能 DAC0 REF0CN|=0x03; DAC0L = 0x00; DAC0H = 0x00; DAC0CN |= 0x80;
0x4F,0x52,0x55,0x58,0x5A,0x5D,0x61,0x64,0x67,0x6A,0x6D,0x70,0x73,0x76,0x79,0x7C, 0x7F,0x83,0x86,0x89,0x8C,0x8F,0x92,0x95,0x98,0x9B,0x9E,0xA2,0xA5,0xA7,0xAA,0xAD, 0xB0,0xB3,0xB6,0xB9,0xBC,0xBE,0xC1,0xC4,0xC6,0xC9,0xCB,0xCE,0xD0,0xD3,0xD5,0xD7, 0xDA,0xDC,0xDE,0xE0,0xE2,0xE4,0xE6,0xE8,0xEA,0xEB,0xED,0xEE,0xF0,0xF1,0xF3,0xF4, 0xF5,0xF6,0xF8,0xF9,0xFA,0xFA,0xFB,0xFC,0xFD,0xFD,0xFE,0xFE,0xFE,0xFF,0xFF,0xFF};
//////////////////////////////////////////////////////////////////////////////////
#endif
///////////////////////////////////////////////////////////
//文件pac_clock.h
///////////////////////////////////////////////////////////
#ifndef _PCA_CLOCK_H_
#define _PCA_CLOCK_H_
/*------------------------------------------------------------------------------*/
/* PCA0模块时钟输出相关代码
//////////////////////////////函数定义/////////////////////////////////////////
/****************************************************/
//初始化PCA0的模块CEX0输出100KHZ的时钟
/****************************************************/
void PCA0_Init (void)
{
PCA0CN = 0x00; // 停止计数,清除所有中断标志位 PCA0MD = 0x00; // 采用系统时钟12分频,禁止中断 PCA0CPM0 = 0x46; // 设置CEX0为频率输出方式
PCA0CPH0 = (24000000/12)/(2*100000); //设置初始时钟为100kHZ
CR = 1;
}
/****************************************************/
//重新配置PCA0的模块CEX0的频率输出
/****************************************************/
33
*/ /*------------------------------------------------------------------------------*/ // 开始计数
void set_pca_clock(int fc)
{
CR=0; //停止PCA0的计数
if(fc>10000)
{ //当所设频率大于1M
PCA0MD = 0x08; // 采用系统时钟,禁止中断
PCA0CPH0=120000/fc; //=24000000/(2*fc*100);
}
else
{ //当所设频率小于1M
PCA0MD = 0x00; // 采用系统时钟12分频,禁止中断 PCA0CPH0=10000/fc; //=(24000000/12)/(2*fc*100);
}
CR=1; //开启PCA0的计数
}
////////////////////////////////////////////////
#endif
///////////////////////////////////////////////////////////
//文件seg_led.h
///////////////////////////////////////////////////////////
#ifndef _SEG_LED_H
#define _SEG_LED_H
//***********************************************
#include <c8051f020.h>
/*------------------------------------------------------------------------------*/
/* 数码管显示相关代码 */ /*------------------------------------------------------------------------------*/
unsigned char code DIGI[]
={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0xff,0xf7};
// 0 1 2 3 4 5 6 7 8 9 null _ 共阴极数码管unsigned char code DIGI_EN[] ={0x20,0x10,0x08,0x04,0x02,0x01};
34
unsigned char Glitter_Count;
extern char Glitter_Point;
/****************************************************/ //数码管显示
/****************************************************/ void display()
{
static char select=0; //数码管显示位标标识位 P1=0xff; //位选前消隐显示 P2=DIGI_EN[select]; //位选数码管
if(select==Glitter_Point&&Glitter_Count<128)
P1=DIGI[11]; //每0.64s在光标闪烁一次 else
P1=DIGI[Frequency_Display[select]];
select=select+1;
if(select==6)
select=0;
}
//***********************************************
#endif
35