编程技术分享

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

0%

我将为你详细解析这个基于立创天空星开发板的室外环境监测系统,并提供一个可靠、高效、可扩展的代码设计架构,以及相应的C代码实现。

关注微信公众号,提前获取相关推文

系统设计架构:分层架构与模块化设计

为了构建一个可靠、高效且易于维护和扩展的嵌入式系统,我推荐采用分层架构模块化设计相结合的方法。这种架构将系统划分为不同的层次,每个层次负责特定的功能,层与层之间通过清晰定义的接口进行通信。模块化设计则将每个层次进一步细分为独立的模块,提高代码的复用性和可维护性。

1. 分层架构:

  • 硬件抽象层 (HAL - Hardware Abstraction Layer): 这是最底层,直接与硬件交互。HAL层封装了底层硬件的细节,向上层提供统一的硬件访问接口。例如,对于不同的传感器,HAL层提供统一的初始化、读取数据等接口,屏蔽了不同传感器驱动的差异。
  • 驱动层 (Driver Layer): 驱动层建立在HAL层之上,负责特定硬件设备的驱动和管理。例如,每个传感器(温度湿度传感器、PM2.5传感器、气压传感器等)都有一个独立的驱动模块。驱动层使用HAL层提供的接口来操作硬件,并向上层提供传感器数据的读取、配置等接口。
  • 服务层 (Service Layer): 服务层构建在驱动层之上,提供更高层次的服务和功能。例如,数据处理服务(滤波、单位转换、校准)、数据存储服务、通信服务等。服务层将底层的硬件操作抽象成更高级别的业务逻辑。
  • 应用层 (Application Layer): 这是最顶层,负责实现系统的具体应用逻辑。例如,数据采集、数据显示、数据上传、告警处理等。应用层调用服务层提供的接口来实现业务功能。

2. 模块化设计:

在每个层次内部,我们都采用模块化设计。例如:

  • HAL层模块: GPIO模块、SPI模块、I2C模块、ADC模块、Timer模块、UART模块等,分别负责不同的硬件外设的抽象。
  • 驱动层模块: 温度湿度传感器驱动模块 (dhtXX.c/h)、PM2.5传感器驱动模块 (pmsXXX.c/h)、气压传感器驱动模块 (bmpXXX.c/h)、光照度传感器驱动模块 (bhXXX.c/h)、风速风向传感器驱动模块 (anemometer.c/h)、雨量传感器驱动模块 (rain_gauge.c/h) 等。
  • 服务层模块: 数据处理模块 (data_process.c/h)、数据存储模块 (data_storage.c/h)、通信模块 (communication.c/h) 等。
  • 应用层模块: 数据采集模块 (data_acquisition.c/h)、数据显示模块 (display.c/h)、数据上传模块 (data_upload.c/h)、告警模块 (alarm.c/h) 等。

代码设计原则:

  • 可靠性: 代码需要稳定可靠,能够长时间运行不崩溃,数据采集准确无误。需要考虑错误处理、异常情况处理、数据校验等机制。
  • 高效性: 代码执行效率要高,占用资源少,响应速度快。需要优化算法、减少不必要的计算、合理使用内存等。
  • 可扩展性: 系统架构要易于扩展,方便添加新的传感器、新的功能。模块化设计和分层架构为此提供了基础。
  • 可维护性: 代码结构清晰、注释完善、命名规范,方便后期维护和升级。
  • 可移植性: 尽量使用标准C语言,避免过度依赖特定硬件平台的特性,方便代码移植到其他平台。

