xiaohack's Blog - 嵌入式开发
type: channel
link: https://blog.xiaohack.org/tag/d99d776d-e7fff8/
items: 51单片机都有哪些优缺点
items: IIC总线的硬件部分的两个关键点：开漏输出+上拉电阻
items: 达林顿管的基础知识
items: C语言的指针
items: 《ESP32-S3使用指南—IDF版 V1.6》第六十四章 LVGL 综合例程
items: 什么是三极管？
items: UART、RS232、RS485的区别

51单片机都有哪些优缺点
type: item
date: 2026-02-04T10:59:22+08:00
description: 大家好，我是良许。今天咱们来聊聊 51 单片机。作为嵌入式开发领域的"老前辈"，51 单片机陪伴了无数工程师走过了学习和工作的岁月。虽然现在 STM32、ESP32 等新一代单片机层出不穷，但 51 单片机依然在某些场景下发挥着不可替代的作用。那么，51 单片机到底有哪些优缺点呢？今天我就从实际开发的角度，给大家详细分析一下。1. 51 单片机的主要优点1.1 学习门槛低，上手快51 单片机最大的优点就是简单易学。它的指令集只有 111 条，相比 ARM Cortex-M 系列动辄上百条指令，学习负担要轻很多。对于刚入门的同学来说，不需要掌握太多复杂的概念就能开始写程序。我记得当年读大学的时候，第一次接触单片机就是从 51 开始的。那时候用 Keil C51 编译器，写个流水灯程序也就几十行代码，调试起来也很直观。这种"所见即所得"的学习体验，让我很快就建立了对嵌入式开发的信心。#include <reg51.h> ​ void delay(unsigned int ms) { unsigned int i？ j; for(i = 0; i < ms; i++) for(j = 0; j < 120; j++); } ​ void main() { unsigned char led = 0xFE; // 初始状态:P0.0点亮 while(1) { P0 = led; // 输出到P0口 delay(500); // 延时500ms led = (led << 1) | 0x01; // 左移一位 if(led == 0xFF) // 全灭后重新开始 led = 0xFE; } }这段流水灯代码非常简单，即使是零基础的同学看几遍也能理解。这就是 51 单片机的魅力所在——它不会让你在一开始就被复杂的寄存器配置、时钟树、中断向量表等概念搞晕。1.2 资料丰富，社区成熟51 单片机诞生于 1980 年代，经过几十年的发展，相关的学习资料、开发工具、例程代码可以说是铺天盖地。无论你遇到什么问题，基本上都能在网上找到解决方案。这对于自学者来说是非常友好的。我在做嵌入式开发的这些年里，经常会在一些论坛、贴吧看到关于 51 单片机的讨论。即使是十几年前的帖子，里面的技术方案现在依然适用。这种技术的延续性和稳定性，是很多新兴平台无法比拟的。而且，51 单片机的开发板、仿真器价格都非常便宜。一套完整的学习套件可能只需要几十块钱，这对于学生党来说非常友好。我当年买的第一块 51 开发板才 35 块钱，上面集成了 LED、数码管、按键、蜂鸣器等常用外设，足够完成大部分基础实验了。1.3 成本低廉，适合批量生产在商业应用中，成本控制是非常重要的考量因素。51 单片机的价格通常在几毛钱到几块钱之间，这对于需要大批量生产的产品来说是个巨大的优势。比如说，一些简单的家电控制器、玩具、小家电等产品，功能需求并不复杂，用 51 单片机完全可以满足。我之前接触过一个做电动车仪表盘的项目，客户最终选择了 STC89C52 作为主控芯片，原因就是成本低、供货稳定。这个项目每年的出货量在几十万台，单片机成本每降低 1 毛钱，一年就能省下好几万。1.4 功耗较低，适合电池供电场景51 单片机的功耗相对较低，特别是国产的 STC 系列，在休眠模式下电流可以降到微安级别。这使得它非常适合一些需要电池供电的场景，比如遥控器、无线传感器节点等。#include <STC89C5xRC.h> ​ void enter_power_down() { EA = 0; // 关闭总中断 PCON |= 0x02; // 进入掉电模式 _nop_(); _nop_(); } ​ void main() { // 初始化配置 P1 = 0xFF; // 设置P1口为高电平 while(1) { // 执行一些任务 // ... // 进入低功耗模式 enter_power_down(); // 被外部中断唤醒后继续执行 } }通过合理的电源管理，51 单片机可以在电池供电的情况下工作很长时间。我曾经做过一个无线温度采集器的项目，使用两节 AA 电池，通过让单片机大部分时间处于休眠状态，只在需要采集数据时唤醒，最终实现了一年以上的续航时间。1.5 结构简单，便于理解底层原理51 单片机的内部结构相对简单，包括 CPU、RAM、ROM、定时器、串口等基本模块。这种简单的架构非常适合用来学习计算机组成原理和嵌入式系统的基本概念。通过学习 51 单片机，你可以清楚地了解到程序是如何在硬件上运行的，寄存器是如何控制外设的，中断机制是如何工作的。这些底层知识对于后续学习更复杂的 ARM、RISC-V 等架构都有很大帮助。2. 51 单片机的主要缺点2.1 性能有限，处理能力较弱51 单片机的主频通…

