Send pinMode info to, and analog/digital readings from Arduino to ESP8266

Hello,

I'm currently working on a wireless audio control surface using an ESP8266.

The ESP handles all the connection stuff, like WiFi, a webserver, UDP, OSC ... and has a couple of shift registers connected to drive all the LEDs. To get input from potentiometers, faders, buttons, rotary encoders etc, I'm thinking of using an Arduino (or a Teensy).

My question is: what is the best approach to get the two to communicate?

I'll need three types of messages:

  1. From the ESP to the Arduino, to setup the pins (pin mapping can be changed in a web interface), for example: 'button on pin 5' (which tells the Arduino to turn on the internal pull-up resistor on pin 5, and report changes of state back to the ESP), or 'rotary encoder on pins 2 and 3' (initialize rotary encoder, and report relative position when moved)

  2. Readings from the Arduino back to the ESP: when a value for an input is different than the previous reading, the Arduino should send that new value back to the ESP.

  3. Values from the ESP to the Arduino: I'd like to support motorized faders as well. This means that the ESP should be able to change the position of the fader, so it should be able to send position values to the Arduino. The Arduino will then control the motor depending on the desired position, and the current analog value.

First question: What interface is the most suitable in this case? UART? SPI? Something else?

Second question: How does the ESP know that a value has changed? In the case of UART, is checking for Serial.available in the loop of the ESP sufficient? Should I use a hardware interrupt (the Arduino driving a pin high when it's going to send a value)? In the case of SPI, who takes the role as master? Does that matter?

And finally: how do I know when a message is finished? I read these articles by Nick Gammon, but he either uses only 1-byte messages, or a datatype/structure with a fixed length (here). My initialization messages have different lengths, for example, a 'button on pin 12' command has only 1 or 2 bytes, while a 'motorized fader with its motors on pins 4, 5 and 6, knob touch sensor on pin 6, analog input A9' message is much longer.

Should the first byte include the packet length? Should I use start and stop bytes? Should I make all packets of equal length, just sending 0x00 for bytes I don't need for a shorter message? Can they get out of sync in this case? (If it expects a new packet every 6 bytes, for example, and it skips one byte, then the first byte of the next packet will be interpreted as the second byte of the packet, with possibly disastrous consequences).

Any help, comments or thoughts are appreciated.

Thanks in advance, Pieter

This thread will give you ideas about the protocol: Serial Input Basics - updated. It's not limited to serial communication !

If you use a binary protocol, life is a lot harder. You need a start indication and an end indication, you would like a length in there and you more than likely will like to add a CRC/checksum/SHA for error checking.

I would use ordinary serial communication (uart); solves the problem of a multi master environment where both sides need to be able to send and receive independently.

PieterP: First question: What interface is the most suitable in this case? UART? SPI? Something else?

In your question there is nothing to limit your choice of bus. So that's up to you. With SPI being the fastest option.

autious: Second question: How does the ESP know that a value has changed?

It doesn't... Unless YOU make a way so it knows. This can be polling but you could also add a "pin-changed"-line (and program it that way). It's all up to you.

autious: In the case of UART, is checking for Serial.available in the loop of the ESP sufficient?

If you let the device just send data when it need to then yes, that's a form of polling. But be sure to not read more then you received ;)

autious: Should I use a hardware interrupt (the Arduino driving a pin high when it's going to send a value)?

That's up to you. Only when timing is very critical. But because it's just user input and I don't expect you to use it right away anyhow I don't see the need.

autious: In the case of SPI, who takes the role as master? Does that matter?

Again, that's up to you. But because the ESP is actually running the show I would say the ESP. The Arduino isn't doing much...

autious: And finally: how do I know when a message is finished?

It's getting a bit borring but again, that's up to you. There is no fixed standard to communicate what you want. Just make you're own protocol and think it through. For example, you could send everything ASCII and use '\0' as an end marker. Or both just know how long a command is and act on that (maybe with a time out?). Options are endless :)

autious: 'motorized fader with its motors on pins 4, 5 and 6

Do the pins really matter? Isn't it much more logical to just group that into a "motor1" and give a position? So "motor1 to position x"

autious: 'motorized fader with its motors on pins 4, 5 and 6, knob touch sensor on pin 6, analog input A9' message is much longer.

And what has the motor to do with the knob or the analog input? I would say those are 3 seperate messages / commando's.

autious: Should the first byte include the packet length?

That's also an option. Up to you...

autious: Should I use start and stop bytes?

And how do they differ from "regular" bytes?

autious: Should I make all packets of equal length, just sending 0x00 for bytes I don't need for a shorter message?

Up to up...

autious: Can they get out of sync in this case? (If it expects a new packet every 6 bytes, for example, and it skips one byte, then the first byte of the next packet will be interpreted as the second byte of the packet, with possibly disastrous consequences).

Possible. If for some reason a byte is dropped. So better to make it in a way you can sync. And deffenatly add a checksum.

@Pieter

Your questions are way too general to respond to them with a 'solution' ... however, I think a dialog can be started that will help you to make a decision. Ultimately, you will have to make the decision that will work best for you.

The ESP handles all the connection stuff, like WiFi, a webserver, UDP, OSC ... and has a couple of shift registers connected to drive all the LEDs. To get input from potentiometers, faders, buttons, rotary encoders etc, I'm thinking of using an Arduino (or a Teensy).

If you have gotten this far, it is obvious that your skills are not novice and you have a more than a working knowledge around the ESP8266. I am assuming you are programming the ESP8266 with the Arduino IDE using igrr's core?

If my assumption is correct, that you are using the Arduino core for ESP8266, then you are aware that the Arduino code "timeshares" the uC with the native ESP code that manages the RF section. Therefore, the total time-slice you can utilize will be based upon the required time that the RF section needs for radio services. To put this into simple terms, if you have developed complex web pages and REST objects, then the 'arduino' side has less total time availability.

You probably know this too: if your Arduino-side code has lengthy loops or functions that extend into the 50mS danger zone, you must utilize delay(0) or yield() to allow the RF section to "catch it breath" and reset the WDT. The RF section (implied delay(0) ) is automatically given control at the top of every loop(); therefore, if you loop() construct is 30mS or shorter there is no issue. loop() repetition that approach 50mS are in the danger zone of having the Watch Dog Timer reboot the uC. What I advise is for you to stick in an UL variable and catch the total loop() time before you go any further - knowing the amount of uC time remaining for you to use is critical knowledge.

If the ESP8266 serial is not being utilized, I would probably use that I/O for communication because it is free. SPI is faster than I2C.

You can move a structure between the Arduino and the ESP8266. You could also move comma-separated data strings much like the way that GPS is managed with separate sentences. Parsing, however, is a time-consuming operation and I tend to like moving binary structs.

In either choice above, you need to prototype and test the code to determine the ESP8266 time required to either parse and set variables or move and update the common struct.

Going any deeper into answering your questions I think would be premature until you do some analysis and testing and come back with some empirical results. My guess is that your testing will show a strong affinity for one solution over the other.

Ray

Thank you all for your comprehensive replies, I will take some time to read it all through, try some of the approaches, and come back here when I've got some results and/or questions.

Thanks again, Pieter

PieterP: Thank you all for your comprehensive replies, I will take some time to read it all through, try some of the approaches, and come back here when I've got some results and/or questions.

Thanks again, Pieter

Great. You may want to take a view at how I am using websockets, HTML5, and javascript in my latest ESP8266 project. This consumes SPIFFS flash, but moves the work from the ESP8266 to the client browser.

Ray

YouTube: http://youtu.be/gkThEp5u-yU

sterretje: If you use a binary protocol, life is a lot harder. You need a start indication and an end indication, you would like a length in there and you more than likely will like to add a CRC/checksum/SHA for error checking.

Well, a binary protocol seems to be the best option, since I don't want to waste too much resources encoding and decoding strings. I'll add some error checking, like you suggested, but I'm not sure what to do with a message if it's corrupted: should I just ignore it, or should I send back a message to send the last packet again? There's a high probability that a new packet has already been sent, so the packet that got corrupted is no longer available on the sender, right?

Can you recommend one particular error checking method, or is that up to me as well? ;)

septillion: Do the pins really matter? Isn't it much more logical to just group that into a "motor1" and give a position? So "motor1 to position x" And what has the motor to do with the knob or the analog input? I would say those are 3 seperate messages / commando's.

Well, the motorized faders are controlled by PID controllers, so I need to know the analog value for feedback, 2 pins for the H-bridge, and a third one for PWM, and then there's a capacitive touch sensor in the knob as well, because it should stop moving when it's touched. (The long message is for initialization only, to set the position afterwards, I can just say fader1, for example.)

septillion: And how do they differ from "regular" bytes?

By using only 7 data bits per byte, so that normal bytes start with a 0 and the start and stop bytes with 1. Or is that bad practice? I don't really see any other way to tell them apart...

mrburnette: If you have gotten this far, it is obvious that your skills are not novice and you have a more than a working knowledge around the ESP8266. I am assuming you are programming the ESP8266 with the Arduino IDE using igrr's core?

Yes, I'm using igrr's core.

mrburnette: If my assumption is correct, that you are using the Arduino core for ESP8266, then you are aware that the Arduino code "timeshares" the uC with the native ESP code that manages the RF section. Therefore, the total time-slice you can utilize will be based upon the required time that the RF section needs for radio services. To put this into simple terms, if you have developed complex web pages and REST objects, then the 'arduino' side has less total time availability.

You probably know this too: if your Arduino-side code has lengthy loops or functions that extend into the 50mS danger zone, you must utilize delay(0) or yield() to allow the RF section to "catch it breath" and reset the WDT. The RF section (implied delay(0) ) is automatically given control at the top of every loop(); therefore, if you loop() construct is 30mS or shorter there is no issue. loop() repetition that approach 50mS are in the danger zone of having the Watch Dog Timer reboot the uC. What I advise is for you to stick in an UL variable and catch the total loop() time before you go any further - knowing the amount of uC time remaining for you to use is critical knowledge.

I am indeed aware of that. On question though: If I run my ESP @160MHz instead of 80MHz, does that mean that the watchdog timer will cause a reboot after 25ms instead of 50ms?

My loop execution takes between 1700µs and 9500µs, depending on how many audio tracks are playing in Reaper, the software that sends the OSC messages. (This is only when receiving a packet, of course, 99% of the time, it's only 1µs when no packets come in.)

Over a third of the time consumption comes from copying data into an OSC buffer, and two thirds is deciding what to do with it. Maybe I can improve that second part, but not by much, I'm afraid.

mrburnette: You can move a structure between the Arduino and the ESP8266. You could also move comma-separated data strings much like the way that GPS is managed with separate sentences. Parsing, however, is a time-consuming operation and I tend to like moving binary structs.

Do you mean sending the struct as one binary stream, without start/stop bytes, no checksums ... (like in the examples at the links of your Google search), or by processing it first, converting them into a series of bytes including start/stop, error checking etc.

mrburnette: You may want to take a view at how I am using websockets, HTML5, and javascript in my latest ESP8266 project. This consumes SPIFFS flash, but moves the work from the ESP8266 to the client browser.

It's an easy and fast protocol indeed, very interesting. I've used it a couple of months ago for web controlled RGB light effects, and a 'wireless oscilloscope' with FFT. The ESP almost didn't do anything, JavaScript did all the heavy processing. And if you have more than 1M of flash, you have to use at least 64KB for SPIFFS, so why not copy some fancy JS to it ;) .

Anyway, thanks for the help, Pieter

Do you mean sending the struct as one binary stream, without start/stop bytes, no checksums ... (like in the examples at the links of your Google search), or by processing it first, converting them into a series of bytes including start/stop, error checking etc.

Binary. The struct can contain a checksum, if you wish, or you can send it as a second element. Just send as fast as you can: discard anything with a bad checksum. This keeps the ack-nak handshaking overhead out of the picture and eliminates having to buffer the old-data for resend. Of course, playing loosy-goosy has downsides, too.

If I run my ESP @160MHz instead of 80MHz, does that mean that the watchdog timer will cause a reboot after 25ms instead of 50ms?

Untested. But my belief is the uC core clock does not affect the WDT. I really need to set this up in lab to say with certain.

Ray

PieterP: Can you recommend one particular error checking method, or is that up to me as well? ;)

checksum, crc, sha

Speed: the first one is the fastest, the last one the slowest Reliability: last one is the best, the first one the worst

So it is indeed fully up to you :)

