02/20/2016 at 21:42 •
So I decided to bite the bullet and develop my framework directly onto the ESP. However, I didn't want to develop it on Linux, which seems to be the standard fare out there. I develop my Arduino/Atmel code using Eclipse on my Windows PC and I like the environment. I found a site out there that documents how to do ESP development via Eclipse.
If that's how you want to roll, here's the link.
Using a USB FTDI Serial cable (at 3.3v Tx & Rx lines) and a little breadboard with a 3.3v regulator (the signals from the cable are 3.3v, but annoyingly the power is 5v), and reset and program switch, I'm able to deliver my code right to the ESP chip.
So I rewrote the Framework to work directly off the ESP. And it works great. I had some challenges. The documentation regarding the ESP is scant and not well organized. I mean you can find the manuals for the API, but it doesn't document the ports, how to set them up, or anything about how memory is organized.
Here's what I learned/figured out:
- There seems to be a lot of RAM (compared to the Arduino), I'd say probably 128K (for variables, stack, etc.)
- There is special section of RAM called IRAM and this is where the program instructions are executed. 32K there (I know because I blew through it quick until I learned the trick).
- There is 512K Flash which lives in SPI ROM. Half of it is used for the kernel and the other for your program.
- IRAM is important because the SPI FLASH is not directly addressable by the processor (i.e. address bus). So any code that needs to be run is placed in IRAM and executed. So how does one run 512K of code in 32K? You don't. Whenever the processor needs to make a call to a function, it checks if it is in the IRAM cache and if not, loads it in from the SPI Flash and then executes it. If it needs room for the function, it tosses an unused piece of code. I suppose the kernel manages all this using interrupts. But the take from this is some code, like functions called from interrupts must be in IRAM all the time, but most doesn't. So if you want to conserve IRAM, you decorate all your functions (not being called by an interrupt) with ICACHE_FLASH_ATTR. If you don't, then the function will get loaded at boot and never bounced and you can burn up IRAM quickly.
I am still working on the code, but I am maintaining it on GitHub here. The GitHub will only have code that somewhat works. As I add stuff, I'll commit it and post a log.
02/20/2016 at 14:28 •
I'm a C++ programmer (among other languages) so I set out to virtualize the ESP into various components like Sockets, MQTT Client and HTTP Server (to change the MQTT Client settings). My first project for the board was the ever popular NeoPixel Christmas Lights controller. I know, cliché, right? But hey, it's good for proving out various types of settings. I had settings for effects (i.e. blink, theater chase, etc.), appearance (i.e. colors) and effect enhancements (i.e. speed, pixel density, etc.).
Additionally, I created an Android app that was also an MQTT Client that would allow me to view the current settings of one or more boards (at one time I had 3 going) and allow it to alter those settings.
My MQTT approach was simple:
- Each client would use the ESP's Hostname as the Client ID
- The Client ID would also be used as the top branch of the MQTT Topic
- For each attribute I wanted to control, there were 2 MQTT subtopics, set and value
- The attribute was a subtopic of the set and value super-topics
- Upon startup, the board would connect to MQTT Server with a keepalive setting (10 seconds), set a 'retained' Will Topic of 'client_id/Active' with a value of 'No', and then finally publish a topic 'client_id/Active' with a value of 'Yes'. This way the Android could subscribe to the topic of '+/Active' and detect when a board came online and went offline.
- Each attribute that the board kept, after it established a connection/will topic, etc. with the MQTT Broker, would publish the attribute as a topic 'client_id/value/attribute'.
- The Android MQTT Client would publish the setting with the topic 'client_id/set/attribute' with the 'retain' so that the board, upon waking, connecting, etc., would subscribe to the topic 'client_id/set/#' to obtain the initial setting.
- Whenever a 'set' topic came in, the board would publish the corresponding 'value' topic (also 'retained') with the new setting to confirm to the Android app that it received it ok. Later, when I developed sensors, switches, etc. on the board, every change in the state would also publish a 'value' topic.
- The Android app would subscribe to 'client_id/value/#' topics to get the values and update the GUI.
I was largely successful in setting up my framework, but there were issues. They were the same issues everyone faces with these microcontrollers: memory (both RAM and Program) and speed. By the time I had the framework for the HTTP server and MQTT client, I had nothing left to include the application! Some will say that C++ chewed a lot of it up, but it really doesn't and I was judicious in my use of memory.
Another issue was the complexity of the IP protocol with just the AT command set of the ESP. First, socket programming is by its nature, asynchronous, but the nature of the AT command set is synchronous. What that means is that I had to create a complex state machine to keep track of the asynchronous traffic in and out of the sockets over the synchronous stream of the UART. You can't start sending data to a socket in the middle of receiving one or you'd get 'busy' from the ESP. Updating a string of 300 NeoPixels takes longer than 2 character transmits from the ESP and since interrupts were off during an update, you could miss data coming from the ESP. I fixed that by altering the NeoPixel code to check the interrupt bit on the UART and bail when it saw it, but still the complexity of parsing the UART stream and reacting to various possible data coming from the ESP was, in the end, just too much to bear. The parser was recursive (Note: recursion is bad when you have only 2K of RAM!) so that if a message from the ESP came in (i.e. +IPD... or +AT+CLOSE) in the middle of issuing a command and waiting for the response, it'd stop what it was working on, parse the message, issue the appropriate method on the Socket, and continue on with what it was last working on. When using a 'slider' on the Android app would cause a flurry of messages to come in (and respond to), all the while maintaining the keepalive messages to the MQTT Broker. It was a nightmare to debug!
In the end, I abandoned the approach in favor of a better one: move all that crap onto the ESP! That will be the subject of my next log.