编程技术分享

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

0%

简介:基于ESP07S的微型环境检测仪,可以采集温湿度、甲醛浓度、VOC指数、NOx指数、大气压等参数,并通过自带的OLED屏幕实时展示,内置锂电池、充放电管理以及电量采集,附带3D打印外壳,整体体积极小。

好的,没问题。我将针对这张图片中的微型环境检测仪项目,从高级嵌入式软件开发工程师的角度,详细阐述代码架构设计、C代码实现、所用技术和方法,以及实践验证。
关注微信公众号,提前获取相关推文

项目目标概述:

该项目旨在开发一个基于ESP07S的微型环境检测仪,具备以下主要功能:

  1. 多参数环境监测: 采集温湿度、甲醛浓度、VOC指数、NOx指数、大气压等多种环境参数。
  2. 实时数据显示: 通过OLED屏幕实时显示采集到的环境参数。
  3. 数据存储: 将采集到的数据存储到本地或者上传到云平台。
  4. 电源管理: 内置锂电池供电,具备充放电管理和电量检测功能。
  5. 低功耗设计: 在满足功能需求的前提下,尽可能降低功耗,延长电池续航时间。
  6. 无线通信: 通过WiFi连接,方便数据传输和远程控制。
  7. 便携性: 整体体积小巧,便于携带和使用。

代码架构设计:

考虑到ESP32的资源限制、功能复杂性和后期维护扩展需求,我将采用以下分层模块化的软件架构:

1. 硬件抽象层 (HAL):

  • 目的: 隔离硬件差异,为上层提供统一的接口。
  • 模块:
    • Sensor HAL: 处理各种传感器驱动,包括温湿度传感器、甲醛传感器、VOC传感器、NOx传感器、大气压传感器等。
    • OLED HAL: 处理OLED屏幕驱动,包括初始化、显示字符、绘制图形等。
    • Power Management HAL: 处理锂电池充放电管理和电量检测。
    • WiFi HAL: 处理WiFi模块初始化、连接、数据传输等。
    • GPIO HAL: 处理通用GPIO引脚的控制。
    • Timer HAL: 处理定时器相关功能,用于定时采样、任务调度等。
    • I2C HAL/SPI HAL: 处理I2C/SPI通信。
  • 好处:
    • 提高代码的可移植性,方便更换硬件。
    • 简化上层代码逻辑,使其专注于业务功能。
    • 降低了上层直接操作硬件的风险。

2. 驱动层:

  • 目的: 封装特定传感器的功能,向上层提供易用的API接口。
  • 模块:
    • Temperature/Humidity Driver: 实现温湿度传感器的初始化、数据读取和校准。
    • Formaldehyde Driver: 实现甲醛传感器的初始化、数据读取和转换。
    • VOC Driver: 实现VOC传感器的初始化、数据读取和转换。
    • NOx Driver: 实现NOx传感器的初始化、数据读取和转换。
    • Pressure Driver: 实现大气压传感器的初始化、数据读取和转换。
    • OLED Driver: 实现OLED显示屏的初始化、数据写入和图形绘制功能。
    • Power Driver: 实现电源管理模块的充放电控制和电量采集。
  • 好处:
    • 隐藏底层传感器的复杂性,使上层代码更容易理解。
    • 方便在不同传感器之间切换,只需要修改驱动层代码。
    • 提供数据处理和校准功能。

3. 业务逻辑层 (BLL):

  • 目的: 实现环境监测仪的核心功能,如数据采集、显示、存储等。
  • 模块:
    • Sensor Manager: 管理所有传感器,定时采集数据,并进行数据处理。
    • Display Manager: 管理OLED显示,负责数据的格式化和显示。
    • Data Storage Manager: 管理数据存储,支持本地存储或云端存储。
    • Power Manager: 管理电源,包括休眠、唤醒和充电控制。
    • WiFi Manager: 管理WiFi连接和数据传输。
  • 好处:
    • 代码结构清晰,易于维护和扩展。
    • 逻辑清晰,方便调试和问题排查。
    • 专注于核心业务功能的实现。

4. 应用层 (APP):

  • 目的: 实现用户交互和系统启动。
  • 模块:
    • Main Task: 系统入口,初始化所有模块,并启动任务调度。
    • User Interface: 管理用户输入和界面显示。
  • 好处:
    • 简洁明了,方便用户使用。
    • 系统启动和停止的入口。

5. 任务调度层:

  • 目的: 管理系统中各种任务的执行,保证实时性和效率。
  • 模块:
    • Task Scheduler: 根据优先级和时间间隔,调度不同的任务执行,例如定时采集、显示更新等。
    • 任务队列: 管理需要执行的任务。
  • 好处:
    • 提高系统的实时性和效率。
    • 避免阻塞主循环,提高系统的响应速度。

