体验c语言开发esp32开发板
前言
我在去年《用 Jetbrains Gateway 体验嵌入式开发》这篇文章中,同时体验了”Jetbrains的远程开发能力”与”用Go开发esp32程序”两件事情,同时那也算是我首次接触嵌入式开发。
tinygo固然有很多优势,例如它与c对比的话可以说不仅好写而且生态足够丰富;但是具体在esp32这个平台上,它目前还不支持wifi能力,这是个极大的硬伤,因此我暂时没有再度深入探究它了。
一年后的今天重新再看,tinygo依然没有支持esp32的wifi,不过这次我有了新的目的:练习c语言开发,因此我又重新找出了这块esp32开发板,重新探索。
开发板的选择
esp32这块芯片,是拥有wifi和蓝牙连接能力的、经济实惠的芯片,经常可以作为入门者的首选。
以我手头这块ESP32-WROOM-32为例,淘宝到手价才21.5元,还不到一顿饭钱,简直就是忽略不计。
Espressif官方网站干净清爽,提供了许多学习资料和开发资料,对新人还是很友好的一点都不友好,看起来资料很多,但是我按它的步骤来做是走不通的。
尝试:c语言
hello_world
当前运行操作系统平台是windows11,注意所有命令行都需要在cmd下运行,不要与powershell混用。
首先需要安装 ESP-IDF(Espressif IoT Development Framework),这里不要偷懒去用它提供的安装器(Installer),因为我走不通;还是得自己下载源码安装。
release页面下载”esp-idf-v5.0.2.zip”,当前最新版本是v5.0.2,发布于7小时之前(2023-05-11)。
将下载的zip,也就是源码仓库解压到电脑任意目录下;
然后在操作系统-系统属性中设置环境变量,新建/编辑IDF_PATH的值为上述解压目录,也就是名为esp-idf-v5.0.2的文件夹的路径;
然后进入目录执行install.bat。
安装过程大概需要一两分钟,它会在你的~/.espressif目录下安装很多东西,包括一个python运行时(当前版本v3.10.1)。
安装之后尝试编译写入一个helloworld程序。找到esp-idf-v5.0.2\examples\get-started\hello_world文件夹,将它拷贝出来,然后用IDE打开。
我的IDE是CLion,在打开项目的时候,需要指定编译配置,此时点击进入”Toolchains”设置框中,新建一个Toolchain配置,点击”Add environment”,然后在”Environment file”输入框中输入esp-idf-v5.0.2\export.bat。随后CLion会立即运行这个脚本并重新检测编译工具链,当所有项目都显示绿勾就说明当前的工具链是有效的,点击Apply先应用当前设置。之后再去”CMake”设置页面中,选择”Toolchain”为刚才新建的那个项目,点击确定保存,随后CLion会开始扫描源码目录并构建源码索引,同时还会在当前项目目录下创建一个sdkconfig文件。之后就可以进行开发、编译了。(参考阅读:ESP-IDF - CLion)
接下来,可以自己修改源码,也可以直接编译运行。(参考阅读:Configure Your Project - espressif.com)
在hello_world目录下打开cmd.exe,首先需要手动运行esp-idf-v5.0.2\export.bat来让当前终端会话加载正确的环境变量,然后运行:
idf.py set-target esp32
然后可以编译并烧录了:
idf.py build
idf.py -p PORT flash
上面的PORT,不是网络编程中的IP协议的端口号,而是USB硬件设备模拟出来的总线端口。如何知道它的值?打开windows设备管理器,在”端口(COM和LPT)“页签下,只要你的开发板与pc通过正确的USB数据线相连了,它就应该出现在列表中。如果不确定是哪一个,就先拔了USB再插回去,观察一下是哪个消失并重新出现了。我的开发板显示的设备名称是”Silicon Labs CP210x USB to UART Bridge (COM3)“,后面这个COM3就是我们需要指定给idf.py的PORT参数了。
烧录(flash)结束之后,开发板会自动运行刚才写入的程序。原样的hello_world代码只有控制台输出,没有灯泡闪烁,因此必须通过工具来读取它的输出内容,运行命令:
idf.py -p PORT monitor
即可看到程序运行的输出内容,包括”Hello, world!”字符串在内的一堆文本内容。
如果想观察到灯泡闪烁,可以编译esp-idf-v5.0.2\examples\get-started\blink目录中的代码;或者自己写更多的逻辑来深度体验。
(注:esp-idf工具链似乎仅支持c语言,c++不受支持)
基础知识:c语言家族的工具链
参考阅读:知乎
简而言之:
gcc(g++)是编译工具minGW-w64是gcc移植到windows平台下的产物LLVM是另一种编译工具MSVC是微软提供的编译工具
CMake是构建工具,是帮助我们在工程规模变大之后更容易地编译的工具
c++语言本身也是有版本的,例如最近c++20就推出了模块化和协程的支持。c++是语言规范,是概念上的东西;而gcc是努力满足c++标准的编译工具,是现实存在的软件程序。gcc所支持的c++特性参阅:C++ Standards Support in GCC
minGW-w64本身也是有版本的,它的版本指的是它附带的工具链的版本,它并没有限定gcc的版本。例如在MinGW-w64 下载页面,可以看到”MingW-W64-builds”这个项目提供的是 12.2.0版本的GCC + 10.0.0版本的MinGW-w64 。
project from scratch
本章节的内容可以直接跳过,你可以直接拷贝idf项目中的 examples/get-started/sample_project 目录来快速搭建项目框架。
上面所展示的”hello_world”项目是idf仓库中附带的、由别人已经预设好的项目配置。为了搞清楚哪些东西是必要的,我接下来尝试从头开始,创建一个新目录,看看到底需要怎样的配置才能启动一个esp32工程项目。
新建一个目录,目录中至少需要如下结构:
+ project_from_scratch
+ main
- CMakeLists.txt
- main.c
- CMakeLists.txt
首先根目录的CMakeLists.txt文件中至少需要包含如下内容:
cmake_minimum_required(VERSION 3.16) # CMAKE版本要求不能高于3.24
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(project_from_scratch) # 这里project_from_scratch是你的工程名称
然后再到main这个子目录,它的名字必须是固定的(虽然与我们之前习惯的src不同),然后它里面的CMakeLists.txt也必须严格按要求来写:
idf_component_register(SRCS "main.c"
INCLUDE_DIRS "")
最后是我们的代码文件main.c,这个文件的名字可以任意,不过必须以.c作为后缀名,并且要与CMakeLists.txt中的配置相匹配。其中的内容也有要求:
#include "..." // 有些必须引入的头文件可以从hello_world项目里抄过来
void app_main(void) { // 函数名必须是app_main,它在预设的头文件中已经声明过了,我们需要实现它。
}
准备好了上述文件(并写一些代码)之后,我们就可以编译并烧录了,依次执行:
C:\......\esp-idf-v5.0.2\export.bat
idf.py set-target esp32
idf.py build
idf.py -p PORT flash
idf.py -p PORT monitor
blink
本章节的内容参考idf项目中的 examples/get-started/blink 目录。
没想到,只是为了点亮灯泡,就这么简单一件事情,也差点让我翻车。
首先需要引入一些额外的依赖文件,我们需要拷贝idf_component.yml和Kconfig.projbuild这两个文件,并修改main/CMakeLists.txt为:
idf_component_register(SRCS "blink_example_main.c"
INCLUDE_DIRS ".") # 这里增加了一个点.
重新运行CMAKE,然后等待IDE加载代码索引。
之后修改main/main.c文件,最简化的代码如下:
#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "sdkconfig.h"
#define BLINK_GPIO 2
static uint8_t s_led_state = 0;
void app_main(void) {
gpio_reset_pin(BLINK_GPIO);
gpio_set_direction(BLINK_GPIO, GPIO_MODE_OUTPUT);
while (1) {
gpio_set_level(BLINK_GPIO, s_led_state);
s_led_state = !s_led_state;
vTaskDelay(CONFIG_BLINK_PERIOD / portTICK_PERIOD_MS);
}
}
首先需要查询技术手册(不,从手册里是找不到LED灯接在哪个IO端口上的),我找到了以前用Golang写的esp32的代码,找到了我这块开发板上与LED灯泡对应的输出端口是GPIO2,因此我在上面的c代码中定义了一个字面量BLINK_GPIO 2。
后面的代码就很好理解了,首先初始化GPIO2端口,将其设置为输出模式(OUTPUT),然后进入一个无限循环,每次反转LED的电压值(0或1对应不亮或亮),然后睡眠1秒,循环……
wifi
(留个坑)
失败:Rust语言
根据官方教程的指引,我是根本走不通 HelloWorld程序的,折腾了一会儿之后只能遗憾宣告放弃。
结语
相对于火热的互联网技术栈,嵌入式的开发量应该只能说是九牛一毛,因此开发体验来说也明显差了不止一个档次。而在这些矮子之中,我甚至觉得tinygo反而可以当个将军,其次是万能的原生的c,最次才是看起来高大上的Rust。
因此在嵌入式这个领域,我依然可以得出结论:Rust目前还只是个玩具,真想要有战斗力还是得靠c才行。