diff --git a/config.h b/config.h index 935a483..591b620 100644 --- a/config.h +++ b/config.h @@ -114,6 +114,14 @@ // time step. Also, keep in mind that the Arduino delay timer is not very accurate for long delays. #define DWELL_TIME_STEP 50 // Integer (1-255) (milliseconds) +// FOR ADVANCED USERS ONLY: Toggles XON/XOFF software flow control for serial communications. +// Officially not supported due to problems involving USB-to-serial chip latency (Atmega8U2/FTDI) +// when connecting to an Arduino through the USB port. This problem has to do with having no control +// of the USB packets and causing standard terminal programs not being able to honor the XON/XOFF +// control characters on time. However, with specially programmed UI's or avoiding the USB interface +// completely, XON/XOFF flow control should work. In any case, please report any successes to grbl +// administrators! +#define ENABLE_XONXOFF 0 // Boolean. Default disabled. // ----------------------------------------------- diff --git a/nuts_bolts.h b/nuts_bolts.h index 35fc7c4..5909876 100644 --- a/nuts_bolts.h +++ b/nuts_bolts.h @@ -21,10 +21,11 @@ #ifndef nuts_bolts_h #define nuts_bolts_h -#include + #include #include #include +#include "config.h" #define false 0 #define true 1 diff --git a/serial.c b/serial.c index 8d385bf..4202c3a 100644 --- a/serial.c +++ b/serial.c @@ -42,6 +42,27 @@ uint8_t tx_buffer[TX_BUFFER_SIZE]; uint8_t tx_buffer_head = 0; volatile uint8_t tx_buffer_tail = 0; +#if ENABLE_XONXOFF + #define RX_BUFFER_FULL 96 // XOFF high watermark + #define RX_BUFFER_LOW 64 // XON low watermark + #define SEND_XOFF 1 + #define SEND_XON 2 + #define XOFF_SENT 3 + #define XON_SENT 4 + #define XOFF_CHAR 0x13 + #define XON_CHAR 0x11 + volatile uint8_t flow_ctrl = XON_SENT; // Flow control state variable + + // Returns the number of bytes in the RX buffer. This replaces a typical byte counter to prevent + // the interrupt and main programs from writing to the counter at the same time. + static uint8_t get_rx_buffer_count() + { + if (rx_buffer_head == rx_buffer_tail) { return(0); } + if (rx_buffer_head < rx_buffer_tail) { return(rx_buffer_tail-rx_buffer_head); } + return (RX_BUFFER_SIZE - (rx_buffer_head-rx_buffer_tail)); + } +#endif + static void set_baud_rate(long baud) { uint16_t UBRR0_value = ((F_CPU / 16 + baud / 2) / baud - 1); UBRR0H = UBRR0_value >> 8; @@ -84,21 +105,33 @@ void serial_write(uint8_t data) { } // Data Register Empty Interrupt handler -ISR(USART_UDRE_vect) { +ISR(USART_UDRE_vect) +{ // Temporary tx_buffer_tail (to optimize for volatile) uint8_t tail = tx_buffer_tail; - + + #if ENABLE_XONXOFF + switch (flow_ctrl) { + case SEND_XOFF: UDR0 = XOFF_CHAR; flow_ctrl = XOFF_SENT; break; + case SEND_XON: UDR0 = XON_CHAR; flow_ctrl = XON_SENT; break; + default: + #endif + // Send a byte from the buffer UDR0 = tx_buffer[tail]; // Update tail position - tail ++; + tail++; if (tail == TX_BUFFER_SIZE) { tail = 0; } + tx_buffer_tail = tail; + + #if ENABLE_XONXOFF + } + #endif + // Turn off Data Register Empty Interrupt to stop tx-streaming if this concludes the transfer if (tail == tx_buffer_head) { UCSR0B &= ~(1 << UDRIE0); } - - tx_buffer_tail = tail; } uint8_t serial_read() @@ -109,6 +142,14 @@ uint8_t serial_read() uint8_t data = rx_buffer[rx_buffer_tail]; rx_buffer_tail++; if (rx_buffer_tail == RX_BUFFER_SIZE) { rx_buffer_tail = 0; } + + #if ENABLE_XONXOFF + if ((get_rx_buffer_count() < RX_BUFFER_LOW) && flow_ctrl != XON_SENT) { + flow_ctrl = SEND_XON; + UCSR0B |= (1 << UDRIE0); // Force TX + } + #endif + return data; } } @@ -116,33 +157,47 @@ uint8_t serial_read() ISR(USART_RX_vect) { uint8_t data = UDR0; - uint8_t next_head = rx_buffer_head + 1; - if (next_head == RX_BUFFER_SIZE) { next_head = 0; } - - // Write data to buffer unless it is full. - if (next_head != rx_buffer_tail) { - // Pick off runtime command characters directly from the serial stream. These characters are - // not passed into the buffer, but these set system state flag bits for runtime execution. - switch (data) { - case CMD_STATUS_REPORT: sys.execute |= EXEC_STATUS_REPORT; break; // Set as true - case CMD_CYCLE_START: sys.execute |= EXEC_CYCLE_START; break; // Set as true - case CMD_FEED_HOLD: sys.execute |= EXEC_FEED_HOLD; break; // Set as true - case CMD_RESET: - // Immediately force stepper and spindle subsystem idle at an interrupt level. - if (!(sys.execute & EXEC_RESET)) { // Force stop only first time. - st_go_idle(); - spindle_stop(); - } - sys.execute |= EXEC_RESET; // Set as true - break; - default : // Write character to buffer + uint8_t next_head; + + // Pick off runtime command characters directly from the serial stream. These characters are + // not passed into the buffer, but these set system state flag bits for runtime execution. + switch (data) { + case CMD_STATUS_REPORT: sys.execute |= EXEC_STATUS_REPORT; break; // Set as true + case CMD_CYCLE_START: sys.execute |= EXEC_CYCLE_START; break; // Set as true + case CMD_FEED_HOLD: sys.execute |= EXEC_FEED_HOLD; break; // Set as true + case CMD_RESET: + // Immediately force stepper and spindle subsystem idle at an interrupt level. + if (!(sys.execute & EXEC_RESET)) { // Force stop only first time. + st_go_idle(); + spindle_stop(); + } + sys.execute |= EXEC_RESET; // Set as true + break; + default: // Write character to buffer + next_head = rx_buffer_head + 1; + if (next_head == RX_BUFFER_SIZE) { next_head = 0; } + + // Write data to buffer unless it is full. + if (next_head != rx_buffer_tail) { rx_buffer[rx_buffer_head] = data; - rx_buffer_head = next_head; - } + rx_buffer_head = next_head; + + #if ENABLE_XONXOFF + if ((get_rx_buffer_count() >= RX_BUFFER_FULL) && flow_ctrl != XOFF_SENT) { + flow_ctrl = SEND_XOFF; + UCSR0B |= (1 << UDRIE0); // Force TX + } + #endif + + } } } void serial_reset_read_buffer() { rx_buffer_tail = rx_buffer_head; + + #if ENABLE_XONXOFF + flow_ctrl = XON_SENT; + #endif }