CH340串口通信详解与实操(stm32)
从实验中可以总结出串口与DMA。
一. 基础理论知识
1. 串口协议和RS-232标准
| 内容 | 串口协议(UART) | RS-232 标准 |
|---|---|---|
| 全称 | Universal Asynchronous Receiver/Transmitter(通用异步收发传输器) | Recommended Standard 232(推荐标准232) |
| 通信方式 | 异步串行通信(无需时钟线) | 支持异步串行通信(物理层支持) |
| 数据同步方式 | 发送端与接收端通过预先约定的波特率进行同步 | 依赖 UART 协议实现数据同步 |
| 典型参数配置 | 波特率(如 9600、115200 bps) 数据位:8 位 停止位:1 位 校验位:无 / 奇 / 偶 |
不定义具体参数,但常配合 UART 使用上述配置 |
| 电气特性 | 无固定电平标准,通常使用 TTL 电平(0V/3.3V 或 0V/5V) | 定义明确电平: 逻辑“1”:-3V ~ -15V 逻辑“0”:+3V ~ +15V(负逻辑) |
| 接口类型 | 无固定接口,常以引脚形式出现在电路板上 | 定义机械接口:常用 DB9 或 DB25 连接器 |
| 传输距离 | 短距离(< 1 米,板级通信) | 中短距离,最长约 15 米(受电缆质量影响) |
| 主要用途 | 嵌入式系统内部通信、MCU 与外设通信 | 工业设备、老式计算机串口、调试接口等长距离通信场景 |
| 是否需要电平转换 | 直接连接 MCU,无需转换 | 必须通过 MAX232 等芯片将 TTL 转为 RS-232 电平才能连接 |
2.RS232电平与TTL电平的区别:
| 项目 | TTL 电平 | RS-232 电平 |
|---|---|---|
| 逻辑“0” | 0 ~ 0.8V | +3V ~ +15V |
| 逻辑“1” | 2.4 ~ 5V(或3.3V系统为1.8~3.3V) | -3V ~ -15V |
| 供电电压 | 3.3V / 5V | ±12V(需专用电源) |
| 传输距离 | 短(板级) | 较长(可达15m) |
| 接口芯片 | 直接连接MCU | 需 MAX232/SP3232 等电平转换 |
✅ 关键点:TTL 是数字电路内部使用的电平;RS-232 是用于远距离通信的负逻辑高压电平。
✅ 补充说明:
- RS-232 是物理层标准,规定了电气特性;而 UART 是协议层,定义了数据帧格式。
- TTL 电平不能直接连接到 RS-232 设备,必须通过 MAX232、SP3232 等电平转换芯片 进行转换。
- 常见的“USB转串口模块”(如 CH340 + MAX232)正是实现 USB → TTL → RS-232 的桥梁。
3. USB/TTL转RS232 模块(以 CH340 模块为例)工作原理
“USB/TTL转232”模块(以CH340芯片为例)的核心作用是解决不同接口和电平的兼容性问题,让电脑的USB接口能与RS232设备(如老式仪表、打印机)或TTL设备(如单片机)通信。其工作原理可拆解为“协议转换”和“电平转换”两步,具体如下:
(1)模块核心组成
- CH340芯片:USB转TTL的“协议转换器”,负责将USB信号转为TTL电平的UART串口信号(反之亦然)。
- 电平转换电路(如MAX232芯片):负责将TTL电平与RS232电平互相转换(因两者电平规则完全不同,无法直接通信)。
- 辅助电路:电源(从USB取5V电)、指示灯(显示工作状态)等。
(2)工作流程(以“电脑→RS232设备”为例)
-
电脑USB信号→CH340→TTL信号:
电脑通过USB线发送的是“USB协议差分信号”(电脑通用接口),进入模块后先被CH340芯片处理——CH340会将USB协议转换为单片机常用的“TTL电平UART信号”(即 TX_TTL(发送)、RX_TTL(接收)、GND(地线),电平规则:1对应3.3V/5V,0对应0V)。 -
TTL信号→MAX232→RS232信号:
由于RS232设备只认“RS232电平”(1对应-3-15V,0对应+3+15V),TTL信号需经MAX232芯片转换:将TTL的3.3V/5V转为RS232的+3+15V(逻辑0),将TTL的0V转为RS232的-3-15V(逻辑1),最终输出符合RS232标准的信号给外部设备。
(3)反向流程(RS232设备→电脑)
RS232设备发送的-3-15V(逻辑1)和+3+15V(逻辑0)信号,先经MAX232转换为TTL电平(3.3V/5V和0V),再由CH340将TTL的UART信号转回USB协议信号,最终通过USB线传给电脑。
(4)信号流向(PC → 外部设备):
USB 协议⇌TTL 电平 UART(CH340 负责) → TTL 电平⇌RS232 电平(MAX232 负责)
功能作用:
- 实现现代无串口电脑与传统RS-232设备的连接。
- 提供虚拟COM端口(在Windows中识别为COMx)。
- 支持常见波特率(如9600, 115200等)。
二. 串口传输文件实验
串口传输文件的练习。将两台笔记本电脑,借助 usb转rs232 模块和杜邦线,建立起串口连接。然后用串口助手等工具软件(带文件传输功能,如:sscom)将一台笔记本上的一个大文件(图片、视频和压缩包软件)传输到另外一台电脑,预算文件大小、波特率和传输时间三者之间的关系,并对比实际传输时间。
1、硬件连接方式
使用两台笔记本电脑,通过以下方式建立串口连接:


