串口通信从入门到实战:STM32与Python完整开发指南

发布时间:2026/8/2 13:52:58
串口通信从入门到实战:STM32与Python完整开发指南 在嵌入式开发和硬件通信中串口通信是最基础也是最常用的通信方式之一。无论是单片机与PC通信还是设备间的数据交换串口都扮演着重要角色。本文将从零开始带你快速掌握串口通信的核心原理、配置方法和实战应用让你在3分钟内搭建起可运行的串口通信系统。1. 串口通信基础概念1.1 什么是串口通信串口通信Serial Communication是指数据按位顺序依次传输的通信方式。与并行通信相比串口通信只需要少数几条线就可以实现系统间的数据交换具有布线简单、成本低、传输距离远等优点。串口通信通常包含两种类型同步串行通信和异步串行通信。在嵌入式领域最常用的是异步串行通信也就是我们常说的UARTUniversal Asynchronous Receiver/Transmitter。1.2 串口通信的核心参数要成功建立串口通信必须正确配置以下几个关键参数波特率Baud Rate数据传输速率表示每秒传输的符号数常见值有9600、115200等数据位Data Bits每个数据包包含的数据位数通常是7或8位停止位Stop Bits表示数据包结束的位数通常是1位或2位校验位Parity Bit用于错误检测可选无校验、奇校验或偶校验1.3 串口通信的应用场景串口通信广泛应用于各种场景单片机与PC机之间的数据传输工业控制设备的通信传感器数据采集嵌入式系统调试和日志输出物联网设备通信2. 硬件连接与接口标准2.1 RS-232标准RS-232是最经典的串口通信标准使用DB9或DB25接口。虽然现在很多设备已经不再使用传统的RS-232接口但其通信原理仍然是理解串口通信的基础。RS-232接口的主要引脚定义TX引脚3数据发送端RX引脚2数据接收端GND引脚5信号地线2.2 TTL电平串口在现代嵌入式系统中更常用的是TTL电平的串口通信。TTL电平使用0V表示逻辑03.3V或5V表示逻辑1可以直接与单片机GPIO引脚连接。2.3 USB转串口模块由于现代电脑大多不再配备原生串口USB转串口模块成为连接PC与嵌入式设备的重要桥梁。常见的芯片有CH340、CP2102、FT232等。3. 串口通信协议详解3.1 数据帧结构串口通信的数据以帧为单位进行传输每帧数据包含以下部分起始位 | 数据位7-8位 | 校验位可选 | 停止位1-2位起始位总是逻辑0表示一帧数据的开始数据位实际要传输的数据从低位到高位依次传输校验位用于简单的错误检测停止位总是逻辑1表示一帧数据的结束3.2 通信流程串口通信的基本流程如下发送端将并行数据转换为串行数据添加起始位、校验位和停止位组成数据帧按照设定的波特率逐位发送接收端检测起始位开始接收数据验证校验位如果启用检查数据完整性将串行数据转换为并行数据供处理器使用4. STM32串口通信实战4.1 环境准备开发板STM32F103C8T6蓝色药丸板开发环境STM32CubeIDE串口模块USB转TTL模块CH340芯片连接线杜邦线若干4.2 硬件连接将USB转TTL模块与STM32开发板正确连接TTL模块的TX引脚 → STM32的PA10引脚USART1_RXTTL模块的RX引脚 → STM32的PA9引脚USART1_TXTTL模块的GND引脚 → STM32的GND引脚4.3 STM32CubeMX配置使用STM32CubeMX进行外设配置打开STM32CubeMX选择对应的STM32型号在Pinout视图中使能USART1配置USART1参数Mode: AsynchronousBaud Rate: 115200Word Length: 8 BitsParity: NoneStop Bits: 1Oversampling: 16生成代码工程4.4 核心代码实现在生成的工程中添加串口通信代码// 文件路径Core/Src/main.c #include main.h #include string.h UART_HandleTypeDef huart1; // 重定向printf到串口 int _write(int file, char *ptr, int len) { HAL_UART_Transmit(huart1, (uint8_t*)ptr, len, HAL_MAX_DELAY); return len; } // 串口接收回调函数 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if(huart-Instance USART1) { // 处理接收到的数据 uint8_t received_data; HAL_UART_Receive_IT(huart1, received_data, 1); printf(Received: %c\n, received_data); } } int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init(); printf(STM32 UART Communication Demo Started!\r\n); uint8_t receive_buffer; HAL_UART_Receive_IT(huart1, receive_buffer, 1); while (1) { // 每隔1秒发送测试数据 printf(Hello from STM32!\r\n); HAL_Delay(1000); } }4.5 串口初始化配置// 文件路径Core/Src/usart.c static void MX_USART1_UART_Init(void) { huart1.Instance USART1; huart1.Init.BaudRate 115200; huart1.Init.WordLength UART_WORDLENGTH_8B; huart1.Init.StopBits UART_STOPBITS_1; huart1.Init.Parity UART_PARITY_NONE; huart1.Init.Mode UART_MODE_TX_RX; huart1.Init.HwFlowCtl UART_HWCONTROL_NONE; huart1.Init.OverSampling UART_OVERSAMPLING_16; if (HAL_UART_Init(huart1) ! HAL_OK) { Error_Handler(); } }5. Python串口通信上位机开发5.1 安装pyserial库pip install pyserial5.2 Python串口通信代码# 文件路径serial_monitor.py import serial import time import threading class SerialMonitor: def __init__(self, portCOM3, baudrate115200): self.ser serial.Serial( portport, baudratebaudrate, bytesizeserial.EIGHTBITS, parityserial.PARITY_NONE, stopbitsserial.STOPBITS_ONE, timeout1 ) self.running True def read_serial(self): 读取串口数据的线程函数 while self.running: if self.ser.in_waiting 0: data self.ser.readline().decode(utf-8).strip() if data: print(fReceived: {data}) time.sleep(0.01) def send_data(self, data): 发送数据到串口 if isinstance(data, str): data data.encode(utf-8) self.ser.write(data b\r\n) print(fSent: {data.decode(utf-8)}) def start_monitor(self): 启动串口监视器 print(Serial Monitor Started!) print(Press CtrlC to stop) # 启动读取线程 read_thread threading.Thread(targetself.read_serial) read_thread.daemon True read_thread.start() try: while True: # 可以在这里添加发送逻辑 user_input input(Enter message to send (or quit to exit): ) if user_input.lower() quit: break self.send_data(user_input) except KeyboardInterrupt: print(\nStopping monitor...) finally: self.running False self.ser.close() if __name__ __main__: # 根据实际情况修改端口号 monitor SerialMonitor(portCOM3, baudrate115200) monitor.start_monitor()5.3 自动检测可用串口import serial.tools.list_ports def list_serial_ports(): 列出所有可用的串口 ports serial.tools.list_ports.comports() available_ports [] for port, desc, hwid in sorted(ports): print(f{port}: {desc} [{hwid}]) available_ports.append(port) return available_ports # 使用示例 if __name__ __main__: ports list_serial_ports() if ports: print(f可用串口: {ports}) else: print(未找到可用串口)6. 常见问题与解决方案6.1 通信失败排查步骤当串口通信出现问题时可以按照以下步骤排查检查硬件连接确认TX、RX交叉连接设备A的TX接设备B的RX检查GND是否共地确认电源供电正常检查参数配置确认双方波特率一致检查数据位、停止位、校验位设置验证流控制设置软件层面检查确认串口未被其他程序占用检查驱动程序是否正常安装验证代码中的端口号是否正确6.2 常见错误代码及解决# 常见的串口错误处理 try: ser serial.Serial(COM3, 115200, timeout1) except serial.SerialException as e: print(f串口打开失败: {e}) # 可能的原因端口不存在、权限不足、端口被占用6.3 数据丢失或乱码问题数据丢失的常见原因及解决方案波特率不匹配重新校准波特率确保发送接收方一致缓冲区溢出增加读取频率或增大缓冲区电磁干扰使用屏蔽线缩短传输距离接地问题确保所有设备共地7. 串口通信高级应用7.1 自定义通信协议在实际项目中通常需要定义自己的通信协议来确保数据的可靠性# 简单的帧结构设计 class SerialProtocol: def __init__(self): self.START_BYTE 0xAA self.END_BYTE 0x55 def pack_data(self, command, data): 打包数据帧 frame bytearray() frame.append(self.START_BYTE) # 起始字节 frame.append(command) # 命令字 frame.append(len(data)) # 数据长度 frame.extend(data) # 数据内容 # 计算校验和 checksum sum(frame) 0xFF frame.append(checksum) # 校验和 frame.append(self.END_BYTE) # 结束字节 return bytes(frame) def unpack_data(self, frame): 解包数据帧 if (frame[0] ! self.START_BYTE or frame[-1] ! self.END_BYTE): return None # 帧格式错误 received_checksum frame[-2] calculated_checksum sum(frame[:-2]) 0xFF if received_checksum ! calculated_checksum: return None # 校验和错误 return { command: frame[1], length: frame[2], data: frame[3:3frame[2]] }7.2 多线程串口通信对于需要同时处理多个任务的应用程序使用多线程可以提高效率import threading import queue import time class ThreadedSerialCommunication: def __init__(self, port, baudrate115200): self.serial_port serial.Serial(port, baudrate, timeout1) self.receive_queue queue.Queue() self.send_queue queue.Queue() self.running True def receiver_thread(self): 接收线程 while self.running: if self.serial_port.in_waiting 0: try: data self.serial_port.readline() if data: self.receive_queue.put(data) except serial.SerialException: break time.sleep(0.01) def sender_thread(self): 发送线程 while self.running: try: if not self.send_queue.empty(): data self.send_queue.get() self.serial_port.write(data) except serial.SerialException: break time.sleep(0.01) def start_communication(self): 启动通信线程 receiver threading.Thread(targetself.receiver_thread) sender threading.Thread(targetself.sender_thread) receiver.daemon True sender.daemon True receiver.start() sender.start()7.3 数据可视化显示将串口接收的数据实时显示在图形界面上import matplotlib.pyplot as plt import matplotlib.animation as animation from collections import deque class SerialPlotter: def __init__(self, max_points100): self.max_points max_points self.data_buffer deque([0] * max_points, maxlenmax_points) self.fig, self.ax plt.subplots() self.line, self.ax.plot(range(max_points), list(self.data_buffer)) def update_plot(self, new_value): 更新绘图数据 self.data_buffer.append(new_value) self.line.set_ydata(self.data_buffer) return self.line, def start_animation(self): 启动实时动画 ani animation.FuncAnimation( self.fig, self.update_plot, interval50, blitTrue ) plt.show()8. 不同平台的串口通信实现8.1 C# 串口通信实现using System; using System.IO.Ports; using System.Threading; public class SerialPortExample { private SerialPort serialPort; public SerialPortExample(string portName, int baudRate) { serialPort new SerialPort(portName, baudRate); serialPort.DataReceived new SerialDataReceivedEventHandler(DataReceivedHandler); } public void OpenConnection() { try { serialPort.Open(); Console.WriteLine(串口连接成功); } catch (Exception ex) { Console.WriteLine($串口连接失败: {ex.Message}); } } private void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e) { SerialPort sp (SerialPort)sender; string data sp.ReadExisting(); Console.WriteLine($接收到数据: {data}); } public void SendData(string data) { if (serialPort.IsOpen) { serialPort.WriteLine(data); Console.WriteLine($发送数据: {data}); } } public void CloseConnection() { if (serialPort.IsOpen) { serialPort.Close(); } } } // 使用示例 class Program { static void Main() { SerialPortExample example new SerialPortExample(COM3, 115200); example.OpenConnection(); example.SendData(Hello from C#); Thread.Sleep(2000); example.CloseConnection(); } }8.2 LabVIEW与单片机串口通信在LabVIEW中实现串口通信的基本步骤前面板设计添加字符串显示控件接收数据显示添加字符串输入控件发送数据输入添加数值控件波特率设置添加布尔按钮连接/断开控制程序框图设计使用VISA配置串口函数使用VISA写入函数发送数据使用VISA读取函数接收数据使用While循环实现持续通信添加错误处理机制8.3 Kotlin Android串口通信在Android应用中实现串口通信class SerialHelper(private val context: Context) { private var serialPort: SerialPort? null private var inputStream: InputStream? null private var outputStream: OutputStream? null private var readThread: Thread? null private var isReading false fun openSerialPort(device: File, baudrate: Int): Boolean { return try { serialPort SerialPort(device, baudrate, 0) inputStream serialPort!!.inputStream outputStream serialPort!!.outputStream startReading() true } catch (e: Exception) { Log.e(SerialHelper, 打开串口失败, e) false } } private fun startReading() { isReading true readThread Thread { val buffer ByteArray(1024) while (isReading) { try { val size inputStream!!.read(buffer) if (size 0) { val data String(buffer, 0, size) // 处理接收到的数据 Log.d(SerialHelper, 收到数据: $data) } } catch (e: Exception) { Log.e(SerialHelper, 读取数据异常, e) } } } readThread!!.start() } fun sendData(data: String) { try { outputStream!!.write(data.toByteArray()) outputStream!!.flush() } catch (e: Exception) { Log.e(SerialHelper, 发送数据失败, e) } } fun closeSerialPort() { isReading false readThread?.interrupt() serialPort?.close() } }9. 性能优化与最佳实践9.1 波特率选择策略选择合适的波特率对通信质量至关重要低速应用传感器数据9600-19200 bps中速应用一般控制38400-115200 bps高速应用图像传输230400-921600 bps选择原则在满足数据传输需求的前提下尽量选择较低的波特率以提高通信稳定性。9.2 错误检测与纠正提高通信可靠性的方法校验和验证每帧数据添加校验和超时重传设置合理的超时时间数据确认机制接收方确认收到数据数据重发机制发送失败时自动重发9.3 资源管理最佳实践# 使用上下文管理器确保资源正确释放 class SafeSerial: def __init__(self, port, baudrate): self.port port self.baudrate baudrate self.ser None def __enter__(self): self.ser serial.Serial(self.port, self.baudrate, timeout1) return self.ser def __exit__(self, exc_type, exc_val, exc_tb): if self.ser and self.ser.is_open: self.ser.close() # 使用示例 with SafeSerial(COM3, 115200) as ser: ser.write(bHello World) response ser.readline() print(fResponse: {response})串口通信作为嵌入式系统中最基础的通信方式掌握其原理和实现方法对嵌入式开发者至关重要。通过本文的讲解和示例你应该能够快速搭建起可用的串口通信系统并根据实际需求进行功能扩展。在实际项目中记得充分考虑通信的可靠性和错误处理确保系统的稳定运行。