ANNEX version 1.70.1 and some great example [Local Link Removed for Guests] have now helped me over the hurdle.
Now a simple ESP32 and a BASIC programme with very few additional components can imitate the DCF77 time signal transmitter and control various clocks.
There are 3 signal outputs:
- Pin 25: 77.5kHz sine time signal with 1Vpp, 100%/25% amplitude modulated
- Pin 26: 77.5kHz sine time signal with 1Vpp, 100%/12.5% amplitude modulated
- Pin 2: Binary time signal, no carrier
These can be coupled to a radio-based DCF clock using three simple interface variants
The ESP32 waveform generator module is used, which outputs a sine wave signal from the DACs purely based on hardware. Even the amplitude modulation can be done with this built-in Generator. However, it requires hardware that is not included in all ESP32 variants but is included in the classic variant.
This make it possible to setup a DCF77 time signal generator with just the ESP32 and no need for an external modulator stage etc.
To cut a long story short: works very well and BASIC-based and I can send any time and date with very little HW effort.
Sorry for the long post

Overview of the DCF77 Signal Generator Program
This program is designed to simulate a DCF77 time signal transmitter using a CLASSIC(!!) ESP32 microcontroller.
The DCF77 signal is a long-wave radio signal transmitted from Mainflingen, Germany, which is used for synchronizing clocks across Europe by providing accurate time and date information. This program allows users to create a similar signal locally, which can be useful for testing or educational purposes. It can even provide the time signal for DCF77-based clocks in a location where no DCF77 signal can otherwise be received .
ANNEX-Basic Interpreter
The program is written using the ANNEX32-Basic interpreter V1.70.1, specifically designed for ESP32 microcontrollers. ANNEX-Basic provides a user-friendly environment for developing and testing programs, offering features like easy syntax, built-in functions, and real-time debugging capabilities. This makes it particularly useful for stepwise development, maintenance, and usage of embedded systems projects like this one.
DCF77 Signaling
•Frequency and Modulation: The DCF77 signal operates at a frequency of 77.5 kHz. It uses amplitude modulation to encode time information. A binary “0” is represented by a 100 ms low pulse, while a binary “1” is represented by a 200 ms low pulse.
•Data Encoded: Each minute, the signal transmits 59 bits of data, including:
- Start bits and minute markers.
- Current minute, hour, day, month, and year in binary-coded decimal (BCD) format.
- Daylight saving time information.
- Parity bits for error checking.
Code: [Local Link Removed for Guests]
' #################################################################################
' D C F 7 7 _ S i g n a l _ G e n e r a t o r
'
' !!!! Works only on ESP32 CLASSIC with BASIC-Interpreter ANNEX32 V1.70.1 !!!!
' Download at https://cicciocb.com'
'
' Creates a DCF77 Time Signal Radio transmission at 77.5kHz signal
' containing the current NTP based date and time data for the current Time Zone
' Outputs a 77.5kHz carrier modulated 100%/25% at pin 25
' Outputs a 77.5kHz carrier modulated 100%/12.5% at pin 26
' Outputs the binary bit pattern at GPIO OUT_PIN
' AUTHOR: Peter.Neufeld@gmx.de
' #################################################################################
VERSION$ = "1.0 DB9JG 11/2024"
option.wlog 1
'option.wlog 0 'deaktivates WLOG output, but still some output at serial line
OUT_PIN = 2 'additional output of DCF77 bit pattern without 77.5kHz carrier
'ON = 1 'for normal output at OUT_PIN
ON = 0 ' for inverse output at OUT_PIN
PIN.MODE OUT_PIN,OUTPUT
TIME_OFFSET= 0 'adds an offset seconds to the transmitted time for tests
'TIME_OFFSET= 10*3600
DIM Signal(59) : sig = 0 : ret = 0 : sec = 0 : mm = 0
GOSUB SETUP_SINE_GENERATOR ' Sine DAC generator at pin 25,26 with 77.5kHz
'Send once per second the appropriate bit of the current 60 bits DCF77_bit_pattern
'The 59_bit_pattern is filled once per minute with the time + data of NEXT minute.
TIMER0 1000, send_appropriate_DCF77_Bit
WAIT
END
' #################################################################################
' #################################################################################
' >>>>>>>>>>>>>>>> SUBROUTINES <<<<<<<<<<<<<<
'----------------------------------------------------------------------------------
send_appropriate_DCF77_Bit:
'----------------------------------------------------------------------------------
'sends a short/long/no puls representing the current bit_to_send
' from the one_minute signal pattern in the array signal()
IF signal(20)=0 GOSUB Create_DCF77_Bit_PATTERN 'fetch signal pattern for next minute
sec=val(right$(time$,2))
SELECT CASE sec
CASE 0 to 58
GOSUB SET_SIGNAL_AMP_LOW 'reduce the signal amplitude
IF signal(sec) = 0 THEN
pause 100 '0 -> 100ms low puls
TX$="L"
ELSE
pause 200 '1 -> 200ms low puls
TX$="H"
ENDIF
IF (sec+1) mod 10 = 0 THEN TX$=TX$ +" "
CASE 59
TX$=""
GOSUB SET_SIGNAL_AMP_HIGH '59th second is completely high signal amplitude
GOSUB Create_DCF77_Bit_PATTERN 'fetch new signal pattern for the next minute
END SELECT
GOSUB SET_SIGNAL_AMP_HIGH 'HIGH signal amplitude for rest of the 1000ms period
PRINT TX$ ' show the transmitted bit of the signal pattern for verification
WLOG TX$;
RETURN
'----------------------------------------------------------------------------------
SET_SIGNAL_AMP_HIGH:
'----------------------------------------------------------------------------------
'set the two DAC's signal amplitudes to 100%
scale1 = 0 'full amplitude
scale2 = 0 'full amplitude
SET_PERI_REG_BITS SENS_SAR_DAC_CTRL2_REG, 0x3, scale1, 16 ' set SENS_DAC_SCALE1
SET_PERI_REG_BITS SENS_SAR_DAC_CTRL2_REG, 0x3, scale2, 18 ' set SENS_DAC_SCALE2
PIN(OUT_PIN)=ON 'pure binary output, no carrier
RETURN
'----------------------------------------------------------------------------------
SET_SIGNAL_AMP_LOW:
'----------------------------------------------------------------------------------
' reduce the two DAC's signal amplitudes
scale1 = 2 '1/4 amplitude
scale2 = 3 '1/8 amplitude
SET_PERI_REG_BITS SENS_SAR_DAC_CTRL2_REG, 0x3, scale1, 16 ' set SENS_DAC_SCALE1
SET_PERI_REG_BITS SENS_SAR_DAC_CTRL2_REG, 0x3, scale2, 18 ' set SENS_DAC_SCALE2
PIN(OUT_PIN)=1-ON 'pure binary output, no carrier
RETURN
'----------------------------------------------------------------------------------
Create_DCF77_Bit_PATTERN:
'----------------------------------------------------------------------------------
' Update the bit pattern array for next transmission period.
' Get the current date and time and increment it with 60 seconds
' DCF77_signal contains always the time information for the start
' of the NEXT future minute (thus maybe even the next hour or day)
' Increment even +120 as we start creating the pattern in 59th second of the minute,
' and the transmission of the bit pattern will need 1 full minute
u = dateunix(date$) + timeunix(time$)
u = u + 120 + TIME_OFFSET 'increment 120 seconds and 1h for wintertime
udate$ = unixdate$(u)
utime$ = unixtime$(u)
T$ = chr$(13) + chr$(13) + time$ + " " + date$
T$ = T$ + " --> " + left$(utime$,5) + " " + udate$ + " = DCF77_time in transmission."
Print T$
wlog T$
T$ = utime$
D$ = udate$
minute = VAL(MID$(T$, 4, 2))
hour = VAL(MID$(T$, 1, 2))
day = VAL(MID$(D$, 1, 2))
month = VAL(MID$(D$, 4, 2))
year = VAL(MID$(D$, 7, 4)) MOD 100 ' Only last two digits of the year
weekday = (VAL(MID$(D$, 1, 2)) + INT((month + 1) * 2.6) + year + INT(year / 4) + INT(year / 400) - INT(year / 100) - 1) MOD 7
' Bit assignment according to DCF77 protocol
Signal(0) = 0 ' Start bit
' Bits for various information (simplified to zero here)
Signal(1) = 0 ' Call bit for start of daylight saving time (CET -> CEST)
Signal(2) = 0 ' Call bit for end of daylight saving time (CEST -> CET)
' Bits for time synchronization (3-14), considered as reserve bits here
FOR i = 3 TO 14
Signal(i) = 0
NEXT i
' Bits for special functions
Signal(15) = 0 ' Call bit for alerting PTB staff
Signal(16) = 0 ' Announcement of switch between CET and CEST
' Time zone identification: Example set; adjust according to current time (CET/CEST)
Signal(17) = 0 ' Time zone identification CET (standard time)
Signal(18) = 1 ' Time zone identification CEST (daylight saving time)
Signal(19) = 0 ' Announcement of a leap second
Signal(20) = 1 ' Start bit for time information
' Minutes (21-27), Hours (29-34), Day (36-41),
EncodeBCD minute, 21, 27
EncodeBCD hour, 29, 34
EncodeBCD day, 36, 41
' Weekday (42-44), Month (45-49), Year (50-57)
EncodeBCD weekday, 42, 44
EncodeBCD month, 45, 49
EncodeBCD year, 50, 57
' Parity bits for Minutes (28), Hours (35), Date (58)
CalculateParity 28, 21, 27
CalculateParity 35, 29, 34
CalculateParity 58, 36, 57
Signal(59) = 0
' Output the signal pattern for verification
S$ = ""
FOR i = 0 TO UBOUND(Signal())-1
S$ = S$ +str$(Signal(i))
IF i MOD 10 = 9 then S$ = S$ + " "
NEXT i
print S$
WLOG S$
S$ = ""
RETURN
'----------------------------------------------------------------------------------
SUB EncodeBCD(value, startBit, endBit)
'----------------------------------------------------------------------------------
' Subroutine to encode dezimal values to BCD values into the signal array
' Bit values are 1,2,4,8,10,20,30,40 ; LSB first
value = convert.to_BCD(value)
FOR i = startBit TO endBit
Signal(i) = value MOD 2 '= the rightmost Bit
value = value \ 2 ' cut off the right most Bit
NEXT i
END SUB
'----------------------------------------------------------------------------------
SUB CalculateParity(Sig, startBit, endBit)
'----------------------------------------------------------------------------------
' Subroutine to calculate the parity bit for a range in the signal array
parity = 0
FOR i = startBit TO endBit - 1
parity = parity XOR Signal(i)
NEXT i
Signal(Sig) = parity
END SUB
'----------------------------------------------------------------------------
sub SET_PERI_REG_MASK(reg,mask)
'----------------------------------------------------------------------------
'functions used to write into registers
local r, v
r = bas.peek(reg)
v = 1 << mask
bas.poke reg, r or v
end sub
'----------------------------------------------------------------------------
sub CLEAR_PERI_REG_MASK(reg,mask)
'----------------------------------------------------------------------------
local r, v
r = bas.peek(reg)
v = 1 << mask
bas.poke reg, r and (not v)
end sub
'----------------------------------------------------------------------------
sub SET_PERI_REG_BITS(reg,bit_map,value,shift)
'----------------------------------------------------------------------------
local r, mask, v
r = bas.peek(reg)
mask = bit_map << shift
r = r and (not mask)
v = (value and bit_map) << shift
r = r or v
bas.poke reg, r
end sub
'----------------------------------------------------------------------------
SETUP_SINE_GENERATOR:
'----------------------------------------------------------------------------
'demo on how use the internal cosine DAC generator
' works only on the ESP32 classic
' for more details look at
' https://github.com/krzychb/dac-cosine
' Technical documentation available here (chapter 'Cosine Waveform Generator')
' https://www.espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf
RTC_FAST_CLK_FREQ_APPROX = 8500000
XTAL_DIV4 = 40000000/4
DR_REG_SENS_BASE = 0x3ff48800
DR_REG_RTCCNTL_BASE = 0x3ff48000
RTC_CNTL_CLK_CONF_REG = DR_REG_RTCCNTL_BASE + 0x70
SENS_SAR_DAC_CTRL1_REG = DR_REG_SENS_BASE + 0x98
SENS_SAR_DAC_CTRL2_REG = DR_REG_SENS_BASE + 0x9C
' set XTAL_DIV4 instead of RC FAST CLK
CLEAR_PERI_REG_MASK RTC_CNTL_CLK_CONF_REG, 29 ' RTC_CNTL_RTC_FAST_CLK_SEL
pin.dac 25,0 ' enable the DAC1
pin.dac 26,0 ' enable the DAC2
'dac_cosine_enable
'Enable tone generator common to both channels
SET_PERI_REG_MASK SENS_SAR_DAC_CTRL1_REG, 16 ' set bit SENS_SW_TONE_EN
'Enable / connect tone tone generator on / to this channel
SET_PERI_REG_MASK SENS_SAR_DAC_CTRL2_REG, 24 ' set bit SENS_DAC_CW_EN1_M
'Invert MSB, otherwise part of waveform will have inverted
SET_PERI_REG_BITS SENS_SAR_DAC_CTRL2_REG, 0x3, 2, 20 ' SENS_DAC_INV1_S (invert MSB)
' DAC Channel 2
SET_PERI_REG_MASK SENS_SAR_DAC_CTRL2_REG, 25 ' set bit SENS_DAC_CW_EN2_M
SET_PERI_REG_BITS SENS_SAR_DAC_CTRL2_REG, 0x3, 2, 22 ' SENS_DAC_INV2_S (invert MSB)
'dac_frequency_set
'* Set frequency of internal CW generator common to both DAC channels
'* clk_8m_div: 0b000 - 0b111
'* frequency_step: range 0x0001 - 0xFFFF
clk_8m_div = 0 ' can be from 0 to 7 (not working if XTAL_DIV4 is selected)
frequency_step = 508 ' 77.514kHz
SET_PERI_REG_BITS RTC_CNTL_CLK_CONF_REG, 0x7, clk_8m_div, 12 ' set RTC_CNTL_CK8M_DIV_SEL
SET_PERI_REG_BITS SENS_SAR_DAC_CTRL1_REG, 0xffff, frequency_step, 0 ' set frequency_step
'frequency = RTC_FAST_CLK_FREQ_APPROX / (1 + clk_8m_div) * frequency_step / 65536
frequency = XTAL_DIV4 * frequency_step / 65536
T$ = "The output frequency at pin 25 and 26 is "+ str$(frequency) + " Hz"
PRINT T$
WLOG T$
'dac_scale_set
' * Scale output of a DAC channel using two bit pattern:
' * - 00: no scale
' * - 01: scale to 1/2
' * - 10: scale to 1/4
' * - 11: scale to 1/8
scale1 = 0
scale2 = 0
SET_PERI_REG_BITS SENS_SAR_DAC_CTRL2_REG, 0x3, scale1, 16 ' set SENS_DAC_SCALE1
SET_PERI_REG_BITS SENS_SAR_DAC_CTRL2_REG, 0x3, scale2, 18 ' set SENS_DAC_SCALE2
'dac_offset_set
' * Offset output of a DAC channel
' * Range 0x00 - 0xFF
offset1 = 0
offset2 = 0
SET_PERI_REG_BITS SENS_SAR_DAC_CTRL2_REG, 0xff, offset1, 0 ' set SENS_DAC_DC1
SET_PERI_REG_BITS SENS_SAR_DAC_CTRL2_REG, 0xff, offset2, 8 ' set SENS_DAC_DC2
'dac_invert_set
' * Invert output pattern of a DAC channel
' * - 00: does not invert any bits,
' * - 01: inverts all bits,
' * - 10: inverts MSB,
' * - 11: inverts all bits except for MSB
invert1 = 2
invert2 = 3
SET_PERI_REG_BITS SENS_SAR_DAC_CTRL2_REG, 0x3, invert1, 20 ' set SENS_DAC_INV1
SET_PERI_REG_BITS SENS_SAR_DAC_CTRL2_REG, 0x3, invert2, 22 ' set SENS_DAC_INV2
RETURN
1. Initialization and Setup
• Version and Configuration: The program starts with version information and configuration settings (e.g., `OUT_PIN` for output).
• Pin Mode Setup: Configures GPIO pins for output.
2. Sine Wave Generator Setup
• DAC Configuration: The `SETUP_SINE_GENERATOR` subroutine configures the ESP32’s digital-to-analog converters (DACs) to generate a continuous sine wave at 77.5 kHz on pins 25 and 26. This involves setting up internal registers to control frequency and waveform characteristics.
3. Signal Transmission Logic
This part of the program is responsible for continuously sending the DCF77 signal, bit by bit, every second. The logic ensures that each bit of the time signal is transmitted at the correct time and with the appropriate modulation.
• Timer Initialization: The program uses a timer (`TIMER0`) set to trigger every 1000 milliseconds (1 second). This ensures that a new bit is sent once per second, aligning with the DCF77 protocol’s timing requirements.
• Subroutine `send_appropriate_DCF77_Bit`:
Purpose: This subroutine is called by the timer every second to send the current bit from the DCF77 signal pattern.
Logic:
• Determine Current Second: It extracts the current second from the system time using `sec = val(right$(time$,2))`.
• Select Case Structure:
Seconds 0 to 58: For these seconds, it sends either a short (100 ms) or long (200 ms) low pulse based on whether the current bit (`signal(sec)`) is 0 or 1. The amplitude of the signal is reduced during these pulses using `SET_SIGNAL_AMP_LOW`.
Second 59: During this second, no data is transmitted as it prepares for the next minute’s pattern. The amplitude is set high using `SET_SIGNAL_AMP_HIGH`, and it calls `Create_DCF77_Bit_PATTERN` to generate a new bit pattern for the upcoming minute.
• Amplitude Reset: After sending each bit, it resets the amplitude to high for the remainder of the second.
• Amplitude Modulation: The subroutine uses two helper subroutines to adjust signal amplitude:
• `SET_SIGNAL_AMP_LOW`: Reduces DAC output levels to create low pulses representing binary data.
• `SET_SIGNAL_AMP_HIGH`: Restores DAC output levels to full amplitude for normal carrier transmission.
• Output and Verification: The transmitted bit (`TX$`) is printed and logged for verification purposes. This helps ensure that the correct sequence is being sent.
4. Bit Pattern Creation[/b]
• Time Calculation: The `Create_DCF77_Bit_PATTERN` subroutine calculates the time for the next minute using NTP synchronization and updates an array (`Signal`) with the corresponding bit pattern.
• BCD Encoding: Subroutines like `EncodeBCD` convert decimal time values into binary-coded decimal format for transmission.
5. Amplitude Control
• Amplitude Adjustment: Subroutines `SET_SIGNAL_AMP_HIGH` and `SET_SIGNAL_AMP_LOW` adjust the DAC output levels to modulate the signal amplitude as required by DCF77 specifications.
6. Utility Functions
• Register Manipulation: Functions like `SET_PERI_REG_BITS` handle low-level register manipulation to control hardware features of the ESP32.
• Parity Calculation: The `CalculateParity` subroutine ensures data integrity by calculating parity bits for different sections of the transmitted data.
Transmitting the Signal
To transmit the generated DCF77 signal to a clock, you can use a simple wire loop connected to the DAC output in series with a 100-ohm resistor. Placing this setup near a DCF77-based clock will allow the clock to receive the simulated time signal. This method effectively creates a local electromagnetic field that mimics the DCF77 broadcast, enabling nearby clocks to synchronize.
An alternative method could be to couple the signal via a resistive voltage divider to the hot end of the RX antenna coil, not forgetting to connect the GNDs of clock and ESP32. This will dampen the antenna and suppress the original DCF77-signal, to avoid signal interference.
If the clock uses a binary signal connection to a separate DCF77-RX board, it can be a good idea to replace the RX board by the ESP32 and use the carrierless output pin (OUT_PIN) of the ESP32.
You should also always bear in mind that the self-generated signal at the clock must be stronger than the actual DCF77 signal that may still be present. Interference between the two can be avoided by the described voltage divider method or for test purpose by simply pointing the ferrite rod aerial upwards, thus weakening the reception of the original signal.
A note: Be patient! Synchronization can take up to 5 minutes or even more, depending on the type of clock. Some clocks even only synchronise when they are switched on and then only once a day!
For extended range, you might consider amplifying the 77.5kHz signal using an operational amplifier (op-amp) and driving a larger loop antenna. However, be cautious, as amplifying and broadcasting such signals may violate local regulations regarding radio transmissions.
Conclusion
The program effectively simulates a DCF77 transmitter using an ESP32, providing a practical tool for testing clock synchronization systems or learning about radio time signals. Using ANNEX-Basic makes development straightforward due to its ease of use and debugging capabilities, allowing for efficient stepwise development and maintenance. Each part of the program plays a crucial role in ensuring accurate and reliable signal generation according to DCF77 protocols.
(This text was written with the moderate support of an AI system, so please be gracious

PeterN