IIC总线的硬件部分的两个关键点：开漏输出+上拉电阻
type: item
date: 2026-01-30T10:18:00+08:00
description: 大家好，我是良许。在嵌入式开发中，IIC（I2C）总线可以说是最常用的通信协议之一了。无论是读取传感器数据、控制EEPROM存储器，还是与各种外设进行通信，IIC总线都扮演着重要角色。但很多初学者在使用IIC时，往往只关注软件层面的时序和协议，却忽略了硬件层面的关键设计。今天我就来聊聊IIC总线硬件部分的两个核心要点：开漏输出和上拉电阻。理解了这两点，你才能真正掌握IIC总线的精髓。1. IIC总线的基本结构在深入讲解之前，我们先简单回顾一下IIC总线的基本构成。IIC总线只需要两根信号线就能实现多主机、多从机之间的通信，这两根线分别是：SCL（Serial Clock）：时钟线，由主机产生时钟信号SDA（Serial Data）：数据线，用于主从设备之间的数据传输一条IIC总线上可以挂载多个设备，每个设备都有唯一的地址。这种简洁的设计让IIC总线在嵌入式系统中广受欢迎，特别是在PCB布线空间有限的场景下。但问题来了：多个设备共用同一根数据线和时钟线，它们是如何避免冲突的呢？这就要说到IIC总线硬件设计的核心机制了。2. 开漏输出：IIC总线的灵魂2.1 什么是开漏输出开漏输出（Open-Drain）是IIC总线最核心的硬件特性。要理解开漏输出，我们先来看看常见的GPIO输出模式。在普通的推挽输出（Push-Pull）模式下，GPIO引脚可以主动输出高电平（通过上管导通）或低电平（通过下管导通）。这种模式下，引脚能够提供较强的驱动能力，但有个致命问题：如果两个推挽输出的引脚连接在一起，一个输出高电平，另一个输出低电平，就会造成短路，可能烧毁芯片。而开漏输出则不同，它的内部结构只有一个下拉的NMOS管，没有上拉的PMOS管。这意味着：当GPIO输出低电平时，NMOS管导通，引脚被拉到地（GND），呈现低电平当GPIO输出高电平时，NMOS管截止，引脚呈现高阻态（既不输出高也不输出低）这种"只能拉低，不能拉高"的特性，正是开漏输出的精髓所在。2.2 开漏输出的优势你可能会问：只能拉低不能拉高，这不是很鸡肋吗？恰恰相反，这正是IIC总线能够实现多设备共享总线的关键。第一个优势：线与逻辑多个开漏输出连接在同一根线上时，会形成"线与"（Wired-AND）逻辑。只要有任何一个设备输出低电平，整条总线就是低电平；只有当所有设备都输出高阻态时，总线才能被上拉电阻拉到高电平。这种特性在IIC总线中至关重要。比如在多主机系统中，如果两个主机同时发送数据产生冲突，通过检测总线电平，主机可以发现冲突并进行仲裁。发送"1"的主机如果检测到总线为"0"，就知道有其他主机在发送数据，会主动放弃总线控制权。第二个优势：电平转换开漏输出配合上拉电阻，可以轻松实现不同电压域之间的电平转换。比如一个3.3V的MCU和一个5V的传感器通信，只需要将上拉电阻接到5V电源，就能实现电平匹配。3.3V的MCU输出低电平时可以将总线拉低，输出高阻态时总线被上拉到5V，这个5V电平不会损坏MCU（因为MCU引脚是高阻态，没有电流流入）。第三个优势：避免总线冲突在推挽输出模式下，如果两个设备同时驱动总线，一个输出高一个输出低，就会造成短路。而开漏输出永远不会主动输出高电平，最多只是高阻态，因此不会产生短路风险。2.3 STM32中的开漏配置在STM32中配置IIC引脚为开漏输出非常简单。使用HAL库的话，代码如下：void MX_I2C1_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; /* 使能GPIOB时钟 */ __HAL_RCC_GPIOB_CLK_ENABLE(); /* 配置IIC引脚：PB6(SCL), PB7(SDA) */ GPIO_InitStruct.Pin = GPIO_PIN_6 | GPIO_PIN_7; GPIO_InitStruct.Mode = GPIO_MODE_AF_OD; // 复用开漏输出 GPIO_InitStruct.Pull = GPIO_NOPULL; // 不使用内部上下拉 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; GPIO_InitStruct.Alternate = GPIO_AF4_I2C1; HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); /* 配置IIC外设 */ hi2c1.Instance = I2C1; hi2c1.Init.ClockSpeed = 100000; // 100kHz标准速率 hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2; hi2c1.Init.OwnAddress1 = 0; hi2c1.Init.AddressingM…

