STM32串口详解

一、什么是串口?

在这里插入图片描述串行接口(Serial Port),简称串口,是一种异步串行通信接口。它通过单一数据线将数据逐位顺序传输,实现设备间的数据交换。

想象一下两个说不同语言的人如何交流?他们需要一个翻译官。在电子世界里,串口(Serial Port) 就扮演着这个角色!

通信协议的意义如同两人通过声波交流需要遵循共同的语言规则一样,设备间通信也需要统一的"语言规则"——这就是通信协议。串口就是这种规范化的通信方式,解决了原始IO控制中时序、同步、容错等复杂问题。

解决三大头疼问题:

时序问题 → 约定好"说话节奏"(波特率)

同步问题 → 明确"谁先说谁后说"(通信协议)

容错问题 → 设置"你听清了吗"确认机制(校验位)

二、串口的作用与特点主要作用: 设备互联:实现计算机与外部设备(如单片机、传感器、调制解调器等)的数据交换

远距离通信:支持较长距离的可靠数据传输

调试接口:广泛应用于嵌入式系统调试和程序烧录

核心特点: ✅ 成本低廉:硬件结构简单,实现成本低

✅ 线路简洁:最少只需3条线(发送、接收、地线)

✅ 易于使用:编程接口简单,开发便捷

✅ 可靠性高:具备完善的错误检测机制

三、通信方式对比串行通信:精打细算的"单车快递"定义:数据在单条数据线上按时间顺序逐位传输

通俗解释:数据像单车送货,一次只送一件,按顺序送达

特点:

-特点:

只需要一条"小路"(传输线少)

适合"长途运输"(长距离成本低)

可利用现有"公路网"(电话网络)

管理稍微复杂(控制逻辑相对复杂)

先送小件(低位LSB),后送大件(高位MSB)

在这里插入图片描述并行通信:财大气粗的"货车车队"定义:使用多条数据线同时传输多个数据位

通俗解释:数据像货车车队,多辆车同时出发送货

特点:

送货速度快

管理简单直接

"修路成本"高(线缆多)

“长途运输"容易"掉队”(同步困难)

在这里插入图片描述对比总结特性

串行通信

并行通信

传输线数量

少(1-3条)

多(8条以上)

传输速度

相对较慢

相对较快

成本

抗干扰能力

适用距离

远距离

短距离

控制复杂度

较高

较低

现阶段绝大部分的通讯口都使用串口。

四、通信协议基础通信模式1.单工通信:广播电台 只能单向说话,不能接收

如:广播、电视信号

在这里插入图片描述2.半双工通信:对讲机 可以说也可以听,但不能同时进行

需要说"完毕"才能切换

在这里插入图片描述3.全双工通信:电话聊天 可以边说边听,双向同时进行

51单片机串口就是这种模式

在这里插入图片描述五、关键技术参数串口电平标准:音量的"大小" TTL电平:+3V~+5V表示1,0V表示0(单片机常用)

RS232电平:-3-15V表示1,+3+15V表示0(计算机串口)

RS485电平:两线压差+2+6V表示1,-2-6V表示0(工业现场)

串口波特率:说话的"语速"定义:单位时间内传输的二进制位数,决定通信速度

常见语速:

慢速:300、1200字/分钟

常速:2400、9600字/分钟

快速:19200、38400字/分钟

高速:115200、230400字/分钟

计算示例:

波特率9600:1秒传输9600位,每位时间=1/9600≈104.17μs

波特率115200:传输速度更快,每位时间约8.68μs

在这里插入图片描述数据帧格式:说话的"语法规范"一个完整的数据帧包含:

代码语言:javascript复制[起始位] + [数据位] + [校验位] + [停止位]起始位:1位低电平,标志传输开始

数据位:5~9位实际数据,通常8位(1字节)

校验位:1位,用于错误检测

停止位:1~2位高电平,标志传输结束

在这里插入图片描述校验方式详解 无校验(N):8位数据位,无校验位

奇校验(O):数据位中"1"的个数为奇数时,校验位为0,否则为1

偶校验(E): 数据位中"1"的个数为偶数时,校验位为0,否则为1

标记校验(M):校验位固定为1

空校验(S):校验位固定为0

在这里插入图片描述空闲位 不属于数据帧部分

传输间隔期间总线保持高电平

标志当前无数据传输

在这里插入图片描述 LSB(Least Significant Bit)则是低地址存放最低有效字节

MSB(Most Significant Bit)是指低地址存放最高有效字节

