无线手持二维码识别项目是中山大学电子与信息工程学院(微电子学院)的工程应用训练课程的设计要求。项目是基于STM32F103开发的,开源代码:stm32f103zet6-qrcode-detect,bilibili:基于STM32F1的无线手持二维码识别项目。CSDN总目录:基于STM32F103的二维码识别项目。项目移植了Zbar库进行二维码识别,而非使用二维码识别模块。
此篇文章讲述有线串口通讯相关的设计。
项目预留了有线的方式实现串口通讯,这样通过USB转TTL,实现了STM32将二维码识别信息传输到电脑。
一、 串口通讯简介
异步通信:
- 在异步通信中,数据传输不需要共享时钟信号,数据以独立的字节或字符进行传输。每个字符都有自己的起始和停止位来标识数据的开始和结束。
- 这种方式适用于不连续、不规则的数据流,常见于串口通信如RS-232。
起始位:
- 起始位用于标识一个数据帧的开始。当线路空闲时,通常保持在高电平(逻辑1),起始位是低电平(逻辑0),用于告诉接收端接下来是一个数据帧。
数据位:
- 数据位是实际传输的数据,每个字符通常由5到9位组成(最常见的是8位)。数据位的长度可以在通信双方之间协商。
奇偶校验位:
- 奇偶校验位用于错误检测,可以选择不使用(无校验)、偶校验或奇校验。校验位是通过计算数据位中的1的数量来决定的,以确保数据的正确性。
停止位:
- 停止位用于标识一个数据帧的结束。停止位可以是1位、1.5位或2位(常见的是1位)。高电平(逻辑1)表示数据传输的结束,允许接收端有时间准备好接收下一个字符。
波特率:
- 波特率是指每秒钟传输的比特数,是通信速率的衡量标准。常见的波特率有9600、19200、38400、57600、115200等。通信双方必须使用相同的波特率才能正常通信。
单工、半双工和全双工:
- 单工:数据只能单方向传输。例如,只能从A传输到B,不能反向。
- 半双工:数据可以双向传输,但不能同时进行。例如,对讲机通信。
- 全双工:数据可以同时双向传输。例如,电话通信。
二、 代码详情
//串口
void USART_Config(){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1,ENABLE); //PA2 TX PA3 RX
USART_InitTypeDef UI;//声明 串口初始化结构体
UI.USART_BaudRate = 115200;//波特率
UI.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
UI.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
UI.USART_Parity = USART_Parity_No;
UI.USART_StopBits = USART_StopBits_1;
UI.USART_WordLength = USART_WordLength_8b;
USART_Init(USART1,&UI);
GPIO_InitTypeDef GPIO_InitStructure;//声明 GPIO初始化结构体
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;//TX //串口输出PA9
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10;//RX //串口输入PA10
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING; //模拟输入
GPIO_Init(GPIOA,&GPIO_InitStructure); /* 初始化GPIO */
//
// USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);//打开中断
USART_Cmd(USART1,ENABLE);//使能串口2
}
1. 使能外设时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);
RCC_APB2PeriphClockCmd
是用来使能时钟的函数。RCC_APB2Periph_GPIOA
使能GPIOA端口时钟。RCC_APB2Periph_USART1
使能USART1外设时钟。ENABLE
表示使能(打开)时钟。
2. 配置USART参数
USART_InitTypeDef UI;
UI.USART_BaudRate = 115200; // 波特率
UI.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
UI.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
UI.USART_Parity = USART_Parity_No;
UI.USART_StopBits = USART_StopBits_1;
UI.USART_WordLength = USART_WordLength_8b;
USART_Init(USART1, &UI);
USART_InitTypeDef
是一个结构体,用于存储USART初始化参数。UI.USART_BaudRate
设置波特率为115200。UI.USART_HardwareFlowControl
设置硬件流控制为无(None)。UI.USART_Mode
设置USART模式为接收(Rx)和发送(Tx)。UI.USART_Parity
设置无奇偶校验。UI.USART_StopBits
设置停止位为1位。UI.USART_WordLength
设置数据位长度为8位。USART_Init(USART1, &UI)
用这些参数初始化USART1。
3. 配置GPIO引脚
GPIO_InitTypeDef GPIO_InitStructure;
// 配置TX引脚(PA9)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 配置RX引脚(PA10)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 模拟输入
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitTypeDef
是一个结构体,用于存储GPIO初始化参数。- 配置TX引脚(PA9):
GPIO_Pin_9
指定引脚9。GPIO_Speed_50MHz
设置引脚速率为50MHz。GPIO_Mode_AF_PP
设置引脚模式为复用推挽输出(适用于USART的TX引脚)。GPIO_Init(GPIOA, &GPIO_InitStructure)
用这些参数初始化GPIOA的第9引脚。
- 配置RX引脚(PA10):
GPIO_Pin_10
指定引脚10。GPIO_Mode_IN_FLOATING
设置引脚模式为浮空输入(适用于USART的RX引脚)。GPIO_Init(GPIOA, &GPIO_InitStructure)
用这些参数初始化GPIOA的第10引脚。
4. 使能USART
USART_Cmd(USART1, ENABLE);
USART_Cmd
是用来使能或失能USART的函数。ENABLE
表示使能USART1。
三、 上位机程序
代码实现一个基于PyQt5的串口通信图形用户界面程序。程序主要功能包括选择串口端口和波特率、打开和关闭串口、发送和接收数据,以及选择数据解码模式(HEX、UTF-8、GBK)。程序初始化时,界面上有串口端口和波特率选择框、打开/关闭端口按钮、发送数据输入框及按钮、接收数据显示框和自动滚动复选框。用户可以通过选择端口和波特率,点击按钮打开或关闭串口。发送数据时,用户可以选择发送的数据编码方式,并通过输入框输入数据点击发送按钮进行发送。接收到的数据会根据选择的解码方式显示在接收数据显示框中,并且可以选择是否自动滚动显示。程序通过QTimer定时器每100毫秒检查一次是否有新数据接收,并在界面上实时更新显示。关闭窗口时,会自动关闭串口连接,确保资源释放。核心代码如下:
1. 打开/关闭端口
refresh_ports
方法获取当前系统中的所有可用串口端口,并更新到端口选择下拉列表中。
def refresh_ports(self):
ports = serial.tools.list_ports.comports()
self.port_combo.clear()
for port in ports:
self.port_combo.addItem(port.device)
2. 发送数据
toggle_port
方法用于打开或关闭串口。如果串口已打开,则关闭串口并停止定时器。如果串口未打开,则根据选择的端口和波特率配置串口,并尝试打开。如果成功,启动定时器以每100毫秒检查一次是否有新数据接收。如果失败,显示错误信息。
def toggle_port(self):
if self.serial.is_open:
self.serial.close()
self.open_button.setText('打开端口')
self.timer.stop() # 端口关闭时停止定时器
else:
self.serial.port = self.port_combo.currentText()
self.serial.baudrate = int(self.baudrate_combo.currentText())
try:
self.serial.open()
self.open_button.setText('关闭端口')
self.timer.start(100) # 以100毫秒间隔启动定时器检查是否有传入数据
except Exception as e:
self.receive_text.append(f"无法打开端口: {e}")
3. 接收数据
receive_data
方法用于从串口接收数据。根据选择的解码模式,将接收到的数据转换为相应的格式,并显示在接收区。如果启用了自动滚动选项,则自动滚动到接收区的末尾。
def receive_data(self):
if self.serial.is_open:
while self.serial.in_waiting:
data = self.serial.read_all()
if self.decode_combo.currentText() == 'HEX':
data = data.hex()
else:
encoding = self.decode_combo.currentText().lower()
data = data.decode(encoding, errors='ignore')
self.receive_text.append(data)
if self.auto_scroll.isChecked():
self.receive_text.moveCursor(QTextCursor.End)
完整代码在GitHub项目的pyqt5文件夹下。
四、 打包为可执行文件
接下来我们使用pyinstaller将程序打包为exe文件。
pip install pyinstaller
我的程序名字为main.py,之后在main.py的文件目录下用下面命令即可。
pyinstaller --onefile --windowed main.py