aboutsummaryrefslogtreecommitdiffstats
path: root/src/avr
diff options
context:
space:
mode:
Diffstat (limited to 'src/avr')
-rw-r--r--src/avr/Kconfig64
-rw-r--r--src/avr/Makefile19
-rw-r--r--src/avr/alloc.c25
-rw-r--r--src/avr/gpio.c337
-rw-r--r--src/avr/gpio.h42
-rw-r--r--src/avr/irq.h29
-rw-r--r--src/avr/main.c17
-rw-r--r--src/avr/misc.h25
-rw-r--r--src/avr/pgm.h25
-rw-r--r--src/avr/serial.c137
-rw-r--r--src/avr/timer.c171
-rw-r--r--src/avr/timer.h12
-rw-r--r--src/avr/watchdog.c38
13 files changed, 941 insertions, 0 deletions
diff --git a/src/avr/Kconfig b/src/avr/Kconfig
new file mode 100644
index 00000000..e0964676
--- /dev/null
+++ b/src/avr/Kconfig
@@ -0,0 +1,64 @@
+# Kconfig settings for AVR processors
+
+if MACH_AVR
+
+config BOARD_DIRECTORY
+ string
+ default "avr"
+
+choice
+ prompt "Processor model"
+ config MACH_atmega168
+ bool "atmega168"
+ config MACH_atmega644p
+ bool "atmega644p"
+ config MACH_atmega1280
+ bool "atmega1280"
+ config MACH_atmega2560
+ bool "atmega2560"
+endchoice
+
+config MCU
+ string
+ default "atmega168" if MACH_atmega168
+ default "atmega644p" if MACH_atmega644p
+ default "atmega1280" if MACH_atmega1280
+ default "atmega2560" if MACH_atmega2560
+
+choice
+ prompt "Processor speed"
+ config AVR_FREQ_8000000
+ bool "8Mhz"
+ config AVR_FREQ_16000000
+ bool "16Mhz"
+ config AVR_FREQ_20000000
+ bool "20Mhz"
+endchoice
+
+config CLOCK_FREQ
+ int
+ default 8000000 if AVR_FREQ_8000000
+ default 16000000 if AVR_FREQ_16000000
+ default 20000000 if AVR_FREQ_20000000
+
+config AVR_STACK_SIZE
+ int
+ default 256 if MACH_atmega2560
+ default 128
+
+config AVR_WATCHDOG
+ bool "Support for automated reset on watchdog timeout"
+ default y
+config AVR_SERIAL
+ bool
+ default y
+config SERIAL_BAUD
+ depends on AVR_SERIAL
+ int "Baud rate for serial port"
+ default 250000
+config SERIAL_BAUD_U2X
+ depends on AVR_SERIAL
+ bool "Use AVR Baud 2X mode"
+ default y
+
+endif
diff --git a/src/avr/Makefile b/src/avr/Makefile
new file mode 100644
index 00000000..758443da
--- /dev/null
+++ b/src/avr/Makefile
@@ -0,0 +1,19 @@
+# Additional avr build rules
+
+# Use the avr toolchain
+CROSS_PREFIX=avr-
+
+CFLAGS-y += -mmcu=$(CONFIG_MCU) -DF_CPU=$(CONFIG_CLOCK_FREQ)
+LDFLAGS-y += -Wl,--relax
+
+# Add avr source files
+src-y += avr/main.c avr/timer.c avr/gpio.c avr/alloc.c
+src-$(CONFIG_AVR_WATCHDOG) += avr/watchdog.c
+src-$(CONFIG_AVR_SERIAL) += avr/serial.c
+
+# Build the additional hex output file
+target-y += $(OUT)klipper.elf.hex
+
+$(OUT)klipper.elf.hex: $(OUT)klipper.elf
+ @echo " Creating hex file $@"
+ $(Q)$(OBJCOPY) -j .text -j .data -O ihex $< $@
diff --git a/src/avr/alloc.c b/src/avr/alloc.c
new file mode 100644
index 00000000..aaa671bc
--- /dev/null
+++ b/src/avr/alloc.c
@@ -0,0 +1,25 @@
+// AVR allocation checking code.
+//
+// Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
+//
+// This file may be distributed under the terms of the GNU GPLv3 license.
+
+#include <avr/io.h> // AVR_STACK_POINTER_REG
+#include <stdlib.h> // __malloc_heap_end
+#include "autoconf.h" // CONFIG_AVR_STACK_SIZE
+#include "compiler.h" // ALIGN
+#include "misc.h" // alloc_maxsize
+
+size_t
+alloc_maxsize(size_t reqsize)
+{
+ uint16_t memend = ALIGN(AVR_STACK_POINTER_REG, 256);
+ __malloc_heap_end = (void*)memend - CONFIG_AVR_STACK_SIZE;
+ extern char *__brkval;
+ int16_t maxsize = __malloc_heap_end - __brkval - 2;
+ if (maxsize < 0)
+ return 0;
+ if (reqsize < maxsize)
+ return reqsize;
+ return maxsize;
+}
diff --git a/src/avr/gpio.c b/src/avr/gpio.c
new file mode 100644
index 00000000..d2cdfbd8
--- /dev/null
+++ b/src/avr/gpio.c
@@ -0,0 +1,337 @@
+// GPIO functions on AVR.
+//
+// Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
+//
+// This file may be distributed under the terms of the GNU GPLv3 license.
+
+#include <stddef.h> // NULL
+#include "autoconf.h" // CONFIG_MACH_atmega644p
+#include "command.h" // shutdown
+#include "gpio.h" // gpio_out_write
+#include "irq.h" // irq_save
+#include "pgm.h" // PROGMEM
+#include "sched.h" // DECL_INIT
+
+
+/****************************************************************
+ * AVR chip definitions
+ ****************************************************************/
+
+#define GPIO(PORT, NUM) (((PORT)-'A') * 8 + (NUM))
+#define GPIO2PORT(PIN) ((PIN) / 8)
+#define GPIO2BIT(PIN) (1<<((PIN) % 8))
+
+static volatile uint8_t * const digital_regs[] PROGMEM = {
+#ifdef PINA
+ &PINA,
+#else
+ NULL,
+#endif
+ &PINB, &PINC, &PIND,
+#ifdef PINE
+ &PINE, &PINF, &PING, &PINH, NULL, &PINJ, &PINK, &PINL
+#endif
+};
+
+struct gpio_digital_regs {
+ // gcc (pre v6) does better optimization when uint8_t are bitfields
+ volatile uint8_t in : 8, mode : 8, out : 8;
+};
+
+#define GPIO2REGS(pin) \
+ ((struct gpio_digital_regs*)READP(digital_regs[GPIO2PORT(pin)]))
+
+struct gpio_pwm_info {
+ volatile void *ocr;
+ volatile uint8_t *rega, *regb;
+ uint8_t en_bit, pin, flags;
+};
+
+enum { GP_8BIT=1, GP_AFMT=2 };
+
+static const struct gpio_pwm_info pwm_regs[] PROGMEM = {
+#if CONFIG_MACH_atmega168
+ { &OCR0A, &TCCR0A, &TCCR0B, 1<<COM0A1, GPIO('D', 6), GP_8BIT },
+ { &OCR0B, &TCCR0A, &TCCR0B, 1<<COM0B1, GPIO('D', 5), GP_8BIT },
+// { &OCR1A, &TCCR1A, &TCCR1B, 1<<COM1A1, GPIO('B', 1), 0 },
+// { &OCR1B, &TCCR1A, &TCCR1B, 1<<COM1B1, GPIO('B', 2), 0 },
+ { &OCR2A, &TCCR2A, &TCCR2B, 1<<COM2A1, GPIO('B', 3), GP_8BIT|GP_AFMT },
+ { &OCR2B, &TCCR2A, &TCCR2B, 1<<COM2B1, GPIO('D', 3), GP_8BIT|GP_AFMT },
+#elif CONFIG_MACH_atmega644p
+ { &OCR0A, &TCCR0A, &TCCR0B, 1<<COM0A1, GPIO('B', 3), GP_8BIT },
+ { &OCR0B, &TCCR0A, &TCCR0B, 1<<COM0B1, GPIO('B', 4), GP_8BIT },
+// { &OCR1A, &TCCR1A, &TCCR1B, 1<<COM1A1, GPIO('D', 5), 0 },
+// { &OCR1B, &TCCR1A, &TCCR1B, 1<<COM1B1, GPIO('D', 4), 0 },
+ { &OCR2A, &TCCR2A, &TCCR2B, 1<<COM2A1, GPIO('D', 7), GP_8BIT|GP_AFMT },
+ { &OCR2B, &TCCR2A, &TCCR2B, 1<<COM2B1, GPIO('D', 6), GP_8BIT|GP_AFMT },
+#elif CONFIG_MACH_atmega1280 || CONFIG_MACH_atmega2560
+ { &OCR0A, &TCCR0A, &TCCR0B, 1<<COM0A1, GPIO('B', 7), GP_8BIT },
+ { &OCR0B, &TCCR0A, &TCCR0B, 1<<COM0B1, GPIO('G', 5), GP_8BIT },
+// { &OCR1A, &TCCR1A, &TCCR1B, 1<<COM1A1, GPIO('B', 5), 0 },
+// { &OCR1B, &TCCR1A, &TCCR1B, 1<<COM1B1, GPIO('B', 6), 0 },
+// { &OCR1C, &TCCR1A, &TCCR1B, 1<<COM1C1, GPIO('B', 7), 0 },
+ { &OCR2A, &TCCR2A, &TCCR2B, 1<<COM2A1, GPIO('B', 4), GP_8BIT|GP_AFMT },
+ { &OCR2B, &TCCR2A, &TCCR2B, 1<<COM2B1, GPIO('H', 6), GP_8BIT|GP_AFMT },
+ { &OCR3A, &TCCR3A, &TCCR3B, 1<<COM3A1, GPIO('E', 3), 0 },
+ { &OCR3B, &TCCR3A, &TCCR3B, 1<<COM3B1, GPIO('E', 4), 0 },
+ { &OCR3C, &TCCR3A, &TCCR3B, 1<<COM3C1, GPIO('E', 5), 0 },
+ { &OCR4A, &TCCR4A, &TCCR4B, 1<<COM4A1, GPIO('H', 3), 0 },
+ { &OCR4B, &TCCR4A, &TCCR4B, 1<<COM4B1, GPIO('H', 4), 0 },
+ { &OCR4C, &TCCR4A, &TCCR4B, 1<<COM4C1, GPIO('H', 5), 0 },
+ { &OCR5A, &TCCR5A, &TCCR5B, 1<<COM5A1, GPIO('L', 3), 0 },
+ { &OCR5B, &TCCR5A, &TCCR5B, 1<<COM5B1, GPIO('L', 4), 0 },
+ { &OCR5C, &TCCR5A, &TCCR5B, 1<<COM5C1, GPIO('L', 5), 0 },
+#endif
+};
+
+struct gpio_adc_info {
+ uint8_t pin;
+};
+
+static const struct gpio_adc_info adc_pins[] PROGMEM = {
+#if CONFIG_MACH_atmega168
+ { GPIO('C', 0) }, { GPIO('C', 1) }, { GPIO('C', 2) }, { GPIO('C', 3) },
+ { GPIO('C', 4) }, { GPIO('C', 5) }, { GPIO('E', 0) }, { GPIO('E', 1) },
+#elif CONFIG_MACH_atmega644p
+ { GPIO('A', 0) }, { GPIO('A', 1) }, { GPIO('A', 2) }, { GPIO('A', 3) },
+ { GPIO('A', 4) }, { GPIO('A', 5) }, { GPIO('A', 6) }, { GPIO('A', 7) },
+#elif CONFIG_MACH_atmega1280 || CONFIG_MACH_atmega2560
+ { GPIO('F', 0) }, { GPIO('F', 1) }, { GPIO('F', 2) }, { GPIO('F', 3) },
+ { GPIO('F', 4) }, { GPIO('F', 5) }, { GPIO('F', 6) }, { GPIO('F', 7) },
+ { GPIO('K', 0) }, { GPIO('K', 1) }, { GPIO('K', 2) }, { GPIO('K', 3) },
+ { GPIO('K', 4) }, { GPIO('K', 5) }, { GPIO('K', 6) }, { GPIO('K', 7) },
+#endif
+};
+
+#if CONFIG_MACH_atmega168
+static const uint8_t SS = GPIO('B', 2), SCK = GPIO('B', 5), MOSI = GPIO('B', 3);
+#elif CONFIG_MACH_atmega644p
+static const uint8_t SS = GPIO('B', 4), SCK = GPIO('B', 7), MOSI = GPIO('B', 5);
+#elif CONFIG_MACH_atmega1280 || CONFIG_MACH_atmega2560
+static const uint8_t SS = GPIO('B', 0), SCK = GPIO('B', 1), MOSI = GPIO('B', 2);
+#endif
+
+static const uint8_t ADMUX_DEFAULT = 0x40;
+
+
+/****************************************************************
+ * gpio functions
+ ****************************************************************/
+
+struct gpio_out
+gpio_out_setup(uint8_t pin, uint8_t val)
+{
+ if (GPIO2PORT(pin) > ARRAY_SIZE(digital_regs))
+ goto fail;
+ struct gpio_digital_regs *regs = GPIO2REGS(pin);
+ if (! regs)
+ goto fail;
+ uint8_t bit = GPIO2BIT(pin);
+ uint8_t flag = irq_save();
+ regs->out = val ? (regs->out | bit) : (regs->out & ~bit);
+ regs->mode |= bit;
+ irq_restore(flag);
+ return (struct gpio_out){ .regs=regs, .bit=bit };
+fail:
+ shutdown("Not an output pin");
+}
+
+void gpio_out_toggle(struct gpio_out g)
+{
+ g.regs->in = g.bit;
+}
+
+void
+gpio_out_write(struct gpio_out g, uint8_t val)
+{
+ uint8_t flag = irq_save();
+ g.regs->out = val ? (g.regs->out | g.bit) : (g.regs->out & ~g.bit);
+ irq_restore(flag);
+}
+
+struct gpio_in
+gpio_in_setup(uint8_t pin, int8_t pull_up)
+{
+ if (GPIO2PORT(pin) > ARRAY_SIZE(digital_regs))
+ goto fail;
+ struct gpio_digital_regs *regs = GPIO2REGS(pin);
+ if (! regs)
+ goto fail;
+ uint8_t bit = GPIO2BIT(pin);
+ uint8_t flag = irq_save();
+ regs->out = pull_up > 0 ? (regs->out | bit) : (regs->out & ~bit);
+ regs->mode &= ~bit;
+ irq_restore(flag);
+ return (struct gpio_in){ .regs=regs, .bit=bit };
+fail:
+ shutdown("Not an input pin");
+}
+
+uint8_t
+gpio_in_read(struct gpio_in g)
+{
+ return !!(g.regs->in & g.bit);
+}
+
+
+void
+gpio_pwm_write(struct gpio_pwm g, uint8_t val)
+{
+ if (g.size8) {
+ *(volatile uint8_t*)g.reg = val;
+ } else {
+ uint8_t flag = irq_save();
+ *(volatile uint16_t*)g.reg = val;
+ irq_restore(flag);
+ }
+}
+
+struct gpio_pwm
+gpio_pwm_setup(uint8_t pin, uint32_t cycle_time, uint8_t val)
+{
+ uint8_t chan;
+ for (chan=0; chan<ARRAY_SIZE(pwm_regs); chan++) {
+ const struct gpio_pwm_info *p = &pwm_regs[chan];
+ if (READP(p->pin) != pin)
+ continue;
+ uint8_t flags = READP(p->flags), cs;
+ if (flags & GP_AFMT) {
+ switch (cycle_time) {
+ case 0 ... 8*510L - 1: cs = 1; break;
+ case 8*510L ... 32*510L - 1: cs = 2; break;
+ case 32*510L ... 64*510L - 1: cs = 3; break;
+ case 64*510L ... 128*510L - 1: cs = 4; break;
+ case 128*510L ... 256*510L - 1: cs = 5; break;
+ case 256*510L ... 1024*510L - 1: cs = 6; break;
+ default: cs = 7; break;
+ }
+ } else {
+ switch (cycle_time) {
+ case 0 ... 8*510L - 1: cs = 1; break;
+ case 8*510L ... 64*510L - 1: cs = 2; break;
+ case 64*510L ... 256*510L - 1: cs = 3; break;
+ case 256*510L ... 1024*510L - 1: cs = 4; break;
+ default: cs = 5; break;
+ }
+ }
+ volatile uint8_t *rega = READP(p->rega), *regb = READP(p->regb);
+ uint8_t en_bit = READP(p->en_bit);
+ struct gpio_digital_regs *regs = GPIO2REGS(pin);
+ uint8_t bit = GPIO2BIT(pin);
+ struct gpio_pwm g = (struct gpio_pwm) {
+ (void*)READP(p->ocr), flags & GP_8BIT };
+
+ // Setup PWM timer
+ uint8_t flag = irq_save();
+ uint8_t old_cs = *regb & 0x07;
+ if (old_cs && old_cs != cs)
+ shutdown("PWM already programmed at different speed");
+ *regb = cs;
+
+ // Set default value and enable output
+ gpio_pwm_write(g, val);
+ *rega |= (1<<WGM00) | en_bit;
+ regs->mode |= bit;
+ irq_restore(flag);
+
+ return g;
+ }
+ shutdown("Not a valid PWM pin");
+}
+
+
+struct gpio_adc
+gpio_adc_setup(uint8_t pin)
+{
+ uint8_t chan;
+ for (chan=0; chan<ARRAY_SIZE(adc_pins); chan++) {
+ const struct gpio_adc_info *a = &adc_pins[chan];
+ if (READP(a->pin) != pin)
+ continue;
+
+ // Enable ADC
+ ADCSRA |= (1<<ADPS0)|(1<<ADPS1)|(1<<ADPS2)|(1<<ADEN);
+
+ // Disable digital input for this pin
+#ifdef DIDR2
+ if (chan >= 8)
+ DIDR2 |= 1 << (chan & 0x07);
+ else
+#endif
+ DIDR0 |= 1 << chan;
+
+ return (struct gpio_adc){ chan };
+ }
+ shutdown("Not a valid ADC pin");
+}
+
+uint32_t
+gpio_adc_sample_time(void)
+{
+ return (13 + 1) * 128 + 200;
+}
+
+enum { ADC_DUMMY=0xff };
+static uint8_t last_analog_read = ADC_DUMMY;
+
+uint8_t
+gpio_adc_sample(struct gpio_adc g)
+{
+ if (ADCSRA & (1<<ADSC))
+ // Busy
+ return 1;
+ if (last_analog_read == g.chan)
+ // Sample now ready
+ return 0;
+ if (last_analog_read != ADC_DUMMY)
+ // Sample on another channel in progress
+ return 1;
+ last_analog_read = g.chan;
+
+#if defined(ADCSRB) && defined(MUX5)
+ // the MUX5 bit of ADCSRB selects whether we're reading from channels
+ // 0 to 7 (MUX5 low) or 8 to 15 (MUX5 high).
+ ADCSRB = (ADCSRB & ~(1 << MUX5)) | (((g.chan >> 3) & 0x01) << MUX5);
+#endif
+
+ ADMUX = ADMUX_DEFAULT | (g.chan & 0x07);
+
+ // start the conversion
+ ADCSRA |= 1<<ADSC;
+ return 1;
+}
+
+void
+gpio_adc_clear_sample(struct gpio_adc g)
+{
+ if (last_analog_read == g.chan)
+ last_analog_read = ADC_DUMMY;
+}
+
+uint16_t
+gpio_adc_read(struct gpio_adc g)
+{
+ last_analog_read = ADC_DUMMY;
+ return ADC;
+}
+
+
+void
+spi_config(void)
+{
+ gpio_out_setup(SS, 1);
+ gpio_out_setup(SCK, 0);
+ gpio_out_setup(MOSI, 0);
+ SPCR = (1<<MSTR) | (1<<SPE);
+}
+
+void
+spi_transfer(char *data, uint8_t len)
+{
+ while (len--) {
+ SPDR = *data;
+ while (!(SPSR & (1<<SPIF)))
+ ;
+ *data++ = SPDR;
+ }
+}
diff --git a/src/avr/gpio.h b/src/avr/gpio.h
new file mode 100644
index 00000000..e6cbbd32
--- /dev/null
+++ b/src/avr/gpio.h
@@ -0,0 +1,42 @@
+#ifndef __AVR_GPIO_H
+#define __AVR_GPIO_H
+
+#include <stdint.h>
+#include "compiler.h" // __always_inline
+
+struct gpio_out {
+ struct gpio_digital_regs *regs;
+ // gcc (pre v6) does better optimization when uint8_t are bitfields
+ uint8_t bit : 8;
+};
+struct gpio_out gpio_out_setup(uint8_t pin, uint8_t val);
+void gpio_out_toggle(struct gpio_out g);
+void gpio_out_write(struct gpio_out g, uint8_t val);
+
+struct gpio_in {
+ struct gpio_digital_regs *regs;
+ uint8_t bit;
+};
+struct gpio_in gpio_in_setup(uint8_t pin, int8_t pull_up);
+uint8_t gpio_in_read(struct gpio_in g);
+
+struct gpio_pwm {
+ void *reg;
+ uint8_t size8;
+};
+struct gpio_pwm gpio_pwm_setup(uint8_t pin, uint32_t cycle_time, uint8_t val);
+void gpio_pwm_write(struct gpio_pwm g, uint8_t val);
+
+struct gpio_adc {
+ uint8_t chan;
+};
+struct gpio_adc gpio_adc_setup(uint8_t pin);
+uint32_t gpio_adc_sample_time(void);
+uint8_t gpio_adc_sample(struct gpio_adc g);
+void gpio_adc_clear_sample(struct gpio_adc g);
+uint16_t gpio_adc_read(struct gpio_adc g);
+
+void spi_config(void);
+void spi_transfer(char *data, uint8_t len);
+
+#endif // gpio.h
diff --git a/src/avr/irq.h b/src/avr/irq.h
new file mode 100644
index 00000000..bfc6cb51
--- /dev/null
+++ b/src/avr/irq.h
@@ -0,0 +1,29 @@
+#ifndef __AVR_IRQ_H
+#define __AVR_IRQ_H
+// Definitions for irq enable/disable on AVR
+
+#include <avr/interrupt.h> // cli
+#include "compiler.h" // barrier
+
+static inline void irq_disable(void) {
+ cli();
+ barrier();
+}
+
+static inline void irq_enable(void) {
+ barrier();
+ sei();
+}
+
+static inline uint8_t irq_save(void) {
+ uint8_t flag = SREG;
+ irq_disable();
+ return flag;
+}
+
+static inline void irq_restore(uint8_t flag) {
+ barrier();
+ SREG = flag;
+}
+
+#endif // irq.h
diff --git a/src/avr/main.c b/src/avr/main.c
new file mode 100644
index 00000000..7651ab4a
--- /dev/null
+++ b/src/avr/main.c
@@ -0,0 +1,17 @@
+// Main starting point for AVR boards.
+//
+// Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
+//
+// This file may be distributed under the terms of the GNU GPLv3 license.
+
+#include "irq.h" // irq_enable
+#include "sched.h" // sched_main
+
+// Main entry point for avr code.
+int
+main(void)
+{
+ irq_enable();
+ sched_main();
+ return 0;
+}
diff --git a/src/avr/misc.h b/src/avr/misc.h
new file mode 100644
index 00000000..244cd5bc
--- /dev/null
+++ b/src/avr/misc.h
@@ -0,0 +1,25 @@
+#ifndef __AVR_MISC_H
+#define __AVR_MISC_H
+
+#include <stdint.h>
+#include <util/crc16.h>
+
+// alloc.c
+size_t alloc_maxsize(size_t reqsize);
+
+// console.c
+char *console_get_input(uint8_t *plen);
+void console_pop_input(uint8_t len);
+char *console_get_output(uint8_t len);
+void console_push_output(uint8_t len);
+
+// Optimized crc16_ccitt for the avr processor
+#define HAVE_OPTIMIZED_CRC 1
+static inline uint16_t _crc16_ccitt(char *buf, uint8_t len) {
+ uint16_t crc = 0xFFFF;
+ while (len--)
+ crc = _crc_ccitt_update(crc, *buf++);
+ return crc;
+}
+
+#endif // misc.h
diff --git a/src/avr/pgm.h b/src/avr/pgm.h
new file mode 100644
index 00000000..ba68d8f9
--- /dev/null
+++ b/src/avr/pgm.h
@@ -0,0 +1,25 @@
+#ifndef __AVR_PGM_H
+#define __AVR_PGM_H
+// This header provides the avr/pgmspace.h definitions for "PROGMEM"
+// on AVR platforms.
+
+#include <avr/pgmspace.h>
+
+#define READP(VAR) ({ \
+ _Pragma("GCC diagnostic push"); \
+ _Pragma("GCC diagnostic ignored \"-Wint-to-pointer-cast\""); \
+ typeof(VAR) __val = \
+ __builtin_choose_expr(sizeof(VAR) == 1, \
+ (typeof(VAR))pgm_read_byte(&(VAR)), \
+ __builtin_choose_expr(sizeof(VAR) == 2, \
+ (typeof(VAR))pgm_read_word(&(VAR)), \
+ __builtin_choose_expr(sizeof(VAR) == 4, \
+ (typeof(VAR))pgm_read_dword(&(VAR)), \
+ __force_link_error__unknown_type))); \
+ _Pragma("GCC diagnostic pop"); \
+ __val; \
+ })
+
+extern void __force_link_error__unknown_type(void);
+
+#endif // pgm.h
diff --git a/src/avr/serial.c b/src/avr/serial.c
new file mode 100644
index 00000000..c73890dd
--- /dev/null
+++ b/src/avr/serial.c
@@ -0,0 +1,137 @@
+// AVR serial port code.
+//
+// Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
+//
+// This file may be distributed under the terms of the GNU GPLv3 license.
+
+#include <avr/interrupt.h> // USART0_RX_vect
+#include <string.h> // memmove
+#include "autoconf.h" // CONFIG_SERIAL_BAUD
+#include "sched.h" // DECL_INIT
+#include "irq.h" // irq_save
+#include "misc.h" // console_get_input
+
+#define SERIAL_BUFFER_SIZE 96
+static char receive_buf[SERIAL_BUFFER_SIZE];
+static uint8_t receive_pos;
+static char transmit_buf[SERIAL_BUFFER_SIZE];
+static uint8_t transmit_pos, transmit_max;
+
+
+/****************************************************************
+ * Serial hardware
+ ****************************************************************/
+
+static void
+serial_init(void)
+{
+ if (CONFIG_SERIAL_BAUD_U2X) {
+ UCSR0A = 1<<U2X0;
+ UBRR0 = DIV_ROUND_CLOSEST(F_CPU, 8UL * CONFIG_SERIAL_BAUD) - 1UL;
+ } else {
+ UCSR0A = 0;
+ UBRR0 = DIV_ROUND_CLOSEST(F_CPU, 16UL * CONFIG_SERIAL_BAUD) - 1UL;
+ }
+
+ UCSR0C = (1<<UCSZ01) | (1<<UCSZ00);
+ UCSR0B = (1<<RXEN0) | (1<<TXEN0) | (1<<RXCIE0) | (1<<UDRIE0);
+}
+DECL_INIT(serial_init);
+
+#ifdef USART_RX_vect
+#define USART0_RX_vect USART_RX_vect
+#define USART0_UDRE_vect USART_UDRE_vect
+#endif
+
+// Rx interrupt - data available to be read.
+ISR(USART0_RX_vect)
+{
+ uint8_t data = UDR0;
+ if (receive_pos >= sizeof(receive_buf))
+ // Serial overflow - ignore it as crc error will force retransmit
+ return;
+ receive_buf[receive_pos++] = data;
+}
+
+// Tx interrupt - data can be written to serial.
+ISR(USART0_UDRE_vect)
+{
+ if (transmit_pos >= transmit_max)
+ UCSR0B &= ~(1<<UDRIE0);
+ else
+ UDR0 = transmit_buf[transmit_pos++];
+}
+
+
+/****************************************************************
+ * Console access functions
+ ****************************************************************/
+
+// Return a buffer (and length) containing any incoming messages
+char *
+console_get_input(uint8_t *plen)
+{
+ *plen = readb(&receive_pos);
+ return receive_buf;
+}
+
+// Remove from the receive buffer the given number of bytes
+void
+console_pop_input(uint8_t len)
+{
+ uint8_t copied = 0;
+ for (;;) {
+ uint8_t rpos = readb(&receive_pos);
+ uint8_t needcopy = rpos - len;
+ if (needcopy) {
+ memmove(&receive_buf[copied], &receive_buf[copied + len]
+ , needcopy - copied);
+ copied = needcopy;
+ }
+ uint8_t flag = irq_save();
+ if (rpos != readb(&receive_pos)) {
+ // Raced with irq handler - retry
+ irq_restore(flag);
+ continue;
+ }
+ receive_pos = needcopy;
+ irq_restore(flag);
+ break;
+ }
+}
+
+// Return an output buffer that the caller may fill with transmit messages
+char *
+console_get_output(uint8_t len)
+{
+ uint8_t tpos = readb(&transmit_pos), tmax = readb(&transmit_max);
+ if (tpos == tmax) {
+ tpos = tmax = 0;
+ writeb(&transmit_max, 0);
+ writeb(&transmit_pos, 0);
+ }
+ if (tmax + len <= sizeof(transmit_buf))
+ return &transmit_buf[tmax];
+ if (tmax - tpos + len > sizeof(transmit_buf))
+ return NULL;
+ // Disable TX irq and move buffer
+ writeb(&transmit_max, 0);
+ barrier();
+ tpos = readb(&transmit_pos);
+ tmax -= tpos;
+ memmove(&transmit_buf[0], &transmit_buf[tpos], tmax);
+ writeb(&transmit_pos, 0);
+ barrier();
+ writeb(&transmit_max, tmax);
+ UCSR0B |= 1<<UDRIE0;
+ return &transmit_buf[tmax];
+}
+
+// Accept the given number of bytes added to the transmit buffer
+void
+console_push_output(uint8_t len)
+{
+ writeb(&transmit_max, readb(&transmit_max) + len);
+ // enable TX interrupt
+ UCSR0B |= 1<<UDRIE0;
+}
diff --git a/src/avr/timer.c b/src/avr/timer.c
new file mode 100644
index 00000000..0b248552
--- /dev/null
+++ b/src/avr/timer.c
@@ -0,0 +1,171 @@
+// AVR timer interrupt scheduling code.
+//
+// Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
+//
+// This file may be distributed under the terms of the GNU GPLv3 license.
+
+#include <avr/interrupt.h> // TCNT1
+#include "command.h" // shutdown
+#include "irq.h" // irq_save
+#include "sched.h" // sched_timer_kick
+#include "timer.h" // timer_from_ms
+
+
+/****************************************************************
+ * Low level timer code
+ ****************************************************************/
+
+// Return the number of clock ticks for a given number of milliseconds
+uint32_t
+timer_from_ms(uint32_t ms)
+{
+ return ms * (F_CPU / 1000);
+}
+
+static inline uint16_t
+timer_get(void)
+{
+ return TCNT1;
+}
+
+static inline void
+timer_set(uint16_t next)
+{
+ OCR1A = next;
+}
+
+static inline void
+timer_set_clear(uint16_t next)
+{
+ OCR1A = next;
+ TIFR1 = 1<<OCF1A;
+}
+
+ISR(TIMER1_COMPA_vect)
+{
+ sched_timer_kick();
+}
+
+static void
+timer_init(void)
+{
+ // no outputs
+ TCCR1A = 0;
+ // Normal Mode
+ TCCR1B = 1<<CS10;
+ // enable interrupt
+ TIMSK1 = 1<<OCIE1A;
+}
+DECL_INIT(timer_init);
+
+
+/****************************************************************
+ * 32bit timer wrappers
+ ****************************************************************/
+
+static uint32_t timer_last;
+
+// Return the 32bit current time given the 16bit current time.
+static __always_inline uint32_t
+calc_time(uint32_t last, uint16_t cur)
+{
+ union u32_u16_u calc;
+ calc.val = last;
+ if (cur < calc.lo)
+ calc.hi++;
+ calc.lo = cur;
+ return calc.val;
+}
+
+// Called by main code once every millisecond. (IRQs disabled.)
+void
+timer_periodic(void)
+{
+ timer_last = calc_time(timer_last, timer_get());
+}
+
+// Return the current time (in absolute clock ticks).
+uint32_t
+timer_read_time(void)
+{
+ uint8_t flag = irq_save();
+ uint16_t cur = timer_get();
+ uint32_t last = timer_last;
+ irq_restore(flag);
+ return calc_time(last, cur);
+}
+
+#define TIMER_MIN_TICKS 100
+
+// Set the next timer wake time (in absolute clock ticks). Caller
+// must disable irqs. The caller should not schedule a time more than
+// a few milliseconds in the future.
+uint8_t
+timer_set_next(uint32_t next)
+{
+ uint16_t cur = timer_get();
+ if ((int16_t)(OCR1A - cur) < 0 && !(TIFR1 & (1<<OCF1A)))
+ // Already processing timer irqs
+ try_shutdown("timer_set_next called during timer dispatch");
+ uint32_t mintime = calc_time(timer_last, cur + TIMER_MIN_TICKS);
+ if (sched_is_before(mintime, next)) {
+ timer_set_clear(next);
+ return 0;
+ }
+ timer_set_clear(mintime);
+ return 1;
+}
+
+static uint8_t timer_repeat;
+#define TIMER_MAX_REPEAT 40
+#define TIMER_MAX_NEXT_REPEAT 15
+
+#define TIMER_MIN_TRY_TICKS 60 // 40 ticks to exit irq; 20 ticks of progress
+#define TIMER_DEFER_REPEAT_TICKS 200
+
+// Similar to timer_set_next(), but wait for the given time if it is
+// in the near future.
+uint8_t
+timer_try_set_next(uint32_t target)
+{
+ uint16_t next = target, now = timer_get();
+ int16_t diff = next - now;
+ if (diff > TIMER_MIN_TRY_TICKS)
+ // Schedule next timer normally.
+ goto done;
+
+ // Next timer is in the past or near future - can't reschedule to it
+ uint8_t tr = timer_repeat-1;
+ if (likely(tr)) {
+ irq_enable();
+ timer_repeat = tr;
+ irq_disable();
+ while (diff >= 0) {
+ // Next timer is in the near future - wait for time to occur
+ now = timer_get();
+ irq_enable();
+ diff = next - now;
+ irq_disable();
+ }
+ return 0;
+ }
+
+ // Too many repeat timers from a single interrupt - force a pause
+ timer_repeat = TIMER_MAX_NEXT_REPEAT;
+ next = now + TIMER_DEFER_REPEAT_TICKS;
+ if (diff < (int16_t)(-timer_from_ms(1)))
+ goto fail;
+
+done:
+ timer_set(next);
+ return 1;
+fail:
+ shutdown("Rescheduled timer in the past");
+}
+
+static void
+timer_task(void)
+{
+ timer_repeat = TIMER_MAX_REPEAT;
+}
+DECL_TASK(timer_task);
diff --git a/src/avr/timer.h b/src/avr/timer.h
new file mode 100644
index 00000000..2165fecd
--- /dev/null
+++ b/src/avr/timer.h
@@ -0,0 +1,12 @@
+#ifndef __AVR_TIMER_H
+#define __AVR_TIMER_H
+
+#include <stdint.h>
+
+uint32_t timer_from_ms(uint32_t ms);
+void timer_periodic(void);
+uint32_t timer_read_time(void);
+uint8_t timer_set_next(uint32_t next);
+uint8_t timer_try_set_next(uint32_t next);
+
+#endif // timer.h
diff --git a/src/avr/watchdog.c b/src/avr/watchdog.c
new file mode 100644
index 00000000..f925a8d5
--- /dev/null
+++ b/src/avr/watchdog.c
@@ -0,0 +1,38 @@
+// Initialization of AVR watchdog timer.
+//
+// Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
+//
+// This file may be distributed under the terms of the GNU GPLv3 license.
+
+#include <avr/interrupt.h> // WDT_vect
+#include <avr/wdt.h> // wdt_enable
+#include "command.h" // shutdown
+#include "sched.h" // DECL_TASK
+
+static uint8_t watchdog_shutdown;
+
+ISR(WDT_vect)
+{
+ watchdog_shutdown = 1;
+ shutdown("Watchdog timer!");
+}
+
+static void
+watchdog_reset(void)
+{
+ wdt_reset();
+ if (watchdog_shutdown) {
+ WDTCSR |= 1<<WDIE;
+ watchdog_shutdown = 0;
+ }
+}
+DECL_TASK(watchdog_reset);
+
+static void
+watchdog_init(void)
+{
+ // 0.5s timeout, interrupt and system reset
+ wdt_enable(WDTO_500MS);
+ WDTCSR |= 1<<WDIE;
+}
+DECL_INIT(watchdog_init);