PieterP: I'll add some error checking, like you suggested, but I'm not sure what to do with a message if it's corrupted: should I just ignore it, or should I send back a message to send the last packet again? There's a high probability that a new packet has already been sent, so the packet that got corrupted is no longer available on the sender, right?

If you only send information if something has changed, you have to resend it else the other side does not know that something has changed if the data was corrupted.

Hi PieterP,

If this were my project, I would avoid the extra complexity of a second mcu if I could. There are many ways to add extra analog and digital inputs/outputs to the esp, using the i2c or spi bus.

Such as this module which provides 4 16-bit analog inputs over i2c bus
16-Bit-I2C-4-Channel-ADS1115-Mod.jpg

Paul

PieterP:
but I’m not sure what to do with a message if it’s corrupted: should I just ignore it, or should I send back a message to send the last packet again? There’s a high probability that a new packet has already been sent, so the packet that got corrupted is no longer available on the sender, right?

Again, up to you. It’s you designing. Is it terrible to skip a packet? If it is I would go for a system in which the receiver has to ACK or NACK. If it’s better to just move on, drop the packet and go to the next one.

PieterP:
Can you recommend one particular error checking method, or is that up to me as well? :wink:

Google :wink: It’s all up to you. How sure do you want to be? Easiest way, just XOR every byte.

