从零搭建W55MH32以太网环境空气质量检测系统:MQTT阿里云平台监测+内置Web网页显示+代码全解析
1 前言
我是个挺爱琢磨生活细节的人,在不同的场所我总是会觉得我鼻子不舒服,嗓子也有点干。但是有的地方也挺干净整洁的呀,我心里时常想:“这也没人抽烟,也没刮大风啊?难道是空气不行?”可空气好不好,看不见摸不着啊!PM2.5:那些比头发丝还细几十倍的灰尘颗粒,吸进去就赖在肺里不走了,长期下来可不是闹着玩的。家里有老人小孩的,更得注意这个。二氧化碳:你以为人多的地方只是“闷”?那是CO2超标了!在教室、会议室甚至自己关着门的卧室待久了,头晕、犯困、注意力不集中,很可能就是它在捣鬼。办公室下午集体犯困?搞不好就是通风不够,CO2爆表了!空气干不干净?不知道! 全凭感觉,鼻子嗓子不舒服了才后知后觉。开不开窗?很纠结! 外面看着天挺好,开了窗结果可能进来一堆灰(PM2.5);关窗吧,屋里人一多,CO2蹭蹭涨,闷得慌。净化器/新风开不开?开多久?纯靠猜!电费哗哗流,效果怎么样?心里没底。我就想:能不能做个简单、便宜又能联网的小玩意儿,让我随时知道身边的空气“底细”?
既然要“随时知道”,那肯定得联网!把数据发到手机或者网页上才方便。自己做网络协议?想想就头大!直到我发现了 W55MH32 这块板子,简直是量身定做:“自带网卡”的MCU: 它最牛的地方就是 芯片里直接集成了硬件TCP/IP协议栈、以太网MAC和PHY!这意味着什么?我不需要再外接复杂的WiFi模块或者额外跑协议栈消耗宝贵的CPU资源。硬件搞定网络底层,开发更简单! 我只需要关心怎么读取传感器数据,然后把数据打包发出去就行。成本低、连线少: 基本上,加个网络变压器和RJ45网口,插根网线,它就能直接上网了!稳定性也比很多WiFi方案靠谱。性能够用: 处理几个传感器的数据,做点简单计算,再通过网络发出去,W55MH32的性能绰绰有余。
基于以上,我决定动手搞这个项目:本项目以W55MH32 以太网开发板为核心,通过JW01-CO2传感器(实时监测二氧化碳浓度)与DC01红外PM2.5传感器(精准检测空气中细颗粒物含量),构建了一套智能化环境监测解决方案。系统通过MQTT协议与阿里云物联网平台建立稳定通信链路,实现监测数据的云端远程传输、存储与分析,支持用户通过移动端或Web端实时查看数据历史趋势与告警信息。 创新性地开发了本地实时数据可视化网页,设备内置轻量级Web服务器,用户可直接通过浏览器访问设备IP地址,获取传感器数据的动态刷新显示(5秒更新一次)。最终达到让“看不见”的空气变得“看得见、摸得着、管得住”,用数据指导生活,呼吸得更明白、更健康!
开源项目源码:https://gitee.com/hupeiyuan0218/w55mh32_aircheck.git
视频演示:https://www.bilibili.com/video/BV162tzzaEcg/?vd_source=0e464ab43d68457de7bac889723ef284
2 项目环境
2.1 硬件环境
- W55MH32开发板
- JW01-CO2传感器
- DC01红外PM2.5传感器
- 网线
- 若干杜邦线
2.2 软件准备
- 开发环境:Keil uVision 5
- Wiznet串口助手(其他串口工具也可以)
- 阿里云服务器
- 浏览器
2.3 方案图示

3 注册阿里云账号以及建立物模型
注册账号在这里不进行赘述,下面我们看如何建立物模型。
3.1 创建产品
搜索物联网平台 → 点击物联网平台 → 点击公共实例

选择设备管理 → 点击产品 → 点击创建产品

设置产品名称、所属品类、节点类型、连网方式、数据格式

3.2 添加设备
创建成功产品后点击添加设备 → 输入DeviceName

3.3 创建物模型
定义物联网模型,选择产品 → 点击功能定义 → 点击前往编辑草稿

点击添加自定义功能 → 选择功能类型 → 输入功能名称 → 设置标识符 → 设置数据类型 → 设置取值范围 → 设置步长 → 设置单位 → 设置读写类型

3.4 获取设备连接信息
点击产品 → 选择创建的产品 → 点击Topic类列表 → 选择物模型通信
订阅平台下发消息的主题:
/sys/a1QsjDLF7Rq/${deviceName}/thing/service/property/set
设备发布消息主题:
/sys/a1QsjDLF7Rq/${deviceName}/thing/event/property/post
注:两个主题中的{device_id}需要更改为自己设备的ID
点击设备管理 → 选择设备 → 点击查看

点击查看DeviceSecret,获取到设备证书
{
"ProductKey": "a1QsjDLF7Rq",
"DeviceName": "W55MH32",
"DeviceSecret": "5a2b4550d9a3e7a8c19c1c8943d8f710"
}

获取MQTT连接参数