案例:例子1:通过串口发送十进制数字 27

代码语言:javascript复制二进制:00011011

传输顺序:起始位(0) + 11011000(数据位,LSB优先) + 校验位 + 停止位(1)例子2:通过串口发送字符串 “hello”

代码语言:javascript复制ASCII编码:

h → 01101000 → 起始位 + 00010110 + 校验 + 停止位

e → 01100101 → 起始位 + 10100110 + 校验 + 停止位

l → 01101100 → 起始位 + 00110110 + 校验 + 停止位

l → 01101100 → 起始位 + 00110110 + 校验 + 停止位

o → 01101111 → 起始位 + 11110110 + 校验 + 停止位六、开发工具准备串口调试助手功能:用于数据收发的调试和监控

在这里插入图片描述USB转串口(TTL)模块:功能:实现USB接口到TTL电平串口的转换

在这里插入图片描述逻辑分析仪:功能:用于信号波形的捕获和分析

在这里插入图片描述七、STM32串口应用硬件接线示意图在这里插入图片描述USART模块简介STM32单片机内置多个USART(Universal Synchronous/Asynchronous Receiver/Transmitter)模块,支持全双工异步通信。

在这里插入图片描述主要特性: 支持同步/异步通信

全双工操作

可配置数据位(8/9位)

可编程校验位

多个中断源

DMA支持

在这里插入图片描述 USART = 嘴巴 + 耳朵,既能说又能听!

USART模块的基本使用方法:USART模块的初始化 → 配置波特率 + 数据帧格式

代码语言:javascript复制//USART1 波特率115200、8位数据位、无校验、1位停止位

#include "stm32f10x.h"

int main(void)

{

// 对 USART1 初始化

RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); // 开启时钟

// 配置相关参数 -> 配置波特率 + 数据帧格式

USART_InitTypeDef USART_InitStruct = {0};

USART_InitStruct.USART_BaudRate = 115200; // 波特率 115200

USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Tx; // 全双工

USART_InitStruct.USART_WordLength = USART_WordLength_8b; // 8位 数据位

USART_InitStruct.USART_Parity = USART_Parity_No; // 无校验

USART_InitStruct.USART_StopBits = USART_StopBits_1; // 1位 停止位

USART_Init(USART1, &USART_InitStruct); // 完成初始化

// 使能 USART1

USART_Cmd(USART1, ENABLE);

} 这一步就相当于:

是否让嘴巴说、是否让耳朵听

让嘴巴怎么说、让耳朵怎么听

为串口初始化IO引脚引脚配置问题核心问题: 串口的引脚在哪里?

怎么设置引脚的参数(模式、速度)?

在这里插入图片描述USART模块引脚分布及配置1.引脚分布表在这里插入图片描述 查看那些IO可以作为嘴巴或耳朵

2.IO配置表在这里插入图片描述 赋予嘴巴、耳朵基本的说、听机能

3.引脚初始化代码代码语言:javascript复制// 默认PA9、PA10引脚配置

void USART1_GPIO_Init(void)

{

GPIO_InitTypeDef GPIO_InitStructure;

// 开启GPIOA时钟

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

// 配置TX引脚(PA9)为复用推挽输出

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 高速

GPIO_Init(GPIOA, &GPIO_InitStructure);

// 配置RX引脚(PA10)为浮空输入

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 浮空输入

GPIO_Init(GPIOA, &GPIO_InitStructure);

}

// 重映射到PB6、PB7引脚配置

void USART1_GPIO_Remap_Init(void)

{

GPIO_InitTypeDef GPIO_InitStructure;

// 开启GPIOB和AFIO时钟

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);

// 使能USART1重映射

GPIO_PinRemapConfig(GPIO_Remap_USART1, ENABLE);

// 配置TX引脚(PB6)为复用推挽输出

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_Init(GPIOB, &GPIO_InitStructure);

// 配置RX引脚(PB7)为浮空输入

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;

GPIO_Init(GPIOB, &GPIO_InitStructure);

}使用串口发送数据串口通信流程空闲状态:

数据线保持高电平(逻辑1)

表示当前无数据传输

开始传输:

起始位:一个低电平脉冲(逻辑0)

标志数据传输开始

数据传输:

数据位按顺序逐位传输(先低位后高位)

数据位长度可配置(通常5-9位)

结束传输:

停止位:高电平脉冲(逻辑1)

标志本次传输结束

传输能力:

