Files
stm32-iot-sensor-node/32_IMPLEMENTATION.md
T
2026-10-19 15:47:18 +08:00

10 KiB
Raw Blame History

实现逻辑详解

1. 系统工作流程

1.1 主程序流程图

┌─────────────────┐
│   系统初始化     │
├─────────────────┤
│ 硬件模块初始化    │
├─────────────────┤
│ WiFi连接尝试     │
├─────────────────┤
│ TCP连接建立      │
├─────────────────┤
│ 设备注册发送     │
├─────────────────┤
│   主循环处理     │
└─────────────────┘

1.2 详细工作流程

第一阶段:系统初始化 (main.c:32-50)

// 1. 中断优先级配置
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

// 2. 硬件模块初始化顺序
led_init();        // LED状态指示
beep_init();       // 蜂鸣器控制
systick_init();    // 系统时钟
usart1_init(115200); // 调试串口
eeprom_init();     // 配置存储
oled_init();       // 显示屏
dht11_init();      // 温湿度传感器
adc1_init();       // ADC采集

初始化逻辑说明:

  • LED初始关闭,避免误触发
  • 串口初始化为115200波特率,用于调试输出
  • OLED显示屏初始化后立即准备显示数据

第二阶段:网络连接 (main.c:52-79)

// WiFi连接逻辑
if (esp8266_connect_wifi(WIFI_SSID, WIFI_PASS)) {
    printf("Connected to WiFi\r\n");

    // TCP连接逻辑
    if (esp8266_tcp_connect(SERVER_IP, SERVER_PORT)) {
        tcp_connected = 1;

        // 设备注册消息
        char reg_msg[128];
        int reg_len = snprintf(reg_msg, sizeof(reg_msg),
            "{\"type\":\"register\",\"device_id\":\"%s\",\"action\":\"connect\"}\n",
            DEVICE_ID);
    }
}

连接策略:

  • 先建立WiFi连接,再尝试TCP连接
  • 连接成功后发送设备注册消息
  • 失败时设置tcp_connected=0,进入重连机制

2. 主循环实现逻辑

2.1 数据采集与显示 (main.c:95-112)

// 传感器数据采集
u8 value[5];
dht11_get_values(value);  // 获取温湿度数据

// OLED显示更新
sprintf(buf, "\xce\xc2\xb6\xc8: %5.1f \xa1\xe6", (float)value[2]);  // 温度
oled_draw_text16(8, 8, buf);

sprintf(buf, "\xca\xaa\xb6\xc8: %5.1f%%", (float)value[0]);          // 湿度
oled_draw_text16(8, 24, buf);

sprintf(buf, "ADC Value: %04d", adc1_get_value(10));                // ADC
oled_draw_text8(8, 40, buf);

sprintf(buf, "LED(0): %3s", led_get_status(0) ? "ON" : "OFF");       // LED状态
oled_draw_text8(8, 48, buf);

oled_refresh_framebuffer();  // 刷新显示

实现细节:

  • DHT11数据格式:value[0-1]湿度,value[2-3]温度,value[4]校验和
  • OLED使用GBK编码显示中文
  • 显示区域划分:温度(8,8),湿度(8,24),ADC(8,40),LED状态(8,48)

2.2 网络重连机制 (main.c:116-148)

static int reconnect_counter = 0;
int reconnect_threshold = RECONNECT_PERIOD_MS / SEND_INTERVAL_MS;

if (!tcp_connected) {
    if (reconnect_counter >= reconnect_threshold) {
        reconnect_counter = 0;

        // 尝试重连
        if (esp8266_tcp_connect(SERVER_IP, SERVER_PORT)) {
            tcp_connected = 1;

            // 重连后重新注册
            char reg_msg[128];
            int reg_len = snprintf(reg_msg, sizeof(reg_msg),
                "{\"type\":\"register\",\"device_id\":\"%s\",\"action\":\"reconnect\"}\n",
                DEVICE_ID);
        }
    } else {
        printf("Not connected to TCP server, skipping send\r\n");
        delay_ms(SEND_INTERVAL_MS);
        continue;  // 跳过本次循环的数据发送
    }
}

