diff options
author | Kevin O'Connor <kevin@koconnor.net> | 2016-05-25 11:37:40 -0400 |
---|---|---|
committer | Kevin O'Connor <kevin@koconnor.net> | 2016-05-25 11:37:40 -0400 |
commit | f582a36e4df16d5709943f7df17a900c8bcc12ab (patch) | |
tree | 628d927c4f3e19e54618f7f47c7a44af66bf0c2f /src/avr | |
parent | 37a91e9c10648208de002c75df304e23ca89e256 (diff) | |
download | kutter-f582a36e4df16d5709943f7df17a900c8bcc12ab.tar.gz kutter-f582a36e4df16d5709943f7df17a900c8bcc12ab.tar.xz kutter-f582a36e4df16d5709943f7df17a900c8bcc12ab.zip |
Initial commit of source code.
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
Diffstat (limited to 'src/avr')
-rw-r--r-- | src/avr/Kconfig | 64 | ||||
-rw-r--r-- | src/avr/Makefile | 19 | ||||
-rw-r--r-- | src/avr/alloc.c | 25 | ||||
-rw-r--r-- | src/avr/gpio.c | 337 | ||||
-rw-r--r-- | src/avr/gpio.h | 42 | ||||
-rw-r--r-- | src/avr/irq.h | 29 | ||||
-rw-r--r-- | src/avr/main.c | 17 | ||||
-rw-r--r-- | src/avr/misc.h | 25 | ||||
-rw-r--r-- | src/avr/pgm.h | 25 | ||||
-rw-r--r-- | src/avr/serial.c | 137 | ||||
-rw-r--r-- | src/avr/timer.c | 171 | ||||
-rw-r--r-- | src/avr/timer.h | 12 | ||||
-rw-r--r-- | src/avr/watchdog.c | 38 |
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); |