Skip to content

Code_Design

Carl edited this page May 2, 2022 · 9 revisions

4.1 Introduction - Coding

Coding the cloudSmoker project proved to be one of the steepest learning curves that I faced during this project. I ended up with a large code base over 1600 lines long (including debug scaffolding) and spanning ~20 library files!

At the project start, I was only a novice C coder (think Arduino "blink" program) and I really didn't have knowledge of the deeper particulars of C++ and, importantly, almost no understanding of object oriented programming concepts and the application of OOP within C++. This certainly changed over the course of this project and I've emerged with a much stronger coding foundation.

I started preparing for my coding journey by working through through a free online C++ course at learncpp.com. Working through this course was time consuming but certainly helped me get started.

4.2 IDE

Given the size of this project, I decided to move away from the Arduino Integrated Development Environment (IDE) software and to Micorsoft's Visual Studio Code and the PlatformIO plugin. Although I was faced with another learning curve, the depth of features in a professional IDE like VS Code / PlatformIO certainly helped me code this large project.

4.3 Code Design

My philosophy was to try to code the project as effectively and professionally as possible. Recognising that the code was going to be lengthy, I did my best to modularise the code base by separating, as much as possible, functionality into multiple files consisting of both public and personal library modules. Not only did I hope that this would make the code easier to understand and to debug, but it would also potentially make the code more portable for future projects. At the end of the day, modularisation also turned out to be a major source of frustration and time, as pursuing this approach often led to compiler and linker errors until I learned to more effectively manage critical interaction issues between files including variable, function and object definition, declaration, scope, duration, passing arguments and return values.

Many times, I ended up writing a "wrapper" library to augment, extend or amalgamate public library functionality. If nothing else, these wrapper libraries allowed me to abstract away much of the setup, instantiation or initialisation code that would otherwise clutter up the main program. I often found the easiest way to use class inheritance to create a child-class which would have the useful functionality of the parent class but allow me to extend the functionality through new methods.

After investigation and consideration, I decided that the core structure of my program would be best implemented as a State Machine framework. I really like the State Machine approach as it provides a clear, logical and methodological way to structure and code the required functionality.

I took the time to draw up a State Machine diagram for the cloudSmoker project and referred to this roadmap diagram often as I coded.


As seen in the state diagram, the required states can be grouped into two main functions within the code: 1) menu display and control and 2) temperature measurement and reporting.

4.4 WiFi Management

Wifi management, fundamental to the ESP8266, is provided in the background. A side benefit of the State Machine approach is that it facilitates non-blocking code, critical to keeping the ESP8266 properly functioning. The ESP8266 must share resources between running the user code and microprocessor utility background activities, such as maintaining the internal WiFi and TCP processes. Every time the main loop() is repeated, the Arduino core is designed to have the microcontroller yield to these background utility processes. If your code functions take too long to read this yield point, the chip's watchdog timers (WDT) will trigger an unwanted hard reset. This takes quite some time, about 3 seconds for the software watchdog and 8 seconds for the hardware watchdog, which is forever in terms of processor cycles on a modern microcontroller. So, in practice, unless you write blocking code, you are unlikely to trigger WDT resets. The heart of the State Machine implementation is a non-blocking processState() method in the main loop which checks and implements state changes as required. However, within the various libraries, I occasionally added a yield() statement within a for or while loops if I was concerned that the loop might be lengthy and trigger a WDT reset. Other programming tips to avoid WDT resets can be found in the blog post Beware of the (watch)Dog!.

4.5 Display and Control

Similar to the Bald Engineer experience with his Open Vapors Project, I found that a significant effort was required to code the menu system, display it on a simple 1602 (16 char by 2 lines) backlit LCD and provide menu interface through the rotary encoder and associated button switch. The linked article has some excellent tips and tricks on LCD buffering and display that I put to good use in my project.

Working with text in C / C++ on a memory-limited microcontroller is quite tricky and full of gotchas. Newbies often gravitate to using Arduino's string class but, as explained in the classic blog post, The Evils of Arduino Strings, String objects have so many shortcomings, particularly heap memory fragmentation, that a better approach, although not code efficient, is to use lower level C-strings. It was no surprise that my lcd library containing menu display functionality was my largest library, at over 600 lines of code.

The low cost KY-20 rotary encoder module proved to be an incredibly useful interface tool once paired with a couple of key public libraries:

  1. newEncoder library by gfvalvo provided excellent functionality to flawlessly software debounce the encoder values using a state table approach, allowed the rotary encoder scale to be redefined as needed and also provided "wrap" ability where scale min/max limits were reached.
  2. Bounce2 and Yabl (yet another button library) made the integrated rotary encoder push-button switch quite useful with the Bounce2 library providing effective switch debouncing (without the hassles of hardware debouncing solutions) and the Yabl library extending the button functionality to recognise three button push types (gestures): tap, double tap and press-hold. In effect, one button becomes three, all dependant on the press-type!

I would not hesitate to use a rotary encoder again as an effective low-cost user interface tool in any new project now that I've worked through the library and interface issues.

##4.5 Temperature Measurement and Reporting

The rest of the state machine dealt with reading and smoothing the thermistor measurements, converting ADC readings into temperature and then uploading the data into the cloud before finally entering a sleep (wait) state.

Note that although cloudSmoker can operate on either degrees Fahrenheit or Celsius basis, I made the decision to store all internal temperature values in degF and, if required, only convert to Celcius when necessary.

ADS1015 ADC

Clone this wiki locally