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
Comments
No comments yet. Be the first to react!