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 } |
|