无线手持二维码识别项目是中山大学电子与信息工程学院(微电子学院)的工程应用训练课程的设计要求。项目是基于STM32F103开发的,开源代码:stm32f103zet6-qrcode-detect,bilibili:基于STM32F1的无线手持二维码识别项目。CSDN总目录:基于STM32F103的二维码识别项目。项目移植了Zbar库进行二维码识别,而非使用二维码识别模块。
此篇文章讲述ESP01模块相关的设计。
文章目录
为了实现检测到二维码后将二维码的信息传到上位机,我使用了ESP01S模块,核心思想为ESP01S开启热点,建立web服务,电脑端通过连接热点访问相应的地址从而获得数据。
注意有两款,博主使用的是ESP-01.
一、 开发环境搭建
进入Arduino IDE 2.3.2安装软件,之后打开软件,文件–>首选项…
复制http://arduino.esp8266.com/stable/package_esp8266com_index.json到下方位置,然后确定。
重启IDE后,工具–>开发板–>开发板管理器…
搜索ESP8266进行安装,安装失败就用梯子。
按照下面的选项选择。
二、 ESP8266代码详解
我们的核心思想是在ESP8266上创建一个WiFi热点和Web服务器。它通过串口接收数据,并在客户端请求时通过Web接口提供这些数据。这样,用户可以通过连接到ESP8266创建的WiFi热点并访问其IP地址,获取串口接收到的数据。
const char *ssid = "ESP8266_HOTSPOT";
const char *password = "lpflpflpf";
ssid:定义WiFi热点的名称。
password:定义WiFi热点的密码。
ESP8266WebServer server(80);
String data;
server:创建一个Web服务器实例,监听80端口。
data:用于存储从串口接收到的数据。
void handleRoot() {
server.send(200, "text/plain", data);
data = ""; // 清空数据
}
handleRoot()
函数用于处理对 Web 服务器根路径(“/”`)的请求。当客户端请求根路径时,将调用此函数来生成响应。
总体而言,handleRoot()
函数会向客户端发送一个 HTTP 响应,其中包含 data
变量中的数据。
1. server.send(200, “text/plain”, data); 这行代码用于向客户端发送 HTTP 响应。它接受三个参数:
statusCode
: HTTP 状态码,指示请求是否成功。在此示例中,使用200
表示请求成功。contentType
: 响应内容的类型。在此示例中,使用"text/plain"
表示响应内容是纯文本。data
: 要发送给客户端的数据。在此示例中,使用data
变量存储的数据。
2. data = ""; // 清空数据
这行代码用于清空 data
变量。
void setup() {
Serial.begin(115200); // 初始化硬件串口
// 设置热点模式
WiFi.softAP(ssid, password);
IPAddress IP = WiFi.softAPIP();
Serial.print("AP IP address: ");
Serial.println(IP);
// 设置Web服务器的路由
server.on("/", handleRoot);
server.begin();
Serial.println("HTTP server started");
}
核心思想即将ESP8266设置为 Wi-Fi 热点并启动一个 Web 服务器,可由连接到网络的设备访问。 Web 服务器的具体功能将取决于 handleRoot()
函数的实现。
1. 初始化串口通信:
- 首先使用
Serial.begin()
函数初始化硬件串口,并将波特率设置为 115200 bps。
2. 创建 Wi-Fi 热点(AP):
WiFi.softAP()
函数用于将微控制器配置为 Wi-Fi 热点。这允许其他设备无线连接到ESP8266。该函数接受两个参数:
ssid
:Wi-Fi 网络的 SSID(服务设置标识符),即扫描可用网络时显示给设备的名称。password
:Wi-Fi 网络的密码,连接设备需要该密码。
使用 WiFi.softAPIP()
函数获取 Wi-Fi 热点的 IP 地址,并使用 Serial.print()
和 Serial.println()
将其打印到串口监视器中。可以提供了可用于访问ESP8266上托管的 Web 服务器的 IP 地址。
3. 启动 Web 服务器:
使用 server
变量创建了一个 HTTP 服务器对象的实例。server.on("/", handleRoot)
函数定义了 Web 服务器的路由,指定当收到对根路径(“/”)的请求时,应调用
handleRoot()` 函数来处理请求。
server.begin()
函数在 Wi-Fi 热点的 IP 地址上启动 Web 服务器。这使得连接到网络的设备可以访问 Web 服务器。最后,使用 Serial.println()
将指示 Web 服务器已启动的消息打印到串口监视器中。
void loop() {
server.handleClient();
// 接收串口数据
if (Serial.available()) {
data = Serial.readStringUntil('\n');
Serial.print("Received data: ");
Serial.println(data);
}
}
loop()
函数会不断循环执行以下操作:检查是否有连接到 Web 服务器的客户端,并处理来自客户端的请求。检查是否有数据可从串口读取。如果有数据可从串口读取,则将数据读入字符串变量并打印到串口监视器上。
1.处理 Web 服务器客户端:
server.handleClient();
这行代码用于处理连接到 Web 服务器的客户端。它会检查是否有任何新的客户端连接请求,并处理来自已连接客户端的任何数据。
2. 接收串口数据 (Serial Communication):
-
if (Serial.available()) { … }, 这部分代码用于检查是否有数据可从串口读取。
-
Serial.available()
函数会检查串口缓冲区中是否有可用的字节。 -
如果有可用字节(即
Serial.available()
返回 true),则程序会继续执行if
语句内部的代码。 -
data = Serial.readStringUntil(‘\n’); 这行代码从串口读取数据并将其存储在 data字符串变量中。
Serial.readStringUntil('\n')
函数会一直读取数据,直到遇到换行符 (‘\n’) 为止。- 读取到的所有数据将被作为一个字符串存储在
data
变量中。
-
Serial.print("Received data: "); 和 Serial.println(data); 这两行代码用于将接收到的数据打印到串口监视器上。
Serial.print()
函数打印一条消息。Serial.println()
函数打印一条消息并添加一个换行符。
三、 上位机代码详解
核心思想:创建了一个使用 PyQt5 的简单 GUI 应用程序,用于从指定的 URL 获取数据并显示在界面上。程序包含两个主要类:`DataFetcher` 和 `MainWindow`。最后使用pyinstaller封装为一个exe文件。DataFetcher
类继承自 QThread
,用于在后台线程中定期发送 HTTP 请求获取数据。它有两个信号:status_update
和 data_update
,分别用于更新连接状态和数据内容。在 run
方法中,线程每隔 2 秒发送一次 HTTP 请求,如果连接成功且有数据返回,则发出相应的信号更新主界面;如果连接失败或无数据,则发出错误信号。stop
方法用于停止线程的运行。
MainWindow
类继承自 QWidget
,负责初始化和管理 GUI 界面。它的 initUI
方法设置了窗口标题,并添加了一个垂直布局,其中包括一个显示连接状态的标签、一个控制数据显示的复选框、一个只读的文本编辑框用于显示数据,以及一个导出数据的按钮。update_status
方法用于更新连接状态标签的文本,update_data
方法用于在复选框选中时将数据追加到文本编辑框中,toggle_data_display
方法用于显示或隐藏数据,export_data
方法将文本编辑框中的数据导出到 data_output.txt
文件中。closeEvent
方法在窗口关闭时停止数据获取线程。
程序的入口部分创建了 QApplication
实例并运行应用程序。实现了一个简单的数据获取和显示功能,并提供了导出数据的选项。通过这种方式,用户可以实时监控从指定 URL 获取的数据,并根据需要导出这些数据。
import sys
import requests
import time
from PyQt5.QtWidgets import (QApplication, QWidget, QVBoxLayout, QLabel, QPushButton, QTextEdit, QCheckBox)
from PyQt5.QtCore import QThread, pyqtSignal
sys
:处理系统相关的操作,比如退出应用程序。
requests
:用于发送HTTP请求。
time
:提供时间相关的功能,如睡眠。
PyQt5.QtWidgets
:包含各种PyQt5的GUI组件。
PyQt5.QtCore
:包含核心的PyQt5功能,如信号和线程。
class DataFetcher(QThread):
status_update = pyqtSignal(str)
data_update = pyqtSignal(str)
def __init__(self, url):
super().__init__()
self.url = url
self.running = True
def run(self):
while self.running:
try:
response = requests.get(self.url)
if response.status_code == 200:
self.status_update.emit("连接成功")
data = response.text.strip()
if data:
self.data_update.emit(f"检测结果为:{data}")
else:
self.data_update.emit("无结果")
else:
self.status_update.emit("无结果")
except requests.ConnectionError:
self.status_update.emit("连接失败")
time.sleep(2)
def stop(self):
self.running = False
DataFetcher
继承自QThread
,用于在后台线程中获取数据。
status_update
和data_update
是自定义信号,用于更新GUI。
__init__
:初始化线程,设置URL和运行状态。
run
:主线程循环,定期(每2秒)发送HTTP请求,并根据响应更新状态和数据。
stop
:停止线程。
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.initUI()
self.data_fetcher = DataFetcher("http://192.168.4.1/")
self.data_fetcher.status_update.connect(self.update_status)
self.data_fetcher.data_update.connect(self.update_data)
self.data_fetcher.start()
def initUI(self):
self.setWindowTitle("数据获取")
self.layout = QVBoxLayout()
self.status_label = QLabel("连接中……")
self.layout.addWidget(self.status_label)
self.data_check = QCheckBox("显示数据")
self.data_check.stateChanged.connect(self.toggle_data_display)
self.layout.addWidget(self.data_check)
self.data_display = QTextEdit()
self.data_display.setReadOnly(True)
self.data_display.setVisible(False)
self.layout.addWidget(self.data_display)
self.export_button = QPushButton("导出数据")
self.export_button.clicked.connect(self.export_data)
self.layout.addWidget(self.export_button)
self.setLayout(self.layout)
def update_status(self, status):
self.status_label.setText(status)
def update_data(self, data):
if self.data_check.isChecked():
self.data_display.append(data)
def toggle_data_display(self, state):
self.data_display.setVisible(state)
def export_data(self):
with open("data_output.txt", "w") as file:
file.write(self.data_display.toPlainText())
def closeEvent(self, event):
self.data_fetcher.stop()
self.data_fetcher.wait()
event.accept()
MainWindow
继承自QWidget
,表示主窗口。
__init__
:初始化窗口,设置UI和数据获取线程。
initUI
:创建并布局GUI组件,包括状态标签、显示数据复选框、数据显示区域和导出按钮。
update_status
:更新状态标签。
update_data
:在数据复选框被选中的情况下,更新数据显示区域。
toggle_data_display
:根据复选框状态显示或隐藏数据区域。
export_data
:将数据导出到文件。
closeEvent
:在关闭窗口时停止数据获取线程。
四、 STM32程序详解
//esp32
void USART2_Init(void) {
// 1. Enable the clock for USART2 and GPIOA
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 2. Configure PA2 as USART2 TX (Alternate Function Push Pull)
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// 3. Configure PA3 as USART2 RX (Input, Floating)
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// 4. Configure USART2 settings
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 115200;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_Init(USART2, &USART_InitStructure);
// 5. Enable USART2
USART_Cmd(USART2, ENABLE);
}
STM32微控制器上初始化USART2外设,用于串行通信。上面的配置实现了:启用USART2和GPIOA的时钟,配置PA2为USART2的TX引脚(推挽复用功能),配置PA3为USART2的RX引脚(浮空输入模式),设置USART2的通信参数(波特率115200、字长8、停止位0、校验方式无、通信模式和硬件流控制),最后使能USART2。
void Serial_SendByte2(uint8_t Byte)
{
USART_SendData(USART2, Byte);
while (USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
}
void Serial_SendArray2(uint8_t *Array, uint16_t Length)
{
uint16_t i;
for (i = 0; i < Length; i ++)
{
Serial_SendByte2(Array[i]);
}
}
void Serial_SendString2(char *String)
{
uint8_t i;
for (i = 0; String[i] != '\0'; i ++)
{
Serial_SendByte2(String[i]);
}
}
接下来的这些函数定义实现了发送字符串的函数,之后在主函数调用即可。
Serial_SendString2((char* )data);
这个data就是调用Zbar库识别后的结果。
五、 无烧录器烧录程序进ESP8266
注意在Arduino IDE.exe写入二中展示的代码后准备烧录进ESP8266。
建议购买烧录器直接烧录,十分方便。直接对着插上,点击烧录即可。
但是,我当时没有买烧录器,只有USB转串口,怎么办?
ESP8266 | STM32 |
---|---|
3.3V | 3.3V |
RST | 不接线 |
EN | 3.3V |
TX | RX |
RX | TX |
IO0 | GND |
IO2 | 不接线 |
GND | GND |
首先,我们现需要动的是两根线,3.3V和IO0,3.3V拔掉相当于ESP8266重启。第一步,按照上面的进行接线,然后3.3V的先别接,这个时候注意IO0接地连了,然后,按上面的箭头按钮准备烧录,当看到*Connect…*的时候,迅速把3.3V插上。
然后,等待烧录成功。
然后把3.3V和IO0都拔掉,再把3.3V插上即可正常工作了(正常工作保持IO0悬空)。
简单来说:IO0接地进入烧录模式,IO0悬空正常工作,每次进入模式需要关电重启。