diff --git a/EXRAIL2MacroReset.h b/EXRAIL2MacroReset.h
index fb0ea23..0211e22 100644
--- a/EXRAIL2MacroReset.h
+++ b/EXRAIL2MacroReset.h
@@ -78,6 +78,7 @@
#undef LATCH
#undef LCD
#undef LCN
+#undef MOVETT
#undef ONACTIVATE
#undef ONACTIVATEL
#undef ONAMBER
@@ -187,6 +188,7 @@
#define LATCH(sensor_id)
#define LCD(row,msg)
#define LCN(msg)
+#define MOVETT(id,steps,activity)
#define ONACTIVATE(addr,subaddr)
#define ONACTIVATEL(linear)
#define ONAMBER(signal_id)
diff --git a/EXRAILMacros.h b/EXRAILMacros.h
index f2b5bca..d5424c6 100644
--- a/EXRAILMacros.h
+++ b/EXRAILMacros.h
@@ -267,6 +267,7 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = {
#define LATCH(sensor_id) OPCODE_LATCH,V(sensor_id),
#define LCD(id,msg) PRINT(msg)
#define LCN(msg) PRINT(msg)
+#define MOVETT(id,steps,activity) OPCODE_SERVO,V(id),OPCODE_PAD,V(steps),OPCODE_PAD,V(EXTurntable::activity),OPCODE_PAD,V(0),
#define ONACTIVATE(addr,subaddr) OPCODE_ONACTIVATE,V(addr<<2|subaddr),
#define ONACTIVATEL(linear) OPCODE_ONACTIVATE,V(linear+3),
#define ONAMBER(signal_id) OPCODE_ONAMBER,V(signal_id),
diff --git a/IODevice.h b/IODevice.h
index 8b786c4..2a3437c 100644
--- a/IODevice.h
+++ b/IODevice.h
@@ -364,6 +364,39 @@ private:
uint8_t *_pinInUse;
};
+/////////////////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * IODevice subclass for EX-Turntable.
+ */
+
+class EXTurntable : public IODevice {
+public:
+ static void create(VPIN firstVpin, int nPins, uint8_t I2CAddress);
+ // Constructor
+ EXTurntable(VPIN firstVpin, int nPins, uint8_t I2CAddress);
+ enum ActivityNumber : uint8_t {
+ 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
+ };
+
+private:
+ // Device-specific write function.
+ void _begin() override;
+ void _loop(unsigned long currentMicros) override;
+ int _read(VPIN vpin) override;
+ void _writeAnalogue(VPIN vpin, int value, uint8_t activity, uint16_t duration) override;
+ void _display() override;
+ uint8_t _stepperStatus;
+};
+
/////////////////////////////////////////////////////////////////////////////////////////////////////
#include "IO_MCP23008.h"
diff --git a/IO_EXTurntable.h b/IO_EXTurntable.h
new file mode 100644
index 0000000..ea3dcb0
--- /dev/null
+++ b/IO_EXTurntable.h
@@ -0,0 +1,121 @@
+/*
+ * © 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/Turntable-EX) and contains the stepper motor logic.
+*
+* This device driver sends a step position to Turntable-EX 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.
+*/
+
+#ifndef IO_EXTurntable_h
+#define IO_EXTurntable_h
+
+#include "IODevice.h"
+#include "I2CManager.h"
+#include "DIAG.h"
+
+void EXTurntable::create(VPIN firstVpin, int nPins, uint8_t I2CAddress) {
+ new EXTurntable(firstVpin, nPins, I2CAddress);
+}
+
+// Constructor
+EXTurntable::EXTurntable(VPIN firstVpin, int nPins, uint8_t I2CAddress) {
+ _firstVpin = firstVpin;
+ _nPins = nPins;
+ _I2CAddress = I2CAddress;
+ addDevice(this);
+}
+
+// Initialisation of TurntableEX
+void EXTurntable::_begin() {
+ I2CManager.begin();
+ I2CManager.setClock(1000000);
+ if (I2CManager.exists(_I2CAddress)) {
+#ifdef DIAG_IO
+ _display();
+#endif
+ } else {
+ _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];
+ // DIAG(F("Turntable-EX returned status: %d"), _stepperStatus);
+ delayUntil(currentMicros + 500000); // Wait 500ms before checking again, turntables turn slowly
+}
+
+// Read returns status as obtained in our loop.
+// Return false if our status value is invalid.
+int EXTurntable::_read(VPIN vpin) {
+ if (_deviceState == DEVSTATE_FAILED) return 0;
+ // DIAG(F("_read status: %d"), _stepperStatus);
+ if (_stepperStatus > 1) {
+ return false;
+ } else {
+ return _stepperStatus;
+ }
+}
+
+// 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;
+ uint8_t stepsMSB = value >> 8;
+ uint8_t stepsLSB = value & 0xFF;
+#ifdef DIAG_IO
+ DIAG(F("TurntableEX WriteAnalogue Vpin:%d Value:%d Activity:%d Duration:%d"),
+ vpin, value, activity, duration);
+ DIAG(F("I2CManager write I2C Address:%d stepsMSB:%d stepsLSB:%d activity:%d"),
+ _I2CAddress, stepsMSB, stepsLSB, activity);
+#endif
+ _stepperStatus = 1; // Tell the device driver Turntable-EX is busy
+ I2CManager.write(_I2CAddress, 3, stepsMSB, stepsLSB, activity);
+}
+
+// Display Turnetable-EX device driver info.
+void EXTurntable::_display() {
+ DIAG(F("TurntableEX I2C:x%x Configured on Vpins:%d-%d %S"), _I2CAddress, (int)_firstVpin,
+ (int)_firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
+}
+
+#endif