65.9K
CodeProject 正在变化。 阅读更多。
Home

在 ESP32 上运行 C 语言解释器

starIconstarIconstarIconstarIconstarIcon

5.00/5 (9投票s)

2019 年 5 月 31 日

MIT

9分钟阅读

viewsIcon

30719

downloadIcon

813

详细介绍在 ESP32 上运行的带有浏览器界面的 C 语言解释器的实现和使用

TFT Panel Screen Shot

引言

我曾花了大量时间在 Arduino IDE 中等待代码编译和上传,结果却发现代码的行为并非如我所愿。然后我开始考虑在代码中添加 Serial.printx 语句,再次等待编译和上传,并绞尽脑汁地试图获得我想要的结果。有什么比在程序中放入一个 if (j==3 && k>11) Debug(); 语句,并在变量满足条件时获得程序的调试信息更好的呢?

因此……我为 ESP32 板卡构建了一个 C 语言解释器,它允许我使用其内置文件系统进行持久化程序存储,并在浏览器页面中编辑和解释我的程序,而无需编译和上传周期。更棒的是,我还内置了对常见的 Arduino I/O 函数的支持,例如 analogWritedigitalRead/Write,并提供了高精度的伺服电机定位函数。但也许最棒的部分是,我在解释器中添加了一个调试器,它支持条件 Debug 跟踪和条件 Watch 函数,可以在程序执行时报告变量值。本文将归功于那些构建了我用于解释器的工具的人,并解释如何将其在您的 ESP32 上使用。

背景

关于解释型代码与编译型代码的优缺点一直有持续的讨论。这里只需说明,解释型代码的运行速度会比编译型代码慢,而且许多解释器提供的错误描述并不理想。但解释器以其作为原型开发工具的价值而闻名,并提供了一个快速周转的代码环境。另一个有用的解释器特性是它能轻易地向新手程序员介绍如何创建有用的程序。

Zik Saleeba 创建了 picoc 解释器,其源代码可在 https://gitlab.com/zsaleeba/picoc 获取。它为 C 语言解释器打下了坚实的基础,我向他为之创造表示敬意。

Web 界面的很大一部分灵感来源于 Michael Molinari 为他的 esp8266 Basic 编写的代码,并且大量借鉴了他的作品,该代码可从 https://github.com/esp8266/Basic 获取。

Using the Code

文章顶部的参考文献是指编译和上传解释器到您的 ESP32 所需的文件。当您在 Arduino IDE 中看到它时,您首先注意到的一点是,我打赌您从未见过一个具有如此多标签页的 Arduino 草图。这在很大程度上是为了在 Arduino IDE 中保留 picoc 解释器的文件结构,并简化 C 和 C++ 代码部分的隔离。好消息是,这个草图提供了对 WebServer Arduino 对象的非常彻底的实践,让您完全透明地了解解释器的内部机制,说明如何在 Arduino IDE 中使用 C 语言代码,并且可以提供无数小时的代码浏览时间来了解这一切是如何工作的。

但是,我希望您不是真的想了解 C 解释器的内部机制。我认为您阅读本文是因为您想探索在您的 ESP32 上运行 C 解释器。解释器的使用将在接下来的段落中进行说明。如果您想了解更多细节,可以查阅 ESP32_picoc_C_Language_Interpreter.pdf

在将草图上传到您的 ESP32 板之前,请使用 Tools/ESP32 Sketch Data Upload 菜单项将草图文件夹内的 data 目录的内容上传到您的 ESP32。您还需要编辑 ESP32Program/data/data/WIFIname.datWIFIpass.dat 文件,以匹配您的 WiFi 环境,然后再上传。当您将 ESP2Program 草图上传到您的 ESP32 板时,它将以 500,000 波特率在串行端口上运行。在板卡复位后查看串行监视器将为您提供板卡正在使用的 IP 地址。zip 文件中包含的 ESP32Program.ino 文件在其开头有一些重要的 #define 语句。

#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

特别重要的是 ALWAYS_STATION 定义,因为它会导致 ESP32 无法连接到 /data/WIFIname.dat 文件中指定的 SSID 时持续重启。或者,您可以将 WIFIname.dat 设置为空行,注释掉 ALWAYS_STATION 定义,ESP32 将作为接入点运行,默认名称为 ESP32PICOC

一旦您连接到 ESP32(无论是作为 Station 使用其 IP 地址,还是连接到接入点 ESP32PICOC 并使用地址 192.168.4.1),您就可以使用管理工具栏中的设置按钮来更改许多配置选项。管理工具栏如下图所示。

Admin Toolbar

您可能已经注意到上面代码片段中的一些被注释掉的定义。TFT、SSD1306OLEDNEO_PIXELePAPER 的定义在那里,是为了允许您包含对 320x240 像素 TFT 彩色显示屏、128x64 OLED 显示屏、200x200 像素 EPaper 显示屏的支持,或者包含对 NEO_PIXEL 支持的 LED 灯带的支持。

大部分解释器函数通过文件管理器按钮访问,文件管理器页面如下图所示。

File Manager Page

