编程技术分享

分享编程知识,探讨技术创新

0%

简介:该项目基于 ESP32-S3 构建,通过立创 EDA 进行设计的项目,该自动浇水设备能够自动连接网络,并能设置浇水的参数,如浇水间隔、浇水次数、浇水周期等。

好的,作为一名高级嵌入式软件开发工程师,我很乐意为您详细阐述基于 ESP32-S3 的自动浇水系统的代码设计架构,并提供具体的 C 代码实现。这个项目旨在展示一个可靠、高效、可扩展的嵌入式系统开发流程。
关注微信公众号,提前获取相关推文

项目背景与需求分析

正如您提供的图片所示,这是一个嵌入式自动浇水设备,基于 ESP32-S3 平台,并且使用了立创 EDA 进行硬件设计。根据项目描述,核心需求如下:

  1. 自动浇水功能: 设备能够按照预设的参数自动执行浇水操作。
  2. 参数配置: 用户可以设置浇水的参数,包括浇水间隔、浇水次数、浇水周期等。
  3. 网络连接: 设备需要连接到网络,以便进行时间同步、远程监控或潜在的远程控制(虽然描述中没有明确要求远程控制,但网络连接为未来扩展提供了可能性)。
  4. 用户界面: 通过屏幕(从图片看应该是触摸屏)提供本地用户界面,用于参数设置和状态显示。
  5. 可靠性和稳定性: 系统必须稳定可靠运行,确保浇水任务按计划执行。
  6. 高效性: 代码设计应高效,充分利用 ESP32-S3 的资源,并保证系统响应速度。
  7. 可扩展性: 系统架构应易于扩展,方便未来添加新功能,例如土壤湿度传感器检测、远程监控、智能浇水策略等。

代码设计架构

为了满足上述需求,并构建一个可靠、高效、可扩展的系统,我推荐采用分层架构的设计模式。这种架构将系统分解为多个独立的层,每一层负责特定的功能,层与层之间通过清晰的接口进行交互。这有助于提高代码的模块化程度、可维护性和可测试性。

