// LoRaService.h #ifndef LoRaService_h #define LoRaService_h #include "SerialService.h" #include "CanService.h" #include #include #include class LoRaService { public: enum LoRaMode { NORMAL, WAKE_UP, POWER_SAVING, CONFIG }; enum CodigosFuncoes { CfgTx = 0xC0, CfgRx = 0xC1, Msg = 0xE1, WrongMode = 0xFC, BeginMsg = 0xAA, EndMsg = 0x55, }; struct LoRaParametrosModel { uint8_t address; uint8_t baudAndAirRate; uint8_t packetSizeAndPower; uint8_t channel; uint8_t tranModeAndWorCycle; }; LoRaService(CanService* canService) { _canService = canService; } bool Conectado = false; bool Configurado = false; byte AddressBase = 0x01; // Inicializa pinos e UART void Inicializar(uint8_t baudRate) { Configurado = false; _baudRate = encontrarChavePorValor(CodigosBaudRates, baudRate, 9600); MostrarLog("Iniciando LoRa nos pinos TXD=" + String(_pinTXD) + ", RXD=" + String(_pinRXD) + ", baudRate=" + String(_baudRate) + "..."); // Inicializa GPIOs pinMode(_pinENA, OUTPUT); digitalWrite(_pinENA, HIGH); vTaskDelay(100); digitalWrite(_pinENA, LOW); vTaskDelay(20); pinMode(_pinM0, OUTPUT); pinMode(_pinM1, OUTPUT); pinMode(_pinAUX, INPUT); _serialLoRa = &Serial2; if (Conectado) { _serialLoRa->end(); } _serialLoRa->begin(_baudRate, SERIAL_8N1, _pinTXD, _pinRXD); // Coloca o módulo em modo normal setLoRaMode(NORMAL); vTaskDelay(500); // Aguarda estabilização // Testa conexão Conectado = checkLoRaConnected(); if (Conectado) { MostrarLog("E220 detectado!"); if (LraTaskRxHandle == NULL) { lraQueueRx = xQueueCreate(50, sizeof(std::vector)); if (lraQueueRx == NULL) { MostrarLog("Falha ao criar fila RX!"); } xTaskCreatePinnedToCore(LoRaService::LraTaskRxWrapper, "LraTaskRx", 4096, this, 14, &LraTaskRxHandle, APP_CPU_NUM); } if (LraTaskTxHandle == NULL) { lraQueueTx = xQueueCreate(50, sizeof(std::vector)); if (lraQueueTx == NULL) { MostrarLog("Falha ao criar fila TX!"); } xTaskCreatePinnedToCore(LoRaService::LraTaskTxWrapper, "LraTaskTx", 4096, this, 13, &LraTaskTxHandle, APP_CPU_NUM); } if (LraTaskProcessHandle == NULL) { xTaskCreatePinnedToCore(LoRaService::LraTaskProcessWrapper, "LraTaskProcess", 4096, this, 15, &LraTaskProcessHandle, APP_CPU_NUM); } } else { MostrarLog("E220 não detectado."); } } std::vector MontarMensagemCAN(CanMessagePosicaoDados posicao) { std::vector data; data.push_back(static_cast(posicao)); data.push_back(_canService->ID_Num_sLRA); switch (posicao) { case CanMessagePosicaoDados::Status: { data.push_back(Conectado ? 1 : 0); data.push_back(Configurado ? 1 : 0); break; } case CanMessagePosicaoDados::Dados1: { data.push_back(parametrosAtual.address); data.push_back(parametrosAtual.baudAndAirRate); data.push_back(parametrosAtual.packetSizeAndPower); data.push_back(parametrosAtual.channel); data.push_back(parametrosAtual.tranModeAndWorCycle); break; } } return data; } void EnviarComando(std::vector dados) { if (dados.size() < 2) return; CanMessagePosicaoDados posicao = (CanMessagePosicaoDados)dados[0]; uint8_t idNum = dados[1]; switch (posicao) { case CanMessagePosicaoDados::Command1: { //bool sucesso = requisitarParametrosLoRa(); EnviarDadosCAN(MontarMensagemCAN(CanMessagePosicaoDados::Dados1)); break; } default: { AdicionarMensagemFilaLoRa(dados); } } } bool AdicionarMensagemFilaLoRa(std::vector dados) { if (xQueueSend(lraQueueTx, &dados, 0) != pdTRUE) { MostrarLog("Fila TX cheia! Mensagem descartada."); return false; } else { MostrarLog("Mensagem adicionada na fila TX"); return true; } } std::vector ConfigurarModulo(std::vector data) { std::vector status; if (data.size() < 2) return status; CanMessagePosicaoDados posicao = (CanMessagePosicaoDados)data[0]; uint8_t idNum = data[1]; //MostrarLog("Recebido comando ConfigurarModulo: posicao = " + String((int)posicao)); switch (posicao) { case CanMessagePosicaoDados::Config1: { if (data.size() < 8) return data; _pinENA = data[2]; _pinM0 = data[3]; _pinM1 = data[4]; _pinAUX = data[5]; _pinRXD = data[6]; _pinTXD = data[7]; break; } case CanMessagePosicaoDados::Config2: { if (data.size() < 8) return status; if (_pinRXD == 0 || _pinTXD == 0) { MostrarLog("Erro ao iniciar E220, pinout ainda nao definido!"); return status; } AddressBase = data[2]; uint8_t addr = data[3]; // address uint8_t baud = data[4]; // baudAndAirRate uint8_t packet = data[5]; // packetSizeAndPower uint8_t channel = data[6]; // channel uint8_t worCycle = data[7]; // tranModeAndWorCycle if (DebugMode) { String log = String("Parametros Config:") + " 0x" + String(AddressBase, HEX) + " 0x" + String(addr, HEX) + " 0x" + String(baud, HEX) + " 0x" + String(packet, HEX) + " 0x" + String(channel, HEX) + " 0x" + String(worCycle, HEX); MostrarLog(log); } uint8_t baudField = baud & 0xE0; // bits 5,6,7 (baudrate) Inicializar(baudField); //EnviarDadosCAN(MontarMensagemCAN(CanMessagePosicaoDados::Status)); if (Conectado) { if (requisitarParametrosLoRa()) { // Log detalhado do que veio da EEPROM MostrarLog("EEPROM -> " "Addr=" + String(parametrosAtual.address, HEX) + " BaudAir=" + String(parametrosAtual.baudAndAirRate, HEX) + " PacketPower=" + String(parametrosAtual.packetSizeAndPower, HEX) + " Channel=" + String(parametrosAtual.channel, HEX) + " TranModeWor=" + String(parametrosAtual.tranModeAndWorCycle, HEX) ); MostrarLog("Desejado -> " "Addr=" + String(addr, HEX) + " BaudAir=" + String(baud, HEX) + " PacketPower=" + String(packet, HEX) + " Channel=" + String(channel, HEX) + " TranModeWor=" + String(worCycle, HEX) ); Configurado = parametrosAtual.address == addr && parametrosAtual.baudAndAirRate == baud && parametrosAtual.packetSizeAndPower == packet && parametrosAtual.channel == channel && parametrosAtual.tranModeAndWorCycle== worCycle; MostrarLog(String("Modulo ja configurado? ") + (Configurado ? "SIM" : "NAO")); if (!Configurado) { Configurado = configurarModuloLoRa(addr, baud, packet, channel, worCycle); if (Configurado) { std::vector frame; frame.push_back(0xFF); frame.push_back(0xFF); frame.push_back(channel); frame.push_back(0x01); enviarDadosSerial(frame.data(), frame.size(), "PING"); } } } else { MostrarLog("Sem resposta do modulo ao requisitar parametros"); } } EnviarDadosCAN(MontarMensagemCAN(CanMessagePosicaoDados::Status)); EnviarDadosCAN(MontarMensagemCAN(CanMessagePosicaoDados::Dados1)); break; } } return status; } private: bool DebugMode = true; void MostrarLog(String mensagem) { if (DebugMode) { PrintTela("[LORA] " + mensagem); } } bool recebendo = false; int bytesEsperados = -1; int addrRemetente = -1; int addrDestinatario = -1; std::vector buffer; unsigned long ultimoByteRecebido = 0; const unsigned long timeoutRecebimentoMs = 800; // por exemplo, 100ms const unsigned long tempoEntreEnvios = 200; void reiniciarEstado() { recebendo = false; PausarTX = false; bytesEsperados = -1; addrRemetente = -1; addrDestinatario = -1; buffer.clear(); } QueueHandle_t lraQueueRx; TaskHandle_t LraTaskRxHandle = NULL; static void LraTaskRxWrapper(void *pvParameters) { LoRaService* service = static_cast(pvParameters); service->LraTaskRx(pvParameters); } void LraTaskRx(void* pvParameters) { LoRaService* service = static_cast(pvParameters); while (true) { if (!Conectado || !Configurado || PausarRX) { vTaskDelay(500); continue; } if (recebendo && (millis() - ultimoByteRecebido > timeoutRecebimentoMs)) { MostrarLog("Timeout de recebimento. Reiniciando estado."); reiniciarEstado(); } while (currentMode == LoRaMode::NORMAL && service->_serialLoRa->available()) { uint8_t byteRecebido = service->_serialLoRa->read(); ultimoByteRecebido = millis(); // <- atualiza o tempo MostrarLog("byteRecebido=0x" + String(byteRecebido, HEX)); if (!recebendo) { if (byteRecebido == CodigosFuncoes::BeginMsg) { reiniciarEstado(); MostrarLog("Iniciou o recebimento dos dados LoRa"); recebendo = true; PausarTX = true; } } else { if (byteRecebido == CodigosFuncoes::BeginMsg) { MostrarLog("Re-sync: novo BeginMsg detectado durante recepcao"); reiniciarEstado(); recebendo = true; PausarTX = true; continue; } if (bytesEsperados == -1) { bytesEsperados = byteRecebido; //MostrarLog("Definindo bytesEsperados = " + String(bytesEsperados)); } else if (addrRemetente == -1) { addrRemetente = byteRecebido; //MostrarLog("Definindo addrRemetente = " + String(addrRemetente)); } else if (addrDestinatario == -1) { addrDestinatario = byteRecebido; if (addrDestinatario != parametrosAtual.address && addrDestinatario != 0xFF) { MostrarLog("Mensagem para outro ID, descartando ate EndMsg"); // drenar ate checksum + 0x55 com base em 'bytesEsperados' size_t toDrain = (bytesEsperados >= 0 ? bytesEsperados + 2 : 0); for (size_t i = 0; i < toDrain; ++i) { uint8_t dump; if (!service->_serialLoRa->available()) break; dump = service->_serialLoRa->read(); } reiniciarEstado(); continue; } } else { buffer.push_back(byteRecebido); if (bytesEsperados >= 0 && buffer.size() == (size_t)bytesEsperados) { // tentar ler checksum e EndMsg com timeouts curtinhos uint32_t t0 = millis(); while (service->_serialLoRa->available() < 2 && (millis() - t0) < 200) { vTaskDelay(1); } if (service->_serialLoRa->available() >= 2) { uint8_t cks = service->_serialLoRa->read(); uint8_t endb = service->_serialLoRa->read(); buffer.push_back(cks); buffer.push_back(endb); } else { MostrarLog("Cauda perdida, adicionando manualmente..."); uint8_t soma = 0; for (size_t i = 0; i < buffer.size(); ++i) { soma += buffer[i]; } buffer.push_back(soma); buffer.push_back(CodigosFuncoes::EndMsg); } } bool protocoloCompleto = addrRemetente != -1 && addrDestinatario != -1 && buffer.size() == bytesEsperados + 2; // +2 = checksum + end MostrarLog("protocoloCompleto = " + String(protocoloCompleto) + ", bufferSize = " + buffer.size() + ", bytesEsperados = " + String(bytesEsperados)); if (protocoloCompleto) { if (buffer.back() == CodigosFuncoes::EndMsg) { buffer.pop_back(); // remove 0x55 (EndMsg) uint8_t checksumRecebido = buffer.back(); buffer.pop_back(); // remove checksum uint8_t soma = 0; for (size_t i = 0; i < buffer.size(); ++i) { soma += buffer[i]; } if ((soma % 256) == checksumRecebido) { MostrarLog("Mensagem LoRa recebida de " + String(addrRemetente) + " com checksum válido"); if (buffer.size() >= 2) { if (xQueueSend(lraQueueRx, &buffer, 0) != pdTRUE) { MostrarLog("Fila RX cheia! Mensagem descartada."); } else { MostrarLog("Mensagem adicionada na fila RX"); } } } else { MostrarLog("Checksum inválido. Descartando."); } } reiniciarEstado(); } } } } vTaskDelay(1); } } QueueHandle_t lraQueueTx; TaskHandle_t LraTaskTxHandle = NULL; static void LraTaskTxWrapper(void *pvParameters) { LoRaService* service = static_cast(pvParameters); service->LraTaskTx(pvParameters); } void LraTaskTx(void* pvParameters) { LoRaService* service = static_cast(pvParameters); std::vector msg; while (true) { if (!Conectado || !Configurado || PausarTX) { vTaskDelay(500); continue; } if (xQueueReceive(service->lraQueueTx, &msg, portMAX_DELAY) == pdTRUE) { service->EnviarDadosLoRa(msg); } else { MostrarLog("Erro ao enviar mensagem lora"); } vTaskDelay(1); } } TaskHandle_t LraTaskProcessHandle = NULL; static void LraTaskProcessWrapper(void *pvParameters) { LoRaService* service = static_cast(pvParameters); service->LraTaskProcess(pvParameters); } void LraTaskProcess(void* pvParameters) { LoRaService* service = static_cast(pvParameters); std::vector msg; while (true) { if (!Conectado || !Configurado) { vTaskDelay(1000); continue; } if (xQueueReceive(service->lraQueueRx, &msg, portMAX_DELAY) == pdTRUE) { service->EnviarDadosCAN(msg); } vTaskDelay(1); } } CanService* _canService; HardwareSerial* _serialLoRa; uint8_t _pinENA; uint8_t _pinTXD; uint8_t _pinRXD; uint8_t _pinM0; uint8_t _pinM1; uint8_t _pinAUX; int _baudRate = 9600; LoRaMode currentMode = NORMAL; LoRaParametrosModel parametrosAtual; int commandTimeout = 800; bool PausarTX = false; bool PausarRX = false; // Métodos internos void pauseLoRaTasks() { // pare produtoras de TX e consumidoras de RX antes de mexer no modo PausarTX = true; // sua flag para impedir enqueue/envio PausarRX = true; // idem recepção vTaskDelay(5); } void resumeLoRaTasks() { PausarTX = false; PausarRX = false; } // Define o modo do LoRa bool setLoRaMode(LoRaMode mode) { if (mode == currentMode) return true; pauseLoRaTasks(); // Ajusta os pinos M0 e M1 conforme o modo desejado switch (mode) { case NORMAL: digitalWrite(_pinM0, LOW); digitalWrite(_pinM1, LOW); break; case WAKE_UP: digitalWrite(_pinM0, HIGH); digitalWrite(_pinM1, LOW); break; case POWER_SAVING: digitalWrite(_pinM0, LOW); digitalWrite(_pinM1, HIGH); break; case CONFIG: digitalWrite(_pinM0, HIGH); digitalWrite(_pinM1, HIGH); break; } MostrarLog("Modo alterado de " + String(currentMode) + " para " + String(mode)); currentMode = mode; vTaskDelay(100); // Aguarda sinalização inicial de troca // ESPERA o AUX ir para HIGH, indicando que a troca completou unsigned long timeout = millis() + commandTimeout; while (digitalRead(_pinAUX) == LOW) { if (millis() > timeout) { MostrarLog("Erro: Timeout aguardando AUX após mudança de modo."); resumeLoRaTasks(); return false; } } vTaskDelay(100); // Manual pede 2ms após AUX ficar HIGH resumeLoRaTasks(); return true; } // Verifica se o E220 responde bool checkLoRaConnected() { MostrarLog("Verificando se o E220 esta conectado..."); if (!setLoRaMode(CONFIG)) { MostrarLog("Falha ao verificar se o E220 esta conectado, erro ao mudar para CONFIG!"); return false; } bool conectado = false; // Lista de possíveis baud rates (em ordem dos mais prováveis) const uint32_t baudRates[] = { 9600 }; // , 19200, 38400, 57600, 115200, 4800, 2400, 1200 const int totalBauds = sizeof(baudRates) / sizeof(baudRates[0]); int tentativaAtual = -1; // -1 = usar o baudrate já setado primeiro while (!conectado && tentativaAtual < totalBauds) { if (tentativaAtual >= 0) { // Se estamos tentando outra taxa, mudar a serial: _serialLoRa->end(); _serialLoRa->begin(baudRates[tentativaAtual], SERIAL_8N1, _pinTXD, _pinRXD); _baudRate = baudRates[tentativaAtual]; MostrarLog("Tentando BaudRate: " + String(_baudRate)); } // Limpa buffers antes de testar limparBufferLoRa(); uint8_t command[1] = { CodigosFuncoes::CfgRx }; enviarDadosSerial(command, 1, "Check Conectado"); unsigned long startTime = millis(); while (millis() - startTime < commandTimeout) { if (_serialLoRa->available()) { uint8_t dado = _serialLoRa->read(); if (dado == CodigosFuncoes::CfgRx) { conectado = true; break; } } vTaskDelay(1); } tentativaAtual++; } limparBufferLoRa(); setLoRaMode(NORMAL); if (conectado) { MostrarLog("E220 conectado com baudRate " + String(_baudRate)); } else { MostrarLog("Falha ao conectar no E220!"); } return conectado; } // Método que monta o comando de configuração bool configurarModuloLoRa(byte addr, byte baudAndAir, byte packetAndPower, byte channel, byte tranModeAndworCycle) { MostrarLog("Configurando modulo..."); if (!setLoRaMode(CONFIG)) { MostrarLog("Falha ao configurar modulo, erro ao mudar para CONFIG!"); return false; } limparBufferLoRa(); uint8_t command[9] = { CodigosFuncoes::CfgTx, // Código para configurar 0x00, // Registro inicial 0x06, // Quantidade de registros 0x00, // AddrH addr, // addrL baudAndAir, packetAndPower, channel, tranModeAndworCycle }; enviarDadosSerial(command, 9, "Config Modulo"); bool sucesso = receberParametrosModulo(command[1], command[2]); setLoRaMode(NORMAL); return sucesso; } // Método que requisita a leitura dos parâmetros atuais bool requisitarParametrosLoRa() { MostrarLog("Requisitando parametros do modulo..."); if (!setLoRaMode(CONFIG)) { MostrarLog("Falha ao requisitar parametros do modulo, erro ao mudar para CONFIG!"); return false; } limparBufferLoRa(); uint8_t command[3] = { CodigosFuncoes::CfgRx, 0x00, 0x0B }; enviarDadosSerial(command, 3, "Requisitar Parametros"); bool sucesso = receberParametrosModulo(command[1], command[2]); setLoRaMode(NORMAL); return sucesso; } bool readByteWithTimeout(uint8_t &out, unsigned long toMs = 500) { unsigned long t0 = millis(); while ((millis() - t0) < toMs) { if (_serialLoRa && _serialLoRa->available()) { int r = _serialLoRa->read(); if (r >= 0) { out = (uint8_t)r; //MostrarLog("0x" + String(out, HEX)); return true; } } // no ESP32 tem FreeRTOS; se preferir, use vTaskDelay(1); vTaskDelay(1); } return false; } bool receberParametrosModulo(uint8_t _startAddr, uint8_t _dataLen) { unsigned long t0 = millis(); // janela total mais folgada p/ rádio while ((millis() - t0) < 1500UL) { uint8_t b = 0; if (!readByteWithTimeout(b, 500)) { // não chegou nada ainda, segue tentando até estourar 1.5s continue; } if (b == CodigosFuncoes::CfgRx) { uint8_t startAddr = 0, dataLen = 0; if (!readByteWithTimeout(startAddr) || !readByteWithTimeout(dataLen)) { limparBufferLoRa(); return false; } if (startAddr == _startAddr && dataLen == _dataLen) { uint8_t addh=0, addl=0, bAir=0, pwr=0, ch=0, wor=0; if (!readByteWithTimeout(addh) || !readByteWithTimeout(addl) || !readByteWithTimeout(bAir) || !readByteWithTimeout(pwr) || !readByteWithTimeout(ch) || !readByteWithTimeout(wor)) { limparBufferLoRa(); return false; } parametrosAtual.address = addl; // assumindo ADDL como address parametrosAtual.baudAndAirRate = bAir; parametrosAtual.packetSizeAndPower = pwr; parametrosAtual.channel = ch; parametrosAtual.tranModeAndWorCycle = wor; uint8_t n1 = 0; uint8_t n2 = 0; uint8_t n3 = 0; uint8_t n4 = 0; uint8_t n5 = 0; readByteWithTimeout(n1); readByteWithTimeout(n2); readByteWithTimeout(n3); readByteWithTimeout(n4); readByteWithTimeout(n5); MostrarLog( "0x" + String(startAddr, HEX) + " " + "0x" + String(dataLen, HEX) + " " + "0x" + String(addh, HEX) + " " + "0x" + String(addl, HEX) + " " + "0x" + String(bAir, HEX) + " " + "0x" + String(pwr, HEX) + " " + "0x" + String(ch, HEX) + " " + "0x" + String(wor, HEX) + " " + "0x" + String(n1, HEX) + " " + "0x" + String(n2, HEX) + " " + "0x" + String(n3, HEX) + " " + "0x" + String(n4, HEX) + " " + "0x" + String(n5, HEX) + " " + "" ); return true; } else { // endereçamento diferente: drena os bytes anunciados pra limpar o stream for (int i = 0; i < dataLen; i++) { uint8_t dump; if (!readByteWithTimeout(dump)) break; } } } else if (b == CodigosFuncoes::WrongMode) { MostrarLog("Resposta WrongMode"); // opcional: logar que o módulo respondeu “modo errado” // e talvez sair para re-tentar setLoRaMode(CONFIG) } // senão, segue laçando e coletando até bater timeout total } limparBufferLoRa(); // garantir que não fique lixo return false; } void enviarDadosSerial(const uint8_t* dados, int tamanho, const String& descricao = "") { if (!_serialLoRa) return; while (digitalRead(_pinAUX) == LOW) { } _serialLoRa->write(dados, tamanho); //_serialLoRa->flush(); uint32_t t0 = millis(); while (digitalRead(_pinAUX) == HIGH && (millis()-t0) < 50) { } while (digitalRead(_pinAUX) == LOW && (millis()-t0) < 200) { } delayMicroseconds(1500); // ~1–2 ms entre frames curtos @9600 if (DebugMode) { String log = "Enviando [" + descricao + "] (" + String(tamanho) + " bytes): "; for (size_t i = 0; i < tamanho; i++) { log += "0x" + String(dados[i], HEX) + " "; } MostrarLog(log); } } // Limpa buffer anterior void limparBufferLoRa() { while (_serialLoRa->available()) { _serialLoRa->read(); } } std::vector MontarFrameLoRa(uint16_t destino, uint8_t canal, const std::vector& payloadApp) { std::vector frame; // Cabeçalho fixo LoRa (modo Fixed) if (destino == 0xFF) { frame.push_back(0xFF); frame.push_back(0xFF); } else { frame.push_back((destino >> 8) & 0xFF); frame.push_back(destino & 0xFF); } frame.push_back(static_cast(canal)); // Canal // Estrutura do payload frame.push_back(CodigosFuncoes::BeginMsg); // 0xAA frame.push_back(payloadApp.size()); // Tamanho do payload frame.push_back(parametrosAtual.address); // rementente frame.push_back(destino); // destinatario frame.insert(frame.end(), payloadApp.begin(), payloadApp.end()); // Calcula checksum (soma dos dados de payload) uint8_t soma = 0; for (uint8_t b : payloadApp) { soma += b; } uint8_t checksum = soma % 256; frame.push_back(checksum); // Checksum frame.push_back(CodigosFuncoes::EndMsg); // 0x55 String log = "Frame LoRa montado: "; for (int i = 0; i < frame.size(); i++) { log += "0x" + String(frame[i], HEX) + " "; } MostrarLog(log); return frame; } bool EnviarDadosCAN(std::vector dados) { _canService->adicionarMensagemFila(dados); return true; } bool EnviarDadosLoRa(std::vector dados) { MostrarLog("Enviando dados via LoRa para o endereco 0x" + String(AddressBase, HEX) + " no canal 0x" + String(parametrosAtual.channel, HEX)); std::vector dadosLora = MontarFrameLoRa(AddressBase, parametrosAtual.channel, dados); enviarDadosSerial(dadosLora.data(), dadosLora.size(), "Dados LoRa"); //vTaskDelay(pdMS_TO_TICKS(tempoEntreEnvios)); return true; } std::map CodigosBaudRates = { {1200, 0x00}, {2400, 0x20}, {4800, 0x40}, {9600, 0x60}, {19200, 0x80}, {38400, 0xA0}, {57600, 0xC0}, {115200, 0xE0}, }; template K encontrarChavePorValor(const std::map& mapa, V valorProcurado, K valorDefault) { for (const auto& par : mapa) { if (par.second == valorProcurado) { return par.first; } } return valorDefault; } }; #endif