news 2026/10/15 16:46:46

51单片机UART串口通信详解:从原理、寄存器配置到调试实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
51单片机UART串口通信详解:从原理、寄存器配置到调试实战

自学51单片机这一路,前面做了流水灯、数码管、按键、中断,东西越来越有意思。到了第11篇,终于开始碰通信了,也就是UART串口通信。说句实话,串口是自学单片机过程中一个分水岭:搞懂它之前,单片机就是一个“自己玩自己的”芯片;搞懂它之后,单片机才能跟电脑、跟其他设备“对话”。这篇内容不光是教你怎么配置寄存器、收发数据,更重要的是把串口通信背后的原理、那些资料里不太会写的坑和实际调试经验一起讲清楚,让正在自学单片机的朋友少走弯路。

无论是做产品调试、传感器数据采集,还是和WiFi模块、蓝牙模块对接,串口都是最常用、最基础的通信方式。这篇适合刚学完中断和定时器、准备接触通信的初学者,也适合已经能跑点灯程序、但一直没搞明白串口收发原理的朋友。看完之后,你能自己写出发送字符串、接收数据的完整程序,并且知道出问题的时候从哪里下手排查。

1. 串口通信先搞懂这几件事:电平、波特率、帧格式

很多自学51的朋友一上来就急着写代码、烧程序,结果接上USB转TTL模块,电脑上啥都收不到,然后就开始怀疑程序有问题。其实大多数时候问题出在没搞懂串口通信的几个基础概念上。这一节不聊虚的,直接把串口通信最核心的三个概念讲透。

1.1 UART为什么是“异步”的,和同步通信差在哪

UART的全称是Universal Asynchronous Receiver/Transmitter,翻译过来就是通用异步收发传输器。注意“异步”这两个字,它是理解串口通信的关键。

异步通信的意思是说,发送方和接收方之间没有一根独立的时钟线来同步节奏。两个设备之间通信,发送方不知道接收方什么时候准备好,接收方也不知道数据什么时候到,全靠通信双方提前约定好一个“说话速度”,也就是波特率。就好比两个人打电话,信号好不好另说,但双方必须说同一种语言、语速保持一致,否则对面听到的全是乱码。

与之相对的是同步通信,比如I2C和SPI,它们都有专门的时钟线(SCL和SCK),时钟线给一个脉冲,数据线上采一个位,时序清清楚楚。而UART没有这根时钟线,所以发送方和接收方必须各自维护一个时钟,并且确保这个时钟的频率足够接近。这就是为什么串口通信对波特率的精度有要求,精度不够,就会出现乱码。

51单片机里,这个时钟是靠定时器1产生的,这也是为什么学串口之前需要先把定时器搞明白。后面讲波特率计算的时候会详细展开。

1.2 一帧数据里到底有哪些东西

串口通信的数据单位叫“帧”,一次完整的传输叫“一帧数据”。UART的帧格式包含以下这些部分,缺一不可:

组成部分作用默认状态
起始位通知接收方“数据要来了”,让接收方开始采样低电平(下降沿触发)
数据位真正要传的数据,一般是8位低位在前,高位在后
校验位简单的错误检测,可以不要奇校验/偶校验/无校验
停止位一帧结束的标志,让接收方恢复到空闲状态高电平(1-2位)

其中起始位是最关键的。线路空闲的时候是高电平,当发送方拉低电平一个位的时间,接收方就知道“来了来了”,开始按波特率采样后续的8个数据位。采样顺序是从低位到高位,这一点写代码的时候不直接感知,但理解协议的时候要知道。

为什么要有停止位?因为接收方需要时间来处理接收到的一帧数据,比如把SBUF里的数据取走、响应中断等。如果两帧数据之间没有间隔,接收方处理不过来,就会丢数据。所以停止位相当于给接收方一个喘息的机会。

1.3 电平标准:TTL、RS232、USB转TTL

串口通信的电平标准是新手最容易踩坑的地方。

单片机的IO口输出的是TTL电平,高电平是5V(或3.3V),低电平是0V。但电脑的串口(老式的九针COM口)用的是RS232电平,高电平是-12V,低电平是+12V,逻辑正好是反的。如果你直接把单片机的TX引脚和电脑的COM口连在一起,不仅通信失败,还有可能烧坏单片机。

