ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Air530 GPS卫星授时继电器:基于STC15的绝对时间定时控制解析

Air530 GPS卫星授时继电器:基于STC15的绝对时间定时控制解析 简介基于Air530 GPS模块与STC15系列单片机的卫星授时继电器控制工程面向单片机开发者、物联网及自动化领域学习者解决高精度授时与定时开关控制的难点适用于电力系统、通信机房或科研仪器等需要UTC时间同步的场景。工程以源码为核心压缩包共19个文件主要包含C语言程序、函数库头文件、Keil工程文件、Hex烧录镜像及少量编译中间文件总计76KB结构紧凑便于直接查阅与二次开发。目前已有424人浏览学习。通过阅读源码可系统掌握Air530模块的串口通信命令与UTC时间解析流程、EEPROM断电保存开关时间参数、单片机轮询比较当前时间并驱动继电器动作的完整逻辑同时可参考OLED显示等周边驱动的实现细节了解如何在同一工程中整合多种外设资源。代码模块划分清晰既适合初学GPS授时与定时控制原理也可作为快速移植到实际产品或竞赛项目的工程模板为后续扩展多路继电器、网络同步等功能提供基础。1. 卫星授时继电器为什么定时控制需要 GPS 的绝对时间做定时控制的人大多经历过这类场景用单片机 RTC 定时一个月后误差能到几十秒用 WiFi 对时断网或掉线后时间直接漂移。Air530 卫星授时继电器解决的不是「延时多少毫秒」而是「现在到底是几点几分几秒」——它通过接收 GPS 卫星信号中的 UTC 时间让单片机获得微秒级的时间基准再按预设时刻驱动继电器通断。这套设计特别适合路灯控制、电力设备轮询、实验室仪器定时供电这类需要长周期、绝对时间同步的场景。项目基于 STC15F2K60S2 单片机工程里包含了完整的 C51 源码、OLED 显示驱动以及 Air530 模块的串口通信解析代码。其中值得拆的地方有两块一是 GPRMC 语句里时间与日期的解析流程二是 EEPROM 中定时参数存储与比较逻辑的设计。前者决定时间读得准不准后者决定继电器动作稳不稳。下文从通信协议开始逐步把整个工程的工作链路梳理清楚。2. 从 NMEA 0183 到北京时间Air530 模块的串口通信与时间解析2.1 模块接线与串口参数Air530 是一款国产 GPS 授时模块输出标准的 NMEA 0183 协议语句。与常见的 ATK-S1216、NEO-6M 相比它的差异化优势在于授时模式下的快速锁定能力和较低的功耗。模块默认输出语句包含$GPRMC这是推荐最小定位信息时间与日期都在这条语句里。接线层面Air530 的 TXD 接 STC15 的 RXDP3.0模块的 RXD 接单片机的 TXDP3.1VCC 接 3.3V 或 5V具体看模块版本GND 共地。部分 Air530 模块还有 PPS 脉冲引脚可输出秒脉冲用于精准对时本项目中用得不多但保留了这个引脚的扩展能力。串口配置是固定的波特率 48008 位数据1 位停止位无校验。STC15F2K60S2 的串口 1 工作在模式 18 位 UART波特率由定时器 2 或定时器 1 产生。工程里用的是定时器 2 做波特率发生器代码如下void UART1_Init(void) { SCON 0x50; // 模式18位UART允许接收 AUXR | 0x04; // T2作为波特率发生器 T2L 0xE8; // 4800bps 11.0592MHz T2H 0xFF; AUXR | 0x10; // T2运行 ES 1; // 开启串口1中断 EA 1; }代码逻辑并不复杂SCON 0x50将串口 1 配置为 8 位可变波特率模式同时打开接收允许位REN。因为 STC15 的定时器 2 可以独立作为波特率发生器而不占用定时器中断所以用AUXR | 0x04把 T2 分配给串口。当系统晶振为 11.0592MHz 时T2 重载值设为 0xFFE8 即可精确产生 4800bps——之所以用 11.0592MHz 晶振就是因为它能整数分频出 4800、9600、115200 这些常用波特率。ES 1使能串口中断之后数据到达就会触发UART1_Interrupt把字节存入接收缓冲区。注意这里没有使用 FIFO工程的处理方式是每接收一个字节就放入数组同时在主循环中判断是否收到完整的$GPRMC帧。2.2 GPRMC 语句结构与解析策略$GPRMC的典型格式为$GPRMC,081836.00,A,3103.4523,N,12122.2345,E,0.5,0.0,160424,,,A*63各字段含义如下表| 字段序号 | 内容 | 示例值 | 说明 | |--------------------------------------------------------| | 1 | UTC 时间 | 081836.00 | 时:分:秒.毫秒UTC 标准时间 | | 2 | 定位状态 | A | A有效定位V无效 | | 3 | 纬度 | 3103.4523 | ddmm.mmmm 格式 | | 4 | 北纬/南纬 | N | N 或 S | | 5 | 经度 | 12122.2345| dddmm.mmmm 格式 | | 6 | 东经/西经 | E | E 或 W | | 9 | UTC 日期 | 160424 | 日/月/年1604242024年4月16日 |工程里对 GPRMC 的解析不是用strstr函数而是直接用状态机扫描。原因很实际51 单片机内存小string.h里的库函数开销偏大而且 NMEA 数据的接收是边收边处理的状态机方式可以做到每收到一个字符就判定当前处于哪一个字段解析完直接跳到下一步。解析代码核心如下void Parse_GPRMC(char *buf) { if (buf[5] ! C) return; // 不是 $GPRMC 直接退出 char *p buf; // 跳过 $GPRMC, while (*p ! ,) p; p; if (*p $) return; // 防止空帧 // 解析 UTC 时间: hhmmss.sss utc_hour (p[0] - 0) * 10 (p[1] - 0); utc_min (p[2] - 0) * 10 (p[3] - 0); utc_sec (p[4] - 0) * 10 (p[5] - 0); // 跳过时间字段到逗号 while (*p ! ,) p; p; gps_valid *p; // A 或 V }解析的思路是先定位到 GPRMC 帧头然后通过逗号跳转字段位置。utc_hour、utc_min、utc_sec分别对应字段 1 中的时、分、秒直接以字符减法转为整数。gps_valid保存的是定位有效标志位后续做时间更新判断时先检查这个标志防止在室外无信号时把无效时间写进系统。2.3 时间更新与有效标志判断GPS 模块输出时间是有条件的必须完成卫星锁定且定位状态为 AActive时GPRMC 中的时间才是可信的。因此代码里有一个明确的门控逻辑if (gps_valid A) { // 只有当卫星定位有效时才更新 RTC 时间 if (utc_hour ! last_hour || utc_min ! last_min || utc_sec ! last_sec) { Set_RTC_Time(utc_hour, utc_min, utc_sec); // 同时更新日期 Set_RTC_Date(gprmc_day, gprmc_month, gprmc_year); last_hour utc_hour; last_min utc_min; last_sec utc_sec; } }Set_RTC_Time是工程里封装的时间写入函数。之所以要做last_sec比较是因为 GPS 模块每秒输出一次 GPRMC 语句如果每次都触发写入EEPROM 和 RTC 寄存器会被频繁擦写。实际运行中这种频繁写入对 Flash 寿命有影响加一个「时间变化才写」的判断是工程上的常规做法。注意 UTC 与北京时间的偏差问题GPS 输出的时间是 UTC 标准时间与北京时间相差 8 小时。工程里是在显示和比较时统一做偏移转换还是直接改 RTC 初值这取决于产品需求。如果是纯授时继电器建议在显示层转换底层 RTC 保持 UTC 不变这样切换时区比如设备被部署到东七区只需要改一个宏定义。3. 授时精度的隐形门槛GPS 纪元翻转与本地时间偏移处理3.1 GPS 周数翻转问题GPS 系统的时间计数基于周数Week Number原始设计用 10 位二进制表示最大 1024达到 1024 后归零重新计数。第一次翻转发生在 1999 年 8 月第二次发生在 2019 年 4 月 6 日。对 Air530 这类模块而言翻转本身由模块固件处理输出的 GPRMC 日期是正常公历日期不用开发者操心。但有一点需要注意如果模块固件较老或未打补丁翻转后可能会输出 1999 年的日期导致单片机拿到错误的时间。工程里的防护手段是日期合法性检查#define GPS_WEEK_ROLLOVER_YEAR 1999 unsigned char Check_Date_Valid(unsigned char year, unsigned char month, unsigned char day) { if (year GPS_WEEK_ROLLOVER_YEAR || year 2100) return 0; if (month 1 || month 12) return 0; if (day 1 || day 31) return 0; return 1; }这里year由 GPRMC 字段里的两位年份加 2000 得 到如果模块翻转未处理读出的年份会变成 1999 年或更早直接拒绝更新 RTC 时间。同时校验月、日范围防止非法日期写入 RTC。这个检查的代价极低但对系统鲁棒性的提升是实打实的——设备挂在户外可能长期无人维护不能指望它自己发现时间异常。3.2 北京时间转换与跨天边界UTC 时间转北京时间常规做法是加 8 小时但跨天的处理容易踩坑。假设 UTC 时间是 18:30:00加 8 小时后是次日 02:30:00日期必须加 1。反之UTC 时间是 02:00:00减 8 小时后是前一日 18:00:00日期要减 1。很多初写者在做加法时不考虑进位导致设备在 UTC 18:00 到 24:00 之间显示的时间日期错位。工程里的转换逻辑通常是这样的void UTC_To_Beijing(unsigned char utc_h, unsigned char utc_m, unsigned char utc_s, unsigned char *bj_h, unsigned char *bj_m, unsigned char *bj_s) { int tmp_h utc_h 8; if (tmp_h 24) { tmp_h - 24; // 标记日期进位主程序里处理日1 date_carry 1; } *bj_h tmp_h; *bj_m utc_m; *bj_s utc_s; }date_carry是一个全局标志位主循环在更新显示时间和比较继电器状态时检查这个标志若为 1 则对 RTC 日期做加一操作。边界情况还需要考虑月末、年末如果当前是 1 月 31 日加一天后应该是 2 月 1 日而不是 2 月 32 日。因此完整处理时建议先把日期转成「自 2000 年以来的天数」做运算再转回年月日。这个算法代码量不大但能避免大量边界 bug。实际工程中如果嫌麻烦也有一个偷懒但可靠的替代方案不做日期计算按「UTC 时间 固定偏移 8 小时」显示日期仅作展示用途继电器比较只用时分秒。如果用户的定时需求限定在一天内不会跨到次日凌晨这个方案能减少至少 30 行代码但通用性差不推荐用在商业产品里。3.3 STC15 的 EEPROM 读写与参数存储设计STC15F2K60S2 的 EEPROM 实际上是程序 Flash 的一部分通过 IAPIn-Application Programming方式访问。与外部 24C02 等独立 EEPROM 芯片不同它不需要 I2C 时序直接用专用寄存器操作省去了一个外设。工程里存放继电器开/关时间参数的地址规划如下| EEPROM 地址 | 内容 | 大小 | 默认值 | |---------------------------------------------| | 0x0000 | 继电器开启时:分 | 2B | 06:30 | | 0x0002 | 继电器关闭时:分 | 2B | 18:30 | | 0x0004 | 使能标志 | 1B | 0x5A (使能) |EEPROM 的读取相对简单直接调库函数或自己实现 IAP 读。以下是基于 STC15 的底层读写代码注意擦除操作是以扇区为单位的每个扇区 512 字节所以修改参数前必须整扇区擦除void IAP_Erase(unsigned int addr) { IAP_CONTR 0x82; // 使能 IAP设置等待参数 IAP_CMD 0x03; // 扇区擦除命令 IAP_ADDRL addr 0xFF; IAP_ADDRH (addr 8) 0xFF; IAP_TRIG 0x5A; // 触发命令 IAP_TRIG 0xA5; _nop_(); IAP_CONTR 0x00; // 关闭 IAP防止误操作 } void IAP_Write_Byte(unsigned int addr, unsigned char dat) { IAP_CONTR 0x82; IAP_CMD 0x02; // 字节编程命令 IAP_ADDRL addr 0xFF; IAP_ADDRH (addr 8) 0xFF; IAP_DATA dat; IAP_TRIG 0x5A; IAP_TRIG 0xA5; _nop_(); IAP_CONTR 0x00; }IAP_CONTR 0x82是使能 IAP 并设置 Flash 操作等待时间。0x82 中高两位10表示 CPU 等待 24MHz 以下系统时钟的 Flash 操作周期这个值在不同频率下需要调整。IAP_TRIG连续写入 0x5A 和 0xA5 是 STC 系列触发 IAP 操作的固定时序缺少任何一步都不会执行。注意每次操作完成后立即关闭 IAPIAP_CONTR 0x00这是为了防止程序跑飞时误擦 Flash导致固件损坏。工程里有个小细节EEPROM 参数第一次上电时可能是 0xFFFlash 擦除后的初始状态所以加一个有效性校验如果不是 0x5A 就写入默认定时参数避免继电器因「未初始化数据」乱动作。4. 继电器动作的核心逻辑时间比较、状态切换与可靠性设计4.1 定时比较与输出控制继电器开关判断是主循环中最核心的判断逻辑。工程中先把时分秒转为「当日秒数」进行比较简单直观unsigned long cur_sec (unsigned long)bj_hour * 3600 (unsigned long)bj_min * 60 bj_sec; unsigned long on_sec (unsigned long)relay_on_hour * 3600 (unsigned long)relay_on_min * 60; unsigned long off_sec (unsigned long)relay_off_hour * 3600 (unsigned long)relay_off_min * 60; if (relay_enable 0x5A) { if (on_sec off_sec) { // 同一天内开关 if (cur_sec on_sec cur_sec off_sec) Relay_On(); else Relay_Off(); } else if (on_sec off_sec) { // 跨天情况: 如 22:00 开, 06:00 关 if (cur_sec on_sec || cur_sec off_sec) Relay_On(); else Relay_Off(); } }这里分两种场景处理on_sec off_sec是常规的同日内定时比如早上 6:30 开启、下午 18:30 关闭on_sec off_sec是跨天定时比如晚上 22:00 开启、次日早上 6:00 关闭。跨天情况下开启条件变成「当前时间大于等于开启时间或者小于关闭时间」用||连接。这个分支的设计很容易被忽略但它决定了设备能否正确处理深夜时段的继电器状态。比较操作的时序上主循环每秒执行一次。每次循环读取 RTC 时间调用UTC_To_Beijing转为北京时间再执行上述比较。继电器吞吐电流较大时比如驱动 220V 交流负载不建议在中断服务函数里直接切换继电器而是在主循环中集中处理避免中断嵌套导致的时间抖动。4.2 继电器驱动与磁保持设计工程中使用的是常规电磁继电器用 STC15 的 IO 口驱动时需要加三极管或 ULN2003。简单方案是 NPN 三极管如 S8050驱动sbit RELAY_PIN P1^0; void Relay_On(void) { RELAY_PIN 1; // 三极管导通继电器线圈得电 } void Relay_Off(void) { RELAY_PIN 0; }要注意单片机上电瞬间 IO 口默认是高电平如果 P1.0 直接接三极管基极继电器会在单片机复位瞬间误动作一次。解决方式有两个一是外部接下拉电阻10K保证上电瞬间基极为低电平二是在main函数最开始就把所有继电器相关 IO 初始化为低电平再延时 200ms 等待系统稳定。工程里两个措施都做了双保险。如果用的是磁保持继电器控制方式完全不同需要正反向脉冲驱动且线圈只在状态切换时通电保持时断电。磁保持继电器对电池供电类设备很友好因为静态功耗几乎为零。但它的驱动电路需要 H 桥或双 MOSFET 结构不像普通继电器一个三极管就能搞定。Air530 授时继电器的场景通常是市电供电的设备用普通电磁继电器更简单工程里选型没有用磁保持。4.3 OLED 显示与调试信息输出工程中带有 OLED 屏幕驱动oled.c和oled.h主界面显示当前北京时间、卫星状态和继电器开关状态。这是工程里比较直观的调试手段否则一块板子放在那里到底有没有收到 GPS 信号只能靠猜测。void Display_Main(void) { OLED_Clear(); OLED_ShowString(0, 0, UTC Time:); OLED_ShowNum(60, 0, utc_hour, 2, 8); OLED_ShowChar(76, 0, :); OLED_ShowNum(84, 0, utc_min, 2, 8); if (gps_valid A) OLED_ShowString(0, 2, GPS: OK); else OLED_ShowString(0, 2, GPS: LOCK); if (relay_state) OLED_ShowString(0, 4, RELAY: ON); else OLED_ShowString(0, 4, RELAY: OFF); }OLED 显示频率建议 1 秒刷新一次不要每轮主循环都刷新。因为 SSD1306 通过 I2C 或 SPI 通信高频刷新会占用总线影响 GPS 数据接收的中断响应。工程里用一个 1 秒定时标志位控制刷新节奏这个思路同样适用于其他外设——GPS 数据接收优先显示可以慢一点。另外工程中的桌面12832.bmp是 OLED 开机显示的位图bmp.h将其转化为数组嵌入代码在系统上电时展示品牌 logo 或产品名。bmp 转换的细节不复杂用 PC 软件将图片取模为 C 数组即可注意 OLED 是 128x32 像素图片尺寸要匹配否则显示会错位。5. 源码工程结构与实战排错技巧拿到工程压缩包后先认清目录结构。顶层文件Air530卫星授时继电器.uvproj是 Keil 工程文件用 Keil uVision4 或 uVision5 打开即可。核心源码只有几个文件| 文件 | 作用 | |------------------------------------------------| | main.c | 主循环、定时比较、继电器控制、GPS 解析 | | oled.c/h | OLED 显示驱动与界面刷新 | | bmp.h | 开机位图数据 | | oledfont.h | OLED 字库包含 ASCII 字符点阵 | | STC15F2K60S2.h | STC15 系列寄存器定义头文件 | | Air530卫星授时继电器.hex | 编译生成的固件可通过 STC-ISP 烧录 |调试时最常遇到的坑有三个。第一个是串口收不到 GPS 数据排查步骤用示波器或逻辑分析仪看 Air530 的 TXD 引脚是否有波形如果有波形但单片机收不到检查波特率配置是否正确——STC15 的定时器 2 做波特率发生器时AUXR寄存器位设置错一个比特波特率就会翻倍或减半。第二个是时间显示始终为 0多半是gps_valid判断条件不满足模块未锁定把模块放到窗边等 1-2 分钟看状态标志位变化。第三个是继电器动作时间不对检查 EEPROM 中的定时参数是否被意外改写可以在 OLED 上增加一个对比显示直观看到当前读出的 on/off 时间。工程里还有一个细节Air530卫星授时继电器.m51是内存映射文件如果编译后 ROM 或 RAM 溢出查看这个文件里最后几行就能定位是哪个函数占用了多少存储空间。比如提示OVERFLOW时优先检查oledfont.h是否包含了过多的全角字符数组这类数组是内存大户。关于 GPS 信号的工程化理解卫星授时不是一上电就有时间的。模块从冷启动到首次锁星通常需要 30-60 秒天线位置好时可能十几秒这期间 GPRMC 输出的状态是 V代码不会更新 RTC 时间。如果设备在室内或天线被金属遮挡锁定时间会显著延长甚至完全无法锁定。所以项目的启动逻辑应当设计为先读取上次 EEPROM 保存的时间作为兜底GPS 信号可用后再校准。这样即使 GPS 永远无信号继电器也能按上一次同步的时间工作只是精度会逐渐劣化。工程里没有实现看门狗但实际部署中建议加上。STC15F2K60S2 内置看门狗在main初始化和主循环喂狗一旦程序跑飞或死循环看门狗自动复位避免继电器卡在错误状态。喂狗位置放在主循环末尾即可同时要保证主循环单次执行时间远小于看门狗溢出周期否则会被误复位。本文还有配套的精品资源点击获取
返回列表