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