【Arduino 动手做】使用 WS2812 圆环的模拟时钟
虽然出于一些充分的理由,大多数时钟使用数字显示来显示时间,但显示指针的时钟仍然具有一些魅力。使用指针的时钟通常称为模拟时钟,尽管不包括模拟组件。这个时钟甚至没有指针,它只通过使用 Neopixel 模块来显示它们指向的位置,这些模块呈圆形,正好有 60 个 WS2812 LED。事实上,它们分为四个部分,您必须将它们焊接在一起。
四个细分市场之一
在这种情况下,它使用发射器在名为 Mainflingen(法兰克福附近)的地方以 77、5 kHz 的频率分配的时间信号,该信号可以由廉价的 DCF77 接收器模块接收。虽然有很多库可以解码该信号,但我开发了自己的代码。关键是,您必须检测传输脉冲的开始时间和结束时间。这是由该脉冲调用的中断服务程序 (ISR) 完成的。当它由 RISING 信号触发时,触发的下一个原因必须设置为 FALLING,反之亦然。所以 ISR 改变了中断本身的原因。这只是真实 ISR 的简短摘录:
void isr() {
long now = millis();
if (edge == START_OF_PULSE) {
risingTime = now;
long downTime = now - fallingTime;
// modify IRQ:
edge = END_OF_PULSE; // invert
attachInterrupt(IRQ, isr, edge);
}
else {
// falling:
fallingTime = now;
long upTime = now - risingTime;
// modify IRQ:
edge = START_OF_PULSE; // invert
attachInterrupt(IRQ, isr, edge);
}
}
要接收和解码时间信号,至少需要一分钟,大部分时间都超过这分钟。在 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 圆环的模拟时钟
项目代码/*
ARDUINO DCF77 clock v2025.6
No library used.
The clock face will remain blue while no
valid data are received. It will turn green
as soon as valid data are decoded.
If any parity mismatch is detected the
internal timekeeper continues to work,
indicated by showing a red clock face.
You can increase the results by turning
the antenna to the optimum angle.
If run by using a USB power bank please note
that it will not work will all power banks.
The LEDs:
red = second, green = minute, blue 0 hour.
INTERRUPT_PIN = 2 input for the DCF receiver
(C) Klaus Koch, klausjkoch@t-online.de
For modules like HK56 or some others
you need to define INVERTED
Check if your module needs INPUT_PULLUP!
When mounting keep a cetain distance
between antenna and ARDUINO!
*/
// #define INVERTED
#include <Adafruit_NeoPixel.h>
/*
if you use Pin-11 you can use one row
of the 6-pin ICSP header. Pin-11 is
just in the middle between GND and Vcc.
*/
const byte PIN = 11;
const byte NUMPIXELS = 60;
Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
// set ROTATION to correct value
const byte ROTATION = 45; // 0, 15, 30, 45
#ifdef INVERTED
#define START_OF_PULSE FALLING
#define END_OF_PULSE RISING
#else
#define START_OF_PULSE RISING
#define END_OF_PULSE FALLING
#endif
const char COMPARE[] = "0??????????????iiiii1mmmmMMMphhhhHHpttttTTwwwMMMMMjjjjJJJJp^";
const byte INTERRUPT_PIN = 2;
byte IRQ;
byte bits; // collect the bit stream
byte second = 0;
byte error_second = 0;
// these constants used only for Serial monitor
const byte A_1 = 16;// Ankuendigung MEZ/MESZ
const byte Z1 = 17; // MEZ
const byte Z2 = 18; // MESZ Sommerzeit
const byte A_2 = 19;// Ankuendigung Schaltsekunde
const byte S = 20;
char outputString[] = "01:34 xyz 01.34.2089 ";
// positions in string outputString[]:
const byte H10 = 0;
const byte H1 = 1;
const byte m10 = 3;
const byte m1 = 4;
const byte W1 = 6;
const byte W2 = 7;
const byte D_10 = 10;
const byte D_1 = 11;
const byte M10 = 13;
const byte M1 = 14;
const byte Y10 = 18;
const byte Y1 = 19;
byte HH_temp = 1;
byte MM_temp = 2;
byte unsyncH, unsyncM;
//---------------------------
const long DARC = 0x010001;
const long FIVE = 0x020200; // yellow
const long SECOND = 0x100000; // red
const long MINUTE = 0x001000; // green
const byte HOUR = 0x000010; // blue
//---------------------------
// ---------Hh:Mm Ww, Dd.Mm,20Jj---
long nextTime;
volatile boolean bit;
boolean sync = false;
// time variables if not sync
long myMillis;
boolean HMS_set = false;
// volatile data also used inside ISR
// start with waiting for RISING:
// edge getting inverted each time
volatile long risingTime, fallingTime;
#ifdef __AVR__
volatile byte edge = START_OF_PULSE; // UNO R3
#else
volatile PinStatus edge = START_OF_PULSE; // UNO R4
#endif
volatile boolean event = false;
volatile boolean endOfMinute = false;
void setup() {
long t = millis() + 3000;
boolean b = true;
pinMode(LED_BUILTIN, OUTPUT);
Serial.begin(9600);
#ifndef __AVR__
// only for ARDUINO UNO R4
do {
if (millis() > t) b = false;
if (Serial) b = false;
// flash LED 3 times
digitalWrite(LED_BUILTIN, HIGH);
delay(500);
digitalWrite(LED_BUILTIN, LOW);
delay(500);
}
while (b);
#endif
// ------------------
Serial.println("\n" __FILE__);
Serial.print(__DATE__);
Serial.print(", ");
Serial.println(__TIME__);
Serial.print("interrupt pin = ");
Serial.println(INTERRUPT_PIN);
IRQ = digitalPinToInterrupt(INTERRUPT_PIN);
Serial.print("IRQ number = ");
Serial.println(IRQ);
/*
used for REICHELT B20084A0
*/
// pinMode(INTERRUPT_PIN, INPUT_PULLUP);
/*
any change at INTERRUPT_PIN will
call the ISR by toggling "edge"
*/
strip.begin();
clockFace(HH_temp, MM_temp, second, 0);
attachInterrupt(IRQ, isr, edge);
}
void loop() {
if (event) {
// called at FALLING edge or endOfMinute
Serial.print(bit);
// backgroundcolor:
long bgc;
if (HMS_set == false) bgc = 0x000001; // blue
else if (sync) bgc = 0x000100; // green
//---------------------------------------
if (sync || (HMS_set == false) )
clockFace(HH_temp, MM_temp, second, bgc);
//---------------------------------------
if (second < sizeof bits) {
bits = bit;
second++;
} else {
Serial.println("-error");
Serial.println(COMPARE);
sync = false;
second = 0;
}
if (endOfMinute) {
decodeTime();
endOfMinute = false;
second = 0;
}
event = false;
}
if (!sync && HMS_set && (millis() > myMillis) ) handleUnsync();
}
void clockFace(byte h, byte m, byte s, long bgc) {
strip.fill(bgc);
/*
the sequence of setPixelColor commands determinds
who will win when addressing the same LED.
*/
long color;
for (int i = 0; i < 60; i = i + 5)
strip.setPixelColor(i, FIVE);
strip.setPixelColor(rotate(s), SECOND);
long mi;
if (second & 1) mi = MINUTE;
else mi = MINUTE / 2;
strip.setPixelColor(rotate(m), mi);
byte h1 = (h % 12) * 5 + m / 12;
strip.setPixelColor(rotate(h1), HOUR);
strip.show();
}
byte rotate(byte b) {
return (b + ROTATION) % 60;
}
// called at the end of a minute:
void decodeTime() {
Serial.println("\nend of minute - decoding ...");
int error = bits;// Bit 0 must be 0 !
// 1 - 19 reserved
if (!bits) error += 2;// Bit 20 must be 1
boolean p1 = parity(21, 28);
boolean p2 = parity(29, 35);
boolean p3 = parity(36, 58);
if (p1) error += 4;
if (p2) error += 8;
if (p3) error += 16;
// convert to printable date:
// minutes:
outputString = bin_to_int(21, 24); // 1 2 4 8
outputString = bin_to_int(25, 27);// 10 20 40
MM_temp = 10 * outputString + outputString;
// 28 = P1
// hours:
outputString = bin_to_int(29, 32); // 1 2 4 8
outputString = bin_to_int(33, 34);// 10 20
HH_temp = 10 * outputString + outputString;
// 35 = P2
// day of month:
outputString = bin_to_int(36, 39); // 1 2 4 8
outputString = bin_to_int(40, 41);// 10 20
// month:
outputString = bin_to_int(45, 48);
outputString = bits;
// year:
outputString = bin_to_int(50, 53);
outputString = bin_to_int(54, 57);
// plausibility tests:
// 01:34 67, 01.34.2089
// Hh:Mm Ww, Dd.Mm,20Jj---
if (invalid(H10, 23)) error += 0x020; // HH
if (invalid(m10, 59)) error += 0x040; // mm
if (invalid(D_10, 31)) error += 0x080;// DD
if (invalid(M10, 12)) error += 0x100; // MM
if (invalid(Y10, 99)) error += 0x200; // YY
// convert numbers to ASCII:
for (byte i = 0; i < sizeof outputString - 1; i++) {
char *x = &outputString; // was byte for R3
if (*x == constrain(*x, 0, 9)) *x = *x + '0';
if (*x == constrain(*x, 10, 31)) error += 0x400;
}
// 58 = P3
// 59 has to be missing
sync = error == 0;
if (sync) {
Serial.print("A1= ");
Serial.print(bits);
Serial.print(", Z1= ");
Serial.print(bits);
Serial.print(", Z2= ");
Serial.print(bits);
Serial.print(", A2= ");
Serial.print(bits);
Serial.print(", S = ");
Serial.println(bits);
Serial.println(outputString);
// prepare for not sync:
HMS_set = true;
myMillis = millis();
//-----------------------------------------------------
clockFace(HH_temp, MM_temp, second, 0x000100); // green
//-----------------------------------------------------
// copy time for use if not sync:
unsyncH = HH_temp;
unsyncM = MM_temp;
} else {
// not sync:
Serial.print(" Error: ");
Serial.println(error, BIN);
}
Serial.println(COMPARE);
}
boolean invalid(byte i, byte max) {
return outputString * 10 + outputString > max;
}
// converts BCD into byte
byte bin_to_int(byte a, byte b) {
byte value = 0;
for (byte i = a, j = 0; i <= b; i++, j++)
if (bits) bitSet(value, j);
return value;
}
// https://rn-wissen.de/wiki/index.php?title=DCF77-Decoder_als_Bascom-Library
byte parity(byte a, byte b) {
byte p = 0;
for (byte i = a; i <= b; i++) p = p + bits;
return p & 1;
}
void handleUnsync() {
error_second++;
if (error_second >= 60) {
error_second = 0;
unsyncM++;
if (unsyncM >= 60) {
unsyncM = 0;
unsyncH++;
if (unsyncH >= 12) unsyncH = 0;
}
}
//---------------------------------------------------------
clockFace(unsyncH, unsyncM, error_second, 0x010000); // red
//---------------------------------------------------------
myMillis = myMillis + 1000;
}
// ***************************************************
const int MIN_SHORT= 70;
const int MAX_SHORT=140;
const int MINUTE_GAP = 1500;
const int MIN_PAUSE=750;
void isr() {
// isr - called when signal on Pin 2 changes
// detect cause of interrupt
static long risingTime, fallingTime;
long now = millis();
if (edge == START_OF_PULSE) {
risingTime = now;
digitalWrite(LED_BUILTIN, HIGH);
long downTime = now - fallingTime;
// modify IRQ:
edge = END_OF_PULSE; // invert
attachInterrupt(IRQ, isr, edge);
if (downTime < MIN_PAUSE) return;
endOfMinute = downTime > MINUTE_GAP;
if (endOfMinute) event = true;
}
else {
// falling:
fallingTime = now;
digitalWrite(LED_BUILTIN, LOW);
long upTime = now - risingTime;
// modify IRQ:
edge = START_OF_PULSE; // invert
attachInterrupt(IRQ, isr, edge);
if (upTime < MIN_SHORT) return;
bit = upTime > MAX_SHORT;
event = true;
}
}
【Arduino 动手做】使用 WS2812 圆环的模拟时钟
【Arduino 动手做】使用 WS2812 圆环的模拟时钟项目链接:https://www.hackster.io/Klausj/analog-clock-using-ws2812-8a7a87
项目作者:克劳斯
项目代码:https://www.hackster.io/code_files/669461/download
页:
[1]