2、下载sscom
3、串口设置

4、发送与接受
选择下面这张图片发送

接收方:
将收到的文件在画图中打开:
5、时间计算
通信参数
波特率 (Baud Rate) 115200 bps
数据位 (Data Bits) 8
停止位 (Stop Bits) 1
校验位 (Parity) None
起始位 (Start Bit) 1
每个字节实际占用 10 位(1 起始 + 8 数据 + 1 停止)
理论传输时间计算公式:
总传输时间 = 文件大小(字节) × 10 波特率(bps) \text{总传输时间} = \frac{\text{文件大小(字节)} \times 10}{\text{波特率(bps)}} 总传输时间=波特率(bps)文件大小(字节)×10
代入数据:
时间 = 1916270 × 10 115200 = 19162700 115200 ≈ 166.33 秒 \text{时间} = \frac{1916270 \times 10}{115200} = \frac{19162700}{115200} \approx 166.33\,\text{秒} 时间=1152001916270×10=11520019162700≈166.33秒
理论预估时间 ≈ 166.33 秒
实际传输时间(来自 SSCOM)
从截图中看到:
文件大小:1916270 字节
波特率:115200 bps
实际时间:大约 173.48500610352 秒
实际 vs 理论 173.49 - 166.33 = 7.16 秒 主要由于软件处理延迟和“分包延时”
SSCOM 中的“每发送256字节已插入1ms延时”
SSCOM 软件本身加入了人为延时!
计算额外延时:
文件大小:1,916,270 字节
每 256 字节加 1ms 延时
总共分包数:
⌊ 1916270 256 ⌋ = 7485 包 \left\lfloor \frac{1916270}{256} \right\rfloor = 7485 \text{ 包} ⌊2561916270⌋=7485 包
每包加 1ms → 总额外延时:
7485 × 1 ms = 7.485 秒 7485 \times 1\,\text{ms} = 7.485\,\text{秒} 7485×1ms=7.485秒
👉 这与实际多出的 7.16 秒非常接近!
6、拓展
如果只接TX–RX, RX–TX 这样两根线,不接电源线或者不接GND地线,文件传输无法正常进行。
分析原因:
不接 GND(地线)时,两个设备没有共同的参考电压。
发送方说“高电平是 3.3V”,是相对于它的地;
接收方却用自己的地来判断,两者地电位可能不同。
结果:
接收方看不懂发送方的信号,导致通信失败或数据出错。
🔌 就像打电话,有声音线但没接地,声音就不清楚。
所以,TX、RX、GND 三根线必须都接,才能正常通信。
三. STM32CubeMX + Keil 使用 HAL 库配置 USART1
1、前期准备
开发环境搭建
-
安装 STM32CubeMX
- 下载地址:https://www.st.com/en/development-tools/stm32cubemx.html
- 安装后用于生成初始化代码。
-
安装 Keil MDK-ARM(推荐版本 5.x 或以上)
-
安装stm32的 HAL库支持包
硬件准备
- 使用STM32F103C8T6开发板
- 连接 CH340模块
- 接线:
- TX→PA10,RX→PA9
- STM32 GND → USB-TTL GND
2、使用 STM32CubeMX 配置工程
步骤1:创建新项目
1 打开 STM32CubeMX,并点击

