综合项目实践
目 录
知识脑图
点击节点可展开/折叠子级,点击🔗图标跳转到对应知识点
知识点字典
按章节分组索引,快速定位所有知识点
综合实验案例
OLED显示屏实验(u8g2库)

本实验使用 u8g2 图形库驱动 SSD1306 OLED 显示屏,实现文字和图形的显示。相比 Adafruit_SSD1306 库,u8g2 支持更多字体和绘图功能,是目前 Arduino 平台最流行的 OLED 驱动库。
硬件连接
| OLED引脚 | ESP8266 NodeMCU | 说明 |
|---|---|---|
| VCC | 3.3V | 电源 |
| GND | GND | 接地 |
| SCL | D1 (GPIO5) | I2C时钟 |
| SDA | D2 (GPIO4) | I2C数据 |
安装库
在 Arduino IDE 中搜索安装 U8g2 库(作者:olikraus)。
完整代码
```C
#include <Arduino.h>
#include <U8g2lib.h>
#include <Wire.h>
// 初始化 u8g2 对象,使用 SSD1306 128x64 I2C 接口
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);
void setup() {
Serial.begin(9600);
Serial.println("OLED u8g2 实验启动");
// 初始化 OLED
u8g2.begin();
u8g2.enableUTF8Print(); // 启用 UTF-8 中文支持
u8g2.setFont(u8g2_font_wqy12_t_gb2312); // 设置中文字体(文泉驿12点阵)
// 显示启动画面
u8g2.clearBuffer();
u8g2.setFontPosTop();
u8g2.setCursor(0, 0);
u8g2.print("OLED u8g2");
u8g2.setCursor(0, 16);
u8g2.print("实验启动");
u8g2.sendBuffer();
delay(2000);
}
void loop() {
u8g2.clearBuffer();
// 第一行:标题
u8g2.setFont(u8g2_font_wqy12_t_gb2312);
u8g2.setFontPosTop();
u8g2.setCursor(0, 0);
u8g2.print("传感器数据");
// 第二行:分隔线
u8g2.drawHLine(0, 15, 128);
// 第三行起:显示模拟数据
u8g2.setCursor(0, 20);
u8g2.print("温度: 25.3 C");
u8g2.setCursor(0, 36);
u8g2.print("湿度: 65.2 %");
u8g2.setCursor(0, 52);
u8g2.print("光照: 512");
u8g2.sendBuffer();
delay(1000);
}
```
代码讲解
U8G2_SSD1306_128X64_NONAME_F_HW_I2C:这是 u8g2 的构造函数,F表示使用完整帧缓冲(Full Buffer),HW_I2C表示使用硬件 I2C。完整缓冲模式下,所有绘制操作先写入内存缓冲区,最后调用sendBuffer()一次性刷新到屏幕,显示更流畅
u8g2.enableUTF8Print():启用 UTF-8 编码支持,这是显示中文的必要步骤
u8g2_font_wqy12_t_gb2312:文泉驿点阵宋体,12像素大小,支持 GB2312 中文字符集。u8g2 还提供u8g2_font_wqy14_t_gb2312(14像素)和u8g2_font_wqy16_t_gb2312(16像素)等中文字体
clearBuffer()/sendBuffer():这是 u8g2 的"双缓冲"工作模式。clearBuffer()清空内存缓冲区,所有绘制操作(print、drawHLine等)写入缓冲区,sendBuffer()将缓冲区内容一次性发送到 OLED 屏幕
setFontPosTop():设置字体绘制起点为字符顶部,便于精确定位
drawHLine(x, y, width):绘制水平线,常用于分隔不同区域
超声波测距实验(HC-SR04)

