Initial commit

This commit is contained in:
Julian Metzler 2022-09-10 18:31:24 +02:00
commit 161de6dd12
12 changed files with 1322 additions and 0 deletions

29
.gitignore vendored Normal file
View File

@ -0,0 +1,29 @@
# Created by https://www.gitignore.io/api/atmelstudio
# Edit at https://www.gitignore.io/?templates=atmelstudio
### AtmelStudio ###
## Ignore Atmel Studio temporary files and build results
# http://www.atmel.com/microsite/atmel_studio6/
# Atmel Studio is powered by an older version of Visual Studio,
# so most of the project and solution files are the same as VS files,
# only prefixed by an `at`.
#Build Directories
[Dd]ebug/
[Rr]elease/
.vs/
#Build Results
*.o
*.d
*.eep
*.elf
*.hex
*.map
*.srec
#User Specific Files
*.atsuo
# End of https://www.gitignore.io/api/atmelstudio

22
Uhrensteuerung.atsln Normal file
View File

@ -0,0 +1,22 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Atmel Studio Solution File, Format Version 11.00
VisualStudioVersion = 14.0.23107.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{E66E83B9-2572-4076-B26E-6BE79FF3018A}") = "Uhrensteuerung", "Uhrensteuerung.cppproj", "{DCE6C7E3-EE26-4D79-826B-08594B9AD897}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|AVR = Debug|AVR
Release|AVR = Release|AVR
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{DCE6C7E3-EE26-4D79-826B-08594B9AD897}.Debug|AVR.ActiveCfg = Debug|AVR
{DCE6C7E3-EE26-4D79-826B-08594B9AD897}.Debug|AVR.Build.0 = Debug|AVR
{DCE6C7E3-EE26-4D79-826B-08594B9AD897}.Release|AVR.ActiveCfg = Release|AVR
{DCE6C7E3-EE26-4D79-826B-08594B9AD897}.Release|AVR.Build.0 = Release|AVR
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,86 @@
<?xml version="1.0" encoding="utf-8"?>
<Store xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="AtmelPackComponentManagement">
<ProjectComponents>
<ProjectComponent z:Id="i1" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/">
<CApiVersion></CApiVersion>
<CBundle></CBundle>
<CClass>Device</CClass>
<CGroup>Startup</CGroup>
<CSub></CSub>
<CVariant></CVariant>
<CVendor>Atmel</CVendor>
<CVersion>1.2.0</CVersion>
<DefaultRepoPath>C:/Program Files (x86)\Atmel\Studio\7.0\Packs</DefaultRepoPath>
<DependentComponents xmlns:d4p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays" />
<Description></Description>
<Files xmlns:d4p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<d4p1:anyType i:type="FileInfo">
<AbsolutePath>C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.209\include</AbsolutePath>
<Attribute></Attribute>
<Category>include</Category>
<Condition>C</Condition>
<FileContentHash i:nil="true" />
<FileVersion></FileVersion>
<Name>include</Name>
<SelectString></SelectString>
<SourcePath></SourcePath>
</d4p1:anyType>
<d4p1:anyType i:type="FileInfo">
<AbsolutePath>C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.209\include\avr\iom1284.h</AbsolutePath>
<Attribute></Attribute>
<Category>header</Category>
<Condition>C</Condition>
<FileContentHash>u35tFhJl9qSwWbpsy/24AA==</FileContentHash>
<FileVersion></FileVersion>
<Name>include/avr/iom1284.h</Name>
<SelectString></SelectString>
<SourcePath></SourcePath>
</d4p1:anyType>
<d4p1:anyType i:type="FileInfo">
<AbsolutePath>C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.209\templates\main.c</AbsolutePath>
<Attribute>template</Attribute>
<Category>source</Category>
<Condition>C Exe</Condition>
<FileContentHash>GD1k8YYhulqRs6FD1B2Hog==</FileContentHash>
<FileVersion></FileVersion>
<Name>templates/main.c</Name>
<SelectString>Main file (.c)</SelectString>
<SourcePath></SourcePath>
</d4p1:anyType>
<d4p1:anyType i:type="FileInfo">
<AbsolutePath>C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.209\templates\main.cpp</AbsolutePath>
<Attribute>template</Attribute>
<Category>source</Category>
<Condition>C Exe</Condition>
<FileContentHash>YXFphlh0CtZJU+ebktABgQ==</FileContentHash>
<FileVersion></FileVersion>
<Name>templates/main.cpp</Name>
<SelectString>Main file (.cpp)</SelectString>
<SourcePath></SourcePath>
</d4p1:anyType>
<d4p1:anyType i:type="FileInfo">
<AbsolutePath>C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.209\gcc\dev\atmega1284</AbsolutePath>
<Attribute></Attribute>
<Category>libraryPrefix</Category>
<Condition>GCC</Condition>
<FileContentHash i:nil="true" />
<FileVersion></FileVersion>
<Name>gcc/dev/atmega1284</Name>
<SelectString></SelectString>
<SourcePath></SourcePath>
</d4p1:anyType>
</Files>
<PackName>ATmega_DFP</PackName>
<PackPath>C:/Program Files (x86)/Atmel/Studio/7.0/Packs/atmel/ATmega_DFP/1.2.209/Atmel.ATmega_DFP.pdsc</PackPath>
<PackVersion>1.2.209</PackVersion>
<PresentInProject>true</PresentInProject>
<ReferenceConditionId>ATmega1284</ReferenceConditionId>
<RteComponents xmlns:d4p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<d4p1:string></d4p1:string>
</RteComponents>
<Status>Resolved</Status>
<VersionMode>Fixed</VersionMode>
<IsComponentInAtProject>true</IsComponentInAtProject>
</ProjectComponent>
</ProjectComponents>
</Store>

