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 %srn", mqtt_params.subtopic); printf("Subscribed:%srnrn", 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,%srnrn", 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:%srn", buf); HttpRequestLine req_line; if (parse_request_line((char *)buf, &req_line) == 0) { printf("Method: %sn", req_line.method); // 输出: GET printf("URI: %sn", req_line.uri); // 输出: / printf("Version: %sn", 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 OKrn" "Content-Type: application/jsonrn" "Connection: closern" "rn" "%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 在物联网的实用性,感谢大家的耐心阅读!如果您在阅读过程中有任何疑问,或者希望进一步了解这款产品及其应用,欢迎随时通过私信或评论区留言。我们会尽快回复您的消息,为您提供更详细的解答和帮助!
审核编辑 黄宇
-
以太网
+关注
关注
41文章
5738浏览量
176766 -
服务器
+关注
关注
13文章
9850浏览量
88361 -
开发板
+关注
关注
25文章
5758浏览量
106183 -
阿里云
+关注
关注
3文章
1016浏览量
44371 -
MQTT
+关注
关注
5文章
697浏览量
23840
发布评论请先 登录
汽车空气质量检测与改善方案简介
STM32空气质量检测应用教程
【Thunderboard Sense试用申请】移动空气质量监测站
什么是空气质量网格化监测?
怎样去设计基于STM32的空气质量监测系统
基于单片机的环境空气质量检测系统的设计
基于单片机的空气质量监测的设计资料分享
网格化空气质量监测站的特点
空气质量监测仪:掌控空气质量
智能农业监控系统:MQTT阿里云平台监测+内置Web网页控制+代码解析

评论