From 4bc8aa15e3309496e2adf98b5933e4825341df07 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Fri, 19 Apr 2013 15:29:43 +0200 Subject: Remove duplicate code from HardwareSerial::begin() methods. There are two begin methods, one which accepts just a baud rate and uses the default bit settings and one which accepts both a baudrate and a bit config. Previously, both of these contained a complete implementation, but now the former just calls the latter, explicitely passing the default 8N1 configuration. Technically, this causes a small change: Before the UCSRC register was untouched when calling begin(baud), now it is explicitely initialized with 8N1. However, since this is the default configuration for at least the Uno and the Mega (didn't check any others), probably for all avrs, this shouldn't effectively change anything. Given that the Arduino documentation also documents this as the default when none is passed, explicitly setting it is probably a good idea in any case. --- cores/arduino/HardwareSerial.cpp | 39 +-------------------------------------- 1 file changed, 1 insertion(+), 38 deletions(-) diff --git a/cores/arduino/HardwareSerial.cpp b/cores/arduino/HardwareSerial.cpp index 9a86fa0..f95fa5d 100644 --- a/cores/arduino/HardwareSerial.cpp +++ b/cores/arduino/HardwareSerial.cpp @@ -283,44 +283,7 @@ HardwareSerial::HardwareSerial( void HardwareSerial::begin(unsigned long baud) { - uint16_t baud_setting; - bool use_u2x = true; - -#if F_CPU == 16000000UL - // hardcoded exception for compatibility with the bootloader shipped - // with the Duemilanove and previous boards and the firmware on the 8U2 - // on the Uno and Mega 2560. - if (baud == 57600) { - use_u2x = false; - } -#endif - -try_again: - - if (use_u2x) { - *_ucsra = 1 << _u2x; - baud_setting = (F_CPU / 4 / baud - 1) / 2; - } else { - *_ucsra = 0; - baud_setting = (F_CPU / 8 / baud - 1) / 2; - } - - if ((baud_setting > 4095) && use_u2x) - { - use_u2x = false; - goto try_again; - } - - // assign the baud_setting, a.k.a. ubbr (USART Baud Rate Register) - *_ubrrh = baud_setting >> 8; - *_ubrrl = baud_setting; - - transmitting = false; - - sbi(*_ucsrb, _rxen); - sbi(*_ucsrb, _txen); - sbi(*_ucsrb, _rxcie); - cbi(*_ucsrb, _udrie); + begin(baud, SERIAL_8N1); } void HardwareSerial::begin(unsigned long baud, byte config) -- cgit v1.2.3-18-g5258 From 0a0cda4f04844cc209c0523ba59e8a2f47fc502e Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Thu, 16 Jan 2014 13:50:59 +0100 Subject: Slightly reduce code utilization by inlining HardwareSerail begin(baud) and operator bool() --- cores/arduino/HardwareSerial.cpp | 9 ------- cores/arduino/HardwareSerial.h | 56 ++++++++++++++++++++-------------------- 2 files changed, 28 insertions(+), 37 deletions(-) diff --git a/cores/arduino/HardwareSerial.cpp b/cores/arduino/HardwareSerial.cpp index f95fa5d..a5a3462 100644 --- a/cores/arduino/HardwareSerial.cpp +++ b/cores/arduino/HardwareSerial.cpp @@ -281,11 +281,6 @@ HardwareSerial::HardwareSerial( // Public Methods ////////////////////////////////////////////////////////////// -void HardwareSerial::begin(unsigned long baud) -{ - begin(baud, SERIAL_8N1); -} - void HardwareSerial::begin(unsigned long baud, byte config) { uint16_t baud_setting; @@ -402,10 +397,6 @@ size_t HardwareSerial::write(uint8_t c) return 1; } -HardwareSerial::operator bool() { - return true; -} - // Preinstantiate Objects ////////////////////////////////////////////////////// #if defined(UBRRH) && defined(UBRRL) diff --git a/cores/arduino/HardwareSerial.h b/cores/arduino/HardwareSerial.h index 0f62262..6dba734 100644 --- a/cores/arduino/HardwareSerial.h +++ b/cores/arduino/HardwareSerial.h @@ -37,6 +37,32 @@ #define SERIAL_BUFFER_SIZE 64 #endif +// Define config for Serial.begin(baud, config); +#define SERIAL_5N1 0x00 +#define SERIAL_6N1 0x02 +#define SERIAL_7N1 0x04 +#define SERIAL_8N1 0x06 +#define SERIAL_5N2 0x08 +#define SERIAL_6N2 0x0A +#define SERIAL_7N2 0x0C +#define SERIAL_8N2 0x0E +#define SERIAL_5E1 0x20 +#define SERIAL_6E1 0x22 +#define SERIAL_7E1 0x24 +#define SERIAL_8E1 0x26 +#define SERIAL_5E2 0x28 +#define SERIAL_6E2 0x2A +#define SERIAL_7E2 0x2C +#define SERIAL_8E2 0x2E +#define SERIAL_5O1 0x30 +#define SERIAL_6O1 0x32 +#define SERIAL_7O1 0x34 +#define SERIAL_8O1 0x36 +#define SERIAL_5O2 0x38 +#define SERIAL_6O2 0x3A +#define SERIAL_7O2 0x3C +#define SERIAL_8O2 0x3E + class HardwareSerial : public Stream { protected: @@ -70,7 +96,7 @@ class HardwareSerial : public Stream volatile uint8_t *ucsra, volatile uint8_t *ucsrb, volatile uint8_t *ucsrc, volatile uint8_t *udr, uint8_t rxen, uint8_t txen, uint8_t rxcie, uint8_t udrie, uint8_t u2x); - void begin(unsigned long); + void begin(unsigned long baud) { begin(baud, SERIAL_8N1); } void begin(unsigned long, uint8_t); void end(); virtual int available(void); @@ -83,35 +109,9 @@ class HardwareSerial : public Stream inline size_t write(unsigned int n) { return write((uint8_t)n); } inline size_t write(int n) { return write((uint8_t)n); } using Print::write; // pull in write(str) and write(buf, size) from Print - operator bool(); + operator bool() { return true; } }; -// Define config for Serial.begin(baud, config); -#define SERIAL_5N1 0x00 -#define SERIAL_6N1 0x02 -#define SERIAL_7N1 0x04 -#define SERIAL_8N1 0x06 -#define SERIAL_5N2 0x08 -#define SERIAL_6N2 0x0A -#define SERIAL_7N2 0x0C -#define SERIAL_8N2 0x0E -#define SERIAL_5E1 0x20 -#define SERIAL_6E1 0x22 -#define SERIAL_7E1 0x24 -#define SERIAL_8E1 0x26 -#define SERIAL_5E2 0x28 -#define SERIAL_6E2 0x2A -#define SERIAL_7E2 0x2C -#define SERIAL_8E2 0x2E -#define SERIAL_5O1 0x30 -#define SERIAL_6O1 0x32 -#define SERIAL_7O1 0x34 -#define SERIAL_8O1 0x36 -#define SERIAL_5O2 0x38 -#define SERIAL_6O2 0x3A -#define SERIAL_7O2 0x3C -#define SERIAL_8O2 0x3E - #if defined(UBRRH) || defined(UBRR0H) extern HardwareSerial Serial; #elif defined(USBCON) -- cgit v1.2.3-18-g5258 From d43fd2014c664d1af7fd71585f4716806288b546 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Fri, 19 Apr 2013 16:12:31 +0200 Subject: Remove unused variable --- cores/arduino/HardwareSerial.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/cores/arduino/HardwareSerial.cpp b/cores/arduino/HardwareSerial.cpp index a5a3462..28a3ef6 100644 --- a/cores/arduino/HardwareSerial.cpp +++ b/cores/arduino/HardwareSerial.cpp @@ -284,7 +284,6 @@ HardwareSerial::HardwareSerial( void HardwareSerial::begin(unsigned long baud, byte config) { uint16_t baud_setting; - uint8_t current_config; bool use_u2x = true; #if F_CPU == 16000000UL -- cgit v1.2.3-18-g5258 From fc45ef0846a426bbc15b1f74b2c70b04bb68e557 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Fri, 19 Apr 2013 16:32:33 +0200 Subject: Simplify HardwareSerial::begin() This simplifies the baud rate calculation, removing the need for a goto and shortening the code a bit. Other than that, this code should not use any different settings than before. Code was suggested by Rob Tillaart on github. Closes: #1262 --- cores/arduino/HardwareSerial.cpp | 35 +++++++++++------------------------ 1 file changed, 11 insertions(+), 24 deletions(-) diff --git a/cores/arduino/HardwareSerial.cpp b/cores/arduino/HardwareSerial.cpp index 28a3ef6..d9059a9 100644 --- a/cores/arduino/HardwareSerial.cpp +++ b/cores/arduino/HardwareSerial.cpp @@ -283,33 +283,20 @@ HardwareSerial::HardwareSerial( void HardwareSerial::begin(unsigned long baud, byte config) { - uint16_t baud_setting; - bool use_u2x = true; - -#if F_CPU == 16000000UL - // hardcoded exception for compatibility with the bootloader shipped - // with the Duemilanove and previous boards and the firmware on the 8U2 - // on the Uno and Mega 2560. - if (baud == 57600) { - use_u2x = false; - } -#endif - -try_again: - - if (use_u2x) { - *_ucsra = 1 << _u2x; - baud_setting = (F_CPU / 4 / baud - 1) / 2; - } else { + // Try u2x mode first + uint16_t baud_setting = (F_CPU / 4 / baud - 1) / 2; + *_ucsra = 1 << _u2x; + + // hardcoded exception for 57600 for compatibility with the bootloader + // shipped with the Duemilanove and previous boards and the firmware + // on the 8U2 on the Uno and Mega 2560. Also, The baud_setting cannot + // be > 4095, so switch back to non-u2x mode if the baud rate is too + // low. + if (((F_CPU == 16000000UL) && (baud == 57600)) || (baud_setting >4095)) + { *_ucsra = 0; baud_setting = (F_CPU / 8 / baud - 1) / 2; } - - if ((baud_setting > 4095) && use_u2x) - { - use_u2x = false; - goto try_again; - } // assign the baud_setting, a.k.a. ubbr (USART Baud Rate Register) *_ubrrh = baud_setting >> 8; -- cgit v1.2.3-18-g5258 From 494929495ea1d64850f77a198d67a66f18a85630 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Sun, 1 Dec 2013 19:18:31 +0100 Subject: Define a _NOP() macro Recent avr-libc releases define one, but this allows using it also on older avr-libc releases. --- cores/arduino/Arduino.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cores/arduino/Arduino.h b/cores/arduino/Arduino.h index 7bf5119..8502e44 100644 --- a/cores/arduino/Arduino.h +++ b/cores/arduino/Arduino.h @@ -88,6 +88,10 @@ void yield(void); #define bitClear(value, bit) ((value) &= ~(1UL << (bit))) #define bitWrite(value, bit, bitvalue) (bitvalue ? bitSet(value, bit) : bitClear(value, bit)) +// avr-libc defines _NOP() since 1.6.2 +#ifndef _NOP +#define _NOP() do { __asm__ volatile ("nop"); } while (0) +#endif typedef unsigned int word; -- cgit v1.2.3-18-g5258 From 3babfc2a855e49c5781f433fb5bd1227c8161dd8 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Thu, 18 Apr 2013 14:17:47 +0200 Subject: Use constants for register bit positions in HardwareSerial Previously, the constants to use for the bit positions of the various UARTs were passed to the HardwareSerial constructor. However, this meant that whenever these values were used, the had to be indirectly loaded, resulting in extra code overhead. Additionally, since there is no instruction to shift a value by a variable amount, the 1 << x expressions (inside _BV and sbi() / cbi()) would be compiled as a loop instead of being evaluated at compiletime. Now, the HardwareSerial class always uses the constants for the bit positions of UART 0 (and some code is present to make sure these constants exist, even for targets that only have a single unnumbered UART or start at UART1). This was already done for the TXC0 constant, for some reason. For the actual register addresses, this approach does not work, since these are of course different between the different UARTs on a single chip. Of course, always using the UART 0 constants is only correct when the constants are actually identical for the different UARTs. It has been verified that this is currently the case for all targets supported by avr-gcc 4.7.2, and the code contains compile-time checks to verify this for the current target, in case a new target is added for which this does not hold. This verification was done using: for i in TXC RXEN TXEN RXCIE UDRIE U2X UPE; do echo $i; grep --no-filename -r "#define $i[0-9]\? " /usr/lib/avr/include/avr/io* | sed "s/#define $i[0-9]\?\s*\(\S\)\+\s*\(\/\*.*\*\/\)\?$/\1/" | sort | uniq ; done This command shows that the above constants are identical for all uarts on all platforms, except for TXC, which is sometimes 6 and sometimes 0. Further investigation shows that it is always 6, except in io90scr100.h, but that file defines TXC0 with value 6 for the UART and uses TXC with value 0 for some USB-related register. This commit reduces program size on the uno by around 120 bytes. --- cores/arduino/HardwareSerial.cpp | 83 +++++++++++++++++++++++++++------------- cores/arduino/HardwareSerial.h | 8 +--- 2 files changed, 57 insertions(+), 34 deletions(-) diff --git a/cores/arduino/HardwareSerial.cpp b/cores/arduino/HardwareSerial.cpp index d9059a9..59e0f1a 100644 --- a/cores/arduino/HardwareSerial.cpp +++ b/cores/arduino/HardwareSerial.cpp @@ -34,19 +34,54 @@ #include "HardwareSerial.h" -/* - * on ATmega8, the uart and its bits are not numbered, so there is no "TXC0" - * definition. - */ +// Ensure that the various bit positions we use are available with a 0 +// postfix, so we can always use the values for UART0 for all UARTs. The +// alternative, passing the various values for each UART to the +// HardwareSerial constructor also works, but makes the code bigger and +// slower. #if !defined(TXC0) #if defined(TXC) +// On ATmega8, the uart and its bits are not numbered, so there is no TXC0 etc. #define TXC0 TXC +#define RXEN0 RXEN +#define TXEN0 TXEN +#define RXCIE0 RXCIE +#define UDRIE0 UDRIE +#define U2X0 U2X +#define UPE0 UPE +#define UDRE0 UDRE #elif defined(TXC1) // Some devices have uart1 but no uart0 #define TXC0 TXC1 +#define RXEN0 RXEN1 +#define TXEN0 TXEN1 +#define RXCIE0 RXCIE1 +#define UDRIE0 UDRIE1 +#define U2X0 U2X1 +#define UPE0 UPE1 +#define UDRE0 UDRE1 #else -#error TXC0 not definable in HardwareSerial.h +#error No UART found in HardwareSerial.cpp +#endif +#endif // !defined TXC0 + +// Check at compiletime that it is really ok to use the bit positions of +// UART0 for the other UARTs as well, in case these values ever get +// changed for future hardware. +#if defined(TXC1) && (TXC1 != TXC0 || RXEN1 != RXEN0 || RXCIE1 != RXCIE0 || \ + UDRIE1 != UDRIE0 || U2X1 != U2X0 || UPE1 != UPE0 || \ + UDRE1 != UDRE0) +#error "Not all bit positions for UART1 are the same as for UART0" +#endif +#if defined(TXC2) && (TXC2 != TXC0 || RXEN2 != RXEN0 || RXCIE2 != RXCIE0 || \ + UDRIE2 != UDRIE0 || U2X2 != U2X0 || UPE2 != UPE0 || \ + UDRE2 != UDRE0) +#error "Not all bit positions for UART2 are the same as for UART0" #endif +#if defined(TXC3) && (TXC3 != TXC0 || RXEN3 != RXEN0 || RXCIE3 != RXCIE0 || \ + UDRIE3 != UDRIE0 || U3X3 != U3X0 || UPE3 != UPE0 || \ + UDRE3 != UDRE0) +#error "Not all bit positions for UART3 are the same as for UART0" #endif inline void store_char(unsigned char c, HardwareSerial *s) @@ -261,8 +296,7 @@ ISR(USART3_UDRE_vect) HardwareSerial::HardwareSerial( volatile uint8_t *ubrrh, volatile uint8_t *ubrrl, volatile uint8_t *ucsra, volatile uint8_t *ucsrb, - volatile uint8_t *ucsrc, volatile uint8_t *udr, - uint8_t rxen, uint8_t txen, uint8_t rxcie, uint8_t udrie, uint8_t u2x) + volatile uint8_t *ucsrc, volatile uint8_t *udr) { _tx_buffer_head = _tx_buffer_tail = 0; _rx_buffer_head = _rx_buffer_tail = 0; @@ -272,11 +306,6 @@ HardwareSerial::HardwareSerial( _ucsrb = ucsrb; _ucsrc = ucsrc; _udr = udr; - _rxen = rxen; - _txen = txen; - _rxcie = rxcie; - _udrie = udrie; - _u2x = u2x; } // Public Methods ////////////////////////////////////////////////////////////// @@ -285,7 +314,7 @@ void HardwareSerial::begin(unsigned long baud, byte config) { // Try u2x mode first uint16_t baud_setting = (F_CPU / 4 / baud - 1) / 2; - *_ucsra = 1 << _u2x; + *_ucsra = 1 << U2X0; // hardcoded exception for 57600 for compatibility with the bootloader // shipped with the Duemilanove and previous boards and the firmware @@ -308,10 +337,10 @@ void HardwareSerial::begin(unsigned long baud, byte config) #endif *_ucsrc = config; - sbi(*_ucsrb, _rxen); - sbi(*_ucsrb, _txen); - sbi(*_ucsrb, _rxcie); - cbi(*_ucsrb, _udrie); + sbi(*_ucsrb, RXEN0); + sbi(*_ucsrb, TXEN0); + sbi(*_ucsrb, RXCIE0); + cbi(*_ucsrb, UDRIE0); } void HardwareSerial::end() @@ -320,10 +349,10 @@ void HardwareSerial::end() while (_tx_buffer_head != _tx_buffer_tail) ; - cbi(*_ucsrb, _rxen); - cbi(*_ucsrb, _txen); - cbi(*_ucsrb, _rxcie); - cbi(*_ucsrb, _udrie); + cbi(*_ucsrb, RXEN0); + cbi(*_ucsrb, TXEN0); + cbi(*_ucsrb, RXCIE0); + cbi(*_ucsrb, UDRIE0); // clear any received data _rx_buffer_head = _rx_buffer_tail; @@ -375,7 +404,7 @@ size_t HardwareSerial::write(uint8_t c) _tx_buffer[_tx_buffer_head] = c; _tx_buffer_head = i; - sbi(*_ucsrb, _udrie); + sbi(*_ucsrb, UDRIE0); // clear the TXC bit -- "can be cleared by writing a one to its bit location" transmitting = true; sbi(*_ucsra, TXC0); @@ -386,9 +415,9 @@ size_t HardwareSerial::write(uint8_t c) // Preinstantiate Objects ////////////////////////////////////////////////////// #if defined(UBRRH) && defined(UBRRL) - HardwareSerial Serial(&UBRRH, &UBRRL, &UCSRA, &UCSRB, &UCSRC, &UDR, RXEN, TXEN, RXCIE, UDRIE, U2X); + HardwareSerial Serial(&UBRRH, &UBRRL, &UCSRA, &UCSRB, &UCSRC, &UDR); #elif defined(UBRR0H) && defined(UBRR0L) - HardwareSerial Serial(&UBRR0H, &UBRR0L, &UCSR0A, &UCSR0B, &UCSR0C, &UDR0, RXEN0, TXEN0, RXCIE0, UDRIE0, U2X0); + HardwareSerial Serial(&UBRR0H, &UBRR0L, &UCSR0A, &UCSR0B, &UCSR0C, &UDR0); #elif defined(USBCON) // do nothing - Serial object and buffers are initialized in CDC code #else @@ -396,13 +425,13 @@ size_t HardwareSerial::write(uint8_t c) #endif #if defined(UBRR1H) - HardwareSerial Serial1(&UBRR1H, &UBRR1L, &UCSR1A, &UCSR1B, &UCSR1C, &UDR1, RXEN1, TXEN1, RXCIE1, UDRIE1, U2X1); + HardwareSerial Serial1(&UBRR1H, &UBRR1L, &UCSR1A, &UCSR1B, &UCSR1C, &UDR1); #endif #if defined(UBRR2H) - HardwareSerial Serial2(&UBRR2H, &UBRR2L, &UCSR2A, &UCSR2B, &UCSR2C, &UDR2, RXEN2, TXEN2, RXCIE2, UDRIE2, U2X2); + HardwareSerial Serial2(&UBRR2H, &UBRR2L, &UCSR2A, &UCSR2B, &UCSR2C, &UDR2); #endif #if defined(UBRR3H) - HardwareSerial Serial3(&UBRR3H, &UBRR3L, &UCSR3A, &UCSR3B, &UCSR3C, &UDR3, RXEN3, TXEN3, RXCIE3, UDRIE3, U2X3); + HardwareSerial Serial3(&UBRR3H, &UBRR3L, &UCSR3A, &UCSR3B, &UCSR3C, &UDR3); #endif #endif // whole file diff --git a/cores/arduino/HardwareSerial.h b/cores/arduino/HardwareSerial.h index 6dba734..5513226 100644 --- a/cores/arduino/HardwareSerial.h +++ b/cores/arduino/HardwareSerial.h @@ -72,11 +72,6 @@ class HardwareSerial : public Stream volatile uint8_t *_ucsrb; volatile uint8_t *_ucsrc; volatile uint8_t *_udr; - uint8_t _rxen; - uint8_t _txen; - uint8_t _rxcie; - uint8_t _udrie; - uint8_t _u2x; bool transmitting; public: @@ -94,8 +89,7 @@ class HardwareSerial : public Stream HardwareSerial( volatile uint8_t *ubrrh, volatile uint8_t *ubrrl, volatile uint8_t *ucsra, volatile uint8_t *ucsrb, - volatile uint8_t *ucsrc, volatile uint8_t *udr, - uint8_t rxen, uint8_t txen, uint8_t rxcie, uint8_t udrie, uint8_t u2x); + volatile uint8_t *ucsrc, volatile uint8_t *udr); void begin(unsigned long baud) { begin(baud, SERIAL_8N1); } void begin(unsigned long, uint8_t); void end(); -- cgit v1.2.3-18-g5258 From 80d6af62730f11f9f23ec7a14d69bc6cfa01dd03 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Thu, 18 Apr 2013 11:38:13 +0200 Subject: Move interrupt handlers into HardwareSerial class The actual interrupt vectors are of course defined as before, but they let new methods in the HardwareSerial class do the actual work. This greatly reduces code duplication and prepares for one of my next commits which requires the tx interrupt handler to be called from another context as well. The actual content of the interrupts handlers was pretty much identical, so that remains unchanged (except that store_char was now only needed once, so it was inlined). Now all access to the buffers are inside the HardwareSerial class, the buffer variables can be made private. One would expect a program size reduction from this change (at least with multiple UARTs), but due to the fact that the interrupt handlers now only have indirect access to a few registers (which previously were just hardcoded in the handlers) and because there is some extra function call overhead, the code size on the uno actually increases by around 70 bytes. On the mega, which has four UARTs, the code size decreases by around 70 bytes. --- cores/arduino/HardwareSerial.cpp | 145 ++++++++++++--------------------------- cores/arduino/HardwareSerial.h | 6 +- 2 files changed, 50 insertions(+), 101 deletions(-) diff --git a/cores/arduino/HardwareSerial.cpp b/cores/arduino/HardwareSerial.cpp index 59e0f1a..6bbef7a 100644 --- a/cores/arduino/HardwareSerial.cpp +++ b/cores/arduino/HardwareSerial.cpp @@ -84,20 +84,6 @@ #error "Not all bit positions for UART3 are the same as for UART0" #endif -inline void store_char(unsigned char c, HardwareSerial *s) -{ - int i = (unsigned int)(s->_rx_buffer_head + 1) % SERIAL_BUFFER_SIZE; - - // if we should be storing the received character into the location - // just before the tail (meaning that the head would advance to the - // current location of the tail), we're about to overflow the buffer - // and so we don't write the character or advance the head. - if (i != s->_rx_buffer_tail) { - s->_rx_buffer[s->_rx_buffer_head] = c; - s->_rx_buffer_head = i; - } -} - #if !defined(USART0_RX_vect) && defined(USART1_RX_vect) // do nothing - on the 32u4 the first USART is USART1 #else @@ -116,23 +102,7 @@ inline void store_char(unsigned char c, HardwareSerial *s) ISR(USART_RXC_vect) // ATmega8 #endif { - #if defined(UDR0) - if (bit_is_clear(UCSR0A, UPE0)) { - unsigned char c = UDR0; - store_char(c, &Serial); - } else { - unsigned char c = UDR0; - }; - #elif defined(UDR) - if (bit_is_clear(UCSRA, PE)) { - unsigned char c = UDR; - store_char(c, &Serial); - } else { - unsigned char c = UDR; - }; - #else - #error UDR not defined - #endif + Serial._rx_complete_irq(); } #endif #endif @@ -143,12 +113,7 @@ inline void store_char(unsigned char c, HardwareSerial *s) #define serialEvent1_implemented ISR(USART1_RX_vect) { - if (bit_is_clear(UCSR1A, UPE1)) { - unsigned char c = UDR1; - store_char(c, &Serial1); - } else { - unsigned char c = UDR1; - }; + Serial1._rx_complete_irq(); } #endif @@ -158,12 +123,7 @@ inline void store_char(unsigned char c, HardwareSerial *s) #define serialEvent2_implemented ISR(USART2_RX_vect) { - if (bit_is_clear(UCSR2A, UPE2)) { - unsigned char c = UDR2; - store_char(c, &Serial2); - } else { - unsigned char c = UDR2; - }; + Serial2._rx_complete_irq(); } #endif @@ -173,12 +133,7 @@ inline void store_char(unsigned char c, HardwareSerial *s) #define serialEvent3_implemented ISR(USART3_RX_vect) { - if (bit_is_clear(UCSR3A, UPE3)) { - unsigned char c = UDR3; - store_char(c, &Serial3); - } else { - unsigned char c = UDR3; - }; + Serial3._rx_complete_irq(); } #endif @@ -215,27 +170,7 @@ ISR(USART0_UDRE_vect) ISR(USART_UDRE_vect) #endif { - if (Serial._tx_buffer_head == Serial._tx_buffer_tail) { - // Buffer empty, so disable interrupts -#if defined(UCSR0B) - cbi(UCSR0B, UDRIE0); -#else - cbi(UCSRB, UDRIE); -#endif - } - else { - // There is more data in the output buffer. Send the next byte - unsigned char c = Serial._tx_buffer[Serial._tx_buffer_tail]; - Serial._tx_buffer_tail = (Serial._tx_buffer_tail + 1) % SERIAL_BUFFER_SIZE; - - #if defined(UDR0) - UDR0 = c; - #elif defined(UDR) - UDR = c; - #else - #error UDR not defined - #endif - } + Serial._tx_udr_empty_irq(); } #endif #endif @@ -243,53 +178,63 @@ ISR(USART_UDRE_vect) #ifdef USART1_UDRE_vect ISR(USART1_UDRE_vect) { - if (Serial1._tx_buffer_head == Serial1._tx_buffer_tail) { - // Buffer empty, so disable interrupts - cbi(UCSR1B, UDRIE1); - } - else { - // There is more data in the output buffer. Send the next byte - unsigned char c = Serial1._tx_buffer[Serial1._tx_buffer_tail]; - Serial1._tx_buffer_tail = (Serial1._tx_buffer_tail + 1) % SERIAL_BUFFER_SIZE; - - UDR1 = c; - } + Serial1._tx_udr_empty_irq(); } #endif #ifdef USART2_UDRE_vect ISR(USART2_UDRE_vect) { - if (Serial2._tx_buffer_head == Serial2._tx_buffer_tail) { - // Buffer empty, so disable interrupts - cbi(UCSR2B, UDRIE2); - } - else { - // There is more data in the output buffer. Send the next byte - unsigned char c = Serial2._tx_buffer[Serial2._tx_buffer_tail]; - Serial2._tx_buffer_tail = (Serial2._tx_buffer_tail + 1) % SERIAL_BUFFER_SIZE; - - UDR2 = c; - } + Serial2._tx_udr_empty_irq(); } #endif #ifdef USART3_UDRE_vect ISR(USART3_UDRE_vect) { - if (Serial3._tx_buffer_head == Serial3._tx_buffer_tail) { - // Buffer empty, so disable interrupts - cbi(UCSR3B, UDRIE3); + Serial3._tx_udr_empty_irq(); +} +#endif + + +// Actual interrupt handlers ////////////////////////////////////////////////////////////// + +void HardwareSerial::_rx_complete_irq(void) +{ + if (bit_is_clear(*_ucsra, UPE0)) { + // No Parity error, read byte and store it in the buffer if there is + // room + unsigned char c = *_udr; + int i = (unsigned int)(_rx_buffer_head + 1) % SERIAL_BUFFER_SIZE; + + // if we should be storing the received character into the location + // just before the tail (meaning that the head would advance to the + // current location of the tail), we're about to overflow the buffer + // and so we don't write the character or advance the head. + if (i != _rx_buffer_tail) { + _rx_buffer[_rx_buffer_head] = c; + _rx_buffer_head = i; + } + } else { + // Parity error, read byte but discard it + unsigned char c = *_udr; + }; +} + +void HardwareSerial::_tx_udr_empty_irq(void) +{ + if (_tx_buffer_head == _tx_buffer_tail) { + // Buffer empty, so disable interrupts + cbi(*_ucsrb, UDRIE0); } else { // There is more data in the output buffer. Send the next byte - unsigned char c = Serial3._tx_buffer[Serial3._tx_buffer_tail]; - Serial3._tx_buffer_tail = (Serial3._tx_buffer_tail + 1) % SERIAL_BUFFER_SIZE; - - UDR3 = c; + unsigned char c = _tx_buffer[_tx_buffer_tail]; + _tx_buffer_tail = (_tx_buffer_tail + 1) % SERIAL_BUFFER_SIZE; + + *_udr = c; } } -#endif // Constructors //////////////////////////////////////////////////////////////// diff --git a/cores/arduino/HardwareSerial.h b/cores/arduino/HardwareSerial.h index 5513226..cf3f6bd 100644 --- a/cores/arduino/HardwareSerial.h +++ b/cores/arduino/HardwareSerial.h @@ -74,7 +74,6 @@ class HardwareSerial : public Stream volatile uint8_t *_udr; bool transmitting; - public: volatile uint8_t _rx_buffer_head; volatile uint8_t _rx_buffer_tail; volatile uint8_t _tx_buffer_head; @@ -86,6 +85,7 @@ class HardwareSerial : public Stream unsigned char _rx_buffer[SERIAL_BUFFER_SIZE]; unsigned char _tx_buffer[SERIAL_BUFFER_SIZE]; + public: HardwareSerial( volatile uint8_t *ubrrh, volatile uint8_t *ubrrl, volatile uint8_t *ucsra, volatile uint8_t *ucsrb, @@ -104,6 +104,10 @@ class HardwareSerial : public Stream inline size_t write(int n) { return write((uint8_t)n); } using Print::write; // pull in write(str) and write(buf, size) from Print operator bool() { return true; } + + // Interrupt handlers - Not intended to be called externally + void _rx_complete_irq(void); + void _tx_udr_empty_irq(void); }; #if defined(UBRRH) || defined(UBRR0H) -- cgit v1.2.3-18-g5258 From bd194db4e317feaf6498cdece2fe7d893ae03403 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Thu, 18 Apr 2013 20:20:03 +0200 Subject: Use bit_is_clear in HardwareSerial::flush() This is slightly more clear than the previous explicit comparison. --- cores/arduino/HardwareSerial.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cores/arduino/HardwareSerial.cpp b/cores/arduino/HardwareSerial.cpp index 6bbef7a..806fe5f 100644 --- a/cores/arduino/HardwareSerial.cpp +++ b/cores/arduino/HardwareSerial.cpp @@ -332,7 +332,7 @@ int HardwareSerial::read(void) void HardwareSerial::flush() { // UDR is kept full while the buffer is not empty, so TXC triggers when EMPTY && SENT - while (transmitting && ! (*_ucsra & _BV(TXC0))); + while (transmitting && bit_is_clear(*_ucsra, TXC0)); transmitting = false; } -- cgit v1.2.3-18-g5258 From 560295c983d6def24021ac94c40d7bbc8912c1ea Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Thu, 18 Apr 2013 21:12:01 +0200 Subject: Improve HardwareSerial::flush() The flush() method blocks until all characters in the serial buffer have been written to the uart _and_ transmitted. This is checked by waiting until the "TXC" (TX Complete) bit is set by the UART, signalling completion. This bit is cleared by write() when adding a new byte to the buffer and set by the hardware after tranmission ends, so it is always guaranteed to be zero from the moment the first byte in a sequence is queued until the moment the last byte is transmitted, and it is one from the moment the last byte in the buffer is transmitted until the first byte in the next sequence is queued. However, the TXC bit is also zero from initialization to the moment the first byte ever is queued (and then continues to be zero until the first sequence of bytes completes transmission). Unfortunately we cannot manually set the TXC bit during initialization, we can only clear it. To make sure that flush() would not (indefinitely) block when it is called _before_ anything was written to the serial device, the "transmitting" variable was introduced. This variable suggests that it is only true when something is transmitting, which isn't currently the case (it remains true after transmission is complete until flush() is called, for example). Furthermore, there is no need to keep the status of transmission, the only thing needed is to remember if anything has ever been written, so the corner case described above can be detected. This commit improves the code by: - Renaming the "transmitting" variable to _written (making it more clear and following the leading underscore naming convention). - Not resetting the value of _written at the end of flush(), there is no point to this. - Only checking the "_written" value once in flush(), since it can never be toggled off anyway. - Initializing the value of _written in both versions of _begin (though it probably gets initialized to 0 by default anyway, better to be explicit). --- cores/arduino/HardwareSerial.cpp | 20 +++++++++++++++----- cores/arduino/HardwareSerial.h | 3 ++- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/cores/arduino/HardwareSerial.cpp b/cores/arduino/HardwareSerial.cpp index 806fe5f..d78c205 100644 --- a/cores/arduino/HardwareSerial.cpp +++ b/cores/arduino/HardwareSerial.cpp @@ -276,6 +276,8 @@ void HardwareSerial::begin(unsigned long baud, byte config) *_ubrrh = baud_setting >> 8; *_ubrrl = baud_setting; + _written = false; + //set the data bits, parity, and stop bits #if defined(__AVR_ATmega8__) config |= 0x80; // select UCSRC register (shared with UBRRH) @@ -331,9 +333,15 @@ int HardwareSerial::read(void) void HardwareSerial::flush() { - // UDR is kept full while the buffer is not empty, so TXC triggers when EMPTY && SENT - while (transmitting && bit_is_clear(*_ucsra, TXC0)); - transmitting = false; + // If we have never written a byte, no need to flush. This special + // case is needed since there is no way to force the TXC (transmit + // complete) bit to 1 during initialization + if (!_written) + return; + + // UDR is kept full while the buffer is not empty, so TXC triggers + // when EMPTY && SENT + while (bit_is_clear(*_ucsra, TXC0)); } size_t HardwareSerial::write(uint8_t c) @@ -350,8 +358,10 @@ size_t HardwareSerial::write(uint8_t c) _tx_buffer_head = i; sbi(*_ucsrb, UDRIE0); - // clear the TXC bit -- "can be cleared by writing a one to its bit location" - transmitting = true; + _written = true; + // clear the TXC bit -- "can be cleared by writing a one to its bit + // location". This makes sure flush() won't return until the bytes + // actually got written sbi(*_ucsra, TXC0); return 1; diff --git a/cores/arduino/HardwareSerial.h b/cores/arduino/HardwareSerial.h index cf3f6bd..0ae51f4 100644 --- a/cores/arduino/HardwareSerial.h +++ b/cores/arduino/HardwareSerial.h @@ -72,7 +72,8 @@ class HardwareSerial : public Stream volatile uint8_t *_ucsrb; volatile uint8_t *_ucsrc; volatile uint8_t *_udr; - bool transmitting; + // Has any byte been written to the UART since begin() + bool _written; volatile uint8_t _rx_buffer_head; volatile uint8_t _rx_buffer_tail; -- cgit v1.2.3-18-g5258 From fa8df58c93844800196453d738bf229a9b44618d Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Fri, 19 Apr 2013 12:56:54 +0200 Subject: Fix HardwareSerial::flush() when interrupts are kept disabled for a while It turns out there is an additional corner case. The analysis in the previous commit wrt to flush() assumes that the data register is always kept filled by the interrupt handler, so the TXC bit won't get set until all the queued bytes have been transmitted. But, when interrupts are disabled for a longer period (for example when an interrupt handler for another device is running for longer than 1-2 byte times), it could happen that the UART stops transmitting while there are still more bytes queued (but these are in the buffer, not in the UDR register, so the UART can't know about them). In this case, the TXC bit would get set, but the transmission is not complete yet. We can easily detect this case by looking at the head and tail pointers, but it seems easier to instead look at the UDRIE bit (the TX interrupt is enabled if and only if there are bytes in the queue). To fix this corner case, this commit: - Checks the UDRIE bit and only if it is unset, looks at the TXC bit. - Moves the clearing of TXC from write() to the tx interrupt handler. This (still) causes the TXC bit to be cleared whenever a byte is queued when the buffer is empty (in this case the tx interrupt will trigger directly after write() is called). It also causes the TXC bit to be cleared whenever transmission is resumed after it halted because interrupts have been disabled for too long. As a side effect, another race condition is prevented. This could occur at very high bitrates, where the transmission would be completed before the code got time to clear the TXC0 register, making the clear happen _after_ the transmission was already complete. With the new code, the clearing of TXC happens directly after writing to the UDR register, while interrupts are disabled, and we can be certain the data transmission needs more time than one instruction to complete. This fixes #1463 and replaces #1456. --- cores/arduino/HardwareSerial.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/cores/arduino/HardwareSerial.cpp b/cores/arduino/HardwareSerial.cpp index d78c205..1bd3bff 100644 --- a/cores/arduino/HardwareSerial.cpp +++ b/cores/arduino/HardwareSerial.cpp @@ -233,6 +233,11 @@ void HardwareSerial::_tx_udr_empty_irq(void) _tx_buffer_tail = (_tx_buffer_tail + 1) % SERIAL_BUFFER_SIZE; *_udr = c; + + // clear the TXC bit -- "can be cleared by writing a one to its bit + // location". This makes sure flush() won't return until the bytes + // actually got written + sbi(*_ucsra, TXC0); } } @@ -339,9 +344,9 @@ void HardwareSerial::flush() if (!_written) return; - // UDR is kept full while the buffer is not empty, so TXC triggers - // when EMPTY && SENT - while (bit_is_clear(*_ucsra, TXC0)); + while (bit_is_set(*_ucsrb, UDRIE0) || bit_is_clear(*_ucsra, TXC0)); + // If we get here, nothing is queued anymore (DRIE is disabled) and + // the hardware finished tranmission (TXC is set). } size_t HardwareSerial::write(uint8_t c) @@ -359,10 +364,6 @@ size_t HardwareSerial::write(uint8_t c) sbi(*_ucsrb, UDRIE0); _written = true; - // clear the TXC bit -- "can be cleared by writing a one to its bit - // location". This makes sure flush() won't return until the bytes - // actually got written - sbi(*_ucsra, TXC0); return 1; } -- cgit v1.2.3-18-g5258 From dbe23685c27cb1467dc84140e4899a0bf8e23248 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Thu, 18 Apr 2013 21:34:00 +0200 Subject: Fix lockup when writing to HardwareSerial with interrupts disabled When interrupts are disabled, writing to HardwareSerial could cause a lockup. When the tx buffer is full, a busy-wait loop is used to wait for the interrupt handler to free up a byte in the buffer. However, when interrupts are disabled, this will of course never happen and the Arduino will lock up. This often caused lockups when doing (big) debug printing from an interrupt handler. Additionally, calling flush() with interrupts disabled while transmission was in progress would also cause a lockup. When interrupts are disabled, the code now actively checks the UDRE (UART Data Register Empty) and calls the interrupt handler to free up room if the bit is set. This can lead to delays in interrupt handlers when the serial buffer is full, but a delay is of course always preferred to a lockup. Closes: #672 References: #1147 --- cores/arduino/HardwareSerial.cpp | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/cores/arduino/HardwareSerial.cpp b/cores/arduino/HardwareSerial.cpp index 1bd3bff..ffdb82c 100644 --- a/cores/arduino/HardwareSerial.cpp +++ b/cores/arduino/HardwareSerial.cpp @@ -344,7 +344,14 @@ void HardwareSerial::flush() if (!_written) return; - while (bit_is_set(*_ucsrb, UDRIE0) || bit_is_clear(*_ucsra, TXC0)); + while (bit_is_set(*_ucsrb, UDRIE0) || bit_is_clear(*_ucsra, TXC0)) { + if (bit_is_clear(SREG, SREG_I) && bit_is_set(*_ucsrb, UDRIE0)) + // Interrupts are globally disabled, but the DR empty + // interrupt should be enabled, so poll the DR empty flag to + // prevent deadlock + if (bit_is_set(*_ucsra, UDRE0)) + _tx_udr_empty_irq(); + } // If we get here, nothing is queued anymore (DRIE is disabled) and // the hardware finished tranmission (TXC is set). } @@ -355,10 +362,19 @@ size_t HardwareSerial::write(uint8_t c) // If the output buffer is full, there's nothing for it other than to // wait for the interrupt handler to empty it a bit - // ???: return 0 here instead? - while (i == _tx_buffer_tail) - ; - + while (i == _tx_buffer_tail) { + if (bit_is_clear(SREG, SREG_I)) { + // 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(bit_is_set(*_ucsra, UDRE0)) + _tx_udr_empty_irq(); + } else { + // nop, the interrupt handler will free up space for us + } + } + _tx_buffer[_tx_buffer_head] = c; _tx_buffer_head = i; -- cgit v1.2.3-18-g5258 From f1cd85da7ad0030d1fb1102aa9e3119851e1c4aa Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Thu, 18 Apr 2013 19:06:00 +0200 Subject: Disable the UDRE interrupt sooner in HardwareSerial Before, the interrupt was disabled when it was triggered and it turned out there was no data to send. However, the interrupt can be disabled already when the last byte is written to the UART, since write() will always re-enable the interrupt when it adds new data to the buffer. Closes: #1008 --- cores/arduino/HardwareSerial.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/cores/arduino/HardwareSerial.cpp b/cores/arduino/HardwareSerial.cpp index ffdb82c..9a02f23 100644 --- a/cores/arduino/HardwareSerial.cpp +++ b/cores/arduino/HardwareSerial.cpp @@ -223,22 +223,22 @@ void HardwareSerial::_rx_complete_irq(void) void HardwareSerial::_tx_udr_empty_irq(void) { + // If interrupts are enabled, there must be more data in the output + // buffer. Send the next byte + unsigned char c = _tx_buffer[_tx_buffer_tail]; + _tx_buffer_tail = (_tx_buffer_tail + 1) % SERIAL_BUFFER_SIZE; + + *_udr = c; + + // clear the TXC bit -- "can be cleared by writing a one to its bit + // location". This makes sure flush() won't return until the bytes + // actually got written + sbi(*_ucsra, TXC0); + if (_tx_buffer_head == _tx_buffer_tail) { // Buffer empty, so disable interrupts cbi(*_ucsrb, UDRIE0); } - else { - // There is more data in the output buffer. Send the next byte - unsigned char c = _tx_buffer[_tx_buffer_tail]; - _tx_buffer_tail = (_tx_buffer_tail + 1) % SERIAL_BUFFER_SIZE; - - *_udr = c; - - // clear the TXC bit -- "can be cleared by writing a one to its bit - // location". This makes sure flush() won't return until the bytes - // actually got written - sbi(*_ucsra, TXC0); - } } // Constructors //////////////////////////////////////////////////////////////// -- cgit v1.2.3-18-g5258 From 99f7ef7c673e02d559eb55d064f38a105393c8f6 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Sun, 1 Dec 2013 17:21:54 +0100 Subject: Centrally decide which hardware UARTS are available Before, this decision was made in few different places, based on sometimes different register defines. Now, HardwareSerial.h decides wich UARTS are available, defines USE_HWSERIALn macros and HardwareSerial.cpp simply checks these macros (together with some #ifs to decide which registers to use for UART 0). For consistency, USBAPI.h also defines a HAVE_CDCSERIAL macro when applicable. For supported targets, this should change any behaviour. For unsupported targets, the error messages might subtly change because some checks are moved or changed. Additionally, this moves the USBAPI.h include form HardareSerial.h into Arduino.h and raises an error when both CDC serial and UART0 are available (previously this would silently use UART0 instead of CDC, but there is not currently any Atmel chip available for which this would occur). --- cores/arduino/Arduino.h | 4 +++ cores/arduino/HardwareSerial.cpp | 65 ++++++++++++++++------------------------ cores/arduino/HardwareSerial.h | 7 +++-- cores/arduino/USBAPI.h | 2 ++ 4 files changed, 35 insertions(+), 43 deletions(-) diff --git a/cores/arduino/Arduino.h b/cores/arduino/Arduino.h index 8502e44..a189777 100644 --- a/cores/arduino/Arduino.h +++ b/cores/arduino/Arduino.h @@ -200,6 +200,10 @@ extern const uint8_t PROGMEM digital_pin_to_timer_PGM[]; #include "WCharacter.h" #include "WString.h" #include "HardwareSerial.h" +#include "USBAPI.h" +#if defined(HAVE_HWSERIAL0) && defined(HAVE_CDCSERIAL) +#error "Targets with both UART0 and CDC serial not supported" +#endif uint16_t makeWord(uint16_t w); uint16_t makeWord(byte h, byte l); diff --git a/cores/arduino/HardwareSerial.cpp b/cores/arduino/HardwareSerial.cpp index 9a02f23..4318a1e 100644 --- a/cores/arduino/HardwareSerial.cpp +++ b/cores/arduino/HardwareSerial.cpp @@ -28,11 +28,11 @@ #include "Arduino.h" #include "wiring_private.h" +#include "HardwareSerial.h" + // this next line disables the entire HardwareSerial.cpp, // this is so I can support Attiny series and any other chip without a uart -#if defined(UBRRH) || defined(UBRR0H) || defined(UBRR1H) || defined(UBRR2H) || defined(UBRR3H) - -#include "HardwareSerial.h" +#if defined(HAVE_HWSERIAL0) || defined(HAVE_HWSERIAL1) || defined(HAVE_HWSERIAL2) || defined(HAVE_HWSERIAL3) // Ensure that the various bit positions we use are available with a 0 // postfix, so we can always use the values for UART0 for all UARTs. The @@ -84,53 +84,44 @@ #error "Not all bit positions for UART3 are the same as for UART0" #endif -#if !defined(USART0_RX_vect) && defined(USART1_RX_vect) -// do nothing - on the 32u4 the first USART is USART1 -#else -#if !defined(USART_RX_vect) && !defined(USART0_RX_vect) && \ - !defined(USART_RXC_vect) - #error "Don't know what the Data Received vector is called for the first UART" -#else +#if defined(HAVE_HWSERIAL0) void serialEvent() __attribute__((weak)); void serialEvent() {} - #define serialEvent_implemented #if defined(USART_RX_vect) ISR(USART_RX_vect) #elif defined(USART0_RX_vect) ISR(USART0_RX_vect) #elif defined(USART_RXC_vect) ISR(USART_RXC_vect) // ATmega8 +#else + #error "Don't know what the Data Received vector is called for the first UART" #endif { Serial._rx_complete_irq(); } #endif -#endif -#if defined(USART1_RX_vect) +#if defined(HAVE_HWSERIAL1) void serialEvent1() __attribute__((weak)); void serialEvent1() {} - #define serialEvent1_implemented ISR(USART1_RX_vect) { Serial1._rx_complete_irq(); } #endif -#if defined(USART2_RX_vect) && defined(UDR2) +#if defined(HAVE_HWSERIAL2) void serialEvent2() __attribute__((weak)); void serialEvent2() {} - #define serialEvent2_implemented ISR(USART2_RX_vect) { Serial2._rx_complete_irq(); } #endif -#if defined(USART3_RX_vect) && defined(UDR3) +#if defined(HAVE_HWSERIAL3) void serialEvent3() __attribute__((weak)); void serialEvent3() {} - #define serialEvent3_implemented ISR(USART3_RX_vect) { Serial3._rx_complete_irq(); @@ -139,27 +130,22 @@ void serialEventRun(void) { -#ifdef serialEvent_implemented +#if defined(HAVE_HWSERIAL0) if (Serial.available()) serialEvent(); #endif -#ifdef serialEvent1_implemented +#if defined(HAVE_HWSERIAL1) if (Serial1.available()) serialEvent1(); #endif -#ifdef serialEvent2_implemented +#if defined(HAVE_HWSERIAL2) if (Serial2.available()) serialEvent2(); #endif -#ifdef serialEvent3_implemented +#if defined(HAVE_HWSERIAL3) if (Serial3.available()) serialEvent3(); #endif } -#if !defined(USART0_UDRE_vect) && defined(USART1_UDRE_vect) -// do nothing - on the 32u4 the first USART is USART1 -#else -#if !defined(UART0_UDRE_vect) && !defined(UART_UDRE_vect) && !defined(USART0_UDRE_vect) && !defined(USART_UDRE_vect) - #error "Don't know what the Data Register Empty vector is called for the first UART" -#else +#if defined(HAVE_HWSERIAL0) #if defined(UART0_UDRE_vect) ISR(UART0_UDRE_vect) #elif defined(UART_UDRE_vect) @@ -168,28 +154,29 @@ ISR(UART_UDRE_vect) ISR(USART0_UDRE_vect) #elif defined(USART_UDRE_vect) ISR(USART_UDRE_vect) +#else + #error "Don't know what the Data Register Empty vector is called for the first UART" #endif { Serial._tx_udr_empty_irq(); } #endif -#endif -#ifdef USART1_UDRE_vect +#if defined(HAVE_HWSERIAL1) ISR(USART1_UDRE_vect) { Serial1._tx_udr_empty_irq(); } #endif -#ifdef USART2_UDRE_vect +#if defined(HAVE_HWSERIAL2) ISR(USART2_UDRE_vect) { Serial2._tx_udr_empty_irq(); } #endif -#ifdef USART3_UDRE_vect +#if defined(HAVE_HWSERIAL3) ISR(USART3_UDRE_vect) { Serial3._tx_udr_empty_irq(); @@ -386,23 +373,21 @@ size_t HardwareSerial::write(uint8_t c) // Preinstantiate Objects ////////////////////////////////////////////////////// +#if defined(HAVE_HWSERIAL0) #if defined(UBRRH) && defined(UBRRL) HardwareSerial Serial(&UBRRH, &UBRRL, &UCSRA, &UCSRB, &UCSRC, &UDR); -#elif defined(UBRR0H) && defined(UBRR0L) - HardwareSerial Serial(&UBRR0H, &UBRR0L, &UCSR0A, &UCSR0B, &UCSR0C, &UDR0); -#elif defined(USBCON) - // do nothing - Serial object and buffers are initialized in CDC code #else - #error no serial port defined (port 0) + HardwareSerial Serial(&UBRR0H, &UBRR0L, &UCSR0A, &UCSR0B, &UCSR0C, &UDR0); +#endif #endif -#if defined(UBRR1H) +#if defined(HAVE_HWSERIAL1) HardwareSerial Serial1(&UBRR1H, &UBRR1L, &UCSR1A, &UCSR1B, &UCSR1C, &UDR1); #endif -#if defined(UBRR2H) +#if defined(HAVE_HWSERIAL2) HardwareSerial Serial2(&UBRR2H, &UBRR2L, &UCSR2A, &UCSR2B, &UCSR2C, &UDR2); #endif -#if defined(UBRR3H) +#if defined(HAVE_HWSERIAL3) HardwareSerial Serial3(&UBRR3H, &UBRR3L, &UCSR3A, &UCSR3B, &UCSR3C, &UDR3); #endif diff --git a/cores/arduino/HardwareSerial.h b/cores/arduino/HardwareSerial.h index 0ae51f4..da9efbc 100644 --- a/cores/arduino/HardwareSerial.h +++ b/cores/arduino/HardwareSerial.h @@ -113,18 +113,19 @@ class HardwareSerial : public Stream #if defined(UBRRH) || defined(UBRR0H) extern HardwareSerial Serial; -#elif defined(USBCON) - #include "USBAPI.h" -// extern HardwareSerial Serial_; + #define HAVE_HWSERIAL0 #endif #if defined(UBRR1H) extern HardwareSerial Serial1; + #define HAVE_HWSERIAL1 #endif #if defined(UBRR2H) extern HardwareSerial Serial2; + #define HAVE_HWSERIAL2 #endif #if defined(UBRR3H) extern HardwareSerial Serial3; + #define HAVE_HWSERIAL3 #endif extern void serialEventRun(void) __attribute__((weak)); diff --git a/cores/arduino/USBAPI.h b/cores/arduino/USBAPI.h index da2e869..5a33002 100644 --- a/cores/arduino/USBAPI.h +++ b/cores/arduino/USBAPI.h @@ -55,6 +55,8 @@ public: }; extern Serial_ Serial; +#define HAVE_CDCSERIAL + //================================================================================ //================================================================================ // Mouse -- cgit v1.2.3-18-g5258 From 1848db3d66e232b10d7e701063f1238809cf6a53 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Tue, 3 Dec 2013 21:15:17 +0100 Subject: Put each HardwareSerial instance in its own .cpp file By putting the ISRs and HardwareSerial instance for each instance in a separate compilation unit, the compile will only consider them for linking when the instance is actually used. The ISR is always referenced by the compiler runtime and the Serialx_available() function is always referenced by SerialEventRun(), but both references are weak and thus do not cause the compilation to be included in the link by themselves. The effect of this is that when multiple HardwareSerial ports are available, but not all are used, buffers are only allocated and ISRs are only included for the serial ports that are used. On the mega, this lowers memory usage from 653 bytes to just 182 when only using the first serial port. On boards with just a single port, there is no change, since the code and memory was already left out when no serial port was used at all. This fixes #1425 and fixes #1259. --- cores/arduino/HardwareSerial.cpp | 105 +++++--------------------------------- cores/arduino/HardwareSerial0.cpp | 54 ++++++++++++++++++++ cores/arduino/HardwareSerial1.cpp | 54 ++++++++++++++++++++ cores/arduino/HardwareSerial2.cpp | 54 ++++++++++++++++++++ cores/arduino/HardwareSerial3.cpp | 54 ++++++++++++++++++++ 5 files changed, 229 insertions(+), 92 deletions(-) create mode 100644 cores/arduino/HardwareSerial0.cpp create mode 100644 cores/arduino/HardwareSerial1.cpp create mode 100644 cores/arduino/HardwareSerial2.cpp create mode 100644 cores/arduino/HardwareSerial3.cpp diff --git a/cores/arduino/HardwareSerial.cpp b/cores/arduino/HardwareSerial.cpp index 4318a1e..d7f1fca 100644 --- a/cores/arduino/HardwareSerial.cpp +++ b/cores/arduino/HardwareSerial.cpp @@ -84,105 +84,46 @@ #error "Not all bit positions for UART3 are the same as for UART0" #endif +// SerialEvent functions are weak, so when the user doesn't define them, +// the linker just sets their address to 0 (which is checked below). +// The Serialx_available is just a wrapper around Serialx.available(), +// but we can refer to it weakly so we don't pull in the entire +// HardwareSerial instance if the user doesn't also refer to it. #if defined(HAVE_HWSERIAL0) void serialEvent() __attribute__((weak)); - void serialEvent() {} -#if defined(USART_RX_vect) - ISR(USART_RX_vect) -#elif defined(USART0_RX_vect) - ISR(USART0_RX_vect) -#elif defined(USART_RXC_vect) - ISR(USART_RXC_vect) // ATmega8 -#else - #error "Don't know what the Data Received vector is called for the first UART" -#endif - { - Serial._rx_complete_irq(); - } + bool Serial0_available() __attribute__((weak)); #endif #if defined(HAVE_HWSERIAL1) void serialEvent1() __attribute__((weak)); - void serialEvent1() {} - ISR(USART1_RX_vect) - { - Serial1._rx_complete_irq(); - } + bool Serial1_available() __attribute__((weak)); #endif #if defined(HAVE_HWSERIAL2) void serialEvent2() __attribute__((weak)); - void serialEvent2() {} - ISR(USART2_RX_vect) - { - Serial2._rx_complete_irq(); - } + bool Serial2_available() __attribute__((weak)); #endif #if defined(HAVE_HWSERIAL3) void serialEvent3() __attribute__((weak)); - void serialEvent3() {} - ISR(USART3_RX_vect) - { - Serial3._rx_complete_irq(); - } + bool Serial3_available() __attribute__((weak)); #endif void serialEventRun(void) { #if defined(HAVE_HWSERIAL0) - if (Serial.available()) serialEvent(); + if (Serial0_available && serialEvent && Serial0_available()) serialEvent(); #endif #if defined(HAVE_HWSERIAL1) - if (Serial1.available()) serialEvent1(); + if (Serial1_available && serialEvent1 && Serial1_available()) serialEvent1(); #endif #if defined(HAVE_HWSERIAL2) - if (Serial2.available()) serialEvent2(); + if (Serial2_available && serialEvent2 && Serial2_available()) serialEvent2(); #endif #if defined(HAVE_HWSERIAL3) - if (Serial3.available()) serialEvent3(); -#endif -} - - -#if defined(HAVE_HWSERIAL0) -#if defined(UART0_UDRE_vect) -ISR(UART0_UDRE_vect) -#elif defined(UART_UDRE_vect) -ISR(UART_UDRE_vect) -#elif defined(USART0_UDRE_vect) -ISR(USART0_UDRE_vect) -#elif defined(USART_UDRE_vect) -ISR(USART_UDRE_vect) -#else - #error "Don't know what the Data Register Empty vector is called for the first UART" -#endif -{ - Serial._tx_udr_empty_irq(); -} + if (Serial3_available && serialEvent2 && Serial3_available()) serialEvent3(); #endif - -#if defined(HAVE_HWSERIAL1) -ISR(USART1_UDRE_vect) -{ - Serial1._tx_udr_empty_irq(); } -#endif - -#if defined(HAVE_HWSERIAL2) -ISR(USART2_UDRE_vect) -{ - Serial2._tx_udr_empty_irq(); -} -#endif - -#if defined(HAVE_HWSERIAL3) -ISR(USART3_UDRE_vect) -{ - Serial3._tx_udr_empty_irq(); -} -#endif - // Actual interrupt handlers ////////////////////////////////////////////////////////////// @@ -371,25 +312,5 @@ size_t HardwareSerial::write(uint8_t c) return 1; } -// Preinstantiate Objects ////////////////////////////////////////////////////// - -#if defined(HAVE_HWSERIAL0) -#if defined(UBRRH) && defined(UBRRL) - HardwareSerial Serial(&UBRRH, &UBRRL, &UCSRA, &UCSRB, &UCSRC, &UDR); -#else - HardwareSerial Serial(&UBRR0H, &UBRR0L, &UCSR0A, &UCSR0B, &UCSR0C, &UDR0); -#endif -#endif - -#if defined(HAVE_HWSERIAL1) - HardwareSerial Serial1(&UBRR1H, &UBRR1L, &UCSR1A, &UCSR1B, &UCSR1C, &UDR1); -#endif -#if defined(HAVE_HWSERIAL2) - HardwareSerial Serial2(&UBRR2H, &UBRR2L, &UCSR2A, &UCSR2B, &UCSR2C, &UDR2); -#endif -#if defined(HAVE_HWSERIAL3) - HardwareSerial Serial3(&UBRR3H, &UBRR3L, &UCSR3A, &UCSR3B, &UCSR3C, &UDR3); -#endif #endif // whole file - diff --git a/cores/arduino/HardwareSerial0.cpp b/cores/arduino/HardwareSerial0.cpp new file mode 100644 index 0000000..dd4d806 --- /dev/null +++ b/cores/arduino/HardwareSerial0.cpp @@ -0,0 +1,54 @@ +#include "Arduino.h" +#include "HardwareSerial.h" + +// Each HardwareSerial is defined in its own file, sine the linker pulls +// in the entire file when any element inside is used. --gc-sections can +// additionally cause unused symbols to be dropped, but ISRs have the +// "used" attribute so are never dropped and they keep the +// HardwareSerial instance in as well. Putting each instance in its own +// file prevents the linker from pulling in any unused instances in the +// first place. + +#if defined(HAVE_HWSERIAL0) + +#if defined(USART_RX_vect) + ISR(USART_RX_vect) +#elif defined(USART0_RX_vect) + ISR(USART0_RX_vect) +#elif defined(USART_RXC_vect) + ISR(USART_RXC_vect) // ATmega8 +#else + #error "Don't know what the Data Received vector is called for the first UART" +#endif + { + Serial._rx_complete_irq(); + } + +#if defined(UART0_UDRE_vect) +ISR(UART0_UDRE_vect) +#elif defined(UART_UDRE_vect) +ISR(UART_UDRE_vect) +#elif defined(USART0_UDRE_vect) +ISR(USART0_UDRE_vect) +#elif defined(USART_UDRE_vect) +ISR(USART_UDRE_vect) +#else + #error "Don't know what the Data Register Empty vector is called for the first UART" +#endif +{ + Serial._tx_udr_empty_irq(); +} + +#if defined(UBRRH) && defined(UBRRL) + HardwareSerial Serial(&UBRRH, &UBRRL, &UCSRA, &UCSRB, &UCSRC, &UDR); +#else + HardwareSerial Serial(&UBRR0H, &UBRR0L, &UCSR0A, &UCSR0B, &UCSR0C, &UDR0); +#endif + +// Function that can be weakly referenced by serialEventRun to prevent +// pulling in this file if it's not otherwise used. +bool Serial0_available() { + return Serial.available(); +} + +#endif // HAVE_HWSERIAL0 diff --git a/cores/arduino/HardwareSerial1.cpp b/cores/arduino/HardwareSerial1.cpp new file mode 100644 index 0000000..c04263e --- /dev/null +++ b/cores/arduino/HardwareSerial1.cpp @@ -0,0 +1,54 @@ +#include "Arduino.h" +#include "HardwareSerial.h" + +// Each HardwareSerial is defined in its own file, sine the linker pulls +// in the entire file when any element inside is used. --gc-sections can +// additionally cause unused symbols to be dropped, but ISRs have the +// "used" attribute so are never dropped and they keep the +// HardwareSerial instance in as well. Putting each instance in its own +// file prevents the linker from pulling in any unused instances in the +// first place. + +#if defined(HAVE_HWSERIAL1) + +#if defined(USART_RX_vect) + ISR(USART_RX_vect) +#elif defined(USART1_RX_vect) + ISR(USART1_RX_vect) +#elif defined(USART_RXC_vect) + ISR(USART_RXC_vect) // ATmega8 +#else + #error "Don't know what the Data Received vector is called for the first UART" +#endif + { + Serial1._rx_complete_irq(); + } + +#if defined(UART1_UDRE_vect) +ISR(UART1_UDRE_vect) +#elif defined(UART_UDRE_vect) +ISR(UART_UDRE_vect) +#elif defined(USART1_UDRE_vect) +ISR(USART1_UDRE_vect) +#elif defined(USART_UDRE_vect) +ISR(USART_UDRE_vect) +#else + #error "Don't know what the Data Register Empty vector is called for the first UART" +#endif +{ + Serial1._tx_udr_empty_irq(); +} + +#if defined(UBRRH) && defined(UBRRL) + HardwareSerial Serial1(&UBRRH, &UBRRL, &UCSRA, &UCSRB, &UCSRC, &UDR); +#else + HardwareSerial Serial1(&UBRR1H, &UBRR1L, &UCSR1A, &UCSR1B, &UCSR1C, &UDR1); +#endif + +// Function that can be weakly referenced by serialEventRun to prevent +// pulling in this file if it's not otherwise used. +bool Serial1_available() { + return Serial1.available(); +} + +#endif // HAVE_HWSERIAL1 diff --git a/cores/arduino/HardwareSerial2.cpp b/cores/arduino/HardwareSerial2.cpp new file mode 100644 index 0000000..2c448f9 --- /dev/null +++ b/cores/arduino/HardwareSerial2.cpp @@ -0,0 +1,54 @@ +#include "Arduino.h" +#include "HardwareSerial.h" + +// Each HardwareSerial is defined in its own file, sine the linker pulls +// in the entire file when any element inside is used. --gc-sections can +// additionally cause unused symbols to be dropped, but ISRs have the +// "used" attribute so are never dropped and they keep the +// HardwareSerial instance in as well. Putting each instance in its own +// file prevents the linker from pulling in any unused instances in the +// first place. + +#if defined(HAVE_HWSERIAL2) + +#if defined(USART_RX_vect) + ISR(USART_RX_vect) +#elif defined(USART2_RX_vect) + ISR(USART2_RX_vect) +#elif defined(USART_RXC_vect) + ISR(USART_RXC_vect) // ATmega8 +#else + #error "Don't know what the Data Received vector is called for the first UART" +#endif + { + Serial2._rx_complete_irq(); + } + +#if defined(UART2_UDRE_vect) +ISR(UART2_UDRE_vect) +#elif defined(UART_UDRE_vect) +ISR(UART_UDRE_vect) +#elif defined(USART2_UDRE_vect) +ISR(USART2_UDRE_vect) +#elif defined(USART_UDRE_vect) +ISR(USART_UDRE_vect) +#else + #error "Don't know what the Data Register Empty vector is called for the first UART" +#endif +{ + Serial2._tx_udr_empty_irq(); +} + +#if defined(UBRRH) && defined(UBRRL) + HardwareSerial Serial2(&UBRRH, &UBRRL, &UCSRA, &UCSRB, &UCSRC, &UDR); +#else + HardwareSerial Serial2(&UBRR2H, &UBRR2L, &UCSR2A, &UCSR2B, &UCSR2C, &UDR2); +#endif + +// Function that can be weakly referenced by serialEventRun to prevent +// pulling in this file if it's not otherwise used. +bool Serial2_available() { + return Serial2.available(); +} + +#endif // HAVE_HWSERIAL2 diff --git a/cores/arduino/HardwareSerial3.cpp b/cores/arduino/HardwareSerial3.cpp new file mode 100644 index 0000000..012839d --- /dev/null +++ b/cores/arduino/HardwareSerial3.cpp @@ -0,0 +1,54 @@ +#include "Arduino.h" +#include "HardwareSerial.h" + +// Each HardwareSerial is defined in its own file, sine the linker pulls +// in the entire file when any element inside is used. --gc-sections can +// additionally cause unused symbols to be dropped, but ISRs have the +// "used" attribute so are never dropped and they keep the +// HardwareSerial instance in as well. Putting each instance in its own +// file prevents the linker from pulling in any unused instances in the +// first place. + +#if defined(HAVE_HWSERIAL3) + +#if defined(USART_RX_vect) + ISR(USART_RX_vect) +#elif defined(USART3_RX_vect) + ISR(USART3_RX_vect) +#elif defined(USART_RXC_vect) + ISR(USART_RXC_vect) // ATmega8 +#else + #error "Don't know what the Data Received vector is called for the first UART" +#endif + { + Serial3._rx_complete_irq(); + } + +#if defined(UART3_UDRE_vect) +ISR(UART3_UDRE_vect) +#elif defined(UART_UDRE_vect) +ISR(UART_UDRE_vect) +#elif defined(USART3_UDRE_vect) +ISR(USART3_UDRE_vect) +#elif defined(USART_UDRE_vect) +ISR(USART_UDRE_vect) +#else + #error "Don't know what the Data Register Empty vector is called for the first UART" +#endif +{ + Serial3._tx_udr_empty_irq(); +} + +#if defined(UBRRH) && defined(UBRRL) + HardwareSerial Serial3(&UBRRH, &UBRRL, &UCSRA, &UCSRB, &UCSRC, &UDR); +#else + HardwareSerial Serial3(&UBRR3H, &UBRR3L, &UCSR3A, &UCSR3B, &UCSR3C, &UDR3); +#endif + +// Function that can be weakly referenced by serialEventRun to prevent +// pulling in this file if it's not otherwise used. +bool Serial3_available() { + return Serial3.available(); +} + +#endif // HAVE_HWSERIAL3 -- cgit v1.2.3-18-g5258 From 49fc2ab8ad43b86f7668da8d0186abe0d48cd6d9 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Wed, 22 Jan 2014 10:12:56 +0100 Subject: Inlined HardwareSerial calls to RX ISR. Moreover, declaring pointers-to-registers as const and using initializer list in class constructor allows the compiler to further improve inlining performance. This change recovers about 50 bytes of program space on single-UART devices. See #1711 --- cores/arduino/HardwareSerial.cpp | 91 +------------------------------- cores/arduino/HardwareSerial.h | 16 +++--- cores/arduino/HardwareSerial0.cpp | 1 + cores/arduino/HardwareSerial1.cpp | 1 + cores/arduino/HardwareSerial2.cpp | 1 + cores/arduino/HardwareSerial3.cpp | 1 + cores/arduino/HardwareSerial_private.h | 95 ++++++++++++++++++++++++++++++++++ 7 files changed, 108 insertions(+), 98 deletions(-) create mode 100644 cores/arduino/HardwareSerial_private.h diff --git a/cores/arduino/HardwareSerial.cpp b/cores/arduino/HardwareSerial.cpp index d7f1fca..5d62273 100644 --- a/cores/arduino/HardwareSerial.cpp +++ b/cores/arduino/HardwareSerial.cpp @@ -26,64 +26,14 @@ #include #include #include "Arduino.h" -#include "wiring_private.h" #include "HardwareSerial.h" +#include "HardwareSerial_private.h" // this next line disables the entire HardwareSerial.cpp, // this is so I can support Attiny series and any other chip without a uart #if defined(HAVE_HWSERIAL0) || defined(HAVE_HWSERIAL1) || defined(HAVE_HWSERIAL2) || defined(HAVE_HWSERIAL3) -// Ensure that the various bit positions we use are available with a 0 -// postfix, so we can always use the values for UART0 for all UARTs. The -// alternative, passing the various values for each UART to the -// HardwareSerial constructor also works, but makes the code bigger and -// slower. -#if !defined(TXC0) -#if defined(TXC) -// On ATmega8, the uart and its bits are not numbered, so there is no TXC0 etc. -#define TXC0 TXC -#define RXEN0 RXEN -#define TXEN0 TXEN -#define RXCIE0 RXCIE -#define UDRIE0 UDRIE -#define U2X0 U2X -#define UPE0 UPE -#define UDRE0 UDRE -#elif defined(TXC1) -// Some devices have uart1 but no uart0 -#define TXC0 TXC1 -#define RXEN0 RXEN1 -#define TXEN0 TXEN1 -#define RXCIE0 RXCIE1 -#define UDRIE0 UDRIE1 -#define U2X0 U2X1 -#define UPE0 UPE1 -#define UDRE0 UDRE1 -#else -#error No UART found in HardwareSerial.cpp -#endif -#endif // !defined TXC0 - -// Check at compiletime that it is really ok to use the bit positions of -// UART0 for the other UARTs as well, in case these values ever get -// changed for future hardware. -#if defined(TXC1) && (TXC1 != TXC0 || RXEN1 != RXEN0 || RXCIE1 != RXCIE0 || \ - UDRIE1 != UDRIE0 || U2X1 != U2X0 || UPE1 != UPE0 || \ - UDRE1 != UDRE0) -#error "Not all bit positions for UART1 are the same as for UART0" -#endif -#if defined(TXC2) && (TXC2 != TXC0 || RXEN2 != RXEN0 || RXCIE2 != RXCIE0 || \ - UDRIE2 != UDRIE0 || U2X2 != U2X0 || UPE2 != UPE0 || \ - UDRE2 != UDRE0) -#error "Not all bit positions for UART2 are the same as for UART0" -#endif -#if defined(TXC3) && (TXC3 != TXC0 || RXEN3 != RXEN0 || RXCIE3 != RXCIE0 || \ - UDRIE3 != UDRIE0 || U3X3 != U3X0 || UPE3 != UPE0 || \ - UDRE3 != UDRE0) -#error "Not all bit positions for UART3 are the same as for UART0" -#endif - // SerialEvent functions are weak, so when the user doesn't define them, // the linker just sets their address to 0 (which is checked below). // The Serialx_available is just a wrapper around Serialx.available(), @@ -127,28 +77,6 @@ void serialEventRun(void) // Actual interrupt handlers ////////////////////////////////////////////////////////////// -void HardwareSerial::_rx_complete_irq(void) -{ - if (bit_is_clear(*_ucsra, UPE0)) { - // No Parity error, read byte and store it in the buffer if there is - // room - unsigned char c = *_udr; - int i = (unsigned int)(_rx_buffer_head + 1) % SERIAL_BUFFER_SIZE; - - // if we should be storing the received character into the location - // just before the tail (meaning that the head would advance to the - // current location of the tail), we're about to overflow the buffer - // and so we don't write the character or advance the head. - if (i != _rx_buffer_tail) { - _rx_buffer[_rx_buffer_head] = c; - _rx_buffer_head = i; - } - } else { - // Parity error, read byte but discard it - unsigned char c = *_udr; - }; -} - void HardwareSerial::_tx_udr_empty_irq(void) { // If interrupts are enabled, there must be more data in the output @@ -169,23 +97,6 @@ void HardwareSerial::_tx_udr_empty_irq(void) } } -// Constructors //////////////////////////////////////////////////////////////// - -HardwareSerial::HardwareSerial( - volatile uint8_t *ubrrh, volatile uint8_t *ubrrl, - volatile uint8_t *ucsra, volatile uint8_t *ucsrb, - volatile uint8_t *ucsrc, volatile uint8_t *udr) -{ - _tx_buffer_head = _tx_buffer_tail = 0; - _rx_buffer_head = _rx_buffer_tail = 0; - _ubrrh = ubrrh; - _ubrrl = ubrrl; - _ucsra = ucsra; - _ucsrb = ucsrb; - _ucsrc = ucsrc; - _udr = udr; -} - // Public Methods ////////////////////////////////////////////////////////////// void HardwareSerial::begin(unsigned long baud, byte config) diff --git a/cores/arduino/HardwareSerial.h b/cores/arduino/HardwareSerial.h index da9efbc..bd3c4c4 100644 --- a/cores/arduino/HardwareSerial.h +++ b/cores/arduino/HardwareSerial.h @@ -66,12 +66,12 @@ class HardwareSerial : public Stream { protected: - volatile uint8_t *_ubrrh; - volatile uint8_t *_ubrrl; - volatile uint8_t *_ucsra; - volatile uint8_t *_ucsrb; - volatile uint8_t *_ucsrc; - volatile uint8_t *_udr; + volatile uint8_t * const _ubrrh; + volatile uint8_t * const _ubrrl; + volatile uint8_t * const _ucsra; + volatile uint8_t * const _ucsrb; + volatile uint8_t * const _ucsrc; + volatile uint8_t * const _udr; // Has any byte been written to the UART since begin() bool _written; @@ -87,7 +87,7 @@ class HardwareSerial : public Stream unsigned char _tx_buffer[SERIAL_BUFFER_SIZE]; public: - HardwareSerial( + inline HardwareSerial( volatile uint8_t *ubrrh, volatile uint8_t *ubrrl, volatile uint8_t *ucsra, volatile uint8_t *ucsrb, volatile uint8_t *ucsrc, volatile uint8_t *udr); @@ -107,7 +107,7 @@ class HardwareSerial : public Stream operator bool() { return true; } // Interrupt handlers - Not intended to be called externally - void _rx_complete_irq(void); + inline void _rx_complete_irq(void); void _tx_udr_empty_irq(void); }; diff --git a/cores/arduino/HardwareSerial0.cpp b/cores/arduino/HardwareSerial0.cpp index dd4d806..efbd527 100644 --- a/cores/arduino/HardwareSerial0.cpp +++ b/cores/arduino/HardwareSerial0.cpp @@ -1,5 +1,6 @@ #include "Arduino.h" #include "HardwareSerial.h" +#include "HardwareSerial_private.h" // Each HardwareSerial is defined in its own file, sine the linker pulls // in the entire file when any element inside is used. --gc-sections can diff --git a/cores/arduino/HardwareSerial1.cpp b/cores/arduino/HardwareSerial1.cpp index c04263e..089f35b 100644 --- a/cores/arduino/HardwareSerial1.cpp +++ b/cores/arduino/HardwareSerial1.cpp @@ -1,5 +1,6 @@ #include "Arduino.h" #include "HardwareSerial.h" +#include "HardwareSerial_private.h" // Each HardwareSerial is defined in its own file, sine the linker pulls // in the entire file when any element inside is used. --gc-sections can diff --git a/cores/arduino/HardwareSerial2.cpp b/cores/arduino/HardwareSerial2.cpp index 2c448f9..aa1397c 100644 --- a/cores/arduino/HardwareSerial2.cpp +++ b/cores/arduino/HardwareSerial2.cpp @@ -1,5 +1,6 @@ #include "Arduino.h" #include "HardwareSerial.h" +#include "HardwareSerial_private.h" // Each HardwareSerial is defined in its own file, sine the linker pulls // in the entire file when any element inside is used. --gc-sections can diff --git a/cores/arduino/HardwareSerial3.cpp b/cores/arduino/HardwareSerial3.cpp index 012839d..0140647 100644 --- a/cores/arduino/HardwareSerial3.cpp +++ b/cores/arduino/HardwareSerial3.cpp @@ -1,5 +1,6 @@ #include "Arduino.h" #include "HardwareSerial.h" +#include "HardwareSerial_private.h" // Each HardwareSerial is defined in its own file, sine the linker pulls // in the entire file when any element inside is used. --gc-sections can diff --git a/cores/arduino/HardwareSerial_private.h b/cores/arduino/HardwareSerial_private.h new file mode 100644 index 0000000..54a0c7a --- /dev/null +++ b/cores/arduino/HardwareSerial_private.h @@ -0,0 +1,95 @@ +#include "wiring_private.h" + +// this next line disables the entire HardwareSerial.cpp, +// this is so I can support Attiny series and any other chip without a uart +#if defined(HAVE_HWSERIAL0) || defined(HAVE_HWSERIAL1) || defined(HAVE_HWSERIAL2) || defined(HAVE_HWSERIAL3) + +// Ensure that the various bit positions we use are available with a 0 +// postfix, so we can always use the values for UART0 for all UARTs. The +// alternative, passing the various values for each UART to the +// HardwareSerial constructor also works, but makes the code bigger and +// slower. +#if !defined(TXC0) +#if defined(TXC) +// On ATmega8, the uart and its bits are not numbered, so there is no TXC0 etc. +#define TXC0 TXC +#define RXEN0 RXEN +#define TXEN0 TXEN +#define RXCIE0 RXCIE +#define UDRIE0 UDRIE +#define U2X0 U2X +#define UPE0 UPE +#define UDRE0 UDRE +#elif defined(TXC1) +// Some devices have uart1 but no uart0 +#define TXC0 TXC1 +#define RXEN0 RXEN1 +#define TXEN0 TXEN1 +#define RXCIE0 RXCIE1 +#define UDRIE0 UDRIE1 +#define U2X0 U2X1 +#define UPE0 UPE1 +#define UDRE0 UDRE1 +#else +#error No UART found in HardwareSerial.cpp +#endif +#endif // !defined TXC0 + +// Check at compiletime that it is really ok to use the bit positions of +// UART0 for the other UARTs as well, in case these values ever get +// changed for future hardware. +#if defined(TXC1) && (TXC1 != TXC0 || RXEN1 != RXEN0 || RXCIE1 != RXCIE0 || \ + UDRIE1 != UDRIE0 || U2X1 != U2X0 || UPE1 != UPE0 || \ + UDRE1 != UDRE0) +#error "Not all bit positions for UART1 are the same as for UART0" +#endif +#if defined(TXC2) && (TXC2 != TXC0 || RXEN2 != RXEN0 || RXCIE2 != RXCIE0 || \ + UDRIE2 != UDRIE0 || U2X2 != U2X0 || UPE2 != UPE0 || \ + UDRE2 != UDRE0) +#error "Not all bit positions for UART2 are the same as for UART0" +#endif +#if defined(TXC3) && (TXC3 != TXC0 || RXEN3 != RXEN0 || RXCIE3 != RXCIE0 || \ + UDRIE3 != UDRIE0 || U3X3 != U3X0 || UPE3 != UPE0 || \ + UDRE3 != UDRE0) +#error "Not all bit positions for UART3 are the same as for UART0" +#endif + +// Constructors //////////////////////////////////////////////////////////////// + +HardwareSerial::HardwareSerial( + volatile uint8_t *ubrrh, volatile uint8_t *ubrrl, + volatile uint8_t *ucsra, volatile uint8_t *ucsrb, + volatile uint8_t *ucsrc, volatile uint8_t *udr) : + _ubrrh(ubrrh), _ubrrl(ubrrl), + _ucsra(ucsra), _ucsrb(ucsrb), _ucsrc(ucsrc), + _udr(udr), + _tx_buffer_head(0), _tx_buffer_tail(0), + _rx_buffer_head(0), _rx_buffer_tail(0) +{ +} + +// Actual interrupt handlers ////////////////////////////////////////////////////////////// + +void HardwareSerial::_rx_complete_irq(void) +{ + if (bit_is_clear(*_ucsra, UPE0)) { + // No Parity error, read byte and store it in the buffer if there is + // room + unsigned char c = *_udr; + int i = (unsigned int)(_rx_buffer_head + 1) % SERIAL_BUFFER_SIZE; + + // if we should be storing the received character into the location + // just before the tail (meaning that the head would advance to the + // current location of the tail), we're about to overflow the buffer + // and so we don't write the character or advance the head. + if (i != _rx_buffer_tail) { + _rx_buffer[_rx_buffer_head] = c; + _rx_buffer_head = i; + } + } else { + // Parity error, read byte but discard it + unsigned char c = *_udr; + }; +} + +#endif // whole file -- cgit v1.2.3-18-g5258 From c3cd35f1979290d072ab77a6805dc4419f2ef6fb Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Wed, 18 Dec 2013 23:21:45 +0100 Subject: In HardwareSerial::write, bypass the queue when it's empty This helps improve the effective datarate on high (>500kbit/s) bitrates, by skipping the interrupt and associated overhead. At 1 Mbit/s the implementation previously got up to about 600-700 kbit/s, but now it actually gets up to the 1Mbit/s (values are rough estimates, though). --- cores/arduino/HardwareSerial.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/cores/arduino/HardwareSerial.cpp b/cores/arduino/HardwareSerial.cpp index 5d62273..9f0a2ec 100644 --- a/cores/arduino/HardwareSerial.cpp +++ b/cores/arduino/HardwareSerial.cpp @@ -197,6 +197,15 @@ void HardwareSerial::flush() size_t HardwareSerial::write(uint8_t c) { + // 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 (_tx_buffer_head == _tx_buffer_tail && bit_is_set(*_ucsra, UDRE0)) { + *_udr = c; + sbi(*_ucsra, TXC0); + return 1; + } int i = (_tx_buffer_head + 1) % SERIAL_BUFFER_SIZE; // If the output buffer is full, there's nothing for it other than to -- cgit v1.2.3-18-g5258