4 例程修改
本次以MQTT&Aliyun为例:
找到do_mqtt.c文件,把上述中的参数更换为自己阿里云的参数,这些参数我们在上面都有讲到,可以自行替换。
mqttconn mqtt_params = {
.mqttHostUrl = "a1QsjDLF7Rq.iot-as-mqtt.cn-shanghai.aliyuncs.com",
.server_ip =
{
0,
}, /*Define the Connection Server IP*/
.port = 1883, /*Define the connection service port number*/
.clientid = "a1QsjDLF7Rq.W55MH32|securemode=2,signmethod=hmacsha256,"
"timestamp=1752545118110|", /*Define the client ID*/
.username = "W55MH32&a1QsjDLF7Rq", /*Define the user name*/
.passwd = "f0dcd6ce4df2493438e42dbd9dd789d348309201ac18f18dd158ddceea45fc5"
"d", /*Define user passwords*/
.pubtopic =
"/sys/a1QsjDLF7Rq/W55MH32/thing/event/property/post", /*Define the
publication
message*/
.subtopic =
"/sys/a1QsjDLF7Rq/W55MH32/thing/service/property/set", /*Define
subscription
messages*/
.pubQoS = QOS0, /*Defines the class of service for publishing messages*/
};
先发送订阅主题{device_id}的数据包 /sys/a1QsjDLF7Rq/W55MH32/thing/service/property/set订阅成功后,接收到来自主题的信息SUCCESS。
case SUB: {
ret = MQTTSubscribe(&c, mqtt_params.subtopic,mqtt_params.subQoS,
messageArrived); /* Subscribe to Topics */
printf("Subscribing to %s\r\n", mqtt_params.subtopic);
printf("Subscribed:%s\r\n\r\n", ret == SUCCESSS ? "success" : "failed");
if (ret != SUCCESSS) {
run_status = ERR;
} else {
run_status = PUB_MESSAGE;
}
break;
}
接下来进行数据发布,这一步请确保服务ID(Temperature)和属性名(CurrentTemperature)与您在阿里云上定义的产品模型完全一致(包括大小写)。发布成功后更新状态为保持连接状态。
case PUB_MESSAGE: {
// 动态构建JSON负载
snprintf(payload_buffer, PAYLOAD_BUFFER_SIZE,
"{\"id\":\"123\",\"version\":\"1.0\","
"\"params\":{\"co2\":%d,\"PM2_5\":%d},"
"\"method\":\"thing.event.property.post\"}",
co2_concentration, concentration);
pubmessage.qos = QOS0;
pubmessage.payload = payload_buffer;
pubmessage.payloadlen = strlen(payload_buffer);
ret = MQTTPublish(&c, (char *)&(mqtt_params.pubtopic),
&pubmessage); /* Publish message */
if (ret != SUCCESSS) {
run_status = ERR;
} else {
printf("publish:%s,%s\r\n\r\n", mqtt_params.pubtopic, payload_buffer);
run_status = KEEPALIVE;
}
break;
}
完成这两步就可以成功连接阿里云,并上传数据到阿里云。
采集数据采用的传感器采用的是JW01-CO2传感器、DC01红外PM2.5传感器,这两个传感器都是通过串口通信,首先进行串口初始化,然后传感器每秒自动将采集到的数据通过串口发送。DC01红外PM2.5传感器通过串口二连接,JW01-CO2传感器通过串口三连接。
初始化串口二、串口三
// 串口2初始化函数
void pm25_usart2_init(uint32_t baudrate)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
// 使能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
// 配置TX引脚 (PA0)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 配置RX引脚 (PA3)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 配置串口参数
USART_InitStructure.USART_BaudRate = baudrate;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART2, &USART_InitStructure);
// 使能接收中断
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);
// 配置NVIC
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
// 使能串口
USART_Cmd(USART2, ENABLE);
}
// 串口3初始化
void co2_usart3_init(uint32_t baudrate)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
// 使能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);
// 配置USART3 TX (PB10) 为复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOB, &GPIO_InitStructure);
// 配置USART3 RX (PB11) 为浮空输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOB, &GPIO_InitStructure);
// USART参数配置
USART_InitStructure.USART_BaudRate = baudrate;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART3, &USART_InitStructure);
// 使能接收中断
USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);
// 配置USART3中断
NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; // 优先级低于串口2
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
// 使能串口
USART_Cmd(USART3, ENABLE);
}
初始化完成之后,通过数据格式进行解析,将PM2.5浓度、二氧化碳浓度计算出来
//解析计算PM2.5浓度
void pm25_data_process_usart2(void)
{
// 计算校验和:前三字节的低7位之和
uint8_t calculated_checksum = (rx_buffer[0] & 0x7F) +
(rx_buffer[1] & 0x7F) +
(rx_buffer[2] & 0x7F);
calculated_checksum &= 0x7F; // 保留低7位
// 获取接收到的校验位(第四字节)
uint8_t received_checksum = rx_buffer[3] & 0x7F;
// 校验检查
if (calculated_checksum != received_checksum)
{
checksum_error = 1;
return; // 校验失败,丢弃数据
}
checksum_error = 0;
// 计算浓度值
// 浓度值 = (DATAH[6:0] << 7) | DATAL[6:0]
g_pm25_concentration = ((rx_buffer[1] & 0x7F) << 7) | (rx_buffer[2] & 0x7F);
}
//解析计算CO2浓度
void co2_usart3_init(uint32_t baudrate) {
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
// 使能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);
// 配置USART3 TX (PB10) 为复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOB, &GPIO_InitStructure);
// 配置USART3 RX (PB11) 为浮空输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOB, &GPIO_InitStructure);
// USART参数配置
USART_InitStructure.USART_BaudRate = baudrate;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl =
USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART3, &USART_InitStructure);
// 使能接收中断
USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);
// 配置USART3中断
NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; // 优先级低于串口2
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
// 使能串口
USART_Cmd(USART3, ENABLE);
}
这样我们就获取到了环境空气质量数据,再创建一个网页,将PM2.5浓度、CO2浓度进行显示出来,每五秒刷新一次。
int32_t loopback_tcps(uint8_t sn, uint8_t *buf, uint16_t port) {
int32_t ret;
uint16_t size = 0, sentsize = 0;
switch (getSn_SR(sn)) {
case SOCK_ESTABLISHED:
if (getSn_IR(sn) & Sn_IR_CON) {
setSn_IR(sn, Sn_IR_CON);
}
if ((size = getSn_RX_RSR(sn)) >
0) // Don't need to check SOCKERR_BUSY because it doesn't not occur.
{
if (size > DATA_BUF_SIZE)
size = DATA_BUF_SIZE;
ret = recv(sn, buf, size);
if (ret <= 0)
return ret; // check SOCKERR_BUSY & SOCKERR_XXX. For showing the
// occurrence of SOCKERR_BUSY.
size = (uint16_t)ret;
sentsize = 0;
buf[size] = 0x00;
printf("rece data:%s\r\n", buf);
HttpRequestLine req_line;
if (parse_request_line((char *)buf, &req_line) == 0) {
printf("Method: %s\n", req_line.method); // 输出: GET
printf("URI: %s\n", req_line.uri); // 输出: /
printf("Version: %s\n", req_line.version); // 输出: HTTP/1.1
if (strcmp(req_line.method, "GET") == 0 &&
strcmp(req_line.uri, "/") == 0) {
char html_buffer[2048]; // 确保缓冲区足够大
snprintf(html_buffer, sizeof(html_buffer), index_page,
g_co2_concentration, g_pm25_concentration);
uint16_t content_len = strlen(html_buffer);
sentsize = 0;
while (content_len != sentsize) {
ret = send(sn, (uint8_t *)html_buffer + sentsize,
content_len - sentsize);
if (ret < 0) {
close(sn);
return ret;
}
sentsize += ret;
}
disconnect(sn);
close(sn);
} else if (strcmp(req_line.method, "GET") == 0 &&
strcmp(req_line.uri, "/api/sensor") == 0) {
char sensor_data[64];
sprintf(sensor_data, "{\"pm25\":%u,\"co2\":%u}", g_co2_concentration,
g_pm25_concentration);
char response[128];
sprintf(response,
"HTTP/1.1 200 OK\r\n"
"Content-Type: application/json\r\n"
"Connection: close\r\n"
"\r\n"
"%s",
sensor_data);
send(sn, (uint8_t *)response, strlen(response));
disconnect(sn);
close(sn);
} else {
send(sn, (uint8_t *)HTTP_RESPONSE_404, strlen(HTTP_RESPONSE_404));
disconnect(sn);
close(sn);
}
}
else {
printf("解析失败!\n");
}
}
break;
case SOCK_CLOSE_WAIT:
if ((ret = disconnect(sn)) != SOCK_OK)
return ret;
break;
case SOCK_INIT:
if ((ret = listen(sn)) != SOCK_OK)
return ret;
break;
case SOCK_CLOSED:
if ((ret = socket(sn, Sn_MR_TCP, port, 0x00)) != sn)
return ret;
break;
default:
break;
}
return 1;
}
最后在主函数中添加初始化并在while函数中添加函数
pm25_usart2_init(9600);
co2_usart3_init(9600);
while (1) {
do_mqtt();
pm25_data_process_usart2();
co2_data_process_usart3();
loopback_tcps(1, ethernet_buf, 80);
}
5 功能验证
程序烧录完毕,硬件连接完成如下图所示:

硬件连接完毕,上电通过串口助手打印如下信息:

二氧化碳传感器需要预热5-10分钟进行测试,预热完成之后即可测试出环境空气中CO2和PM2.5浓度含量,同步上传到阿里云服务器和在本地网页,每五秒刷新一次。

6 总结
本项目通过整合硬件资源、通信协议与云平台能力,成功实现环境空气质量检测功能,验证了 W55MH32 在物联网的实用性,感谢大家的耐心阅读!如果您在阅读过程中有任何疑问,或者希望进一步了解这款产品及其应用,欢迎随时通过私信或评论区留言。我们会尽快回复您的消息,为您提供更详细的解答和帮助!
3018

被折叠的 条评论
为什么被折叠?