PieterP:
Well, the motorized faders are controlled by PID controllers, so I need to know the analog value for feedback, 2 pins for the H-bridge, and a third one for PWM, and then there’s a capacitive touch sensor in the knob as well, because it should stop moving when it’s touched.

But would it not make sens to do THAT on the Arduino? Including complicated transmission in a PID loop is making things complex. Just release the ESP and do that on the Arduino. Then all you need to do is send a position and the rest is done automagicly :wink:

PieterP:
By using only 7 data bits per byte, so that normal bytes start with a 0 and the start and stop bytes with 1. Or is that bad practice? I don’t really see any other way to tell them apart…

That’s an option. Only thing, you reduce the bandwidth. If you want to send an 8-bit value it now takes two bytes to transmit. But it is indeed an option :slight_smile:

@PaulRB, I think I would not. I2C is not that fast which can be a bit hard if you want to PID control a load of faders. By giving them a separate MCU life get easier. All the heave lifting for the faders can be done there and the ESP just uses the info :slight_smile:

septillion: Again, up to you. It's you designing. Is it terrible to skip a packet? If it is I would go for a system in which the receiver has to ACK or NACK. If it's better to just move on, drop the packet and go to the next one.

In the setup (where I tell the Arduino what pins are inputs, and what pins are outputs etc) I'll probably use ACK/NACK (to prevent frying the Arduino by accidentally setting a pin with a switch connected to output) and after the initialization is done, I'll just drop bad packets.

