/* UART MODULE Copyright (C) 2022 by Maxim Prokhorov */ #include "espurna.h" #if UART_SUPPORT #include "uart.h" #include "utils.h" #if UART_SOFTWARE_SUPPORT #include #endif #include #include namespace espurna { namespace driver { namespace uart { namespace { enum class Parity { None, Even, Odd, }; struct Config { uint8_t data_bits; Parity parity; uint8_t stop_bits; }; } // namespace } // namespace uart } // namespace driver namespace settings { namespace internal { namespace { PROGMEM_STRING(ParityNone, "none"); PROGMEM_STRING(ParityEven, "even"); PROGMEM_STRING(ParityOdd, "odd"); static constexpr std::array, 3> ParityOptions PROGMEM { {{driver::uart::Parity::None, ParityNone}, {driver::uart::Parity::Even, ParityEven}, {driver::uart::Parity::Odd, ParityOdd}} }; } // namespace template <> driver::uart::Parity convert(const String& value) { return convert(ParityOptions, value, driver::uart::Parity::None); } String serialize(driver::uart::Parity value) { return espurna::settings::internal::serialize(ParityOptions, value); } } // namespace internal } // namespace settings namespace driver { namespace uart { namespace { namespace types { using HardwareConfig = ::SerialConfig; using HardwareMode = ::SerialMode; #if UART_SOFTWARE_SUPPORT #if defined(ARDUINO_ESP8266_RELEASE_2_7_2) \ || defined(ARDUINO_ESP8266_RELEASE_2_7_3) \ || defined(ARDUINO_ESP8266_RELEASE_2_7_4) \ || defined(ARDUINO_ESP8266_RELEASE_3_0_0) \ || defined(ARDUINO_ESP8266_RELEASE_3_0_1) \ || defined(ARDUINO_ESP8266_RELEASE_3_1_0) \ || defined(ARDUINO_ESP8266_RELEASE_3_1_1) using SoftwareConfig = ::SoftwareSerialConfig; #elif defined(ARDUINO_ESP8266_RELEASE_3_1_2) using SoftwareConfig = ::EspSoftwareSerial::Config; #else using SoftwareConfig = ::EspSoftwareSerial::Config; #endif #endif } // namespace types namespace build { // i.e. uart0, uart1 and a single sw port // for general use, hardware uart *should* be prefered method constexpr size_t PortsMax { 3 }; // todo; technically, tx==2 is also possible // but, we reserve that for the uart1 TX-only interface constexpr bool uart0_normal(uint8_t tx, uint8_t rx) { return ((tx == 1) && (rx == 3)) || ((tx == GPIO_NONE) && (rx == 3)) || ((tx == 1) && (rx == GPIO_NONE)); } constexpr bool uart0_swapped(uint8_t tx, uint8_t rx) { return ((tx == 15) && (rx == 13)) || ((tx == GPIO_NONE) && (rx == 13)) || ((tx == 15) && (rx == GPIO_NONE)); } constexpr bool uart1_normal(uint8_t tx, uint8_t rx) { return (tx == 2) && (rx == GPIO_NONE); } constexpr uint8_t tx(size_t index) { return (0 == index) ? (UART1_TX_PIN) : (1 == index) ? (UART2_TX_PIN) : (2 == index) ? (UART3_TX_PIN) : GPIO_NONE; } constexpr uint8_t rx(size_t index) { return (0 == index) ? (UART1_RX_PIN) : (1 == index) ? (UART2_RX_PIN) : (2 == index) ? (UART3_RX_PIN) : GPIO_NONE; } constexpr uint32_t baudrate(size_t index) { return (0 == index) ? (UART1_BAUDRATE) : (1 == index) ? (UART2_BAUDRATE) : (2 == index) ? (UART3_BAUDRATE) : 0; } constexpr uint8_t data_bits(size_t index) { return (0 == index) ? (UART1_DATA_BITS) : (1 == index) ? (UART2_DATA_BITS) : (2 == index) ? (UART3_DATA_BITS) : 0; } constexpr Parity parity(size_t index) { return (0 == index) ? (Parity::UART1_PARITY) : (1 == index) ? (Parity::UART2_PARITY) : (2 == index) ? (Parity::UART3_PARITY) : Parity::None; } constexpr uint8_t stop_bits(size_t index) { return (0 == index) ? (UART1_STOP_BITS) : (1 == index) ? (UART2_STOP_BITS) : (2 == index) ? (UART3_STOP_BITS) : 0; } constexpr bool invert(size_t index) { return (0 == index) ? (UART1_INVERT == 1) : (1 == index) ? (UART2_INVERT == 1) : (2 == index) ? (UART3_INVERT == 1) : false; } } // namespace build constexpr int data_bits_from_config(uint8_t bits) { return (bits == 5) ? 0 : (bits == 6) ? 0b100 : (bits == 7) ? 0b1000 : (bits == 8) ? 0b1100 : data_bits_from_config(8); } constexpr int parity_from_config(Parity parity) { return (parity == Parity::None) ? 0 : (parity == Parity::Even) ? 0b10 : (parity == Parity::Odd) ? 0b11 : parity_from_config(Parity::None); } constexpr int stop_bits_from_config(uint8_t bits) { return (bits == 1) ? 0b10000 : (bits == 2) ? 0b110000 : stop_bits_from_config(1); } template constexpr T from_config(Config); template <> constexpr types::HardwareConfig from_config(Config config) { return static_cast( data_bits_from_config(config.data_bits) | parity_from_config(config.parity) | stop_bits_from_config(config.stop_bits)); } namespace settings { namespace keys { PROGMEM_STRING(TxPin, "uartTx"); PROGMEM_STRING(RxPin, "uartRx"); PROGMEM_STRING(Baudrate, "uartBaud"); PROGMEM_STRING(DataBits, "uartDataBits"); PROGMEM_STRING(StopBits, "uartStopBits"); PROGMEM_STRING(Parity, "uartParity"); PROGMEM_STRING(Invert, "uartInv"); } // namespace keys uint8_t tx(size_t index) { return getSetting({keys::TxPin, index}, build::tx(index)); } uint8_t rx(size_t index) { return getSetting({keys::RxPin, index}, build::rx(index)); } uint32_t baudrate(size_t index) { return getSetting({keys::Baudrate, index}, build::baudrate(index)); } uint8_t data_bits(size_t index) { return getSetting({keys::DataBits, index}, build::data_bits(index)); } uint8_t stop_bits(size_t index) { return getSetting({keys::StopBits, index}, build::stop_bits(index)); } Parity parity(size_t index) { return getSetting({keys::Parity, index}, build::parity(index)); } bool invert(size_t index) { return getSetting({keys::Invert, index}, build::invert(index)); } } // namespace settings using StreamPtr = std::unique_ptr; struct BasePort { Type type; bool tx; bool rx; StreamPtr stream; }; using BasePortPtr = std::unique_ptr; namespace internal { BasePortPtr ports[build::PortsMax]; bool used_hardware_ports[2] = {false, false}; } // namespace internal BasePortPtr hardware_port( uint32_t baudrate, uint8_t tx, uint8_t rx, Config config, bool invert) { const int number = build::uart0_normal(tx, rx) ? 0 : build::uart0_swapped(tx, rx) ? 0 : build::uart1_normal(tx, rx) ? 1 : -1; if ((number < 0) || (internal::used_hardware_ports[number])) { return nullptr; } const int mode = (tx == GPIO_NONE) ? SERIAL_RX_ONLY : (rx == GPIO_NONE) ? SERIAL_TX_ONLY : ((tx != GPIO_NONE) && (rx != GPIO_NONE)) ? SERIAL_FULL : -1; if (mode < 0) { return nullptr; } internal::used_hardware_ports[number] = true; auto* ptr = new HardwareSerial(number); ptr->begin(baudrate, from_config(config), static_cast(mode), tx, invert); if ((number == 0) && (build::uart0_swapped(tx, rx))) { ptr->flush(); ptr->swap(); } return std::make_unique( BasePort{ .type = (number == 0) ? Type::Uart0 : Type::Uart1, .tx = (tx != GPIO_NONE), .rx = (rx != GPIO_NONE), .stream = StreamPtr(ptr), }); } // based on the values in v6 of the lib. still, return bits instead of the octal notation used there #if UART_SOFTWARE_SUPPORT constexpr int software_serial_data_bits_from_config(uint8_t bits) { return (bits == 5) ? 0 : (bits == 6) ? 0b1 : (bits == 7) ? 0b10 : (bits == 8) ? 0b11 : software_serial_data_bits_from_config(8); } // btw, SoftwareSerial also has Mark and Space // no support on the hardware peripheral though (afaik) constexpr int software_serial_parity_from_config(Parity parity) { return (parity == Parity::None) ? 0 : (parity == Parity::Even) ? 0b10000 : (parity == Parity::Odd) ? 0b11000 : software_serial_parity_from_config(Parity::None); } constexpr int software_serial_stop_bits_from_config(uint8_t bits) { return (bits == 1) ? 0b0 : (bits == 2) ? 0b10000000 : software_serial_stop_bits_from_config(1); } template <> constexpr types::SoftwareConfig from_config(Config config) { return static_cast( software_serial_data_bits_from_config(config.data_bits) | software_serial_parity_from_config(config.parity) | software_serial_stop_bits_from_config(config.stop_bits)); } BasePortPtr software_serial_port( uint32_t baudrate, uint8_t tx, uint8_t rx, Config config, bool invert) { const int8_t tx_pin = (tx == GPIO_NONE) ? -1 : tx; const int8_t rx_pin = (rx == GPIO_NONE) ? -1 : rx; auto* ptr = new SoftwareSerial(rx_pin, tx_pin, invert); ptr->begin(baudrate, from_config(config)); return std::make_unique( BasePort{ .type = Type::Software, .tx = (tx_pin > 0), .rx = (rx_pin > 0), .stream = StreamPtr(ptr), }); } #endif BasePortPtr make_port(size_t index) { BasePortPtr out; const auto tx = settings::tx(index); const auto rx = settings::rx(index); if ((tx == GPIO_NONE) && (rx == GPIO_NONE)) { return out; } if ((tx != GPIO_NONE) && !gpioLock(tx)) { return out; } if ((rx != GPIO_NONE) && !gpioLock(rx)) { gpioUnlock(tx); return out; } const auto config = Config{ .data_bits = settings::data_bits(index), .parity = settings::parity(index), .stop_bits = settings::stop_bits(index), }; const auto baudrate = settings::baudrate(index); const auto invert = settings::invert(index); if (build::uart0_normal(tx, rx) || build::uart0_swapped(tx, rx) || build::uart1_normal(tx, rx)) { out = hardware_port(baudrate, tx, rx, config, invert); } #if UART_SOFTWARE_SUPPORT if (!out) { out = software_serial_port(baudrate, tx, rx, config, invert); } #endif return out; } size_t ports() { size_t out = 0; for (const auto& port : internal::ports) { if (!port) { break; } ++out; } return out; } namespace settings { namespace query { #define ID_VALUE(NAME)\ String NAME (size_t id) {\ return espurna::settings::internal::serialize(\ espurna::driver::uart::settings::NAME(id));\ } ID_VALUE(tx) ID_VALUE(rx) ID_VALUE(baudrate) ID_VALUE(data_bits) ID_VALUE(stop_bits) ID_VALUE(parity) ID_VALUE(invert) #undef ID_VALUE static constexpr espurna::settings::query::IndexedSetting IndexedSettings[] PROGMEM { {keys::TxPin, query::tx}, {keys::RxPin, query::rx}, {keys::Baudrate, query::baudrate}, {keys::DataBits, query::data_bits}, {keys::StopBits, query::stop_bits}, {keys::Parity, query::parity}, {keys::Invert, query::invert}, }; bool checkSamePrefix(StringView key) { PROGMEM_STRING(Prefix, "uart"); return espurna::settings::query::samePrefix(key, Prefix); } String findIndexedValueFrom(StringView key) { return espurna::settings::query::IndexedSetting::findValueFrom( ports(), IndexedSettings, key); } void setup() { settingsRegisterQueryHandler({ .check = checkSamePrefix, .get = findIndexedValueFrom, }); } } // namespace query } // namespace settings #if TERMINAL_SUPPORT namespace terminal { namespace commands { String port_type(Type type) { const char* out = PSTR("UNKNOWN"); switch (type) { case Type::Unknown: break; case Type::Software: out = PSTR("SOFTWARE"); break; case Type::Uart0: out = PSTR("UART0"); break; case Type::Uart1: out = PSTR("UART1"); break; } return out; } void uart(::terminal::CommandContext&& ctx) { if (ctx.argv.size() == 1) { for (size_t index = 0; index < std::size(internal::ports); ++index) { const auto& port = internal::ports[index]; if (!port) { break; } ctx.output.printf_P( PSTR("%zu - %s{tx=%c rx=%c}\n"), index, port_type(port->type).c_str(), port->tx ? 'y' : 'n', port->rx ? 'y' : 'n'); } } else if (ctx.argv.size() == 2) { const auto result = parseUnsigned(ctx.argv[1], 10); if (!result.ok) { terminalError(ctx, F("Invalid ID")); return; } if (result.value >= ports()) { ctx.output.print(F("(Not active)")); } settingsDump(ctx, settings::query::IndexedSettings, result.value); } else { terminalError(ctx, F("UART []")); return; } terminalOK(ctx); } PROGMEM_STRING(Uart, "UART"); static constexpr ::terminal::Command List[] PROGMEM { {Uart, commands::uart}, }; } // namespace commands void setup() { espurna::terminal::add(commands::List); } } // namespace terminal #endif PortPtr port(size_t index) { const auto& ptr = internal::ports[index]; if ((index < std::size(internal::ports)) && (ptr)) { return std::make_unique( Port{ .type = ptr->type, .tx = ptr->tx, .rx = ptr->rx, .stream = ptr->stream.get(), }); } return nullptr; } void setup() { #if TERMINAL_SUPPORT terminal::setup(); #endif settings::query::setup(); for (size_t index = 0; index < build::PortsMax; ++index) { auto& port = internal::ports[index]; port = make_port(index); if (!port) { break; } } } } // namespace uart } // namespace } // namespace driver } // namespace espurna espurna::driver::uart::PortPtr uartPort(size_t index) { return espurna::driver::uart::port(index); } void uartSetup() { espurna::driver::uart::setup(); } #endif // UART_SUPPORT