文件管理器页面的左侧列出了您 ESP32 上的文本类型文件,示例草图包含许多说明性程序,展示了部分解释器功能。您可以通过双击文件名在编辑器中打开它来开始操作,或者通过选择一个文件名并单击“编辑”或“运行”按钮来开始操作。编辑器页面提供了一个带有剪切和粘贴所见即所得编辑功能的 textarea。下面是一个示例编辑器页面,请注意,仅当 textarea 内的程序发生更改时,“保存”按钮才会显示。

Editor Page

通过单击文件管理器页面或编辑器页面上的“运行”按钮来解释程序。下图展示了 default.c 程序的示例运行。仅当使用 TFT 定义编译解释器草图时,“屏幕截图”按钮才会显示。

Run Program Page

如果您注意细节,您可能会注意到我已经内置了对 % 格式说明符的支持,这可以打印整数或长整型(它们在解释器中大小相同),并在三位数字组之间添加逗号。本文接下来要重点介绍的是错误报告。default.c 程序已被编辑,以调用名为 aatoi 的函数而不是 atoi,后者是一个正确的函数调用。运行程序后,结果显示如下。

Run Program Page with Error

解释器会在错误运行页面停留 30 秒,让您吸收错误的严重性,然后返回到编辑器。在此期间,ESP32 会被重置,以消除解释器检测到错误后的内存恢复和清理问题。

文章接下来要讨论的是一个示例调试会话,展示了递归程序的跟踪输出。要运行的程序是一个简单的递归算法,用于计算从 n 到 1 的数字之和。其代码如下,并附带行号以便阅读接下来的调试跟踪信息。

   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 }

在解释器中运行 sums.c 程序会产生以下输出。

sums.c Debug Output

检查跟踪输出,可以看到调试器显示了解释器将要执行的下一个操作的行号和列号,并在显示的程序行上突出显示该字符位置。程序输出显示在前面,后面带有 ->,任何被监视的变量都显示在第二列,其中包含其类型和值。跟踪解释很简单,Debug 输出在调用 Debug() 后开始,显示第 5 行。然后您会看到重复的第 2-5 行组,其中 top 的值在每次调用 sums 函数时都会减小。Watches 会显示全局变量和局部变量,变量搜索会优先显示局部变量。在此情况下,top 是 sums 函数的局部变量。

关于管理工具栏的一些附加项。尚未讨论代码模板和 picoc 函数帮助按钮。代码模板按钮会创建一个名为 deafultn.c 的文件,其中 n 在每次创建新模板时递增。该文件的内容如下。

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();
}

正如 Arduino 草图作者习惯于 setup()loop() 函数一样,该模板声明了这些函数并为函数体提供了占位符。for (;;) loop(); 行被注释掉了,因为您可能不希望您的程序永远运行。但如果您想要一个连续运行的 loop() 函数,请取消注释该行。正如注释所述,如果您在 loop 函数中包含 doLoop() 调用,解释器将有机会运行其 loop() 函数并处理 Web 请求等。main 函数是 C 编程的通用函数,Arduino IDE 中也有一个隐藏的 main 函数。您可以像处理任何其他 C 程序一样向 main 函数传递参数,解释器随附的程序示例说明了如何从程序参数中获取信息。此外,设置页面允许您将解释器设置为在启动时运行 default.c,因此您可以编写一个程序,将其重命名为 default.c,然后在 ESP32 重启后并完成必要的 WiFi 设置后,它就会运行。

picoc 函数帮助按钮提供对解释器随附的 defs.txt 文件的交互式搜索。支持通配符搜索,这为您提供了解释器内置函数的使用交互式视图。您可以编辑 defs.txt 文件来添加自己的函数或为 CLibrary 标准函数(如 atoi)添加行,以帮助您创建正确的代码语法。

最后,请注意解释器在启动时加载 /usefull.h 的内容。在 zip 文件中交付时,它只包含 16 位 rrrrggggggbbbbb 颜色值的定义,格式为 oxnnnn。但是,您可以添加任何语法正确的代码来为常用代码设置舞台。假设您创建了一个世界上最好的函数,并希望将其包含在几乎所有程序中,请将函数体添加到 usefull.h 文件中,它将可用在您所有的程序中使用,而无需进一步的麻烦。

关注点

在测试解释器时,我引入了两个包含递归的文件。一个简单的快速排序实现,一个不区分大小写的快速排序用于在解释器中对文件管理器文件列表进行排序,因此我认为它也将是一个很好的程序示例。我还包含了一个 sums.c 程序,它使用递归来计算从给定 n 到 1 的数字之和。令我惊讶的是,它们都导致了解释器崩溃。奇怪的是,它们在 Windows 或 Linux 上的 picoc 实现中运行正常。经过大量的 Google 搜索和一些困惑后,我发现 Arduino IDE 的 ESP32 实现使用了非常小的堆栈分配,而这导致了崩溃。因此,要成功运行这些递归程序,您需要编辑 ..\ArduinoData\packages\esp32\hardware\esp32\1.0.2\cores\esp32\main.cpp 文件中的代码,并进行如下粗体显示的更改。

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

历史

  • 2019 年 5 月 30 日:首次发布
© . All rights reserved.