前言
笔者一直以来对辉光管、荧光管等充满年代感的显示器件非常感兴趣,然而,苦于水平有限,始终望而却步。
(图源:立创开源硬件平台)
数年以前,笔者用现成的套件制作过一个数码管时钟,聊以替代,然而它的显示效果毕竟还不够复古。
半年前,笔者在某宝发现了这样一款6位VFD模块,体积很小,SPI通信协议,集成了驱动电路,并且提供了详细的示例程序!于是头脑一热,果断入手。
点亮的效果果然很nice,但是从开发板上飞线连接不nice。于是又头脑一热,决定边学边做,设计一个专门的电路,做个时钟出来。既然都这样了何不当时就连显示器件驱动一块设计了
就是为了这点醋,我才包的这顿饺子。
功能构想
- 网络对时
- 主要是不熟悉RTC(实时时钟)。
- 用笔者最熟悉的ESP8266或ESP32做MCU。
- USB TYPE-C 供电
- 主要是现在TYPE-C的线随处可得。
- 一组按钮,用于更改工作模式以及调整设置等。
- 自动调节亮度
- 不必十分精确,因此只需光敏电阻即可
- 刚好利用ESP8266或ESP32的ADC实现
硬件部分
电路设计
经过历时半个月断断续续反复修改后,终于画出了一版较为满意的原理图和PCB。(不是很敢相信立创EDA的自动布线,所以最后还是手动布线了。懒,所以不想铺铜了。)
综合考虑价格、性能、体积、加工难度、外围电路复杂度等方面,最终选用了ESP-12F。
同样考虑到焊接难度,选用了只有供电功能的2P TYPE-C。因此没有办法把USB转串口芯片安置在板子上了,只好引出一组排针,作程序烧录以及串口调试用。
后续发现VFD模块的输入电压应该是5V而不是3.3V,但是对于其正常工作并没有什么影响。
电路焊接
从嘉立创打的板子以及立创商城购买的元件到货以后,正式开工。
我本以为新买的调温焊台已经足够好用了,0805、SOP-123、SOT-89等封装的贴片元件已经足够好焊了,但是没想到实际操作起来还是很难办。
折腾了一顿以后,总算把元件都焊上去了。检查一番,利用助焊剂、刀片等“土法”处理完虚焊连锡等问题以后,不管好不好看,至少没有故障了。
软件部分
采用Arduino IDE编写。目前还没有写出一个尽善尽美的程序来,所以在这里只截取部分代码。
2024-03-03 程序基本完善了
2025-03-08 完整代码发布在Github,链接见附录
关于VFD屏幕显示的函数是由示例程序提供的,就不写在这里了。
连接WiFi
为了在不同的环境下不必在程序里修改配置,采用了WiFiManager
库。在没有连接到已保存的WiFi时,ESP模组会开启热点,用移动设备或计算机连接该热点即可修改网络配置。
1
2
3
4
5
void setup(){
WiFiManager wifiManager;
wifiManager.autoConnect("ESP-12F");
}
网络对时
没有用大部分文章经常采用的NTPClient
库,而是用configTime
函数实现。
1
2
3
4
5
6const char *ntpServer = "ntp.ntsc.ac.cn";
const long gmtOffset_sec = 8 * 3600;
const int daylightOffset_sec = 0;
void setup(){
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
}tm
类型的结构体,然后getLocalTime(&结构体名)
即可将当前时间信息存入结构体中。
网络获取天气信息
本打算采用心知天气API,用ArduinoJSON库解析天气信息,但是在运行一段时间后ESP模组会莫名其妙自动复位(大概是内存溢出了?)。只好放弃这个功能,回头再细细研究。
亮度调节
通过向VFD屏幕发送命令可以调节其亮度,该命令是一个16进制的两位数,即十进制的0-255,而ADC读取到的光敏电阻的模拟信号数值是0-1024。因此利用map
函数映射就可以很方便的实现自动亮度调节。
虽然该模拟信号不是与环境光强线性相关的,但至少是正相关,实测亮度调节效果还是可以的。
另外,需要用Ticker
库做一个定时中断,以使得亮度调节实时运行。
1
2
3
4
5
6
7void setup(){
ticker.attach(1, autoChangeLight);
}
void autoChangeLight(){
lightValue = analogRead(analogInPin);
lightCommand = map(lightValue,0,1024,0,255);
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46volatile int lightMode;
void lightSetting(){
attachInterrupt(digitalPinToInterrupt(5), changeLightMode, FALLING);
switch(lightMode){
case 0:
ticker.attach(1,autoChangeLight);
VFD_clear();
VFD_WriteStr(0,"auto");
delay(1000);
break;
case 1:
ticker.detach();
lightCommand=0x40;
VFD_clear();
VFD_WriteStr(0,"111111");
delay(1000);
break;
case 2:
ticker.detach();
lightCommand=0x80;
VFD_clear();
VFD_WriteStr(0,"222222");
delay(1000);
break;
case 3:
ticker.detach();
lightCommand=0xc0;
VFD_clear();
VFD_WriteStr(0,"333333");
delay(1000);
break;
case 4:
ticker.detach();
lightCommand=0xff;
VFD_clear();
VFD_WriteStr(0,"444444");
delay(1000);
break;
}
}
IRAM_ATTR void changeLightMode(){
lightMode++;
if(lightMode>=5){
lightMode=0;
}
}IRAM_ATTR
,参见这篇文章。
按钮改变工作模式
思路与改变亮度模式的部分类似,也是利用外部中断和switch
语句实现。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31volatile int mode;
void setup(){
attachInterrupt(digitalPinToInterrupt(0), changeMode, FALLING);
}
void loop(){
switch(mode){
case 0:
printLocalTime();
break;
case 1:
printCalendar();
break;
case 2:
printTime();
break;
case 3:
lightSetting();
break;
case 4:
detachInterrupt(digitalPinToInterrupt(5));
printAboutInfo();
break;
}
}
IRAM_ATTR void changeMode(){
mode++;
if(mode>=5){
mode=0;
}
}
结语
虽然最终程序还没有写得尽善尽美,但也算是初步完成了这样一个gadget。
就是为了这点醋,我才包的这顿饺子。
从硬件设计到程序设计的全部流程都体验了一番,这顿饺子也算是没白包。
这两个时间差这么多的原因大抵是后面那个时钟上的DS1307不够精确罢(迫真
只可惜期末考试之前一直在画PCB导致绩点惨不忍睹(悲
附录
完