Dynamic Serial port selection and use without instantiating all Serialx objects
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(&amp;UBRR0H, &amp;UBRR0L,
                     &amp;UCSR0A, &amp;UCSR0B, &amp;UCSR0C, &amp;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(&amp;UBRR0H, &amp;UBRR0L,
                     &amp;UCSR0A, &amp;UCSR0B, &amp;UCSR0C, &amp;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 &lt;HardwareSerial_private.h&gt;

HardwareSerial *serial;

ISR(USART0_RX_vect) { serial-&gt;_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-&gt;_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(&amp;UBRR0H, &amp;UBRR0L,
                         &amp;UCSR0A, &amp;UCSR0B, &amp;UCSR0C, &amp;UDR0);
            break;
        case 1:
            serial = new HardwareSerial(&amp;UBRR1H, &amp;UBRR1L,
                         &amp;UCSR1A, &amp;UCSR1B, &amp;UCSR1C, &amp;UDR1);
            break;
        case 2:
            serial = new HardwareSerial(&amp;UBRR2H, &amp;UBRR2L,
                         &amp;UCSR2A, &amp;UCSR2B, &amp;UCSR2C, &amp;UDR2);
            break;
        case 3:
            serial = new HardwareSerial(&amp;UBRR3H, &amp;UBRR3L,
                         &amp;UCSR3A, &amp;UCSR3B, &amp;UCSR3C, &amp;UDR3);
            break;
    }
    serial-&gt;begin(9600);
}

void setup() {
    initPort(0);
    serial-&gt;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.