/* * 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; return true; } 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. */ delay(150); } 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); return true; } 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; }