200
Uhrensteuerung.cppproj Normal file
View File

@ -0,0 +1,200 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="14.0">
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
<ProjectVersion>7.0</ProjectVersion>
<ToolchainName>com.Atmel.AVRGCC8.CPP</ToolchainName>
<ProjectGuid>dce6c7e3-ee26-4d79-826b-08594b9ad897</ProjectGuid>
<avrdevice>ATmega1284</avrdevice>
<avrdeviceseries>none</avrdeviceseries>
<OutputType>Executable</OutputType>
<Language>CPP</Language>
<OutputFileName>$(MSBuildProjectName)</OutputFileName>
<OutputFileExtension>.elf</OutputFileExtension>
<OutputDirectory>$(MSBuildProjectDirectory)\$(Configuration)</OutputDirectory>
<AssemblyName>Uhrensteuerung</AssemblyName>
<Name>Uhrensteuerung</Name>
<RootNamespace>Uhrensteuerung</RootNamespace>
<ToolchainFlavour>Native</ToolchainFlavour>
<KeepTimersRunning>true</KeepTimersRunning>
<OverrideVtor>false</OverrideVtor>
<CacheFlash>true</CacheFlash>
<ProgFlashFromRam>true</ProgFlashFromRam>
<RamSnippetAddress>0x20000000</RamSnippetAddress>
<UncachedRange />
<preserveEEPROM>true</preserveEEPROM>
<OverrideVtorValue>exception_table</OverrideVtorValue>
<BootSegment>2</BootSegment>
<eraseonlaunchrule>0</eraseonlaunchrule>
<AsfFrameworkConfig>
<framework-data xmlns="">
<options />
<configurations />
<files />
<documentation help="" />
<offline-documentation help="" />
<dependencies>
<content-extension eid="atmel.asf" uuidref="Atmel.ASF" version="3.34.1" />
</dependencies>
</framework-data>
</AsfFrameworkConfig>
<avrtool>com.atmel.avrdbg.tool.atmelice</avrtool>
<avrtoolserialnumber>J42700051414</avrtoolserialnumber>
<avrdeviceexpectedsignature>0x1E9706</avrdeviceexpectedsignature>
<com_atmel_avrdbg_tool_atmelice>
<ToolOptions>
<InterfaceProperties>
<JtagDbgClock>200000</JtagDbgClock>
</InterfaceProperties>
<InterfaceName>JTAG</InterfaceName>
</ToolOptions>
<ToolType>com.atmel.avrdbg.tool.atmelice</ToolType>
<ToolNumber>J42700051414</ToolNumber>
<ToolName>Atmel-ICE</ToolName>
</com_atmel_avrdbg_tool_atmelice>
<avrtoolinterface>JTAG</avrtoolinterface>
<avrtoolinterfaceclock>200000</avrtoolinterfaceclock>
<ResetRule>0</ResetRule>
<EraseKey />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<ToolchainSettings>
<AvrGccCpp>
<avrgcc.common.Device>-mmcu=atmega1284 -B "%24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\gcc\dev\atmega1284"</avrgcc.common.Device>
<avrgcc.common.optimization.RelaxBranches>True</avrgcc.common.optimization.RelaxBranches>
<avrgcc.common.outputfiles.hex>True</avrgcc.common.outputfiles.hex>
<avrgcc.common.outputfiles.lss>True</avrgcc.common.outputfiles.lss>
<avrgcc.common.outputfiles.eep>True</avrgcc.common.outputfiles.eep>
<avrgcc.common.outputfiles.srec>True</avrgcc.common.outputfiles.srec>
<avrgcc.common.outputfiles.usersignatures>False</avrgcc.common.outputfiles.usersignatures>
<avrgcc.compiler.general.ChangeDefaultCharTypeUnsigned>True</avrgcc.compiler.general.ChangeDefaultCharTypeUnsigned>
<avrgcc.compiler.general.ChangeDefaultBitFieldUnsigned>True</avrgcc.compiler.general.ChangeDefaultBitFieldUnsigned>
<avrgcc.compiler.symbols.DefSymbols>
<ListValues>
<Value>NDEBUG</Value>
<Value>F_CPU=16000000UL</Value>
</ListValues>
</avrgcc.compiler.symbols.DefSymbols>
<avrgcc.compiler.directories.IncludePaths>
<ListValues>
<Value>%24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\include</Value>
</ListValues>
</avrgcc.compiler.directories.IncludePaths>
<avrgcc.compiler.optimization.level>Optimize for size (-Os)</avrgcc.compiler.optimization.level>
<avrgcc.compiler.optimization.PackStructureMembers>True</avrgcc.compiler.optimization.PackStructureMembers>
<avrgcc.compiler.optimization.AllocateBytesNeededForEnum>True</avrgcc.compiler.optimization.AllocateBytesNeededForEnum>
<avrgcc.compiler.warnings.AllWarnings>True</avrgcc.compiler.warnings.AllWarnings>
<avrgcc.compiler.miscellaneous.OtherFlags>-std=c++11</avrgcc.compiler.miscellaneous.OtherFlags>
<avrgcccpp.compiler.general.ChangeDefaultCharTypeUnsigned>True</avrgcccpp.compiler.general.ChangeDefaultCharTypeUnsigned>
<avrgcccpp.compiler.general.ChangeDefaultBitFieldUnsigned>True</avrgcccpp.compiler.general.ChangeDefaultBitFieldUnsigned>
<avrgcccpp.compiler.symbols.DefSymbols>
<ListValues>
<Value>NDEBUG</Value>
<Value>F_CPU=16000000UL</Value>
</ListValues>
</avrgcccpp.compiler.symbols.DefSymbols>
<avrgcccpp.compiler.directories.IncludePaths>
<ListValues>
<Value>%24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\include</Value>
</ListValues>
</avrgcccpp.compiler.directories.IncludePaths>
<avrgcccpp.compiler.optimization.level>Optimize for size (-Os)</avrgcccpp.compiler.optimization.level>
<avrgcccpp.compiler.optimization.PackStructureMembers>True</avrgcccpp.compiler.optimization.PackStructureMembers>
<avrgcccpp.compiler.optimization.AllocateBytesNeededForEnum>True</avrgcccpp.compiler.optimization.AllocateBytesNeededForEnum>
<avrgcccpp.compiler.warnings.AllWarnings>True</avrgcccpp.compiler.warnings.AllWarnings>
<avrgcccpp.compiler.miscellaneous.OtherFlags>-std=c++11</avrgcccpp.compiler.miscellaneous.OtherFlags>
<avrgcccpp.linker.libraries.Libraries>
<ListValues>
<Value>libm</Value>
</ListValues>
</avrgcccpp.linker.libraries.Libraries>
<avrgcccpp.assembler.general.IncludePaths>
<ListValues>
<Value>%24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\include</Value>
</ListValues>
</avrgcccpp.assembler.general.IncludePaths>
</AvrGccCpp>
</ToolchainSettings>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<ToolchainSettings>
<AvrGccCpp>
<avrgcc.common.Device>-mmcu=atmega1284 -B "%24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\gcc\dev\atmega1284"</avrgcc.common.Device>
<avrgcc.common.optimization.RelaxBranches>True</avrgcc.common.optimization.RelaxBranches>
<avrgcc.common.outputfiles.hex>True</avrgcc.common.outputfiles.hex>
<avrgcc.common.outputfiles.lss>True</avrgcc.common.outputfiles.lss>
<avrgcc.common.outputfiles.eep>True</avrgcc.common.outputfiles.eep>
<avrgcc.common.outputfiles.srec>True</avrgcc.common.outputfiles.srec>
<avrgcc.common.outputfiles.usersignatures>False</avrgcc.common.outputfiles.usersignatures>
<avrgcc.compiler.general.ChangeDefaultCharTypeUnsigned>True</avrgcc.compiler.general.ChangeDefaultCharTypeUnsigned>
<avrgcc.compiler.general.ChangeDefaultBitFieldUnsigned>True</avrgcc.compiler.general.ChangeDefaultBitFieldUnsigned>
<avrgcc.compiler.symbols.DefSymbols>
<ListValues>
<Value>DEBUG</Value>
<Value>F_CPU=16000000UL</Value>
</ListValues>
</avrgcc.compiler.symbols.DefSymbols>
<avrgcc.compiler.directories.IncludePaths>
<ListValues>
<Value>%24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\include</Value>
</ListValues>
</avrgcc.compiler.directories.IncludePaths>
<avrgcc.compiler.optimization.PackStructureMembers>True</avrgcc.compiler.optimization.PackStructureMembers>
<avrgcc.compiler.optimization.AllocateBytesNeededForEnum>True</avrgcc.compiler.optimization.AllocateBytesNeededForEnum>
<avrgcc.compiler.optimization.DebugLevel>Default (-g2)</avrgcc.compiler.optimization.DebugLevel>
<avrgcc.compiler.warnings.AllWarnings>True</avrgcc.compiler.warnings.AllWarnings>
<avrgcc.compiler.miscellaneous.OtherFlags>-std=c++11</avrgcc.compiler.miscellaneous.OtherFlags>
<avrgcccpp.compiler.general.ChangeDefaultCharTypeUnsigned>True</avrgcccpp.compiler.general.ChangeDefaultCharTypeUnsigned>
<avrgcccpp.compiler.general.ChangeDefaultBitFieldUnsigned>True</avrgcccpp.compiler.general.ChangeDefaultBitFieldUnsigned>
<avrgcccpp.compiler.symbols.DefSymbols>
<ListValues>
<Value>DEBUG</Value>
<Value>F_CPU=16000000UL</Value>
</ListValues>
</avrgcccpp.compiler.symbols.DefSymbols>
<avrgcccpp.compiler.directories.IncludePaths>
<ListValues>
<Value>%24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\include</Value>
</ListValues>
</avrgcccpp.compiler.directories.IncludePaths>
<avrgcccpp.compiler.optimization.PackStructureMembers>True</avrgcccpp.compiler.optimization.PackStructureMembers>
<avrgcccpp.compiler.optimization.AllocateBytesNeededForEnum>True</avrgcccpp.compiler.optimization.AllocateBytesNeededForEnum>
<avrgcccpp.compiler.optimization.DebugLevel>Default (-g2)</avrgcccpp.compiler.optimization.DebugLevel>
<avrgcccpp.compiler.warnings.AllWarnings>True</avrgcccpp.compiler.warnings.AllWarnings>
<avrgcccpp.compiler.miscellaneous.OtherFlags>-std=c++11</avrgcccpp.compiler.miscellaneous.OtherFlags>
<avrgcccpp.linker.libraries.Libraries>
<ListValues>
<Value>libm</Value>
</ListValues>
</avrgcccpp.linker.libraries.Libraries>
<avrgcccpp.assembler.general.IncludePaths>
<ListValues>
<Value>%24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\include</Value>
</ListValues>
</avrgcccpp.assembler.general.IncludePaths>
<avrgcccpp.assembler.debugging.DebugLevel>Default (-Wa,-g)</avrgcccpp.assembler.debugging.DebugLevel>
</AvrGccCpp>
</ToolchainSettings>
</PropertyGroup>
<ItemGroup>
<Compile Include="clock.cpp">
<SubType>compile</SubType>
</Compile>
<Compile Include="clock.h">
<SubType>compile</SubType>
</Compile>
<Compile Include="config.h">
<SubType>compile</SubType>
</Compile>
<Compile Include="dcf77.cpp">
<SubType>compile</SubType>
</Compile>
<Compile Include="dcf77.h">
<SubType>compile</SubType>
</Compile>
<Compile Include="main.cpp">
<SubType>compile</SubType>
</Compile>
</ItemGroup>
<Import Project="$(AVRSTUDIO_EXE_PATH)\\Vs\\Compiler.targets" />
</Project>

