I thought I’d try putting together a YouTube video to show my latest crazy project.
As usual, the schematic (navi-misc/tedrx/hardware/kwclock-v1-schematic.png) and firmware (navi-misc/tedrx/firmware/kwclock/main.c) are in Subversion.


/* -*- Mode: C; c-basic-offset: 4 -*-
*
* The Kilowatt Clock: This firmware receives packets over the
* powerline from The Energy Detective, and uses the current
* whole-house power usage to tick a standard Quartz clock movement at
* every kilowatt-second.
*
* Copyright (c) 2008 Micah Dowty <micah@navi.cx>
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <tedrx.h>
#include <filter.h>
#include <ted-raw.h>
FUSES = {
.low = 0xE1, /* 16 MHz PLL */
.high = 0xDF, /* Defaults */
.extended = 0xFF, /* Defaults */
};
/*
* Watchdog timer duration, in TMR1 cycles.
* If we go this long without receiving a TED packet, stop the clock.
*/
#define WATCHDOG_TICKS (TMR1_HZ * 30)
uint32_t current_power;
uint32_t watchdog;
int main(void)
{
uint8_t buf[16];
DDRB |= _BV(LED_PIN) | _BV(CLOCK_PIN_1) | _BV(CLOCK_PIN_2);
/*
* Set up Timer 1 to drive our clock interrupt, at TMR1_HZ.
*
* When the timer reaches OCR1C, it will be reset. When it
* reaches OCR1B, we fire an interrupt.
*/
TCCR1 = _BV(CTC1) | _BV(CS13) | _BV(CS11) |_BV(CS10);
OCR1B = 1;
OCR1C = F_CPU / 1024 / TMR1_HZ;
TIMSK |= _BV(OCIE1B);
filter_init();
sei();
while (1) {
uint8_t size;
uint32_t power;
TEDPacket *packet;
size = filter_rx(buf, sizeof buf);
packet = ted_packet_validate(buf, size);
if (!packet) {
/* Bad packet */
continue;
}
/* Atomically store the new power value. */
power = ted_packet_watts(packet);
cli();
current_power = power;
watchdog = WATCHDOG_TICKS;
sei();
/* We got a good packet. Flash the LED. */
PORTB |= _BV(LED_PIN);
_delay_ms(50);
PORTB &= ~_BV(LED_PIN);
}
}
/*
* Timer 1 compare match vector --
*
* This fires at TMR1_HZ, and runs our clock accumulator.
*
* We intentionally use COMPB instead of COMPA, because this
* interrupt must be lower priority than the Timer 0 overflow. This
* interrupt must not block the Timer 0 interrupt, or our ASK
* demodulation timebase will be inaccurate.
*/
ISR(TIM1_COMPB_vect, ISR_NOBLOCK)
{
static uint32_t clock_acc;
const uint32_t acc_rollover = TMR1_HZ * 1000L;
uint8_t need_tick;
static uint8_t state;
/*
* Update the watchdog timer.
*/
if (!watchdog) {
return;
}
watchdog--;
/*
* Clock accumulator. Every TMR1_HZ, we accumulate the current_power
* (in watts). Every time we've stored one kilowatt-second (TMR1_HZ *
* 1000), we tick the clock mechanism.
*/
clock_acc += current_power;
need_tick = clock_acc > acc_rollover;
if (need_tick) {
clock_acc -= acc_rollover;
}
/*
* Drive the clock ticks themselves using a state machine.
*
* The clock motor is connected to two I/O pins, and we drive
* it somewhat like a stepper motor. Every other tick toggles
* the pulse polarity. Ticks are short pulses of about 1/30th
* of a second, or three TMR1 interrupts.
*/
switch (state & 7) {
case 0: /* Waiting for tick (+) */
PORTB &= ~_BV(CLOCK_PIN_1);
PORTB &= ~_BV(CLOCK_PIN_2);
if (need_tick) {
state++;
}
break;
case 1: /* Emitting tick (+) */
case 2:
case 3:
PORTB |= _BV(CLOCK_PIN_1);
PORTB &= ~_BV(CLOCK_PIN_2);
state++;
break;
case 4: /* Waiting for tick (-) */
PORTB |= _BV(CLOCK_PIN_1);
PORTB |= _BV(CLOCK_PIN_2);
if (need_tick) {
state = 5;
}
break;
case 5: /* Emitting tick (-) */
case 6:
case 7:
PORTB &= ~_BV(CLOCK_PIN_1);
PORTB |= _BV(CLOCK_PIN_2);
state++;
break;
}
}

