/* * Receive an FM-modulated signal by using a timer with input capture */ #include "usbcas.h" static inline void fmrx_led_on(void) { PORTB &= ~(1 << 0); } static inline void fmrx_led_off(void) { PORTB |= (1 << 0); } void fmrx_init(void) { uint8_t tccr1b; /* ICP1/PD4 is input, with pullup */ DDRD &= ~(1 << 4); PORTD |= (1 << 4); /* PB0 is RXLED */ DDRB |= (1 << 0); fmrx_led_off(); /* Stop counter */ TCCR1B = 0; /* Normal mode, no comparators */ TCCR1A = 0; /* Enable interrupts on input capture */ TIFR1 = TIMSK1 = 1 << ICIE1; /* Clear timer counter */ TCNT1 = 0; /* * Input capture noise canceler on, normal mode; not counting yet */ tccr1b = (1 << ICNC1) + (0 << CS10); if (!(PIND & (1 << 4))) tccr1b |= 1 << ICES1; TCCR1B = tccr1b; /* * Default speed */ fmrx_set_speed(CAS_DIVISOR_ABC80); } /* Timing constants: start and end of data bit interval, and timeout */ static uint16_t one_bit_lo, one_bit_hi, one_bit_timeout; static uint16_t bytes; static uint16_t last_edge; static uint16_t data; /* 16 bit for sync pattern detect */ static uint8_t bits; static uint16_t bytes; static bool started; /* Bit receive timeout on/off */ static inline void enable_timeout(uint16_t when) { OCR1A = when; TIMSK1 = TIFR1 = (1 << ICIE1) | (1 << OCIE1A); } static inline void disable_timeout(void) { TIFR1 = TIMSK1 = (1 << ICIE1); } void fmrx_set_speed(uint8_t divisor) { uint8_t prescale = 1; /* No prescaling */ uint32_t baudrate; if (!divisor) divisor = CAS_DIVISOR_ABC80; baudrate = (CAS_BASE_CLOCK+(divisor >> 1)-1)/divisor; /* Enable prescaler for low baud rates to reduce wraparound */ if (baudrate < 976) { baudrate <<= 3; /* ... for calculation purposes ... */ prescale++; /* Prescale by 8 */ } TCCR1B &= ~7; /* Stop timer */ one_bit_lo = F_CPU / (4 * baudrate); one_bit_hi = 3 * one_bit_lo; one_bit_timeout = 16 * one_bit_lo; /* 4 total bit times... */ disable_timeout(); started = false; bits = bytes = 0; last_edge = TCNT1; TCCR1B |= prescale; /* Start timer */ } /* * When the motor is switched off, reset the download speed, so * we can load the next header block correctly */ void fmrx_motor_off(void) { fmrx_set_speed(CAS_DIVISOR_ABC80); } /* We "hunt" for a block until we find this bit pattern; same as ABC800 */ #define CAS_SYNC_PATTERN 0x0216 /* SYNC + STX */ #define CAS_BLOCK_LEN (1+2+253+1+2) /* type+num+data+ETX+checksum */ /* Finish a bit due to clock edge or timeout */ static inline ATTR_ALWAYS_INLINE void finish_bit(void) { static uint16_t rx_csum; static uint8_t header; static uint8_t header_divisor; if (!started) return; if (bytes) { if (--bits == 0) { uint8_t outbyte = data >> 8; data = 0; /* To avoid false sync */ bits = 8; switch (--bytes) { case 0: fmrx_led_off(); if (header) fmrx_set_speed(header_divisor); return; case 1: if (rx_csum != data) set_modem_status(CDC_CONTROL_LINE_IN_PARITYERROR); return; case 2: /* First checksum byte */ return; case 3: if (outbyte != 0x03) set_modem_status(CDC_CONTROL_LINE_IN_FRAMEERROR); return; case CAS_BLOCK_LEN-1: header = outbyte; break; case CAS_BLOCK_LEN-3-11-1: /* Byte after filename */ header_divisor = outbyte; break; default: break; } rx_csum += outbyte; fmrx_recv_byte(outbyte); } } else if (data == CAS_SYNC_PATTERN) { bits = 8; bytes = CAS_BLOCK_LEN; rx_csum = 0x03; /* Final ETX */ fmrx_led_on(); } } /* Interrupt routine for edge capture */ ISR(TIMER1_CAPT_vect) { uint16_t edge, delta; TCCR1B ^= (1 << ICES1); /* Next edge -> opposite polarity */ edge = ICR1; delta = edge - last_edge; if (delta < one_bit_lo) { /* Early edge, no action */ return; } else if (delta < one_bit_hi) { /* A data bit edge */ data |= (1 << 15); } else { /* Clock edge */ last_edge = edge; enable_timeout(edge + one_bit_timeout); finish_bit(); started = true; data >>= 1; } } /* Interrupt routine for bit timeout */ ISR(TIMER1_COMPA_vect) { disable_timeout(); finish_bit(); started = false; }