2. 选择芯片:STM32F103C8T6,收藏后点击右上角Start Project
3.在RCC中选择外部高速晶振(只有选择了这个之后才能在后续配置时钟树)
4. 在SYS中选择调试接口
注:如果不选择的话就只能下载一次代码,后续下载之后可能需要点击复位键,比较麻烦
为了增强大家对后续操作的理解,这里简单补充关于时钟树的相关知识
当然可以!以下是极简版 STM32 时钟树,用一张简单表格告诉你所有关键点,适合初学者快速理解:
| 名称 | 是什么 | 频率 | 用途 |
|---|---|---|---|
| HSI | 内部时钟(自带) | 8 MHz | 开机默认,不用外接 |
| HSE | 外部晶振(小水晶) | 8 MHz | 精准时钟,用于主系统 |
| PLL | 锁相环(倍频器) | 72 MHz | 把 8MHz 放大到 72MHz |
| SYSCLK | 主时钟(系统心跳) | 最高 72 MHz | 决定 CPU 跑多快 |
| HCLK | CPU 时钟 | 通常 = SYSCLK(72MHz) | 给 CPU 和内存用 |
| PCLK1 | 低速外设时钟 | 通常 36 MHz | 给 I2C、UART、TIM2/3/4 等 |
| PCLK2 | 高速外设时钟 | 通常 72 MHz | 给 GPIO、USART1、TIM1 等 |
| LSE | 外部低速晶振 | 32.768 kHz | 给“闹钟”(RTC)用 |
| LSI | 内部低速时钟 | ~40 kHz | 给看门狗(IWDG)用 |
其中PLL为锁相环倍频输出,其时钟输入源可选择为HSI/2、HSE或者HSE/2。倍频可选择为2~16倍,但是其输出频率最大不得超过72MHz。 通过倍频之后作为系统时钟的时钟源。
所以我们后续选择HSE ,然后PLL进行倍频
步骤2:配置时钟(Clock Configuration)
1、进入 Clock Configuration 标签页
- 设置 HSE 为 Crystal/Ceramic Resonator
- 将系统时钟(SYSCLK)设置为 72MHz(典型值)
- HCLK = 72MHz
- PCLK1 = 36MHz, PCLK2 = 72MHz

步骤 3:配置 USART1
选择USART1进行传输,将串口设置为异步工作模式
- 在 Connectivity → USART1 中:

步骤 4:启用中断
- 在 NVIC Settings 标签页中:
- ✅ USART1 global interrupt

- ✅ USART1 global interrupt
步骤 5:生成代码
-
Project Manager → 设置项目名、路径、工具链为
MDK-ARM V5
-
Code Generator Options:
- 勾选
Generate peripheral initialization as a pair of '.c/.h' files per peripheral
- 勾选
-
点击右上角
Generate Code生成代码