项目采用的技术和方法:

  • C语言编程: 选择C语言作为主要的开发语言,因为它高效、灵活,并且在嵌入式领域应用广泛。
  • 模块化编程: 将系统划分为独立的模块,提高代码的复用性和可维护性。
  • 分层架构: 将系统划分为不同的层次,降低层与层之间的耦合度,提高系统的可扩展性和可维护性。
  • 状态机: 可以使用状态机来管理系统的运行状态和事件处理,提高代码的逻辑清晰度和可靠性。
  • 中断处理: 使用中断来处理外部事件,例如传感器数据就绪、定时器到期等,提高系统的实时性。
  • 定时器: 使用定时器来周期性地采集传感器数据、执行任务调度等。
  • 数据滤波算法: 例如滑动平均滤波、卡尔曼滤波等,用于去除传感器数据中的噪声,提高数据精度。
  • 数据校验: 对接收到的数据进行校验,例如CRC校验、奇偶校验等,保证数据的完整性和正确性。
  • 错误处理机制: 完善的错误处理机制,能够及时检测和处理系统运行过程中出现的错误,保证系统的可靠性。
  • 代码版本控制 (Git): 使用Git进行代码版本控制,方便团队协作和代码管理。
  • 代码注释: 编写清晰详细的代码注释,提高代码的可读性和可维护性。

具体C代码实现 (示例代码,总代码量远超3000行,这里只展示核心框架和关键模块的实现思路,完整代码需要根据具体的硬件平台和传感器型号进行详细编写,并进行充分的测试和验证):

为了方便展示和理解,以下代码将分为几个部分进行说明,实际项目中应该将它们组织成独立的 .c.h 文件。

1. hal.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
#ifndef HAL_H
#define HAL_H

#include <stdint.h>
#include <stdbool.h>

// GPIO 相关函数
typedef enum {
GPIO_MODE_INPUT,
GPIO_MODE_OUTPUT
} GPIO_ModeTypeDef;

typedef enum {
GPIO_PIN_RESET,
GPIO_PIN_SET
} GPIO_PinStateTypeDef;

typedef enum {
GPIO_PIN_0 = (1U << 0),
GPIO_PIN_1 = (1U << 1),
GPIO_PIN_2 = (1U << 2),
GPIO_PIN_3 = (1U << 3),
GPIO_PIN_4 = (1U << 4),
GPIO_PIN_5 = (1U << 5),
GPIO_PIN_6 = (1U << 6),
GPIO_PIN_7 = (1U << 7),
GPIO_PIN_8 = (1U << 8),
GPIO_PIN_9 = (1U << 9),
GPIO_PIN_10 = (1U << 10),
GPIO_PIN_11 = (1U << 11),
GPIO_PIN_12 = (1U << 12),
GPIO_PIN_13 = (1U << 13),
GPIO_PIN_14 = (1U << 14),
GPIO_PIN_15 = (1U << 15),
GPIO_PIN_ALL = 0xFFFFU
} GPIO_PinTypeDef;

typedef struct {
GPIO_ModeTypeDef Mode;
// ... 其他 GPIO 配置参数,例如 Pull-up/Pull-down, Speed 等
} GPIO_InitTypeDef;

void HAL_GPIO_Init(GPIO_PinTypeDef Pin, GPIO_InitTypeDef* GPIO_Init);
void HAL_GPIO_WritePin(GPIO_PinTypeDef Pin, GPIO_PinStateTypeDef PinState);
GPIO_PinStateTypeDef HAL_GPIO_ReadPin(GPIO_PinTypeDef Pin);

// UART 相关函数 (示例,根据实际需求添加)
typedef struct {
uint32_t BaudRate;
// ... 其他 UART 配置参数,例如 DataBits, StopBits, Parity 等
} UART_InitTypeDef;

void HAL_UART_Init(UART_InitTypeDef* UART_Init);
void HAL_UART_Transmit(uint8_t *pData, uint16_t Size);
uint8_t HAL_UART_ReceiveByte(void); // 接收一个字节

// I2C 相关函数 (示例,根据实际需求添加)
typedef struct {
uint32_t ClockSpeed;
// ... 其他 I2C 配置参数,例如 Addressing Mode, Duty Cycle 等
} I2C_InitTypeDef;