septillion: But would it not make sens to do THAT on the Arduino? Including complicated transmission in a PID loop is making things complex. Just release the ESP and do that on the Arduino. Then all you need to do is send a position and the rest is done automagicly ;)

Yes, I was going to do that on the Arduino, but I need to send all the pin information during the setup. After that, I can just send the position.

septillion: That's an option. Only thing, you reduce the bandwidth. If you want to send an 8-bit value it now takes two bytes to transmit. But it is indeed an option :)

My pin numbers are 6-bit anyway, and analog values will probably be 10-bits, so that doesn't really matter.

PaulRB: Hi PieterP,

If this were my project, I would avoid the extra complexity of a second mcu if I could. There are many ways to add extra analog and digital inputs/outputs to the esp, using the i2c or spi bus.

Such as this module which provides 4 16-bit analog inputs over i2c bus

Paul

It would make a lot of sense to use something like that if I only needed analog inputs. However, the advantage of having a second µ-controller is that it can do switch debouncing, PID control of the faders, it has interrupts for rotary encoders, PWM for motors and analog meters, etc ...

But why are you trying to send all the info to the Arduino? Just set it up as a module with all the components already in it. Because it sounds like you need multiple nodes with the same functions. That will save you A LOT of hassle to get it very very universal and overhead on the ESP.

So just make modules that can handle a subset of the functions. So you have a node that can handle a couple of faders, another for just buttons, etc. Way easier to implement and maintain.

I want to be able to change the pin mapping without having to change he code, I’d like to have a fully modular design. The configuration is done in a web interface, and stored in the ESP’s flash memory.

I would not call that modular. In a module something has somewhat of a specific task. This is more universal with no specific task. Which can be done but is a lot harder. Especially if you want to implement things like control loops on the Arduino. (Which would be a smart use of the resource.)