本实验使用 HC-SR04 超声波传感器测量距离,并通过串口输出结果。HC-SR04 的工作原理是:发射端发出 40kHz 超声波脉冲,接收端检测回波,通过计算声波往返时间得到距离。
硬件连接
| HC-SR04引脚 | ESP8266 NodeMCU | 说明 |
|---|---|---|
| VCC | 3.3V 或 VIN | 电源(5V时测量更稳定) |
| GND | GND | 接地 |
| Trig | D5 (GPIO14) | 触发信号输出 |
| Echo | D6 (GPIO12) | 回波信号输入 |
完整代码
```C
#include <Arduino.h>
#define TRIG_PIN 14 // D5 - 触发引脚
#define ECHO_PIN 12 // D6 - 回波引脚
void setup() {
Serial.begin(9600);
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
Serial.println("HC-SR04 超声波测距实验");
Serial.println("----------------------");
}
void loop() {
// 发送触发脉冲:先拉低确保干净的低电平,再发送10us高电平脉冲
digitalWrite(TRIG_PIN, LOW);
delayMicroseconds(2);
digitalWrite(TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
// 测量回波脉冲宽度(单位:微秒)
long duration = pulseIn(ECHO_PIN, HIGH);
// 计算距离(单位:厘米)
// 声速约 340m/s = 0.034cm/us,往返距离除以2
float distance = duration * 0.034 / 2;
// 输出结果
Serial.print("距离: ");
Serial.print(distance);
Serial.println(" cm");
delay(500);
}
```
代码讲解
- 触发脉冲时序:HC-SR04 要求至少 10μs 的高电平脉冲来触发测量。代码中先拉低 2μs 确保起始状态干净,再拉高 10μs,然后拉低等待回波
pulseIn(pin, value):阻塞式等待指定引脚变为value(HIGH 或 LOW),然后测量其持续高/低电平的时间(微秒)。超时默认为 1 秒。该函数在 ESP8266 上可用
- 距离计算公式:
distance = duration × 0.034 / 2。其中 0.034 cm/μs 是声速(340 m/s)换算而来,除以 2 是因为duration包含了往返时间
- 测量范围:HC-SR04 的有效测量范围为 2cm ~ 400cm。当超出范围时,
pulseIn()可能返回 0 或超时值,实际项目中应加入范围判断
- 精度影响因素:温度会影响声速(0°C 时约 331 m/s,20°C 时约 343 m/s),高精度应用中需做温度补偿
音乐播放实验(MIDI音调)