176
clock.cpp Normal file
View File

@ -0,0 +1,176 @@
/*
* clock.cpp
*
* Created: 04.09.2022 17:34:31
* Author: Julian Metzler
*/
#include <avr/eeprom.h>
#include "clock.h"
// Actual, currently displayed time
uint8_t clock_curHour = 0;
uint8_t clock_curMinute = 0;
// Desired time (might be different in case of catching up)
uint8_t clock_setHour = 0;
uint8_t clock_setMinute = 0;
// Status data in EEPROM
uint32_t* clock_eepromStartAddr = 0;
uint16_t clock_eepromWriteCounter = 0;
// Millisecond counter of last loop execution, used for non-blocking timing
uint32_t clock_lastPulseStartTimestamp = 0;
// Stores if there is a pulse currently ongoing
uint8_t clock_pulseOngoing = 0;
void clock_init() {
BRIDGE_PWM_DDR |= (1 << BRIDGE_PWM_PIN);
BRIDGE_DIR_DDR |= (1 << BRIDGE_DIR_PIN);
BRIDGE_DIS_DDR |= (1 << BRIDGE_DIS_PIN);
clock_pulseEnd();
//clock_initEeprom();
clock_eepromStartAddr = clock_findEepromStartAddress();
clock_updateDataFromEeprom();
clock_setHour = clock_curHour;
clock_setMinute = clock_curMinute;
}
void clock_loop(uint32_t loopTimestamp) {
if (clock_pulseOngoing) {
if ((loopTimestamp - clock_lastPulseStartTimestamp) >= CLOCK_PULSE_DURATION) {
clock_pulseEnd();
clock_advanceCurrent();
clock_writeDataToEeprom();
}
} else {
uint16_t pulsesNeeded = clock_calcPulsesNeeded();
if (pulsesNeeded > 0) {
clock_pulseStart(clock_getPolarity());
clock_lastPulseStartTimestamp = loopTimestamp;
}
}
}
void clock_pulseStart(uint8_t polarity) {
clock_pulseOngoing = 1;
if (polarity) BRIDGE_DIR_PORT |= (1 << BRIDGE_DIR_PIN);
BRIDGE_PWM_PORT |= (1 << BRIDGE_PWM_PIN);
BRIDGE_DIS_PORT &= ~(1 << BRIDGE_DIS_PIN);
}
void clock_pulseEnd() {
BRIDGE_DIS_PORT |= (1 << BRIDGE_DIS_PIN);
BRIDGE_PWM_PORT &= ~(1 << BRIDGE_PWM_PIN);
BRIDGE_DIR_PORT &= ~(1 << BRIDGE_DIR_PIN);
clock_pulseOngoing = 0;
}
void clock_advanceCurrent() {
clock_curMinute++;
if (clock_curMinute >= 60) {
clock_curMinute = 0;
clock_curHour++;
if (clock_curHour >= 12) {
clock_curHour = 0;
}
}
}
void clock_advanceSet() {
clock_setMinute++;
if (clock_setMinute >= 60) {
clock_setMinute = 0;
clock_setHour++;
if (clock_setHour >= 12) {
clock_setHour = 0;
}
}
}
void clock_setTime(uint8_t hour, uint8_t minute) {
clock_setHour = hour % 12;
clock_setMinute = minute % 60;
}
uint16_t clock_calcPulsesNeeded() {
// Calculate the number of pulses needed to get from the current time to the desired time
int16_t numPulses = 0;
if (clock_setHour < clock_curHour) {
numPulses += (clock_setHour + 12 - clock_curHour) * 60;
} else {
numPulses += (clock_setHour - clock_curHour) * 60;
}
if (clock_setMinute < clock_curMinute) {
// In this case, we need to subtract because this already involves increasing the hour by one
numPulses -= (clock_curMinute - clock_setMinute);
} else {
numPulses += clock_setMinute - clock_curMinute;
}
if (numPulses < 0) numPulses += 720;
return (uint16_t)numPulses;
}
uint8_t clock_getPolarity() {
// Get the polarity required for the UPCOMING minute transition
return clock_curMinute & 1;
}
void clock_initEeprom() {
for (uint8_t* i = 0; i < (uint8_t*)EEPROM_SIZE; i++) {
eeprom_write_byte(i, 0x00);
}
}
uint32_t* clock_findEepromStartAddress() {
// Scan through EEPROM to find the first location with less than
// the number of cycles specified for wear leveling
// Entry format: <16 Bit Write Counter> <8 Bit Hour> <8 Bit Minute>
uint16_t* startAddr = 0;
uint32_t counter = 0;
do {
counter = eeprom_read_word(startAddr);
if (counter < EEPROM_MAX_WRITE_COUNTER) {
break;
}
startAddr += 4;
// This should only happen after about 35555 years
if ((uint32_t*)startAddr > (uint32_t*)(EEPROM_SIZE - 4)) {
return 0;
}
} while(true);
return (uint32_t*)startAddr;
}
void clock_updateDataFromEeprom() {
uint32_t data = eeprom_read_dword(clock_eepromStartAddr);
clock_eepromWriteCounter = data >> 16;
clock_curHour = (data >> 8) & 0xFF;
clock_curMinute = data & 0xFF;
}
void clock_writeDataToEeprom() {
clock_eepromWriteCounter++;
uint32_t eepromValue = (((uint32_t)clock_eepromWriteCounter) << 16);
eepromValue |= (((uint16_t)clock_curHour) << 8);
eepromValue |= clock_curMinute;
eeprom_update_dword(clock_eepromStartAddr, eepromValue);
if (clock_eepromWriteCounter >= EEPROM_MAX_WRITE_COUNTER) {
clock_eepromStartAddr += 4;
clock_eepromWriteCounter = 0;
// This should only happen after about 35555 years
if (clock_eepromStartAddr > (uint32_t*)(EEPROM_SIZE - 4)) {
clock_eepromStartAddr = 0;
}
}
}

