Screw Wires

Source code and notes

This article accompanies my video: Screw Wires

This is the C code for the atMega328p:

#include <avr/interrupt.h>
#include <avr/io.h>
#include <util/delay.h>
#include <util/twi.h>

// Use the servo output on OC1A (pin 15)
#define SERVO_PIN PB1

// Set the status lights to pin 12 and 13
#define HAS_DATA_PIN PD7
#define DATA_FULL_PIN PD6

#define SERVO_TIME_MIN 0x0300
#define SERVO_TIME_MAX 0x0A00

#define TIMES_TO_STORE 30
// Store the first this many bytes from the RTC time data
#define TIME_BYTE_COUNT 5
uint8_t timedata[TIMES_TO_STORE][TIME_BYTE_COUNT];
uint8_t timedata_count = 0;

void fetch_time();
void setup_time();

int main(void) {
    // Setup UART
    // Set bit rate for 9600
    // See page 196 of the datasheet
    UBRR0H = 0;
    UBRR0L = 12;
    UCSR0A = (1 << U2X0);                   // Double speed
    UCSR0B = (1 << TXEN0);                  // Enable transmit
    UCSR0C = (1 << UCSZ00) | (1 << UCSZ01); // 8 bits

    // Setup interrupt for UART
    EICRA = (1 << ISC01) | (1 << ISC11); // Interrupt on falling edge
    EIMSK = (1 << INT0) | (1 << INT1);   // Enable interrupt int0 and int1

    // Setup I2C
    TWBR = 32; // Set bit rate
    TWCR = (1 << TWEN);

    // Set servo pin to output
    DDRB |= (1 << SERVO_PIN);

    // Setup PWM
    //  Compare mode non-inverting
    // 10 bit fast PWM
    // PWM mode 14 - count until ICR
    // Clock select with no scaling
    TCCR1A = (1 << COM1A1) | (1 << WGM11);
    TCCR1B = (1 << WGM12) | (1 << WGM13) | (1 << CS10);

    // Set ICR to good value for servo timing
    ICR1H = 0x1F;
    ICR1L = 0xFF;

    // Setup timer 0
    // clock scale minimum speed
    TCCR0B = (1 << CS00) | (1 << CS02);
    TIMSK0 = 0;

    // Set up indicator LED output
    // Enable outputs
    DDRD = (1 << HAS_DATA_PIN) | (1 << DATA_FULL_PIN);

    // setup the starting conditions
    OCR1A = SERVO_TIME_MIN;
    uint8_t increasing = 1;
    uint8_t slowdown = 0;
    uint8_t slowdown_more = 0;

    setup_time();
    // Get the starting time
    fetch_time();

    sei(); // enable interrupts globally

    while (1) {
        if (TIFR0 | (1 << TOV0)) {
            // if the slow counter has overflowed
            TIFR0 |= (1 << TOV0); // clear flag

            // It's still not slow enough for us
            slowdown++;
            if (slowdown == 0) {
                slowdown_more++;
                if (slowdown_more % 2 == 0) {
                    // Change servo value
                    if (increasing) {
                        OCR1A += 1;
                        if (OCR1A >= SERVO_TIME_MAX) {
                            increasing = 0;
                        }
                    } else {
                        OCR1A -= 1;
                        if (OCR1A <= SERVO_TIME_MIN) {
                            increasing = 1;
                        }
                    }

                    if (timedata_count == TIMES_TO_STORE) {
                        PORTD |= (1 << HAS_DATA_PIN) | (1 << DATA_FULL_PIN);
                    } else if (timedata_count > 1) {
                        PORTD |= (1 << HAS_DATA_PIN);
                        PORTD &= ~(1 << DATA_FULL_PIN);
                    } else {
                        PORTD &= ~((1 << HAS_DATA_PIN) | (1 << DATA_FULL_PIN));
                    }
                }
            }
        }
    }
}

void clock_wait () {
    uint8_t slowdown = 1;
    while (slowdown != 0) {
        if (TIFR0 | (1 << TOV0)) {
            // if the slow counter has overflowed
            TIFR0 |= (1 << TOV0); // clear flag
            slowdown ++;
        }
    }
}

void i2cWait() {
    // Wait for hardware to acknowledge
    while (!(TWCR & (1 << TWINT))) {
    }
}

void i2cWrite(uint8_t val) {
    TWDR = val;
    TWCR = (1 << TWEN) | (1 << TWINT); // Transmit byte
    i2cWait();
}

