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