论坛风格切换切换到宽版
  • 15751阅读
  • 25回复

使用GPS模块输出的1PPS频标,来标定晶体频率 [复制链接]

上一主题 下一主题
离线BG4UVR
 
发帖
11287
只看楼主 倒序阅读 0楼 发表于: 2008-05-09
前几天学习了一个新的红外线遥控器解码的程序,使用的方法与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更新:加入源代码(仅供参考~)
  1. /************************************************
  2. atmega128板使用gps模块1pps频标测量主时钟频率程序
  3.            版权所有 限群内交流 qq group:433**333
  4.                                          write by bg4uvr
  5. +++++++++++++++++++++++++++++++++++++++++++++++++
  6. 最后更新:
  7. 2008.05.08:
  8. 1、创意中。。。
  9. 2、调试成功
  10. *************************************************/
  11. //包含头文件
  12. #include <avr/io.h>
  13. #include <avr/interrupt.h>
  14. #include "5110.h"
  15. //gps数据存储数组
  16. uint8_t lon[10];      //经度
  17. uint8_t e_w;            //经度方向
  18. uint8_t lat[9];            //纬度
  19. uint8_t n_s;            //纬度方向
  20. uint8_t time[6];      //时间
  21. uint8_t date[8];      //日期
  22. uint8_t spd[5];            //速度
  23. uint8_t alt[6];            //高度
  24. uint8_t deg[5];            //方位角
  25. uint8_t station;      //定位状态
  26. uint8_t stalate[2];      //收星数
  27. //显示需要的变量
  28. uint8_t alt_cnt;            //高度位数
  29. volatile uint8_t buf;                  //1:整句接收完成,相应数据有效。0:缓存数据无效。
  30. //定时器需要的变量
  31. volatile uint16_t over_cnt;            //定时器溢出次数计数
  32. volatile uint16_t start_timer;      //捕捉开始值
  33. volatile uint16_t stop_timer;      //捕捉结束值
  34. volatile uint8_t mode;                  //测量状态存储。1正在测量,0未测量
  35. volatile uint8_t flag;                  //结果状态。1有效,0无效
  36. void sys_init(void);
  37. int main(void)
  38. {
  39.      uint8_t hour;                        //北京时间
  40.      uint32_t fre;                        //频率结果存储
  41.      sys_init();
  42.      mode=0;                                    //状态未测量
  43.      flag=0;                                    //测量结果无效
  44.      lcd_write_english_string(0,0,pstr("fre="));
  45.      lcd_write_english_string(0,2,pstr("satellites="));
  46.      lcd_write_english_string(6,5,pstr("bjt"));
  47.      while(1)
  48.      {
  49.            if(flag==1)                        //如果测量已完成
  50.            {
  51.                  flag=0;
  52.                  if(over_cnt==0)
  53.                  {
  54.                        fre=stop_timer-start_timer;
  55.                  }
  56.                  else
  57.                  {
  58.                        fre=(over_cnt*0x10000)-start_timer+stop_timer;
  59.                  }
  60.                  display_32hex(24,0,fre);      //显示频率
  61.                  lcd_write_char('h');
  62.                  lcd_write_char('z');
  63.            }      
  64.            if(buf&0x01)                  //显示定位卫星数
  65.            {
  66.                  lcd_set_xy(72,2);
  67.                  lcd_write_char(stalate[0]);
  68.                  lcd_write_char(stalate[1]);
  69.                  buf&=~0x01;
  70.            }
  71.            
  72.            //显示实时时钟
  73.            if(buf&0x02)
  74.            {
  75.                  hour=(time[0]-0x30)*10+time[1]-0x30+8;
  76.                  if(hour>=24)      hour-=24;
  77.                  lcd_set_xy(30,5);
  78.                  lcd_write_char(hour/10+0x30);
  79.                  lcd_write_char(hour%10+0x30);
  80.                  lcd_write_char(':');
  81.                  lcd_write_char(time[2]);
  82.                  lcd_write_char(time[3]);
  83.                  lcd_write_char(':');
  84.                  lcd_write_char(time[4]);
  85.                  lcd_write_char(time[5]);
  86.                  buf&=~2;
  87.            }
  88.      }
  89. }
  90. //usart 初始化
  91. void init_usart(void)      
  92. {
  93.   ucsr1c = (1<<ucsz11) | (1<<ucsz10);        //异步,8位数据,无奇偶校验,一个停止位,无倍速
  94.   ubrr1l= (f_cpu/9600/16-1)%256;              //ubrr= (f_cpu/(baudrate*16))-1;
  95.   ubrr1h= (f_cpu/9600/16-1)/256;
  96.   ucsr1b = (1<<rxcie)|(1<<rxen);                  //使能接收中断,使能接收,使能发送
  97. }
  98. //定时t1初始化
  99. void timer1_init(void)
  100. {
  101.      timsk |= _bv(ticie1);      //允许定时器1捕捉中断
  102.      timsk |= _bv(toie1);      //允许定时器1溢出中断
  103.      tccr1b = 0x81;                  //启动定时器 噪声抑制、下边沿触发、0分频
  104. }
  105. //定时器t1输入捕捉中断服务程序
  106. signal(sig_input_capture1)
  107. {
  108.      if(mode==0)
  109.      {
  110.            mode=1;                        //正在测量
  111.            flag=0;                        //结果无效
  112.            start_timer=icr1;
  113.            over_cnt=0;
  114.      }
  115.      else if(mode==1)
  116.      {
  117.            mode=0;                        //测量完成
  118.            stop_timer=icr1;
  119.            flag=1;                        //结果有效
  120.      }
  121. }
  122. //定时器1溢出中断
  123. signal(sig_overflow1)
  124. {
  125.      over_cnt++;      //溢出次数加1
  126. }
  127. //系统初始化
  128. void sys_init()
  129. {
  130.      lcd_init();                        //初始化lcd
  131.      init_usart();                  //初始化串口
  132.      timer1_init();                  //初始化定时器1
  133.      sei();                              //开启中断
  134. }
  135. //串口接收中断
  136. signal(sig_uart1_recv) //串口接收中断服务程序
  137. {
  138. /*
  139. 需要量:
  140. 日期、时间、经、纬度、高度、速度、方位角
  141. 需要解码语句:
  142. gga:高度
  143. gll:经、纬度
  144. rmc:日期、时间、方位角
  145. vtg:速度
  146. savepoint完成存点为date数据格式文件。
  147. 间隔计算函数,使用速度、方位角变化,来确定存点时间间隔。
  148. */
  149.      static uint8_t comma_cnt;      //逗号计数器
  150.      static uint8_t byte_cnt;      //位数计数器
  151.      static uint8_t cmd_type;      //命令类型
  152.      static uint8_t mode;            //0:结束模式,1:命令模式,2:数据模式
  153.            
  154.      uint8_t tmp;
  155.      tmp=udr1;
  156.      switch(tmp)
  157.      {
  158.            case '$':
  159.                  mode=1;                        //接收命令模式
  160.                  byte_cnt=0;                  //接收位数计数器清空
  161.                  break;
  162.            case ',':
  163.                  if(mode)                  //只有在有效模式才对逗号计数
  164.                  {
  165.                        comma_cnt++;            //逗号计数加1
  166.                        byte_cnt=0;
  167.                  }
  168.                  break;
  169.            default:
  170.                  if(mode==1)                        //命令接收模式
  171.                  {
  172.                        if(byte_cnt==3)                  //当接收的是第4位字符时
  173.                        {
  174.                              switch(tmp)                              //对字符进行判断,以得到当前语句类型
  175.                              {
  176.                                    case 'g':                        //gpgga 高度
  177.                                          cmd_type=1;                  //命令类型存储
  178.                                          alt_cnt=0;                  //调试位数计数清零
  179.                                          buf&=~1;                  //在接收某语句期间,此语句的输出空间标志为空,防止部分被改写的缓冲被读。
  180.                                          break;
  181.                                    case 'd':                        //gpzda 时间、日期
  182.                                          cmd_type=2;
  183.                                          buf&=~2;
  184.                                          break;
  185.                                    case 'l':                        //gpgll 经纬度,经纬度方向,定位状态
  186.                                          cmd_type=3;
  187.                                          buf&=~4;
  188.                                          break;
  189.                                    case 't':                        //gpvtg 运动方向、速度
  190.                                          cmd_type=4;
  191.                                          buf&=~8;
  192.                                          break;
  193.                                    default:                        //其他无效命令
  194.                                          cmd_type=0;
  195.                                          break;
  196.                              }
  197.                              if(cmd_type)                        //如果接收到的语句为需要接收的
  198.                              {
  199.                                    mode=2;                              //数据接收方式
  200.                                    comma_cnt=0;                  //清0逗号计数器
  201.                              }
  202.                              else                                    //如果接收的语句是不需要接收的
  203.                              {
  204.                                    mode=0;                              //进入无效接收模式
  205.                              }
  206.                        }                                          //命令判断结束                                                
  207.                  }                                                //命令接收模式结束
  208.                  else if(mode==2)                  //数据接收模式。处理数据。
  209.                  {
  210.                        switch (cmd_type)            //判断当前语句类型
  211.                        {
  212.                              case 1:                        //gpgga
  213.                                    switch(comma_cnt)
  214.                                    {
  215.                                          case 7:                        //收星数
  216.                                                if(byte_cnt<2)
  217.                                                {
  218.                                                      stalate[byte_cnt]=tmp;
  219.                                                }
  220.                                                break;
  221.                                          case 9:                                          //高度处理
  222.                                                if(byte_cnt<6){
  223.                                                      alt[byte_cnt]=tmp;
  224.                                                      alt_cnt++;                        //高度数据位数计数
  225.                                                }
  226.                                                break;
  227.                                          case 14:
  228.                                                mode=0;                                    //进入无效接收模式,直到有“$”字符被接收到
  229.                                                buf|=1;                                    //接收完某语句,标志其输出为有效
  230.                                                break;
  231.                                    }
  232.                                    break;
  233.                              case 2:                        //gpzda
  234.                                    switch(comma_cnt)
  235.                                    {
  236.                                          case 1:                                          //时间
  237.                                                if(byte_cnt<6)
  238.                                                {                        
  239.                                                      time[byte_cnt]=tmp;      
  240.                                                }
  241.                                                break;
  242.                                          case 2:                                          //日期
  243.                                                if(byte_cnt<2)
  244.                                                {
  245.                                                      date[byte_cnt+4]=tmp;
  246.                                                }
  247.                                                break;
  248.                                          case 3:                                          //月
  249.                                                if(byte_cnt<2)
  250.                                                {
  251.                                                      date[byte_cnt+2]=tmp;
  252.                                                }
  253.                                                break;
  254.                                          case 4:                                          //年
  255.                                                if(byte_cnt<4)
  256.                                                {
  257.                                                      date[byte_cnt]=tmp;
  258.                                                }
  259.                                                break;
  260.                                          case 6:
  261.                                                mode=0;
  262.                                                buf|=2;
  263.                                                break;
  264.                                    }
  265.                                    break;
  266.                              case 3:                        //gpgll
  267.                                    switch(comma_cnt)
  268.                                    {
  269.                                          case 1:                        //纬度
  270.                                                if(byte_cnt<9)
  271.                                                {
  272.                                                      lat[byte_cnt]=tmp;
  273.                                                }
  274.                                                break;
  275.                                          case 2:                        //n or s
  276.                                                n_s=tmp;
  277.                                                break;
  278.                                          case 3:                        //经度
  279.                                                if(byte_cnt<10)
  280.                                                {
  281.                                                      lon[byte_cnt]=tmp;
  282.                                                }
  283.                                                break;
  284.                                          case 4:                        //e or w
  285.                                                e_w=tmp;
  286.                                                break;
  287.                                          case 6:                        //定位状态
  288.                                                station=tmp;      //a or v
  289.                                                break;
  290.                                          case 7:
  291.                                                mode=0;
  292.                                                buf|=4;
  293.                                                break;
  294.                                    }
  295.                                    break;
  296.                              case 4:                        //gpvtg
  297.                                    switch(comma_cnt)
  298.                                    {
  299.                                          case 1:                        //方位角
  300.                                                if(byte_cnt<5)
  301.                                                {
  302.                                                      deg[byte_cnt]=tmp;
  303.                                                }
  304.                                                break;
  305.                                          case 7:                        //速度 km/h
  306.                                                if(byte_cnt<5)
  307.                                                {
  308.                                                      spd[byte_cnt]=tmp;
  309.                                                }
  310.                                                break;
  311.                                          case 9:
  312.                                                mode=0;
  313.                                                buf|=8;
  314.                                                break;
  315.                                    }
  316.                                    break;
  317.                        }//类型判断结束
  318.                  }//数据接收模式结束
  319.                  byte_cnt++;            //接收数位加1
  320.                  break;
  321.      }//判断接收数据内容结束
  322. }//中断结束
  323. /*
  324. $gpgga,024518.00,3153.7225,n,12111.9951,e,1,04,1.48,-00009,m,007,m,,*4c
  325. $gpgll,3153.7225,n,12111.9951,e,024518.00,a,a*63
  326. $gpvtg,000.0,t,004.7,m,000.0,n,000.0,k,a*20                  //方向(真),方向(磁),速度(英),速度(公)
  327. $gpgsa,a,2,04,08,17,20,,,,,,,,,1.48,1.48,0.03*08
  328. $gpgsv,2,1,08,04,15,231,38,08,29,218,42,11,49,043,,19,09,082,*76
  329. $gpgsv,2,2,08,27,14,198,29,28,71,316,,17,32,300,36,20,45,124,43*70
  330. $gprmc,024518.00,a,3153.7225,n,12111.9951,e,000.0,000.0,280107,04.7,w,a*12
  331. $gpzda,024519.45,28,01,2007,,*62
  332. */
  333. /*
  334. //上电后未定位时输出的数据(已有星历)
  335. $gpzda,061523.30,21,02,2008,,*6d
  336. $gpgga,,,,,,0,01,,,,,,,*67
  337. $gpgll,,,,,,v,n*64
  338. $gpvtg,,,,,,,,,n*30
  339. $gpgsa,a,1,,,,,,,,,,,,,,,*1e
  340. $gpgsv,3,1,11,02,64,082,,04,30,108,,15,29,204,,10,64,330,*75
  341. $gpgsv,3,2,11,08,16,100,,01,58,287,,24,22,310,,25,08,050,*79
  342. $gpgsv,3,3,11,27,16,069,,26,36,197,34,29,29,311,,,,,*4e
  343. $gprmc,061523.37,v,,,,,,,,,,n*7a
  344. $gpzda,061524.30,21,02,2008,,*6a
  345. $gpgga,,,,,,0,01,,,,,,,*67
  346. $gpgll,,,,,,v,n*64
  347. $gpvtg,,,,,,,,,n*30
  348. $gpgsa,a,1,,,,,,,,,,,,,,,*1e
  349. $gpgsv,3,1,11,02,64,082,,04,30,108,,15,29,204,,10,64,330,*75
  350. $gpgsv,3,2,11,08,16,100,,01,58,287,,24,22,310,,25,08,050,*79
  351. $gpgsv,3,3,11,27,16,069,,26,36,197,34,29,29,311,,,,,*4e
  352. $gprmc,061524.37,v,,,,,,,,,,n*7d
  353. */
