1 /******************************************************************************
3 * Trennfix firmware - mm_switch.c
5 * Maerklin Motorola switch command receiver
7 * Copyright (C) 2017 Philipp Hachtmann
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.
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.
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/>.
22 *****************************************************************************/
29 #include <avr/eeprom.h>
30 #include <avr/interrupt.h>
31 #include <avr/pgmspace.h>
33 #include <util/delay.h>
36 #include <config/hardware.h>
37 #include <mm/mm_switch.h>
42 * Check for stuff we need
45 #if !defined(MM_TSTART) || !defined(MM_SENSE) || !defined(MM_TIMER_INT_VECT)
47 #error Missing needed MM_... macro!
52 * Private global variables
55 #ifndef MM_USE_REGISTER_VARS
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
;
66 * Lookup trinary nibble
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.
72 static const uint8_t nibble_table
[16]={
83 #define lookup_nibble(nibble) nibble_table[nibble & 0xf]
85 static uint8_t lookup_decoder(uint8_t mm_byte
)
91 low
= lookup_nibble(mm_byte
>> 4);
92 high
= lookup_nibble(mm_byte
& 0xf);
95 return 9 * high
+ low
;
98 static uint8_t lookup_command(uint8_t mm_command
)
102 * Check for aabbccdd condition
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
112 if ((mm_command
^ (mm_command
<< 1)) & 0xaa)
115 * Protocol differences:
116 * =====================
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.
122 * In MM1, switch commands are different from MM2 with respect what
123 * happens if you release a button.
125 * When you press a button, both protocols send
127 * <aaaaaaaa><00><aabbcc11>
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.
132 * The last two bits correspond to "on" state of the button/coil.
134 * When a key is released under MM1 protocol, the sequence sent is
135 * analogue to the button down sequence:
137 * <aaaaaaaa><00><aabbcc00> where abc again represents the button's
138 * address and the last bits now signal "off".
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
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"...
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
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
162 * What this code now actually does:
163 * =================================
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.
170 if (!(mm_command
& 0x01))
173 res
= (mm_command
& 0x80) * 1 + (mm_command
& 0x20) * 0x02
174 + (mm_command
& 0x08) * 0x04 + 1;
179 /* We will shift from right to left.
180 * XXXXXXXX XX XXXXXXXX
181 * shift_address shift_function shift_command
183 * The bits 7 downto 2 of shift_function are ignored.
185 #define SAVE_ANOTHER_40_BYTES
186 #ifdef SAVE_ANOTHER_40_BYTES
188 void shift(uint8_t value
)
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
)
204 #else /* This is what we do to shift */
206 void shift(uint8_t value
)
209 if (shift_function
& 2)
211 shift_function
<<= 1;
212 if (shift_command
& 0x80)
220 static volatile uint8_t mm_rec_tolerated_timeouts
;
223 ISR(MM_TIMER_INT_VECT
) {
225 static volatile uint8_t shift_command_first
;
226 static volatile uint8_t shift_function_first
;
227 static volatile uint8_t shift_address_first
;
231 #ifdef MM_FILTER_REPEATED
232 static uint8_t address_last
= 0xff;
233 static uint8_t function_last
= 0xff;
234 static uint8_t command_last
= 0xff;
239 case MM_FIRST_FAST_SAMPLE
:
240 recstate
= MM_FIRST_SLOW_SAMPLE
;
243 case MM_FIRST_SLOW_SAMPLE
:
247 recstate
= MM_SLOW_WAIT_FOR_CLOCK_DELAY
;
250 case MM_SLOW_WAIT_FOR_CLOCK_DELAY
:
251 recstate
= MM_SLOW_WAIT_FOR_CLOCK
;
255 recstate
= MM_FAST_WAIT_FOR_CLOCK
;
258 case MM_FAST_WAIT_FOR_CLOCK
: /* A timeout! */
259 if (mm_rec_tolerated_timeouts
)
260 mm_rec_tolerated_timeouts
--;
265 case MM_SLOW_SAMPLE_DELAY
:
266 recstate
= MM_SLOW_SAMPLE
;
269 case MM_SLOW_WAIT_FOR_CLOCK
:
270 if (mm_rec_tolerated_timeouts
) {
271 mm_rec_tolerated_timeouts
--;
272 recstate
= MM_SLOW_WAIT_FOR_CLOCK_DELAY
;
284 if (bitno
== 18) { /* Save first received word */
285 shift_address_first
= shift_address
;
286 shift_function_first
= shift_function
;
287 shift_command_first
= shift_command
;
288 mm_rec_tolerated_timeouts
= 18;
293 if ((shift_command
== shift_command_first
) &&
294 (shift_address
== shift_address_first
) &&
295 (shift_function
== shift_function_first
)) {
298 #ifdef MM_FILTER_REPEATED
299 if ((shift_address
!= address_last
) || (shift_command
!= command_last
) ||
300 shift_function
!= function_last
) {
302 address
= lookup_decoder(shift_address
);
303 if (recstate
== MM_SLOW_WAIT_FOR_CLOCK_DELAY
) {
304 mm_switch_drive(address
, shift_function
, shift_command
);
305 } else if (recstate
== MM_FAST_WAIT_FOR_CLOCK
) {
306 command
= lookup_command(shift_command
);
307 mm_switch_command(address
, command
);
310 #ifdef MM_FILTER_REPEATED
312 address_last
= shift_address
;
313 function_last
= shift_function
;
314 command_last
= shift_command
;
321 //void __attribute((weak)) mm_switch_drive(uint8_t address, uint8_t function, uint8_t command);
351 uint8_t register sense_last
asm("r9");
354 /* Pin change interrupt vector */
356 static uint8_t sense_last
;
357 if (MM_SENSE
&& !sense_last
) {
362 recstate
= MM_FIRST_FAST_SAMPLE
;
365 case MM_FIRST_SLOW_SAMPLE
:
366 recstate
= MM_FAST_SAMPLE
;
369 case MM_FAST_WAIT_FOR_CLOCK
:
370 recstate
= MM_FAST_SAMPLE
;
371 mm_rec_tolerated_timeouts
= 0;
374 case MM_SLOW_WAIT_FOR_CLOCK_DELAY
: /* If clock comes early */
375 recstate
= MM_SLOW_SAMPLE_DELAY
;
378 case MM_SLOW_WAIT_FOR_CLOCK
:
379 recstate
= MM_SLOW_SAMPLE_DELAY
;
380 mm_rec_tolerated_timeouts
= 0;
383 case MM_SLOW_SAMPLE_DELAY
:
384 recstate
= MM_SLOW_SAMPLE
;
388 case MM_FIRST_FAST_SAMPLE
:
396 sense_last
= MM_SENSE
;
397 mm_switch_pinchange_callback();
400 void __attribute__((weak
))mm_switch_pinchange_callback(void)
404 void __attribute__((weak
))mm_switch_drive(uint8_t decoder
, uint8_t function
,
409 void __attribute__((weak
))mm_switch_command(uint8_t address
, uint8_t command
)
414 /******************************************************************************