我将系统架构设计为以下几个层次:

  1. 硬件抽象层 (HAL - Hardware Abstraction Layer):

    • 目的: 屏蔽底层硬件差异,为上层提供统一的硬件访问接口。
    • 功能:
      • GPIO 控制:控制 GPIO 引脚的输入输出,用于控制水泵、LED 指示灯等。
      • SPI 接口:驱动 LCD 屏幕和触摸屏控制器(如果使用 SPI 接口)。
      • I2C 接口:驱动传感器(例如土壤湿度传感器,如果添加)。
      • 定时器:提供定时功能,用于浇水定时、周期性任务等。
      • ADC 接口:读取模拟传感器数据(例如土壤湿度传感器,如果添加)。
      • PWM 接口:可能用于更精细的水泵控制(例如调节水流量,如果需要)。
    • 优势: 当硬件平台发生变化时,只需要修改 HAL 层代码,上层代码无需改动,提高了代码的可移植性。
  2. 设备驱动层 (Device Driver Layer):

    • 目的: 驱动具体的硬件设备,例如 LCD 屏幕、触摸屏、水泵、传感器等。
    • 功能:
      • LCD 驱动:初始化 LCD 屏幕,提供绘制文本、图形等 API。
      • 触摸屏驱动:初始化触摸屏控制器,处理触摸事件,提供触摸坐标信息。
      • 水泵驱动:控制水泵的开关,根据浇水指令执行浇水操作。
      • 传感器驱动:初始化传感器,读取传感器数据,进行数据处理和校准(如果需要)。
      • Wi-Fi 驱动:封装 ESP-IDF 提供的 Wi-Fi API,简化 Wi-Fi 连接、断开、状态查询等操作。
    • 优势: 将硬件操作细节封装在驱动层内部,上层服务层可以直接调用驱动层提供的 API,而无需关心底层的硬件操作细节。
  3. 服务层 (Service Layer):

    • 目的: 提供系统核心业务逻辑服务,例如浇水管理、参数配置、网络管理、用户界面管理等。
    • 功能:
      • 浇水服务 (Watering Service):
        • 负责浇水任务的调度和执行。
        • 根据浇水参数(间隔、次数、周期)生成浇水计划。
        • 控制水泵驱动执行浇水操作。
        • 记录浇水日志。
      • 配置服务 (Configuration Service):
        • 负责浇水参数的存储和读取。
        • 可以使用 NVS (Non-Volatile Storage) 或 SPIFFS 文件系统进行参数持久化存储。
        • 提供参数设置和获取 API。
      • 网络服务 (Network Service):
        • 负责 Wi-Fi 连接管理,包括 Wi-Fi 连接、断开、重连、状态监控。
        • 可以扩展实现 NTP 时间同步功能。
        • 可以扩展实现 MQTT 或 HTTP 客户端功能,用于远程监控和控制。
      • 用户界面服务 (UI Service):
        • 负责用户界面的管理和交互。
        • 处理触摸事件,响应用户操作。
        • 更新屏幕显示内容,例如浇水状态、参数设置界面等。
      • 传感器服务 (Sensor Service, 可选):
        • 如果添加了传感器(例如土壤湿度传感器),则负责传感器数据的采集和处理。
        • 可以实现智能浇水策略,根据传感器数据动态调整浇水计划。
    • 优势: 服务层是系统的核心逻辑层,将业务逻辑与硬件操作和用户界面分离,使得代码结构清晰,易于维护和扩展。
  4. 应用层 (Application Layer):

    • 目的: 作为系统的入口点,负责初始化各个服务层,并协调各服务层之间的工作。
    • 功能:
      • 系统初始化:初始化 HAL 层、设备驱动层、服务层。
      • 任务调度:创建和管理 FreeRTOS 任务,分配各个服务层的功能到不同的任务中并行执行。
      • 事件处理:处理系统事件,例如用户操作事件、定时器事件、网络事件等。
      • 主循环:运行主循环,监控系统状态,处理用户输入,执行浇水任务等。
    • 优势: 应用层是系统的顶层,负责整体系统的协调和控制,使得系统各部分有机地结合在一起。

技术选型与方法

在这个项目中,我将采用以下技术和方法,这些都是经过实践验证的成熟技术:

  • 操作系统: FreeRTOS (ESP-IDF 默认集成了 FreeRTOS)。FreeRTOS 是一个轻量级的实时操作系统,非常适合嵌入式系统,提供任务调度、互斥锁、信号量、队列等机制,方便实现多任务并发执行和任务间通信。
  • 开发框架: ESP-IDF (Espressif IoT Development Framework)。ESP-IDF 是乐鑫官方提供的 ESP32 系列芯片的开发框架,提供了丰富的 API 和组件,包括 Wi-Fi、蓝牙、TCP/IP 协议栈、文件系统、OTA 升级等,极大地简化了开发工作。
  • 编程语言: C 语言。C 语言是嵌入式系统开发中最常用的编程语言,具有高效、灵活、可移植性强等特点。
  • 配置管理: 使用 NVS (Non-Volatile Storage) 组件进行浇水参数的持久化存储。NVS 是 ESP-IDF 提供的用于存储键值对的组件,基于 Flash 存储,可靠性高。
  • 用户界面: 基于 LCD 屏幕和触摸屏构建本地用户界面。可以使用简单的图形库(例如 lvgl - LittlevGL,虽然对于这个简单的项目可能略显重量级,但如果未来需要更复杂的 UI,lvgl 是一个不错的选择,或者也可以选择更轻量级的 UI 库,甚至直接使用 LCD 驱动提供的绘图 API)来绘制界面元素,处理触摸事件。
  • 网络连接: 使用 ESP-IDF 提供的 Wi-Fi 组件进行 Wi-Fi 连接管理。
  • 时间同步: 可以使用 SNTP (Simple Network Time Protocol) 客户端进行网络时间同步,确保系统时间准确。
  • 代码管理: 使用 Git 进行代码版本控制,方便代码的管理和协作。
  • 调试方法: 使用 ESP-IDF 提供的调试工具,例如 GDB 调试器、日志输出功能,进行代码调试和问题排查。