现在电脑上基本都没有COM口了,大家用的都是USB转TTL模块。这种模块内部集成了电平转换芯片,USB那头插电脑,TTL那头的TX、RX、GND引脚可以直接和单片机连接。选模块的时候要看清楚,有些模块上会有“3.3V/5V”跳线帽,根据单片机的工作电压选择。

连接规则很简单:单片机TX接模块RX,单片机RX接模块TX,GND接GND。这个交叉连接很多人第一次都接反了,于是电脑收不到任何数据。模块上的TX和RX引脚一般会标注清楚,接之前花十秒钟确认一下,能省下半小时排查时间。

2. 51单片机串口模块的硬件结构,搞懂SBUF和寄存器

51单片机(以经典的STC89C52为例)内部自带一个全双工串口模块,也就是说发送和接收可以同时进行。这个模块的使用难度其实不高,核心就是操作几个特殊功能寄存器。但很多初学者被那些零散的资料搞得一头雾水,这里我按“先硬件后软件”的思路把整个结构梳理一遍。

2.1 SBUF:同一个地址,两个物理缓冲器

SBUF是串口数据缓冲寄存器,是访问串口模块的核心入口。这个名字很直观,Serial Data Buffer。但SBUF有一个很有意思的特性:它虽然只有一个地址(0x99),实际上却是两个物理上独立的8位寄存器——一个负责发送,一个负责接收。

当你写SBUF,也就是执行SBUF = 0x55这样的语句时,数据被送入发送缓冲器,然后硬件会自动把这一字节数据按照你配置好的帧格式逐位移出去。当你读SBUF,也就是执行x = SBUF时,读的是接收缓冲器,里面的数据是从串口上完整接收到的一字节。

这两个寄存器共用一个地址,但互不干扰。这就是51单片机串口模块非常巧妙的设计:软件上你只需要操作一个寄存器,硬件上自动完成收发分离。

注意:SBUF的读写操作是完全独立的,这也意味着你在读取串口接收到的数据之前,一定要确认接收完成标志位RI已经被置1,否则读出来的可能是上一次残留的数据或者无意义的数据。这个后面在写接收代码时会具体处理。

2.2 串口相关的四个寄存器:SCON、PCON、TMOD、TH1/TL1

51单片机串口通信涉及的寄存器并不多,但每个都要搞清楚作用,因为它们分别管控串口模式、波特率倍频、定时器配置这几个关键环节。

第一个是SCON,串口控制寄存器,可位寻址。它最核心的几个位是SM0和SM1,这两位组合决定串口的工作模式(详见表);REN位是接收使能,要置1才能接收数据;TI是发送中断标志位,发送完一帧数据后硬件自动置1;RI是接收中断标志位,接收完一帧数据后硬件自动置1。

第二个是PCON,电源控制寄存器,串口相关的只有SMOD这一个位(在最高位bit7)。SMOD是波特率倍频位,置1时波特率翻倍。这个位的用途在后面计算波特率的时候会体现,因为很多时候需要靠它来把波特率误差调到最小。

第三个是TMOD,定时器模式寄存器。串口波特率发生器最常用的是定时器1的模式2,也就是8位自动重载模式。为什么选模式2?因为这种方式下,定时器溢出后硬件自动把TH1的值重载到TL1里,不需要软件在中断里重新赋值,生成的波特率没有软件抖动,更稳定。

TMOD寄存器的高四位控制定时器1,低四位控制定时器0。配置的时候建议先TMOD &= 0x0F清零高四位,再TMOD |= 0x20设置定时器1为模式2,这样不会影响定时器0的现有配置。

第四个是TH1和TL1,定时器1的两个计数寄存器。TH1存重载值,TL1存当前计数值。初始化的时候两个寄存器都要赋同一个值。

2.3 四种工作模式怎么选,实际用的是模式1

51单片机串口有四种工作模式,由SCON寄存器的SM0和SM1两位控制:

模式SM0 SM1波特率来源特点
00 0系统时钟/12同步移位寄存器模式,用于扩展IO口,不常用于通信
10 1定时器1溢出率8位UART,标准串口通信模式,最常用
21 0系统时钟/32或/649位UART,波特率固定,不灵活
31 1定时器1溢出率9位UART,多用于多机通信

