W55MH32 环境检测:内置 Web 页面实时可视化

目录

[1 前言](#1 前言)

[2 项目环境](#2 项目环境)

[2.1 硬件准备](#2.1 硬件准备)

[2.2 软件准备](#2.2 软件准备)

[2.3 方案图示](#2.3 方案图示)

[3 应用场景](#3 应用场景)

[3.1 农业大棚环境管理](#3.1 农业大棚环境管理)

[3.2 仓储环境监测](#3.2 仓储环境监测)

[3.3 办公室与公共空间监测](#3.3 办公室与公共空间监测)

[4 例程修改](#4 例程修改)

[4.1 主函数分析](#4.1 主函数分析)

[4.2 ADC采集](#4.2 ADC采集)

[4.3 CO2传感器读取](#4.3 CO2传感器读取)

[4.4 温湿度传感器读取](#4.4 温湿度传感器读取)

[4.5 网页通信处理](#4.5 网页通信处理)

[4.6 网页显示](#4.6 网页显示)

[5 功能验证](#5 功能验证)

[6 总结](#6 总结)


1 前言

HTTP(超文本传输协议,HyperText Transfer Protocol)是一种用于分布式、协作式、超媒体信息系统的应用层协议, 基于 TCP/IP 通信协议来传递数据,是万维网(WWW)的数据通信的基础。设计 HTTP 最初的目的是为了提供一种发布和接收 HTML 页面的方法,通过 HTTP 或者 HTTPS 协议请求的资源由统一资源标识符(Uniform Resource Identifiers,URI)来标识。 以上是HTTP协议的简介,如想深入了解该协议,请参考mozilla网站上的介绍: HTTP 概述 - HTTP | MDN

W55MH32 是 WIZnet 新推出的高性能以太网单片机。它采用高性能 Arm® Cortex-M3 内核,主频最高达 216MHz,内置 1024KB FLASH、96KB SRAM 。尤为突出的是,其搭载 WIZnet TCP/IP offload 引擎(TOE),集成全硬件 TCP/IP 协议栈、MAC 及 PHY ,还配备 32KB 独立以太网收发缓存,供 8 个硬件 socket 使用,是真正的All-in-One解决方案。

2 项目环境

2.1 硬件准备

  1. W55MH32L-EVB
  2. 一根网线
  3. USB Type-C
  4. C02传感器
  5. 光照传感器

2.2 软件准备

  1. 例程链接:w5500.com/w55mh32.html
  2. 开发环境:keil uvision 5
  3. 飞思创串口助手
  4. 浏览器

2.3 方案图示

3 应用场景

本项目基于 W55MH32 构建的多参数环境监测系统,凭借其低成本、高集成度、Web 可视化等特性,可广泛应用于多个实际场景,满足不同领域的环境感知需求。以下是主要应用场景说明:

3.1 农业大棚环境管理

农业生产中,环境参数直接影响作物生长效率。本系统可部署于温室大棚,实现:

  • 实时监测大棚内温湿度(影响作物蒸腾与病害风险)、光照强度(光合作用关键因素)和 CO₂浓度(作物生长碳源);
  • 通过 Web 页面远程查看数据,无需现场巡检,降低人工成本;
  • 结合历史数据曲线分析环境变化规律,优化灌溉、通风、补光等农事操作策略。

相较于传统农业监测设备,本方案成本更低且支持多参数集成,适合中小规模种植户使用。

3.2 仓储环境监测

对于粮食、药品、电子元件等仓储场景,环境温湿度和空气质量是保障存储品质的关键:

  • 监测仓库内温湿度,防止粮食霉变、药品失效或电子元件受潮;
  • 实时追踪封闭空间 CO₂浓度,避免因通风不足导致的货物变质;
  • 通过历史数据曲线分析环境变化趋势,优化仓储条件与通风周期。

系统支持 24 小时不间断运行,数据稳定性高,适合长期无人值守的仓储场景。

3.3 办公室与公共空间监测

在办公楼、教室、商场等人员密集场所,空气质量与舒适度直接影响人员健康与工作效率:

  • 实时监测 CO₂浓度,当人员密集导致浓度升高时,自动联动通风系统改善空气质量;
  • 光照强度数据可用于公共区域照明调节,实现节能降耗;
  • 管理人员通过浏览器即可远程查看各区域环境状态,集中管控多个监测点(可通过扩展 IP 地址实现多设备部署)。

系统部署简单,只需连接网线即可接入局域网,无需复杂配置。

4 例程修改

4 .1 主函数分析

这段代码实现了一个环境传感器监控系统,通过多种传感器采集温湿度、光照强度和CO2浓度数据,并通过HTTP更新网页显示生成数据曲线。

复制代码
#include <stdlib.h>`
`#include <string.h>`
`#include <stdio.h>`
`#include "wizchip_conf.h"`
`#include "wiz_interface.h"`
`#include "bsp_tim.h"`
`#include "bsp_uart.h"`
`#include "bsp_rcc.h"`
`#include "delay.h"`
`#include "loopback.h"`
`#include "bsp_adc.h"`
`#include "i2c_sensor.h"`
`#include "uart_sensor.h"`
`#include "light_sensor.h"`

`extern MYI2C_Struct SENx;`

`#define DEFAULT_MAC_EN        1`
`#define SOCKET_ID             0`
`#define ETHERNET_BUF_MAX_SIZE (1024 * 2)`

`float g_humidity_value =` `0.0;`
`float g_temperature =` `0.0;`
`float g_light_intensity =` `0.0;`
`uint16_t g_co2_concentration =` `0;`

`wiz_NetInfo default_net_info =` `{`
    `.mac  =` `{0x00,` `0x08,` `0xdc,` `0x12,` `0x22,` `0x12},`
    `.ip   =` `{192,` `168,` `1,` `30},`
    `.gw   =` `{192,` `168,` `1,` `1},`
    `.sn   =` `{255,` `255,` `255,` `0},`
    `.dns  =` `{8,` `8,` `8,` `8},`
    `.dhcp = NETINFO_DHCP`
`};`

`uint16_t local_port =` `8080;`
`uint8_t  ethernet_buf[ETHERNET_BUF_MAX_SIZE]` `=` `{0};`

`void` `read_sensors(void)`
`{`
`    g_light_intensity =light_read_intensity();`
    
    `// 读取温湿度`
    `MYI2C_Handle(&SENx);`
`    g_humidity_value = SENx.RH;`
`    g_temperature = SENx.T;`
    
    `// 读取CO2浓度`
`    g_co2_concentration =` `co2_sensor_get_concentration();`
    
`printf("Sensor Data - Temp: %.1fC, Humidity: %.1f%%, Light: %.0flux, CO2: %dppm\r\n",` 
`           g_temperature, g_humidity_value, g_light_intensity, g_co2_concentration);`
`}`
`int` `main(void)`
`{`
    `rcc_clk_config();`
    `delay_init();`
    `console_usart_init(115200);`
    `tim3_init();`
    
    `printf("Environmental Sensor Monitoring System\r\n");`
    
    `adc_dma_init();`
    `co2_sensor_init();`
    `MYI2C_Init(&SENx,` `2000,` `0x38);`

    `wiz_toe_init();`
`#if DEFAULT_MAC_EN == 1`
    `getSHAR(default_net_info.mac);`
`#endif`

    `wiz_phy_link_check();`
    `network_init(ethernet_buf,` `&default_net_info);`

    `while` `(1)`
    `{`
        `read_sensors();`
        `loopback_tcps(SOCKET_ID, ethernet_buf, local_port);`
        `delay_ms(2000);`
    `}`
`}`




`

4 .2 ADC采集

通过配置 ADC 为连续扫描模式、DMA 为循环传输模式并关联二者,实现了对 PA3 引脚连接的光传感器数据的高效连续采集,还提供了对 DMA 缓冲区样本求平均值进行滤波,为光照强度计算提供原始数据。

复制代码
#include "bsp_adc.h"`
`#include "w55mh32_adc.h"`
`#include "w55mh32_dma.h"`
`#include "w55mh32_gpio.h"`
`#include "w55mh32_rcc.h"`

`/* Static buffer for ADC DMA data (stores light sensor readings) */`
`static` `uint16_t s_adc_dma_buffer[ADC_BUFFER_SIZE];`

`/**`
` * @brief Initialize ADC with DMA`
` * @details Configures ADC for light sensor (PA3), GPIO, and DMA for`
` * continuous sampling`
` */`
`void` `adc_dma_init(void)`
`{`
`    ADC_InitTypeDef  adc_init_struct;`
`    GPIO_InitTypeDef gpio_init_struct;`
`    DMA_InitTypeDef  dma_init_struct;`

    `// Enable clocks for ADC, GPIO, and DMA`
    `RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE);`
    `RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);`

    `// Configure GPIO pin PA3 as analog input (light sensor)`
`    gpio_init_struct.GPIO_Pin  = GPIO_Pin_3;`
`    gpio_init_struct.GPIO_Mode = GPIO_Mode_AIN;`
    `GPIO_Init(GPIOA,` `&gpio_init_struct);`

    `// Configure ADC in continuous scan mode`
    `ADC_StructInit(&adc_init_struct);`
`    adc_init_struct.ADC_Mode               = ADC_Mode_Independent;`
`    adc_init_struct.ADC_ScanConvMode       = ENABLE;`
`    adc_init_struct.ADC_ContinuousConvMode = ENABLE;`
`    adc_init_struct.ADC_ExternalTrigConv   = ADC_ExternalTrigConv_None;`
`    adc_init_struct.ADC_DataAlign          = ADC_DataAlign_Right;`
`    adc_init_struct.ADC_NbrOfChannel       =` `1;` `// Only light sensor channel`
    `ADC_Init(ADC1,` `&adc_init_struct);`

    `// Configure ADC channel and sampling time (light sensor on PA3)`
    `ADC_RegularChannelConfig(ADC1, ADC_Channel_3,` `1, ADC_SampleTime_55Cycles5);`

    `// Configure DMA for ADC data transfer`
    `DMA_DeInit(DMA1_Channel1);`
`    dma_init_struct.DMA_PeripheralBaseAddr =` `(uint32_t)&ADC1->DR;`
`    dma_init_struct.DMA_MemoryBaseAddr     =` `(uint32_t)s_adc_dma_buffer;`
`    dma_init_struct.DMA_DIR                = DMA_DIR_PeripheralSRC;`
`    dma_init_struct.DMA_BufferSize         = ADC_BUFFER_SIZE;`
`    dma_init_struct.DMA_PeripheralInc      = DMA_PeripheralInc_Disable;`
`    dma_init_struct.DMA_MemoryInc          = DMA_MemoryInc_Enable;`
`    dma_init_struct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;`
`    dma_init_struct.DMA_MemoryDataSize     = DMA_MemoryDataSize_HalfWord;`
`    dma_init_struct.DMA_Mode               = DMA_Mode_Circular;`
`    dma_init_struct.DMA_Priority           = DMA_Priority_High;`
`    dma_init_struct.DMA_M2M                = DMA_M2M_Disable;`
    `DMA_Init(DMA1_Channel1,` `&dma_init_struct);`

    `// Enable DMA and ADC`
    `DMA_Cmd(DMA1_Channel1, ENABLE);`
    `ADC_DMACmd(ADC1, ENABLE);`

    `// Calibrate and start ADC`
    `ADC_Cmd(ADC1, ENABLE);`
    `ADC_ResetCalibration(ADC1);`
    `while` `(ADC_GetResetCalibrationStatus(ADC1));`
    `ADC_StartCalibration(ADC1);`
    `while` `(ADC_GetCalibrationStatus(ADC1));`

    `ADC_SoftwareStartConvCmd(ADC1, ENABLE);`
`}`

`/**`
` * @brief Get average ADC value for light sensor`
` * @param sample_count Number of samples to average`
` * @return Average ADC value (12-bit)`
` * @note channel_index parameter is now unused (kept for compatibility)`
` */`
`uint16_t` `adc_get_average_value(uint8_t channel_index,` `uint8_t sample_count)`
`{`
    `uint32_t sum =` `0;`
    `uint8_t valid_samples =` `0;`

    `// Ensure we don't exceed buffer size`
    `if` `(sample_count > ADC_BUFFER_SIZE)` `{`
`        sample_count = ADC_BUFFER_SIZE;`
    `}`

    `// Sum recent light sensor readings from DMA buffer`
    `for` `(uint8_t i =` `0; i < sample_count; i++)` `{`
`        sum += s_adc_dma_buffer[i];`
`        valid_samples++;`
    `}`

    `return` `(valid_samples >` `0)` `?` `(sum / valid_samples)` `:` `0;`
`}`


`

4 . 3 CO2传感器读取

实现了 CO₂传感器的驱动功能,通过配置 USART3(波特率 9600bps、8 数据位、1 停止位、无校验)与传感器进行 UART 通信,具体包括初始化 GPIO 和串口硬件、配置中断优先级以处理接收数据。代码中通过co2_sensor_byte_handle函数逐字节接收数据,按照固定格式(6 字节帧,首字节为 0x2C)组装数据帧,再通过co2_sensor_process_data_frame函数验证校验和(B6 为 B1-B5 的 8 位和),若校验通过则将 B2 和 B3 字节组合计算出 CO₂浓度(单位 PPM)并存储到全局变量中,最终通过co2_sensor_get_concentration函数供外部获取,整体实现了传感器数据的可靠接收、校验与解析。

复制代码
/**`
` * @file co2_sensor.c`
` * @brief CO₂ Sensor drive`
` * `
` * @section uart_config UART Configuration`
` * | Parameter    | Value       | Description                  |`
` * |--------------|-------------|------------------------------|`
` * | Baud Rate    | 9600bps     | Data transmission rate       |`
` * | Data Bits    | 8 bits      | Number of data bits per frame|`
` * | Stop Bit     | 1 bit       | Stop bit count               |`
` * | Parity       | None        | No parity check              |`
` * `
` * @section data_format Data Frame Format`
` * A complete UART data frame consists of 6 bytes (B1 - B6), structured as:`
` * `
` * | Byte Index | Field Name        | Fixed Value/Description       |`
` * |------------|-------------------|-------------------------------|`
` * | B1         | Module Address    | 0x2C (fixed module address)   |`
` * | B2         | CO₂ High Byte     | 0XXh (high 8 bits of CO₂ data)|`
` * | B3         | CO₂ Low Byte      | 0XXh (low 8 bits of CO₂ data) |`
` * | B4         | Full Scale High   | 0x03h (fixed value)           |`
` * | B5         | Full Scale Low    | 0xFFh (fixed value)           |`
` * | B6         | Checksum          | unit_8 sum of B1-B5           |`
` * `
` * @section checksum_calc Checksum Calculation`
` * Checksum (B6) is computed as:  `
` * \f$ B6 = \text{unit\_8}(B1 + B2 + B3 + B4 + B5) \f$  `
` * (Sum B1-B5, then take the 8-bit result)`
` * `
` * @section co2_calc CO₂ Concentration Calculation`
` * CO₂ concentration (in PPM) is calculated by:  `
` * \f$ \text{CO₂ (PPM)} = (B2 \times 256) + B3 \f$  `
` * (Combine high/low bytes of CO₂ data into a 16-bit value)`
` * `
` * @section example Example Data`
` * Sample received data frame: 0x2C, 0x01h, 0x90h, 0x03h, 0xFFh, 0xBFh  `
` * - Checksum Validation:  `
` *   \f$ 0xBFh = 0x2C + 0x01h + 0x90h + 0x03h + 0xFFh \f$ (valid)  `
` * - CO₂ Concentration:  `
` *   \f$ (0x01h \times 256) + 0x90h = 256 + 144 = 400 \text{ PPM} \f$  `
` * `
` * @note Ensure data frame length (6 bytes) and module address (B1=0x2C) `
` *       are validated before processing.`
` * `
` */`
`#include "w55mh32.h"`

`static` `uint16_t g_co2_concentration =` `0;` `/* Global variable to store current CO2 concentration value */`

`/**`
` * @brief Initialize CO2 sensor`
` * @details Configure USART3 and related GPIO pins, set baud rate to 9600, `
` *          8 data bits, 1 stop bit, no parity`
` */`
`void` `co2_sensor_init(void)`
`{`
`    GPIO_InitTypeDef  GPIO_InitStructure;`
`    USART_InitTypeDef USART_InitStructure;`
`    NVIC_InitTypeDef  NVIC_InitStructure;`

    `/* Enable GPIOB and USART3 clock */`
    `RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);`
    `RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);`

    `/* Configure USART3 TX pin (PB10) as alternate function push-pull */`
`    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);`

    `/* Configure USART3 RX pin (PB11) as input floating */`
`    GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_11;`
`    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;`
    `GPIO_Init(GPIOB,` `&GPIO_InitStructure);`

    `/* Configure USART3 parameters */`
`    USART_InitStructure.USART_BaudRate            =` `9600;`
`    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);`

    `/* Configure USART3 interrupt priority */`
`    NVIC_InitStructure.NVIC_IRQChannel                   = USART3_IRQn;`
`    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority =` `0;`
`    NVIC_InitStructure.NVIC_IRQChannelSubPriority        =` `2;`
`    NVIC_InitStructure.NVIC_IRQChannelCmd                = ENABLE;`
    `NVIC_Init(&NVIC_InitStructure);`

    `/* Enable USART3 */`
    `USART_Cmd(USART3, ENABLE);`
`}`

`/**`
` * @brief Get current CO2 concentration`
` * @return Current CO2 concentration in PPM`
` */`
`uint16_t` `co2_sensor_get_concentration(void)`
`{`
    `return g_co2_concentration;`
`}`

`/**`
` * @brief Process received data frame from CO2 sensor`
` * @param frame Pointer to the 6-byte data frame`
` * @details Validates checksum and updates CO2 concentration if checksum is correct`
` */`
`static` `void` `co2_sensor_process_data_frame(uint8_t` `*frame)`
`{`
    `uint8_t i;`
    `uint8_t checksum =` `0;`

    `/* Calculate checksum */`
    `for` `(i =` `0; i <` `5; i++)`
    `{`
`        checksum += frame[i];`
    `}`

    `/* Update CO2 concentration if checksum matches */`
    `if` `(checksum == frame[5])`
    `{`
`        g_co2_concentration = frame[1]` `*` `256` `+ frame[2];`
    `}`
`}`

`/**`
` * @brief Handle received byte from CO2 sensor`
` * @param data Received byte`
` * @details Assembles complete data frame and processes it when full frame is received`
` */`
`void` `co2_sensor_byte_handle(uint8_t data)`
`{`
    `static` `uint8_t co2_data_frame[6];`
    `static` `uint8_t co2_data_index =` `0;`

    `/* Check for frame start (0x2C) */`
    `if` `(co2_data_index ==` `0` `&& data ==` `0x2C)`
    `{`
`        co2_data_frame[co2_data_index++]` `= data;`
    `}`
    `/* Continue collecting frame data */`
    `else` `if` `(co2_data_index <` `6)`
    `{`
`        co2_data_frame[co2_data_index++]` `= data;`
        `/* Process complete frame */`
        `if` `(co2_data_index ==` `6)`
        `{`
            `co2_sensor_process_data_frame(co2_data_frame);`
`            co2_data_index =` `0;`
        `}`
    `}`
    `else`
    `{`
`        co2_data_index =` `0;`
    `}`
`}`

`/**`
` * @brief USART3 interrupt handler`
` * @details Processes received data and clears interrupt flag`
` */`
`void` `USART3_IRQHandler(void)`
`{`
    `uint8_t data;`
    `if` `(USART_GetITStatus(USART3, USART_IT_RXNE)` `!= RESET)`
    `{`
        `/* Read received data and process it */`
`        data =` `(uint8_t)USART_ReceiveData(USART3);`
        `co2_sensor_byte_handle(data);`

        `/* Clear interrupt flag */`
        `USART_ClearITPendingBit(USART3, USART_IT_RXNE);`
    `}`
`}`

`

4 . 4 温湿度传感器读取

该文件实现了 I2C 通信协议的驱动功能,包括初始化 I2C 相关 GPIO 引脚(配置为开漏输出或输入模式)、定义 I2C 起始 / 停止信号、应答 / 等待应答、读写字节等基础通信函数,同时提供了通过 I2C 读写设备寄存器的功能函数,并实现了 CRC8 校验以确保数据传输正确性。代码还包含了针对温湿度传感器的处理逻辑,通过状态机(空闲、测量、完成)管理传感器工作流程,在测量阶段发送指令并读取 7 字节数据,经校验后解析计算出湿度(RH)和温度(T)值,最终通过 MYI2C_Handle 函数完成整个传感器数据的获取与处理过程,用于W55MH32L-EVB板载AHT20温湿度传感器的读取。

复制代码
#include "i2c_sensor.h "`
`#include "delay.h"`

`MYI2C_Struct SENx;`


`/**********************************************`
`//MYI2C_Delay_us`
`**********************************************/`
`void` `MYI2C_Delay_us(unsigned` `long nTim)`
`{`
    `unsigned` `int i;`

    `while` `(nTim--)`
    `{`
`        i = MYI2C_delay_us_cnt;`
        `while` `(i--);`
    `}`
`}`
`/*******************************************************************************`
`* Function Name  : MYI2C_GPIO_MODE`
`* Description    : Configures the different GPIO ports.`
`* Input          : None`
`* Output         : None`
`* Return         : None`
`*******************************************************************************/`
`void` `MYI2C_GPIO_MODE(unsigned` `char TYP)` `//根据MCU修改相应的IO初始化`
`{`
`#ifdef ARM32`
`    GPIO_InitTypeDef GPIO_InitStruct;`
`#endif`
    `switch` `(TYP)`
    `{`
    `case SDA_OUT:` `//设置开漏输出,需要加外部上拉电阻`
`#ifdef ARM32`
`        GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_Out_OD;`
`        GPIO_InitStruct.GPIO_Pin   = SDA_Pin;`
`        GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;`
        `GPIO_Init(IIC_SDA_PORT,` `&GPIO_InitStruct);`
`#else`
`        P0M0 |=` `1` `<<` `3;`
`        P0M1 |=` `3` `<<` `2;`
`#endif`
        `break;`
    `case SDA_IN:` `//设置输入,需要加外部上拉电阻`
`#ifdef ARM32`
`        GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_IPU;`
`        GPIO_InitStruct.GPIO_Pin   = SDA_Pin;`
`        GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;`
        `GPIO_Init(IIC_SDA_PORT,` `&GPIO_InitStruct);`
`#else`
`        P0M0 &=` `~(1` `<<` `3);`
`        P0M1 |=` `3` `<<` `2;`
`#endif`
        `break;`
    `case SCL_OUT:` `//设置开漏输出,需要加外部上拉电阻`
`#ifdef ARM32`
`        GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_Out_OD;`
`        GPIO_InitStruct.GPIO_Pin   = SCL_Pin;`
`        GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;`
        `GPIO_Init(IIC_SCL_PORT,` `&GPIO_InitStruct);`
`#else`
`        P0M0 |=` `1` `<<` `2;`
`        P0M1 |=` `3` `<<` `2;`
`#endif`
        `break;`
    `}`
`}`
`/*******************************************************************************`
`* Function Name  : MYI2C_GPIO_DATA`
`* Description    : Configures the different GPIO ports.`
`* Input          : None`
`* Output         : None`
`* Return         : None`
`*******************************************************************************/`
`unsigned` `char` `MYI2C_GPIO_DATA(unsigned` `char TYP)` `//根据MCU修改相应的IO操作`
`{`
    `unsigned` `int dat =` `0;`

    `switch` `(TYP)`
    `{`
    `case SDA_H:`
`#ifdef ARM32`
        `GPIO_SetBits(IIC_SDA_PORT, SDA_Pin);`
`#else`
`        I2C_SDA_PIN =` `1;`
`#endif`
        `break;`
    `case SDA_L:`
`#ifdef ARM32`
        `GPIO_ResetBits(IIC_SDA_PORT, SDA_Pin);`
`#else`
`        I2C_SDA_PIN =` `0;`
`#endif`
        `break;`
    `case SCL_H:`
`#ifdef ARM32`
        `GPIO_SetBits(IIC_SCL_PORT, SCL_Pin);`
`#else`
`        I2C_SCL_PIN =` `1;`
`#endif`
        `break;`
    `case SCL_L:`
`#ifdef ARM32`
        `GPIO_ResetBits(IIC_SCL_PORT, SCL_Pin);`
`#else`
`        I2C_SCL_PIN =` `0;`
`#endif`
        `break;`
    `case SDA_R:`
`#ifdef ARM32`
`        dat =` `GPIO_ReadInputDataBit(IIC_SDA_PORT, SDA_Pin);`
`#else`
`        dat = I2C_SDA_PIN;`
`#endif`
        `break;`
    `}`

    `return dat;`
`}`
`/**********************************************`
`//IIC Start`
`**********************************************/`
`void` `MYI2C_IIC_Start(void)`
`{`
    `MYI2C_SCK_Set();`
    `MYI2C_SDA_Set();`
    `MYI2C_SDA_Clr();`
    `MYI2C_SCK_Clr();`
`}`
`/**********************************************`
`//IIC Stop`
`**********************************************/`
`void` `MYI2C_IIC_Stop(void)`
`{`
    `MYI2C_SCK_Clr();`
    `MYI2C_SDA_Clr();`
    `MYI2C_SCK_Set();`
    `MYI2C_SDA_Set();`
`}`
`/**********************************************`
`//IIC Ack`
`**********************************************/`
`void` `MYI2C_IIC_Ack(unsigned` `char ack)`
`{`
    `MYI2C_SCK_Clr();`
    `if` `(ack)`
    `{`
        `MYI2C_SDA_Clr();`
    `}`
    `else`
    `{`
        `MYI2C_SDA_Set();`
    `}`
    `MYI2C_SCK_Set();`
`}`
`/**********************************************`
`//IIC Wait_Ack`
`**********************************************/`
`unsigned` `char` `MYI2C_IIC_Wait_Ack(unsigned` `int wait_time)`
`{`
    `MYI2C_SCK_Clr();`
`    MYI2C_SDA_IN_Mode;`
    `MYI2C_SDA_Set();`
    `MYI2C_SCK_Set();`
    `while` `(wait_time)`
    `{`
        `if` `(MYI2C_GPIO_DATA(SDA_R)` `==` `0)` `break;`
        `MYI2C_Delay_us(1);`
`        wait_time--;` `//=======================`
    `}`

`    MYI2C_SDA_OD_Mode;`
    `return wait_time;` `//是否应答=======================`
`}`
`/**********************************************`
`// IIC Write byte`
`**********************************************/`
`void` `MYI2C_Write_IIC_Byte(unsigned` `char dat)`
`{`
    `unsigned` `char i;`

    `for` `(i =` `0; i <` `8; i++)`
    `{`
        `MYI2C_SCK_Clr();`
        `if` `(dat &` `0x80)`
        `{`
            `MYI2C_SDA_Set();`
        `}`
        `else`
        `{`
            `MYI2C_SDA_Clr();`
        `}`
        `MYI2C_SCK_Set();`
`        dat = dat <<` `1;`
    `}`
`}`
`/**********************************************`
`// IIC Read byte`
`**********************************************/`
`unsigned` `char` `MYI2C_Read_IIC_Byte(void)`
`{`
    `unsigned` `char i, byt =` `0;`

    `MYI2C_SCK_Clr();`
`    MYI2C_SDA_IN_Mode;`
    `MYI2C_SDA_Set();`
    `for` `(i =` `0; i <` `8; i++)`
    `{`
        `MYI2C_SCK_Clr();`
        `if` `(MYI2C_GPIO_DATA(SDA_R)) byt++;`
        `MYI2C_SCK_Set();`
        `if` `(i <` `7) byt = byt <<` `1;`
    `}`
`    MYI2C_SDA_OD_Mode;`

    `return byt;`
`}`

`/*******************************************************************************`
`* Function Name  : MYI2C_Init`
`* Description    : 初始化MYI2C`
`* Input          :  None`
`* Output         : None`
`* Return         :None`
`*******************************************************************************/`
`void` `MYI2C_Init(MYI2C_Struct *pst,` `unsigned` `int ReadTimMS,` `unsigned` `char xAddr)`
`{`
    `RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);` `//使能SDA、SCL端口时钟,如是51单片机不需要则注释掉该行。`

`    pst->Adrr = xAddr;`
`    pst->Step = SENSOR_IDLE;`
    `if` `(ReadTimMS > MinReadTim)`
`        pst->SetRTim = ReadTimMS;`
    `else`
`        pst->SetRTim = MinReadTim;`

`    MYI2C_SCK_OD_Mode;`
`    MYI2C_SDA_OD_Mode;`

    `MYI2C_SCK_Set();`
    `MYI2C_SDA_Set();`
`}`
`/*******************************************************************************`
`* Function Name  : `
`* Description    : `
`* Input          :None`
`* Output         :None`
`* Return         :None`
`*******************************************************************************/`
`unsigned` `char` `MYI2C_READ_FUNC(MYI2C_Struct *pst,` `unsigned` `char device_addr,` `unsigned` `char register_addr,` `unsigned` `char` `*pDat,` `unsigned` `char len)`
`{`
    `unsigned` `char NoAck =` `0;`

    `if` `(register_addr)`
    `{`
        `/* Send STRAT condition a second time */`
        `MYI2C_IIC_Start();`
        `/* Send slave address for Write Regsiter */`
        `MYI2C_Write_IIC_Byte((device_addr <<` `1)` `+` `0);`
        `/* Ack */`
        `if` `(MYI2C_IIC_Wait_Ack(Wait_Ack_time)` `==` `0) NoAck++;`
        `/*Send register_addr*/`
        `MYI2C_Write_IIC_Byte((register_addr));`
        `/* Ack */`
        `if` `(MYI2C_IIC_Wait_Ack(Wait_Ack_time)` `==` `0) NoAck++;`

        `//   MYI2C_IIC_Stop(pst);`
        `MYI2C_SCK_Clr();`
        `MYI2C_SCK_Set();`
    `}`
    `/* Send STRAT condition a second time */`
    `MYI2C_IIC_Start();`
    `/* Send slave address for Read */`
    `MYI2C_Write_IIC_Byte((device_addr <<` `1)` `+` `1);`
    `/* Ack */`
    `if` `(MYI2C_IIC_Wait_Ack(Wait_Ack_time)` `==` `0) NoAck++;`
    `/* While there is data to be read */`
    `while` `(len && NoAck ==` `0` `&& len < MYI2C_Buffer_Size)` `//`
    `{`
        `*pDat =` `MYI2C_Read_IIC_Byte();`
        `/*  Ack */`
        `MYI2C_IIC_Ack(len -` `1);` `//len = 1 NoAck`
`        pDat++;`
`        len--;`
    `}`
    `/* Send STOP Condition */`
    `MYI2C_IIC_Stop();`

`    pst->ErrFlag = NoAck;`

    `return NoAck;`
`}`
`/*******************************************************************************`
`* Function Name  : `
`* Description    : `
`* Input          :  None`
`* Output         : None`
`* Return         :None`
`*******************************************************************************/`
`unsigned` `char` `MYI2C_WRITE_FUNC(MYI2C_Struct *pst,` `unsigned` `char device_addr,` `unsigned` `char register_addr,` `unsigned` `char` `*pDat,` `unsigned` `char len)`
`{`
    `unsigned` `int NoAck =` `0;`

    `/* Send STRAT condition */`
    `MYI2C_IIC_Start();`
    `/* Send slave address for write */`
    `MYI2C_Write_IIC_Byte((device_addr <<` `1)` `+` `0);`
    `/* ACK */`
    `if` `(MYI2C_IIC_Wait_Ack(Wait_Ack_time)` `==` `0) NoAck++;`
    `/* Send register_addr for read */`
    `MYI2C_Write_IIC_Byte((register_addr));`
    `/* ACK */`
    `if` `(MYI2C_IIC_Wait_Ack(Wait_Ack_time)` `==` `0) NoAck++;`
    `while` `(NoAck ==` `0` `&& len && len < MYI2C_Buffer_Size)` `//`
    `{`
        `/* Send the byte to be written */`
        `MYI2C_Write_IIC_Byte(*pDat);`
        `/*  Acknowledgement */`
        `MYI2C_IIC_Wait_Ack(Wait_Ack_time);`
`        pDat++;`
`        len--;`
    `}`
    `/* Send STOP condition */`
    `MYI2C_IIC_Stop();`

`    pst->ErrFlag = NoAck;`

    `return NoAck;`
`}`
`/*******************************************************************************`
`* Function Name  : CheckCrc`
`* Description    :  `
`* Input          :  None`
`* Output         : None`
`* Return         :None`
`*******************************************************************************/`
`unsigned` `char` `CheckCrc8(unsigned` `char` `*pDat,` `unsigned` `char Lenth)`
`{`
    `unsigned` `char crc =` `0xff, i, j;`

    `for` `(i =` `0; i < Lenth; i++)`
    `{`
`        crc = crc ^` `*pDat;`
        `for` `(j =` `0; j <` `8; j++)`
        `{`
            `if` `(crc &` `0x80)`
`                crc =` `(crc <<` `1)` `^` `0x31;`
            `else`
`                crc <<=` `1;`
        `}`
`        pDat++;`
    `}`
    `return crc;`
`}`
`/*******************************************************************************`
`* Function Name  :  `
`* Description    :  `
`* Input          :None`
`* Output         :None`
`* Return         :None`
`*******************************************************************************/`
`void` `MYI2C_Handle(MYI2C_Struct *pst)`
`{`
    `unsigned` `long s32x;`

`    pst->timcnt += MYI2C_Tick;`
    `if` `(pst->timcnt > PowerOnTim && pst->Step == SENSOR_IDLE)`
    `{`
`        pst->Step        = SENSOR_MEASURE;`
`        pst->SendByte[0]` `=` `0x33;`
`        pst->SendByte[1]` `=` `0x00;`
        `MYI2C_WRITE_FUNC(pst, pst->Adrr,` `0xAC,` `&pst->SendByte[0],` `2);`
    `}`
    `else` `if` `(pst->timcnt > MeasureTim && pst->Step == SENSOR_MEASURE)`
    `{`
`        pst->Step = SENSOR_COMPLETE;`
        `MYI2C_READ_FUNC(pst, pst->Adrr,` `0,` `&pst->ReadByte[0],` `7);`
        `if` `(pst->ErrFlag ==` `0)`
        `{`
            `if` `((CheckCrc8(&pst->ReadByte[0],` `6)` `== pst->ReadByte[6])` `&&` `((pst->ReadByte[0]` `&` `0x98)` `==` `0x18))`
            `{`
`                s32x     = pst->ReadByte[1];`
`                s32x     = s32x <<` `8;`
`                s32x    += pst->ReadByte[2];`
`                s32x     = s32x <<` `8;`
`                s32x    += pst->ReadByte[3];`
`                s32x     = s32x >>` `4;`
`                pst->RH  = s32x;`
`                pst->RH  = pst->RH *` `100` `/` `1048576;`
`                s32x     = pst->ReadByte[3]` `&` `0x0F;`
`                s32x     = s32x <<` `8;`
`                s32x    += pst->ReadByte[4];`
`                s32x     = s32x <<` `8;`
`                s32x    += pst->ReadByte[5];`
`                pst->T   = s32x;`
`                pst->T   = pst->T *` `200` `/` `1048576` `-` `50;`
            `}`
        `}`
        `else`
        `{`
`            pst->RH =` `0;`
`            pst->T  =` `0;`
        `}`
    `}`
    `else` `if` `(pst->timcnt > pst->SetRTim)`
    `{`
`        pst->Step   = SENSOR_IDLE;`
`        pst->timcnt =` `0;`
    `}`
`}`
`

4 .5 网页通信处理

这段代码实现了基于TCP的HTTP服务器功能,通过状态机管理Socket连接(包括初始化、监听、建立连接、关闭等状态处理),解析HTTP请求行获取方法、URI和版本信息,针对不同请求做出响应:对OPTIONS请求返回204无内容,对根路径GET请求发送网页内容,对`/api/sensor`路径GET请求以JSON格式返回温度、湿度、光照强度和CO₂浓度等传感器数据,对无效请求返回400或404错误,同时处理数据发送和连接断开逻辑,实现传感器数据的网络访问功能。

cpp 复制代码
#include "loopback.h"
#include "socket.h"
#include "web_page.h"  
#include "wizchip_conf.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define DATA_BUF_SIZE 1024

extern float g_humidity_value;
extern float g_temperature;
extern float g_light_intensity;
extern uint16_t g_co2_concentration;

typedef struct {
    char method[16];
    char uri[256];
    char version[16];
} HttpReqLine;

static int http_parse_request_line(const char *request, HttpReqLine *req_line)
{
    char buffer[1024];
    strncpy(buffer, request, sizeof(buffer));
    buffer[sizeof(buffer) - 1] = '\0';

    char *line_end = strstr(buffer, "\r\n");
    if (!line_end) line_end = strstr(buffer, "\n");
    if (!line_end) return -1;
    *line_end = '\0';

    char *method = strtok(buffer, " ");
    char *uri = strtok(NULL, " ");
    char *version = strtok(NULL, " ");

    if (!method || !uri || !version) return -1;

    strncpy(req_line->method, method, sizeof(req_line->method) - 1);
    strncpy(req_line->uri, uri, sizeof(req_line->uri) - 1);
    strncpy(req_line->version, version, sizeof(req_line->version) - 1);

    return 0;
}

static void send_http_response(uint8_t sn, const char *status,
                               const char *content_type, const char *body)
{
    char response[512];
    int body_len = strlen(body);

    sprintf(response,
            "HTTP/1.1 %s\r\n"
            "Content-Type: %s\r\n"
            "Access-Control-Allow-Origin: *\r\n"
            "Connection: close\r\n"
            "Content-Length: %d\r\n"
            "\r\n"
            "%s",
            status, content_type, body_len, body);

    send(sn, (uint8_t *)response, strlen(response));
}

int32_t loopback_tcps(uint8_t sn, uint8_t *network_buf, uint16_t port)
{
    int32_t ret;
    uint16_t recv_size = 0;
    uint16_t send_size = 0;

    switch (getSn_SR(sn)) {
        case SOCK_ESTABLISHED:
            if (getSn_IR(sn) & Sn_IR_CON) {
                setSn_IR(sn, Sn_IR_CON);
            }

            if ((recv_size = getSn_RX_RSR(sn)) > 0) {
                if (recv_size > DATA_BUF_SIZE) recv_size = DATA_BUF_SIZE;

                ret = recv(sn, network_buf, recv_size);
                if (ret <= 0) return ret;
                
                recv_size = (uint16_t)ret;
                network_buf[recv_size] = '\0';

                HttpReqLine req_line;
                if (http_parse_request_line((char *)network_buf, &req_line) == 0) {
                    printf("HTTP %s: %s\n", req_line.method, req_line.uri);

                    if (strcmp(req_line.method, "OPTIONS") == 0) {
                        send_http_response(sn, "204 No Content", "text/plain", "");
                    }
                    else if (strcmp(req_line.method, "GET") == 0 && strcmp(req_line.uri, "/") == 0) {
                        // 直接发送 index_page
                        uint16_t content_len = strlen(index_page);
                        send_size = 0;
                        while (send_size < content_len) {
                            ret = send(sn, (uint8_t *)index_page + send_size, content_len - send_size);
                            if (ret < 0) {
                                close(sn);
                                return ret;
                            }
                            send_size += ret;
                        }
                    }
                    else if (strcmp(req_line.method, "GET") == 0 && strcmp(req_line.uri, "/api/sensor") == 0) {
                        char sensor_json[256];
                        sprintf(sensor_json,
                                "{\"temp\":%.1f,\"humi\":%.1f,\"light\":%.1f,\"co2\":%d}",
                                g_temperature, g_humidity_value, g_light_intensity, g_co2_concentration);
                        send_http_response(sn, "200 OK", "application/json", sensor_json);
                    }
                    else {
                        send_http_response(sn, "404 Not Found", "text/html",
                                           "<html><body>Page Not Found</body></html>");
                    }
                }
                else {
                    send_http_response(sn, "400 Bad Request", "text/plain", "Invalid request");
                }
                
                disconnect(sn);
                close(sn);
            }
            break;

        case SOCK_CLOSE_WAIT:
            disconnect(sn);
            break;

        case SOCK_INIT:
            listen(sn);
            break;

        case SOCK_CLOSED:
            socket(sn, Sn_MR_TCP, port, 0x00);
            break;

        default:
            break;
    }
    return 1;
}

4 .6 网页显示

这段代码定义了一个环境传感器仪表盘的HTML页面,通过引入Chart.js实现温度、湿度、光照强度和二氧化碳浓度的实时数值显示与历史数据折线图展示,支持手动刷新和5秒自动刷新,通过请求`/api/sensor`接口获取数据并动态更新页面内容,整体采用网格布局和响应式设计提升视觉体验。

cpp 复制代码
#ifndef _WEBP_PAGE_H_
#define _WEBP_PAGE_H_

#define index_page \
"HTTP/1.1 200 OK\r\n" \
"Content-Type: text/html\r\n" \
"Connection: close\r\n" \
"\r\n" \
"<!DOCTYPE html>\r\n" \
"<html>\r\n" \
"<head>\r\n" \
"    <meta charset=\"UTF-8\">\r\n" \
"    <title>传感器仪表盘</title>\r\n" \
"    <script src=\"https://cdn.jsdelivr.net/npm/chart.js\"></script>\r\n" \
"    <style>\r\n" \
"        body { font-family: \"Microsoft YaHei\", \"微软雅黑\", sans-serif; max-width: 1000px; margin: 0 auto; padding: 20px; }\r\n" \
"        .dashboard { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; }\r\n" \
"        .sensor-panel { background: #f8f9fa; padding: 15px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }\r\n" \
"        .chart-container { height: 200px; margin-top: 15px; }\r\n" \
"        .refresh-btn { background: #4CAF50; color: white; border: none; padding: 10px 15px; border-radius: 4px; cursor: pointer; width: 100%; margin: 10px 0; font-size: 16px; }\r\n" \
"        .value-display { font-size: 24px; font-weight: bold; text-align: center; margin: 10px 0; }\r\n" \
"        h1, h2 { color: #2c3e50; }\r\n" \
"    </style>\r\n" \
"</head>\r\n" \
"<body>\r\n" \
"    <h1 style=\"text-align: center;\">环境传感器仪表盘</h1>\r\n" \
"    <button id=\"refresh_button\" class=\"refresh-btn\">刷新数据</button>\r\n" \
"    \r\n" \
"    <div class=\"dashboard\">\r\n" \
"        <div class=\"sensor-panel\">\r\n" \
"            <h2>温度</h2>\r\n" \
"            <div id=\"temp_value\" class=\"value-display\">-- °C</div>\r\n" \
"            <div class=\"chart-container\">\r\n" \
"                <canvas id=\"temp_chart\"></canvas>\r\n" \
"            </div>\r\n" \
"        </div>\r\n" \
"        \r\n" \
"        <div class=\"sensor-panel\">\r\n" \
"            <h2>湿度</h2>\r\n" \
"            <div id=\"humi_value\" class=\"value-display\">-- %</div>\r\n" \
"            <div class=\"chart-container\">\r\n" \
"                <canvas id=\"humi_chart\"></canvas>\r\n" \
"            </div>\r\n" \
"        </div>\r\n" \
"        \r\n" \
"        <div class=\"sensor-panel\">\r\n" \
"            <h2>光照强度</h2>\r\n" \
"            <div id=\"light_value\" class=\"value-display\">-- lux</div>\r\n" \
"            <div class=\"chart-container\">\r\n" \
"                <canvas id=\"light_chart\"></canvas>\r\n" \
"            </div>\r\n" \
"        </div>\r\n" \
"        \r\n" \
"        <div class=\"sensor-panel\">\r\n" \
"            <h2>二氧化碳浓度</h2>\r\n" \
"            <div id=\"co2_value\" class=\"value-display\">-- ppm</div>\r\n" \
"            <div class=\"chart-container\">\r\n" \
"                <canvas id=\"co2_chart\"></canvas>\r\n" \
"            </div>\r\n" \
"        </div>\r\n" \
"    </div>\r\n" \
"    \r\n" \
"    <script>\r\n" \
"        // 初始化变量和DOM元素\r\n" \
"        const sensors = {\r\n" \
"            temp: { element: document.getElementById('temp_value'), chart: null, values: [] },\r\n" \
"            humi: { element: document.getElementById('humi_value'), chart: null, values: [] },\r\n" \
"            light: { element: document.getElementById('light_value'), chart: null, values: [] },\r\n" \
"            co2: { element: document.getElementById('co2_value'), chart: null, values: [] }\r\n" \
"        };\r\n" \
"        \r\n" \
"        const refreshBtn = document.getElementById('refresh_button');\r\n" \
"        const url = '/api/sensor';\r\n" \
"        \r\n" \
"        // 初始化图表\r\n" \
"        function initCharts() {\r\n" \
"            // 传感器中文名称映射\r\n" \
"            const sensorNames = {\r\n" \
"                temp: '温度',\r\n" \
"                humi: '湿度',\r\n" \
"                light: '光照',\r\n" \
"                co2: '二氧化碳'\r\n" \
"            };\r\n" \
"            \r\n" \
"            Object.keys(sensors).forEach(sensor => {\r\n" \
"                const ctx = document.getElementById(sensor + '_chart').getContext('2d');\r\n" \
"                sensors[sensor].chart = new Chart(ctx, {\r\n" \
"                    type: 'line',\r\n" \
"                    data: {\r\n" \
"                        labels: [],\r\n" \
"                        datasets: [{\r\n" \
"                            label: sensorNames[sensor] + '历史数据',\r\n" \
"                            data: [],\r\n" \
"                            borderColor: getColor(sensor),\r\n" \
"                            backgroundColor: getColor(sensor, 0.2),\r\n" \
"                            tension: 0.4,\r\n" \
"                            fill: true\r\n" \
"                        }]\r\n" \
"                    },\r\n" \
"                    options: {\r\n" \
"                        responsive: true,\r\n" \
"                        maintainAspectRatio: false,\r\n" \
"                        scales: {\r\n" \
"                            y: { \r\n" \
"                                beginAtZero: sensor !== 'temp',\r\n" \
"                                title: {\r\n" \
"                                    display: true,\r\n" \
"                                    text: sensor === 'temp' ? '温度(°C)' : \r\n" \
"                                           sensor === 'humi' ? '湿度(%)' : \r\n" \
"                                           sensor === 'light' ? '光照(lux)' : '浓度(ppm)'\r\n" \
"                                }\r\n" \
"                            },\r\n" \
"                            x: {\r\n" \
"                                title: {\r\n" \
"                                    display: true,\r\n" \
"                                    text: '时间'\r\n" \
"                                }\r\n" \
"                            }\r\n" \
"                        },\r\n" \
"                        plugins: {\r\n" \
"                            legend: {\r\n" \
"                                labels: {\r\n" \
"                                    font: {\r\n" \
"                                        family: 'Microsoft YaHei',\r\n" \
"                                        size: 12\r\n" \
"                                    }\r\n" \
"                                }\r\n" \
"                            }\r\n" \
"                        }\r\n" \
"                    }\r\n" \
"                });\r\n" \
"            });\r\n" \
"        }\r\n" \
"        \r\n" \
"        // 获取传感器对应的颜色\r\n" \
"        function getColor(sensor, opacity = 1) {\r\n" \
"            const colors = {\r\n" \
"                temp: `rgba(219, 68, 55, ${opacity})`,\r\n" \
"                humi: `rgba(66, 133, 244, ${opacity})`,\r\n" \
"                light: `rgba(244, 180, 0, ${opacity})`,\r\n" \
"                co2: `rgba(15, 157, 88, ${opacity})`\r\n" \
"            };\r\n" \
"            return colors[sensor] || `rgba(100, 100, 100, ${opacity})`;\r\n" \
"        }\r\n" \
"        \r\n" \
"        // 更新传感器显示和图表\r\n" \
"        function updateSensorData(data) {\r\n" \
"            // 更新数值显示\r\n" \
"            sensors.temp.element.textContent = data.temp.toFixed(1) + ' °C';\r\n" \
"            sensors.humi.element.textContent = data.humi.toFixed(1) + ' %';\r\n" \
"            sensors.light.element.textContent = Math.round(data.light) + ' lux';\r\n" \
"            sensors.co2.element.textContent = data.co2 + ' ppm';\r\n" \
"            \r\n" \
"            // 更新图表\r\n" \
"            const now = new Date();\r\n" \
"            const timeLabel = now.getHours() + ':' + now.getMinutes().toString().padStart(2, '0');\r\n" \
"            \r\n" \
"            Object.keys(sensors).forEach(sensor => {\r\n" \
"                const chart = sensors[sensor].chart;\r\n" \
"                const values = sensors[sensor].values;\r\n" \
"                \r\n" \
"                values.push(data[sensor]);\r\n" \
"                if (values.length > 20) values.shift();\r\n" \
"                \r\n" \
"                chart.data.labels.push(timeLabel);\r\n" \
"                if (chart.data.labels.length > 20) chart.data.labels.shift();\r\n" \
"                \r\n" \
"                chart.data.datasets[0].data = [...values];\r\n" \
"                chart.update();\r\n" \
"            });\r\n" \
"        }\r\n" \
"        \r\n" \
"        // 从服务器获取传感器数据\r\n" \
"        function fetchSensorData() {\r\n" \
"            fetch(url)\r\n" \
"                .then(response => {\r\n" \
"                    if (!response.ok) {\r\n" \
"                        throw new Error('网络响应异常');\r\n" \
"                    }\r\n" \
"                    return response.json();\r\n" \
"                })\r\n" \
"                .then(data => {\r\n" \
"                    updateSensorData(data);\r\n" \
"                })\r\n" \
"                .catch(error => {\r\n" \
"                    console.error('获取传感器数据失败:', error);\r\n" \
"                    // 显示错误信息\r\n" \
"                    Object.keys(sensors).forEach(sensor => {\r\n" \
"                    });\r\n" \
"                });\r\n" \
"        }\r\n" \
"        \r\n" \
"        // 事件监听\r\n" \
"        refreshBtn.addEventListener('click', fetchSensorData);\r\n" \
"        \r\n" \
"        // 初始化并开始定期更新\r\n" \
"        initCharts();\r\n" \
"        fetchSensorData();\r\n" \
"        setInterval(fetchSensorData, 5000);\r\n" \
"    </script>\r\n" \
"</body>\r\n" \
"</html>"

#endif

5 功能验证

  1. 硬件连接完毕,烧录程序上电打印如下信息:PHY配置打印网络信息。
  1. 在浏览器输入19168.2.49:8080来进入html页面,可以看到读取传感器数据变化曲线。

6 总结

本项目基于 W55MH32 以太网单片机构建的多传感器环境监测系统,成功实现了温湿度、光照、CO₂等环境参数的实时采集、Web 页面可视化及数据动态更新。通过硬件 TCP/IP 协议栈与 HTTP 服务器的结合,验证了嵌入式设备直接构建网络监测平台的可行性,为低成本环境感知方案提供了可靠参考。感谢大家的耐心阅读!如果您在阅读过程中有任何疑问,或者希望进一步了解这款产品及其应用,欢迎随时通过私信或评论区留言。我们会尽快回复您的消息,为您提供更详细的解答和帮助!