From ee43c12f398367e7e348a2c16cfc8f9eab780b5c Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Sun, 1 Dec 2013 21:45:57 -0700 Subject: [PATCH] Working clock + display --- Makefile | 41 +++++++ ascii.h | 100 +++++++++++++++ avr.c | 44 +++++++ blink.c | 176 ++++++++++++++++++++++++++ main.c | 367 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 728 insertions(+) create mode 100644 Makefile create mode 100644 ascii.h create mode 100644 avr.c create mode 100644 blink.c create mode 100644 main.c diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5dc4ea6 --- /dev/null +++ b/Makefile @@ -0,0 +1,41 @@ +PROG = blink + +MCU = attiny84 + +CC = avr-gcc +CFLAGS += -mmcu=$(MCU) +CFLAGS += -Os +CFLAGS += -w + +LDFLAGS += -mmcu=$(MCU) + +AVDFLAGS += -p $(MCU) +AVDFLAGS += -c usbtiny + +CLOCK_HZ = 16000000 +FUSES += -U lfuse:w:0x7f:m +FUSES += -U hfuse:w:0xdd:m +FUSES += -U efuse:w:0xff:m + +upload: .upload + +.upload: $(PROG).hex + avrdude $(AVDFLAGS) -U flash:w:$< + touch $@ + +fuses: + avrdude $(AVDFLAGS) $(FUSES) + +main: main.o avr.o +blink: blink.o avr.o +avr.o: CFLAGS += -DCLOCK_HZ=$(CLOCK_HZ) + +%.hex: % + avr-objcopy -O ihex -R .eeprom -R .fuse -R .lock -R .signature $< $@ + +clean: + rm -f $(PROG) *.o *.hex .upload + + + + diff --git a/ascii.h b/ascii.h new file mode 100644 index 0000000..2b9f655 --- /dev/null +++ b/ascii.h @@ -0,0 +1,100 @@ +//This table contains the hex values that represent pixels +//for a font that is 5 pixels wide and 8 pixels high +static const uint8_t ASCII[][5] = { + {0x00, 0x00, 0x00, 0x00, 0x00} // 20 + ,{0x00, 0x00, 0x5f, 0x00, 0x00} // 21 ! + ,{0x00, 0x07, 0x00, 0x07, 0x00} // 22 " + ,{0x14, 0x7f, 0x14, 0x7f, 0x14} // 23 # + ,{0x24, 0x2a, 0x7f, 0x2a, 0x12} // 24 $ + ,{0x23, 0x13, 0x08, 0x64, 0x62} // 25 % + ,{0x36, 0x49, 0x55, 0x22, 0x50} // 26 & + ,{0x00, 0x05, 0x03, 0x00, 0x00} // 27 ' + ,{0x00, 0x1c, 0x22, 0x41, 0x00} // 28 ( + ,{0x00, 0x41, 0x22, 0x1c, 0x00} // 29 ) + ,{0x14, 0x08, 0x3e, 0x08, 0x14} // 2a * + ,{0x08, 0x08, 0x3e, 0x08, 0x08} // 2b + + ,{0x00, 0x50, 0x30, 0x00, 0x00} // 2c , + ,{0x08, 0x08, 0x08, 0x08, 0x08} // 2d - + ,{0x00, 0x60, 0x60, 0x00, 0x00} // 2e . + ,{0x20, 0x10, 0x08, 0x04, 0x02} // 2f / + ,{0x3e, 0x51, 0x49, 0x45, 0x3e} // 30 0 + ,{0x00, 0x42, 0x7f, 0x40, 0x00} // 31 1 + ,{0x42, 0x61, 0x51, 0x49, 0x46} // 32 2 + ,{0x21, 0x41, 0x45, 0x4b, 0x31} // 33 3 + ,{0x18, 0x14, 0x12, 0x7f, 0x10} // 34 4 + ,{0x27, 0x45, 0x45, 0x45, 0x39} // 35 5 + ,{0x3c, 0x4a, 0x49, 0x49, 0x30} // 36 6 + ,{0x01, 0x71, 0x09, 0x05, 0x03} // 37 7 + ,{0x36, 0x49, 0x49, 0x49, 0x36} // 38 8 + ,{0x06, 0x49, 0x49, 0x29, 0x1e} // 39 9 + ,{0x00, 0x36, 0x36, 0x00, 0x00} // 3a : + ,{0x00, 0x56, 0x36, 0x00, 0x00} // 3b ; + ,{0x08, 0x14, 0x22, 0x41, 0x00} // 3c < + ,{0x14, 0x14, 0x14, 0x14, 0x14} // 3d = + ,{0x00, 0x41, 0x22, 0x14, 0x08} // 3e > + ,{0x02, 0x01, 0x51, 0x09, 0x06} // 3f ? + ,{0x32, 0x49, 0x79, 0x41, 0x3e} // 40 @ + ,{0x7e, 0x11, 0x11, 0x11, 0x7e} // 41 A + ,{0x7f, 0x49, 0x49, 0x49, 0x36} // 42 B + ,{0x3e, 0x41, 0x41, 0x41, 0x22} // 43 C + ,{0x7f, 0x41, 0x41, 0x22, 0x1c} // 44 D + ,{0x7f, 0x49, 0x49, 0x49, 0x41} // 45 E + ,{0x7f, 0x09, 0x09, 0x09, 0x01} // 46 F + ,{0x3e, 0x41, 0x49, 0x49, 0x7a} // 47 G + ,{0x7f, 0x08, 0x08, 0x08, 0x7f} // 48 H + ,{0x00, 0x41, 0x7f, 0x41, 0x00} // 49 I + ,{0x20, 0x40, 0x41, 0x3f, 0x01} // 4a J + ,{0x7f, 0x08, 0x14, 0x22, 0x41} // 4b K + ,{0x7f, 0x40, 0x40, 0x40, 0x40} // 4c L + ,{0x7f, 0x02, 0x0c, 0x02, 0x7f} // 4d M + ,{0x7f, 0x04, 0x08, 0x10, 0x7f} // 4e N + ,{0x3e, 0x41, 0x41, 0x41, 0x3e} // 4f O + ,{0x7f, 0x09, 0x09, 0x09, 0x06} // 50 P + ,{0x3e, 0x41, 0x51, 0x21, 0x5e} // 51 Q + ,{0x7f, 0x09, 0x19, 0x29, 0x46} // 52 R + ,{0x46, 0x49, 0x49, 0x49, 0x31} // 53 S + ,{0x01, 0x01, 0x7f, 0x01, 0x01} // 54 T + ,{0x3f, 0x40, 0x40, 0x40, 0x3f} // 55 U + ,{0x1f, 0x20, 0x40, 0x20, 0x1f} // 56 V + ,{0x3f, 0x40, 0x38, 0x40, 0x3f} // 57 W + ,{0x63, 0x14, 0x08, 0x14, 0x63} // 58 X + ,{0x07, 0x08, 0x70, 0x08, 0x07} // 59 Y + ,{0x61, 0x51, 0x49, 0x45, 0x43} // 5a Z + ,{0x00, 0x7f, 0x41, 0x41, 0x00} // 5b [ + ,{0x02, 0x04, 0x08, 0x10, 0x20} // 5c [backslash] + ,{0x00, 0x41, 0x41, 0x7f, 0x00} // 5d ] + ,{0x04, 0x02, 0x01, 0x02, 0x04} // 5e ^ + ,{0x40, 0x40, 0x40, 0x40, 0x40} // 5f _ + ,{0x00, 0x01, 0x02, 0x04, 0x00} // 60 ` + ,{0x20, 0x54, 0x54, 0x54, 0x78} // 61 a + ,{0x7f, 0x48, 0x44, 0x44, 0x38} // 62 b + ,{0x38, 0x44, 0x44, 0x44, 0x20} // 63 c + ,{0x38, 0x44, 0x44, 0x48, 0x7f} // 64 d + ,{0x38, 0x54, 0x54, 0x54, 0x18} // 65 e + ,{0x08, 0x7e, 0x09, 0x01, 0x02} // 66 f + ,{0x0c, 0x52, 0x52, 0x52, 0x3e} // 67 g + ,{0x7f, 0x08, 0x04, 0x04, 0x78} // 68 h + ,{0x00, 0x44, 0x7d, 0x40, 0x00} // 69 i + ,{0x20, 0x40, 0x44, 0x3d, 0x00} // 6a j + ,{0x7f, 0x10, 0x28, 0x44, 0x00} // 6b k + ,{0x00, 0x41, 0x7f, 0x40, 0x00} // 6c l + ,{0x7c, 0x04, 0x18, 0x04, 0x78} // 6d m + ,{0x7c, 0x08, 0x04, 0x04, 0x78} // 6e n + ,{0x38, 0x44, 0x44, 0x44, 0x38} // 6f o + ,{0x7c, 0x14, 0x14, 0x14, 0x08} // 70 p + ,{0x08, 0x14, 0x14, 0x18, 0x7c} // 71 q + ,{0x7c, 0x08, 0x04, 0x04, 0x08} // 72 r + ,{0x48, 0x54, 0x54, 0x54, 0x20} // 73 s + ,{0x04, 0x3f, 0x44, 0x40, 0x20} // 74 t + ,{0x3c, 0x40, 0x40, 0x20, 0x7c} // 75 u + ,{0x1c, 0x20, 0x40, 0x20, 0x1c} // 76 v + ,{0x3c, 0x40, 0x30, 0x40, 0x3c} // 77 w + ,{0x44, 0x28, 0x10, 0x28, 0x44} // 78 x + ,{0x0c, 0x50, 0x50, 0x50, 0x3c} // 79 y + ,{0x44, 0x64, 0x54, 0x4c, 0x44} // 7a z + ,{0x00, 0x08, 0x36, 0x41, 0x00} // 7b { + ,{0x00, 0x00, 0x7f, 0x00, 0x00} // 7c | + ,{0x00, 0x41, 0x36, 0x08, 0x00} // 7d } + ,{0x10, 0x08, 0x08, 0x10, 0x08} // 7e ~ + ,{0x78, 0x46, 0x41, 0x46, 0x78} // 7f DEL +}; diff --git a/avr.c b/avr.c new file mode 100644 index 0000000..f85a573 --- /dev/null +++ b/avr.c @@ -0,0 +1,44 @@ +#include +#include +#include +#include +#include "avr.h" + +/* Clock must be a multiple of 2MHz or there will be clock drift */ +#define TICK_HZ (CLOCK_HZ / 8 / 64) +#define TICKS_PER_JIFFY (TICK_HZ / 10) + +#define cbi(byt, bit) (byt &= ~_BV(bit)) +#define sbi(byt, bit) (byt |= _BV(bit)) + +extern volatile bool tick; +extern volatile uint32_t jiffies; + +// Interrupt called every jiffy +ISR(TIM1_COMPA_vect) +{ + jiffies += 1; + tick = true; +} + +void +init(void) +{ + int i; + + DDRA = ~(_BV(NESOUT)); + DDRB = 0xff; + + TCCR1A = 0; + TCCR1B = 0; + TCNT1 = 0; // reset counter + + OCR1A = TICKS_PER_JIFFY - 1; + TCCR1B |= _BV(WGM12); + TCCR1B |= _BV(CS11) | _BV(CS10); // prescale: clk_io / 64 + TIMSK1 |= _BV(OCIE1A); + + bit(PORTA, _BV(7), true); + + sei(); +} diff --git a/blink.c b/blink.c new file mode 100644 index 0000000..3b9e7ef --- /dev/null +++ b/blink.c @@ -0,0 +1,176 @@ +#include +#include +#include +#include +#include + +#include "ascii.h" + +#define bit(reg, bit, on) reg = (on ? (reg | _BV(bit)) : (reg & ~_BV(bit))); + +#define sce(on) bit(PORTA, 0, on) +#define rst(on) bit(PORTA, 1, on) +#define dc(on) bit(PORTA, 2, on) +#define sclk(on) bit(PORTA, 4, on) +#define dn(on) bit(PORTA, 5, on) + +#define LED _BV(OC0A) + +#define LCD_X 84 +#define LCD_Y 48 + +#define LCD_COMMAND false +#define LCD_DATA true + +volatile uint32_t jiffies = 0; +volatile bool tick = false; + +void +initPWM0() +{ + DDRA |= (1 << PA7); // Set OC0B to output + + // Phase-correct PWM, non-inverting mode + TCCR0A |= _BV(COM0A1) | _BV(WGM00); + + // No clock prescaling + TCCR0B |= ((1 << CS00)); +} + +inline void +setPWM0A(uint8_t num) +{ + OCR0A = num; +} + +void +LCDWrite(bool datap, uint8_t v) +{ + int i; + + dc(datap); + sce(false); + + // Push out over SPI + USIDR = v; + USISR = _BV(USIOIF); + while ((USISR & _BV(USIOIF)) == 0) { + USICR = _BV(USIWM0) | _BV(USICS1) | _BV(USICLK) | _BV(USITC); + } + + sce(true); +} + +void +LCDChar(char c) +{ + int i; + + LCDWrite(true, 0); + for (i = 0; i < 5; i += 1) { + LCDWrite(true, ASCII[c - 0x20][i]); + } + LCDWrite(true, 0); +} + +void +LCDString(char *s) +{ + for (; *s; s += 1) { + LCDChar(*s); + } +} + +void +LCDGoto(int x, int y) +{ + LCDWrite(false, 0x80 | x); // Column. + LCDWrite(false, 0x40 | y); // Row. ? +} + +void +LCDClear(void) +{ + int i; + + LCDGoto(0, 0); + for (i = 0; i < (LCD_X * LCD_Y / 8); i++) { + LCDWrite(true, 0x00); + } + + LCDGoto(0, 0); // After we clear the display, return to the home position +} + +void +write_digit(uint8_t d) +{ + LCDChar('0' + (d % 10)); +} + +void +write_time(uint16_t secs) +{ + uint16_t min; + + min = secs / 60; + secs = secs % 60; + + if (min >= 10) { + write_digit(min / 10); + } else { + LCDChar(' '); + } + write_digit(min % 10); + LCDChar(':'); + write_digit(secs / 10); + write_digit(secs % 10); +} + +void +loop(void) +{ + static uint8_t v = 0; + + if (tick) { + int i; + + tick = 0; + + OCR0A = (jiffies % 10) * 2; + + if (jiffies % 10 == 0) { + LCDGoto(0, 0); + LCDString("Jam "); + write_time(jiffies / 10); + } + } +} + + +int +main(void) +{ + init(); + initPWM0(); + + DDRA = 0xFF; + + // Reset display + rst(false); + rst(true); + + LCDWrite(false, 0x21); // Tell LCD that extended commands follow + LCDWrite(false, 0xB0); // Set LCD Vop (Contrast): Try 0xB1(good @ 3.3V) or 0xBF if your display is too dark + LCDWrite(false, 0x04); // Set Temp coefficent + LCDWrite(false, 0x13); // LCD bias mode 1:48: Try 0x13 or 0x14 + + LCDWrite(false, 0x20); // We must send 0x20 before modifying the display control mode + LCDWrite(false, 0x0C); // Set display control, normal mode. 0x0D for inverse + + LCDClear(); + + for (;;) { + loop(); + } + return 0; +} diff --git a/main.c b/main.c new file mode 100644 index 0000000..26bd0e8 --- /dev/null +++ b/main.c @@ -0,0 +1,367 @@ +#include +#include +#include + +#include +#include + +#include "avr.h" + +// Number of shift registers in your scoreboard +// If you want scores to go over 199, you need 8 +const int nsr = 8; + +// +// Timing stuff +// + +// +// 2**32 deciseconds = 13.610221 years +// +// As long as you unplug your scoreboard once every 10 years or so, +// you're good. +// +volatile uint32_t jiffies = 0; // Elapsed time in deciseconds +volatile bool tick = false; // Set high when jiffy clock ticks + + +// Clocks are in deciseconds +uint16_t score_a = 0; +uint16_t score_b = 0; +int16_t period_clock = -(30 * 60 * 10); +int16_t jam_duration = -(2 * 60 * 10); +int16_t lineup_duration = (-30 * 10); +int16_t jam_clock = 0; +enum { + SETUP, + JAM, + LINEUP, + TIMEOUT, + KONAMI +} state = SETUP; + + +// NES Controller buttons + +#define BTN_A _BV(7) +#define BTN_B _BV(6) +#define BTN_SELECT _BV(5) +#define BTN_START _BV(4) +#define BTN_UP _BV(3) +#define BTN_DOWN _BV(2) +#define BTN_LEFT _BV(1) +#define BTN_RIGHT _BV(0) + +const uint8_t konami_code[] = { + BTN_UP, BTN_UP, BTN_DOWN, BTN_DOWN, + BTN_LEFT, BTN_RIGHT, BTN_LEFT, BTN_RIGHT, + BTN_B, BTN_A, + 0 +}; +int konami_pos = 0; +const uint8_t test_pattern[] = { + _BV(2), _BV(3), _BV(4), _BV(5), _BV(6), _BV(1), _BV(0), _BV(7) +}; + +const uint8_t seven_segment_digits[] = { + 0x7b, 0x60, 0x37, 0x76, 0x6c, 0x5e, 0x5f, 0x70, 0x7f, 0x7e +}; + +const uint8_t setup_digits[] = { + 0x1b, 0x12, 0x72 +}; + +void +latch() +{ + sltch(true); + sltch(false); +} + +void +pulse() +{ + sclk(true); + sclk(false); +} + +void +write(uint8_t number) +{ + int i; + int j; + + // MSB first + for (i = 7; i >= 0; i -= 1) { + sin(number & (1 << i)); + pulse(); + } +} + +void +write_num(uint16_t number, int digits) +{ + int i; + + for (i = 0; i < digits; i += 1) { + uint8_t out = seven_segment_digits[number % 10]; + + // Overflow indicator + if ((i == digits - 1) && (number > 9)) { + // Blink to indicate double-rollover + if ((number > 19) && (jiffies % 3 == 0)) { + // leave it blank + } else { + out ^= 0x80; + } + } + + write(out); + number /= 10; + } +} + +/* + * Update all the digits + */ +void +draw() +{ + + uint16_t jclk; + uint16_t pclk; + bool blank = ((state == TIMEOUT) && (jiffies % 8 == 0)); + + // Segments test mode + if (KONAMI == state) { + int i; + + for (i = 0; i < 12; i += 1) { + write(test_pattern[jiffies % (sizeof test_pattern)]);; + } + + latch(); + pulse(); + return; + } + + jclk = (abs(jam_clock / 600) % 10) * 1000; + jclk += abs(jam_clock) % 600; + + pclk = (abs(period_clock / 10) / 60) * 100; + pclk += abs(period_clock / 10) % 60; + +#ifdef DEMO + if (jam_clock == 0) { + if (state == LINEUP) { + state = JAM; + jam_clock = jam_duration; + } else { + state = LINEUP; + jam_clock = lineup_duration; + } + } + + if (period_clock == 0) { + period_clock = - (30 * 60 * 10); + jam_clock = jam_duration; + state = JAM; + } +#endif + + // Score A + write_num(score_b, 2); + + // Jam clock, least significant half + write_num(jclk % 100, 2); + + // Period clock + if (blank) { + write(0); + write(0); + write(0); + write(0); + } else if (state == SETUP) { + write(setup_digits[2]); + write(setup_digits[1]); + write(setup_digits[1]); + write(setup_digits[0]); + } else { + write_num(pclk, 4); + } + + // Jam clock, most significant half + write_num(jclk / 100, 2); + + // Score A + write_num(score_a, 2); + + if (false) { + int i; + for (i = 0; i < 12; i += 1) { + write_num(jiffies / 10, 1); + } + } + // Tell chips to start displaying new values + latch(); + pulse(); +} + +/* + * Probe the NES controller + */ +uint8_t +nesprobe() +{ + int i; + uint8_t state = 0; + + nesltch(true); + nesltch(false); + + for (i = 0; i < 8; i += 1) { + state <<= 1; + if (nesout()) { + // Button not pressed + } else { + state |= 1; + } + nesclk(true); + nesclk(false); + } + + // Only report button down events. + return state; +} + +void +update_controller() +{ + static uint8_t last_val = 0; + static uint32_t last_change = 0; + static uint32_t last_typematic = 0; + uint8_t cur; + uint8_t pressed; + int inc = 1; + + cur = nesprobe(); + pressed = (last_val ^ cur) & cur; + if (last_val != cur) { + last_change = jiffies; + last_val = cur; + } + + if (pressed == konami_code[konami_pos]) { + konami_pos += 1; + + if (konami_code[konami_pos] == 0) { + state = KONAMI; + konami_pos = 0; + return; + } else if (konami_pos > 3) { + return; + } + } + + // Select means subtract + if (cur & BTN_SELECT) { + inc = -1; + } + + if ((pressed & BTN_A) && ((state != JAM) || (jam_clock == 0))) { + state = JAM; + jam_clock = jam_duration; + } + + if ((pressed & BTN_B) && ((state != LINEUP) || (jam_clock == 0))) { + state = LINEUP; + jam_clock = lineup_duration; + } + + if ((pressed & BTN_START) && (state != TIMEOUT)) { + state = TIMEOUT; + jam_clock = 1; + } + + if ((state == TIMEOUT) || (state == SETUP)) { + uint8_t v = pressed; + + if ((jiffies - last_change > 10) && (last_typematic < jiffies)) { + v = cur; + last_typematic = jiffies; + } + if (v & BTN_UP) { + period_clock -= 10; + } + if (v & BTN_DOWN) { + period_clock += 10; + } + } + + if (pressed & BTN_LEFT) { + score_a += inc; + } + + if (pressed & BTN_RIGHT) { + score_b += inc; + } +} + +/* + * + * Main program + * + */ + + +void +setup() +{ + // The TLC5941 required some setup. + // The TPIC doesn't. + // Hooray. + + PORTB = 0xff; +} + +void +loop() +{ + update_controller(); + + if (tick) { + tick = false; + + if (jiffies % 5 == 0) { + PORTB ^= 0xff; + } + + switch (state) { + case SETUP: + break; + case JAM: + case LINEUP: + if (period_clock) { + period_clock += 1; + } + // fall through + case TIMEOUT: + if (jam_clock) { + jam_clock += 1; + } + } + } + + draw(); +} + +int +main(void) +{ + init(); + setup(); + for (;;) { + loop(); + } + return 0; +}