Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / IoT / Arduino

Run a C Language Interpreter on Your ESP32

5.00/5 (9 votes)
31 May 2019MIT9 min read 29.7K   771  
Details the Implementation and use of a C Language Interpreter with a browser Interface running on an ESP32

TFT Panel Screen Shot

Introduction

I've spent considerable time in the Arduino IDE waiting for the sketch to compile and upload only to discover that the sketch did not behave as I expected and to then consider where to add Serial.printx statements to the sketch, wait again for compile and upload, and lose hair trying to get the results I desired. What could be better than putting an if (j==3 && k>11) Debug(); statement into your program and to be able to get debug information out from the program when the variables meet the conditional requirements?

So... I put together a C language interpreter for the ESP32 based boards that allows me to use their built-in file system for persistent program storage, to edit and Interpret my programs within a browser page without needing a compile and upload cycle. Better yet, I built in support for the common I/O Arduino functions such as analogWrite, digitalRead/Write, and supplied highly accurate servo positioning functions. But maybe the best part is that I added a debugger to the Interpreter that supports conditional Debug tracing, and conditional Watch functions that report variable values as the program executes. This article gives credit to those who built tools that I used for the Interpreter and also explains how to use it on your ESP32.

Background

There has been continuing discussion of Interpreted versus Compiled code and the virtues and disadvantages of each approach. Suffice it to say that interpreted code will run more slowly than compiled code and that many interpreters offer less than brilliant error descriptions. But the Interpreter is known for its value as a prototyping tool and provides a rapid turnaround code environment. Another useful Interpreter feature is the ease with which it introduces novice programmers to creating useful programs.

Zik Saleeba created the picoc Interpreter and its source is available from https://gitlab.com/zsaleeba/picoc. It is a solid foundation for a C Language interpreter and I tip my hat to him for its creation.

Much of the web interface is inspired by, and liberally copied from work by Michael Molinari for his esp8266 Basic available from https://github.com/esp8266/Basic.

Using the Code

The references at the top of the article are for the files needed to compile and upload the Interpreter to your ESP32. The first thing that you will notice when you have it in the Arduino IDE is, I bet, that you have never seen an Arduino sketch with so many tabs. Much of this is due to keeping the file structure of the picoc interpreter in place within the Arduino IDE, and to simplify the segregation of C and C++ code sections. The good news is that the sketch provides a very thorough exercise of the WebServer Arduino object, a completely transparent look into the bowels of an Interpreter, illustrates how to use C language code within the Arduino IDE, and can provide countless hours of code browsing to see how this all works.

But, I hope that you really aren't here to understand the internals of a C Interpreter. I think that you are reading this because you want to explore running the C Interpreter on your ESP32. The Interpreters use is documented in the following paragraphs. If you want even more detail, you can consult ESP32_picoc_C_Language_Interpreter.pdf.

Before you upload the sketch to your ESP32 board, use the Tools/ESP32 Sketch Data Upload menu item to put the contents of the data directory within the sketch folder onto your ESP32. You will also need to edit the ESP32Program/data/data/WIFIname.dat and WIFIpass.dat files before their upload to match your WiFi environment. When you have uploaded the ESP2Program sketch to your ESP32 board, it will be running on the Serial port at 500,000 baud. Consulting the Serial Monitor after a board reset will give you the IP address that the board is using. The ESP32Program.ino file contained in the zip file has a few #define statements at its beginning that are important.

C++
#define ALWAYS_STATION
// define ALWAYS_STATION to restart EPS32 if it can't connect to the specified WIFIname
//                       When defined it will not enter AP mode on a Station Connect
//                       failure. It will restart until it successfully connects
//                       to the SSID specified in /data/WIFIname.dat file, using the
//                       password specified in /data/WIFIpass.dat
//#define TFT
// define TFT to enable use of an attached 320x240 TFT Display
//#define SSD1306OLED
// define SSD1306OLED if using the Wemos ESP32 WROOM with OLED
//#define BME280
// defining BME 280 will include support for BME280 sensor
//#define NEO_PIXEL
// defining NEO_PIXEL includes Adafruit_NeoPixel support functions;
//#define ePAPER
// defining ePAPER includes 1.54 in ePaper Display from WaveShare

Of particular importance is the ALWAYS_STATION define since it will cause the ESP32 to continuously restart if it cannot connect to the SSID specified in /data/WIFIname.dat file. Alternately, you can set WIFIname.dat to an empty line, comment out the ALWAYS_STATION define, and the ESP32 will run as an Access point named, by default, ESP32PICOC.

Once you have connected to the ESP32 either as a Station using its IP address, or connected to the Access Point ESP32PICOC and using address 192.168.4.1, you can use the Settings button in the Admin Toolbar to change many configuration options. The Admin Toolbar is shown below:

Admin Toolbar

You may have noticed in the code snippet above defines that there were commented out. Defines for TFT, SSD1306OLED, NEO_PIXEL, and ePAPER. Those are there to allow you to include support for a 320x240 pixel TFT color display, or a 128x64 OLED display, or a 200x200 pixel EPaper display, or to include support for NEO_PIXEL supported LED strings.

The majority of the Interpreter functions are accessed through the File Manager button, and the File Manager page is shown below:

File Manager Page

