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

系统设计架构:分层架构与模块化设计
为了构建一个可靠、高效且易于维护和扩展的嵌入式系统,我推荐采用分层架构和模块化设计相结合的方法。这种架构将系统划分为不同的层次,每个层次负责特定的功能,层与层之间通过清晰定义的接口进行通信。模块化设计则将每个层次进一步细分为独立的模块,提高代码的复用性和可维护性。
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>
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_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);
typedef struct { uint32_t BaudRate; } 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);
typedef struct { uint32_t ClockSpeed; } 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);
typedef struct { uint32_t BaudRatePrescaler; } 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);
void HAL_ADC_Init(void); uint32_t HAL_ADC_GetValue(uint32_t Channel);
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
|
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"
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) { return GPIO_PIN_RESET; }
void HAL_UART_Init(UART_InitTypeDef* UART_Init) { }
void HAL_UART_Transmit(uint8_t *pData, uint16_t Size) { for (uint16_t i = 0; i < Size; i++) { while(); } }
uint8_t HAL_UART_ReceiveByte(void) { while(); return 0; }
void HAL_Delay_ms(uint32_t Delay) { for (uint32_t i = 0; i < Delay; i++) { HAL_Delay_us(1000); } }
void HAL_Delay_us(uint32_t Delay) { volatile uint32_t j; for (j = 0; j < Delay; j++) { 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_TypeDef;
typedef struct { GPIO_PinTypeDef DataPin; DHT_TypeDef Type; } DHT_InitTypeDef;
bool DHT_Init(DHT_InitTypeDef* DHT_InitStruct); DHT_DataTypeDef DHT_ReadData(void);
#endif
|
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;
bool DHT_Init(DHT_InitTypeDef* DHT_InitStruct) { if (DHT_InitStruct == NULL) { return false; } DHT_Config = *DHT_InitStruct;
GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT; 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}; uint8_t checksum;
HAL_GPIO_WritePin(DHT_Config.DataPin, GPIO_PIN_RESET); HAL_Delay_ms(1); HAL_GPIO_WritePin(DHT_Config.DataPin, GPIO_PIN_SET); HAL_Delay_us(30);
GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; HAL_GPIO_Init(DHT_Config.DataPin, &GPIO_InitStruct);
while (HAL_GPIO_ReadPin(DHT_Config.DataPin) == GPIO_PIN_SET) { }
while (HAL_GPIO_ReadPin(DHT_Config.DataPin) == GPIO_PIN_RESET) { }
for (int i = 0; i < 5; i++) { for (int j = 0; j < 8; j++) { while (HAL_GPIO_ReadPin(DHT_Config.DataPin) == GPIO_PIN_SET) { }
HAL_Delay_us(30);
if (HAL_GPIO_ReadPin(DHT_Config.DataPin) == GPIO_PIN_SET) { raw_data[i] |= (1 << (7 - j)); } else { }
while (HAL_GPIO_ReadPin(DHT_Config.DataPin) == GPIO_PIN_RESET) { } } }
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_InitTypeDef;
bool PMS5003_Init(PMS5003_InitTypeDef* PMS5003_InitStruct); PMS5003_DataTypeDef PMS5003_ReadData(void);
#endif
|
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;
bool PMS5003_Init(PMS5003_InitTypeDef* PMS5003_InitStruct) { if (PMS5003_InitStruct == NULL) { return false; } PMS5003_Config = *PMS5003_InitStruct;
return true; }
PMS5003_DataTypeDef PMS5003_ReadData(void) { PMS5003_DataTypeDef pms_data = {0}; uint8_t raw_data[32] = {0}; uint16_t frame_len; uint16_t checksum_calc = 0; uint16_t checksum_recv;
for (int i = 0; i < 32; i++) { raw_data[i] = HAL_UART_ReceiveByte(); }
if (raw_data[0] != 0x42 || raw_data[1] != 0x4d) { pms_data.data_valid = false; return pms_data; }
frame_len = (raw_data[2] << 8) | raw_data[3]; if (frame_len != 28) { pms_data.data_valid = false; return pms_data; }
for (int i = 0; i < 30; i++) { checksum_calc += raw_data[i]; }
checksum_recv = (raw_data[30] << 8) | raw_data[31];
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
| #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
#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;
dht_data = DHT_ReadData(); pms_data = PMS5003_ReadData();
if (!dht_data.data_valid || !pms_data.data_valid ) { env_data.data_valid = false; return env_data; } env_data.data_valid = true;
env_data.temperature = dht_data.temperature; env_data.humidity = dht_data.humidity; env_data.pm2_5 = pms_data.pm2_5_standard;
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>
int main(void) {
DHT_InitTypeDef dht_init = { .DataPin = GPIO_PIN_1, .Type = DHT_TYPE_DHT22 }; DHT_Init(&dht_init);
PMS5003_InitTypeDef pms5003_init = { }; PMS5003_Init(&pms5003_init);
while (1) { EnvironmentDataTypeDef env_data = DataProcess_GetEnvironmentData();
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"); }
HAL_Delay_ms(1000); } }
|
代码编译和运行:
- 开发环境搭建: 需要根据立创天空星开发板选择合适的开发环境,例如 Keil MDK, IAR Embedded Workbench, GCC 等。
- 代码编译: 使用开发环境编译上述C代码,生成可执行文件。
- 代码下载: 将可执行文件下载到立创天空星开发板。
- 连接硬件: 将传感器连接到立创天空星开发板的相应引脚。
- 运行和测试: 上电运行系统,通过串口或其他方式查看输出数据,验证系统功能和性能。
- 调试和优化: 根据测试结果进行代码调试和优化,提高系统的可靠性和效率。
代码扩展和升级方向:
- 数据存储: 添加数据存储模块,将采集到的环境数据存储到本地存储介质 (例如 SD卡, Flash) 或云端数据库。
- 数据可视化: 可以通过 LCD 屏幕、OLED 屏幕或者 Web 页面等方式将环境数据可视化显示出来。
- 数据上传: 可以通过 WiFi, GPRS, NB-IoT 等通信方式将环境数据上传到云平台,实现远程监控和管理。
- 告警功能: 设置环境参数阈值,当环境参数超过阈值时,触发告警 (例如蜂鸣器报警, 短信告警, App 推送等)。
- 低功耗设计: 对于电池供电的室外环境监测系统,低功耗设计非常重要。可以采用低功耗模式、休眠模式、优化代码执行效率等方法降低功耗。
- OTA 升级: 支持 OTA (Over-The-Air) 固件升级,方便远程维护和升级系统。
- 添加更多传感器: 根据实际需求,可以方便地添加更多类型的传感器,例如 CO2 传感器, 噪声传感器, 土壤湿度传感器等。
总结:
以上代码提供了一个基于分层架构和模块化设计的嵌入式室外环境监测系统的基本框架和核心模块的实现思路。 实际项目中,需要根据具体的硬件平台、传感器型号、功能需求和性能指标进行详细的代码编写、测试和优化。 整个开发过程需要遵循可靠性、高效性、可扩展性、可维护性的设计原则,并采用经过实践验证的技术和方法,最终构建一个稳定可靠、功能完善的嵌入式系统。
希望这个详细的解答和代码示例能够帮助你理解和实现这个嵌入式项目。 请记住,这只是一个示例代码框架,实际应用中需要根据具体的硬件平台和传感器进行适配和调整,并进行充分的测试和验证。