diff --git a/IO_AnalogueInputs.h b/IO_AnalogueInputs.h
index 4c73329..0628bc9 100644
--- a/IO_AnalogueInputs.h
+++ b/IO_AnalogueInputs.h
@@ -42,12 +42,18 @@
*
* The ADS111x is set up as follows:
* Single-shot scan
- * Data rate 128 samples/sec (7.8ms/sample)
+ * Data rate 128 samples/sec (7.8ms/sample, but scanned every 10ms)
* Comparator off
* Gain FSR=6.144V
* The gain means that the maximum input voltage of 5V (when Vss=5V) gives a reading
* of 32767*(5.0/6.144) = 26666.
*
+ * A device is configured by the following:
+ * ADS111x::create(firstVpin, nPins, i2cAddress);
+ * for example
+ * ADS111x::create(300, 1, 0x48); // single-input ADS1113
+ * ADS111x::create(300, 4, 0x48); // four-input ADS1115
+ *
* Note: The device is simple and does not need initial configuration, so it should recover from
* temporary loss of communications or power.
**********************************************************************************************/
diff --git a/IO_DFPlayer.h b/IO_DFPlayer.h
new file mode 100644
index 0000000..bb0e5c7
--- /dev/null
+++ b/IO_DFPlayer.h
@@ -0,0 +1,219 @@
+/*
+ * © 2021, Neil McKechnie. All rights reserved.
+ *
+ * This file is part of DCC++EX API
+ *
+ * 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 .
+ */
+
+/*
+ * DFPlayer is an MP3 player module with an SD card holder. It also has an integrated
+ * amplifier, so it only needs a power supply and a speaker.
+ *
+ * This driver allows the device to be controlled through IODevice::write() and
+ * IODevice::writeAnalogue() calls.
+ *
+ * The driver is configured as follows:
+ *
+ * DFPlayer::create(firstVpin, nPins, Serialn);
+ * Where firstVpin is the first vpin reserved for reading the device,
+ * nPins is the number of pins to be allocated (max 5)
+ * and Serialn is the name of the Serial port connected to the DFPlayer (e.g. Serial2).
+ *
+ *
+ * Example:
+ * In mySetup function within mySetup.cpp:
+ * DFPlayer::create(3500, 5, Serial2);
+ * Writing a value 0-2999 to the first pin will select a numbered file from the SD card;
+ * Writing a value 0-30 to the second pin will set the volume of the output;
+ * Writing a digital value to the first pin will play or stop the file;
+ *
+ * From EX-RAIL, the following commands may be used:
+ * SET(3500) -- starts playing the first file on the SD card
+ * SET(3501) -- starts playing the second file on the SD card
+ * etc.
+ * RESET(3500) -- stops all playing on the player
+ * WAITFOR(3500) -- wait for the file currently being played by the player to complete
+ *
+ * NB The DFPlayer's serial lines are not 5V safe, so connecting the Arduino TX directly
+ * to the DFPlayer's RX terminal will cause lots of noise over the speaker, or worse.
+ * A 1k resistor in series with the module's RX terminal will alleviate this.
+ */
+
+#ifndef IO_DFPlayer_h
+#define IO_DFPlayer_h
+
+#include "IODevice.h"
+
+class DFPlayer : public IODevice {
+private:
+ HardwareSerial *_serial;
+ bool _playing = false;
+ uint8_t _inputIndex = 0;
+
+public:
+ DFPlayer(VPIN firstVpin, int nPins, HardwareSerial &serial) {
+ _firstVpin = firstVpin;
+ _nPins = min(nPins, 3);
+ _serial = &serial;
+ addDevice(this);
+ }
+ static void create(VPIN firstVpin, int nPins, HardwareSerial &serial) {
+ new DFPlayer(firstVpin, nPins, serial);
+ }
+
+protected:
+ void _begin() override {
+ _serial->begin(9600);
+ _display();
+ }
+
+ void _loop(unsigned long) override {
+ // Check for incoming data on _serial, and update busy flag accordingly.
+ // Expected message is in the form "7F FF 06 3D xx xx xx xx xx EF"
+ while (_serial->available()) {
+ int c = _serial->read();
+// DIAG(F("Received: %x"), c);
+ if (c == 0x7E)
+ _inputIndex = 1;
+ else if ((c==0xFF && _inputIndex==1) || (c==0x06 && _inputIndex==2)
+ || (c==0x3D && _inputIndex==3) || (_inputIndex >=4 && _inputIndex <= 8))
+ _inputIndex++;
+ else if (c==0xEF && _inputIndex==9) {
+ // End of play
+ #ifdef DIAG_IO
+ DIAG(F("DFPlayer: Finished"));
+ #endif
+ _playing = false;
+ _inputIndex = 0;
+ }
+ }
+ }
+
+ // Write with value 1 starts playing a song. The relative pin number is the file number.
+ // Write with value 0 stops playing.
+ void _write(VPIN vpin, int value) override {
+ int pin = vpin - _firstVpin;
+ if (value) {
+ // Value 1, start playing
+ #ifdef DIAG_IO
+ DIAG(F("DFPlayer: Play %d"), pin+1);
+ #endif
+ sendPacket(0x03, pin+1);
+ _playing = true;
+ } else {
+ // Value 0, stop playing
+ #ifdef DIAG_IO
+ DIAG(F("DFPlayer: Stop"));
+ #endif
+ sendPacket(0x16);
+ _playing = false;
+ }
+ }
+
+ // WriteAnalogue on first pin uses the nominated value as a file number to start playing, if file number > 0.
+ // If value is zero, it stops playing.
+ // WriteAnalogue on second pin sets the output volume.
+ void _writeAnalogue(VPIN vpin, int value, uint8_t, uint16_t) override {
+ uint8_t pin = _firstVpin - vpin;
+ switch (pin) {
+ case 0:
+ if (value > 0) {
+ // Play global track
+ if (value > 2999) value = 2999;
+ #ifdef DIAG_IO
+ DIAG(F("DFPlayer: Play %d"), value);
+ #endif
+ sendPacket(0x03, value);
+ _playing = true;
+ } else {
+ #ifdef DIAG_IO
+ DIAG(F("DFPlayer: Stop"));
+ #endif
+ sendPacket(0x16);
+ _playing = false;
+ }
+ break;
+ case 1:
+ // Set volume (0-30)
+ if (value > 30) value = 30;
+ else if (value < 0) value = 0;
+ #ifdef DIAG_IO
+ DIAG(F("DFPlayer: Volume %d"), value);
+ #endif
+ sendPacket(0x06, value);
+ break;
+ default:
+ break;
+ }
+ }
+
+ bool _isBusy(VPIN vpin) override {
+ (void)vpin; // avoid compiler warning.
+ return _playing;
+ }
+
+ void _display() override {
+ DIAG(F("DFPlayer Configured on Vpins:%d-%d"));
+ }
+
+private:
+ // 7E FF 06 0F 00 01 01 xx xx EF
+ // 0 -> 7E is start code
+ // 1 -> FF is version
+ // 2 -> 06 is length
+ // 3 -> 0F is command
+ // 4 -> 00 is no receive
+ // 5~6 -> 01 01 is argument
+ // 7~8 -> checksum = 0 - ( FF+06+0F+00+01+01 )
+ // 9 -> EF is end code
+
+ void sendPacket(uint8_t command, uint16_t arg = 0)
+ {
+ uint8_t out[] = { 0x7E,
+ 0xFF,
+ 06,
+ command,
+ 00,
+ static_cast(arg >> 8),
+ static_cast(arg & 0x00ff),
+ 00,
+ 00,
+ 0xEF };
+
+ setChecksum(out);
+
+ _serial->write(out, sizeof(out));
+ }
+
+ uint16_t calcChecksum(uint8_t* packet)
+ {
+ uint16_t sum = 0;
+ for (int i = 1; i < 7; i++)
+ {
+ sum += packet[i];
+ }
+ return -sum;
+ }
+
+ void setChecksum(uint8_t* out)
+ {
+ uint16_t sum = calcChecksum(out);
+
+ out[7] = (sum >> 8);
+ out[8] = (sum & 0xff);
+ }
+};
+
+#endif // IO_DFPlayer_h
diff --git a/IO_HCSR04.h b/IO_HCSR04.h
index 98340ff..76b8493 100644
--- a/IO_HCSR04.h
+++ b/IO_HCSR04.h
@@ -17,31 +17,37 @@
* along with CommandStation. If not, see .
*/
-/*
+/*
* The HC-SR04 module has an ultrasonic transmitter (40kHz) and a receiver.
- * It is operated through two signal pins. When the transmit pin is set to 1 for
- * 10us, on the falling edge the transmitter sends a short transmission of
+ * It is operated through two signal pins. When the transmit pin is set to 1
+ * for 10us, on the falling edge the transmitter sends a short transmission of
* 8 pulses (like a sonar 'ping'). This is reflected off objects and received
* by the receiver. A pulse is sent on the receive pin whose length is equal
* to the delay between the transmission of the pulse and the detection of
* its echo. The distance of the reflecting object is calculated by halving
* the time (to allow for the out and back distance), then multiplying by the
* speed of sound (assumed to be constant).
- *
+ *
* This driver polls the HC-SR04 by sending the trigger pulse and then measuring
- * the length of the received pulse. If the calculated distance is less than the
- * threshold, the output changes to 1. If it is greater than the threshold plus
- * a hysteresis margin, the output changes to 0.
- *
- * The measurement would be more reliable if interrupts were disabled while the
- * pulse is being timed. However, this would affect other functions in the CS
- * so the measurement is being performed with interrupts enabled. Also, we could
- * use an interrupt pin in the Arduino for the timing, but the same consideration
- * applies.
- *
- * Note: The timing accuracy required by this means that the pins have to be
- * direct Arduino pins; GPIO pins on an IO Extender cannot provide the required
- * accuracy.
+ * the length of the received pulse. If the calculated distance is less than
+ * the threshold, the output state returned by a read() call changes to 1. If
+ * the distance is greater than the threshold plus a hysteresis margin, the
+ * output changes to 0. The device also supports readAnalogue(), which returns
+ * the measured distance in cm, or 32767 if the distance exceeds the
+ * offThreshold.
+ *
+ * It might be thought that the measurement would be more reliable if interrupts
+ * were disabled while the pulse is being timed. However, this would affect
+ * other functions in the CS so the measurement is being performed with
+ * interrupts enabled. Also, we could use an interrupt pin in the Arduino for
+ * the timing, but the same consideration applies. In any case, the DCC
+ * interrupt occurs once every 58us, so any IRC code is much faster than that.
+ * And 58us corresponds to 1cm in the calculation, so the effect of
+ * interrupts is negligible.
+ *
+ * Note: The timing accuracy required for measuring the pulse length means that
+ * the pins have to be direct Arduino pins; GPIO pins on an IO Extender cannot
+ * provide the required accuracy.
*/
#ifndef IO_HCSR04_H
@@ -58,6 +64,8 @@ private:
// Thresholds for setting active state in cm.
uint8_t _onThreshold; // cm
uint8_t _offThreshold; // cm
+ // Last measured distance in cm.
+ uint16_t _distance;
// Active=1/inactive=0 state
uint8_t _value = 0;
// Time of last loop execution
@@ -101,12 +109,17 @@ protected:
return _value;
}
+ int _readAnalogue(VPIN vpin) override {
+ (void)vpin; // avoid compiler warning
+ return _distance;
+ }
+
// _loop function - read HC-SR04 once every 50 milliseconds.
void _loop(unsigned long currentMicros) override {
if (currentMicros - _lastExecutionTime > 50000UL) {
_lastExecutionTime = currentMicros;
- _value = read_HCSR04device();
+ read_HCSR04device();
}
}
@@ -127,12 +140,13 @@ private:
// measured distance is less than the onThreshold, and is set to 0 if the measured distance is
// greater than the offThreshold.
//
- uint8_t read_HCSR04device() {
+ void read_HCSR04device() {
// uint16 enough to time up to 65ms
uint16_t startTime, waitTime, currentTime, maxTime;
// If receive pin is still set on from previous call, abort the read.
- if (ArduinoPins::fastReadDigital(_receivePin)) return _value;
+ if (ArduinoPins::fastReadDigital(_receivePin))
+ return;
// Send 10us pulse to trigger transmitter
ArduinoPins::fastWriteDigital(_transmitPin, 1);
@@ -148,7 +162,7 @@ private:
waitTime = currentTime - startTime;
if (waitTime > maxTime) {
// Timeout waiting for pulse start, abort the read
- return _value;
+ return;
}
}
@@ -162,16 +176,16 @@ private:
// and finish without waiting for end of pulse.
if (waitTime > maxTime) {
// Pulse length longer than maxTime, reset value.
- return 0;
+ _value = 0;
+ _distance = 32767;
+ return;
}
}
// Check if pulse length is below threshold, if so set value.
//DIAG(F("HCSR04: Pulse Len=%l Distance=%d"), waitTime, distance);
- uint16_t distance = waitTime / factor; // in centimetres
- if (distance < _onThreshold)
- return 1;
-
- return _value;
+ _distance = waitTime / factor; // in centimetres
+ if (_distance < _onThreshold)
+ _value = 1;
}
};
diff --git a/IO_VL53L0X.h b/IO_VL53L0X.h
index a0ac902..08de1aa 100644
--- a/IO_VL53L0X.h
+++ b/IO_VL53L0X.h
@@ -94,7 +94,7 @@ private:
uint16_t _signal;
uint16_t _onThreshold;
uint16_t _offThreshold;
- uint8_t _xshutPin;
+ VPIN _xshutPin;
bool _value;
bool _initialising = true;
uint8_t _entryCount = 0;
@@ -105,6 +105,7 @@ private:
VL53L0X_REG_SYSRANGE_START=0x00,
VL53L0X_REG_RESULT_INTERRUPT_STATUS=0x13,
VL53L0X_REG_RESULT_RANGE_STATUS=0x14,
+ VL53L0X_CONFIG_PAD_SCL_SDA__EXTSUP_HV=0x89,
VL53L0X_REG_I2C_SLAVE_DEVICE_ADDRESS=0x8A,
};
const uint8_t VL53L0X_I2C_DEFAULT_ADDRESS=0x29;
@@ -157,6 +158,9 @@ protected:
case 3:
if (I2CManager.exists(_i2cAddress)) {
_display();
+ // Set 2.8V mode
+ write_reg(VL53L0X_CONFIG_PAD_SCL_SDA__EXTSUP_HV,
+ read_reg(VL53L0X_CONFIG_PAD_SCL_SDA__EXTSUP_HV) | 0x01);
}
_initialising = false;
_entryCount = 0;
@@ -211,7 +215,7 @@ protected:
}
}
// For digital read, return the same value for all pins.
- int _read(VPIN vpin) override {
+ int _read(VPIN) override {
return _value;
}
void _display() override {