Contents
Part description
This program controls the clock operations. It can be compiled with avr-gcc and avr-libc, the Makefile is written for gmake.
Overview
The clock firmware runs on an Atmel ATmega8535 microcontroller and is designed for a clock frequency of 11.0592 MHz. It utilises two external interrupt lines, INT0 for DCF77 data input, and INT2 for input from the user interface input dial. Furthermore it uses three hardware timers, TIMER0 to measure the length of DCF77 pulses, TIMER1 (which is a 16-bit timer, the others are 8-bit) to take care of housekeeping tasks, and TIMER2 to measure pulse lengths of user input dial signals.
It communicates through a 4-bit wide interface (plus a clock and data/command selector line) with a display module using a KS0073 compatible command set, and manages several digital Input/Output lines for peripheral functions (alarm and backlight control, user interface dial input, user interface snooze switch, and others). It also acts as an I2C bus master device, controlling a DS1307 Real Time Clock chip, and leaving room for the extension with an RGB LED controller (which is developed in another project), that will attach as an I2C slave device, too. I2C bus frequency is specified at "normal" speed of 100 kHz. The RTC also provides battery-buffered RAM space, which is used by the firmware to store certain user settings and restore them after reset or power failure. For some settings, the storage addresses used depend on the day of week number transmitted through DCF77 and kept current by the RTC.
The microcontroller's internal EEPROM is not being used by this firmware, because the frequent write accesses when the clock user changes basic values has been found to cause too much wear on the EEPROM cells, which are specified to endure 10'000 write cycles. It would have been possible to include a "sliding window" writing scheme, wearing down all 512 Byte EEPROM equally though the data to store is only about 30 Byte, but since the RTC provides an easy way for sufficiently safe data storage, this approach has not been followed.
Main program
The main() routine consists of two parts: First, the neccessary initializations for the clock function are done:
- output and input pins have their data direction bit set or cleared, and are configured either to output low or high signals, or to activate or deactivate their internal pullup resistor, respectively.
- External interrupt INT2 is configured and activated.
- TIMER1, which is responsible for housekeeping work, is configured and activated.
- The display output pins are configured, and an initialization command sequence is sent to the display module.
- The I2C interface is initialized.
Current time is read from the RTC chip, together with the appropriate alarm settings for the current day of week. If certain values seem invalid (for example, there is one "magic number" stored in RTC data RAM upon writing time into it), the RTC RAM is zeroed out, and a flag for invalid time data is set (time_status is set to '?').
- TIMER2 is configured, which is responsible for counting pulse length from the input dial. This timer is not started here, though, as this is done by the INT2 service routine.
- Finally, interrupts are enabled globally.
The second part, which is entered then, is an endless loop. Basically, this does nothing - but on every loop, it checks a bit field for flags set by the various interrupt service routines. If these are set, the respective action is taken - these actions will be described in the following sections.
This main program loop is not meant to terminate, and not programmed to terminate. If it does, the clock will hang. Currently, no watchdog timer is implemented, but this could be done as further precaution against clock loss and alarm failure.
Housekeeping
About 21 times per second, TIMER1 overflows and causes its interrupt service routine to set the IRQ_TIMER1 flag in the irqticked byte. The housekeeping part of the main() loop does several actions, mostly based on the current program status:
If no valid time was read from RTC, or if the minute counter has reached 58, the DCF77 receiver is started by calling dcf77_init(). How this works is described later in this text.
When DCF77 synchronization is currently running, a slower ticking counter is running in cnt_slowticks, which is used to stop the synchronization in case no valid time has been received from DCF77 by calling the dcf77_stop() routine, and increasing the sync failure counter if the SYNC_MANUAL flag does not indicate that this sync attempt was started by the user through the respective menu action.
The handpiece contact is checked for activation, and if it has been lifted, action is taken: If the alarm is currently ringing, it is put into snooze mode by calling al_snooze(). Otherwise, the backlight is turned on.
Next in the program, the mentioned slower ticking part is handled: A variable upcounter is increased on every execution of the housekeeping routine. This one overflows about every 12 seconds (it's a byte value). When it does, the following actions are taken:
- The current time is read from the RTC and the display is marked for refresh.
- The hour value (which might have changed after RTC readout) is sent to the RGBLED module through I2C - this function is currently excluded on compilation.
- If the backlight is currently on, but alarm is not ringing, the backlight is turned off.
If the very slow counter used for DCF77 sync timeout is activated (do_cnt_slowticks is set to 1), cnt_slowticks is increased.
Then, the value ui_input is checked. This is set to the digit dialed by the user by the input dial routines, which are described later. This part takes action according to the current menu being displayed, which is stored in currentmenu as a byte value, and the ui_input value. If any data is changed by the user's input, a flag is set in do_refresh_menu to cause a display redraw.
do_refresh_menu is checked, and if set, the display is updated with the new currentmenu and the newly configured clock state.
- Finally, hour and minute are checked against the alarm time, if the alarm is turned on. If equal, and if the alarm was not already running in the current minute, the alarm is started.
DCF77 operation
For a description of the DCF77 time transmission protocol, refer to e.g. http://www.eecis.udel.edu/~mills/ntp/dcf77.html.
To initiate the DCF77 synchronization, the function dcf77_init() is called. It configures the TIMER0 prescaler to 1/1024 CPU clock, and enables the TIMER0 overflow interrupt and the external interrupt INT0, which is connected to the DCF77 receiver's data line. Furthermore, the do_cnt_slowticks flag is turned on so that the main program can detect a sync timeout.
On the next DCF signal's rising edge, INT0 fires. The service routine resets the TIMER0 counter, marks the high level in dcf_active, and disables itself.
On each overflow of TIMER0, the respective interrupt service routine counts the duration of the high level on the DCF77 signal input in timecount and checks if it is still high. If it is not, it writes either '0' or '1' into the dcf_array[], depending on the duration of the last high level. Then, it starts counting the TIMER0 ticks while the input is low in breakcount, to detect the synchronization pause between two data transmissions.
When array_count, which is the counter used as index into dcf_array[], reaches 58, a full DCF77 datagram has been received. In this case, the flag do_dcf77_exec is set to evaluate the data, which will be evaluated in the housekeeping part of the main program.
At this point, the interrupt context is left.
dcf77_exec() adds up the BCD encoded date and time data in the array and checks the parity bits included in the datagram. If this test confirms the successful reception, another time datagram is received and parity checked. If the second time is greater than the first, and differs less than five minutes (and less then three hours) from the first, the clock's date and time values are updated with the freshly received data, time_status is set to 'S' (for synchronized), the dcf77_stop() routine is invoked, and the current time data is written into the RTC chip.
Before main.c revision 1.117, the time was received only once and stored to RTC as soon as the parity check succeeded.
dcf77_stop(), finally, disables the TIMER0 and INT0 interrupts, updates several status variables, and stops counting of the cnt_slowticks value that was to be used for sync timeout.
I2C bus
The I2C bus communication is handled using twimaster.c written by Peter Fleury. This code provides all the low level functions like putting start and stop conditions onto the bus, and reading/writing data through it. This implementation uses the hardware I2C interface present in the ATmega8535. For other microcontrollers not featuring hardware I2C/TWI support, the software implementation from i2cmaster.S can be activated by the respective change in the Makefile.
A handful of functions exist that handle time and other data transfer with the RTC chip and, in preparation of a later extension, an RGB LED controller yet to be specified. These functions are quite self-explanatory, and further documented in source code.
Major state variables
irqticked is an 8-bit value in which the interrupt handling routines mark the ticking of an interrupt so that the respective functions can be done later in the main program. Bits can be set for IRQ_TIMER0, 1, 2. Furthermore, when the user starts a DCF77 manually through the UI menu function, a bit SYNC_MANUAL is set in irqticked, so that the sync timeout function can decide whether to increase the count of unsynchronized hours or not.
alarmstate is an 8-bit value where the current alarm state is held. The following flags are currently defined:
AL_ONOFF is set when the alarm is currently switched on, and the clock is waiting for the alarm time to arrive.
AL_RINGING is set when the alarm currently sounds (i.e. when the alarm signal output is high).
AL_MODE_DELTA marks relative or absolute alarm mode. When set, relative alarm mode is on, and the time to be displayed is the delta between current and alarm time.
AL_SNOOZING is set when the clock is currently in snooze mode: The alarm has already rung at least once, has been silenced through the snooze function, and the snooze counter al_snzcnt has not yet reached the configured maximum snooze counter al_snzmax.
AL_RANTHISMINUTE is set when the alarm has been started after the alarm time has been reached. It remains set until the next minute arrives to prevent the alarm from ringing again when user has silenced the alarm but it is still the same minute.
Time display
Time is displayed together with the regular menu by the functions in menu.c. On user input, either a new menu is entered by setting currentmenu to one of the defined constants in menu.h, or a value in the clock's RAM is changed, for example when setting the alarm time.
The time to be displayed is kept in variables seperately from al_hour and al_minute which are always the absolute values at which the alarm shall ring next. However, after having refreshed the time from RTC, the function calc_disp_time() is called to calculate new values in al_disp_hour and al_disp_minute, which are the values that will be displayed in the clock's alarm time line. When absolute alarm mode is active (AL_MODE_DELTA is not set in alarmstate), these values are simply copied from the configured alarm time. When relative alarm mode is set, the difference between alarm time and current time is calculated by this function, and this is put into the al_disp_... variables. In this way, the part of main() responsible for launching the alarm can still just compare current time with alarm time (because this is still kept as absolute time), but the user will see the remaining time until the alarm rings, which is what might be expected when choosing relative alarm mode.
Debugging support
Two debugging functions are currently implemented:
- In the config menu, there is a function "Info" which displays, besides author contact information, the CVS revision number of main.c
- An undocumented option exists in the Config menu. Dialing "8" here leads to a debug menu, giving the following information:
- The currently active absolute alarm time is displayed even in relative alarm mode
- Some letters indicate status information from the firmware's internals. A capital "S" indicates that the last DCF sync attempt succeeded. A small letter "s" indicates that the last DCF sync attempt failed. If interested, please see the source code for the rest of those status bits.
- An option exists to see a binary representation of the last received DCF77 datagram, whether valid or not. Because of its size, it had to be split over two screens. Dialing "1" from DCF view goes back to the debug menu.
- Dialing "1" returns to main menu.
Revisions
CL-P01.02
Current development revision.
I am working with low intensity on a new firmware revision, with improvements to scheduling of the different tasks within the clock. This is also an experiment with co-operative "multi-task" scheduling on microcontrollers - I want to get comfortable with a bit more advanced programming techniques.
CL-P01.01
This is the first revision being used in production (i.e. at my bedside). No warranties for not oversleeping, and no other warranties of any kind (as usual, and as stated in my usage notice on about every web page here).
THIS DATA IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS DATA, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
All product and brand names mentioned on there pages and in the source code are registered names and/or trademarks of the respective owner and are mentioned for identification purposes only.
For a full copyright notice, please see this link. For imprint and contact information, please see http://www.thiemo.net/.