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 | 38 | |
93cb14d4 PH |
39 | /* |
40 | * Private data types | |
41 | */ | |
42 | ||
70095677 PH |
43 | |
44 | /* | |
93cb14d4 PH |
45 | * |
46 | * Check for stuff we need | |
70095677 PH |
47 | * |
48 | */ | |
93cb14d4 PH |
49 | #if !defined(MM_TSTART_FAST) || !defined(MM_TSTART_SLOW) || !defined(MM_TSTOP) \ |
50 | || !defined(MM_SENSE) || !defined(MM_TIMER_INT_VECT) | |
56b25f8b | 51 | |
93cb14d4 | 52 | #error Missing timer start macro MM_TSTART_FAST! |
56b25f8b | 53 | |
93cb14d4 PH |
54 | #endif |
55 | ||
56 | /* | |
57 | * Private global variables | |
58 | */ | |
93cb14d4 | 59 | |
7c08d02a PH |
60 | #ifndef MM_USE_REGISTER_VARS |
61 | ||
62 | static volatile uint8_t bitno = 0; | |
63 | static uint8_t shift_command; | |
64 | static uint8_t shift_function; | |
65 | static uint8_t shift_address; | |
66 | static enum mm_recstate recstate = MM_IDLE; | |
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 | ||
70095677 PH |
90 | static uint8_t lookup_decoder(uint8_t mm_byte) |
91 | { | |
56b25f8b PH |
92 | uint8_t low; |
93 | uint8_t high; | |
94 | if (mm_byte == 0) | |
93cb14d4 | 95 | return 80; |
56b25f8b PH |
96 | low = lookup_nibble(mm_byte >> 4); |
97 | high = lookup_nibble(mm_byte & 0xf); | |
98 | if (!low) | |
70095677 | 99 | return 0; |
56b25f8b | 100 | return 9 * high + low; |
70095677 PH |
101 | } |
102 | ||
7c08d02a | 103 | static uint8_t lookup_command(uint8_t mm_command) |
70095677 | 104 | { |
7c08d02a PH |
105 | uint8_t res; |
106 | /* | |
107 | * Check for aabbccdd condition | |
108 | * | |
109 | * a a b b c c d d mm_command | |
110 | * XOR a b b c c d d 0 mm_command << 1 | |
111 | * Mask 1 0 1 0 1 0 1 0 0xaa | |
112 | * | |
113 | * Must be zero! | |
114 | * | |
115 | */ | |
116 | ||
117 | if ((mm_command ^ (mm_command << 1)) & 0xaa) | |
70095677 | 118 | return 0; |
7c08d02a PH |
119 | /* |
120 | * Protocol differences: | |
121 | * ===================== | |
122 | * | |
123 | * I have an old "central control" 6022 and a "control unit" 6021 | |
124 | * for measurements and test. It is assumed that the 6022 outputs | |
125 | * old MM1 format while the 6021 definitively outputs MM2 telegrams. | |
126 | * | |
127 | * In MM1, switch commands are different from MM2 with respect what | |
128 | * happens if you release a button. | |
129 | * | |
130 | * When you press a button, both protocols send | |
131 | * | |
132 | * <aaaaaaaa><00><aabbcc11> | |
133 | * | |
134 | * where a = 1, b = 2, c = 4 and the keys are numerated from 0 to 7 | |
135 | * in the order 1 red, 1 green, 2 red, 2 green and so on. | |
136 | * | |
137 | * The last two bits correspond to "on" state of the button/coil. | |
138 | * | |
139 | * When a key is released under MM1 protocol, the sequence sent is | |
140 | * analogue to the button down sequence: | |
141 | * | |
142 | * <aaaaaaaa><00><aabbcc00> where abc again represents the button's | |
143 | * address and the last bits now signal "off". | |
144 | * | |
145 | * MM2 handles this differently: | |
146 | * Whenever any key from the addressed decoder is released, the sequence | |
147 | * <aaaaaaaa>00<00000000> is sent - not only for key 0, but for all | |
148 | * keys! | |
149 | * | |
150 | * While MM1 presents the theoretical possibility to press several keys | |
151 | * independently and simultaneously (which my keyboard does NOT | |
152 | * support), MM2 supports only one key at a time (besides strange | |
153 | * sequences like "one down, another down, all up"... | |
154 | * | |
155 | * A decoder that strictly adheres to the MM1 standard would not work | |
156 | * properly with MM2 control units. As far as I know all K83/K84 | |
157 | * decoders always worked with MM2 control units. That means that | |
158 | * they reduce the commands to the possibilities of MM2 from the | |
159 | * beginning. | |
160 | * | |
161 | * Possible use cases for the old protocol button release commands: | |
162 | * - Determine if the protocol is MM1 or MM2 | |
163 | * - Implement hidden evil features into the controller which can | |
164 | * only be summoned by old MM1 gear or selfmade control telegram | |
165 | * generators. | |
166 | * | |
167 | * What this code now actually does: | |
168 | * ================================= | |
169 | * | |
170 | * When key pressed (aabbcc11), it will send out the key number in the | |
171 | * range 1-8 and 0 if it gets any key up command and therefore ignore | |
172 | * the key number if it is transmitted with the key up command. | |
173 | * | |
174 | */ | |
175 | if (!(mm_command & 0x01)) | |
176 | res = 0; | |
177 | else | |
178 | res = (mm_command & 0x80) * 1 + (mm_command & 0x20) * 0x02 | |
179 | + (mm_command & 0x08) * 0x04 + 1; | |
180 | return res; | |
70095677 PH |
181 | } |
182 | ||
7c08d02a | 183 | |
70095677 PH |
184 | /* We will shift from right to left. |
185 | * XXXXXXXX XX XXXXXXXX | |
56b25f8b | 186 | * shift_address shift_function shift_command |
70095677 | 187 | * |
56b25f8b | 188 | * The bits 7 downto 2 of shift_function are ignored. |
70095677 | 189 | */ |
7c08d02a PH |
190 | #define SAVE_ANOTHER_40_BYTES |
191 | #ifdef SAVE_ANOTHER_40_BYTES | |
70095677 | 192 | |
7c08d02a PH |
193 | void shift(uint8_t value) |
194 | { | |
195 | asm("ror %[val] ; Shift value right into carry\n\t" | |
196 | "rol %[cmd] ; and shift to command reg\n\t" | |
197 | "mov __tmp_reg__, %[func] ; save function value \n\t" | |
198 | "rol %[func] ; Shift up function value\n\t" | |
199 | "ror __tmp_reg__ ; shift bit 1\n\t" | |
200 | "ror __tmp_reg__ ; down to carry\n\t" | |
201 | "rol %[addr] ; And we're at the address\n\t" | |
202 | : [cmd] "=r" (shift_command), [func] "=r" (shift_function), | |
203 | [addr] "=r" (shift_address) | |
204 | : "0" (shift_command), "1" (shift_function), | |
205 | "2" (shift_address), [val] "r" (value) | |
206 | ); | |
207 | } | |
208 | ||
209 | #else /* This is what we do to shift */ | |
210 | ||
211 | void shift(uint8_t value) | |
70095677 | 212 | { |
56b25f8b PH |
213 | shift_address <<= 1; |
214 | if (shift_function & 2) | |
215 | shift_address |= 1; | |
216 | shift_function <<= 1; | |
217 | if (shift_command & 0x80) | |
218 | shift_function |= 1; | |
219 | shift_command <<= 1; | |
70095677 | 220 | if (value) |
56b25f8b | 221 | shift_command |= 1; |
70095677 | 222 | } |
7c08d02a PH |
223 | #endif |
224 | ||
9c77e706 PH |
225 | static volatile uint8_t mm_rec_tolerated_timeouts; |
226 | ||
70095677 | 227 | |
93cb14d4 | 228 | ISR(MM_TIMER_INT_VECT) { |
70095677 | 229 | |
93cb14d4 PH |
230 | static volatile uint8_t shift_command_first; |
231 | static volatile uint8_t shift_function_first; | |
232 | static volatile uint8_t shift_address_first; | |
233 | uint8_t address; | |
234 | uint8_t command; | |
235 | ||
9c77e706 PH |
236 | MM_TSTART_FAST; |
237 | ||
93cb14d4 | 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 |
70095677 PH |
243 | |
244 | switch(recstate) { | |
9c77e706 | 245 | |
7c08d02a | 246 | case MM_FIRST_FAST_SAMPLE: |
9c77e706 | 247 | recstate = MM_FIRST_SLOW_SAMPLE ; |
70095677 PH |
248 | break; |
249 | ||
7c08d02a | 250 | case MM_FIRST_SLOW_SAMPLE: |
70095677 PH |
251 | bitno = 0; |
252 | ||
7c08d02a | 253 | case MM_SLOW_SAMPLE: |
9c77e706 | 254 | recstate = MM_SLOW_WAIT_FOR_CLOCK_DELAY; |
70095677 | 255 | break; |
9c77e706 | 256 | |
7c08d02a PH |
257 | case MM_FAST_SAMPLE: |
258 | recstate = MM_FAST_WAIT_FOR_CLOCK; | |
70095677 PH |
259 | break; |
260 | ||
7c08d02a | 261 | case MM_FAST_WAIT_FOR_CLOCK: /* A timeout! */ |
9c77e706 PH |
262 | if (mm_rec_tolerated_timeouts) { |
263 | mm_rec_tolerated_timeouts--; | |
264 | } else { | |
265 | recstate = MM_IDLE; | |
266 | ||
70095677 | 267 | } |
9c77e706 PH |
268 | return; |
269 | ||
270 | case MM_SLOW_SAMPLE_DELAY: | |
271 | recstate = MM_SLOW_SAMPLE; | |
272 | return; | |
273 | ||
274 | case MM_SLOW_WAIT_FOR_CLOCK_DELAY: | |
275 | recstate = MM_SLOW_WAIT_FOR_CLOCK; | |
70095677 PH |
276 | return; |
277 | ||
7c08d02a | 278 | case MM_SLOW_WAIT_FOR_CLOCK: |
9c77e706 PH |
279 | if (mm_rec_tolerated_timeouts) { |
280 | mm_rec_tolerated_timeouts--; | |
281 | recstate = MM_SLOW_WAIT_FOR_CLOCK_DELAY; | |
70095677 PH |
282 | return; |
283 | } | |
56b25f8b | 284 | default: |
7c08d02a | 285 | recstate = MM_IDLE; |
9c77e706 | 286 | case MM_IDLE: |
70095677 PH |
287 | return; |
288 | } | |
289 | ||
290 | shift(MM_SENSE); | |
291 | bitno++; | |
56b25f8b | 292 | |
70095677 | 293 | if (bitno == 18) { /* Save first received word */ |
93cb14d4 PH |
294 | shift_address_first = shift_address; |
295 | shift_function_first = shift_function; | |
296 | shift_command_first = shift_command; | |
9c77e706 | 297 | mm_rec_tolerated_timeouts = 18; |
70095677 | 298 | } |
56b25f8b | 299 | |
70095677 | 300 | if (bitno == 36) { |
56b25f8b | 301 | if ((shift_command == shift_command_first) && |
93cb14d4 PH |
302 | (shift_address == shift_address_first) && |
303 | (shift_function == shift_function_first)) { | |
304 | ||
305 | #ifdef MM_FILTER_REPEATED | |
56b25f8b PH |
306 | if ((shift_address != address_last) || (shift_command != command_last) || |
307 | shift_function != function_last) { | |
93cb14d4 PH |
308 | #endif |
309 | address = lookup_decoder(shift_address); | |
56b25f8b | 310 | |
9c77e706 PH |
311 | if (recstate == MM_SLOW_WAIT_FOR_CLOCK_DELAY) { |
312 | trigger(); | |
93cb14d4 | 313 | mm_switch_drive(address, shift_function, shift_command); |
7c08d02a | 314 | } else if (recstate == MM_FAST_WAIT_FOR_CLOCK) { |
9c77e706 | 315 | trigger(); |
93cb14d4 PH |
316 | command = lookup_command(shift_command); |
317 | mm_switch_command(address, command); | |
70095677 | 318 | } |
93cb14d4 | 319 | #ifdef MM_FILTER_REPEATED |
70095677 | 320 | } |
93cb14d4 PH |
321 | address_last = shift_address; |
322 | function_last = shift_function; | |
323 | command_last = shift_command; | |
324 | #endif | |
70095677 | 325 | } |
56b25f8b | 326 | |
70095677 PH |
327 | } |
328 | } | |
329 | ||
56b25f8b PH |
330 | //void __attribute((weak)) mm_switch_drive(uint8_t address, uint8_t function, uint8_t command); |
331 | ||
70095677 PH |
332 | ISR(BADISR_vect) |
333 | { | |
334 | while(1) { | |
335 | /* | |
336 | setpin(PIN_LED, 1); | |
337 | _delay_ms(30); | |
338 | setpin(PIN_LED, 0); | |
339 | _delay_ms(30); | |
340 | setpin(PIN_LED, 1); | |
341 | _delay_ms(30); | |
342 | setpin(PIN_LED, 0); | |
343 | _delay_ms(2000); | |
344 | */ | |
345 | } | |
346 | } | |
347 | ||
348 | ISR(TIM0_OVF_vect) | |
349 | { | |
350 | return; | |
351 | while(1) { | |
56b25f8b | 352 | setpin(PIN_LED, 1); |
70095677 PH |
353 | _delay_ms(30); |
354 | setpin(PIN_LED, 0); | |
355 | _delay_ms(300); | |
356 | } | |
357 | ||
358 | } | |
359 | ||
70095677 PH |
360 | /* Pin change interrupt vector */ |
361 | void mm_pinchange_handler(void) | |
362 | { | |
363 | static uint8_t sense_last; | |
364 | ||
365 | if (MM_SENSE == sense_last) | |
366 | return; | |
367 | sense_last = MM_SENSE; | |
368 | if (!sense_last) | |
369 | return; | |
56b25f8b | 370 | |
9c77e706 PH |
371 | MM_TSTART_FAST; |
372 | ||
70095677 | 373 | switch(recstate) { |
7c08d02a | 374 | case MM_IDLE: |
70095677 | 375 | bitno = 0; |
7c08d02a | 376 | recstate = MM_FIRST_FAST_SAMPLE; |
70095677 | 377 | break; |
9c77e706 | 378 | |
7c08d02a PH |
379 | case MM_FIRST_SLOW_SAMPLE: |
380 | recstate = MM_FAST_SAMPLE; | |
70095677 | 381 | break; |
9c77e706 | 382 | |
7c08d02a PH |
383 | case MM_FAST_WAIT_FOR_CLOCK: |
384 | recstate = MM_FAST_SAMPLE; | |
9c77e706 | 385 | mm_rec_tolerated_timeouts = 0; |
70095677 | 386 | break; |
9c77e706 PH |
387 | |
388 | case MM_SLOW_WAIT_FOR_CLOCK_DELAY: /* If clock comes early */ | |
389 | recstate = MM_SLOW_WAIT_FOR_CLOCK; | |
390 | break; | |
391 | ||
7c08d02a | 392 | case MM_SLOW_WAIT_FOR_CLOCK: |
9c77e706 PH |
393 | recstate = MM_SLOW_SAMPLE_DELAY; |
394 | mm_rec_tolerated_timeouts = 0; | |
70095677 PH |
395 | break; |
396 | ||
397 | /* Not expected */ | |
7c08d02a PH |
398 | case MM_FIRST_FAST_SAMPLE: |
399 | case MM_FAST_SAMPLE: | |
400 | case MM_SLOW_SAMPLE: | |
9c77e706 | 401 | recstate = MM_IDLE; |
70095677 PH |
402 | default: |
403 | break; | |
404 | } | |
405 | } | |
406 | ||
93cb14d4 PH |
407 | void __attribute__((weak))mm_switch_drive(uint8_t decoder, uint8_t function, |
408 | uint8_t command) | |
409 | { | |
410 | while(1); | |
411 | } | |
412 | ||
413 | void __attribute__((weak))mm_switch_command(uint8_t address, uint8_t command) | |
414 | { | |
415 | } | |
416 | ||
417 | ||
70095677 PH |
418 | /****************************************************************************** |
419 | * The end :-) | |
420 | */ | |
421 | ||
422 | ||
423 |