Created
August 14, 2018 22:16
-
-
Save kierdavis/d114cd6c2fde2f7e44df33c37da8c176 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Buffers for incoming and outgoing bytes respectively. | |
static RingBuffer recv_buffer; | |
static RingBuffer send_buffer; | |
// True when we've sent an XOFF and the peer has (hopefully) stopped sending data. | |
static bool recv_paused = false; | |
// True when the peer has sent an XOFF and we should stop sending data. | |
static bool send_paused = false; | |
// Adds a byte to the end of the receive queue, sending an XOFF if it's starting to get full. | |
// Returns true if there was space in the buffer or false if there was not. | |
static bool recv_buffer_push(char c) { | |
if (!recv_buffer.push_back(c)) { | |
return false; | |
} | |
if (recv_buffer.nearly_full()) { | |
send_buffer.push_front(XOFF); | |
attempt_transmit(); | |
recv_paused = true; | |
} | |
return true; | |
} | |
// Removes the oldest byte in the receive queue and stores it into dest, sending an XON if the buffer is nearly empty and we've previously sent an XOFF. | |
// Returns true if there was a byte in the queue to retrieve or false if there was not. | |
static bool recv_buffer_pop(char *dest) { | |
if (!recv_buffer.pop_front(dest)) { | |
return false; | |
} | |
if (recv_paused && recv_buffer.nearly_empty()) { | |
send_buffer.push_front(XON); | |
attempt_transmit(); | |
recv_paused = false; | |
} | |
return true; | |
} | |
// Interrupt handler called whenever the hardware USART has finished receiving a byte. | |
ISR(USART_RX_vect) { | |
char c = UDR0; | |
// Handle special control codes. | |
if (c == XOFF) { | |
send_paused = true; | |
return; | |
} else if (c == XON) { | |
send_paused = false; | |
attempt_transmit(); | |
return; | |
} | |
// Not a control code, add it to the buffer. | |
if (!recv_buffer_push(c)) { | |
// Buffer is full :( | |
panic(); | |
} | |
} | |
// Attempt to start transmitting a byte, if all of the following conditions are true: | |
// 1) the hardware is not currently transmitting another byte | |
// 2) the peer has not paused our transmitting by sending an XOFF | |
// 3) there is data in the send_buffer | |
// This function should be called whenever any of these conditions may transition from false to true; that is, when: | |
// 1) the UDRE (USART data register empty) interrupt is received | |
// 2) an XON is received from the peer | |
// 3) data is added to the send_buffer, either by serial_write or when we enqueue XOFFs/XONs for transmission | |
static void attempt_transmit() { | |
if (!(UCSR0A & _BV(UDRE0))) { | |
return; // Hardware is still transmitting the previous byte. | |
} | |
if (send_paused) { | |
return; // Peer has sent an XOFF, obey it. | |
} | |
char c; | |
if (!send_buffer.pop_front(&c)) { | |
return; // Nothing to send. | |
} | |
// Write byte into hardware register, which automatically triggers transmission. | |
UDR0 = c; | |
} | |
// Interrupt handler called whenever the hardware USART is ready to accept another byte to transmit. | |
ISR(USART_UDRE_vect) { | |
attempt_transmit(); | |
} | |
////////////////////////////// | |
// Public interface follows // | |
////////////////////////////// | |
// Returns the number of bytes that are ready to be returned by serial_read. | |
int serial_available() { | |
return (int) recv_buffer.len(); | |
} | |
// Reads one byte from the serial port. | |
// Returns -1 if no data was available. | |
int serial_read() { | |
char c; | |
if (recv_buffer_pop(&c)) { | |
return c; | |
} else { | |
return -1; | |
} | |
} | |
// Reads one byte from the serial port. | |
// Blocks until data is available. | |
int serial_read_blocking() { | |
while (recv_buffer.empty()) { | |
// TODO: enter a low-power state | |
} | |
return serial_read(); | |
} | |
// Writes one byte to the serial port. | |
// Returns false if no space is available in the buffer. | |
bool serial_write(char c) { | |
if (!send_buffer.push_back(c)) { | |
return false; | |
} | |
attempt_transmit(); | |
return true; | |
} | |
// Writes one byte to the serial port. | |
// Blocks until space is available in the buffer. | |
void serial_write_blocking(char c) { | |
while (send_buffer.full()) { | |
// TODO: enter a low-power state | |
} | |
serial_write(c); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment