agrobot_base/Firmware/Modulos/I2CService.h

2773 lines
79 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>
#include <cstring>
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;
};
struct DiagnosticoCAN {
bool i2cIniciado = false;
bool muxIniciado = false;
bool infraestruturaMuxOk = false;
bool recoveryEmAndamento = false;
bool recoveryPendente = false;
bool sdaHigh = false;
bool sclHigh = false;
bool tcaRdyConfigurado = false;
bool tcaReady = false;
bool tcaEnConfigurado = false;
bool tcaEnHigh = false;
bool algumCanalMuxQuarentena = false;
bool algumAdsQuarentena = false;
uint32_t geracaoBarramento = 0;
uint32_t totalRecoveries = 0;
uint32_t falhasRecoveryConsecutivas = 0;
uint32_t falhasTransacaoConsecutivas = 0;
int ultimoIdFalha = -1;
int ultimoCanalFalha = -1;
uint8_t muxEsperadoMask = 0;
uint8_t muxValidadoMask = 0;
uint8_t mux0QuarentenaMask = 0;
// ADS:
// maxLeituraAdsMs = tempo total de parede do ciclo ADS.
// maxInicioAdsMs = maior duracao da escrita que inicia a conversao.
// maxResultadoAdsMs = maior duracao da leitura do registrador de conversao.
// falhasIoAds = NACK/leitura incompleta/erro rapido que nao foi timeout.
uint32_t maxLeituraAdsMs = 0;
uint32_t maxInicioAdsMs = 0;
uint32_t maxResultadoAdsMs = 0;
uint32_t timeoutsAds = 0;
uint32_t readyDropsAds = 0;
uint32_t falhasIoAds = 0;
uint32_t wireTimeoutMs = 0;
};
static DiagnosticoCAN ObterDiagnosticoCAN() {
DiagnosticoCAN d;
portENTER_CRITICAL(&_estadoLock);
d.i2cIniciado = I2CIniciado;
d.muxIniciado = MuxIniciado;
d.recoveryEmAndamento = _recuperacaoEmAndamento;
d.recoveryPendente = _recoveryPendente;
d.geracaoBarramento = _geracaoBarramento;
d.totalRecoveries = _totalRecoveries;
d.falhasRecoveryConsecutivas = _falhasRecoveryConsecutivas;
d.falhasTransacaoConsecutivas = _falhasTransacaoConsecutivas;
d.ultimoIdFalha = _ultimoIdFalhaDiag;
d.ultimoCanalFalha = _ultimoCanalFalhaDiag;
d.muxEsperadoMask = _muxEsperadoMask;
uint8_t validMask = 0;
for (int i = 0; i < 8; i++) {
if (_muxValidado[i]) {
validMask |= (uint8_t)(1U << i);
}
}
d.muxValidadoMask = validMask;
d.maxLeituraAdsMs = _maxLeituraAdsMs;
d.maxInicioAdsMs = _maxInicioAdsMs;
d.maxResultadoAdsMs = _maxResultadoAdsMs;
d.timeoutsAds = _timeoutsAds;
d.readyDropsAds = _readyDropsAds;
d.falhasIoAds = _falhasIoAds;
portEXIT_CRITICAL(&_estadoLock);
d.infraestruturaMuxOk = (d.muxEsperadoMask & d.muxValidadoMask) == d.muxEsperadoMask;
d.sdaHigh = _pinoSDA >= 0 && digitalRead(_pinoSDA) == HIGH;
d.sclHigh = _pinoSCL >= 0 && digitalRead(_pinoSCL) == HIGH;
d.tcaRdyConfigurado = _pinoTcaRdy >= 0;
d.tcaReady = d.tcaRdyConfigurado && digitalRead(_pinoTcaRdy) == HIGH;
d.tcaEnConfigurado = _pinoTcaEn >= 0;
d.tcaEnHigh = d.tcaEnConfigurado && digitalRead(_pinoTcaEn) == HIGH;
uint8_t quarentenaMask = 0;
// SEN atual usa o primeiro MUX, 0x70.
for (uint8_t canal = 0; canal < 8; canal++) {
if (CanalMuxEmQuarentena(canal)) {
quarentenaMask |= (uint8_t)(1U << canal);
}
}
d.mux0QuarentenaMask = quarentenaMask;
d.algumCanalMuxQuarentena = quarentenaMask != 0;
d.algumAdsQuarentena = QuantidadeADSEmQuarentena() > 0;
// getTimeOut() apenas devolve o valor configurado no objeto Wire.
d.wireTimeoutMs = I2CIniciado ? Wire.getTimeOut() : 0;
return d;
}
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;
}
// Cada Wire.begin() bem-sucedido cria uma nova geracao logica do barramento.
// Drivers de sensores podem usar este valor para invalidar inicializacoes
// feitas antes de um recovery completo.
static uint32_t GeracaoBarramento() {
uint32_t geracao;
portENTER_CRITICAL(&_estadoLock);
geracao = _geracaoBarramento;
portEXIT_CRITICAL(&_estadoLock);
return geracao;
}
static bool InfraestruturaMuxOk() {
uint8_t esperados;
portENTER_CRITICAL(&_estadoLock);
esperados = _muxEsperadoMask;
portEXIT_CRITICAL(&_estadoLock);
if (esperados == 0) {
return true;
}
for (int i = 0; i < 8; i++) {
if ((esperados & (1U << i)) != 0 && !_muxValidado[i]) {
return false;
}
}
return true;
}
static bool IniciarI2C(bool forcar = false) {
GarantirMutexes();
ConfigurarPinosControle();
// Serializa todo o lifecycle Wire.end()/Wire.begin() com os recoveries.
if (xSemaphoreTake(lifecycleMutex, pdMS_TO_TICKS(500)) != pdTRUE) {
MostrarLog("I2C", "Inicializacao adiada: lifecycle ocupado");
return false;
}
bool tomouI2C = false;
bool resultado = false;
do {
if (xSemaphoreTake(i2cMutex, pdMS_TO_TICKS(300)) != pdTRUE) {
MostrarLog("I2C", "Inicializacao adiada: barramento em uso");
break;
}
tomouI2C = true;
DefinirEstadoRecovery(true, true);
bool barramentoLivre = forcar || BarramentoFisicamenteLivre();
// Recuperacoes localizadas antes de desmontar o Wire.
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);
}
FinalizarWireAtual();
if (!barramentoLivre && (_pinoTcaEn < 0 || TCAReadyOk())) {
MostrarLog("I2C", "Tentando destravamento manual SDA/SCL antes do Wire.begin");
DestravarBarramentoI2C(_pinoSDA, _pinoSCL);
barramentoLivre = AguardarBarramentoLivre(100);
}
if (!barramentoLivre && !forcar) {
MostrarLog("I2C", "Wire nao iniciado: linhas continuam ocupadas");
I2CIniciado = false;
break;
}
resultado = IniciarWireNovo();
idAtualUsandoI2C = -1;
tempoEntradaI2C = 0;
_readyLowDesdeMs = 0;
portENTER_CRITICAL(&_estadoLock);
_falhasTransacaoConsecutivas = 0;
_recoveryPendente = false;
_motivoRecoveryPendente[0] = '\0';
portEXIT_CRITICAL(&_estadoLock);
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(BarramentoFisicamenteLivre() ? 1 : 0) +
" geracao=" + String(GeracaoBarramento()) +
" res=" + String(resultado ? 1 : 0)
);
} while (false);
DefinirEstadoRecovery(false, false);
if (tomouI2C) {
xSemaphoreGive(i2cMutex);
}
xSemaphoreGive(lifecycleMutex);
if (i2cTaskHandle == NULL) {
xTaskCreatePinnedToCore(
I2CService::i2cTaskWrapper,
"i2cTask",
4096,
nullptr,
2,
&i2cTaskHandle,
tskNO_AFFINITY
);
}
return resultado;
}
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 TentarRecuperar(const String& motivo, bool ignorarCooldown = false) {
return RecuperarBarramento(motivo, ignorarCooldown);
}
static void NotificarFalhaDispositivo(int idSensor, const String& motivo) {
RegistrarFalhaTransacao("ID " + String(idSensor) + " | " + motivo);
}
static bool CanalMuxEmQuarentena(uint8_t canalGlobal) {
uint8_t indiceMux = canalGlobal / 10;
uint8_t canal = canalGlobal % 10;
if (indiceMux > 7 || canal > 7) {
return true;
}
const uint32_t agora = millis();
bool bloqueado = false;
bool expirou = false;
portENTER_CRITICAL(&_estadoLock);
EstadoQuarentena& estado = _saudeCanaisMux[indiceMux][canal];
if (
estado.bloqueado &&
estado.ultimaOcorrenciaMs > 0 &&
(uint32_t)(agora - estado.ultimaOcorrenciaMs) >= JanelaQuarentenaMs
) {
estado = EstadoQuarentena();
expirou = true;
}
bloqueado = estado.bloqueado;
portEXIT_CRITICAL(&_estadoLock);
if (expirou) {
MostrarLog("MUX", "Quarentena expirada automaticamente | canalGlobal=" + String(canalGlobal));
}
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;
}
(void)CanalMuxEmQuarentena(canalGlobal);
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;
}
const uint32_t agora = millis();
bool bloqueado = false;
bool expirou = false;
portENTER_CRITICAL(&_estadoLock);
EstadoQuarentena& estado = _saudeADS[indice];
if (
estado.bloqueado &&
estado.ultimaOcorrenciaMs > 0 &&
(uint32_t)(agora - estado.ultimaOcorrenciaMs) >= JanelaQuarentenaMs
) {
estado = EstadoQuarentena();
expirou = true;
}
bloqueado = estado.bloqueado;
portEXIT_CRITICAL(&_estadoLock);
if (expirou) {
MostrarLog("ADS", "Quarentena expirada automaticamente | endereco=0x" + String(endereco, HEX));
}
return bloqueado;
}
static uint8_t RecuperacoesConfirmadasADS(uint8_t endereco = enderecoAds) {
int indice = IndiceADS(endereco);
if (indice < 0) {
return 0;
}
(void)ADSEmQuarentena(endereco);
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() {
bool recovery;
bool pendente;
uint32_t falhasTx;
uint32_t geracao;
uint8_t muxEsperados;
portENTER_CRITICAL(&_estadoLock);
recovery = _recuperacaoEmAndamento;
pendente = _recoveryPendente;
falhasTx = _falhasTransacaoConsecutivas;
geracao = _geracaoBarramento;
muxEsperados = _muxEsperadoMask;
portEXIT_CRITICAL(&_estadoLock);
String s = "";
s += "i2c=" + String(I2CIniciado ? 1 : 0);
s += " gen=" + String(geracao);
s += " usando=" + String(idAtualUsandoI2C);
s += " recovery=" + String(recovery ? 1 : 0);
s += " pendente=" + String(pendente ? 1 : 0);
s += " fails_tx=" + String(falhasTx);
s += " total_recovery=" + String(_totalRecoveries);
s += " falhas_recovery=" + String(_falhasRecoveryConsecutivas);
s += " mux=" + String(MuxIniciado ? 1 : 0);
s += " mux_ok=" + String(InfraestruturaMuxOk() ? 1 : 0);
s += " mux_mask=0x" + String(muxEsperados, HEX);
s += " mux_addr=0x" + String(enderecoMuxAtivo, HEX);
s += " canal=" + String(CanalGlobalAtivoAtual());
s += " SDA=" + String((_pinoSDA >= 0) ? digitalRead(_pinoSDA) : -1);
s += " SCL=" + String((_pinoSCL >= 0) ? digitalRead(_pinoSCL) : -1);
s += " q_mux=" + String(QuantidadeCanaisMuxEmQuarentena());
s += " q_ads=" + String(QuantidadeADSEmQuarentena());
if (_pinoTcaRdy >= 0) {
s += " tca_rdy=" + String(digitalRead(_pinoTcaRdy) == HIGH ? 1 : 0);
} else {
s += " tca_rdy=-1";
}
if (_pinoTcaEn >= 0) {
s += " tca_en=" + String(digitalRead(_pinoTcaEn) == HIGH ? 1 : 0);
} else {
s += " tca_en=-1";
}
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 VerificaEnderecoBarramentoPassivo(byte endereco, uint32_t timeoutMs = 60) {
/*
* IMPORTANTE:
* esta função pressupõe que o chamador
* já possui ownership do i2cMutex.
*
* Ausência de um periférico durante retry
* NÃO é evidência de falha global.
*/
if (!I2CIniciado) {
return false;
}
if (!TCAReadyOk()) {
return false;
}
const uint32_t inicio =
millis();
Wire.beginTransmission(
endereco
);
const uint8_t erro =
Wire.endTransmission();
const uint32_t duracao =
(uint32_t)(
millis() - inicio
);
if (erro != 0) {
return false;
}
if (duracao >= timeoutMs) {
return false;
}
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 (RecoveryBloqueandoAcesso()) {
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 (RecoveryBloqueandoAcesso() || !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)
);
String motivo;
bool precisaRecuperar = ObterRecoveryPendente(motivo);
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;
}
String motivo;
bool precisaRecuperar = ObterRecoveryPendente(motivo);
ContextoBarramento contexto = _contextoAtual;
if (
contexto.valido &&
contexto.canalMuxSolicitado >= 0 &&
!RecoveryPendente()
) {
DesabilitarMuxAtualSemMutex();
precisaRecuperar = ObterRecoveryPendente(motivo);
}
if (precisaRecuperar) {
CapturarContextoFalha();
DefinirMutexReiniciando(true);
}
LimparContextoAtual();
idAtualUsandoI2C = -1;
tempoEntradaI2C = 0;
xSemaphoreGive(i2cMutex);
if (precisaRecuperar) {
RecuperarBarramento(motivo, true);
}
}
static void RecriarMutex() {
// Em produto nao deletamos um mutex FreeRTOS em runtime porque outra
// task pode estar aguardando nele. Se o handle sumiu, apenas o recriamos.
GarantirMutexes();
MostrarLog("I2C", "Mutexes verificados; recriacao destrutiva desabilitada em runtime");
}
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;
}
portENTER_CRITICAL(&_estadoLock);
_muxEsperadoMask |= (uint8_t)(1U << indiceMux);
portEXIT_CRITICAL(&_estadoLock);
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 IniciarADSSemMutex(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;
}
const 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++) {
int16_t leituraADC = 0;
const bool leituraOk = LerADSSingleShotComTimeout(canal, leituraADC);
if (!leituraOk) {
leituraFalhou = true;
break;
}
/*
* Só chegamos aqui se:
*
* - conversão iniciou
* - terminou no prazo
* - TCA continuou READY
* - resultado foi obtido
*/
RegistrarSucessoTransacao();
const int leitura12bits = map((int)leituraADC, 0, 32767, 0, 4095);
soma += leitura12bits;
leiturasConcluidas++;
}
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;
}
portENTER_CRITICAL(&_estadoLock);
_muxEsperadoMask |= (uint8_t)(1U << indiceMux);
portEXIT_CRITICAL(&_estadoLock);
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 = PingEnderecoSemRegistrar(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)
return false;
constexpr int idSensorAds = 202;
if (!SolicitarAcessoI2C(idSensorAds, -1)) {
return false;
}
MarcarEnderecoADSNoContexto(endereco);
bool ok = IniciarADSSemMutex(endereco);
LiberarAcessoI2C(idSensorAds);
return ok;
}
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 SemaphoreHandle_t lifecycleMutex;
static TaskHandle_t i2cTaskHandle;
static bool _recuperacaoEmAndamento;
static bool _recoveryPendente;
static char _motivoRecoveryPendente[192];
static uint32_t _geracaoBarramento;
static uint8_t _muxEsperadoMask;
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;
// Transacoes deste SEN sao de poucos bytes a 100 kHz.
// 25 ms ainda e uma margem muito grande, mas limita melhor uma
// transferencia realmente emperrada.
static constexpr uint32_t TimeoutWireMs = 25;
static constexpr uint32_t LimiteReadyLowMs = 200;
static constexpr uint32_t CooldownRecoveryMs = 1000;
// O tempo total do ciclo ADS e apenas diagnostico: pode incluir
// preempcao/escalonamento do FreeRTOS. Falha eletrica e julgada pelas
// fases I2C individuais abaixo.
static constexpr uint32_t LimiteLeituraADS_MS = 50;
static constexpr uint32_t LimiteFaseADS_MS = 35;
static constexpr uint32_t EsperaConversaoADS_MS = 10;
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 int _ultimoIdFalhaDiag;
static int _ultimoCanalFalhaDiag;
static uint32_t _maxLeituraAdsMs;
static uint32_t _maxInicioAdsMs;
static uint32_t _maxResultadoAdsMs;
static uint32_t _timeoutsAds;
static uint32_t _readyDropsAds;
static uint32_t _falhasIoAds;
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 GarantirMutexes() {
GarantirMutex();
if (lifecycleMutex == nullptr) {
lifecycleMutex = xSemaphoreCreateMutex();
}
}
static bool RecoveryEmAndamento() {
bool valor;
portENTER_CRITICAL(&_estadoLock);
valor = _recuperacaoEmAndamento;
portEXIT_CRITICAL(&_estadoLock);
return valor;
}
static bool RecoveryPendente() {
bool valor;
portENTER_CRITICAL(&_estadoLock);
valor = _recoveryPendente;
portEXIT_CRITICAL(&_estadoLock);
return valor;
}
static void DefinirMutexReiniciando(bool valor) {
portENTER_CRITICAL(&_estadoLock);
i2cMutexReiniciando = valor;
portEXIT_CRITICAL(&_estadoLock);
}
static bool RecoveryBloqueandoAcesso() {
bool valor;
portENTER_CRITICAL(&_estadoLock);
valor = _recuperacaoEmAndamento || i2cMutexReiniciando;
portEXIT_CRITICAL(&_estadoLock);
return valor;
}
static void DefinirEstadoRecovery(bool emAndamento, bool reiniciandoMutex) {
portENTER_CRITICAL(&_estadoLock);
_recuperacaoEmAndamento = emAndamento;
i2cMutexReiniciando = reiniciandoMutex;
portEXIT_CRITICAL(&_estadoLock);
}
static void IncrementarGeracaoBarramento() {
portENTER_CRITICAL(&_estadoLock);
if (_geracaoBarramento == UINT32_MAX) {
_geracaoBarramento = 1;
} else {
_geracaoBarramento++;
if (_geracaoBarramento == 0) {
_geracaoBarramento = 1;
}
}
portEXIT_CRITICAL(&_estadoLock);
}
static void FinalizarWireAtual() {
if (I2CIniciado) {
Wire.end();
I2CIniciado = false;
DelayMs(5);
}
}
static bool IniciarWireNovo() {
MostrarLog("I2C", "Iniciando Wire...");
bool iniciou = Wire.begin(_pinoSDA, _pinoSCL);
I2CIniciado = iniciou;
if (!iniciou) {
MostrarLog("I2C", "Wire.begin falhou");
return false;
}
Wire.setClock(100000);
Wire.setTimeOut(TimeoutWireMs);
IncrementarGeracaoBarramento();
MostrarLog(
"I2C",
"Wire configurado | clock=" + String(Wire.getClock()) +
" timeout=" + String(Wire.getTimeOut()) + "ms" +
" geracao=" + String(GeracaoBarramento())
);
return true;
}
static bool PingEnderecoSemRegistrar(uint8_t endereco, uint32_t timeoutMs = 50) {
if (!I2CIniciado) {
return false;
}
// Não toca no Wire enquanto o TCA4307 indicar
// que os dois lados do barramento não estão prontos.
if (!TCAReadyOk()) {
return false;
}
const uint32_t inicio = millis();
Wire.beginTransmission(endereco);
const uint8_t erro = Wire.endTransmission();
const uint32_t duracao = (uint32_t)(millis() - inicio);
return erro == 0 && duracao <= timeoutMs;
}
static bool ValidarMuxEsperadosSemRegistrar() {
uint8_t esperados;
portENTER_CRITICAL(&_estadoLock);
esperados = _muxEsperadoMask;
portEXIT_CRITICAL(&_estadoLock);
if (esperados == 0) {
return true;
}
bool todosOk = true;
bool algumOk = false;
for (int i = 0; i < 8; i++) {
if ((esperados & (1U << i)) == 0) {
continue;
}
uint8_t endereco = enderecoMux + i;
bool ok = PingEnderecoSemRegistrar(endereco, 60);
_muxValidado[i] = ok;
algumOk = algumOk || ok;
todosOk = todosOk && ok;
MostrarLog(
"MUX",
"Revalidacao pos-recovery | endereco=0x" + String(endereco, HEX) +
" ok=" + String(ok ? 1 : 0)
);
}
MuxIniciado = algumOk;
enderecoMuxAtivo = 0xFF;
canalAtivo = -1;
return todosOk;
}
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();
ContextoBarramento contexto =
ObterContextoFalha();
int canalSuspeito =
CanalSuspeitoDoContexto(
contexto
);
portENTER_CRITICAL(&_estadoLock);
if (contexto.valido) {
_ultimoIdFalhaDiag =
contexto.idSensor;
_ultimoCanalFalhaDiag =
canalSuspeito;
}
_recoveryPendente = true;
strncpy(
_motivoRecoveryPendente,
motivo.c_str(),
sizeof(_motivoRecoveryPendente) - 1
);
_motivoRecoveryPendente[
sizeof(_motivoRecoveryPendente) - 1
] = '\0';
portEXIT_CRITICAL(&_estadoLock);
}
static bool ObterRecoveryPendente(String& motivo) {
char buffer[sizeof(_motivoRecoveryPendente)];
bool pendente;
portENTER_CRITICAL(&_estadoLock);
pendente = _recoveryPendente;
strncpy(buffer, _motivoRecoveryPendente, sizeof(buffer) - 1);
buffer[sizeof(buffer) - 1] = '\0';
portEXIT_CRITICAL(&_estadoLock);
motivo = String(buffer);
return pendente;
}
static void LimparRecoveryPendente() {
portENTER_CRITICAL(&_estadoLock);
_recoveryPendente = false;
_motivoRecoveryPendente[0] = '\0';
portEXIT_CRITICAL(&_estadoLock);
}
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() {
const uint32_t agora = millis();
uint8_t total = 0;
portENTER_CRITICAL(&_estadoLock);
for (int mux = 0; mux < 8; mux++) {
for (int canal = 0; canal < 8; canal++) {
EstadoQuarentena& estado = _saudeCanaisMux[mux][canal];
if (
estado.bloqueado &&
estado.ultimaOcorrenciaMs > 0 &&
(uint32_t)(agora - estado.ultimaOcorrenciaMs) >= JanelaQuarentenaMs
) {
estado = EstadoQuarentena();
}
if (estado.bloqueado) {
total++;
}
}
}
portEXIT_CRITICAL(&_estadoLock);
return total;
}
static uint8_t QuantidadeADSEmQuarentena() {
const uint32_t agora = millis();
uint8_t total = 0;
portENTER_CRITICAL(&_estadoLock);
for (int i = 0; i < 4; i++) {
EstadoQuarentena& estado = _saudeADS[i];
if (
estado.bloqueado &&
estado.ultimaOcorrenciaMs > 0 &&
(uint32_t)(agora - estado.ultimaOcorrenciaMs) >= JanelaQuarentenaMs
) {
estado = EstadoQuarentena();
}
if (estado.bloqueado) {
total++;
}
}
portEXIT_CRITICAL(&_estadoLock);
return total;
}
static void RegistrarFalhaTransacao(const String& motivo) {
uint32_t falhas;
portENTER_CRITICAL(&_estadoLock);
if (_falhasTransacaoConsecutivas < UINT32_MAX) {
_falhasTransacaoConsecutivas++;
}
falhas = _falhasTransacaoConsecutivas;
portEXIT_CRITICAL(&_estadoLock);
MostrarLog(
"I2C",
"Falha transacao #" + String(falhas) +
" | " + motivo
);
if (falhas >= LimiteFalhasTransacaoRecovery) {
AgendarRecovery("Falhas consecutivas no I2C: " + motivo);
}
}
static void RegistrarSucessoTransacao() {
portENTER_CRITICAL(&_estadoLock);
_falhasTransacaoConsecutivas = 0;
portEXIT_CRITICAL(&_estadoLock);
}
static bool RecuperarBarramento(const String& motivo, bool ignorarCooldown = false) {
GarantirMutexes();
// Um unico dono pode desmontar/recriar Wire por vez.
if (xSemaphoreTake(lifecycleMutex, 0) != pdTRUE) {
AgendarRecovery(motivo);
return false;
}
bool tomouI2C = false;
bool retorno = false;
do {
const uint32_t agora = millis();
if (
!ignorarCooldown &&
_ultimoRecoveryMs > 0 &&
!TempoPassou(_ultimoRecoveryMs, CooldownRecoveryMs)
) {
AgendarRecovery(motivo);
break;
}
DefinirEstadoRecovery(true, true);
_ultimoRecoveryMs = agora;
_totalRecoveries++;
MostrarLog("I2C", "Recovery iniciado | Motivo: " + motivo);
if (xSemaphoreTake(i2cMutex, pdMS_TO_TICKS(150)) != pdTRUE) {
MostrarLog("I2C", "Recovery adiado porque o mutex ainda esta ocupado");
AgendarRecovery(motivo);
break;
}
tomouI2C = true;
ContextoBarramento contexto = ObterContextoFalha();
int canalSuspeito = CanalSuspeitoDoContexto(contexto);
bool travadoInicialmente = !BarramentoFisicamenteLivre();
bool barramentoLivre = !travadoInicialmente;
bool responsavelConfirmado = false;
// 1) Isola o ramal mais provavel primeiro.
if (travadoInicialmente && canalSuspeito >= 0 && _pinoMuxRst >= 0) {
ResetarMuxSePossivel();
barramentoLivre = AguardarBarramentoLivre(150);
if (barramentoLivre) {
RegistrarResponsavelCanalMux((uint8_t)canalSuspeito);
responsavelConfirmado = true;
}
}
// 2) Mantem suporte legado ao ADS quando houver alimentacao controlavel.
if (!barramentoLivre && contexto.enderecoADS != 0xFF && _pinoAdsPwrEn >= 0) {
ResetarADSSePossivel();
barramentoLivre = AguardarBarramentoLivre(150);
if (barramentoLivre) {
RegistrarResponsavelADS(contexto.enderecoADS);
responsavelConfirmado = true;
}
}
// Falha logica sem linha presa: reinicia o alvo relacionado, mas nao
// atribui quarentena sem evidencia eletrica.
if (!travadoInicialmente) {
if (canalSuspeito >= 0 && _pinoMuxRst >= 0) {
ResetarMuxSePossivel();
} else if (contexto.enderecoADS != 0xFF && _pinoAdsPwrEn >= 0) {
ResetarADSSePossivel();
}
barramentoLivre = AguardarBarramentoLivre(100);
}
// 3) Se existir TCA4307, usa a camada de isolamento/buffer global.
if (!barramentoLivre && _pinoTcaEn >= 0) {
ResetarTCA4307(true);
barramentoLivre = AguardarBarramentoLivre(200);
}
FinalizarWireAtual();
// 4) Ultimo fallback eletrico antes de recriar o controlador I2C.
if (!barramentoLivre && (_pinoTcaEn < 0 || TCAReadyOk())) {
DestravarBarramentoI2C(_pinoSDA, _pinoSCL);
barramentoLivre = AguardarBarramentoLivre(100);
}
bool wireReiniciado = false;
if (barramentoLivre) {
wireReiniciado = IniciarWireNovo();
}
MarcarDispositivosParaRevalidacao();
// Primeiro criterio: o barramento-base precisa estar realmente vivo.
bool baseOk =
wireReiniciado &&
TCAReadyOk() &&
BarramentoFisicamenteLivre();
// Segundo criterio: se algum MUX ja foi declarado parte da topologia,
// ele precisa responder para chamarmos o recovery de completo.
bool muxOk = false;
if (baseOk) {
muxOk = ValidarMuxEsperadosSemRegistrar();
}
bool infraestruturaOk = baseOk && muxOk;
_readyLowDesdeMs = 0;
idAtualUsandoI2C = -1;
tempoEntradaI2C = 0;
LimparContextoAtual();
portENTER_CRITICAL(&_estadoLock);
_falhasTransacaoConsecutivas = 0;
portEXIT_CRITICAL(&_estadoLock);
if (infraestruturaOk) {
LimparRecoveryPendente();
_falhasRecoveryConsecutivas = 0;
LimparContextoFalha();
retorno = true;
MostrarLog(
"I2C",
String("Recovery concluido com sucesso") +
(responsavelConfirmado
? " | responsavel identificado"
: " | responsavel nao confirmado") +
" | geracao=" + String(GeracaoBarramento())
);
}
else if (baseOk) {
// Wire esta saudavel, mas a infraestrutura downstream nao.
// Nao reinicia o ESP: deixa o modulo vivo e permite retry de MUX/sensores.
LimparRecoveryPendente();
_falhasRecoveryConsecutivas = 0;
LimparContextoFalha();
retorno = false;
MostrarLog(
"I2C",
"Recovery restaurou o barramento-base, mas MUX esperado nao respondeu; "
"mantendo SEN ativo para revalidacao posterior"
);
}
else {
AgendarRecovery("Nova tentativa apos recovery global sem sucesso");
_falhasRecoveryConsecutivas++;
MostrarLog(
"I2C",
"Recovery global falhou | falhas_consecutivas=" +
String(_falhasRecoveryConsecutivas)
);
// Restart completo fica reservado a falha do barramento-base,
// nunca a simples ausencia de um periferico/MUX.
if (_falhasRecoveryConsecutivas >= MaxFalhasRecoveryAntesRestart) {
if (tomouI2C) {
xSemaphoreGive(i2cMutex);
tomouI2C = false;
}
DefinirEstadoRecovery(false, false);
xSemaphoreGive(lifecycleMutex);
MostrarLog("I2C", "Barramento-base irrecuperavel; reiniciando ESP");
DelayMs(100);
ESP.restart();
return false;
}
}
} while (false);
if (tomouI2C) {
xSemaphoreGive(i2cMutex);
}
DefinirEstadoRecovery(false, false);
xSemaphoreGive(lifecycleMutex);
return retorno;
}
static void VerificarTCAReady() {
if (_pinoTcaRdy < 0 || RecoveryEmAndamento()) {
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 (RecoveryEmAndamento() || idAtualUsandoI2C < 0) {
return;
}
const uint32_t tempoOwnership =
(uint32_t)(millis() - tempoEntradaI2C);
if (tempoOwnership <= LimiteTempoI2C) {
return;
}
/*
* tempoEntradaI2C mede ownership logico do service, nao uma unica
* transferencia Wire. Esse intervalo pode incluir:
*
* - espera da conversao ADS;
* - varias leituras do mesmo sensor;
* - preempcao/escalonamento da task.
*
* Portanto ownership longo, sozinho, NAO prova falha eletrica.
* So agenda recovery se tambem houver evidencia fisica no barramento.
*/
const bool readyOk = TCAReadyOk();
const bool linhasLivres = WirePodeFinalizar();
const bool evidenciaEletrica = !readyOk || !linhasLivres;
if (!RecoveryPendente()) {
if (evidenciaEletrica) {
String motivo =
"Ownership I2C prolongado com barramento ocupado | ID " +
String(idAtualUsandoI2C) +
" | tempo=" + String(tempoOwnership) + "ms" +
" | READY=" + String(readyOk ? 1 : 0) +
" | SDA=" + String(digitalRead(_pinoSDA)) +
" | SCL=" + String(digitalRead(_pinoSCL));
MostrarLog("I2C", motivo);
AgendarRecovery(motivo);
}
else {
/*
* Barramento fisicamente livre: pode ser task preemptada,
* processamento lento ou espera por lock interno de software.
* Nao derruba toda a infraestrutura por isso.
*/
MostrarLog(
"I2C",
"Ownership prolongado sem evidencia eletrica | ID=" +
String(idAtualUsandoI2C) +
" tempo=" + String(tempoOwnership) + "ms"
);
}
}
/*
* Mesmo com linhas livres, ownership que nunca retorna pode significar
* deadlock de software/lock interno do Wire. Mantemos o hard restart
* como ultimo recurso, sem tentar deletar mutex em uso.
*/
if (
tempoOwnership >
LimiteTempoI2C + MargemHardRestartMs
) {
MostrarLog(
"I2C",
"Task dona do I2C nao retornou; reiniciando ESP por seguranca"
);
DelayMs(100);
ESP.restart();
}
}
static void VerificarRecoveryPendente() {
if (RecoveryEmAndamento() || idAtualUsandoI2C >= 0) {
return;
}
String motivo;
if (!ObterRecoveryPendente(motivo)) {
return;
}
if (motivo.length() == 0) {
motivo = "Recovery pendente";
}
RecuperarBarramento(motivo, 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;
}
static bool IniciarADSSemMutex(byte endereco) {
if (!I2CIniciado)
return false;
if (ADSEmQuarentena(endereco))
return false;
if (AdsIniciado && endereco == ultimoEnderecoADS) {
return true;
}
// Descoberta passiva.
if (!PingEnderecoSemRegistrar(endereco)) {
AdsIniciado = false;
ultimoEnderecoADS = 0xFF;
return false;
}
AdsIniciado = ads.begin(endereco, &Wire);
if (!AdsIniciado) {
ultimoEnderecoADS = 0xFF;
return false;
}
ads.setGain(GAIN_ONE);
ads.setDataRate(RATE_ADS1115_128SPS);
ultimoEnderecoADS = endereco;
return true;
}
static void AtualizarMaxDuracaoADS(uint32_t inicioMs) {
const uint32_t duracao = (uint32_t)(millis() - inicioMs);
portENTER_CRITICAL(&_estadoLock);
if (duracao > _maxLeituraAdsMs) {
_maxLeituraAdsMs = duracao;
}
portEXIT_CRITICAL(&_estadoLock);
}
static void AtualizarMaxFaseInicioADS(uint32_t duracaoMs) {
portENTER_CRITICAL(&_estadoLock);
if (duracaoMs > _maxInicioAdsMs) {
_maxInicioAdsMs = duracaoMs;
}
portEXIT_CRITICAL(&_estadoLock);
}
static void AtualizarMaxFaseResultadoADS(uint32_t duracaoMs) {
portENTER_CRITICAL(&_estadoLock);
if (duracaoMs > _maxResultadoAdsMs) {
_maxResultadoAdsMs = duracaoMs;
}
portEXIT_CRITICAL(&_estadoLock);
}
static void IncrementarTimeoutADS() {
portENTER_CRITICAL(&_estadoLock);
if (_timeoutsAds < UINT32_MAX) {
_timeoutsAds++;
}
portEXIT_CRITICAL(&_estadoLock);
}
static void IncrementarFalhaIoADS() {
portENTER_CRITICAL(&_estadoLock);
if (_falhasIoAds < UINT32_MAX) {
_falhasIoAds++;
}
portEXIT_CRITICAL(&_estadoLock);
}
static void RegistrarTimeoutADS(uint32_t inicioMs, const char* motivo) {
AtualizarMaxDuracaoADS(inicioMs);
IncrementarTimeoutADS();
RegistrarFalhaTransacao(motivo);
}
static void RegistrarReadyDropADS(uint32_t inicioMs) {
AtualizarMaxDuracaoADS(inicioMs);
portENTER_CRITICAL(&_estadoLock);
if (_readyDropsAds < UINT32_MAX) {
_readyDropsAds++;
}
portEXIT_CRITICAL(&_estadoLock);
RegistrarFalhaTransacao("READY caiu durante leitura ADS");
}
static void RegistrarFalhaIoADS(
uint32_t inicioMs,
const char* etapa,
int erro,
uint32_t duracaoMs
) {
AtualizarMaxDuracaoADS(inicioMs);
IncrementarFalhaIoADS();
RegistrarFalhaTransacao(
String("ADS ") + etapa +
" | erro=" + String(erro) +
" | duracao=" + String(duracaoMs) + "ms"
);
}
/*
* Leitura ADS1115 controlada pelo I2CService.
*
* Motivos para nao usar conversionComplete():
* - ele executa uma leitura I2C a cada consulta;
* - o tempo total medido ao redor dele mistura transferencia I2C com
* escalonamento/preempcao da task;
* - para 128 SPS a conversao nominal termina em ~7,8 ms.
*
* Aqui:
* 1) escreve CONFIG e dispara single-shot;
* 2) aguarda 10 ms sem trafego I2C;
* 3) le diretamente o registrador CONVERSION;
* 4) mede separadamente as duas fases I2C.
*
* IMPORTANTE:
* O chamador ja possui i2cMutex. Nao chamar esta funcao sem ownership
* do I2CService.
*/
static bool LerADSSingleShotComTimeout(int canal, int16_t& leituraADC) {
if (canal < 0 || canal > 3) {
return false;
}
const uint8_t endereco = ultimoEnderecoADS;
if (
endereco == 0xFF ||
IndiceADS(endereco) < 0
) {
RegistrarFalhaTransacao("ADS sem endereco ativo");
return false;
}
const uint32_t inicioCiclo = millis();
if (!TCAReadyOk()) {
RegistrarReadyDropADS(inicioCiclo);
return false;
}
/*
* Mesmo perfil que configuramos na biblioteca:
* - single-ended AINx
* - ganho 1 (+/-4,096 V)
* - single-shot
* - 128 SPS
* - comparador desabilitado
*/
const uint16_t config =
ADS1X15_REG_CONFIG_OS_SINGLE |
MUX_BY_CHANNEL[canal] |
ADS1X15_REG_CONFIG_PGA_4_096V |
ADS1X15_REG_CONFIG_MODE_SINGLE |
RATE_ADS1115_128SPS |
ADS1X15_REG_CONFIG_CMODE_TRAD |
ADS1X15_REG_CONFIG_CPOL_ACTVLOW |
ADS1X15_REG_CONFIG_CLAT_NONLAT |
ADS1X15_REG_CONFIG_CQUE_NONE;
// FASE 1: disparar conversao.
const uint32_t inicioStart = millis();
Wire.beginTransmission(endereco);
Wire.write((uint8_t)ADS1X15_REG_POINTER_CONFIG);
Wire.write((uint8_t)(config >> 8));
Wire.write((uint8_t)(config & 0xFF));
const uint8_t erroStart = Wire.endTransmission();
const uint32_t duracaoStart =
(uint32_t)(millis() - inicioStart);
AtualizarMaxFaseInicioADS(duracaoStart);
if (!TCAReadyOk()) {
RegistrarReadyDropADS(inicioCiclo);
return false;
}
if (
erroStart == 5 ||
duracaoStart > LimiteFaseADS_MS
) {
RegistrarTimeoutADS(
inicioCiclo,
"Timeout ao iniciar conversao ADS"
);
return false;
}
if (erroStart != 0) {
RegistrarFalhaIoADS(
inicioCiclo,
"falha ao iniciar conversao",
erroStart,
duracaoStart
);
return false;
}
/*
* 128 SPS => ~7,8 ms nominal.
* Esperamos sem consultar CONFIG. Se a task for preemptada e voltar
* muito depois, isso NAO e falha I2C; apenas aumenta maxLeituraAdsMs.
*/
DelayMs(EsperaConversaoADS_MS);
if (!TCAReadyOk()) {
RegistrarReadyDropADS(inicioCiclo);
return false;
}
// FASE 2: selecionar CONVERSION e ler dois bytes.
const uint32_t inicioResultado = millis();
Wire.beginTransmission(endereco);
Wire.write((uint8_t)ADS1X15_REG_POINTER_CONVERT);
const uint8_t erroPonteiro = Wire.endTransmission();
if (erroPonteiro != 0) {
const uint32_t duracaoResultado =
(uint32_t)(millis() - inicioResultado);
AtualizarMaxFaseResultadoADS(duracaoResultado);
if (
erroPonteiro == 5 ||
duracaoResultado > LimiteFaseADS_MS
) {
RegistrarTimeoutADS(
inicioCiclo,
"Timeout selecionando resultado ADS"
);
}
else {
RegistrarFalhaIoADS(
inicioCiclo,
"falha selecionando resultado",
erroPonteiro,
duracaoResultado
);
}
return false;
}
const size_t recebidos =
Wire.requestFrom(
(int)endereco,
2
);
const uint32_t duracaoResultado =
(uint32_t)(millis() - inicioResultado);
AtualizarMaxFaseResultadoADS(duracaoResultado);
if (!TCAReadyOk()) {
RegistrarReadyDropADS(inicioCiclo);
return false;
}
if (duracaoResultado > LimiteFaseADS_MS) {
RegistrarTimeoutADS(
inicioCiclo,
"Timeout obtendo resultado ADS"
);
return false;
}
if (
recebidos != 2 ||
Wire.available() < 2
) {
RegistrarFalhaIoADS(
inicioCiclo,
"resultado incompleto",
(int)recebidos,
duracaoResultado
);
return false;
}
const uint16_t bruto =
((uint16_t)Wire.read() << 8) |
(uint16_t)Wire.read();
leituraADC = (int16_t)bruto;
AtualizarMaxDuracaoADS(inicioCiclo);
/*
* Em single-ended esperamos valor nao-negativo.
* Nao classificamos isso como timeout: e falha de amostra/I/O.
*/
if (leituraADC < 0) {
RegistrarFalhaIoADS(
inicioCiclo,
"leitura single-ended negativa",
-1,
(uint32_t)(millis() - inicioCiclo)
);
return false;
}
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;
SemaphoreHandle_t I2CService::lifecycleMutex = nullptr;
TaskHandle_t I2CService::i2cTaskHandle = NULL;
bool I2CService::i2cMutexReiniciando = false;
bool I2CService::_recuperacaoEmAndamento = false;
bool I2CService::_recoveryPendente = false;
char I2CService::_motivoRecoveryPendente[192] = {0};
uint32_t I2CService::_geracaoBarramento = 0;
uint8_t I2CService::_muxEsperadoMask = 0;
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;
int I2CService::_ultimoIdFalhaDiag = -1;
int I2CService::_ultimoCanalFalhaDiag = -1;
uint32_t I2CService::_maxLeituraAdsMs = 0;
uint32_t I2CService::_maxInicioAdsMs = 0;
uint32_t I2CService::_maxResultadoAdsMs = 0;
uint32_t I2CService::_timeoutsAds = 0;
uint32_t I2CService::_readyDropsAds = 0;
uint32_t I2CService::_falhasIoAds = 0;
#endif