void i2cStop() { TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO); }

void setup_time() {
    uint8_t transmitted = 0;
    while (transmitted == 0) {
        // First we start communication:
        TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTA);
        i2cWait();
        // Check that it has started
        if ((TWSR & 0xF8) != TW_START) {
            // Did not start
            return;
        }
        i2cWrite((104 << 1) | 0); // write address
        if (TWSR == TW_MT_SLA_NACK) { // If there is no acknoledgement
            // Stop sending
            TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);
            clock_wait();
            continue; // retry
        }

        i2cWrite(0 | 0);          // write 0
        i2cWrite(0); // Write seconds (0) and Clock Halt bit (0 = enable clock)

        i2cWrite(0); // Write minutes (0)
        i2cWrite(0); // Write hours (0)
        i2cWrite(1); // Write Day (1)
        i2cWrite(1); // Write Month (1)
        i2cWrite(1); // Write Year (1)
        TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);
        transmitted = 1;
        clock_wait();
        clock_wait();
    }
}

void fetch_time() {
    // Make sure we still have data space left
    if (timedata_count == TIMES_TO_STORE) {
        return;
    }

    // When this interrupt happens we want to log the time
    // First we start communication:
    TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTA);
    i2cWait();
    // Check that it has started
    if ((TWSR & 0xF8) != TW_START) {
        // Did not start
        return;
    }
    i2cWrite((104 << 1) | 0); // write address
    i2cWrite(0 | 0);          // write 0

    i2cStop();

    // we again start communication:
    TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTA);
    // Wait for hardware to acknowledge
    i2cWait();
    i2cWrite((104 << 1) | 1); // write address, then read

    // Now we tell the RTC to start talking:
    TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWEA);

    // Now we can actually receive the data
    for (int i = 0; i < TIME_BYTE_COUNT; i++) {
        // Wait for hardware to acknowledge
        i2cWait();
        // Make sure we got something
        if ((TWSR & 0xF8) == TW_MR_DATA_ACK) {
            // Store the time data
            timedata[timedata_count][i] = TWDR;
            TWCR = (1 << TWINT) | (1 << TWEA) | (1 << TWEN);
        } else {
            // End this now
            i2cStop();
            return;
        }
    }
    // Don't send acknowledge
    // If we don't do this then we don't get a good stop signal, and the
    // program will get stuck.
    TWCR = (1 << TWINT) | (1 << TWEN);
    i2cWait();

    // We got all the bytes, now we can safely increase the counter
    timedata_count++;

    // End this
    i2cStop();
}

ISR(INT0_vect) {
    // Disconnection detected!
    cli(); // Disable interrupts
    fetch_time();
    sei(); // re-enable interrupts
}

ISR(INT1_vect) {
    // Request send data over serial
    for (uint8_t i = 0; i < timedata_count; i++) {
        for (uint8_t j = 0; j < TIME_BYTE_COUNT; j++) {
            while (!(UCSR0A & (1 << UDRE0))) {
            } // Wait for transmit buffer to clear
            UDR0 = timedata[i][j];
        }
    }
}

The makefile:

OUTFILE=out.hex

main.hex: main.elf
    avr-objcopy -j .text -j .data -O ihex main.elf main.hex

main.elf: main.o
    avr-gcc -Wl,-Map,main.map  -Wl,--gc-sections  -mmcu=atmega328 main.o  -o main.elf

main.o: main.c
    avr-gcc -Os -g -std=gnu99 -Wall -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums -ffunction-sections -fdata-sections -DF_CPU=1000000UL -DBAUD=9600UL -mmcu=atmega328 -c -o main.o main.c

.PHONY: upload setdebug unsetdebug

upload: main.hex
    avrdude -c dragon_isp -p m328p -B100kHz -U flash:w:main.hex

setdebug:
    avrdude -c dragon_isp -p m328p -U hfuse:w:0x99:m

unsetdebug:
    avrdude -c dragon_isp -p m328p -U hfuse:w:0xD9:m

This is the python source code to interpret the data from the clock:

