基于STM32的自动对时电波钟设计

angel
发布于 2020-11-13 16:32
浏览
3收藏

我们通过电波钟模块接收国家授时中心发出的电波钟信号,通过STM32单片机将信号解码,就可以获得极为准确的时间信息。我的毕业设计中有一个模块是自动对时,在这里将我调出来的电波钟程序发出来,有感兴趣的或者设计需要电波钟的可以参考一下,节省开发时间。

 

一、电波钟介绍

 

旧式的钟表具有一个共同的缺点就是时间误差较大,未解决电子钟的时间同步问题,出现了低频时码对时技术。电波钟表是一种新兴的计时产品,是一种通过接受国家授时中心的无线信号以确定时间准确性的计时工具,其成本低,使用方便,广泛应用在一些要求时间高精度同步的设备中,电波钟的电池只要有电就会精确的走动,不需要时常校正时间,它会自动接收电波对时信号并自动校正时间,在电力电网、通信、电子商务等方面都在大规模使用。

 

二、使用的元器件

 

STM32F103开发板

电波钟模块,可以在淘宝上买,价格在15元左右。

一些排针和杜邦线,由于连接电波钟模块

电波钟模块实物就是这样子的,一个小电路板以及一个磁棒。

基于STM32的自动对时电波钟设计-鸿蒙开发者社区

 

基于STM32的自动对时电波钟设计-鸿蒙开发者社区

三、硬件设计


模块共有5个焊脚,在使用时只用到了V、G、T、P四个接口,具体的连接是:

V-接电源VCC,1.5~3.5V

G-接地GND

T-时间信号反向输出端

P-模块使能端,工作接底,静态接高

H-自动增益控制接高电平(模块已连好高电平,不用连接)

我将T端连到了PA0口,使用了STM32单片机的定时器2的通道1即TIM2_CH1,使用单片机的输入捕获,将信号进行解码。电波钟模块与STM32的连接原理图如下:

基于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 位的偶校验。

编码格式如下图:

基于STM32的自动对时电波钟设计-鸿蒙开发者社区

可以看到,一帧的第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;

}
}
}
  

 

六、运行效果展示

基于STM32的自动对时电波钟设计-鸿蒙开发者社区

 

 

 

分类
已于2020-11-13 16:32:10修改
3
收藏 3
回复
举报
1条回复
按时间正序
/
按时间倒序
wx66836015b0e6d
wx66836015b0e6d

您好,有完整代码和压缩包吗

回复
2024-7-2 10:19:04
回复
    相关推荐