详细 C 代码实现 (部分关键代码示例)

由于完整代码量会非常庞大,这里我提供一些关键模块的 C 代码示例,以展示代码架构和实现思路。请注意,以下代码仅为示例,可能需要根据具体的硬件和需求进行调整和完善。

(1) HAL 层 (hal_gpio.h, hal_gpio.c)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// hal_gpio.h
#ifndef HAL_GPIO_H
#define HAL_GPIO_H

#include <stdint.h>
#include "driver/gpio.h" // ESP-IDF GPIO 驱动头文件

typedef enum {
GPIO_LEVEL_LOW = 0,
GPIO_LEVEL_HIGH = 1
} hal_gpio_level_t;

typedef enum {
GPIO_MODE_INPUT = 0,
GPIO_MODE_OUTPUT = 1
} hal_gpio_mode_t;

typedef struct {
gpio_num_t gpio_num;
hal_gpio_mode_t mode;
} hal_gpio_config_t;

// 初始化 GPIO
esp_err_t hal_gpio_init(const hal_gpio_config_t *config);

// 设置 GPIO 输出电平
esp_err_t hal_gpio_set_level(gpio_num_t gpio_num, hal_gpio_level_t level);

// 读取 GPIO 输入电平
hal_gpio_level_t hal_gpio_get_level(gpio_num_t gpio_num);

#endif // HAL_GPIO_H
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// hal_gpio.c
#include "hal_gpio.h"
#include "esp_log.h"

static const char *TAG = "HAL_GPIO";

esp_err_t hal_gpio_init(const hal_gpio_config_t *config) {
gpio_config_t io_conf;
io_conf.intr_type = GPIO_INTR_DISABLE; // 禁止中断
io_conf.pin_bit_mask = (1ULL << config->gpio_num); // 配置 GPIO 引脚
io_conf.pull_down_en = 0; // 禁止下拉
io_conf.pull_up_en = 0; // 禁止上拉

if (config->mode == GPIO_MODE_OUTPUT) {
io_conf.mode = GPIO_MODE_OUTPUT; // 设置为输出模式
} else {
io_conf.mode = GPIO_MODE_INPUT; // 设置为输入模式
}

esp_err_t ret = gpio_config(&io_conf);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "gpio_config failed, gpio_num=%d, error=%d", config->gpio_num, ret);
return ret;
}
ESP_LOGI(TAG, "GPIO %d initialized, mode=%d", config->gpio_num, config->mode);
return ESP_OK;
}

esp_err_t hal_gpio_set_level(gpio_num_t gpio_num, hal_gpio_level_t level) {
return gpio_set_level(gpio_num, level);
}

hal_gpio_level_t hal_gpio_get_level(gpio_num_t gpio_num) {
return gpio_get_level(gpio_num);
}

(2) 设备驱动层 (driver_pump.h, driver_pump.c)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// driver_pump.h
#ifndef DRIVER_PUMP_H
#define DRIVER_PUMP_H

#include "hal_gpio.h"
#include "esp_err.h"

typedef struct {
gpio_num_t pump_gpio_num;
} pump_config_t;

// 初始化水泵驱动
esp_err_t pump_init(const pump_config_t *config);

// 开启水泵
esp_err_t pump_on();

// 关闭水泵
esp_err_t pump_off();

#endif // DRIVER_PUMP_H
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// driver_pump.c
#include "driver_pump.h"
#include "esp_log.h"

static const char *TAG = "DRIVER_PUMP";
static gpio_num_t pump_gpio;

esp_err_t pump_init(const pump_config_t *config) {
pump_gpio = config->pump_gpio_num;

hal_gpio_config_t gpio_config = {
.gpio_num = pump_gpio,
.mode = GPIO_MODE_OUTPUT
};
esp_err_t ret = hal_gpio_init(&gpio_config);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "hal_gpio_init failed for pump gpio, error=%d", ret);
return ret;
}

pump_off(); // 初始化时关闭水泵
ESP_LOGI(TAG, "Pump driver initialized, GPIO=%d", pump_gpio);
return ESP_OK;
}

