terom@58: /* terom@58: * Control the JY-LKM1638 LED display module. terom@58: */ terom@58: terom@58: #include terom@59: #include terom@58: terom@58: #include "stdlib.h" terom@58: terom@58: // XXX terom@58: #include "timer.c" terom@58: terom@58: #define LKM_DDR DDRC terom@58: #define LKM_PORT PORTC terom@59: #define LKM_PIN PINC terom@58: #define LKM_CLK 0 terom@58: #define LKM_DIO 1 terom@58: #define LKM_STB 2 terom@58: terom@58: enum lkm_cmd { terom@58: LKM_CMD_DATA = 0b01000000, terom@58: LKM_CMD_CONTROL = 0b10000000, terom@58: LKM_CMD_ADDRESS = 0b11000000, terom@58: terom@59: LKM_DATA_WRITE = 0b00000000, terom@59: LKM_DATA_READ = 0b00000010, terom@58: terom@58: LKM_DATA_ADDR_AUTO = 0b00000000, terom@58: LKM_DATA_ADDR_FIXED = 0b00000100, terom@58: terom@58: LKM_DATA_TEST_NORMAL = 0b00000000, terom@58: LKM_DATA_TEST_TEST = 0b00001000, terom@58: terom@58: LKM_ADDRESS = 0b00001111, terom@58: terom@58: LKM_CONTROL_INTENSITY = 0b00000111, terom@58: LKM_CONTROL_INTENSITY_MIN = 0b00000000, terom@58: LKM_CONTROL_INTENSITY_MAX = 0b00000111, terom@58: terom@58: LKM_CONTROL_DISPLAY_OFF = 0b00000000, terom@58: LKM_CONTROL_DISPLAY_ON = 0b00001000, terom@58: }; terom@58: terom@58: static const uint8_t LKM_DISPLAY_FONT[] = { terom@58: 0b00111111, // 0 terom@58: 0b00000110, // 1 terom@58: 0b01011011, // 2 terom@58: 0b01001111, // 3 terom@58: 0b01100110, // 4 terom@58: 0b01101101, // 5 terom@58: 0b01111101, // 6 terom@58: 0b00000111, // 7 terom@58: 0b01111111, // 8 terom@58: 0b01100111, // 9 terom@58: 0b01110111, // A terom@58: 0b01111100, // B terom@58: 0b00111001, // C terom@58: 0b01011110, // D terom@58: 0b01111001, // E terom@58: 0b01110001, // F terom@58: }; terom@58: terom@58: enum { terom@60: LKM_DISPLAY_DECIMAL = 0b10000000, terom@58: }; terom@58: terom@60: /* terom@60: * XXX: RED/GREEN somehow swapped vs reference; bug in our write code? terom@60: */ terom@59: enum { terom@59: LKM_LED_OFF = 0b00, terom@60: LKM_LED_GREEN = 0b01, terom@60: LKM_LED_RED = 0b10, terom@59: LKM_LED_ORANGE = 0b11, terom@59: }; terom@59: terom@59: /* terom@59: * Button bitfields. terom@59: * terom@59: * The JY-LKM1638 uses K3 terom@59: */ terom@59: enum { terom@59: LKM_BUTTON_K1L = 0b00000100, terom@59: LKM_BUTTON_K2L = 0b00000010, terom@59: LKM_BUTTON_K3L = 0b00000001, terom@59: terom@59: LKM_BUTTON_K1H = 0b01000000, terom@59: LKM_BUTTON_K2H = 0b00100000, terom@59: LKM_BUTTON_K3H = 0b00010000, terom@59: }; terom@59: terom@58: void lkm_init () terom@58: { terom@58: // strobe off: high output terom@58: // XXX: should use an external pull-up resistor? terom@58: sbi(&LKM_PORT, LKM_STB); terom@58: sbi(&LKM_DDR, LKM_STB); terom@58: terom@58: // clock low terom@58: sbi(&LKM_DDR, LKM_CLK); terom@58: cbi(&LKM_PORT, LKM_CLK); terom@58: terom@58: // data tri-state terom@58: cbi(&LKM_DDR, LKM_DIO); terom@58: cbi(&LKM_DDR, LKM_DIO); terom@58: } terom@58: terom@58: /* terom@58: * Select the LKM for write. terom@58: */ terom@59: void lkm_out () terom@58: { terom@58: // clock low terom@58: cbi(&LKM_PORT, LKM_CLK); terom@58: terom@58: // data out terom@58: sbi(&LKM_DDR, LKM_DIO); terom@58: terom@58: // select on: low terom@58: cbi(&LKM_PORT, LKM_STB); terom@58: } terom@58: terom@58: /* terom@59: * Select the LKM for read. terom@59: */ terom@59: void lkm_in () terom@59: { terom@59: // data in terom@59: cbi(&LKM_DDR, LKM_DIO); terom@59: cbi(&LKM_PORT, LKM_DIO); terom@59: terom@59: // select on: low terom@59: cbi(&LKM_PORT, LKM_STB); terom@59: terom@59: // clock high terom@59: sbi(&LKM_PORT, LKM_CLK); terom@59: terom@59: // pause terom@59: _delay_us(1); terom@59: } terom@59: terom@59: /* terom@58: * Write out one byte terom@58: */ terom@58: void lkm_write (byte b) terom@58: { terom@58: char i; terom@58: terom@58: for (i = 0; i < 8; i++) { terom@59: // clock read: low terom@58: cbi(&LKM_PORT, LKM_CLK); terom@58: terom@58: // set output terom@58: if (b & 1) terom@58: sbi(&LKM_PORT, LKM_DIO); terom@58: else terom@58: cbi(&LKM_PORT, LKM_DIO); terom@58: terom@59: // clock write: high terom@58: sbi(&LKM_PORT, LKM_CLK); terom@58: terom@58: // next bit terom@58: b >>= 1; terom@58: } terom@58: } terom@58: terom@58: /* terom@59: * Read in one byte. terom@59: */ terom@59: byte lkm_read () terom@59: { terom@59: byte b = 0; terom@59: char i; terom@59: terom@59: // XXX: this loop is timing-critical; we must allow the signal to settle betwen clocks terom@59: for (i = 0; i < 8; i++) { terom@59: // next bit terom@59: b >>= 1; terom@59: terom@59: // clock read: low terom@59: cbi(&LKM_PORT, LKM_CLK); terom@59: terom@59: // pause terom@59: _delay_us(1); terom@59: terom@59: // read input terom@59: if (tbi(&LKM_PIN, LKM_DIO)) terom@59: b |= 0x80; terom@59: terom@59: // pause terom@59: _delay_us(1); terom@59: terom@59: // clock write: high terom@59: sbi(&LKM_PORT, LKM_CLK); terom@59: terom@59: // pause terom@59: _delay_us(1); terom@59: } terom@59: terom@59: return b; terom@59: } terom@59: terom@59: /* terom@58: * End command. terom@58: */ terom@58: void lkm_end () terom@58: { terom@58: // select off: high terom@58: sbi(&LKM_PORT, LKM_STB); terom@58: terom@58: // tristate data terom@58: cbi(&LKM_DDR, LKM_DIO); terom@58: } terom@58: terom@58: void lkm_cmd (byte cmd) terom@58: { terom@59: lkm_out(); terom@58: lkm_write(cmd); terom@58: lkm_end(); terom@58: } terom@58: terom@58: void lkm_cmd1 (byte cmd, byte arg) terom@58: { terom@59: lkm_out(); terom@58: lkm_write(cmd); terom@58: lkm_write(arg); terom@58: lkm_end(); terom@58: } terom@58: terom@58: static inline void lkm_control (byte display, byte intensity) terom@58: { terom@58: lkm_cmd(LKM_CMD_CONTROL terom@58: | (display ? LKM_CONTROL_DISPLAY_ON : LKM_CONTROL_DISPLAY_OFF) terom@58: | (intensity & LKM_CONTROL_INTENSITY) terom@58: ); terom@58: } terom@58: terom@58: /* terom@59: * Blank display/LEDs terom@58: */ terom@59: static void lkm_clear () terom@58: { terom@58: char i; terom@58: terom@58: lkm_cmd(LKM_CMD_DATA terom@59: | LKM_DATA_TEST_NORMAL terom@59: | LKM_DATA_ADDR_AUTO terom@58: | LKM_DATA_WRITE terom@58: ); terom@58: terom@58: // write out all 16 bytes of 0x00 terom@59: lkm_out(); terom@58: lkm_write(LKM_CMD_ADDRESS | (0x0) & LKM_ADDRESS); terom@58: for (i = 0; i < 16; i++) { terom@58: lkm_write(0x00); terom@58: } terom@58: lkm_end(); terom@58: terom@58: } terom@58: terom@58: /* terom@58: * Set raw output mask for given display 0..7 terom@58: */ terom@58: static inline void lkm_display (byte display, byte value) terom@58: { terom@58: lkm_cmd(LKM_CMD_DATA terom@59: | LKM_DATA_TEST_NORMAL terom@59: | LKM_DATA_ADDR_AUTO terom@58: | LKM_DATA_WRITE terom@58: ); terom@58: lkm_cmd1(LKM_CMD_ADDRESS | ((display * 2 + 0) & LKM_ADDRESS), terom@58: value terom@58: ); terom@58: } terom@58: terom@58: /* terom@59: * Set raw output mask for given led 0..7 terom@59: */ terom@59: static inline void lkm_led (byte led, byte value) terom@59: { terom@59: lkm_cmd(LKM_CMD_DATA terom@59: | LKM_DATA_TEST_NORMAL terom@59: | LKM_DATA_ADDR_AUTO terom@59: | LKM_DATA_WRITE terom@59: ); terom@59: lkm_cmd1(LKM_CMD_ADDRESS | ((led * 2 + 1) & LKM_ADDRESS), terom@59: value terom@59: ); terom@59: } terom@59: terom@59: /* terom@58: * Set 4-bit hexadecimal output for given display 0..7 terom@58: */ terom@58: static inline void lkm_display_hex (byte display, byte hex) terom@58: { terom@58: lkm_display(display, LKM_DISPLAY_FONT[hex]); terom@58: } terom@58: terom@60: static inline void lkm_display_dec (byte display, byte dec, byte decimal) terom@60: { terom@60: byte raw; terom@60: terom@60: if (dec < 10) terom@60: raw = LKM_DISPLAY_FONT[dec]; terom@60: else terom@60: raw = 0x00; terom@60: terom@60: if (decimal) terom@60: raw |= LKM_DISPLAY_DECIMAL; terom@60: terom@60: lkm_display(display, raw); terom@60: } terom@60: terom@58: /* terom@59: * Read the 8-bit key states terom@59: */ terom@59: byte lkm_buttons () terom@59: { terom@59: byte k3 = 0; terom@59: char i; terom@59: terom@59: lkm_out(); terom@59: lkm_write(LKM_CMD_DATA terom@59: | LKM_DATA_TEST_NORMAL terom@59: | LKM_DATA_ADDR_AUTO terom@59: | LKM_DATA_READ terom@59: ); terom@59: lkm_in(); terom@59: for (i = 0; i < 4; i++) { terom@59: /* terom@59: * The ordering of keys used is weird; it seems to go 04 15 26 37 terom@59: */ terom@59: k3 |= lkm_read() << i; terom@59: terom@59: /* terom@59: k3 >>= 1; terom@59: terom@59: byte b = lkm_read(); terom@59: terom@59: if (b & LKM_BUTTON_K3L) terom@59: k3 |= 0b00001000; terom@59: terom@59: if (b & LKM_BUTTON_K3H) terom@59: k3 |= 0b10000000; terom@59: */ terom@59: } terom@59: lkm_end(); terom@59: terom@59: return k3; terom@59: } terom@59: terom@59: /* terom@60: * Set 8-bit hexadecimal output on displays n..n+1 terom@60: */ terom@60: void lkm_display_hh (byte display, byte hex) terom@60: { terom@60: lkm_display_hex(display + 0, ((hex >> 4) & 0xF)); terom@60: lkm_display_hex(display + 1, ((hex >> 0) & 0xF)); terom@60: } terom@60: terom@60: /* terom@58: * Set 16-bit hexadecimal output on displays 4..7 terom@58: */ terom@60: void lkm_display_hhhh (short hex) terom@58: { terom@58: lkm_display_hex(7, ((hex >> 0) & 0xF)); terom@58: lkm_display_hex(6, ((hex >> 4) & 0xF)); terom@58: lkm_display_hex(5, ((hex >> 8) & 0xF)); terom@58: lkm_display_hex(4, ((hex >> 12) & 0xF)); terom@58: } terom@58: terom@60: /* terom@60: * Set 2-digit decimal output on displays n..n+1 terom@60: */ terom@60: void lkm_display_dd (byte display, byte dec) terom@60: { terom@60: byte d = dec / 10; terom@60: byte u = dec % 10; terom@60: terom@60: byte c = d / 10; terom@60: d = d % 10; terom@60: terom@60: lkm_display_dec(display + 0, d, c > 1); terom@60: lkm_display_dec(display + 1, u, c > 0); terom@60: } terom@60: terom@58: // debug terom@58: #define DEBUG_DDR DDRB terom@58: #define DEBUG_PORT PORTB terom@58: #define DEBUG_LED 0 terom@58: terom@58: int main (void) terom@58: { terom@58: // led_init(); terom@58: sbi(&DEBUG_DDR, DEBUG_LED); terom@58: terom@58: timer_init(); terom@58: lkm_init(); terom@58: terom@58: sei(); terom@58: terom@58: // setup display terom@59: lkm_clear(); terom@58: lkm_control(LKM_CONTROL_DISPLAY_ON, LKM_CONTROL_INTENSITY_MAX); terom@58: terom@60: /* terom@60: // test terom@60: lkm_led(0, LKM_LED_OFF); terom@60: lkm_led(1, LKM_LED_RED); terom@60: lkm_led(2, LKM_LED_GREEN); terom@60: lkm_led(3, LKM_LED_ORANGE); terom@60: terom@60: return 0; terom@60: */ terom@60: terom@58: // start terom@60: byte channels[4] = { }; terom@61: enum channel_state { terom@61: CHANNEL_IDLE = 0, terom@61: CHANNEL_INC, terom@61: CHANNEL_DEC, terom@61: } channel_state[4] = { }; terom@61: byte channel_count[4] = { }; terom@60: char c; terom@58: terom@58: while (true) { terom@59: // scan input terom@59: byte buttons = lkm_buttons(); terom@59: terom@60: for (c = 0; c < 4; c++) { terom@61: enum channel_state state = channel_state[c], next; terom@61: byte count = channel_count[c]; terom@61: terom@61: // decode buttons -> state terom@60: if (buttons & 0b1) { terom@61: next = CHANNEL_INC; terom@60: } else if (buttons & 0b10) { terom@61: next = CHANNEL_DEC; terom@59: } else { terom@61: next = CHANNEL_IDLE; terom@59: } terom@60: terom@60: buttons >>= 2; terom@60: terom@61: // feedback terom@61: if (state == CHANNEL_INC || next == CHANNEL_INC) terom@61: lkm_led(c * 2 + 0, LKM_LED_GREEN); terom@61: else if (state == CHANNEL_DEC && next == CHANNEL_DEC) terom@61: lkm_led(c * 2 + 0, LKM_LED_RED); terom@61: else terom@61: lkm_led(c * 2 + 0, LKM_LED_OFF); terom@61: terom@61: if (state == CHANNEL_DEC || next == CHANNEL_DEC) terom@61: lkm_led(c * 2 + 1, LKM_LED_RED); terom@61: else if (state == CHANNEL_INC && next == CHANNEL_INC) terom@61: lkm_led(c * 2 + 1, LKM_LED_GREEN); terom@61: else terom@61: lkm_led(c * 2 + 1, LKM_LED_OFF); terom@61: terom@61: // counts terom@61: if ((channel_state[c] = next) != state) terom@61: channel_count[c] = 0; terom@61: else terom@61: count = ++channel_count[c]; terom@61: terom@61: // state transitions terom@61: if (next == CHANNEL_INC && count > 0) { terom@61: channels[c] += count; terom@61: } else if (next == CHANNEL_DEC && count > 0) { terom@61: channels[c] -= count; terom@61: } else if (state == CHANNEL_INC && !count) { terom@61: channels[c] = 0xff; terom@61: } else if (state == CHANNEL_DEC && !count) { terom@61: channels[c] = 0x00; terom@61: } terom@61: terom@60: lkm_display_hh(c * 2, channels[c]); terom@61: terom@59: xbi(&DEBUG_PORT, DEBUG_LED); terom@59: } terom@59: terom@61: timer_sleep(8000); terom@58: } terom@58: }