视频展示:
项目简介
本项目基于STM32配合ESP01S连接WIFI来实现自动对时,通过74HC595和三极管来驱动IN12辉光管显示时间,通过OLED显示连接状态和温湿度,温湿度数据来源于DHT11温湿度传感器。
看到大多数辉光管设计都是基于ESP32或不带WiFi连接功能的STM32单片机,我就想用STM32主控搭配ESP01S这个全新方案来实现IN12辉光管WiFi时钟的设计,历经2个月的各种打板和代码调试终于完成了,以下分享我的相关制作过程。

项目功能
1.高精度时间显示
采用DS1302实时时钟芯片独立走时,通过74HC595串行驱动4只辉光管,实现时、分四位数字的显示。
支持WiFi联网自动对时:通过ESP8266模块从网络API获取Unix时间戳,自动转换并写入DS1302,确保时间长期准确。在WiFi连接失败的情况下自动调用RTC时间,连接WiFi成功后更新RTC时间。
2.辉光管防阴极中毒保护
为防止辉光管长时间显示同一数字导致阴极中毒,程序内置了定时自动防中毒机制(默认每10分钟触发一次,持续随机显示不同数字)。
同时支持手动触摸按键(PB0)触发随机显示,运行一次后自动恢复时间显示,全程非阻塞(由定时器计时触发),不影响系统其他任务。
3.氖管秒钟闪烁
利用两个氖管(NE_ON/NE_OFF)模拟秒闪烁,每1秒交替亮灭。
4.WiFi连接状态指示
上电初始化期间,4个DOT指示灯(PB12~PB15)以流水灯效果模拟WiFi连接过程,连接成功或超时后自动熄灭,直观反馈网络状态。
5.OLED同步显示
0.91英寸OLED屏实时显示当前时间、温湿度(DHT11)、WiFi连接状态,便于调试与信息查看。
6.非阻塞软件架构
所有周期性任务(时间刷新、氖管控制、防中毒更新、按键扫描、流水灯驱动)均在TIM3和TIM4中断中完成,主循环仅负责OLED刷新与WiFi重连逻辑,确保系统响应迅速且稳定。
项目参数
组件 | 型号/规格 |
|---|---|
主控芯片 | STM32F103C8T6 72MHz,64KB Flash,20KB RAM |
辉光管驱动 | 5片74HC595级联 三线SPI控制 |
辉光管 | IN12-B 4只 |
实时时钟 | DS1302 独立供电,带RAM |
WiFi模块 | ESP8266-01S 串口AT指令,支持TCP连接 |
温湿度传感器 | DHT11 单总线 |
显示屏幕 | 0.91英寸OLED (128×64) I2C接口 |
用户输入 | TTP223 触摸模块 电容式触摸 |
电源 | 12V DC输入,功率约 3W 适配器或开关电源,含高压升压电路 |
外壳 | 3D打印外壳 |
4只IN12b辉光管是某鱼收来的,现在价格大概50左右一只。

管脚图
硬件说明

将STM32输出的串行数据(SDA/SCK)转换为并行输出,驱动 4 组辉光管的阴极三极管。5 片 74HC595 级联,U1 的 Q7' 输出连接到 U2 的 DS,U2 的 Q7' 连接到 U3 的 DS,以此类推,形成 40 位的移位寄存器链。STM32 通过SCK时钟脉冲,将 40 位串行数据依次移入5片74HC595。

采用44只13001高压三极管,每只对应辉光管的一个阴极,包括4个小数点。当595输出高电平时,三极管导通,对应辉光管阴极接地,形成电流通路;输出低电平时,三极管截止,阴极悬空。辉光管的阳极统一连接到170V电源,通过20k限流电阻限制电流,防止过流损坏。

还有另外一对13001用来驱动两个氖管工作


基于MAX1771的170v升压电源模块也上传了,也可以使用自己购买的12V升压170V模块替代(使用时一定要注意高压)。
PCB及注意事项

集成版主控板(RTC电池型号CR1220)

IN12插座面板

负责连接两块板子的排针要买这种长排针