44
clock.h Normal file
View File

@ -0,0 +1,44 @@
/*
* clock.h
*
* Created: 04.09.2022 17:34:40
* Author: Julian Metzler
*/
#ifndef CLOCK_H_
#define CLOCK_H_
#define BRIDGE_PWM_DDR DDRB
#define BRIDGE_PWM_PORT PORTB
#define BRIDGE_PWM_PIN 3
#define BRIDGE_DIR_DDR DDRB
#define BRIDGE_DIR_PORT PORTB
#define BRIDGE_DIR_PIN 4
#define BRIDGE_DIS_DDR DDRD
#define BRIDGE_DIS_PORT PORTD
#define BRIDGE_DIS_PIN 5
#define EEPROM_MAX_WRITE_COUNTER 50000
#define EEPROM_SIZE 4096
#define CLOCK_PULSE_DURATION 250
void clock_init();
void clock_loop(uint32_t loopTimestamp);
void clock_pulseStart(uint8_t polarity);
void clock_pulseEnd();
void clock_advanceCurrent();
void clock_advanceSet();
void clock_setTime(uint8_t hour, uint8_t minute);
uint16_t clock_calcPulsesNeeded();
uint8_t clock_getPolarity();
void clock_initEeprom();
uint32_t* clock_findEepromStartAddress();
void clock_updateDataFromEeprom();
void clock_writeDataToEeprom();
#endif /* CLOCK_H_ */

