Browse Source

New: Battery-monitoring and dsleep after bootfail

master
Torsten Stauder 5 years ago
parent
commit
b5a29a5a39
  1. 6
      PCBs/Headphone with PCM5102a and TMD1308/README.md
  2. 22
      README.md
  3. 3
      src/logmessages.h
  4. 3
      src/logmessages_EN.h
  5. 131
      src/main.cpp

6
PCBs/Headphone with PCM5102a and TMD1308/README.md

@ -0,0 +1,6 @@
# Headphone-PCB
This is a pcb, that was kindly provided by a user of my Tonuino-fork. It makes use of a DAC named 'PCM5102A' with a TDA1308 as amp. PCM5102A supports I2S, so it can connected in parallel to MAX98357a to the I2S-pins BCLK, LRC and DIN. Of course, it needs 3.3V and GND, too. Please note that SMD-technique is used. <br />
The 6th pin of connector J1 is used to indicate whether there's a plug or not. That means if there is a plug, this pin is pulled to GND and therefore can be used to connect to MAX98357.SD. Doing so will mute MAX's amp and vice versa. Off course you need a special [headphone jack](https://www.conrad.de/de/p/cliff-fcr1295-klinken-steckverbinder-3-5-mm-buchse-einbau-horizontal-polzahl-3-stereo-schwarz-1-st-705830.html) to use this feature. Additionaly this 6th pin can be connected to the ESP32 in order to make use of the feature `HEADPHONE_ADJUST_ENABLE`. Doing so can optionally limit the headphone's maximum volume (configureable via GUI). <br />
## Disclaimer
PCB-circuit is provided 'as is' without warranty. As previously stated it was kindly provided by a user and I only can give limited support to it. However: it runs fine without any problems in my Tonuinos.

22
README.md

@ -17,7 +17,7 @@ The basic idea of Tonuino (and my fork, respectively) is to provide a way, to us
## Hardware-setup ## Hardware-setup
The heart of my project is an ESP32 on a [Wemos Lolin32 development-board](https://www.ebay.de/itm/4MB-Flash-WEMOS-Lolin32-V1-0-0-WIFI-Bluetooth-Card-Based-ESP-32-ESP-WROOM-32/162716855489). If ordered in China (Aliexpress, eBay e.g.) it's pretty cheap (around 4€) but even in Europe it's only around 8€. Make sure to install the drivers for the USB/Serial-chip (CP2102 e.g.). If unsure have a look at eBay or Aliexpress for "Lolin 32". The heart of my project is an ESP32 on a [Wemos Lolin32 development-board](https://www.ebay.de/itm/4MB-Flash-WEMOS-Lolin32-V1-0-0-WIFI-Bluetooth-Card-Based-ESP-32-ESP-WROOM-32/162716855489). If ordered in China (Aliexpress, eBay e.g.) it's pretty cheap (around 4€) but even in Europe it's only around 8€. Make sure to install the drivers for the USB/Serial-chip (CP2102 e.g.). If unsure have a look at eBay or Aliexpress for "Lolin 32".
* [Adafruit's MAX98357A](https://www.ebay.de/itm/MAX98357-Amplifier-Breakout-Interface-I2S-Class-D-Module-For-ESP32-Raspberry-Pi/174319322988)
* [MAX98357A (like Adafruit's)](https://www.ebay.de/itm/MAX98357-Amplifier-Breakout-Interface-I2S-Class-D-Module-For-ESP32-Raspberry-Pi/174319322988)
* [uSD-card-reader 3.3V + 5V](https://www.amazon.de/AZDelivery-Reader-Speicher-Memory-Arduino/dp/B077MB17JB) * [uSD-card-reader 3.3V + 5V](https://www.amazon.de/AZDelivery-Reader-Speicher-Memory-Arduino/dp/B077MB17JB)
* [uSD-card-reader 3.3V only](https://www.ebay.de/itm/Micro-SD-Karten-SD-Card-Modul-SPI-fur-Breadboard-Arduino-Raspberry-Pi/183106778276) * [uSD-card-reader 3.3V only](https://www.ebay.de/itm/Micro-SD-Karten-SD-Card-Modul-SPI-fur-Breadboard-Arduino-Raspberry-Pi/183106778276)
* [RFID-reader](https://www.amazon.de/AZDelivery-Reader-Arduino-Raspberry-gratis/dp/B074S8MRQ7) * [RFID-reader](https://www.amazon.de/AZDelivery-Reader-Arduino-Raspberry-gratis/dp/B074S8MRQ7)
@ -36,7 +36,7 @@ In the upper section of main.cpp you can specify the modules that should be comp
Please note: if MQTT is enabled it's still possible to deactivate it via webgui. Please note: if MQTT is enabled it's still possible to deactivate it via webgui.
## Wiring (2 SPI-instances) ## Wiring (2 SPI-instances)
A lot of wiring is necessary to get ESP32-Tonuino working. After my first experiments I soldered the stuff on a board in order to avoid wild-west-cabling. Especially for the interconnect between uC and uSD-card-reader make sure to use short wires (like 10cm or so)! Important: you can easily connect another I2S-DACs but just connecting them in parallel to the I2S-pins (DIN, BCLK, LRC). This is true for example if you plan to integrate a [line/headphone-pcb](https://www.adafruit.com/product/3678). In general, this runs fine. But unfortunately this board lacks of a headphone jack, that takes note if a plug is inserted or not. Best way is to use a [headphone jack](https://www.conrad.de/de/p/cliff-fcr1295-klinken-steckverbinder-3-5-mm-buchse-einbau-horizontal-polzahl-3-stereo-schwarz-1-st-705830.html) that has a pin that is set to GND, if there's no plug and vice versa. Using for example a MOSFET-circuit, this signal can be inverted in a way, that MAX98357.SD is pulled down to GND if there's a plug. Doing that will turn off the speaker immediately if there's a plug and vice versa.
A lot of wiring is necessary to get ESP32-Tonuino working. After my first experiments I soldered the stuff on a board in order to avoid wild-west-cabling. Especially for the interconnect between uC and uSD-card-reader make sure to use short wires (like 10cm or so)! Important: you can easily connect another I2S-DACs but just connecting them in parallel to the I2S-pins (DIN, BCLK, LRC). This is true for example if you plan to integrate a [line/headphone-pcb](https://www.adafruit.com/product/3678). In general, this runs fine. But unfortunately this board lacks of a headphone jack, that takes note if a plug is inserted or not. Best way is to use a [headphone jack](https://www.conrad.de/de/p/cliff-fcr1295-klinken-steckverbinder-3-5-mm-buchse-einbau-horizontal-polzahl-3-stereo-schwarz-1-st-705830.html) that has a pin that is set to GND, if there's no plug and vice versa. Using for example a MOSFET-circuit, this signal can be inverted in a way, that MAX98357.SD is pulled down to GND if there's a plug. Doing that will turn off the speaker immediately if there's a plug and vice versa. Have a look at the PCB-folder in order to view the detailed solution.
| ESP32 (GPIO) | Hardware | Pin | Comment | | ESP32 (GPIO) | Hardware | Pin | Comment |
| ------------- | --------------------- | ------ | ------------------------------------------------------------ | | ------------- | --------------------- | ------ | ------------------------------------------------------------ |
@ -57,7 +57,7 @@ A lot of wiring is necessary to get ESP32-Tonuino working. After my first experi
| 25 | MAX98357 | DIN | | | 25 | MAX98357 | DIN | |
| 27 | MAX98357 | BCLK | | | 27 | MAX98357 | BCLK | |
| 26 | MAX98357 | LRC | | | 26 | MAX98357 | LRC | |
| --- | MAX98357 | SD | If pulled down to GND amp will turn off |
| --- | MAX98357 | SD | Info: if pulled down to GND amp will turn off |
| 34 | Rotary encoder | CLR | Invert CLR with DT if you want to change the direction of RE | | 34 | Rotary encoder | CLR | Invert CLR with DT if you want to change the direction of RE |
| 35 | Rotary encoder | DT | Invert CLR with DT if you want to change the direction of RE | | 35 | Rotary encoder | DT | Invert CLR with DT if you want to change the direction of RE |
| 32 | Rotary encoder | BUTTON | | | 32 | Rotary encoder | BUTTON | |
@ -65,7 +65,7 @@ A lot of wiring is necessary to get ESP32-Tonuino working. After my first experi
| GND | Rotary encoder | GND | | | GND | Rotary encoder | GND | |
| 4 | Button (next) | | | | 4 | Button (next) | | |
| GND | Button (next) | | | | GND | Button (next) | | |
| 33 | Button (previous) | | |
| 2 | Button (previous) | | |
| GND | Button (previous) | | | | GND | Button (previous) | | |
| 5 | Button (pause/play) | | | | 5 | Button (pause/play) | | |
| GND | Button (pause/play) | | | | GND | Button (pause/play) | | |
@ -73,12 +73,15 @@ A lot of wiring is necessary to get ESP32-Tonuino working. After my first experi
| GND | Neopixel | GND | | | GND | Neopixel | GND | |
| 12 | Neopixel | DI | Might be necessary to use a logic-converter 3.3 => 5V | | 12 | Neopixel | DI | Might be necessary to use a logic-converter 3.3 => 5V |
| 17 | (e.g.) BC337 (via R5) | Base | Don't forget R5! | | 17 | (e.g.) BC337 (via R5) | Base | Don't forget R5! |
| 33 | Voltage-divider / BAT | | Optional: Voltage-divider to monitor battery-voltage |
Optionally, GPIO 17 can be used to drive a NPN-transistor (BC337-40) that pulls a p-channel MOSFET (IRF9520) to GND in order to switch on/off 5V-current. Transistor-circuit is described [here](https://dl6gl.de/schalten-mit-transistoren.html): Just have a look at Abb. 4. Values of the resistors I used: R1: 10k, R2: omitted(!), R4: 10k, R5: 4,7k. <br /> Optionally, GPIO 17 can be used to drive a NPN-transistor (BC337-40) that pulls a p-channel MOSFET (IRF9520) to GND in order to switch on/off 5V-current. Transistor-circuit is described [here](https://dl6gl.de/schalten-mit-transistoren.html): Just have a look at Abb. 4. Values of the resistors I used: R1: 10k, R2: omitted(!), R4: 10k, R5: 4,7k. <br />
Also tested this successfully for a 3.3V-setup with IRF530NPBF (N-channel MOSFET) and NDP6020P (P-channel MOSFET). Resistor-values: R1: 100k, R2: omitted(!), R4: 100k, R5: 4,7k. A 3.3V-setup is helpful if you want to battery-power your Tonuino and 5V is not available in battery-mode. For example this is the case when using Wemos Lolin32 with only having LiPo connected. <br /> Also tested this successfully for a 3.3V-setup with IRF530NPBF (N-channel MOSFET) and NDP6020P (P-channel MOSFET). Resistor-values: R1: 100k, R2: omitted(!), R4: 100k, R5: 4,7k. A 3.3V-setup is helpful if you want to battery-power your Tonuino and 5V is not available in battery-mode. For example this is the case when using Wemos Lolin32 with only having LiPo connected. <br />
Advice: When powering a SD-card-reader solely with 3.3V, make sure to use one WITHOUT a voltage regulator. Or at least one with a pin dedicated for 3.3V (bypassing voltage regulator). This is because if 3.3V go through the voltage regulator a small voltage-drop will be introduced, which may lead to SD-malfunction as the resulting voltage is a bit too low. Vice versa if you want to connect your reader solely to 5V, make sure to have one WITH a voltage regulator :-). Advice: When powering a SD-card-reader solely with 3.3V, make sure to use one WITHOUT a voltage regulator. Or at least one with a pin dedicated for 3.3V (bypassing voltage regulator). This is because if 3.3V go through the voltage regulator a small voltage-drop will be introduced, which may lead to SD-malfunction as the resulting voltage is a bit too low. Vice versa if you want to connect your reader solely to 5V, make sure to have one WITH a voltage regulator :-).
## Wiring (1 SPI-instance) [EXPERIMENTAL!] ## Wiring (1 SPI-instance) [EXPERIMENTAL!]
Basically the same as using 2 SPI-instances but...
In this case RFID-reader + SD-reader share SPI's SCK, MISO and MOSI. But make sure to use different CS-pins. In this case RFID-reader + SD-reader share SPI's SCK, MISO and MOSI. But make sure to use different CS-pins.
| ESP32 (GPIO) | Hardware | Pin | Comment | | ESP32 (GPIO) | Hardware | Pin | Comment |
@ -97,7 +100,7 @@ In this case RFID-reader + SD-reader share SPI's SCK, MISO and MOSI. But make su
| 25 | MAX98357 | DIN | | | 25 | MAX98357 | DIN | |
| 27 | MAX98357 | BCLK | | | 27 | MAX98357 | BCLK | |
| 26 | MAX98357 | LRC | | | 26 | MAX98357 | LRC | |
| --- | MAX98357 | SD | If pulled down to GND amp will turn off |
| --- | MAX98357 | SD | Info: if pulled down to GND amp will turn off |
| 34 | Rotary encoder | CLR | Invert CLR with DT if you want to change the direction of RE | | 34 | Rotary encoder | CLR | Invert CLR with DT if you want to change the direction of RE |
| 35 | Rotary encoder | DT | Invert CLR with DT if you want to change the direction of RE | | 35 | Rotary encoder | DT | Invert CLR with DT if you want to change the direction of RE |
| 32 | Rotary encoder | BUTTON | | | 32 | Rotary encoder | BUTTON | |
@ -105,7 +108,7 @@ In this case RFID-reader + SD-reader share SPI's SCK, MISO and MOSI. But make su
| GND | Rotary encoder | GND | | | GND | Rotary encoder | GND | |
| 4 | Button (next) | | | | 4 | Button (next) | | |
| GND | Button (next) | | | | GND | Button (next) | | |
| 33 | Button (previous) | | |
| 2 | Button (previous) | | |
| GND | Button (previous) | | | | GND | Button (previous) | | |
| 5 | Button (pause/play) | | | | 5 | Button (pause/play) | | |
| GND | Button (pause/play) | | | | GND | Button (pause/play) | | |
@ -113,6 +116,7 @@ In this case RFID-reader + SD-reader share SPI's SCK, MISO and MOSI. But make su
| GND | Neopixel | GND | | | GND | Neopixel | GND | |
| 12 | Neopixel | DI | Might be necessary to use a logic-converter 3.3 => 5V | | 12 | Neopixel | DI | Might be necessary to use a logic-converter 3.3 => 5V |
| 17 | (e.g.) BC337 (via R5) | Base | Don't forget R5! | | 17 | (e.g.) BC337 (via R5) | Base | Don't forget R5! |
| 33 | Voltage-divider / BAT | | Optional: Voltage-divider to monitor battery-voltage |
## Wiring (custom) / different pinout ## Wiring (custom) / different pinout
When using a develboard with for example SD-card-reader already integrated, the pinouts described above my not fit your needs; feel free to change them according your needs. Additionaly some boards may use one or some of the GPIOs I used for their internal purposes and that reason for are maybe not exposed via pin-headers. However, having them exposed doesn't mean they can be used without limits. This is because some GPIOs have to be logical LOW or HIGH at start for example and this is probably not the case when connecting stuff to it. Feel free to adjust the GPIOs proposed by me (but be adviced it could take a while to get it running). If you encounter problems please refer the board's manual first. <br /> When using a develboard with for example SD-card-reader already integrated, the pinouts described above my not fit your needs; feel free to change them according your needs. Additionaly some boards may use one or some of the GPIOs I used for their internal purposes and that reason for are maybe not exposed via pin-headers. However, having them exposed doesn't mean they can be used without limits. This is because some GPIOs have to be logical LOW or HIGH at start for example and this is probably not the case when connecting stuff to it. Feel free to adjust the GPIOs proposed by me (but be adviced it could take a while to get it running). If you encounter problems please refer the board's manual first. <br />
@ -128,10 +132,12 @@ Keep in mind the RFID-lib I used is intended for default-SPI-pins only (SCK, MIS
* please note: by using audiobook-mode any playlist-savings will be overwritten with every start unless the RFID-cards in setup() are commented out. Main way to link RFID to an action will be a webservice (still under development) * please note: by using audiobook-mode any playlist-savings will be overwritten with every start unless the RFID-cards in setup() are commented out. Main way to link RFID to an action will be a webservice (still under development)
* If you're using Arduino-IDE please make sure to change ESP32's partition-layout to `No OTA (2MB APP/2MB Spiffs)` as otherwise the sketch won't fit into the flash-memory. * If you're using Arduino-IDE please make sure to change ESP32's partition-layout to `No OTA (2MB APP/2MB Spiffs)` as otherwise the sketch won't fit into the flash-memory.
* compile and upload the sketch * compile and upload the sketch
* Please keep in mind that working SD is mandatory. Unless `SD_NOT_MANDATORY_ENABLE` is not set, Tonuino will never fully start up if SD is not working. Only use `SD_NOT_MANDATORY_ENABLE` for debugging as for normal operational mode, not having SD working doesn't make sense!
* Please keep in mind that working SD is mandatory. Unless `SD_NOT_MANDATORY_ENABLE` is not set, Tonuino will never fully start up if SD is not working. Only use `SD_NOT_MANDATORY_ENABLE` for debugging as for normal operational mode, not having SD working doesn't make sense.
* If you want to monitor the battery-voltage, make sure to enable `MEASURE_BATTERY_VOLTAGE`. Make sure to use a voltage-divider as voltage of a LiPo is way too high. For my tests I connected VBat with a serial connection of 130k + 390k resistors (VBat--130k--X--390k--GND). X is the measure-point where to connect the GPIO to.
* If you're using a headphone-pcb with a [headphone jack](https://www.conrad.de/de/p/cliff-fcr1295-klinken-steckverbinder-3-5-mm-buchse-einbau-horizontal-polzahl-3-stereo-schwarz-1-st-705830.html) that has a pin to indicate if there's a plug, you can use this signal along with the feature `HEADPHONE_ADJUST_ENABLE` to limit the maximum headphone-voltage automatically.
## Starting Tonuino-ESP32 first time ## Starting Tonuino-ESP32 first time
After plugging in it takes a few seconds until neopixel indicates that Tonuino is ready (by four (slow) rotating LEDs). If uC was not able to connect to WiFi, an access-point (named Tonuino) is opened and after connecting this WiFi, a [configuration-Interface](http://192.168.4.1) is available. Enter WiFI-credentials + the hostname (Tonuio's name) and save them and restart the uC. Then reconnect to your "regular" WiFi. Now you're ready to got: place your favourite RFID-tag next to the RFID-reader and the music should start to play. While the playlist is generated, fast-rotating LEDs are shown. The more tracks a playlist/directory contains the longer this step takes. <br >
After plugging in it takes a few seconds until neopixel indicates that Tonuino is ready (by four (slow) rotating LEDs). If uC was not able to connect to WiFi, an access-point (named Tonuino) is opened and after connecting this WiFi, a [configuration-Interface](http://192.168.4.1) is available. Enter WiFI-credentials + the hostname (Tonuio's name) and save them and restart the uC. Then reconnect to your "regular" WiFi. Now you're ready to go: start learning RFID-tags via GUI. To reach the GUI enter the IP stated in the serial console or use the hostname. For example if you're using a Fritzbox as router and entered tonuino as hostname, in your webbrowser tonuino.fritz.box should work. After running the rfid-learning, place your RFID-tag next to the RFID-reader and the music (or whatever else you defined) should start to play. While the playlist is generated, fast-rotating LEDs are shown to indicate, that Tonuino is busy. The more tracks a playlist/directory contains the longer this step takes. <br >
Please note: hostname can be used to call webgui or FTP-server. I tested it with a Fritzbox 7490 and worked fine. Make sure you don't use a name that already exists in you local network (LAN). Please note: hostname can be used to call webgui or FTP-server. I tested it with a Fritzbox 7490 and worked fine. Make sure you don't use a name that already exists in you local network (LAN).
## After Tonuino-ESP32 is connected to your WiFi ## After Tonuino-ESP32 is connected to your WiFi

3
src/logmessages.h

@ -140,3 +140,6 @@ static const char wifiStaticIpConfigNotFoundInNvs[] PROGMEM = "Statische WLAN-IP
static const char wifiHostnameNotSet[] PROGMEM = "Keine Hostname-Konfiguration im NVS gefunden."; static const char wifiHostnameNotSet[] PROGMEM = "Keine Hostname-Konfiguration im NVS gefunden.";
static const char mqttConnFailed[] PROGMEM = "Verbindung fehlgeschlagen, versuche erneut in Kürze erneut"; static const char mqttConnFailed[] PROGMEM = "Verbindung fehlgeschlagen, versuche erneut in Kürze erneut";
static const char restoredHostnameFromNvs[] PROGMEM = "Hostname aus NVS geladen"; static const char restoredHostnameFromNvs[] PROGMEM = "Hostname aus NVS geladen";
static const char currentVoltageMsg[] PROGMEM = "Aktuelle Batteriespannung";
static const char voltageTooLow[] PROGMEM = "Batteriespannung niedrig";
static const char sdBootFailedDeepsleep[] PROGMEM = "Bootgang wegen SD fehlgeschlagen. Gehe in Deepsleep...";

3
src/logmessages_EN.h

@ -140,3 +140,6 @@ static const char wifiStaticIpConfigNotFoundInNvs[] PROGMEM = "Unable to find st
static const char wifiHostnameNotSet[] PROGMEM = "Unable to find hostname-configuration to NVS."; static const char wifiHostnameNotSet[] PROGMEM = "Unable to find hostname-configuration to NVS.";
static const char mqttConnFailed[] PROGMEM = "Unable to establish mqtt-connection, trying again..."; static const char mqttConnFailed[] PROGMEM = "Unable to establish mqtt-connection, trying again...";
static const char restoredHostnameFromNvs[] PROGMEM = "Restored hostname from NVS"; static const char restoredHostnameFromNvs[] PROGMEM = "Restored hostname from NVS";
static const char currentVoltageMsg[] PROGMEM = "Current battery-voltage";
static const char voltageTooLow[] PROGMEM = "Low battery-voltage";
static const char sdBootFailedDeepsleep[] PROGMEM = "Failed to boot due to SD. Will go to deepsleep...";

131
src/main.cpp

@ -4,10 +4,12 @@
#define NEOPIXEL_ENABLE // Don't forget configuration of NUM_LEDS if enabled #define NEOPIXEL_ENABLE // Don't forget configuration of NUM_LEDS if enabled
#define NEOPIXEL_REVERSE_ROTATION // Some Neopixels are adressed/soldered counter-clockwise. This can be configured here. #define NEOPIXEL_REVERSE_ROTATION // Some Neopixels are adressed/soldered counter-clockwise. This can be configured here.
#define LANGUAGE 1 // 1 = deutsch; 2 = english #define LANGUAGE 1 // 1 = deutsch; 2 = english
//#define HEADPHONE_ADJUST_ENABLE // Used to adjust (lower) volume for optional headphone-pcb (refer maxVolumeSpeaker / maxVolumeHeadphone)
//#define SINGLE_SPI_ENABLE // If only one SPI-instance should be used instead of two
#define HEADPHONE_ADJUST_ENABLE // Used to adjust (lower) volume for optional headphone-pcb (refer maxVolumeSpeaker / maxVolumeHeadphone)
//#define SINGLE_SPI_ENABLE // If only one SPI-instance should be used instead of two (not yet working!)
#define SHUTDOWN_IF_SD_BOOT_FAILS // Will put ESP to deepsleep if boot fails due to SD. Really recommend this if there's in battery-mode no other way to restart ESP! Interval adjustable via deepsleepTimeAfterBootFails.
//#define SD_NOT_MANDATORY_ENABLE // Only for debugging-purposes: Tonuino will also start without mounted SD-card anyway (will only try once to mount it )
//#define MEASURE_BATTERY_VOLTAGE // Enables battery-measurement via GPIO (ADC) and voltage-divider
//#define SD_NOT_MANDATORY_ENABLE // Only for debugging-purposes: Tonuino will also start without mounted SD-card anyway (will only try once to mount it). Will overwrite SHUTDOWN_IF_SD_BOOT_FAILS!
//#define BLUETOOTH_ENABLE // Doesn't work currently (so don't enable) as there's not enough DRAM available //#define BLUETOOTH_ENABLE // Doesn't work currently (so don't enable) as there's not enough DRAM available
#include <ESP32Encoder.h> #include <ESP32Encoder.h>
@ -89,12 +91,14 @@ char *logBuf = (char*) calloc(serialLoglength, sizeof(char)); // Buffer for all
#define I2S_BCLK 27 #define I2S_BCLK 27
#define I2S_LRC 26 #define I2S_LRC 26
// GPIO to detect if headphone was plugged in (set to GND)
// GPIO to detect if headphone was plugged in (pulled to GND)
#ifdef HEADPHONE_ADJUST_ENABLE #ifdef HEADPHONE_ADJUST_ENABLE
#define HP_DETECT 22 // Detects if there's a plug in the headphone jack or not #define HP_DETECT 22 // Detects if there's a plug in the headphone jack or not
uint16_t headphoneLastDetectionDebounce = 1000; // Debounce-interval in ms when plugging in headphone
// Internal values
bool headphoneLastDetectionState; bool headphoneLastDetectionState;
uint32_t headphoneLastDetectionTimestamp = 0; uint32_t headphoneLastDetectionTimestamp = 0;
uint16_t headphoneLastDetectionDebounce = 1000; // Debounce-interval in ms when plugging in headphone
#endif #endif
#ifdef BLUETOOTH_ENABLE #ifdef BLUETOOTH_ENABLE
@ -105,17 +109,33 @@ char *logBuf = (char*) calloc(serialLoglength, sizeof(char)); // Buffer for all
#define POWER 17 #define POWER 17
// GPIOs (Rotary encoder) // GPIOs (Rotary encoder)
#define DREHENCODER_CLK 34
#define DREHENCODER_DT 35
#define DREHENCODER_BUTTON 32
#define DREHENCODER_CLK 34 // If you want to reverse encoder's direction, just switch GPIOs of CLK with DT
#define DREHENCODER_DT 35 // If you want to reverse encoder's direction, just switch GPIOs of CLK with DT
#define DREHENCODER_BUTTON 32 // Button is used to switch Tonuino on and off
// GPIOs (Control-buttons) // GPIOs (Control-buttons)
#define PAUSEPLAY_BUTTON 5 #define PAUSEPLAY_BUTTON 5
#define NEXT_BUTTON 4 #define NEXT_BUTTON 4
#define PREVIOUS_BUTTON 33
#define PREVIOUS_BUTTON 2 // Please note: as of 19.11.2020 changed from 33 to 2
// GPIOs (LEDs) // GPIOs (LEDs)
#define LED_PIN 12
#define LED_PIN 12 // Pin where Neopixel is connected to
#ifdef MEASURE_BATTERY_VOLTAGE
#define VOLTAGE_READ_PIN 33 // Pin to monitor battery-voltage. Change to 35 if you're using Lolin D32 or Lolin D32 pro
uint16_t r1 = 391; // First resistor of voltage-divider (kOhms) (measure exact value with multimeter!)
uint8_t r2 = 128; // Second resistor of voltage-divider (kOhms) (measure exact value with multimeter!)
float warningLowVoltage = 3.22; // If battery-voltage is >= this value, a cyclic warning will be indicated by Neopixel
uint8_t voltageCheckInterval = 5; // How of battery-voltage is measured (in minutes)
// Internal values
float refVoltage = 3.3; // Operation-voltage of ESP32; don't change!
uint16_t maxAnalogValue = 4095; // Highest value given by analogRead(); don't change!
uint32_t lastVoltageCheckTimestamp = 0;
#ifdef NEOPIXEL_ENABLE
bool showVoltageWarning = false;
#endif
#endif
// Neopixel-configuration // Neopixel-configuration
#ifdef NEOPIXEL_ENABLE #ifdef NEOPIXEL_ENABLE
@ -198,6 +218,10 @@ uint8_t initialLedBrightness = 16; // Initial brightness of
uint8_t ledBrightness = initialLedBrightness; uint8_t ledBrightness = initialLedBrightness;
uint8_t nightLedBrightness = 2; // Brightness of Neopixel in nightmode uint8_t nightLedBrightness = 2; // Brightness of Neopixel in nightmode
// Automatic restart
#ifdef SHUTDOWN_IF_SD_BOOT_FAILS
uint32_t deepsleepTimeAfterBootFails = 20; // Automatic restart takes place if boot was not successful after this period (in seconds)
#endif
// MQTT // MQTT
bool enableMqtt = true; bool enableMqtt = true;
#ifdef MQTT_ENABLE #ifdef MQTT_ENABLE
@ -300,6 +324,7 @@ char *mqttPassword = strndup((char*) "mqtt-password", mqttPasswordLength); // M
static const char topicRepeatModeState[] PROGMEM = "State/Tonuino/RepeatMode"; static const char topicRepeatModeState[] PROGMEM = "State/Tonuino/RepeatMode";
static const char topicLedBrightnessCmnd[] PROGMEM = "Cmnd/Tonuino/LedBrightness"; static const char topicLedBrightnessCmnd[] PROGMEM = "Cmnd/Tonuino/LedBrightness";
static const char topicLedBrightnessState[] PROGMEM = "State/Tonuino/LedBrightness"; static const char topicLedBrightnessState[] PROGMEM = "State/Tonuino/LedBrightness";
static const char topicBatteryVoltage[] PROGMEM = "State/Tonuino/Voltage";
#endif #endif
char stringDelimiter[] = "#"; // Character used to encapsulate data in linear NVS-strings (don't change) char stringDelimiter[] = "#"; // Character used to encapsulate data in linear NVS-strings (don't change)
@ -374,6 +399,7 @@ static int arrSortHelper(const void* a, const void* b);
#ifdef MQTT_ENABLE #ifdef MQTT_ENABLE
void callback(const char *topic, const byte *payload, uint32_t length); void callback(const char *topic, const byte *payload, uint32_t length);
#endif #endif
void batteryVoltageTester(void);
void buttonHandler(); void buttonHandler();
void deepSleepManager(void); void deepSleepManager(void);
void doButtonActions(void); void doButtonActions(void);
@ -484,6 +510,35 @@ void IRAM_ATTR onTimer() {
} }
// Measures voltage of a battery as per interval or after bootup (after allowing a few seconds to settle down)
#ifdef MEASURE_BATTERY_VOLTAGE
void batteryVoltageTester(void) {
if ((millis() - lastVoltageCheckTimestamp >= voltageCheckInterval*60000) || (!lastVoltageCheckTimestamp && millis()>=10000)) {
float factor = 1 / ((float) r1/(r1+r2));
float voltage = ((float) analogRead(VOLTAGE_READ_PIN) / maxAnalogValue) * refVoltage * factor;
#ifdef NEOPIXEL_ENABLE
if (voltage <= warningLowVoltage) {
snprintf(logBuf, serialLoglength, "%s: (%.2f V)", (char *) FPSTR(voltageTooLow), voltage);
loggerNl(logBuf, LOGLEVEL_ERROR);
showVoltageWarning = true;
}
#endif
#ifdef MQTT_ENABLE
char vstr[6];
snprintf(vstr, 6, "%.2f", voltage);
publishMqtt((char *) FPSTR(topicBatteryVoltage), vstr, false);
#endif
snprintf(logBuf, serialLoglength, "%s: %.2f V", (char *) FPSTR(currentVoltageMsg), voltage);
loggerNl(logBuf, LOGLEVEL_INFO);
//Serial.printf("Spannung: %f\n", voltage);
lastVoltageCheckTimestamp = millis();
}
}
#endif
// If timer-semaphore is set, read buttons (unless controls are locked) // If timer-semaphore is set, read buttons (unless controls are locked)
void buttonHandler() { void buttonHandler() {
if (xSemaphoreTake(timerSemaphore, 0) == pdTRUE) { if (xSemaphoreTake(timerSemaphore, 0) == pdTRUE) {
@ -1689,6 +1744,29 @@ void showLed(void *parameter) {
vTaskDelay(portTICK_RATE_MS * 400); vTaskDelay(portTICK_RATE_MS * 400);
} }
#ifdef MEASURE_BATTERY_VOLTAGE
if (showVoltageWarning) { // Flashes red three times if battery-voltage is low
showVoltageWarning = false;
notificationShown = true;
for (uint8_t i=0; i<3; i++) {
FastLED.clear();
for (uint8_t led = 0; led < NUM_LEDS; led++) {
leds[ledAddress(led)] = CRGB::Red;
}
FastLED.show();
vTaskDelay(portTICK_RATE_MS * 200);
FastLED.clear();
for (uint8_t led = 0; led < NUM_LEDS; led++) {
leds[ledAddress(led)] = CRGB::Black;
}
FastLED.show();
vTaskDelay(portTICK_RATE_MS * 200);
}
}
#endif
if (hlastVolume != currentVolume) { // If volume has been changed if (hlastVolume != currentVolume) { // If volume has been changed
uint8_t numLedsToLight = map(currentVolume, 0, maxVolume, 0, NUM_LEDS); uint8_t numLedsToLight = map(currentVolume, 0, maxVolume, 0, NUM_LEDS);
hlastVolume = currentVolume; hlastVolume = currentVolume;
@ -1733,7 +1811,11 @@ void showLed(void *parameter) {
for (uint8_t i=0; i < numLedsToLight; i++) { for (uint8_t i=0; i < numLedsToLight; i++) {
leds[ledAddress(i)] = CRGB::Blue; leds[ledAddress(i)] = CRGB::Blue;
FastLED.show(); FastLED.show();
#ifdef MEASURE_BATTERY_VOLTAGE
if (hlastVolume != currentVolume || lastLedBrightness != ledBrightness || showLedError || showLedOk || showVoltageWarning || !buttons[3].currentState) {
#else
if (hlastVolume != currentVolume || lastLedBrightness != ledBrightness || showLedError || showLedOk || !buttons[3].currentState) { if (hlastVolume != currentVolume || lastLedBrightness != ledBrightness || showLedError || showLedOk || !buttons[3].currentState) {
#endif
break; break;
} else { } else {
vTaskDelay(portTICK_RATE_MS*30); vTaskDelay(portTICK_RATE_MS*30);
@ -1741,7 +1823,11 @@ void showLed(void *parameter) {
} }
for (uint8_t i=0; i<=100; i++) { for (uint8_t i=0; i<=100; i++) {
#ifdef MEASURE_BATTERY_VOLTAGE
if (hlastVolume != currentVolume || lastLedBrightness != ledBrightness || showLedError || showLedOk || showVoltageWarning || !buttons[3].currentState) {
#else
if (hlastVolume != currentVolume || lastLedBrightness != ledBrightness || showLedError || showLedOk || !buttons[3].currentState) { if (hlastVolume != currentVolume || lastLedBrightness != ledBrightness || showLedError || showLedOk || !buttons[3].currentState) {
#endif
break; break;
} else { } else {
vTaskDelay(portTICK_RATE_MS*15); vTaskDelay(portTICK_RATE_MS*15);
@ -1751,7 +1837,11 @@ void showLed(void *parameter) {
for (uint8_t i=numLedsToLight; i>0; i--) { for (uint8_t i=numLedsToLight; i>0; i--) {
leds[ledAddress(i)-1] = CRGB::Black; leds[ledAddress(i)-1] = CRGB::Black;
FastLED.show(); FastLED.show();
#ifdef MEASURE_BATTERY_VOLTAGE
if (hlastVolume != currentVolume || lastLedBrightness != ledBrightness || showLedError || showLedOk || showVoltageWarning || !buttons[3].currentState) {
#else
if (hlastVolume != currentVolume || lastLedBrightness != ledBrightness || showLedError || showLedOk || !buttons[3].currentState) { if (hlastVolume != currentVolume || lastLedBrightness != ledBrightness || showLedError || showLedOk || !buttons[3].currentState) {
#endif
break; break;
} else { } else {
vTaskDelay(portTICK_RATE_MS*30); vTaskDelay(portTICK_RATE_MS*30);
@ -1778,7 +1868,11 @@ void showLed(void *parameter) {
} }
FastLED.show(); FastLED.show();
for (uint8_t i=0; i<=50; i++) { for (uint8_t i=0; i<=50; i++) {
#ifdef MEASURE_BATTERY_VOLTAGE
if (hlastVolume != currentVolume || lastLedBrightness != ledBrightness || showLedError || showLedOk || showVoltageWarning || playProperties.playMode != NO_PLAYLIST || !buttons[3].currentState) {
#else
if (hlastVolume != currentVolume || lastLedBrightness != ledBrightness || showLedError || showLedOk || playProperties.playMode != NO_PLAYLIST || !buttons[3].currentState) { if (hlastVolume != currentVolume || lastLedBrightness != ledBrightness || showLedError || showLedOk || playProperties.playMode != NO_PLAYLIST || !buttons[3].currentState) {
#endif
break; break;
} else { } else {
vTaskDelay(portTICK_RATE_MS * 10); vTaskDelay(portTICK_RATE_MS * 10);
@ -1813,7 +1907,11 @@ void showLed(void *parameter) {
default: // If playlist is active (doesn't matter which type) default: // If playlist is active (doesn't matter which type)
if (!playProperties.playlistFinished) { if (!playProperties.playlistFinished) {
#ifdef MEASURE_BATTERY_VOLTAGE
if (playProperties.pausePlay != lastPlayState || lockControls != lastLockState || notificationShown || ledBusyShown || volumeChangeShown || showVoltageWarning || !buttons[3].currentState) {
#else
if (playProperties.pausePlay != lastPlayState || lockControls != lastLockState || notificationShown || ledBusyShown || volumeChangeShown || !buttons[3].currentState) { if (playProperties.pausePlay != lastPlayState || lockControls != lastLockState || notificationShown || ledBusyShown || volumeChangeShown || !buttons[3].currentState) {
#endif
lastPlayState = playProperties.pausePlay; lastPlayState = playProperties.pausePlay;
lastLockState = lockControls; lastLockState = lockControls;
notificationShown = false; notificationShown = false;
@ -3098,6 +3196,7 @@ void handleUpload(AsyncWebServerRequest *request, String filename, size_t index,
void setup() { void setup() {
Serial.begin(115200); Serial.begin(115200);
esp_sleep_enable_ext0_wakeup((gpio_num_t) DREHENCODER_BUTTON, 0);
srand(esp_random()); srand(esp_random());
pinMode(POWER, OUTPUT); pinMode(POWER, OUTPUT);
digitalWrite(POWER, HIGH); digitalWrite(POWER, HIGH);
@ -3163,6 +3262,13 @@ void setup() {
#ifdef SD_NOT_MANDATORY_ENABLE #ifdef SD_NOT_MANDATORY_ENABLE
break; break;
#endif #endif
#ifdef SHUTDOWN_IF_SD_BOOT_FAILS
if (millis() >= deepsleepTimeAfterBootFails*1000) {
loggerNl((char *) FPSTR(sdBootFailedDeepsleep), LOGLEVEL_ERROR);
esp_deep_sleep_start();
}
#endif
} }
#ifdef HEADPHONE_ADJUST_ENABLE #ifdef HEADPHONE_ADJUST_ENABLE
@ -3381,7 +3487,7 @@ void setup() {
); );
esp_sleep_enable_ext0_wakeup((gpio_num_t) DREHENCODER_BUTTON, 0);
//esp_sleep_enable_ext0_wakeup((gpio_num_t) DREHENCODER_BUTTON, 0);
// Activate internal pullups for all buttons // Activate internal pullups for all buttons
pinMode(DREHENCODER_BUTTON, INPUT_PULLUP); pinMode(DREHENCODER_BUTTON, INPUT_PULLUP);
@ -3449,6 +3555,9 @@ void loop() {
#ifdef HEADPHONE_ADJUST_ENABLE #ifdef HEADPHONE_ADJUST_ENABLE
headphoneVolumeManager(); headphoneVolumeManager();
#endif #endif
#ifdef MEASURE_BATTERY_VOLTAGE
batteryVoltageTester();
#endif
volumeHandler(minVolume, maxVolume); volumeHandler(minVolume, maxVolume);
buttonHandler(); buttonHandler();
doButtonActions(); doButtonActions();

Loading…
Cancel
Save