达林顿管的基础知识
type: item
date: 2026-01-28T10:31:28+08:00
description: 达林顿管的基础知识大家好，我是良许。在嵌入式开发中，我们经常需要驱动各种负载，比如继电器、电机、LED灯带等。这些负载往往需要较大的电流，而单片机的IO口输出能力有限，这时候就需要用到功率放大电路。达林顿管（Darlington Transistor）就是一种非常实用的功率放大器件，它能够提供极高的电流增益，让我们用很小的基极电流就能控制很大的负载电流。今天我就来详细聊聊达林顿管的相关知识。1. 什么是达林顿管1.1 达林顿管的结构达林顿管，又称达林顿晶体管或复合管，是由两个或多个三极管按照特定方式连接而成的复合器件。最常见的是由两个NPN型或PNP型三极管组成。其基本连接方式是：第一个三极管（称为驱动管）的发射极直接连接到第二个三极管（称为输出管）的基极，而两个三极管的集电极连接在一起作为复合管的集电极。这种连接方式使得第一个三极管的输出电流成为第二个三极管的输入电流，从而实现了电流的二次放大。如果第一个三极管的电流增益是β1，第二个三极管的电流增益是β2，那么整个达林顿管的总电流增益约为β1×β2，通常可以达到几百甚至上千。1.2 达林顿管的符号在电路图中，达林顿管有专门的符号表示。对于NPN型达林顿管，符号看起来像一个普通的NPN三极管，但在内部会画出两个三极管的连接关系。有些封装好的达林顿管芯片，比如ULN2003、TIP120等，在电路图中可能直接用一个三角形加箭头表示，并标注型号。1.3 常见的达林顿管型号在实际应用中，常见的达林顿管型号包括：TIP120/TIP121/TIP122：NPN型达林顿管，最大电流5A，常用于中等功率场合TIP125/TIP126/TIP127：PNP型达林顿管，与TIP120系列互补ULN2003/ULN2803：集成了7路/8路达林顿管阵列的芯片，内置续流二极管，特别适合驱动继电器、步进电机等感性负载BD681/BD682：大功率达林顿管，最大电流可达4A2. 达林顿管的工作原理2.1 电流放大过程达林顿管的核心优势在于其超高的电流放大能力。让我们详细分析一下电流是如何被放大的。假设我们有一个由Q1和Q2组成的NPN型达林顿管，当基极B输入一个微小的电流Ib时，这个电流首先流入Q1的基极。根据三极管的放大原理，Q1的集电极电流Ic1=β1×Ib，发射极电流Ie1=(β1+1)×Ib。由于Q1的发射极连接到Q2的基极，因此Ie1就成为了Q2的基极电流。Q2再次进行电流放大，其集电极电流Ic2=β2×Ie1=β2×(β1+1)×Ib。最终，达林顿管的总集电极电流Ic=Ic1+Ic2≈β1×β2×Ib（当β1和β2都远大于1时）。这就是达林顿管能够实现超高电流增益的原因。2.2 导通压降达林顿管有一个需要注意的特点，就是它的基极-发射极导通压降（Vbe）比普通三极管要高。普通三极管的Vbe约为0.7V，而达林顿管的Vbe约为1.4V（两个三极管的Vbe相加）。这意味着在设计电路时，我们需要确保基极电压至少比发射极高1.4V以上，达林顿管才能可靠导通。同样，集电极-发射极的饱和压降（Vce(sat)）也会比普通三极管略高，通常在0.9V到2V之间。2.3 开关速度由于达林顿管是两级放大，其开关速度相对较慢。这是因为关断时需要等待两个三极管的存储电荷都消散完毕。因此，达林顿管不太适合用于高频开关场合，更适合用于低频或直流驱动应用。3. 达林顿管的典型应用3.1 驱动继电器继电器是嵌入式系统中常用的执行器件，但其线圈电流通常在几十到上百毫安，远超单片机IO口的驱动能力。使用达林顿管可以轻松解决这个问题。以STM32驱动继电器为例，我们可以使用TIP120达林顿管。电路连接方式是：STM32的GPIO通过一个限流电阻（比如10kΩ）连接到TIP120的基极，继电器线圈一端接电源正极，另一端接TIP120的集电极，发射极接地。继电器线圈两端还需要并联一个续流二极管（如1N4007），防止关断时的反向电动势损坏达林顿管。下面是一个简单的HAL库代码示例：// 初始化GPIO void Relay_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; // 使能GPIOA时钟 __HAL_RCC_GPIOA_CLK_ENABLE(); // 配置PA5为输出模式 GPIO_InitStruct.Pin = GPIO_PIN_5; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct)…

