数码之家

 找回密码
 立即注册
搜索
查看: 996|回复: 30

[Arduino] 旧式数码万年历升级改造为 NTP 时钟

[复制链接]
发表于 2024-6-9 20:18:12 | 显示全部楼层 |阅读模式
本帖最后由 zgm1996 于 2024-6-9 20:48 编辑

整个改造过程跨度好几个月,今年二月份就改完了,终于今天坐下来把过程写写,供有改造想法的各位朋友参考。

先上主角照片:


关于万年历的来历:
据说 2014 年有一次老爹老娘出去串门,在亲戚家楼下垃圾箱里一眼就看见它了,刚被扔出来,玻璃面板都没破,两个小人儿还挺好看的,果断领回家。
据说回家通电发现只是调时间的 4 个按键氧化了按下去没反应,于是老爹果断的拆下按键,去了附近修家电的铺子换了 4 个按键花了 5 枚华币。
(为什么是据说?因为当年还在上学。等我中午回家表已经挂在墙上了)

血亏啊!!!这样的按键我抽屉里有啊!!!

想改造的起因是在每一次停电又送电之后,这个万年历的时间都会变回 09 年 12 月。

您问会不会是纽扣电池没电了?不可能,绝对不可能,这货自打 2014 年到家,每次来电都要重新调一遍的。

所以这块表在我家工作了即将 10 年,次次停电次次调。

忍无可忍,必须搞它。换个主控,升级一下!

先拆开看看,极度简洁:


220V 转 12V 的变压器直接接到板子,经过 4 个 1N4007 构成的整流桥之后,到了两个像三极管一样的东西,仔细一看,两个并联的 78L05,发热量巨大,上面的电容都烤黑了。
两个 78L05 并联电流最大也就 200 mA,改的时候拆下来换成 7805。
主控是一个34脚小板上的牛屎芯,实现了年月日、星期、时分、秒闪烁、农历、温度与按键调整时间。重制也要复现这些功能。
有 6 个 PNP 三极管 S8550,用于驱动6条“线”上的数码管;测温用的玻璃封装的 NTC 热敏电阻;至于扬声器,压根就没响过。

充分利用互联网资源,找到了这位老哥的改造先例:
“古董万年历升级WiFi授时 STM32+ESP8266” https://blog.csdn.net/qq_19262979/article/details/104857420
但由于我没用过 STM32(真是惭愧,有时间一定补上这块短板),考虑将全部功能都放在 ESP8266 上实现。

其中文章提到了另一位老哥的农历转换:
“农历和阳历互转(c语言)” https://www.cnblogs.com/liyang31tg/p/4123171.html
可以修改移植到 ESP8266 上为我所用。

于是有了以下方向:
ESP8266 快速验证想法,用 Arduino C 写。
ESP8266 内部 RTC 误差大,挂一个 PCF8563 做 RTC。
测温度,数字方案有 DS18B20,模拟方案可以继续利用万年历自带的 NTC 热敏电阻。

鉴于 Arduino IDE 及其难用的代码编辑器和每次都无脑重新编译全部的文件极耗时间的问题,改用 VSCode + PlatformIO。

PlatformIO 下只重新编译变动的文件,而且 VSCode 的各种好功能(格式化、高亮、跟踪跳转)都可以用起来了


PlatformIO编译环境搭建就不写了,互联网玄学,部署时请备好极为充足的耐心 + 降压药 或 仅出于必要的科研目的使用一下国际互联网。

下面开始动手:

继续利用互联网资源。

框架程序参考了“ESP8266 NodeMCU NTP Client-Server: Get Date and Time (Arduino IDE)” https://RandomNerdTutorials.com/esp8266-nodemcu-date-time-ntp-client-server-arduino/

这位外国老哥的示例只有 WI-FI 连接和 NTP 授时,靠 delay 控制每 2 秒获取一次时间,但讲解十分详细。

在此基础上,逐步添加 DS18B20 测温、PCF8563 时钟模块、农历、OLED 显示等功能,拼拼凑凑就改出了一个的万年历雏形。

PlatformIO 和 Arduino IDE 区别在于:
1、PlatformIO 中子函数要写在 void setup() 和 void loop() ,编译时才不会报找不到的错误,好多 Arduino IDE 编译没问题的 *.ino 的程序换成 PlatformIO 都要调顺序
2、PlatformIO 要写 #include <Arduino.h>

  1. // 结构体 disp_info:存储全部显示数据
  2. struct display_data
  3. {
  4.     unsigned short year;
  5.     unsigned char month;
  6.     unsigned char day;
  7.     unsigned char weekday;
  8.     unsigned char hour;
  9.     unsigned char minute;
  10.     unsigned char second;
  11.     int lunar_month;
  12.     int lunar_day;
  13.     int temperature;
  14.     int adc;
  15. } disp_info;
