1092 lines
22 KiB
C++
1092 lines
22 KiB
C++
#include "SerialService.h"
|
|
#include "Pinout.h"
|
|
#include "Utils.h"
|
|
#include "IComponenteCAN.h"
|
|
#include "I2CService.h"
|
|
|
|
#include <Arduino.h>
|
|
#include <vector>
|
|
#include <math.h>
|
|
#include <limits.h>
|
|
|
|
#ifndef SensorTemperaturaNtcModel
|
|
#define SensorTemperaturaNtcModel
|
|
|
|
/*
|
|
* SensorTemperaturaNtc - versão produto
|
|
*
|
|
* Topologia física da placa:
|
|
*
|
|
* Retorno do motor ---- Rserie 4k7 ----+---- ADC / ADS1115
|
|
* |
|
|
* Rgnd 6k8
|
|
* |
|
|
* GND
|
|
*
|
|
* Logo, visto pelo retorno do motor, a placa adiciona uma carga:
|
|
*
|
|
* Rcarga = Rserie + Rgnd = 11k5
|
|
*
|
|
* e a tensão realmente lida pelo ADC é:
|
|
*
|
|
* Vadc = Vretorno * Rgnd / (Rserie + Rgnd)
|
|
*
|
|
* A classe reconstrói Vretorno antes de calcular o NTC.
|
|
*
|
|
*
|
|
* ntcGnd = false:
|
|
*
|
|
* 5 V
|
|
* |
|
|
* NTC
|
|
* |
|
|
* Vretorno ------- Rserie ------- ADC
|
|
* | |
|
|
* Rfixo 10k Rgnd
|
|
* | |
|
|
* GND GND
|
|
*
|
|
*
|
|
* ntcGnd = true:
|
|
*
|
|
* 5 V
|
|
* |
|
|
* Rfixo 10k
|
|
* |
|
|
* Vretorno ------- Rserie ------- ADC
|
|
* | |
|
|
* NTC Rgnd
|
|
* | |
|
|
* GND GND
|
|
*
|
|
*
|
|
* Para EXPANSOR_ADS:
|
|
* - usa geração do Wire/I2C;
|
|
* - faz uma única aquisição lógica por ciclo;
|
|
* - preserva a última amostra válida em falhas;
|
|
* - invalida o lifecycle após falhas consecutivas;
|
|
* - o retry externo pode reinicializar o sensor.
|
|
*/
|
|
class SensorTemperaturaNtc : public ComponenteCAN {
|
|
public:
|
|
String ID;
|
|
uint8_t ID_Num = 0;
|
|
PinoModel _pino;
|
|
|
|
// Mantido público para compatibilidade com código legado.
|
|
bool Iniciado = false;
|
|
|
|
int leitura = -1;
|
|
double temperatura = NAN;
|
|
|
|
// Tensão REAL medida no pino do ADC/ADS, depois do divisor 4k7/6k8.
|
|
double tensao = NAN;
|
|
|
|
float OffsetTemperatura = 0.0f;
|
|
|
|
SensorTemperaturaNtc() {}
|
|
|
|
/*
|
|
* Mantém exatamente o contrato histórico do construtor.
|
|
*
|
|
* ntcGnd:
|
|
* false = NTC no lado de 5 V
|
|
* true = NTC no lado de GND
|
|
*/
|
|
SensorTemperaturaNtc(
|
|
const String& id,
|
|
uint8_t idNum,
|
|
int pino,
|
|
TiposBarramentos tipoBarramento,
|
|
int canalMux = -1,
|
|
float offsetTemperatura = 0.0f,
|
|
bool ntcGnd = false
|
|
) :
|
|
ID(id),
|
|
ID_Num(idNum),
|
|
_pino(
|
|
PinoModel(
|
|
pino,
|
|
tipoBarramento,
|
|
_INPUT,
|
|
ANALOGICO,
|
|
idNum,
|
|
canalMux
|
|
)
|
|
),
|
|
OffsetTemperatura(offsetTemperatura),
|
|
_ntcGnd(ntcGnd),
|
|
_tipoBarramento(tipoBarramento)
|
|
{
|
|
}
|
|
|
|
bool EstaIniciado() {
|
|
bool iniciado;
|
|
uint32_t geracaoInicializacao;
|
|
|
|
portENTER_CRITICAL(&_snapshotLock);
|
|
iniciado = Iniciado;
|
|
geracaoInicializacao = _geracaoI2CInicializacao;
|
|
portEXIT_CRITICAL(&_snapshotLock);
|
|
|
|
if (!iniciado) {
|
|
return false;
|
|
}
|
|
|
|
// ADC interno não depende do lifecycle do Wire.
|
|
if (_tipoBarramento != TiposBarramentos::EXPANSOR_ADS) {
|
|
return true;
|
|
}
|
|
|
|
const uint32_t geracaoAtual =
|
|
I2CService::GeracaoBarramento();
|
|
|
|
return
|
|
I2CService::I2CIniciado &&
|
|
I2CService::AdsIniciado &&
|
|
geracaoAtual != 0 &&
|
|
geracaoInicializacao == geracaoAtual;
|
|
}
|
|
|
|
bool PossuiAmostraValida() {
|
|
bool valor;
|
|
|
|
portENTER_CRITICAL(&_snapshotLock);
|
|
valor = _possuiAmostraValida;
|
|
portEXIT_CRITICAL(&_snapshotLock);
|
|
|
|
return valor;
|
|
}
|
|
|
|
uint8_t FalhasConsecutivas() {
|
|
uint8_t valor;
|
|
|
|
portENTER_CRITICAL(&_snapshotLock);
|
|
valor = _falhasConsecutivas;
|
|
portEXIT_CRITICAL(&_snapshotLock);
|
|
|
|
return valor;
|
|
}
|
|
|
|
uint32_t TotalFalhasLeitura() {
|
|
uint32_t valor;
|
|
|
|
portENTER_CRITICAL(&_snapshotLock);
|
|
valor = _totalFalhasLeitura;
|
|
portEXIT_CRITICAL(&_snapshotLock);
|
|
|
|
return valor;
|
|
}
|
|
|
|
uint32_t IdadeUltimaAmostraValidaMs() {
|
|
uint32_t ultima;
|
|
bool possui;
|
|
|
|
portENTER_CRITICAL(&_snapshotLock);
|
|
ultima = _ultimaAmostraValidaMs;
|
|
possui = _possuiAmostraValida;
|
|
portEXIT_CRITICAL(&_snapshotLock);
|
|
|
|
if (!possui || ultima == 0) {
|
|
return UINT32_MAX;
|
|
}
|
|
|
|
return (uint32_t)(millis() - ultima);
|
|
}
|
|
|
|
void Inicializar() {
|
|
if (EstaIniciado()) {
|
|
MostrarLog("Sensor ja inicializado");
|
|
return;
|
|
}
|
|
|
|
// Limpa lifecycle, mas preserva a última leitura válida.
|
|
AtualizarEstadoIniciado(false);
|
|
|
|
if (_tipoBarramento == TiposBarramentos::EXPANSOR_ADS) {
|
|
if (!I2CService::I2CIniciado) {
|
|
MostrarLog(
|
|
"Sensor nao iniciado | I2C indisponivel"
|
|
);
|
|
return;
|
|
}
|
|
|
|
if (!I2CService::AdsIniciado) {
|
|
MostrarLog(
|
|
"Sensor nao iniciado | ADS1115 indisponivel"
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
_pino.Conectar();
|
|
|
|
if (!_pino.Definido() || !_pino.Conectado) {
|
|
MostrarLog(
|
|
"Sensor nao iniciado | pino/barramento indisponivel"
|
|
);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Se algum dia esta classe voltar ao ADC interno,
|
|
* preserva a configuração histórica de 12 bits / 11 dB.
|
|
*
|
|
* Para o ADS1115 isso não é usado.
|
|
*/
|
|
if (_tipoBarramento == TiposBarramentos::CONTROLADOR) {
|
|
analogReadResolution(12);
|
|
analogSetPinAttenuation(
|
|
_pino.num,
|
|
ADC_11db
|
|
);
|
|
}
|
|
|
|
AtualizarEstadoIniciado(true);
|
|
|
|
portENTER_CRITICAL(&_snapshotLock);
|
|
_falhasConsecutivas = 0;
|
|
portEXIT_CRITICAL(&_snapshotLock);
|
|
|
|
MostrarLog(
|
|
"Sensor iniciado"
|
|
" | canal=" + String(_pino.num) +
|
|
" | ntc_gnd=" + String(_ntcGnd ? 1 : 0) +
|
|
" | geracao=" +
|
|
String(_geracaoI2CInicializacao)
|
|
);
|
|
}
|
|
|
|
void Desligar() {
|
|
_pino.Desconectar();
|
|
AtualizarEstadoIniciado(false);
|
|
|
|
MostrarLog("Sensor desligado");
|
|
}
|
|
|
|
void RequisitarDados() {
|
|
if (!EstaIniciado()) {
|
|
/*
|
|
* Caso a geração tenha mudado, força Iniciado=false
|
|
* para que o retry periódico da firmware enxergue
|
|
* corretamente a necessidade de reinicialização.
|
|
*/
|
|
bool estavaMarcadoIniciado;
|
|
|
|
portENTER_CRITICAL(&_snapshotLock);
|
|
estavaMarcadoIniciado = Iniciado;
|
|
portEXIT_CRITICAL(&_snapshotLock);
|
|
|
|
if (
|
|
estavaMarcadoIniciado &&
|
|
_tipoBarramento ==
|
|
TiposBarramentos::EXPANSOR_ADS
|
|
) {
|
|
AtualizarEstadoIniciado(false);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
AferirTemperatura();
|
|
}
|
|
|
|
std::vector<uint8_t> MontarMensagemCAN(
|
|
CanMessagePosicaoDados posicao
|
|
) {
|
|
std::vector<uint8_t> data;
|
|
|
|
data.push_back(
|
|
static_cast<uint8_t>(posicao)
|
|
);
|
|
data.push_back(ID_Num);
|
|
|
|
const bool iniciado =
|
|
EstaIniciado();
|
|
|
|
int snapLeitura;
|
|
double snapTemperatura;
|
|
double snapTensao;
|
|
bool snapValido;
|
|
|
|
portENTER_CRITICAL(&_snapshotLock);
|
|
snapLeitura = leitura;
|
|
snapTemperatura = temperatura;
|
|
snapTensao = tensao;
|
|
snapValido = _possuiAmostraValida;
|
|
portEXIT_CRITICAL(&_snapshotLock);
|
|
|
|
switch (posicao) {
|
|
case CanMessagePosicaoDados::Status: {
|
|
data.push_back(
|
|
iniciado ? 1 : 0
|
|
);
|
|
break;
|
|
}
|
|
|
|
case CanMessagePosicaoDados::Dados1: {
|
|
/*
|
|
* Mantém o contrato CAN atual:
|
|
*
|
|
* [2..3] raw ADC normalizado 0..4095
|
|
* [4..5] temperatura * 100
|
|
* [6..7] tensão no PINO ADC em mV
|
|
*/
|
|
|
|
uint16_t leituraAdc =
|
|
snapValido &&
|
|
snapLeitura >= 0
|
|
? static_cast<uint16_t>(
|
|
snapLeitura
|
|
)
|
|
: 0xFFFF;
|
|
|
|
int16_t temperaturaInt =
|
|
snapValido &&
|
|
isfinite(snapTemperatura)
|
|
? static_cast<int16_t>(
|
|
constrain(
|
|
(long)lround(
|
|
snapTemperatura *
|
|
100.0
|
|
),
|
|
(long)INT16_MIN + 1,
|
|
(long)INT16_MAX
|
|
)
|
|
)
|
|
: INT16_MIN;
|
|
|
|
uint16_t tensaoMv =
|
|
snapValido &&
|
|
isfinite(snapTensao) &&
|
|
snapTensao >= 0.0
|
|
? static_cast<uint16_t>(
|
|
constrain(
|
|
(long)lround(
|
|
snapTensao *
|
|
1000.0
|
|
),
|
|
0L,
|
|
65534L
|
|
)
|
|
)
|
|
: 0xFFFF;
|
|
|
|
data.push_back(
|
|
static_cast<uint8_t>(
|
|
leituraAdc >> 8
|
|
)
|
|
);
|
|
data.push_back(
|
|
static_cast<uint8_t>(
|
|
leituraAdc & 0xFF
|
|
)
|
|
);
|
|
|
|
data.push_back(
|
|
static_cast<uint8_t>(
|
|
(uint16_t)temperaturaInt >> 8
|
|
)
|
|
);
|
|
data.push_back(
|
|
static_cast<uint8_t>(
|
|
(uint16_t)temperaturaInt & 0xFF
|
|
)
|
|
);
|
|
|
|
data.push_back(
|
|
static_cast<uint8_t>(
|
|
tensaoMv >> 8
|
|
)
|
|
);
|
|
data.push_back(
|
|
static_cast<uint8_t>(
|
|
tensaoMv & 0xFF
|
|
)
|
|
);
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
private:
|
|
bool DebugMode = false;
|
|
|
|
/*
|
|
* false:
|
|
* 5V -> NTC -> retorno -> Rfixo -> GND
|
|
*
|
|
* true:
|
|
* 5V -> Rfixo -> retorno -> NTC -> GND
|
|
*/
|
|
bool _ntcGnd = false;
|
|
|
|
TiposBarramentos _tipoBarramento =
|
|
TiposBarramentos::CONTROLADOR;
|
|
|
|
portMUX_TYPE _snapshotLock =
|
|
portMUX_INITIALIZER_UNLOCKED;
|
|
|
|
uint8_t _falhasConsecutivas = 0;
|
|
uint32_t _totalFalhasLeitura = 0;
|
|
|
|
uint32_t _geracaoI2CInicializacao = 0;
|
|
|
|
bool _possuiAmostraValida = false;
|
|
uint32_t _ultimaAmostraValidaMs = 0;
|
|
|
|
/*
|
|
* Hardware real.
|
|
*/
|
|
static constexpr float Vs = 5.0f;
|
|
|
|
static constexpr float Rfixo =
|
|
10000.0f;
|
|
|
|
static constexpr float Rserie =
|
|
4700.0f;
|
|
|
|
static constexpr float Rgnd =
|
|
6800.0f;
|
|
|
|
/*
|
|
* Do ponto de vista do Vretorno:
|
|
*
|
|
* retorno -> 4k7 -> ADC -> 6k8 -> GND
|
|
*
|
|
* equivale a 11k5 para GND.
|
|
*/
|
|
static constexpr float Rcarga =
|
|
Rserie + Rgnd;
|
|
|
|
/*
|
|
* ADS1115 atual:
|
|
* I2CService usa GAIN_ONE.
|
|
*
|
|
* readADC_SingleEnded() é convertido no service
|
|
* de 0..32767 para 0..4095.
|
|
*
|
|
* Portanto:
|
|
*
|
|
* 4095 <=> 4.096 V
|
|
*/
|
|
static constexpr float
|
|
FullScaleAdsV = 4.096f;
|
|
|
|
/*
|
|
* O máximo físico no ADC devido ao divisor 4k7/6k8 é:
|
|
*
|
|
* 5V * 6k8 / (4k7 + 6k8)
|
|
* ~= 2.9565 V
|
|
*/
|
|
static constexpr float
|
|
TensaoAdcMaxFisicaV =
|
|
Vs * Rgnd / Rcarga;
|
|
|
|
static constexpr float
|
|
MargemTensaoV = 0.010f;
|
|
|
|
static constexpr float Beta =
|
|
3950.0f;
|
|
|
|
static constexpr float T0K =
|
|
298.15f;
|
|
|
|
static constexpr float Rntc25 =
|
|
10000.0f;
|
|
|
|
static constexpr float
|
|
TemperaturaMinC = -40.0f;
|
|
|
|
static constexpr float
|
|
TemperaturaMaxC = 150.0f;
|
|
|
|
/*
|
|
* ADS1115 já é delta-sigma.
|
|
* 4 amostras por NTC são suficientes para uma grandeza lenta.
|
|
*
|
|
* 4 NTC * 4 conversões * 2 Hz = 32 conversões/s.
|
|
*/
|
|
static constexpr int
|
|
QuantidadeAmostras = 3;
|
|
|
|
/*
|
|
* Filtro final leve.
|
|
* O valor só entra aqui depois de passar por todas as validações.
|
|
*/
|
|
static constexpr float
|
|
AlphaTemperatura = 0.35f;
|
|
|
|
static constexpr uint8_t
|
|
LimiteFalhasAntesReinit = 3;
|
|
|
|
void MostrarLog(
|
|
const String& mensagem
|
|
) {
|
|
if (DebugMode) {
|
|
PrintTela(
|
|
"[TMP " +
|
|
ID +
|
|
"] " +
|
|
mensagem
|
|
);
|
|
}
|
|
}
|
|
|
|
void AtualizarEstadoIniciado(
|
|
bool iniciado
|
|
) {
|
|
const uint32_t geracao =
|
|
iniciado &&
|
|
_tipoBarramento ==
|
|
TiposBarramentos::EXPANSOR_ADS
|
|
? I2CService::GeracaoBarramento()
|
|
: 0;
|
|
|
|
portENTER_CRITICAL(&_snapshotLock);
|
|
|
|
Iniciado = iniciado;
|
|
_geracaoI2CInicializacao =
|
|
geracao;
|
|
|
|
portEXIT_CRITICAL(&_snapshotLock);
|
|
}
|
|
|
|
void RegistrarFalhaAquisicao(
|
|
const String& motivo
|
|
) {
|
|
uint8_t falhas;
|
|
|
|
portENTER_CRITICAL(&_snapshotLock);
|
|
|
|
if (
|
|
_falhasConsecutivas <
|
|
UINT8_MAX
|
|
) {
|
|
_falhasConsecutivas++;
|
|
}
|
|
|
|
_totalFalhasLeitura++;
|
|
falhas =
|
|
_falhasConsecutivas;
|
|
|
|
portEXIT_CRITICAL(&_snapshotLock);
|
|
|
|
MostrarLog(
|
|
"Falha de leitura " +
|
|
String(falhas) +
|
|
"/" +
|
|
String(
|
|
LimiteFalhasAntesReinit
|
|
) +
|
|
" | " +
|
|
motivo
|
|
);
|
|
|
|
/*
|
|
* Nunca sobrescreve o último valor bom com NaN.
|
|
*
|
|
* Se a falha persistir, o sensor perde o estado
|
|
* Iniciado e entra no retry controlado da firmware.
|
|
*/
|
|
if (
|
|
falhas >=
|
|
LimiteFalhasAntesReinit
|
|
) {
|
|
AtualizarEstadoIniciado(false);
|
|
|
|
MostrarLog(
|
|
"Sensor marcado para "
|
|
"reinicializacao controlada"
|
|
);
|
|
}
|
|
}
|
|
|
|
void RegistrarSucessoAquisicao() {
|
|
portENTER_CRITICAL(&_snapshotLock);
|
|
_falhasConsecutivas = 0;
|
|
portEXIT_CRITICAL(&_snapshotLock);
|
|
}
|
|
|
|
/*
|
|
* Converte o raw 0..4095 recebido do I2CService
|
|
* para a tensão REAL no pino do ADS1115.
|
|
*/
|
|
bool ConverterRawParaTensaoAdc(
|
|
int raw,
|
|
float& vAdc
|
|
) {
|
|
if (
|
|
raw < 0 ||
|
|
raw > 4095
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
if (
|
|
_tipoBarramento ==
|
|
TiposBarramentos::EXPANSOR_ADS
|
|
) {
|
|
vAdc =
|
|
((float)raw / 4095.0f) *
|
|
FullScaleAdsV;
|
|
}
|
|
else {
|
|
/*
|
|
* Fallback histórico para ADC de 12 bits.
|
|
* Esta versão produto está otimizada para ADS.
|
|
*
|
|
* Se voltar ao ADC interno, recomenda-se usar
|
|
* analogReadMilliVolts() no PinoModel para calibração absoluta.
|
|
*/
|
|
vAdc =
|
|
((float)raw / 4095.0f) *
|
|
3.3f;
|
|
}
|
|
|
|
if (!isfinite(vAdc)) {
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Com o divisor físico, o ADS jamais deveria receber
|
|
* mais que aproximadamente 2.9565 V.
|
|
*
|
|
* Deixamos pequena tolerância para resistores/medição.
|
|
*/
|
|
if (
|
|
_tipoBarramento ==
|
|
TiposBarramentos::EXPANSOR_ADS &&
|
|
(
|
|
vAdc <
|
|
-MargemTensaoV ||
|
|
vAdc >
|
|
TensaoAdcMaxFisicaV +
|
|
0.050f
|
|
)
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Reconstrói a tensão do nó original do sensor,
|
|
* antes do divisor 4k7/6k8.
|
|
*
|
|
* Vadc =
|
|
* Vretorno *
|
|
* Rgnd /
|
|
* (Rserie + Rgnd)
|
|
*
|
|
* logo:
|
|
*
|
|
* Vretorno =
|
|
* Vadc *
|
|
* (Rserie + Rgnd) /
|
|
* Rgnd
|
|
*/
|
|
bool CalcularTensaoRetorno(
|
|
float vAdc,
|
|
float& vRetorno
|
|
) {
|
|
if (
|
|
!isfinite(vAdc) ||
|
|
vAdc < 0.0f
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
vRetorno =
|
|
vAdc *
|
|
Rcarga /
|
|
Rgnd;
|
|
|
|
if (!isfinite(vRetorno)) {
|
|
return false;
|
|
}
|
|
|
|
if (
|
|
vRetorno <=
|
|
MargemTensaoV ||
|
|
vRetorno >=
|
|
Vs -
|
|
MargemTensaoV
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Calcula Rntc considerando também a carga
|
|
* criada pelo divisor 4k7 + 6k8.
|
|
*/
|
|
bool CalcularResistenciaNtc(
|
|
float vRetorno,
|
|
float& resistenciaNtc
|
|
) {
|
|
if (
|
|
!isfinite(vRetorno) ||
|
|
vRetorno <=
|
|
MargemTensaoV ||
|
|
vRetorno >=
|
|
Vs -
|
|
MargemTensaoV
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
if (_ntcGnd) {
|
|
/*
|
|
* 5V -> Rfixo -> retorno -> NTC -> GND
|
|
*
|
|
* Abaixo do retorno temos:
|
|
*
|
|
* NTC || Rcarga
|
|
*
|
|
* Primeiro calculamos a resistência equivalente
|
|
* inferior do divisor:
|
|
*
|
|
* Req =
|
|
* Rfixo *
|
|
* Vretorno /
|
|
* (Vs - Vretorno)
|
|
*/
|
|
const float rEquivalente =
|
|
Rfixo *
|
|
vRetorno /
|
|
(Vs - vRetorno);
|
|
|
|
if (
|
|
!isfinite(rEquivalente) ||
|
|
rEquivalente <= 0.0f
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* 1 / Req =
|
|
* 1 / Rntc +
|
|
* 1 / Rcarga
|
|
*/
|
|
const float inversoRntc =
|
|
(1.0f / rEquivalente) -
|
|
(1.0f / Rcarga);
|
|
|
|
if (
|
|
!isfinite(inversoRntc) ||
|
|
inversoRntc <= 0.0f
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
resistenciaNtc =
|
|
1.0f /
|
|
inversoRntc;
|
|
}
|
|
else {
|
|
/*
|
|
* 5V -> NTC -> retorno -> Rfixo -> GND
|
|
*
|
|
* Abaixo do retorno temos:
|
|
*
|
|
* Rfixo || Rcarga
|
|
*/
|
|
const float rInferior =
|
|
(Rfixo * Rcarga) /
|
|
(Rfixo + Rcarga);
|
|
|
|
if (
|
|
!isfinite(rInferior) ||
|
|
rInferior <= 0.0f
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Vretorno =
|
|
*
|
|
* Vs *
|
|
* rInferior /
|
|
* (Rntc + rInferior)
|
|
*
|
|
* logo:
|
|
*
|
|
* Rntc =
|
|
* rInferior *
|
|
* (Vs - Vretorno) /
|
|
* Vretorno
|
|
*/
|
|
resistenciaNtc =
|
|
rInferior *
|
|
(Vs - vRetorno) /
|
|
vRetorno;
|
|
}
|
|
|
|
return
|
|
isfinite(resistenciaNtc) &&
|
|
resistenciaNtc > 0.0f;
|
|
}
|
|
|
|
bool CalcularTemperaturaC(
|
|
float resistenciaNtc,
|
|
float& temperaturaC
|
|
) {
|
|
if (
|
|
!isfinite(resistenciaNtc) ||
|
|
resistenciaNtc <= 0.0f
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
const float termoLog =
|
|
logf(
|
|
resistenciaNtc /
|
|
Rntc25
|
|
);
|
|
|
|
if (!isfinite(termoLog)) {
|
|
return false;
|
|
}
|
|
|
|
const float inversoT =
|
|
(1.0f / T0K) +
|
|
(termoLog / Beta);
|
|
|
|
if (
|
|
!isfinite(inversoT) ||
|
|
inversoT <= 0.0f
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
temperaturaC =
|
|
(1.0f / inversoT) -
|
|
273.15f +
|
|
OffsetTemperatura;
|
|
|
|
if (
|
|
!isfinite(temperaturaC) ||
|
|
temperaturaC <
|
|
TemperaturaMinC ||
|
|
temperaturaC >
|
|
TemperaturaMaxC
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
temperaturaC =
|
|
roundf(
|
|
temperaturaC *
|
|
100.0f
|
|
) /
|
|
100.0f;
|
|
|
|
return true;
|
|
}
|
|
|
|
void PublicarAmostraValida(
|
|
int novaLeitura,
|
|
float novaTensaoAdc,
|
|
float novaTemperatura
|
|
) {
|
|
float temperaturaPublicada =
|
|
novaTemperatura;
|
|
|
|
portENTER_CRITICAL(&_snapshotLock);
|
|
|
|
if (
|
|
_possuiAmostraValida &&
|
|
isfinite(temperatura)
|
|
) {
|
|
temperaturaPublicada =
|
|
AlphaTemperatura *
|
|
novaTemperatura +
|
|
(
|
|
1.0f -
|
|
AlphaTemperatura
|
|
) *
|
|
(float)temperatura;
|
|
}
|
|
|
|
leitura =
|
|
novaLeitura;
|
|
|
|
/*
|
|
* Mantemos no CAN a tensão que realmente
|
|
* chegou ao ADC, depois do divisor 4k7/6k8.
|
|
*/
|
|
tensao =
|
|
novaTensaoAdc;
|
|
|
|
temperatura =
|
|
roundf(
|
|
temperaturaPublicada *
|
|
100.0f
|
|
) /
|
|
100.0f;
|
|
|
|
_possuiAmostraValida =
|
|
true;
|
|
|
|
_ultimaAmostraValidaMs =
|
|
millis();
|
|
|
|
portEXIT_CRITICAL(&_snapshotLock);
|
|
}
|
|
|
|
void AferirTemperatura() {
|
|
/*
|
|
* UMA única aquisição lógica.
|
|
*
|
|
* No EXPANSOR_ADS:
|
|
* PinoModel::get(N) ->
|
|
* I2CService::RealizarLeituraADS()
|
|
*
|
|
* O ownership do I2C fica mantido durante
|
|
* todas as N conversões.
|
|
*/
|
|
const int novaLeitura =
|
|
_pino.get(
|
|
QuantidadeAmostras
|
|
);
|
|
|
|
if (novaLeitura < 0) {
|
|
RegistrarFalhaAquisicao(
|
|
"falha ao obter amostra ADC"
|
|
);
|
|
return;
|
|
}
|
|
|
|
float novaTensaoAdc = NAN;
|
|
|
|
if (
|
|
!ConverterRawParaTensaoAdc(
|
|
novaLeitura,
|
|
novaTensaoAdc
|
|
)
|
|
) {
|
|
RegistrarFalhaAquisicao(
|
|
"raw/tensao ADC invalido"
|
|
" | raw=" +
|
|
String(novaLeitura)
|
|
);
|
|
return;
|
|
}
|
|
|
|
float vRetorno = NAN;
|
|
|
|
if (
|
|
!CalcularTensaoRetorno(
|
|
novaTensaoAdc,
|
|
vRetorno
|
|
)
|
|
) {
|
|
RegistrarFalhaAquisicao(
|
|
"tensao de retorno invalida"
|
|
" | raw=" +
|
|
String(novaLeitura) +
|
|
" | Vadc=" +
|
|
String(
|
|
novaTensaoAdc,
|
|
3
|
|
)
|
|
);
|
|
return;
|
|
}
|
|
|
|
float resistenciaNtc = NAN;
|
|
|
|
if (
|
|
!CalcularResistenciaNtc(
|
|
vRetorno,
|
|
resistenciaNtc
|
|
)
|
|
) {
|
|
RegistrarFalhaAquisicao(
|
|
"resistencia NTC invalida"
|
|
" | Vadc=" +
|
|
String(
|
|
novaTensaoAdc,
|
|
3
|
|
) +
|
|
" | Vret=" +
|
|
String(
|
|
vRetorno,
|
|
3
|
|
)
|
|
);
|
|
return;
|
|
}
|
|
|
|
float novaTemperatura = NAN;
|
|
|
|
if (
|
|
!CalcularTemperaturaC(
|
|
resistenciaNtc,
|
|
novaTemperatura
|
|
)
|
|
) {
|
|
RegistrarFalhaAquisicao(
|
|
"temperatura fora da faixa"
|
|
" | Rntc=" +
|
|
String(
|
|
resistenciaNtc,
|
|
1
|
|
) +
|
|
" | Vret=" +
|
|
String(
|
|
vRetorno,
|
|
3
|
|
)
|
|
);
|
|
return;
|
|
}
|
|
|
|
RegistrarSucessoAquisicao();
|
|
|
|
PublicarAmostraValida(
|
|
novaLeitura,
|
|
novaTensaoAdc,
|
|
novaTemperatura
|
|
);
|
|
|
|
MostrarLog(
|
|
"Raw=" +
|
|
String(novaLeitura) +
|
|
" | Vadc=" +
|
|
String(
|
|
novaTensaoAdc,
|
|
3
|
|
) +
|
|
" V | Vret=" +
|
|
String(
|
|
vRetorno,
|
|
3
|
|
) +
|
|
" V | Rntc=" +
|
|
String(
|
|
resistenciaNtc,
|
|
1
|
|
) +
|
|
" ohm | T=" +
|
|
String(
|
|
novaTemperatura,
|
|
2
|
|
) +
|
|
" C"
|
|
);
|
|
}
|
|
};
|
|
|
|
#endif
|