esp_err_t pump_on() {
ESP_LOGI(TAG, "Turning pump ON");
return hal_gpio_set_level(pump_gpio, GPIO_LEVEL_HIGH); // 假设高电平开启水泵,具体根据硬件电路调整
}

esp_err_t pump_off() {
ESP_LOGI(TAG, "Turning pump OFF");
return hal_gpio_set_level(pump_gpio, GPIO_LEVEL_LOW);
}

(3) 服务层 (service_watering.h, service_watering.c)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// service_watering.h
#ifndef SERVICE_WATERING_H
#define SERVICE_WATERING_H

#include "esp_err.h"

// 浇水参数结构体
typedef struct {
uint32_t interval_seconds; // 浇水间隔 (秒)
uint32_t duration_seconds; // 每次浇水时长 (秒)
uint32_t cycle_days; // 浇水周期 (天,例如每天浇水为 1,隔天浇水为 2)
uint32_t start_hour; // 开始浇水的小时 (0-23)
uint32_t start_minute; // 开始浇水的分钟 (0-59)
} watering_parameters_t;

// 初始化浇水服务
esp_err_t watering_service_init();

// 设置浇水参数
esp_err_t watering_service_set_parameters(const watering_parameters_t *params);

// 获取浇水参数
esp_err_t watering_service_get_parameters(watering_parameters_t *params);

// 启动浇水计划任务
esp_err_t watering_service_start_schedule();

// 停止浇水计划任务
esp_err_t watering_service_stop_schedule();

#endif // SERVICE_WATERING_H
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
// service_watering.c
#include "service_watering.h"
#include "driver_pump.h"
#include "service_config.h" // 假设配置服务用于存储浇水参数
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "time.h"
#include "sys/time.h"

static const char *TAG = "SERVICE_WATERING";
static watering_parameters_t current_watering_params;
static TaskHandle_t watering_task_handle = NULL;

// 浇水任务函数
static void watering_task(void *pvParameters) {
while (1) {
// 获取当前时间
time_t now;
struct tm timeinfo;
time(&now);
localtime_r(&now, &timeinfo);

// 检查是否到达浇水时间
if (timeinfo.tm_hour == current_watering_params.start_hour &&
timeinfo.tm_min == current_watering_params.start_minute &&
(timeinfo.tm_wday % current_watering_params.cycle_days == 0)) { // 简单周期判断,可以根据需求调整

ESP_LOGI(TAG, "It's watering time!");
pump_on(); // 开启水泵
vTaskDelay(pdMS_TO_TICKS(current_watering_params.duration_seconds * 1000)); // 浇水指定时长
pump_off(); // 关闭水泵
ESP_LOGI(TAG, "Watering finished.");
}

vTaskDelay(pdMS_TO_TICKS(current_watering_params.interval_seconds * 1000)); // 间隔检查
}
}

esp_err_t watering_service_init() {
// 从配置服务加载浇水参数 (假设配置服务已经初始化)
esp_err_t ret = config_service_get_watering_parameters(&current_watering_params);
if (ret != ESP_OK) {
ESP_LOGW(TAG, "Failed to load watering parameters from config, using default.");
// 使用默认参数,或者返回错误
current_watering_params.interval_seconds = 60 * 60; // 默认 1 小时间隔
current_watering_params.duration_seconds = 30; // 默认 30 秒浇水时长
current_watering_params.cycle_days = 1; // 默认每天浇水
current_watering_params.start_hour = 8; // 默认早上 8 点开始
current_watering_params.start_minute = 0;
}

return ESP_OK;
}

esp_err_t watering_service_set_parameters(const watering_parameters_t *params) {
memcpy(&current_watering_params, params, sizeof(watering_parameters_t));
// 保存参数到配置服务 (假设配置服务提供保存参数的 API)
return config_service_set_watering_parameters(&current_watering_params);
}

esp_err_t watering_service_get_parameters(watering_parameters_t *params) {
memcpy(params, &current_watering_params, sizeof(watering_parameters_t));
return ESP_OK;
}

