#include "serial.h" #include #include #include #include #include #include "utils.h" #define SERIAL_BUFFER_SIZE 0x40 struct serial_buffer { volatile unsigned char buffer[SERIAL_BUFFER_SIZE]; volatile unsigned head; volatile unsigned tail; }; static volatile struct serial_buffer serial_rx; static volatile struct serial_buffer serial_tx; __attribute__((always_inline)) static inline int serial_buffer_isempty(volatile struct serial_buffer *buffer) { return buffer->head == buffer->tail; } __attribute__((always_inline)) static inline int serial_buffer_isfull(volatile struct serial_buffer *buffer) { return ((buffer->head + 1) % SERIAL_BUFFER_SIZE) == buffer->tail; } __attribute__((always_inline)) static inline void serial_buffer_put(volatile struct serial_buffer *buffer, char c) { volatile unsigned head = buffer->head; buffer->buffer[head++] = c; buffer->head = head % SERIAL_BUFFER_SIZE; } __attribute__((always_inline)) static inline char serial_buffer_get(volatile struct serial_buffer *buffer) { volatile unsigned tail = buffer->tail; char c = buffer->buffer[tail++]; buffer->tail = tail % SERIAL_BUFFER_SIZE; return c; } static void serial_transmit(void) { // Called when UDR0 is ready to receive new data UDR0 = serial_buffer_get(&serial_tx); // If we've sent everything, disable the interrupt if (serial_buffer_isempty(&serial_tx)) cbi(UCSR0B, UDRIE0); } static void serial_receive(void) { // Called when UDR0 contains new data // Discard the byte if a parity error has occurred or the buffer is full if (bit_is_set(UCSR0A, UPE0) || serial_buffer_isfull(&serial_rx)) { UDR0; return; } // There's not much we can do with a full buffer, unfortunately. // Delaying the read will only cause it to be replaced with further data. serial_buffer_put(&serial_rx, UDR0); } ISR(USART_UDRE_vect) { serial_transmit(); } ISR(USART_RX_vect) { serial_receive(); } __attribute__((always_inline)) inline int serial_putchar_inline(const char c) { // If the data register and buffer are empty, just send it straight away if (bit_is_set(UCSR0A, UDRE0) && serial_buffer_isempty(&serial_tx)) { UDR0 = c; return 1; } // Make sure there's space in the buffer and put the character while (serial_buffer_isfull(&serial_tx)); serial_buffer_put(&serial_tx, c); // Enable the interrupt to transmit as soon as we can sbi(UCSR0B, UDRIE0); return 1; } int serial_putchar(const char c) { return serial_putchar_inline(c); } __attribute__((always_inline)) inline int serial_getchar_inline(void) { // Wait until there's something in the buffer while (serial_buffer_isempty(&serial_rx)); // Read the next character return serial_buffer_get(&serial_rx); } int serial_getchar(void) { return serial_getchar_inline(); } static int stdio_serial_putchar(const char c, __attribute__((unused)) FILE *stream) { return serial_putchar(c); } static int stdio_serial_getchar(__attribute__((unused)) FILE *stream) { return serial_getchar(); } static FILE serial = FDEV_SETUP_STREAM(stdio_serial_putchar, stdio_serial_getchar, _FDEV_SETUP_RW); unsigned serial_available(void) { return ((unsigned)(SERIAL_BUFFER_SIZE + serial_rx.head - serial_rx.tail)) % SERIAL_BUFFER_SIZE; } void serial_drain(void) { while (bit_is_set(UCSR0B, UDRIE0) || bit_is_set(UCSR0A, RXC0)); } void serial_init_config(const unsigned long bauds, const uint8_t config) { // Calculate UBRRn value as per the datasheet uint16_t ubrr = F_CPU / 4 / 2 / bauds - 1; UBRR0H = (ubrr >> 8) & 0xFF; UBRR0L = ubrr & 0xFF; // Configure the serial UCSR0A = _BV(U2X0); // Double speed UCSR0B = _BV(RXEN0) | _BV(TXEN0) | _BV(RXCIE0); // Enable it and the receive interrupt UCSR0C = config; // Setup stdio streams stdout = stderr = stdin = &serial; } void serial_init(const unsigned long bauds) { serial_init_config(bauds, SERIAL_8N1); }