我们通过电波钟模块接收国家授时中心发出的电波钟信号,通过STM32单片机将信号解码,就可以获得极为准确的时间信息。我的毕业设计中有一个模块是自动对时,在这里将我调出来的电波钟程序发出来,有感兴趣的或者设计需要电波钟的可以参考一下,节省开发时间。
一、电波钟介绍
电波钟是使用了低频授时技术进行对时的电子钟,更加详细的介绍可以查看 能够实现精确对时的电波钟(BPC)介绍 。
二、使用的元器件
STM32F103开发板
电波钟模块,可以在淘宝上买,价格在15元左右。
一些排针和杜邦线,由于连接电波钟模块
电波钟模块实物就是这样子的,一个小电路板以及一个磁棒。
三、硬件设计
模块共有5个焊脚,在使用时只用到了V、G、T、P四个接口,具体的连接是:
V-接电源VCC,1.5~3.5V
G-接地GND
T-时间信号反向输出端
P-模块使能端,工作接底,静态接高
H-自动增益控制接高电平(模块已连好高电平,不用连接)
我将T端连到了PA0口,使用了STM32单片机的定时器2的通道1即TIM2_CH1,使用单片机的输入捕获,将信号进行解码。电波钟模块与STM32的连接原理图如下:
四、BPC编码格式
中国的 BPC 时间信号编码规则的特征是:
(1)每一帧信号的周期是 20 秒钟,所以每分钟可以发播 3 帧时间调制信号。
(2)脉冲周期是 1 秒钟,但是,脉冲宽度(即高电平持续时间)有四种,分别对应着四进制数的 0、1、2、3,脉宽400ms 对应 3,脉宽 300ms 对应 2,脉宽 200ms 对应 1,脉宽 100ms 对应 0。每帧时间信号包含 19个脉冲,缺少1个秒脉冲作为分隔,有规律的脉冲组合得以用来表示对应的时间信息。
(3)帧与帧的间隔,以缺少 1 秒脉冲方式表示。
P0:每帧的开始,每分钟 3 次,间隔 20 秒,因此 P0 则是在第 0 秒、第 20 秒和第 40 的位置。
P1:用做帧定位,P1 有三种状态,分别为 0、1、2,0 表示该帧从第 1 秒钟开始,1 表示该帧从第 21 秒钟开始,2 表示该帧从第 41 秒钟开始。
P2:保留帧,由于以后扩展,所以接收到的P2永远为0。
P3:组合使用位,前半位用于表示上午或下午,后半位用于第 0-8 位的偶校验,其中0,1表示上午,23表示下午。
P4:组合使用位,前半位保留,后半位用于第 10-17 位的偶校验。
编码格式如下图:
可以看到,一帧的第4、5个脉冲表示当前的时,6、7、8脉冲表示当前的分钟信息,依次类推即可获得当前的时间信息,接收一帧完成后,根据P1 可以知道这一帧一分钟内的那一帧,从而可以获得当前的秒信息。
五、软件设计
使用keil 5进行程序开发,首先编写解码、显示程序,编译通过后将程序下载到单片机上运行,找到干扰较小的地方,就可以接收到BPC信号,从而获得当前的时间信息。输入捕获以及单片机对BPC信号进行解码的主要程序如下,要查看完整的文件,可以在文章末尾下载我调好的工程文件,可以通过LCD显示屏查看时间,串口也会输出接收到的BPC信息。
输入捕获程序:
timer.c
#include "timer.h"
#include "led.h"
#include "usart.h"
#include "sys.h"
char BPC_Get_Status=0;
u8 bhour=0,bmin=0,bweek=0,bday=0,bmon=0,byear=0,wu=0,bsec=0;
//定时器2通道1输入捕获配置
TIM_ICInitTypeDef TIM2_ICInitStructure;
void TIM2_Cap_Init(u16 arr,u16 psc)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //使能TIM2时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能GPIOA时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //PA0 清除之前设置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //PA0 输入
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_SetBits(GPIOA,GPIO_Pin_0);
//初始化定时器2 TIM2
TIM_TimeBaseStructure.TIM_Period = arr; //设定计数器自动重装值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //预分频器
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
//初始化TIM2输入捕获参数
TIM2_ICInitStructure.TIM_Channel = TIM_Channel_1; //CC1S=01 选择输入端 IC1映射到TI1上
TIM2_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Falling; //下降沿捕获
TIM2_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //映射到TI1上
TIM2_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //配置输入分频,不分频
TIM2_ICInitStructure.TIM_ICFilter = 0x01;//IC1F=0000 配置输入滤波器 不滤波 //n=2 滤波
TIM_ICInit(TIM2, &TIM2_ICInitStructure);
//中断分组初始化
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; //TIM2中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //先占优先级2级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //从优先级0级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
TIM_ITConfig(TIM2,TIM_IT_Update|TIM_IT_CC1,ENABLE);//允许更新中断 ,允许CC1IE捕获中断
TIM_Cmd(TIM2,ENABLE ); //使能定时器2
}
u8 TIM2CH1_CAPTURE_STA=0; //输入捕获状态
u16 TIM2CH1_CAPTURE_VAL; //输入捕获值
//定时器2中断服务程序
void TIM2_IRQHandler(void)
{
if((TIM2CH1_CAPTURE_STA&0X80)==0)//还未成功捕获
{
//发生溢出后的情况
if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
{
if(TIM2CH1_CAPTURE_STA&0X40)//已经捕获到高电平了
{
if((TIM2CH1_CAPTURE_STA&0X3F)==0X3F)//高电平太长了
{
TIM2CH1_CAPTURE_STA|=0X80;//标记成功捕获了一次
TIM2CH1_CAPTURE_VAL=0XFFFF;
}else TIM2CH1_CAPTURE_STA++;
}
}
//未发生溢出
if (TIM_GetITStatus(TIM2, TIM_IT_CC1) != RESET)//捕获1发生捕获事件
{
if(TIM2CH1_CAPTURE_STA&0X40) //捕获到一个上降沿
{
TIM2CH1_CAPTURE_STA|=0X80; //标记成功捕获到一次上升沿
TIM2CH1_CAPTURE_VAL=TIM_GetCapture1(TIM2);
TIM_OC1PolarityConfig(TIM2,TIM_ICPolarity_Falling); //CC1P=0 设置为上升沿捕获
}else //还未开始,第一次捕获上升沿
{
TIM2CH1_CAPTURE_STA=0; //清空
TIM2CH1_CAPTURE_VAL=0;
TIM_SetCounter(TIM2,0);
TIM2CH1_CAPTURE_STA|=0X40; //标记捕获到了上升沿
TIM_OC1PolarityConfig(TIM2,TIM_ICPolarity_Rising); //CC1P=1 设置为下降沿捕获
}
}
}
TIM_ClearITPendingBit(TIM2, TIM_IT_CC1|TIM_IT_Update); //清除中断标志位
}
主函数 main.c
#include "led.h"
#include "delay.h"
#include "sys.h"
#include "timer.h"
#include "usart.h"
#include "lcd.h"
extern u8 TIM2CH1_CAPTURE_STA; //输入捕获状态
extern u16 TIM2CH1_CAPTURE_VAL; //输入捕获值
char xiaoshi=0,fenzon=0,xinqi=0,ri=0,yue=0,nian=0,wu=0,miao=0;
int P1;
int i=0;
u8 a[19];
int main(void)
{
double temp=0;
int dat;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);// 设置中断优先级分组2
delay_init(); //延时函数初始化
uart_init(9600); //9600
LED_Init(); //初始化与LED连接的硬件接口
LCD_Init();
TIM2_Cap_Init(0XFFFF,72-1); //以1Mhz的频率计数
POINT_COLOR=RED;
LCD_ShowString(50,130,200,16,16," - - ");
LCD_ShowString(60,80,200,24,24," : : ");
while(1)
{
delay_us(10);
if(TIM2CH1_CAPTURE_STA&0X80)//成功捕获到了一次高电平
{
miao=miao+1;
if(miao==60) miao=0;
temp=TIM2CH1_CAPTURE_STA&0X3F;
temp*=65536; //溢出时间总和
temp+=TIM2CH1_CAPTURE_VAL; //得到总的高电平时间
temp=(double)temp/1000; //ms
if((temp>=850)&&(temp<950)) dat=0;
else if((temp>=750)&&(temp<850)) dat=1;
else if((temp>=650)&&(temp<750)) dat=2;
else if((temp>=550)&&(temp<650)) dat=3;
if(temp>1200)
{
printf("\r\n");
i=0;
miao++;
LCD_ShowNum(128,80,miao,2,24);
}
a[i]=dat;
i++;
if(i==19)
{
xiaoshi=((a[3])*4)+(a[4]);
fenzon=(((a[5])*16)+((a[6])*4)+(a[7]));
xinqi=((a[8])*4)+(a[9]);
ri=((a[11])*16)+((a[12])*4)+a[13]; // 换算方式
yue=((a[14])*4)+a[15];
nian=((a[16])*16)+((a[17])*4)+a[18];
wu=a[10]; //0和1表示上午 2和3表示下午
P1=a[1]; // 0表示1秒 1表示21秒 2表示41秒
if(P1==0)
{
miao=19;
}
if(P1==1)
{
miao=39;
}
if(P1==2)
{
miao=59;
}
if(wu>1)
{
xiaoshi=xiaoshi+12; //时间处理
}
}
TIM2CH1_CAPTURE_STA=0; //开启下一次捕获
printf("%d ",dat);
LCD_ShowNum(50,130,nian,4,16);
LCD_ShowNum(90,130,yue,2,16);
LCD_ShowNum(114,130,ri,2,16);
switch(xinqi)
{
case 0:
LCD_ShowString(160,130,200,16,16,"Sunday ");
break;
case 1:
LCD_ShowString(160,130,200,16,16,"Monday ");
break;
case 2:
LCD_ShowString(160,130,200,16,16,"Tuesday ");
break;
case 3:
LCD_ShowString(160,130,200,16,16,"Wednesday");
break;
case 4:
LCD_ShowString(160,130,200,16,16,"Thursday ");
break;
case 5:
LCD_ShowString(160,130,200,16,16,"Friday ");
break;
case 6:
LCD_ShowString(160,130,200,16,16,"Saturday ");
break;
}
LCD_ShowNum(60,80,xiaoshi,2,24);
LCD_ShowNum(94,80,fenzon,2,24);
LCD_ShowNum(128,80,miao,2,24);
POINT_COLOR=GBLUE;
LCD_ShowxNum(10,10,dat,10,24,0);
POINT_COLOR=RED;
}
}
}
六、运行效果展示
电波钟实物视频演示
http://player.youku.com/player.php/sid/XMTYwOTYzMjk0OA==/v.swf
七、附件
1、工程全部代码
好使,今天在家再次自动对时成功!
话说甘肃这边接收到的信号还很强
谢谢
磁棒?感觉是时钟晶体
不是晶体,是接收信号的
请问如果捕获到的不是钟头,而是从中间开始,或者有什么干扰信号,会不会读书不准呀?
请问如果捕获到的不是钟头,而是从中间开始,或者有什么干扰信号,会不会读数不准呀?