☰
基于pyserial与自定义帧协议的LCD1602串口上位机开发实战
2026/10/16 4:51:32 网站建设 项目流程

1. 为什么我要自己写一个串口上位机,而不是用现成工具

市面上串口调试助手一抓一大把,随便搜一下就能找到十几种,功能从基础的收发到协议解析、波形绘制应有尽有。那我为什么还要花时间自己写一个?原因其实很朴素:通用工具解决的是通用问题,而我要解决的是一个非常具体的、带交互逻辑的显示控制问题。

LCD1602这块屏,搞过单片机的人基本都接触过。它本身很简单,两行十六个字符,基于HD44780控制器,支持并行和四线模式。但当你把它接到单片机或者开发板上,想动态地往屏幕上推内容的时候,如果每次都要改代码、重新编译、烧录,这个循环就太长了。尤其是调试阶段,你可能只是想看看某个变量当前的值,或者临时改一下显示的文字,结果每次都要走一遍完整的编译烧录流程,效率极低。

所以我想要的是一套从PC端直接控制LCD1602显示内容的方案。PC端跑一个自己写的上位机,通过串口把要显示的内容发给下位机,下位机收到之后直接刷到LCD上。这样我就可以在PC上实时地改显示内容,不用碰下位机的代码。

那为什么不用现成的串口助手?因为现成的工具只能发原始数据,它不知道我要发的是一条“显示指令”还是别的什么。我需要的是一个带协议封装的上位机——我输入要显示的文字,它自动打包成下位机能识别的帧格式发出去。这个逻辑用通用串口助手也能做,但你每次都要手动拼帧、算校验,太麻烦了。自己写一个上位机,把这些逻辑固化进去,用起来才顺手。

另外还有一个原因:我想把整个链路搞清楚。从PC端的串口编程,到数据帧的设计,到下位机的解析和LCD驱动,这一整条链路走通一遍,对串口通信和LCD控制的理解会深很多。这比单纯调用一个库函数要有价值得多。

这篇文章适合哪些人看?如果你正在学单片机、想了解串口通信的完整链路、或者手头正好有LCD1602想玩点不一样的东西,那这篇内容应该能给你一些参考。如果你已经是串口通信的老手,也可以看看我在帧协议设计和异常处理上的一些取舍,或许有可以借鉴的地方。

2. 串口通信链路的整体设计思路

2.1 从上位机到下位机的数据流向

整条链路其实不复杂,但每个环节都有需要想清楚的地方。先看数据是怎么流的:

PC端上位机(Python + pyserial)→ USB转串口模块 → 下位机串口接收 → 帧解析 → LCD1602显示

上位机这边,我用Python来写,核心库就是pyserial。选Python的原因很简单:开发快、调试方便、跨平台。pyserial这个库封装了底层的串口操作,打开串口、配置波特率、读写数据都是几行代码的事。而且Python的字符串处理能力很强,做协议封装和解析很顺手。

下位机这边,我用的是常见的8位单片机,串口接收用中断方式,收到完整帧之后解析,然后驱动LCD1602。LCD1602我用的是4位数据模式,这样只需要6根IO口(RS、RW、E、D4-D7)就能驱动,省下来的IO口可以干别的。

这里有一个关键的设计决策:上位机和下位机之间传什么?有两种方案:

  • 方案A:上位机直接发ASCII字符,下位机收到什么就显示什么
  • 方案B:上位机发结构化的数据帧,包含命令字、地址、数据、校验

方案A最简单,但灵活性差。比如我想控制光标位置、清屏、开关显示,这些操作没法用纯ASCII表达。方案B更复杂,但扩展性强,后面想加什么功能都方便。

我选的是方案B的简化版:自定义一个轻量级的帧协议,既能传显示内容,也能传控制命令。

2.2 为什么选pyserial而不是其他方案

Python下面操作串口,其实不止pyserial一个选择。但pyserial是事实上的标准库,社区活跃、文档齐全、跨平台支持好。我在Windows和Linux上都跑过,行为一致,没有遇到什么兼容性问题。

安装很简单:

pip install pyserial

就这一条命令。装完之后,import serial就能用了。

有一个细节需要注意:pyserial的版本。如果你用的是比较老的版本(比如2.x),API和3.x有一些差异。现在默认装的都是3.x,用serial.Serial()创建对象,参数名是port、baudrate这些。老版本有些参数名不一样,网上搜到的代码如果跑不起来,先检查一下版本。

