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