C语言的指针
type: item
date: 2026-01-23T10:40:35+08:00
description: 大家好，我是良许。今天我们来聊一聊 C 语言中最让初学者头疼，却又最强大的特性——指针。作为一名从事嵌入式开发多年的程序员，我深知指针在底层编程中的重要性。无论是操作硬件寄存器、管理动态内存，还是实现高效的数据结构，指针都扮演着不可或缺的角色。1. 什么是指针1.1 指针的本质指针其实就是一个变量，只不过这个变量存储的不是普通的数值，而是内存地址。我们可以把内存想象成一排排的房间，每个房间都有一个门牌号（地址），而指针就是记录这个门牌号的本子。通过这个门牌号，我们可以找到对应的房间，进而访问或修改房间里的内容。在嵌入式开发中，这个概念尤为重要。比如 STM32 的 GPIO 端口，其实就是通过固定的内存地址来访问的。当我们要点亮一个 LED 灯时，本质上就是通过指针操作特定地址的寄存器。1.2 为什么需要指针指针的存在主要解决了以下几个问题：第一，高效传递数据。当我们需要在函数之间传递大型数据结构时，如果直接传递整个结构体，会产生大量的复制开销。而使用指针，只需要传递一个地址（通常是 4 字节或 8 字节），效率大大提升。第二，动态内存管理。在嵌入式系统中，内存资源往往非常有限。通过指针和动态内存分配，我们可以在程序运行时根据实际需要申请和释放内存，提高内存利用率。第三，直接操作硬件。在嵌入式开发中，我们经常需要直接访问硬件寄存器。这些寄存器都有固定的物理地址，必须通过指针来访问。2. 指针的基本使用2.1 指针的声明和初始化声明一个指针变量的语法是在类型名后面加上星号（*）。例如：int *p; // 声明一个指向整型的指针 char *str; // 声明一个指向字符的指针 float *fp; // 声明一个指向浮点数的指针需要注意的是，刚声明的指针是野指针，它指向一个不确定的地址，使用前必须初始化。我们可以用取地址符（&）来获取变量的地址：int num = 100; int *p = # // p指向num的地址 ​ printf("num的值: %d\n", num); printf("num的地址: %p\n", &num); printf("p存储的地址: %p\n", p); printf("p指向的值: %d\n", *p);这段代码会输出 num 的值、num 的地址、指针 p 存储的地址（与 num 的地址相同），以及通过指针 p 访问到的值（也是 100）。2.2 指针的解引用解引用就是通过指针访问它所指向的内存中的值。使用星号（*）操作符可以实现解引用：int a = 50; int *ptr = &a; ​ printf("a的值: %d\n", a); // 输出50 printf("*ptr的值: %d\n", *ptr); // 输出50 ​ *ptr = 80; // 通过指针修改a的值 printf("修改后a的值: %d\n", a); // 输出80在这个例子中，我们通过指针 ptr 修改了变量 a 的值。这在函数参数传递中非常有用，可以实现真正的"传址调用"。2.3 指针与函数在 C 语言中，函数参数默认是值传递，也就是说函数内部对参数的修改不会影响外部变量。但通过指针，我们可以实现传址调用：void swap(int *a, int *b) { int temp = *a; *a = *b; *b = temp; } ​ int main(void) { int x = 10, y = 20; printf("交换前: x=%d, y=%d\n", x, y); swap(&x, &y); printf("交换后: x=%d, y=%d\n", x, y); return 0; }这个经典的交换函数例子展示了指针的威力。通过传递变量的地址，函数内部可以直接修改外部变量的值。3. 指针的进阶应用3.1 指针与数组数组名本身就是一个指针常量，指向数组的首元素。这是 C 语言中一个非常重要的概念：int arr[5] = {1, 2, 3, 4, 5}; int *p = arr; // 等价于 int *p = &arr[0]; ​ printf("arr[0] = %d\n", arr[0]); // 输出1 printf("*p = %d\n", *p); // 输出1 printf("*(p+1) = %d\n", *(p+1)); // 输出2 printf("p[2] = %d\n", p[2]); // 输出3指针可以进行算术运算。当指针加 1 时，实际上是移动了一个所指类型的大小。比如 int 类型占 4 字节，那么 p+1 实际上是地址增加 4。在嵌入式开发中，这个特性经常用于遍历数据缓冲区：uint8_t buffer[256]; uint8_t *ptr = bu…

