ESP8266 Espressif SDK programming in C++

Intro

Are you a C++ crazy fan? I’m! don’t be shy. I can’t imagine life without programming in C++, so now that I’m getting into IoT I wanted to explore the feasibility of using C++, after all programming ESP8266 in the Arduino IDE you can, why can’t we on the official SDK?

Out of the box the projects build on top of Espressif SDK are compiled in C. And that’s a good thing. A better one is to program our projects in C++. I didn’t find any useful information, so I tried it myself, and I succeded!

So far I just tried to use my own classes, I haven’t tested the STL library, but they would work (I don’t want to forget the steps I followed, so I’m in a hurry!).

Procedure

I tried theses steps in Linux Mint, but there’s nothing weird that cannot be done in either Windows or Mac. I’m assuming you have an Espressif SDK environment up and running.

My setup is so simple:

Setup using a ESP-01 board on top of DIY programmer.

STEP 1: Do everything is needed to compile your projects in C under the Espressif SDK. This is a great introduction.

STEP 2: Say you’re following the “Hello world” example. Look for the file hello_world_main.c under the hello_world/main folder, and delete it.

STEP 3: In that same folder create a new file called hello_world_main.cpp and populate it with the next content:

/* Hello World Example

   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.

   Modified by fjrg76, 26/04/21, Mexico City
*/

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_spi_flash.h"


class MyClass
{
   int a;

public:
   void set( int val )
   {
      a = val;
   }

   int get()
   {
      return a;
   }
};


int f()
{
   MyClass c;

   c.set( 100 );

   return c.get();
}


// app_main() is the entry point for our programs and it must be a C function:

#ifdef __cplusplus
extern "C"{
#endif  

void app_main()
{
   printf("Hello world!\n");

   /* Print chip information */
   esp_chip_info_t chip_info;
   esp_chip_info(&chip_info);
   printf("This is ESP8266 chip with %d CPU cores, WiFi, ",
         chip_info.cores);

   printf("silicon revision %d, ", chip_info.revision);

   printf("%dMB %s flash\n", spi_flash_get_chip_size() / (1024 * 1024),
         (chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "embedded" : "external");

   for (int i = 10; i >= 0; i--) {
      printf("Restarting in %d seconds...\n", i);
      vTaskDelay(1000 / portTICK_PERIOD_MS);
   }

   // for testing the C++ compiler:
   int v = f();
   printf( "Value is: %d\n", v );
   vTaskDelay( 500 / portTICK_PERIOD_MS );


   printf("Restarting now.\n");
   fflush(stdout);
   esp_restart();
}

#ifdef __cplusplus
}
#endif  

It’s almost the same code as the original example, but I added some things:

  1. The most important fact to be aware is that the function app_main() is surrounded with the macro __cplusplus in order to be compiled as C function. Otherwise it won’t work. The entry point for the user code is the function app_main() and it must be a C function.
  2. I added a silly class and a silly function f(). Actually this f() function might be the entry point for our programs (if you aren’t using FreeRTOS tasks).
  3. Inside the function app_main() I called the function f() so that it is compiled, and we use the instance c from the class MyClass.

You’ll end up with these 3 files:

STEP 4: Open the file CMakeLists.txt and modify it with this next content:

idf_component_register(SRCS "hello_world_main.cpp"
                    INCLUDE_DIRS "")

The only change with regards to the original one is that I’ve changed the extension from .c to .cpp.

STEP 5: Build the project and enjoy programming the ESP8266 in C++ along the real time operating system FreeRTOS:

$ make clean
$ make 

The make clean instruction is to clean up the environment. It contains debris from those times when the project was compiled with the C compiler. Once done you don’t need to repeat it.

The make instruction actually compiles the project using the C++ compiler. If you get this next output, you’re done!

STEP 5: Upload your program into the chip:

$ make flash

You don’t need to emit two instructions: make and then make flash; with the latter is enough. I used a single make so you can see the output.

Output after flashing our program into the chip.

You might want to see what the program is doing. Press and release the reset button on your board (I need to do it by hand on mine) and type:

$ make monitor

(To exit from the monitor type Ctrl + ]). This is the output:

Program output. Look at the line before the last line. That’s the result of calling our class MyClass.

What’s next

