From aaaec8ff40eaa695a4ade6350973b5e3ceeac84f Mon Sep 17 00:00:00 2001 From: Gavan Fantom Date: Thu, 13 Aug 2015 14:43:19 +0000 Subject: [PATCH] First commit of partly-working reflow oven controller. --- .mtn-ignore | 4 + Makefile | 67 +++ beep.c | 24 + beep.h | 9 + button.c | 33 ++ button.h | 8 + common.h | 11 + config.c | 15 + config.h | 25 + control.c | 122 +++++ control.h | 12 + display.c | 140 ++++++ display.h | 18 + fields.c | 268 ++++++++++ fields.h | 53 ++ font.c | 123 +++++ font.h | 8 + humancontrol/Makefile | 3 + humancontrol/soldertimer.c | 115 +++++ i2c.c | 95 ++++ i2c.h | 12 + menu.c | 970 +++++++++++++++++++++++++++++++++++++ menu.h | 30 ++ profile.c | 130 +++++ profile.h | 34 ++ reflow.c | 36 ++ spi.c | 54 +++ spi.h | 15 + test/Makefile | 23 + test/avr/io.h | 0 test/avr/pgmspace.h | 13 + test/test-display.c | 68 +++ test/test-graphics.c | 70 +++ test/test-i2c.c | 62 +++ test/test-menu.c | 46 ++ test/test-therm.c | 49 ++ test/types.h | 23 + test/util/atomic.h | 1 + therm.c | 68 +++ therm.h | 17 + timer.c | 87 ++++ timer.h | 24 + types.h | 25 + 43 files changed, 3010 insertions(+) create mode 100644 .mtn-ignore create mode 100644 Makefile create mode 100644 beep.c create mode 100644 beep.h create mode 100644 button.c create mode 100644 button.h create mode 100644 common.h create mode 100644 config.c create mode 100644 config.h create mode 100644 control.c create mode 100644 control.h create mode 100644 display.c create mode 100644 display.h create mode 100644 fields.c create mode 100644 fields.h create mode 100644 font.c create mode 100644 font.h create mode 100644 humancontrol/Makefile create mode 100644 humancontrol/soldertimer.c create mode 100644 i2c.c create mode 100644 i2c.h create mode 100644 menu.c create mode 100644 menu.h create mode 100644 profile.c create mode 100644 profile.h create mode 100644 reflow.c create mode 100644 spi.c create mode 100644 spi.h create mode 100644 test/Makefile create mode 100644 test/avr/io.h create mode 100644 test/avr/pgmspace.h create mode 100644 test/test-display.c create mode 100644 test/test-graphics.c create mode 100644 test/test-i2c.c create mode 100644 test/test-menu.c create mode 100644 test/test-therm.c create mode 100644 test/types.h create mode 100644 test/util/atomic.h create mode 100644 therm.c create mode 100644 therm.h create mode 100644 timer.c create mode 100644 timer.h create mode 100644 types.h 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<