重连策略:

  • 每5秒尝试一次重连(RECONNECT_PERIOD_MS = 5000ms)
  • 重连成功后发送"reconnect"类型的注册消息
  • 连接断开期间跳过数据发送,避免错误

2.3 数据发送逻辑 (main.c:150-169)

// JSON数据构造
char json[256];
int msg_len = snprintf(json, sizeof(json),
    "{\"device_id\":\"%s\",\"temp\":%.1f,\"humidity\":%.1f,\"buzzer\":%d,\"led0\":%d,\"led1\":%d,\"led2\":%d}\n",
    DEVICE_ID,
    (float)value[2],           // 温度
    (float)value[0],           // 湿度
    beep_get_status(),         // 蜂鸣器状态
    led_get_status(0),         // LED0状态
    led_get_status(1),         // LED1状态
    led_get_status(2));        // LED2状态

// 发送数据
esp8266_clear_read_buf();     // 清空接收缓冲区
int sent = esp8266_write(json, (uint16_t)msg_len);

数据格式说明:

  • 发送频率:200ms一次(SEND_INTERVAL_MS)
  • 数据格式:JSON,以换行符结束
  • 包含设备ID、传感器数据、执行器状态

2.4 控制命令接收与解析 (main.c:171-218)

// 主动请求控制命令
if (tcp_connected) {
    char getcmd[64];
    snprintf(getcmd, sizeof(getcmd), "GET control %s\n", DEVICE_ID);
    int gsent = esp8266_write(getcmd, (uint16_t)strlen(getcmd));
}

// 接收并解析控制命令
char inbuf[256];
int rlen = esp8266_read(inbuf, sizeof(inbuf));
if (rlen > 0) {
    inbuf[rlen] = '\0';
    printf("Control recv: %s\r\n", inbuf);

    // 解析蜂鸣器控制
    char *p = strstr(inbuf, "\"buzzer\"");
    if (p) {
        p = strchr(p, ':');
        if (p && sscanf(p+1, "%d", &val) == 1) {
            if (val) {
                beep_on();
            } else {
                beep_off();
            }
        }
    }

    // 解析LED控制 (led0, led1, led2)
    for (int i = 0; i < 3; i++) {
        char key[16];
        sprintf(key, "\"led%d\"", i);
        p = strstr(inbuf, key);
        if (p) {
            p = strchr(p, ':');
            if (p && sscanf(p+1, "%d", &val) == 1) {
                led_set_status(i, val ? 1 : 0);
            }
        }
    }
}

控制协议解析:

  • 使用轮询模式主动获取控制命令
  • 支持的控制命令:{"buzzer":1,"led0":1,"led1":0,"led2":0}
  • 实时应用控制命令到硬件设备

3. 关键算法实现

3.1 DHT11时序控制算法 (dht11.c)

int dht11_get_values(u8 buf[5]) {
    // 1. 发送起始信号
    DHT11_DATA_OUT = 0;        // 拉低数据线
    delay_ms(20);              // 保持18ms以上
    DHT11_DATA_OUT = 1;        // 拉高数据线
    delay_us(30);              // 等待20-40us

    // 2. 等待传感器响应
    while(DHT11_DATA_IN && timeout < 20) {
        delay_us(2);
        timeout++;
    }

    // 3. 读取40位数据
    for(int i = 0; i < 5; i++) {
        for(int bit = 0; bit < 8; bit++) {
            // 等待50us低电平
            while(!DHT11_DATA_IN);

            // 等待高电平结束
            delay_us(40);
            if(DHT11_DATA_IN) {
                data_bit = 1;   // '1'位
            } else {
                data_bit = 0;   // '0'位
            }

            // 等待高电平结束
            while(DHT11_DATA_IN);

            // 组装字节
            buf[i] <<= 1;
            buf[i] |= data_bit;
        }
    }

    return 1;
}

时序要点:

  • 起始信号:拉低18ms,拉高20-40us
  • 数据位判断:40us后检测电平状态
  • 完整数据:40位(5字节),包含校验和

3.2 ESP8266 AT指令控制 (esp8266.c)

int esp8266_connect_wifi(const char *ssid, const char *passwd) {
    char cmd[128];

    // 1. 设置WiFi模式
    usart2_send_string("AT+CWMODE=1\r\n");
    if(!esp8266_wait_response("OK", 2000)) return 0;

    // 2. 连接WiFi
    sprintf(cmd, "AT+CWJAP=\"%s\",\"%s\"\r\n", ssid, passwd);
    usart2_send_string(cmd);
    if(!esp8266_wait_response("OK", 10000)) return 0;

    return 1;
}

int esp8266_tcp_connect(const char *ip, uint16_t port) {
    char cmd[64];

    sprintf(cmd, "AT+CIPSTART=\"TCP\",\"%s\",%d\r\n", ip, port);
    usart2_send_string(cmd);

    return esp8266_wait_response("CONNECT", 5000);
}

AT指令流程:

  • 设置WiFi模式为Station模式
  • 连接指定WiFi网络(最长等待10秒)
  • 建立TCP连接到服务器

3.3 OLED显示驱动 (oled.c)

void oled_refresh_framebuffer(void) {
    // 1. 设置列地址范围
    OLED_write_command(0x21);  // 列地址设置命令
    OLED_write_command(0);     // 起始列
    OLED_write_command(127);   // 结束列

    // 2. 设置页地址范围
    OLED_write_command(0x22);  // 页地址设置命令
    OLED_write_command(0);     // 起始页
    OLED_write_command(7);     // 结束页

    // 3. 发送帧缓冲区数据
    for(int i = 0; i < 1024; i++) {
        OLED_write_data(OLED_FrameBuffer[i]);
    }
}

显示原理:

  • 使用1024字节帧缓冲区(128×64÷8)
  • SSD1306支持页寻址模式,每页8行
  • 批量发送整个帧缓冲区内容

4. 错误处理与容错机制

4.1 网络错误处理

// 发送失败处理
int sent = esp8266_write(json, (uint16_t)msg_len);
if (sent > 0) {
    printf("Sent: %s\r\n", json);
} else {
    printf("Send failed\r\n");
    tcp_connected = 0;  // 标记连接断开
}

4.2 传感器读取异常处理

u8 dht11_data[5];
if (dht11_get_values(dht11_data)) {
    // 数据校验
    u8 checksum = dht11_data[0] + dht11_data[1] + dht11_data[2] + dht11_data[3];
    if (checksum != dht11_data[4]) {
        printf("DHT11 checksum error\r\n");
        // 使用上一次的有效数据或默认值
    }
}

4.3 内存保护机制

// 缓冲区溢出保护
int msg_len = snprintf(json, sizeof(json), "...");
if (msg_len >= sizeof(json)) {
    msg_len = sizeof(json) - 1;  // 截断过长数据
    json[msg_len] = '\0';
}

5. 性能优化策略

5.1 时间片调度

  • 数据采集:每次循环执行
  • 网络发送:200ms间隔
  • 显示刷新:每次循环执行
  • 控制请求:每次循环执行

5.2 资源使用优化

  • 静态内存分配,避免动态内存碎片
  • 字符串缓冲区复用
  • 中断驱动的外设操作

5.3 网络通信优化

  • 批量发送JSON数据
  • 清空接收缓冲区避免数据累积
  • 连接断开时跳过发送操作

文档版本: v1.0 最后更新: 2025-11-16 作者: STM32项目组