本实验使用 tone() 函数驱动蜂鸣器播放 MIDI 格式的音乐旋律。通过定义音符频率数组,可以实现复杂的音乐播放。
硬件连接
| 蜂鸣器 | ESP8266 NodeMCU | 说明 |
|---|---|---|
| VCC / + | 3.3V 或 VIN | 电源(有源蜂鸣器) |
| GND / - | GND | 接地 |
| I/O / S | D7 (GPIO13) | 信号输入(无源蜂鸣器) |
完整代码
```C
#include <Arduino.h>
#define BUZZER_PIN 13 // D7 - 蜂鸣器引脚
// MIDI 音符编号到频率的转换宏
#define NOTE_C4 262
#define NOTE_D4 294
#define NOTE_E4 330
#define NOTE_F4 349
#define NOTE_G4 392
#define NOTE_A4 440
#define NOTE_B4 494
#define NOTE_C5 523
#define NOTE_D5 587
#define NOTE_E5 659
#define NOTE_REST 0 // 休止符
// 旋律:欢乐颂(贝多芬第九交响曲简化版)
// 格式:{音符频率, 持续时间(拍)}
int melody[][2] = {
{NOTE_E4, 1}, {NOTE_E4, 1}, {NOTE_F4, 1}, {NOTE_G4, 1},
{NOTE_G4, 1}, {NOTE_F4, 1}, {NOTE_E4, 1}, {NOTE_D4, 1},
{NOTE_C4, 1}, {NOTE_C4, 1}, {NOTE_D4, 1}, {NOTE_E4, 1},
{NOTE_E4, 1}, {NOTE_D4, 2}, {NOTE_D4, 1},
{NOTE_E4, 1}, {NOTE_E4, 1}, {NOTE_F4, 1}, {NOTE_G4, 1},
{NOTE_G4, 1}, {NOTE_F4, 1}, {NOTE_E4, 1}, {NOTE_D4, 1},
{NOTE_C4, 1}, {NOTE_C4, 1}, {NOTE_D4, 1}, {NOTE_E4, 1},
{NOTE_D4, 1}, {NOTE_C4, 2}, {NOTE_C4, 1},
};
int tempo = 300; // 每拍的持续时间(毫秒)
void setup() {
Serial.begin(9600);
pinMode(BUZZER_PIN, OUTPUT);
Serial.println("音乐播放实验 - 欢乐颂");
}
void playNote(int frequency, int duration) {
if (frequency == NOTE_REST) {
delay(duration); // 休止符:静音
} else {
tone(BUZZER_PIN, frequency, duration);
delay(duration * 1.3); // 音符之间留 30% 间隔
noTone(BUZZER_PIN); // 确保停止发声
}
}
void loop() {
Serial.println("开始播放...");
int noteCount = sizeof(melody) / sizeof(melody[0]);
for (int i = 0; i < noteCount; i++) {
int freq = melody[i][0];
int beats = melody[i][1];
int duration = beats * tempo;
playNote(freq, duration);
}
Serial.println("播放完毕,3秒后重播");
delay(3000);
}
```
代码讲解
- MIDI 音符编号:MIDI 标准中每个音符对应一个编号(中央C = 60),频率可通过公式
f = 440 × 2^((n-69)/12)计算。代码中直接使用预定义的频率常量,更直观
- 旋律数据结构:使用二维数组
melody[][2]存储旋律,每行包含{频率, 拍数}。这种结构便于编辑和扩展旋律
tone(pin, frequency, duration):在指定引脚输出方波信号。duration参数为可选,指定后会在该时间后自动停止发声。但实际中建议手动调用noTone()确保停止
- 音符间隔:
delay(duration * 1.3)在每个音符后额外等待 30% 的时间,使音符之间有明显的分隔,避免粘连。这是音乐播放中常见的处理方式
tempo变量:控制播放速度(每拍的毫秒数),调小则播放更快,调大则更慢
WiFi配置与OLED显示实验
本实验实现一个常见的物联网应用场景:ESP8266 连接 WiFi 后,在 OLED 屏幕上显示网络配置信息(IP地址、WiFi名称、信号强度等)。这是物联网项目的基础框架。
硬件连接
| OLED引脚 | ESP8266 NodeMCU | 说明 |
|---|---|---|
| VCC | 3.3V | 电源 |
| GND | GND | 接地 |
| SCL | D1 (GPIO5) | I2C时钟 |
| SDA | D2 (GPIO4) | I2C数据 |
完整代码
```C
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <U8g2lib.h>
#include <Wire.h>
// WiFi 配置 —— 请修改为你的 WiFi 信息
const char* ssid = "YourWiFiName";
const char* password = "YourWiFiPassword";
// 初始化 OLED(u8g2 库)
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);
void setup() {
Serial.begin(115200);
u8g2.begin();
u8g2.enableUTF8Print();
// OLED 显示连接中提示
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_wqy12_t_gb2312);
u8g2.setFontPosTop();
u8g2.setCursor(0, 0);
u8g2.print("正在连接WiFi...");
u8g2.setCursor(0, 20);
u8g2.print("SSID:");
u8g2.setCursor(0, 36);
u8g2.print(ssid);
u8g2.sendBuffer();
// 连接 WiFi
WiFi.mode(WIFI_STA); // 设置为 STA(站点)模式
WiFi.begin(ssid, password); // 开始连接
Serial.print("正在连接");
int dotCount = 0;
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
dotCount++;
// OLED 显示等待动画
u8g2.setCursor(0, 52);
String dots = "";
for (int i = 0; i < (dotCount % 4); i++) dots += ".";
u8g2.print(dots + " "); // 清除旧点
u8g2.sendBuffer();
}
Serial.println("\n连接成功!");
}
void loop() {
// 获取网络信息
String ip = WiFi.localIP().toString();
String ssid_connected = WiFi.SSID();
int rssi = WiFi.RSSI(); // 信号强度(负值,绝对值越小信号越强)
// 信号强度转换为百分比
int quality;
if (rssi >= -50) quality = 100;
else if (rssi <= -100) quality = 0;
else quality = 2 * (rssi + 100);
// 串口输出
Serial.println("----- 网络信息 -----");
Serial.print("IP地址: "); Serial.println(ip);
Serial.print("WiFi: "); Serial.println(ssid_connected);
Serial.print("信号强度: "); Serial.print(rssi); Serial.print(" dBm ("); Serial.print(quality); Serial.println("%)");
// OLED 显示网络信息
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_wqy12_t_gb2312);
u8g2.setFontPosTop();
u8g2.setCursor(0, 0);
u8g2.print("WiFi已连接");
u8g2.drawHLine(0, 15, 128);
u8g2.setCursor(0, 20);
u8g2.print("IP:");
u8g2.setCursor(0, 36);
u8g2.print(ip);
u8g2.setCursor(0, 52);
u8g2.print("信号:");
u8g2.setCursor(40, 52);
u8g2.print(String(rssi) + "dBm");
u8g2.sendBuffer();
delay(5000); // 每5秒刷新一次
}
```
代码讲解
#include <ESP8266WiFi.h>:ESP8266 专用 WiFi 库,提供 STA(连接路由器)和 AP(创建热点)两种模式
WiFi.mode(WIFI_STA):将 ESP8266 设置为站点模式(Station),即作为客户端连接到已有的 WiFi 路由器
WiFi.begin(ssid, password):发起 WiFi 连接。此函数为非阻塞式,需通过WiFi.status() == WL_CONNECTED轮询判断是否连接成功
WiFi.localIP():获取 ESP8266 被分配的 IP 地址,返回IPAddress对象,通过.toString()转为字符串
WiFi.RSSI():获取 WiFi 信号强度(Received Signal Strength Indicator),单位为 dBm。范围通常为 -30(极强)到 -90(极弱)。代码中将其转换为 0~100% 的百分比,更直观
- 等待动画:连接过程中,OLED 显示动态的省略号(
.、..、...),通过dotCount % 4实现循环
- OLED 刷新策略:
loop()中每 5 秒刷新一次网络信息。实际项目中可改为仅在状态变化时刷新,以减少屏幕闪烁
MPU6050跌倒检测实验