esp_err_t watering_service_start_schedule() {
if (watering_task_handle == NULL) {
BaseType_t xReturned;
xReturned = xTaskCreate(
watering_task, /* 任务函数 */
"WateringTask", /* 任务名称 */
4096, /* 任务堆栈大小 */
NULL, /* 任务参数 */
configMAX_PRIORITIES - 1, /* 任务优先级 */
&watering_task_handle); /* 任务句柄 */

if (xReturned != pdPASS) {
ESP_LOGE(TAG, "Failed to create watering task");
watering_task_handle = NULL;
return ESP_FAIL;
}
ESP_LOGI(TAG, "Watering schedule task started.");
} else {
ESP_LOGW(TAG, "Watering schedule task already running.");
}
return ESP_OK;
}

esp_err_t watering_service_stop_schedule() {
if (watering_task_handle != NULL) {
vTaskDelete(watering_task_handle);
watering_task_handle = NULL;
ESP_LOGI(TAG, "Watering schedule task stopped.");
} else {
ESP_LOGW(TAG, "Watering schedule task is not running.");
}
return ESP_OK;
}

(4) 应用层 (main.c)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// main.c
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "driver_pump.h"
#include "service_watering.h"
#include "service_config.h" // 假设配置服务
#include "service_network.h" // 假设网络服务
#include "service_ui.h" // 假设用户界面服务

static const char *TAG = "APP_MAIN";

void app_main(void) {
ESP_LOGI(TAG, "Starting Automatic Watering System...");

// 硬件初始化 (GPIO, SPI, I2C 等) - 根据具体硬件连接配置
pump_config_t pump_cfg = {
.pump_gpio_num = GPIO_NUM_2 // 假设水泵控制 GPIO 为 GPIO2
};
ESP_ERROR_CHECK(pump_init(&pump_cfg));

// 初始化配置服务
ESP_ERROR_CHECK(config_service_init());

// 初始化网络服务 (Wi-Fi 连接, NTP 时间同步等)
ESP_ERROR_CHECK(network_service_init());
ESP_ERROR_CHECK(network_service_connect_wifi("YOUR_WIFI_SSID", "YOUR_WIFI_PASSWORD")); // 替换为实际 Wi-Fi 信息
ESP_ERROR_CHECK(network_service_sync_time_ntp()); // NTP 时间同步

// 初始化浇水服务
ESP_ERROR_CHECK(watering_service_init());

// 初始化用户界面服务 (LCD, 触摸屏)
ESP_ERROR_CHECK(ui_service_init());
ESP_ERROR_CHECK(ui_service_show_main_screen()); // 显示主界面

// 启动浇水计划任务
ESP_ERROR_CHECK(watering_service_start_schedule());

ESP_LOGI(TAG, "System initialization complete.");

// 主循环 (可以用于处理用户输入,更新 UI 等)
while (1) {
// 处理用户输入 (例如触摸屏事件) - 示例代码,需要根据实际 UI 服务实现
// ui_event_t event;
// if (ui_service_get_event(&event) == ESP_OK) {
// if (event.type == UI_EVENT_TYPE_BUTTON_CLICK) {
// if (event.button_id == BUTTON_SET_PARAMS) {
// ui_service_show_settings_screen();
// }
// }
// }

vTaskDelay(pdMS_TO_TICKS(100)); // 适当延时,降低 CPU 占用
}
}

(5) 配置服务 (service_config.h, service_config.c) - 示例,使用 NVS 存储

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// service_config.h
#ifndef SERVICE_CONFIG_H
#define SERVICE_CONFIG_H

#include "esp_err.h"
#include "service_watering.h" // 包含浇水参数结构体

// 初始化配置服务
esp_err_t config_service_init();

// 获取浇水参数
esp_err_t config_service_get_watering_parameters(watering_parameters_t *params);

// 设置浇水参数
esp_err_t config_service_set_watering_parameters(const watering_parameters_t *params);

#endif // SERVICE_CONFIG_H
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
// service_config.c
#include "service_config.h"
#include "nvs_flash.h"
#include "nvs.h"
#include "esp_log.h"

