]>
Commit | Line | Data |
---|---|---|
ec1bef8e | 1 | /* vim:ts=4 sts=4 et tw=80 |
2 | * | |
3 | * fnordlicht firmware | |
4 | * | |
5 | * for additional information please | |
6 | * see http://lochraster.org/fnordlichtmini | |
7 | * | |
8 | * (c) by Alexander Neumann <alexander@bumpern.de> | |
9 | * | |
10 | * This program is free software: you can redistribute it and/or modify it | |
11 | * under the terms of the GNU General Public License version 3 as published by | |
12 | * the Free Software Foundation. | |
13 | * | |
14 | * This program is distributed in the hope that it will be useful, but WITHOUT | |
15 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
16 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
17 | * more details. | |
18 | * | |
19 | * You should have received a copy of the GNU General Public License along with | |
20 | * this program. If not, see <http://www.gnu.org/licenses/>. | |
21 | */ | |
22 | ||
23 | #include <stdlib.h> | |
24 | #include <string.h> | |
25 | #include <avr/wdt.h> | |
26 | #include <avr/pgmspace.h> | |
27 | #include <avr/sleep.h> | |
28 | #include <avr/interrupt.h> | |
29 | #include <util/delay.h> | |
30 | #include "globals.h" | |
31 | #include "remote.h" | |
32 | #include "fifo.h" | |
33 | #include "uart.h" | |
34 | #include "pwm.h" | |
35 | #include "../common/pt/pt.h" | |
36 | #include "script.h" | |
37 | #include "remote-proto.h" | |
38 | #include "../common/common.h" | |
39 | #include "storage.h" | |
40 | #include "timer.h" | |
41 | ||
42 | /* abbreviations for port, ddr and pin */ | |
43 | #define R_PORT _OUTPORT(REMOTE_INT_PORT) | |
44 | #define R_DDR _DDRPORT(REMOTE_INT_PORT) | |
45 | #define R_PIN _INPORT(REMOTE_INT_PORT) | |
46 | #define INTPIN REMOTE_INT_PIN | |
47 | ||
48 | #define M_PORT _OUTPORT(REMOTE_MASTER_PORT) | |
49 | #define M_DDR _DDRPORT(REMOTE_MASTER_PORT) | |
50 | #define M_PIN _INPORT(REMOTE_MASTER_PORT) | |
51 | #define M_PIN1 REMOTE_MASTER_PIN1 | |
52 | #define M_PIN2 REMOTE_MASTER_PIN2 | |
53 | ||
54 | struct remote_state_t | |
55 | { | |
56 | /* serial communication */ | |
57 | union { | |
58 | struct remote_msg_t msg; | |
59 | uint8_t buf[REMOTE_MSG_LEN]; | |
60 | }; | |
61 | uint8_t buflen; | |
62 | ||
63 | uint8_t sync; | |
64 | uint8_t synced; | |
65 | struct pt thread; | |
66 | #if CONFIG_MASTER_MODE | |
67 | struct pt master_thread; | |
68 | #endif | |
69 | ||
70 | /* int line */ | |
71 | timer_t int_timer; | |
72 | enum { | |
73 | INT_IDLE = 0, | |
74 | INT_PULLED = 1, | |
75 | INT_PULLED_TIMER = 2, | |
76 | } int_state; | |
77 | }; | |
78 | ||
79 | struct remote_state_t remote; | |
80 | struct global_remote_t global_remote; | |
81 | ||
82 | #if CONFIG_SERIAL && CONFIG_REMOTE | |
83 | ||
84 | /* static parsing routines */ | |
85 | static void parse_fade_rgb(struct remote_msg_fade_rgb_t *msg); | |
86 | static void parse_fade_hsv(struct remote_msg_fade_hsv_t *msg); | |
87 | static void parse_save_rgb(struct remote_msg_save_rgb_t *msg); | |
88 | static void parse_save_hsv(struct remote_msg_save_hsv_t *msg); | |
89 | static void parse_save_current(struct remote_msg_save_current_t *msg); | |
90 | static void parse_config_offsets(struct remote_msg_config_offsets_t *msg); | |
91 | static void parse_start_program(struct remote_msg_start_program_t *msg); | |
92 | static void parse_stop(struct remote_msg_stop_t *msg); | |
93 | static void parse_modify_current(struct remote_msg_modify_current_t *msg); | |
94 | static void parse_pull_int(struct remote_msg_pull_int_t *msg); | |
95 | static void parse_config_startup(struct remote_msg_config_startup_t *msg); | |
96 | static void parse_bootloader(struct remote_msg_bootloader_t *msg); | |
97 | static void parse_powerdown(void); | |
98 | ||
99 | void remote_init(void) | |
100 | { | |
101 | /* master mode check: check if PIN2 pulls down PIN1 */ | |
102 | /* configure M_PIN1 as input with pullup */ | |
103 | M_DDR &= ~_BV(M_PIN1); | |
104 | M_PORT |= _BV(M_PIN1); | |
105 | /* configure M_PIN2 as output, set low */ | |
106 | M_DDR |= _BV(M_PIN2); | |
107 | M_PORT &= ~_BV(M_PIN2); | |
108 | ||
109 | /* initialize offsets */ | |
110 | global_remote.offsets.saturation = 255; | |
111 | global_remote.offsets.value = 255; | |
112 | ||
113 | /* initialize int pin, tri-state */ | |
114 | R_DDR &= ~_BV(INTPIN); | |
115 | R_PORT &= ~_BV(INTPIN); | |
116 | #ifdef INIT_ZERO | |
117 | remote.int_state = INT_IDLE; | |
118 | ||
119 | PT_INIT(&remote.master_thread); | |
120 | #endif | |
121 | ||
122 | #if CONFIG_MASTER_MODE == 1 | |
123 | /* check master state: read value of M_PIN1 */ | |
124 | if (!(M_PIN & _BV(M_PIN1))) | |
125 | global_remote.master = true; | |
126 | #elif CONFIG_MASTER_MODE == 2 | |
127 | /* statically configure master mode */ | |
128 | global_remote.master = true; | |
129 | #endif | |
130 | } | |
131 | ||
132 | static void remote_parse_msg(struct remote_msg_t *msg) | |
133 | { | |
134 | /* verify address */ | |
135 | if (msg->address != global_remote.address && msg->address != REMOTE_ADDR_BROADCAST) | |
136 | return; | |
137 | ||
138 | /* parse command */ | |
139 | switch (msg->cmd) { | |
140 | case REMOTE_CMD_FADE_RGB: | |
141 | parse_fade_rgb((struct remote_msg_fade_rgb_t *)msg); | |
142 | break; | |
143 | case REMOTE_CMD_FADE_HSV: | |
144 | parse_fade_hsv((struct remote_msg_fade_hsv_t *)msg); | |
145 | break; | |
146 | case REMOTE_CMD_SAVE_RGB: | |
147 | parse_save_rgb((struct remote_msg_save_rgb_t *)msg); | |
148 | break; | |
149 | case REMOTE_CMD_SAVE_HSV: | |
150 | parse_save_hsv((struct remote_msg_save_hsv_t *)msg); | |
151 | break; | |
152 | case REMOTE_CMD_SAVE_CURRENT: | |
153 | parse_save_current((struct remote_msg_save_current_t *)msg); | |
154 | break; | |
155 | case REMOTE_CMD_CONFIG_OFFSETS: | |
156 | parse_config_offsets((struct remote_msg_config_offsets_t *)msg); | |
157 | break; | |
158 | case REMOTE_CMD_START_PROGRAM: | |
159 | parse_start_program((struct remote_msg_start_program_t *)msg); | |
160 | break; | |
161 | case REMOTE_CMD_STOP: | |
162 | parse_stop((struct remote_msg_stop_t *)msg); | |
163 | break; | |
164 | case REMOTE_CMD_MODIFY_CURRENT: | |
165 | parse_modify_current((struct remote_msg_modify_current_t *)msg); | |
166 | break; | |
167 | case REMOTE_CMD_PULL_INT: | |
168 | parse_pull_int((struct remote_msg_pull_int_t *)msg); | |
169 | break; | |
170 | case REMOTE_CMD_CONFIG_STARTUP: | |
171 | parse_config_startup((struct remote_msg_config_startup_t *)msg); | |
172 | break; | |
173 | case REMOTE_CMD_BOOTLOADER: | |
174 | parse_bootloader((struct remote_msg_bootloader_t *)msg); | |
175 | break; | |
176 | case REMOTE_CMD_POWERDOWN: | |
177 | parse_powerdown(); | |
178 | break; | |
179 | ||
180 | } | |
181 | } | |
182 | ||
183 | static PT_THREAD(remote_thread(struct pt *thread)) | |
184 | { | |
185 | PT_BEGIN(thread); | |
186 | ||
187 | while (1) { | |
188 | PT_WAIT_UNTIL(thread, remote.buflen == REMOTE_MSG_LEN); | |
189 | ||
190 | remote_parse_msg(&remote.msg); | |
191 | remote.buflen = 0; | |
192 | } | |
193 | ||
194 | PT_END(thread); | |
195 | } | |
196 | ||
197 | static __noinline void send_msg(struct remote_msg_t *msg) | |
198 | { | |
199 | uint8_t *ptr = (uint8_t *)msg; | |
200 | for (uint8_t i = 0; i < REMOTE_MSG_LEN; i++) | |
201 | uart_putc(*ptr++); | |
202 | } | |
203 | ||
204 | ||
205 | static void send_resync(uint8_t addr) | |
206 | { | |
207 | for (uint8_t i = 0; i < REMOTE_SYNC_LEN; i++) | |
208 | uart_putc(REMOTE_CMD_RESYNC); | |
209 | uart_putc(addr); | |
210 | } | |
211 | ||
212 | #if CONFIG_MASTER_MODE | |
213 | /* parameters for master mode script commands */ | |
214 | #define MASTER_PROGRAMS 2 | |
215 | static PROGMEM uint8_t master_parameters[] = { | |
216 | /* first: colorwheel forward */ | |
217 | 0, /* program index */ | |
218 | 1, /* fade step */ | |
219 | 2, /* fade delay */ | |
220 | 0, /* fade sleep */ | |
221 | 0, 0, /* hue start (little endian) */ | |
222 | 20, 0, /* hue step (little endian) */ | |
223 | -1, /* addr add */ | |
224 | 255, /* saturation */ | |
225 | 255, /* value */ | |
226 | ||
227 | /* first: colorwheel backward */ | |
228 | 0, /* program index */ | |
229 | 1, /* fade step */ | |
230 | 2, /* fade delay */ | |
231 | 0, /* fade sleep */ | |
232 | 0, 0, /* hue start (little endian) */ | |
233 | 20, 0, /* hue step (little endian) */ | |
234 | 1, /* addr add */ | |
235 | 255, /* saturation */ | |
236 | 255, /* value */ | |
237 | }; | |
238 | ||
239 | static PT_THREAD(remote_master_thread(struct pt *thread)) | |
240 | { | |
241 | static struct remote_msg_start_program_t msg; | |
242 | static timer_t timer; | |
243 | static uint16_t sleep; | |
244 | static uint8_t *ptr; | |
245 | static uint8_t idx; | |
246 | ||
247 | PT_BEGIN(thread); | |
248 | ||
249 | /* wait */ | |
250 | timer_set(&timer, MASTER_WAIT_BEFORE_SYNC); | |
251 | while(!timer_expired(&timer)) | |
252 | PT_YIELD(thread); | |
253 | ||
254 | /* start program on all devices */ | |
255 | msg.address = 0xff; | |
256 | msg.cmd = REMOTE_CMD_START_PROGRAM; | |
257 | ||
258 | while (1) { | |
259 | ptr = &master_parameters[0]; | |
260 | ||
261 | for (idx = 0; idx < MASTER_PROGRAMS; idx++) { | |
262 | /* stop current program and fading */ | |
263 | script_stop(); | |
264 | pwm_stop_fading(); | |
265 | ||
266 | /* start program colorwheel on all nodes */ | |
267 | msg.script = pgm_read_byte(ptr++); | |
268 | /* load parameters */ | |
269 | for (uint8_t i = 0; i < sizeof(msg.params); i++) | |
270 | msg.params.raw[i] = pgm_read_byte(ptr++); | |
271 | ||
272 | /* send */ | |
273 | send_resync(MASTER_MODE_FIRST_ADDRESS); | |
274 | PT_YIELD(thread); | |
275 | send_msg((struct remote_msg_t *)&msg); | |
276 | ||
277 | /* start program locally */ | |
278 | script_start(0, msg.script, &msg.params); | |
279 | ||
280 | /* sleep */ | |
281 | sleep = MASTER_MODE_SLEEP; | |
282 | while (sleep--) { | |
283 | /* sleep 1s */ | |
284 | timer_set(&timer, 100); | |
285 | ||
286 | while (!timer_expired(&timer)) | |
287 | PT_YIELD(thread); | |
288 | } | |
289 | } | |
290 | } | |
291 | ||
292 | PT_END(thread); | |
293 | } | |
294 | #endif | |
295 | ||
296 | void remote_poll(void) | |
297 | { | |
298 | if (fifo_fill((fifo_t *)&global_uart.rx) > 0) { | |
299 | uint8_t data = fifo_dequeue((fifo_t *)&global_uart.rx); | |
300 | ||
301 | /* check if sync sequence has been received before */ | |
302 | if (remote.sync == REMOTE_SYNC_LEN) { | |
303 | /* synced, safe address and send next address to following device */ | |
304 | global_remote.address = data; | |
305 | uart_putc(data+1); | |
306 | ||
307 | /* reset remote buffer */ | |
308 | remote.buflen = 0; | |
309 | ||
310 | /* enable remote command thread */ | |
311 | remote.synced = 1; | |
312 | PT_INIT(&remote.thread); | |
313 | #if CONFIG_MASTER_MODE | |
314 | global_remote.master = false; | |
315 | #endif | |
316 | } else { | |
317 | /* just pass through data */ | |
318 | uart_putc(data); | |
319 | ||
320 | /* put data into remote buffer | |
321 | * (just processed if remote.synced == 1) */ | |
322 | if (remote.buflen < sizeof(remote.buf)) | |
323 | remote.buf[remote.buflen++] = data; | |
324 | } | |
325 | ||
326 | /* remember the number of sync bytes received so far */ | |
327 | if (data == REMOTE_CMD_RESYNC) | |
328 | remote.sync++; | |
329 | else | |
330 | remote.sync = 0; | |
331 | } | |
332 | ||
333 | if (remote.synced) | |
334 | PT_SCHEDULE(remote_thread(&remote.thread)); | |
335 | ||
336 | /* check int timer */ | |
337 | if (remote.int_state == INT_PULLED_TIMER && timer_expired(&remote.int_timer)) { | |
338 | remote_release_int(); | |
339 | } | |
340 | ||
341 | #if CONFIG_MASTER_MODE | |
342 | if (global_remote.master) | |
343 | PT_SCHEDULE(remote_master_thread(&remote.master_thread)); | |
344 | #endif | |
345 | } | |
346 | ||
347 | void remote_pull_int(void) | |
348 | { | |
349 | if (remote.int_state != INT_IDLE) | |
350 | return; | |
351 | ||
352 | /* pull int pin to ground */ | |
353 | R_DDR |= _BV(INTPIN); | |
354 | R_PORT &= ~_BV(INTPIN); | |
355 | } | |
356 | ||
357 | void remote_release_int(void) | |
358 | { | |
359 | if (remote.int_state == INT_IDLE) | |
360 | return; | |
361 | ||
362 | /* reset pin to tri-state */ | |
363 | R_DDR &= ~_BV(INTPIN); | |
364 | R_PORT &= ~_BV(INTPIN); | |
365 | remote.int_state = INT_IDLE; | |
366 | } | |
367 | ||
368 | /* offset helper functions */ | |
369 | uint8_t remote_apply_offset(uint8_t value, int8_t offset) | |
370 | { | |
371 | if (offset < 0) { | |
372 | if (value > -offset) | |
373 | value += offset; | |
374 | else | |
375 | value = 1; | |
376 | } else if (offset > 0) { | |
377 | uint16_t temp = value; | |
378 | temp += offset; | |
379 | if (temp > 255) | |
380 | value = 255; | |
381 | else | |
382 | value = LO8(temp); | |
383 | } | |
384 | ||
385 | return value; | |
386 | } | |
387 | ||
388 | static uint8_t apply_scale(uint8_t value, uint8_t scale) | |
389 | { | |
390 | uint16_t temp = value * scale; | |
391 | temp /= 255; | |
392 | return LO8(temp); | |
393 | } | |
394 | ||
395 | void remote_apply_hsv_offset(struct hsv_color_t *color) | |
396 | { | |
397 | color->hue += global_remote.offsets.hue; | |
398 | color->saturation = apply_scale(color->saturation, global_remote.offsets.saturation); | |
399 | color->value = apply_scale(color->value, global_remote.offsets.value); | |
400 | } | |
401 | ||
402 | /* parser functions */ | |
403 | ||
404 | void parse_fade_rgb(struct remote_msg_fade_rgb_t *msg) | |
405 | { | |
406 | pwm_fade_rgb(&msg->color, msg->step, msg->delay); | |
407 | } | |
408 | ||
409 | void parse_fade_hsv(struct remote_msg_fade_hsv_t *msg) | |
410 | { | |
411 | pwm_fade_hsv(&msg->color, msg->step, msg->delay); | |
412 | } | |
413 | ||
414 | void parse_save_rgb(struct remote_msg_save_rgb_t *msg) | |
415 | { | |
416 | if (msg->slot >= CONFIG_EEPROM_COLORS) | |
417 | return; | |
418 | ||
419 | struct storage_color_t c; | |
420 | c.step = msg->step; | |
421 | c.delay = msg->delay; | |
422 | c.pause = msg->pause; | |
423 | ||
424 | /* mark color as rgb */ | |
425 | c.color.rgb_marker = 255; | |
426 | ||
427 | c.color.red = msg->color.red; | |
428 | c.color.green = msg->color.green; | |
429 | c.color.blue = msg->color.blue; | |
430 | ||
431 | storage_save_color(msg->slot, &c); | |
432 | } | |
433 | ||
434 | void parse_save_hsv(struct remote_msg_save_hsv_t *msg) | |
435 | { | |
436 | if (msg->slot >= CONFIG_EEPROM_COLORS) | |
437 | return; | |
438 | ||
439 | struct storage_color_t c; | |
440 | c.step = msg->step; | |
441 | c.delay = msg->delay; | |
442 | c.pause = msg->pause; | |
443 | ||
444 | c.color.hue = msg->color.hue; | |
445 | c.color.saturation = msg->color.saturation; | |
446 | c.color.value = msg->color.value; | |
447 | ||
448 | storage_save_color(msg->slot, &c); | |
449 | } | |
450 | ||
451 | void parse_save_current(struct remote_msg_save_current_t *msg) | |
452 | { | |
453 | if (msg->slot >= CONFIG_EEPROM_COLORS) | |
454 | return; | |
455 | ||
456 | struct storage_color_t c; | |
457 | c.step = msg->step; | |
458 | c.delay = msg->delay; | |
459 | c.pause = msg->pause; | |
460 | ||
461 | /* mark color as rgb */ | |
462 | c.color.rgb_marker = 255; | |
463 | ||
464 | c.color.red = global_pwm.current.red; | |
465 | c.color.green = global_pwm.current.green; | |
466 | c.color.blue = global_pwm.current.blue; | |
467 | ||
468 | storage_save_color(msg->slot, &c); | |
469 | } | |
470 | ||
471 | void parse_config_offsets(struct remote_msg_config_offsets_t *msg) | |
472 | { | |
473 | global_remote.offsets.step = msg->step; | |
474 | global_remote.offsets.delay = msg->delay; | |
475 | global_remote.offsets.hue = msg->hue; | |
476 | global_remote.offsets.saturation = msg->saturation; | |
477 | global_remote.offsets.value = msg->value; | |
478 | } | |
479 | ||
480 | void parse_start_program(struct remote_msg_start_program_t *msg) | |
481 | { | |
482 | script_stop(); | |
483 | pwm_stop_fading(); | |
484 | script_start(0, msg->script, &msg->params); | |
485 | } | |
486 | ||
487 | void parse_stop(struct remote_msg_stop_t *msg) | |
488 | { | |
489 | script_stop(); | |
490 | if (msg->fade) | |
491 | pwm_stop_fading(); | |
492 | } | |
493 | ||
494 | void parse_modify_current(struct remote_msg_modify_current_t *msg) | |
495 | { | |
496 | /* return if a color change is in progress */ | |
497 | #if CONFIG_SCRIPT | |
498 | if (!global_script.disable || !pwm_target_reached()) | |
499 | return; | |
500 | #else | |
501 | if (!pwm_target_reached()) | |
502 | return; | |
503 | #endif | |
504 | ||
505 | /* apply rgb and hsv offsets */ | |
506 | pwm_modify_rgb(&msg->rgb, msg->step, msg->delay); | |
507 | pwm_modify_hsv(&msg->hsv, msg->step, msg->delay); | |
508 | } | |
509 | ||
510 | void parse_pull_int(struct remote_msg_pull_int_t *msg) | |
511 | { | |
512 | /* pull pin to ground */ | |
513 | remote_pull_int(); | |
514 | ||
515 | uint8_t delay = msg->delay; | |
516 | ||
517 | /* start timer, delay between 50ms and 2550ms */ | |
518 | if (delay == 0) | |
519 | delay = 5; | |
520 | else if (delay <= 51) | |
521 | delay *= 5; | |
522 | else | |
523 | delay = 255; | |
524 | ||
525 | timer_set(&remote.int_timer, delay); | |
526 | ||
527 | /* remember state */ | |
528 | remote.int_state = INT_PULLED_TIMER; | |
529 | } | |
530 | ||
531 | void parse_config_startup(struct remote_msg_config_startup_t *msg) | |
532 | { | |
533 | /* set mode and copy data */ | |
534 | memcpy(&startup_config.params, &msg->params, sizeof(struct startup_parameters_t)); | |
535 | /* save config to eeprom */ | |
536 | storage_save_config(); | |
537 | } | |
538 | ||
539 | static void wait_for_uart(void) | |
540 | { | |
541 | while (fifo_fill((fifo_t *)&global_uart.tx) != 0 || !uart_send_complete()); | |
542 | } | |
543 | ||
544 | void parse_bootloader(struct remote_msg_bootloader_t *msg) | |
545 | { | |
546 | /* check magic bytes */ | |
547 | if (msg->magic[0] != BOOTLOADER_MAGIC_BYTE1 || | |
548 | msg->magic[1] != BOOTLOADER_MAGIC_BYTE2 || | |
549 | msg->magic[2] != BOOTLOADER_MAGIC_BYTE3 || | |
550 | msg->magic[3] != BOOTLOADER_MAGIC_BYTE4) | |
551 | return; | |
552 | ||
553 | /* wait until the tx fifo is empty, then start watchdog, but never kick it | |
554 | * (bootloader and firmware both disable the watchdog) */ | |
555 | wait_for_uart(); | |
556 | wdt_enable(WDTO_120MS); | |
557 | } | |
558 | ||
559 | void parse_powerdown(void) | |
560 | { | |
561 | /* configure sleep mode */ | |
562 | set_sleep_mode(SLEEP_MODE_PWR_DOWN); | |
563 | ||
564 | /* wait until the tx fifo is empty before sleep */ | |
565 | wait_for_uart(); | |
566 | ||
567 | /* save all output registers */ | |
568 | uint8_t portb = PORTB; | |
569 | uint8_t portc = PORTC; | |
570 | uint8_t portd = PORTD; | |
571 | uint8_t ddrb = DDRB; | |
572 | uint8_t ddrc = DDRC; | |
573 | uint8_t ddrd = DDRD; | |
574 | PORTB = 0; | |
575 | PORTC = 0; | |
576 | PORTD = 0; | |
577 | DDRB = 0; | |
578 | DDRC = 0; | |
579 | DDRD = 0; | |
580 | ||
581 | /* configure int pin as input (already done by setting the | |
582 | * DDR register to zero) with pullup */ | |
583 | R_PORT = _BV(INTPIN); | |
584 | ||
585 | /* enable int0 low level interrupt */ | |
586 | _IFR_INT0 = _BV(INTF0); | |
587 | _ICR_INT0 |= _BV(INT0); | |
588 | /* enter sleep mode */ | |
589 | sleep_mode(); | |
590 | ||
591 | /* wakeup, disable int0 */ | |
592 | _ICR_INT0 &= ~_BV(INT0); | |
593 | ||
594 | /* restore output registers */ | |
595 | PORTB = portb; | |
596 | PORTC = portc; | |
597 | PORTD = portd; | |
598 | DDRB = ddrb; | |
599 | DDRC = ddrc; | |
600 | DDRD = ddrd; | |
601 | } | |
602 | ||
603 | /* do nothing, just for wakeup after sleep */ | |
604 | EMPTY_INTERRUPT(INT0_vect); | |
605 | ||
606 | #endif |