项目开发背景
随着社会对特殊群体关怀意识的不断提升,盲人群体的出行安全问题日益受到广泛关注。据统计,全球约有2.5亿视力障碍者,他们在日常行走中常面临碰撞、迷失方向等风险。传统盲杖虽能提供基础触觉反馈,但存在探测范围有限、无法预警空中障碍物、缺乏数据记录功能等局限性。
近年来物联网技术和智能传感器的发展为解决这一问题提供了新的思路。通过融合超声波测距、多模态人机交互和云平台技术,可构建具有实时环境感知能力的辅助系统。这类系统不仅能扩展盲人的空间感知维度,还能通过数据积累为城市无障碍设施规划提供参考依据。
本项目基于STM32微控制器设计智能导航系统,旨在通过高精度超声波传感器实现障碍物三维感知,结合振动与语音的双重反馈机制适应不同使用场景,同时借助物联网技术实现行为数据云端化管理。系统既弥补了传统辅助工具的不足,又为视障人群提供了兼具安全性、实时性和可追溯性的现代化出行解决方案。
设计实现的功能
(1)实时采集前方障碍物距离信息并进行危险等级判断
(2)通过振动马达和语音模块提供多模式导航提示
(3)行走轨迹和遇障数据上传至华为云平台
(4)QT上位机显示行走路径地图和遇障统计信息
项目硬件模块组成
(1)STM32F103C8T6最小系统核心板
(2)HC-SR04超声波传感器
(3)JL-03语音模块
(4)振动马达模块
(5)ESP8266-01S Wi-Fi模块
(6)洞洞板焊接传感器接口电路和杜邦线
设计意义
该智能盲人辅助导航系统通过集成STM32微控制器和多种传感器模块,旨在显著提升盲人用户的独立出行能力和安全性。系统实时采集前方障碍物距离信息并进行危险等级判断,帮助用户及时感知环境变化,减少碰撞风险,从而增强户外活动的信心和自主性。
系统采用振动马达和语音模块提供多模式导航提示,这种设计考虑了盲人用户的不同感知需求和环境适应性。触觉反馈通过振动强度传达障碍物距离,而语音提示则提供更详细的导航信息,确保用户在嘈杂或安静环境中都能获得有效引导,提升用户体验的包容性和实用性。
通过ESP8266 Wi-Fi模块将行走轨迹和遇障数据上传至华为云平台,并结合QT上位机显示地图和统计信息,该系统实现了数据的远程监控和分析。这使得 caregivers 或研究人员能够追踪用户行为模式,识别高频遇障区域,并为后续系统优化或城市无障碍设施规划提供数据支持,具有重要的社会应用价值。
硬件组成基于STM32F103C8T6核心板和常见模块如HC-SR04超声波传感器,设计注重成本效益和可扩展性。洞洞板焊接和杜邦线连接确保了系统的灵活性和易于维护,为未来功能升级或定制化应用奠定了基础,体现了嵌入式系统在辅助技术中的实用性和创新性。
设计思路
该系统以STM32F103C8T6最小系统核心板作为主控制器,负责协调整个系统的运行。通过HC-SR04超声波传感器实时采集前方障碍物的距离信息,STM32对采集到的数据进行处理,并根据预设阈值判断危险等级,例如将距离分为高、中、低风险级别,以便及时提供导航提示。
对于导航提示,系统集成振动马达模块和JL-03语音模块,实现多模式反馈。当检测到障碍物时,STM32根据危险等级控制振动马达产生不同强度的振动,同时语音模块播放相应的警告或导航语音,确保盲人用户能通过触觉和听觉获取实时环境信息,增强行走安全性。
数据上传部分通过ESP8266-01S Wi-Fi模块实现,STM32将采集到的行走轨迹和遇障数据打包,通过Wi-Fi模块发送至华为云平台。这一过程确保数据远程存储和可访问性,为后续分析提供基础。
上位机显示基于QT开发,通过华为云平台获取数据,并在软件界面中可视化显示行走路径地图和遇障统计信息。这使得 caregivers 或用户自身可以回顾行走历史和分析障碍 patterns,但上位机部分独立于嵌入式系统,依赖于云数据。
硬件连接采用洞洞板焊接传感器接口电路,并使用杜邦线连接各模块,确保系统紧凑可靠。STM32通过GPIO和UART等接口与传感器和模块通信,实现低功耗和实时响应。整个设计注重实用性和成本效益,符合盲人辅助导航的实际需求。
框架图
+------------------------+ +-----------------------------+
| HC-SR04 Ultrasound |-----| STM32F103C8T6 |
| Sensor (GPIO) | | Main Controller |
+------------------------+ | - Process distance data |
| - Danger level judgment |
+------------------------+ | - Control outputs |
| JL-03 Voice Module |-----| |
| (UART) | | |
+------------------------+ | |
| |
+------------------------+ | |
| Vibration Motor Module |-----| |
| (GPIO) | | |
+------------------------+ +----------------------------+
|
| (UART)
|
+----------------------------+
| ESP8266-01S Wi-Fi Module |
| (Communicate with Cloud) |
+----------------------------+
|
| (Wi-Fi, MQTT/HTTP)
|
+----------------------------+
| Huawei Cloud Platform |
| - Store trajectory data |
| - Store obstacle data |
+----------------------------+
|
| (API/WebSocket)
|
+----------------------------+
| QT Upper Computer |
| - Display path map |
| - Show obstacle statistics |
+----------------------------+
系统总体设计
系统总体设计基于STM32F103C8T6最小系统核心板作为主控制器,负责协调整个系统的运行。该系统通过HC-SR04超声波传感器实时采集前方障碍物的距离信息,主控制器对采集到的数据进行处理,根据预设阈值进行危险等级判断,例如将距离分为安全、警告和危险等级别。
处理后的数据用于驱动导航反馈机制,通过振动马达模块提供触觉提示,例如不同频率的振动表示不同危险级别,同时JL-03语音模块提供语音导航提示,如播放障碍物距离和方向信息,实现多模式导航辅助。这些反馈机制帮助盲人用户感知周围环境,提高行走安全性。
系统还集成ESP8266-01S Wi-Fi模块,用于将行走轨迹和遇障数据上传至华为云平台。主控制器通过串口通信与Wi-Fi模块交互,封装数据并发送到云平台,实现远程数据存储和监控。同时,QT上位机软件从云平台获取数据,显示用户的行走路径地图和遇障统计信息,便于 caregivers 或管理员进行数据分析和管理。
硬件连接方面,所有传感器和模块通过洞洞板焊接的接口电路和杜邦线连接到STM32核心板,确保系统结构紧凑且可靠。整个设计注重实用性和稳定性,以实际需求为导向,无需额外功能添加。
系统功能总结
| 功能描述 | 实现方式 |
|---|---|
| 实时采集前方障碍物距离信息并进行危险等级判断 | HC-SR04超声波传感器检测距离,STM32F103C8T6处理数据并判断危险等级 |
| 通过振动马达和语音模块提供多模式导航提示 | 振动马达模块提供触觉反馈,JL-03语音模块提供语音提示 |
| 行走轨迹和遇障数据上传至华为云平台 | ESP8266-01S Wi-Fi模块实现无线通信,将数据上传至华为云 |
| QT上位机显示行走路径地图和遇障统计信息 | 基于华为云平台数据,QT应用程序解析并显示路径地图和统计信息 |
设计的各个功能模块描述
STM32F103C8T6最小系统核心板作为主控制器,负责整个系统的协调与控制。它实时处理来自传感器的数据,进行障碍物距离的危险等级判断,并根据结果驱动振动马达和语音模块提供反馈。同时,主控制器管理Wi-Fi模块的数据上传功能,确保系统高效运行。
HC-SR04超声波传感器用于检测前方障碍物的距离信息。该传感器通过发射和接收超声波信号,计算与障碍物之间的距离,并将数据实时传输给STM32主控制器,以支持危险评估和导航决策。
JL-03语音模块接收STM32的指令,提供语音导航提示。根据障碍物危险等级或系统状态,模块播放预录的语音消息,如警告音或方向指示,帮助用户感知周围环境。
振动马达模块提供触觉导航反馈,通过不同的振动模式和强度来表示危险等级或导航信息。STM32控制马达的振动行为,使得用户可以通过触觉感知障碍物 proximity 或系统提示。
ESP8266-01S Wi-Fi模块实现与华为云平台的通信。它将STM32处理后的行走轨迹和遇障数据通过Wi-Fi网络上传输至云平台,支持远程数据存储和监控,为上位机显示提供数据源。
硬件连接方面,使用洞洞板焊接传感器接口电路,并通过杜邦线连接各模块,包括STM32、超声波传感器、语音模块、振动马达和Wi-Fi模块。这种设计确保各组件稳定互联,便于系统集成和调试。
QT上位机软件基于华为云平台的数据,显示行走路径地图和遇障统计信息。它提供可视化界面,帮助用户或管理员查看历史轨迹和障碍事件,增强系统的监控和分析能力。
上位机代码设计
以下是基于QT框架的上位机代码设计,用于显示智能盲人辅助导航系统的行走路径地图和遇障统计信息。代码采用C++开发,包括主窗口类、网络数据获取、JSON解析和UI显示。假设数据从华为云平台通过HTTP GET请求获取,返回JSON格式的轨迹和障碍数据。
项目文件结构:
main.cpp:应用程序入口点。mainwindow.h:主窗口类声明。mainwindow.cpp:主窗口类实现。project.pro:QT项目文件(需自行配置)。
代码实现:
1. main.cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
2. mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
#include <QLabel>
#include <QTimer>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void fetchData(); // 获取数据
void onTrackDataReceived(QNetworkReply *reply); // 处理轨迹数据
void onObstacleDataReceived(QNetworkReply *reply); // 处理障碍数据
void updateDisplay(); // 更新显示
private:
QNetworkAccessManager *networkManager;
QGraphicsScene *scene; // 用于绘制地图
QGraphicsView *view; // 显示地图
QLabel *statsLabel; // 显示统计信息
QTimer *timer; // 定时器用于定期更新数据
// 假设的数据结构
QList<QPointF> trackPoints; // 存储轨迹点
QList<QPair<QDateTime, double>> obstacles; // 存储障碍数据:时间和距离
void parseTrackData(const QByteArray &data); // 解析轨迹JSON
void parseObstacleData(const QByteArray &data); // 解析障碍JSON
void drawPath(); // 绘制路径
void updateStats(); // 更新统计信息
};
#endif // MAINWINDOW_H
3. mainwindow.cpp
#include "mainwindow.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QGraphicsLineItem>
#include <QDateTimeAxis>
#include <QValueAxis>
#include <QChartView>
#include <QChart>
#include <QDebug>
// 华为云API端点(示例URL,需根据实际修改)
const QString TRACK_API_URL = "http://your-huawei-cloud-api/track";
const QString OBSTACLE_API_URL = "http://your-huawei-cloud-api/obstacles";
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
// 设置窗口
setWindowTitle("智能盲人辅助导航系统上位机");
setGeometry(100, 100, 800, 600);
// 初始化网络管理器
networkManager = new QNetworkAccessManager(this);
// 初始化UI
QWidget *centralWidget = new QWidget(this);
setCentralWidget(centralWidget);
QVBoxLayout *layout = new QVBoxLayout(centralWidget);
// 地图显示区域
scene = new QGraphicsScene(this);
view = new QGraphicsView(scene);
view->setRenderHint(QPainter::Antialiasing);
view->setSceneRect(0, 0, 600, 400);
layout->addWidget(view);
// 统计信息标签
statsLabel = new QLabel("统计信息加载中...", this);
layout->addWidget(statsLabel);
// 定时器,每5秒更新一次数据
timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, &MainWindow::fetchData);
timer->start(5000); // 5秒间隔
// 初始获取数据
fetchData();
}
MainWindow::~MainWindow()
{
// 清理资源
}
void MainWindow::fetchData()
{
// 获取轨迹数据
QNetworkRequest trackRequest(QUrl(TRACK_API_URL));
QNetworkReply *trackReply = networkManager->get(trackRequest);
connect(trackReply, &QNetworkReply::finished, this, [this, trackReply]() { onTrackDataReceived(trackReply); });
// 获取障碍数据
QNetworkRequest obstacleRequest(QUrl(OBSTACLE_API_URL));
QNetworkReply *obstacleReply = networkManager->get(obstacleRequest);
connect(obstacleReply, &QNetworkReply::finished, this, [this, obstacleReply]() { onObstacleDataReceived(obstacleReply); });
}
void MainWindow::onTrackDataReceived(QNetworkReply *reply)
{
if (reply->error() == QNetworkReply::NoError) {
QByteArray data = reply->readAll();
parseTrackData(data);
} else {
qDebug() << "轨迹数据请求错误:" << reply->errorString();
}
reply->deleteLater();
updateDisplay(); // 更新显示
}
void MainWindow::onObstacleDataReceived(QNetworkReply *reply)
{
if (reply->error() == QNetworkReply::NoError) {
QByteArray data = reply->readAll();
parseObstacleData(data);
} else {
qDebug() << "障碍数据请求错误:" << reply->errorString();
}
reply->deleteLater();
updateDisplay(); // 更新显示
}
void MainWindow::parseTrackData(const QByteArray &data)
{
trackPoints.clear();
QJsonDocument doc = QJsonDocument::fromJson(data);
if (doc.isNull() || !doc.isObject()) {
qDebug() << "无效的轨迹JSON数据";
return;
}
QJsonObject obj = doc.object();
if (obj.contains("points") && obj["points"].isArray()) {
QJsonArray pointsArray = obj["points"].toArray();
for (const QJsonValue &value : pointsArray) {
if (value.isObject()) {
QJsonObject pointObj = value.toObject();
double x = pointObj["x"].toDouble();
double y = pointObj["y"].toDouble();
trackPoints.append(QPointF(x, y));
}
}
}
}
void MainWindow::parseObstacleData(const QByteArray &data)
{
obstacles.clear();
QJsonDocument doc = QJsonDocument::fromJson(data);
if (doc.isNull() || !doc.isObject()) {
qDebug() << "无效的障碍JSON数据";
return;
}
QJsonObject obj = doc.object();
if (obj.contains("obstacles") && obj["obstacles"].isArray()) {
QJsonArray obstaclesArray = obj["obstacles"].toArray();
for (const QJsonValue &value : obstaclesArray) {
if (value.isObject()) {
QJsonObject obstacleObj = value.toObject();
double distance = obstacleObj["distance"].toDouble();
QString timeStr = obstacleObj["time"].toString();
QDateTime time = QDateTime::fromString(timeStr, "yyyy-MM-dd hh:mm:ss");
obstacles.append(qMakePair(time, distance));
}
}
}
}
void MainWindow::updateDisplay()
{
drawPath();
updateStats();
}
void MainWindow::drawPath()
{
scene->clear(); // 清除之前绘制
if (trackPoints.isEmpty()) return;
// 缩放和偏移以适应视图
double minX = trackPoints.first().x(), maxX = minX;
double minY = trackPoints.first().y(), maxY = minY;
for (const QPointF &point : trackPoints) {
if (point.x() < minX) minX = point.x();
if (point.x() > maxX) maxX = point.x();
if (point.y() < minY) minY = point.y();
if (point.y() > maxY) maxY = point.y();
}
double scaleX = view->width() / (maxX - minX + 1);
double scaleY = view->height() / (maxY - minY + 1);
double scale = qMin(scaleX, scaleY) * 0.8; // 缩放因子,留边距
// 绘制路径
QPen pen(Qt::blue);
pen.setWidth(2);
for (int i = 1; i < trackPoints.size(); ++i) {
QPointF p1 = trackPoints[i-1];
QPointF p2 = trackPoints[i];
// 应用缩放和偏移
p1 = QPointF((p1.x() - minX) * scale, (p1.y() - minY) * scale);
p2 = QPointF((p2.x() - minX) * scale, (p2.y() - minY) * scale);
scene->addLine(QLineF(p1, p2), pen);
}
// 绘制障碍点(用红色标记)
QPen obstaclePen(Qt::red);
obstaclePen.setWidth(5);
for (const auto &obstacle : obstacles) {
// 假设障碍点对应轨迹点,这里简化处理:使用最后一个点或特定逻辑
// 实际应根据时间匹配轨迹点,这里仅示例
if (!trackPoints.isEmpty()) {
QPointF point = trackPoints.last(); // 简化:用最后一个点代表障碍位置
point = QPointF((point.x() - minX) * scale, (point.y() - minY) * scale);
scene->addEllipse(point.x() - 2, point.y() - 2, 4, 4, obstaclePen);
}
}
}
void MainWindow::updateStats()
{
int obstacleCount = obstacles.size();
double totalDistance = 0.0;
if (!trackPoints.isEmpty()) {
for (int i = 1; i < trackPoints.size(); ++i) {
totalDistance += QLineF(trackPoints[i-1], trackPoints[i]).length();
}
}
QString statsText = QString("总行走距离: %1 米n遇障次数: %2n最后更新: %3")
.arg(totalDistance, 0, 'f', 2)
.arg(obstacleCount)
.arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"));
statsLabel->setText(statsText);
}
使用说明:
- 在QT Creator中创建新项目,将上述代码文件添加到项目中。
- 在
project.pro文件中确保添加网络模块:QT += network。 - 根据实际华为云API修改
TRACK_API_URL和OBSTACLE_API_URL。 - 编译并运行程序。上位机将定期从云平台获取数据并显示路径和统计信息。
此代码提供了基本功能,实际应用中可能需要根据数据格式和业务逻辑进行调整。
模块代码设计
#include <stdint.h>
#include <string.h>
// RCC registers
#define RCC_BASE 0x40021000
#define RCC_APB2ENR (*((volatile uint32_t *)(RCC_BASE + 0x18)))
#define RCC_APB1ENR (*((volatile uint32_t *)(RCC_BASE + 0x1C)))
// GPIOA registers
#define GPIOA_BASE 0x40010800
#define GPIOA_CRL (*((volatile uint32_t *)(GPIOA_BASE + 0x00)))
#define GPIOA_CRH (*((volatile uint32_t *)(GPIOA_BASE + 0x04)))
#define GPIOA_IDR (*((volatile uint32_t *)(GPIOA_BASE + 0x08)))
#define GPIOA_ODR (*((volatile uint32_t *)(GPIOA_BASE + 0x0C)))
// USART1 registers
#define USART1_BASE 0x40013800
#define USART1_SR (*((volatile uint32_t *)(USART1_BASE + 0x00)))
#define USART1_DR (*((volatile uint32_t *)(USART1_BASE + 0x04)))
#define USART1_BRR (*((volatile uint32_t *)(USART1_BASE + 0x08)))
#define USART1_CR1 (*((volatile uint32_t *)(USART1_BASE + 0x0C)))
// USART2 registers
#define USART2_BASE 0x40004400
#define USART2_SR (*((volatile uint32_t *)(USART2_BASE + 0x00)))
#define USART2_DR (*((volatile uint32_t *)(USART2_BASE + 0x04)))
#define USART2_BRR (*((volatile uint32_t *)(USART2_BASE + 0x08)))
#define USART2_CR1 (*((volatile uint32_t *)(USART2_BASE + 0x0C)))
// TIM2 registers
#define TIM2_BASE 0x40000000
#define TIM2_CR1 (*((volatile uint32_t *)(TIM2_BASE + 0x00)))
#define TIM2_CR2 (*((volatile uint32_t *)(TIM2_BASE + 0x04)))
#define TIM2_SMCR (*((volatile uint32_t *)(TIM2_BASE + 0x08)))
#define TIM2_DIER (*((volatile uint32_t *)(TIM2_BASE + 0x0C)))
#define TIM2_SR (*((volatile uint32_t *)(TIM2_BASE + 0x10)))
#define TIM2_EGR (*((volatile uint32_t *)(TIM2_BASE + 0x14)))
#define TIM2_CCMR1 (*((volatile uint32_t *)(TIM2_BASE + 0x18)))
#define TIM2_CCMR2 (*((volatile uint32_t *)(TIM2_BASE + 0x1C)))
#define TIM2_CCER (*((volatile uint32_t *)(TIM2_BASE + 0x20)))
#define TIM2_CNT (*((volatile uint32_t *)(TIM2_BASE + 0x24)))
#define TIM2_PSC (*((volatile uint32_t *)(TIM2_BASE + 0x28)))
#define TIM2_ARR (*((volatile uint32_t *)(TIM2_BASE + 0x2C)))
#define TIM2_CCR1 (*((volatile uint32_t *)(TIM2_BASE + 0x34)))
#define TIM2_CCR2 (*((volatile uint32_t *)(TIM2_BASE + 0x38)))
// Bit definitions
#define USART_SR_TXE (1 << 7)
#define USART_SR_RXNE (1 << 5)
#define RCC_APB2ENR_IOPAEN (1 << 2)
#define RCC_APB2ENR_USART1EN (1 << 14)
#define RCC_APB1ENR_USART2EN (1 << 17)
#define RCC_APB1ENR_TIM2EN (1 << 0)
// Pin definitions
#define TRIG_PIN 0 // PA0
#define ECHO_PIN 1 // PA1
#define MOTOR_PIN 4 // PA4
void SystemInit(void) {
RCC_APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_USART1EN;
RCC_APB1ENR |= RCC_APB1ENR_USART2EN | RCC_APB1ENR_TIM2EN;
}
void GPIO_Init(void) {
// PA0 as output push-pull 50MHz for Trig
GPIOA_CRL &= ~(0xF << (4 * TRIG_PIN));
GPIOA_CRL |= (0x3 << (4 * TRIG_PIN));
// PA1 as input floating for Echo
GPIOA_CRL &= ~(0xF << (4 * ECHO_PIN));
GPIOA_CRL |= (0x4 << (4 * ECHO_PIN));
// PA4 as output push-pull 50MHz for vibration motor
GPIOA_CRL &= ~(0xF << (4 * MOTOR_PIN));
GPIOA_CRL |= (0x3 << (4 * MOTOR_PIN));
// PA9 as USART1 TX, alternate function push-pull
GPIOA_CRH &= ~(0xF << 4);
GPIOA_CRH |= (0xA << 4);
// PA10 as USART1 RX, input floating
GPIOA_CRH &= ~(0xF << 8);
GPIOA_CRH |= (0x4 << 8);
// PA2 as USART2 TX, alternate function push-pull
GPIOA_CRL &= ~(0xF << 8);
GPIOA_CRL |= (0xA << 8);
// PA3 as USART2 RX, input floating
GPIOA_CRL &= ~(0xF << 12);
GPIOA_CRL |= (0x4 << 12);
}
void USART1_Init(void) {
USART1_BRR = 0x3415; // 9600 baud at 8MHz
USART1_CR1 |= (1 << 13) | (1 << 3) | (1 << 2); // UE, TE, RE
}
void USART2_Init(void) {
USART2_BRR = 0x3415; // 9600 baud at 8MHz
USART2_CR1 |= (1 << 13) | (1 << 3) | (1 << 2); // UE, TE, RE
}
void TIM2_Init(void) {
TIM2_PSC = 7; // 1MHz timer clock
TIM2_ARR = 0xFFFF;
TIM2_CCMR1 &= ~(0x3 << 8);
TIM2_CCMR1 |= (0x1 << 8); // CC2S = 01, input
TIM2_CCMR1 &= ~(0xF << 12); // IC2F = 0000
TIM2_CCMR1 &= ~(0x3 << 10); // IC2PSC = 00
TIM2_CCER &= ~(1 << 5); // CC2P = 0, rising edge
TIM2_CCER &= ~(1 << 7); // CC2NP = 0
TIM2_CCER |= (1 << 4); // CC2E enable
TIM2_CR1 |= (1 << 0); // Enable TIM2
}
void USART1_SendChar(char c) {
while (!(USART1_SR & USART_SR_TXE));
USART1_DR = c;
}
void USART1_SendString(const char *str) {
while (*str) {
USART1_SendChar(*str++);
}
}
void USART2_SendChar(char c) {
while (!(USART2_SR & USART_SR_TXE));
USART2_DR = c;
}
void USART2_SendString(const char *str) {
while (*str) {
USART2_SendChar(*str++);
}
}
char USART2_ReceiveChar(void) {
while (!(USART2_SR & USART_SR_RXNE));
return (char)USART2_DR;
}
void Vibrate(int on) {
if (on) {
GPIOA_ODR |= (1 << MOTOR_PIN);
} else {
GPIOA_ODR &= ~(1 << MOTOR_PIN);
}
}
void PlayVoice(int num) {
char cmd[20];
// Simple command format, adjust based on JL-03 module
sprintf(cmd, "PLAY:%drn", num);
USART1_SendString(cmd);
}
uint32_t GetDistance(void) {
GPIOA_ODR |= (1 << TRIG_PIN);
for (volatile int i = 0; i < 10; i++); // ~10us delay
GPIOA_ODR &= ~(1 << TRIG_PIN);
while (!(GPIOA_IDR & (1 << ECHO_PIN)));
TIM2_CNT = 0;
while (GPIOA_IDR & (1 << ECHO_PIN));
uint32_t time_us = TIM2_CNT;
return time_us / 58; // Distance in cm
}
void ESP8266_SendATCommand(const char *cmd) {
USART2_SendString(cmd);
USART2_SendString("rn");
}
int ESP8266_WaitForResponse(const char *response, uint32_t timeout) {
uint32_t start_time = TIM2_CNT;
char buffer[100] = {0};
int index = 0;
while (TIM2_CNT - start_time < timeout) {
if (USART2_SR & USART_SR_RXNE) {
char c = USART2_ReceiveChar();
buffer[index++] = c;
if (index >= 99) index = 0;
if (strstr(buffer, response) != NULL) {
return 1;
}
}
}
return 0;
}
void ESP8266_Init(void) {
ESP8266_SendATCommand("AT");
ESP8266_WaitForResponse("OK", 1000000);
ESP8266_SendATCommand("AT+CWMODE=1");
ESP8266_WaitForResponse("OK", 1000000);
// Replace with actual Wi-Fi credentials
ESP8266_SendATCommand("AT+CWJAP="SSID","password"");
ESP8266_WaitForResponse("OK", 5000000);
}
void UploadToCloud(uint32_t distance, int obstacle) {
ESP8266_SendATCommand("AT+CIPSTART="TCP","cloud.huawei.com",80");
if (ESP8266_WaitForResponse("OK", 1000000)) {
char post_data[100];
sprintf(post_data, "POST /api/data HTTP/1.1rnHost: cloud.huawei.comrnContent-Type: application/jsonrnContent-Length: 50rnrn{"distance":%lu,"obstacle":%d}", distance, obstacle);
ESP8266_SendATCommand("AT+CIPSEND=100");
ESP8266_WaitForResponse(">", 1000000);
USART2_SendString(post_data);
ESP8266_WaitForResponse("SEND OK", 1000000);
ESP8266_SendATCommand("AT+CIPCLOSE");
}
}
int main(void) {
SystemInit();
GPIO_Init();
USART1_Init();
USART2_Init();
TIM2_Init();
ESP8266_Init();
uint32_t last_upload = 0;
while (1) {
uint32_t dist = GetDistance();
int danger_level = 0;
if (dist < 50) {
danger_level = 2;
Vibrate(1);
PlayVoice(1);
} else if (dist < 100) {
danger_level = 1;
Vibrate(1);
PlayVoice(2);
} else {
Vibrate(0);
}
if (TIM2_CNT - last_upload > 5000000) {
UploadToCloud(dist, danger_level);
last_upload = TIM2_CNT;
}
for (volatile int i = 0; i < 100000; i++);
}
}
项目核心代码
#include "stm32f10x.h" // 寄存器定义头文件
#include "delay.h" // 假设有延时模块,提供Delay_ms函数
#include "ultrasonic.h" // 假设有超声波模块,提供Ultrasonic_Init和Ultrasonic_GetDistance
#include "voice.h" // 假设有语音模块,提供Voice_Init和Voice_Play
#include "vibrate.h" // 假设有振动模块,提供Vibrate_Init和Vibrate
#include "wifi.h" // 假设有Wi-Fi模块,提供WiFi_Init和WiFi_SendData
// 定义危险等级常量
#define DANGER_LEVEL_SAFE 0
#define DANGER_LEVEL_WARNING 1
#define DANGER_LEVEL_DANGER 2
// 定义距离阈值(单位:厘米)
#define DISTANCE_DANGER 50.0f
#define DISTANCE_WARNING 100.0f
// 全局变量用于存储距离和危险等级
volatile uint32_t distance_cm = 0;
volatile uint8_t danger_level = DANGER_LEVEL_SAFE;
/**
* @brief 系统时钟初始化函数(使用寄存器方式设置时钟到72MHz)
* @param 无
* @retval 无
*/
void SystemClock_Init(void)
{
// 启用HSE(外部高速时钟)
RCC->CR |= ((uint32_t)RCC_CR_HSEON);
while (!(RCC->CR & RCC_CR_HSERDY));
// 配置PLL:HSE作为源,9倍频(8MHz * 9 = 72MHz)
RCC->CFGR |= RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9;
// 启用PLL
RCC->CR |= RCC_CR_PLLON;
while (!(RCC->CR & RCC_CR_PLLRDY));
// 设置FLASH延迟(2等待状态,因为72MHz > 48MHz)
FLASH->ACR |= FLASH_ACR_LATENCY_2;
// 切换系统时钟到PLL
RCC->CFGR |= RCC_CFGR_SW_PLL;
while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL);
// 设置AHB、APB1、APB2分频(HCLK=72MHz, PCLK1=36MHz, PCLK2=72MHz)
RCC->CFGR |= RCC_CFGR_HPRE_DIV1 | RCC_CFGR_PPRE1_DIV2 | RCC_CFGR_PPRE2_DIV1;
}
/**
* @brief 系统初始化函数
* @param 无
* @retval 无
*/
void SystemInit(void)
{
// 设置向量表偏移(可选,通常默认)
SCB->VTOR = FLASH_BASE;
// 初始化系统时钟
SystemClock_Init();
// 初始化SysTick定时器用于延时(假设SysTick_Init在delay.h中实现,但这里简单处理)
// 假设Delay_ms函数基于SysTick或软件延时
}
int main(void)
{
// 系统初始化
SystemInit();
// 初始化各模块
Ultrasonic_Init(); // 初始化超声波传感器(假设已实现)
Voice_Init(); // 初始化语音模块(假设已实现)
Vibrate_Init(); // 初始化振动马达(假设已实现)
WiFi_Init(); // 初始化Wi-Fi模块(假设已实现)
// 主循环
while (1)
{
// 读取超声波距离(单位:厘米)
distance_cm = Ultrasonic_GetDistance();
// 判断危险等级
if (distance_cm < DISTANCE_DANGER)
{
danger_level = DANGER_LEVEL_DANGER;
}
else if (distance_cm < DISTANCE_WARNING)
{
danger_level = DANGER_LEVEL_WARNING;
}
else
{
danger_level = DANGER_LEVEL_SAFE;
}
// 根据危险等级控制振动和语音提示
switch (danger_level)
{
case DANGER_LEVEL_DANGER:
Vibrate(255); // 最大振动强度
Voice_Play(1); // 播放危险语音(例如:"前方障碍物,请停止")
break;
case DANGER_LEVEL_WARNING:
Vibrate(128); // 中等振动强度
Voice_Play(2); // 播放警告语音(例如:"前方有障碍物,注意")
break;
case DANGER_LEVEL_SAFE:
Vibrate(0); // 关闭振动
Voice_Play(0); // 播放安全语音或无声(例如:"道路畅通")
break;
}
// 上传数据到华为云平台(距离和危险等级)
WiFi_SendData((float)distance_cm, danger_level);
// 延时100ms,控制循环频率
Delay_ms(100);
}
}
总结
本系统基于STM32F103C8T6核心板设计,实现了智能盲人辅助导航功能,通过实时采集前方障碍物距离信息并进行危险等级判断,有效提升了盲人出行的安全性和自主性。系统整合了多模式导航提示,包括振动马达的触觉反馈和JL-03语音模块的语音提示,为用户提供直观、及时的导航辅助。
硬件组成上,采用HC-SR04超声波传感器进行障碍物检测,ESP8266-01S Wi-Fi模块实现与华为云平台的数据通信,确保行走轨迹和遇障数据能够实时上传。此外,通过洞洞板焊接传感器接口电路和杜邦线连接各模块,保证了系统的稳定性和可扩展性。QT上位机则用于显示行走路径地图和遇障统计信息,方便用户或监护人员远程监控和分析。
整体而言,该系统不仅实现了基本的导航和避障功能,还通过云平台和上位机集成,提供了数据记录和可视化支持,具有较高的实用性和创新性。未来可进一步优化传感器精度和通信效率,以更好地服务于盲人群体。
1250