另外,在Windows上,串口号是COM1、COM3这种格式;在Linux上通常是/dev/ttyUSB0或者/dev/ttyACM0。写代码的时候最好把串口号做成可配置的,不要写死。

2.3 下位机串口接收的中断处理逻辑

下位机这边,串口接收我用的是中断方式。为什么不轮询?因为轮询会占用主循环的时间,而且如果波特率稍微高一点,轮询很容易丢数据。中断方式下,每收到一个字节就进一次中断,把数据存到缓冲区里,主循环再慢慢处理。

中断服务函数里做的事情要尽量少:只负责把接收到的字节存入环形缓冲区,然后更新缓冲区指针。帧的解析放到主循环里做,这样不会因为解析逻辑耗时太长而影响后续数据的接收。

环形缓冲区的大小要算一下。假设波特率是9600,一帧数据最长20个字节,那么缓冲区至少要有两帧的容量,也就是40个字节以上。我一般会开到64字节或者128字节,留足余量。如果波特率更高,比如115200,那缓冲区还要再大一些,因为同样时间内收到的数据量更大。

还有一个容易忽略的点:帧与帧之间的间隔。如果上位机连续快速发送多帧,下位机的中断可能会在很短时间内收到大量数据。如果主循环处理不过来,缓冲区就会溢出。解决办法有两个:一是加大缓冲区,二是上位机发送时加适当的延时。我一般两个都用,缓冲区开到128字节,上位机每帧之间加10ms延时,实测下来很稳。

3. 自定义帧协议的设计与取舍

3.1 帧格式的字段定义

帧协议的设计是整个项目的核心。设计得好,后面扩展方便;设计得不好,用起来处处别扭。我最终定的帧格式是这样的:

字段长度说明
帧头2字节固定为0xAA 0x55,用于帧同步
命令字1字节0x01=显示字符串,0x02=清屏,0x03=设置光标,0x04=开关显示
数据长度1字节后续数据段的字节数
数据段N字节具体的数据内容
校验和1字节从命令字到数据段所有字节的累加和取低8位

帧头用两个字节而不是一个字节,是为了降低误同步的概率。如果只用一个0xAA做帧头,数据段里出现0xAA就会误判。用两个字节0xAA 0x55,数据段里连续出现这两个字节的概率就低很多了。

命令字目前定义了四个:显示字符串、清屏、设置光标位置、开关显示。后面如果需要还可以扩展,比如控制背光、自定义字符等。

数据长度字段告诉下位机后面有多少个字节的数据。这样下位机就知道要收多少个字节才算一帧完整的数据。

校验和用的是最简单的累加和。为什么不用CRC?因为在这个场景下,传输距离短、数据量小、环境干扰有限,累加和足够用了。CRC计算量大,在下位机上跑有点浪费。当然,如果你对可靠性要求特别高,换成CRC16也不是不行,但代码会复杂一些。

3.2 帧头选择的考量:为什么是0xAA 0x55

0xAA和0x55这两个值在二进制下是交替的:0xAA是10101010,0x55是01010101。这种交替模式在串口通信里有个好处:方便接收方做位同步。虽然串口本身是异步通信,每个字节有起始位和停止位来做同步,但在数据流中频繁出现交替模式,对接收方的时钟恢复也有帮助。

另外,0xAA和0x55在ASCII里不是可打印字符,正常显示内容里不会出现这两个字节的连续组合。所以用它们做帧头,误判的概率很低。

还有一个细节:帧头之前可以有任意垃圾数据。下位机的解析逻辑是:先在缓冲区里找0xAA,找到之后再检查下一个字节是不是0x55。如果是,就从这里开始解析一帧;如果不是,就继续往后找。这样即使前面有一些杂散数据,也不会影响帧的解析。

3.3 校验和的计算方式与容错策略

校验和的计算很简单,就是把命令字、数据长度、数据段的所有字节加起来,取低8位。上位机发送前算好,下位机收到后重新算一遍,对比是否一致。

def calc_checksum(data): return sum(data) & 0xFF

下位机那边,如果校验和不匹配,直接丢弃这一帧,不做任何处理。同时可以做一个错误计数,如果连续多帧校验失败,说明通信质量有问题,可以考虑让下位机通过某种方式反馈(比如点亮一个LED)。不过在这个项目里,我没有做反馈机制,因为LCD本身就能显示状态,如果显示不正常,一眼就能看出来。

