src/hello-lkm.c
author Tero Marttila <terom@paivola.fi>
Sat, 05 Apr 2014 02:19:36 +0300
changeset 60 b9648067e9d7
parent 59 7090f61e5e17
child 61 a960cf5981f7
permissions -rw-r--r--
hello-lkm: simple 4-channel control
/*
 * 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] = { };
    char c;
        
    while (true) {
        // scan input
        byte buttons = lkm_buttons();
        
        for (c = 0; c < 4; c++) {
            if (buttons & 0b1) {
                channels[c]++;
                lkm_led(c * 2 + 0, LKM_LED_GREEN);
                lkm_led(c * 2 + 1, LKM_LED_OFF);
            } else if (buttons & 0b10) {
                channels[c]--;
                lkm_led(c * 2 + 0, LKM_LED_OFF);
                lkm_led(c * 2 + 1, LKM_LED_RED);
            } else {
                lkm_led(c * 2 + 0, LKM_LED_OFF);
                lkm_led(c * 2 + 1, LKM_LED_OFF);
            }
            
            buttons >>= 2;
            
            lkm_display_hh(c * 2, channels[c]);
            
            xbi(&DEBUG_PORT, DEBUG_LED);
        }

        timer_sleep(1000);
    }
}