《ESP32-S3使用指南—IDF版 V1.6》第六十四章 LVGL 综合例程
type: item
date: 2026-01-23T09:33:08+08:00
description: 第六十四章 LVGL 综合例程本章，简单的介绍一下DNESP32S3开发板的 LVGL 综合例程。需要说明一下的是：本例程是一个不完整的例程。因为该例程只是实现一个基于 LVGL 的 GUI 界面，里面的 APP基本没有实现功能，所以这只是给大家参考的 GUI demo。实现这样简单的 GUI demo 原因如下：1， 板载的2.4寸TFTLCD并未具备触摸条件，所以设计UI时受到很大的制约。2， 想做出一个 LVGL 综合例程给大家参考，但时间比较赶。3， 要实现一个不错的 LVGL 综合例程，要花费不少精力。4， 要考虑板载资源，兼容性等。5， 工程师们手头的事情比较多，等后续空闲些再规划。大家可以把自己期待的 LVGL 界面、功能等，通过各种渠道跟我们沟通，比如：B 站视频评论区，销售客户/技术支持等。后续有时间，我们会把大家的建议都考虑上去的。最后，敬请大家心怀一个小小的期待，期待正点原子的 LVGL 综合例程，感谢大家的支持！！！本章将分为如下 2 个小节：64.1 LVGL 综合例程注意事项64.2 LVGL 综合例程界面展示64.1 LVGL 综合例程注意事项注意事项如下：1，DNESP32S3开发板的LVGL综合例程只支持正点原子的2.4寸 TFTLCD屏。其它屏幕会出现图标显示异常。2，所用的LVGL版本是V8.2。3，需要准备一张TF卡，将A盘资料的SD卡根目录文件复制到TF卡根目录当中，SD卡根目录文件如下图所示。图64.1.1 拷贝资料到TF卡当中图64.1.2展示的是LVGL例程界面所用到的 bin 文件。LVGL 综合例程会将这些bin文件拷贝到16MB Flash分区表的storage子分区表备份，方便GUI界面读取。如果直接从TF卡中读取，速度会比较慢，影响 GUI 的流畅性。图 64.1.2 LVGL例程界面所用到的bin文件64.2 LVGL 综合例程界面展示图64.2.1 GUI主界面和视频播放器界面图64.2.2 图片浏览界面和计算机界面由于DNESP32S3开发板的2.4寸TFTLCD显示屏未具备触摸条件，所以作者只能实现一些简单的APP应用。

