From 02a715d54d29cbc2d35a62221d37e53d722869f1 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Wed, 15 Sep 2021 00:23:24 +0100 Subject: [PATCH] New DFPlayer MP3 device, and tidy comments in other drivers. --- IO_AnalogueInputs.h | 8 +- IO_DFPlayer.h | 219 ++++++++++++++++++++++++++++++++++++++++++++ IO_HCSR04.h | 68 ++++++++------ IO_VL53L0X.h | 8 +- 4 files changed, 273 insertions(+), 30 deletions(-) create mode 100644 IO_DFPlayer.h 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 {