We’ve seen how to program using C++; the next logical step is to try the STL.

If you speak spanish, or at least you understand it, and you aren’t aquainted with FreeRTOS, you might want to take a look to my free course on “Arduino en tiempo real” (Arduino in real time).

If you like this post please drop me a line to say “Hello!”. Or better off, suscribe!

The Molcajete Project 0.9.3 is here!

I’m proud to announce that The Molcajete Project 0.9.3 is ready to be downloaded.

What the heck is The Molcajete Project?

It’s my attempt to bring together Arduino, FreeRTOS, and the availability of compiling from the command line. This project can convert your Arduino into real time!

This release mainly comprises the lastest versions of Arduino (1.8.13) and FreeRTOS (202012.00).

It’s available for Linux and Windows, and you can create your programs in both sketches and the command line (my prefered one).

Give it a try!

https://sourceforge.net/projects/molcajete/

The Molcajete project: where Arduino meets FreeRTOS

It’s ready the bundle in which you can program your Arduino apps using the FreeRTOS kernel. You can download it from

The Molcajete project

The Molcajete project by Xavier R

OVERVIEW

The aim of this project is to program Arduino apps using the world’s most downloaded real time kernel, FreeRTOS, in a terminal through Arduino-mk makefile.

In fact, this project is kind of a FreeRTOS port for the ATMEGA328 chip so that it can be used in the Arduino platform. The client of this project MUST provide the several hooks that FreeRTOS needs to run. It’s assumed that the client has knowledged of what FreeRTOS is about, and in general, what a kernel is, and why and how it’s used.

Two examples are provided: The classical one that implements two tasks in a dynamic fashion in order to test the system. The other example does the same but with static tasks.

This port uses the standard malloc and free implementations for AVR when using dynamic tasks. For reliable embedded systems it’s recommended not to abuse in the creation and deletion of tasks in the heap. If the client chooses this way it’s recommended to create all tasks at once at the global space and to avoid any task deletion. Even better if static tasks are used as they are already enabled in the kernel (since FreeRTOS 9.0).

The system tick is fixed at 1 millisecond after the AVR’s T0 timer. Tickless and/or low-power modes have not been tested yet.

SKETCHES

I don’t provide any sketches. Everything I do I do it in a terminal. But you can contribute with some!

OPERATING SYSTEM

For now this project only works on 64 bits Linux boxes. Later I’ll try to create a patch file so you can apply it to your already Arduino installation.

CONFIGURATION

Two files need to be edited in order to configure each project:

  1. Makefile. There is one makefile per project. In this the client configures stuff related to Arduino.
  2. FreeRTOSconfig.h. There is one FreeRTOS configuration file for ALL projects. In this the client configures stuff related to FreeRTOS. Having one global FreeRTOS configuration file is a bad idea. However, due to my lack of knowledge about paths and makefiles, this undesired behavior is all I have by now. Hope to fix it ASAP.

STACK SIZE

The stack size assigned to each task depends on many factors. FreeRTOS includes some schemes so that the client can choose the number that fits the most each project’s tasks. Such a number can also be chosen by heart, provided a good understanding of the task’s job.

As mentioned earlier, the RAM should be asked for and given globally at the very start of the application. This is also true for static tasks. If dynamic tasks are used, then it’s recommended not to delete them; the heap might fragment and such situation isn’t good for reliable embedded systems.

PRIORITY LEVELS

Priority levels in FreeRTOS are implemented using a linked list. The more the priority levels, the more RAM it uses and more time is spent looking for the next task. Some projects use one priority level per task, while others share a priority level among several tasks. It depends totally on the project requirements.

I’ve written an article on how three priority levels fulfill the needs for a small systems using event-driven programming. The article can be reached at: https://bit.ly/2qoE2Kn

HOOKS

Some hook functions might be required for each project. The client MUST provide them. The number of hook functions, if some, depends on the FreeRTOS configuration.

MODIFIED FILES

I modified some files so that FreeRTOS can work along Arduino. If you are interested in study them, these are the files:

  • FreeRTOS side
    • port.c
  • Arduino side
    • wiring.c

Side effects

I’ve modified a little bit the optiboot bootloader, and this change is included in the bundle. If you don’t use it, then you have nothing to worry about. If you’re planning to burn the bootloader into your chips, you might want to check my modifications.

