/* * PC remote control * * Copyright (c) 2013 Michael Buesch * Licensed under the terms of the GNU General Public License version 2. */ #include "util.h" #include "comm.h" #include #include #include /* IR receiver connection */ #define RECEIVER_PORT PORTC #define RECEIVER_DDR DDRC #define RECEIVER_PIN PINC #define RECEIVER_BIT 0 /* Notification LED */ #define NOTIFY_PORT PORTC #define NOTIFY_DDR DDRC #define NOTIFY_BIT 1 enum ir_encoding { ENC_RC5, ENC_NEC, NR_ENCODINGS, }; enum msg_id { MSG_IR, MSG_CONFIG, }; enum payload_ir_flags { IR_FLG_FIELDBIT = 1 << 0, /* RC-5 only */ IR_FLG_TOGGLEBIT = 1 << 1, /* RC-5 only */ IR_FLG_REPEAT = 1 << 2, /* NEC only */ }; struct msg_payload { uint8_t id; union { struct { uint8_t encoding; uint8_t flags; uint8_t address[2]; uint8_t command[2]; } _packed ir; struct config_data { uint8_t encoding; uint8_t notify_en; } _packed config; } _packed; } _packed; enum detect_status { STAT_IDLE = 0, /* No signal detected. */ STAT_PREAMBLE, /* In start-bit/preamble. */ STAT_PREAMBLE_GAP, /* In preamble-gap. (NEC only) */ STAT_DATAWAIT, /* In preample-gap, waiting for data start. (NEC only) */ STAT_FIELDBIT, /* In field-bit. (RC5 only) */ STAT_TOGGLEBIT, /* In toggle-bit. (RC5 only) */ STAT_ADDR, /* In address field. */ STAT_CMD, /* In command field. */ STAT_END, /* Wait for end. */ STAT_RECEIVED, /* Receive Ok. */ }; struct detect_config { enum ir_encoding encoding; bool notify_enabled; }; struct detect_context { enum detect_status stat; uint8_t nibble_nr; uint8_t count; bool prev_nibble_value; uint8_t fieldbit; uint8_t togglebit; uint8_t is_repeat; uint8_t address[2]; uint8_t command[2]; }; static struct detect_config config; static struct detect_context detect; static uint8_t timer_tick; static bool get_rx_signal(void) { return !(RECEIVER_PIN & (1 << RECEIVER_BIT)); } static void receiver_init(void) { RECEIVER_PORT &= ~(1 << RECEIVER_BIT); RECEIVER_DDR &= ~(1 << RECEIVER_BIT); } static void context_reset(void) { memset(&detect, 0, sizeof(detect)); } static void sigtimer_start(void) { uint16_t ocr; switch (config.encoding) { default: case ENC_RC5: /* Initialize timer to 1124 Hz (889us period) */ ocr = 1779; break; case ENC_NEC: /* Initialize timer to 1777.78 Hz (562.5us period) */ ocr = 1125; break; } /* Start timer */ TCCR1A = 0; TCCR1B = (1 << WGM12) | (1 << CS11); /* Prescaler 8 */ OCR1A = ocr; TCNT1 = ocr / 2; /* Move start half a cycle ahead. */ TIFR |= (1 << OCF1A); TIMSK |= (1 << OCIE1A); } static void sigtimer_stop(void) { TIMSK &= ~(1 << OCIE1A); TIFR |= (1 << OCF1A); } static int8_t decode_bits_rc5(uint8_t *buf, uint8_t nr_bits, bool signal) { if (detect.nibble_nr == 0) { detect.prev_nibble_value = signal; detect.nibble_nr = 1; } else { if (detect.prev_nibble_value == signal) return -1; if (signal) buf[detect.count / 8] |= (1 << (detect.count % 8)); detect.count++; detect.nibble_nr = 0; if (detect.count == nr_bits) { detect.count = 0; return 1; } } return 0; } static int8_t detect_timer_rc5(bool signal) { int8_t res; switch (detect.stat) { default: /* Should not happen */ return -1; case STAT_PREAMBLE: if (!signal) return -1; detect.stat = STAT_FIELDBIT; break; case STAT_FIELDBIT: res = decode_bits_rc5(&detect.fieldbit, 1, signal); if (res < 0) return -1; if (res > 0) detect.stat = STAT_TOGGLEBIT; break; case STAT_TOGGLEBIT: res = decode_bits_rc5(&detect.togglebit, 1, signal); if (res < 0) return -1; if (res > 0) detect.stat = STAT_ADDR; break; case STAT_ADDR: res = decode_bits_rc5(detect.address, 5, signal); if (res < 0) return -1; if (res > 0) detect.stat = STAT_CMD; break; case STAT_CMD: res = decode_bits_rc5(detect.command, 6, signal); if (res < 0) return -1; if (res > 0) detect.stat = STAT_END; break; case STAT_END: if (signal) return -1; detect.stat = STAT_RECEIVED; sigtimer_stop(); break; } return 0; } static int8_t decode_bits_nec(uint8_t *buf, uint8_t nr_bits, bool signal) { switch (detect.nibble_nr) { case 0: if (!signal) return -1; detect.nibble_nr = 1; break; case 1: if (signal) return -1; detect.nibble_nr = 2; break; case 2: if (signal) { /* This is a logical 0. * We are already in the first nibble of the next symbol. */ detect.nibble_nr = 1; detect.count++; } else { /* This is a logical 1. */ buf[detect.count / 8] |= (1 << (detect.count % 8)); detect.nibble_nr = 3; } break; case 3: if (signal) return -1; detect.nibble_nr = 0; detect.count++; break; default: return -1; /* Should not happen. */ } if (detect.count >= nr_bits) { /* All bits received. */ detect.count = 0; return 1; } return 0; } static int8_t detect_timer_nec(bool signal) { int8_t res; switch (detect.stat) { default: /* Should not happen */ return -1; case STAT_PREAMBLE: if (!signal) return -1; detect.count++; if (detect.count >= 16) { detect.count = 0; detect.stat = STAT_PREAMBLE_GAP; } break; case STAT_PREAMBLE_GAP: if (detect.count == 4) { if (signal) { detect.is_repeat = 1; detect.count = 0; detect.stat = STAT_END; break; } else { detect.count = 0; detect.stat = STAT_DATAWAIT; sigtimer_stop(); break; } } else { if (signal) return -1; } detect.count++; break; case STAT_ADDR: res = decode_bits_nec(detect.address, 16, signal); if (res < 0) return -1; if (res > 0) detect.stat = STAT_CMD; break; case STAT_CMD: res = decode_bits_nec(detect.command, 16, signal); if (res < 0) return -1; if (res > 0) detect.stat = STAT_END; break; case STAT_END: if (detect.count >= 2) { if (signal) return -1; detect.count = 0; detect.stat = STAT_RECEIVED; sigtimer_stop(); } detect.count++; break; } return 0; } /* Signal detection timer */ ISR(TIMER1_COMPA_vect) { bool signal; int8_t err = -1; mb(); signal = get_rx_signal(); switch (config.encoding) { case ENC_RC5: err = detect_timer_rc5(signal); break; case ENC_NEC: err = detect_timer_nec(signal); break; default: break; } if (err) { context_reset(); sigtimer_stop(); } mb(); } static void ticktimer_init(void) { /* Initialize timer2 to 100Hz */ OCR2 = 156; TCNT2 = 0; TCCR2 = (1 << WGM21) | (1 << CS20) | (1 << CS21) | (1 << CS22); TIMSK |= (1 << OCIE2); } ISR(TIMER2_COMP_vect) { mb(); timer_tick++; mb(); } bool comm_handle_rx_message(const struct comm_message *msg, void *reply_payload) { const struct msg_payload *pl = comm_payload(msg); uint8_t sreg; if (msg->fc & COMM_FC_ACK) return 1; switch (pl->id) { case MSG_CONFIG: { if (pl->config.encoding >= NR_ENCODINGS) return 0; sreg = irq_disable_save(); if (config.encoding != pl->config.encoding) { config.encoding = pl->config.encoding; context_reset(); } config.notify_enabled = pl->config.notify_en; irq_restore(sreg); break; } } return 1; } static bool handle_detector(void) { COMM_MSG(msg); struct msg_payload *msg_pl = comm_payload(&msg); bool have_packet = 0; bool signal; irq_disable(); switch (detect.stat) { default: break; case STAT_IDLE: case STAT_DATAWAIT: /* Wait for a positive edge. */ signal = get_rx_signal(); if (signal) { if (detect.stat == STAT_IDLE) detect.stat = STAT_PREAMBLE; else detect.stat = STAT_ADDR; sigtimer_start(); } break; case STAT_RECEIVED: /* We received a full packet. */ msg_pl->id = MSG_IR; msg_pl->ir.encoding = config.encoding; msg_pl->ir.flags = 0; if (detect.fieldbit) msg_pl->ir.flags |= IR_FLG_FIELDBIT; if (detect.togglebit) msg_pl->ir.flags |= IR_FLG_TOGGLEBIT; if (detect.is_repeat) msg_pl->ir.flags |= IR_FLG_REPEAT; memcpy(msg_pl->ir.address, detect.address, sizeof(msg_pl->ir.address)); memcpy(msg_pl->ir.command, detect.command, sizeof(msg_pl->ir.command)); have_packet = 1; context_reset(); break; } irq_enable(); if (have_packet) comm_message_send(&msg, 1); return have_packet; } int main(void) { uint8_t tick = 0, notify_count = 0; bool received; NOTIFY_PORT &= ~(1 << NOTIFY_BIT); NOTIFY_DDR |= (1 << NOTIFY_BIT); receiver_init(); ticktimer_init(); comm_init(); context_reset(); config.encoding = ENC_RC5; irq_enable(); while (1) { received = handle_detector(); if (received) { if (config.notify_enabled) { NOTIFY_PORT |= (1 << NOTIFY_BIT); notify_count = 5; } } comm_work(); irq_disable(); tick = timer_tick; timer_tick = 0; irq_enable(); while (tick) { tick--; if (notify_count) { notify_count--; if (!notify_count) NOTIFY_PORT &= ~(1 << NOTIFY_BIT); } comm_centisecond_tick(); } } }