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