什么是三极管？
type: item
date: 2026-01-21T10:30:50+08:00
description: 大家好，我是良许说到三极管，可能很多刚入门的朋友会觉得这个名字有点陌生，但如果你接触过电子电路或者嵌入式开发，那你一定见过它的身影。三极管可以说是电子世界里最基础、最重要的元器件之一，几乎所有的电子设备里都能找到它的踪迹。今天咱们就来聊聊三极管到底是什么，它有什么用，以及在实际开发中我们该怎么使用它。1. 三极管的基本概念1.1 三极管是什么三极管，全称叫做"半导体三极管"，英文名是 Transistor，有时候也叫做晶体管。从名字就能看出来，它有三个电极，这也是"三极管"名字的由来。这三个电极分别叫做：基极（Base，简称 B）、集电极（Collector，简称 C）和发射极（Emitter，简称 E）。三极管本质上是一种半导体器件，它是由两个 PN 结组成的。根据这两个 PN 结的排列方式不同，三极管可以分为 NPN 型和 PNP 型两种。NPN 型就是中间是 P 型半导体，两边是 N 型半导体；PNP 型则相反，中间是 N 型半导体，两边是 P 型半导体。在实际应用中，NPN 型三极管使用得更多一些。1.2 三极管的工作原理三极管最神奇的地方在于，它可以用一个很小的电流去控制一个很大的电流。具体来说，就是通过控制基极和发射极之间的电流（基极电流，记作IB​），来控制集电极和发射极之间的电流（集电极电流，记作IC）。这个过程就像是用一个小水龙头去控制一个大水龙头的开关一样。这里有一个很重要的参数，叫做电流放大倍数，用希腊字母β（贝塔）来表示。这个β值表示的是集电极电流和基极电流的比值，也就是：$$ \beta = \frac{I_C}{I_B} $$一般来说，普通三极管的β值在几十到几百之间。比如说，如果一个三极管的β值是 100，那么当基极电流是 1mA 的时候，集电极电流就可以达到 100mA。这就是三极管的放大作用。1.3 三极管的三种工作状态三极管在电路中有三种基本的工作状态：截止状态、放大状态和饱和状态。截止状态：当基极电流为零或者很小的时候，三极管就处于截止状态。这时候集电极电流也基本为零，三极管相当于一个断开的开关。放大状态：当基极电流在一个合适的范围内时，三极管就工作在放大状态。这时候集电极电流和基极电流成正比关系，也就是IC​=β×IB。这个状态主要用于模拟电路中的信号放大。饱和状态：当基极电流足够大的时候，三极管就进入了饱和状态。这时候集电极电流不再随基极电流的增加而增加，三极管相当于一个闭合的开关。在数字电路中，我们经常让三极管工作在饱和状态或截止状态，用来实现开关功能。2. 三极管的实际应用2.1 三极管作为开关使用在嵌入式开发中，我们最常用三极管来做的事情就是当开关用。比如说，STM32 的 GPIO 口输出电流一般只有几十毫安，如果我们要驱动一个需要几百毫安电流的负载（比如继电器、电机等），直接用 GPIO 口是不行的，这时候就需要用三极管来做电流放大。举个具体的例子，假设我们要用 STM32 控制一个 12V 的继电器，这个继电器的线圈电流是 100mA。我们可以这样设计电路：用 STM32 的 GPIO 口控制三极管的基极，三极管的集电极接继电器线圈，发射极接地。当 GPIO 口输出高电平时，三极管导通，继电器得电工作；当 GPIO 口输出低电平时，三极管截止，继电器断电。下面是一个简单的 HAL 库代码示例：// 初始化GPIO void Relay_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; // 使能GPIOA时钟 __HAL_RCC_GPIOA_CLK_ENABLE(); // 配置PA5为输出模式 GPIO_InitStruct.Pin = GPIO_PIN_5; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出 GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); // 初始状态设为低电平，继电器断电 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET); } ​ // 控制继电器开 void Relay_On(void) { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET); } ​ // 控制继电器关 void Relay_Off(void) { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET); }在这个应用…

