Выбор и использование динамического последовательного порта без создания экземпляров всех объектов Serialx

У меня есть проект Arduino, который считывает файл конфигурации с SD, а затем получает данные на последовательный порт, указанный в файле конфигурации.

Поэтому мне нужно создать экземпляр Serial (Serial0), Serial1,..2,3 и т. д. на основе динамических данных.

Я использую mega2560 с 4x HardwareSerial. Serial (также Serial0) — это USB. Серийные номера 1, 2, 3 находятся на определенных аппаратных контактах.

Я начал с примерно такого кода:

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;
}

Однако при этом создаются экземпляры всех объектов Serialx (каждый использует 157 байт данных/bss).

С тех пор я написал оболочку класса, которая использует ссылку на нужный последовательный объект в качестве параметра инициализации, но это просто подталкивает проблему к вызывающему коду.

Есть ли способ динамического создания объекта Serial с переданным номером порта, чтобы в итоге к нужному порту был подключен только один экземпляр?

, 👍2

Обсуждение

Вы говорите об интерфейсах SoftwareSerial или аппаратных интерфейсах?, @chrisl

все они созданы, но оптимизированы, если код их не использует. класс Serial зависит от платы. на некоторых платах Serial — это USB, а Setial1 — это HardwareSerial. но на платах с 328p это что-то вроде HardwareSerial Serial(&UBRRH, &UBRRL, &UCSRA, &UCSRB, &UCSRC, &UDR);, @Juraj

Сообщение отредактировано для уточнения mega2560 - аппаратный серийный номер., @BrendanMcL

@Juraj они не оптимизированы компоновщиком в моем приложении (в чем проблема), потому что компоновщик не знает, какой из них будет использоваться во время выполнения - это основано на данных конфигурации пользователя, считанных с SD. Таким образом, он включает в себя все 4 с ассоциированным использованием памяти, что является основной проблемой., @BrendanMcL

все они используются в вашем эскизе в некоторых случаях, поэтому должны быть в окончательной сборке., @Juraj

@TisteAndii, это не так сложно. только экземпляры должны создаваться динамически, как показывает Эдгар в ответе, @Juraj

но что такое 3 раза по 157 байт в 8 кБ SRAM Меги? :-), @Juraj

иногда это действительно важно :-) https://forum.arduino.cc/index.php?topic=622002.msg4215655#msg4215655, @Juraj


1 ответ


Лучший ответ:

2

Вы можете создать экземпляр HardwareSerial, ссылающийся на порт, зависящий от параметра функции, например

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

Однако, поскольку таким образом можно создать только локальную переменную, не очень полезно. В идеале вы хотели бы иметь глобальную переменную для последовательного порта и инициализируйте его в initPort(), как

HardwareSerial serial;

void initPort(uint8_t port) {
    switch (port) {
        case 0:
            // Вызов вымышленного метода init().
            serial.init(&UBRR0H, &UBRR0L,
                     &UCSR0A, &UCSR0B, &UCSR0C, &UDR0);
            break;
        case 1:
            // и т. д...
    }
    serial.begin(9600);
}

Увы, это невозможно, так как класс HardwareSerial не имеет значения по умолчанию. конструктор. Это означает, что привязка объекта к аппаратному порту может только после его создания.

Если не считать модификации ядра Arduino (добавление конструктора по умолчанию и init()), единственный вариант, который я вижу, это иметь глобальный быть указателем. Затем в initPort() объект создается при инициализации указателя. Я попробовал этот подход в пример программы ниже. Возникла небольшая техническая сложность однако: поскольку объектные файлы из ядра определяют Serial, Мы надеемся, что Serail1 и т. д. не будут связаны с программой, Определенные в нем подпрограммы обслуживания прерываний должны быть также определены в сам скетч. И поскольку ISR не может быть привязан к прерыванию при запуске время, ISR должны быть определены для всех портов, даже если только один из них фактически используется.

Вот моя тестовая программа:

#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(){}

Следует отметить, что ISR не занимают столько места во флэш-памяти. На самом деле их всего два, хотя на каждый ссылается четыре. раз в таблице векторов прерываний.

,

Отлично, что вы поделились своим продвижением к ответу. Большое спасибо. Теперь для следующего углубленного изучения — правильного понимания ISR на arduino и изучения всех переменных U в классе HardwareSerial. Я так понимаю, это какие-то регистры?, @BrendanMcL

@BrendanMcL: Если вы хотите копнуть глубже, вам придется прочитать главу «USART» [техническое описание ATmega2560](http://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-2549-8-bit). -AVR-Microcontroller-ATmega640-1280-1281-2560-2561_datasheet.pdf) и главу «Прерывания» [документации avr-libc](https://www.nongnu.org/avr-libc/user-manual /group__avr__interrupts.html)., @Edgar Bonet

@BrendanMcL: «Переменные U» — это ссылки на регистры аппаратного ввода-вывода. Например, UBRR0H — это макрос, который расширяется до (*(uint8_t*)0xc5), где 0xc5 — это отображаемый в памяти адрес «старшего байта регистра скорости передачи данных USART0»., @Edgar Bonet

@Edgar_Bonet большое спасибо. Понял. Наличие такого большого количества отличных библиотек на платформе arduino — отличный плюс для быстрой работы. Недостатком является то, что он абстрагирует так много деталей, что делает его полным обучением, чтобы делать что-то за пределами спецификации., @BrendanMcL