-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathORP_Sensor.ino
More file actions
177 lines (159 loc) · 6.16 KB
/
Copy pathORP_Sensor.ino
File metadata and controls
177 lines (159 loc) · 6.16 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
/*
* Firmware sensor ORP - Waveshare RP2040 Zero
* ============================================
*
* Hardware:
* Sensor VCC -> 5V (pino 5V do RP2040 Zero)
* Sensor GND -> GND
* Sensor AO -> GP28 (ligacao direta, sem divisor de tensao)
*
* AVISO: o sensor entrega ate 4V mas o ADC do RP2040 satura em ~3,3V.
* Faixa util: aproximadamente +2000..-1300 mV (acima fica clipado).
*
* ============================================
* CALIBRACAO COM SOLUCOES PADRAO (3 pontos)
* ============================================
*
* PASSO 1: Defina CALIBRATION_MODE = true e regrave.
*
* PASSO 2: Com o eletrodo, mergulhe em cada solucao padrao por vez,
* aguarde estabilizar (~30-60s) e anote o valor "RAW" do
* Serial Monitor.
* Lave o eletrodo com agua deionizada entre cada solucao.
*
* PASSO 3: Preencha CAL_MEASURED[] com os valores RAW lidos,
* mantendo a mesma ordem de CAL_KNOWN[].
*
* PASSO 4: Defina CALIBRATION_MODE = false e regrave.
* O firmware aplica regressao linear (minimos quadrados)
* para corrigir ganho e offset automaticamente.
*/
// =================== HARDWARE ===================
constexpr uint8_t ORP_PIN = 29; // GP28 = ADC2
constexpr float ADC_VREF = 3.3f; // Referencia do ADC do RP2040
constexpr float ADC_MAX_COUNTS = 4096.0f; // ADC 12 bits
constexpr float DIVIDER_RATIO = 1.0f; // Ligacao direta, sem divisor
// =================== CALIBRACAO ===================
constexpr bool CALIBRATION_MODE = true; // true = imprime RAW; false = aplica correcao
constexpr int N_CAL = 3;
// Valores conhecidos das solucoes padrao (mV)
constexpr float CAL_KNOWN[N_CAL] = { 476.0f, 220.0f, 110.0f };
// Valores RAW lidos pelo sensor para cada solucao acima (preencher depois do passo 2)
constexpr float CAL_MEASURED[N_CAL] = { 0.0f, 0.0f, 0.0f };
// =================== AMOSTRAGEM ===================
constexpr uint16_t SAMPLE_COUNT = 40; // Amostras por janela
constexpr uint32_t SAMPLE_INTERVAL = 20; // ms entre amostras
constexpr uint32_t PRINT_INTERVAL = 800; // ms entre impressoes
// =================== ESTADO ===================
uint16_t orpArray[SAMPLE_COUNT];
uint16_t orpIndex = 0;
float calGain = 1.0f; // ORP_corrigido = calGain * ORP_raw + calOffset
float calOffset = 0.0f;
// =================== HELPERS ===================
float averageArray(const uint16_t *arr, uint16_t n) {
if (n < 3) return 0.0f;
uint32_t sum = 0;
uint16_t vmin = arr[0], vmax = arr[0];
for (uint16_t i = 0; i < n; i++) {
sum += arr[i];
if (arr[i] > vmax) vmax = arr[i];
if (arr[i] < vmin) vmin = arr[i];
}
return (float)(sum - vmin - vmax) / (n - 2);
}
// Regressao linear por minimos quadrados: y = a*x + b
// x = CAL_MEASURED (raw lido), y = CAL_KNOWN (real)
void computeCalibration() {
if (CALIBRATION_MODE) {
calGain = 1.0f;
calOffset = 0.0f;
return;
}
float sumX = 0, sumY = 0, sumXY = 0, sumXX = 0;
for (int i = 0; i < N_CAL; i++) {
sumX += CAL_MEASURED[i];
sumY += CAL_KNOWN[i];
sumXY += CAL_MEASURED[i] * CAL_KNOWN[i];
sumXX += CAL_MEASURED[i] * CAL_MEASURED[i];
}
const float n = (float)N_CAL;
const float denom = n * sumXX - sumX * sumX;
// Caso degenerado (todos os RAW iguais): cai para identidade
if (fabsf(denom) < 1e-6f) {
calGain = 1.0f;
calOffset = 0.0f;
return;
}
calGain = (n * sumXY - sumX * sumY) / denom;
calOffset = (sumY - calGain * sumX) / n;
}
// =================== SETUP ===================
void setup() {
Serial.begin(115200);
analogReadResolution(12);
pinMode(ORP_PIN, INPUT);
// Pre-enche buffer
for (uint16_t i = 0; i < SAMPLE_COUNT; i++) {
orpArray[i] = analogRead(ORP_PIN);
delay(SAMPLE_INTERVAL);
}
computeCalibration();
Serial.println();
Serial.println(F("=== Sensor ORP - RP2040 Zero ==="));
if (CALIBRATION_MODE) {
Serial.println(F("MODO: CALIBRACAO (anote os valores RAW)"));
Serial.println(F("Padroes esperados (mV):"));
for (int i = 0; i < N_CAL; i++) {
Serial.print(F(" Solucao "));
Serial.print(i + 1);
Serial.print(F(": "));
Serial.print(CAL_KNOWN[i], 1);
Serial.println(F(" mV"));
}
} else {
Serial.println(F("MODO: MEDICAO (calibracao aplicada)"));
Serial.print (F("Ganho: ")); Serial.println(calGain, 5);
Serial.print (F("Offset: ")); Serial.print(calOffset, 2); Serial.println(F(" mV"));
// Erro residual em cada ponto (sanity check)
Serial.println(F("Verificacao por ponto:"));
for (int i = 0; i < N_CAL; i++) {
float predicted = calGain * CAL_MEASURED[i] + calOffset;
Serial.print(F(" "));
Serial.print(CAL_KNOWN[i], 1); Serial.print(F(" mV -> "));
Serial.print(predicted, 1); Serial.print(F(" mV (erro "));
Serial.print(predicted - CAL_KNOWN[i], 2); Serial.println(F(" mV)"));
}
}
Serial.println();
Serial.println(F("ADC\tV_pino\tV_sensor\tRAW\tORP"));
Serial.println();
}
// =================== LOOP ===================
void loop() {
static uint32_t lastSample = 0;
static uint32_t lastPrint = 0;
const uint32_t now = millis();
if (now - lastSample >= SAMPLE_INTERVAL) {
lastSample = now;
orpArray[orpIndex++] = analogRead(ORP_PIN);
if (orpIndex >= SAMPLE_COUNT) orpIndex = 0;
}
if (now - lastPrint >= PRINT_INTERVAL) {
lastPrint = now;
float adcRaw = averageArray(orpArray, SAMPLE_COUNT);
float vPin = (adcRaw * ADC_VREF) / ADC_MAX_COUNTS;
float vSensor = vPin * DIVIDER_RATIO;
// Mapeamento do hardware (0V->+2000, 4V->-2000)
float orpRaw = 2000.0f - 1000.0f * vSensor;
// Correcao por regressao linear das solucoes padrao
float orpCal = calGain * orpRaw + calOffset;
bool saturated = (adcRaw >= ADC_MAX_COUNTS - 2);
Serial.print((int)adcRaw); Serial.print(F("\t"));
Serial.print(vPin, 3); Serial.print(F("\t"));
Serial.print(vSensor, 3); Serial.print(F("\t"));
Serial.print(orpRaw, 1); Serial.print(F("\t"));
Serial.print(orpCal, 1); Serial.print(F(" mV"));
if (saturated) Serial.print(F(" [SATURADO]"));
Serial.println();
}
}