调试时一定要注意高压部分,可以看到这里我设计了一个继电器来控制高压的输入和输出,修改代码可以控制继电器的断开和闭合。 接口电源线是从旁边的2P排针引出到安装在外壳上的DC接口。
软件代码
代码基于STM32标准库开发,开发软件为Keil5,注意要把esp8266.c里的WiFi名称和密码改成自己的 #define ESP8266_WIFI_INFO "AT+CWJAP=\"name\",\"password\"\r\n"//wifi的账号和密码
API选择的是拼多多的,实测在AT指令里是能返回正确的unix时间戳的 // 拼多多时间API TCP连接配置 #define ESP8266_PDD_INFO "AT+CIPSTART=\"TCP\",\"api.pinduoduo.com\",80\r\n" ESP01S模块的AT指令测试可以复制以下指令(逐行写入):
AT
AT+RST
AT+CWMODE=1
AT+CIPMUX=0
AT+CWJAP="name","password"
AT+CIPMODE=1
AT+CIPSTART="TCP","api.pinduoduo.com",80
AT+CIPSEND
GET http://api.pinduoduo.com/api/server/_stm
比较麻烦的就是esp01s的连接,,直接让AI写了:
_Bool ESP8266_Sync_To_DS1302(void)
{
UsartPrintf(USART_DEBUG, "[Sync] Start sync time to DS1302...\r\n");
// 1. 确保TCP连接已建立
if(ESP8266_SendCmd("AT+CIPSTATUS\r\n", "CONNECTED") != 0) {
// TCP未连接,尝试重新连接
UsartPrintf(USART_DEBUG, "[Sync] TCP not connected, reconnecting...\r\n");
if(ESP8266_SendCmd(ESP8266_PDD_INFO, "CONNECT") != 0) {
UsartPrintf(USART_DEBUG, "[Sync] TCP connect failed\r\n");
return 1;
}
DelayXms(500);
}
// 2. 发送HTTP请求(使用更精确的API)
char *http_req = "GET /api/server/_stm HTTP/1.1\r\n"
"Host: api.pinduoduo.com\r\n"
"User-Agent: STM32/ESP8266\r\n"
"Accept: */*\r\n"
"Connection: close\r\n\r\n";
char send_cmd[32];
sprintf(send_cmd, "AT+CIPSEND=%d\r\n", strlen(http_req));
ESP8266_Clear();
if(ESP8266_SendCmd(send_cmd, ">") == 0) {
Usart_SendString(USART2, (unsigned char *)http_req, strlen(http_req));
UsartPrintf(USART_DEBUG, "[Sync] HTTP request sent, waiting for response...\r\n");
// 3. 等待响应(增加等待时间)
int retry = 0;
while(retry < 50) { // 5秒超时
DelayXms(100);
if(strstr((char *)esp8266_buf, "server_time") != NULL) {
break; // 找到了时间戳
}
retry++;
}
if(retry >= 50) {
UsartPrintf(USART_DEBUG, "[Sync] Timeout waiting for response\r\n");
return 1;
}
// 4. 解析时间戳并写入DS1302
char *ptr = strstr((char *)esp8266_buf, "server_time");
if(ptr) {
ptr = strchr(ptr, ':');
if(ptr) {
ptr++; // 跳过冒号
// 跳过空格和引号
while(*ptr == ' ' || *ptr == '\"' || *ptr == '\'') ptr++;
// 提取13位时间戳(毫秒)
long long ts_ms = atoll(ptr);
UsartPrintf(USART_DEBUG, "[Sync] Timestamp ms: %lld\r\n", ts_ms);
if(ts_ms > 0) {
// 毫秒转秒,加8小时(北京时间UTC+8)
time_t rawtime = (time_t)(ts_ms / 1000) + 28800;
struct tm *timeinfo;
timeinfo = localtime(&rawtime);
// 填充时间结构体
TimeData.year = timeinfo->tm_year + 1900;
TimeData.month = timeinfo->tm_mon + 1;
TimeData.day = timeinfo->tm_mday;
TimeData.hour = timeinfo->tm_hour;
TimeData.minute = timeinfo->tm_min;
TimeData.second = timeinfo->tm_sec;
TimeData.week = (timeinfo->tm_wday == 0) ? 7 : timeinfo->tm_wday;
UsartPrintf(USART_DEBUG, "[Sync] Parsed time: %04d-%02d-%02d %02d:%02d:%02d\r\n",
TimeData.year, TimeData.month, TimeData.day,
TimeData.hour, TimeData.minute, TimeData.second);
// 写入DS1302
DS1302_SetTime();
// 验证写入
DS1302_read_realTime();
UsartPrintf(USART_DEBUG, "[Sync] DS1302 time after write: %02d:%02d:%02d\r\n",
TimeData.hour, TimeData.minute, TimeData.second);
return 0; // 成功
}
}
}
}
UsartPrintf(USART_DEBUG, "[Sync] Sync failed\r\n");
return 1; // 失败
}代码还有些部分也是交给AI写的,比较冗杂,但能实现功能就懒得删了,代码注释AI也给写的比较清楚。程序文件已上传至github,欢迎查看指正ggd-nixie-clock
组装流程

小心安装辉光管

安装DHT11模块,DC接口和开关(开关口尺寸已经修改成合适尺寸)

嵌入主控板和TTP223触摸模块

插上辉光管顶板

外壳安装复位按键和OLED显示屏

安装底部支架
实物图



正面

背面
外壳
3D外壳链接-makerworld也可以直接下载附件里提供的3mf文件
