|
前几天学习了一个新的红外线遥控器解码的程序,使用的方法与51下的中断独占方法完全不同,可以说尽量发挥了avr单片机的功能。程序的思想是,使用定时器的捕捉模式,来完成对红外解调信号的宽度测量。与51下传统的方法相比,优点是明显的,这个代码工作在中断模式,不会独占外部中断(51下的代码为了完成对外部信号宽度较为精确的测量,强制关闭了所有的中断响应)。 这个代码很快调通了,并且也很好用。这是我第1次使用avr定时器的外部信号捕捉功能。今天突发奇想:如果用来捕捉gps模块输出的1pps频标信号会有什么后果呢?后果就是可以精确地测量出,在1pps外部信号的周期内,mcu时钟走了多少步。这意味着,如果外部1pps信号是准确的(我查了一下我模块的ds,给出的指标是+-50ns),那么通过计算,就可以得出mcu时钟的准确频率值,而且精度可以达到hz级别。 也就是说,如果mcu的时钟,是由外部有源晶体、tcxo甚至ocxo提供的,那么这种想法就可以用来测量出外部时钟的准确频率(这似乎对留有调整孔的tcxo更有意义)。当然,限制就是此方法,只能对avr适用的时钟频率范围的晶体有效了(x-20mhz)。 于是在实验板上写了一个程序,来验证想法。 1、程序首先对gps进行解码,用来完成gps是否已定位的检测 (后来的结果似乎表明,gps模块的定位与否,对它输出的1pps频标似乎没有影响,怀疑gps模块具备有1pps信号自校准功能。就是说定位后,它会根据gps卫星的信号,计算出本身的晶体偏差数值,并且完成对输出的1pps信号的修正。当然,这纯属猜想)。 2、打开定时器1的捕捉模式,并且开启了溢出中断。在捕捉中断中,程序完成对工作状态的判断(是否在测量状态),并且根据状态的不同,保存时间值到2个分别代表启动和停止的变量中。并且在测量完成时,置位一个数据有效标志。 3、溢出中断相当简单,只是完成溢出次数保存。 3、主程序中,挂接一个lcd驱动。然后检查测量标志是否有效,有效时,立即进行计算,并显示出来,结束后复位标志。 上述算法在我的实验板上经过了检验,测量14.745600mhz的时钟频率为14.745158mhz,粗算了一下,似乎相当于-30ppm的精度。这比较符合普通晶体的性能。 另外同时对avr的内部rc振荡器进行了测试,发现无论是稳定性,还是准确度,rc都是无法和晶体相比的(废话~) btw:图片上显示的频率和我上面描述的频率(14.745158)相差较大的原因是,我描述的频率是3.3v电源供电情况下的,而图片是5v电源供电情况下拍的(为了使背光更亮些,容易看清楚)。2010.5.19更新:加入源代码(仅供参考~) - /************************************************
- atmega128板使用gps模块1pps频标测量主时钟频率程序
- 版权所有 限群内交流 qq group:433**333
- write by bg4uvr
- +++++++++++++++++++++++++++++++++++++++++++++++++
- 最后更新:
- 2008.05.08:
- 1、创意中。。。
- 2、调试成功
- *************************************************/
- //包含头文件
- #include <avr/io.h>
- #include <avr/interrupt.h>
- #include "5110.h"
- //gps数据存储数组
- uint8_t lon[10]; //经度
- uint8_t e_w; //经度方向
- uint8_t lat[9]; //纬度
- uint8_t n_s; //纬度方向
- uint8_t time[6]; //时间
- uint8_t date[8]; //日期
- uint8_t spd[5]; //速度
- uint8_t alt[6]; //高度
- uint8_t deg[5]; //方位角
- uint8_t station; //定位状态
- uint8_t stalate[2]; //收星数
- //显示需要的变量
- uint8_t alt_cnt; //高度位数
- volatile uint8_t buf; //1:整句接收完成,相应数据有效。0:缓存数据无效。
- //定时器需要的变量
- volatile uint16_t over_cnt; //定时器溢出次数计数
- volatile uint16_t start_timer; //捕捉开始值
- volatile uint16_t stop_timer; //捕捉结束值
- volatile uint8_t mode; //测量状态存储。1正在测量,0未测量
- volatile uint8_t flag; //结果状态。1有效,0无效
- void sys_init(void);
- int main(void)
- {
- uint8_t hour; //北京时间
- uint32_t fre; //频率结果存储
- sys_init();
- mode=0; //状态未测量
- flag=0; //测量结果无效
- lcd_write_english_string(0,0,pstr("fre="));
- lcd_write_english_string(0,2,pstr("satellites="));
- lcd_write_english_string(6,5,pstr("bjt"));
- while(1)
- {
- if(flag==1) //如果测量已完成
- {
- flag=0;
- if(over_cnt==0)
- {
- fre=stop_timer-start_timer;
- }
- else
- {
- fre=(over_cnt*0x10000)-start_timer+stop_timer;
- }
- display_32hex(24,0,fre); //显示频率
- lcd_write_char('h');
- lcd_write_char('z');
- }
- if(buf&0x01) //显示定位卫星数
- {
- lcd_set_xy(72,2);
- lcd_write_char(stalate[0]);
- lcd_write_char(stalate[1]);
- buf&=~0x01;
- }
-
- //显示实时时钟
- if(buf&0x02)
- {
- hour=(time[0]-0x30)*10+time[1]-0x30+8;
- if(hour>=24) hour-=24;
- lcd_set_xy(30,5);
- lcd_write_char(hour/10+0x30);
- lcd_write_char(hour%10+0x30);
- lcd_write_char(':');
- lcd_write_char(time[2]);
- lcd_write_char(time[3]);
- lcd_write_char(':');
- lcd_write_char(time[4]);
- lcd_write_char(time[5]);
- buf&=~2;
- }
- }
- }
- //usart 初始化
- void init_usart(void)
- {
- ucsr1c = (1<<ucsz11) | (1<<ucsz10); //异步,8位数据,无奇偶校验,一个停止位,无倍速
- ubrr1l= (f_cpu/9600/16-1)%256; //ubrr= (f_cpu/(baudrate*16))-1;
- ubrr1h= (f_cpu/9600/16-1)/256;
- ucsr1b = (1<<rxcie)|(1<<rxen); //使能接收中断,使能接收,使能发送
- }
- //定时t1初始化
- void timer1_init(void)
- {
- timsk |= _bv(ticie1); //允许定时器1捕捉中断
- timsk |= _bv(toie1); //允许定时器1溢出中断
- tccr1b = 0x81; //启动定时器 噪声抑制、下边沿触发、0分频
- }
- //定时器t1输入捕捉中断服务程序
- signal(sig_input_capture1)
- {
- if(mode==0)
- {
- mode=1; //正在测量
- flag=0; //结果无效
- start_timer=icr1;
- over_cnt=0;
- }
- else if(mode==1)
- {
- mode=0; //测量完成
- stop_timer=icr1;
- flag=1; //结果有效
- }
- }
- //定时器1溢出中断
- signal(sig_overflow1)
- {
- over_cnt++; //溢出次数加1
- }
- //系统初始化
- void sys_init()
- {
- lcd_init(); //初始化lcd
- init_usart(); //初始化串口
- timer1_init(); //初始化定时器1
- sei(); //开启中断
- }
- //串口接收中断
- signal(sig_uart1_recv) //串口接收中断服务程序
- {
- /*
- 需要量:
- 日期、时间、经、纬度、高度、速度、方位角
- 需要解码语句:
- gga:高度
- gll:经、纬度
- rmc:日期、时间、方位角
- vtg:速度
- savepoint完成存点为date数据格式文件。
- 间隔计算函数,使用速度、方位角变化,来确定存点时间间隔。
- */
- static uint8_t comma_cnt; //逗号计数器
- static uint8_t byte_cnt; //位数计数器
- static uint8_t cmd_type; //命令类型
- static uint8_t mode; //0:结束模式,1:命令模式,2:数据模式
-
- uint8_t tmp;
- tmp=udr1;
- switch(tmp)
- {
- case '$':
- mode=1; //接收命令模式
- byte_cnt=0; //接收位数计数器清空
- break;
- case ',':
- if(mode) //只有在有效模式才对逗号计数
- {
- comma_cnt++; //逗号计数加1
- byte_cnt=0;
- }
- break;
- default:
- if(mode==1) //命令接收模式
- {
- if(byte_cnt==3) //当接收的是第4位字符时
- {
- switch(tmp) //对字符进行判断,以得到当前语句类型
- {
- case 'g': //gpgga 高度
- cmd_type=1; //命令类型存储
- alt_cnt=0; //调试位数计数清零
- buf&=~1; //在接收某语句期间,此语句的输出空间标志为空,防止部分被改写的缓冲被读。
- break;
- case 'd': //gpzda 时间、日期
- cmd_type=2;
- buf&=~2;
- break;
- case 'l': //gpgll 经纬度,经纬度方向,定位状态
- cmd_type=3;
- buf&=~4;
- break;
- case 't': //gpvtg 运动方向、速度
- cmd_type=4;
- buf&=~8;
- break;
- default: //其他无效命令
- cmd_type=0;
- break;
- }
- if(cmd_type) //如果接收到的语句为需要接收的
- {
- mode=2; //数据接收方式
- comma_cnt=0; //清0逗号计数器
- }
- else //如果接收的语句是不需要接收的
- {
- mode=0; //进入无效接收模式
- }
- } //命令判断结束
- } //命令接收模式结束
- else if(mode==2) //数据接收模式。处理数据。
- {
- switch (cmd_type) //判断当前语句类型
- {
- case 1: //gpgga
- switch(comma_cnt)
- {
- case 7: //收星数
- if(byte_cnt<2)
- {
- stalate[byte_cnt]=tmp;
- }
- break;
- case 9: //高度处理
- if(byte_cnt<6){
- alt[byte_cnt]=tmp;
- alt_cnt++; //高度数据位数计数
- }
- break;
- case 14:
- mode=0; //进入无效接收模式,直到有“$”字符被接收到
- buf|=1; //接收完某语句,标志其输出为有效
- break;
- }
- break;
- case 2: //gpzda
- switch(comma_cnt)
- {
- case 1: //时间
- if(byte_cnt<6)
- {
- time[byte_cnt]=tmp;
- }
- break;
- case 2: //日期
- if(byte_cnt<2)
- {
- date[byte_cnt+4]=tmp;
- }
- break;
- case 3: //月
- if(byte_cnt<2)
- {
- date[byte_cnt+2]=tmp;
- }
- break;
- case 4: //年
- if(byte_cnt<4)
- {
- date[byte_cnt]=tmp;
- }
- break;
- case 6:
- mode=0;
- buf|=2;
- break;
- }
- break;
- case 3: //gpgll
- switch(comma_cnt)
- {
- case 1: //纬度
- if(byte_cnt<9)
- {
- lat[byte_cnt]=tmp;
- }
- break;
- case 2: //n or s
- n_s=tmp;
- break;
- case 3: //经度
- if(byte_cnt<10)
- {
- lon[byte_cnt]=tmp;
- }
- break;
- case 4: //e or w
- e_w=tmp;
- break;
- case 6: //定位状态
- station=tmp; //a or v
- break;
- case 7:
- mode=0;
- buf|=4;
- break;
- }
- break;
- case 4: //gpvtg
- switch(comma_cnt)
- {
- case 1: //方位角
- if(byte_cnt<5)
- {
- deg[byte_cnt]=tmp;
- }
- break;
- case 7: //速度 km/h
- if(byte_cnt<5)
- {
- spd[byte_cnt]=tmp;
- }
- break;
- case 9:
- mode=0;
- buf|=8;
- break;
- }
- break;
- }//类型判断结束
- }//数据接收模式结束
- byte_cnt++; //接收数位加1
- break;
- }//判断接收数据内容结束
- }//中断结束
- /*
- $gpgga,024518.00,3153.7225,n,12111.9951,e,1,04,1.48,-00009,m,007,m,,*4c
- $gpgll,3153.7225,n,12111.9951,e,024518.00,a,a*63
- $gpvtg,000.0,t,004.7,m,000.0,n,000.0,k,a*20 //方向(真),方向(磁),速度(英),速度(公)
- $gpgsa,a,2,04,08,17,20,,,,,,,,,1.48,1.48,0.03*08
- $gpgsv,2,1,08,04,15,231,38,08,29,218,42,11,49,043,,19,09,082,*76
- $gpgsv,2,2,08,27,14,198,29,28,71,316,,17,32,300,36,20,45,124,43*70
- $gprmc,024518.00,a,3153.7225,n,12111.9951,e,000.0,000.0,280107,04.7,w,a*12
- $gpzda,024519.45,28,01,2007,,*62
- */
- /*
- //上电后未定位时输出的数据(已有星历)
- $gpzda,061523.30,21,02,2008,,*6d
- $gpgga,,,,,,0,01,,,,,,,*67
- $gpgll,,,,,,v,n*64
- $gpvtg,,,,,,,,,n*30
- $gpgsa,a,1,,,,,,,,,,,,,,,*1e
- $gpgsv,3,1,11,02,64,082,,04,30,108,,15,29,204,,10,64,330,*75
- $gpgsv,3,2,11,08,16,100,,01,58,287,,24,22,310,,25,08,050,*79
- $gpgsv,3,3,11,27,16,069,,26,36,197,34,29,29,311,,,,,*4e
- $gprmc,061523.37,v,,,,,,,,,,n*7a
- $gpzda,061524.30,21,02,2008,,*6a
- $gpgga,,,,,,0,01,,,,,,,*67
- $gpgll,,,,,,v,n*64
- $gpvtg,,,,,,,,,n*30
- $gpgsa,a,1,,,,,,,,,,,,,,,*1e
- $gpgsv,3,1,11,02,64,082,,04,30,108,,15,29,204,,10,64,330,*75
- $gpgsv,3,2,11,08,16,100,,01,58,287,,24,22,310,,25,08,050,*79
- $gpgsv,3,3,11,27,16,069,,26,36,197,34,29,29,311,,,,,*4e
- $gprmc,061524.37,v,,,,,,,,,,n*7d
- */
|