Pin Output Measured Voltage

Hey guys, I'm strugging to measure the expected 2.7v from my Particle Tracker One device. It is connected to the M8 accessosory and I am using a Fluke 789 Proccessmeter to measure between the 3 pins I am testing: A3, D9, D8, D1 (D1 not using).

I use the particle web IDE to set each pin and I can see the state change from the varible array that is updated after each pin write.

Device OS version 6.2.1.

Code below:

//#include "Particle.h"
#include "RTClibrary.h"
#include "TimeLib.h"

// ===================
// === DEFINITIONS ===
// ===================
#define BUTTON_COUNT 4
#define NTP_SERVER "pool.ntp.org"

// Relay pins (open-drain configuration, active LOW relays)
const int relayPins[BUTTON_COUNT] = { A3, D9, D8, D1 }; // Adjust D2 if needed
int pinSetDriveStrength(pin_t pin, DriveStrength drive);
// ==========================
// === GLOBAL VARIABLES ====
// ==========================
bool buttonState[BUTTON_COUNT] = {false, false, false, false};  // ON/OFF state of each relay
String buttonStates = "0,0,0,0";  // Particle cloud-visible state string

RTC_DS3231 rtc;  // External RTC module

// ==========================
// === HELPER FUNCTIONS ====
// ==========================

// Convert the button states into a CSV string
String getButtonStateString() {
    String stateStr = "";
    for (int i = 0; i < BUTTON_COUNT; i++) {
        stateStr += String(buttonState[i]);
        if (i < BUTTON_COUNT - 1) {
            stateStr += ",";
        }
    }
    return stateStr;
}

// Set pin behavior based on desired state (open-drain style logic)
void applyRelayState(int index) {
    if (buttonState[index]) {
        // Relay ON: drive pin LOW
    
        pinMode(relayPins[index], OUTPUT);
        digitalWrite(relayPins[index], HIGH);
    } else {
        // Relay OFF: release pin to high impedance
        pinMode(relayPins[index], OUTPUT);
        digitalWrite(relayPins[index], LOW); // releases to float
    }
}

// Particle cloud function to toggle a relay by button ID (1-based)
int setButtonState(String command) {
    int buttonId = command.toInt();

    if (buttonId >= 1 && buttonId <= BUTTON_COUNT) {
        int index = buttonId - 1;

        // Toggle state
        buttonState[index] = !buttonState[index];
        applyRelayState(index);

        // Update cloud variable and publish
        buttonStates = getButtonStateString();
        Particle.publish("buttonStates", buttonStates, PRIVATE);

        // Log the action with timestamp
        DateTime now = rtc.now();
        String logMessage = String("Relay ") + String(buttonId) + (buttonState[index] ? " ON" : " OFF");
        logMessage += " | " + String(now.year()) + "-" + String(now.month()) + "-" + String(now.day()) + " "
                      + String(now.hour()) + ":" + String(now.minute()) + ":" + String(now.second());
        Log.info(logMessage);

        return 1;
    } else {
        Log.error("Invalid relay ID: " + String(buttonId));
        return -1;
    }
}

// Sync RTC time from NTP or fallback to compilation time
void syncRTC() {
    DateTime now = rtc.now();

    if (now.year() == 2000) {
        Time.zone(2);  // Adjust to your local timezone
        Particle.syncTime();
        Log.info("Time synced with Particle NTP");
    }

    if (now.year() == 2000) {
        rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
        Log.warn("RTC set to firmware compile time");
    }
}

// ===================
// === SETUP FUNC ====
// ===================
void setup() {
    // Set all relay pins to off state (floating)
    for (int i = 0; i < BUTTON_COUNT; i++) {
        pinMode(relayPins[i], OUTPUT);
        digitalWrite(relayPins[i], LOW); // releases to 0V
      //  pinSetDriveStrength(relayPins[i], DriveStrength::HIGH);
    }
    //SystemPowerFeature(PW_FEATURE_HIGH_CURRENT);


    Particle.function("setButtonState", setButtonState);
    Particle.variable("buttonStates", buttonStates);

    syncRTC();

    Particle.publish("buttonStates", buttonStates, PRIVATE);
    Log.info("Setup complete. All relays OFF.");
    pinMode(A3, OUTPUT);
    pinSetDriveStrength(A3, DriveStrength::HIGH);
    digitalWrite(A3, HIGH);
    pinMode(D9, OUTPUT);
    pinSetDriveStrength(D9, DriveStrength::HIGH);
    digitalWrite(D9, HIGH);
    pinMode(D8, OUTPUT);
    pinSetDriveStrength(D8, DriveStrength::HIGH);
    digitalWrite(D8, HIGH);

    Log.info("set  A3, D9, D8, D1 to HIGH.");
}