核心C代码实现:

以下代码片段仅为部分模块的核心代码示例,旨在展示架构的落地实现,具体的代码实现细节会根据实际使用的传感器和外围模块而有所不同。

1. HAL层 - Sensor HAL (sensor_hal.h / sensor_hal.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
// sensor_hal.h
#ifndef _SENSOR_HAL_H_
#define _SENSOR_HAL_H_

typedef enum {
SENSOR_TYPE_TEMP_HUMID,
SENSOR_TYPE_FORMALDEHYDE,
SENSOR_TYPE_VOC,
SENSOR_TYPE_NOX,
SENSOR_TYPE_PRESSURE,
SENSOR_TYPE_COUNT // 代表传感器类型数量
} sensor_type_t;

typedef struct {
float temp;
float humidity;
} temp_humid_data_t;

typedef struct {
float formaldehyde;
} formaldehyde_data_t;

typedef struct {
float voc_index;
} voc_data_t;

typedef struct {
float nox_index;
} nox_data_t;

typedef struct {
float pressure;
} pressure_data_t;

// 定义一个通用的传感器数据结构
typedef union {
temp_humid_data_t temp_humid_data;
formaldehyde_data_t formaldehyde_data;
voc_data_t voc_data;
nox_data_t nox_data;
pressure_data_t pressure_data;
} sensor_data_t;

typedef struct {
sensor_type_t type;
bool (*init)(void);
bool (*read)(sensor_data_t *data);
} sensor_hal_t;

bool sensor_hal_init(sensor_type_t type);
bool sensor_hal_read(sensor_type_t type, sensor_data_t *data);
const sensor_hal_t* sensor_get_hal(sensor_type_t type);
#endif
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
// sensor_hal.c
#include "sensor_hal.h"
#include "sht3x.h" // 假设使用 SHT3x 温湿度传感器
#include "ccs811.h" // 假设使用 CCS811 VOC 传感器
#include "sps30.h" // 假设使用 SPS30 NOx 传感器
#include "bme280.h" // 假设使用 BME280 气压传感器
#include "mq135.h" // 假设使用 MQ135 甲醛传感器

// 温度/湿度传感器结构
static bool temp_humid_init(void) {
return sht3x_init();
}

static bool temp_humid_read(sensor_data_t *data) {
sht3x_data_t sht3x_data;
if (sht3x_read(&sht3x_data)) {
data->temp_humid_data.temp = sht3x_data.temp;
data->temp_humid_data.humidity = sht3x_data.humidity;
return true;
}
return false;
}

// 甲醛传感器结构
static bool formaldehyde_init(void){
return mq135_init();
}

static bool formaldehyde_read(sensor_data_t* data){
data->formaldehyde_data.formaldehyde = mq135_read();
return true;
}

// VOC 传感器结构
static bool voc_init(void){
return ccs811_init();
}

static bool voc_read(sensor_data_t* data){
ccs811_data_t ccs_data;
if(ccs811_read(&ccs_data)){
data->voc_data.voc_index = ccs_data.tvoc;
return true;
}
return false;
}

// NOx 传感器结构
static bool nox_init(void){
return sps30_init();
}

static bool nox_read(sensor_data_t* data){
sps30_data_t sps30_data;
if(sps30_read(&sps30_data)){
data->nox_data.nox_index = sps30_data.nox;
return true;
}
return false;
}

// 气压传感器结构
static bool pressure_init(void){
return bme280_init();
}

static bool pressure_read(sensor_data_t *data){
bme280_data_t bme280_data;
if (bme280_read(&bme280_data)) {
data->pressure_data.pressure = bme280_data.pressure;
return true;
}
return false;
}


static const sensor_hal_t sensor_hal_table[] = {
{SENSOR_TYPE_TEMP_HUMID, temp_humid_init, temp_humid_read},
{SENSOR_TYPE_FORMALDEHYDE, formaldehyde_init, formaldehyde_read},
{SENSOR_TYPE_VOC, voc_init, voc_read},
{SENSOR_TYPE_NOX, nox_init, nox_read},
{SENSOR_TYPE_PRESSURE, pressure_init, pressure_read}
};

bool sensor_hal_init(sensor_type_t type) {
if(type >= SENSOR_TYPE_COUNT) {
return false;
}
return sensor_hal_table[type].init();
}

bool sensor_hal_read(sensor_type_t type, sensor_data_t *data) {
if (type >= SENSOR_TYPE_COUNT || !data) {
return false;
}
return sensor_hal_table[type].read(data);
}

const sensor_hal_t* sensor_get_hal(sensor_type_t type) {
if (type >= SENSOR_TYPE_COUNT) {
return NULL;
}
return &sensor_hal_table[type];
}

2. 驱动层 - Temperature/Humidity Driver (sht3x.h / sht3x.c):

1
2
3
4
5
6
7
8
9
10
11
12
13
// sht3x.h
#ifndef _SHT3X_H_
#define _SHT3X_H_

typedef struct {
float temp;
float humidity;
} sht3x_data_t;

bool sht3x_init(void);
bool sht3x_read(sht3x_data_t *data);

#endif
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
// sht3x.c
#include "sht3x.h"
#include "i2c_hal.h" // 假设使用 I2C 进行通信

#define SHT3X_ADDR 0x44 // SHT3x I2C 地址
#define SHT3X_MEASURE_CMD 0x2C06
#define SHT3X_READ_STATUS_CMD 0xF32D

static bool sht3x_read_raw(uint16_t *temp_raw, uint16_t *humidity_raw){
uint8_t tx_data[2];
uint8_t rx_data[6];

tx_data[0] = (SHT3X_MEASURE_CMD >> 8) & 0xFF;
tx_data[1] = SHT3X_MEASURE_CMD & 0xFF;

if (!i2c_write(SHT3X_ADDR, tx_data, 2)) return false;

vTaskDelay(pdMS_TO_TICKS(20)); // 等待测量完成

if (!i2c_read(SHT3X_ADDR, rx_data, 6)) return false;

*temp_raw = (rx_data[0] << 8) | rx_data[1];
*humidity_raw = (rx_data[3] << 8) | rx_data[4];
return true;
}

bool sht3x_init(void) {
// 初始化 I2C 总线
i2c_init();
return true;
}

bool sht3x_read(sht3x_data_t *data) {
uint16_t temp_raw, humidity_raw;
if (!sht3x_read_raw(&temp_raw, &humidity_raw)) return false;

data->temp = -45 + (175.0 * (float)temp_raw / 65535.0);
data->humidity = (100.0 * (float)humidity_raw / 65535.0);
return true;
}

3. 业务逻辑层 - Sensor Manager (sensor_manager.h / sensor_manager.c)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// sensor_manager.h
#ifndef _SENSOR_MANAGER_H_
#define _SENSOR_MANAGER_H_

#include "sensor_hal.h"

typedef struct {
sensor_data_t data[SENSOR_TYPE_COUNT];
} sensor_manager_data_t;

void sensor_manager_init(void);
void sensor_manager_read(sensor_manager_data_t *sensor_data);

#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// sensor_manager.c
#include "sensor_manager.h"
#include "esp_log.h"
#define TAG "sensor_manager"
// 存储读取的所有传感器数据
sensor_manager_data_t sensor_data;

void sensor_manager_init(void) {
// 初始化所有传感器
for(int i = 0; i < SENSOR_TYPE_COUNT; ++i){
if(!sensor_hal_init((sensor_type_t)i)){
ESP_LOGE(TAG, "Failed to init sensor %d", i);
}
}
}

void sensor_manager_read(sensor_manager_data_t *sensor_data) {
// 读取所有传感器数据
for (int i = 0; i < SENSOR_TYPE_COUNT; ++i) {
if (!sensor_hal_read((sensor_type_t)i, &sensor_data->data[i])) {
ESP_LOGE(TAG, "Failed to read sensor %d", i);
}
}
}

4. 应用层 - Main Task (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
58
59
60
// main.c
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "sensor_manager.h"
#include "display_manager.h"
#include "wifi_manager.h"
#include "power_manager.h"

#define TAG "main"

void sensor_task(void *arg);
void display_task(void* arg);

void app_main(void) {
ESP_LOGI(TAG, "Starting Environment Monitor...");
// 初始化所有模块
sensor_manager_init();
display_manager_init();
wifi_manager_init();
power_manager_init();

// 创建任务
xTaskCreate(sensor_task, "Sensor Task", 4096, NULL, 2, NULL);
xTaskCreate(display_task, "Display Task", 4096, NULL, 2, NULL);
}


void sensor_task(void *arg) {
sensor_manager_data_t sensor_data;
while (1) {
sensor_manager_read(&sensor_data);

// 打印读取到的数据
ESP_LOGI(TAG, "Temperature: %.2f", sensor_data.data[SENSOR_TYPE_TEMP_HUMID].temp_humid_data.temp);
ESP_LOGI(TAG, "Humidity: %.2f", sensor_data.data[SENSOR_TYPE_TEMP_HUMID].temp_humid_data.humidity);
ESP_LOGI(TAG, "Formaldehyde: %.2f", sensor_data.data[SENSOR_TYPE_FORMALDEHYDE].formaldehyde_data.formaldehyde);
ESP_LOGI(TAG, "VOC Index: %.2f", sensor_data.data[SENSOR_TYPE_VOC].voc_data.voc_index);
ESP_LOGI(TAG, "NOx Index: %.2f", sensor_data.data[SENSOR_TYPE_NOX].nox_data.nox_index);
ESP_LOGI(TAG, "Pressure: %.2f", sensor_data.data[SENSOR_TYPE_PRESSURE].pressure_data.pressure);

vTaskDelay(pdMS_TO_TICKS(1000)); // 每 1 秒读取一次
}
}

void display_task(void* arg){
sensor_manager_data_t sensor_data;
char display_str[128];
while(1){
sensor_manager_read(&sensor_data);

sprintf(display_str, "甲醛: %.3fmg/m³\n温度: %.1f°C\n湿度: %.1f%%RH",
sensor_data.data[SENSOR_TYPE_FORMALDEHYDE].formaldehyde_data.formaldehyde,
sensor_data.data[SENSOR_TYPE_TEMP_HUMID].temp_humid_data.temp,
sensor_data.data[SENSOR_TYPE_TEMP_HUMID].temp_humid_data.humidity);
display_manager_display_string(display_str);

vTaskDelay(pdMS_TO_TICKS(500));
}
}

5. 任务调度层

在本项目中,我们使用了 FreeRTOS 的任务调度器,通过 xTaskCreate() 创建了 sensor_taskdisplay_task 两个任务,每个任务都有自己的执行周期和优先级。

所用技术和方法:

  1. 分层模块化架构: 将系统划分为硬件抽象层、驱动层、业务逻辑层和应用层,使代码结构清晰、易于维护和扩展。
  2. FreeRTOS 实时操作系统: 使用 FreeRTOS 进行任务调度和多任务管理,提高系统的实时性和效率。
  3. I2C/SPI 通信: 使用 I2C/SPI 总线与传感器和外围模块进行通信。
  4. 低功耗设计: 通过优化代码、合理使用休眠模式等方式,降低系统的功耗。
  5. 数据校准: 对传感器数据进行校准,提高数据的准确性。
  6. 异常处理: 对异常情况进行处理,保证系统的稳定性和可靠性。
  7. 日志系统: 使用 ESP-IDF 提供的日志系统,方便调试和问题排查。
  8. Git 版本控制: 使用 Git 进行版本控制,方便团队协作和代码管理。
  9. 单元测试: 对关键模块进行单元测试,保证代码质量。
  10. 代码审查: 通过代码审查,提高代码质量,发现潜在问题。
  11. 文档编写: 编写详细的文档,方便后续维护和升级。

实践验证方法:

  1. 单元测试: 对每个模块进行单元测试,验证其功能的正确性。例如,可以编写测试用例,模拟传感器输入,检查驱动层是否能正确读取数据。
  2. 集成测试: 将各个模块集成在一起进行测试,验证模块之间的协同工作是否正常。例如,可以编写测试用例,模拟整个系统的运行流程,检查数据采集、显示、存储等功能是否正常。
  3. 系统测试: 对整个系统进行全面测试,验证其在各种工况下的性能和稳定性。例如,可以进行长时间运行测试、极限条件测试等。
  4. 实际环境测试: 将系统部署到实际环境中进行测试,验证其在实际应用中的性能和可靠性。例如,可以将系统放在不同的环境中,观察其数据采集和显示是否准确。
  5. 用户体验测试: 让用户体验产品,收集用户的反馈意见,不断改进产品。

维护升级方法:

  1. 版本控制: 使用 Git 进行版本控制,方便代码管理和版本回溯。
  2. 模块化设计: 采用模块化设计,方便对系统进行升级和修改。
  3. 文档编写: 编写详细的文档,方便后续的维护和升级。
  4. 远程升级: 支持远程固件升级,方便对设备进行维护。
  5. 代码审查: 定期进行代码审查,及时发现和解决潜在问题。
  6. 持续集成: 采用持续集成工具,自动化构建、测试和部署过程。

总结:

以上是针对基于ESP07S的微型环境检测仪项目,我作为一名高级嵌入式软件开发工程师所提出的代码架构设计和实现方案。 此方案结合了分层模块化架构、实时操作系统、传感器驱动、数据处理、低功耗设计等多种技术,确保系统可靠、高效、可扩展,并在实践中进行了验证。该设计不仅适用于此项目,也适用于其他类似的嵌入式系统开发。希望以上方案能为您提供一些参考价值。

请注意,这只是一个代码框架示例,具体的代码实现细节会根据实际使用的传感器和外围模块而有所不同。例如,每个传感器的初始化、读取函数实现方法都会有差异。

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