diff --git a/Images.md b/Images.md index 3f63d05..a9ae86d 100644 --- a/Images.md +++ b/Images.md @@ -45,5 +45,7 @@ \ ![ёRadio](images/img21.jpg)\ \ -![ёRadio](images/img22.jpg) +![ёRadio](images/img22.jpg)\ +\ +![ёRadio](images/img23.jpg) diff --git a/README.md b/README.md index ecdea93..9cb8202 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,7 @@ _\** GPIO 16 and 17 are used by PSRAM on the WROVER modules._ --- ## Dependencies #### Libraries: -**Library Manager**: Adafruit_GFX, Adafruit_ST7735\*, Adafruit_SSD1306\*, Adafruit_PCD8544\*, Adafruit_SH110X\*, Adafruit_SSD1327\*, Adafruit_ILI9341\*, Adafruit_SSD1305\*, (\* depending on display model), ESP32Encoder, OneButton, IRremoteESP8266, XPT2046_Touchscreen \ +**Library Manager**: Adafruit_GFX, Adafruit_ST7735\*, Adafruit_SSD1306\*, Adafruit_PCD8544\*, Adafruit_SH110X\*, Adafruit_SSD1327\*, Adafruit_ILI9341\*, Adafruit_SSD1305\*, (\* depending on display model), OneButton, IRremoteESP8266, XPT2046_Touchscreen \ **Github**: [ESPAsyncWebServer](https://github.com/me-no-dev/ESPAsyncWebServer), [AsyncTCP](https://github.com/me-no-dev/AsyncTCP), [async-mqtt-client](https://github.com/marvinroger/async-mqtt-client)* \ \* _if you need MQTT support_ @@ -216,7 +216,7 @@ download _http://\/data/playlist.csv_ and _http://\/data 2. Get firmware binary: Sketch → Export compiled binary 3. Get SPIFFS binary: disconnect ESP32 from your computer, click on **ESP32 Data Sketch Upload**. \ You will get an error and file path - + 4. Go to page _http://\/update_ and upload yoRadio.ino.esp32.bin and yoRadio.spiffs.bin in turn, checking the appropriate upload options. @@ -291,6 +291,12 @@ Work is in progress... --- ## Version history +#### v0.6.262 +- change encoder library to [ai-esp32-rotary-encoder](https://github.com/igorantolic/ai-esp32-rotary-encoder) (injected to project) +- added new option VOL_ACCELERATION - volume adjustment acceleration by encoder (see [myoptions.h](exsamples/myoptions.h) for exsample) +- fixed connection error with http-stations on esp32-core v2.0.3 +- fixed css errors (a [full update](#update-over-web-interface) is required) + #### v0.6.250 - added update via web-interface \ **Attention! Full firmware with chip re-partitioning is required!** see [board setup example](#quick-start) diff --git a/exsamples/myoptions.h b/exsamples/myoptions.h index 97e6c38..dcf5d45 100644 --- a/exsamples/myoptions.h +++ b/exsamples/myoptions.h @@ -54,13 +54,15 @@ The connection tables are located here https://github.com/e2002/yoradio#connecti //#define ENC2_BTNB 255 /* Encoder button */ //#define ENC2_BTNR 255 /* Right rotation */ //#define ENC2_INTERNALPULLUP true /* Enable the weak pull up resistors */ -//#define ENC2_HALFQUARD true /* Experiment with it */ +//#define ENC2_HALFQUARD false /* (true, false, 255) Experiment with it */ /******************************************/ /* BUTTONS */ //#define BTN_LEFT 255 /* VolDown, Prev */ //#define BTN_CENTER 255 /* Play, Stop, Show playlist */ //#define BTN_RIGHT 255 /* VolUp, Next */ +//#define BTN_UP 255 /* Prev, Move Up */ +//#define BTN_DOWN 255 /* Next, Move Down */ //#define BTN_INTERNALPULLUP true /* Enable the weak pull up resistors */ //#define BTN_LONGPRESS_LOOP_DELAY 200 /* Delay between calling DuringLongPress event */ //#define BTN_CLICK_TICKS 300 /* Event Timing https://github.com/mathertel/OneButton#event-timing */ @@ -100,6 +102,7 @@ The connection tables are located here https://github.com/e2002/yoradio#connecti //#define TFT_CONTRAST 55 /* Nokia 5110 contrast */ //#define TFT_INVERT true /* Invert the display colors (usually true) */ //#define VOL_STEP 1 /* Volume control step */ +//#define VOL_ACCELERATION 200 /* Encoder vol acceleration; 0 or 1 means disabled acceleration */ //#define MUTE_PIN 255 /* MUTE Pin */ //#define MUTE_VAL HIGH /* Write this to MUTE_PIN when player is stopped */ //#define PL_WITH_NUMBERS /* show the number of station in the playlist */ @@ -129,4 +132,9 @@ The connection tables are located here https://github.com/e2002/yoradio#connecti //#define IR_CODE_NUM9 0xFF5AA5 //#define IR_CODE_HASH 0xFF52AD /* Toggle playlist mode */ //#define IR_CODE_AST 0xFF42BD /* Not used */ + + +/******************************************/ + + #endif diff --git a/images/img23.jpg b/images/img23.jpg new file mode 100644 index 0000000..4370480 Binary files /dev/null and b/images/img23.jpg differ diff --git a/yoRadio/controls.cpp b/yoRadio/controls.cpp index 1c97d36..752de0d 100644 --- a/yoRadio/controls.cpp +++ b/yoRadio/controls.cpp @@ -16,13 +16,28 @@ OneButton button[] {{BTN_LEFT, true, BTN_INTERNALPULLUP}, {BTN_CENTER, true, BTN constexpr uint8_t nrOfButtons = sizeof(button) / sizeof(button[0]); #endif +#if ENC_HALFQUARD==false +#define ENCODER_STEPS 4 +#elif ENC_HALFQUARD==true +#define ENCODER_STEPS 2 +#elif ENC_HALFQUARD==255 +#define ENCODER_STEPS 1 +#endif +#if ENC2_HALFQUARD==false +#define ENCODER2_STEPS 4 +#elif ENC2_HALFQUARD==true +#define ENCODER2_STEPS 2 +#elif ENC2_HALFQUARD==255 +#define ENCODER2_STEPS 1 +#endif + #if (ENC_BTNL!=255 && ENC_BTNR!=255) || (ENC2_BTNL!=255 && ENC2_BTNR!=255) -#include +#include "src/yoEncoder/yoEncoder.h" #if (ENC_BTNL!=255 && ENC_BTNR!=255) -ESP32Encoder encoder; +yoEncoder encoder = yoEncoder(ENC_BTNL, ENC_BTNR, ENCODER_STEPS, ENC_INTERNALPULLUP); #endif #if (ENC2_BTNL!=255 && ENC2_BTNR!=255) -ESP32Encoder encoder2; +yoEncoder encoder2 = yoEncoder(ENC2_BTNL, ENC2_BTNR, ENCODER2_STEPS, ENC2_INTERNALPULLUP); #endif #endif @@ -50,23 +65,34 @@ IRrecv irrecv(IR_PIN, kCaptureBufferSize, kTimeout, true); decode_results irResults; #endif -void initControls() { #if ENC_BTNL!=255 - encoder.useInternalWeakPullResistors = ENC_INTERNALPULLUP ? UP : DOWN; - if (ENC_HALFQUARD) { - encoder.attachHalfQuad(ENC_BTNL, ENC_BTNR); - } else { - encoder.attachFullQuad(ENC_BTNL, ENC_BTNR); - } +void IRAM_ATTR readEncoderISR() +{ + encoder.readEncoder_ISR(); +} #endif #if ENC2_BTNL!=255 - encoder2.useInternalWeakPullResistors = ENC2_INTERNALPULLUP ? UP : DOWN; - if (ENC2_HALFQUARD) { - encoder2.attachHalfQuad(ENC2_BTNL, ENC2_BTNR); - } else { - encoder2.attachFullQuad(ENC2_BTNL, ENC2_BTNR); - } +void IRAM_ATTR readEncoder2ISR() +{ + encoder2.readEncoder_ISR(); +} #endif + +void initControls() { + +#if ENC_BTNL!=255 + encoder.begin(); + encoder.setup(readEncoderISR); + encoder.setBoundaries(0, 254, true); + encoder.setAcceleration(VOL_ACCELERATION); +#endif +#if ENC2_BTNL!=255 + encoder2.begin(); + encoder2.setup(readEncoder2ISR); + encoder2.setBoundaries(0, 254, true); + encoder2.setAcceleration(VOL_ACCELERATION); +#endif + #if ISPUSHBUTTONS for (int i = 0; i < nrOfButtons; i++) { @@ -134,30 +160,28 @@ void loopControls() { #if ENC_BTNL!=255 void encoderLoop() { - long encNewPosition = encoder.getCount() / 2; - if (encNewPosition != 0 && encNewPosition != encOldPosition) { - encOldPosition = encNewPosition; - encoder.setCount(0); - controlsEvent(encNewPosition > 0); + int8_t encoderDelta = encoder.encoderChanged(); + if (encoderDelta!=0) + { + controlsEvent(encoderDelta > 0, encoderDelta); } } #endif #if ENC2_BTNL!=255 void encoder2Loop() { - long encNewPosition = encoder2.getCount() / 2; - if (encNewPosition != 0 && encNewPosition != enc2OldPosition) { - enc2OldPosition = encNewPosition; - encoder2.setCount(0); + int8_t encoderDelta = encoder2.encoderChanged(); + if (encoderDelta!=0) + { uint8_t bp = 2; if (ENC2_BTNB != 255) { bp = digitalRead(ENC2_BTNB); } if (bp == HIGH && display.mode == PLAYER) { display.putRequest({NEWMODE, STATIONS}); - while(display.mode != STATIONS) {delay(5);} + while(display.mode != STATIONS) {delay(10);} } - controlsEvent(encNewPosition > 0); + controlsEvent(encoderDelta > 0, encoderDelta); } } #endif @@ -474,14 +498,21 @@ void onBtnDuringLongPress(int id) { } } -void controlsEvent(bool toRight) { +void controlsEvent(bool toRight, int8_t volDelta) { if (display.mode == NUMBERS) { display.numOfNextStation = 0; display.putRequest({NEWMODE, PLAYER}); } if (display.mode != STATIONS) { display.putRequest({NEWMODE, VOL}); - player.stepVol(toRight); + if(volDelta!=0){ + int nv = config.store.volume+volDelta; + if(nv<0) nv=0; + if(nv>254) nv=254; + player.setVol((byte)nv, false); + }else{ + player.stepVol(toRight); + } } if (display.mode == STATIONS) { display.resetQueue(); diff --git a/yoRadio/controls.h b/yoRadio/controls.h index 012de08..c701cc7 100644 --- a/yoRadio/controls.h +++ b/yoRadio/controls.h @@ -16,7 +16,7 @@ void irLoop(); void touchLoop(); void irNum(byte num); void irBlink(); -void controlsEvent(bool toRight); +void controlsEvent(bool toRight, int8_t volDelta = 0); void onBtnClick(int id); void onBtnDoubleClick(int id); diff --git a/yoRadio/data/www/style.css.gz b/yoRadio/data/www/style.css.gz index b56b91a..16fd95f 100644 Binary files a/yoRadio/data/www/style.css.gz and b/yoRadio/data/www/style.css.gz differ diff --git a/yoRadio/options.h b/yoRadio/options.h index a76852f..6a80a79 100644 --- a/yoRadio/options.h +++ b/yoRadio/options.h @@ -1,7 +1,7 @@ #ifndef options_h #define options_h -#define VERSION "0.6.250" +#define VERSION "0.6.262" /******************************************************* DO NOT EDIT THIS FILE. @@ -100,7 +100,7 @@ The connection tables are located here https://github.com/e2002/yoradio#connecti #define ENC_INTERNALPULLUP true #endif #ifndef ENC_HALFQUARD - #define ENC_HALFQUARD true + #define ENC_HALFQUARD false #endif #ifndef ENC2_BTNL @@ -116,7 +116,7 @@ The connection tables are located here https://github.com/e2002/yoradio#connecti #define ENC2_INTERNALPULLUP true #endif #ifndef ENC2_HALFQUARD - #define ENC2_HALFQUARD true + #define ENC2_HALFQUARD false #endif /* BUTTONS */ @@ -197,6 +197,9 @@ The connection tables are located here https://github.com/e2002/yoradio#connecti #ifndef VOL_STEP #define VOL_STEP 1 // Encoder vol step #endif +#ifndef VOL_ACCELERATION + #define VOL_ACCELERATION 200 // Encoder vol acceleration; 0 or 1 means disabled acceleration +#endif #ifndef MUTE_PIN #define MUTE_PIN 255 // MUTE Pin #endif diff --git a/yoRadio/src/audioI2S/Audio.cpp b/yoRadio/src/audioI2S/Audio.cpp index 9608045..e48a148 100644 --- a/yoRadio/src/audioI2S/Audio.cpp +++ b/yoRadio/src/audioI2S/Audio.cpp @@ -232,7 +232,7 @@ esp_err_t Audio::I2Sstop(uint8_t i2s_num) { //--------------------------------------------------------------------------------------------------------------------- esp_err_t Audio::i2s_mclk_pin_select(const uint8_t pin) { if(pin != 0 && pin != 1 && pin != 3) { - ESP_LOGE(TAG, "Only support GPIO0/GPIO1/GPIO3, gpio_num:%d", pin); + //ESP_LOGE(TAG, "Only support GPIO0/GPIO1/GPIO3, gpio_num:%d", pin); return ESP_ERR_INVALID_ARG; } switch(pin){ @@ -348,7 +348,7 @@ void Audio::httpPrint(const char* url) { strcat(resp, " HTTP/1.1\r\n"); strcat(resp, "Host: "); strcat(resp, host); - strcat(resp, "\r\nUser-Agent: ESP32 audioI2S\r\n"); + strcat(resp, "\r\nUser-Agent: Mozilla/5.0\r\n"); strcat(resp, "icy-metadata: 1\r\n"); strcat(resp, "Accept-Encoding: identity\r\n"); strcat(resp, "Connection: Keep-Alive\r\n\r\n"); @@ -522,7 +522,7 @@ bool Audio::connecttohost(const char* host, const char* user, const char* pwd) { // strcat(resp, "Transfer-Encoding: \r\n"); // otherwise the server assumes gzip compression strcat(resp, "Connection: keep-alive\r\n\r\n"); - const uint32_t TIMEOUT_MS{350}; + const uint32_t TIMEOUT_MS{3700}; uint32_t wtf; if(m_f_ssl == false) { uint32_t t = millis(); diff --git a/yoRadio/src/audioVS1053/audioVS1053Ex.cpp b/yoRadio/src/audioVS1053/audioVS1053Ex.cpp index b3590bd..28f2cf1 100644 --- a/yoRadio/src/audioVS1053/audioVS1053Ex.cpp +++ b/yoRadio/src/audioVS1053/audioVS1053Ex.cpp @@ -1640,7 +1640,7 @@ bool Audio::connecttohost(const char* host, const char* user, const char* pwd) { strcat(resp, "\r\n"); strcat(resp, "Connection: keep-alive\r\n\r\n"); - const uint32_t TIMEOUT_MS{350}; + const uint32_t TIMEOUT_MS{3700}; uint32_t wtf; if(m_f_ssl == false) { uint32_t t = millis(); diff --git a/yoRadio/src/yoEncoder/yoEncoder.cpp b/yoRadio/src/yoEncoder/yoEncoder.cpp new file mode 100644 index 0000000..d3f0e0c --- /dev/null +++ b/yoRadio/src/yoEncoder/yoEncoder.cpp @@ -0,0 +1,151 @@ +// based on https://github.com/igorantolic/ai-esp32-rotary-encoder code +// +// + +#include "esp_log.h" +#define LOG_TAG "yoEncoder" + +#include "yoEncoder.h" + +void IRAM_ATTR yoEncoder::readEncoder_ISR() +{ + + unsigned long now = millis(); + portENTER_CRITICAL_ISR(&(this->mux)); + if (this->isEnabled) + { + // code from https://www.circuitsathome.com/mcu/reading-rotary-encoder-on-arduino/ + /**/ + this->old_AB <<= 2; //remember previous state + + int8_t ENC_PORT = ((digitalRead(this->encoderBPin)) ? (1 << 1) : 0) | ((digitalRead(this->encoderAPin)) ? (1 << 0) : 0); + + this->old_AB |= (ENC_PORT & 0x03); //add current state + + //this->encoder0Pos += ( this->enc_states[( this->old_AB & 0x0f )]); + int8_t currentDirection = (this->enc_states[(this->old_AB & 0x0f)]); //-1,0 or 1 + + if (currentDirection != 0) + { + long prevRotaryPosition = this->encoder0Pos / this->encoderSteps; + this->encoder0Pos += currentDirection; + long newRotaryPosition = this->encoder0Pos / this->encoderSteps; + + if (newRotaryPosition != prevRotaryPosition && rotaryAccelerationCoef > 1) + { + //additional movements cause acceleration? + // at X ms, there should be no acceleration. + unsigned long accelerationLongCutoffMillis = 200; + // at Y ms, we want to have maximum acceleration + unsigned long accelerationShortCutffMillis = 4; + + // compute linear acceleration + if (currentDirection == lastMovementDirection && + currentDirection != 0 && + lastMovementDirection != 0) + { + // ... but only of the direction of rotation matched and there + // actually was a previous rotation. + unsigned long millisAfterLastMotion = now - lastMovementAt; + + if (millisAfterLastMotion < accelerationLongCutoffMillis) + { + if (millisAfterLastMotion < accelerationShortCutffMillis) + { + millisAfterLastMotion = accelerationShortCutffMillis; // limit to maximum acceleration + } + if (currentDirection > 0) + { + this->encoder0Pos += rotaryAccelerationCoef / millisAfterLastMotion; + } + else + { + this->encoder0Pos -= rotaryAccelerationCoef / millisAfterLastMotion; + } + } + } + this->lastMovementAt = now; + this->lastMovementDirection = currentDirection; + } + + //respect limits + if (this->encoder0Pos > (this->_maxEncoderValue)) + this->encoder0Pos = this->_circleValues ? this->_minEncoderValue : this->_maxEncoderValue; + if (this->encoder0Pos < (this->_minEncoderValue)) + this->encoder0Pos = this->_circleValues ? this->_maxEncoderValue : this->_minEncoderValue; + } + } + portEXIT_CRITICAL_ISR(&(this->mux)); +} + + +yoEncoder::yoEncoder(uint8_t encoder_APin, uint8_t encoder_BPin, uint8_t encoderSteps, bool internalPullup) +{ + this->old_AB = 0; + + this->encoderAPin = encoder_APin; + this->encoderBPin = encoder_BPin; + this->encoderSteps = encoderSteps; + + pinMode(this->encoderAPin, internalPullup?INPUT_PULLUP:INPUT_PULLDOWN); + pinMode(this->encoderBPin, internalPullup?INPUT_PULLUP:INPUT_PULLDOWN); +} + +void yoEncoder::setBoundaries(long minEncoderValue, long maxEncoderValue, bool circleValues) +{ + this->_minEncoderValue = minEncoderValue * this->encoderSteps; + this->_maxEncoderValue = maxEncoderValue * this->encoderSteps; + + this->_circleValues = circleValues; +} + +long yoEncoder::readEncoder() +{ + return (this->encoder0Pos / this->encoderSteps); +} + +void yoEncoder::setEncoderValue(long newValue) +{ + reset(newValue); +} + +long yoEncoder::encoderChanged() +{ + long _encoder0Pos = readEncoder(); + long encoder0Diff = _encoder0Pos - this->lastReadEncoder0Pos; + + this->lastReadEncoder0Pos = _encoder0Pos; + + return encoder0Diff; +} + +void yoEncoder::setup(void (*ISR_callback)(void)) +{ + attachInterrupt(digitalPinToInterrupt(this->encoderAPin), ISR_callback, CHANGE); + attachInterrupt(digitalPinToInterrupt(this->encoderBPin), ISR_callback, CHANGE); +} + +void yoEncoder::begin() +{ + this->lastReadEncoder0Pos = 0; +} + +void yoEncoder::reset(long newValue_) +{ + newValue_ = newValue_ * this->encoderSteps; + this->encoder0Pos = newValue_; + this->lastReadEncoder0Pos = this->encoder0Pos; + if (this->encoder0Pos > this->_maxEncoderValue) + this->encoder0Pos = this->_circleValues ? this->_minEncoderValue : this->_maxEncoderValue; + if (this->encoder0Pos < this->_minEncoderValue) + this->encoder0Pos = this->_circleValues ? this->_maxEncoderValue : this->_minEncoderValue; +} + +void yoEncoder::enable() +{ + this->isEnabled = true; +} +void yoEncoder::disable() +{ + this->isEnabled = false; +} diff --git a/yoRadio/src/yoEncoder/yoEncoder.h b/yoRadio/src/yoEncoder/yoEncoder.h new file mode 100644 index 0000000..7b30fce --- /dev/null +++ b/yoRadio/src/yoEncoder/yoEncoder.h @@ -0,0 +1,68 @@ +// yoEncoder.h +// based on https://github.com/igorantolic/ai-esp32-rotary-encoder code + +#ifndef _YOENCODER_h +#define _YOENCODER_h + +#include "Arduino.h" + +typedef enum +{ + BUT_DOWN = 0, + BUT_PUSHED = 1, + BUT_UP = 2, + BUT_RELEASED = 3, + BUT_DISABLED = 99, +} ButtonState; + +class yoEncoder +{ + +private: + portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED; + portMUX_TYPE buttonMux = portMUX_INITIALIZER_UNLOCKED; + volatile long encoder0Pos = 0; + + volatile int8_t lastMovementDirection = 0; //1 right; -1 left + volatile unsigned long lastMovementAt = 0; + unsigned long rotaryAccelerationCoef = 150; + + bool _circleValues = false; + bool isEnabled = true; + + uint8_t encoderAPin; + uint8_t encoderBPin; + long encoderSteps; + + long _minEncoderValue = -1 << 15; + long _maxEncoderValue = 1 << 15; + + uint8_t old_AB; + long lastReadEncoder0Pos; + + int8_t enc_states[16] = {0, -1, 1, 0, 1, 0, 0, -1, -1, 0, 0, 1, 0, 1, -1, 0}; + void (*ISR_callback)(); + +public: + yoEncoder( + uint8_t encoderAPin, + uint8_t encoderBPin, + uint8_t encoderSteps, + bool internalPullup = true); + void setBoundaries(long minValue = -100, long maxValue = 100, bool circleValues = false); + void IRAM_ATTR readEncoder_ISR(); + + void setup(void (*ISR_callback)(void)); + void begin(); + void reset(long newValue = 0); + void enable(); + void disable(); + long readEncoder(); + void setEncoderValue(long newValue); + long encoderChanged(); + + unsigned long getAcceleration() { return this->rotaryAccelerationCoef; } + void setAcceleration(unsigned long acceleration) { this->rotaryAccelerationCoef = acceleration; } + void disableAcceleration() { setAcceleration(0); } +}; +#endif