// ==================
// === MAIN LOOP ====
// ==================
static unsigned long lastSyncTime = 0;
void loop() {
   

    if (millis() - lastSyncTime > 3600000) { // Every hour
        syncRTC();
        lastSyncTime = millis();
        Log.info("RTC resynced.");
    }

  //  digitalWrite(A3, HIGH); // sets the LED on
 //   delay(200);              // waits for 200mS
  //  digitalWrite(A3, LOW);  // sets the LED off
 //   delay(200);              // waits for 200mS

    // Background tasks if needed
}

I measure voltages below 1v, unchanging with pin change.

Hope someone can have a look at this simple issue, not my first device but cant get this little thing right crying face.

Some extra useful info:

The 8-pin connector has these signals:

M8 Pin Function Function Function I/O Color
1 CAN_P IO2 Yellow
2 VIN3 I Red
3 Analog A3 GPIO D3 IO1 White
4 Serial1 RX Wire3 SDA GPIO D9 IO1 Green
5 Serial1 TX Wire3 SCL GPIO D8 IO1 Brown
6 CAN_5V4 CAN_PWR O Orange
7 CAN_N IO2 Blue
8 GND Black

Some additional info:

CAN_P (positive), CANH (high), or CAN+
CAN_N (negative), CANL (low), or CAN-
As the signals are differential you don't need to connect GND for CAN bus, but you do still need to
connect it for Serial, I2C, or GPIO.
The three GPIO and port pins (A3/D3, RX/SDA/D9, TX/SCL/D8) have the following characteristics:
Symbol Parameter Min Typ Max Unit
VIH Input high voltage 0.7 xVDD VDD V
VIL Input low voltage VSS 0.3 xVDD V
VOH,SD Output high voltage, standard drive, 0.5 mA, VDD ≥1.7 VDD - 0.4 VDD V
VOH,HDH Output high voltage, high drive, 5 mA, VDD >= 2.7 V VDD - 0.4 VDD V
VOH,HDL Output high voltage, high drive, 3 mA, VDD >= 1.7 V VDD - 0.4 VDD V
VOL,SD Output low voltage, standard drive, 0.5 mA, VDD ≥1.7 VSS VSS + 0.4 V
VOL,HDH Output low voltage, high drive, 5 mA, VDD >= 2.7 V VSS VSS + 0.4 V
VOL,HDL Output low voltage, high drive,3 mA, VDD >= 1.7 V VSS VSS + 0.4 V
IOL,SD Current at VSS+0.4 V, output set low, standard drive, VDD≥1.7 1 2 4 mA
IOL,HDH Current at VSS+0.4 V, output set low, high drive, VDD >= 2.7V 6 10 15 mA
IOL,HDL Current at VSS+0.4 V, output set low, high drive, VDD >= 1.7V 3 mA
IOH,SD Current at VDD-0.4 V, output set high, standard drive, VDD≥1.7 1 2 4 mA
IOH,HDH Current at VDD-0.4 V, output set high, high drive, VDD >= 2.7V 6 9 14 mA
IOH,HDL Current at VDD-0.4 V, output set high, high drive, VDD >= 1.7V 3 mA
RPU Pull-up resistance 11 13 16 kΩ
RPD Pull-down resistance 11 13 16 kΩ
GPIO default to standard drive (2mA) but can be reconfigured to high drive (9mA) in Device OS
2.0.0 and later using the pinSetDriveStrength() function.

You need to turn on CAN_PWR (5V). There's an analog switch that disconnects the GPIO on the M8 connector when it's turned off.

    // If using the M8 connector, turn on the CAN_5V power
    pinMode(CAN_PWR, OUTPUT);
    digitalWrite(CAN_PWR, HIGH);
2 Likes