
终于完成电子系统综合设计了。选的题目是数字频率计,使用了MSP430单片机和Cyclone的EP1C3T144 FPGA。单片机系统板用的是创新院ZM的EZ430,FPGA用的是Smoking Fish的开发板,还用了LCD1602液晶显示器。
本数字频率计的频率范围是1Hz - 1MHz,还具有显示周期和脉宽功能。频率测量原理采用等精度法。等精度法的优点是相对误差与被测频率无关,在预置门控信号持续时间不变的情况下,无论被测频率是否变化,测量精度是不变的。
下面分两部分介绍Donald制作的数字频率计。
第一部分,FPGA部分
本设计FPGA测量原理图如下:

其中计数器模块由两个32位高速计数器组成。计数器的实现比较简单,主要问题是需要一个32位-8位的输出缓冲器,因为单片机的数据总线只有8位。这里,Donald的缓冲器输出仿真图如下:

另外,为了实现测量频率与测量占空比同时进行,Donald多加了一个32位计数器和输出缓冲器,此计数器的预置门控信号由被测频率信号的高电平和原预置门控信号共同控制,并对标准频率进行计数。
第二部分,单片机部分
本等精度数字频率计由于采用MSP430,片内资源丰富,对浮点数和长整型数据运算都比较容易。
根据等精度测量原理,被测频率为
fx = (fs / Ns) * Nx
式子中,fs为标准频率,Ns为预置时间内标准频率计数值,Nx为被测频率计数值,这些值均为已知,被测频率fx即可求出。Ns和Nx均为从FPGA部分读出。所以单片机的任务就是从FPGA部分正确读出Ns和Nx,然后计算出fx,最后显示出来。当然,为了测量占空比,还要读出相应计数值Np。占空比的等精度测量原理参考相应书籍。
读数时,按照Donald自己设计的32位-8位输出缓冲器的时序,经过简单调试,便正确读出。计算更是简单,直接按公式进行,便可得到被测频率和占空比。在Donald的程序里,频率和占空比均以float类型存储,分别定义为g_fFx,g_fDty。于是,剩下的问题就是如何将浮点数显示在LCD1602上,即浮点数如何以字符数组的形式显示出来。
这个问题困扰了Donald很久,为了显示,还专门查了浮点的数的存储结构。这里简单复述下:
C51里用4字节存储一个浮点数,格式遵循IEEE-754标准(详见c51.pdf第179页说明)。一
个浮点数用两个部分表示,尾数和2的幂,尾数代表浮点上的实际二进制数,2的幂代表指
数,指数的保存形式是一个0到255的8位值,指数的实际值是保存值(0到255)减去127,一个
范围在-127到+128之间的值,尾数是一个24位值(代表大约7个十进制数),最高位MSB通常是1,因此不保存。一个符号位表示浮点数是正或负。
浮点数保存的字节格式如下:
地址 +0 +1 +2 +3
内容 SEEE EEEE EMMM MMMM MMMM MMMM MMMM MMMM
这里
S 代表符号位,1是负,0是正
E 偏移127的幂,二进制阶码=(EEEEEEEE)-127。
M 24位的尾数保存在23位中,只存储23位,最高位固定为1。此方法用最较少的位数实现了
较高的有效位数,提高了精度。
零是一个特定值,幂是0 尾数也是0。
考虑了很久,都不知道如何将浮点数以字符形式存到数组。于是上网一顿找,只有一个程序能实现将浮点数以科学计数法的形式显示出来,而且在float范围内精确显示。是个好程序,但需进行相当多的浮点运算,如其作者所说,“花在显示上的时间比计算的耗时都要多得多。”于是苦苦寻了段时间,最后从21ic上搜到一篇提问:浮点数怎么显示用数码管。一位网友zyg的回答是:
要是固定的,那就容易了,要是保留小数后两位,你就将浮点数乘以100然后赋值给整形或者长整形,之后送显示。要是你要求LED显示的数字的小数点位置是浮动的,那么就麻烦了,先判断数值落入的区域,然后再相应的乘以一个10的几次方数据再赋值显示。比如最大可以显示6个数码,要显示数字120.246,你发现数据在100-200之间,于是你定小数位置在第3,乘以1000送显;对于数字30.7899,你发现在10-100之间,小数位置就定在第二,乘以10000送显示。。。。一般情况别用sprintf,一下子就耗费你2.7k代码空间,2051这样的芯片连一条都装不下,肯定死
Donald根据本频率计的精度要求,误差范围0.01%,于是定了8个数位显示,并根据测量结果自动换档(换单位)显示。用了两个函数,frq_f2c()和frq_l2c(),程序如下:
// Frequence, float type to char type
// float to char[17] as format:
// 0000.0000 Hz
void frq_f2c(float f, char* cp)
{
unsigned long ltmp = 0;
if(f == 0){
frq_l2c(ltmp, cp, 0);
return;
}
if(f > 0 && f < 1E4){ // f >= 0 AND f < 10000
ltmp = (unsigned long)(f * 10000);
frq_l2c(ltmp, cp, 1);
}
else if(f >= 1E4 && f < 1E7){ // f >= 10000 AND f < 10000000
ltmp = (unsigned long)(f * 10);
frq_l2c(ltmp, cp, 2);
}
else if(f >= 1E7){ // out of range
frq_l2c(ltmp, cp, 3);
}
}
// Frequence, long type to char type
// unsigned long to char[17] as format:
// 0000.0000 Hz
void frq_l2c(unsigned long lnum, char* cp, char cUnit)
{
int i;
if(cUnit == 0){
for(i = 8; i >= 0; i--){
*(cp + i) = '0';
}
}
else if(cUnit == 3){
for(i = 8; i >= 0; i--){
*(cp + i) = '?';
}
}
else{
for(i = 8; i >= 5; i--){
*(cp + i) = '0' + lnum % 10;
lnum /= 10;
}
for(i = 3; i >= 0; i--){
*(cp + i) = '0' + lnum % 10;
lnum /= 10;
}
}
// insert decimal point
*(cp + 4) = '.';
for(i = 9; i < 16; i++){
*(cp + i) = ' ';
}
// insert unit
if(cUnit == 2) *(cp + 10) = 'k';
*(cp + 11) = 'H';
*(cp + 12) = 'z';
*(cp + 16) = '\0';
}
在完成的最后,添加些按键显示功能,通过按键,分别显示频率,周期,占空比。
等精度数字频率计完成。

