20浏览
查看: 20|回复: 2

[项目] 【Arduino 动手做】使用 WS2812 圆环的模拟时钟

[复制链接]
虽然出于一些充分的理由,大多数时钟使用数字显示来显示时间,但显示指针的时钟仍然具有一些魅力。

使用指针的时钟通常称为模拟时钟,尽管不包括模拟组件。这个时钟甚至没有指针,它只通过使用 Neopixel 模块来显示它们指向的位置,这些模块呈圆形,正好有 60 个 WS2812 LED。事实上,它们分为四个部分,您必须将它们焊接在一起。

四个细分市场之一
在这种情况下,它使用发射器在名为 Mainflingen(法兰克福附近)的地方以 77、5 kHz 的频率分配的时间信号,该信号可以由廉价的 DCF77 接收器模块接收。虽然有很多库可以解码该信号,但我开发了自己的代码。关键是,您必须检测传输脉冲的开始时间和结束时间。这是由该脉冲调用的中断服务程序 (ISR) 完成的。当它由 RISING 信号触发时,触发的下一个原因必须设置为 FALLING,反之亦然。所以 ISR 改变了中断本身的原因。这只是真实 ISR 的简短摘录:

  1. void isr() {
  2.   long now = millis();
  3.   if (edge == START_OF_PULSE) {
  4.     risingTime = now;
  5.     long downTime = now - fallingTime;
  6.     // modify IRQ:
  7.     edge = END_OF_PULSE; // invert
  8.     attachInterrupt(IRQ, isr, edge);
  9.   }
  10.   else {
  11.     // falling:
  12.     fallingTime = now;
  13.     long upTime = now - risingTime;
  14.     // modify IRQ:
  15.     edge = START_OF_PULSE; // invert
  16.     attachInterrupt(IRQ, isr, edge);
  17.   }
  18. }
复制代码


要接收和解码时间信号,至少需要一分钟,大部分时间都超过这分钟。在 DCF77 标准战引入时,空气中没有太多的电磁噪声,但现在你有很多设备(合法的和非法的)会产生混乱,如果你收到清晰的信号,你会很幸运。
在我的项目中,当它仍在等待完全有效的传输时,钟面显示为蓝色,当检测到有效传输时,钟面显示为绿色。当检测到错误时,内部 clock 将继续运行,这将通过显示红色 clock face 来指示。
“秒针”由红色 LED 指示,“MINUTE”指针由绿色 LED 指示,“HOUR”指针由蓝色 LED 指示。为了更容易找到分钟,该 LED 将闪烁。

Arduino nano
该代码提供使用 UNO-R3(在本例中为 NANO)或 UNO-R4(在本例中为 R4 克隆,所有 IO 引脚都有额外的接头)微控制器和具有任一极性的 DCF77 模块。微控制器和天线之间应该有一定的最小距离,因为控制器本身会产生一些影响接收质量的噪声。

连接到 ARDUINO UNO R4 克隆的 DCF77 模块
必须承认,使用 R4 完全是矫枉过正。
RAM 和 ROM 使用情况:
ROM:R3:6192 字节 (19%),R4:42404 字节 (16%)
RAM:R3:639 字节 (31%),R4:4092 字节 (12%)
当将 Segments 安装到电路板上时,您可能希望电路板顶部的 LED 数字为零,对应于众所周知的时钟,其中 TWELVE 位于顶部位置。在下图中,白色显示的线条是水平线和垂直线。

因此,两段之间的连接必须旋转 3 度角才能实现此目的,如黑线所示。
我较旧的 clock projects (我较旧的时钟项目):
03.12.2021: 只是另一个模拟时钟
12.09.2023: R4-Wifi:在 LED 矩阵上显示时间
14.02.2025: Wordclock 以及为什么它们如此昂贵

【Arduino 动手做】使用 WS2812 圆环的模拟时钟图1

【Arduino 动手做】使用 WS2812 圆环的模拟时钟图2

【Arduino 动手做】使用 WS2812 圆环的模拟时钟图3

【Arduino 动手做】使用 WS2812 圆环的模拟时钟图4

【Arduino 动手做】使用 WS2812 圆环的模拟时钟图5

驴友花雕  中级技神
 楼主|

发表于 昨天 13:19

【Arduino 动手做】使用 WS2812 圆环的模拟时钟