void HAL_I2C_Init(I2C_InitTypeDef* I2C_Init);
HAL_StatusTypeDef HAL_I2C_Master_Transmit(uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_I2C_Master_Receive(uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);

// SPI 相关函数 (示例,根据实际需求添加)
typedef struct {
uint32_t BaudRatePrescaler;
// ... 其他 SPI 配置参数,例如 Mode, DataSize, ClockPolarity, ClockPhase 等
} SPI_InitTypeDef;

void HAL_SPI_Init(SPI_InitTypeDef* SPI_Init);
HAL_StatusTypeDef HAL_SPI_Transmit(uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_SPI_Receive(uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_SPI_TransmitReceive(uint8_t *pTxData, uint8_t *pRxData, uint16_t Size, uint32_t Timeout);

// ADC 相关函数 (示例,根据实际需求添加)
void HAL_ADC_Init(void);
uint32_t HAL_ADC_GetValue(uint32_t Channel); // 获取 ADC 通道的值

// Timer 相关函数 (示例,根据实际需求添加)
void HAL_TIM_Base_Start(uint32_t Period, uint32_t Prescaler);
void HAL_TIM_Delay(uint32_t Delay); // 毫秒级延时

// 延时函数 (通用延时,可以使用硬件定时器实现更精确的延时)
void HAL_Delay_ms(uint32_t Delay);
void HAL_Delay_us(uint32_t Delay);

#endif // HAL_H

2. 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
54
55
56
57
58
59
#include "hal.h"
// ... 包含立创天空星开发板的头文件,例如寄存器定义,时钟配置等

// GPIO 相关函数实现 (示例,需要根据实际硬件平台进行配置)
void HAL_GPIO_Init(GPIO_PinTypeDef Pin, GPIO_InitTypeDef* GPIO_Init) {
// ... 根据 Pin 和 GPIO_Init 配置 GPIO 寄存器,例如设置 GPIO 模式、方向、上下拉等
// ... 例如: if (Pin & GPIO_PIN_0) { // 配置 GPIO Pin 0 ... }
}

void HAL_GPIO_WritePin(GPIO_PinTypeDef Pin, GPIO_PinStateTypeDef PinState) {
// ... 根据 Pin 和 PinState 控制 GPIO 输出,例如设置 GPIO 输出高低电平
// ... 例如: if (Pin & GPIO_PIN_0) { // 控制 GPIO Pin 0 输出 ... }
}

GPIO_PinStateTypeDef HAL_GPIO_ReadPin(GPIO_PinTypeDef Pin) {
// ... 读取 GPIO 输入状态,返回 GPIO_PIN_RESET 或 GPIO_PIN_SET
// ... 例如: if (Pin & GPIO_PIN_0) { // 读取 GPIO Pin 0 输入 ... }
return GPIO_PIN_RESET; // 示例返回值,实际需要读取寄存器
}

// UART 相关函数实现 (示例,需要根据实际硬件平台进行配置)
void HAL_UART_Init(UART_InitTypeDef* UART_Init) {
// ... 初始化 UART 寄存器,配置波特率、数据位、停止位、校验位等
}

void HAL_UART_Transmit(uint8_t *pData, uint16_t Size) {
// ... 发送 Size 个字节的数据 pData
for (uint16_t i = 0; i < Size; i++) {
// ... 将 pData[i] 写入 UART 发送数据寄存器
while(/* UART 发送缓冲区满 */); // 等待发送缓冲区空闲
// ... 发送字节 pData[i]
}
}

uint8_t HAL_UART_ReceiveByte(void) {
// ... 接收一个字节,并返回接收到的字节数据
while(/* UART 接收缓冲区为空 */); // 等待接收到数据
// ... 从 UART 接收数据寄存器读取数据并返回
return 0; // 示例返回值,实际需要读取寄存器
}

// I2C, SPI, ADC, Timer 等 HAL 函数的实现 (类似 UART,需要根据具体的硬件平台进行实现)
// ... (省略 I2C, SPI, ADC, Timer 等 HAL 函数的具体实现,原理类似 GPIO 和 UART)

// 延时函数实现 (示例,可以使用循环延时,或者更精确的定时器延时)
void HAL_Delay_ms(uint32_t Delay) {
for (uint32_t i = 0; i < Delay; i++) {
HAL_Delay_us(1000); // 1ms = 1000us
}
}

void HAL_Delay_us(uint32_t Delay) {
volatile uint32_t j;
for (j = 0; j < Delay; j++) {
// 简单的循环延时,实际应用中可以使用更精确的定时器延时
// 可以根据实际的 CPU 时钟频率调整循环次数
for(volatile uint32_t k=0; k<10; k++); // 粗略延时,需要根据实际情况调整
}
}

3. dhtxx.h (DHT22 温湿度传感器驱动头文件 - 示例,根据实际传感器型号修改):

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
#ifndef DHTXX_H
#define DHTXX_H

#include <stdint.h>
#include <stdbool.h>
#include "hal.h"

typedef struct {
float temperature;
float humidity;
bool data_valid;
} DHT_DataTypeDef;

typedef enum {
DHT_TYPE_DHT11,
DHT_TYPE_DHT22
// ... 可以添加其他 DHT 系列传感器类型
} DHT_TypeDef;

typedef struct {
GPIO_PinTypeDef DataPin; // DHT22 数据引脚
DHT_TypeDef Type; // 传感器类型
} DHT_InitTypeDef;

bool DHT_Init(DHT_InitTypeDef* DHT_InitStruct);
DHT_DataTypeDef DHT_ReadData(void);

#endif // DHTXX_H

4. dhtxx.c (DHT22 温湿度传感器驱动源文件 - 示例,根据实际传感器型号和硬件连接修改):

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
#include "dhtxx.h"

static DHT_InitTypeDef DHT_Config; // 保存 DHT 传感器配置

bool DHT_Init(DHT_InitTypeDef* DHT_InitStruct) {
if (DHT_InitStruct == NULL) {
return false;
}
DHT_Config = *DHT_InitStruct;

// 初始化 DHT 数据引脚为输出模式 (用于发送起始信号)
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT;
// ... 其他 GPIO 初始化参数
HAL_GPIO_Init(DHT_Config.DataPin, &GPIO_InitStruct);

return true;
}

DHT_DataTypeDef DHT_ReadData(void) {
DHT_DataTypeDef dht_data = {0.0f, 0.0f, false};
uint8_t raw_data[5] = {0}; // DHT22 返回 5 字节数据
uint8_t checksum;

// 1. 发送起始信号
HAL_GPIO_WritePin(DHT_Config.DataPin, GPIO_PIN_RESET); // 拉低数据线
HAL_Delay_ms(1); // 至少 1ms
HAL_GPIO_WritePin(DHT_Config.DataPin, GPIO_PIN_SET); // 拉高数据线
HAL_Delay_us(30); // 等待 DHT22 响应

// 2. 接收 DHT22 响应信号和数据
// 将数据引脚配置为输入模式
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
HAL_GPIO_Init(DHT_Config.DataPin, &GPIO_InitStruct);

// 等待 DHT22 拉低信号 (80us)
// ... 超时判断 ...
while (HAL_GPIO_ReadPin(DHT_Config.DataPin) == GPIO_PIN_SET) {
// ... 超时处理 ...
}

// 等待 DHT22 拉高信号 (80us)
// ... 超时判断 ...
while (HAL_GPIO_ReadPin(DHT_Config.DataPin) == GPIO_PIN_RESET) {
// ... 超时处理 ...
}

// 接收 40 bits 数据 (5 bytes)
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 8; j++) {
// 等待数据线拉低 (50us)
// ... 超时判断 ...
while (HAL_GPIO_ReadPin(DHT_Config.DataPin) == GPIO_PIN_SET) {
// ... 超时处理 ...
}

// 开始接收数据位
HAL_Delay_us(30); // 延时 30us

if (HAL_GPIO_ReadPin(DHT_Config.DataPin) == GPIO_PIN_SET) {
// 数据位为 1
raw_data[i] |= (1 << (7 - j));
} else {
// 数据位为 0
}

// 等待数据线拉高结束 (大约 26-28us for bit '0', 70us for bit '1')
// ... 超时判断 ...
while (HAL_GPIO_ReadPin(DHT_Config.DataPin) == GPIO_PIN_RESET) {
// ... 超时处理 ...
}
}
}

// 3. 校验数据
checksum = raw_data[0] + raw_data[1] + raw_data[2] + raw_data[3];
if (checksum == raw_data[4]) {
dht_data.humidity = (float)(((uint16_t)raw_data[0] << 8) | raw_data[1]) / 10.0f;
dht_data.temperature = (float)(((uint16_t)raw_data[2] << 8) | raw_data[3]) / 10.0f;
dht_data.data_valid = true;
} else {
dht_data.data_valid = false; // 校验失败
}

return dht_data;
}

5. pms5003.h (PMS5003 PM2.5 传感器驱动头文件 - 示例,根据实际传感器型号修改):

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
#ifndef PMS5003_H
#define PMS5003_H

#include <stdint.h>
#include <stdbool.h>
#include "hal.h"

typedef struct {
uint16_t pm1_0_standard;
uint16_t pm2_5_standard;
uint16_t pm10_standard;
uint16_t pm1_0_atmospheric;
uint16_t pm2_5_atmospheric;
uint16_t pm10_atmospheric;
uint16_t particles_0_3um;
uint16_t particles_0_5um;
uint16_t particles_1_0um;
uint16_t particles_2_5um;
uint16_t particles_5_0um;
uint16_t particles_10um;
bool data_valid;
} PMS5003_DataTypeDef;

typedef struct {
// 可以添加 PMS5003 传感器相关的配置参数,例如 UART 端口号等
} PMS5003_InitTypeDef;

bool PMS5003_Init(PMS5003_InitTypeDef* PMS5003_InitStruct);
PMS5003_DataTypeDef PMS5003_ReadData(void);

#endif // PMS5003_H

6. pms5003.c (PMS5003 PM2.5 传感器驱动源文件 - 示例,根据实际传感器型号和硬件连接修改):

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
#include "pms5003.h"

static PMS5003_InitTypeDef PMS5003_Config; // 保存 PMS5003 传感器配置

bool PMS5003_Init(PMS5003_InitTypeDef* PMS5003_InitStruct) {
if (PMS5003_InitStruct == NULL) {
return false;
}
PMS5003_Config = *PMS5003_InitStruct;

// 初始化 PMS5003 传感器使用的 UART 端口 (如果需要)
// ... 例如 HAL_UART_Init(...)

return true;
}

PMS5003_DataTypeDef PMS5003_ReadData(void) {
PMS5003_DataTypeDef pms_data = {0};
uint8_t raw_data[32] = {0}; // PMS5003 返回 32 字节数据帧
uint16_t frame_len;
uint16_t checksum_calc = 0;
uint16_t checksum_recv;

// 1. 接收 PMS5003 数据帧 (32 bytes)
for (int i = 0; i < 32; i++) {
raw_data[i] = HAL_UART_ReceiveByte(); // 从 UART 接收一个字节
// ... 可以添加接收超时处理 ...
}

// 2. 检查帧头
if (raw_data[0] != 0x42 || raw_data[1] != 0x4d) {
pms_data.data_valid = false;
return pms_data; // 帧头错误
}

// 3. 获取帧长度
frame_len = (raw_data[2] << 8) | raw_data[3];
if (frame_len != 28) { // PMS5003 数据帧长度应该为 28 字节 (数据部分)
pms_data.data_valid = false;
return pms_data; // 帧长度错误
}

// 4. 计算校验和
for (int i = 0; i < 30; i++) { // 校验和计算包括帧头和数据部分,不包括校验和本身
checksum_calc += raw_data[i];
}

// 5. 获取接收到的校验和
checksum_recv = (raw_data[30] << 8) | raw_data[31];

// 6. 校验和对比
if (checksum_calc == checksum_recv) {
// 数据校验通过
pms_data.pm1_0_standard = (raw_data[4] << 8) | raw_data[5];
pms_data.pm2_5_standard = (raw_data[6] << 8) | raw_data[7];
pms_data.pm10_standard = (raw_data[8] << 8) | raw_data[9];
pms_data.pm1_0_atmospheric = (raw_data[10] << 8) | raw_data[11];
pms_data.pm2_5_atmospheric = (raw_data[12] << 8) | raw_data[13];
pms_data.pm10_atmospheric = (raw_data[14] << 8) | raw_data[15];
pms_data.particles_0_3um = (raw_data[16] << 8) | raw_data[17];
pms_data.particles_0_5um = (raw_data[18] << 8) | raw_data[19];
pms_data.particles_1_0um = (raw_data[20] << 8) | raw_data[21];
pms_data.particles_2_5um = (raw_data[22] << 8) | raw_data[23];
pms_data.particles_5_0um = (raw_data[24] << 8) | raw_data[25];
pms_data.particles_10um = (raw_data[26] << 8) | raw_data[27];
pms_data.data_valid = true;
} else {
pms_data.data_valid = false; // 校验失败
}

return pms_data;
}

7. 其他传感器驱动模块 (bmp280.c/h, bh1750.c/h, anemometer.c/h, rain_gauge.c/h):

  • BMP280 (气压传感器): 通常使用 I2C 或 SPI 接口,需要编写驱动程序进行初始化、读取气压和温度数据。
  • BH1750 (光照度传感器): 通常使用 I2C 接口,需要编写驱动程序进行初始化、配置测量模式、读取光照度数据。
  • 风速风向传感器 (Anemometer): 风速传感器通常输出脉冲信号,风向传感器可能输出模拟电压或数字信号 (例如编码器)。需要根据具体传感器类型编写驱动程序,读取风速和风向数据。 可能需要使用 ADC 模块读取模拟电压,使用 GPIO 中断或定时器捕获脉冲信号。
  • 雨量传感器 (Rain Gauge): 通常是翻斗式雨量计,每次翻斗会产生一个脉冲信号。需要使用 GPIO 中断或定时器计数脉冲信号,计算雨量。

8. data_process.c/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
// data_process.h
#ifndef DATA_PROCESS_H
#define DATA_PROCESS_H

#include "dhtxx.h"
#include "pms5003.h"
// ... 其他传感器数据头文件

typedef struct {
float temperature;
float humidity;
uint16_t pm2_5;
float pressure;
uint16_t light_intensity;
float wind_speed;
uint16_t wind_direction;
float rainfall;
bool data_valid;
} EnvironmentDataTypeDef;

EnvironmentDataTypeDef DataProcess_GetEnvironmentData(void);

#endif // DATA_PROCESS_H


// data_process.c
#include "data_process.h"
#include "dhtxx.h"
#include "pms5003.h"
// ... 其他传感器驱动头文件

EnvironmentDataTypeDef DataProcess_GetEnvironmentData(void) {
EnvironmentDataTypeDef env_data = {0};
DHT_DataTypeDef dht_data;
PMS5003_DataTypeDef pms_data;
// ... 其他传感器数据结构

// 1. 读取传感器数据
dht_data = DHT_ReadData();
pms_data = PMS5003_ReadData();
// ... 读取其他传感器数据 ...

// 2. 数据校验 (可以根据需要添加更复杂的校验逻辑)
if (!dht_data.data_valid || !pms_data.data_valid /* ... 其他传感器数据校验 ... */) {
env_data.data_valid = false;
return env_data;
}
env_data.data_valid = true;

// 3. 数据处理 (例如单位转换、滤波、校准等,这里只做简单赋值)
env_data.temperature = dht_data.temperature;
env_data.humidity = dht_data.humidity;
env_data.pm2_5 = pms_data.pm2_5_standard; // 使用标准环境下的 PM2.5 值
// ... 从 BMP280 读取气压数据并赋值 ...
// ... 从 BH1750 读取光照度数据并赋值 ...
// ... 从风速风向传感器驱动读取风速风向数据并赋值 ...
// ... 从雨量传感器驱动读取雨量数据并赋值 ...

return env_data;
}

9. 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
#include "hal.h"
#include "dhtxx.h"
#include "pms5003.h"
#include "data_process.h"
// ... 其他模块头文件

#include <stdio.h> // For printf (调试输出,实际应用中可能需要更合适的日志输出方式)

int main(void) {
// 1. 初始化硬件平台 (时钟、外设等) - *平台相关代码*
// ... 初始化系统时钟、GPIO、UART、I2C、SPI、ADC、Timer 等

// 2. 初始化 HAL 层 (根据具体的硬件平台实现 hal.c 中的函数)
// ... HAL 层初始化已经在 hal.c 中通过函数实现,这里不需要显式调用

// 3. 初始化传感器驱动
DHT_InitTypeDef dht_init = {
.DataPin = GPIO_PIN_1, // 假设 DHT22 数据引脚连接到 GPIO Pin 1
.Type = DHT_TYPE_DHT22
};
DHT_Init(&dht_init);

PMS5003_InitTypeDef pms5003_init = {
// ... PMS5003 初始化参数 (如果需要)
};
PMS5003_Init(&pms5003_init);

// ... 初始化其他传感器驱动 ...

// 4. 主循环
while (1) {
// 4.1. 获取环境数据
EnvironmentDataTypeDef env_data = DataProcess_GetEnvironmentData();

// 4.2. 数据处理和显示 (示例,实际应用中可能需要更复杂的处理和显示方式)
if (env_data.data_valid) {
printf("Temperature: %.1f C, Humidity: %.1f %%\r\n", env_data.temperature, env_data.humidity);
printf("PM2.5: %d ug/m3\r\n", env_data.pm2_5);
// ... 输出其他传感器数据 ...
} else {
printf("Error: Sensor data invalid!\r\n");
}

// 4.3. 延时一段时间 (例如 1 秒采样一次)
HAL_Delay_ms(1000);
}
}

代码编译和运行:

  1. 开发环境搭建: 需要根据立创天空星开发板选择合适的开发环境,例如 Keil MDK, IAR Embedded Workbench, GCC 等。
  2. 代码编译: 使用开发环境编译上述C代码,生成可执行文件。
  3. 代码下载: 将可执行文件下载到立创天空星开发板。
  4. 连接硬件: 将传感器连接到立创天空星开发板的相应引脚。
  5. 运行和测试: 上电运行系统,通过串口或其他方式查看输出数据,验证系统功能和性能。
  6. 调试和优化: 根据测试结果进行代码调试和优化,提高系统的可靠性和效率。

代码扩展和升级方向:

  • 数据存储: 添加数据存储模块,将采集到的环境数据存储到本地存储介质 (例如 SD卡, Flash) 或云端数据库。
  • 数据可视化: 可以通过 LCD 屏幕、OLED 屏幕或者 Web 页面等方式将环境数据可视化显示出来。
  • 数据上传: 可以通过 WiFi, GPRS, NB-IoT 等通信方式将环境数据上传到云平台,实现远程监控和管理。
  • 告警功能: 设置环境参数阈值,当环境参数超过阈值时,触发告警 (例如蜂鸣器报警, 短信告警, App 推送等)。
  • 低功耗设计: 对于电池供电的室外环境监测系统,低功耗设计非常重要。可以采用低功耗模式、休眠模式、优化代码执行效率等方法降低功耗。
  • OTA 升级: 支持 OTA (Over-The-Air) 固件升级,方便远程维护和升级系统。
  • 添加更多传感器: 根据实际需求,可以方便地添加更多类型的传感器,例如 CO2 传感器, 噪声传感器, 土壤湿度传感器等。

总结:

以上代码提供了一个基于分层架构和模块化设计的嵌入式室外环境监测系统的基本框架和核心模块的实现思路。 实际项目中,需要根据具体的硬件平台、传感器型号、功能需求和性能指标进行详细的代码编写、测试和优化。 整个开发过程需要遵循可靠性、高效性、可扩展性、可维护性的设计原则,并采用经过实践验证的技术和方法,最终构建一个稳定可靠、功能完善的嵌入式系统。

希望这个详细的解答和代码示例能够帮助你理解和实现这个嵌入式项目。 请记住,这只是一个示例代码框架,实际应用中需要根据具体的硬件平台和传感器进行适配和调整,并进行充分的测试和验证。

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