src/hello-lkm.c
changeset 89 1b3cea759eff
parent 88 c923295ee520
child 90 13c2deb919d1
equal deleted inserted replaced
88:c923295ee520 89:1b3cea759eff
     1 /*
       
     2  * Control the JY-LKM1638 LED display module.
       
     3  */
       
     4 
       
     5 #include <avr/io.h>
       
     6 #include <util/delay.h>
       
     7 
       
     8 #include "stdlib.h"
       
     9 
       
    10 // XXX
       
    11 #include "timer.c"
       
    12 
       
    13 #define LKM_DDR     DDRC
       
    14 #define LKM_PORT    PORTC
       
    15 #define LKM_PIN     PINC
       
    16 #define LKM_CLK     0
       
    17 #define LKM_DIO     1
       
    18 #define LKM_STB     2
       
    19 
       
    20 enum lkm_cmd {
       
    21     LKM_CMD_DATA    = 0b01000000,
       
    22     LKM_CMD_CONTROL = 0b10000000,
       
    23     LKM_CMD_ADDRESS = 0b11000000,
       
    24 
       
    25     LKM_DATA_WRITE          = 0b00000000,
       
    26     LKM_DATA_READ           = 0b00000010,
       
    27 
       
    28     LKM_DATA_ADDR_AUTO      = 0b00000000,
       
    29     LKM_DATA_ADDR_FIXED     = 0b00000100,
       
    30 
       
    31     LKM_DATA_TEST_NORMAL    = 0b00000000,
       
    32     LKM_DATA_TEST_TEST      = 0b00001000,
       
    33 
       
    34     LKM_ADDRESS             = 0b00001111,
       
    35     
       
    36     LKM_CONTROL_INTENSITY       = 0b00000111,
       
    37     LKM_CONTROL_INTENSITY_MIN   = 0b00000000,
       
    38     LKM_CONTROL_INTENSITY_MAX   = 0b00000111,
       
    39 
       
    40     LKM_CONTROL_DISPLAY_OFF = 0b00000000,
       
    41     LKM_CONTROL_DISPLAY_ON  = 0b00001000,
       
    42 };
       
    43 
       
    44 static const uint8_t LKM_DISPLAY_FONT[] = {
       
    45     0b00111111,	// 0
       
    46     0b00000110,	// 1
       
    47     0b01011011,	// 2
       
    48     0b01001111,	// 3
       
    49     0b01100110,	// 4
       
    50     0b01101101,	// 5
       
    51     0b01111101,	// 6
       
    52     0b00000111,	// 7
       
    53     0b01111111,	// 8
       
    54     0b01100111,	// 9
       
    55     0b01110111,	// A
       
    56     0b01111100,	// B
       
    57     0b00111001,	// C
       
    58     0b01011110,	// D
       
    59     0b01111001,	// E
       
    60     0b01110001,	// F
       
    61 };
       
    62 
       
    63 enum {
       
    64     LKM_DISPLAY_DECIMAL = 0b10000000,
       
    65 };
       
    66 
       
    67 /*
       
    68  * XXX: RED/GREEN somehow swapped vs reference; bug in our write code?
       
    69  */
       
    70 enum {
       
    71     LKM_LED_OFF     = 0b00,
       
    72     LKM_LED_GREEN   = 0b01,
       
    73     LKM_LED_RED     = 0b10,
       
    74     LKM_LED_ORANGE  = 0b11,
       
    75 };
       
    76 
       
    77 /*
       
    78  * Button bitfields.
       
    79  *
       
    80  * The JY-LKM1638 uses K3
       
    81  */
       
    82 enum {
       
    83     LKM_BUTTON_K1L  = 0b00000100,
       
    84     LKM_BUTTON_K2L  = 0b00000010,
       
    85     LKM_BUTTON_K3L  = 0b00000001,
       
    86     
       
    87     LKM_BUTTON_K1H  = 0b01000000,
       
    88     LKM_BUTTON_K2H  = 0b00100000,
       
    89     LKM_BUTTON_K3H  = 0b00010000,
       
    90 };
       
    91 
       
    92 void lkm_init ()
       
    93 {
       
    94     // strobe off: high output
       
    95     // XXX: should use an external pull-up resistor?
       
    96     sbi(&LKM_PORT, LKM_STB);
       
    97     sbi(&LKM_DDR, LKM_STB);
       
    98 
       
    99     // clock low
       
   100     sbi(&LKM_DDR, LKM_CLK);
       
   101     cbi(&LKM_PORT, LKM_CLK);
       
   102 
       
   103     // data tri-state
       
   104     cbi(&LKM_DDR, LKM_DIO);
       
   105     cbi(&LKM_DDR, LKM_DIO);
       
   106 }
       
   107 
       
   108 /*
       
   109  * Select the LKM for write.
       
   110  */
       
   111 void lkm_out ()
       
   112 {
       
   113     // clock low
       
   114     cbi(&LKM_PORT, LKM_CLK);
       
   115 
       
   116     // data out
       
   117     sbi(&LKM_DDR, LKM_DIO);
       
   118 
       
   119     // select on: low
       
   120     cbi(&LKM_PORT, LKM_STB);
       
   121 }
       
   122 
       
   123 /*
       
   124  * Select the LKM for read.
       
   125  */
       
   126 void lkm_in ()
       
   127 {
       
   128     // data in
       
   129     cbi(&LKM_DDR, LKM_DIO);
       
   130     cbi(&LKM_PORT, LKM_DIO);
       
   131 
       
   132     // select on: low
       
   133     cbi(&LKM_PORT, LKM_STB);
       
   134 
       
   135     // clock high
       
   136     sbi(&LKM_PORT, LKM_CLK);
       
   137 
       
   138     // pause
       
   139     _delay_us(1);
       
   140 }
       
   141 
       
   142 /*
       
   143  * Write out one byte
       
   144  */
       
   145 void lkm_write (byte b)
       
   146 {
       
   147     char i;
       
   148 
       
   149     for (i = 0; i < 8; i++) {
       
   150         // clock read: low
       
   151         cbi(&LKM_PORT, LKM_CLK);
       
   152 
       
   153         // set output
       
   154         if (b & 1)
       
   155             sbi(&LKM_PORT, LKM_DIO);
       
   156         else 
       
   157             cbi(&LKM_PORT, LKM_DIO);
       
   158         
       
   159         // clock write: high
       
   160         sbi(&LKM_PORT, LKM_CLK);
       
   161         
       
   162         // next bit
       
   163         b >>= 1;
       
   164     }
       
   165 }
       
   166 
       
   167 /*
       
   168  * Read in one byte.
       
   169  */
       
   170 byte lkm_read ()
       
   171 {
       
   172     byte b = 0;
       
   173     char i;
       
   174 
       
   175     // XXX: this loop is timing-critical; we must allow the signal to settle betwen clocks
       
   176     for (i = 0; i < 8; i++) {
       
   177         // next bit
       
   178         b >>= 1;
       
   179 
       
   180         // clock read: low
       
   181         cbi(&LKM_PORT, LKM_CLK);
       
   182     
       
   183         // pause
       
   184         _delay_us(1);
       
   185 
       
   186         // read input
       
   187         if (tbi(&LKM_PIN, LKM_DIO))
       
   188             b |= 0x80;
       
   189         
       
   190         // pause
       
   191         _delay_us(1);
       
   192 
       
   193         // clock write: high
       
   194         sbi(&LKM_PORT, LKM_CLK);
       
   195         
       
   196         // pause
       
   197         _delay_us(1);
       
   198     }
       
   199 
       
   200     return b;
       
   201 }
       
   202 
       
   203 /*
       
   204  * End command.
       
   205  */
       
   206 void lkm_end ()
       
   207 {
       
   208     // select off: high
       
   209     sbi(&LKM_PORT, LKM_STB);
       
   210 
       
   211     // tristate data
       
   212     cbi(&LKM_DDR, LKM_DIO);
       
   213 }
       
   214 
       
   215 void lkm_cmd (byte cmd)
       
   216 {
       
   217     lkm_out();
       
   218     lkm_write(cmd);
       
   219     lkm_end();
       
   220 }
       
   221 
       
   222 void lkm_cmd1 (byte cmd, byte arg)
       
   223 {
       
   224     lkm_out();
       
   225     lkm_write(cmd);
       
   226     lkm_write(arg);
       
   227     lkm_end();
       
   228 }
       
   229 
       
   230 static inline void lkm_control (byte display, byte intensity)
       
   231 {
       
   232     lkm_cmd(LKM_CMD_CONTROL
       
   233         |   (display ? LKM_CONTROL_DISPLAY_ON : LKM_CONTROL_DISPLAY_OFF)
       
   234         |   (intensity & LKM_CONTROL_INTENSITY)
       
   235     );
       
   236 }
       
   237 
       
   238 /*
       
   239  * Blank display/LEDs
       
   240  */
       
   241 static void lkm_clear ()
       
   242 {
       
   243     char i;
       
   244 
       
   245     lkm_cmd(LKM_CMD_DATA
       
   246         |   LKM_DATA_TEST_NORMAL
       
   247         |   LKM_DATA_ADDR_AUTO
       
   248         |   LKM_DATA_WRITE
       
   249     );
       
   250     
       
   251     // write out all 16 bytes of 0x00
       
   252     lkm_out();
       
   253     lkm_write(LKM_CMD_ADDRESS | (0x0) & LKM_ADDRESS);
       
   254     for (i = 0; i < 16; i++) {
       
   255         lkm_write(0x00);
       
   256     }
       
   257     lkm_end();
       
   258 
       
   259 }
       
   260 
       
   261 /*
       
   262  * Set raw output mask for given display 0..7
       
   263  */
       
   264 static inline void lkm_display (byte display, byte value)
       
   265 {
       
   266     lkm_cmd(LKM_CMD_DATA
       
   267         |   LKM_DATA_TEST_NORMAL
       
   268         |   LKM_DATA_ADDR_AUTO
       
   269         |   LKM_DATA_WRITE
       
   270     );
       
   271     lkm_cmd1(LKM_CMD_ADDRESS | ((display * 2 + 0) & LKM_ADDRESS),
       
   272         value
       
   273     );
       
   274 }
       
   275 
       
   276 /*
       
   277  * Set raw output mask for given led 0..7
       
   278  */
       
   279 static inline void lkm_led (byte led, byte value)
       
   280 {
       
   281     lkm_cmd(LKM_CMD_DATA
       
   282         |   LKM_DATA_TEST_NORMAL
       
   283         |   LKM_DATA_ADDR_AUTO
       
   284         |   LKM_DATA_WRITE
       
   285     );
       
   286     lkm_cmd1(LKM_CMD_ADDRESS | ((led * 2 + 1) & LKM_ADDRESS),
       
   287         value
       
   288     );
       
   289 }
       
   290 
       
   291 /*
       
   292  * Set 4-bit hexadecimal output for given display 0..7
       
   293  */
       
   294 static inline void lkm_display_hex (byte display, byte hex)
       
   295 {
       
   296     lkm_display(display, LKM_DISPLAY_FONT[hex]);
       
   297 }
       
   298 
       
   299 static inline void lkm_display_dec (byte display, byte dec, byte decimal)
       
   300 {
       
   301     byte raw;
       
   302 
       
   303     if (dec < 10)
       
   304         raw = LKM_DISPLAY_FONT[dec];
       
   305     else
       
   306         raw = 0x00;
       
   307 
       
   308     if (decimal)
       
   309         raw |= LKM_DISPLAY_DECIMAL;
       
   310 
       
   311     lkm_display(display, raw);
       
   312 }
       
   313 
       
   314 /*
       
   315  * Read the 8-bit key states
       
   316  */
       
   317 byte lkm_buttons ()
       
   318 {
       
   319     byte k3 = 0;
       
   320     char i;
       
   321 
       
   322     lkm_out();
       
   323     lkm_write(LKM_CMD_DATA
       
   324         |   LKM_DATA_TEST_NORMAL
       
   325         |   LKM_DATA_ADDR_AUTO
       
   326         |   LKM_DATA_READ
       
   327     );
       
   328     lkm_in();
       
   329     for (i = 0; i < 4; i++) {
       
   330         /*
       
   331          * The ordering of keys used is weird; it seems to go 04 15 26 37
       
   332          */
       
   333         k3 |= lkm_read() << i;
       
   334         
       
   335         /*
       
   336         k3 >>= 1;
       
   337 
       
   338         byte b = lkm_read();
       
   339 
       
   340         if (b & LKM_BUTTON_K3L)
       
   341             k3 |= 0b00001000;
       
   342 
       
   343         if (b & LKM_BUTTON_K3H)
       
   344             k3 |= 0b10000000;
       
   345         */
       
   346     }
       
   347     lkm_end();
       
   348 
       
   349     return k3;
       
   350 }
       
   351 
       
   352 /*
       
   353  * Set 8-bit hexadecimal output on displays n..n+1
       
   354  */
       
   355 void lkm_display_hh (byte display, byte hex)
       
   356 {
       
   357     lkm_display_hex(display + 0, ((hex >> 4) & 0xF));
       
   358     lkm_display_hex(display + 1, ((hex >> 0) & 0xF));
       
   359 }
       
   360 
       
   361 /*
       
   362  * Set 16-bit hexadecimal output on displays 4..7
       
   363  */
       
   364 void lkm_display_hhhh (short hex)
       
   365 {
       
   366     lkm_display_hex(7, ((hex >> 0) & 0xF));
       
   367     lkm_display_hex(6, ((hex >> 4) & 0xF));
       
   368     lkm_display_hex(5, ((hex >> 8) & 0xF));
       
   369     lkm_display_hex(4, ((hex >> 12) & 0xF));
       
   370 }
       
   371 
       
   372 /*
       
   373  * Set 2-digit decimal output on displays n..n+1
       
   374  */
       
   375 void lkm_display_dd (byte display, byte dec)
       
   376 {
       
   377     byte d = dec / 10;
       
   378     byte u = dec % 10;
       
   379 
       
   380     byte c = d / 10;
       
   381     d = d % 10;
       
   382 
       
   383     lkm_display_dec(display + 0, d, c > 1);
       
   384     lkm_display_dec(display + 1, u, c > 0);
       
   385 }
       
   386 
       
   387 // debug
       
   388 #define DEBUG_DDR   DDRB
       
   389 #define DEBUG_PORT  PORTB
       
   390 #define DEBUG_LED   0
       
   391 
       
   392 int main (void)
       
   393 {
       
   394     // led_init();
       
   395     sbi(&DEBUG_DDR, DEBUG_LED);
       
   396 
       
   397     timer_init();
       
   398     lkm_init();
       
   399 
       
   400     sei();
       
   401 
       
   402     // setup display
       
   403     lkm_clear();
       
   404     lkm_control(LKM_CONTROL_DISPLAY_ON, LKM_CONTROL_INTENSITY_MAX);
       
   405 
       
   406 /*
       
   407     // test
       
   408     lkm_led(0, LKM_LED_OFF);
       
   409     lkm_led(1, LKM_LED_RED);
       
   410     lkm_led(2, LKM_LED_GREEN);
       
   411     lkm_led(3, LKM_LED_ORANGE);
       
   412 
       
   413     return 0;
       
   414 */
       
   415 
       
   416     // start
       
   417     byte channels[4] = { };
       
   418     enum channel_state {
       
   419         CHANNEL_IDLE    = 0,
       
   420         CHANNEL_INC,
       
   421         CHANNEL_DEC,
       
   422     } channel_state[4] = { };
       
   423     byte channel_count[4] = { };
       
   424     char c;
       
   425         
       
   426     while (true) {
       
   427         // scan input
       
   428         byte buttons = lkm_buttons();
       
   429         
       
   430         for (c = 0; c < 4; c++) {
       
   431             enum channel_state state = channel_state[c], next;
       
   432             byte count = channel_count[c];
       
   433 
       
   434             // decode buttons -> state
       
   435             if (buttons & 0b1) {
       
   436                 next = CHANNEL_INC;
       
   437             } else if (buttons & 0b10) {
       
   438                 next = CHANNEL_DEC;
       
   439             } else {
       
   440                 next = CHANNEL_IDLE;
       
   441             }
       
   442             
       
   443             buttons >>= 2;
       
   444             
       
   445             // feedback
       
   446             if (state == CHANNEL_INC || next == CHANNEL_INC)
       
   447                 lkm_led(c * 2 + 0, LKM_LED_GREEN);
       
   448             else if (state == CHANNEL_DEC && next == CHANNEL_DEC)
       
   449                 lkm_led(c * 2 + 0, LKM_LED_RED);
       
   450             else
       
   451                 lkm_led(c * 2 + 0, LKM_LED_OFF);
       
   452 
       
   453             if (state == CHANNEL_DEC || next == CHANNEL_DEC)
       
   454                 lkm_led(c * 2 + 1, LKM_LED_RED);
       
   455             else if (state == CHANNEL_INC && next == CHANNEL_INC)
       
   456                 lkm_led(c * 2 + 1, LKM_LED_GREEN);
       
   457             else
       
   458                 lkm_led(c * 2 + 1, LKM_LED_OFF);
       
   459 
       
   460             // counts
       
   461             if ((channel_state[c] = next) != state)
       
   462                 channel_count[c] = 0;
       
   463             else
       
   464                 count = ++channel_count[c];
       
   465 
       
   466             // state transitions
       
   467             if (next == CHANNEL_INC && count > 0) {
       
   468                 channels[c] += count;
       
   469             } else if (next == CHANNEL_DEC && count > 0) {
       
   470                 channels[c] -= count;
       
   471             } else if (state == CHANNEL_INC && !count) {
       
   472                 channels[c] = 0xff;
       
   473             } else if (state == CHANNEL_DEC && !count) {
       
   474                 channels[c] = 0x00;
       
   475             }
       
   476 
       
   477             lkm_display_hh(c * 2, channels[c]);
       
   478         
       
   479             xbi(&DEBUG_PORT, DEBUG_LED);
       
   480         }
       
   481 
       
   482         timer_sleep(8000);
       
   483     }
       
   484 }