21
config.h Normal file
View File

@ -0,0 +1,21 @@
/*
* config.h
*
* Created: 03.09.2022 22:31:23
* Author: Julian Metzler
*/
#include <avr/io.h>
#ifndef CONFIG_H_
#define CONFIG_H_
#define DCF77_DDR DDRA
#define DCF77_PINR PINA
#define DCF77_PORT PORTA
#define DCF77_PIN 4
#define DCF77_UPDATE_INTERVAL (60 * 60 * 1000UL) // 1 hour
#endif /* CONFIG_H_ */

196
dcf77.cpp Normal file
View File

@ -0,0 +1,196 @@
/**
* libdcf77 -- Cross Platform C++ DCF77 decoder
* Copyright (C) 2016 Andreas Stöckel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "dcf77.h"
namespace dcf77 {
/******************************************************************************
* Class "debounce" *
******************************************************************************/
static constexpr uint8_t FIXED_POINT_LOG2_BASE = 7;
static constexpr uint8_t FIXED_POINT_BASE = (1 << FIXED_POINT_LOG2_BASE);
static constexpr uint8_t FLT_F1 = FIXED_POINT_BASE * 0.97;
static constexpr uint8_t FLT_F2 = FIXED_POINT_BASE - FLT_F1;
static constexpr uint8_t filter(bool ctrl, uint8_t x)
{
return (uint16_t(x) * uint16_t(FLT_F1) +
(ctrl ? uint16_t(FLT_F2) << FIXED_POINT_LOG2_BASE : 0)) >>
FIXED_POINT_LOG2_BASE;
}
static constexpr uint8_t filter_convergence(bool ctrl,
uint8_t x = FIXED_POINT_BASE / 2)
{
return filter(ctrl, x) == x ? x : filter_convergence(ctrl, filter(ctrl, x));
}
static constexpr uint8_t FLT_MAX = filter_convergence(true);
static constexpr uint8_t FLT_MIN = filter_convergence(false);
debounce::debounce(uint8_t hysteresis)
: m_low_pass(FIXED_POINT_BASE / 2), m_last_t(0), m_last_state_change(0),
m_hysteresis((uint16_t(hysteresis) * (FLT_MAX - FLT_MIN)) >> 8),
m_last_input_value(false)
{
}
const debounce::result &debounce::sample(bool value, uint16_t t)
{
// Apply a low-pass filter to the input signal
const uint16_t dt = t - m_last_t;
uint8_t lv = m_low_pass;
for (uint16_t i = 0; i < dt; i++) {
m_low_pass = filter(value, m_low_pass);
if (m_low_pass == lv) {
break;
}
lv = m_low_pass;
}
// Remember the time of the last state change
if (value != m_last_input_value) {
m_last_state_change = t;
}
// Assemble the result structure, apply the hysteresis
if (m_low_pass > FLT_MAX - m_hysteresis && m_result.value == false) {
m_result.t = m_last_state_change;
m_result.edge = true;
m_result.value = true;
} else if (m_low_pass < FLT_MIN + m_hysteresis && m_result.value == true) {
m_result.t = m_last_state_change;
m_result.edge = true;
m_result.value = false;
} else {
m_result.edge = false;
}
// Remember time and input/output values
m_last_t = t;
m_last_input_value = value;
return m_result;
}
/******************************************************************************
* Union "data" *
******************************************************************************/
template <typename T>
uint8_t parity(T x)
{
// __builtin_popcount is 16 bits only, so call it twice
// return __builtin_popcount(x) & 1;
uint8_t result = 0;
result += __builtin_popcount(x >> 16);
result += __builtin_popcount(x & 0xFFFF);
return result & 1;
}
template <uint8_t max_hi, uint8_t max_lo, typename T>
static bool valid_bcd(T x)
{
const uint8_t hi = (x & 0xF0) >> 4;
const uint8_t lo = (x & 0x0F) >> 0;
// The individual digits must not be larger than 9!
if (hi > 9 || lo > 9) {
return false;
}
// The first digit must not be larger than the maximum first digit
if (hi > max_hi) {
return false;
}
// If the first digit is equal to the maximum first digit, the second digit
// must not be larger than the maximum second digit
if (hi == max_hi && lo > max_lo) {
return false;
}
return true;
};
bool data::valid(bool time_and_date_only) const
{
return (time_and_date_only || raw.minute_start == 0) &&
(raw.time_start == 1) // Constant flags
&&
(time_and_date_only || raw.cest != raw.cet) // There can be only one!
&& (raw.parity_minute == parity(raw.minute)) && // Check parity
(raw.parity_hour == parity(raw.hour)) &&
(raw.parity_date ==
parity(uint32_t((bitstream & 0x3FFFFF000000000LL) >> 32))) &&
valid_bcd<5, 9>(raw.minute) && // Check BCD values
valid_bcd<2, 3>(raw.hour) && valid_bcd<3, 1>(raw.day) &&
(raw.day > 0) && (raw.day_of_week > 0) &&
valid_bcd<1, 2>(raw.month) && (raw.month > 0) &&
valid_bcd<9, 9>(raw.year);
}
/******************************************************************************
* Class "decoder" *
******************************************************************************/
decoder::state decoder::sample(bool value, uint16_t t)
{
auto event = m_debouncer.sample(value, t);
state res = state::no_result;
if (event.edge) {
uint16_t dt = event.t - m_last_t;
if (!event.value) {
// Falling edge
if (dt > SYNC_HIGH_TIME - SLACK) {
// Handle a sync event
if (m_state < 59) {
m_data_new.bitstream = m_data_new.bitstream
<< (59 - m_state);
if (m_data_new.valid(true)) {
res = state::has_time_and_date;
}
} else if (m_data_new.valid(false)) {
res = state::has_complete;
}
if (res >= state::has_time_and_date) {
m_data_current = m_data_new;
m_phase = event.t;
}
m_state = 0;
m_data_new.bitstream = 0;
}
}
if (event.value) {
// Rising edge
if (dt > LOW_ZERO_TIME - SLACK) {
// We received a "one" or a "zero"
if (dt > LOW_ONE_TIME - SLACK) {
// It's a "one"
m_data_new.bitstream |= uint64_t(1) << m_state;
}
m_state++;
}
}
m_last_t = event.t;
}
return res;
}
}

