/* * © 2021, Peter Cole. All rights reserved. * * This file is part of CommandStation-EX * * This is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * It is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with CommandStation. If not, see . */ /* * The IO_EXTurntable device driver is used to control a turntable via an Arduino with a stepper motor over I2C. * * The EX-Turntable code lives in a separate repo (https://github.com/DCC-EX/EX-Turntable) and contains the stepper motor logic. * * This device driver sends a step position to EX-Turntable to indicate the step position to move to using either of these commands: * in the serial console * MOVETT(vpin, steps, activity) in EX-RAIL * Refer to the documentation for further information including the valid activities. */ #include "IODevice.h" #include "I2CManager.h" #include "DIAG.h" #include "Turntables.h" #include "CommandDistributor.h" #ifndef IO_NO_HAL void EXTurntable::create(VPIN firstVpin, int nPins, I2CAddress I2CAddress) { new EXTurntable(firstVpin, nPins, I2CAddress); } // Constructor EXTurntable::EXTurntable(VPIN firstVpin, int nPins, I2CAddress I2CAddress) { _firstVpin = firstVpin; _nPins = nPins; _I2CAddress = I2CAddress; _stepperStatus = 0; _previousStatus = 0; addDevice(this); } // Initialisation of EXTurntable void EXTurntable::_begin() { I2CManager.begin(); if (I2CManager.exists(_I2CAddress)) { DIAG(F("EX-Turntable device found, I2C:%s"), _I2CAddress.toString()); #ifdef DIAG_IO _display(); #endif } else { DIAG(F("EX-Turntable I2C:%s device not found"), _I2CAddress.toString()); _deviceState = DEVSTATE_FAILED; } } // Processing loop to obtain status of stepper // 0 = finished moving and in correct position // 1 = still moving void EXTurntable::_loop(unsigned long currentMicros) { uint8_t readBuffer[1]; I2CManager.read(_I2CAddress, readBuffer, 1); _stepperStatus = readBuffer[0]; if (_stepperStatus != _previousStatus && _stepperStatus == 0) { // Broadcast when a rotation finishes if ( _currentActivity < 4) { _broadcastStatus(_firstVpin, _stepperStatus, _currentActivity); } _previousStatus = _stepperStatus; } delayUntil(currentMicros + 100000); // Wait 100ms before checking again } // Read returns status as obtained in our loop. // Return false if our status value is invalid. int EXTurntable::_read(VPIN vpin) { (void)vpin; // surpress warning if (_deviceState == DEVSTATE_FAILED) return 0; if (_stepperStatus > 1) { return false; } else { return _stepperStatus; } } // If a status change has occurred for a turntable object, broadcast it void EXTurntable::_broadcastStatus (VPIN vpin, uint8_t status, uint8_t activity) { Turntable *tto = Turntable::getByVpin(vpin); if (tto) { if (activity < 4) { tto->setMoving(status); CommandDistributor::broadcastTurntable(tto->getId(), tto->getPosition(), status); } } } // writeAnalogue to send the steps and activity to Turntable-EX. // Sends 3 bytes containing the MSB and LSB of the step count, and activity. // value contains the steps, bit shifted to MSB + LSB. // activity contains the activity flag as per this list: // // Turn = 0, // Rotate turntable, maintain phase // Turn_PInvert = 1, // Rotate turntable, invert phase // Home = 2, // Initiate homing // Calibrate = 3, // Initiate calibration sequence // LED_On = 4, // Turn LED on // LED_Slow = 5, // Set LED to a slow blink // LED_Fast = 6, // Set LED to a fast blink // LED_Off = 7, // Turn LED off // Acc_On = 8, // Turn accessory pin on // Acc_Off = 9 // Turn accessory pin off void EXTurntable::_writeAnalogue(VPIN vpin, int value, uint8_t activity, uint16_t duration) { if (_deviceState == DEVSTATE_FAILED) return; if (value < 0) return; uint8_t stepsMSB = value >> 8; uint8_t stepsLSB = value & 0xFF; #ifdef DIAG_IO DIAG(F("EX-Turntable WriteAnalogue VPIN:%u Value:%d Activity:%d Duration:%d"), vpin, value, activity, duration); DIAG(F("I2CManager write I2C Address:%d stepsMSB:%d stepsLSB:%d activity:%d"), _I2CAddress.toString(), stepsMSB, stepsLSB, activity); #else (void)duration; #endif if (activity < 4) _stepperStatus = 1; // Tell the device driver Turntable-EX is busy _previousStatus = _stepperStatus; _currentActivity = activity; _broadcastStatus(vpin, _stepperStatus, activity); // Broadcast when the rotation starts I2CManager.write(_I2CAddress, 3, stepsMSB, stepsLSB, activity); } // Display Turnetable-EX device driver info. void EXTurntable::_display() { DIAG(F("EX-Turntable I2C:%s Configured on Vpins:%u-%u %S"), _I2CAddress.toString(), (int)_firstVpin, (int)_firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); } #endif