在线bg4iww
发帖
8697
只看该作者 1楼 发表于: 2008-05-09
楼主在做频率计啊
离线BG4UVR
发帖
11287
只看该作者 2楼 发表于: 2008-05-09
不是频率计,只是用来测量晶体准确频率的东西。
可以用这来校准低档频率计的基准,或者tcxo晶体的频率什么的
离线BG7TBL
发帖
2965
只看该作者 3楼 发表于: 2008-05-09
1pps脉冲宽度那么窄,好捕捉吗,还有mcu处理1pps脉冲有无延迟.
我觉得用硬件可靠点!!
离线BG4UVR
发帖
11287
只看该作者 4楼 发表于: 2008-05-09
'
1pps脉冲宽度那么窄,好捕捉吗,还有mcu处理1pps脉冲有无延迟.
我觉得用硬件可靠点!!
'

一pps脉冲的宽度我没有测量,但这“1pps”的意思我觉得应该是指“1秒1次”的意思。avr定时器捕捉,误差肯定是有的,就是最大1个时钟周期。也正因为是这样,所以这种方法的精度也只是最大不大于1hz。从实际使用的结果看来,测量的结果应该还算是比较准确的。
离线BA5RW
发帖
48263
只看该作者 5楼 发表于: 2008-05-09
很好,学习了.
离线BG4UVR
发帖
11287
只看该作者 6楼 发表于: 2008-05-09
刚刚测量了bg4uat送我的taltron 20mhz的ocxo,结果见图。热稳定后,每分钟变化小于1hz…… 真爽,同时也验证了我算法的正确性。