static const char *TAG = "SERVICE_CONFIG";
static const char *NVS_NAMESPACE = "watering_config";

esp_err_t config_service_init() {
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
ESP_LOGI(TAG, "NVS flash initialized");
return ESP_OK;
}

esp_err_t config_service_get_watering_parameters(watering_parameters_t *params) {
nvs_handle_t nvs_handle;
esp_err_t ret = nvs_open(NVS_NAMESPACE, NVS_READONLY, &nvs_handle);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "NVS open failed: %s", esp_err_to_name(ret));
return ret;
}

size_t size = sizeof(watering_parameters_t);
ret = nvs_get_blob(nvs_handle, "watering_params", params, &size);
if (ret != ESP_OK) {
ESP_LOGW(TAG, "Failed to load watering parameters from NVS: %s", esp_err_to_name(ret));
nvs_close(nvs_handle);
return ret; // 可以选择返回错误,或者使用默认值
}
nvs_close(nvs_handle);
ESP_LOGI(TAG, "Watering parameters loaded from NVS");
return ESP_OK;
}

esp_err_t config_service_set_watering_parameters(const watering_parameters_t *params) {
nvs_handle_t nvs_handle;
esp_err_t ret = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs_handle);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "NVS open failed: %s", esp_err_to_name(ret));
return ret;
}

ret = nvs_set_blob(nvs_handle, "watering_params", params, sizeof(watering_parameters_t));
if (ret != ESP_OK) {
ESP_LOGE(TAG, "NVS set blob failed: %s", esp_err_to_name(ret));
nvs_close(nvs_handle);
return ret;
}

ret = nvs_commit(nvs_handle);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "NVS commit failed: %s", esp_err_to_name(ret));
nvs_close(nvs_handle);
return ret;
}

nvs_close(nvs_handle);
ESP_LOGI(TAG, "Watering parameters saved to NVS");
return ESP_OK;
}

(6) 网络服务 (service_network.h, service_network.c) - 示例,Wi-Fi 连接和 NTP 同步

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// service_network.h
#ifndef SERVICE_NETWORK_H
#define SERVICE_NETWORK_H

#include "esp_err.h"

// 初始化网络服务
esp_err_t network_service_init();

// 连接 Wi-Fi
esp_err_t network_service_connect_wifi(const char *ssid, const char *password);

// 断开 Wi-Fi
esp_err_t network_service_disconnect_wifi();

// 获取 Wi-Fi 连接状态
bool network_service_is_wifi_connected();

// 同步 NTP 时间
esp_err_t network_service_sync_time_ntp();

#endif // SERVICE_NETWORK_H
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
// service_network.c
#include "service_network.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "lwip/err.h"
#include "lwip/sys.h"
#include "esp_sntp.h"
#include "time.h"

static const char *TAG = "SERVICE_NETWORK";
static bool wifi_connected = false;

static void wifi_event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) {
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
wifi_connected = false;
ESP_LOGI(TAG, "Wi-Fi disconnected, retrying...");
esp_wifi_connect(); // 尝试重连
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
ESP_LOGI(TAG, "Got IP address: " IPSTR, IP2STR(&event->ip_info.ip));
wifi_connected = true;
}
}

esp_err_t network_service_init() {
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_sta();

wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));

esp_event_handler_instance_t instance_any_id;
esp_event_handler_instance_t instance_got_ip;
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL, &instance_any_id));
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler, NULL, &instance_got_ip));

ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_start());

ESP_LOGI(TAG, "Wi-Fi initialized");
return ESP_OK;
}

esp_err_t network_service_connect_wifi(const char *ssid, const char *password) {
wifi_config_t wifi_config = {
.sta = {
.ssid = "",
.password = "",
.threshold.authmode = WIFI_AUTH_WPA2_PSK, // 根据实际情况选择加密方式
.pmf_cfg = {
.capable = true,
.required = false
},
},
};
strncpy((char *)wifi_config.sta.ssid, ssid, sizeof(wifi_config.sta.ssid) - 1);
strncpy((char *)wifi_config.sta.password, password, sizeof(wifi_config.sta.password) - 1);

ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
ESP_LOGI(TAG, "Connecting to Wi-Fi SSID: %s...", ssid);
return ESP_OK; // 连接过程是异步的,连接结果通过事件回调处理
}