LICENSES

Each entity in this project might have its own licensing scheme. For details visit:

CONTRIBUTIONS

All contributions are welcome: examples, sketches, testing in boards other than UNO or Leonardo, testing in OSs other than Linux, etc. I’m NOT asking for money. I’ve done this little contribution due to my love for embedded systems and my beliefs on the open source and open hardware movement.

You can reach me at fjrg76 at hotmail dot com and ingenieria at fjrg76 dot com.

Non-blocking ADC converter class

The built-in analogRead() function blocks until the conversion is ready; for most scenarios this behavior is ok. However, for a high-responsive systems such behavior would be unacceptable.

Another reason I wrote this small non-blocking class is because I bought a lcd-keypad shield, and then I built some improved clones:

IMAG1112
Commercial LCD-keypad shield.

IMAG1110
My improved clon: it includes a relay, a buzzer and a screw terminal for the power supply. Besides, the distance among push-buttons is larger.

The keypad is implemented through an analog ladder, where each push-button is a rung. Tipically any keypad is decoded through a state machine driven inside the system-tick ISR for debounce purposes. That’s way I cannot use the analogRead() function. My class allows me to handle the conversion inside the system-tick ISR without blocking it. Weeee! You might discover other useful applications for a non-blocking conversions. I will glad to hear them!

Source code

My class is totally based upon the analogRead() source code. Moreover, I’ve left the original comments. What I did was to split it out in 3 stages: start, observe and read. Although my goal is to use a templetized class, this version works well  for now for most purposes.

// Analog.hpp

/*Copyright (C) 
 * 2018 - fjrg76 at hotmail dot com
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 * 
 */

#ifndef  ANALOG_INC
#define  ANALOG_INC

#include <Arduino.h>
#include "wiring_private.h"
#include "pins_arduino.h"

extern uint8_t analog_reference;

class Analog
{
public:
	explicit Analog( uint8_t _pin );
	void Start();
	bool IsReady() const;
	int Read() const;

private:
	uint8_t pin;
};


#endif   /* ----- #ifndef ANALOG_INC  ----- */

// Analog.cpp

#include "Analog.hpp"

Analog::Analog( uint8_t _pin ) : pin{ _pin }
{
	pinMode( this->pin, INPUT );
	digitalWrite( this->pin, LOW );

#if defined(analogPinToChannel)
#if defined(__AVR_ATmega32U4__)
	if (pin >= 18) pin -= 18; // allow for channel or pin numbers
#endif
	pin = analogPinToChannel(pin);
#elif defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
	if (pin >= 54) pin -= 54; // allow for channel or pin numbers
#elif defined(__AVR_ATmega32U4__)
	if (pin >= 18) pin -= 18; // allow for channel or pin numbers
#elif defined(__AVR_ATmega1284__) || defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega644__) || defined(__AVR_ATmega644A__) || defined(__AVR_ATmega644P__) || defined(__AVR_ATmega644PA__)
	if (pin >= 24) pin -= 24; // allow for channel or pin numbers
#else
	if (pin >= 14) pin -= 14; // allow for channel or pin numbers
#endif

#if defined(ADCSRB) && defined(MUX5)
	// the MUX5 bit of ADCSRB selects whether we're reading from channels
	// 0 to 7 (MUX5 low) or 8 to 15 (MUX5 high).
	ADCSRB = (ADCSRB & ~(1 << MUX5)) | (((pin >> 3) & 0x01) << MUX5);
#endif

	// set the analog reference (high two bits of ADMUX) and select the
	// channel (low 4 bits).  this also sets ADLAR (left-adjust result)
	// to 0 (the default).
#if defined(ADMUX)
#if defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
	ADMUX = (analog_reference << 4) | (pin & 0x07);
#else
	ADMUX = (analog_reference << 6) | (pin & 0x07);
#endif
#endif
}


void Analog::Start()
{
	// without a delay, we seem to read from the wrong channel
	//delay(1);

#if defined(ADCSRA) && defined(ADCL)
	// start the conversion
	sbi(ADCSRA, ADSC);
#endif
}

