commit aaaec8ff40eaa695a4ade6350973b5e3ceeac84f Author: Gavan Fantom Date: Thu Aug 13 14:43:19 2015 +0000 First commit of partly-working reflow oven controller. diff --git a/.mtn-ignore b/.mtn-ignore new file mode 100644 index 0000000..7f37238 --- /dev/null +++ b/.mtn-ignore @@ -0,0 +1,4 @@ +\.o$ +\.hex$ +\.elf$ +\.elf.map$ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2393fc5 --- /dev/null +++ b/Makefile @@ -0,0 +1,67 @@ +# Makefile + +DEVICE = attiny88 +CLOCK = 4000000 +PROGRAMMER = -c buspirate -P /dev/ttyUSB0 +OBJECTS = reflow.o display.o font.o i2c.o menu.o spi.o therm.o timer.o config.o beep.o control.o fields.o profile.o +SRC = $(OBJECTS:%.o=%.c) +#FUSES = -U lfuse:w:0xEC:m -U hfuse:w:0xDF:m -U efuse:w:0xFF:m -U lock:w:0xFF:m +FUSES = -U lfuse:w:0xEC:m -U hfuse:w:0xDF:m -U efuse:w:0x07:m + +# For computing fuse byte values for other devices and options see +# the fuse bit calculator at http://www.engbedded.com/fusecalc/ + +#AVRDUDE = avrdude $(PROGRAMMER) -p $(DEVICE) +AVRDUDE = avrdude $(PROGRAMMER) -p $(DEVICE) -V +# Don't use -Werror in the link stage to avoid a compiler bug which is fixed in gcc 4.8.3 +CFLAGS = -Werror +COMPILE = avr-gcc -std=c99 -Wall -Wl,-Map,$@.map -Os -g -DF_CPU=$(CLOCK) -mmcu=$(DEVICE) -mcall-prologues -flto -fuse-linker-plugin + +# symbolic targets: +all: reflow.hex + +.c.o: + $(COMPILE) $(CFLAGS) -c $< -o $@ + +.S.o: + $(COMPILE) -x assembler-with-cpp -c $< -o $@ + +.c.s: + $(COMPILE) $(CFLAGS) -S $< -o $@ + +flash: + $(AVRDUDE) -U flash:w:reflow.hex:i + +fuse: + $(AVRDUDE) $(FUSES) + +install: all flash fuse + +# if you use a bootloader, change the command below appropriately: +load: all + bootloadHID reflow.hex + +clean: + rm -f reflow.hex reflow.elf $(OBJECTS) + +# file targets: +#reflow.elf: $(SRC) +# $(COMPILE) $(LDFLAGS) -o reflow.elf $(SRC) + +reflow.elf: $(OBJECTS) + $(COMPILE) $(LDFLAGS) -o reflow.elf $(OBJECTS) + +reflow.hex: reflow.elf + rm -f reflow.hex + avr-objcopy -j .text -j .data -O ihex reflow.elf reflow.hex + avr-size --format=avr --mcu=$(DEVICE) reflow.elf +# If you have an EEPROM section, you must also create a hex file for the +# EEPROM and add it to the "flash" target. + +# Targets for code debugging and analysis: +disasm: reflow.elf + avr-objdump -d reflow.elf + +cpp: + $(COMPILE) -E reflow.c + diff --git a/beep.c b/beep.c new file mode 100644 index 0000000..b0a3f86 --- /dev/null +++ b/beep.c @@ -0,0 +1,24 @@ +/* beep.c */ + +#include + +#include "common.h" + +#define DDR_BEEP DDRB +#define PORT_BEEP PORTB + +#define PIN_BEEP 1 + +void beep_on(void) +{ + TIMSK1 = 0; + TCCR1A = (1< +#include +#include "common.h" + +uint8_t bounce_timer; +uint8_t button; + +#define BOUNCE_TIMER_RESET 10 + +void button_init(void) +{ + bounce_timer = 0; + button = 0; +} + +ISR(TIMER2_vect) +{ + uint8_t port = PORTD; + uint8_t old_button; + port = (~port >> 4); + if (bounce_timer) + bounce_timer--; + else + if (port) + button = 1 + ctz(port); + else + button = 0; + + if (button != old_button) + bounce_timer = BOUNCE_TIMER_RESET; +} diff --git a/button.h b/button.h new file mode 100644 index 0000000..5bef626 --- /dev/null +++ b/button.h @@ -0,0 +1,8 @@ +/* button.h */ + +#ifndef _BUTTON_H_ +#define _BUTTON_H_ + +extern uint8 button; + +#endif diff --git a/common.h b/common.h new file mode 100644 index 0000000..c76a7b1 --- /dev/null +++ b/common.h @@ -0,0 +1,11 @@ +/* common.h */ + +#ifndef _COMMON_H_ +#define _COMMON_H_ + +#include +#include "types.h" + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) + +#endif diff --git a/config.c b/config.c new file mode 100644 index 0000000..6bff43a --- /dev/null +++ b/config.c @@ -0,0 +1,15 @@ +/* config.c */ + +#include "config.h" + +config_t config = { + 127, + 1, /* beep */ + 3, /* thermocouple */ + 0, /* frequency */ + 0, /* units */ + 0x080, + 0x040, + 0xfc0 +}; + diff --git a/config.h b/config.h new file mode 100644 index 0000000..3014bdb --- /dev/null +++ b/config.h @@ -0,0 +1,25 @@ +/* config.h */ + +#ifndef _CONFIG_H_ +#define _CONFIG_H_ + +#include "common.h" + +/* So there's a total of 64 bytes of EEPROM. + * This is going to be a squeeze. Unless we can + * program the profiles into program flash or something. + */ +typedef struct config { + uint8_t contrast; + unsigned int beep:1; + unsigned int thermocouple_type:3; + unsigned int frequency:1; + unsigned int units:4; + signed int p:12; + signed int i:12; + signed int d:12; +} config_t; + +extern config_t config; + +#endif diff --git a/control.c b/control.c new file mode 100644 index 0000000..c5231c9 --- /dev/null +++ b/control.c @@ -0,0 +1,122 @@ +/* control.c */ + +#include + +#include "common.h" +#include "timer.h" +#include "config.h" +#include "therm.h" +#include "beep.h" + +static temp_t last_temp; +static volatile bool preheat; +static bool control_running; +static uint32_t control_start_time; +static int16_t integral; +static int16_t control_output; + +temp_t temperature_at_time(uint32_t time); + +int16_t __attribute__ ((noinline)) clip16(int16_t val, int16_t min, int16_t max) +{ + if (val < min) + val = min; + if (val > max) + val = max; + return val; +} + +uint32_t control_now(void) +{ + uint32_t this_seconds; + + ATOMIC_BLOCK(ATOMIC_FORCEON) { + this_seconds = seconds; + } + + if (preheat) + control_start_time = this_seconds; + return this_seconds - control_start_time; +} + +void control_start(void) +{ + preheat = TRUE; + last_temp = therm_temp(); + integral = 0; + control_running = TRUE; + control_output = 0; +} + +#define TIME_UNIT 1 +#define INTEGRAL_MAX (CONTROL_MAX * 2) +#define INTEGRAL_MIN (-(INTEGRAL_MAX)) + +#define CONTROL_MAX (10 * 128) +#define CONTROL_MIN (0) + +/* P = 1.00 means 1 degree C leads to full-scale output + * This is represented by config.p == 128 + * The calculations done here are in units of 0.1 degrees + */ + +#define ABS(x) ((x) < 0 ? -(x) : (x)) +#define INT_MAX 32767 +#define INT_MIN (-32768) + +#if 0 +int16_t multiply_clip(int16_t a, int16_t b) +{ + if ((b == 0) || (ABS(a) > INT_MAX / ABS(b))) + return ((a < 0) ^ (b < 0)) ? INT_MIN : INT_MAX; + return a * b; +} +#endif + +void control_poll(void) +{ + temp_t temp = therm_temp(); + temp_t profile_temp; + int16_t error; + int16_t temp_diff = temp - last_temp; + int16_t maxgain; + int16_t p, i, d; + + last_temp = temp; + + if (!control_running) + return; + + profile_temp = temperature_at_time(control_now()); + if (profile_temp == INVALID_TEMPERATURE) { + control_running = FALSE; + output0 = 0; + beep_off(); + return; + } + + error = (int16_t)(profile_temp - temp); + + if (error <= 0) { + beep_on(); + preheat = FALSE; + } else { + beep_off(); + } + + maxgain = (error == 0) ? INT_MAX : ((2*CONTROL_MAX) / error); + maxgain = ABS(maxgain); + + p = clip16(config.p, -maxgain, maxgain); + i = clip16(config.i, -maxgain, maxgain); + d = clip16(config.d, -maxgain, maxgain); + + integral += i * error * TIME_UNIT; + integral = clip16(integral, INTEGRAL_MIN, INTEGRAL_MAX); + + control_output += p * error + integral + d * temp_diff / TIME_UNIT; + control_output = clip16(control_output, CONTROL_MIN, CONTROL_MAX); + + output0 = (TIMER_MAX/5) * control_output / (CONTROL_MAX/5); +} + diff --git a/control.h b/control.h new file mode 100644 index 0000000..161016c --- /dev/null +++ b/control.h @@ -0,0 +1,12 @@ +/* control.h */ + +#ifndef _CONTROL_H_ +#define _CONTROL_H_ + +#include "common.h" + +void control_start(void); +void control_poll(void); +uint32_t control_now(void); + +#endif diff --git a/display.c b/display.c new file mode 100644 index 0000000..5891b9d --- /dev/null +++ b/display.c @@ -0,0 +1,140 @@ +/* display.c */ + +#include +#include + +#include "common.h" +#include "i2c.h" +#include "font.h" + +#define ADDRESS 0x3c + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) + +#define HORIZONTAL_FLIP 0 +#define VERTICAL_FLIP 0 + +#define COM_ALT 1 +#define COM_LR_REMAP 0 + +#define DISPLAY_INVERT 0 + +#define DEFAULT_CONTRAST 0x7f + +/* Init sequence from SSD1306 datasheet */ +const uint8 display_init_seq[] = { + 0xa8, 0x3f, /* Multiplex ratio */ + 0xd3, 0x00, /* Display offset */ + 0x40, /* Display start line */ + 0xa0 | HORIZONTAL_FLIP, + 0xc0 | (VERTICAL_FLIP << 3), + 0xda, 0x02 | (COM_ALT << 4) | (COM_LR_REMAP << 5), + 0x81, DEFAULT_CONTRAST, + 0xa4, /* Disable entire display on */ + 0xa6 | DISPLAY_INVERT, + 0xd5, 0x80, /* clock divide ratio */ + 0x8d, 0x14, /* charge pump setting */ + 0xaf /* display on */ +}; + +uint8 curpos; + +#if 0 +void delay(uint8_t count) +{ + volatile uint8_t i; + + while (count--) + for (i = 255; i; i--) + ; +} +#endif + +void display_command(uint8_t command) +{ + i2c_set_xor(0); + i2c_write(ADDRESS, 0x80, 1, &command); +} + +void display_init(void) +{ + uint8 i; + + i2c_init(); + + for (i = 0; i < ARRAY_SIZE(display_init_seq); i++) + display_command(display_init_seq[i]); + + curpos = 0; +} + +void display_data(uint8_t len, uint8_t *data) +{ + i2c_write(ADDRESS, 0x40, len, data); + curpos += len; +#if 0 + /* This bit is only needed if you are relying on wraparound. */ + if (curpos > 127) + curpos -= 128; +#endif + //delay(255); +} + +/* col = graphics column, row = text rows + * 0-127 0-7 + */ +void display_setposition(uint8_t col, uint8_t row) +{ + display_command(0xb0 + (row & 0x0f)); + display_command(0x00 + (col & 0x0f)); + display_command(0x10 + ((col >> 4) & 0x0f)); + curpos = col; +} + +void display_setinverse(bool on) +{ + i2c_set_xor(on ? 0xff : 0); +} + +void display_putchar(char c) +{ + uint8_t *data = font_getchar(c); + display_data(6, data); +} + +void display_putstr(char *s) +{ + while (*s) + display_putchar(*(s++)); +} + +void display_putstr_P(const char *s) +{ + char c; + while ((c = pgm_read_byte(s++)) != '\0') + display_putchar(c); +} + +#ifdef FAST_CLEAR +const uint8_t zero_data[] = {0, 0, 0, 0, 0, 0, 0, 0}; +#endif + +void display_clearline(void) +{ + uint8_t remaining = 128 - curpos; + +#ifdef FAST_CLEAR + while (remaining) { + uint8_t todo = remaining; + if (todo > sizeof(zero_data)) + todo = sizeof(zero_data); + display_data(todo, (uint8_t *)zero_data); + remaining -= todo; + } +#else + uint8_t zero = 0; + + while (remaining--) + display_data(1, &zero); +#endif +} diff --git a/display.h b/display.h new file mode 100644 index 0000000..0ee1d8c --- /dev/null +++ b/display.h @@ -0,0 +1,18 @@ +/* display.h */ + +#ifndef _DISPLAY_H_ +#define _DISPLAY_H_ + +#include "common.h" + +void display_init(void); +void display_data(uint8_t len, uint8_t *data); +void display_setposition(uint8_t col, uint8_t row); +#define display_settextpos(col, row) display_setposition((col)*6, row) +void display_setinverse(bool on); +void display_putchar(char c); +void display_putstr(char *s); +void display_putstr_P(const char *s); +void display_clearline(void); + +#endif diff --git a/fields.c b/fields.c new file mode 100644 index 0000000..8803467 --- /dev/null +++ b/fields.c @@ -0,0 +1,268 @@ +/* fields.c */ + +#include +#include +#include +#include + +#include "common.h" +#include "fields.h" +#include "menu.h" +#include "config.h" + +uint8_t field_row; // XXX ? +uint8_t field_values[DISPLAY_CHARS]; + +static uint8_t field_index(char c) +{ + return ((uint8_t)(c) & (uint8_t)15)-1; +} +//#define field(c) fields[((uint8_t)(c) & (uint8_t)15)-1] +#define field(c) fields[field_index(c)] + +uint8_t field_length(char c) +{ + return field(c).length; +} + +bool field_is_text(char c) +{ + return field(c).display == NULL; +} + +PGM_P field_text(char c, uint8_t n) +{ + return get_string(&field(c).display[n]); +} + +uint8_t field_display_entries(char c) +{ + return field(c).display_entries; +} + +bool is_field(char c) { + return (c > 0) && (c < 32); +} + +bool is_editable(char c) { + return (c > 16) && (c < 32); +} + +PGM_P get_string(PGM_P const *addr) +{ + PGM_P ptr; + memcpy_P(&ptr, addr, sizeof(PGM_P)); + return ptr; +} + +uint8_t find_field(uint8_t n) +{ + char c; + uint8_t index = 0; + char type = 0; + + while ((c = menu_getchar(index)) != '\0') { + index++; + if (type && (type == c)) + continue; + if (is_field(c)) { + type = c; + if (n == 0) + return index-1; + n--; + } else { + type = 0; + } + } + return 255; +} + +uint8_t find_field_length_by_place(uint8_t index) +{ + uint8_t orig_index = index; + char type = menu_getchar(index); + uint8_t tlen = field_length(type); + + while (menu_getchar(index) == type) + index += tlen; + + return index - orig_index; +} + +uint8_t find_field_length(uint8_t index) +{ + return find_field_length_by_place(find_field(index)); +} + +uint8_t find_field_number(uint8_t index) +{ + uint8_t n, p; + for (n = 0; (p = find_field(n)) != 255; n++) + { + if (p == index) + return n; + } + return 255; +} + +uint8_t find_editable_field(uint8_t index, bool left) +{ + int8_t increment = left?(-1):1; + char c; + uint8_t len; + + if (index >= 255) + return 255; + + len = strlen_P(get_string(&menu_current_p->text[field_row])); + + if (left && (index >= len)) + index = len-1; + + while ((c = menu_getchar(index)) != '\0') { + if (is_editable(c)) + return index; + index += increment; + } + return 255; +} + +void write_field_enum(uint8_t field, uint8_t val) +{ + uint8_t p = find_field(field); + field_values[p] = val; +} + +uint8_t read_field_enum(uint8_t field) +{ + uint8_t p = find_field(field); + return field_values[p]; +} + +uint16_t read_field_uint16(uint8_t field) +{ + uint8_t i; + uint16_t val = 0; + + uint8_t p = find_field(field); + uint8_t l = find_field_length(field); + + for (i = 0; i < l; i++) { + val = val * 10; + val += field_values[p+i]; + } + + return val; +} + +void write_field_uint16(uint8_t field, uint16_t val) +{ + uint8_t i; + + uint8_t p = find_field(field); + uint8_t l = find_field_length(field); + + for (i = l; i; i--) { + field_values[p+i-1] = val % 10; + val = val / 10; + } +} + +void write_field_integer_part(uint8_t field, int32_t val) +{ + if (val < 0) + val = -val; + + write_field_uint16(field, val >> 12); +} + +void write_field_fractional_part(uint8_t field, int32_t val) +{ + uint8_t i; + uint16_t v; + + uint8_t p = find_field(field); + uint8_t l = find_field_length(field); + + if (val < 0) + val = -val; + + v = val & 0xfff; + + for (i = 0; i < l; i++) { + uint8_t digit; + v = v * 10; + digit = (v >> 12) % 10; + v = v - (digit << 12); + field_values[p+i] = digit; + } +} + +int32_t round_dp(int32_t n, uint8_t dp) +{ + int32_t rval = 0x800; + int i; + + /* Round away from zero. Remove this bit + * to round up + */ + if (n < 0) + rval = -rval; + + for (i = 0; i < dp; i++) + rval = rval / 10; + + return n + rval; +} + +uint32_t read_field_fracint(int8_t index, bool frac) +{ + int32_t val, v_int, v_frac; + uint8_t sign = read_field_enum(index++); + + v_int = read_field_uint16(index++); + + if (frac) { + uint8_t l_frac; + v_frac = read_field_uint16(index); + l_frac = find_field_length(index); + + v_frac = v_frac << 12; + while (l_frac--) + v_frac = v_frac / 10; + } else { + v_frac = 0; + } + + val = (v_int << 12) + v_frac; + + if (sign == FIELD_SIGN_NEGATIVE) + val = -val; + + return val; +} + +void write_field_fracint(uint8_t index, bool sign, bool frac, int32_t val) +{ + uint8_t l_frac; + + l_frac = find_field_length(index+1+(sign?1:0)); + + val = round_dp(val, frac?l_frac:0); + + if (sign) + write_field_enum(index++, (val<0)?FIELD_SIGN_NEGATIVE:FIELD_SIGN_POSITIVE); + + write_field_integer_part(index++, val); + if (frac) + write_field_fractional_part(index, val); +} + + +void write_field_temperature(uint8_t index, bool frac, temp_t k_temp) +{ + int32_t temp = temperature_from_kelvin(k_temp); + write_field_fracint(index, TRUE, frac, temp); + write_field_enum(index+2+(frac?1:0), config.units); +} + diff --git a/fields.h b/fields.h new file mode 100644 index 0000000..5ac3377 --- /dev/null +++ b/fields.h @@ -0,0 +1,53 @@ +/* fields.h */ + +#ifndef _FIELDS_H_ +#define _FIELDS_H_ + +#include "common.h" +#include "avr/pgmspace.h" + +#define DISPLAY_CHARS 21 + +extern uint8_t field_values[DISPLAY_CHARS]; + +typedef struct field { + PGM_P const *display; + uint8_t display_entries; + uint8_t length; +} field_t; + +//#define field(c) fields[((uint8_t)(c) & (uint8_t)15)-1] + +//#define field_length(c) (field(c).length) + +#define FIELD_SIGN_NEGATIVE 0 +#define FIELD_SIGN_POSITIVE 1 + +uint8_t field_length(char c); +bool field_is_text(char c); +PGM_P field_text(char c, uint8_t n); +uint8_t field_display_entries(char c); +bool is_field(char c); +bool is_editable(char c); +PGM_P get_string(PGM_P const *addr); +uint8_t find_field(uint8_t n); +uint8_t find_field_length_by_place(uint8_t index); +uint8_t find_field_length(uint8_t index); +uint8_t find_field_number(uint8_t index); +uint8_t find_editable_field(uint8_t index, bool left); +void write_field_enum(uint8_t field, uint8_t val); +uint8_t read_field_enum(uint8_t field); +uint16_t read_field_uint16(uint8_t field); +void write_field_uint16(uint8_t field, uint16_t val); + +#define read_field_uint8(x) read_field_uint16(x) +#define write_field_uint8(x, y) write_field_uint16(x, y) + +void write_field_integer_part(uint8_t field, int32_t val); +void write_field_fractional_part(uint8_t field, int32_t val); +int32_t round_dp(int32_t n, uint8_t dp); +uint32_t read_field_fracint(int8_t index, bool frac); +void write_field_fracint(uint8_t index, bool sign, bool frac, int32_t val); +void write_field_temperature(uint8_t index, bool frac, temp_t k_temp); + +#endif diff --git a/font.c b/font.c new file mode 100644 index 0000000..0ee7363 --- /dev/null +++ b/font.c @@ -0,0 +1,123 @@ +/* font.c */ + +#include +#include + +#include "common.h" + +// standard ascii 5x7 font +// defines ascii characters 0x20-0x7F (32-127) +static const uint8_t PROGMEM font[] = { + 0x00, 0x00, 0x00, 0x00, 0x00,// (space) + 0x00, 0x00, 0x5F, 0x00, 0x00,// ! + 0x00, 0x07, 0x00, 0x07, 0x00,// " + 0x14, 0x7F, 0x14, 0x7F, 0x14,// # + 0x24, 0x2A, 0x7F, 0x2A, 0x12,// $ + 0x23, 0x13, 0x08, 0x64, 0x62,// % + 0x36, 0x49, 0x55, 0x22, 0x50,// & + 0x00, 0x05, 0x03, 0x00, 0x00,// ' + 0x00, 0x1C, 0x22, 0x41, 0x00,// ( + 0x00, 0x41, 0x22, 0x1C, 0x00,// ) + 0x08, 0x2A, 0x1C, 0x2A, 0x08,// * + 0x08, 0x08, 0x3E, 0x08, 0x08,// + + 0x00, 0x50, 0x30, 0x00, 0x00,// , + 0x08, 0x08, 0x08, 0x08, 0x08,// - + 0x00, 0x60, 0x60, 0x00, 0x00,// . + 0x20, 0x10, 0x08, 0x04, 0x02,// / + 0x3E, 0x51, 0x49, 0x45, 0x3E,// 0 + 0x00, 0x42, 0x7F, 0x40, 0x00,// 1 + 0x42, 0x61, 0x51, 0x49, 0x46,// 2 + 0x21, 0x41, 0x45, 0x4B, 0x31,// 3 + 0x18, 0x14, 0x12, 0x7F, 0x10,// 4 + 0x27, 0x45, 0x45, 0x45, 0x39,// 5 + 0x3C, 0x4A, 0x49, 0x49, 0x30,// 6 + 0x01, 0x71, 0x09, 0x05, 0x03,// 7 + 0x36, 0x49, 0x49, 0x49, 0x36,// 8 + 0x06, 0x49, 0x49, 0x29, 0x1E,// 9 + 0x00, 0x36, 0x36, 0x00, 0x00,// : + 0x00, 0x56, 0x36, 0x00, 0x00,// ; + 0x00, 0x08, 0x14, 0x22, 0x41,// < + 0x14, 0x14, 0x14, 0x14, 0x14,// = + 0x41, 0x22, 0x14, 0x08, 0x00,// > + 0x02, 0x01, 0x51, 0x09, 0x06,// ? + 0x32, 0x49, 0x79, 0x41, 0x3E,// @ + 0x7E, 0x11, 0x11, 0x11, 0x7E,// A + 0x7F, 0x49, 0x49, 0x49, 0x36,// B + 0x3E, 0x41, 0x41, 0x41, 0x22,// C + 0x7F, 0x41, 0x41, 0x22, 0x1C,// D + 0x7F, 0x49, 0x49, 0x49, 0x41,// E + 0x7F, 0x09, 0x09, 0x01, 0x01,// F + 0x3E, 0x41, 0x41, 0x51, 0x32,// G + 0x7F, 0x08, 0x08, 0x08, 0x7F,// H + 0x00, 0x41, 0x7F, 0x41, 0x00,// I + 0x20, 0x40, 0x41, 0x3F, 0x01,// J + 0x7F, 0x08, 0x14, 0x22, 0x41,// K + 0x7F, 0x40, 0x40, 0x40, 0x40,// L + 0x7F, 0x02, 0x04, 0x02, 0x7F,// M + 0x7F, 0x04, 0x08, 0x10, 0x7F,// N + 0x3E, 0x41, 0x41, 0x41, 0x3E,// O + 0x7F, 0x09, 0x09, 0x09, 0x06,// P + 0x3E, 0x41, 0x51, 0x21, 0x5E,// Q + 0x7F, 0x09, 0x19, 0x29, 0x46,// R + 0x46, 0x49, 0x49, 0x49, 0x31,// S + 0x01, 0x01, 0x7F, 0x01, 0x01,// T + 0x3F, 0x40, 0x40, 0x40, 0x3F,// U + 0x1F, 0x20, 0x40, 0x20, 0x1F,// V + 0x7F, 0x20, 0x18, 0x20, 0x7F,// W + 0x63, 0x14, 0x08, 0x14, 0x63,// X + 0x03, 0x04, 0x78, 0x04, 0x03,// Y + 0x61, 0x51, 0x49, 0x45, 0x43,// Z + 0x00, 0x00, 0x7F, 0x41, 0x41,// [ + 0x02, 0x04, 0x08, 0x10, 0x20,// "\" + 0x41, 0x41, 0x7F, 0x00, 0x00,// ] + 0x04, 0x02, 0x01, 0x02, 0x04,// ^ + 0x40, 0x40, 0x40, 0x40, 0x40,// _ + 0x00, 0x01, 0x02, 0x04, 0x00,// ` + 0x20, 0x54, 0x54, 0x54, 0x78,// a + 0x7F, 0x48, 0x44, 0x44, 0x38,// b + 0x38, 0x44, 0x44, 0x44, 0x20,// c + 0x38, 0x44, 0x44, 0x48, 0x7F,// d + 0x38, 0x54, 0x54, 0x54, 0x18,// e + 0x08, 0x7E, 0x09, 0x01, 0x02,// f + 0x08, 0x14, 0x54, 0x54, 0x3C,// g + 0x7F, 0x08, 0x04, 0x04, 0x78,// h + 0x00, 0x44, 0x7D, 0x40, 0x00,// i + 0x20, 0x40, 0x44, 0x3D, 0x00,// j + 0x00, 0x7F, 0x10, 0x28, 0x44,// k + 0x00, 0x41, 0x7F, 0x40, 0x00,// l + 0x7C, 0x04, 0x18, 0x04, 0x78,// m + 0x7C, 0x08, 0x04, 0x04, 0x78,// n + 0x38, 0x44, 0x44, 0x44, 0x38,// o + 0x7C, 0x14, 0x14, 0x14, 0x08,// p + 0x08, 0x14, 0x14, 0x18, 0x7C,// q + 0x7C, 0x08, 0x04, 0x04, 0x08,// r + 0x48, 0x54, 0x54, 0x54, 0x20,// s + 0x04, 0x3F, 0x44, 0x40, 0x20,// t + 0x3C, 0x40, 0x40, 0x20, 0x7C,// u + 0x1C, 0x20, 0x40, 0x20, 0x1C,// v + 0x3C, 0x40, 0x30, 0x40, 0x3C,// w + 0x44, 0x28, 0x10, 0x28, 0x44,// x + 0x0C, 0x50, 0x50, 0x50, 0x3C,// y + 0x44, 0x64, 0x54, 0x4C, 0x44,// z + 0x00, 0x08, 0x36, 0x41, 0x00,// { + 0x00, 0x00, 0x7F, 0x00, 0x00,// | + 0x00, 0x41, 0x36, 0x08, 0x00,// } + 0x08, 0x08, 0x2A, 0x1C, 0x08,// -> + 0x08, 0x1C, 0x2A, 0x08, 0x08 // <- +}; + +uint8_t font_char[6]; + +uint8_t *font_getchar(char ch) +{ + uint8_t c = ch - 32; + + if (c > (127-32)) + c = 0; + + memcpy_P(font_char, font+5*c, 5); + + font_char[5] = 0; + + return font_char; +} diff --git a/font.h b/font.h new file mode 100644 index 0000000..fe91e0e --- /dev/null +++ b/font.h @@ -0,0 +1,8 @@ +/* font.h */ + +#ifndef _FONT_H_ +#define _FONT_H_ + +uint8_t *font_getchar(char c); + +#endif diff --git a/humancontrol/Makefile b/humancontrol/Makefile new file mode 100644 index 0000000..da47112 --- /dev/null +++ b/humancontrol/Makefile @@ -0,0 +1,3 @@ +# Makefile + +soldertimer: soldertimer.c diff --git a/humancontrol/soldertimer.c b/humancontrol/soldertimer.c new file mode 100644 index 0000000..b04e9e9 --- /dev/null +++ b/humancontrol/soldertimer.c @@ -0,0 +1,115 @@ +/* soldertimer.c */ + +#include +#include + +#include +#include +#include + + +typedef struct profile_line { + int32_t temperature; /* store degrees C * 4096 */ + unsigned int time; +} profile_line_t; + +typedef struct profile { + char name[21]; + int32_t start_temp; + profile_line_t lines[6]; +} profile_t; + +#define DEGREES * 4096 + +profile_t profile = {"Lead-free solder", 25 DEGREES, { + { 150 DEGREES, 100 }, + { 200 DEGREES, 100 }, + { 250 DEGREES, 40 }, + { 250 DEGREES, 30 }, + { 100 DEGREES, 30 }, + { 0, 0 } } + }; + +static int32_t expand_profile_temperature(uint8_t row) +{ + int32_t temp; + + if (row > 1) + temp = profile.lines[row-2].temperature; + else + temp = profile.start_temp; + + return temp; +} + +static uint32_t profile_time(uint8_t line) +{ + uint32_t time = profile.lines[line].time; + return time; +} + +static int finished; + +static int32_t temperature_at_time(uint32_t time) { + uint32_t time0, time1; + int32_t temp0, temp1; + uint8_t i; + + temp1 = expand_profile_temperature(1); + time1 = 0; + + i = 0; + do { + uint32_t ptime; + temp0 = temp1; + time0 = time1; + ptime = profile_time(i); + if (ptime > 0) { + temp1 = expand_profile_temperature(2+i); + time1 += ptime; + } + i++; + if (i >= 6) + break; + } while (time > time1); + + if (time > time1) { + finished = 1; + temp0 = temp1; + time0 = time1; + time1 += 60; /* Arbitrary number here */ + temp1 = expand_profile_temperature(1); + } + + if (time > time1) + return expand_profile_temperature(1); + + return temp0 + (temp1 - temp0) * (int32_t)(time - time0) / (int32_t)(time1 - time0); +} + +int main(void) { + time_t start; + time_t now; + time_t end; + + finished = 0; + + time(&start); + + start = start + 10; + + while (!finished) { + int32_t temp; + struct timespec interval = {0, 2000000}; + time(&now); + if (now < start) + temp = temperature_at_time(0); + else + temp = temperature_at_time(now - start); + printf("\r%4ds %.1f degrees", (int)(now - start), (double)temp / 4096); + fflush(stdout); + nanosleep(&interval, NULL); + } + printf("\n"); +} + diff --git a/i2c.c b/i2c.c new file mode 100644 index 0000000..b2d84ec --- /dev/null +++ b/i2c.c @@ -0,0 +1,95 @@ +/* i2c.c */ + +#include +#include +#include + +#include "common.h" +#include "beep.h" + +//#define i2c_wait() do { uint16_t count = 0; while (!(TWCR & (1< 1000) break; } while (0) +//#define i2c_status() (TWSR & 0xF8) +#define i2c_check(status) if (i2c_status() != (status)) goto ERROR + +static void i2c_wait(void) +{ + uint16_t count = 0; + while (!(TWCR & (1< 1000) + break; +} + +static __attribute__ ((noinline)) uint8_t i2c_status(void) +{ + return TWSR & 0xF8; +} + +uint8_t i2c_xor; + +void i2c_init(void) +{ + TWSR = 0; /* prescaler TWPS = 0 */ + TWBR = 10; /* minimum value allowed */ + /* f_SCL = clk / (16+(2*TWBR*TWPS)) */ + + TWCR = (1< + +#include +#include +#include +#include +#include + +#include "common.h" +#include "menu.h" +#include "display.h" +#include "timer.h" +#include "therm.h" +#include "config.h" +#include "beep.h" +#include "fields.h" +#include "profile.h" +#include "control.h" + +uint8_t menu_current; +uint8_t menu_row; +uint8_t field_row; +uint8_t menu_next; +uint8_t menu_redraw; +bool edit_dirty; + +#define REDRAW_NONE 0 +#define REDRAW_FIELDS 1 +#define REDRAW_ALL 2 + +uint8_t run_status; + +bool menu_editing; +uint8_t menu_editing_field; + +menu_t *menu_current_p; + +const char str_b[] PROGMEM = "B"; +const char str_e[] PROGMEM = "E"; +const char str_j[] PROGMEM = "J"; +const char str_k[] PROGMEM = "K"; +const char str_n[] PROGMEM = "N"; +const char str_r[] PROGMEM = "R"; +const char str_s[] PROGMEM = "S"; +const char str_t[] PROGMEM = "T"; + +PGM_P const enum_thermocouple_types[] PROGMEM = { + str_b, + str_e, + str_j, + str_k, + str_n, + str_r, + str_s, + str_t +}; + +const char str_negative[] PROGMEM = "-"; +const char str_positive[] PROGMEM = "+"; + +PGM_P const enum_posneg[] PROGMEM = { + str_negative, + str_positive +}; + +const char str_off[] PROGMEM = "Off"; +const char str_on[] PROGMEM = "On"; + +PGM_P const enum_boolean[] PROGMEM = { + str_off, + str_on +}; + +const char str_50hz[] PROGMEM = "50Hz"; +const char str_60hz[] PROGMEM = "60Hz"; + +PGM_P const enum_frequency[] PROGMEM = { + str_50hz, + str_60hz +}; + +const char str_degc[] PROGMEM = "\047C"; +const char str_degf[] PROGMEM = "\047F"; +/* str_k already defined */ +const char str_degde[] PROGMEM = "\047De"; +const char str_degn[] PROGMEM = "\047N"; +const char str_degr[] PROGMEM = "\047R"; +const char str_degre[] PROGMEM = "\047Re"; +const char str_degro[] PROGMEM = "\047Ro"; +const char str_mev[] PROGMEM = "meV"; + +#define TEMPERATURE_CELSIUS 0 +#define TEMPERATURE_FAHRENHEIT 1 +#define TEMPERATURE_KELVIN 2 +#define TEMPERATURE_DELISLE 3 +#define TEMPERATURE_NEWTON 4 +#define TEMPERATURE_RANKINE 5 +#define TEMPERATURE_REAUMUR 6 +#define TEMPERATURE_ROMER 7 +#define TEMPERATURE_MEV 8 + +PGM_P const enum_units[] PROGMEM = { + str_degc, + str_degf, + str_k, + str_degde, + str_degn, + str_degr, + str_degre, + str_degro, + str_mev +}; + +const char str_seconds[] PROGMEM = "s"; +const char str_minutes[] PROGMEM = "m"; +const char str_hours[] PROGMEM = "h"; + +PGM_P const enum_time_units[] PROGMEM = { + str_seconds, + str_minutes, + str_hours +}; + +const char str_0[] PROGMEM = "0"; +const char str_1[] PROGMEM = "1"; +const char str_2[] PROGMEM = "2"; +const char str_3[] PROGMEM = "3"; +const char str_4[] PROGMEM = "4"; +const char str_5[] PROGMEM = "5"; +const char str_6[] PROGMEM = "6"; +const char str_7[] PROGMEM = "7"; +const char str_8[] PROGMEM = "8"; +const char str_9[] PROGMEM = "9"; + +PGM_P const enum_digits[] PROGMEM = { + str_0, + str_1, + str_2, + str_3, + str_4, + str_5, + str_6, + str_7, + str_8, + str_9, +}; + +const char str_start[] PROGMEM = "Start?"; +const char str_running[] PROGMEM = "Run"; +const char str_finished[] PROGMEM = "Done"; +//const char str_error[] PROGMEM = "Error"; + +PGM_P const enum_status[] PROGMEM = { + str_start, + str_running, + str_finished +// str_error +}; + +#define RUN_STATUS_READY 0 +#define RUN_STATUS_RUNNING 1 +#define RUN_STATUS_FINISHED 2 +#define RUN_STATUS_ERROR 3 + +const char str_empty[] PROGMEM = ""; +const char str_fault[] PROGMEM = "FAULT"; +//const char str_open[] PROGMEM = "Connect thermocouple"; +//const char str_chiprange[] PROGMEM = "Cold junction range"; +//const char str_temprange[] PROGMEM = "Thermocouple range"; + +PGM_P const enum_faultstatus[] PROGMEM = { + str_empty, + str_fault +// str_open, +// str_chiprange, +// str_temprange +}; + +const char str_run_profile[] PROGMEM = "Run profile"; +const char str_edit_profile[] PROGMEM = "Edit profile"; +const char str_configure[] PROGMEM = "Configure"; +const char str_faultstatus[] PROGMEM = "\x0a"; +const char str_oven[] PROGMEM = "Oven \x08\x02\x02\x02\x02.\x02\x04\x04\x04"; +const char str_ambient[] PROGMEM = "Ambient \x08\x02\x02\x02\x02.\x02\x04\x04\x04"; + +PGM_P const menu_main[] PROGMEM = { + str_run_profile, + str_edit_profile, + str_configure, + NULL, + str_faultstatus, + NULL, + str_oven, + str_ambient +}; + +const char str_placeholder[] PROGMEM = "Placeholder"; +const char str_selecting_profile[] PROGMEM = "Selecting profile"; +const char str_str[] PROGMEM = "\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01"; +const char str_editstr[] PROGMEM = "\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11"; + +PGM_P const menu_select_profile[] PROGMEM = { + str_str, + str_str, + str_str, + str_str, + str_str, + str_str, + str_str, + str_str +}; + +const char str_editing_profile[] PROGMEM = "Editing profile"; +const char str_name_field[] PROGMEM = "> Name field <"; +const char str_start_temperature[] PROGMEM = "Start temp \x18\x12\x12\x12\x12\x04<<"; +const char str_edit_field[] PROGMEM = "\x02\x02.\x02\x02/ \x12\x12\x12\x12\x13 \x18\x12\x12\x12\x12\x04<<"; + +PGM_P const menu_edit_profile[] PROGMEM = { + str_editstr, + str_start_temperature, + str_edit_field, + str_edit_field, + str_edit_field, + str_edit_field, + str_edit_field, + str_edit_field, +}; + +const char str_contrast[] PROGMEM = "Contrast \x12\x12\x12"; +const char str_beep[] PROGMEM = "Beep \x15>>"; +const char str_thermocouple[] PROGMEM = "Thermocouple \x16"; +const char str_frequency[] PROGMEM = "Frequency \x17>>>"; +const char str_units[] PROGMEM = "Units \x14>>"; +const char str_p[] PROGMEM = "P \x18\x12.\x12\x12"; +const char str_i[] PROGMEM = "I \x18\x12.\x12\x12"; +const char str_d[] PROGMEM = "D \x18\x12.\x12\x12"; + +PGM_P const menu_configuration[] PROGMEM = { + str_contrast, + str_beep, + str_thermocouple, + str_frequency, + str_units, + str_p, + str_i, + str_d +}; + +const char str_running_profile[] PROGMEM = "\x09\x09\x09\x09\x09\x09\x12\x12\x12\x12\x13\x08\x02\x02\x02\x02.\x02\x04<<"; + +PGM_P const menu_run_profile[] PROGMEM = { + str_running_profile, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL +}; + +static void main_menu_fields(uint8_t row); +static void configuration_fields(uint8_t row); +static void save_configuration_fields(uint8_t row); +static void select_profile_fields(uint8_t row); +static void edit_profile_fields(uint8_t row); +static void save_profile_fields(uint8_t row); +static void update_run_status(uint8_t row); + +menu_t menus[] = { + { menu_main, 0, 2, main_menu_fields, NULL }, + { menu_select_profile, 0, 7, select_profile_fields, NULL }, + { menu_configuration, 0, 7, configuration_fields, save_configuration_fields }, + { menu_edit_profile, 0, 7, edit_profile_fields, save_profile_fields }, + { menu_run_profile, 7, 7, update_run_status, NULL } +}; + +#define TEMPERATURE_UNIT 0x1000L + +int8_t temp_n[] = {1, 9, 1, -3, 33, 9, 4, 21, 10}; +int8_t temp_d[] = {1, 5, 1, 2, 100, 5, 5, 40, 116}; +int32_t temp_off[] = {0, 32*TEMPERATURE_UNIT, 1118822, -614400, 0, 2013880, 0, 30720, 96450}; + +int32_t temperature_from_kelvin(temp_t temp) +{ + uint8_t units = config.units; + int32_t temp32 = temp; + int32_t source = ((temp32-2732)<<12) / 10; + int32_t result; + result = source * temp_n[units] / temp_d[units] + temp_off[units]; +// printf("temp = %d, source = %d (%x), result = %d (%x)\n", temp, source, source, result, result); + return result; +} + +temp_t temperature_to_kelvin(int32_t source) +{ + uint8_t units = config.units; + int32_t result = (source - temp_off[units]) * temp_d[units] / temp_n[units]; + + return therm_reduce(result); +} + +static void update_run_status(uint8_t row) +{ + temp_t temp = therm_temp(); + write_field_enum(0, run_status); + write_field_uint16(1, control_now()); + write_field_enum(2, 0); + write_field_temperature(3, TRUE, temp); +} + +static void main_menu_fields(uint8_t row) +{ + temp_t temp; + + switch (row) { + case 4: + write_field_enum(0, therm_fault()?1:0); + return; + case 6: + temp = therm_temp(); + break; + case 7: + temp = therm_coldtemp(); + break; + default: + return; + } + + write_field_temperature(0, TRUE, temp); +} + +static void configuration_fields(uint8_t row) +{ + if (row == 0) { + write_field_uint8(0, config.contrast); + return; + } + + if (row > 4) { + int16_t value16; + int32_t val; + switch (row) { + case 5: + value16 = config.p; + break; + case 6: + value16 = config.i; + break; + case 7: + value16 = config.d; + break; + default: + return; + } + val = (int32_t)value16 << 5; + write_field_fracint(0, TRUE, TRUE, val); + } else { + uint8_t value8; + switch (row) { + case 1: + value8 = config.beep; + break; + case 2: + value8 = config.thermocouple_type; + break; + case 3: + value8 = config.frequency; + break; + case 4: + value8 = config.units; + break; + } + write_field_enum(0, value8); + } +} + +static void save_configuration_fields(uint8_t row) +{ + if (row == 0) { + config.contrast = read_field_uint8(0); + return; + } + + if (row > 4) { + int16_t value16 = read_field_fracint(0, TRUE) >> 5; + switch (row) { + case 5: + config.p = value16; + break; + case 6: + config.i = value16; + break; + case 7: + config.d = value16; + break; + } + } else { + uint8_t value8 = read_field_enum(0); + switch (row) { + case 1: + config.beep = value8; + break; + case 2: + config.thermocouple_type = value8; + break; + case 3: + config.frequency = value8; + break; + case 4: + config.units = value8; + break; + } + } +} + +static void select_profile_fields(uint8_t row) +{ + profile_select(row); + memcpy(field_values, &profile_p->name, PROFILE_NAME_LENGTH); +} + +static void save_profile_fields(uint8_t row) +{ + int32_t temp; + temp_t k_temp; + uint8_t n; + + if (row == 0) { + memcpy(profile_p->name, field_values, PROFILE_NAME_LENGTH); + return; + } + + n = find_field_number(find_editable_field(0, FALSE)); + + temp = read_field_fracint(n+((row == 1)?0:2), FALSE); + + k_temp = temperature_to_kelvin(temp); + set_profile_temperature(k_temp); + + if (row == 1) + return; + + set_profile_time(row-2, read_field_uint16(n), read_field_enum(n+1)); +} + +static void edit_profile_fields(uint8_t row) +{ + int32_t unit_temp, prev_temp, step; + temp_t temp; + uint16_t time; + uint8_t time_units; + if (row == 0) { + memcpy(field_values, profile_p->name, PROFILE_NAME_LENGTH); + return; + } + + temp = get_profile_temperature(row); + write_field_temperature(((row == 1)?0:4), FALSE, temp); + if (row == 1) + return; + + unit_temp = temperature_from_kelvin(temp); + prev_temp = temperature_from_kelvin(get_profile_temperature(row-1)); + + time = profile_get_time(row-2); + time_units = profile_get_time_units(row-2); + + if (time == 0) + step = 0; + else + step = (unit_temp - prev_temp) / time; + + write_field_fracint(0, FALSE, TRUE, step); + write_field_uint16(2, time); + write_field_enum(3, time_units); +} + +#define MENU_MAIN 0 +#define MENU_SELECT_PROFILE 1 +#define MENU_CONFIGURATION 2 +#define MENU_EDIT_PROFILE 3 +#define MENU_RUN_PROFILE 4 + +char menu_getchar(uint8_t col) +{ + PGM_P ptr = get_string(&menu_current_p->text[field_row]); + + if (!ptr) + return '\0'; + + if (col > 20) + return '\0'; + + return pgm_read_byte(ptr + col); +} + +field_t fields[] = { + {NULL /* ASCII */, 127-32, 1}, + {enum_digits, ARRAY_SIZE(enum_digits), 1}, + {enum_time_units, ARRAY_SIZE(enum_time_units), 1}, + {enum_units, ARRAY_SIZE(enum_units), 3}, + {enum_boolean, ARRAY_SIZE(enum_boolean), 3}, + {enum_thermocouple_types, ARRAY_SIZE(enum_thermocouple_types), 1}, + {enum_frequency, ARRAY_SIZE(enum_frequency), 4}, + {enum_posneg, ARRAY_SIZE(enum_posneg), 1}, + {enum_status, ARRAY_SIZE(enum_status), 6}, + {enum_faultstatus, ARRAY_SIZE(enum_faultstatus), 21} +}; + +static void display_field(char c, uint8_t col) +{ + uint8_t i; + + if (field_is_text(c)) { + if ((field_values[col] >= 32) && (field_values[col] < 32+field_display_entries(c))) + display_putchar(field_values[col]); + else + display_putchar('?'); + return; + } + + if (field_values[col] >= field_display_entries(c)) { + for (i = 0; i < field_length(c); i++) + display_putchar('?'); + } else { + PGM_P str = field_text(c, field_values[col]); + i = 0; + if (field_length(c) > 1) { + i = field_length(c) - strlen_P(str); + if (menu_getchar(col+1) == '>') + for (; i; i--) + display_putchar(' '); + } + display_putstr_P(str); + for (; i; i--) + display_putchar(' '); + } +} + +#define menu_draw_current_row() menu_draw_row(menu_row) + +static void menu_set_fields(uint8_t row) +{ + menu_t *menu = menu_current_p; + field_row = row; + if ((!menu_editing) && menu->get_field) + menu->get_field(row); +} + +static void menu_draw_row(uint8_t row) +{ + uint8_t col; + display_settextpos(0, row); + display_setinverse((row == menu_row) && !menu_editing); + if (get_string(&menu_current_p->text[row])) { + char c; + menu_set_fields(row); + col = 0; + while ((c = menu_getchar(col)) != '\0') { + if (menu_editing) + display_setinverse(col == menu_editing_field); + if (is_field(c)) { + display_field(c, col); + col += field_length(c); + } else { + display_putchar(c); + col++; + } + } + } + display_clearline(); +} + +#if 1 +static void display_menu(bool all) +{ + uint8_t row; + + for (row = 0; row < 8; row++) { + field_row = row; + if (all || (find_field(0) != 255)) + menu_draw_row(row); + } +} +#endif + +void set_menu(uint8_t new_menu) +{ + if (new_menu == menu_current) + return; + menu_current = new_menu; + menu_current_p = &menus[new_menu]; + menu_redraw = TRUE; +// memcpy_P(&menu_row, &menu_current_p->first_line, sizeof(menu_row)); + menu_row = 0; + menu_editing = FALSE; +} + +void menu_init(void) +{ + /* Make sure set_menu does its work */ + menu_current = MENU_MAIN+1; + set_menu(MENU_MAIN); + display_init(); +} + +#define BUTTON_UP 1 +#define BUTTON_DOWN 2 +#define BUTTON_LEFT 4 +#define BUTTON_RIGHT 3 + +static void menu_end_edit(void) +{ + menu_t *menu = menu_current_p; + menu_editing = FALSE; + if (menu->put_field) + menu->put_field(menu_row); + menu_redraw = TRUE; +} + +static void menu_edit_new_field(uint8_t field, bool left) +{ + field = find_editable_field(field, left); + menu_editing_field = field; + menu_editing = TRUE; + edit_dirty = FALSE; + if (field == 255) + menu_end_edit(); + menu_draw_row(menu_row); +} + +static void increment_value(int8_t inc) +{ + uint8_t val = field_values[menu_editing_field]; + uint8_t c = menu_getchar(menu_editing_field); + + uint8_t min = 0; + uint8_t max = field_display_entries(c) - 1; + + if (field_is_text(c)) { + min += 32; + max += 32; + } + + if (inc < 0 && val == min) + val = max; + else if (inc > 0 && val == max) + val = min; + else + val += inc; + + field_values[menu_editing_field] = val; + edit_dirty = TRUE; +} + +static void menu_navigate_up(void) +{ + if (menu_row == menu_current_p->first_line) + return; + + menu_row--; + menu_draw_row(menu_row+1); + menu_draw_row(menu_row); +} + +static void menu_navigate_down(void) +{ + if (menu_row == menu_current_p->last_line) + return; + + menu_row++; + menu_draw_row(menu_row-1); + menu_draw_row(menu_row); +} + +static void menu_navigate_into(void) +{ + uint8_t menu = menu_current; + switch (menu) { + case MENU_MAIN: + switch (menu_row) { + case 0: + menu = MENU_SELECT_PROFILE; + menu_next = MENU_RUN_PROFILE; + break; + case 1: + menu = MENU_SELECT_PROFILE; + menu_next = MENU_EDIT_PROFILE; + break; + case 2: + menu = MENU_CONFIGURATION; + break; + } + break; + + case MENU_SELECT_PROFILE: + menu = menu_next; + profile_select(menu_row); + break; + + case MENU_RUN_PROFILE: + control_start(); + run_status = RUN_STATUS_RUNNING; + break; + + default: + menu_edit_new_field(21, TRUE); + break; + } + + set_menu(menu); +} + +static void menu_navigate_back(void) +{ + uint8_t menu = menu_current; + switch (menu) { + case MENU_EDIT_PROFILE: + profile_save(); + case MENU_RUN_PROFILE: + menu = MENU_SELECT_PROFILE; + break; + default: + menu = MENU_MAIN; + break; + } + + set_menu(menu); +} + +static void menu_process_leftright(bool left) +{ + int8_t increment = left?(-1):1; + + if (menu_editing) + menu_edit_new_field(menu_editing_field+increment, left); + else { + if (left) + menu_navigate_back(); + else + menu_navigate_into(); + } +} + +static void menu_process_updown(bool up) +{ + int8_t increment = up?1:(-1); + + if (menu_editing) { + increment_value(increment); + menu_draw_row(menu_row); + } else { + if (up) + menu_navigate_up(); + else + menu_navigate_down(); + } +} + +#define GRAPH_POINTS 128 +//#define GRAPH_POINTS 128 +//#define GRAPH_POINTS 12 +#define GRAPH_START_X 0 + +#define GRAPH_HEIGHT (GRAPH_PAGES * 8) +#define GRAPH_PAGES 7 +#define GRAPH_START_Y 1 + +//uint8_t graph_desired[GRAPH_POINTS+1]; +//uint8_t graph_actual[GRAPH_POINTS+1]; +uint8_t graph_actual[4]; + +static void minmax_mark(uint8_t *min_p, uint8_t *max_p, uint8_t mid) +{ + uint8_t min = *min_p; + uint8_t max = *max_p; + + if (mid == 255) { + *min_p = 255; + return; + } + + if (min == 255) + min = mid; + if (max == 255) + max = mid; + if (max < min) { + uint8_t tmp = min; + min = max; + max = tmp; + } + *min_p = mid - (mid-min)/2; + *max_p = (max-mid)/2 + mid; +} + +temp_t graph_temp_min, graph_temp_range; +uint32_t time_per_unit; +uint8_t graph_cursor; + +static uint8_t graph_value(temp_t temp); + +static uint8_t graph_at(uint8_t col) +{ + return graph_value(temperature_at_time(col*time_per_unit)); +} + +static void graphics_draw_column(uint8_t col) +{ + uint8_t data; + uint8_t pr_mid, pr_min, pr_max; + uint8_t act_mid, act_min, act_max; + uint8_t i; + uint8_t page; + uint8_t act_col; + + if (col >= GRAPH_POINTS) + return; + + if (col == graph_cursor) + act_col = 1; + else + act_col = 0; + + pr_mid = graph_at(col); + pr_min = graph_at(col-1); + pr_max = graph_at(col+1); + + minmax_mark(&pr_min, &pr_max, pr_mid); + + act_min = graph_actual[act_col]; + act_mid = graph_actual[act_col+1]; + act_max = graph_actual[act_col+2]; + + minmax_mark(&act_min, &act_max, act_mid); + + if (col == 0) { + pr_min = 0; + pr_max = GRAPH_HEIGHT-1; + } + + page = GRAPH_PAGES-1; + for (i = 0; i < GRAPH_HEIGHT; i++) { + data <<= 1; + if ((i >= pr_min && i <= pr_max) || + (i >= act_min && i <= act_max) || + (i == 0)) { + data |= 1; + } + if ((i & 0x7) == 7) { + if (graph_cursor == col) + data |= 0x55; + display_setposition(col, GRAPH_START_Y + page); + display_data(1, &data); + page--; + } + } +} + +static void graphics_draw_screen(void) +{ + int i; + + /* initialise profile */ + + for (i = 0; i < GRAPH_POINTS; i++) + graphics_draw_column(i); +} + +static uint8_t __attribute__ ((noinline)) graph_value(temp_t temp) +{ + int32_t v; + + if (temp == INVALID_TEMPERATURE) + v = 255; + else + v = ((int32_t)temp - graph_temp_min) * GRAPH_HEIGHT / graph_temp_range; + + return v; +} + +static void graphics_init(void) +{ + uint8_t i; + uint32_t total_time; + temp_t temp_min, temp_max; + uint8_t *graph_actual_p; + + run_status = RUN_STATUS_READY; + + temp_min = temp_max = get_profile_temperature(1); + + total_time = 0; + + for (i = 0; i < 6; i++) { + temp_t temp = get_profile_temperature(i+2); + uint32_t time = profile_time(i); + if (time == 0) + continue; + total_time += time; + if (temp > temp_max) + temp_max = temp; + if (temp < temp_min) + temp_min = temp; + } + + time_per_unit = (total_time + GRAPH_POINTS - 1) / GRAPH_POINTS; + + temp_min -= 10 * 10; + temp_max += 10 * 10; + graph_temp_range = temp_max - temp_min; + graph_temp_min = temp_min; + + graph_cursor = 255; + + graph_actual_p = &graph_actual[0]; + + for (i = 0; i < 4; i++) + *(graph_actual_p++) = 255; +} + +static void graphics_poll(void) +{ + if (menu_redraw) { + graphics_init(); + graphics_draw_screen(); + } + if (run_status == RUN_STATUS_RUNNING) { + uint8_t col = control_now() / time_per_unit; + if (col <= GRAPH_POINTS) { + if (col != graph_cursor) { + graph_actual[0] = graph_actual[1]; + graph_actual[1] = graph_actual[2]; + } + graph_actual[2] = graph_value(therm_temp()); + } else { + run_status = RUN_STATUS_FINISHED; + } + graph_cursor = col; + graphics_draw_column(col-1); + graphics_draw_column(col); + } +} + +void menu_poll(void) +{ + uint8_t button_pressed; + + ATOMIC_BLOCK(ATOMIC_FORCEON) { + button_pressed = button; + button = 0; + } + + if (button_pressed == BUTTON_LEFT || button_pressed == BUTTON_RIGHT) + menu_process_leftright(button_pressed == BUTTON_LEFT); + if (button_pressed == BUTTON_UP || button_pressed == BUTTON_DOWN) + menu_process_updown(button_pressed == BUTTON_UP); + + if (menu_redraw || (menu_current == MENU_RUN_PROFILE) || (menu_current == MENU_MAIN)) { + display_menu(menu_redraw); + menu_set_fields(menu_row); + } + + if (menu_current == MENU_RUN_PROFILE) { + graphics_poll(); + } + menu_redraw = FALSE; +} diff --git a/menu.h b/menu.h new file mode 100644 index 0000000..45bdabc --- /dev/null +++ b/menu.h @@ -0,0 +1,30 @@ +/* menu.h */ + +#ifndef _MENU_H_ +#define _MENU_H_ + +#include "common.h" + +void menu_init(void); +void menu_poll(void); +void menu_new_data(void); + +/* This stuff is for fields.c only, for now */ +#include "fields.h" +extern field_t fields[]; +char menu_getchar(uint8_t col); +typedef void (*field_handler)(uint8_t row); +typedef struct menu { + PGM_P const *text; + uint8_t first_line; + uint8_t last_line; + field_handler get_field; + field_handler put_field; +} menu_t; +extern menu_t *menu_current_p; +int32_t temperature_from_kelvin(temp_t source); +temp_t temperature_to_kelvin(int32_t source); +extern uint8_t field_row; +extern uint8_t current_profile; + +#endif diff --git a/profile.c b/profile.c new file mode 100644 index 0000000..91586a2 --- /dev/null +++ b/profile.c @@ -0,0 +1,130 @@ +/* profile.c */ + +#include +#include +#include +#include +#include + +#include "common.h" +#include "profile.h" +#include "menu.h" +#include "config.h" +#include "fields.h" +#include "therm.h" + +const profile_t profiles[2] PROGMEM = { +// { "123456789012345678901", 250, +// { "Lead free solder ", 250 + 2732, { + { "NoPb ", 250 + 2732, { + { 1500 + 2732, 100, 0 }, + { 2000 + 2732, 100, 0 }, + { 2500 + 2732, 40, 0 }, + { 2500 + 2732, 30, 0 }, + { 1000 + 2732, 30, 0 }, + { 0 + 2732, 0, 0 } + } }, +// { "Leaded solder ", 250 + 2732, { + { "Pb ", 250 + 2732, { + { 1000 + 2732, 100, 0 }, + { 1500 + 2732, 100, 0 }, + { 2350 + 2732, 40, 0 }, + { 2350 + 2732, 30, 0 }, + { 1000 + 2732, 30, 0 }, + { 0 + 2732, 0, 0 } + } } +}; + +profile_t profile; +profile_t *profile_p = &profile; +uint8_t current_profile; + +void profile_select(uint8_t n) +{ + current_profile = n; + memcpy_P(&profile, &profiles[n], sizeof(profile_t)); +} + +void profile_save(void) +{ +// memcpy(&profiles[current_profile], profile_p, sizeof(profile_t)); +} + + +#if 0 +static void select_profile_fields(uint8_t row) +{ + memcpy(field_values, profiles[row].name, PROFILE_NAME_LENGTH); +} +#endif + +void set_profile_temperature(temp_t temp) +{ + uint8_t row = field_row; + + if (row > 1) + profile_p->lines[row-2].temperature = temp; + else + profile_p->start_temp = temp; +} + +temp_t get_profile_temperature(uint8_t row) +{ + if (row > 1) + return profile_p->lines[row-2].temperature; + else + return profile_p->start_temp; +} + +void set_profile_time(uint8_t row, uint16_t time, uint8_t time_units) +{ + profile_p->lines[row].time = time; + profile_p->lines[row].time_units = time_units; +} + +uint16_t profile_get_time(uint8_t line) +{ + return profile_p->lines[line].time; +} + +uint8_t profile_get_time_units(uint8_t row) +{ + return profile_p->lines[row].time_units; +} + +uint32_t profile_time(uint8_t line) +{ + uint32_t time = profile_get_time(line); + uint8_t time_units = profile_get_time_units(line); + while (time_units--) + time = time * 60; + + return time; +} + +temp_t temperature_at_time(uint32_t time) { + uint32_t time0, time1; + temp_t temp0, temp1; + uint8_t i; + + temp1 = get_profile_temperature(1); + time1 = 0; + + for (i = 0; i < 6; i++) { + uint32_t ptime; + temp0 = temp1; + time0 = time1; + ptime = profile_time(i); + if (ptime > 0) { + temp1 = get_profile_temperature(2+i); + time1 += ptime; + } + if (time <= time1) + break; + } + + if (time > time1) + return INVALID_TEMPERATURE; + + return temp0 + ((int32_t)temp1 - (int32_t)temp0) * (int32_t)(time - time0) / (int32_t)(time1 - time0); +} diff --git a/profile.h b/profile.h new file mode 100644 index 0000000..d906517 --- /dev/null +++ b/profile.h @@ -0,0 +1,34 @@ +/* profile.h */ + +#ifndef _PROFILE_H_ +#define _PROFILE_H_ + +#include "common.h" + +void set_profile_temperature(temp_t temp); +temp_t get_profile_temperature(uint8_t row); +uint32_t profile_time(uint8_t line); +temp_t temperature_at_time(uint32_t time); +void set_profile_time(uint8_t row, uint16_t time, uint8_t time_units); +uint16_t profile_get_time(uint8_t row); +uint8_t profile_get_time_units(uint8_t row); +void profile_select(uint8_t n); +void profile_save(void); + +typedef struct profile_line { + uint16_t temperature; /* store degrees C * 10 reference to absolute zero */ + unsigned int time:14; + unsigned int time_units:2; +} profile_line_t; + +#define PROFILE_NAME_LENGTH 6 + +typedef struct profile { + char name[PROFILE_NAME_LENGTH]; + uint16_t start_temp; + profile_line_t lines[6]; +} profile_t; + +extern profile_t *profile_p; + +#endif diff --git a/reflow.c b/reflow.c new file mode 100644 index 0000000..cb5769c --- /dev/null +++ b/reflow.c @@ -0,0 +1,36 @@ +/* reflow.c */ + +#include "common.h" +#include "therm.h" +#include "display.h" +#include "menu.h" +#include "beep.h" +#include "timer.h" +#include "control.h" + +/* + * Reflow controller firmware + * coolfactor.org + */ + +int main(void) +{ + uint32_t last_seconds = 0; + uint32_t this_seconds; + /* init code here */ + + therm_init(); + timer_init(); + menu_init(); + + while (1) { + therm_read(); +// menu_new_data(); + this_seconds = seconds; + if (this_seconds != last_seconds) { + control_poll(); + last_seconds = this_seconds; + } + menu_poll(); + } +} diff --git a/spi.c b/spi.c new file mode 100644 index 0000000..90f06e8 --- /dev/null +++ b/spi.c @@ -0,0 +1,54 @@ +/* spi.c */ + +#include + +#include "common.h" + +#define DDR_SPI DDRB +#define PORT_SPI PORTB + +#define PIN_MOSI 3 +#define PIN_SCK 5 +#define PIN_SS 2 + +void spi_init(void) +{ + /* Set MOSI, SCK and SS output */ + PORT_SPI |= (1< + +#define PROGMEM +#define PGM_P const char * +#define pgm_read_byte(x) (*(char *)(x)) +#define pgm_read_ptr(x) (*(char **)(x)) +#define strlen_P strlen +#define memcpy_P memcpy + +#endif diff --git a/test/test-display.c b/test/test-display.c new file mode 100644 index 0000000..f2878c7 --- /dev/null +++ b/test/test-display.c @@ -0,0 +1,68 @@ +/* test-display.c */ + +#include +#include + +#include "types.h" + +unsigned int curpos; + +void display_cleanup(void) +{ + endwin(); +} + +void display_data(uint8_t len, uint8_t *data) +{ + /* can't do anything with this graphics */ +} + +void display_init(void) +{ + initscr(); + noecho(); + atexit(display_cleanup); +} + +void display_setposition(char col, char row) +{ + move(row, col/6); + curpos = col/6; + //printf("Position set to row %d, col %d\n", row, col/6); +} + +void display_setinverse(bool on) +{ + if (on) + attron(A_REVERSE); + else + attroff(A_REVERSE); + //printf("Inverse set to %s\n", on?"on":"off"); +} + +void display_putchar(char c) +{ + printw("%c", c); + refresh(); + curpos++; + //printf("Put character %c\n", c); +} + +void display_putstr(char *s) +{ + while (*s) + display_putchar(*(s++)); +} + +void display_putstr_P(const char *s) +{ + display_putstr((char *)s); +} + +void display_clearline(void) +{ + while (curpos < 21) { + display_putchar(' '); + } + //printf("Cleared line\n"); +} diff --git a/test/test-graphics.c b/test/test-graphics.c new file mode 100644 index 0000000..103fe55 --- /dev/null +++ b/test/test-graphics.c @@ -0,0 +1,70 @@ +/* test-graphics.c */ + +#include "SDL.h" +#include "SDL_gfxPrimitives.h" +#include "menu.h" + +#include "common.h" + +SDL_Surface *screen; + +SDL_Event event; + +#define WHITE 0xffffffff + +int button = 0; + +uint32_t seconds; +uint8_t output0; + +void beep_off(void) +{ +} + +int main(int argc, char *argv[]) { + SDL_Init(SDL_INIT_VIDEO); + + screen = SDL_SetVideoMode(128, 64, 16, SDL_SWSURFACE); + + SDL_WM_SetCaption("Reflow oven controller", "Reflow oven controller"); + + bool done=FALSE; + + menu_init(); + + while(!done) { + while(SDL_PollEvent(&event)) { + if (event.type == SDL_QUIT) { + done=TRUE; + } + if (event.type == SDL_KEYDOWN) { + switch(event.key.keysym.sym) { + case SDLK_UP: + button = 1; + break; + case SDLK_DOWN: + button = 2; + break; + case SDLK_LEFT: + button = 4; + break; + case SDLK_RIGHT: + button = 3; + break; + case SDLK_q: + done = TRUE; + break; + default: + break; + } + } + menu_poll(); + SDL_Flip(screen); + } + } + + SDL_Quit(); + + return 0; +} + diff --git a/test/test-i2c.c b/test/test-i2c.c new file mode 100644 index 0000000..b1dd1ab --- /dev/null +++ b/test/test-i2c.c @@ -0,0 +1,62 @@ +/* i2c.c */ + +#include "common.h" +#include "SDL.h" +#include "SDL_gfxPrimitives.h" + + +#define WHITE 0xffffffff +#define BLACK 0x000000ff + +uint8_t i2c_xor; +uint8_t page; +uint8_t col; + + +extern SDL_Surface *screen; + +void i2c_init(void) +{ +} + +void i2c_set_xor(char xor) +{ + i2c_xor = xor; +} + +bool i2c_write(char address, char prefix, char bytes, char *data) +{ + uint8_t i, j; + + switch ((uint8_t) prefix) { + case 0x80: + switch (data[0] & 0xf0) { + case 0xb0: + page = data[0] & 0xf; + break; + case 0x00: + col = (col & ~0xf) + (data[0] & 0xf); + break; + case 0x10: + col = (col & 0xf) + ((data[0] & 0xf) << 4); + break; + } + break; + case 0x40: + for (i = 0; i < bytes; i++) { + uint8_t byte = data[i] ^ i2c_xor; + for (j = 0; j < 8; j++) { + pixelColor(screen, col, page*8 + j, (byte & 1)?WHITE:BLACK); + byte = byte >> 1; + } + col++; + if (col > 127) + col = 0; + } + break; + } + + + return TRUE; +} + diff --git a/test/test-menu.c b/test/test-menu.c new file mode 100644 index 0000000..ab5d3c6 --- /dev/null +++ b/test/test-menu.c @@ -0,0 +1,46 @@ +/* test-menu.c */ + +#include +#include +#include +#include "display.h" +#include "menu.h" + +int button = 0; + +int main(void) +{ +/* + display_init(); + display_putstr("Hello, world!"); +*/ + + menu_init(); + keypad(stdscr, TRUE); + menu_poll(); + + while (1) { + int c = getch(); + switch (c) { + case KEY_UP: + button = 1; + break; + case KEY_DOWN: + button = 2; + break; + case KEY_LEFT: + button = 3; + break; + case KEY_RIGHT: + button = 4; + break; + case 'q': + case 'Q': + exit(0); + } + menu_poll(); + } + + return 0; +} + diff --git a/test/test-therm.c b/test/test-therm.c new file mode 100644 index 0000000..06b5dfd --- /dev/null +++ b/test/test-therm.c @@ -0,0 +1,49 @@ +/* test-therm.c */ + +#include "common.h" +#include "therm.h" + +int32_t extend24(int32_t n) +{ + if (n & 0x00800000) { + return n | (~0 & 0xff000000); + } + return n; +} + +temp_t therm_temp(void) +{ +// return extend24(0xf06000); + return 3932; +#if 0 + return 0x078000; + return 0x064f00; + return 0x52d000; +#endif +} + +temp_t therm_coldtemp(void) +{ + return 2987; +#if 0 + return 0x19800; +#endif +} + +uint8_t therm_fault(void) +{ + return 0; +} + +temp_t therm_reduce(int32_t temp) +{ + temp = (temp * 10) / 4096; + temp += 2732; + if (temp < 0) + temp = 0; + if (temp > 65535) + temp = 65535; + + return (temp_t) temp; +} + diff --git a/test/types.h b/test/types.h new file mode 100644 index 0000000..a983b19 --- /dev/null +++ b/test/types.h @@ -0,0 +1,23 @@ +/* types.h */ + +#ifndef _TYPES_H_ +#define _TYPES_H_ + +/* Probably right for a PC */ + +typedef unsigned char uint8; +typedef signed char int8; + +typedef unsigned short uint16; +typedef signed short int16; + +typedef unsigned int uint32; +typedef signed int int32; + +#ifndef TRUE +typedef unsigned char bool; +#define TRUE 1 +#define FALSE 0 +#endif + +#endif diff --git a/test/util/atomic.h b/test/util/atomic.h new file mode 100644 index 0000000..cbeb58c --- /dev/null +++ b/test/util/atomic.h @@ -0,0 +1 @@ +#define ATOMIC_BLOCK(x) diff --git a/therm.c b/therm.c new file mode 100644 index 0000000..9fbd18c --- /dev/null +++ b/therm.c @@ -0,0 +1,68 @@ +/* therm.c */ + +#include "common.h" +#include "therm.h" + +#include "spi.h" + +uint8_t therm_cjth; +uint8_t therm_cjtl; +uint8_t therm_ltcbh; +uint8_t therm_ltcbm; +uint8_t therm_ltcbl; +uint8_t therm_sr; + +void therm_init(void) +{ + spi_init(); + + spi_start(); + spi_txbyte(0x80); + spi_txbyte(0x91); /* Automatic conversion, open detection on, 50Hz */ + spi_txbyte(0x03); /* No averaging, K-type */ + spi_txbyte(0x00); /* No masking. ~FAULT asserted for most faults. */ + spi_stop(); +} + +void therm_read(void) +{ + spi_start(); + spi_txbyte(0x0a); + therm_cjth = spi_rxbyte(); + therm_cjtl = spi_rxbyte(); + therm_ltcbh = spi_rxbyte(); + therm_ltcbm = spi_rxbyte(); + therm_ltcbl = spi_rxbyte(); + therm_sr = spi_rxbyte(); + spi_stop(); +} + +temp_t therm_reduce(int32_t temp) +{ + temp = (temp * 10) / 4096; + temp += 2732; + if (temp < 0) + temp = 0; + if (temp > 65535) + temp = 65535; + + return (temp_t) temp; +} + +temp_t therm_coldtemp(void) +{ + int16_t temp = (int16_t) (((int16_t)therm_cjth << 8) | therm_cjtl); + return therm_reduce((int32_t)temp * 16); +} + +temp_t therm_temp(void) +{ + int32_t temp = ((int32_t)therm_ltcbh << 16) | ((int32_t)therm_ltcbm << 8) | therm_ltcbl; + + return therm_reduce(temp); +} + +uint8_t therm_fault(void) +{ + return therm_sr; +} diff --git a/therm.h b/therm.h new file mode 100644 index 0000000..99bf5d4 --- /dev/null +++ b/therm.h @@ -0,0 +1,17 @@ +/* therm.h */ + +#ifndef _THERM_H_ +#define _THERM_H_ + +#include "common.h" + +void therm_init(void); +void therm_read(void); +temp_t therm_coldtemp(void); +temp_t therm_temp(void); +uint8_t therm_fault(void); +temp_t therm_reduce(int32_t); + +#define INVALID_TEMPERATURE 0xffff + +#endif diff --git a/timer.c b/timer.c new file mode 100644 index 0000000..8290a51 --- /dev/null +++ b/timer.c @@ -0,0 +1,87 @@ +/* timer.c */ + +#include +#include + +#include "common.h" +#include "timer.h" + +uint8_t bounce_timer; +uint8_t button; +uint8_t button_port; + +#define BOUNCE_TIMER_RESET 10 + +#define PIN_OUTPUT0 0 +#define PIN_OUTPUT1 1 + +volatile uint8_t output0; +volatile uint8_t output1; +uint8_t tick; +volatile uint32_t seconds; + +void timer_init(void) +{ + bounce_timer = 0; + button = 0; + button_port = 0; + tick = TICKS_PER_SECOND-1; + seconds = 0; + + TCCR0A = (1<> 4); +/* + if (bounce_timer) + bounce_timer--; + else +*/ + button_port = port; + + if (button_port != old_button) + bounce_timer = BOUNCE_TIMER_RESET; + + button_pressed = button_port & ~old_button; + if (button_pressed & 8) + button = 4; + if (button_pressed & 4) + button = 3; + if (button_pressed & 2) + button = 2; + if (button_pressed & 1) + button = 1; + + if ((TICKS_PER_SECOND-1 - tick) < output0) + PORTD |= (1<