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> | |
f12652df | 38 | #include <mm/mm_decode.h> |
70095677 PH |
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 | ||
f12652df PH |
49 | #ifndef MM_QUEUE_DEPTH |
50 | #define MM_QUEUE_DEPTH 8 | |
51 | #endif | |
93cb14d4 | 52 | |
f12652df PH |
53 | |
54 | /* | |
55 | * The nominal length of a bit cycle is 208 us. | |
56 | * This consists of 8 parts, each 26 us: | |
57 | * 1 d d d d d d 0 | |
58 | * | |
59 | * That means that the 1 pulse is 7 * 26 = 182us | |
60 | * and the short pulse is 1 * 26 = 26us. | |
61 | * | |
62 | * Reality seems to look not as that exact. I measure | |
63 | * 26us for the clock pulse, but 196 for the long pulse. | |
64 | * | |
65 | * Keep in mind: Time is counted in 500ns steps. | |
66 | * | |
67 | */ | |
68 | #define IN_RANGE(duration, lower, upper) \ | |
69 | ((duration >= (2 * lower) \ | |
70 | && (duration <= (2 * upper)))) | |
71 | ||
72 | #define MM_FAST_SHORT(duration) IN_RANGE(duration, 7, 18) | |
73 | #define MM_FAST_LONG(duration) IN_RANGE(duration, 70, 130) | |
74 | #define MM_FAST_GAP(duration) IN_RANGE(duration, 500, 1100) | |
75 | ||
76 | #define MM_SLOW_SHORT(duration) IN_RANGE(duration, 18, 35) | |
77 | #define MM_SLOW_LONG(duration) IN_RANGE(duration, 150, 210) | |
78 | #define MM_SLOW_GAP(duration) IN_RANGE(duration, 1000, 2000) | |
79 | ||
80 | #define MM_SUFFICIENT_IDLE(duration) (duration > 800) | |
81 | ||
82 | ||
83 | ||
84 | static enum mm_recmode recmode = __MM_INIT; | |
85 | ||
86 | #ifdef MM_USE_QUEUE | |
87 | static uint8_t queue_wpos = 0; | |
88 | static uint8_t queue_rpos = 0; | |
89 | static struct mm_command queue[MM_QUEUE_DEPTH]; | |
90 | #endif | |
c839c431 | 91 | |
7c08d02a PH |
92 | #ifndef MM_USE_REGISTER_VARS |
93 | ||
27b551dd | 94 | static volatile uint8_t mm_bitno = 0; |
7c08d02a PH |
95 | static uint8_t shift_command; |
96 | static uint8_t shift_function; | |
97 | static uint8_t shift_address; | |
27b551dd PH |
98 | uint8_t mm_time_h = 0; |
99 | uint8_t mm_time_l = 0; | |
100 | uint8_t mm_bit_val = 23; | |
101 | static uint8_t mm_flavor; | |
102 | static uint8_t mm_polarity = 1; | |
7c08d02a PH |
103 | |
104 | #endif | |
93cb14d4 | 105 | |
70095677 PH |
106 | /* We will shift from right to left. |
107 | * XXXXXXXX XX XXXXXXXX | |
56b25f8b | 108 | * shift_address shift_function shift_command |
70095677 | 109 | * |
56b25f8b | 110 | * The bits 7 downto 2 of shift_function are ignored. |
27b551dd PH |
111 | * |
112 | * First comes the C implementation of the shift routine. | |
113 | * It is usually not used anymore, but I left it for | |
114 | * illustration purposes. | |
115 | * The real shift function is written in assembly and saves many instructions. | |
70095677 | 116 | */ |
27b551dd | 117 | #if 0 |
f12652df | 118 | static void shift(uint8_t value) |
27b551dd PH |
119 | { |
120 | shift_address <<= 1; | |
121 | if (shift_function & 2) | |
122 | shift_address |= 1; | |
123 | shift_function <<= 1; | |
124 | if (shift_command & 0x80) | |
125 | shift_function |= 1; | |
126 | shift_command <<= 1; | |
127 | if (value) | |
128 | shift_command |= 1; | |
129 | } | |
130 | #else | |
70095677 | 131 | |
f12652df | 132 | static void shift(uint8_t value) |
7c08d02a PH |
133 | { |
134 | asm("ror %[val] ; Shift value right into carry\n\t" | |
135 | "rol %[cmd] ; and shift to command reg\n\t" | |
136 | "mov __tmp_reg__, %[func] ; save function value \n\t" | |
137 | "rol %[func] ; Shift up function value\n\t" | |
138 | "ror __tmp_reg__ ; shift bit 1\n\t" | |
139 | "ror __tmp_reg__ ; down to carry\n\t" | |
140 | "rol %[addr] ; And we're at the address\n\t" | |
141 | : [cmd] "=r" (shift_command), [func] "=r" (shift_function), | |
142 | [addr] "=r" (shift_address) | |
143 | : "0" (shift_command), "1" (shift_function), | |
144 | "2" (shift_address), [val] "r" (value) | |
145 | ); | |
146 | } | |
7c08d02a PH |
147 | #endif |
148 | ||
c839c431 | 149 | static void mm_feed_bit(uint8_t bit) |
b1a7e65e | 150 | { |
93cb14d4 PH |
151 | static volatile uint8_t shift_command_first; |
152 | static volatile uint8_t shift_function_first; | |
153 | static volatile uint8_t shift_address_first; | |
154 | uint8_t address; | |
93cb14d4 | 155 | |
b1a7e65e | 156 | shift(bit); |
27b551dd | 157 | mm_bitno++; |
c839c431 | 158 | |
27b551dd | 159 | if (mm_bitno == 18) { /* Save first received word */ |
93cb14d4 PH |
160 | shift_address_first = shift_address; |
161 | shift_function_first = shift_function; | |
162 | shift_command_first = shift_command; | |
f12652df | 163 | } |
56b25f8b | 164 | |
27b551dd | 165 | if (mm_bitno == 36) { |
56b25f8b | 166 | if ((shift_command == shift_command_first) && |
93cb14d4 PH |
167 | (shift_address == shift_address_first) && |
168 | (shift_function == shift_function_first)) { | |
c839c431 | 169 | |
f12652df PH |
170 | #ifdef MM_USE_CALLBACK |
171 | address = mm_lookup_decoder(shift_address); | |
172 | if (recmode == MM_SLOW) { | |
173 | mm_drive_cb(address, shift_function, | |
174 | shift_command); | |
27b551dd | 175 | } else { |
f12652df PH |
176 | mm_key_cb(address, shift_function, shift_command); |
177 | } | |
178 | #endif | |
179 | ||
180 | #ifdef MM_USE_QUEUE | |
181 | queue[queue_wpos].recmode = recmode; | |
182 | queue[queue_wpos].address = shift_address; | |
183 | queue[queue_wpos].function = shift_function; | |
184 | queue[queue_wpos].command = shift_command; | |
185 | queue_wpos = (queue_wpos + 1) % MM_QUEUE_DEPTH; | |
186 | #endif | |
70095677 PH |
187 | } |
188 | } | |
189 | } | |
f12652df PH |
190 | #ifdef MM_USE_QUEUE |
191 | ||
192 | struct mm_command *mm_get(void) | |
193 | { | |
194 | struct mm_command *result = NULL; | |
195 | uint8_t sreg_bak = SREG; | |
196 | cli(); | |
197 | if (queue_rpos != queue_wpos) { | |
198 | result = &queue[queue_rpos]; | |
199 | queue_rpos = (queue_rpos + 1) % MM_QUEUE_DEPTH; | |
200 | } | |
201 | SREG = sreg_bak; | |
202 | return result; | |
203 | } | |
204 | #endif | |
70095677 | 205 | |
b1a7e65e PH |
206 | /* |
207 | * The timeout interrupt vector does nothing else | |
27b551dd | 208 | * than incrementing the mm_time_h round counter. |
b1a7e65e PH |
209 | * |
210 | * It is written in naked assembly because we want to avoid pushing | |
211 | * and popping of all upper registers. | |
212 | */ | |
213 | void __attribute__((naked)) MM_TIMER_INT_VECT(void) | |
70095677 | 214 | { |
b1a7e65e PH |
215 | asm("push r0 ; save r0 \n\t" |
216 | "in r0, __SREG__ \n\t" | |
217 | #ifdef MM_USE_REGISTER_VARS | |
218 | "inc %[th] \n\t" | |
c839c431 PH |
219 | "brne nover \n\t" |
220 | "dec %[th] \n\t" | |
221 | "nover: \n\t" | |
b1a7e65e PH |
222 | #else |
223 | "push r1 \n\t" | |
c839c431 | 224 | "lds r1, mm_time_h \n\t" |
b1a7e65e | 225 | "inc r1 \n\t" |
c839c431 PH |
226 | "brne nover \n\t" |
227 | "dec r1 \n\t" | |
228 | "nover: \n\t" | |
27b551dd | 229 | "sts mm_time_h, r1 \n\t" |
b1a7e65e PH |
230 | "pop r1 \n\t" |
231 | #endif | |
232 | "out __SREG__, r0 \n\t" | |
233 | "pop r0 \n\t" | |
b1a7e65e PH |
234 | "reti \n\t" |
235 | #ifdef MM_USE_REGISTER_VARS | |
27b551dd | 236 | :: [th] "r" (mm_time_h) |
b1a7e65e PH |
237 | #endif |
238 | ); | |
70095677 PH |
239 | } |
240 | ||
b1a7e65e PH |
241 | /* |
242 | * Another naked interrupt trampoline | |
243 | * | |
244 | * Here we first save the timer value as fast as possible, then we jump (!) | |
245 | * into the "official" interrupt handler with all its decorations. | |
246 | */ | |
247 | void __attribute__((naked)) PCINT0_vect(void) | |
70095677 | 248 | { |
b1a7e65e PH |
249 | #ifdef MM_USE_REGISTER_VARS |
250 | asm("in %[tl], %[tmr] \n\t" | |
251 | "rjmp __vector_pinchange \n\t" | |
27b551dd | 252 | :: [tl] "r" (mm_time_l), [tmr] "I" (_SFR_IO_ADDR(TCNT0)) |
b1a7e65e PH |
253 | ); |
254 | #else | |
255 | asm("push r0 \n\t" | |
256 | "in r0, %[tmr] \n\t" | |
27b551dd | 257 | "sts mm_time_l, r0 \n\t" |
b1a7e65e PH |
258 | "pop r0 \n\t" |
259 | "rjmp __vector_pinchange \n\t" | |
260 | :: [tmr] "I" (_SFR_IO_ADDR(TCNT0)) | |
261 | ); | |
262 | #endif | |
263 | } | |
f12652df | 264 | |
b1a7e65e PH |
265 | /* Pin change interrupt vector, here we have a bit more time */ |
266 | ISR(__vector_pinchange){ | |
c839c431 PH |
267 | uint16_t duration; |
268 | ||
b1a7e65e | 269 | /* First kill off that timer */ |
b1a7e65e | 270 | MM_TSTART; /* Restart timer */ |
f12652df | 271 | |
b1a7e65e | 272 | /* Account for not yet handled timer overflow */ |
c839c431 | 273 | TIFR |= _BV(TOV0); |
b1a7e65e | 274 | |
c839c431 | 275 | duration = mm_time_h << 8; |
27b551dd | 276 | duration += mm_time_l; |
c839c431 | 277 | mm_bit_val = MM_SENSE; |
27b551dd | 278 | mm_time_h = 0; |
b1a7e65e | 279 | |
f12652df PH |
280 | if (recmode != MM_SLOW) { |
281 | /* Fast short MM pulse */ | |
282 | if (MM_FAST_SHORT(duration)){ | |
283 | recmode = MM_FAST; | |
284 | if (mm_bit_val == mm_polarity) | |
c839c431 | 285 | mm_feed_bit(0); |
f12652df PH |
286 | goto done; |
287 | } | |
288 | /* Fast long MM pulse */ | |
289 | if (MM_FAST_LONG(duration)) { | |
290 | recmode = MM_FAST; | |
291 | if (mm_bit_val == mm_polarity) | |
c839c431 | 292 | mm_feed_bit(1); |
f12652df PH |
293 | goto done; |
294 | } | |
295 | } else { | |
296 | /* Accepted slow inter package gap */ | |
297 | if (MM_SLOW_GAP(duration)) | |
298 | if (mm_bit_val != mm_polarity) | |
c839c431 | 299 | goto done; |
f12652df | 300 | } |
c839c431 | 301 | |
f12652df | 302 | if (recmode != MM_FAST) { |
c839c431 | 303 | |
f12652df PH |
304 | /* Slow short MM pulse */ |
305 | if (MM_SLOW_SHORT(duration)) { | |
306 | recmode = MM_SLOW; | |
307 | if (mm_bit_val == mm_polarity) | |
c839c431 | 308 | mm_feed_bit(0); |
f12652df | 309 | goto done; |
b1a7e65e | 310 | } |
f12652df PH |
311 | /* Slow long MM pulse */ |
312 | if (MM_SLOW_LONG(duration)) { | |
313 | recmode = MM_SLOW; | |
314 | if (mm_bit_val == mm_polarity) | |
c839c431 | 315 | mm_feed_bit(1); |
f12652df PH |
316 | goto done; |
317 | } | |
318 | } else { | |
319 | /* Accepted fast interpackage gap */ | |
320 | if (MM_FAST_GAP(duration)) { | |
321 | if (mm_bit_val != mm_polarity) | |
c839c431 | 322 | goto done; |
d0047978 PH |
323 | } |
324 | } | |
b1a7e65e | 325 | |
f12652df | 326 | /* |
c839c431 PH |
327 | * If we have reached here, our pulse comes in somehow unexpected. |
328 | * We kill of everything by re-arming the state machine. | |
329 | */ | |
330 | /* Start over receiver */ | |
331 | mm_bitno = 0; | |
f12652df PH |
332 | |
333 | if (MM_SUFFICIENT_IDLE(duration)) { | |
334 | recmode = __MM_ARMED; | |
335 | mm_polarity = !mm_bit_val; | |
336 | } else { | |
337 | recmode = __MM_INIT; | |
338 | } | |
b1a7e65e | 339 | done: |
f12652df | 340 | mm_pinchange_callback(); |
d0047978 | 341 | } |
9c77e706 | 342 | |
27b551dd | 343 | |
f12652df | 344 | void __attribute__((weak))mm_pinchange_callback(void) |
d0047978 | 345 | { |
70095677 PH |
346 | } |
347 | ||
f12652df | 348 | void __attribute__((weak))mm_drive_cb(uint8_t decoder, uint8_t function, |
93cb14d4 PH |
349 | uint8_t command) |
350 | { | |
93cb14d4 PH |
351 | } |
352 | ||
f12652df PH |
353 | void __attribute__((weak))mm_key_cb(uint8_t address, uint8_t function, |
354 | uint8_t command) | |
93cb14d4 PH |
355 | { |
356 | } | |
357 | ||
358 | ||
70095677 PH |
359 | /****************************************************************************** |
360 | * The end :-) | |
361 | */ |