The left side of the File Manager page presents a list of the text type files on your ESP32 and the sample sketch includes a number of illustrative programs showing some of the Interpreter features. You get the action started by double clicking a file name to open it in the editor, or by selecting a file name and clicking either the Edit of Run buttons. The Editor page presents a textarea with Cut and Paste WYSWIG editing. A sample Editor Page follows, note that the Save button will not be shown unless a change is made to the program within the textarea.

Editor Page

A program is Interpreted by clicking the Run button on the File Manager page or the Editor Page. A sample run of the default.c program is shown in the following figure. The Screen Capture button will only be displayed if the Interpreter sketch is compiled with TFT defined.

Run Program Page

If you are paying attention to details, you may have noticed that I have built in support for a printf format specifier of %, This supplies printing of integers or longs (they are both the same size in the Interpreter) including commas between three digit groups. The next thing that will be highlighted in this article is error reporting. The default.c program has been edited to call a function named aatoi instead of atoi which would be a correct function call. When the program is run, the resulting display is shown below:

Run Program Page with Error

The Interpreter remains on the error run time page for 30 seconds to allow you to absorb the gravity of your error and then takes you back to the Editor. In the meantime, the ESP32 is reset to eliminate any problems with memory recovery and cleanup after the Interpreter detected the error.

The next thing to be discussed in the article is a sample debug session showing the output of a trace on a recursive program. The program to be run is a simple recursion algorithm to compute the sum of numbers from n to 1. Its code appears below with line numbers to help read the Debug trace information shown next:

C++
 1 int sums(int top)
 2 {
 3    if (top==1) return 1;
 4    Debug();
 5    return top+sums(top-1);
 6 }
 7 void main()
 8 {
 9    Watch("top");
10    int start=10;
11    int sum=sums(start);
12    printf("Sums of 1 to %d are %d\n",start,sum);
13    stopDebug();
14 }

Running the sums.c program in the interpreter results in the following output:

sums.c Debug Output

Examining the trace print out reveals that the debugger shows the line number and column number where the Interpreter will take its next action and highlights this character position on the displayed program lines. Program outputs are shown with a leading -> and any Watched variables are shown in a second column with their type and value. Trace interpretation is straightforward, Debug outputs start after a call to Debug() with the display of line 5. Then you see repeated line 2-5 groups with the value of top decreasing with each call to the sums function. Watches will show both global and local variables with variable searches giving Local variables preference. In this case, top is a variable local to the sums function.

Some additional items on the Admin tool bar. The Code Template and picoc Function Help buttons have not been discussed. The code template button creates a file named deafultn.c when n increases with each new template created. This file's contents are shown below:

C++
void setup();
void loop();
int main(int argc,char ** argv)
{
  setup();
  //for (;;) loop();
  //  Uncomment line above to run your loop() function forever.
  //  When running loop forever,
  //  you'll get no response to web events
  //  unless you include doLoop();
  //  in your interpreted loop() function
 return 0;
}

void setup()
{

}

void loop()
{
  doLoop();
}

As Arduino sketch authors are accustomed to setup() and loop() functions, the template declares these functions and provides placeholders for the function bodies. The for (;;) loop(); line is commented out since you may not want your program to be never ending. But if you want a continuously running loop() function, uncomment that line. As the comments note, if you include a doLoop() call in your loop function, the Interpreter will be given the opportunity to run its loop() function and service web requests among other things. The main function is common to C programming, there's one hidden in the Arduino IDE too. You can pass arguments to the main function as you would with any other C program and the program examples included with the Interpreter illustrate getting information from the program arguments. In addition, the settings page allows you to set the Interpreter to run default.c at startup so you could write a program, rename it to default.c and then have it run each time the ESP32 reboots and after needed WiFi setup is completed.

The picoc Function help button provides interactive search of the defs.txt file included with the Interpreter. Wild card searches are supported and this gives you an interactive view of the Interpreter's built-in functions for use in your code. You can edit the defs.txt file to add your own functions or to add lines for the CLibrary standard function such as atoi to assist in your creation of correct code syntax.

Finally, note that the interpreter loads the contents of /usefull.h at startup. As delivered in the zip file, it contains just definitions for 16 bit rrrrggggggbbbbb color values in oxnnnn format. But, you can add anything that is syntactically correct to set the stage for the code that you commonly write. Say you created the world's greatest function and want to include in nearly all your programs, add the function body to the usefull.h file and it will be available to use in all your programs without further fuss.

Points of Interest

When testing the Interpreter, I introduced two files with recursion. A simple implementation of quicksort, a case insensitive quicksort is used to sort the File Manager file list within the Interpreter, so I thought that it would also make a good program example. I also included a sums.c program that uses recursion to calculate the sum of numbers from a given n to 1. I found that, to my surprise, they both caused the Interpreter to crash. Oddly, they run fine on a Windows or Linux implementation of picoc. After some meaningful time with Google and some head scratching, I discovered that the ESP32 implementation for the Arduino IDE uses a very small stack allocation and that this was causing the crash. So to run these recursive programs successfully, you'll need to edit the code in your ..\ArduinoData\packages\esp32\hardware\esp32\1.0.2\cores\esp32\main.cpp file and make the change highlighted in bold below.

C++
extern "C" void app_main()
{
    loopTaskWDTEnabled = false;
    initArduino();
    xTaskCreateUniversal(loopTask, "loopTask", 1.5*16384, 
                         NULL, 1, &loopTaskHandle, CONFIG_ARDUINO_RUNNING_CORE);
}

History

  • 30th May, 2019: First released

License

This article, along with any associated source code and files, is licensed under The MIT License