Initial import of STM32 IoT sensor node
This commit is contained in:
@@ -0,0 +1,376 @@
|
||||
# 实现逻辑详解
|
||||
|
||||
## 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项目组
|
||||
Reference in New Issue
Block a user