自学阶段,模式1是最合适的。它是8位数据位的标准UART格式,波特率可以通过定时器1自由调整,应用范围最广。模式2和模式3是9位UART,多一个附加的第9位数据,这个第9位常被用作多机通信时的地址/数据标志位,或者校验位。对于日常的PC通信、模块对接,模式1足够了。

实际操作中,如果只需要8位数据位的UART通信,直接使用模式1。模式0虽然波特率固定不用定时器,但它不是真正意义上的串口异步通信,而是移位寄存器同步方式,别搞混了。

3. 波特率计算:为什么大家都用11.0592MHz晶振

波特率是串口通信中最重要的一个参数。很多人用现成的配置代码能跑通,但一到自己改波特率或者换晶振就懵了。这一节把波特率计算的过程掰开揉碎,讲清楚公式、算例和误差来源。

3.1 波特率的本质,以及和定时器溢出率的关系

波特率(Baud Rate)表示每秒传输的码元数,串口通信中一般就等于每秒传输的位数。比如9600波特率,意味着每秒钟传输9600个位,那么传输一位所需的时间就是1/9600秒,约104.2微秒。

51单片机的UART模块没有独立的波特率发生器,需要借助定时器1的溢出脉冲来驱动移位时钟。所谓的“溢出率”,就是定时器每秒溢出的次数。定时器1工作在模式2时,TL1从初值开始加1计数,每加满256个脉冲溢出一次,溢出后自动从TH1重新装载初值。

那么波特率跟定时器溢出率是什么关系?这就要说到UART模块内部的分频逻辑了。波特率发生器的时钟源是定时器1的溢出脉冲,但这个脉冲不能被直接用,还要经过一个分频电路。在模式1下,串口模块会对溢出脉冲进行16分频(因为接收方采样时每位数需要16个时钟脉冲来完成同步和采样)。

最终波特率公式是:波特率 = 定时器1溢出率 / 16。在典型的12T单片机(12个时钟周期执行一条机器周期指令)上,定时器1的溢出率又跟系统时钟有关,所以完整的计算方式是:波特率 = 系统时钟频率 / (12 × 32 × (256 - TH1))。需要注意,这个公式对应SMOD=0的情况,SMOD=1时结果要乘以2。

3.2 定时器1重载值计算全过程,用9600举例子

直接算一个最常见的情况:系统时钟11.0592MHz,目标波特率9600,SMOD=0,12T模式。

带入公式:9600 = 11059200 / (12 × 32 × (256 - TH1))

分母化简:12 × 32 = 384,那么 9600 = 11059200 / (384 × (256 - TH1))

反解出 256 - TH1 = 11059200 / (384 × 9600) = 11059200 / 3686400 = 3

于是 TH1 = 256 - 3 = 253 = 0xFD

这就是为什么所有51单片机教程里,波特率9600的初始化代码都是TH1 = 0xFD, TL1 = 0xFD的原因。8000Hz系统的时候,同样的计算会得到TH1 = 0xFD这个值。整条链路就通了。

同样的方法,如果系统时钟是11.0592MHz,目标波特率是4800,那么256 - TH1 = 6,TH1 = 250 = 0xFA。所以4800对应的初值是0xFA。这些值都不需要背,掌握计算方法后随时可以推算。

3.3 误差分析:乱码的真正根源

如果用的晶振频率不是11.0592MHz,而是常用的12MHz,情况就有意思了。还是算波特率9600:9600 = 12000000 / (384 × (256 - TH1))

反解出 256 - TH1 = 12000000 / (384 × 9600) = 12000000 / 3686400 ≈ 3.255

问题来了,TH1必须是一个整数(寄存器只能存整数),所以只能取3,那么实际产生的波特率就是 12000000 / (384 × 3) ≈ 10416.7。和9600之间的误差大约是 (10416.7 - 9600) / 9600 ≈ 8.5%。

这个误差意味着什么?意味着接收方按9600的节奏去采样一个每一位约104.2微秒的数据流,但实际数据流每一位只有96微秒(1/10416.7)。刚开始一位的误差还看不出来,但积累到第8位、第10位、第20位时,采样时刻已经偏离到了别的比特区间里,出来的就是乱码。

而11.0592MHz这个频率是专门为串口通信设计的,它能整除出一系列整数因子,从而精确产生9600、4800、2400等波特率。这就是为什么几乎所有开发板上都放着11.0592MHz的晶振。

