试玩微控制器开发:ESP32 初体验
以前在项目中有过几次需要涉及物联网的东东,但当时对微控制器开发这块完全不了解,以至于做方案时信心不足。
最新终于闲下来了,想试试这方面的开发,于是买了两块乐鑫 ESP32-C3 开发板玩
连接设备
- 运行
ls /dev/cu.*
查看电脑上当前连接的串口设备。 - 先按乐鑫官方文档安装好驱动,然后将开发板(我的是
ESP32-C3
)连接至电脑。
- 再次运行
ls /dev/cu.*
,多出来的那个设备就是开发板了。
然后就可以使用串口终端连接至开发板,可以使用 idf.py monitor
也可以使用第三方工具。 比如 MacOS 下可以使用 COMTool 这个工具连接进行连接。
初体验
安装乐鑫提供的开发框架 IDF , 注意选择支持自己设备的版本。
将 IDF 的代码拉到本地后,切换到自己设备对应的版本,运行 ./install.sh
脚本安装,然后运行 . ./export.sh
在当前终端会话中导出相关环境变量,然后就可以使用 idf.py
这个工具了。
IDF 有 VS Code 扩展,安装并配置扩展后很多操作就不用在终端里进行了。
从 IDF 仓库示例目录中拉个 hello world 示例过来。
编译代码:idf.py build
列出可编译的目标设备:idf.py --list-targets
设置编译到的目标设备: idf.py set-target esp32c3
(官方文档没提到这条,导致烧录到设备时报错,搜了好久没找到答案)
烧录到指定设备:idf.py -p /dev/cu.usbserial-210 -b 115200 flash
(可选参数 -b 115200
用于指定波特率)
烧录完成后设备会自动复位设备,此时我们可使用 COMTool
或者 idf.py -p /dev/cu.usbserial-210 monitor
查看设备的输出信息。
Arduino 支持
Arduino 为非专业嵌入式开发人员提供了一个更简单的开发环境,并且极其繁荣的生态系统,有大量第三方包可用(特别是各式各样的驱动),要在 Arduino 中进行 ESP32 芯片的开发,需要先添加第三方芯片管理器: https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_dev_index.json
然后就在切换开发板界面中找到对应的型号了。
搞个温度传感器测个温
我使用的是 DS18B20
温度传感器,商家提供的资料是不支持 ESC32-C3
的,主要是因为资料中使用的 OneWire
这个通信库不支持,幸运的是找到了 OneWireNg 这个开源替代库(支持的板子型号较多,目前提交比较活跃),并且该库与 OneWire
完全兼容。
#include <OneWireNg.h>
#include <OneWireNg_BitBang.h>
#include <OneWire.h>
#include <OneWireNg_Config.h>
#include <OneWireNg_CurrentPlatform.h>
int pin = 6;
//Temperature chip i/o
OneWire ds(pin);
void setup(void) {
Serial.begin(115200);
}
void loop(void) {
float temperature = getTemp();
Serial.print("当前温度:");
Serial.println(temperature);
delay(1500);
}
float getTemp() {
byte data[12];
byte addr[8];
if ( !ds.search(addr)) {
//no more sensors on chain, reset search
ds.reset_search();
return -1000;
}
if ( OneWire::crc8( addr, 7) != addr[7]) {
Serial.println("CRC is not valid!");
return -1000;
}
if ( addr[0] != 0x10 && addr[0] != 0x28) {
Serial.print("Device is not recognized");
return -1000;
}
ds.reset();
ds.select(addr);
ds.write(0x44, 1); // start conversion, with parasite power on at the end
byte present = ds.reset();
ds.select(addr);
ds.write(0xBE); // Read Scratchpad
for (int i = 0; i < 9; i++) { // we need 9 bytes
data[i] = ds.read();
}
ds.reset_search();
byte MSB = data[1];
byte LSB = data[0];
float tempRead = ((MSB << 8) | LSB); //using two's compliment
float TemperatureSum = tempRead / 16;
return TemperatureSum;
}
开发方式对比:Arduino、IDF、TinyGo、MicroPython、NodeMcu
目前我尝试着使用 Arduino、IDF、TinyGo 分别进行了一些类似功能的开发。
- Arduino: Arduino 无疑是最简单方便的,其繁荣的生态几乎可以在上面找到大部分驱动,遇到不懂的东西也很好查资料。
- ESP-IDF 乐鑫官方提供的
ESP-IDF
开发框架功能最为强大,对底层细节控制更多,当然这也会导致开发效率比 Arduino 明显慢很多。
- TinyGo:从开发体验上说,我个人更喜欢 TinyGo,兼顾了开发效率与运行效率(虽然它有一个GC),但 TinyGo 目前的成熟度还不够,对 ESP32 的支持不完整,生态也比较差,要是乐鑫能加大 TinyGo 对 ESP32 的支持就好了(比如公司员工参与项目开发)。
同时 - MicroPython: ESP32 也支持使用 MicroPython 进行开发,但我没尝试,也许对于完全不在意性能的 Py 玩家来说还是有些吸引力的。
- NodeMcu:乐鑫还搞了这么一套基于 Lua 的开发框架,这货看起来就像 MicroPython 一样,我感觉搞这东东的目的是不是只是为了“自主控制权”。
比如发起一个 http 请求并获取响应结果为例,对比以下 Arduino 和 IDF 两种不同实现方式:
Arduino:
#include <HTTPClient.h>
void httpGet(char* url ) {
HTTPClient http;
http.begin(url);
int httpCode = http.GET();
if (httpCode == 200) {
Serial.print("http.GET() done,httpCode:");
Serial.println(httpCode);
String payload = http.getString();
Serial.println(payload);
} else {
Serial.println("Error on HTTP request");
}
http.end();
}
IDF:
#include <string.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "esp_event.h"
#include "esp_netif.h"
#include "esp_crt_bundle.h"
#include "esp_http_client.h"
static const char *HTTP_TAG = "HTTP_CLIENT";
// 定义一个用于处理 http 事件的函数
esp_err_t _http_event_handler(esp_http_client_event_t *evt)
{
static char *output_buffer; // Buffer to store response of http request from event handler
static int output_len; // Stores number of bytes read
switch (evt->event_id) // 根据不同事件进行处理
{
case HTTP_EVENT_ERROR:
ESP_LOGD(HTTP_TAG, "HTTP_EVENT_ERROR");
break;
case HTTP_EVENT_ON_CONNECTED:
ESP_LOGD(HTTP_TAG, "HTTP_EVENT_ON_CONNECTED");
break;
case HTTP_EVENT_HEADER_SENT:
ESP_LOGD(HTTP_TAG, "HTTP_EVENT_HEADER_SENT");
break;
case HTTP_EVENT_ON_HEADER:
ESP_LOGD(HTTP_TAG, "HTTP_EVENT_ON_HEADER, key=%s, value=%s", evt->header_key, evt->header_value);
break;
case HTTP_EVENT_ON_DATA:
ESP_LOGD(HTTP_TAG, "HTTP_EVENT_ON_DATA, len=%d", evt->data_len);
break;
// ……
default:
ESP_LOGD(HTTP_TAG, "OTHER ERROR. (HTTP_EVENT_REDIRECT?)");
esp_http_client_set_header(evt->client, "From", "user@example.com");
esp_http_client_set_header(evt->client, "Accept", "text/html");
break;
}
return ESP_OK;
}
// http_get 主函数
static int http_get(char *path, char *query, char *ret_content)
{
esp_http_client_config_t config = {
.host = "example.com",
.path = path,
.query = query,
.event_handler = _http_event_handler,
.user_data = ret_content,
.disable_auto_redirect = true,
};
esp_http_client_handle_t client = esp_http_client_init(&config);
// GET
esp_err_t err = esp_http_client_perform(client);
if (err == ESP_OK)
{
printf("hhtp ok.\n");
ESP_LOGI(HTTP_TAG, "HTTP GET Status = %d, content_length = %d",
esp_http_client_get_status_code(client),
esp_http_client_get_content_length(client));
}
else
{
printf("hhtp fail.\n");
ESP_LOGE(HTTP_TAG, "HTTP GET request failed: %s", esp_err_to_name(err));
}
ESP_LOG_BUFFER_HEX(HTTP_TAG, ret_content, strlen(ret_content));
esp_http_client_cleanup(client);
return 0;
}