有一个容易踩的坑:数据段里如果包含0xAA 0x55怎么办?比如我要显示的文字里恰好有这两个字节。在ASCII文本里,0xAA和0x55不是可打印字符,所以正常显示内容不会包含它们。但如果你要传二进制数据,就需要做转义处理。我的做法是:在数据段中,如果遇到0xAA,就转义成0xAA 0x00;如果遇到0x55,就转义成0x55 0x00。接收方解析时,遇到0xAA后面跟0x00就还原成0xAA,遇到0x55后面跟0x00就还原成0x55。这样虽然增加了数据量,但保证了帧的完整性。

不过在这个项目里,我传的都是ASCII文本,所以转义逻辑实际上没有用到。但设计的时候要考虑进去,后面万一要传二进制数据就不用改协议了。

4. 上位机pyserial代码的完整实现

4.1 串口初始化与参数配置

上位机的核心就是pyserial的使用。先看串口初始化的代码:

import serial import serial.tools.list_ports import time class LCDController: def __init__(self, port=None, baudrate=9600, timeout=1): if port is None: port = self.auto_detect_port() self.ser = serial.Serial( port=port, baudrate=baudrate, bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, timeout=timeout ) time.sleep(2) # 等待串口稳定 def auto_detect_port(self): ports = serial.tools.list_ports.comports() for p in ports: if 'USB' in p.description or 'CH340' in p.description: return p.device if ports: return ports[0].device raise Exception('未找到可用串口')

几个关键参数说明一下:

  • baudrate:波特率,我用的9600。这个要和下位机一致,不一致的话收到的就是乱码。9600在短距离传输下很稳定,没必要追求高速。
  • bytesize:数据位,8位。这是最常用的配置。
  • parity:校验位,无校验。因为我在帧协议里已经做了校验,串口本身的校验位就不需要了。
  • stopbits:停止位,1位。标准配置。
  • timeout:读超时,1秒。这个参数影响read()的行为,设成1秒意味着如果1秒内没有收到数据,read()就会返回。

time.sleep(2)这一行很重要。串口打开之后,下位机那边可能还在复位或者初始化,如果立刻发数据,可能会丢失。等2秒让下位机稳定下来,实测下来这个延时是必要的。

自动检测串口的逻辑用到了serial.tools.list_ports,这个模块可以列出当前系统上所有可用的串口。我根据描述里是否包含“USB”或“CH340”来判断哪个是目标串口。CH340是最常见的USB转串口芯片之一,很多开发板都用它。

4.2 帧封装函数的编写

帧封装就是把要发送的数据打包成前面定义的格式:

def build_frame(self, cmd, data=b''): frame = bytearray() frame.append(0xAA) frame.append(0x55) frame.append(cmd) frame.append(len(data)) frame.extend(data) checksum = sum(frame[2:]) & 0xFF frame.append(checksum) return bytes(frame)

这个函数接收命令字和数据,返回完整的帧字节串。注意校验和是从命令字开始算的,不包括帧头。因为帧头是固定的,不需要校验。

发送显示字符串的调用方式:

def display_string(self, text, row=0, col=0): # 先设置光标位置 self.send_command(0x03, bytes([row, col])) # 再发送显示内容 data = text.encode('ascii') self.send_command(0x01, data)

这里分两步:先设置光标,再显示内容。也可以合并成一帧,但分开更灵活,后面如果只想改光标位置不显示内容,也可以单独调用。

4.3 发送逻辑与异常处理

发送数据的核心方法:

def send_command(self, cmd, data=b''): frame = self.build_frame(cmd, data) try: self.ser.write(frame) self.ser.flush() except serial.SerialException as e: print(f'发送失败: {e}') self.reconnect()

flush()的作用是确保数据全部写出去了再返回。不加flush()的话,数据可能还在缓冲区里,程序就继续往下走了。虽然大多数情况下不加也没问题,但加上更保险。

异常处理主要是捕获SerialException,比如串口被拔掉了、被其他程序占用了,都会抛这个异常。捕获到之后,我尝试重新连接。重连的逻辑就是关掉当前的串口对象,重新打开:

def reconnect(self): try: self.ser.close() except: pass time.sleep(1) self.ser = serial.Serial( port=self.ser.port, baudrate=self.ser.baudrate, timeout=1 ) time.sleep(2)

重连之后同样要等2秒,让下位机稳定。

还有一个实际使用中遇到的问题:如果上位机发送速度太快,下位机处理不过来。我在send_command里加了一个小延时:

time.sleep(0.01) # 10ms延时

10ms听起来不多,但足以让下位机的中断服务函数把数据收完,主循环开始处理。如果不加这个延时,连续发送多帧的时候,下位机的缓冲区很容易溢出。

5. 下位机LCD1602驱动与帧解析的配合

5.1 LCD1602的4位模式初始化时序

LCD1602的4位模式初始化是有固定时序的,这个时序不能乱。HD44780的数据手册里写得很清楚,但第一次搞的人很容易在这里翻车。

初始化流程大概是这样的:

  1. 上电后等待15ms以上
  2. 发送0x03(8位模式),等待4.1ms
  3. 再发送0x03,等待100us
  4. 再发送0x03,等待100us
  5. 发送0x02,切换到4位模式
  6. 发送0x28,设置4位模式、2行显示、5x8点阵
  7. 发送0x0C,开显示、关光标、不闪烁
  8. 发送0x06,设置输入模式:光标右移、屏幕不移动
  9. 发送0x01,清屏,等待1.52ms以上

这里面有几个时间参数是硬性要求,不能缩短。特别是最后清屏的1.52ms,如果不等够,清屏命令可能没执行完就发下一条命令,屏幕就会显示异常。

我在代码里把这些延时都写死了:

void lcd_init(void) { delay_ms(15); lcd_write_cmd(0x03); delay_ms(5); lcd_write_cmd(0x03); delay_us(150); lcd_write_cmd(0x03); delay_us(150); lcd_write_cmd(0x02); delay_us(150); lcd_write_cmd(0x28); delay_us(150); lcd_write_cmd(0x0C); delay_us(150); lcd_write_cmd(0x06); delay_us(150); lcd_write_cmd(0x01); delay_ms(2); }

注意这里的lcd_write_cmd在4位模式下要分两次写:先写高4位,再写低4位。每次写之前要确保LCD不忙(检查忙标志位),或者用足够的延时来等待。

我用的方法是延时等待而不是查忙标志位。查忙标志位需要把数据口设为输入模式,读完之后再设回输出模式,比较麻烦。用延时的话,只要延时足够长,就不会有问题。对于1602来说,大多数命令的执行时间在40us左右,清屏和归位命令需要1.52ms。我统一用150us的延时,清屏用2ms,实测下来很稳。

5.2 串口中断接收与主循环解析的分工

下位机的串口接收和帧解析是分开的。中断服务函数只做一件事:把收到的字节存入环形缓冲区。