btw:频率偏差了19hz,但目前还不能下定论说这个ocxo目前的频率准确度是1ppm。因为这个ocxo额定电压是3.3v的,而我测量时由于条件限制,是5v供电的。(3.3v下我的atmega128在20mhz时钟时无法工作,点不亮~)
离线FMer
发帖
2549
只看该作者 7楼 发表于: 2008-05-09
專程來頂貼!
离线bd7bq
发帖
1491
只看该作者 8楼 发表于: 2008-05-09
我几年前实验过用gps的10khz作为参考频率去锁一个12.8mhz的pll.但是当时无法对其精度做定量的测量.
搂主的东东不错,学习.
离线BG4UVR
发帖
11287
只看该作者 9楼 发表于: 2008-05-09
'
我几年前实验过用gps的10khz作为参考频率去锁一个12.8mhz的pll.但是当时无法对其精度做定量的测量.
搂主的东东不错,学习.
'

看了你的话,我终于明白那些gps做的输出10mhz标准频率的是怎么做的了,学习了
离线bellstudio
发帖
2820
只看该作者 10楼 发表于: 2008-05-10
强!学习了不少东西
离线bg4kc
发帖
2638
只看该作者 11楼 发表于: 2008-05-11
直接用gps的信号作基准,不能用来检验恒温晶体,
离线BG4UVR
发帖
11287
只看该作者 12楼 发表于: 2008-05-11
'
直接用gps的信号作基准,不能用来检验恒温晶体,
'