经验值:波特率误差在2%以内通信基本还能正常工作,超过3%就可能出现随机错位、乱码。所以12MHz晶振调9600波特率虽然偶尔能通,但不可靠。如果板子上只有12MHz晶振,有两种补救思路:一是切换波特率目标值(比如4800,误差更小),二是设置SMOD=1来调整倍频关系,把误差往另一个方向压。

3.4 用STC-ISP软件自动生成串口配置代码

如果你用的单片机是STC系列,那STC官方的STC-ISP烧录软件里自带的波特率计算器可以直接用。选择单片机型号、晶振频率、目标波特率,软件会自动算出定时器的重载值、SMOD位的设置,并生成初始化代码。这对新手来说非常友好,也能帮助对照验证自己手动计算的结果。

但我的建议是:自动生成代码可以拿来用,但一定要回头看你自己的计算过程。原因很简单,自动生成的代码是死板的,你换了一颗不同频率晶振的单片机、或者换了一个波特率,如果只会粘贴不会算,就可能因为一点小改动而翻车。现在花十分钟把公式理解透,后面调任何波特率都不慌。

4. 实战代码:串口发送和接收,从零写一个收发Demo

理论部分到这里基本够用了,接下来直接进入写代码环节。我用Keil C51编写,基于STC89C52RC单片机,11.0592MHz晶振,串口工作模式1,波特率9600。代码风格尽量直观,方便一点点透视每一步的作用。

4.1 串口初始化,这一步是全部的基础

初始化代码的核心任务是:配置串口工作模式、使能接收、设置定时器1为波特率发生器并赋初值、开启中断。

#include <reg52.h> // 串口初始化:9600bps @ 11.0592MHz void UART_Init(void) { SCON = 0x50; // 0101 0000, SM0=0 SM1=1 模式1, REN=1 允许接收 // 配置定时器1为波特率发生器 TMOD &= 0x0F; // 清空高四位 TMOD |= 0x20; // 定时器1工作方式2:8位自动重载 TH1 = 0xFD; // 根据波特率9600计算出的初值 TL1 = 0xFD; // 同上 TR1 = 1; // 启动定时器1 ES = 1; // 打开串口中断 EA = 1; // 打开总中断 }

逐行解释几点容易被忽略的细节。SCON = 0x50是一次性完成多项配置,等价于手动设置SM0=0、SM1=1、REN=1。为什么REN要置1?因为REN是接收使能位,不置1的话单片机只发不收,你后面写接收代码也白搭。TMOD &= 0x0F这一行的作用是保护定时器0的配置,如果你之前的程序里已经用定时器0做了延时或者定时,不清零高四位的话会导致定时器0配置被破坏。

TH1和TL1为什么要赋同一个值?因为工作方式2是自动重载的,TH1里面存的值会不断被重载到TL1中。TR1=1启动定时器1后,它就开始以目标溢出率不断产生脉冲,驱动串口模块工作。ES=1和EA=1是中断使能,如果你想用中断方式接收数据,这两行必须都有。

4.2 发送一个字节与字符串

发送数据的基本原理是:把要发送的数据写入SBUF,硬件自动开始移位发送;当发送完成后,硬件会置TI=1。TI可以通过查询方式或者中断方式处理。这里先讲查询方式,逻辑最简单。

// 发送单个字符 void UART_SendByte(unsigned char dat) { SBUF = dat; // 数据写入发送缓冲器 while (TI == 0); // 等待发送完成 TI = 0; // 手动清除发送完成标志 }

TI这个标志位很关键。它初始为0,数据发送完成后硬件自动置1。所以循环while(TI==0)就是“死死盯着”发送是否完成,完成后跳出循环并立即把TI清0,方便下一次发送。这里有个新手常犯的错:忘记清TI,或者清早于判断,导致下一次发送直接跳过等待,数据发出去了但时序错乱。

基于这个单字节发送函数,可以封装一个发送字符串的函数,字符串在C语言中以'\0'结尾,所以通过逐个发送直到遇到结束符即可。

// 发送字符串 void UART_SendString(unsigned char *str) { while (*str != '\0') { UART_SendByte(*str); str++; } }

这个字符串发送函数非常常用,调试的时候直接UART_SendString("Hello UART!\r\n")就能在电脑上看到输出。为什么推荐以\r\n结尾?因为串口调试助手里换行一般需要这两个字符配合,只发\n有些调试工具不会换行,显示会连在一行。

