LCC Fusion Project 1.0
LCC Automation
Loading...
Searching...
No Matches
PstTestPins.h
Go to the documentation of this file.
1#pragma once
2
28 #include <Arduino.h>
29 #include <SerialIo.h>
30
31 #include <CardMri.h>
33// Expects a global/static SerialIO named `g_serialIO`
34
49class PstTestPins
50{
51public:
70 enum class Type : uint8_t
71 {
72 IO,
73 Input,
74 Output,
75 OpenDrain,
76 ADC,
77 I2C_SDA,
78 I2C_SCL,
79 PWM,
80 Button,
81 UART_RX,
82 UART_TX,
83 McpInput,
84 Unknown
85 };
86
89
95 static inline void set_led(uint8_t pin, bool on)
96 {
97 digitalWrite(pin, on ? LOW : HIGH);
98 }
99
104 static inline void init_led(uint8_t pin)
105 {
106 pinMode(pin, OUTPUT);
107 set_led(pin, false);
108 }
109
110 // ------------------------------------------------------------
111 // One-call dispatcher
112 // ------------------------------------------------------------
132 bool testPin(uint8_t pin, Type type, const char *label = nullptr) const
133 {
134 const char *nm = (label && *label) ? label : "PIN";
135 switch (type)
136 {
137 case Type::IO:
138 return _testShorted(pin, nm, /*gentle=*/false) & _testOpen(pin, nm, /*gentle=*/false) & _testDriveRead(pin, nm);
139
140 case Type::Input:
141 return _testShorted(pin, nm, /*gentle=*/false) & _testOpen(pin, nm, /*gentle=*/false);
142
143 case Type::Output:
144 return _testShorted(pin, nm, /*gentle=*/false) & _testDriveRead(pin, nm);
145
146 case Type::OpenDrain:
147 return _testOpenDrain(pin, nm);
148
149 case Type::ADC:
150 return _testAdc(pin, nm);
151
152 case Type::I2C_SDA:
153 return _testI2cLineCheck(pin, nm, "SDA");
154
155 case Type::I2C_SCL:
156 return _testI2cLineCheck(pin, nm, "SCL");
157
158 case Type::PWM:
159 return _testDriveRead(pin, nm); // minimal sanity (HIGH/LOW)
160
161 case Type::UART_RX:
162 // UART RX: use gentle mode (avoid PULLDOWN)
163 return _testShorted(pin, nm, /*gentle=*/true) & _testOpen(pin, nm, /*gentle=*/true);
164
165 case Type::UART_TX:
166 return _testUartTx(pin, nm) & _testOpen(pin, nm, /*gentle=*/false);
167
168 case Type::Button:
169 return _testShorted(pin, nm, /*gentle=*/false) & _testOpen(pin, nm, /*gentle=*/false);
170
171 case Type::Unknown:
172 default:
173 g_serialIO.sprintf(pstTestPinsMri::MSG_UNKNOWN_TYPE, nm, pin);
174 return false;
175 }
176 }
177
186 bool testMcpInput(Adafruit_MCP23017 *pMcp, uint8_t mcpPin, const char *name) const
187 {
188 if (!pMcp)
189 {
190 g_serialIO.print(pstTestPinsMri::MSG_MCP_WARN);
191 return false;
192 }
193 g_serialIO.sprintf(pstTestPinsMri::MSG_MCP_HDR, name ? name : "MCP_PIN", mcpPin);
194
195 // Safe: never drive OLAT; only input + pull-up
196 pMcp->pinMode(mcpPin, INPUT);
197 pMcp->pullUp(mcpPin, true);
198 delay(2);
199 int v = pMcp->digitalRead(mcpPin);
200
201 if (v == LOW)
202 {
203 g_serialIO.print(pstTestPinsMri::MSG_MCP_GND); // “wired to GND”
204 }
205 else
206 {
207 g_serialIO.print(pstTestPinsMri::MSG_MCP_FLOAT); // “open / floating”
208 }
209 return true; // informational test
210 }
211
219 bool testOpen(uint8_t pin, const char *label = nullptr, bool gentle = false) const
220 {
221 return _testOpen(pin, label ? label : "NET", gentle);
222 }
223
231 bool testShorted(uint8_t pin, const char *label = nullptr, bool gentle = false) const
232 {
233 return _testShorted(pin, label ? label : "NET", gentle);
234 }
235
236private:
248 bool _testUartTx(uint8_t pin, const char *label) const
249 {
250 if (_isInputOnly(pin))
251 {
252 g_serialIO.sprintf(pstTestPinsMri::MSG_INPUT_ONLY, label ? label : "PIN", pin);
253 return false;
254 }
255 pinMode(pin, OUTPUT);
256 digitalWrite(pin, HIGH); // UART idle level
257 delay(1);
258 return _testShorted(pin, label ? label : "PIN", /*gentle=*/false) & _testDriveRead(pin, label ? label : "PIN");
259 }
260
266 static bool _isInputOnly(uint8_t pin)
267 {
268 return (pin >= 34 && pin <= 39);
269 }
270
276 static bool _hasInternalPulls(uint8_t pin)
277 {
278 return !_isInputOnly(pin);
279 } // 34-39 lack pulls
280
300 bool _testOpen(uint8_t pin, const char *name, bool gentle = false) const
301 {
302 g_serialIO.sprintf(pstTestPinsMri::MSG_OPEN_HDR, name, pin);
303
304 if (!_hasInternalPulls(pin))
305 {
306 g_serialIO.sprintf(pstTestPinsMri::MSG_NO_NUDGE, name, pin);
307 return true; // not a failure-we just can't probe here
308 }
309
310 if (!gentle)
311 {
312 pinMode(pin, INPUT_PULLUP);
313 delay(2);
314 int up = digitalRead(pin);
315 pinMode(pin, INPUT_PULLDOWN);
316 delay(2);
317 int dn = digitalRead(pin);
318
319 if (up == HIGH && dn == LOW)
320 {
321 g_serialIO.print(pstTestPinsMri::MSG_OPEN_PASS);
322 return true;
323 }
324 if (up == LOW && dn == LOW)
325 {
326 g_serialIO.sprintf(pstTestPinsMri::MSG_STUCK_GND, name, pin);
327 return false;
328 }
329 if (up == HIGH && dn == HIGH)
330 {
331 g_serialIO.sprintf(pstTestPinsMri::MSG_STUCK_VCC, name, pin);
332 return false;
333 }
334 g_serialIO.sprintf(pstTestPinsMri::MSG_OPEN_SUSPECT, name, pin, up, dn);
335 return false;
336 }
337 else
338 {
339 // Gentle: unbiased read + pull-up only
340 pinMode(pin, INPUT);
341 delay(2);
342 int idle = digitalRead(pin);
343 pinMode(pin, INPUT_PULLUP);
344 delay(2);
345 int up = digitalRead(pin);
346 pinMode(pin, INPUT);
347
348 if (up == HIGH)
349 {
350 g_serialIO.print(pstTestPinsMri::MSG_OPEN_PASS);
351 return true;
352 }
353 // If we couldn't lift it HIGH with PULLUP, likely shorted LOW or actively held
354 g_serialIO.sprintf(pstTestPinsMri::MSG_OPEN_SUSPECT, name, pin, idle, up);
355 return false;
356 }
357 }
358
378 bool _testShorted(uint8_t pin, const char *name, bool gentle = false) const
379 {
380 g_serialIO.sprintf(pstTestPinsMri::MSG_SHORT_HDR, name, pin);
381 if (!_hasInternalPulls(pin))
382 {
383 g_serialIO.sprintf(pstTestPinsMri::MSG_NO_NUDGE, name, pin);
384 return true;
385 }
386
387 if (!gentle)
388 {
389 pinMode(pin, INPUT_PULLUP);
390 delay(2);
391 int up = digitalRead(pin);
392 pinMode(pin, INPUT_PULLDOWN);
393 delay(2);
394 int dn = digitalRead(pin);
395 if (up == LOW && dn == LOW)
396 {
397 g_serialIO.sprintf(pstTestPinsMri::MSG_STUCK_GND, name, pin);
398 return false;
399 }
400 if (up == HIGH && dn == HIGH)
401 {
402 g_serialIO.sprintf(pstTestPinsMri::MSG_STUCK_VCC, name, pin);
403 return false;
404 }
405 g_serialIO.print(pstTestPinsMri::MSG_SHORT_PASS);
406 return true;
407 }
408 else
409 {
410 // Gentle: never assert LOW (no PULLDOWN)
411 pinMode(pin, INPUT);
412 delay(2);
413 int idle = digitalRead(pin);
414 pinMode(pin, INPUT_PULLUP);
415 delay(2);
416 int up = digitalRead(pin);
417 pinMode(pin, INPUT);
418
419 if (up == HIGH)
420 {
421 g_serialIO.print(pstTestPinsMri::MSG_SHORT_PASS);
422 return true;
423 }
424 g_serialIO.sprintf(pstTestPinsMri::MSG_STUCK_GND, name, pin);
425 return false;
426 }
427 }
428
440 bool _testDriveRead(uint8_t pin, const char *name) const
441 {
442 if (_isInputOnly(pin))
443 {
444 g_serialIO.sprintf(pstTestPinsMri::MSG_INPUT_ONLY, name, pin);
445 return false;
446 }
447 g_serialIO.sprintf(pstTestPinsMri::MSG_DRIVE_HDR, name, pin);
448 pinMode(pin, OUTPUT);
449 delay(1);
450 digitalWrite(pin, HIGH);
451 delay(3);
452 int rH = digitalRead(pin);
453 digitalWrite(pin, LOW);
454 delay(3);
455 int rL = digitalRead(pin);
456
457 if (rH == HIGH && rL == LOW)
458 {
459 g_serialIO.print(pstTestPinsMri::MSG_DRIVE_PASS);
460 return true;
461 }
462 g_serialIO.sprintf(pstTestPinsMri::MSG_DRIVE_FAIL, name, pin, rH, rL);
463 return false;
464 }
465
477 bool _testOpenDrain(uint8_t pin, const char *name) const
478 {
479 if (_isInputOnly(pin))
480 {
481 g_serialIO.sprintf(pstTestPinsMri::MSG_INPUT_ONLY, name, pin);
482 return false;
483 }
484 g_serialIO.sprintf(pstTestPinsMri::MSG_OD_HDR, name, pin);
485 // Drive LOW should read LOW
486 pinMode(pin, OUTPUT);
487 digitalWrite(pin, LOW);
488 delay(3);
489 int low = digitalRead(pin);
490 // Release (input pull-up) should read HIGH
491 pinMode(pin, INPUT_PULLUP);
492 delay(3);
493 int hi = digitalRead(pin);
494 if (low == LOW && hi == HIGH)
495 {
496 g_serialIO.print(pstTestPinsMri::MSG_OD_PASS);
497 return true;
498 }
499 g_serialIO.sprintf(pstTestPinsMri::MSG_OD_FAIL, name, pin, low, hi);
500 return false;
501 }
502
513 bool _testAdc(uint8_t pin, const char *name) const
514 {
515 g_serialIO.sprintf(pstTestPinsMri::MSG_ADC_HDR, name, pin);
516 analogSetPinAttenuation(pin, ADC_11db); // good up to ~3.3V
517 const int N = 8;
518 int sumMv = 0;
519 for (int i = 0; i < N; ++i)
520 {
521 sumMv += analogReadMilliVolts(pin);
522 delay(2);
523 }
524 int mv = sumMv / N;
525 g_serialIO.sprintf(pstTestPinsMri::MSG_ADC_INFO, name, pin, mv);
526 if (mv < 50)
527 {
528 g_serialIO.print(pstTestPinsMri::MSG_ADC_SHORT_TO_GND_WARN);
529 return false;
530 }
531 if (mv > 3250)
532 {
533 g_serialIO.print(pstTestPinsMri::MSG_ADC_SHORT_TO_3V3_WARN);
534 return false;
535 }
536 return true;
537 }
538
556 bool _testI2cLineCheck(uint8_t pin, const char *name, const char *lineName) const
557 {
558 g_serialIO.sprintf(pstTestPinsMri::MSG_I2C_LINE_HDR, lineName, name, pin);
559
560 // Look at bus state without our own pulls
561 pinMode(pin, INPUT);
562 delay(2);
563 int idle = digitalRead(pin);
564
565 if (idle == HIGH)
566 {
567 g_serialIO.sprintf(pstTestPinsMri::MSG_I2C_PULLUP_PASS, lineName);
568 return true;
569 }
570
571 // Gentle nudge to diagnose "missing pull-ups” vs "stuck low”
572 pinMode(pin, INPUT_PULLUP);
573 delay(2);
574 int nudged = digitalRead(pin);
575 pinMode(pin, INPUT); // restore neutral
576 if (nudged == HIGH)
577 {
578 g_serialIO.sprintf(pstTestPinsMri::MSG_I2C_MISSING_PULLUP_FAIL, lineName);
579 return false;
580 }
581 else
582 {
583 g_serialIO.sprintf(pstTestPinsMri::MSG_I2C_SHORT_TO_GROUND_FAIL, lineName);
584 return false;
585 }
586 }
587};
588 // end of group
Header-only class for receiving and buffering command packets over UART, Bluetooth,...
MCP23017 main class.
uint8_t digitalRead(uint8_t p)
Reads the specified pin.
void pinMode(uint8_t p, uint8_t d)
void pullUp(uint8_t p, uint8_t d)
Enables the pull-up resistor on the specified pin.
One-stop pin self-test utility with a single dispatcher entry point.
Definition: PstTestPins.h:45
static void set_led(uint8_t pin, bool on)
Convenience: drive an indicator LED with active-LOW polarity.
Definition: PstTestPins.h:95
static void init_led(uint8_t pin)
Initialize an indicator LED pin (OUTPUT, start OFF).
Definition: PstTestPins.h:104
PstTestPins()
Default constructor (no configuration needed for ESP32).
Definition: PstTestPins.h:88
bool testShorted(uint8_t pin, const char *label=nullptr, bool gentle=false) const
Run only the "shorted" heuristic on a pin.
Definition: PstTestPins.h:231
@ I2C_SCL
I²C clock line (idle HIGH)
@ UART_RX
DF TX -> ESP32 RX (Input with gentle bias)
@ UART_TX
ESP32 TX -> DF RX (Output; idle HIGH)
@ Output
digital output
@ Input
digital input only
@ ADC
analog input
@ PWM
PWM-capable output (basic toggle check here)
@ Button
momentary input (basic stuck check)
@ Unknown
unknown type
@ OpenDrain
OD output: drive LOW, release HIGH.
@ I2C_SDA
I²C data line (idle HIGH)
@ IO
general-purpose (can be in/out)
@ McpInput
NEW: probe an MCP23017 input pin (pull-up ON, read HIGH/LOW)
bool testMcpInput(Adafruit_MCP23017 *pMcp, uint8_t mcpPin, const char *name) const
Run input test on an MCP23017 pin.
Definition: PstTestPins.h:186
bool testOpen(uint8_t pin, const char *label=nullptr, bool gentle=false) const
Run only the "open" heuristic on a pin.
Definition: PstTestPins.h:219
bool testPin(uint8_t pin, Type type, const char *label=nullptr) const
Run the appropriate self-test(s) for a pin based on its Type.
Definition: PstTestPins.h:132
void print(const char *msg)
Print a string to all active consoles.
Definition: SerialIO.h:595
void sprintf(const char *fmt,...)
printf-style logging to all active consoles (no newline).
Definition: SerialIO.h:617