PART 5

综合项目实践

第五篇
第8章 综合实验案例
电子信息系统认知与实践·杂志风电子书
第8章 综合实验案例
8.1 OLED显示屏实验(u8g2库)
8.2 超声波测距实验(HC-SR04)
8.3 音乐播放实验(MIDI音调)
8.4 WiFi配置与OLED显示实验
8.5 MPU6050跌倒检测实验
8.6 MPU6050数据上传巴法云实验
第8章 综合实验案例
8.1 OLED显示屏实验(u8g2库)
8.1.1 OLED显示屏实验(u8g2库)🔗
8.2 超声波测距实验(HC-SR04)
8.2.1 超声波测距实验(HC-SR04)🔗
8.3 音乐播放实验(MIDI音调)
8.3.1 音乐播放实验(MIDI音调)🔗
8.4 WiFi配置与OLED显示实验
8.4.1 WiFi配置与OLED显示实验🔗
8.5 MPU6050跌倒检测实验
8.5.1 MPU6050跌倒检测实验🔗
8.6 MPU6050数据上传巴法云实验
8.6.1 MPU6050数据上传巴法云实验🔗
第8章

综合实验案例

第8章 综合实验案例 > 8.1 OLED显示屏实验(u8g2库)
8.1.1

OLED显示屏实验(u8g2库)

本实验使用 u8g2 图形库驱动 SSD1306 OLED 显示屏,实现文字和图形的显示。相比 Adafruit_SSD1306 库,u8g2 支持更多字体和绘图功能,是目前 Arduino 平台最流行的 OLED 驱动库。

硬件连接

OLED引脚ESP8266 NodeMCU说明
VCC3.3V电源
GNDGND接地
SCLD1 (GPIO5)I2C时钟
SDAD2 (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() 清空内存缓冲区,所有绘制操作(printdrawHLine 等)写入缓冲区,sendBuffer() 将缓冲区内容一次性发送到 OLED 屏幕
  • setFontPosTop():设置字体绘制起点为字符顶部,便于精确定位
  • drawHLine(x, y, width):绘制水平线,常用于分隔不同区域
第8章 综合实验案例 > 8.2 超声波测距实验(HC-SR04)
8.2.1

超声波测距实验(HC-SR04)

本实验使用 HC-SR04 超声波传感器测量距离,并通过串口输出结果。HC-SR04 的工作原理是:发射端发出 40kHz 超声波脉冲,接收端检测回波,通过计算声波往返时间得到距离。

硬件连接

HC-SR04引脚ESP8266 NodeMCU说明
VCC3.3V 或 VIN电源(5V时测量更稳定)
GNDGND接地
TrigD5 (GPIO14)触发信号输出
EchoD6 (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),高精度应用中需做温度补偿
第8章 综合实验案例 > 8.3 音乐播放实验(MIDI音调)
8.3.1

音乐播放实验(MIDI音调)

本实验使用 tone() 函数驱动蜂鸣器播放 MIDI 格式的音乐旋律。通过定义音符频率数组,可以实现复杂的音乐播放。

硬件连接

蜂鸣器ESP8266 NodeMCU说明
VCC / +3.3V 或 VIN电源(有源蜂鸣器)
GND / -GND接地
I/O / SD7 (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 变量:控制播放速度(每拍的毫秒数),调小则播放更快,调大则更慢
第8章 综合实验案例 > 8.4 WiFi配置与OLED显示实验
8.4.1

WiFi配置与OLED显示实验

本实验实现一个常见的物联网应用场景:ESP8266 连接 WiFi 后,在 OLED 屏幕上显示网络配置信息(IP地址、WiFi名称、信号强度等)。这是物联网项目的基础框架。

硬件连接

OLED引脚ESP8266 NodeMCU说明
VCC3.3V电源
GNDGND接地
SCLD1 (GPIO5)I2C时钟
SDAD2 (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 秒刷新一次网络信息。实际项目中可改为仅在状态变化时刷新,以减少屏幕闪烁
第8章 综合实验案例 > 8.5 MPU6050跌倒检测实验
8.5.1

MPU6050跌倒检测实验

本实验使用 MPU6050 六轴传感器(三轴加速度计 + 三轴陀螺仪)实现跌倒检测功能。通过检测加速度的合矢量是否超过阈值来判断是否发生跌倒,这是老年人监护、运动保护等场景中的典型应用。

硬件连接(I2C方式)

MPU6050引脚ESP8266 NodeMCU说明
VCC3.3V电源
GNDGND接地
SCLD1 (GPIO5)I2C时钟
SDAD2 (GPIO4)I2C数据
AD0GND 或 3.3VI2C地址选择(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(),确保传感器持续采样
第8章 综合实验案例 > 8.6 MPU6050数据上传巴法云实验
8.6.1

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)注册账号后创建设备,每个设备有一个唯一的私钥,用于标识数据来源