本实验使用 MPU6050 六轴传感器(三轴加速度计 + 三轴陀螺仪)实现跌倒检测功能。通过检测加速度的合矢量是否超过阈值来判断是否发生跌倒,这是老年人监护、运动保护等场景中的典型应用。
硬件连接(I2C方式)
| MPU6050引脚 | ESP8266 NodeMCU | 说明 |
|---|---|---|
| VCC | 3.3V | 电源 |
| GND | GND | 接地 |
| SCL | D1 (GPIO5) | I2C时钟 |
| SDA | D2 (GPIO4) | I2C数据 |
| AD0 | GND 或 3.3V | I2C地址选择(GND=0x68, 3.3V=0x69) |
安装库
在 Arduino IDE 中搜索安装 MPU6050_light 库(作者:rfetick)。
完整代码
```C
#include <Arduino.h>
#include <Wire.h>
#include <MPU6050_light.h>
MPU6050 mpu(Wire);
// 跌倒检测阈值
#define FALL_THRESHOLD 2.5 // 加速度合矢量阈值(g)
#define RECOVERY_DELAY 3000 // 跌倒后恢复等待时间(毫秒)
unsigned long lastFallTime = 0;
bool fallDetected = false;
void setup() {
Serial.begin(115200);
Wire.begin();
// 初始化 MPU6050
byte status = mpu.begin();
Serial.print(F("MPU6050 初始化: "));
Serial.println(status == 0 ? F("成功") : F("失败"));
if (status != 0) {
Serial.println("请检查接线!");
while (1) { delay(10); }
}
Serial.println("跌倒检测实验启动");
Serial.println("----------------------");
Serial.println("阈值: " + String(FALL_THRESHOLD) + "g");
}
void loop() {
mpu.update();
// 读取三轴加速度(单位:g)
float ax = mpu.getAccX();
float ay = mpu.getAccY();
float az = mpu.getAccZ();
// 计算加速度合矢量
float totalAccel = sqrt(ax * ax + ay * ay + az * az);
// 串口输出数据
Serial.print("加速度: X=");
Serial.print(ax, 2);
Serial.print(" Y=");
Serial.print(ay, 2);
Serial.print(" Z=");
Serial.print(az, 2);
Serial.print(" | 合矢量=");
Serial.print(totalAccel, 2);
Serial.print("g");
// 跌倒检测逻辑
if (totalAccel > FALL_THRESHOLD && !fallDetected) {
fallDetected = true;
lastFallTime = millis();
Serial.println(" >>> 跌倒检测! <<<");
} else if (fallDetected && (millis() - lastFallTime > RECOVERY_DELAY)) {
// 恢复等待后重置检测状态
if (totalAccel < 1.5) { // 确认已恢复静止
fallDetected = false;
Serial.println(" [已恢复]");
}
} else {
Serial.println("");
}
delay(100);
}
```
代码讲解
MPU6050_light库:轻量级 MPU6050 驱动库,API 简洁。初始化后通过mpu.update()更新传感器数据,再用getAccX/Y/Z()和getGyroX/Y/Z()读取
- 加速度合矢量:
totalAccel = sqrt(ax² + ay² + az²)。正常静止时合矢量约为 1g(重力加速度),跌倒瞬间会产生远大于 1g 的冲击加速度
- 跌倒判定条件:当合矢量超过
FALL_THRESHOLD(2.5g)时判定为跌倒。阈值需根据实际场景调整:运动场景中阈值应适当提高以避免误触发
- 恢复机制:跌倒检测后设置
fallDetected = true,并等待RECOVERY_DELAY(3秒)后才允许再次检测。恢复条件是合矢量低于 1.5g(说明人已静止)。这避免了跌倒后的持续震荡导致多次误报
millis()计时:使用非阻塞式计时,不使用delay(),确保传感器持续采样
MPU6050数据上传巴法云实验
本实验在 11.5 的基础上,将 MPU6050 采集的加速度数据通过 WiFi 上传到巴法云物联网平台,实现远程监控。巴法云是一个免费的物联网云平台,支持 TCP/MQTT 协议,适合教学和原型开发。
硬件连接
与 11.5 相同(MPU6050 + ESP8266),额外需要 WiFi 环境。
安装库
MPU6050_light(同 11.5)
- 巴法云使用标准 WiFi 客户端,无需额外库
完整代码
```C
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <Wire.h>
#include <MPU6050_light.h>
// WiFi 配置
const char* ssid = "YourWiFiName";
const char* password = "YourWiFiPassword";
// 巴法云 TCP 服务器配置
const char* tcpServer = "tcp.bemfa.com";
const int tcpPort = 8344;
const char* privateKey = "你的巴法云私钥"; // 在 bemfa.com 注册获取
MPU6050 mpu(Wire);
WiFiClient client;
unsigned long lastUploadTime = 0;
const long uploadInterval = 2000; // 上传间隔(毫秒)
void setup() {
Serial.begin(115200);
Wire.begin();
// 初始化 MPU6050
byte status = mpu.begin();
Serial.println(status == 0 ? F("MPU6050 OK") : F("MPU6050 FAIL"));
if (status != 0) while (1) { delay(10); }
// 连接 WiFi
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
Serial.print("连接WiFi");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWiFi已连接, IP: " + WiFi.localIP().toString());
}
void uploadToBafa(float ax, float ay, float az) {
// 构建 TCP 连接
if (!client.connect(tcpServer, tcpPort)) {
Serial.println("巴法云连接失败");
return;
}
// 构建上传数据(JSON 格式)
// 巴法云 TCP 协议格式:私钥\n数据
String payload = String(privateKey) + "\n";
payload += "{\"ax\":" + String(ax, 2);
payload += ",\"ay\":" + String(ay, 2);
payload += ",\"az\":" + String(az, 2);
payload += "}";
// 发送数据
client.print(payload);
Serial.println("已上传: " + payload);
// 等待服务器响应
unsigned long timeout = millis();
while (client.available() == 0) {
if (millis() - timeout > 2000) {
Serial.println("服务器超时");
client.stop();
return;
}
}
// 读取服务器响应
while (client.available()) {
String line = client.readStringUntil('\n');
Serial.println("响应: " + line);
}
client.stop();
}
void loop() {
mpu.update();
float ax = mpu.getAccX();
float ay = mpu.getAccY();
float az = mpu.getAccZ();
float totalAccel = sqrt(ax * ax + ay * ay + az * az);
// 串口输出
Serial.print("加速度合矢量: ");
Serial.print(totalAccel, 2);
Serial.println("g");
// 定时上传数据
if (millis() - lastUploadTime >= uploadInterval) {
lastUploadTime = millis();
uploadToBafa(ax, ay, az);
}
delay(100);
}
```
代码讲解
- 巴法云 TCP 协议:巴法云使用简单的 TCP 协议,数据格式为
私钥\nJSON数据。每次发送数据需要重新建立 TCP 连接,发送完毕后关闭连接。这种方式适合低频率的数据上传
WiFiClient:ESP8266 内置的 TCP 客户端类。client.connect(server, port)建立连接,client.print()发送数据,client.stop()关闭连接
- JSON 数据格式:
{"ax":0.12,"ay":0.35,"az":9.78}。使用字符串拼接构建 JSON,简单直接。生产环境中建议使用ArduinoJson库来处理 JSON
- 定时上传:使用
millis()实现 2 秒间隔的非阻塞定时上传,不影响传感器数据的持续采集
- 超时处理:连接服务器后等待响应,设置 2 秒超时。如果服务器未响应则主动断开连接,避免阻塞程序
- 私钥:在巴法云平台(bemfa.com)注册账号后创建设备,每个设备有一个唯一的私钥,用于标识数据来源