项目代码

  1. /*
  2.   ARDUINO DCF77 clock v2025.6
  3.   No library used.
  4.   The clock face will remain blue while no
  5.   valid data are received. It will turn green
  6.   as soon as valid data are decoded.
  7.   If any parity mismatch is detected the
  8.   internal timekeeper continues to work,
  9.   indicated by showing a red clock face.
  10.   You can increase the results by turning
  11.   the antenna to the optimum angle.
  12.   If run by using a USB power bank please note
  13.   that it will not work will all power banks.
  14.   The LEDs:
  15.   red = second, green = minute, blue 0 hour.
  16.   INTERRUPT_PIN = 2 input for the DCF receiver
  17.   (C) Klaus Koch, klausjkoch@t-online.de
  18.   For modules like HK56 or some others
  19.   you need to define INVERTED
  20.   Check if your module needs INPUT_PULLUP!
  21.   When mounting keep a cetain distance
  22.   between antenna and ARDUINO!
  23. */
  24. // #define INVERTED
  25. #include <Adafruit_NeoPixel.h>
  26. /*
  27.   if you use Pin-11 you can use one row
  28.   of the 6-pin ICSP header. Pin-11 is
  29.   just in the middle between GND and Vcc.
  30. */
  31. const byte PIN = 11;
  32. const byte NUMPIXELS = 60;
  33. Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
  34. // set ROTATION to correct value
  35. const byte ROTATION = 45; // 0, 15, 30, 45
  36. #ifdef INVERTED
  37. #define START_OF_PULSE FALLING
  38. #define END_OF_PULSE RISING
  39. #else
  40. #define START_OF_PULSE RISING
  41. #define END_OF_PULSE FALLING
  42. #endif
  43. const char COMPARE[] = "0??????????????iiiii1mmmmMMMphhhhHHpttttTTwwwMMMMMjjjjJJJJp^";
  44. const byte INTERRUPT_PIN = 2;
  45. byte IRQ;
  46. byte bits[61];        // collect the bit stream
  47. byte second = 0;
  48. byte error_second = 0;
  49. // these constants used only for Serial monitor
  50. const byte A_1 = 16;  // Ankuendigung MEZ/MESZ
  51. const byte Z1 = 17;   // MEZ
  52. const byte Z2 = 18;   // MESZ Sommerzeit
  53. const byte A_2 = 19;  // Ankuendigung Schaltsekunde
  54. const byte S = 20;
  55. char outputString[] = "01:34 xyz 01.34.2089 ";
  56. // positions in string outputString[]:
  57. const byte H10 = 0;
  58. const byte H1 = 1;
  59. const byte m10 = 3;
  60. const byte m1 = 4;
  61. const byte W1 = 6;
  62. const byte W2 = 7;
  63. const byte D_10 = 10;
  64. const byte D_1 = 11;
  65. const byte M10 = 13;
  66. const byte M1 = 14;
  67. const byte Y10 = 18;
  68. const byte Y1 = 19;
  69. byte HH_temp = 1;
  70. byte MM_temp = 2;
  71. byte unsyncH, unsyncM;
  72. //---------------------------
  73. const long DARC   = 0x010001;
  74. const long FIVE   = 0x020200; // yellow
  75. const long SECOND = 0x100000; // red
  76. const long MINUTE = 0x001000; // green
  77. const byte HOUR   = 0x000010; // blue
  78. //---------------------------
  79. // ---------Hh:Mm Ww, Dd.Mm,20Jj---
  80. long nextTime;
  81. volatile boolean bit;
  82. boolean sync = false;
  83. // time variables if not sync
  84. long myMillis;
  85. boolean HMS_set = false;
  86. // volatile data also used inside ISR
  87. // start with waiting for RISING:
  88. // edge getting inverted each time
  89. volatile long risingTime, fallingTime;
  90. #ifdef __AVR__
  91. volatile byte edge = START_OF_PULSE; // UNO R3
  92. #else
  93. volatile PinStatus edge = START_OF_PULSE; // UNO R4
  94. #endif
  95. volatile boolean event = false;
  96. volatile boolean endOfMinute = false;
  97. void setup() {
  98.   long t = millis() + 3000;
  99.   boolean b = true;
  100.   pinMode(LED_BUILTIN, OUTPUT);
  101.   Serial.begin(9600);
  102. #ifndef __AVR__
  103.   // only for ARDUINO UNO R4
  104.   do {
  105.     if (millis() > t) b = false;
  106.     if (Serial) b = false;
  107.     // flash LED 3 times
  108.     digitalWrite(LED_BUILTIN, HIGH);
  109.     delay(500);
  110.     digitalWrite(LED_BUILTIN, LOW);
  111.     delay(500);
  112.   }
  113.   while (b);
  114. #endif
  115.   // ------------------
  116.   Serial.println("\n" __FILE__);
  117.   Serial.print(__DATE__);
  118.   Serial.print(", ");
  119.   Serial.println(__TIME__);
  120.   Serial.print("interrupt pin = ");
  121.   Serial.println(INTERRUPT_PIN);
  122.   IRQ = digitalPinToInterrupt(INTERRUPT_PIN);
  123.   Serial.print("IRQ number = ");
  124.   Serial.println(IRQ);
  125.   /*
  126.     used for REICHELT B20084A0
  127.   */
  128.   // pinMode(INTERRUPT_PIN, INPUT_PULLUP);
  129.   /*
  130.     any change at INTERRUPT_PIN will
  131.     call the ISR by toggling "edge"
  132.   */
  133.   strip.begin();
  134.   clockFace(HH_temp, MM_temp, second, 0);
  135.   attachInterrupt(IRQ, isr, edge);
  136. }
  137. void loop() {
  138.   if (event) {
  139.     // called at FALLING edge or endOfMinute
  140.     Serial.print(bit);
  141.     // backgroundcolor:
  142.     long bgc;
  143.     if (HMS_set == false) bgc = 0x000001; // blue
  144.     else if (sync) bgc = 0x000100; // green
  145.     //---------------------------------------
  146.     if (sync || (HMS_set == false) )
  147.       clockFace(HH_temp, MM_temp, second, bgc);
  148.     //---------------------------------------
  149.     if (second < sizeof bits) {
  150.       bits[second] = bit;
  151.       second++;
  152.     } else {
  153.       Serial.println("-error");
  154.       Serial.println(COMPARE);
  155.       sync = false;
  156.       second = 0;
  157.     }
  158.     if (endOfMinute) {
  159.       decodeTime();
  160.       endOfMinute = false;
  161.       second = 0;
  162.     }
  163.     event = false;
  164.   }
  165.   if (!sync && HMS_set && (millis() > myMillis) ) handleUnsync();
  166. }
  167. void clockFace(byte h, byte m, byte s, long bgc) {
  168.   strip.fill(bgc);
  169.   /*
  170.     the sequence of setPixelColor commands determinds
  171.     who will win when addressing the same LED.
  172.   */
  173.   long color;
  174.   for (int i = 0; i < 60; i = i + 5)
  175.     strip.setPixelColor(i, FIVE);
  176.   strip.setPixelColor(rotate(s), SECOND);
  177.   long mi;
  178.   if (second & 1) mi = MINUTE;
  179.   else mi = MINUTE / 2;
  180.   strip.setPixelColor(rotate(m), mi);
  181.   byte h1 = (h % 12) * 5 + m / 12;
  182.   strip.setPixelColor(rotate(h1), HOUR);
  183.   strip.show();
  184. }
  185. byte rotate(byte b) {
  186.   return (b + ROTATION) % 60;
  187. }
  188. // called at the end of a minute:
  189. void decodeTime() {
  190.   Serial.println("\nend of minute - decoding ...");
  191.   int error = bits[0];  // Bit 0 must be 0 !
  192.   // 1 - 19 reserved
  193.   if (!bits[20]) error += 2;  // Bit 20 must be 1
  194.   boolean p1 = parity(21, 28);
  195.   boolean p2 = parity(29, 35);
  196.   boolean p3 = parity(36, 58);
  197.   if (p1) error += 4;
  198.   if (p2) error += 8;
  199.   if (p3) error += 16;
  200.   // convert to printable date:
  201.   // minutes:
  202.   outputString[m1] = bin_to_int(21, 24);   // 1 2 4 8
  203.   outputString[m10] = bin_to_int(25, 27);  // 10 20 40
  204.   MM_temp = 10 * outputString[m10] + outputString[m1];
  205.   // 28 = P1
  206.   // hours:
  207.   outputString[H1] = bin_to_int(29, 32);   // 1 2 4 8
  208.   outputString[H10] = bin_to_int(33, 34);  // 10 20
  209.   HH_temp = 10 * outputString[H10] + outputString[H1];
  210.   // 35 = P2
  211.   // day of month:
  212.   outputString[D_1] = bin_to_int(36, 39);   // 1 2 4 8
  213.   outputString[D_10] = bin_to_int(40, 41);  // 10 20
  214.   // month:
  215.   outputString[M1] = bin_to_int(45, 48);
  216.   outputString[M10] = bits[49];
  217.   // year:
  218.   outputString[Y1] = bin_to_int(50, 53);
  219.   outputString[Y10] = bin_to_int(54, 57);
  220.   // plausibility tests:
  221.   // 01:34 67, 01.34.2089
  222.   // Hh:Mm Ww, Dd.Mm,20Jj---
  223.   if (invalid(H10, 23)) error += 0x020;   // HH
  224.   if (invalid(m10, 59)) error += 0x040;   // mm
  225.   if (invalid(D_10, 31)) error += 0x080;  // DD
  226.   if (invalid(M10, 12)) error += 0x100;   // MM
  227.   if (invalid(Y10, 99)) error += 0x200;   // YY
  228.   // convert numbers to ASCII:
  229.   for (byte i = 0; i < sizeof outputString - 1; i++) {
  230.     char *x = &outputString[i]; // was byte for R3
  231.     if (*x == constrain(*x, 0, 9)) *x = *x + '0';
  232.     if (*x == constrain(*x, 10, 31)) error += 0x400;
  233.   }
  234.   // 58 = P3
  235.   // 59 has to be missing
  236.   sync = error == 0;
  237.   if (sync) {
  238.     Serial.print("  A1  = ");
  239.     Serial.print(bits[A_1]);
  240.     Serial.print(", Z1  = ");
  241.     Serial.print(bits[Z1]);
  242.     Serial.print(", Z2  = ");
  243.     Serial.print(bits[Z2]);
  244.     Serial.print(", A2  = ");
  245.     Serial.print(bits[A_2]);
  246.     Serial.print(", S   = ");
  247.     Serial.println(bits[S]);
  248.     Serial.println(outputString);
  249.     // prepare for not sync:
  250.     HMS_set = true;
  251.     myMillis = millis();
  252.     //-----------------------------------------------------
  253.     clockFace(HH_temp, MM_temp, second, 0x000100); // green
  254.     //-----------------------------------------------------
  255.     // copy time for use if not sync:
  256.     unsyncH = HH_temp;
  257.     unsyncM = MM_temp;
  258.   } else {
  259.     // not sync:
  260.     Serial.print(" Error: ");
  261.     Serial.println(error, BIN);
  262.   }
  263.   Serial.println(COMPARE);
  264. }
  265. boolean invalid(byte i, byte max) {
  266.   return outputString[i] * 10 + outputString[i + 1] > max;
  267. }
  268. // converts BCD into byte
  269. byte bin_to_int(byte a, byte b) {
  270.   byte value = 0;
  271.   for (byte i = a, j = 0; i <= b; i++, j++)
  272.     if (bits[i]) bitSet(value, j);
  273.   return value;
  274. }
  275. // https://rn-wissen.de/wiki/index.php?title=DCF77-Decoder_als_Bascom-Library
  276. byte parity(byte a, byte b) {
  277.   byte p = 0;
  278.   for (byte i = a; i <= b; i++) p = p + bits[i];
  279.   return p & 1;
  280. }
  281. void handleUnsync() {
  282.   error_second++;
  283.   if (error_second >= 60) {
  284.     error_second = 0;
  285.     unsyncM++;
  286.     if (unsyncM >= 60) {
  287.       unsyncM = 0;
  288.       unsyncH++;
  289.       if (unsyncH >= 12) unsyncH = 0;
  290.     }
  291.   }
  292.   //---------------------------------------------------------
  293.   clockFace(unsyncH, unsyncM, error_second, 0x010000); // red
  294.   //---------------------------------------------------------
  295.   myMillis = myMillis + 1000;
  296. }
  297. // ***************************************************
  298. const int MIN_SHORT  =   70;
  299. const int MAX_SHORT  =  140;
  300. const int MINUTE_GAP = 1500;
  301. const int MIN_PAUSE  =  750;
  302. void isr() {
  303.   // isr - called when signal on Pin 2 changes
  304.   // detect cause of interrupt
  305.   static long risingTime, fallingTime;
  306.   long now = millis();
  307.   if (edge == START_OF_PULSE) {
  308.     risingTime = now;
  309.     digitalWrite(LED_BUILTIN, HIGH);
  310.     long downTime = now - fallingTime;
  311.     // modify IRQ:
  312.     edge = END_OF_PULSE; // invert
  313.     attachInterrupt(IRQ, isr, edge);
  314.     if (downTime < MIN_PAUSE) return;
  315.     endOfMinute = downTime > MINUTE_GAP;
  316.     if (endOfMinute) event = true;
  317.   }
  318.   else {
  319.     // falling:
  320.     fallingTime = now;
  321.     digitalWrite(LED_BUILTIN, LOW);
  322.     long upTime = now - risingTime;
  323.     // modify IRQ:
  324.     edge = START_OF_PULSE; // invert
  325.     attachInterrupt(IRQ, isr, edge);
  326.     if (upTime < MIN_SHORT) return;
  327.     bit = upTime > MAX_SHORT;
  328.     event = true;
  329.   }
  330. }
复制代码


回复

使用道具 举报

驴友花雕  中级技神
 楼主|

发表于 昨天 13:22

【Arduino 动手做】使用 WS2812 圆环的模拟时钟

【Arduino 动手做】使用 WS2812 圆环的模拟时钟
项目链接:https://www.hackster.io/Klausj/analog-clock-using-ws2812-8a7a87
项目作者:克劳斯
项目代码:https://www.hackster.io/code_files/669461/download

【Arduino 动手做】使用 WS2812 圆环的模拟时钟图1

回复

使用道具 举报

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

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4 备案 沪公网安备31011502402448

© 2013-2025 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail