376 lines
10 KiB
Markdown
376 lines
10 KiB
Markdown
# 实现逻辑详解
|
||
|
||
## 1. 系统工作流程
|
||
|
||
### 1.1 主程序流程图
|
||
|
||
```
|
||
┌─────────────────┐
|
||
│ 系统初始化 │
|
||
├─────────────────┤
|
||
│ 硬件模块初始化 │
|
||
├─────────────────┤
|
||
│ WiFi连接尝试 │
|
||
├─────────────────┤
|
||
│ TCP连接建立 │
|
||
├─────────────────┤
|
||
│ 设备注册发送 │
|
||
├─────────────────┤
|
||
│ 主循环处理 │
|
||
└─────────────────┘
|
||
```
|
||
|
||
### 1.2 详细工作流程
|
||
|
||
#### 第一阶段:系统初始化 (main.c:32-50)
|
||
```c
|
||
// 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)
|
||
```c
|
||
// 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)
|
||
|
||
```c
|
||
// 传感器数据采集
|
||
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)
|
||
|
||
```c
|
||
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)
|
||
|
||
```c
|
||
// 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)
|
||
|
||
```c
|
||
// 主动请求控制命令
|
||
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)
|
||
|
||
```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)
|
||
|
||
```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)
|
||
|
||
```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 网络错误处理
|
||
```c
|
||
// 发送失败处理
|
||
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 传感器读取异常处理
|
||
```c
|
||
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 内存保护机制
|
||
```c
|
||
// 缓冲区溢出保护
|
||
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项目组 |