Commit | Line | Data |
---|---|---|
70095677 PH |
1 | /****************************************************************************** |
2 | * | |
3 | * Trennfix firmware - mm_switch.c | |
4 | * | |
5 | * Maerklin Motorola switch command receiver | |
6 | * | |
7 | * Copyright (C) 2017 Philipp Hachtmann | |
8 | * | |
9 | * This program is free software: you can redistribute it and/or modify | |
10 | * it under the terms of the GNU General Public License as published by | |
11 | * the Free Software Foundation, either version 3 of the License, or | |
12 | * (at your option) any later version. | |
13 | * | |
14 | * This program is distributed in the hope that it will be useful, | |
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
17 | * GNU General Public License for more details. | |
18 | * | |
19 | * You should have received a copy of the GNU General Public License | |
20 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
21 | * | |
22 | *****************************************************************************/ | |
23 | ||
24 | #include <stdio.h> | |
25 | #include <stdlib.h> | |
26 | #include <string.h> | |
27 | ||
28 | #include <avr/io.h> | |
29 | #include <avr/eeprom.h> | |
30 | #include <avr/interrupt.h> | |
31 | #include <avr/pgmspace.h> | |
32 | ||
33 | #include <util/delay.h> | |
34 | #include <stdint.h> | |
35 | ||
93cb14d4 PH |
36 | #include <config/hardware.h> |
37 | #include <mm/mm_switch.h> | |
70095677 PH |
38 | |
39 | ||
40 | /* | |
93cb14d4 PH |
41 | * |
42 | * Check for stuff we need | |
70095677 PH |
43 | * |
44 | */ | |
dc41eb22 | 45 | #if !defined(MM_TSTART) || !defined(MM_SENSE) || !defined(MM_TIMER_INT_VECT) |
dc41eb22 | 46 | #error Missing needed MM_... macro! |
93cb14d4 PH |
47 | #endif |
48 | ||
49 | /* | |
50 | * Private global variables | |
51 | */ | |
93cb14d4 | 52 | |
c839c431 PH |
53 | enum recmode { |
54 | INIT, | |
55 | ARMED, | |
56 | MM_SLOW, | |
57 | MM_FAST, | |
58 | DCC, | |
59 | }; | |
60 | enum recmode recmode = INIT; | |
61 | ||
7c08d02a PH |
62 | #ifndef MM_USE_REGISTER_VARS |
63 | ||
27b551dd | 64 | static volatile uint8_t mm_bitno = 0; |
7c08d02a PH |
65 | static uint8_t shift_command; |
66 | static uint8_t shift_function; | |
67 | static uint8_t shift_address; | |
27b551dd PH |
68 | uint8_t mm_time_h = 0; |
69 | uint8_t mm_time_l = 0; | |
70 | uint8_t mm_bit_val = 23; | |
71 | static uint8_t mm_flavor; | |
72 | static uint8_t mm_polarity = 1; | |
7c08d02a PH |
73 | |
74 | #endif | |
93cb14d4 PH |
75 | |
76 | /* | |
77 | * Lookup trinary nibble | |
78 | * | |
79 | * This was implemented using a switch statement before. | |
80 | * Changing the lookup to a table did only add two bytes | |
81 | * of memory and saved ca. 50 bytes program memory. | |
82 | */ | |
83 | static const uint8_t nibble_table[16]={ | |
84 | [0x0] = 0, | |
85 | [0xc] = 1, | |
86 | [0x8] = 2, | |
87 | [0x3] = 3, | |
88 | [0xf] = 4, | |
89 | [0xb] = 5, | |
90 | [0x2] = 6, | |
91 | [0xe] = 7, | |
92 | [0xa] = 8 | |
93 | }; | |
94 | #define lookup_nibble(nibble) nibble_table[nibble & 0xf] | |
95 | ||
b1a7e65e | 96 | static uint8_t __attribute__((unused)) lookup_decoder(uint8_t mm_byte) |
70095677 | 97 | { |
56b25f8b PH |
98 | uint8_t low; |
99 | uint8_t high; | |
27b551dd PH |
100 | uint8_t retval; |
101 | ||
56b25f8b | 102 | if (mm_byte == 0) |
93cb14d4 | 103 | return 80; |
56b25f8b PH |
104 | low = lookup_nibble(mm_byte >> 4); |
105 | high = lookup_nibble(mm_byte & 0xf); | |
106 | if (!low) | |
70095677 | 107 | return 0; |
27b551dd PH |
108 | |
109 | /* retval = 9 * high + low; */ | |
110 | retval = high << 3; | |
111 | retval += high; | |
112 | retval += low; | |
113 | return retval; | |
70095677 PH |
114 | } |
115 | ||
b1a7e65e | 116 | static uint8_t __attribute__((unused)) lookup_command(uint8_t mm_command) |
70095677 | 117 | { |
7c08d02a PH |
118 | uint8_t res; |
119 | /* | |
120 | * Check for aabbccdd condition | |
121 | * | |
122 | * a a b b c c d d mm_command | |
123 | * XOR a b b c c d d 0 mm_command << 1 | |
124 | * Mask 1 0 1 0 1 0 1 0 0xaa | |
125 | * | |
126 | * Must be zero! | |
127 | * | |
128 | */ | |
129 | ||
130 | if ((mm_command ^ (mm_command << 1)) & 0xaa) | |
70095677 | 131 | return 0; |
7c08d02a PH |
132 | /* |
133 | * Protocol differences: | |
134 | * ===================== | |
135 | * | |
136 | * I have an old "central control" 6022 and a "control unit" 6021 | |
137 | * for measurements and test. It is assumed that the 6022 outputs | |
138 | * old MM1 format while the 6021 definitively outputs MM2 telegrams. | |
139 | * | |
140 | * In MM1, switch commands are different from MM2 with respect what | |
141 | * happens if you release a button. | |
142 | * | |
143 | * When you press a button, both protocols send | |
144 | * | |
145 | * <aaaaaaaa><00><aabbcc11> | |
146 | * | |
147 | * where a = 1, b = 2, c = 4 and the keys are numerated from 0 to 7 | |
148 | * in the order 1 red, 1 green, 2 red, 2 green and so on. | |
149 | * | |
150 | * The last two bits correspond to "on" state of the button/coil. | |
151 | * | |
152 | * When a key is released under MM1 protocol, the sequence sent is | |
153 | * analogue to the button down sequence: | |
154 | * | |
155 | * <aaaaaaaa><00><aabbcc00> where abc again represents the button's | |
156 | * address and the last bits now signal "off". | |
157 | * | |
158 | * MM2 handles this differently: | |
159 | * Whenever any key from the addressed decoder is released, the sequence | |
160 | * <aaaaaaaa>00<00000000> is sent - not only for key 0, but for all | |
161 | * keys! | |
162 | * | |
163 | * While MM1 presents the theoretical possibility to press several keys | |
164 | * independently and simultaneously (which my keyboard does NOT | |
165 | * support), MM2 supports only one key at a time (besides strange | |
166 | * sequences like "one down, another down, all up"... | |
167 | * | |
168 | * A decoder that strictly adheres to the MM1 standard would not work | |
169 | * properly with MM2 control units. As far as I know all K83/K84 | |
170 | * decoders always worked with MM2 control units. That means that | |
171 | * they reduce the commands to the possibilities of MM2 from the | |
172 | * beginning. | |
173 | * | |
174 | * Possible use cases for the old protocol button release commands: | |
175 | * - Determine if the protocol is MM1 or MM2 | |
176 | * - Implement hidden evil features into the controller which can | |
177 | * only be summoned by old MM1 gear or selfmade control telegram | |
178 | * generators. | |
179 | * | |
180 | * What this code now actually does: | |
181 | * ================================= | |
182 | * | |
183 | * When key pressed (aabbcc11), it will send out the key number in the | |
184 | * range 1-8 and 0 if it gets any key up command and therefore ignore | |
185 | * the key number if it is transmitted with the key up command. | |
186 | * | |
187 | */ | |
188 | if (!(mm_command & 0x01)) | |
189 | res = 0; | |
190 | else | |
191 | res = (mm_command & 0x80) * 1 + (mm_command & 0x20) * 0x02 | |
192 | + (mm_command & 0x08) * 0x04 + 1; | |
193 | return res; | |
70095677 PH |
194 | } |
195 | ||
70095677 PH |
196 | /* We will shift from right to left. |
197 | * XXXXXXXX XX XXXXXXXX | |
56b25f8b | 198 | * shift_address shift_function shift_command |
70095677 | 199 | * |
56b25f8b | 200 | * The bits 7 downto 2 of shift_function are ignored. |
27b551dd PH |
201 | * |
202 | * First comes the C implementation of the shift routine. | |
203 | * It is usually not used anymore, but I left it for | |
204 | * illustration purposes. | |
205 | * The real shift function is written in assembly and saves many instructions. | |
70095677 | 206 | */ |
27b551dd PH |
207 | #if 0 |
208 | void shift(uint8_t value) | |
209 | { | |
210 | shift_address <<= 1; | |
211 | if (shift_function & 2) | |
212 | shift_address |= 1; | |
213 | shift_function <<= 1; | |
214 | if (shift_command & 0x80) | |
215 | shift_function |= 1; | |
216 | shift_command <<= 1; | |
217 | if (value) | |
218 | shift_command |= 1; | |
219 | } | |
220 | #else | |
70095677 | 221 | |
7c08d02a PH |
222 | void shift(uint8_t value) |
223 | { | |
224 | asm("ror %[val] ; Shift value right into carry\n\t" | |
225 | "rol %[cmd] ; and shift to command reg\n\t" | |
226 | "mov __tmp_reg__, %[func] ; save function value \n\t" | |
227 | "rol %[func] ; Shift up function value\n\t" | |
228 | "ror __tmp_reg__ ; shift bit 1\n\t" | |
229 | "ror __tmp_reg__ ; down to carry\n\t" | |
230 | "rol %[addr] ; And we're at the address\n\t" | |
231 | : [cmd] "=r" (shift_command), [func] "=r" (shift_function), | |
232 | [addr] "=r" (shift_address) | |
233 | : "0" (shift_command), "1" (shift_function), | |
234 | "2" (shift_address), [val] "r" (value) | |
235 | ); | |
236 | } | |
7c08d02a PH |
237 | #endif |
238 | ||
c839c431 | 239 | static void mm_feed_bit(uint8_t bit) |
b1a7e65e | 240 | { |
93cb14d4 PH |
241 | static volatile uint8_t shift_command_first; |
242 | static volatile uint8_t shift_function_first; | |
243 | static volatile uint8_t shift_address_first; | |
244 | uint8_t address; | |
245 | uint8_t command; | |
246 | ||
b1a7e65e | 247 | shift(bit); |
27b551dd | 248 | mm_bitno++; |
c839c431 | 249 | |
27b551dd | 250 | if (mm_bitno == 18) { /* Save first received word */ |
93cb14d4 PH |
251 | shift_address_first = shift_address; |
252 | shift_function_first = shift_function; | |
253 | shift_command_first = shift_command; | |
70095677 | 254 | } |
56b25f8b | 255 | |
27b551dd | 256 | if (mm_bitno == 36) { |
b1a7e65e | 257 | |
56b25f8b | 258 | if ((shift_command == shift_command_first) && |
93cb14d4 PH |
259 | (shift_address == shift_address_first) && |
260 | (shift_function == shift_function_first)) { | |
27b551dd | 261 | address = lookup_decoder(shift_address); |
c839c431 PH |
262 | if (recmode == MM_SLOW) { |
263 | trigger(); | |
264 | ||
27b551dd PH |
265 | mm_switch_drive(address, shift_function, |
266 | shift_command); | |
267 | } else { | |
27b551dd PH |
268 | command = lookup_command(shift_command); |
269 | mm_switch_command(address, command); | |
270 | } | |
70095677 | 271 | } |
56b25f8b | 272 | |
70095677 PH |
273 | } |
274 | } | |
275 | ||
b1a7e65e PH |
276 | /* |
277 | * The timeout interrupt vector does nothing else | |
27b551dd | 278 | * than incrementing the mm_time_h round counter. |
b1a7e65e PH |
279 | * |
280 | * It is written in naked assembly because we want to avoid pushing | |
281 | * and popping of all upper registers. | |
282 | */ | |
283 | void __attribute__((naked)) MM_TIMER_INT_VECT(void) | |
70095677 | 284 | { |
b1a7e65e PH |
285 | asm("push r0 ; save r0 \n\t" |
286 | "in r0, __SREG__ \n\t" | |
287 | #ifdef MM_USE_REGISTER_VARS | |
288 | "inc %[th] \n\t" | |
c839c431 PH |
289 | "brne nover \n\t" |
290 | "dec %[th] \n\t" | |
291 | "nover: \n\t" | |
b1a7e65e PH |
292 | #else |
293 | "push r1 \n\t" | |
c839c431 | 294 | "lds r1, mm_time_h \n\t" |
b1a7e65e | 295 | "inc r1 \n\t" |
c839c431 PH |
296 | "brne nover \n\t" |
297 | "dec r1 \n\t" | |
298 | "nover: \n\t" | |
27b551dd | 299 | "sts mm_time_h, r1 \n\t" |
b1a7e65e PH |
300 | "pop r1 \n\t" |
301 | #endif | |
302 | "out __SREG__, r0 \n\t" | |
303 | "pop r0 \n\t" | |
b1a7e65e PH |
304 | "reti \n\t" |
305 | #ifdef MM_USE_REGISTER_VARS | |
27b551dd | 306 | :: [th] "r" (mm_time_h) |
b1a7e65e PH |
307 | #endif |
308 | ); | |
70095677 PH |
309 | } |
310 | ||
b1a7e65e PH |
311 | /* |
312 | * Another naked interrupt trampoline | |
313 | * | |
314 | * Here we first save the timer value as fast as possible, then we jump (!) | |
315 | * into the "official" interrupt handler with all its decorations. | |
316 | */ | |
317 | void __attribute__((naked)) PCINT0_vect(void) | |
70095677 | 318 | { |
b1a7e65e PH |
319 | #ifdef MM_USE_REGISTER_VARS |
320 | asm("in %[tl], %[tmr] \n\t" | |
321 | "rjmp __vector_pinchange \n\t" | |
27b551dd | 322 | :: [tl] "r" (mm_time_l), [tmr] "I" (_SFR_IO_ADDR(TCNT0)) |
b1a7e65e PH |
323 | ); |
324 | #else | |
325 | asm("push r0 \n\t" | |
326 | "in r0, %[tmr] \n\t" | |
27b551dd | 327 | "sts mm_time_l, r0 \n\t" |
b1a7e65e PH |
328 | "pop r0 \n\t" |
329 | "rjmp __vector_pinchange \n\t" | |
330 | :: [tmr] "I" (_SFR_IO_ADDR(TCNT0)) | |
331 | ); | |
332 | #endif | |
333 | } | |
c839c431 | 334 | |
b1a7e65e PH |
335 | /* Pin change interrupt vector, here we have a bit more time */ |
336 | ISR(__vector_pinchange){ | |
c839c431 PH |
337 | uint16_t duration; |
338 | ||
b1a7e65e | 339 | /* First kill off that timer */ |
b1a7e65e PH |
340 | MM_TSTART; /* Restart timer */ |
341 | ||
342 | /* Account for not yet handled timer overflow */ | |
c839c431 | 343 | TIFR |= _BV(TOV0); |
b1a7e65e | 344 | |
c839c431 | 345 | duration = mm_time_h << 8; |
27b551dd | 346 | duration += mm_time_l; |
c839c431 | 347 | mm_bit_val = MM_SENSE; |
27b551dd | 348 | mm_time_h = 0; |
b1a7e65e PH |
349 | |
350 | /* | |
351 | * The nominal length of a bit cycle is 208 us. | |
352 | * This consists of 8 parts, each 26 us: | |
353 | * 1 d d d d d d 0 | |
354 | * | |
355 | * That means that the 1 pulse is 7 * 26 = 182us | |
356 | * and the short pulse is 1 * 26 = 26us. | |
357 | * | |
358 | * Reality seems to look not as that exact. I measure | |
359 | * 26us for the clock pulse, but 196 for the long pulse. | |
360 | * | |
c839c431 PH |
361 | * Keep in mind: Time is counted in 500ns steps. |
362 | * | |
b1a7e65e | 363 | */ |
b1a7e65e PH |
364 | #define D_MATCH(d, v) ((duration > (d * 2 - 2 * v)) && (duration < (d * 2 + 2 * v))) |
365 | ||
c839c431 PH |
366 | switch(recmode) { |
367 | case INIT: | |
368 | default: | |
369 | break; | |
370 | case ARMED: | |
371 | /* Maerklin only interested when signal is 1 */ | |
372 | if (mm_bit_val == mm_polarity) { | |
373 | /* Fast short MM pulse (logical 0) */ | |
374 | if (D_MATCH(13, 4)){ | |
375 | recmode = MM_FAST; | |
376 | mm_feed_bit(0); | |
377 | goto done; | |
378 | } | |
379 | ||
380 | /* Slow short MM pulse (logical 0) */ | |
381 | if (D_MATCH(26, 4)) { | |
382 | recmode = MM_SLOW; | |
383 | mm_feed_bit(0); | |
384 | goto done; | |
385 | } | |
386 | ||
387 | /* Fast long MM pulse (logical 1) */ | |
388 | if (D_MATCH(91, 17)) { | |
389 | recmode = MM_FAST; | |
390 | mm_feed_bit(1); | |
391 | goto done; | |
392 | } | |
393 | ||
394 | /* Slow long MM pulse (logical 1) */ | |
395 | if (D_MATCH(182, 25)) { | |
396 | recmode = MM_SLOW; | |
397 | mm_feed_bit(1); | |
398 | goto done; | |
399 | } | |
400 | } | |
401 | break; | |
402 | ||
403 | case MM_FAST: | |
404 | if (mm_bit_val == mm_polarity) { | |
405 | ||
406 | /* Fast short MM pulse (logical 0) */ | |
407 | if (D_MATCH(13, 4)){ | |
408 | mm_feed_bit(0); | |
409 | goto done; | |
410 | } | |
411 | /* Fast long MM pulse (logical 1) */ | |
412 | if (D_MATCH(91, 17)) { | |
413 | mm_feed_bit(1); | |
414 | goto done; | |
415 | } | |
416 | } else { | |
417 | if (D_MATCH(13, 4)) | |
418 | goto done; | |
419 | if (D_MATCH(91, 17)) | |
420 | goto done; | |
421 | ||
422 | if (D_MATCH(700, 200)) | |
423 | goto done; | |
b1a7e65e | 424 | } |
c839c431 PH |
425 | break; |
426 | ||
427 | case MM_SLOW: | |
428 | if (mm_bit_val == mm_polarity) { | |
429 | /* Slow short MM pulse (logical 0) */ | |
430 | if (D_MATCH(26, 4)) { | |
431 | mm_feed_bit(0); | |
432 | goto done; | |
433 | } | |
434 | /* Slow long MM pulse (logical 1) */ | |
435 | if (D_MATCH(182, 40)) { | |
436 | mm_feed_bit(1); | |
437 | goto done; | |
438 | } | |
439 | } else { | |
440 | if (D_MATCH(26, 4)) | |
441 | goto done; | |
442 | if (D_MATCH(182, 40)) | |
443 | goto done; | |
444 | ||
445 | /* Accepted inter package gap */ | |
446 | if (D_MATCH(1400, 400)) | |
447 | goto done; | |
d0047978 | 448 | } |
c839c431 | 449 | break; |
d0047978 | 450 | } |
b1a7e65e | 451 | |
c839c431 PH |
452 | /* |
453 | * If we have reached here, our pulse comes in somehow unexpected. | |
454 | * We kill of everything by re-arming the state machine. | |
455 | */ | |
456 | /* Start over receiver */ | |
457 | mm_bitno = 0; | |
458 | mm_polarity = !mm_bit_val; | |
459 | recmode = ARMED; | |
b1a7e65e | 460 | done: |
d0047978 PH |
461 | mm_switch_pinchange_callback(); |
462 | } | |
9c77e706 | 463 | |
27b551dd | 464 | |
d0047978 PH |
465 | void __attribute__((weak))mm_switch_pinchange_callback(void) |
466 | { | |
70095677 PH |
467 | } |
468 | ||
93cb14d4 PH |
469 | void __attribute__((weak))mm_switch_drive(uint8_t decoder, uint8_t function, |
470 | uint8_t command) | |
471 | { | |
93cb14d4 PH |
472 | } |
473 | ||
474 | void __attribute__((weak))mm_switch_command(uint8_t address, uint8_t command) | |
475 | { | |
476 | } | |
477 | ||
478 | ||
70095677 PH |
479 | /****************************************************************************** |
480 | * The end :-) | |
481 | */ |