4.3 接收数据:查询方式和中断方式怎么选

接收数据的核心是检测RI标志位。RI与TI很相似,接收完成后硬件自动置1,需要软件手动清零。

最简单的查询方式代码如下:

// 查询方式读取一字节(阻塞等待) unsigned char UART_ReceiveByte(void) { while (RI == 0); // 等待接收完成 RI = 0; // 清除接收标志 return SBUF; // 返回接收到的数据 }

这种方式简单直接,但有一个明显的问题:在主循环里调用这个函数时,整个程序会被阻塞在这个while里,期间单片机什么都干不了。如果主程序还要处理按键、显示、传感器采集等任务,这种方式就不能用。

实际项目中,更常见的是用中断方式接收。配置串口中断后,每收到一个字节,硬件就会自动跳到中断服务函数。这样可以做到“边干活边等数据”。

volatile unsigned char rx_data; volatile unsigned char rx_flag = 0; void UART_Isr() interrupt 4 { if (RI == 1) // 接收中断 { RI = 0; rx_data = SBUF; rx_flag = 1; // 通知主循环有新数据 } // 如果是TI,说明是发送完成中断,这里不做处理 }

中断服务函数里if (RI == 1)这个判断是非常重要的。因为串口中断有多个来源——发送完成(TI)和接收完成(RI)都会进入这个中断函数。如果不区分,发送完成后也会误进中断里做接收处理,导致数据混乱。

定义成volatile的原因是主循环和中断函数都在访问这个变量,volatile能避免编译器优化导致主循环读取到缓存中的旧值。

4.4 完整的收发回显Demo

把上面这些函数串成一个完整的用户程序,实现功能:单片机通过串口接收PC发来的数据,收到后把原数据回传,并在串口调试助手上显示出来。

#include <reg52.h> volatile unsigned char rx_data; volatile unsigned char rx_flag = 0; void UART_Init(void) { SCON = 0x50; TMOD &= 0x0F; TMOD |= 0x20; TH1 = 0xFD; TL1 = 0xFD; TR1 = 1; ES = 1; EA = 1; } void UART_SendByte(unsigned char dat) { SBUF = dat; while (TI == 0); TI = 0; } void UART_SendString(unsigned char *str) { while (*str != '\0') { UART_SendByte(*str); str++; } } void UART_Isr() interrupt 4 { if (RI == 1) { RI = 0; rx_data = SBUF; rx_flag = 1; } } void main() { UART_Init(); UART_SendString("UART Demo Ready!\r\n"); while (1) { if (rx_flag == 1) { rx_flag = 0; UART_SendByte(rx_data); // 回显 } } }

这个Demo跑通后,你可以做一个自己跟自己打招呼的串口实验:在串口调试助手里发送一个字符“A”,调试助手里立刻回显“A”。看似简单,但表面上无感的“收发闭环”已经包含了初始化、发送、接收、中断、标志位管理这些串口通信的所有核心知识。后面做上位机、接传感器模块、甚至多机通信,都是在这个骨架上扩展的。

4.5 串口调试助手的正确使用方式

代码写完了,还得能在电脑上看到效果。串口调试助手类工具很多,功能大同小异,关键是把几个配置项设置正确。波特率选9600,数据位选8,停止位选1,校验位选None——这四个参数必须跟单片机侧的配置完全一致,任何一个不一致都会导致显示乱码或者收不到数据。

还有两个小细节容易被忽略。第一,一定要选对COM口,插上USB转TTL模块后,在设备管理器里查看新增的COM口编号。别接在COM1上(如果电脑有的话),那是板载串口,跟你的USB模块没关系。第二,发送新行(发送新行)选项一般要勾上,不然你发送的时候不带回车换行,很多程序里的判断逻辑会受影响。勾选了发送新行,相当于ASCII码中在数据后面自动加上0x0D和0x0A。

5. 踩坑记录:串口调试中那些让人崩溃的问题

串口通信写程序不难,难的是排查问题。因为串口涉及硬件连接、参数配置、软件逻辑和电平转换等多个环节,任何一环有问题,表现都是“收不到数据”或者“收到乱码”。下面把这些年踩过的典型坑逐一列出来,相当于一份“串口自救手册”。

5.1 乱码问题

乱码的原因绝大多数是波特率不匹配,也就是发送方的波特率和接收方的波特率不一致。注意,不一致不仅仅是“你设9600我设9600”这么简单,还包括误差累积问题。11.0592MHz晶振配合9600波特率是精确的,但如果你用的开发板实际晶振频率是12MHz而程序按11.0592MHz计算初值,发射端实际波特率就偏了若干个百分点,短时间内看不出乱码,数据一长就出来了。

排查乱码的思路是:先看代码里TH1/TL1的初值是否跟晶振频率匹配,再在串口助手里核对波特率设置,最后用示波器或逻辑分析仪测一下单片机TX引脚的实际波特率,看偏差大不大。没有示波器的话,可以换个短报文测试,比如只发一个字符“U”,如果还是乱码,那基本可以确认不是累积误差的问题,而是根本性的参数不匹配。

5.2 完全收不到数据

这个问题八成出在硬件连接上。先确认模块的TX接单片机的RX,模块的RX接单片机的TX。交叉连接,不要直连。很多人第一次接的时候,想着“同名相连”,把TX接TX、RX接RX,结果自然不通。然后检查共地:单片机的地和USB转TTL模块的地必须连在一起,这是所有通信协议的基本前提,不共地的话电平基准不同,信号逻辑根本无法判断。

如果连接没问题,再测量单片机TX引脚在不发送数据时是否保持高电平。UART空闲状态就是高电平,如果测到低电平,可能是单片机没正常工作、程序没烧进去,或者引脚配置被复用掉了。如果TX引脚正常,那就是程序没跑对,逐步检查初始化代码。

5.3 第一次发送正常,第二次以后没响应或数据错乱

这个问题很典型,常见于查询方式发送和中断方式接收混用的场景。根因往往是标志位没有正确清零,或者主循环里的逻辑跟中断产生了竞争。

比如你在发送函数里用了while(TI==0)等待发送完成,而同时串口中断又开着,发送完成后TI=1会触发发送中断,如果中断服务函数里对TI做了清0处理,那主循环里发送函数的while(TI==0)就可能等待一个永远也不会变成1的标志——因为中断抢先把它清了。这就会导致程序卡死在发送函数里。

解决方案是:中断服务函数里只处理接收(RI),对TI不做任何操作。如果确实需要在中断里处理发送,那发送函数就不要再用查询等待方式,让中断来负责后续数据的发送。

5.4 115200这类高波特率需要注意什么

把波特率从9600提高到115200,表面上只是改个初值的事,但有几个伴随问题。第一,晶振频率必须足够精确,11.0592MHz下115200对应的初值也不是整除的,它会落在某两个整数之间,实际误差不能过大。第二,USB转TTL模块的质量影响放大,劣质模块或者劣质USB线在高波特率下的位误差很大。第三,接线长度和数据线质量在高波特率下开始变得敏感,杜邦线如果太长或者接触不良,就可能造成错位。

实际经验:如果只是调试,9600或19200完全够用,没必要非追115200。等到做产品、需要大数据量传输时,再考虑上高波特率,并且一定要做完整的通信压力测试。

6. 串口之后还能干什么,几条扩展思路

搞定了基础串口收发,后面的事情就水到渠成了。串口是“万金油”接口,它不挑设备,几乎所有单片机模块都支持串口通信,区别只是电平标准、波特率和协议格式。这一节简单说说串口学完后的几个扩展方向,给自学路线指个方向。

如果想做上位机控制,可以用Python的pySerial库或者C#的串口控件写个简单的PC端程序,和单片机通过串口通信。这样你就有了一个“电脑控制单片机”的通道,可以做出远程控制、数据采集、曲线绘制这些很有意思的小项目。而且此时你对协议也会建立直观概念——比如定义一帧通信协议:帧头、地址、命令、长度、数据、校验、帧尾。

如果想接无线模块,很多WiFi模块、蓝牙模块、LoRa模块都提供串口透传接口。单片机把数据发给模块,模块用无线方式发出去,另一边再用同样的模块收回来恢复成串口数据,从程序员的角度来看,和直接连一根串口线没有任何区别。这就是“透传”的本质,串口通信能力在这里就变成了无线通信的基石。

如果想做多机通信,可以用模式2或模式3的9位UART,主机发送地址帧时第9位为1,所有从机都收到;主机发送数据帧时第9位为0,只有地址匹配的从机才接收数据。这就实现了简单的主从式多机分发。51单片机的串口虽然只有一个UART,但依靠这种协议机制就能串起多个节点。

还有一种很实用的玩法是串口和printf结合。重定向C标准库中的putchar函数到串口,一行printf就能在电脑上打印各种格式化信息。调试程序时比单步仿真更直观,尤其在处理时序敏感的逻辑时,printf是最好用的调试手段。很多编译器里只需要重写一个fputc函数并设置一下编译选项,就可以实现。

这些扩展方向都依赖一个共同的基础:对串口的工作模式和寄存器配置非常熟悉。基础扎实了,后面这些花样都能快速上手。基础不牢的话,换一个模块、换一个波特率、换一种工作模式就能让你重新陷入迷茫。

回到最初那个点:UART串口不是最酷的外设,但它是最“通情达理”的外设。学习它你不需要理解复杂的硬件协议,也不用面对难读的数据手册,它把一切简化为一个寄存器读写动作。而正是这种简单,让它成为所有通信学习的起点。这个基础打牢,后面学SPI、I2C这些都自然顺畅多了。踩过这么多坑之后我的体会是:串口调试就像对暗号,两边约定好了暗号就畅通无阻,有一边记错了词就满屏乱码。先把暗号对上,再谈其他。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 16:46:19

x86-64实模式内核启动:从0x7c00到kernel_main的硬核准备知识

1. 这不是“写个玩具系统”的速成课&#xff0c;而是一次操作系统内核开发的实操切片你点开这个标题&#xff0c;大概率不是为了凑热闹看个乐子&#xff0c;而是真想摸一摸操作系统底层的脉搏——不是调用几个API、不是改几行配置&#xff0c;是亲手让一段代码在裸机上接管CPU、…

作者头像 李华
网站建设 2026/10/15 16:45:06

优化器实例测试:从原理到实践,快速定位训练配置问题

1. 从“优化器实例测试”说起&#xff1a;为什么这件事值得单独拎出来做但凡做过一段时间模型训练的人&#xff0c;大概率都经历过这样的场景&#xff1a;模型结构调了好几版&#xff0c;数据管道也反复清洗过&#xff0c;训练脚本跑起来 loss 就是不降&#xff0c;或者降着降着…

作者头像 李华
网站建设 2026/10/15 16:43:33

FPGA自制家用电脑:从芯片组设计到整机搭建的工程实践

1. 一台带FPGA的“家用电脑”到底意味着什么 第一次看到“Kromi One”这个项目标题的时候&#xff0c;我脑子里蹦出来的第一个念头是&#xff1a;又有人要做复古电脑了&#xff1f;但仔细一看关键词—— home computer 加上 its own FPGA chipset &#xff0c;这就不是简单…

作者头像 李华
网站建设 2026/10/15 16:43:22

ARDU-cade:在2KB内存中实现本地自适应AI的嵌入式掌机

1. 从一个“不务正业”的掌机说起&#xff1a;ARDU-cade 到底想解决什么问题第一次看到 ARDU-cade 这个名字&#xff0c;很多人会以为又是一个拿开发板拼出来的复古游戏机。市面上用单片机做掌机的项目太多了&#xff0c;硬件方案大同小异&#xff0c;软件无非是跑个模拟器或者…

作者头像 李华
网站建设 2026/10/15 16:41:25

使用C#调用Core Audio APIs获取Windows音频设备信息实战

前阵子有个朋友找到我&#xff0c;他想做一个音频设备管理工具&#xff0c;需要在C#程序里把当前系统的扬声器、麦克风设备列出来&#xff0c;能显示设备名称、状态&#xff0c;还能判断哪个是默认设备。一开始他想用老一套的API去查&#xff0c;结果不是拿不到完整信息&#x…

作者头像 李华
网站建设 2026/10/15 16:41:20

32位Windows下ffmpeg-4.3.1静态包:老旧环境音视频转码实战指南

简介&#xff1a;ffmpeg-4.3.1 windows-static 32位是面向音视频开发者的静态编译工具包&#xff0c;专为32位Windows环境设计&#xff0c;所有依赖库已打包进单个可执行文件&#xff0c;无需额外安装运行库即可直接调用&#xff0c;适合在受限系统或便携场景下完成音视频处理任…

作者头像 李华