体验c语言开发esp32开发板

发布于 更新于
c语言原始且直接

前言

我在去年《用 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.pyPORT参数了。

烧录(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

本章节的内容参考idf项目中的 examples/get-started/blink 目录。

没想到,只是为了点亮灯泡,就这么简单一件事情,也差点让我翻车。

首先需要引入一些额外的依赖文件,我们需要拷贝idf_component.ymlKconfig.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才行。