447
dcf77.h Normal file
View File

@ -0,0 +1,447 @@
/**
* libdcf77 -- Cross Platform C++ DCF77 decoder
* Copyright (C) 2016 Andreas Stöckel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @file dcf77.hpp
*
* Implementation of a decoder of the time signal sent by the DCF77 station in
* Mainflingen, Germany near Frankfurt on 77.5kHz.
*
* @author Andreas Stöckel
*/
#include <stdint.h>
/**
* Namespace encompassing all types used in the DCF77 decoder.
*/
namespace dcf77 {
/**
* A digital low-pass filter with Schmitt-Trigger and user-definable
* hysteresis. The filter is a simple finite impulse response filter with a
* single coefficient (also known as moving average or exponential filter). As
* an important feature for time signal analysis, the debounce filter allows to
* recover the current input signal phase, allowing for a reconstruction of the
* current time.
*/
class debounce {
public:
/**
* Structure describing the output of the debounce filter.
*/
struct result {
/**
* Timestamp at which the state transition occured.
*/
uint16_t t;
/**
* Current output state.
*/
bool value : 1;
/**
* True if a state transition occured just now.
*/
bool edge : 1;
result() : t(0), value(false), edge(false) {}
};
private:
/**
* Low-pass filtered input value.
*/
uint8_t m_low_pass;
/**
* Last timestamp passed to the sample() function.
*/
uint16_t m_last_t;
/**
* Time of the last raw state change.
*/
uint16_t m_last_state_change;
/**
* User-supplied hysteresis.
*/
uint8_t m_hysteresis;
/**
* Last input value received by the sample function.
*/
bool m_last_input_value;
/**
* Current/last result.
*/
result m_result;
public:
/**
* Constructor of the debounce class with user-definable hysteresis.
*
* @param hysteresis is a value between zero and 255, which is mapped to a
* value between zero and one hundred percent (for example, the default
* value of 64 corresponds to 25 percent). This percentage p is then used to
* derive the values at which the output is switched: if the current output
* of the filter is zero, and the low-pass filtered value reaches 1 - p, the
* filter output is set to one, otherwise, if the current filter output is
* one and the low-pass filtered values reaches p, the output is set to
* zero.
*/
debounce(uint8_t hysteresis = 64);
/**
* Processes a new sample.
*
* @param value is the input bit.
* @param t is a monotonous timestamp in milliseconds. This value is used
* to determine the* number of filter steps. The longer the time that has
* passed since the last call to "sample", the more filter steps are
* required.
*/
const result &sample(bool value, uint16_t t);
};
#pragma pack(push, 1)
/**
* The data union stores the data received from the DCF77 radio station. It
* provides a view on both the incomming bitstream as a 64-bit integer, as well
* as the corresponding raw fields. The access methods provided by this class
* can be used to validate and read the decoded data in a convenient manner.
* Data should only be used if the valid() method evaluates to true. When using
* the decoder class, this check is already performed by the sample() method.
* The return value of that method informs about the validity of the
* information.
*/
union data {
/**
* Contains the raw fields as received from the DCF77 station. Except for
* special applications, use the access methods below to read the data.
*/
struct {
/**
* First minute bit -- should always be set to zero.
*/
uint8_t minute_start : 1;
/**
* Auxiliary data -- used to tranmit alarms and weather data. The latter
* is encrypted.
*/
uint16_t aux_data : 14;
/**
* Used to inform about an irregularity at the station.
*/
uint8_t call_bit : 1;
/**
* If true, there's a swtich from CET to CEST or vice versa at the end
* of this hour.
*/
uint8_t dst_leap_hour : 1;
/**
* The time described by this transmission is central european summer
* time (CEST).
*/
uint8_t cest : 1;
/**
* The time described by this transmission is central european time
* (CET).
*/
uint8_t cet : 1;
/**
* This hour will end with a leap second.
*/
uint8_t leap_second : 1;
/**
* Time start indicator. Always set to one.
*/
uint8_t time_start : 1;
/**
* The minute as 2-digit binary coded decimal (BCD).
*/
uint8_t minute : 7;
/**
* Even parity of the minute bits.
*/
uint8_t parity_minute : 1;
/**
* The current hour as 2-digit binary coded decimal (BCD).
*/
uint8_t hour : 6;
/**
* Even parity of the minute bits.
*/
uint8_t parity_hour : 1;
/**
* The current day of the month as 2-digit binary coded decimal (BCD).
*/
uint8_t day : 6;
/**
* The current day of the week from one to seven.
*/
uint8_t day_of_week : 3;
/**
* The current month as a value from one to twelve.
*/
uint8_t month : 5;
/**
* The current year as a two digit BCD.
*/
uint8_t year : 8;
/**
* Even parity of the day, day_of_week, month and year bits.
*/
uint8_t parity_date : 1;
/**
* Additional bit used for leap seconds. Is always set to zero.
*/
uint8_t leap_second_bit : 1;
} raw;
/**
* Integer representing the DCF77 data. The decoder directly sets the bits
* in this integer. The data can then be read using the bitfields in the
* "raw" structure.
*/
uint64_t bitstream;
/**
* Initialises an empty DCF77 data object.
*/
data() : bitstream(0) {}
/**
* Validates the data contained in this object. Checks the constant flags,
* the parity and the numerical values for validity. If incomplete data
* has been read, the "time_and_date_only" flag can be used to skip
* validation of the parts which are not relevant for time and date.
*
* @param time_and_date_only if true, does not check the validity of the
* first 19 bits of the data stream.
*/
bool valid(bool time_and_date_only = false) const;
/**
* Used to decode two-digit bcd values to bits.
*/
static uint8_t decode_bcd(uint8_t v)
{
uint8_t res = v & 0x0F;
if (v & 0x10) {
res += 10;
}
if (v & 0x20) {
res += 20;
}
if (v & 0x40) {
res += 40;
}
if (v & 0x80) {
res += 80;
}
return res;
}
/**
* If true, daylight_saving is currently active.
*/
bool daylight_saving() const { return raw.cest; }
/**
* If true, the timestamps will swtich from CEST to CET or vice versa in the
* next hour.
*/
bool daylight_saving_leap_hour() const { return raw.dst_leap_hour; }
/**
* If true, the current hour ends with a leap second.
*/
bool leap_second() const { return raw.leap_second; }
/**
* Minute encoded in the transmission (0 to 59).
*/
uint8_t minute() const { return decode_bcd(raw.minute); }
/**
* Hour encoded in the transmission (0 to 23).
*/
uint8_t hour() const { return decode_bcd(raw.hour); }
/**
* Day encoded in the transmission (1 to 31).
*/
uint8_t day() const { return decode_bcd(raw.day); }
/**
* Day of the week (1 to 7), a value of one corresponds to Monday.
*/
uint8_t day_of_week() const { return raw.day_of_week; }
/**
* Current month (1 to 12), one is January.
*/
uint8_t month() const { return decode_bcd(raw.month); }
/**
* Current year. Assumes we are in the 21th century.
*/
uint16_t year() const { return decode_bcd(raw.year) + 2000; }
};
#pragma pack(pop)
/**
* The DCF77 decoder class allows to decode the DCF77 signal. It performs phase
* recovery, input signal low-pass filtering with hysteresis and data
* validation.
*/
class decoder {
public:
/**
* Enum describing the state of the decoder.
*/
enum class state : int8_t {
/**
* Currently, there is no result available.
*/
no_result = 0,
/**
* A synchronisation bit has been received, but the data could not
* be validated.
*/
invalid_result = -1,
/**
* Time and date have been received and are valid, but supplementary
* information is missing.
*/
has_time_and_date = 1,
/**
* An entire dataset is available.
*/
has_complete = 2
};
private:
/**
* All measured time values may be smaller than the nominal value by this
* value (in milliseconds).
*/
static constexpr uint16_t SLACK = 50;
/**
* Synchronisation time in milliseconds. The carrier has a high amplitude
* for this amount of time.
*/
static constexpr uint16_t SYNC_HIGH_TIME = 1800;
/**
* Time for which the carrier has a low value when encoding a zero-bit.
*/
static constexpr uint16_t LOW_ZERO_TIME = 100;
/**
* Time for which the carrier has a low value when encoding a one-bit.
*/
static constexpr uint16_t LOW_ONE_TIME = 200;
/**
* Instance of the "debouncer" class used to software-filter the input
* signal.
*/
debounce m_debouncer;
/**
* Timestamp at which the falling edge corresponding to the start of the
* first second in a minute was received. Only updated when valid data is
* received.
*/
uint16_t m_phase = 0;
/**
* Last timestamp when the "sample" method was called.
*/
uint16_t m_last_t = 0;
/**
* Working data register. Incomming bits are written to this variable.
*/
data m_data_new;
/**
* Valid data store. Valid received transmissions are stored in this
* variable.
*/
data m_data_current;
/**
* Current bit into which data is written.
*/
uint8_t m_state = 0;
public:
/**
* Pushes a new input sample into the decoder.
*
* @param value is the current value of the DCF77 carrier amplitude. "True"
* corresponds to a high amplitude, "False" to a low amplitude. Depending
* on the receiving circuitry you may have to invert this signal.
* @param t is a monotonously increasing timestamp in milliseconds. This
* time stamp can for example be generated by a simple one-millisecond
* timer.
* @return the decoder state. If has_time_and_date or has_complete is
* returned, the time data can be read via get_data() and the information
* can be accessed using the get_phase() method.
*/
state sample(bool value, uint16_t t);
/**
* Returns the timestamp at which the end of the last valid synchronisation
* pulse was received.
*/
uint16_t get_phase() const { return m_phase; }
/**
* Returns a reference at the last validated time data.
*/
const data &get_data() const { return m_data_current; }
};
}

