Here's my test setup:
More about this below. But first, getting there was not all that easy:
IRremote.h and the ESP32
IRremote.h does not want to play nice with ANY library saving anything to the NVS memory. That's sort of a letdown if you'd like to use an infrared remote AND save preferences or states to the ESP32's internal storage.
Here's what happens (continuously, like every 0.2 of a second), showing only one instance of the error:
ets Jun 8 2016 00:22:57
rst:0x3 (SW_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:1
load:0x3fff0018,len:4
load:0x3fff001c,len:808
load:0x40078000,len:6084
load:0x40080000,len:6696
entry 0x400802e4
Guru Meditation Error: Core 1 panic'ed (Cache disabled but cached memory region accessed)
Core 1 register dump:
PC : 0x400d0d78 PS : 0x00060034 A0 : 0x40081664 A1 : 0x3ffc0be0
A2 : 0x00000001 A3 : 0x00000002 A4 : 0x000000ff A5 : 0x40086d14
A6 : 0xf0000040 A7 : 0x00290000 A8 : 0x80081188 A9 : 0x3ff5f024
A10 : 0x3ffc1044 A11 : 0x20000000 A12 : 0x00000400 A13 : 0x3ffb1e60
A14 : 0x00000020 A15 : 0xffffffff SAR : 0x00000015 EXCCAUSE: 0x00000007
EXCVADDR: 0x00000000 LBEG : 0x400012e5 LEND : 0x40001309 LCOUNT : 0x800d3245
Core 1 was running in ISR context:
EPC1 : 0x4008a377 EPC2 : 0x00000000 EPC3 : 0x00000000 EPC4 : 0x400d0d78
Backtrace: 0x400d0d78:0x3ffc0be0 0x40081661:0x3ffc0c00 0x4008a374:0x00000000
Rebooting...
I tried using EEPROM.h and Preferences.h. It still behaves the same, so I guess the problem must lie with IRremote.h.
The Solution
Sure enough, here's somebody who's encountered the same problem. Not only that, he's also solved it:
https://github.com/espressif/arduino-esp32/issues/928
His solution lets you turn off the IR Receiver when you do an EEPROM.commit, and turn it on again afterwards.
A small problem though, the instructions were a little off. Here's how to make IRremote.h work on the ESP32:
Change the File IRremote.h
On Windows, you'll find the file in Documents\Arduino\Libraries\IRremote\IRremote.h. Search for the following line:
void enableIRIn ( ) ;
Replace it with:
void enableIRIn (bool enable) ;
Don't forget to do the changes described here as well:
At the end of the file IRremote.h, find this piece of code:
#else
const int sendPin = SEND_PIN;
#endif
} ;
Replace the first line (#else) with:
#elif defined(SEND_PIN)
Replace the File esp32.cpp
On Windows, you'll find the file in Documents\Arduino\Libraries\IRremote\esp32.cpp. Replace everything in the file with this code:
#ifdef ESP32
// This file contains functions specific to the ESP32.
#include "IRremote.h"
#include "IRremoteInt.h"
// "Idiot check"
#ifdef USE_DEFAULT_ENABLE_IR_IN
#error Must undef USE_DEFAULT_ENABLE_IR_IN
#endif
hw_timer_t *timer;
void IRTimer(); // defined in IRremote.cpp, masqueraded as ISR(TIMER_INTR_NAME)
//+=============================================================================
// initialization
//
void IRrecv::enableIRIn (bool enable)
{
// Interrupt Service Routine - Fires every 50uS
// ESP32 has a proper API to setup timers, no weird chip macros needed
// simply call the readable API versions :)
// 3 timers, choose #1, 80 divider nanosecond precision, 1 to count up
if (enable) {
timer = timerBegin(1, 80, 1);
timerAttachInterrupt(timer, &IRTimer, 1);
// every 50ns, autoreload = true
timerAlarmWrite(timer, 50, true);
timerAlarmEnable(timer);
}
else {
timerEnd(timer);
timerDetachInterrupt(timer);
}
// Initialize state machine variables
irparams.rcvstate = STATE_IDLE;
irparams.rawlen = 0;
// Set pin modes
pinMode(irparams.recvpin, INPUT);
}
#endif // ESP32
The Program
Before we begin, here's how I've hooked up everything:
IR Receiver: TSOP4838
- Pin 1: GPIO13
- Pin 2: GND
- Pin 3: 3.3V
Rotary Encoder: KY-040
I'm using the KY-040 rotary encoder, and connecting the pins to the ESP32 this way:
- GND: GND
- +: 3.3V
- SW: GPIO14
- DT: GPIO34
- CLK: GPIO35
Power ON LED
- GPIO2 (will also control the onboard LED)
Code
I'll readily admit that I'm relatively bad at programming. This code has a good basic structure though, and everything is done as functions. It should be reasonably understandable, and quite easy to modify.
It all revolves around the function relayOn():
- relayOn() gets called if the variable relayCount changes:
- It then changes to a new input relay
- relayOn() gets called if the variable previousRelay changes:
- Used when the unit powers on, to turn on the previously selected input relay
- The functions irRemote(), rotaryEncoder() and powerControl() will change the variables relayCount and previousRelay only if the power is set to ON (powerState == 1)
- relayOn() handles relayCount < 0 and relayCount > 4, any changes to relayCount can be done as relayCount++, relayCount-- and relayCount = relay#.
The code will most likely change (there's probably stuff that's left over from testing), but here's the current functionality:
- When the ESP32 boots, it will read the saved channel so it starts up where you left off.
- It will start "powered off", all relays are off and nothing will happen until you power on using the rotational encoder's push-button or the IR remote ON/OFF button.
- The rotational encoder turned right or left will increase or decrease the input channel
- The IR remote's UP/DOWN buttons will increase or decrease the input channel
- The IR remote's buttons 1-5 will choose the channels directly
- All remote control button presses will be shown on the serial console, so that you can change the values in the irRemote() function
Enough talk, here's the code:
/* Muffsy Relay Input Selector
*
* Control relays using IR and rotary encoder
* Control external power to amp using IR and push button
*
*/
/*
* powerState:
*
* 0: Boot
* powerOn()
* startup procedure
* read NVRAM (relayCount)
* set relays to off (previousRelay = relayCount)
* set power amp to off, SSR = LOW
*
* 1: Powered ON
* turn on power button LED
* set power amp to on, SSR = HIGH
* trigger relayOn(): previousRelay = relayCount + 1
* rotaryEncoder()
* increases or decreases relayCount depending on rotational direction
* pushbutton: Power ON/OFF
* does only Power ON if powerState == 2
* irRemote()
* input up/down
* input direct (buttons 1-5)
* power on/off
* does only Power ON if powerState == 2
* relayOn()
* activates relays based on the relayCount
* handles relayCount too high or low
* powerControl()
* read power button, set powerState accordingly
*
* 2: Powered OFF
* turn off all relays
* set power amp to off (SSR = LOW)
* powerControl()
* read power button, set powerState == 1 if pushed
* irRemote()
* read power button, set powerState == 1 if pushed
*/
// Libraries
#include <IRremote.h> // IR Remote Library
#include <EEPROM.h> // EEPROM Library
// Size: 1 (relayCount)
#define EEPROM_SIZE 1
// Variables, pin definitions
// Onboard LED/Power LED
#define LED 2
// IR Receiver pin and setup
#define IR_Recv 13
IRrecv irrecv(IR_Recv);
decode_results results;
// Power button
#define poweronButton 14
// Pins for the rotary encoder:
#define rotaryA 35
#define rotaryB 34
// Relays
#define R1 23
#define R2 22
#define R3 21
#define R4 19
#define R5 18
//Solid State Relay
#define SSR 17
// Rotary Encoder variables
int counter = 0;
int previous = 0;
int aState;
int aPreviousState;
// Relay Array
int relays[] = {23, 22, 21, 19, 18};
// Relay variables
int relayCount;
int previousRelay;
int relayNumber;
// Power/Mute variables
int powerState;
int buttonState = 1; // the current reading from the input pin
int lastButtonState = 1; // the previous reading from the input pin
unsigned long lastDebounceTime = 0; // the last time the output pin was toggled
unsigned long debounceDelay = 50; // the debounce time; increase if the output flickers
int mute = 0; // Mute on/off (1/0)
// Setup
void setup() {
// Power button
pinMode (poweronButton,INPUT_PULLUP);
// Onboard LED
pinMode (LED,OUTPUT);
// Rotary Encoder
pinMode (rotaryA,INPUT);
pinMode (rotaryB,INPUT);
// Reads the initial state of the rotaryA
aPreviousState = digitalRead(rotaryA);
// Relays
pinMode (R1,OUTPUT);
pinMode (R2,OUTPUT);
pinMode (R3,OUTPUT);
pinMode (R4,OUTPUT);
pinMode (R5,OUTPUT);
pinMode (SSR,OUTPUT);
// Relay variables
EEPROM.begin(EEPROM_SIZE);
relayCount = EEPROM.read(0);
previousRelay = relayCount + 1; // Start out not matching relayCount???
// Start the IR Receiver
//pinMode(IR_Recv, INPUT_PULLDOWN);
irrecv.enableIRIn(true); // Starts the receiver
/*
* powerStates:
* 0: Powering on
* 1: Powered on
* 2: Powered off
*/
powerState = 0;
mute = 0; // Mute on/off (1/0)
// Serial monitor
Serial.begin (115200);
}
/*
* Main program
*/
void loop() {
if (powerState == 0) {
powerOn();
} else if (powerState == 1){
relayOn();
rotaryEncoder(); // Include Push = MUTE
powerControl(); // Read power button
irRemote(); // Up, Down, Direct, Volume, MUTE, Power
} else {
rotaryEncoder(); // Rotary push button is temporarily power button???
powerControl(); // Read power button
irRemote(); // Power on/off only
}
}
/*
* Turn on current relay
*/
void relayOn() {
// If relayCount has changed: Turn on the selected relay (next, previous, direct)
// If previousRelay has changed: Turn on the last selected relay
if (relayCount != previousRelay) {
// Rollover 4 or 0
if (relayCount > 4) {
relayCount = 0;
} else if (relayCount < 0) {
relayCount = 4;
}
// Turn off all relays, then turn on relayCount
relayOff();
digitalWrite(relays[relayCount], HIGH);
// Stop IR, write relayCount to memory, start IR
irrecv.enableIRIn(false);
EEPROM.write(0,relayCount);
EEPROM.commit();
irrecv.enableIRIn(true);
Serial.print("[http://muffsy.com]: Written \"relayCount = ");
Serial.print(relayCount);
Serial.println("\" to save slot 0");
// Reset counters, output relayNumber
previousRelay = relayCount;
relayNumber = relayCount + 1;
Serial.print("[http://muffsy.com]: Activated relay #");
Serial.println(relayNumber);
Serial.println();
}
}
/*
* Power on amplifier
*/
void powerOn() { // Only called if powerState is 0 (Powering on)
Serial.println("\n --- http://muffsy.com ---\n");
Serial.println("The Muffsy Relay Input Selector has woken up!\n");
Serial.print(" ** Reading saved relay state from NVRAM: ");
Serial.println(relayCount);
Serial.println("\n ** All input relays are turned OFF");
relayOff();
Serial.println(" ** Power amplifier is turned OFF\n");
digitalWrite (SSR,LOW);
Serial.println(" ** Startup completed - waiting for Power ON\n");
Serial.println(" -------------------------\n");
// Set powerState to 2 (Powered off):
powerState = 2;
}
/*
* Read powerbutton, turn on or off
*/
void powerControl() {
int reading = digitalRead(poweronButton);
if (reading != lastButtonState) {
lastDebounceTime = millis();
}
if ((millis() - lastDebounceTime) > debounceDelay) {
// whatever the reading is at, it's been there for longer than the debounce
// delay, so take it as the actual current state:
// if the button state has changed:
if (reading != buttonState) {
buttonState = reading;
// only toggle the LED if the new button state is HIGH
if (buttonState == 1) {
Serial.println("[http://muffsy.com]: Power button pushed");
if (powerState == 1) { // Turning power OFF: All relays OFF, power amp OFF
powerState = 2;
digitalWrite (SSR,LOW);
digitalWrite (LED,LOW);
relayOff();
Serial.println("[http://muffsy.com]: Power amplifier OFF");
Serial.println("[http://muffsy.com]: Power OFF\n");
} else if (powerState == 2) { // Turning power ON: Last selected relay ON, power amp ON
powerState = 1;
digitalWrite (SSR,HIGH);
digitalWrite (LED,HIGH);
previousRelay = relayCount + 1; // Trigger relayOn()
Serial.println("[http://muffsy.com]: Power ON");
Serial.println("[http://muffsy.com]: Power amplifier ON\n");
}
}
}
}
lastButtonState = reading;
}
/*
* IR Remote
*/
void irRemote() { // Start irRemote function
// Decode the infrared input
// Decodes the infrared input
if (irrecv.decode(&results)) {
long int decCode = results.value;
Serial.print("[http://muffsy.com]: Received IR code: ");
Serial.print(decCode);
Serial.println();
// Switch case to use the selected remote control button
switch (results.value) { // Start switch/case
case 7770223: // Relay 1
{
Serial.println("[http://muffsy.com]: Button \"1\"");
if (powerState == 1) {
relayCount = 0;
} else {
Serial.println("[http://muffsy.com]: Powered off, doing nothing...\n");
}
break;
}
case 7774303: // Relay 2
{
Serial.println("[http://muffsy.com]: Button \"2\"");
if (powerState == 1) {
relayCount = 1;
} else {
Serial.println("[http://muffsy.com]: Powered off, doing nothing...\n");
}
break;
}
case 7766143: // Relay 3
{
Serial.println("[http://muffsy.com]: Button \"3\"");
if (powerState == 1) {
relayCount = 2;
} else {
Serial.println("[http://muffsy.com]: Powered off, doing nothing...\n");
}
break;
}
case 7787053: // Relay 4
{
Serial.println("[http://muffsy.com]: Button \"4\"");
if (powerState == 1) {
relayCount = 3;
} else {
Serial.println("[http://muffsy.com]: Powered off, doing nothing...\n");
}
break;
}
case 7791133: // Relay 5
{
Serial.println("[http://muffsy.com]: Button \"5\"");
if (powerState == 1) {
relayCount = 4;
} else {
Serial.println("[http://muffsy.com]: Powered off, doing nothing...\n");
}
break;
}
case 7742173: // Channel UP
{
Serial.println("[http://muffsy.com]: Button \"UP\"");
if (powerState == 1) {
relayCount++;
} else {
Serial.println("[http://muffsy.com]: Powered off, doing nothing...\n");
}
break;
}
case 7738093: // Channel DOWN
{
Serial.println("[http://muffsy.com]: Button \"DOWN\"");
if (powerState == 1) {
relayCount--;
} else {
Serial.println("[http://muffsy.com]: Powered off, doing nothing...\n");
};
break;
}
case 7745743: // Power button
{
Serial.println("[http://muffsy.com]: Button \"POWER\"");
if (powerState == 1) {
powerState = 2;
digitalWrite (SSR,LOW);
digitalWrite (LED,LOW);
relayOff();
Serial.println("[http://muffsy.com]: Power amplifier OFF");
Serial.println("[http://muffsy.com]: Power OFF\n");
} else {
powerState = 1;
digitalWrite (SSR,HIGH);
digitalWrite (LED,HIGH);
previousRelay = relayCount + 1; // Trigger relayOn()
Serial.println("[http://muffsy.com]: Power ON");
Serial.println("[http://muffsy.com]: Power amplifier ON\n");
}
break;
}
default:
{
Serial.println("[http://muffsy.com]: Going back to waiting for IR remote keypress\n");
}
} // End switch/case
irrecv.resume(); // Receives the next value from the button you press
}
} // End irRemote function
/*
* Mute (turn off all relays)
*/
void relayOff() {
for (int off = 0; off <= 4; off++) {
digitalWrite(relays[off], LOW);
}
}
/*
* Rotary Encoder Control of Relays
*/
void rotaryEncoder() {
aState = digitalRead(rotaryA); // Reads the "current" state of the rotaryA
// If the previous and the current state of the rotaryA are different, that means a Pulse has occured
if (aState != aPreviousState){
// If the rotaryB state is different to the rotaryA state, that means the encoder is rotating clockwise
if (digitalRead(rotaryB) != aState) {
counter ++;
} else {
counter --;
}
}
// What to do if rotating Right of Left
if (previous != counter) {
if (counter > 1) { // Since the encoder gives two signals when turning
Serial.print("[http://muffsy.com]: Rotational encoder turned ");
Serial.println("clockwise");
if (powerState == 1) {
// Increase relayCount
relayCount++;
} else {
Serial.println("[http://muffsy.com]: Powered off, doing nothing...\n"); // Powered off???
}
} else if (counter < -1) { // Since the encoder gives two signals when turning
Serial.print("[http://muffsy.com]: Rotational encoder turned ");
Serial.println("counter-clockwise");
if (powerState == 1) {
// Increase relayCount
relayCount--;
} else {
Serial.println("[http://muffsy.com]: Powered off, doing nothing...\n"); // Powered off???
}
}
}
// Reset counters
previous = counter;
if (counter < -1) {
counter = 0;
previous = 0;
} else if (counter > 1){
counter = 0;
previous = 0;
}
// Updates the previous state of the rotaryA with the current state
aPreviousState = aState;
}
You can get an idea of what's going on by looking at the serial console:
-skrodahl
Discussions
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.
I have the same problem. I don't have a file esp32.cpp in library IRremote. Can you send link to your revised library?. Please.
Are you sure? yes | no