跳到主要内容基于ESP32-C3的RISC-V智能家居中控实战 | 极客日志C
基于ESP32-C3的RISC-V智能家居中控实战
用ESP32-C3开发板实现完整的RISC-V智能家居中控系统。硬件集成温湿度、光照、红外等传感器,通过FreeRTOS多任务调度。支持Wi-Fi MQTT上报数据上云、BLE本地控制,以及简易Web界面。加入TLS加密和OTA安全更新,兼顾低功耗设计。全过程覆盖裸机驱动、RTOS应用和网络通信。
基于ESP32-C3的RISC-V智能家居中控实战
市面上的智能家居中控大多用ARM或x86方案,成本高、功耗也大。RISC-V这种开源指令集,没有授权费,还能按需裁剪,挺适合电池供电的IoT设备。我折腾了一套基于ESP32-C3的中控系统,把硬件、固件、通信都跑通了,这里记录一下过程。
系统整体架构
主控用的是ESP32-C3-DevKitM-1,板载160MHz RISC-V单核,自带Wi-Fi和BLE 5.0,SDK也比较成熟。外设连了一堆传感器和执行器:
- DHT11 温湿度(GPIO8)
- BH1750 光照(I2C,SDA=GPIO5, SCL=GPIO6)
- HC-SR501 人体红外(GPIO9)
- 继电器(GPIO10)
- WS2812B 灯带(GPIO7)
传感器都从3.3V取电,DHT11记得加4.7kΩ上拉电阻。固件部分用FreeRTOS调度任务,分别跑传感器采集、控制逻辑、Wi-Fi上报和BLE服务。
开发环境
直接用ESP-IDF v5.3,官方对RISC-V支持很完善。装起来就几步:
sudo apt update
sudo apt install git wget flex bison gperf python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0
git clone -b v5.3 --recursive https://github.com/espressif/esp-idf.git
cd esp-idf
./install.sh
. ./export.sh
运行idf.py --version确认安装,应该输出ESP-IDF v5.3。
裸机驱动:先理解硬件
在上RTOS之前,我习惯先把外设的裸机驱动调通,这样出了问题知道是硬件还是调度的事。
DHT11(单总线)
DHT11的时序很严格,得靠精确延时。下面这个驱动用esp_timer_get_time测高电平长度来区分0和1,能跑,但偶尔会丢数据。实际项目里还是建议用ESP-IDF自带的dht组件,更稳。
#include "driver/gpio.h"
#include "esp_timer.h"
#define DHT11_PIN 8
void dht11_init() {
gpio_set_direction(DHT11_PIN, GPIO_MODE_OUTPUT);
gpio_set_level(DHT11_PIN, 1);
}
bool dht11_read(uint8_t* humidity, uint8_t* temperature) {
uint8_t data[5] = {0};
gpio_set_direction(DHT11_PIN, GPIO_MODE_OUTPUT);
gpio_set_level(DHT11_PIN, );
esp_rom_delay_us();
gpio_set_level(DHT11_PIN, );
esp_rom_delay_us();
gpio_set_direction(DHT11_PIN, GPIO_MODE_INPUT);
(gpio_get_level(DHT11_PIN) == );
(gpio_get_level(DHT11_PIN) == );
( i = ; i < ; i++) {
(gpio_get_level(DHT11_PIN) == );
t = esp_timer_get_time();
(gpio_get_level(DHT11_PIN) == );
dt = esp_timer_get_time() - t;
data[i / ] <<= ;
(dt > ) data[i / ] |= ;
}
(data[] == (data[] + data[] + data[] + data[])) {
*humidity = data[];
*temperature = data[];
;
}
;
}
0
18000
1
30
while
0
while
1
for
int
0
40
while
0
uint32_t
while
1
uint32_t
8
1
if
40
8
1
if
4
0
1
2
3
0
2
return
true
return
false
BH1750(I2C)
BH1750走I2C,封装比DHT11省心。连续高分辨率模式,读出两个字节换算成lux就行。
#include "driver/i2c.h"
#define BH1750_ADDR 0x23
void bh1750_init(i2c_port_t i2c_num) {
i2c_config_t conf = {
.mode = I2C_MODE_MASTER,
.sda_io_num = 5,
.scl_io_num = 6,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = 100000
};
i2c_param_config(i2c_num, &conf);
i2c_driver_install(i2c_num, conf.mode, 0, 0, 0);
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, BH1750_ADDR << 1, true);
i2c_master_write_byte(cmd, 0x10, true);
i2c_master_stop(cmd);
i2c_master_cmd_begin(i2c_num, cmd, 1000 / portTICK_PERIOD_MS);
i2c_cmd_link_delete(cmd);
}
float bh1750_read_lux(i2c_port_t i2c_num) {
uint8_t data[2];
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (BH1750_ADDR << 1) | I2C_MASTER_READ, true);
i2c_master_read_byte(cmd, &data[0], I2C_MASTER_ACK);
i2c_master_read_byte(cmd, &data[1], I2C_MASTER_NACK);
i2c_master_stop(cmd);
i2c_master_cmd_begin(i2c_num, cmd, 1000 / portTICK_PERIOD_MS);
i2c_cmd_link_delete(cmd);
uint16_t raw = (data[0] << 8) | data[1];
return raw / 1.2;
}
FreeRTOS分任务跑
裸机轮询没法同时搞定传感器读取、继电器控制和网络通信,必须上RTOS。这里简单创建两个任务:一个每5秒读传感器,另一个根据光照自动开关灯。
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "dht11.h"
#include "bh1750.h"
#include "nvs_flash.h"
void sensor_task(void* pvParameters) {
uint8_t hum, temp;
float lux;
while (1) {
if (dht11_read(&hum, &temp)) {
printf("Temp: %d°C, Hum: %d%%\n", temp, hum);
}
lux = bh1750_read_lux(I2C_NUM_0);
printf("Light: %.2f lux\n", lux);
vTaskDelay(pdMS_TO_TICKS(5000));
}
}
void relay_control_task(void* pvParameters) {
gpio_set_direction(10, GPIO_MODE_OUTPUT);
while (1) {
float lux = bh1750_read_lux(I2C_NUM_0);
gpio_set_level(10, (lux < 50) ? 1 : 0);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void app_main(void) {
nvs_flash_init();
dht11_init();
bh1750_init(I2C_NUM_0);
xTaskCreate(sensor_task, "sensor", 2048, NULL, 5, NULL);
xTaskCreate(relay_control_task, "relay", 2048, NULL, 4, NULL);
}
烧录idf.py build flash monitor就能看到串口输出。
Wi-Fi与MQTT:数据上云
智能家居不联网就差点意思。ESP32-C3连Wi-Fi后用MQTT上报传感器数据,这里用的免费公共broker broker.emqx.io,测试可以,正式环境还是自己搭一个。
#include "esp_wifi.h"
#include "esp_event.h"
#include "nvs_flash.h"
void wifi_init_sta(void) {
nvs_flash_init();
esp_netif_init();
esp_event_loop_create_default();
esp_netif_create_default_wifi_sta();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
esp_wifi_init(&cfg);
wifi_config_t wifi_config = {
.sta = {
.ssid = "Your_SSID",
.password = "Your_PASSWORD",
},
};
esp_wifi_set_mode(WIFI_MODE_STA);
esp_wifi_set_config(WIFI_IF_STA, &wifi_config);
esp_wifi_start();
esp_wifi_connect();
}
#include "mqtt_client.h"
static esp_mqtt_client_handle_t client;
void mqtt_app_start(void) {
esp_mqtt_client_config_t mqtt_cfg = {
.broker.address.uri = "mqtt://broker.emqx.io",
.credentials.client_id = "riscv_home_controller_001"
};
client = esp_mqtt_client_init(&mqtt_cfg);
esp_mqtt_client_start(client);
}
void publish_sensor_data(float temp, float hum, float lux) {
char payload[100];
snprintf(payload, sizeof(payload), "{\"temp\":%.1f,\"hum\":%.1f,\"lux\":%.1f}", temp, hum, lux);
esp_mqtt_client_publish(client, "home/sensors", payload, 0, 1, 0);
}
用MQTT Explorer订阅home/sensors就能看到实时数据。
BLE本地控制
万一Wi-Fi挂了,BLE可以当备用通道。ESP32-C3的BLE 5.0建一个GATT服务,一个特征读传感器数据,另一个特征写继电器状态。
#include "esp_bt.h"
#include "esp_gap_ble_api.h"
#include "esp_gatts_api.h"
#define SERVICE_UUID 0xFFE0
#define CHAR_SENSOR_UUID 0xFFE1
#define CHAR_RELAY_UUID 0xFFE2
static uint16_t sensor_handle, relay_handle;
void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t* param) {
switch (event) {
case ESP_GATTS_REG_EVT:
esp_ble_gap_set_device_name("RISC-V Home Hub");
esp_ble_gatts_create_service(gatts_if, &service_uuid, 20);
break;
case ESP_GATTS_CREATE_EVT:
esp_ble_gatts_start_service(param->create.service_handle);
esp_ble_gatts_add_char(param->create.service_handle, &char_sensor_uuid, ESP_GATT_PERM_READ, ESP_GATT_CHAR_PROP_BIT_READ, NULL, NULL);
esp_ble_gatts_add_char(param->create.service_handle, &char_relay_uuid, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, ESP_GATT_CHAR_PROP_BIT_READ | ESP_GATT_CHAR_PROP_BIT_WRITE, NULL, NULL);
break;
case ESP_GATTS_READ_EVT:
if (param->read.handle == sensor_handle) {
char data[50];
snprintf(data, sizeof(data), "%.1f,%.1f,%.1f", temp, hum, lux);
esp_ble_gatts_send_response(gatts_if, param->read.conn_id, param->read.trans_id, ESP_GATT_OK, strlen(data), (uint8_t*)data);
}
break;
case ESP_GATTS_WRITE_EVT:
if (param->write.handle == relay_handle) {
gpio_set_level(10, param->write.value[0] ? 1 : 0);
}
break;
}
}
手机装个nRF Connect就能扫描设备、读写特征。
简易Web界面
再跑一个HTTP server,同一个Wi-Fi下的设备就能用浏览器控制,纯裸页,能用就行。
#include "esp_http_server.h"
static httpd_handle_t server = NULL;
esp_err_t sensor_get_handler(httpd_req_t* req) {
char resp[200];
snprintf(resp, sizeof(resp), "<html><body>"
"<h1>RISC-V Smart Hub</h1>"
"<p>Temperature: %d°C</p>"
"<p>Humidity: %d%%</p>"
"<p>Light: %.1f lux</p>"
"<a href='/relay?on=1'>Turn ON Light</a> | "
"<a href='/relay?on=0'>Turn OFF Light</a>"
"</body></html>", temp, hum, lux);
httpd_resp_send(req, resp, HTTPD_RESP_USE_STRLEN);
return ESP_OK;
}
esp_err_t relay_handler(httpd_req_t* req) {
char* buf = httpd_req_get_url_query_str(req);
if (buf) {
char val[10];
if (httpd_query_key_value(buf, "on", val, sizeof(val)) == ESP_OK) {
gpio_set_level(10, atoi(val));
}
free(buf);
}
httpd_resp_sendstr(req, "OK");
return ESP_OK;
}
void start_webserver(void) {
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
if (httpd_start(&server, &config) == ESP_OK) {
httpd_register_uri_handler(server, &(httpd_uri_t){
.uri = "/",
.method = HTTP_GET,
.handler = sensor_get_handler
});
httpd_register_uri_handler(server, &(httpd_uri_t){
.uri = "/relay",
.method = HTTP_GET,
.handler = relay_handler
});
}
}
加点安全
MQTT明文传很不安全,换成TLS端口mqtts://broker.emqx.io:8883,再把CA证书嵌进固件。
esp_mqtt_client_config_t mqtt_cfg = {
.broker.address.uri = "mqtts://broker.emqx.io:8883",
.broker.verification.certificate = (const char*)server_cert_pem_start,
};
OTA更新也要走HTTPS,校验服务器证书,防止固件被篡改。
esp_https_ota_config_t ota_config = {
.url = "https://your-server.com/firmware.bin",
.cert_pem = server_cert_pem_start,
};
esp_https_ota(&ota_config);
低功耗的一点考虑
要是用电池供电,必须开深度睡眠。定时唤醒后干活,干完再睡。代价是RAM全丢,重要状态得提前存RTC memory或Flash。
#include "esp_sleep.h"
esp_sleep_enable_timer_wakeup(10 * 60 * 1000000);
esp_deep_sleep_start();
收尾
这样一套下来,从硬件到固件、从本地到云端,RISC-V智能家居中控就跑通了。成本比ARM方案低不少,而且ESP-IDF生态很方便。后续可以加语音识别、Zigbee网关,或者接入HomeAssistant。代码全部开源,有兴趣的可以拿去改。
相关免费在线工具
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
- HTML转Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
- JSON 压缩
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
- JSON美化和格式化
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online