esp_err_t network_service_disconnect_wifi() {
ESP_ERROR_CHECK(esp_wifi_disconnect());
wifi_connected = false;
ESP_LOGI(TAG, "Wi-Fi disconnected");
return ESP_OK;
}

bool network_service_is_wifi_connected() {
return wifi_connected;
}

esp_err_t network_service_sync_time_ntp() {
if (!network_service_is_wifi_connected()) {
ESP_LOGW(TAG, "Wi-Fi not connected, cannot sync NTP time.");
return ESP_FAIL;
}

sntp_setoperatingmode(SNTP_OPMODE_POLL);
sntp_setservername(0, "pool.ntp.org"); // 使用公共 NTP 服务器
sntp_init();

// 等待时间同步完成 (可以设置超时时间)
int retry = 0;
const int retry_count = 10;
while (sntp_get_sync_status() == SNTP_SYNC_STATUS_RESET && ++retry < retry_count) {
ESP_LOGI(TAG, "Waiting for system time to be set... (%d/%d)", retry, retry_count);
vTaskDelay(pdMS_TO_TICKS(1000));
}

if (sntp_get_sync_status() != SNTP_SYNC_STATUS_RESET) {
time_t now;
struct tm timeinfo;
time(&now);
localtime_r(&now, &timeinfo);
char strftime_buf[64];
strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo);
ESP_LOGI(TAG, "NTP time synchronized: %s", strftime_buf);
return ESP_OK;
} else {
ESP_LOGE(TAG, "NTP time synchronization failed");
return ESP_FAIL;
}
}

(7) 用户界面服务 (service_ui.h, service_ui.c) - 简易示例,需要根据实际 LCD 和触摸屏驱动进行完善)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// service_ui.h
#ifndef SERVICE_UI_H
#define SERVICE_UI_H

#include "esp_err.h"
#include "service_watering.h" // 包含浇水参数结构体

// 初始化用户界面服务
esp_err_t ui_service_init();

// 显示主界面
esp_err_t ui_service_show_main_screen();

// 显示设置参数界面
esp_err_t ui_service_show_settings_screen();

// 获取用户界面事件 (例如按钮点击) - 需要根据实际触摸屏驱动实现
// esp_err_t ui_service_get_event(ui_event_t *event);

#endif // SERVICE_UI_H
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
// service_ui.c
#include "service_ui.h"
#include "driver_lcd.h" // 假设 LCD 驱动
#include "driver_touch.h" // 假设触摸屏驱动
#include "service_watering.h"
#include "esp_log.h"
#include <stdio.h>
#include <string.h>

static const char *TAG = "SERVICE_UI";

esp_err_t ui_service_init() {
// 初始化 LCD 驱动 (需要根据实际 LCD 硬件配置参数)
// lcd_config_t lcd_cfg = { ... };
// ESP_ERROR_CHECK(lcd_init(&lcd_cfg));

// 初始化触摸屏驱动 (需要根据实际触摸屏硬件配置参数)
// touch_config_t touch_cfg = { ... };
// ESP_ERROR_CHECK(touch_init(&touch_cfg));

ESP_LOGI(TAG, "UI service initialized");
return ESP_OK;
}

esp_err_t ui_service_show_main_screen() {
// 清屏
// lcd_clear_screen(COLOR_WHITE);

// 绘制标题
// lcd_draw_string(10, 10, "自动浇水系统", FONT_SIZE_LARGE, COLOR_BLACK);

// 绘制按钮和显示区域 (例如 "设置参数", "启动浇水", "浇水状态")
// ... 使用 LCD 驱动提供的绘图 API

ESP_LOGI(TAG, "Main screen displayed");
return ESP_OK;
}

