Skip to content
Snippets Groups Projects
electrical_test.cpp 11.5 KiB
Newer Older
  • Learn to ignore specific revisions
  • /*
     * Electrical test for Fadecandy boards
     * 
     * Copyright (c) 2013 Micah Elizabeth Scott
     * 
     * Permission is hereby granted, free of charge, to any person obtaining a copy of
     * this software and associated documentation files (the "Software"), to deal in
     * the Software without restriction, including without limitation the rights to
     * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
     * the Software, and to permit persons to whom the Software is furnished to do so,
     * subject to the following conditions:
     * 
     * The above copyright notice and this permission notice shall be included in all
     * copies or substantial portions of the Software.
     * 
     * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
     * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
     * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
     * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
     * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
     */
    
    #include <Arduino.h>
    #include "electrical_test.h"
    
    #include "testjig.h"
    #include "arm_kinetis_reg.h"
    
    
    
    float ElectricalTest::analogVolts(int pin)
    {
        // Analog input and voltage divider constants
        const float reference = 1.2;
        const float dividerA = 1000;    // Input to ground
        const float dividerB = 6800;    // Input to signal
        const int adcMax = 1023;
    
        const float scale = (reference / adcMax) * ((dividerA + dividerB) / dividerA);
        return analogRead(pin) * scale;
    }
    
    bool ElectricalTest::analogThreshold(int pin, float nominal, float tolerance)
    {
        // Measure an analog input, and verify it's close enough to expected values.
    
        return analogThresholdFromSample(analogVolts(pin), pin, nominal, tolerance);
    }
    
    bool ElectricalTest::analogThresholdFromSample(float volts, int pin, float nominal, float tolerance)
    {
    
        float lower = nominal - tolerance;
        float upper = nominal + tolerance;
    
        if (volts < lower || volts > upper) {
    
            target.log(LOG_ERROR,
    
                    "ETEST: Analog value %d outside reference range! "
                    "value = %.2fv, ref = %.2fv +/- %.2fv",
                    pin, volts, nominal, tolerance);
            return false;
        }
    
        return true;
    }
    
    bool ElectricalTest::testOutputPattern(uint8_t bits)
    {
        // Set the target's 8-bit output port to the given value, and check all analog values
    
        // Write the port all at once
    
        if (!target.digitalWritePort(outPin(0), bits))
            return false;
    
    
        // Check power supply each time
    
        if (!analogThreshold(analogTarget33vPin, 3.3)) return false;
        if (!analogThreshold(analogTargetVUsbPin, 5.0)) return false;
    
    
        // Check all data signal levels
        for (unsigned n = 0; n < 8; n++) {
            bool bit = (bits >> n) & 1;
            if (!analogThreshold(n, bit ? 5.0 : 0.0))
                return false;
        }
    
        return true;
    }
    
    bool ElectricalTest::testAllOutputPatterns()
    {
    
        target.log(logLevel, "ETEST: Testing data output patterns");
    
    
        // All on, all off
        if (!testOutputPattern(0x00)) return false;
        if (!testOutputPattern(0xFF)) return false;
    
        // One bit set
        for (unsigned n = 0; n < 8; n++) {
            if (!testOutputPattern(1 << n))
                return false;
        }
    
        // One bit missing
        for (unsigned n = 0; n < 8; n++) {
            if (!testOutputPattern(0xFF ^ (1 << n)))
                return false;
        }
    
        // Leave all outputs on
        return testOutputPattern(0xFF);
    }
    
    bool ElectricalTest::initTarget()
    {
        // Target setup that's needed only once per test run
    
        // Output pin directions
        for (unsigned n = 0; n < 8; n++) {
            if (!target.pinMode(outPin(n), OUTPUT))
                return false;
        }
    
    
        // Disable target USB USB pull-ups
        if (!target.usbSetPullup(false))
            return false;
    
    
    void ElectricalTest::setPowerSupplyVoltage(float volts)
    {
        // Set the variable power supply voltage. Usable range is from 0V to system VUSB.
    
    
        int pwm = constrain(volts * (255 / powerSupplyFullScaleVoltage), 0, 255);
        pinMode(powerPWMPin, OUTPUT);
        analogWriteFrequency(powerPWMPin, 1000000);
        analogWrite(powerPWMPin, pwm);
    
        /*
         * Time for the PSU to settle. Our testjig's power supply settles very
         * fast (<1ms), but the capacitors on the target need more time to charge.
         */
    
    bool ElectricalTest::testBoostConverter()
    {
        target.log(logLevel, "ETEST: Testing boost converter");
    
        // Test over a range of input voltages
        for (float supply = 5.0; supply > 3.5; supply -= 0.2) {
    
            // Turn all outputs on
            if (!target.digitalWritePort(outPin(0), 0xFF))
                return false;
    
            // Adjust power supply
            setPowerSupplyVoltage(supply);
    
            // Collect all relevant voltages
            float vusb = analogVolts(analogTargetVUsbPin);
            float vcc = analogVolts(analogTarget33vPin);
            float v0 = analogVolts(0);
            float v1 = analogVolts(1);
            float v2 = analogVolts(2);
            float v3 = analogVolts(3);
            float v4 = analogVolts(4);
            float v5 = analogVolts(5);
            float v6 = analogVolts(6);
            float v7 = analogVolts(7);
    
            target.log(logLevel,
                "  Supply at %.1fv : Target vusb=%.2fv vcc=%.2fv outputs=["
                "%.2fv %.2fv %.2fv %.2fv %.2fv %.2fv %.2fv %.2fv]",
                supply, vusb, vcc, v0, v1, v2, v3, v4, v5, v6, v7);
    
            if (!analogThresholdFromSample(vusb, analogTargetVUsbPin, supply)) return false;
            if (!analogThresholdFromSample(vcc, analogTarget33vPin, 3.3)) return false;
            if (!analogThresholdFromSample(v0, 0, 5.0)) return false;
            if (!analogThresholdFromSample(v1, 1, 5.0)) return false;
            if (!analogThresholdFromSample(v2, 2, 5.0)) return false;
            if (!analogThresholdFromSample(v3, 3, 5.0)) return false;
            if (!analogThresholdFromSample(v4, 4, 5.0)) return false;
            if (!analogThresholdFromSample(v5, 5, 5.0)) return false;
            if (!analogThresholdFromSample(v6, 6, 5.0)) return false;
            if (!analogThresholdFromSample(v7, 7, 5.0)) return false;
    
            // Also make sure we can turn outputs off properly
            if (!target.digitalWritePort(outPin(0), 0x00))
                return false;
            for (unsigned n = 0; n < 8; n++)
                if (!analogThreshold(n, 0))
                    return false;
        }
    
    
        // Done! Go back to a nominal 5V supply. We'll want this to be stable for flash programming.
        setPowerSupplyVoltage(5.0);
    
    void ElectricalTest::powerOff()
    {
        setPowerSupplyVoltage(0);
    }
    
    
    bool ElectricalTest::powerOn()
    
    {
        target.log(logLevel, "ETEST: Enabling power supply");
    
        const float volts = 5.0;
        setPowerSupplyVoltage(volts);
        return analogThreshold(analogTargetVUsbPin, volts);
    }
    
    bool ElectricalTest::testHighZ(int pin)
    {
        // Test a pin to make sure it's high-impedance, by using its parasitic capacitance
        for (unsigned i = 0; i < 10; i++) {
            pinMode(pin, OUTPUT);
            digitalWrite(pin, i & 1);
            pinMode(pin, INPUT);
            if (digitalRead(pin) != (i & 1))
                return false;
        }
        return true;
    }
    
    bool ElectricalTest::testPull(int pin, bool state)
    {
        // Test a pin for a pull-up/down resistor
        for (unsigned i = 0; i < 10; i++) {
            pinMode(pin, OUTPUT);
            digitalWrite(pin, i & 1);
            pinMode(pin, INPUT);
            if (digitalRead(pin) != state)
                return false;
        }
        return true;
    }    
    
    bool ElectricalTest::testUSBConnections()
    {
        target.log(logLevel, "ETEST: Testing USB connections");
    
        // Run this test a few times
        for (unsigned iter = 0; iter < 4; iter++) {
    
            // Start with pull-up disabled
            if (!target.usbSetPullup(false))
                return false;
    
            // Test both USB ground connections
            pinMode(usbShieldGroundPin, INPUT_PULLUP);
            pinMode(usbSignalGroundPin, INPUT_PULLUP);
            if (digitalRead(usbShieldGroundPin) != LOW) {
                target.log(LOG_ERROR, "ETEST: Faulty USB shield ground");
                return false;
            }
            if (digitalRead(usbSignalGroundPin) != LOW) {
                target.log(LOG_ERROR, "ETEST: Faulty USB signal ground");
                return false;
            }
    
            // Test for a high-impedance USB D+ and D- by charging and discharging parasitic capacitance
            if (!testHighZ(usbDMinusPin)) {
                target.log(LOG_ERROR, "ETEST: Fault on USB D-, expected High-Z");
                return false;
            }
            if (!testHighZ(usbDPlusPin)) {
                target.log(LOG_ERROR, "ETEST: Fault on USB D+, expected High-Z");
                return false;
            }
    
            // Turn on USB pull-up on D+
            if (!target.usbSetPullup(true))
                return false;
    
            // Now D+ should be pulled up, and D- needs to still be high-Z
            if (!testPull(usbDPlusPin, HIGH)) {
                target.log(LOG_ERROR, "ETEST: Fault on USB D+, no pull-up found");
                return false;
            }
            if (!testHighZ(usbDMinusPin)) {
                target.log(LOG_ERROR, "ETEST: Fault on USB D-, expected High-Z. Possible short to D+");
                return false;
            }
    
        }
    
        return true;
    
    bool ElectricalTest::testSerialConnections()
    {
        target.log(logLevel, "ETEST: Testing serial connections");
    
        // This tests serial RX, TX, and the DMA loopback, which are all adjacent.
        target.pinMode(target.PTB0, OUTPUT);
        target.pinMode(target.PTC0, INPUT);
        for (unsigned i = 0; i < 10; i++) {
            target.digitalWrite(target.PTB0, i&1);
            if (target.digitalRead(target.PTC0) != (i&1)) {
                target.log(LOG_ERROR, "ETEST: Bad connection between DMA loopback pins PTB0 and PTC0");
                return false;
            }
        }
    
        // Leave that connection driven, check for shorts to serial RX/TX
        if (!testHighZ(fcTXPin)) {
            target.log(LOG_ERROR, "ETEST: Fault on serial TX pin, expected High-Z");
            return false;
        }
        if (!testHighZ(fcRXPin)) {
            target.log(LOG_ERROR, "ETEST: Fault on serial RX pin, expected High-Z");
            return false;
        }
    
        // Drive serial TX, check for results and make sure there's no short to RX
        target.pinMode(target.PTB17, OUTPUT);
        for (unsigned i = 0; i < 10; i++) {
            target.digitalWrite(target.PTB17, i&1);
            if (digitalRead(fcTXPin) != (i&1)) {
                target.log(LOG_ERROR, "ETEST: Bad connection on serial TX pin");
                return false;
            }
        }
        if (!testHighZ(fcRXPin)) {
            target.log(LOG_ERROR, "ETEST: Short between serial TX and RX");
            return false;
        }
    
        // Drive RX, and test that
        pinMode(fcRXPin, OUTPUT);
        target.pinMode(target.PTB16, INPUT);
        for (unsigned i = 0; i < 10; i++) {
            digitalWrite(fcRXPin, i&1);
            if (target.digitalRead(target.PTB16) != (i&1)) {
                target.log(LOG_ERROR, "ETEST: Bad connection on serial RX pin");
                return false;
            }
        }
    
        return true;
    }
    
    
    bool ElectricalTest::runAll()
    {
        target.log(logLevel, "ETEST: Beginning electrical test");
    
        if (!initTarget())
            return false;
    
    
        // USB tests
        if (!testUSBConnections())
            return false;
    
    
        // Output patterns
        if (!testAllOutputPatterns())
            return false;
    
    
        // Test serial connections, and the adjacent DMA loopback
        if (!testSerialConnections())
            return false;
    
    
        // Now try dialing down the power supply voltage, and make sure it still works
        if (!testBoostConverter())
            return false;
    
    
        target.log(logLevel, "ETEST: Successfully completed electrical test");
        return true;
    }