bare = "63 63 00 07 31 32 08 00 07 31 21 02 66 07 31"
ferrule = "E3 63 00 03 31 E3 63 00 03 31"
soldered = "63 63 00 03 31 09 01 00 03 31"
folded = "63 63 00 03 31 28 52 48 03 31 34 52 48 03 31 40 52 48 03 31 45 52 48 03 31 51 52 48 03 31 57 52 48 03 31 02 53 48 03 31 08 53 48 03 31 14 53 48 03 31 19 53 48 03 31 25 53 48 03 31 31 53 48 03 31 37 53 48 03 31 42 53 48 03 31 48 53 48 03 31 54 53 48 03 31 59 53 48 03 31 05 54 48 03 31 11 54 48 03 31 16 54 48 03 31 22 54 48 03 31 28 54 48 03 31 33 54 48 03 31 39 54 48 03 31 39 54 48 03 31 45 54 48 03 31 50 54 48 03 31 56 54 48 03 31 02 55 48 03 31"
soldered2 = "00 00 00 01 01 16 07 00 01 01"
ferrule2 = "00 00 00 01 01 16 25 02 01 01"
folded2 = "00 00 00 01 01 38 03 09 01 01 39 03 09 01 01 39 03 09 01 01 39 03 09 01 01 44 03 09 01 01 51 03 09 01 01 51 03 09 01 01 51 03 09 01 01 51 03 09 01 01 51 03 09 01 01 51 03 09 01 01 02 04 09 01 01 02 04 09 01 01 02 04 09 01 01 02 04 09 01 01 02 04 09 01 01 02 04 09 01 01 13 04 09 01 01 13 04 09 01 01 13 04 09 01 01 13 04 09 01 01 13 04 09 01 01 13 04 09 01 01 13 04 09 01 01 13 04 09 01 01 24 04 09 01 01 24 04 09 01 01 24 04 09 01 01 24 04 09 01 01"
bare2 = "00 00 00 01 01 43 12 00 01 01"

def to_times(title, input_string):
   print(title)
   inputs = [int(x, 16) for x in input_string.split(" ")]
   while len(inputs) > 4:
       date = inputs[:5]
       inputs = inputs[5:]
       seconds = (date[0] & 0b00001111) + 10* ((date[0] & 0b01110000) >> 4)
       minutes = (date[1] & 0b00001111) + 10* ((date[1] & 0b01110000) >> 4)

       # ampm indicates if this is set to AM/PM mode
       ampm = date[2] & 0b01000000
       hours = (date[2] & 0b00001111) +  10 * ((date[2] & 0b00010000) >> 4)
       if ampm and (date[2] & 0b00100000):
           hours += 12
       if not ampm and (date[2] & 0b00100000):
           hours += 10

       day = date[3]
       print(f"  {day} {hours}:{seconds}:{minutes}")


to_times("bare", bare)
to_times("bare take 2", bare2)
to_times("ferrule", ferrule)
to_times("ferrule take 2", ferrule2)
to_times("soldered", soldered)
to_times("soldered take 2", soldered2)
to_times("folded", folded)
to_times("folded take 2", folded2)

And the output:

bare
  7 0:63:63
  7 0:32:8
  7 18:21:2
bare take 2
  1 0:0:0
  1 0:43:12
ferrule
  3 0:63:63
  3 0:63:63
ferrule take 2
  1 0:0:0
  1 2:16:25
soldered
  3 0:63:63
  3 0:9:1
soldered take 2
  1 0:0:0
  1 0:16:7
folded
  3 0:63:63
  3 8:28:52
  3 8:34:52
  3 8:40:52
  3 8:45:52
  3 8:51:52
  3 8:57:52
  3 8:2:53
  3 8:8:53
  3 8:14:53
  3 8:19:53
  3 8:25:53
  3 8:31:53
  3 8:37:53
  3 8:42:53
  3 8:48:53
  3 8:54:53
  3 8:59:53
  3 8:5:54
  3 8:11:54
  3 8:16:54
  3 8:22:54
  3 8:28:54
  3 8:33:54
  3 8:39:54
  3 8:39:54
  3 8:45:54
  3 8:50:54
  3 8:56:54
  3 8:2:55
folded take 2
  1 0:0:0
  1 9:38:3
  1 9:39:3
  1 9:39:3
  1 9:39:3
  1 9:44:3
  1 9:51:3
  1 9:51:3
  1 9:51:3
  1 9:51:3
  1 9:51:3
  1 9:51:3
  1 9:2:4
  1 9:2:4
  1 9:2:4
  1 9:2:4
  1 9:2:4
  1 9:2:4
  1 9:13:4
  1 9:13:4
  1 9:13:4
  1 9:13:4
  1 9:13:4
  1 9:13:4
  1 9:13:4
  1 9:13:4
  1 9:24:4
  1 9:24:4
  1 9:24:4
  1 9:24:4