每次传输一个完整的字节(8位)或字符数据发送过程 CPU将数据写入发送寄存器

USART将数据写入移位寄存器

通过移位寄存器将数据逐位发送

循环往复

在这里插入图片描述关键标志位标志位是什么?我们可以通过这些标志位的值获取USART的工作状态

我们可以把它看作说话的进度条

TXE标志位:话准备好了吗? Transmit Data Register Empty

判断发送数据寄存器是否为空

当TDR空时,TxE = 1(可以准备下一句话)

否则 TxE = 0(还在准备中)

TC标志位:话说完了吗? Transmit Complete

判断数据是否发送完成

当TDR空且移位寄存器为空时,TC = 1(说完了)

否则 TC = 0(还在说)

在这里插入图片描述编程接口FlagStatus

作用:查询USART标志位的值,返回值:RESET-0;SET-1

代码语言:javascript复制// 查询USART标志位的值

FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG);

// 返回值:RESET-0;SET-1USART_SendData

作用:把要发送的数据写入到发送数据寄存器里

代码语言:javascript复制// 发送数据到数据寄存器

void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);

// 参数为uint16_t:为了支持9位数据位实时发送函数代码语言:javascript复制/**

* @brief 使用串口发送多个字节

*

* @param USARTx 指定发送串口

* @param pData 要发送的数据

* @param Size 要发送的字节的数

*/

void USART_Send_Bytes(USART_TypeDef* USARTx, uint8_t* pData, uint16_t Size)

{

for(uint32_t i = 0; i < Size; i++)

{

// #1. 等待发送数据寄存器空

while(USART_GetFlagStatus(USARTx, USART_FLAG_TXE) == RESET);

// #2. 将要发送的数据写入到发送数据寄存器

USART_SendData(USARTx, pData[i]);

}

// #3. 等待数据发送完成

while(USART_GetFlagStatus(USARTx, USART_FLAG_TC) == RESET);

}格式化打印字符串格式化字符串的编程原理在这里插入图片描述1.生成格式化字符串

在这里插入图片描述2.通过fputc发送到控制台

在这里插入图片描述重写fputc函数代码语言:javascript复制#include

// 重定向printf到串口

int fputc(int ch, FILE *f)

{

// #3. 等待发送寄存器为空

while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);

// #2. 发送字符

USART_SendData(USART1, (uint8_t)ch);

return ch;

}

// 重定向scanf从串口输入(可选)

int fgetc(FILE *f)

{

// #1. 等待接收到数据

while(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET);

// #2. 返回接收到的字符

return (int)USART_ReceiveData(USART1);

} 重写fputs函数其实就是给fputs函数输出重定向,使其发送目标由控制台变为串口

格式化输出时间字符串代码语言:javascript复制#include

#include

// 格式化输出时间信息

void Print_TimeInfo(void)

{

// 获取系统运行时间(需要自己实现计时功能)

uint32_t system_time = GetSystemTick();

uint32_t hours = system_time / 3600000;

uint32_t minutes = (system_time % 3600000) / 60000;

uint32_t seconds = (system_time % 60000) / 1000;

uint32_t milliseconds = system_time % 1000;

printf("系统运行时间: %02lu:%02lu:%02lu.%03lu\r\n",

hours, minutes, seconds, milliseconds);

}使用串口接收数据数据接收过程 数据通过RX引脚逐位接收

数据存入接收移位寄存器

当完整字节接收完成后,数据转移到接收数据寄存器(RDR)

触发RXNE标志位

关键标志位RxNE标志位:听到新内容了吗? Receive Data Register Not Empty

判断接收数据寄存器是否为空

当RDR非空时,RxNE = 1(有新消息)

否则RxNE = 0(没听到什么)

可以把他当作听力的状态

编程接口USART_ReceiveData

作用:从接收数据寄存器读取数据

代码语言:javascript复制uint16_t USART_ReceiveData(USART_TypeDef* USARTx);

// 作用:从接收数据寄存器读取数据

// 返回uint16_t:为了支持9位数据位错误标志位说明PE:Parity Error - 奇偶校验错

如果接收到的数据有校验错误,则PE = 1;否则 PE = 0

FE:Frame Error - 帧格式错误

接收到了无效的数据帧,则FE = 1;否则FE = 0

NE:Noise Error - 噪声错

接收的数据中检测到了噪声,则NE = 1;否则NE = 0

ORE:Overrun Error - 过载错

由于过载造成了数据丢失,则ORE = 1;否则ORE = 0