我看了一我的ocxo的参数,日稳定度是小于+-5ppb,而gps的1pps信号,误差是+-50ns,相当于+-50ppb吧,显然无法达到ocxo的精度。

但本例只是做为精确到hz的测量,在20mhz时,+-50ppb的误差相当于+-1hz。换句话说,这个例子在测量20mhz晶体的频率时,除去单片机执行机构的不确定性的误差,算法本身的误差,相当于hz位正负1 ,基本可以满足普通定量测量的需要了。
离线bd1es
发帖
2096
只看该作者 13楼 发表于: 2008-05-19
刚注意到,这个实验真棒。

avr机器的速度较高,内存也大,这样干脆楼主再加点儿简单的滤波吧,或者是滑动平均之类的机制。这样对减小gps的秒信号漂移有好处,:)。
离线BG4UVR
发帖
11287
只看该作者 14楼 发表于: 2008-05-19
初步想了一下,如果想加大分辨率,简单的方法,可以采用加长测量时间的方法。比如计算11次1pps信号的总长间隔的时间内,对晶振荡器进行计数,这样可以得到0.1hz的准确分辨率。不过我想对于我们业余应用中,几m到10几m的晶体来说,似乎也没有太大必要达到这种精度了,呵呵
离线bd7bq
发帖
1491
只看该作者 15楼 发表于: 2008-05-23
贴个图给参考.
离线Titan
发帖
2908
只看该作者 16楼 发表于: 2009-05-07
我搜到这个, http://www.jrmiller.demon.co.uk/projects/ministd/frqstd.htm
这个谁做过啊,刚好这个恒温晶体和这个gps模块我都有,嘿嘿
离线BH7NAO
发帖
18519
只看该作者 17楼 发表于: 2009-07-12
进来记号 学习一下 火腿强人就是多!
离线wang_xm
发帖
16
只看该作者 18楼 发表于: 2009-08-22
这样的测量精度有问题,有原理上的错误.
离线BG4UVR
发帖
11287
只看该作者 19楼 发表于: 2009-08-25
[quote=wang_xm]这样的测量精度有问题,有原理上的错误.[/quote]

请明示