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) |
56b25f8b | 46 | |
dc41eb22 | 47 | #error Missing needed MM_... macro! |
56b25f8b | 48 | |
93cb14d4 PH |
49 | #endif |
50 | ||
51 | /* | |
52 | * Private global variables | |
53 | */ | |
93cb14d4 | 54 | |
7c08d02a PH |
55 | #ifndef MM_USE_REGISTER_VARS |
56 | ||
57 | static volatile uint8_t bitno = 0; | |
58 | static uint8_t shift_command; | |
59 | static uint8_t shift_function; | |
60 | static uint8_t shift_address; | |
61 | static enum mm_recstate recstate = MM_IDLE; | |
b1a7e65e PH |
62 | static uint8_t sense_last = 23; |
63 | uint8_t time_h = 0; | |
64 | uint8_t time_l = 0; | |
65 | uint8_t bit_val = 23; | |
7c08d02a PH |
66 | |
67 | #endif | |
93cb14d4 PH |
68 | |
69 | /* | |
70 | * Lookup trinary nibble | |
71 | * | |
72 | * This was implemented using a switch statement before. | |
73 | * Changing the lookup to a table did only add two bytes | |
74 | * of memory and saved ca. 50 bytes program memory. | |
75 | */ | |
76 | static const uint8_t nibble_table[16]={ | |
77 | [0x0] = 0, | |
78 | [0xc] = 1, | |
79 | [0x8] = 2, | |
80 | [0x3] = 3, | |
81 | [0xf] = 4, | |
82 | [0xb] = 5, | |
83 | [0x2] = 6, | |
84 | [0xe] = 7, | |
85 | [0xa] = 8 | |
86 | }; | |
87 | #define lookup_nibble(nibble) nibble_table[nibble & 0xf] | |
88 | ||
b1a7e65e | 89 | static uint8_t __attribute__((unused)) lookup_decoder(uint8_t mm_byte) |
70095677 | 90 | { |
56b25f8b PH |
91 | uint8_t low; |
92 | uint8_t high; | |
93 | if (mm_byte == 0) | |
93cb14d4 | 94 | return 80; |
56b25f8b PH |
95 | low = lookup_nibble(mm_byte >> 4); |
96 | high = lookup_nibble(mm_byte & 0xf); | |
97 | if (!low) | |
70095677 | 98 | return 0; |
56b25f8b | 99 | return 9 * high + low; |
70095677 PH |
100 | } |
101 | ||
b1a7e65e | 102 | static uint8_t __attribute__((unused)) lookup_command(uint8_t mm_command) |
70095677 | 103 | { |
7c08d02a PH |
104 | uint8_t res; |
105 | /* | |
106 | * Check for aabbccdd condition | |
107 | * | |
108 | * a a b b c c d d mm_command | |
109 | * XOR a b b c c d d 0 mm_command << 1 | |
110 | * Mask 1 0 1 0 1 0 1 0 0xaa | |
111 | * | |
112 | * Must be zero! | |
113 | * | |
114 | */ | |
115 | ||
116 | if ((mm_command ^ (mm_command << 1)) & 0xaa) | |
70095677 | 117 | return 0; |
7c08d02a PH |
118 | /* |
119 | * Protocol differences: | |
120 | * ===================== | |
121 | * | |
122 | * I have an old "central control" 6022 and a "control unit" 6021 | |
123 | * for measurements and test. It is assumed that the 6022 outputs | |
124 | * old MM1 format while the 6021 definitively outputs MM2 telegrams. | |
125 | * | |
126 | * In MM1, switch commands are different from MM2 with respect what | |
127 | * happens if you release a button. | |
128 | * | |
129 | * When you press a button, both protocols send | |
130 | * | |
131 | * <aaaaaaaa><00><aabbcc11> | |
132 | * | |
133 | * where a = 1, b = 2, c = 4 and the keys are numerated from 0 to 7 | |
134 | * in the order 1 red, 1 green, 2 red, 2 green and so on. | |
135 | * | |
136 | * The last two bits correspond to "on" state of the button/coil. | |
137 | * | |
138 | * When a key is released under MM1 protocol, the sequence sent is | |
139 | * analogue to the button down sequence: | |
140 | * | |
141 | * <aaaaaaaa><00><aabbcc00> where abc again represents the button's | |
142 | * address and the last bits now signal "off". | |
143 | * | |
144 | * MM2 handles this differently: | |
145 | * Whenever any key from the addressed decoder is released, the sequence | |
146 | * <aaaaaaaa>00<00000000> is sent - not only for key 0, but for all | |
147 | * keys! | |
148 | * | |
149 | * While MM1 presents the theoretical possibility to press several keys | |
150 | * independently and simultaneously (which my keyboard does NOT | |
151 | * support), MM2 supports only one key at a time (besides strange | |
152 | * sequences like "one down, another down, all up"... | |
153 | * | |
154 | * A decoder that strictly adheres to the MM1 standard would not work | |
155 | * properly with MM2 control units. As far as I know all K83/K84 | |
156 | * decoders always worked with MM2 control units. That means that | |
157 | * they reduce the commands to the possibilities of MM2 from the | |
158 | * beginning. | |
159 | * | |
160 | * Possible use cases for the old protocol button release commands: | |
161 | * - Determine if the protocol is MM1 or MM2 | |
162 | * - Implement hidden evil features into the controller which can | |
163 | * only be summoned by old MM1 gear or selfmade control telegram | |
164 | * generators. | |
165 | * | |
166 | * What this code now actually does: | |
167 | * ================================= | |
168 | * | |
169 | * When key pressed (aabbcc11), it will send out the key number in the | |
170 | * range 1-8 and 0 if it gets any key up command and therefore ignore | |
171 | * the key number if it is transmitted with the key up command. | |
172 | * | |
173 | */ | |
174 | if (!(mm_command & 0x01)) | |
175 | res = 0; | |
176 | else | |
177 | res = (mm_command & 0x80) * 1 + (mm_command & 0x20) * 0x02 | |
178 | + (mm_command & 0x08) * 0x04 + 1; | |
179 | return res; | |
70095677 PH |
180 | } |
181 | ||
70095677 PH |
182 | /* We will shift from right to left. |
183 | * XXXXXXXX XX XXXXXXXX | |
56b25f8b | 184 | * shift_address shift_function shift_command |
70095677 | 185 | * |
56b25f8b | 186 | * The bits 7 downto 2 of shift_function are ignored. |
70095677 | 187 | */ |
7c08d02a PH |
188 | #define SAVE_ANOTHER_40_BYTES |
189 | #ifdef SAVE_ANOTHER_40_BYTES | |
70095677 | 190 | |
7c08d02a PH |
191 | void shift(uint8_t value) |
192 | { | |
193 | asm("ror %[val] ; Shift value right into carry\n\t" | |
194 | "rol %[cmd] ; and shift to command reg\n\t" | |
195 | "mov __tmp_reg__, %[func] ; save function value \n\t" | |
196 | "rol %[func] ; Shift up function value\n\t" | |
197 | "ror __tmp_reg__ ; shift bit 1\n\t" | |
198 | "ror __tmp_reg__ ; down to carry\n\t" | |
199 | "rol %[addr] ; And we're at the address\n\t" | |
200 | : [cmd] "=r" (shift_command), [func] "=r" (shift_function), | |
201 | [addr] "=r" (shift_address) | |
202 | : "0" (shift_command), "1" (shift_function), | |
203 | "2" (shift_address), [val] "r" (value) | |
204 | ); | |
205 | } | |
206 | ||
207 | #else /* This is what we do to shift */ | |
208 | ||
209 | void shift(uint8_t value) | |
70095677 | 210 | { |
56b25f8b PH |
211 | shift_address <<= 1; |
212 | if (shift_function & 2) | |
213 | shift_address |= 1; | |
214 | shift_function <<= 1; | |
215 | if (shift_command & 0x80) | |
216 | shift_function |= 1; | |
217 | shift_command <<= 1; | |
70095677 | 218 | if (value) |
56b25f8b | 219 | shift_command |= 1; |
70095677 | 220 | } |
7c08d02a PH |
221 | #endif |
222 | ||
9c77e706 PH |
223 | static volatile uint8_t mm_rec_tolerated_timeouts; |
224 | ||
b1a7e65e PH |
225 | #define MM_SLOW 0 |
226 | #define MM_FAST 1 | |
70095677 | 227 | |
b1a7e65e | 228 | static uint8_t style; |
70095677 | 229 | |
b1a7e65e PH |
230 | void mm_feed_bit(uint8_t bit, uint8_t seen_style) |
231 | { | |
93cb14d4 PH |
232 | static volatile uint8_t shift_command_first; |
233 | static volatile uint8_t shift_function_first; | |
234 | static volatile uint8_t shift_address_first; | |
235 | uint8_t address; | |
236 | uint8_t command; | |
237 | ||
238 | #ifdef MM_FILTER_REPEATED | |
56b25f8b PH |
239 | static uint8_t address_last = 0xff; |
240 | static uint8_t function_last = 0xff; | |
241 | static uint8_t command_last = 0xff; | |
93cb14d4 | 242 | #endif |
b1a7e65e PH |
243 | |
244 | if (bitno == 0) | |
245 | style = seen_style; | |
246 | else | |
247 | if (seen_style != style) { | |
248 | bitno = 0; | |
70095677 PH |
249 | return; |
250 | } | |
b1a7e65e PH |
251 | |
252 | shift(bit); | |
70095677 | 253 | bitno++; |
70095677 | 254 | if (bitno == 18) { /* Save first received word */ |
93cb14d4 PH |
255 | shift_address_first = shift_address; |
256 | shift_function_first = shift_function; | |
257 | shift_command_first = shift_command; | |
9c77e706 | 258 | mm_rec_tolerated_timeouts = 18; |
70095677 | 259 | } |
56b25f8b | 260 | |
70095677 | 261 | if (bitno == 36) { |
b1a7e65e | 262 | |
56b25f8b | 263 | if ((shift_command == shift_command_first) && |
93cb14d4 PH |
264 | (shift_address == shift_address_first) && |
265 | (shift_function == shift_function_first)) { | |
d0047978 | 266 | |
93cb14d4 PH |
267 | |
268 | #ifdef MM_FILTER_REPEATED | |
56b25f8b PH |
269 | if ((shift_address != address_last) || (shift_command != command_last) || |
270 | shift_function != function_last) { | |
93cb14d4 | 271 | #endif |
b1a7e65e PH |
272 | if (style == MM_SLOW) { |
273 | address = lookup_decoder(shift_address); | |
93cb14d4 | 274 | mm_switch_drive(address, shift_function, shift_command); |
b1a7e65e PH |
275 | } else { |
276 | trigger(); | |
277 | ||
93cb14d4 PH |
278 | command = lookup_command(shift_command); |
279 | mm_switch_command(address, command); | |
b1a7e65e PH |
280 | } |
281 | bitno = 0; | |
93cb14d4 | 282 | #ifdef MM_FILTER_REPEATED |
70095677 | 283 | } |
93cb14d4 PH |
284 | address_last = shift_address; |
285 | function_last = shift_function; | |
286 | command_last = shift_command; | |
287 | #endif | |
70095677 | 288 | } |
56b25f8b | 289 | |
70095677 PH |
290 | } |
291 | } | |
292 | ||
b1a7e65e PH |
293 | ISR(__vector_timer_extra) { |
294 | //trigger(); | |
295 | } | |
56b25f8b | 296 | |
b1a7e65e PH |
297 | /* |
298 | * The timeout interrupt vector does nothing else | |
299 | * than incrementing the time_h round counter. | |
300 | * | |
301 | * It is written in naked assembly because we want to avoid pushing | |
302 | * and popping of all upper registers. | |
303 | */ | |
304 | void __attribute__((naked)) MM_TIMER_INT_VECT(void) | |
70095677 | 305 | { |
b1a7e65e PH |
306 | asm("push r0 ; save r0 \n\t" |
307 | "in r0, __SREG__ \n\t" | |
308 | #ifdef MM_USE_REGISTER_VARS | |
309 | "inc %[th] \n\t" | |
310 | #else | |
311 | "push r1 \n\t" | |
312 | "lds r1, time_h \n\t" | |
313 | "inc r1 \n\t" | |
314 | "sts time_h, r1 \n\t" | |
315 | "pop r1 \n\t" | |
316 | #endif | |
317 | "out __SREG__, r0 \n\t" | |
318 | "pop r0 \n\t" | |
319 | // "rjmp __vector_timer_extra \n\t" | |
320 | "reti \n\t" | |
321 | #ifdef MM_USE_REGISTER_VARS | |
322 | :: [th] "r" (time_h) | |
323 | #endif | |
324 | ); | |
70095677 PH |
325 | } |
326 | ||
b1a7e65e PH |
327 | /* |
328 | * Another naked interrupt trampoline | |
329 | * | |
330 | * Here we first save the timer value as fast as possible, then we jump (!) | |
331 | * into the "official" interrupt handler with all its decorations. | |
332 | */ | |
333 | void __attribute__((naked)) PCINT0_vect(void) | |
70095677 | 334 | { |
b1a7e65e PH |
335 | #ifdef MM_USE_REGISTER_VARS |
336 | asm("in %[tl], %[tmr] \n\t" | |
337 | "rjmp __vector_pinchange \n\t" | |
338 | :: [tl] "r" (time_l), [tmr] "I" (_SFR_IO_ADDR(TCNT0)) | |
339 | ); | |
340 | #else | |
341 | asm("push r0 \n\t" | |
342 | "in r0, %[tmr] \n\t" | |
343 | "sts time_l, r0 \n\t" | |
344 | "pop r0 \n\t" | |
345 | "rjmp __vector_pinchange \n\t" | |
346 | :: [tmr] "I" (_SFR_IO_ADDR(TCNT0)) | |
347 | ); | |
348 | #endif | |
349 | } | |
350 | ||
351 | /* Pin change interrupt vector, here we have a bit more time */ | |
352 | ISR(__vector_pinchange){ | |
353 | /* First kill off that timer */ | |
354 | ||
355 | MM_TSTART; /* Restart timer */ | |
356 | ||
357 | /* Account for not yet handled timer overflow */ | |
358 | if (TIFR & _BV(TOV0)) { | |
359 | time_h++; | |
360 | TIFR |= _BV(TOV0); | |
70095677 PH |
361 | } |
362 | ||
b1a7e65e PH |
363 | bit_val = !MM_SENSE; |
364 | ||
365 | uint16_t duration = time_h << 8; | |
366 | duration += time_l; | |
367 | time_h = 0; | |
368 | ||
369 | /* | |
370 | * The nominal length of a bit cycle is 208 us. | |
371 | * This consists of 8 parts, each 26 us: | |
372 | * 1 d d d d d d 0 | |
373 | * | |
374 | * That means that the 1 pulse is 7 * 26 = 182us | |
375 | * and the short pulse is 1 * 26 = 26us. | |
376 | * | |
377 | * Reality seems to look not as that exact. I measure | |
378 | * 26us for the clock pulse, but 196 for the long pulse. | |
379 | * | |
380 | */ | |
381 | ||
382 | #define D_MATCH(d, v) ((duration > (d * 2 - 2 * v)) && (duration < (d * 2 + 2 * v))) | |
383 | ||
384 | static uint8_t mm_positive = 1; | |
385 | static uint8_t last_was_short = 0; | |
70095677 | 386 | |
b1a7e65e PH |
387 | if (D_MATCH(26, 4)) { |
388 | if (last_was_short) | |
389 | mm_positive = bit_val; | |
390 | last_was_short = 1; | |
d0047978 | 391 | |
b1a7e65e PH |
392 | if (bit_val == mm_positive) |
393 | mm_feed_bit(0, MM_SLOW); | |
394 | } else { | |
395 | last_was_short = 0; | |
396 | } | |
d0047978 | 397 | |
b1a7e65e PH |
398 | if (style == MM_SLOW) { |
399 | if (duration > 4000) { /* Maerklin inter package timeout 2ms */ | |
d0047978 | 400 | bitno = 0; |
b1a7e65e PH |
401 | goto done; |
402 | } | |
403 | } else { | |
404 | if (duration > 2000) { /* Maerklin inter package timeout 1ms */ | |
405 | bitno = 0; | |
406 | goto done; | |
d0047978 PH |
407 | } |
408 | } | |
b1a7e65e PH |
409 | |
410 | if (bit_val != mm_positive) | |
411 | goto done; | |
412 | ||
413 | if (D_MATCH(182, 25)) mm_feed_bit(1, MM_SLOW); | |
414 | ||
415 | if (D_MATCH(91, 17)) {mm_feed_bit(1, MM_FAST);} | |
416 | if (D_MATCH(13, 4)) {mm_feed_bit(0, MM_FAST);} | |
417 | ||
418 | done: | |
d0047978 PH |
419 | mm_switch_pinchange_callback(); |
420 | } | |
9c77e706 | 421 | |
d0047978 PH |
422 | void __attribute__((weak))mm_switch_pinchange_callback(void) |
423 | { | |
70095677 PH |
424 | } |
425 | ||
93cb14d4 PH |
426 | void __attribute__((weak))mm_switch_drive(uint8_t decoder, uint8_t function, |
427 | uint8_t command) | |
428 | { | |
93cb14d4 PH |
429 | } |
430 | ||
431 | void __attribute__((weak))mm_switch_command(uint8_t address, uint8_t command) | |
432 | { | |
433 | } | |
434 | ||
435 | ||
b1a7e65e | 436 | |
70095677 PH |
437 | /****************************************************************************** |
438 | * The end :-) | |
439 | */ |