volatile uint8_t rx_buf[128]; volatile uint8_t rx_head = 0; volatile uint8_t rx_tail = 0; void uart_isr(void) { uint8_t data = UART_DATA_REG; uint8_t next = (rx_head + 1) % sizeof(rx_buf); if (next != rx_tail) { rx_buf[rx_head] = data; rx_head = next; } // 如果缓冲区满了,丢弃这个字节 }

主循环里做帧解析:

void process_frame(void) { while (rx_tail != rx_head) { uint8_t byte = rx_buf[rx_tail]; rx_tail = (rx_tail + 1) % sizeof(rx_buf); // 状态机解析 parse_byte(byte); } }

解析用一个状态机来实现,状态包括:等待帧头1、等待帧头2、等待命令字、等待长度、接收数据、等待校验和。每个状态收到不符合预期的字节就回到初始状态重新找帧头。

这种状态机的好处是不会因为某一帧出错而卡死。即使中间丢了几个字节,状态机也能自动恢复到找帧头的状态,继续解析后面的帧。

5.3 显示命令的解析与执行

解析完一帧之后,根据命令字执行对应的操作:

void execute_command(uint8_t cmd, uint8_t *data, uint8_t len) { switch (cmd) { case 0x01: // 显示字符串 for (uint8_t i = 0; i < len; i++) { lcd_write_data(data[i]); } break; case 0x02: // 清屏 lcd_write_cmd(0x01); delay_ms(2); break; case 0x03: // 设置光标 if (len >= 2) { uint8_t row = data[0]; uint8_t col = data[1]; uint8_t addr = (row == 0) ? (0x80 + col) : (0xC0 + col); lcd_write_cmd(addr); } break; case 0x04: // 开关显示 if (len >= 1) { lcd_write_cmd(data[0] ? 0x0C : 0x08); } break; } }

设置光标位置的时候,LCD1602的地址计算要注意:第一行起始地址是0x80,第二行起始地址是0xC0。所以第一行第col列的地址是0x80+col,第二行是0xC0+col。col的范围是0到15。

显示字符串的时候,如果字符串长度超过一行剩余的空间,LCD会自动换行吗?不会。LCD1602的显示RAM是连续的,第一行0x80-0x8F,第二行0xC0-0xCF。如果你在第一行第15列写了一个字符,再写下一个字符,它会写到第二行第0列(因为0x8F的下一个地址是0xC0)。但这个行为不是“自动换行”,而是地址递增的结果。如果你不想让它跨行,就要自己控制长度。

6. 联调过程中踩过的坑与排查方法

6.1 串口打开失败与权限问题

在Linux上跑上位机的时候,第一次打开串口经常会遇到权限问题:

Permission denied: '/dev/ttyUSB0'

这是因为普通用户没有权限访问串口设备。解决办法有两个:一是用sudo运行,但不推荐,因为这样会把整个程序提权;二是把当前用户加到dialout组里:

sudo usermod -a -G dialout $USER

然后重新登录(或者重启),权限就生效了。这个坑我踩过好几次,每次换新机器都要重新设置一遍。

在Windows上,串口被其他程序占用是常见问题。比如你开着一个串口助手,又跑上位机,就会报“拒绝访问”。解决办法就是关掉其他占用串口的程序。有时候串口助手关了但进程还在后台,需要在任务管理器里结束进程。

6.2 乱码问题的三种可能原因

串口通信出现乱码,原因通常有三种:

第一种:波特率不匹配。这是最常见的。上位机设的9600,下位机也必须是9600。如果下位机用的是内部RC振荡器,时钟精度不够,波特率可能会有偏差。偏差太大的话,收到的数据就是乱码。解决办法是改用外部晶振,或者把波特率调低一些(比如4800),降低对时钟精度的要求。

第二种:数据位、停止位、校验位配置不一致。上位机设的是8N1,下位机也必须是8N1。这个一般不会错,但如果你用的是某个现成的例程,它可能默认是7位数据位或者有校验位,那就对不上了。

第三种:地线没接。串口通信需要共地。如果上位机和下位机的地线没有连在一起,信号电平就没有参考点,收到的数据也是乱的。这个问题在只用USB供电的时候不容易发现,因为USB线里已经包含了地线。但如果你用的是独立的电源给下位机供电,就一定要把地线连起来。

排查的时候,我一般先用一个简单的回环测试:把上位机的TX和RX短接,发什么就应该收到什么。如果回环测试正常,说明上位机和串口线没问题,问题在下位机那边。如果回环测试都不正常,那就是上位机或者串口线的问题。

6.3 LCD显示异常:花屏、缺划、偏移

LCD1602显示异常有几种典型表现:

花屏:屏幕上出现一堆乱码字符。原因通常是初始化时序不对,或者数据线接触不良。先检查初始化代码里的延时够不够,再检查接线有没有松动。

缺划:某个字符的某一段不显示。这通常是数据线的问题,比如D4-D7中某根线没接好。因为4位模式下,一个字节分两次传,如果某根数据线有问题,高4位或低4位就会出错。

偏移:显示的内容位置不对,比如应该在第一行显示的跑到了第二行。这通常是光标地址设置错了。检查地址计算:第一行是0x80+col,第二行是0xC0+col。col从0开始,不要搞错。

还有一个我遇到过的问题:显示内容闪烁。原因是清屏太频繁。每次显示新内容之前都清屏,屏幕就会闪。解决办法是不要每次都清屏,而是用空格覆盖原来的内容,或者只更新变化的部分。

6.4 数据丢帧的定位过程

联调的时候遇到过一个烦人的问题:连续发送多帧,下位机偶尔会丢帧。表现是LCD上显示的内容偶尔会缺几个字符,或者光标位置不对。

排查过程是这样的:

第一步,确认上位机有没有发出去。我在send_command里加了打印,每次发送都输出帧的十六进制内容。确认上位机确实发了。

第二步,确认下位机有没有收到。我在下位机的中断服务函数里加了一个计数器,每收到一个字节就加一。通过某种方式(比如用另一个串口输出,或者用LED闪烁)把计数器读出来。发现收到的字节数比发送的少,说明确实丢了。

第三步,定位丢在哪里。我加大了环形缓冲区,从64字节加到128字节,丢帧概率降低了但没有完全消失。后来发现是主循环处理太慢,中断频繁触发的时候,缓冲区还是会被填满。

最终的解决办法是上位机加延时。每帧之间加10ms,给下位机足够的时间处理。加上延时之后,丢帧问题彻底消失了。这个延时看起来简单,但确实有效。因为下位机的主循环里除了处理串口数据,还要刷LCD,刷LCD本身就需要时间(每个字符大概几十微秒),如果数据来得太快,主循环根本来不及处理。

7. 实测效果与可扩展的方向

7.1 实际运行效果与性能数据

整套系统跑起来之后,实测效果是这样的:

  • 波特率9600下,发送一条16字符的显示命令,从PC发出到LCD显示出来,延迟大约在20-30ms左右。这个延迟人眼基本感觉不到,实时性够用。
  • 连续发送100帧,每帧间隔10ms,下位机接收并显示,没有出现丢帧或显示错误。
  • 长时间运行(连续几个小时),系统稳定,没有出现死机或显示异常。

性能瓶颈主要在下位机的LCD刷新速度。LCD1602的写操作需要一定的时间,尤其是清屏命令,需要1.52ms。如果频繁清屏,整体刷新率就会下降。所以我在设计上尽量避免清屏,只更新变化的部分。

7.2 可以继续扩展的功能

这套基础框架搭好之后,后面想加功能就很方便了。几个我考虑过的扩展方向:

自定义字符:LCD1602支持8个自定义字符,每个字符是5x8的点阵。可以通过扩展命令字,让上位机把自定义字符的点阵数据发下去,下位机写入CGRAM。这样就能显示一些特殊符号,比如箭头、图标等。

背光控制:如果LCD模块有背光控制引脚,可以加一个命令字来控制背光开关。不过这个需要硬件支持,普通的1602模块背光一般是直接接电源的。

多行滚动:通过定时发送设置光标命令,让显示内容滚动起来。这个可以在上位机做,定时改变光标位置,下位机不需要改。

协议升级:如果以后要传二进制数据,可以把校验和换成CRC16,增加转义机制。不过对于文本显示来说,现在的协议已经够用了。

上位机界面:现在上位机是命令行调用的,可以加一个简单的GUI,用tkinter或者PyQt,做成一个带输入框和发送按钮的小工具。这样用起来更直观,不用每次都改代码。

7.3 一些个人经验总结

这个项目做下来,有几个体会比较深:

协议设计要留余量。我一开始只定义了显示字符串和清屏两个命令,后来想设置光标位置,发现没有对应的命令字,只能临时加。如果一开始就把命令字设计得宽裕一些,后面扩展就不用改协议版本了。

延时不是坏事。在PC端编程的时候,我们习惯追求速度,能不加延时就不加。但在嵌入式联调的时候,适当的延时反而是保证稳定性的关键。上位机发太快,下位机处理不过来,加个10ms延时,问题就解决了。

状态机是解析不定长帧的最好方式。用状态机解析帧,代码清晰,容错性好。即使中间丢了字节,状态机也能自动恢复。比用固定偏移去取字节要可靠得多。

调试信息要能输出。下位机没有屏幕的时候,调试信息只能靠LED或者另一个串口输出。我在调试阶段用了一个IO口接LED,每收到一帧就翻转一次,通过LED的闪烁频率就能判断有没有收到数据。这个方法虽然原始,但很有效。

先跑通再优化。一开始不要追求完美的代码结构,先把链路跑通,看到LCD上显示出正确的字符,再回头优化代码。如果一开始就想着把代码写得多么优雅,可能半天都看不到效果,容易失去信心。

这套方案的核心思路其实可以迁移到很多类似的场景:只要是通过串口控制某种外设,都可以用“自定义帧协议+上位机封装+下位机状态机解析”这个模式。把LCD1602换成其他模块,比如OLED、步进电机、继电器板,整体框架不用大改,只需要改命令字和执行逻辑就行了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询