3、Keil 工程修改与代码实现
打开生成的工程,在 main.c 文件中进行如下修改:
✅ (1):连续发送“hello windows!”并测试是否丢包
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "usart.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER INCLUDE END */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* Global variables */
uint8_t send_data[] = "hello windows!\r\n";
uint8_t rx_data; // 用于接收中断
volatile uint8_t send_pause = 0; // 控制发送暂停标志
/**
* @brief The application entry point.
*/
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
/* Enable UART interrupt for RX */
HAL_UART_Receive_IT(&huart1, &rx_data, 1);
/* USER CODE BEGIN WHILE */
while (1)
{
if (!send_pause)
{
HAL_UART_Transmit(&huart1, send_data, sizeof(send_data) - 1, HAL_MAX_DELAY);
// 注意:这里没有加延时
}
}
}
结果如下:
串口无时延
➤仿真分析:
检测:
USART1_SR 寄存器的 RXNE 位 的变化情况
分析:
第一次上升沿 RXNE = 1 发送了一个字节
第二次上升沿 RXNE = 1 再次发送一个字节
第三次上升沿 RXNE = 1 第三次发送
3 个 RXNE 脉冲,以下原因如下:
接收中断触发条件
HAL_UART_Receive_IT() 设置的是 单字节接收中断。
每当收到一个字节,就会触发一次中断,并将 RXNE 标志置 1。
中断处理完成后,RXNE 自动清零(由硬件自动清除)。
(2):上位机控制 STM32 发送启停
功能描述:
- 上位机发
#→ 停止发送 - 上位机发
*→ 继续发送
✅ 添加串口中断回调函数(处理接收命令)
在 main.c 中添加以下函数(放在 main() 后面即可):
/**
* @brief UART 回调函数
*/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART1)
{
if (rx_data == '#')
{
send_pause = 1; // 暂停发送
}
else if (rx_data == '*')
{
send_pause = 0; // 恢复发送
}
// 重新开启下一次中断接收
HAL_UART_Receive_IT(&huart1, &rx_data, 1);
}
}
效果如下:
串口阻塞
四. 使用中断和 DMA 方式实现串口通信
我们继续深入学习 STM32 的中断与 DMA 通信机制,使用 STM32CubeMX + HAL 库 + Keil 完成以下两个任务:
1、采用串口中断方式重做任务二(2)
目标:用 中断方式接收上位机命令(
#暂停,*继续),并用 中断方式发送数据 替代阻塞发送。
为什么使用中断?
- 避免
HAL_UART_Transmit()阻塞 CPU - 提高系统响应性和效率
- 实现“后台发送 + 实时响应接收”
步骤 1:CubeMX 配置
确保已配置:
- USART1 异步模式,115200, 8N1
- ✅ 使能 USART1 全局中断(NVIC)
- ✅ 初始化时已调用
HAL_UART_Receive_IT()和HAL_UART_Transmit_IT()
CubeMX 中无需额外设置,只需在代码中启用中断传输。
步骤 2:完整代码(main.c)
/* Includes */
#include "main.h"
#include "usart.h"
#include "gpio.h"
/* Private variables */
extern UART_HandleTypeDef huart1;
uint8_t send_data[] = "hello windows!\r\n";
uint8_t rx_data;
volatile uint8_t send_pause = 0;
volatile uint8_t tx_complete = 1; // 标志:上一次发送完成
void SystemClock_Config(void);
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
// 启动串口接收中断(单字节循环接收)
HAL_UART_Receive_IT(&huart1, &rx_data, 1);
while (1)
{
// 非阻塞发送控制
if (!send_pause && tx_complete)
{
tx_complete = 0;
HAL_UART_Transmit_IT(&huart1, send_data, sizeof(send_data) - 1);
// 不加延时,靠中断触发下一次发送
}
}
}
/**
* @brief UART 发送完成回调函数
*/
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART1)
{
tx_complete = 1; // 发送完成,可触发下一次
}
}
/**
* @brief UART 接收完成回调函数
*/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART1)
{
if (rx_data == '#')
{
send_pause = 1;
}
else if (rx_data == '*')
{
send_pause = 0;
// 若暂停后恢复,尝试立即触发一次发送
if (tx_complete)
{
tx_complete = 0;
HAL_UART_Transmit_IT(&huart1, send_data, sizeof(send_data) - 1);
}
}
// 必须重新启动接收中断!!
HAL_UART_Receive_IT(&huart1, &rx_data, 1);
}
}
效果如下:
串口中断非阻塞
2、使用DMA 方式连续向上位机高速发送数据
目标:利用 DMA 实现 零 CPU 干预 的连续串口发送,支持 115200 或更高速率(如 921600、2Mbps)
为什么使用 DMA?
- 发送大量数据时不占用 CPU
- 支持高波特率下稳定传输
- 适合音频、图像、日志等大数据流
步骤 1:CubeMX 配置 USART1 + DMA
-
打开 CubeMX,选择 USART1
-
在 DMA Settings 标签页中点击 Add:
- Request: USART1_TX
- Mode: Circular(循环发送)
- Inc: Memory Increment Enable
- Data Width: Byte
- Direction: Memory to Peripheral