esp_err_t ui_service_show_settings_screen() {
// 清屏
// lcd_clear_screen(COLOR_WHITE);

// 绘制标题
// lcd_draw_string(10, 10, "设置浇水参数", FONT_SIZE_LARGE, COLOR_BLACK);

// 绘制输入框和按钮,用于设置浇水间隔、时长、周期等
// ... 使用 LCD 驱动提供的绘图 API,并处理触摸输入

// 获取当前浇水参数并显示在界面上
watering_parameters_t params;
watering_service_get_parameters(&params);

char buffer[100];
sprintf(buffer, "间隔: %d 秒", params.interval_seconds);
// lcd_draw_string(10, 50, buffer, FONT_SIZE_MEDIUM, COLOR_BLACK);
sprintf(buffer, "时长: %d 秒", params.duration_seconds);
// lcd_draw_string(10, 70, buffer, FONT_SIZE_MEDIUM, COLOR_BLACK);
sprintf(buffer, "周期: %d 天", params.cycle_days);
// lcd_draw_string(10, 90, buffer, FONT_SIZE_MEDIUM, COLOR_BLACK);

ESP_LOGI(TAG, "Settings screen displayed");
return ESP_OK;
}

// ... 用户界面事件处理函数 (需要根据实际触摸屏驱动实现)
// esp_err_t ui_service_get_event(ui_event_t *event) { ... }

项目实践验证与改进方向

以上代码架构和示例代码提供了一个自动浇水系统的基本框架。在实际项目开发中,需要进行以下实践验证和改进:

  1. 硬件验证: 根据立创 EDA 设计的硬件原理图和 PCB,搭建硬件电路,并验证硬件连接的正确性,例如水泵、LCD 屏幕、触摸屏、传感器等。
  2. 驱动开发与调试: 根据硬件 Datasheet,编写 HAL 层和设备驱动层代码,并进行充分的单元测试和集成测试,确保驱动程序的稳定性和可靠性。特别是 LCD 和触摸屏驱动,需要根据具体的控制器型号进行适配。
  3. 服务层功能实现与测试: 逐步实现服务层的功能,例如浇水服务、配置服务、网络服务、用户界面服务等,并进行模块化测试和集成测试,确保服务层逻辑的正确性和高效性。
  4. 用户界面交互设计与优化: 设计友好的用户界面,优化用户交互体验。例如,可以使用触摸屏按钮、滑动条等控件,方便用户设置浇水参数。
  5. 系统稳定性测试: 进行长时间的系统稳定性测试,模拟各种异常情况,例如网络断开、电源波动等,验证系统的鲁棒性和容错能力。
  6. 功耗优化: 对于电池供电的应用场景,需要进行功耗优化,例如使用 ESP32-S3 的低功耗模式,优化代码执行效率,降低系统功耗。
  7. 安全加固: 如果系统需要连接到互联网,需要考虑安全问题,例如使用 HTTPS 协议进行数据传输,防止数据泄露和恶意攻击。
  8. OTA 升级: 实现 OTA (Over-The-Air) 升级功能,方便未来进行固件升级和维护。
  9. 可扩展性考虑: 在系统架构设计和代码实现过程中,充分考虑系统的可扩展性,方便未来添加新的功能,例如土壤湿度传感器检测、远程监控、智能浇水策略等。

总结

这个自动浇水系统项目展示了一个典型的嵌入式系统开发流程,从需求分析、架构设计、代码实现,到测试验证和维护升级。通过采用分层架构、模块化设计、以及成熟的嵌入式开发技术和方法,我们可以构建一个可靠、高效、可扩展的自动浇水系统平台。

请注意,以上代码示例仅为框架和思路展示,实际项目开发需要根据具体的硬件平台、需求细节和技术选型进行详细的设计和实现。 整个项目代码量远不止示例代码, 完整的实现会超过 3000 行代码, 包含更详细的错误处理、更完善的 UI 界面、更全面的功能模块和更严格的测试验证。

希望这份详细的代码架构和示例代码能够帮助您理解嵌入式系统开发,并为您构建自己的自动浇水系统提供参考。 如果您有任何进一步的问题,欢迎随时提出。

欢迎关注我的其它发布渠道