复制代码

DS18B20初始化,设置为非阻塞模式,转换完成后等待读取。
  1. void ds18B20_init(){
  2.   sensors.begin();      //启动DS18B20
  3.   sensors.setWaitForConversion(false); //设置DS18B20为非阻塞模式
  4. }
复制代码

OLED 显示,12864的屏,屏芯片 SSD1315,使用了 U8G2 库,为了避免烧屏,增加了滚动显示,每秒上/下移动一行:
  1. void oled_display(){

  2.   // 清屏,设置初始位置
  3.   u8g2.clearBuffer();   //清除缓冲区,不清除屏幕
  4.   u8g2.setCursor(0, place+10);


  5. /*---------------------- 第一行 -------------------------------*/
  6.   //年月日
  7.   u8g2.setFont(u8g2_font_profont15_tf);  //字号8x10
  8.   u8g2.print(disp_info.year%100);   // 只取年份后两位
  9.   u8g2.print("-");
  10.   u8g2.print(disp_info.mouth);
  11.   u8g2.print("-");
  12.   u8g2.print(disp_info.day);

  13.   //星期
  14.   u8g2.setCursor(64, place+10);     // 星期位置固定
  15.   u8g2.print("<");
  16.   u8g2.print(disp_info.weekday);
  17.   u8g2.print("> ");

  18.   //农历
  19.   u8g2.setCursor(88, place+10);
  20.   if (disp_info.lunar_mouth<10){     // 靠右对齐,月份为个位数时补一个空格
  21.     u8g2.print(" ");//1个
  22.   }
  23.   u8g2.print(disp_info.lunar_mouth);
  24.   u8g2.print('-');
  25.   u8g2.println(disp_info.lunar_day);
  26.   
  27.   /*---------------------- 第二行 -------------------------------*/
  28.   //时分秒
  29.   
  30.   u8g2.setFont(u8g2_font_profont29_tf);
  31.   if (disp_info.hour<10){     // 小时居中
  32.     u8g2.setCursor(10, place+35);
  33.   }
  34.   else{
  35.     u8g2.setCursor(0, place+35);
  36.   }

  37.   u8g2.print(disp_info.hour);
  38.   u8g2.print(':');
  39.   if (disp_info.minute<10){     // 分钟补零
  40.     u8g2.print(0);
  41.   }
  42.   u8g2.print(disp_info.minute);
  43.   u8g2.print(':');
  44.   if (disp_info.second<10){     // 秒补零
  45.     u8g2.print(0);
  46.   }
  47.   u8g2.println(disp_info.second);

  48.   /*---------------------- 第三行 -------------------------------*/
  49.   //温度
  50.   u8g2.setCursor(5, place+52);
  51.   u8g2.setFont(u8g2_font_profont15_tf);
  52.   // u8g2.print("T:");
  53.   u8g2.print(disp_info.temperature);
  54.   u8g2.print(char(176));u8g2.print("C");
  55.   

  56.   //光强
  57.   u8g2.setCursor(64, place+52);
  58.   u8g2.print("ADC:");
  59.   u8g2.println(disp_info.adc);


  60.   u8g2.sendBuffer();      // 显示本次数据


  61.   //修改下一次显示位置及设置滚动方向
  62.   if (count_flag==0){
  63.     place++;
  64.     if (place>11){
  65.       count_flag=1;
  66.     }
  67.   }
  68.   else{
  69.     place--;
  70.     if (place==0){
  71.       count_flag=0;
  72.     }
  73.   }

  74. }
复制代码

不加OLED 用串口显示也可以:
  1. void Serial_print(){

  2.   Serial.print(disp_info.year);
  3.   Serial.print("-");
  4.   Serial.print(disp_info.mouth);
  5.   Serial.print("-");
  6.   Serial.print(disp_info.day);

  7.   //星期
  8.   Serial.print(" <");
  9.   Serial.print(disp_info.weekday);
  10.   Serial.print("> ");

  11.   //农历
  12.   Serial.print(disp_info.lunar_mouth);
  13.   Serial.print('-');
  14.   Serial.println(disp_info.lunar_day);
  15.   
  16.   //时分秒
  17.   Serial.print(disp_info.hour);
  18.   Serial.print(':');
  19.   if (disp_info.minute<10){     // 分钟补零
  20.     Serial.print(0);
  21.   }
  22.   Serial.print(disp_info.minute);
  23.   Serial.print(':');
  24.   if (disp_info.second<10){     // 秒补零
  25.     Serial.print(0);
  26.   }
  27.   Serial.println(disp_info.second);

  28.   //温度与光强
  29.   Serial.print("T: ");
  30.   Serial.print(disp_info.temperature);
  31.   Serial.print("ºC  ");

  32.   Serial.print("ADC: ");
  33.   Serial.println(disp_info.adc);
  34.   
  35.   Serial.println("");   //分隔下次数据

  36. }