-
NVIC Settings:
- ✅ USART1 global interrupt
-
生成代码
自动生成
MX_DMA_Init()和 DMA 相关初始化
步骤 2: 完整代码(main.c)
#include "main.h"
#include "dma.h"
#include "usart.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <string.h>
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
// 发送缓冲区(可自定义内容和长度,最大65535字节)
#define TX_BUFFER_LEN 32
uint8_t tx_buffer[TX_BUFFER_LEN] = "Hello DMA\r\n";
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
// 启动DMA连续发送(循环模式下一次启动即可持续发送)
if (HAL_UART_Transmit_DMA(&huart1, tx_buffer, TX_BUFFER_LEN) != HAL_OK)
{
Error_Handler();
}
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
// 可添加其他业务逻辑(DMA自动循环发送,无需干预)
}
/* USER CODE END 3 */
}
效果如下:
DMA
3、提高波特率的方法(支持更高速率)
在 CubeMX 中修改 USART1 波特率:
- 设置为:
921600或1000000(1Mbps)甚至2000000 - 注意:PC 端串口芯片(如 CH340、CP2102)必须支持该速率
- ✅ CP2102 支持最高 2Mbps
- ❌ CH340 最高通常 2Mbps,但部分驱动支持有限
推荐使用 CP2102 模块测试高速传输
五、 总结
从实验中可以总结出串口与DMA的核心作用,二者在数据传输中分工不同但协同工作,共同保障通信的可靠性与效率:
1、串口(UART/USART)的作用
串口是设备间数据交换的“物理链路与协议规范”,核心作用是实现不同设备(如STM32与上位机、电脑与电脑)之间的异步数据传输,具体体现为:
-
提供标准化的通信协议
定义数据传输的“规则”:包括波特率(数据传输速率,如115200bps)、数据帧格式(1位起始位、8位数据位、1位停止位、无校验位等),确保发送方与接收方能“听懂”彼此的信号(如实验中STM32与上位机需一致配置波特率才能正确解析“hello windows!”)。 -
实现电平信号的转换与传输
作为物理接口,串口通过TX(发送)和RX(接收)引脚传输电信号(TTL电平或RS232电平),将二进制数据转换为符合协议的高低电平波形(如实验中STM32的PA9引脚输出的串口波形),完成数据的物理层传输(如文件传输实验中电脑间的二进制文件数据通过串口线传递)。 -
支持双向通信与指令交互
串口可同时实现发送(TX)和接收(RX),支持设备间的双向数据交换。例如实验中“#暂停、*继续”功能:上位机通过串口发送控制指令,STM32通过串口接收并响应,体现了串口作为“指令交互通道”的作用。
2、DMA(直接存储器访问)的作用
DMA是解放CPU、提升数据传输效率的“硬件搬运工”,核心作用是在无需CPU干预的情况下,直接实现内存与外设(如串口)之间的数据传输,具体体现为:
-
减少CPU负担,提高系统效率
传统串口发送(轮询或中断方式)中,CPU需频繁参与数据搬运(如轮询等待发送完成,或中断响应处理),占用大量CPU资源。而DMA可独立完成“内存缓冲区→串口外设”的数据传输(如实验中DMA循环发送“hello windows!”时,CPU可空闲处理其他任务)。 -
支持高速率、连续数据传输
由于DMA由硬件直接控制传输,速度远快于CPU软件操作,适合高波特率(如115200bps及以上)或大量数据的连续传输。实验中DMA循环发送时,数据无间断输出,且不会因CPU忙碌导致传输延迟或丢失,保障了高速通信的稳定性。 -
简化连续传输的软件逻辑
通过配置DMA为“循环模式”,可实现数据的自动重复发送(如实验中发送缓冲区的数据发送完成后,DMA自动从头开始传输),无需CPU在主循环中重复调用发送函数,简化了连续传输的代码设计。
二者结合,既保证了通信的可靠性(串口协议),又提升了系统的效率(DMA硬件加速),是嵌入式系统中高效数据传输的经典方案。
📌 如本文对你有帮助,欢迎点赞 ✅、收藏 ⭐、关注 💡,你的支持是我持续创作的最大动力!
💬 有任何问题或建议,欢迎在评论区留言,我会一一回复!
3、参考文献
更多推荐



所有评论(0)