Initial import
authorJan "Yenya" Kasprzak <kas@fi.muni.cz>
Thu, 19 Dec 2013 14:20:26 +0000 (15:20 +0100)
committerJan "Yenya" Kasprzak <kas@fi.muni.cz>
Thu, 19 Dec 2013 14:27:43 +0000 (15:27 +0100)
Makefile [new file with mode: 0644]
scxreader.c [new file with mode: 0644]
uart.c [new file with mode: 0644]
uart.h [new file with mode: 0644]
version.pl [new file with mode: 0755]

diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..a5fb25f
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,72 @@
+
+PROGRAM=scxreader
+SRC=scxreader.c uart.c version.c
+OBJ=$(SRC:.c=.o)
+
+
+MCU=atmega328p
+# AVRDUDE_MCU=$(MCU)
+AVRDUDE_MCU=atmega328p
+AVRDUDE_PROGRAMMER=arduino
+
+CFLAGS=-Wall -Os -mmcu=$(MCU) -DUSE_LOGGING=1 -DF_CPU=16000000UL -std=gnu99
+LDFLAGS=
+AVRDUDE_FLAGS= -p$(AVRDUDE_MCU) -c $(AVRDUDE_PROGRAMMER) -P /dev/ttyUSB0 -b 57600
+
+FORMAT=ihex
+
+CC=avr-gcc
+OBJCOPY=avr-objcopy
+OBJDUMP=avr-objdump
+AVRDUDE=avrdude
+
+all: $(PROGRAM).hex $(PROGRAM).eep
+
+program: $(PROGRAM).hex $(PROGRAM).eep
+       $(AVRDUDE) $(AVRDUDE_FLAGS) -U flash:w:$(PROGRAM).hex:i -U eeprom:w:$(PROGRAM).eep:i
+
+program_flash: $(PROGRAM).hex
+       $(AVRDUDE) $(AVRDUDE_FLAGS) -U flash:w:$(PROGRAM).hex:i
+
+program_eeprom: $(PROGRAM).eep
+       $(AVRDUDE) $(AVRDUDE_FLAGS) eeprom:w:$(PROGRAM).eep:i
+
+dump_eeprom:
+       $(AVRDUDE) $(AVRDUDE_FLAGS) -U eeprom:r:eeprom.raw:r
+       od -tx1 eeprom.raw
+
+objdump: $(PROGRAM).elf
+       $(OBJDUMP) --disassemble $<
+
+.PRECIOUS : $(OBJ) $(PROGRAM).elf
+
+%.hex: %.elf
+       $(OBJCOPY) -O $(FORMAT) -R .eeprom $< $@
+
+%.eep: %.elf
+       $(OBJCOPY) -j .eeprom --set-section-flags=.eeprom="alloc,load" \
+               --change-section-lma .eeprom=0 -O $(FORMAT) $< $@
+
+%.elf: $(OBJ)
+       $(CC) $(CFLAGS) $(OBJ) -o $@ $(LDFLAGS)
+
+%.o: %.c lights.h Makefile
+       $(CC) -c $(CFLAGS) $< -o $@
+
+%.s: %.c lights.h Makefile
+       $(CC) -S -c $(CFLAGS) $< -o $@
+
+%.o: %.S
+       $(CC) -c $(CFLAGS) $< -o $@
+
+clean:
+       rm -f $(PROGRAM).hex $(PROGRAM).eep $(PROGRAM).elf *.o *.s eeprom.raw \
+               version.c
+
+version.c:
+       ./version.pl > version.c
+
+.PHONY: all clean dump_eeprom program program_flash program_eeprom objdump \
+       version.c
+
+
diff --git a/scxreader.c b/scxreader.c
new file mode 100644 (file)
index 0000000..71e1f7e
--- /dev/null
@@ -0,0 +1,175 @@
+#include <avr/io.h>
+#include <avr/interrupt.h>
+#include <util/atomic.h>
+#include <util/delay.h>
+
+#include "uart.h"
+
+// which pin is the zener diode connected to
+#define read_scx_pin() (PINB & _BV(PB2))
+
+// how many consecutive readings have to be the same in order to
+// accept the pin value
+#define DENOISE_COUNT 4
+
+#ifndef F_CPU
+#error "F_CPU not defined. Set F_CPU in Makefile."
+#endif
+
+#define STEP (F_CPU/115200)            // duration of one bit
+#define BYTE_PAUSE_MAX (20*STEP)       // inter-packet length threshold
+
+void tc1_init()
+{
+       TCNT1 = 0;
+       TCCR1B = _BV(CS10); // run at F_CPU clock, no prescaling
+}
+
+// read the value until it is stable, i.e. until DENOISE_COUNT consecutive
+// readings give the same value
+static unsigned char read_scx_pin_denoised()
+{
+       unsigned char prev, count;
+
+       count = 0;
+       prev = read_scx_pin();
+
+       while (count < DENOISE_COUNT) {
+               unsigned char curr = read_scx_pin();
+               if (curr != prev) {
+                       count = 0;
+                       prev = curr;
+               } else {
+                       ++count;
+               }
+       }
+       return prev;
+}
+
+// how long we waited for the start bit
+static unsigned char long_byte_wait;
+
+static unsigned char read_scx_byte()
+{
+       unsigned short curr_time, prev_time, interval;
+       unsigned char data, i, toolong = 0, step;
+
+retry:
+       // wait for 0, distinguish between inter- and intra-packet wait times
+       prev_time = TCNT1;
+
+       long_byte_wait = 0;
+       while (read_scx_pin_denoised()) {
+               curr_time = TCNT1;
+               if (curr_time - prev_time > BYTE_PAUSE_MAX)
+                       long_byte_wait = 1;
+       }
+
+       ATOMIC_BLOCK(ATOMIC_FORCEON) {
+               // wait for 1 (end of the first start-bit),
+               // remember the length of the start bit
+               // in order to distinguish between car and other packets
+               prev_time = curr_time = TCNT1;
+               interval = 0;
+
+               while (!read_scx_pin_denoised()) { // wait for 1
+                       curr_time = TCNT1;
+                       interval = curr_time - prev_time;
+
+                       if (interval >= 4*STEP) { // start bit too long
+                               if (!toolong) {
+                                       uart_tx_byte(0x12, 0);
+                                       toolong = 1;
+                               }
+                               goto retry;
+                       }
+               };
+
+               toolong = 0;
+
+               // start bit too short
+               if (interval < 3*STEP/4) {
+                       uart_tx_byte(0x10, 0);
+                       goto retry;
+               }
+
+               // car packets are at 57600 baud, start bit is twice as long
+               step = interval > (3*STEP/2) ? 2*STEP : STEP;
+
+               // advance into the middle of the next bit
+               curr_time += step / 2;
+
+               // start bit detected, now read the eight data bits
+               data = 0;
+               for (i = 0; i < 8; i++) {
+                       data >>= 1;
+                       while (TCNT1 - curr_time < step)
+                               ;
+                       data |= read_scx_pin_denoised() ? 0x80 : 0;
+                       curr_time += step;
+               }
+
+               // read stop bit
+               while (TCNT1 - curr_time < step)
+                       ;
+
+               if (!read_scx_pin_denoised()) // no stop bit?
+                       uart_tx_byte(0x13, 0); // report error, but return data anyway
+       }; // ATOMIC
+       return data;
+}
+
+extern unsigned char version[], version_len;
+
+static void print_version()
+{
+       unsigned char i;
+
+       for (i = 0; i < version_len; i++)
+               uart_tx_byte(version[i], 0);
+}
+
+int main()
+{
+       unsigned char count = 0, pkt_len = 0;
+
+       uart_init();
+       tc1_init();
+
+       sei();
+
+       _delay_ms(1000);
+       print_version();
+       _delay_ms(500);
+
+       // main loop
+       while (1) {
+               unsigned char byte = read_scx_byte();
+
+               uart_tx_byte(byte, 0);
+
+               count++;
+               count &= 0x7F; // avoid overflow
+
+               // try to synchronize with the packet start
+               // - we need this in order to determine the packet length
+               // and in order to add 0x05 end byte after the packet
+               if (byte == 0x55 && long_byte_wait)
+                       count = 1;
+
+               if (count == 2) {
+                       if (byte >= 0x40 && byte <= 0x45) { // car pkt
+                               pkt_len = 5;
+                       } else {
+                               pkt_len = 9;
+                       }
+               }
+
+               if (count == pkt_len) {
+                       uart_tx_byte(0x05, 0); // SEB interface compatibility
+                       count = 0;
+               }
+       }
+       // NOTREACHED
+}
+
diff --git a/uart.c b/uart.c
new file mode 100644 (file)
index 0000000..265c608
--- /dev/null
+++ b/uart.c
@@ -0,0 +1,113 @@
+/*
+ * Minimalistic TX-only compile-time configured asynchronous buffered
+ * UART communication for Arduino Nano (Atmega 88/168/328).
+ */
+#include <avr/io.h>
+#include <avr/interrupt.h>
+#include <util/atomic.h>
+#include <util/delay.h>
+
+// user settable parameters start
+#define UART_BAUD      115200
+#define TX_BUFFER      (1 << 4)        // this has to be a power of two
+// user settable parameters end
+
+#define TX_BUFFER_MASK (TX_BUFFER-1)
+
+#if (TX_BUFFER & TX_BUFFER_MASK)
+#error TX buffer size TX_BUFFER must be a power of two
+#endif
+
+#ifndef F_CPU
+#error F_CPU not defined, set it in Makefile
+#endif
+
+#define UBRR_VAL        ((F_CPU + 8UL * UART_BAUD) / (16UL*UART_BAUD) - 1)
+
+static volatile unsigned char buffer[TX_BUFFER];
+static volatile unsigned char b_head, b_tail, b_overrun;
+/*
+ * b_head == b_tail: empty queue
+ * b_head + 1 == b_tail: full queue
+ * b_overrun is set but not currently handled in any way
+ */
+
+void uart_init()
+{
+       UBRR0H = (unsigned char)(UBRR_VAL >> 8);
+       UBRR0L = (unsigned char)(UBRR_VAL & 0xFF);
+
+       UCSR0C = _BV(USBS0) | _BV(UCSZ00) | _BV(UCSZ01);
+       UCSR0B = _BV(TXEN0); // enable TX only
+
+       b_head = 0;
+       b_tail = 0;
+       b_overrun = 0;
+}
+
+ISR(USART_UDRE_vect) // tx end irq
+{
+       // maybe handle overrun here?
+       if (b_head == b_tail) { // queue empty
+               UCSR0B &= ~_BV(UDRIE0); // disable IRQ
+       } else {
+               UDR0 = buffer[b_tail];
+               b_tail = (b_tail + 1) & TX_BUFFER_MASK;
+       }
+}
+
+// low-level byte sending
+unsigned char uart_tx_byte(unsigned char byte, unsigned char wait)
+{
+       ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
+       retry:
+               if (((b_head + 1) & TX_BUFFER_MASK) == b_tail) {
+                       if (wait) {
+                               NONATOMIC_BLOCK(NONATOMIC_FORCEOFF) {
+                                       while (((b_head + 1) & TX_BUFFER_MASK)
+                                               == b_tail)
+                                               ;
+                               }
+                               goto retry;
+                       }
+
+                       b_overrun = 1;
+                       return 0;
+               }
+
+               buffer[b_head] = byte;
+               b_head = (b_head + 1) & TX_BUFFER_MASK;
+
+               UCSR0B |= _BV(UDRIE0);
+       }
+       return 1;
+}
+
+// ASCII hex bytes, for debugging purposes
+
+#define hex_nibble(x) ((x) < 10 ? '0' + (x) : 'A' + (x) - 10)
+
+unsigned char uart_tx_hex(unsigned char byte, unsigned char wait)
+{
+       if (
+                  uart_tx_byte(hex_nibble(byte >> 4), wait)
+               && uart_tx_byte(hex_nibble(byte & 0xF), wait)
+       )
+               return 1;
+       else
+               return 0;
+}
+
+unsigned char uart_tx_hex2(unsigned short word, unsigned char wait)
+{
+       if (
+                  uart_tx_byte(hex_nibble((word >> 12) & 0xF), wait)
+               && uart_tx_byte(hex_nibble((word >>  8) & 0xF), wait)
+               && uart_tx_byte(hex_nibble((word >>  4) & 0xF), wait)
+               && uart_tx_byte(hex_nibble( word        & 0xF), wait)
+       )
+               return 1;
+       else
+               return 0;
+}
+
diff --git a/uart.h b/uart.h
new file mode 100644 (file)
index 0000000..c091798
--- /dev/null
+++ b/uart.h
@@ -0,0 +1,10 @@
+#ifndef UART_H__
+#define UART_H__ 1
+
+void uart_init();
+unsigned char uart_tx_byte(unsigned char byte, unsigned char wait);
+unsigned char uart_tx_hex(unsigned char byte, unsigned char wait);
+unsigned char uart_tx_hex2(unsigned short word, unsigned char wait);
+
+#endif /* !UART_H__ */
+
diff --git a/version.pl b/version.pl
new file mode 100755 (executable)
index 0000000..e73dbe5
--- /dev/null
@@ -0,0 +1,31 @@
+#!/usr/bin/perl -w
+
+use strict;
+use POSIX qw(strftime);
+
+my $git = `git rev-parse --short HEAD`;
+chomp $git;
+
+my $now = strftime('%Y%m%d', localtime(time));
+
+print <<EOF;
+/* DO NOT EDIT - GENERATED BY $0 */
+
+unsigned char version[] = {
+EOF
+
+print hex2c($now, "date");
+print hex2c($git, "git revision");
+
+print "};\n\nunsigned char version_len = 8;\n";
+print "\n/* EOF - this file has not been truncated */\n\n";
+
+sub hex2c {
+       my ($data, $comment) = @_;
+
+       my $data1 = $data;
+       $data1 .= '0' if (length($data1) & 1 == 1);
+       $data1 =~ s/(..)/0x$1, /g;
+       return "\t$data1 /* $comment $data */\n";
+}
+