/* * Transmit an FM-modulated signal using the USART in SPI master mode. * As per ABC standard, this signal is transmitted MSB first. */ #include "usbcas.h" #define BIT_PATTERN_LEN_LG2 2 #define BITS_PER_PATTERN_BYTE (8 >> BIT_PATTERN_LEN_LG2) #define PATTERN_BIT_MASK ((1 << BITS_PER_PATTERN_BYTE) - 1) #include "fmpat.h" static void fmtx_set_speed(uint8_t divisor); /* Post-block silent pause, in pattern times */ static unsigned int block_pause(uint8_t blktype, uint8_t divisor) { if (blktype == 0) /* data block */ return 362 << BIT_PATTERN_LEN_LG2; else /* header block */ return (3*362) << BIT_PATTERN_LEN_LG2; } /* Triple buffer: one being transmitted, one being received, and one ready. */ static struct cas_block block[3]; #define TX_BUSY 0 #define TX_IDLE (1 << TXC1) /* Disable the TX ISR */ static inline ATTR_ALWAYS_INLINE void fmtx_disable(void) { UCSR1B &= ~(1 << UDRIE1); } static uint8_t parity; /* * Send stuff if we have anything to send */ ISR(USART1_UDRE_vect) { static uint8_t tx_data, tx_bits; static struct cas_block *blk = &block[0]; uint8_t pattern; while (!tx_bits) { if (!blk->ready || !motor_relay()) { /* Hold the line for one pattern time, then idle */ UCSR1A = TX_IDLE; UDR1 = parity; fmtx_disable(); /* Nothing left to do */ return; } else if (blk->divisor) { fmtx_set_speed(blk->divisor); blk->divisor = 0; return; } else if (blk->offset < sizeof blk->data) { tx_data = ((const uint8_t *)&blk->data)[blk->offset++]; tx_bits = 8; } else if (blk->pause) { /* Interblock silence; hold the line */ UCSR1A = TX_IDLE; UDR1 = parity; blk->pause--; return; } else { blk->ready = false; /* Free buffer */ blk = blk->next; } } /* Actual data bits available */ pattern = fm_pat[tx_data & PATTERN_BIT_MASK] ^ parity; tx_data >>= BITS_PER_PATTERN_BYTE; parity = -((int8_t)pattern < 0); tx_bits -= BITS_PER_PATTERN_BYTE; UCSR1A = TX_IDLE; UDR1 = pattern; } /* * Synchronize transmitter before reconfiguration */ void fmtx_drain(void) { /* Wait until input queue is idle and we have had at least one output */ while ((UCSR1B & (1 << UDRIE1)) || !(UCSR1A & TX_IDLE)) do_usb_tasks(); } static inline uint16_t divisor_to_ubrr(uint8_t divisor) { uint16_t ubrr; if (!divisor) divisor = CAS_DIVISOR_ABC80; #if F_CPU != 16000000 #error "Assuming F_CPU is 16 MHz" #endif ubrr = ((uint16_t)divisor << (8-BIT_PATTERN_LEN_LG2))/3 - 1; if (ubrr > 4095) ubrr = 4095; return ubrr; } static void fmtx_set_speed(uint8_t divisor) { if (!divisor) divisor = CAS_DIVISOR_ABC80; parity = 0xff; /* We start out with a high idle */ DDRD |= 0x28; /* PD3 = outdata, PD5 = XCK/TXLED */ PORTD |= 0x28; /* Drive data and clock high while idle */ UCSR1B = 0; /* Disable transmitter and ISR */ /* * SPI master mode, mode 1, LSB first * UCPHA doesn't matter, but have UCPOL=1 to that the clock * is 1 when idle and the TXLED is OFF. */ UCSR1C = (3 << UMSEL10) | (1 << UDORD1) | (1 << UCPOL1); /* UBRR must be zero when the transmitter is enabled */ UBRR1 = 0; /* Enable transmitter, but not receiver and not the ISR (yet) */ UCSR1B = (1 << TXEN1); /* Configure baud rate */ UBRR1 = divisor_to_ubrr(divisor); /* * Enable transmitter ISR (which probably will immediately * go to idle...) */ fmtx_enable(); } static enum rx_state { rx_amp, /* Waiting for & */ rx_cmd, /* & received */ rx_filename, /* Getting filename */ rx_num, /* Getting numeric arguments */ rx_data, /* Receiving data */ rx_idle = rx_num /* Root state */ } rx_state; static unsigned int rx_blk_cnt; static struct cas_block *rx_blk; void fmtx_init(void) { int i; /* Fill in the invariant part of the block buffers */ memset(block, 0, sizeof block); block[0].next = &block[1]; block[1].next = &block[2]; block[2].next = &block[0]; rx_blk = &block[0]; rx_state = rx_idle; for (i = 0; i <= 2; i++) { memset(block[i].data.sync, 0x16, sizeof block[i].data.sync); block[i].data.stx = 0x02; block[i].data.etx = 0x03; } fmtx_set_speed(CAS_DIVISOR_ABC80); } bool fmtx_full(void) { return rx_blk->ready; } static void fmtx_wait_for_free_buffer(void) { while (rx_blk->ready) do_usb_tasks(); } static enum rx_state setup_block(unsigned int blkno) { uint8_t blktype; rx_blk->data.blkno = blkno; blktype = -(blkno == -1U); rx_blk->data.blktype = blktype; rx_blk->pause = block_pause(blktype, rx_blk->divisor); /* Initialize checksum */ rx_blk->data.csum = 0x03 /* ETX is part of the checksum */ + blktype + (uint8_t)blkno + (uint8_t)(blkno >> 8); /* Start at beginning of data */ rx_blk->offset = 0; return rx_data; } static uint16_t checksum_range(const uint8_t *data, unsigned int len) { uint16_t csum = 0; while (len--) csum += *data++; return csum; } #define MAX_ARGS 4 static enum rx_state do_cmd(uint8_t cmd, const uint16_t *arg) { switch (cmd) { case 'U': /* Firmware update, kick us to boot loader */ /* &U */ go_to_bootloader(); return rx_idle; case 'R': /* Set receive baudrate divisor */ /* &R divisor */ fmtx_wait_for_free_buffer(); rx_blk->divisor = arg[0]; return rx_idle; case 'H': /* Send header block */ /* &H */ rx_blk_cnt = 1; return setup_block(-1); case 'D': /* Send data block */ /* &D blocknr */ rx_blk_cnt = 1; return setup_block(arg[0]); case 'F': { /* Send file */ /* &F filename,blocks[,divisor] */ uint8_t divisor = arg[1]; if (divisor == CAS_DIVISOR_ABC80) divisor = 0; setup_block(-1); rx_blk_cnt = arg[0]; rx_blk->divisor = CAS_DIVISOR_ABC80; /* Header always slow */ rx_blk->data.hdr.divisor = divisor; memset(rx_blk->data.hdr.zero, 0, sizeof rx_blk->data.hdr.zero); rx_blk->data.hdr.nblocks = rx_blk_cnt; /* Checksum filename and divisor */ rx_blk->data.csum += checksum_range(rx_blk->data.data, 12); rx_blk->data.csum += (uint8_t)rx_blk_cnt + (uint8_t)(rx_blk_cnt >> 8); rx_blk->ready = true; rx_blk = rx_blk->next; if (!rx_blk_cnt) return rx_idle; if (divisor) { fmtx_wait_for_free_buffer(); rx_blk->divisor = divisor; } return rx_data; } case 'P': /* Pause (produce silence) */ /* &P byte_times */ if (arg[0]) { fmtx_wait_for_free_buffer(); rx_blk->pause = arg[0] << BIT_PATTERN_LEN_LG2; rx_blk->offset = sizeof rx_blk->data; /* No data */ rx_blk->ready = true; } return rx_idle; default: return rx_idle; /* Unknown/unimplemented command */ } } void fmtx_recv_byte(uint8_t byte) { static uint8_t cmd; /* Command byte */ static uint8_t ctr; /* State-specific counter */ static uint16_t arg[4]; switch (rx_state) { case rx_amp: if (byte == '&') rx_state = rx_cmd; break; case rx_cmd: ctr = 0; memset(arg, 0, sizeof arg); cmd = byte; if (byte == 'F') { memset(rx_blk->data.hdr.filename, ' ', sizeof rx_blk->data.hdr.filename); rx_state = rx_filename; } else if (byte > ' ' && byte <= '~') rx_state = rx_num; else rx_state = rx_idle; break; case rx_filename: switch (byte) { case '\n': ctr = 0; rx_state = do_cmd(cmd, arg); break; case '.': memset(rx_blk->data.hdr.filename+8, ' ', 3); ctr = 8; break; case ':': ctr = 0; break; case ',': rx_state = rx_num; break; default: if (byte <= ' ' || byte > '~' || ctr >= 11) break; if (byte & 0x40) byte &= ~0x20; /* Upper case */ rx_blk->data.hdr.filename[ctr++] = byte; break; } break; case rx_num: if (byte == '\n') { ctr = 0; rx_state = do_cmd(cmd, arg); break; } else if (byte == ',') { ctr++; } else if (ctr < MAX_ARGS) { byte -= '0'; if (byte < 10) arg[ctr] = arg[ctr]*10 + byte; } break; case rx_data: rx_blk->data.data[ctr++] = byte; rx_blk->data.csum += byte; if (ctr >= 253) { uint16_t nextblk = rx_blk->data.blkno + 1; rx_blk->ready = true; rx_blk = rx_blk->next; fmtx_enable(); if (--rx_blk_cnt) { fmtx_wait_for_free_buffer(); setup_block(nextblk); } else { rx_state = rx_idle; } } break; } }