@@ -48,11 +48,12 @@ namespace cfg = kart::cfg;
4848
4949namespace {
5050
51- constexpr const char *kVersion = " 0.4.9-freeshift " ;
51+ constexpr const char *kVersion = " 0.4.15-spdspeedo " ;
5252
5353// ── Pin map (docs/SMCSKart-Mainboard/README.md). Output lines are
5454// MOSFET-switched grounds: HIGH = asserted at the ESC, LOW = released. ──
55- constexpr uint8_t kPinHallPulses = 2 ;
55+ constexpr uint8_t kPinHallPulses = 2 ; // ESC pin 18 (raw motor-hall tach)
56+ constexpr uint8_t kPinSpd = 22 ; // ESC pin 13 (SPD digital speed pulse)
5657constexpr uint8_t kPinReverse = 3 ;
5758constexpr uint8_t kPinBrakeLow = 4 ;
5859constexpr uint8_t kPinSpeedHigh = 5 ;
@@ -234,6 +235,33 @@ void hallIsr() {
234235 }
235236}
236237
238+ // ── SPD tach ISR (ESC pin 13 "digital speed pulse" -> Teensy pin 22) ──
239+ // The dedicated digital-speedo output, a candidate cleaner source than the raw
240+ // hall on pin 2. Instrumented identically so HALLDIAG can compare the two lines
241+ // side by side (even spacing = clean square-wave tach vs. the hall's 1:2 jitter).
242+ volatile uint32_t g_spdCount = 0 ; // glitch-filtered
243+ volatile uint32_t g_spdCountRaw = 0 ; // every edge, no filter
244+ volatile uint32_t g_lastSpdUs = 0 ; // last ACCEPTED (filtered) edge
245+ volatile uint32_t g_lastSpdRawUs = 0 ; // last raw edge
246+ volatile uint32_t g_spdIntMinUs = 0xFFFFFFFFu ; // min raw inter-edge, this window
247+ volatile uint32_t g_spdIntMaxUs = 0 ; // max raw inter-edge, this window
248+ volatile bool g_spdRawSeen = false ;
249+ void spdIsr () {
250+ uint32_t now = micros ();
251+ g_spdCountRaw++;
252+ if (g_spdRawSeen) {
253+ uint32_t dt = now - g_lastSpdRawUs;
254+ if (dt < g_spdIntMinUs) g_spdIntMinUs = dt;
255+ if (dt > g_spdIntMaxUs) g_spdIntMaxUs = dt;
256+ }
257+ g_lastSpdRawUs = now;
258+ g_spdRawSeen = true ;
259+ if ((uint32_t )(now - g_lastSpdUs) >= cfg::kHallMinIntervalUs ) {
260+ g_lastSpdUs = now;
261+ g_spdCount++;
262+ }
263+ }
264+
237265// ── HALLDIAG: bench streaming of the raw tach line to diagnose the speedo ──
238266// `HALLDIAG [ON|secs]` streams one INFO line per window (~10 Hz) to the issuing
239267// port for kHallDiagDefaultMs (auto-stops); `HALLDIAG OFF` stops it.
@@ -245,7 +273,14 @@ uint32_t g_hallDiagUntilMs = 0;
245273uint32_t g_hallDiagNextMs = 0 ;
246274uint32_t g_hallDiagLastFilt = 0 ;
247275uint32_t g_hallDiagLastRaw = 0 ;
276+ uint32_t g_hallDiagLastSpdFilt = 0 ;
277+ uint32_t g_hallDiagLastSpdRaw = 0 ;
248278uint32_t g_hallDiagLastMs = 0 ;
279+ // Analog envelope of pin 22 (A8) across the current HALLDIAG window, updated
280+ // from loop() while streaming; catches an analog speedo voltage or low-going
281+ // pulses the edge interrupt might miss.
282+ uint16_t g_adcMin = 1023 , g_adcMax = 0 ;
283+ uint32_t g_adcSum = 0 , g_adcCount = 0 ;
249284
250285// ─────────────────────────────────────────────────────────────────────────
251286// Low-level output helpers
@@ -596,7 +631,7 @@ kart::DriveInputs gatherInputs(uint32_t now) {
596631 g_implausibleActive &&
597632 (uint32_t )(now - g_implausibleSinceMs) >= cfg::kPedalImplausibleMs ;
598633
599- g_hall.update (g_hallCount , now);
634+ g_hall.update (g_spdCount , now); // speed from SPD (pin 22), not pin-2 hall
600635
601636 // Steering setpoint from the wheel axis (sent every tick via STEER_SET).
602637 g_steerSetpointCdeg = g_steerLink.axis_to_setpoint (rawAxis (g_axisSteer));
@@ -780,7 +815,7 @@ void sendTelemetry(uint32_t now, const kart::DriveInputs &in) {
780815 t.brake_pct = (uint8_t )(g_brakePct + 0 .5f );
781816 t.steer_setpoint_cdeg = g_steerSetpointCdeg;
782817 t.steer_measured_cdeg = g_steerLink.last_status ().measured_cdeg ;
783- t.hall_count = g_hallCount;
818+ t.hall_count = g_spdCount; // SPD pulses (speedometer source)
784819 t.hall_hz_x10 = g_hall.hz_x10 ();
785820 t.batt_dv = 0 ;
786821 t.batt_da = 0 ;
@@ -949,7 +984,7 @@ void cmdStatus(Stream &out) {
949984 out.print (" brk=" );
950985 out.print (g_brakePct, 1 );
951986 out.print (" hall=" );
952- out.print (g_hallCount);
987+ out.print (g_spdCount); // SPD pulse count (speedometer source; pin-2 raw via HALLDIAG)
953988 out.print (" hz10=" );
954989 out.print (g_hall.hz_x10 ());
955990 out.print (" contactor=" );
@@ -1086,7 +1121,15 @@ void handleCommand(const String &line, Stream &out) {
10861121 g_hallDiagLastRaw = g_hallCountRaw;
10871122 g_hallIntMinUs = 0xFFFFFFFFu ;
10881123 g_hallIntMaxUs = 0 ;
1124+ g_hallDiagLastSpdFilt = g_spdCount;
1125+ g_hallDiagLastSpdRaw = g_spdCountRaw;
1126+ g_spdIntMinUs = 0xFFFFFFFFu ;
1127+ g_spdIntMaxUs = 0 ;
10891128 interrupts ();
1129+ g_adcMin = 1023 ;
1130+ g_adcMax = 0 ;
1131+ g_adcSum = 0 ;
1132+ g_adcCount = 0 ;
10901133 g_hallDiagLastMs = now;
10911134 g_hallDiagNextMs = now + kHallDiagPeriodMs ;
10921135 g_hallDiagUntilMs = now + durMs;
@@ -1268,6 +1311,7 @@ void servicePort(Stream &port, String &buffer) {
12681311
12691312void setup () {
12701313 pinMode (kPinHallPulses , INPUT_PULLUP );
1314+ pinMode (kPinSpd , INPUT_PULLUP );
12711315 pinMode (kPinPps , INPUT );
12721316 pinMode (kPinReverse , OUTPUT );
12731317 pinMode (kPinBrakeLow , OUTPUT );
@@ -1293,6 +1337,7 @@ void setup() {
12931337
12941338 g_usbHost.begin ();
12951339 attachInterrupt (digitalPinToInterrupt (kPinHallPulses ), hallIsr, RISING );
1340+ attachInterrupt (digitalPinToInterrupt (kPinSpd ), spdIsr, RISING );
12961341
12971342 // Steering CAN bus (CAN3, mainboard pins 30/31). KART_CAN_BITRATE per can-ids.md.
12981343 // Interrupt-driven RX FIFO: the ISR drains the 6-deep hardware FIFO into the
@@ -1338,43 +1383,76 @@ void serviceHallDiag(uint32_t now) {
13381383 if ((int32_t )(now - g_hallDiagNextMs) < 0 ) return ;
13391384 g_hallDiagNextMs = now + kHallDiagPeriodMs ;
13401385
1341- // Snapshot + reset the ISR-owned window stats with interrupts briefly masked.
1386+ // Snapshot + reset the ISR-owned window stats for BOTH tach lines (pin 2 raw
1387+ // hall, pin 22 SPD) with interrupts briefly masked.
13421388 noInterrupts ();
1343- uint32_t filt = g_hallCount;
1344- uint32_t raw = g_hallCountRaw;
1345- uint32_t minUs = g_hallIntMinUs;
1346- uint32_t maxUs = g_hallIntMaxUs;
1389+ uint32_t filt = g_hallCount, raw = g_hallCountRaw;
1390+ uint32_t minUs = g_hallIntMinUs, maxUs = g_hallIntMaxUs;
13471391 g_hallIntMinUs = 0xFFFFFFFFu ;
13481392 g_hallIntMaxUs = 0 ;
1393+ uint32_t sFilt = g_spdCount, sRaw = g_spdCountRaw;
1394+ uint32_t sMinUs = g_spdIntMinUs, sMaxUs = g_spdIntMaxUs;
1395+ g_spdIntMinUs = 0xFFFFFFFFu ;
1396+ g_spdIntMaxUs = 0 ;
1397+ uint16_t adcMin = g_adcMin, adcMax = g_adcMax;
1398+ uint32_t adcSum = g_adcSum, adcCount = g_adcCount;
1399+ g_adcMin = 1023 ;
1400+ g_adcMax = 0 ;
1401+ g_adcSum = 0 ;
1402+ g_adcCount = 0 ;
13491403 interrupts ();
13501404
13511405 uint32_t winMs = now - g_hallDiagLastMs;
13521406 if (winMs == 0 ) winMs = 1 ;
13531407 uint32_t dFilt = filt - g_hallDiagLastFilt;
13541408 uint32_t dRaw = raw - g_hallDiagLastRaw;
1409+ uint32_t dSFilt = sFilt - g_hallDiagLastSpdFilt;
1410+ uint32_t dSRaw = sRaw - g_hallDiagLastSpdRaw;
13551411 g_hallDiagLastFilt = filt;
13561412 g_hallDiagLastRaw = raw;
1413+ g_hallDiagLastSpdFilt = sFilt ;
1414+ g_hallDiagLastSpdRaw = sRaw ;
13571415 g_hallDiagLastMs = now;
13581416
13591417 Stream *o = g_hallDiagOut ? g_hallDiagOut : &Serial;
13601418 o->print (" INFO HALLDIAG win_ms=" );
13611419 o->print (winMs);
1362- o->print (" filt=" ); // filtered edges this window (drives speed)
1420+ // pin 2 (raw motor hall)
1421+ o->print (" hall_filt=" );
13631422 o->print (dFilt);
1364- o->print (" raw =" ); // ALL edges this window
1423+ o->print (" hall_raw =" );
13651424 o->print (dRaw);
1366- o->print (" dropped=" ); // edges the 120 us glitch filter rejected
1367- o->print (dRaw >= dFilt ? dRaw - dFilt : 0 );
1368- o->print (" fhz=" );
1369- o->print (dFilt * 1000u / winMs);
1370- o->print (" rhz=" );
1425+ o->print (" hall_hz=" );
13711426 o->print (dRaw * 1000u / winMs);
1372- o->print (" min_us =" ); // tightest raw inter-edge gap (spacing evenness )
1427+ o->print (" hall_min_us =" ); // spacing evenness (hall shows ~1:2 jitter )
13731428 o->print (minUs == 0xFFFFFFFFu ? 0 : minUs);
1374- o->print (" max_us =" );
1429+ o->print (" hall_max_us =" );
13751430 o->print (maxUs);
1376- o->print (" lvl=" ); // instantaneous pin level (stuck-line check)
1377- o->println (digitalRead (kPinHallPulses ));
1431+ // pin 22 (SPD digital speed pulse) — the candidate clean source
1432+ o->print (" spd_filt=" );
1433+ o->print (dSFilt);
1434+ o->print (" spd_raw=" );
1435+ o->print (dSRaw);
1436+ o->print (" spd_hz=" );
1437+ o->print (dSRaw * 1000u / winMs);
1438+ o->print (" spd_min_us=" ); // even spacing here => clean square-wave tach
1439+ o->print (sMinUs == 0xFFFFFFFFu ? 0 : sMinUs );
1440+ o->print (" spd_max_us=" );
1441+ o->print (sMaxUs );
1442+ o->print (" spd_lvl=" );
1443+ o->print (digitalRead (kPinSpd ));
1444+ // pin 22 (A8) analog envelope this window: 0..1023 over 0..3.3 V. Pinned high
1445+ // (~1023) => idle/high-Z (pullup wins, no signal). Swinging min~0/max~1023 =>
1446+ // a digital square wave present. A steady mid value that tracks speed =>
1447+ // analog speedo voltage.
1448+ o->print (" adc_min=" );
1449+ o->print (adcMin);
1450+ o->print (" adc_max=" );
1451+ o->print (adcMax);
1452+ o->print (" adc_avg=" );
1453+ o->print (adcCount ? (uint16_t )(adcSum / adcCount) : 0 );
1454+ o->print (" adc_n=" );
1455+ o->println (adcCount);
13781456}
13791457
13801458void loop () {
@@ -1403,6 +1481,13 @@ void loop() {
14031481
14041482 servicePort (Serial, g_usbRx);
14051483 servicePort (Serial2, g_piRx);
1484+ if (g_hallDiag) { // sample pin 22 (A8) voltage across the window
1485+ uint16_t a = (uint16_t )analogRead (kPinSpd );
1486+ if (a < g_adcMin) g_adcMin = a;
1487+ if (a > g_adcMax) g_adcMax = a;
1488+ g_adcSum += a;
1489+ g_adcCount++;
1490+ }
14061491 serviceHallDiag (now);
14071492
14081493#if KART_ENABLE_WATCHDOG
0 commit comments