UART、RS232、RS485的区别
type: item
date: 2026-01-19T10:03:44+08:00
description: 大家好，我是良许。在嵌入式开发中，串口通信是我们最常用的通信方式之一。但很多初学者经常会被 UART、RS232、RS485 这几个概念搞混，不清楚它们之间到底有什么区别和联系。今天我就来详细聊聊这三者的区别，帮助大家彻底理解这些概念。1. 基本概念解析1.1 UART 是什么UART（Universal Asynchronous Receiver/Transmitter）的中文名称是通用异步收发器，它本质上是一种通信协议和硬件电路。UART 定义了数据如何在设备之间进行串行传输，包括数据格式、传输速率、起始位、停止位等。简单来说，UART 是一种逻辑层面的协议标准。它规定了数据帧的格式，比如一个标准的 UART 数据帧通常包含：1 个起始位（低电平）、5 到 8 个数据位、可选的校验位、1 到 2 个停止位（高电平）。在我们的 STM32 单片机中，UART 就是芯片内部集成的一个硬件模块，负责将并行数据转换为串行数据发送出去，或者将接收到的串行数据转换为并行数据。UART 通信只需要两根线：TX（发送）和 RX（接收），再加上一根地线 GND。1.2 RS232 是什么RS232 是由美国电子工业协会（EIA）制定的一种物理层标准，全称是 EIA-RS-232。它定义了数据终端设备（DTE）与数据通信设备（DCE）之间的物理接口标准，包括电气特性、机械特性、功能特性等。RS232 最重要的特点是它的电平标准：逻辑 1（MARK）的电压范围是-15V 到-3V，逻辑 0（SPACE）的电压范围是 +3V 到 +15V。注意，这个电平标准和我们单片机的 TTL 电平（0V 和 3.3V 或 5V）是完全不同的。RS232 通常使用 DB9 或 DB25 接口，最大传输距离约为 15 米，最大传输速率一般不超过 20kbps（理论上可以更高，但实际应用中受限于线缆长度和质量）。1.3 RS485 是什么RS485 同样是一种物理层标准，它是 RS232 的改进版本。RS485 采用差分信号传输方式，使用两根线（A 和 B）来传输数据，通过两根线之间的电压差来表示逻辑 0 和 1。RS485 的主要优势包括：传输距离可达 1200 米，传输速率可达 10Mbps（短距离下），支持多点通信（最多可以连接 128 个设备），抗干扰能力强。由于采用差分信号，RS485 在工业环境中的应用非常广泛。2. 三者之间的关系理解了基本概念后，我们来看看它们之间的关系。简单来说：UART 是协议层，RS232 和 RS485 是物理层。这就好比我们说话时，UART 定义了"说什么"（语言规则），而 RS232 和 RS485 定义了"怎么说"（声音的大小、传播方式）。一个完整的串口通信系统，既需要 UART 协议来组织数据，也需要 RS232 或 RS485 这样的物理层标准来实际传输数据。在实际应用中，我们的单片机 UART 输出的是 TTL 电平信号（比如 0V 和 3.3V），如果要通过 RS232 接口通信，就需要使用电平转换芯片（如 MAX232）将 TTL 电平转换为 RS232 电平；如果要通过 RS485 通信，就需要使用 RS485 收发器芯片（如 MAX485）进行转换。3. 详细对比分析3.1 电气特性对比从电气特性来看，三者有明显的区别：UART（TTL 电平）：逻辑 1 通常是 3.3V 或 5V，逻辑 0 是 0V。这是单片机内部直接使用的电平标准，驱动能力弱，抗干扰能力差，只适合板级通信。RS232：采用负逻辑，逻辑 1 是-3V 到-15V，逻辑 0 是 +3V 到 +15V。这种较大的电压摆幅提供了一定的抗干扰能力，但功耗相对较高。RS232 是单端信号传输，容易受到共模干扰的影响。RS485：采用差分信号传输，两根线之间的电压差大于 +200mV 表示逻辑 1，小于-200mV 表示逻辑 0。差分传输的最大优势是抗共模干扰能力强，即使两根线同时受到相同的干扰，只要它们之间的电压差保持不变，就不会影响数据传输。3.2 传输距离和速率对比在实际应用中，传输距离和速率是我们选择通信方式的重要考虑因素：UART（TTL 电平）：传输距离非常有限，一般不超过 1 米，速率可以很高，但受限于线缆和驱动能力。在 PCB 板上的芯片间通信非常合适。RS232：标准规定最大传输距离为 15 米，但在实际应用中，如果降低波特率，可以达到更远的距离。比如在 9600bps 的速率下，可以传输 30 米甚至更远。但随着距离增加，信号衰减和干扰会导致通信质量下降。RS485：这是三者中传输距离最远的，标准距离可达 1200 米。而且 RS485 的传输速率和距离是可以权衡的：短距离下可以达到 10Mbps，而在最大距离 1200 米时，…