复制代码

初始化程序:
  1. void setup() {

  2. Rtc.Begin();              //初始化PCF8563
  3. Serial.begin(115200); // 启动串口 设置波特率115200
  4. oled_init();               //初始化 OLED
  5. ds18B20_init();        //初始化 ds18B20

  6. int connect_flag = wifi_connect();
  7. if (connect_flag==1){
  8. set_time();
  9. }
  10. timer1.attach(1,every_second);

  11. }


  12. void loop() {

  13. // 主程序为空,核心程序在 every_second()
复制代码

秒刷新程序:
  1. void every_second(){

  2. RtcDateTime now = Rtc.GetDateTime();

  3. disp_info.year=now.Year();
  4. disp_info.mouth=now.Month();
  5. disp_info.day=now.Day();
  6. disp_info.weekday=now.DayOfWeek();

  7. using namespace std;
  8. using namespace lunar;
  9. Date lunar_date = LuanrDate(disp_info.year, disp_info.mouth, disp_info.day);

  10. disp_info.lunar_mouth=lunar_date.month;
  11. disp_info.lunar_day=lunar_date.day;

  12. disp_info.hour=now.Hour();
  13. disp_info.minute=now.Minute();
  14. disp_info.second=now.Second();


  15. disp_info.temperature=sensors.getTempCByIndex(0); //取上次温度数据
  16. sensors.requestTemperatures(); //启动下次转换

  17. disp_info.adc=analogRead(sensorPin); //取ADC数据

  18. oled_display();
  19. Serial_print();

  20. }
复制代码

效果就是这样的:




如果只是 DIY 一个时钟当作摆件的话,到这儿就结束了,这一版本的源代码送上。


但下面才是重头戏,因为万年历的数码管要重新驱动起来继续利用。
(老娘说这个表晚上也能看见时间,不用点灯。所以这块表的家庭地位十分稳固,只能升级改造不能扔)。

分析一下万年历电路,拆掉主控,万用表打“二极管挡”,跑一遍数码管线路,得到以下的分布:






数码管采用共阳接法,主控的 1、2、3、32、33、34 各控制一个三极管基极作为“位线”驱动,每条“位线”驱动 3 个数码管(个别的有 2 个)。

每条“位线” 3 个数码管的各段引脚由原主控 4-10、11-17、31-20(10/16/17)直接控制。

各“位线”上的数码管“同名段”之间并在一起,个别用不到的“段线”用来控制其它位置的“笔画”。
(比如 2 组 34 线:月份 < 2,其余段线控制农历和温度的笔画;2 组 1 线:月份十位 < 4 等等)。

这样一来,要用到的也就不止一种共阳字符表了。

需要这么多IO,ESP8266 自己肯定不够用,请出著名的移位寄存器 74HC595 和著名的 3-8 译码器 74HC138 登场。

将 3 颗 74HC595 级联,用于控制数码管段,74HC138 用于控制 6 个三极管的基极,做位选。

数码管驱动电路原理图:


手搓 74HC595 和 74HC138 驱动板,把线焊上去:


下一步,就是针对 74HC595 和 74HC138 写驱动了。

直接放代码:74HC595.h,解释放在注释


  1. // 程序名称:康巴丝万年历 74HC595 三级联 + 74HC138 译码位选驱动
  2. // 作    者:zgm1996
  3. // 创建时间:2024/01/06 12:40:25
  4. // =================================================================================
  5. // 版    本:V1.0
  6. // 当前版本完成时间:2024年1月26日19:51:39
  7. // 变更内容:
  8. //            通过 ESP8266 + 74HC595 + 74HC138 测试
  9. //
  10. // 存在问题:秒中断闪烁过于明显!!!
  11. //        
  12. // =================================================================================

  13. #include <Arduino.h>

  14. // 0-9,关,常规共阳数码管,0xff全关断
  15. static const unsigned char table[] = {0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8, 0x80, 0x90, 0xff};

  16. // 特殊数码管--农历日期十位,0xff关断做消隐
  17. static const unsigned char lunar_ten_table[] = {0xff, 0xf6, 0x8c, 0x86};

  18. // 特殊数码管--小时十位,0xff直接关断做消隐
  19. static const unsigned char hour_ten_table[] = {0xff, 0xf9, 0xc4};

  20. // 秒灯所在段码
  21. static const unsigned char flash_table[] = {0xff, 0xf6};

  22. class HC595
  23. {

  24. public:
  25.     HC595(int input_pin, int shift_clock_pin, int storage_clock_pin);//int input_pin, int shift_clock_pin, int storage_clock_pin, int b0, int b1, int b2
  26.     //串行数据输入,移位寄存时钟,存储时钟
  27.     void display();

  28. private:
  29.     int _DS;    // 串行数据输入
  30.     int _SH_CP; // 移位寄存时钟
  31.     int _ST_CP; // 存储时钟
  32.     int _b0;
  33.     int _b1;
  34.     int _b2;

  35.     void send_to_595(unsigned char Data);
  36. };

  37. HC595::HC595(int input_pin, int shift_clock_pin, int storage_clock_pin)
  38. {
  39.     _DS = input_pin;
  40.     _SH_CP = shift_clock_pin;
  41.     _ST_CP = storage_clock_pin;

  42.     pinMode(_DS, OUTPUT);
  43.     pinMode(_SH_CP, OUTPUT);
  44.     pinMode(_ST_CP, OUTPUT);

  45.     _b0 = D0;
  46.     _b1 = D4;
  47.     _b2 = D8;

  48.     pinMode(_b0, OUTPUT);
  49.     pinMode(_b1, OUTPUT);
  50.     pinMode(_b2, OUTPUT);
  51. }

  52. void HC595::send_to_595(unsigned char Data)
  53. {
  54.     shiftOut(_DS, _SH_CP, MSBFIRST, Data); // 这个就是用MSBFIRST参数让0-7个针脚以高字节优先输出(LSBFIRST 低字节)是DS的参数,
  55. }


  56. /*===================================================

  57.             康巴斯万年历-数码管核心驱动程序

  58. ===================================================*/
  59. // 由全局结构体:“disp_info” 传入 “年,月,日,星期,时,分,秒,农历月,农历日,温度”
  60. void HC595::display()
  61. {
  62.     // 按各显示位分解数据
  63.     unsigned char year_1, year_2, month_1, month_2, date_1, date_2,
  64.         hour_1, hour_2, flash_flag, flash, minute_1, minute_2,
  65.         L_month_1, L_month_2, L_date_1, L_date_2,
  66.         temp_1, temp_2;

  67.     // 临时存储联合显示的中间数据
  68.     unsigned char sendtemp;

  69.     // 分解各显示位
  70.     year_1 = disp_info.year % 1000 / 10; // 年月日
  71.     year_2 = disp_info.year % 1000 % 10;

  72.     month_1 = disp_info.month / 10;
  73.     month_2 = disp_info.month % 10;

  74.     date_1 = disp_info.day / 10;
  75.     date_2 = disp_info.day % 10;

  76.     hour_1 = disp_info.hour / 10; // 时分,秒灯指示
  77.     hour_2 = disp_info.hour % 10;

  78.     flash_flag = disp_info.second % 2;
  79.     flash = flash_table[flash_flag]; // 秒灯闪烁控制

  80.     minute_1 = disp_info.minute / 10;
  81.     minute_2 = disp_info.minute % 10;

  82.     L_month_1 = disp_info.lunar_month / 10; // 农历月日
  83.     L_month_2 = disp_info.lunar_month % 10;

  84.     L_date_1 = disp_info.lunar_day / 10;
  85.     L_date_2 = disp_info.lunar_day % 10;

  86.     temp_1 = disp_info.temperature / 10; // 温度
  87.     temp_2 = disp_info.temperature % 10;

  88.     //=====================  line_1,位选-000 =================================
  89.     // 第一线位选,控制:农历月份个位,分钟十位,日期十位
  90.     digitalWrite(_ST_CP, LOW); // 准备接收
  91.     send_to_595(table[L_month_2]);
  92.     send_to_595(table[minute_1]);

  93.     if (date_1 == 0)
  94.     {                          // 取公历月份十位数,
  95.         sendtemp = table[10]; // 对0进行消隐
  96.     }
  97.     else
  98.     {
  99.         sendtemp = table[date_1];
  100.     }

  101.     // 农历月份个位,笔画C段受公历日期十位数(G段,I/O_16)控制
  102.     // C段码值 0x04(00000100),将农历月份个位与0x04按位与,共阳接法为0时,该位点亮
  103.     // 则将该位映射到F段(00100000b)0x20并取反得0xdf,再按位与运算,附加到农历月份个位上
  104.     if ((table[L_month_2] & 0x04) == 0)
  105.     {
  106.         sendtemp = sendtemp & 0xdf;
  107.     }

  108.     send_to_595(sendtemp);
  109.     digitalWrite(_ST_CP, HIGH);

  110.     digitalWrite(_b0, LOW);
  111.     digitalWrite(_b1, LOW);
  112.     digitalWrite(_b2, LOW);
  113.     delayMicroseconds(3000);
  114.     digitalWrite(_b0, HIGH);
  115.     digitalWrite(_b1, HIGH);
  116.     digitalWrite(_b2, HIGH);

  117.     //=====================  line_2  位选-001 =================================
  118.     // 控制农历日期十位(特殊码表),小时个位,日期个位
  119.     digitalWrite(_ST_CP, LOW); // 准备接收
  120.     send_to_595(lunar_ten_table[L_date_1]);
  121.     send_to_595(table[hour_2]);
  122.     send_to_595(table[date_2]); // 位选110
  123.     digitalWrite(_ST_CP, HIGH);

  124.     digitalWrite(_b0, HIGH);
  125.     digitalWrite(_b1, LOW);
  126.     digitalWrite(_b2, LOW);
  127.     delayMicroseconds(3000);
  128.     digitalWrite(_b0, HIGH);
  129.     digitalWrite(_b1, HIGH);
  130.     digitalWrite(_b2, HIGH);

  131.     //=====================  line_3  位选-010 =================================
  132.     // 第三线位选,控制月份个位,小时十位,农历日期个位
  133.     digitalWrite(_ST_CP, LOW); // 准备接收
  134.     send_to_595(table[L_date_2]);
  135.     sendtemp = hour_ten_table[hour_1];

  136.     // 农历日期个位笔画C段受小时十位(G段,I/O_10)控制
  137.     // C段码值 0x04(00000100),将农历月份个位与0x04按位与,共阳接法为0时,该位点亮
  138.     // 则将该位映射到G段(01000000b)0x40并取反得0x0xbf,再按位与运算,附加到小时十位上
  139.     if ((table[L_date_2] & 0x04) == 0)
  140.     {
  141.         sendtemp = sendtemp & 0xbf;
  142.     }

  143.     send_to_595(sendtemp);
  144.     send_to_595(table[month_2]); // 位选101
  145.     digitalWrite(_ST_CP, HIGH);

  146.     digitalWrite(_b0, LOW);
  147.     digitalWrite(_b1, HIGH);
  148.     digitalWrite(_b2, LOW);
  149.     delayMicroseconds(3000);
  150.     digitalWrite(_b0, HIGH);
  151.     digitalWrite(_b1, HIGH);
  152.     digitalWrite(_b2, HIGH);

  153.     //=====================  line_32  位选-100 =================================
  154.     // 第四线位选,控制年份个位,星期
  155.     digitalWrite(_ST_CP, LOW); // 准备接收
  156.     send_to_595(0xff);           // 用0xff填充最高位74HC595,无显示用处
  157.     send_to_595(table[disp_info.weekday]);
  158.     send_to_595(table[year_2]); // 位选100
  159.     digitalWrite(_ST_CP, HIGH);

  160.     digitalWrite(_b0, HIGH);
  161.     digitalWrite(_b1, HIGH);
  162.     digitalWrite(_b2, LOW);
  163.     delayMicroseconds(3000);
  164.     digitalWrite(_b0, HIGH);
  165.     digitalWrite(_b1, HIGH);
  166.     digitalWrite(_b2, HIGH);

  167.     //=====================  line_33  位选-100 =================================
  168.     // 第五线位选,控制年份个位,秒灯,温度十位
  169.     digitalWrite(_ST_CP, LOW); // 准备接收
  170.     send_to_595(table[temp_1]);
  171.     sendtemp = flash;

  172.     // 温度十位笔画C段受flash(I/O_10)控制
  173.     // C段码值 0x04(00000100),将农历月份个位与0x04按位与,共阳接法为0时,该位点亮
  174.     // 则将该位映射到G段(01000000b)0x40并取反得0x0xbf,再按位与运算,附加到小时十位上
  175.     if ((table[temp_1] & 0x04) == 0)
  176.     {
  177.         sendtemp = sendtemp & 0xbf;
  178.     }

  179.     send_to_595(sendtemp);
  180.     send_to_595(table[year_1]); // 位选011
  181.     digitalWrite(_ST_CP, HIGH);


  182.     digitalWrite(_b0, LOW);
  183.     digitalWrite(_b1, LOW);
  184.     digitalWrite(_b2, HIGH);
  185.     delayMicroseconds(3000);
  186.     digitalWrite(_b0, HIGH);
  187.     digitalWrite(_b1, HIGH);
  188.     digitalWrite(_b2, HIGH);

  189.     //=====================  line_34  位选-101 =================================
  190.     // 第六线位选,控制月份十位(附带农历月份十位),分钟个位,温度个位
  191.     digitalWrite(_ST_CP, LOW); // 准备接收
  192.     send_to_595(table[temp_2]);
  193.     send_to_595(table[minute_2]);

  194.     if (month_1 == 0)
  195.     {
  196.         sendtemp = table[10]; // 正常计算公历月份十位数,并对0进行消隐
  197.     }
  198.     else
  199.     {
  200.         sendtemp = table[month_1];
  201.     }

  202.     // 农历月份的十位数 1X月(b,c段)受公历月份的十位数(d-I/O_12,f-I/O_16)控制
  203.     // 农历月份的十位数为1时,映射到(d-I/O_12,f-I/O_16)(11010111b)0xd7,再按位与运算,附加到小时十位上
  204.     if (L_month_1 == 1)
  205.     {
  206.         sendtemp = sendtemp & 0xd7; // 将对应位置零
  207.     }

  208.     // 温度个位笔画C段受公历月份十位数(G段,I/O_17)控制
  209.     // C段码值 0x04(00000100),将农历月份个位与0x04按位与,共阳接法为0时,该位点亮
  210.     // 则将该位映射到G段(01000000b)0x40并取反得0xbf,再按位与运算,公历月份十位数上
  211.     if ((table[temp_2] & 0x04) == 0)
  212.     {                                // 按位与,C段值为0时点亮(共阳接法)
  213.         sendtemp = sendtemp & 0xbf; //
  214.     }
  215.     send_to_595(sendtemp); // 位选010
  216.     digitalWrite(_ST_CP, HIGH);


  217.     digitalWrite(_b0, HIGH);
  218.     digitalWrite(_b1, LOW);
  219.     digitalWrite(_b2, HIGH);
  220.     delayMicroseconds(3000);
  221.     digitalWrite(_b0, HIGH);
  222.     digitalWrite(_b1, HIGH);
  223.     digitalWrite(_b2, HIGH);

  224. }
复制代码



点亮了。


把程序和前面“DIY小时钟”合并整理一下。

顺利的话我以为这就完了。

才怪。

每秒刷新时,有个明显的抖动。(鼠标出镜,在老数码之家拆过)


逐个部分排查,问题锁定在了 DS18B20 上。尽管已经设置成了非阻塞模式,但读出数据的时间仍然超出了可接受的范围。

果断换方案,改用 NTC 热敏电阻测温,用 ESP8266 的 ADC 。热敏电阻型号为 NTC_3950 50K。

继续利用互联网资源:

热敏电阻使用 Steinhart-Hart 方程计算温度,引用一张网上的图:


三个系数可由以下网站计算得出:
SRS Thermistor Calculator https://www.thinksrs.com/downloads/programs/therm%20calc/ntccalibrator/ntccalculator.html



  1. /*-----------------------  NTC 电阻测温  ---------------------------------*/
  2. #include <math.h>

  3. int sensorPin = A0;      // GPIO 17
  4. int adcValue = 0;        // 数模转换值 0-1024
  5. unsigned int Rs = 47495; // Rs 实测值为 47.49k
  6. double V_REF = 1.017;      // NodeMCU开发板自带分压电路,应修改参考电压
  7.                          // 将 NodeMCU 的 A0 引脚接至 VCC,测量 ESP12 核心板的 A0 电压 作为参考电压
  8.     //+3.3V
  9.     // ---
  10.     //  |
  11.     // | |  Rs,47.495K
  12.     //  |
  13.     //  |---------------------A0
  14.     //  |
  15.     // |/|  R2,NTC 3950 50k
  16.     //  |
  17.     //  |
  18.     // -==-
  19.     // GND


  20. // 利用Steinhart-Hart方程计算温度
  21. double Thermister(int val)
  22. {
  23.     double V_NTC = (double)val / 1024;
  24.     double R_NTC = (Rs * V_NTC) / (V_REF - V_NTC);
  25.     R_NTC = log(R_NTC);
  26.     double Temp = 1 / (0.0008494662974 + (0.0002191266040 + (0.0000001055140254 * R_NTC * R_NTC)) * R_NTC); // 开式温度
  27.     Temp = Temp - 273.15;                                                                                   // 转摄氏度
  28.     return Temp;
  29. }
复制代码




完美,不闪了!

做几处优化:
为了进一步减小不必要的时间开销,把中断程序中更新数据结构体的逻辑进行优化,按变化频次 “秒>>年” 分先后考虑,其余不变的直接 return 回去刷新数码管,不必再写到结构体。


  1. // 中断服务程序,每秒更新数据
  2. void IRAM_ATTR every_second()
  3. {
  4.     DateTime now = Ext_RTC.now(); // 取当前时间
  5.     disp_info.temperature = int(Thermister(analogRead(sensorPin)));

  6.     if (disp_info.second != now.second()) // 秒发生变化
  7.     {
  8.         disp_info.second = now.second(); // 秒一定改变,用于更新闪烁
  9.         if (disp_info.minute != now.minute())
  10.         {
  11.             disp_info.minute = now.minute(); // 更新分钟
  12.             if (disp_info.hour != now.hour())
  13.             {
  14.                 disp_info.hour = now.hour(); // 更新小时

  15.                 if (disp_info.day != now.day()) // 日期发生变化,判断年份是否变化
  16.                 {
  17.                     if (disp_info.year != now.year())
  18.                     { // 年份变了更新年,之后更新其它数据,因为农历依赖年份信息;
  19.                       // 年份不变直接更新其它数据
  20.                         disp_info.year = now.year();
  21.                     }

  22.                     disp_info.day = now.day(); // 更新日期、农历、星期
  23.                     disp_info.month = now.month();

  24.                     if (now.dayOfTheWeek() != 0)
  25.                     {
  26.                         disp_info.weekday = now.dayOfTheWeek();
  27.                     }
  28.                     else
  29.                     {
  30.                         disp_info.weekday = 8;
  31.                     }

  32.                     using namespace std;   // 超出知识储备范围,后续在解释
  33.                     using namespace lunar; // 超出知识储备范围,后续在解释
  34.                     Date lunar_date = LuanrDate(disp_info.year, disp_info.month, disp_info.day);

  35.                     disp_info.lunar_month = lunar_date.month;
  36.                     disp_info.lunar_day = lunar_date.day;
  37.                 }
  38.                 else
  39.                 {
  40.                     return; // 日期不变直接返回
  41.                 }
  42.             }
  43.             else
  44.             {
  45.                 return; // 小时不变直接返回
  46.             }
  47.         }
  48.         else
  49.         {
  50.             return; // 分不变直接返回
  51.         }
  52.     }
  53.     else
  54.     {
  55.         return; // 秒不变直接返回
  56.     }
  57. }
复制代码


装上之后就不能用串口下载程序了,改用 Arduino OTA。
  1. void setup()
  2. {

  3.     Ext_RTC_init();

  4.     int connect_flag = wifi_connect();
  5.     if (connect_flag == 1)
  6.     {
  7.         set_time();
  8.     }

  9.     ArduinoOTA.begin();
  10.     pinMode(second_Irq, INPUT_PULLUP);
  11.     attachInterrupt(digitalPinToInterrupt(second_Irq), every_second, RISING); // RISING: 上升沿触发
  12. }

  13. void loop()
  14. {
  15.     ArduinoOTA.handle_noirq(second_Irq); // Arduino OTA 监听
  16.     LED_disp.display();
  17. }
复制代码

这次编译下载还是需要串口的,下次就不用了。

再修改 platformio.ini,添加以下配置:(IP可以登录路由器做个绑定)

  1. upload_protocol = espota
  2. upload_port = 192.168.0.220
复制代码

最后编译下载,组装上去,只保留一个复位键,把 NTC 放在壳子外面。


上面板,大功告成!以后有时间在立创打个板子,就更好了。

(小声说:其实年初打了一版但74138控制位选的方案错了,原想把74138位选编进数码管H位那个用不到的小数点比特,能节省 ESP8266 3个IO,结果数码管点亮时序和延时冲突用不了……)


(才知道,不要直接Ctrl+V直接粘贴图片,应该用上传,不然帖子会超长,我改了好半天)


最终版:

谢谢大家观看,祝大家端午安康!







本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

打赏

参与人数 2家元 +50 收起 理由
闻太师 + 30 謝謝分享
w9988 + 20

查看全部打赏

发表于 2024-6-9 21:03:15 | 显示全部楼层
围观大佬,表示完全看不懂...

----------------------
回复 支持 反对

使用道具 举报

发表于 2024-6-9 21:52:52 | 显示全部楼层
大神的改造,基本上可以整个新的了
回复 支持 反对

使用道具 举报

发表于 2024-6-9 22:05:31 | 显示全部楼层
虽不明,但觉屌!
我之前在一个破产的超市里也是捡了两个玻璃面破掉的这种万年历,回来大卸八块,把外壳扔了,然后其中一个横款的把自己的婚纱照左上角空白处开孔后镶到相框背面,正面只露出了数码管部分,用了一个老剃须刀的9V充电器供电,挂在了客厅里,除了时间久了会有误差(半年大概3-5分钟左右)外,其他的完美,包括停电再上电,也不会掉原有的设定。
喇叭会响,但不会报时,只是白天整点的时候会放段音乐,然后可以通过观察上面一个LED灯的状态实现开和关这项功能,看了你第一张图,应该就是那个整点的功能,但看了拆机图,发现好像没焊对应的那个显示该状态的LED,所以你这个到底是直接没搞这个功能,还是说搞了但没装状态指示,你其实可以测试一下,因为这玩意原有的操作逻辑是按了MODE后,从第一个(对)数码管开始逐个往后跳,遇到功能选项(12/24小时切换、整点报时之类)时也是一样,然后再通过加减去改变数值,所以假如说调整完分钟后,还需要两次的空按才能跳回年份的话,那大概率最后这两下空按就是管的整点和闹钟。
不过你这折腾成8266接管的话,貌似这整点响不响的也没意义了
另外还有一块像你这个这种竖款的,但是发现那个有像千年虫那样的问题,虽然能设置20之后的年份,但下面自动换算的农历却与实际对不上,然后加之之前看论坛有大佬说这玩意5V供电也行,于是某次闲着没事,拿了个剪掉MICRUSB口的数据线,跳过上面的整流和变压部分直接怼了上去,导致上面的数码管全成了“日”,算是彻底废了
回复 支持 反对

使用道具 举报

发表于 2024-6-9 22:06:00 | 显示全部楼层
学习一下,谢谢啦
回复 支持 反对

使用道具 举报

 楼主| 发表于 2024-6-10 14:56:42 | 显示全部楼层
1325133 发表于 2024-6-9 22:05
虽不明,但觉屌!
我之前在一个破产的超市里也是捡了两个玻璃面破掉的这种万年历,回来大卸八块,把外壳扔 ...

可能是电压过高损坏了。我这个表测试原主控工作电压只有2.8V,供电从78L05出去之后经过了3个二极管,每次降低0.7V,才到主控。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2024-6-10 15:03:35 | 显示全部楼层

这种万年历,我见过3个,每个里面的数码管接法都不一样,算上参考例子里的就4个了。后面有时间的话打板还是有必要的,毕竟我那8266开发板整个用胶粘在里面了,打板还能把它拆出来做别的用。但量产通用性太低,而且能放主控的空间也不一样,只能具体的表具体分析了,自己改着玩儿
回复 支持 反对

使用道具 举报

发表于 2024-6-10 15:35:39 | 显示全部楼层
厉害  佩服!
回复 支持 反对

使用道具 举报

发表于 2024-6-10 16:52:07 | 显示全部楼层
逆向工程+全新开发
回复 支持 反对

使用道具 举报

发表于 2024-6-11 08:36:25 | 显示全部楼层
我也有一个电路板,面板丢了,正常耗电较大,时间不准确,编程学不会,加个RX8025T应该省事。
回复 支持 反对

使用道具 举报

发表于 2024-6-11 16:40:46 | 显示全部楼层
本帖最后由 kindzhon 于 2024-6-11 16:45 编辑

楼主可以试试TM1640B,https://item.szlcsc.com/6103283.html,一块就替换74系列,还能很方便的调节亮度,而且esp8266还不用总工作着,联网工作的电流有几十mA呢。
NTP校时也不用那么频繁,有PCF8563了,一天校时一次就足够了,省不少电呢,总电路应该不到10mA都够了。
回复 支持 反对

使用道具 举报

发表于 2024-6-11 16:58:37 来自手机浏览器 | 显示全部楼层
这是利用旧外壳,然后内部大换血,最重要的大脑也换掉了。厉害
回复 支持 反对

使用道具 举报

发表于 2024-6-11 19:41:00 来自手机浏览器 | 显示全部楼层
为啥不直接用esp32?
回复 支持 反对

使用道具 举报

 楼主| 发表于 2024-6-12 07:32:52 | 显示全部楼层
kindzhon 发表于 2024-6-11 16:40
楼主可以试试TM1640B,https://item.szlcsc.com/6103283.html,一块就替换74系列,还能很方便的调节亮度, ...

测试的时候,用过TM1650模块加光敏电阻,成功地实现了动态调节亮度,但最终如何用TM1650驱动原来的数码管没有设计出来,只能用最基础的方法了

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复 支持 反对

使用道具 举报

 楼主| 发表于 2024-6-12 07:36:17 | 显示全部楼层
sufe08 发表于 2024-6-11 19:41
为啥不直接用esp32?

测试的时候觉得ESP32性能过剩了,有些大材小用。用ESP8266刚刚好用完所有IO引脚。当然现有代码移植成ESP32也是比较方便的
回复 支持 反对

使用道具 举报

发表于 2024-6-12 10:32:07 | 显示全部楼层
很流弊,再加个天气预报就更完美了
回复 支持 反对

使用道具 举报

发表于 2024-6-12 15:11:23 来自手机浏览器 | 显示全部楼层
牛逼大发了…果断收藏了…手头有个板子我觉得离线版也挺好的
回复 支持 反对

使用道具 举报

发表于 2024-6-12 19:39:32 | 显示全部楼层
生命在于折腾!不过这技术功底还是杠杠的
话说用ESP8266来只做个LED数码管时钟,感觉有点大材小用,成本与成果不太成正比
回复 支持 反对

使用道具 举报

 楼主| 发表于 2024-6-13 08:14:14 | 显示全部楼层
南天音乐 发表于 2024-6-12 10:32
很流弊,再加个天气预报就更完美了

加上天气预报的话,就要考虑添加新的显示装置了,原来的表一共17个数码管,没法显示有效的天气预报
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

APP|手机版|小黑屋|关于我们|联系我们|法律条款|技术知识分享平台

闽公网安备35020502000485号

闽ICP备2021002735号-2

GMT+8, 2024-7-27 23:27 , Processed in 0.171600 second(s), 9 queries , Redis On.

Powered by Discuz!

© 2006-2023 smzj.net

快速回复 返回顶部 返回列表