Firmware2/Marlin/src/HAL/HAL_DUE/MarlinSerial_Due.cpp
Eduardo José Tagle ac168a03c8 Fixes for the Arduino DUE HAL (Serial Port, Graphics Display, EEPROM emulation) (#8651)
* Fixing the DUE serial port assignments: Now -1 means the SAM3x USB Device emulating a serial port, and 0 means the USB to serial adapter included as a programming port

* Improving the Fast IO port access implementation on Arduino DUE

* Implemented EEPROM emulation on Due by storing data on the internal FLASH (with wear leveling)

* Implemented a Software SPI for the ST7920 graphics display for the Arduino RAMPS for DUE, as the default one in u8glib is clocking data too fast on ARM, and the display does not understand it.

* Fixing the case where the serial port selected is the USB device

* Adding configuration for the Makerparts 3D printer (www.makerparts.net)

* Tuned MakerParts acceleration on X and Y axis so it never loses steps. Also adjusted pulses per mm to match default hw configuration

* Fine tuned Maximum acceleration for MakerParts printer

* Style cleanup

* Style cleanup (2)

* Style fixes (3)

* Fixing the DUE serial port assignments: Now -1 means the SAM3x USB Device emulating a serial port, and 0 means the USB to serial adapter included as a programming port

* Improving the Fast IO port access implementation on Arduino DUE

* Implemented EEPROM emulation on Due by storing data on the internal FLASH (with wear leveling)

* Implemented a Software SPI for the ST7920 graphics display for the Arduino RAMPS for DUE, as the default one in u8glib is clocking data too fast on ARM, and the display does not understand it.

* Fixing the case where the serial port selected is the USB device

* Adding configuration for the Makerparts 3D printer (www.makerparts.net)

* Tuned MakerParts acceleration on X and Y axis so it never loses steps. Also adjusted pulses per mm to match default hw configuration

* Fine tuned Maximum acceleration for MakerParts printer

* Style cleanup

* Style changes to u8g_dev_st7920_128_64_sw_spi.cpp

* Even more improvements to the FastIO HAL for DUE. Now WRITE() is 2 ASM instructions, if value is constant, and 5 cycles if value is not constant. Previously, it was 7..8 cycles

* After some problems and debugging, seems we need to align the interrupt vector table to 256 bytes, otherwise, the program sometimes stops working

* Moved comments out of macro, otherwise, token pasting does not properly work sometimes

* Improved Software SPI implementation on DUE: Now it honors the selected speed passed to spiInit(). This allows much faster SDCARD access, improving SDCARD menus and reducing latency

* Update u8g_dev_st7920_128_64_sw_spi.cpp

* Disabling EEPROM over FLASH emulatiion if an I2C or SPI EEPROM is present
2017-12-12 17:51:36 -06:00

688 lines
20 KiB
C++

/**
* Marlin 3D Printer Firmware
* Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
*
* Based on Sprinter and grbl.
* Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/**
* MarlinSerial_Due.cpp - Hardware serial library for Arduino DUE
* Copyright (c) 2017 Eduardo José Tagle. All right reserved
* Based on MarlinSerial for AVR, copyright (c) 2006 Nicholas Zambetti. All right reserved.
*/
#ifdef ARDUINO_ARCH_SAM
#include "../../inc/MarlinConfig.h"
#include "MarlinSerial_Due.h"
#include "InterruptVectors_Due.h"
#include "../../Marlin.h"
// If not using the USB port as serial port
#if SERIAL_PORT >= 0
// Based on selected port, use the proper configuration
#if SERIAL_PORT == 0
#define HWUART UART
#define HWUART_IRQ UART_IRQn
#define HWUART_IRQ_ID ID_UART
#elif SERIAL_PORT == 1
#define HWUART ((Uart*)USART0)
#define HWUART_IRQ USART0_IRQn
#define HWUART_IRQ_ID ID_USART0
#elif SERIAL_PORT == 2
#define HWUART ((Uart*)USART1)
#define HWUART_IRQ USART1_IRQn
#define HWUART_IRQ_ID ID_USART1
#elif SERIAL_PORT == 3
#define HWUART ((Uart*)USART2)
#define HWUART_IRQ USART2_IRQn
#define HWUART_IRQ_ID ID_USART2
#elif SERIAL_PORT == 4
#define HWUART ((Uart*)USART3)
#define HWUART_IRQ USART3_IRQn
#define HWUART_IRQ_ID ID_USART3
#endif
struct ring_buffer_r {
unsigned char buffer[RX_BUFFER_SIZE];
volatile ring_buffer_pos_t head, tail;
};
#if TX_BUFFER_SIZE > 0
struct ring_buffer_t {
unsigned char buffer[TX_BUFFER_SIZE];
volatile uint8_t head, tail;
};
#endif
ring_buffer_r rx_buffer = { { 0 }, 0, 0 };
#if TX_BUFFER_SIZE > 0
ring_buffer_t tx_buffer = { { 0 }, 0, 0 };
static bool _written;
#endif
#if ENABLED(SERIAL_XON_XOFF)
constexpr uint8_t XON_XOFF_CHAR_SENT = 0x80; // XON / XOFF Character was sent
constexpr uint8_t XON_XOFF_CHAR_MASK = 0x1F; // XON / XOFF character to send
// XON / XOFF character definitions
constexpr uint8_t XON_CHAR = 17;
constexpr uint8_t XOFF_CHAR = 19;
uint8_t xon_xoff_state = XON_XOFF_CHAR_SENT | XON_CHAR;
// Validate that RX buffer size is at least 4096 bytes- According to several experiments, on
// the original Arduino Due that uses a ATmega16U2 as USB to serial bridge, due to the introduced
// latencies, at least 2959 bytes of RX buffering (when transmitting at 250kbits/s) are required
// to avoid overflows.
#if RX_BUFFER_SIZE < 4096
#error Arduino DUE requires at least 4096 bytes of RX buffer to avoid buffer overflows when using XON/XOFF handshake
#endif
#endif
#if ENABLED(SERIAL_STATS_DROPPED_RX)
uint8_t rx_dropped_bytes = 0;
#endif
#if ENABLED(SERIAL_STATS_MAX_RX_QUEUED)
ring_buffer_pos_t rx_max_enqueued = 0;
#endif
// A SW memory barrier, to ensure GCC does not overoptimize loops
#define sw_barrier() asm volatile("": : :"memory");
#if ENABLED(EMERGENCY_PARSER)
// Currently looking for: M108, M112, M410
// If you alter the parser please don't forget to update the capabilities in Conditionals_post.h
FORCE_INLINE void emergency_parser(const uint8_t c) {
static e_parser_state state = state_RESET;
switch (state) {
case state_RESET:
switch (c) {
case ' ': break;
case 'N': state = state_N; break;
case 'M': state = state_M; break;
default: state = state_IGNORE;
}
break;
case state_N:
switch (c) {
case '0': case '1': case '2':
case '3': case '4': case '5':
case '6': case '7': case '8':
case '9': case '-': case ' ': break;
case 'M': state = state_M; break;
default: state = state_IGNORE;
}
break;
case state_M:
switch (c) {
case ' ': break;
case '1': state = state_M1; break;
case '4': state = state_M4; break;
default: state = state_IGNORE;
}
break;
case state_M1:
switch (c) {
case '0': state = state_M10; break;
case '1': state = state_M11; break;
default: state = state_IGNORE;
}
break;
case state_M10:
state = (c == '8') ? state_M108 : state_IGNORE;
break;
case state_M11:
state = (c == '2') ? state_M112 : state_IGNORE;
break;
case state_M4:
state = (c == '1') ? state_M41 : state_IGNORE;
break;
case state_M41:
state = (c == '0') ? state_M410 : state_IGNORE;
break;
case state_IGNORE:
if (c == '\n') state = state_RESET;
break;
default:
if (c == '\n') {
switch (state) {
case state_M108:
wait_for_user = wait_for_heatup = false;
break;
case state_M112:
kill(PSTR(MSG_KILLED));
break;
case state_M410:
quickstop_stepper();
break;
default:
break;
}
state = state_RESET;
}
}
}
#endif // EMERGENCY_PARSER
FORCE_INLINE void store_rxd_char() {
const ring_buffer_pos_t h = rx_buffer.head,
i = (ring_buffer_pos_t)(h + 1) & (ring_buffer_pos_t)(RX_BUFFER_SIZE - 1);
// Read the character
const uint8_t c = HWUART->UART_RHR;
// If the character is to be stored at the index just before the tail
// (such that the head would advance to the current tail), the buffer is
// critical, so don't write the character or advance the head.
if (i != rx_buffer.tail) {
rx_buffer.buffer[h] = c;
rx_buffer.head = i;
}
#if ENABLED(SERIAL_STATS_DROPPED_RX)
else if (!++rx_dropped_bytes) ++rx_dropped_bytes;
#endif
#if ENABLED(SERIAL_STATS_MAX_RX_QUEUED)
// calculate count of bytes stored into the RX buffer
ring_buffer_pos_t rx_count = (ring_buffer_pos_t)(rx_buffer.head - rx_buffer.tail) & (ring_buffer_pos_t)(RX_BUFFER_SIZE - 1);
// Keep track of the maximum count of enqueued bytes
NOLESS(rx_max_enqueued, rx_count);
#endif
#if ENABLED(SERIAL_XON_XOFF)
// for high speed transfers, we can use XON/XOFF protocol to do
// software handshake and avoid overruns.
if ((xon_xoff_state & XON_XOFF_CHAR_MASK) == XON_CHAR) {
// calculate count of bytes stored into the RX buffer
ring_buffer_pos_t rx_count = (ring_buffer_pos_t)(rx_buffer.head - rx_buffer.tail) & (ring_buffer_pos_t)(RX_BUFFER_SIZE - 1);
// if we are above 12.5% of RX buffer capacity, send XOFF before
// we run out of RX buffer space .. We need 325 bytes @ 250kbits/s to
// let the host react and stop sending bytes. This translates to 13mS
// propagation time.
if (rx_count >= (RX_BUFFER_SIZE) / 8) {
// If TX interrupts are disabled and data register is empty,
// just write the byte to the data register and be done. This
// shortcut helps significantly improve the effective datarate
// at high (>500kbit/s) bitrates, where interrupt overhead
// becomes a slowdown.
if (!(HWUART->UART_IMR & UART_IMR_TXRDY) && (HWUART->UART_SR & UART_SR_TXRDY)) {
// Send an XOFF character
HWUART->UART_THR = XOFF_CHAR;
// And remember it was sent
xon_xoff_state = XOFF_CHAR | XON_XOFF_CHAR_SENT;
}
else {
// TX interrupts disabled, but buffer still not empty ... or
// TX interrupts enabled. Reenable TX ints and schedule XOFF
// character to be sent
#if TX_BUFFER_SIZE > 0
HWUART->UART_IER = UART_IER_TXRDY;
xon_xoff_state = XOFF_CHAR;
#else
// We are not using TX interrupts, we will have to send this manually
while (!(HWUART->UART_SR & UART_SR_TXRDY)) { sw_barrier(); };
HWUART->UART_THR = XOFF_CHAR;
// And remember we already sent it
xon_xoff_state = XOFF_CHAR | XON_XOFF_CHAR_SENT;
#endif
}
}
}
#endif // SERIAL_XON_XOFF
#if ENABLED(EMERGENCY_PARSER)
emergency_parser(c);
#endif
}
#if TX_BUFFER_SIZE > 0
FORCE_INLINE void _tx_thr_empty_irq(void) {
// If interrupts are enabled, there must be more data in the output
// buffer.
#if ENABLED(SERIAL_XON_XOFF)
// Do a priority insertion of an XON/XOFF char, if needed.
const uint8_t state = xon_xoff_state;
if (!(state & XON_XOFF_CHAR_SENT)) {
HWUART->UART_THR = state & XON_XOFF_CHAR_MASK;
xon_xoff_state = state | XON_XOFF_CHAR_SENT;
}
else
#endif
{ // Send the next byte
const uint8_t t = tx_buffer.tail, c = tx_buffer.buffer[t];
tx_buffer.tail = (t + 1) & (TX_BUFFER_SIZE - 1);
HWUART->UART_THR = c;
}
// Disable interrupts if the buffer is empty
if (tx_buffer.head == tx_buffer.tail)
HWUART->UART_IDR = UART_IDR_TXRDY;
}
#endif // TX_BUFFER_SIZE > 0
static void UART_ISR(void) {
uint32_t status = HWUART->UART_SR;
// Did we receive data?
if (status & UART_SR_RXRDY)
store_rxd_char();
#if TX_BUFFER_SIZE > 0
// Do we have something to send, and TX interrupts are enabled (meaning something to send) ?
if ((status & UART_SR_TXRDY) && (HWUART->UART_IMR & UART_IMR_TXRDY))
_tx_thr_empty_irq();
#endif
// Acknowledge errors
if ((status & UART_SR_OVRE) || (status & UART_SR_FRAME)) {
// TODO: error reporting outside ISR
HWUART->UART_CR = UART_CR_RSTSTA;
}
}
// Public Methods
void MarlinSerial::begin(const long baud_setting) {
// Disable UART interrupt in NVIC
NVIC_DisableIRQ( HWUART_IRQ );
// Disable clock
pmc_disable_periph_clk( HWUART_IRQ_ID );
// Configure PMC
pmc_enable_periph_clk( HWUART_IRQ_ID );
// Disable PDC channel
HWUART->UART_PTCR = UART_PTCR_RXTDIS | UART_PTCR_TXTDIS;
// Reset and disable receiver and transmitter
HWUART->UART_CR = UART_CR_RSTRX | UART_CR_RSTTX | UART_CR_RXDIS | UART_CR_TXDIS;
// Configure mode: 8bit, No parity, 1 bit stop
HWUART->UART_MR = UART_MR_CHMODE_NORMAL | US_MR_CHRL_8_BIT | US_MR_NBSTOP_1_BIT | UART_MR_PAR_NO;
// Configure baudrate (asynchronous, no oversampling)
HWUART->UART_BRGR = (SystemCoreClock / (baud_setting << 4));
// Configure interrupts
HWUART->UART_IDR = 0xFFFFFFFF;
HWUART->UART_IER = UART_IER_RXRDY | UART_IER_OVRE | UART_IER_FRAME;
// Install interrupt handler
install_isr(HWUART_IRQ, UART_ISR);
// Enable UART interrupt in NVIC
NVIC_EnableIRQ(HWUART_IRQ);
// Enable receiver and transmitter
HWUART->UART_CR = UART_CR_RXEN | UART_CR_TXEN;
#if TX_BUFFER_SIZE > 0
_written = false;
#endif
}
void MarlinSerial::end() {
// Disable UART interrupt in NVIC
NVIC_DisableIRQ( HWUART_IRQ );
pmc_disable_periph_clk( HWUART_IRQ_ID );
}
void MarlinSerial::checkRx(void) {
if (HWUART->UART_SR & UART_SR_RXRDY) {
CRITICAL_SECTION_START;
store_rxd_char();
CRITICAL_SECTION_END;
}
}
int MarlinSerial::peek(void) {
CRITICAL_SECTION_START;
const int v = rx_buffer.head == rx_buffer.tail ? -1 : rx_buffer.buffer[rx_buffer.tail];
CRITICAL_SECTION_END;
return v;
}
int MarlinSerial::read(void) {
int v;
CRITICAL_SECTION_START;
const ring_buffer_pos_t t = rx_buffer.tail;
if (rx_buffer.head == t)
v = -1;
else {
v = rx_buffer.buffer[t];
rx_buffer.tail = (ring_buffer_pos_t)(t + 1) & (RX_BUFFER_SIZE - 1);
#if ENABLED(SERIAL_XON_XOFF)
if ((xon_xoff_state & XON_XOFF_CHAR_MASK) == XOFF_CHAR) {
// Get count of bytes in the RX buffer
ring_buffer_pos_t rx_count = (ring_buffer_pos_t)(rx_buffer.head - rx_buffer.tail) & (ring_buffer_pos_t)(RX_BUFFER_SIZE - 1);
// When below 10% of RX buffer capacity, send XON before
// running out of RX buffer bytes
if (rx_count < (RX_BUFFER_SIZE) / 10) {
xon_xoff_state = XON_CHAR | XON_XOFF_CHAR_SENT;
CRITICAL_SECTION_END; // End critical section before returning!
writeNoHandshake(XON_CHAR);
return v;
}
}
#endif
}
CRITICAL_SECTION_END;
return v;
}
ring_buffer_pos_t MarlinSerial::available(void) {
CRITICAL_SECTION_START;
const ring_buffer_pos_t h = rx_buffer.head, t = rx_buffer.tail;
CRITICAL_SECTION_END;
return (ring_buffer_pos_t)(RX_BUFFER_SIZE + h - t) & (RX_BUFFER_SIZE - 1);
}
void MarlinSerial::flush(void) {
// Don't change this order of operations. If the RX interrupt occurs between
// reading rx_buffer_head and updating rx_buffer_tail, the previous rx_buffer_head
// may be written to rx_buffer_tail, making the buffer appear full rather than empty.
CRITICAL_SECTION_START;
rx_buffer.head = rx_buffer.tail;
CRITICAL_SECTION_END;
#if ENABLED(SERIAL_XON_XOFF)
if ((xon_xoff_state & XON_XOFF_CHAR_MASK) == XOFF_CHAR) {
xon_xoff_state = XON_CHAR | XON_XOFF_CHAR_SENT;
writeNoHandshake(XON_CHAR);
}
#endif
}
#if TX_BUFFER_SIZE > 0
uint8_t MarlinSerial::availableForWrite(void) {
CRITICAL_SECTION_START;
const uint8_t h = tx_buffer.head, t = tx_buffer.tail;
CRITICAL_SECTION_END;
return (uint8_t)(TX_BUFFER_SIZE + h - t) & (TX_BUFFER_SIZE - 1);
}
void MarlinSerial::write(const uint8_t c) {
#if ENABLED(SERIAL_XON_XOFF)
const uint8_t state = xon_xoff_state;
if (!(state & XON_XOFF_CHAR_SENT)) {
// Send 2 chars: XON/XOFF, then a user-specified char
writeNoHandshake(state & XON_XOFF_CHAR_MASK);
xon_xoff_state = state | XON_XOFF_CHAR_SENT;
}
#endif
writeNoHandshake(c);
}
void MarlinSerial::writeNoHandshake(const uint8_t c) {
_written = true;
CRITICAL_SECTION_START;
bool emty = (tx_buffer.head == tx_buffer.tail);
CRITICAL_SECTION_END;
// If the buffer and the data register is empty, just write the byte
// to the data register and be done. This shortcut helps
// significantly improve the effective datarate at high (>
// 500kbit/s) bitrates, where interrupt overhead becomes a slowdown.
if (emty && (HWUART->UART_SR & UART_SR_TXRDY)) {
CRITICAL_SECTION_START;
HWUART->UART_THR = c;
HWUART->UART_IER = UART_IER_TXRDY;
CRITICAL_SECTION_END;
return;
}
const uint8_t i = (tx_buffer.head + 1) & (TX_BUFFER_SIZE - 1);
// If the output buffer is full, there's nothing for it other than to
// wait for the interrupt handler to empty it a bit
while (i == tx_buffer.tail) {
if (__get_PRIMASK()) {
// Interrupts are disabled, so we'll have to poll the data
// register empty flag ourselves. If it is set, pretend an
// interrupt has happened and call the handler to free up
// space for us.
if (HWUART->UART_SR & UART_SR_TXRDY)
_tx_thr_empty_irq();
}
else {
// nop, the interrupt handler will free up space for us
}
sw_barrier();
}
tx_buffer.buffer[tx_buffer.head] = c;
{ CRITICAL_SECTION_START;
tx_buffer.head = i;
HWUART->UART_IER = UART_IER_TXRDY;
CRITICAL_SECTION_END;
}
return;
}
void MarlinSerial::flushTX(void) {
// TX
// If we have never written a byte, no need to flush.
if (!_written)
return;
while ((HWUART->UART_IMR & UART_IMR_TXRDY) || !(HWUART->UART_SR & UART_SR_TXEMPTY)) {
if (__get_PRIMASK())
if ((HWUART->UART_SR & UART_SR_TXRDY))
_tx_thr_empty_irq();
sw_barrier();
}
// If we get here, nothing is queued anymore (TX interrupts are disabled) and
// the hardware finished tranmission (TXEMPTY is set).
}
#else // TX_BUFFER_SIZE == 0
void MarlinSerial::write(const uint8_t c) {
#if ENABLED(SERIAL_XON_XOFF)
// Do a priority insertion of an XON/XOFF char, if needed.
const uint8_t state = xon_xoff_state;
if (!(state & XON_XOFF_CHAR_SENT)) {
writeNoHandshake(state & XON_XOFF_CHAR_MASK);
xon_xoff_state = state | XON_XOFF_CHAR_SENT;
}
#endif
writeNoHandshake(c);
}
void MarlinSerial::writeNoHandshake(const uint8_t c) {
while (!(HWUART->UART_SR & UART_SR_TXRDY)) { sw_barrier(); };
HWUART->UART_THR = c;
}
#endif // TX_BUFFER_SIZE == 0
/**
* Imports from print.h
*/
void MarlinSerial::print(char c, int base) {
print((long)c, base);
}
void MarlinSerial::print(unsigned char b, int base) {
print((unsigned long)b, base);
}
void MarlinSerial::print(int n, int base) {
print((long)n, base);
}
void MarlinSerial::print(unsigned int n, int base) {
print((unsigned long)n, base);
}
void MarlinSerial::print(long n, int base) {
if (base == 0)
write(n);
else if (base == 10) {
if (n < 0) {
print('-');
n = -n;
}
printNumber(n, 10);
}
else
printNumber(n, base);
}
void MarlinSerial::print(unsigned long n, int base) {
if (base == 0) write(n);
else printNumber(n, base);
}
void MarlinSerial::print(double n, int digits) {
printFloat(n, digits);
}
void MarlinSerial::println(void) {
print('\r');
print('\n');
}
void MarlinSerial::println(const String& s) {
print(s);
println();
}
void MarlinSerial::println(const char c[]) {
print(c);
println();
}
void MarlinSerial::println(char c, int base) {
print(c, base);
println();
}
void MarlinSerial::println(unsigned char b, int base) {
print(b, base);
println();
}
void MarlinSerial::println(int n, int base) {
print(n, base);
println();
}
void MarlinSerial::println(unsigned int n, int base) {
print(n, base);
println();
}
void MarlinSerial::println(long n, int base) {
print(n, base);
println();
}
void MarlinSerial::println(unsigned long n, int base) {
print(n, base);
println();
}
void MarlinSerial::println(double n, int digits) {
print(n, digits);
println();
}
// Private Methods
void MarlinSerial::printNumber(unsigned long n, uint8_t base) {
if (n) {
unsigned char buf[8 * sizeof(long)]; // Enough space for base 2
int8_t i = 0;
while (n) {
buf[i++] = n % base;
n /= base;
}
while (i--)
print((char)(buf[i] + (buf[i] < 10 ? '0' : 'A' - 10)));
}
else
print('0');
}
void MarlinSerial::printFloat(double number, uint8_t digits) {
// Handle negative numbers
if (number < 0.0) {
print('-');
number = -number;
}
// Round correctly so that print(1.999, 2) prints as "2.00"
double rounding = 0.5;
for (uint8_t i = 0; i < digits; ++i)
rounding *= 0.1;
number += rounding;
// Extract the integer part of the number and print it
unsigned long int_part = (unsigned long)number;
double remainder = number - (double)int_part;
print(int_part);
// Print the decimal point, but only if there are digits beyond
if (digits) {
print('.');
// Extract digits from the remainder one at a time
while (digits--) {
remainder *= 10.0;
int toPrint = int(remainder);
print(toPrint);
remainder -= toPrint;
}
}
}
// Preinstantiate
MarlinSerial customizedSerial;
#endif
#endif // ARDUINO_ARCH_SAM