67
main.cpp Normal file
View File

@ -0,0 +1,67 @@
/*
* Uhrensteuerung.cpp
*
* Created: 03.09.2022 22:29:19
* Author : Julian Metzler
*/
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include "config.h"
#include "clock.h"
#include "dcf77.h"
dcf77::decoder dcf77_dec;
static volatile uint32_t t = 0;
uint32_t dcf77_lastUpdate = 0;
uint32_t minute_lastUpdate = 0;
uint8_t dcf77_initial = 1;
ISR(TIMER1_COMPA_vect) { t++; }
int main(void) {
clock_init();
// Set up pin directions
DCF77_DDR &= ~(1 << DCF77_PIN);
DCF77_PORT |= (1 << DCF77_PIN);
// Configure a millisecond timer
TCCR1B = (1 << WGM12) | (1 << CS10);
OCR1A = F_CPU / 1000;
TIMSK1 = (1 << OCIE1A);
sei();
//clock_setTime(1, 23);
//while(1) clock_loop(t);
while (1) {
if (dcf77_initial || (t - dcf77_lastUpdate) >= DCF77_UPDATE_INTERVAL) {
const bool dcf77_signal = !(DCF77_PINR & (1 << DCF77_PIN));
if (dcf77_dec.sample(!dcf77_signal, t) >=
dcf77::decoder::state::has_time_and_date) {
auto &d = dcf77_dec.get_data();
// Valid data has been received, do something with it
clock_setTime(d.hour(), d.minute());
dcf77_lastUpdate = t;
minute_lastUpdate = t;
dcf77_initial = 0;
}
}
if ((t - minute_lastUpdate) >= 60000) {
clock_advanceSet();
minute_lastUpdate = t;
}
clock_loop(t);
// Wait for the next millisecond
set_sleep_mode(SLEEP_MODE_IDLE);
sleep_mode();
}
}

19
utils.cpp Normal file
View File

@ -0,0 +1,19 @@
/*
* utils.cpp
*
* Created: 03.09.2022 22:50:15
* Author: Julian
*/
#include <stdint.h>
uint32_t min(uint32_t a, uint32_t b) {
if (a < b) return a;
return b;
}
uint32_t max(uint32_t a, uint32_t b) {
if (a > b) return a;
return b;
}

15
utils.h Normal file
View File

@ -0,0 +1,15 @@
/*
* utils.h
*
* Created: 03.09.2022 22:50:06
* Author: Julian
*/
#ifndef UTILS_H_
#define UTILS_H_
uint32_t min(uint32_t a, uint32_t b);
uint32_t max(uint32_t a, uint32_t b);
#endif /* UTILS_H_ */