bool Analog::IsReady() const
{
#if defined(ADCSRA) && defined(ADCL)
	// ADSC is cleared when the conversion finishes
	return !bit_is_set( ADCSRA, ADSC );
#else
	// we don't want to get stuck
	return true;
#endif
}


int Analog::Read() const
{
	uint8_t low, high;

#if defined(ADCSRA) && defined(ADCL)
	// we have to read ADCL first; doing so locks both ADCL
	// and ADCH until ADCH is read.  reading ADCL second would
	// cause the results of each conversion to be discarded,
	// as ADCL and ADCH would be locked when it completed.
	low  = ADCL;
	high = ADCH;
#else
	// we dont have an ADC, return 0
	low  = 0;
	high = 0;
#endif

	// combine the two bytes
	return (high << 8) | low;
}

Example

Just a little example:

#include <Arduino.h>
#include "Analog.h"

int main(void)
{
	init();

	Analog a0( A0 );

	while( 1 )
	{
		a0.Start();
		while( not a0.IsReady() );
	        auto read = a0.Read();
                // do something with the reading
        }
}


Greetings!

System-tick for the Arduino platform

A real embedded system needs a system-tick, and Arduino doesn’t have one … No, the yield() function is not a real system-tick, so I’ve swimmed a little bit into the code so that I get a real one. This is my progress.

In the file hooks.c we need to add a place holder for our callback. Add this code at the end of such file:

static void __empty2() { 
 // Empty 
}
void tick_hook(void) __attribute__ ((weak, alias("__empty2")));

Then we need to declare our callback in Arduino.h. Look for the line

void yield(void);

and write after it:

void tick_hook( void );

It’s supposed that our callback is going to be called in every system tick, so look for timer 0 ISR function in wiring.c:

