agrobot_base/Firmware/Modulos/I2CService.h

1851 lines
52 KiB
C++

#ifndef I2CService_h
#define I2CService_h
#include "SerialService.h"
#include <Arduino.h>
#include <Wire.h>
#include <Adafruit_MCP23X17.h>
#include <Adafruit_ADS1X15.h>
#include <freertos/FreeRTOS.h>
#include <freertos/semphr.h>
class I2CService {
public:
static constexpr uint8_t enderecoMux = 0x70;
static constexpr uint8_t enderecoMcp = 0x20;
static constexpr uint8_t enderecoAds = 0x48;
struct EstadoQuarentena {
uint8_t recuperacoesConfirmadas = 0;
uint32_t inicioJanelaMs = 0;
uint32_t ultimaOcorrenciaMs = 0;
bool bloqueado = false;
};
struct ContextoBarramento {
bool valido = false;
int idSensor = -1;
int canalMuxSolicitado = -1;
int canalMuxAtivo = -1;
uint8_t enderecoADS = 0xFF;
uint32_t inicioMs = 0;
};
static bool I2CIniciado;
static bool MuxIniciado;
static bool McpIniciado;
static bool AdsIniciado;
// Mantidos publicos para compatibilidade com as firmwares atuais.
static int canalAtivo;
static int idAtualUsandoI2C;
static bool i2cMutexReiniciando;
static Adafruit_MCP23X17 mcp;
static Adafruit_ADS1115 ads;
/*
* Todos os pinos extras sao opcionais.
*
* tcaRdy : READY open-drain do TCA4307 (entrada)
* tcaEn : EN do TCA4307 (saida, ativo em HIGH)
* muxRst : RESET_N do TCA9548A (saida, ativo em LOW)
* adsPwrEn : enable do load switch que alimenta o ADS1115
* (saida, considerado ativo em HIGH)
*/
static bool DefinirPinos(
int sda,
int scl,
int tcaRdy = -1,
int tcaEn = -1,
int muxRst = -1,
int adsPwrEn = -1
) {
bool mudou = false;
if (_pinoSDA != sda) {
_pinoSDA = sda;
mudou = true;
}
if (_pinoSCL != scl) {
_pinoSCL = scl;
mudou = true;
}
if (_pinoTcaRdy != tcaRdy) {
_pinoTcaRdy = tcaRdy;
mudou = true;
}
if (_pinoTcaEn != tcaEn) {
_pinoTcaEn = tcaEn;
mudou = true;
}
if (_pinoMuxRst != muxRst) {
_pinoMuxRst = muxRst;
mudou = true;
}
if (_pinoAdsPwrEn != adsPwrEn) {
_pinoAdsPwrEn = adsPwrEn;
mudou = true;
}
return mudou;
}
static void DefinirDebug(bool ativo) {
DebugMode = ativo;
}
static void DefinirLimiteTempoI2C(uint32_t limiteMs) {
LimiteTempoI2C = limiteMs;
}
static bool IniciarI2C(bool forcar = false) {
GarantirMutex();
ConfigurarPinosControle();
bool barramentoLivre = forcar || BarramentoFisicamenteLivre();
// Primeiro tenta as recuperacoes mais localizadas, sem atribuir culpa
// durante a inicializacao.
if (!barramentoLivre && _pinoMuxRst >= 0) {
MostrarLog("I2C", "Barramento preso no inicio; resetando MUX");
ResetarMuxSePossivel();
barramentoLivre = AguardarBarramentoLivre(150);
}
if (!barramentoLivre && _pinoAdsPwrEn >= 0) {
MostrarLog("I2C", "Barramento ainda preso; executando power-cycle do ADS");
ResetarADSSePossivel();
barramentoLivre = AguardarBarramentoLivre(150);
}
if (!barramentoLivre && _pinoTcaEn >= 0) {
MostrarLog("I2C", "Barramento ainda preso; resetando TCA4307");
ResetarTCA4307(false);
barramentoLivre = AguardarBarramentoLivre(200);
}
if (I2CIniciado) {
Wire.end();
I2CIniciado = false;
DelayMs(5);
}
if (!barramentoLivre && (_pinoTcaEn < 0 || TCAReadyOk())) {
MostrarLog("I2C", "Tentando destravamento manual SDA/SCL antes do Wire.begin");
DestravarBarramentoI2C(_pinoSDA, _pinoSCL);
barramentoLivre = AguardarBarramentoLivre(100);
}
MostrarLog("I2C", "Iniciando Wire...");
I2CIniciado = Wire.begin(_pinoSDA, _pinoSCL);
MostrarLog(
"I2C",
"Wire configurado | clock=" + String(Wire.getClock()) +
" timeout=" + String(Wire.getTimeOut()) + "ms"
);
if (I2CIniciado) {
Wire.setClock(100000);
Wire.setTimeOut(TimeoutWireMs);
}
idAtualUsandoI2C = -1;
tempoEntradaI2C = 0;
_readyLowDesdeMs = 0;
_falhasTransacaoConsecutivas = 0;
_recoveryPendente = false;
_motivoRecoveryPendente = "";
LimparContextoAtual();
LimparContextoFalha();
MarcarDispositivosParaRevalidacao();
MostrarLog(
"I2C",
"I2C inicializado | SDA=" + String(_pinoSDA) +
" SCL=" + String(_pinoSCL) +
" RDY=" + String(_pinoTcaRdy) +
" EN=" + String(_pinoTcaEn) +
" MUX_RST=" + String(_pinoMuxRst) +
" ADS_PWR_EN=" + String(_pinoAdsPwrEn) +
" linhas_livres=" + String(barramentoLivre ? 1 : 0) +
" res=" + String(I2CIniciado ? 1 : 0)
);
if (i2cTaskHandle == NULL) {
xTaskCreatePinnedToCore(
I2CService::i2cTaskWrapper,
"i2cTask",
4096,
nullptr,
2,
&i2cTaskHandle,
tskNO_AFFINITY
);
}
return I2CIniciado;
}
static void VerificarSaudeBarramento() {
VerificarTCAReady();
VerificarI2CPreso();
VerificarRecoveryPendente();
}
static bool TCA4307Configurado() {
return _pinoTcaEn >= 0 || _pinoTcaRdy >= 0;
}
static bool TCA4307Ready() {
return TCAReadyOk();
}
static uint32_t TotalRecoveries() {
return _totalRecoveries;
}
static uint32_t FalhasRecoveryConsecutivas() {
return _falhasRecoveryConsecutivas;
}
static bool CanalMuxEmQuarentena(uint8_t canalGlobal) {
uint8_t indiceMux = canalGlobal / 10;
uint8_t canal = canalGlobal % 10;
if (indiceMux > 7 || canal > 7) {
return true;
}
bool bloqueado;
portENTER_CRITICAL(&_estadoLock);
bloqueado = _saudeCanaisMux[indiceMux][canal].bloqueado;
portEXIT_CRITICAL(&_estadoLock);
return bloqueado;
}
static uint8_t RecuperacoesConfirmadasCanalMux(uint8_t canalGlobal) {
uint8_t indiceMux = canalGlobal / 10;
uint8_t canal = canalGlobal % 10;
if (indiceMux > 7 || canal > 7) {
return 0;
}
uint8_t total;
portENTER_CRITICAL(&_estadoLock);
total = _saudeCanaisMux[indiceMux][canal].recuperacoesConfirmadas;
portEXIT_CRITICAL(&_estadoLock);
return total;
}
static bool ADSEmQuarentena(uint8_t endereco = enderecoAds) {
int indice = IndiceADS(endereco);
if (indice < 0) {
return true;
}
bool bloqueado;
portENTER_CRITICAL(&_estadoLock);
bloqueado = _saudeADS[indice].bloqueado;
portEXIT_CRITICAL(&_estadoLock);
return bloqueado;
}
static uint8_t RecuperacoesConfirmadasADS(uint8_t endereco = enderecoAds) {
int indice = IndiceADS(endereco);
if (indice < 0) {
return 0;
}
uint8_t total;
portENTER_CRITICAL(&_estadoLock);
total = _saudeADS[indice].recuperacoesConfirmadas;
portEXIT_CRITICAL(&_estadoLock);
return total;
}
static void DesbloquearCanalMux(uint8_t canalGlobal) {
uint8_t indiceMux = canalGlobal / 10;
uint8_t canal = canalGlobal % 10;
if (indiceMux > 7 || canal > 7) {
return;
}
portENTER_CRITICAL(&_estadoLock);
_saudeCanaisMux[indiceMux][canal] = EstadoQuarentena();
portEXIT_CRITICAL(&_estadoLock);
MostrarLog("MUX", "Quarentena removida | canalGlobal=" + String(canalGlobal));
}
static void DesbloquearADS(uint8_t endereco = enderecoAds) {
int indice = IndiceADS(endereco);
if (indice < 0) {
return;
}
portENTER_CRITICAL(&_estadoLock);
_saudeADS[indice] = EstadoQuarentena();
portEXIT_CRITICAL(&_estadoLock);
MostrarLog("ADS", "Quarentena removida | endereco=0x" + String(endereco, HEX));
}
static void LimparTodasQuarentenas() {
portENTER_CRITICAL(&_estadoLock);
for (int mux = 0; mux < 8; mux++) {
for (int canal = 0; canal < 8; canal++) {
_saudeCanaisMux[mux][canal] = EstadoQuarentena();
}
}
for (int i = 0; i < 4; i++) {
_saudeADS[i] = EstadoQuarentena();
}
portEXIT_CRITICAL(&_estadoLock);
MostrarLog("I2C", "Todas as quarentenas foram removidas");
}
static String StatusBarramento() {
String s = "";
s += "i2c=" + String(I2CIniciado ? 1 : 0);
s += " usando=" + String(idAtualUsandoI2C);
s += " recovery=" + String(_recuperacaoEmAndamento ? 1 : 0);
s += " pendente=" + String(_recoveryPendente ? 1 : 0);
s += " fails_tx=" + String(_falhasTransacaoConsecutivas);
s += " total_recovery=" + String(_totalRecoveries);
s += " q_mux=" + String(QuantidadeCanaisMuxEmQuarentena());
s += " q_ads=" + String(QuantidadeADSEmQuarentena());
if (_pinoTcaRdy >= 0) {
s += " tca_rdy=" + String(digitalRead(_pinoTcaRdy) == HIGH ? 1 : 0);
}
return s;
}
static bool VerificaEnderecoBarramento(byte endereco, uint32_t timeoutMs = 50) {
if (!I2CIniciado) {
return false;
}
if (!TCAReadyOk()) {
RegistrarFalhaTransacao("READY baixo ao verificar endereco 0x" + String(endereco, HEX));
return false;
}
unsigned long t0 = millis();
Wire.beginTransmission(endereco);
byte error = Wire.endTransmission();
if ((uint32_t)(millis() - t0) > timeoutMs) {
MostrarLog("I2C", "Timeout verificando endereco 0x" + String(endereco, HEX));
RegistrarFalhaTransacao("Timeout endereco 0x" + String(endereco, HEX));
return false;
}
if (error != 0) {
RegistrarFalhaTransacao(
"Endereco 0x" + String(endereco, HEX) +
" respondeu erro " + String(error)
);
return false;
}
RegistrarSucessoTransacao();
return true;
}
static bool SolicitarAcessoI2C(
int idSensor = 0,
int canalMux = -1,
uint32_t timeoutMs = 200
) {
if (!I2CIniciado) {
MostrarLog("I2C", "Acesso negado ID " + String(idSensor) + " | I2C nao inicializado");
return false;
}
if (canalMux >= 0 && CanalMuxEmQuarentena((uint8_t)canalMux)) {
MostrarLog(
"I2C",
"Acesso negado ID " + String(idSensor) +
" | Canal MUX em quarentena: " + String(canalMux)
);
return false;
}
if (i2cMutexReiniciando || _recuperacaoEmAndamento) {
MostrarLog("I2C", "Acesso negado ID " + String(idSensor) + " | Recovery em andamento");
return false;
}
if (!TCAReadyOk()) {
MostrarLog("I2C", "Acesso negado ID " + String(idSensor) + " | TCA4307 READY baixo");
AgendarRecovery("READY baixo antes do acesso do ID " + String(idSensor));
return false;
}
GarantirMutex();
if (xSemaphoreTake(i2cMutex, pdMS_TO_TICKS(timeoutMs)) != pdTRUE) {
// Contencao do mutex nao prova falha eletrica no I2C. O watchdog
// do proprietario atual e quem decide se o barramento ficou preso.
MostrarLog("I2C", "Timeout ao tentar acessar I2C | ID=" + String(idSensor));
return false;
}
// Uma recuperacao pode ter comecado entre as verificacoes acima e o
// recebimento do mutex.
if (i2cMutexReiniciando || _recuperacaoEmAndamento || !I2CIniciado) {
xSemaphoreGive(i2cMutex);
return false;
}
idAtualUsandoI2C = idSensor;
tempoEntradaI2C = millis();
PrepararContextoAtual(idSensor, canalMux);
if (canalMux >= 0 && !SelecionarCanalMux((uint8_t)canalMux)) {
MostrarLog(
"I2C",
"Falha ao selecionar canal mux " + String(canalMux) +
" | ID=" + String(idSensor)
);
bool precisaRecuperar = _recoveryPendente;
String motivo = _motivoRecoveryPendente;
if (precisaRecuperar) {
CapturarContextoFalha();
}
LimparContextoAtual();
idAtualUsandoI2C = -1;
tempoEntradaI2C = 0;
xSemaphoreGive(i2cMutex);
if (precisaRecuperar) {
RecuperarBarramento(motivo, true);
}
return false;
}
if (canalMux >= 0) {
ConfirmarCanalMuxNoContexto(canalMux);
}
return true;
}
static void LiberarAcessoI2C(int idSensor = 0) {
if (idSensor != idAtualUsandoI2C || i2cMutex == nullptr) {
return;
}
bool precisaRecuperar = _recoveryPendente;
String motivo = _motivoRecoveryPendente;
ContextoBarramento contexto = _contextoAtual;
if (
contexto.valido &&
contexto.canalMuxSolicitado >= 0 &&
!_recoveryPendente
) {
DesabilitarMuxAtualSemMutex();
precisaRecuperar = _recoveryPendente;
motivo = _motivoRecoveryPendente;
}
if (precisaRecuperar) {
CapturarContextoFalha();
i2cMutexReiniciando = true;
}
LimparContextoAtual();
idAtualUsandoI2C = -1;
tempoEntradaI2C = 0;
xSemaphoreGive(i2cMutex);
if (precisaRecuperar) {
RecuperarBarramento(motivo, true);
}
}
static void RecriarMutex() {
if (idAtualUsandoI2C >= 0 || _recuperacaoEmAndamento) {
MostrarLog("I2C", "Mutex nao recriado porque o barramento esta em uso");
return;
}
i2cMutexReiniciando = true;
RecriarMutexSeguro();
i2cMutexReiniciando = false;
MostrarLog("I2C", "Mutex recriado manualmente");
}
static bool WirePodeFinalizar() {
if (!I2CIniciado) {
pinMode(_pinoSCL, INPUT_PULLUP);
pinMode(_pinoSDA, INPUT_PULLUP);
DelayMs(1);
}
return digitalRead(_pinoSCL) == HIGH && digitalRead(_pinoSDA) == HIGH;
}
static void DestravarBarramentoI2C(int sda, int scl) {
MostrarLog("I2C", "Gerando pulsos manuais para destravar SDA/SCL");
pinMode(scl, OUTPUT_OPEN_DRAIN);
pinMode(sda, INPUT_PULLUP);
digitalWrite(scl, HIGH);
delayMicroseconds(5);
// 16 pulsos cobrem dispositivos que ficaram no meio de uma palavra
// e acompanham a margem utilizada pelo TCA4307.
for (int i = 0; i < 16; i++) {
digitalWrite(scl, LOW);
delayMicroseconds(5);
digitalWrite(scl, HIGH);
delayMicroseconds(5);
if (digitalRead(sda) == HIGH && i >= 8) {
break;
}
}
// Condicao STOP manual.
pinMode(sda, OUTPUT_OPEN_DRAIN);
digitalWrite(sda, LOW);
delayMicroseconds(5);
digitalWrite(scl, HIGH);
delayMicroseconds(5);
digitalWrite(sda, HIGH);
delayMicroseconds(5);
pinMode(scl, INPUT_PULLUP);
pinMode(sda, INPUT_PULLUP);
DelayMs(5);
}
static int QuemEstaUsando() {
return idAtualUsandoI2C;
}
static bool LeituraSegura(
uint8_t addr,
uint8_t* buffer,
size_t qtd,
uint8_t reg = 0xFF,
uint32_t timeoutMs = 50
) {
if (!I2CIniciado || buffer == nullptr || qtd == 0) {
return false;
}
if (!TCAReadyOk()) {
RegistrarFalhaTransacao("READY baixo antes de leitura 0x" + String(addr, HEX));
return false;
}
unsigned long t0 = millis();
if (reg != 0xFF) {
Wire.beginTransmission(addr);
Wire.write(reg);
int err = Wire.endTransmission(false);
if (err != 0) {
MostrarLog(
"I2C",
"Falha endTransmission leitura | addr=0x" +
String(addr, HEX) + " err=" + String(err)
);
RegistrarFalhaTransacao("Falha endTransmission leitura 0x" + String(addr, HEX));
return false;
}
}
size_t received = Wire.requestFrom((int)addr, (int)qtd);
if (received != qtd || (uint32_t)(millis() - t0) > timeoutMs) {
MostrarLog("I2C", "Timeout ou leitura incompleta | addr=0x" + String(addr, HEX));
RegistrarFalhaTransacao("Timeout leitura 0x" + String(addr, HEX));
return false;
}
for (size_t i = 0; i < qtd; i++) {
if (!Wire.available()) {
MostrarLog("I2C", "Dados insuficientes | addr=0x" + String(addr, HEX));
RegistrarFalhaTransacao("Dados insuficientes leitura 0x" + String(addr, HEX));
return false;
}
buffer[i] = Wire.read();
}
RegistrarSucessoTransacao();
return true;
}
static bool EscritaSegura(
uint8_t addr,
const uint8_t* dados,
size_t qtd,
uint32_t timeoutMs = 50
) {
if (!I2CIniciado || dados == nullptr || qtd == 0) {
return false;
}
if (!TCAReadyOk()) {
RegistrarFalhaTransacao("READY baixo antes de escrita 0x" + String(addr, HEX));
return false;
}
unsigned long t0 = millis();
Wire.beginTransmission(addr);
for (size_t i = 0; i < qtd; i++) {
Wire.write(dados[i]);
}
int resultado = Wire.endTransmission();
if ((uint32_t)(millis() - t0) > timeoutMs || resultado != 0) {
MostrarLog(
"I2C",
"Falha escrita | addr=0x" + String(addr, HEX) +
" err=" + String(resultado)
);
RegistrarFalhaTransacao("Falha escrita 0x" + String(addr, HEX));
return false;
}
RegistrarSucessoTransacao();
return true;
}
static bool SelecionarCanalMux(uint8_t canalGlobal) {
uint8_t indiceMux = canalGlobal / 10;
uint8_t canal = canalGlobal % 10;
if (indiceMux > 7 || canal > 7) {
MostrarLog("MUX", "Canal global invalido: " + String(canalGlobal));
return false;
}
if (CanalMuxEmQuarentena(canalGlobal)) {
MostrarLog("MUX", "Canal em quarentena: " + String(canalGlobal));
return false;
}
return TrocarCanalMux(enderecoMux + indiceMux, canal);
}
static bool TrocarCanalMux(uint8_t endereco, uint8_t canalMux) {
int indiceMux = IndiceMux(endereco);
if (indiceMux < 0 || canalMux >= 8) {
MostrarLog(
"MUX",
"Endereco ou canal invalido | endereco=0x" +
String(endereco, HEX) + " canal=" + String(canalMux)
);
return false;
}
uint8_t canalGlobal = (indiceMux * 10) + canalMux;
int canalAnterior = CanalGlobalAtivoAtual();
int sdaAntes = digitalRead(_pinoSDA);
int sclAntes = digitalRead(_pinoSCL);
int rdyAntes = TCAReadyOk() ? 1 : 0;
uint32_t inicio = millis();
if (CanalMuxEmQuarentena(canalGlobal)) {
MostrarLog("MUX", "Canal bloqueado por quarentena | canalGlobal=" + String(canalGlobal));
return false;
}
if (!_muxValidado[indiceMux]) {
MostrarLog("MUX", "Validando MUX em 0x" + String(endereco, HEX));
if (!VerificaEnderecoBarramento(endereco)) {
MostrarLog("MUX", "MUX nao encontrado em 0x" + String(endereco, HEX));
return false;
}
_muxValidado[indiceMux] = true;
MuxIniciado = true;
}
if (enderecoMuxAtivo == endereco && canalAtivo == canalMux) {
return true;
}
// Evita deixar dois ramais conectados simultaneamente.
if (enderecoMuxAtivo != 0xFF && enderecoMuxAtivo != endereco) {
Wire.beginTransmission(enderecoMuxAtivo);
Wire.write(0x00);
int erroDesabilitar = Wire.endTransmission();
if (erroDesabilitar != 0) {
MostrarLog(
"MUX",
"Falha ao desabilitar MUX anterior 0x" +
String(enderecoMuxAtivo, HEX) +
" | erro=" + String(erroDesabilitar)
);
RegistrarFalhaTransacao("Falha ao desabilitar MUX anterior");
return false;
}
enderecoMuxAtivo = 0xFF;
canalAtivo = -1;
}
Wire.beginTransmission(endereco);
Wire.write((uint8_t)(1U << canalMux));
int erro = Wire.endTransmission();
uint32_t duracao = (uint32_t)(millis() - inicio);
int sdaDepois = digitalRead(_pinoSDA);
int sclDepois = digitalRead(_pinoSCL);
int rdyDepois = TCAReadyOk() ? 1 : 0;
MostrarLog(
"MUX",
"Troca"
" | anterior=" + String(canalAnterior) +
" solicitado=" + String(canalGlobal) +
" erro=" + String(erro) +
" duracao=" + String(duracao) + "ms" +
" antes[SDA=" + String(sdaAntes) +
" SCL=" + String(sclAntes) +
" RDY=" + String(rdyAntes) + "]" +
" depois[SDA=" + String(sdaDepois) +
" SCL=" + String(sclDepois) +
" RDY=" + String(rdyDepois) + "]"
);
if (erro != 0) {
MostrarLog(
"MUX",
"Falha ao trocar canal | endereco=0x" + String(endereco, HEX) +
" canal=" + String(canalMux) + " erro=" + String(erro)
);
RegistrarFalhaTransacao("Falha ao trocar canal MUX");
return false;
}
enderecoMuxAtivo = endereco;
canalAtivo = canalMux;
RegistrarSucessoTransacao();
MostrarLog(
"MUX",
"Canal ativo | endereco=0x" + String(endereco, HEX) +
" canal=" + String(canalMux)
);
return true;
}
static int SelecionarCanalADS(uint8_t canalGlobal) {
if (canalGlobal > 33) {
return -1;
}
uint8_t endereco = enderecoAds + (canalGlobal / 10);
uint8_t canal = canalGlobal % 10;
if (canal > 3) {
return -1;
}
if (ADSEmQuarentena(endereco)) {
MostrarLog("ADS", "ADS em quarentena | endereco=0x" + String(endereco, HEX));
return -1;
}
MarcarEnderecoADSNoContexto(endereco);
return IniciarADS(endereco) ? canal : -1;
}
static int RealizarLeituraADS(int canalGlobal, int leituras, int idSensor, int canalMux) {
if (leituras <= 0) {
return -1;
}
if (!SolicitarAcessoI2C(idSensor, canalMux)) {
return -1;
}
int canal = SelecionarCanalADS(canalGlobal);
if (canal < 0) {
LiberarAcessoI2C(idSensor);
return -1;
}
int64_t soma = 0;
int leiturasConcluidas = 0;
bool leituraFalhou = false;
for (int i = 0; i < leituras; i++) {
uint32_t inicioLeitura = millis();
int leituraADC = (int)ads.readADC_SingleEnded(canal);
uint32_t duracaoLeitura = (uint32_t)(millis() - inicioLeitura);
if (!TCAReadyOk() || duracaoLeitura > LimiteLeituraADS_MS) {
RegistrarFalhaTransacao(
!TCAReadyOk()
? "READY caiu durante leitura ADS"
: "Leitura ADS excedeu o limite de tempo"
);
leituraFalhou = true;
break;
}
int leitura12bits = map(leituraADC, 0, 32767, 0, 4095);
soma += leitura12bits;
leiturasConcluidas++;
MostrarLog(
"ADS",
"Canal=" + String(canal) +
" ADC=" + String(leituraADC) +
" 12bits=" + String(leitura12bits)
);
}
if (_recoveryPendente) {
leituraFalhou = true;
}
LiberarAcessoI2C(idSensor);
if (leituraFalhou || leiturasConcluidas != leituras || leiturasConcluidas == 0) {
return -1;
}
return (int)(soma / leiturasConcluidas);
}
static void IniciarMUX(byte endereco = enderecoMux, bool Mandatorio = false) {
if (!I2CIniciado) {
MostrarLog("MUX", "Impossivel iniciar TCA9548A, I2C nao iniciado");
return;
}
int indiceMux = IndiceMux(endereco);
if (indiceMux < 0) {
MostrarLog("MUX", "Endereco de MUX invalido: 0x" + String(endereco, HEX));
return;
}
if (_muxValidado[indiceMux]) {
MostrarLog("MUX", "TCA9548A ja iniciado em 0x" + String(endereco, HEX));
return;
}
while (true) {
MostrarLog("MUX", "Iniciando TCA9548A em 0x" + String(endereco, HEX));
if (!SolicitarAcessoI2C(idSensorMux)) {
MostrarLog("MUX", "Falha ao solicitar acesso ao I2C");
if (!Mandatorio) {
return;
}
DelayMs(5000);
continue;
}
bool iniciado = VerificaEnderecoBarramento(endereco);
if (iniciado) {
_muxValidado[indiceMux] = true;
MuxIniciado = true;
enderecoMuxAtivo = 0xFF;
canalAtivo = -1;
}
LiberarAcessoI2C(idSensorMux);
if (iniciado) {
MostrarLog("MUX", "TCA9548A iniciado em 0x" + String(endereco, HEX));
return;
}
MostrarLog("MUX", "TCA9548A nao encontrado em 0x" + String(endereco, HEX));
if (!Mandatorio) {
MostrarLog("MUX", "TCA9548A nao mandatorio, seguindo fluxo");
return;
}
MostrarLog("MUX", "Conexao obrigatoria, tentando novamente em 5 segundos");
DelayMs(5000);
}
}
static void IniciarMCP(byte endereco = enderecoMcp, bool Mandatorio = false) {
if (!I2CIniciado) {
MostrarLog("MCP", "Impossivel iniciar MCP23X17, I2C nao iniciado");
return;
}
if (McpIniciado) {
MostrarLog("MCP", "MCP23X17 ja iniciado");
return;
}
while (true) {
MostrarLog("MCP", "Iniciando MCP23X17 em 0x" + String(endereco, HEX));
if (!SolicitarAcessoI2C(idSensorMcp)) {
MostrarLog("MCP", "Falha ao solicitar acesso ao I2C");
if (!Mandatorio) {
return;
}
DelayMs(5000);
continue;
}
McpIniciado = mcp.begin_I2C(endereco, &Wire);
if (!McpIniciado) {
RegistrarFalhaTransacao("Falha iniciar MCP23X17");
} else {
RegistrarSucessoTransacao();
}
LiberarAcessoI2C(idSensorMcp);
if (McpIniciado) {
MostrarLog("MCP", "MCP23X17 iniciado");
return;
}
MostrarLog("MCP", "Erro ao iniciar MCP23X17");
if (!Mandatorio) {
MostrarLog("MCP", "MCP23X17 nao mandatorio, seguindo fluxo");
return;
}
MostrarLog("MCP", "Conexao obrigatoria, tentando novamente em 5 segundos");
DelayMs(5000);
}
}
static bool IniciarADS(byte endereco = enderecoAds) {
if (!I2CIniciado) {
MostrarLog("ADS", "Impossivel iniciar ADS1115, I2C nao iniciado");
return false;
}
if (ADSEmQuarentena(endereco)) {
MostrarLog(
"ADS",
"Inicializacao recusada, ADS em quarentena | endereco=0x" +
String(endereco, HEX)
);
return false;
}
MarcarEnderecoADSNoContexto(endereco);
if (AdsIniciado && endereco == ultimoEnderecoADS) {
return true;
}
MostrarLog("ADS", "Iniciando ADS1115 em 0x" + String(endereco, HEX));
AdsIniciado = ads.begin(endereco, &Wire);
if (!AdsIniciado) {
ultimoEnderecoADS = 0xFF;
MostrarLog("ADS", "Erro ao iniciar ADS1115 em 0x" + String(endereco, HEX));
RegistrarFalhaTransacao("Falha iniciar ADS1115");
return false;
}
ads.setGain(GAIN_ONE);
ultimoEnderecoADS = endereco;
MostrarLog("ADS", "ADS1115 iniciado em 0x" + String(endereco, HEX));
RegistrarSucessoTransacao();
return true;
}
private:
static bool DebugMode;
static int idSensorMux;
static int idSensorMcp;
static int _pinoSDA;
static int _pinoSCL;
static int _pinoTcaRdy;
static int _pinoTcaEn;
static int _pinoMuxRst;
static int _pinoAdsPwrEn;
static unsigned long tempoEntradaI2C;
static unsigned long LimiteTempoI2C;
static byte ultimoEnderecoADS;
static uint8_t enderecoMuxAtivo;
static bool _muxValidado[8];
static SemaphoreHandle_t i2cMutex;
static TaskHandle_t i2cTaskHandle;
static bool _recuperacaoEmAndamento;
static bool _recoveryPendente;
static String _motivoRecoveryPendente;
static uint32_t _falhasTransacaoConsecutivas;
static uint32_t _falhasRecoveryConsecutivas;
static uint32_t _totalRecoveries;
static unsigned long _ultimoRecoveryMs;
static unsigned long _readyLowDesdeMs;
static EstadoQuarentena _saudeCanaisMux[8][8];
static EstadoQuarentena _saudeADS[4];
static ContextoBarramento _contextoAtual;
static ContextoBarramento _contextoFalha;
static portMUX_TYPE _estadoLock;
static constexpr uint32_t TimeoutWireMs = 80;
static constexpr uint32_t LimiteReadyLowMs = 200;
static constexpr uint32_t CooldownRecoveryMs = 1000;
static constexpr uint32_t LimiteLeituraADS_MS = 50;
static constexpr uint32_t LimiteFalhasTransacaoRecovery = 3;
static constexpr uint32_t MaxFalhasRecoveryAntesRestart = 3;
static constexpr uint32_t JanelaQuarentenaMs = 120000;
static constexpr uint8_t LimiteRecuperacoesMesmoAlvo = 3;
static constexpr uint32_t MargemHardRestartMs = 1500;
static void MostrarLog(const String& componente, const String& mensagem) {
if (DebugMode) {
PrintTela("[" + componente + "] " + mensagem);
}
}
static void DelayMs(uint32_t ms) {
vTaskDelay(pdMS_TO_TICKS(ms));
}
static bool TempoPassou(unsigned long inicio, uint32_t limiteMs) {
return (uint32_t)(millis() - inicio) >= limiteMs;
}
static void GarantirMutex() {
if (i2cMutex == nullptr) {
i2cMutex = xSemaphoreCreateMutex();
}
}
static void RecriarMutexSeguro() {
if (i2cMutex != nullptr) {
vSemaphoreDelete(i2cMutex);
i2cMutex = nullptr;
}
i2cMutex = xSemaphoreCreateMutex();
idAtualUsandoI2C = -1;
tempoEntradaI2C = 0;
}
static void ConfigurarPinosControle() {
if (_pinoTcaEn >= 0) {
pinMode(_pinoTcaEn, OUTPUT);
digitalWrite(_pinoTcaEn, HIGH);
}
if (_pinoTcaRdy >= 0) {
pinMode(_pinoTcaRdy, INPUT_PULLUP);
}
if (_pinoMuxRst >= 0) {
pinMode(_pinoMuxRst, OUTPUT);
digitalWrite(_pinoMuxRst, HIGH);
}
if (_pinoAdsPwrEn >= 0) {
pinMode(_pinoAdsPwrEn, OUTPUT);
digitalWrite(_pinoAdsPwrEn, HIGH);
DelayMs(10);
}
}
static bool TCAReadyOk() {
return _pinoTcaRdy < 0 || digitalRead(_pinoTcaRdy) == HIGH;
}
static bool EsperarTCAReady(uint32_t timeoutMs) {
if (_pinoTcaRdy < 0) {
return true;
}
unsigned long inicio = millis();
while (!TempoPassou(inicio, timeoutMs)) {
if (digitalRead(_pinoTcaRdy) == HIGH) {
return true;
}
DelayMs(2);
}
return digitalRead(_pinoTcaRdy) == HIGH;
}
static bool ResetarTCA4307(bool logDetalhado = true) {
if (_pinoTcaEn < 0) {
if (logDetalhado) {
MostrarLog("TCA4307", "EN nao configurado, reset indisponivel");
}
return false;
}
if (logDetalhado) {
MostrarLog("TCA4307", "Resetando TCA4307 via EN");
}
digitalWrite(_pinoTcaEn, LOW);
DelayMs(10);
digitalWrite(_pinoTcaEn, HIGH);
DelayMs(5);
bool ready = EsperarTCAReady(200);
if (logDetalhado) {
MostrarLog("TCA4307", String("READY apos reset: ") + (ready ? "HIGH" : "LOW"));
}
return ready;
}
static bool ResetarMuxSePossivel() {
enderecoMuxAtivo = 0xFF;
canalAtivo = -1;
if (_pinoMuxRst < 0) {
return false;
}
MostrarLog("MUX", "Resetando TCA9548A via pino RESET_N");
digitalWrite(_pinoMuxRst, LOW);
DelayMs(2);
digitalWrite(_pinoMuxRst, HIGH);
DelayMs(5);
MuxIniciado = false;
for (int i = 0; i < 8; i++) {
_muxValidado[i] = false;
}
return true;
}
static bool ResetarADSSePossivel() {
if (_pinoAdsPwrEn < 0) {
return false;
}
MostrarLog("ADS", "Executando power-cycle do ADS1115");
digitalWrite(_pinoAdsPwrEn, LOW);
DelayMs(30);
digitalWrite(_pinoAdsPwrEn, HIGH);
DelayMs(15);
AdsIniciado = false;
ultimoEnderecoADS = 0xFF;
return true;
}
static void MarcarDispositivosParaRevalidacao() {
canalAtivo = -1;
enderecoMuxAtivo = 0xFF;
MuxIniciado = false;
McpIniciado = false;
AdsIniciado = false;
ultimoEnderecoADS = 0xFF;
for (int i = 0; i < 8; i++) {
_muxValidado[i] = false;
}
}
static int IndiceMux(uint8_t endereco) {
if (endereco < enderecoMux || endereco > enderecoMux + 7) {
return -1;
}
return endereco - enderecoMux;
}
static int IndiceADS(uint8_t endereco) {
if (endereco < enderecoAds || endereco > enderecoAds + 3) {
return -1;
}
return endereco - enderecoAds;
}
static int CanalGlobalAtivoAtual() {
int indiceMux = IndiceMux(enderecoMuxAtivo);
if (indiceMux < 0 || canalAtivo < 0 || canalAtivo > 7) {
return -1;
}
return (indiceMux * 10) + canalAtivo;
}
static void PrepararContextoAtual(int idSensor, int canalMuxSolicitado) {
ContextoBarramento contexto;
contexto.valido = true;
contexto.idSensor = idSensor;
contexto.canalMuxSolicitado = canalMuxSolicitado;
// Uma transacao direta no barramento principal nao deve herdar
// o ultimo canal que ficou selecionado no MUX.
contexto.canalMuxAtivo =
canalMuxSolicitado >= 0
? CanalGlobalAtivoAtual()
: -1;
contexto.enderecoADS = 0xFF;
contexto.inicioMs = millis();
portENTER_CRITICAL(&_estadoLock);
_contextoAtual = contexto;
portEXIT_CRITICAL(&_estadoLock);
}
static void ConfirmarCanalMuxNoContexto(int canalGlobal) {
portENTER_CRITICAL(&_estadoLock);
if (_contextoAtual.valido) {
_contextoAtual.canalMuxAtivo = canalGlobal;
}
portEXIT_CRITICAL(&_estadoLock);
}
static void MarcarEnderecoADSNoContexto(uint8_t endereco) {
portENTER_CRITICAL(&_estadoLock);
if (_contextoAtual.valido) {
_contextoAtual.enderecoADS = endereco;
}
portEXIT_CRITICAL(&_estadoLock);
}
static void CapturarContextoFalha() {
portENTER_CRITICAL(&_estadoLock);
if (_contextoAtual.valido) {
_contextoFalha = _contextoAtual;
}
portEXIT_CRITICAL(&_estadoLock);
}
static ContextoBarramento ObterContextoFalha() {
ContextoBarramento contexto;
portENTER_CRITICAL(&_estadoLock);
if (_contextoFalha.valido) {
contexto = _contextoFalha;
} else {
contexto = _contextoAtual;
}
portEXIT_CRITICAL(&_estadoLock);
return contexto;
}
static void LimparContextoAtual() {
portENTER_CRITICAL(&_estadoLock);
_contextoAtual = ContextoBarramento();
portEXIT_CRITICAL(&_estadoLock);
}
static void LimparContextoFalha() {
portENTER_CRITICAL(&_estadoLock);
_contextoFalha = ContextoBarramento();
portEXIT_CRITICAL(&_estadoLock);
}
static int CanalSuspeitoDoContexto(const ContextoBarramento& contexto) {
if (contexto.canalMuxAtivo >= 0) {
return contexto.canalMuxAtivo;
}
return contexto.canalMuxSolicitado;
}
static bool BarramentoFisicamenteLivre() {
return TCAReadyOk() && WirePodeFinalizar();
}
static bool AguardarBarramentoLivre(uint32_t timeoutMs) {
uint32_t inicio = millis();
while (!TempoPassou(inicio, timeoutMs)) {
if (BarramentoFisicamenteLivre()) {
return true;
}
DelayMs(2);
}
return BarramentoFisicamenteLivre();
}
static void AgendarRecovery(const String& motivo) {
CapturarContextoFalha();
_recoveryPendente = true;
_motivoRecoveryPendente = motivo;
}
static void RegistrarResponsavelCanalMux(uint8_t canalGlobal) {
uint8_t indiceMux = canalGlobal / 10;
uint8_t canal = canalGlobal % 10;
if (indiceMux > 7 || canal > 7) {
return;
}
uint32_t agora = millis();
uint8_t total;
bool bloqueado;
portENTER_CRITICAL(&_estadoLock);
EstadoQuarentena& estado = _saudeCanaisMux[indiceMux][canal];
if (
estado.inicioJanelaMs == 0 ||
(uint32_t)(agora - estado.inicioJanelaMs) > JanelaQuarentenaMs
) {
estado.recuperacoesConfirmadas = 0;
estado.inicioJanelaMs = agora;
}
if (estado.recuperacoesConfirmadas < 255) {
estado.recuperacoesConfirmadas++;
}
estado.ultimaOcorrenciaMs = agora;
if (estado.recuperacoesConfirmadas >= LimiteRecuperacoesMesmoAlvo) {
estado.bloqueado = true;
}
total = estado.recuperacoesConfirmadas;
bloqueado = estado.bloqueado;
portEXIT_CRITICAL(&_estadoLock);
MostrarLog(
"MUX",
"Recovery atribuido ao canal " + String(canalGlobal) +
" | ocorrencias=" + String(total) +
" | quarentena=" + String(bloqueado ? "SIM" : "NAO")
);
}
static void RegistrarResponsavelADS(uint8_t endereco) {
int indice = IndiceADS(endereco);
if (indice < 0) {
return;
}
uint32_t agora = millis();
uint8_t total;
bool bloqueado;
portENTER_CRITICAL(&_estadoLock);
EstadoQuarentena& estado = _saudeADS[indice];
if (
estado.inicioJanelaMs == 0 ||
(uint32_t)(agora - estado.inicioJanelaMs) > JanelaQuarentenaMs
) {
estado.recuperacoesConfirmadas = 0;
estado.inicioJanelaMs = agora;
}
if (estado.recuperacoesConfirmadas < 255) {
estado.recuperacoesConfirmadas++;
}
estado.ultimaOcorrenciaMs = agora;
if (estado.recuperacoesConfirmadas >= LimiteRecuperacoesMesmoAlvo) {
estado.bloqueado = true;
}
total = estado.recuperacoesConfirmadas;
bloqueado = estado.bloqueado;
portEXIT_CRITICAL(&_estadoLock);
MostrarLog(
"ADS",
"Recovery atribuido ao endereco 0x" + String(endereco, HEX) +
" | ocorrencias=" + String(total) +
" | quarentena=" + String(bloqueado ? "SIM" : "NAO")
);
}
static uint8_t QuantidadeCanaisMuxEmQuarentena() {
uint8_t total = 0;
portENTER_CRITICAL(&_estadoLock);
for (int mux = 0; mux < 8; mux++) {
for (int canal = 0; canal < 8; canal++) {
if (_saudeCanaisMux[mux][canal].bloqueado) {
total++;
}
}
}
portEXIT_CRITICAL(&_estadoLock);
return total;
}
static uint8_t QuantidadeADSEmQuarentena() {
uint8_t total = 0;
portENTER_CRITICAL(&_estadoLock);
for (int i = 0; i < 4; i++) {
if (_saudeADS[i].bloqueado) {
total++;
}
}
portEXIT_CRITICAL(&_estadoLock);
return total;
}
static void RegistrarFalhaTransacao(const String& motivo) {
_falhasTransacaoConsecutivas++;
MostrarLog(
"I2C",
"Falha transacao #" + String(_falhasTransacaoConsecutivas) +
" | " + motivo
);
if (_falhasTransacaoConsecutivas >= LimiteFalhasTransacaoRecovery) {
AgendarRecovery("Falhas consecutivas no I2C: " + motivo);
}
}
static void RegistrarSucessoTransacao() {
_falhasTransacaoConsecutivas = 0;
}
static bool RecuperarBarramento(const String& motivo, bool ignorarCooldown = false) {
if (_recuperacaoEmAndamento) {
return false;
}
if (
!ignorarCooldown &&
_ultimoRecoveryMs > 0 &&
!TempoPassou(_ultimoRecoveryMs, CooldownRecoveryMs)
) {
return false;
}
_recuperacaoEmAndamento = true;
i2cMutexReiniciando = true;
_ultimoRecoveryMs = millis();
_totalRecoveries++;
MostrarLog("I2C", "Recovery iniciado | Motivo: " + motivo);
GarantirMutex();
if (xSemaphoreTake(i2cMutex, pdMS_TO_TICKS(150)) != pdTRUE) {
MostrarLog("I2C", "Recovery adiado porque o mutex ainda esta ocupado");
_recoveryPendente = true;
i2cMutexReiniciando = false;
_recuperacaoEmAndamento = false;
return false;
}
ContextoBarramento contexto = ObterContextoFalha();
int canalSuspeito = CanalSuspeitoDoContexto(contexto);
bool travadoInicialmente = !BarramentoFisicamenteLivre();
bool barramentoLivre = !travadoInicialmente;
bool responsavelConfirmado = false;
// 1) Isola primeiro o ramal do MUX, que e a acao mais localizada.
if (travadoInicialmente && canalSuspeito >= 0 && _pinoMuxRst >= 0) {
ResetarMuxSePossivel();
barramentoLivre = AguardarBarramentoLivre(150);
if (barramentoLivre) {
RegistrarResponsavelCanalMux((uint8_t)canalSuspeito);
responsavelConfirmado = true;
}
}
// 2) Se o MUX nao resolveu e o ADS estava envolvido, faz power-cycle.
if (!barramentoLivre && contexto.enderecoADS != 0xFF && _pinoAdsPwrEn >= 0) {
ResetarADSSePossivel();
barramentoLivre = AguardarBarramentoLivre(150);
if (barramentoLivre) {
RegistrarResponsavelADS(contexto.enderecoADS);
responsavelConfirmado = true;
}
}
// Em falhas logicas, sem linha fisicamente presa, reinicia o alvo
// relacionado, mas nao soma ponto de quarentena.
if (!travadoInicialmente) {
if (canalSuspeito >= 0 && _pinoMuxRst >= 0) {
ResetarMuxSePossivel();
} else if (contexto.enderecoADS != 0xFF && _pinoAdsPwrEn >= 0) {
ResetarADSSePossivel();
}
barramentoLivre = AguardarBarramentoLivre(100);
}
// 3) Recuperacao global pelo TCA4307.
if (!barramentoLivre && _pinoTcaEn >= 0) {
ResetarTCA4307(true);
barramentoLivre = AguardarBarramentoLivre(200);
}
// Para bit-bang, primeiro tira o periferico Wire do controle dos pinos.
if (I2CIniciado) {
Wire.end();
I2CIniciado = false;
DelayMs(5);
}
// 4) Fallback manual. Com TCA desconectado (READY baixo), pulsar o lado
// do ESP nao alcanca o lado OUT, portanto so tenta quando faz sentido.
if (!barramentoLivre && (_pinoTcaEn < 0 || TCAReadyOk())) {
DestravarBarramentoI2C(_pinoSDA, _pinoSCL);
barramentoLivre = AguardarBarramentoLivre(100);
}
bool wireReiniciado = false;
if (barramentoLivre) {
I2CIniciado = Wire.begin(_pinoSDA, _pinoSCL);
MostrarLog(
"I2C",
"Wire configurado | clock=" + String(Wire.getClock()) +
" timeout=" + String(Wire.getTimeOut()) + "ms"
);
if (I2CIniciado) {
Wire.setClock(100000);
Wire.setTimeOut(TimeoutWireMs);
wireReiniciado = true;
}
}
MarcarDispositivosParaRevalidacao();
bool ok = wireReiniciado && TCAReadyOk() && BarramentoFisicamenteLivre();
_readyLowDesdeMs = 0;
_falhasTransacaoConsecutivas = 0;
idAtualUsandoI2C = -1;
tempoEntradaI2C = 0;
LimparContextoAtual();
if (ok) {
_recoveryPendente = false;
_motivoRecoveryPendente = "";
_falhasRecoveryConsecutivas = 0;
LimparContextoFalha();
} else {
_recoveryPendente = true;
_motivoRecoveryPendente = "Nova tentativa apos recovery sem sucesso";
_falhasRecoveryConsecutivas++;
}
xSemaphoreGive(i2cMutex);
i2cMutexReiniciando = false;
_recuperacaoEmAndamento = false;
if (ok) {
MostrarLog(
"I2C",
String("Recovery concluido com sucesso") +
(responsavelConfirmado ? " | responsavel identificado" : " | responsavel nao confirmado")
);
} else {
MostrarLog(
"I2C",
"Recovery falhou | falhas_consecutivas=" +
String(_falhasRecoveryConsecutivas)
);
if (_falhasRecoveryConsecutivas >= MaxFalhasRecoveryAntesRestart) {
MostrarLog("I2C", "Limite de recoveries excedido, reiniciando ESP");
DelayMs(100);
ESP.restart();
}
}
return ok;
}
static void VerificarTCAReady() {
if (_pinoTcaRdy < 0 || _recuperacaoEmAndamento) {
return;
}
bool ready = digitalRead(_pinoTcaRdy) == HIGH;
if (ready) {
_readyLowDesdeMs = 0;
return;
}
if (_readyLowDesdeMs == 0) {
_readyLowDesdeMs = millis();
return;
}
if (!TempoPassou(_readyLowDesdeMs, LimiteReadyLowMs)) {
return;
}
String motivo =
"TCA4307 READY baixo por " +
String(LimiteReadyLowMs) + "ms";
if (idAtualUsandoI2C >= 0) {
AgendarRecovery(motivo);
return;
}
AgendarRecovery(motivo);
}
static void VerificarI2CPreso() {
if (_recuperacaoEmAndamento || idAtualUsandoI2C < 0) {
return;
}
uint32_t tempoPreso = (uint32_t)(millis() - tempoEntradaI2C);
if (tempoPreso <= LimiteTempoI2C) {
return;
}
if (!_recoveryPendente) {
String motivo =
"Acesso I2C preso pelo ID " +
String(idAtualUsandoI2C) +
" por " + String(tempoPreso) + "ms";
MostrarLog("I2C", motivo);
AgendarRecovery(motivo);
}
// Nunca apaga o mutex enquanto outra task pode estar dentro do Wire.
// O timeout do Wire deve permitir o retorno. Se nem isso acontecer,
// o restart completo e o ultimo recurso seguro.
if (tempoPreso > LimiteTempoI2C + MargemHardRestartMs) {
MostrarLog(
"I2C",
"Task I2C nao retornou apos o timeout; reiniciando ESP por seguranca"
);
DelayMs(100);
ESP.restart();
}
}
static void VerificarRecoveryPendente() {
if (
!_recoveryPendente ||
_recuperacaoEmAndamento ||
idAtualUsandoI2C >= 0
) {
return;
}
RecuperarBarramento(_motivoRecoveryPendente, false);
}
static void i2cTaskWrapper(void *pvParameters) {
(void)pvParameters;
I2CService::i2cTask();
}
static void i2cTask() {
while (true) {
VerificarSaudeBarramento();
DelayMs(50);
}
}
static bool DesabilitarMuxAtualSemMutex() {
if (enderecoMuxAtivo == 0xFF) {
MostrarLog("MUX", "Nenhum MUX marcado como ativo");
canalAtivo = -1;
return true;
}
const uint8_t endereco = enderecoMuxAtivo;
const int canalAnterior = canalAtivo;
uint32_t inicio = millis();
Wire.beginTransmission(endereco);
Wire.write((uint8_t)0x00);
int erro = Wire.endTransmission();
uint32_t duracao = (uint32_t)(millis() - inicio);
MostrarLog(
"MUX",
"Desabilitar canais"
" | anterior=" + String(canalAnterior) +
" erro=" + String(erro) +
" duracao=" + String(duracao) + "ms" +
" SDA=" + String(digitalRead(_pinoSDA)) +
" SCL=" + String(digitalRead(_pinoSCL)) +
" RDY=" + String(TCAReadyOk() ? 1 : 0)
);
if (erro != 0) {
RegistrarFalhaTransacao("Falha ao desabilitar canais do MUX");
return false;
}
enderecoMuxAtivo = 0xFF;
canalAtivo = -1;
RegistrarSucessoTransacao();
return true;
}
};
bool I2CService::DebugMode = false;
int I2CService::_pinoSDA = 1;
int I2CService::_pinoSCL = 2;
int I2CService::_pinoTcaRdy = -1;
int I2CService::_pinoTcaEn = -1;
int I2CService::_pinoMuxRst = -1;
int I2CService::_pinoAdsPwrEn = -1;
unsigned long I2CService::LimiteTempoI2C = 2000;
bool I2CService::I2CIniciado = false;
bool I2CService::MuxIniciado = false;
bool I2CService::McpIniciado = false;
bool I2CService::AdsIniciado = false;
int I2CService::idSensorMux = 200;
int I2CService::idSensorMcp = 201;
Adafruit_MCP23X17 I2CService::mcp;
Adafruit_ADS1115 I2CService::ads;
byte I2CService::ultimoEnderecoADS = 0xFF;
uint8_t I2CService::enderecoMuxAtivo = 0xFF;
bool I2CService::_muxValidado[8] = {
false, false, false, false,
false, false, false, false
};
int I2CService::canalAtivo = -1;
int I2CService::idAtualUsandoI2C = -1;
unsigned long I2CService::tempoEntradaI2C = 0;
SemaphoreHandle_t I2CService::i2cMutex = nullptr;
TaskHandle_t I2CService::i2cTaskHandle = NULL;
bool I2CService::i2cMutexReiniciando = false;
bool I2CService::_recuperacaoEmAndamento = false;
bool I2CService::_recoveryPendente = false;
String I2CService::_motivoRecoveryPendente = "";
uint32_t I2CService::_falhasTransacaoConsecutivas = 0;
uint32_t I2CService::_falhasRecoveryConsecutivas = 0;
uint32_t I2CService::_totalRecoveries = 0;
unsigned long I2CService::_ultimoRecoveryMs = 0;
unsigned long I2CService::_readyLowDesdeMs = 0;
I2CService::EstadoQuarentena I2CService::_saudeCanaisMux[8][8] = {};
I2CService::EstadoQuarentena I2CService::_saudeADS[4] = {};
I2CService::ContextoBarramento I2CService::_contextoAtual = {};
I2CService::ContextoBarramento I2CService::_contextoFalha = {};
portMUX_TYPE I2CService::_estadoLock = portMUX_INITIALIZER_UNLOCKED;
#endif