1851 lines
52 KiB
C++
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
|