#if defined(__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
ISR(TIM0_OVF_vect)
#else
ISR(TIMER0_OVF_vect)
#endif
{

and write at its end the call to our callback:

tick_hook();

Finally, we need to write the body of our callback. My first attempt was to determine the tick period, so in the main source code I wrote:

void tick_hook()
{
  static bool ledState = false;
  // should exist a toggle function? Hope so!

  if( ledState ) {
    digitalWrite(LED_BUILTIN, HIGH);
    ledState = false;
  }
  else {
    digitalWrite(LED_BUILTIN, LOW);
    ledState = true;
  }
}

The result was that our callback is called every one millisecond, as seen in the next image:

IMAG1056

From here we can do whatever is required in our application using the system-tick. Just remember, any code inside an ISR should be as short and fast as possible.

Update: A complete example!

/*Copyright (C)
 * 2018 - fjrg76 at hotmail dot com
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 */

//----------------------------------------------------------------------
//  Class: HeartBeat
//----------------------------------------------------------------------
template<const size_t Ticks, const uint8_t Pin>
class HeartBeat
{
public:
	HeartBeat();
	void Toggle();

private:
	bool pinState;
	size_t ticks;
};

template<const size_t Ticks, const uint8_t Pin>
HeartBeat<Ticks, Pin>::HeartBeat() : pinState{ false }, ticks{ Ticks }
{
	pinMode(Pin, OUTPUT);
}

template<const size_t Ticks, const uint8_t Pin>
void HeartBeat<Ticks, Pin>::Toggle()
{
	--ticks;
	if( ticks == 0 ) {
		ticks = Ticks;

		pinState = pinState ? false : true;
		digitalWrite( Pin, pinState );
	}
}

HeartBeat<500, LED_BUILTIN> heartBeat;

//----------------------------------------------------------------------
//  Our callback
//----------------------------------------------------------------------
void tick_hook()
{
	heartBeat.Toggle();
	// the built-in led is toggled every 500 milliseconds
}

void setup()
{
	// empty
}

void loop()
{
	// empty
}

In the console write:

make 
make upload

Greetings!

Building a solution from the command line

The Arduino’s IDE is just awful. Period. To write sketches is awful as well. Scketches make me sick. Hopefully we can build our solutions from the command line, directly from the main() function, even using our favorite code editor, VIM for me. A more-in-depth tutorial on this topic can be found here.

Installing Arduino

We need to download the latest Arduino‘s release, as for this writing, 1.8.5. Decompress it wherever you want. Then enter into the uncompressed folder and install it with:

./install.sh

Note: Uninstall any previous installation. Older releases will work,  but they won’t compile my tweakings.

Installing Arduino.mk

In order for me to work I’d rather install this tool from the Linux Mint repositories:

sudo apt install Arduino.mk

I guess that’s all for the needed tools. If any missing I’ll update this section.

Makefile for our solution

We need to write a Makefile for each of our projects:

ARDUINO_DIR = /your/home/dir/arduino-1.8.5
BOARD_TAG = uno
ARDUINO_PORT = /dev/ttyUSB*
ARDUINO_LIBS =
CFLAGS_STD = -std=c99 -Wall
CXXFLAGS_STD = -std=gnu++11 -Wall -DUNO
include /usr/share/arduino/Arduino.mk

The flag ‘-DUNO’ isn’t needed at all, but as I’m using the Leonardo and Uno boards, I need it to make the difference between them as easy as possible.

Test it!

Before we can upload our executable we must add our user to the dialout group so the serial port is available to us as humans:

$sudo adduser <your_user> dialout

Then log out and log in into your session again.


Last step is to write a test program. Let’s call it main.cpp:

#include <Arduino.h>

int main(void)
{
 init();
 // initialize the Arduino's environment

 pinMode(LED_BUILTIN, OUTPUT);

 while( 1 )
 {
   digitalWrite( LED_BUILTIN, LOW );
   delay( 100 );
   digitalWrite( LED_BUILTIN, HIGH );
   delay( 100 );
 }

 return 0;
}

For compiling write in the console:

make

and for uploading it:

make upload

And the serial monitor?

We have two options:

  • The arduino.mk includes one: make monitor (to exit ctrl-a + ctrl-k).
  • But if you don’t like it, then you might use one of the many serial monitors available in Linux. I like GtkTerm because you can close and open the USB port on demand (for when you’re going to upload the code, for example) without leaving it.

Greetings!

Building the Optiboot bootloader

The optiboot source code blundled along the Arduino package is out of date; it means, it doesn’t compile with the newer GCC releases. I got this similar situation, while trying to build my own release as of march 2018 (AVR-GCC 4.9). As it was suggested in the post I got the latest release on github. And it worked.

After that I wanted to customize the optiboot for my own boards, in which the reset line isn’t driven by my home-made USB-TTL adapter (and other commercial adapters). So I made two changes to the project: I made the led to blink 5 times, and let the board to wait longer than a second before the bootloader resets itself (one second wasn’t enough to sync the PC and the board). For this the optiboot.c and Makefile files must be edited.

Changes to optiboot.c

Look for this line (as for this writing it’s the line 509):

watchdogConfig(WATCHDOG_1S);

I changed it to:

watchdogConfig(WATCHDOG_8S);

It gives me enough time to sync. From lines 319 to 330 you can see other options.

Changes to Makefile

Look for the line 165 and set the number of flashes that you want. I chose 5:

LED_START_FLASHES_CMD = -DLED_START_FLASHES=5

Building the solution

Open a console in the same location as the files and type:

make clean
make atmega328

Depending on where you uncompressed or downloaded the Optiboot github project this can be the last step, but not for me.

Copying the optiboot_atmega329.hex file

In order not to break things, I decompressed the project in a new folder called Optiboot-master. So after the solution was build, I copied such a file into the original optiboot folder so the IDE is able to find it whenever I upload the bootloader to the chip. A softlink seems to be a better workaround.

Test

I tested all this mess using a USBAsp V2.0 programmer and a home-made chip place-holder, as shown:

IMAG1067

Open a terminal in ~/arduino-1.8.5/hardware/arduino/avr/bootloaders/optiboot and type:

$ avrdude -p m328p -P usb -c USBasp -e -U flash:w:optiboot_atmega328.hex -Ulock:w:0x3F:m -Uefuse:w:0xfd:m -Uhfuse:w:0xde:m -Ulfuse:w:0xff:m -C/usr/share/arduino/hardware/tools/avrdude.conf

and an output’s extract is shown as well:

Captura de pantalla de 2018-03-11 00-23-01

Greetings!

 

Introduction

The primary goal of this blog is to keep track of my advances on using Arduino in a proffesional way; of course I hope all that info is useful for you.