2

I have an arduino project that reads a config file from SD and then receives data on a serial port that is specifed in the config file.

I therefore need to instantiate the Serial (Serial0), Serial1,..2,3, etc based on dynamic data.

I am using a mega2560 with 4x HardwareSerial. Serial (also Serial0) is the USB. Serial 1, 2, 3 are on specific hardware pins.

I started with code something like:

uint8_t InitPort(uint8_t port) {
    switch (port) {
        case 0:
            if(Serial.begin(9600) return 1;
            break;
        case 1:
            if(Serial1.begin(9600) return 1;
            break;

        ... etc...

        default:
            return 0;
}

However, this instantiates all Serialx objects (using 157 bytes of data/bss each).

I've since written a class wrapper that takes a referece to the desired serial object as an initialising paramter, but this just pushes the problem up-stack to the calling code.

Is there a way that a Serial object can be instanitated dynamically with a port number passed in, to only end up with one instance connected to the desired port?

8
  • Do you speak about SoftwareSerial interfaces or the hardware interfaces? – chrisl Jun 17 '19 at 13:43
  • 2
    they are all instanced, but optimized away if the code doesn't use it. the class of Serials depends on the board. on some boards Serial is USB and Setial1 is HardwareSerial. but on boards with 328p it is something like HardwareSerial Serial(&UBRRH, &UBRRL, &UCSRA, &UCSRB, &UCSRC, &UDR); – Juraj Jun 17 '19 at 14:04
  • Post editied to clarify mega2560 - hardware serial. – BrendanMcL Jun 17 '19 at 23:03
  • @Juraj they are not optimised away by the linker in my appliction (which is the problem) becuase the linker does not know which one will be use at runtime - this is based on user config data read from the SD. So it includes all 4 with assoicated memory use which is the underlying problem. – BrendanMcL Jun 17 '19 at 23:06
  • all are used in your sketch in some case, so must be in the final build. – Juraj Jun 18 '19 at 4:30
3

You can create an instance of HardwareSerial referencing a hardware port that depends on a function parameter, as in

void initPort(uint8_t port) {
    switch (port) {
        case 0: {
            HardwareSerial serial(&UBRR0H, &UBRR0L,
                     &UCSR0A, &UCSR0B, &UCSR0C, &UDR0);
            serial.begin(9600);
            break;
        }
        case 1:
            // etc...
    }
}

However, since only a local variable can be created in this manner, it is not of much use. Ideally, you would want to have a global variable for the serial port, and initialize it in initPort(), as

HardwareSerial serial;

void initPort(uint8_t port) {
    switch (port) {
        case 0:
            // Invoke fictional init() method.
            serial.init(&UBRR0H, &UBRR0L,
                     &UCSR0A, &UCSR0B, &UCSR0C, &UDR0);
            break;
        case 1:
            // etc...
    }
    serial.begin(9600);
}

Alas, this is not possible, as the HardwareSerial class has no default constructor. This means that binding the object to the hardware port can only be done when it is constructed.

Short of modifying the Arduino core (adding a default constructor and an init() method), the only option I can see is to have the global variable be a pointer. Then, in initPort() the object is instantiated while initializing the pointer. I tried this approach in the example program below. There was a slight technical difficulty though: since the object files from the core defining Serial, Serail1, etc. will hopefully not be linked to the program, the interrupt service routines defined therein must be also defined in the sketch itself. And since an ISR cannot be bound to an interrupt at run time, the ISRs have to be defined for all the ports, even though only one ends up being actually used.

Here is my test program:

#include <HardwareSerial_private.h>

HardwareSerial *serial;

ISR(USART0_RX_vect) { serial->_rx_complete_irq(); }
ISR(USART1_RX_vect, ISR_ALIASOF(USART0_RX_vect));
ISR(USART2_RX_vect, ISR_ALIASOF(USART0_RX_vect));
ISR(USART3_RX_vect, ISR_ALIASOF(USART0_RX_vect));
ISR(USART0_UDRE_vect) { serial->_tx_udr_empty_irq(); }
ISR(USART1_UDRE_vect, ISR_ALIASOF(USART0_UDRE_vect));
ISR(USART2_UDRE_vect, ISR_ALIASOF(USART0_UDRE_vect));
ISR(USART3_UDRE_vect, ISR_ALIASOF(USART0_UDRE_vect));

void initPort(uint8_t port)
{
    switch (port) {
        case 0:
            serial = new HardwareSerial(&UBRR0H, &UBRR0L,
                         &UCSR0A, &UCSR0B, &UCSR0C, &UDR0);
            break;
        case 1:
            serial = new HardwareSerial(&UBRR1H, &UBRR1L,
                         &UCSR1A, &UCSR1B, &UCSR1C, &UDR1);
            break;
        case 2:
            serial = new HardwareSerial(&UBRR2H, &UBRR2L,
                         &UCSR2A, &UCSR2B, &UCSR2C, &UDR2);
            break;
        case 3:
            serial = new HardwareSerial(&UBRR3H, &UBRR3L,
                         &UCSR3A, &UCSR3B, &UCSR3C, &UDR3);
            break;
    }
    serial->begin(9600);
}

void setup() {
    initPort(0);
    serial->println("Hello, World!");
}

void loop(){}

It should be noted that the ISRs do not take that mush flash space. There are actually only two of them, even though each is referenced four times in the interrupt vector table.

4
  • Excellent that you have shared your progression to the answer. Many thanks. Now for the next learning deep dive - understanding ISRs properly on arduino and getting accross all the U variables in the HardwareSerial class. I presume they are registers of some sort? – BrendanMcL Jun 19 '19 at 5:27
  • 1
    @BrendanMcL: If you want to dig deeper, you will have to read the chapter “USART” of the ATmega2560 datasheet, and the chapter “Interrupts” of the avr-libc documentation. – Edgar Bonet Jun 19 '19 at 7:28
  • 1
    @BrendanMcL: The “U variables” are references to hardware I/O registers. For example, UBRR0H is a macro that expands to (*(uint8_t*)0xc5), where 0xc5 is the memory-mapped address of the “USART0 Baud Rate Register High Byte”. – Edgar Bonet Jun 19 '19 at 7:29
  • 2
    @Edgar_Bonet many thanks. Understood. Having so many great libraries on the arduino platform is a great upside for working quickly. Downside being that it abstracts away so much detail making it a full learning curve to do something outside the spec. – BrendanMcL Jun 19 '19 at 9:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.