一、前言

处理器与外部设备通信的两种方式:

并行通信

   -传输原理:数据各个位同时传输。

   -优点:速度快

   -缺点:占用引脚资源多


串行通信

   -传输原理:数据按位顺序传输。

   -优点:占用引脚资源少

   -缺点:速度相对较慢

串行通信:
按照数据传送方向,分为:
单工:
           数据传输只支持数据在一个方向上传输
半双工:
           允许数据在两个方向上传输,但是,在某一时刻,只允许数
            据在一个方向上传输,它实际上是一种切换方向的单工通信;
全双工:
           允许数据同时在两个方向上传输,因此,全双工通信是两个
           单工通信方式的结合,它要求发送设备和接收设备都有独立
           的接收和发送能力。 
串行通信的通信方式
同步通信:带时钟同步信号传输。
    -SPI,IIC通信接口
异步通信:不带时钟同步信号。
    -UART(通用异步收发器),单总线
常见的串行通信接口:
 通讯标准 引脚说明 通讯方式 通讯方式
 UART
 (通用异步收发器)
 TXD:发送端
 RXD:接受端
 GND:公共地
 异步通信 全双工
 单总线
 (1-wire)
 DQ:发送/接受端 异步通信 半双工
 SPI SCK:同步时钟
 MISO:主机输入,从机输出
 MOSI:主机输出,从机输入
 异步通信 全双工
 I2C SCL:同步时钟
 SDA:数据输入/输出端 同步通信
 异步通信 半双工

STM32的串口通信接口:
USART框图:

UART异步通信方式引脚连接方法:
-RXD:数据输入引脚。数据接受。
-TXD:数据发送引脚。数据发送。


UART:通用异步收发器
USART:通用同步异步收发器
大容量STM32F10x系列芯片,包含3个USART和2个UART
 串口号 RXD TXD 
 串口1 PA10 PA9 
 串口2 PA3 PA2 
 串口3 PB11 PB10
 串口4 PC11  PC10 
 串口5 PD2 PC12 
UART异步通信方式特点:
全双工异步通信。
分数波特率发生器系统,提供精确的波特率。
     -发送和接受共用的可编程波特率,最高可达4.5Mbits/s
可编程的数据字长度(8位或者9位);
可配置的停止位(支持1或者2位停止位);
可配置的使用DMA多缓冲器通信。
单独的发送器和接收器使能位。
检测标志:① 接受缓冲器  ②发送缓冲器空 ③传输结束标志
多个带标志的中断源。触发中断。
其他:校验控制,四个错误检测标志。
STM32串口异步通信需要定义的参数:
 ① 起始位
 ② 数据位(8位或者9位)
 ③ 奇偶校验位(第9位)
 ④ 停止位(1,15,2位)
 ⑤ 波特率设置
9位字长、1个停止位时序示意图如下:

串口(USART)是STM32最基础、最常用的外设之一,既是调试信息的输出窗口,也是与GPS、蓝牙、WiFi等模块通信的桥梁。

很多初学者在学习串口时,往往对时钟使能、引脚复用、中断配置这几个环节感到困惑。今天,我们就以经典的正点原子战舰V3开发板为例,从零开始搭建一个串口通信实验,实现“PC发什么,STM32就回什么”的回声功能,并在中断处理中加入LED指示,让整个过程看得见、摸得着。

二、硬件准备与原理简析

开发板主角:战舰V3

战舰V3开发板的主芯片是STM32F103ZET6,这是一款拥有112个GPIO口、5个串口的强大MCU。我们本次实验使用的是串口1(USART1)。

串口1的硬件连接

在战舰V3开发板上,有两个非常重要的接口与串口1有关:

  • USB转串口接口(USB_232):通过板载的CH340G芯片,将USB信号转换为TTL电平,连接到STM32的串口1。

  • P4跳线帽接口:这是关键连接点。原理图上,CH340G的TXD/RXD与STM32的PA9(USART1_TX)/PA10(USART1_RX)正是通过这里的跳线帽连接的。
实战检查: 确保开发板上的P4跳帽是短接状态(默认即是)。这样,当用USB线连接开发板到电脑时,电脑不仅能供电,还会虚拟出一个串口,直接与STM32通信。
 引脚 功能 连接外设
 PA9 USART1_TX (发送) CH340G 的 RXD
 PA10 USART1_RX (接收) CH340G 的 TXD

三、代码编写与原理解析

我们不直接复制例程,而是手动建立一个核心逻辑。代码基于STM32标准库。

3.1 编程目标

  1. 初始化串口1,波特率设置为115200。

  2. 实现printf重定向,方便打印调试信息。

  3. 开启接收中断。当收到PC发来的数据后,立即在中断中把数据原样发回,并翻转LED0(DS0)状态作为视觉反馈。

3.2 串口初始化函数

我们需要一个独立的串口初始化函数。注意,开启中断必须配置NVIC。

// usart.c 部分代码

#include "usart.h"
#include "led.h" // 用于中断中控制LED
/**
 * @brief 串口1初始化
 * @param bound: 波特率,如 115200
 */
void uart_init(u32 bound)
{
    // GPIO和USART结构体定义
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    // 1. 使能时钟 (重要:GPIOA和USART1均挂载在APB2总线上)
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);

    // 2. 配置GPIO引脚模式
    // TX (PA9) 需要配置为复用推挽输出
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出
    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);

    // 3. 配置USART模式
    USART_InitStructure.USART_BaudRate = bound;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 8位数据
    USART_InitStructure.USART_StopBits = USART_StopBits_1;      // 1位停止位
    USART_InitStructure.USART_Parity = USART_Parity_No;        // 无奇偶校验
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 无硬件流控
    USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; // 收发模式均使能
    USART_Init(USART1, &USART_InitStructure);

    // 4. 配置中断 (接收非空中断)
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); // RXNE: 接收缓冲区非空中断

    // 5. 配置NVIC中断优先级
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;        // 串口1中断通道
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // 抢占优先级1
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;       // 子优先级1
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    // 6. 使能串口
    USART_Cmd(USART1, ENABLE);
}

3.3 中断服务函数

这是串口通信的灵魂。当串口收到数据时,硬件会自动触发此函数。

// stm32f10x_it.c 或者单独的文件中
/**
 * @brief 串口1中断服务函数
 */
void USART1_IRQHandler(void)
{
    u8 receivedData;

    // 判断中断类型是否为“接收非空”(即收到了数据)
    if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
    {
        // 1. 读取数据寄存器,这会自动清除中断挂起位
        receivedData = USART_ReceiveData(USART1);

        // 2. 将收到的数据立即发送回去 (回声功能)
        USART_SendData(USART1, receivedData);
        // 等待发送完成,防止在中断中连续发送导致数据覆盖
        while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);

        // 3. 顺便做点有趣的事情:翻转LED0 (连接在PB5,低电平点亮)
        LED0 = !LED0; // 假设 LED0 在 led.h 中宏定义为 PBout(5)
    }
}
3.4 主函数

在主函数中,我们只需要完成初始化,然后让程序空转。所有收发的逻辑都交给中断处理。

// main.c

#include "delay.h"
#include "usart.h"
#include "led.h"
int main(void)
{
    // 硬件初始化
    delay_init();        // 延时函数初始化
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 设置NVIC中断分组为2 (2位抢占,2位响应)
    LED_Init();           // 初始化LED引脚
    uart_init(115200);    // 串口1初始化,波特率115200

    // 打印提示信息 (需要配合fputc重定向)
    printf("STM32F103 串口实验 (战舰V3)\r\n");
    printf("PC发什么,我就回什么,同时LED0翻转一次\r\n");
    while(1)
    {
        // 主循环无任务,一切依赖中断
        // 这里可以干其他事情,比如延时、读取传感器等
    }
}

3.5 关于printf的重定向

为了让printf能通过串口输出,需要重写fputc函数:

// usart.c 中添加
#include <stdio.h>
// 重定向c库函数printf到串口1
int fputc(int ch, FILE *f)
{
    // 发送一个字节数据到串口1
    USART_SendData(USART1, (uint8_t)ch);
    // 等待发送完成
    while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
    return ch;
}

四、实验现象与验证

  1. 编译下载:将代码编译无误后,通过FlyMcu或者ST-Link下载到战舰V3开发板。

  2. 打开串口助手:推荐使用正点原子的XCOM。选择对应的COM口,设置波特率115200,数据位8,停止位1,无校验。

  3. 复位观察:

    • 此时XCOM应该会收到两行开头的提示信息。

    • 在发送区输入任意字符串,如Hello STM32,点击发送。

    • 观察现象:接收区立刻显示同样的Hello STM32。同时,开发板上的DS0(红色LED)每收到一个字节就会闪烁一次,与发送的字数对应。

如果发现没反应,请按照以下顺序排查:

  • P4跳帽是否插紧?这是最常见的疏忽。

  • 波特率是否一致?

  • 下载的程序是否进入了BOOT0=0的模式正常启动?

五、进阶思考

本次实验实现了简单的单字节中断处理。但在实际项目中,我们往往需要处理不定长度的数据帧。这时候,仅靠上面的简单中断是不够的,通常需要引入空闲中断(IDLE)来判断一帧数据的结束,或者构建环形缓冲区来存储数据。

对于战舰V3开发板,如果你想实现更复杂的串口协议解析(比如连接ESP8266模块发送AT指令),建议使用串口2或串口3,这样可以避免与调试用的串口1冲突,也方便模块化管理。

六、结语

串口通信看似简单,但却是嵌入式开发的“任督二脉”。通过本次实战,相信你已经掌握了STM32标准库下串口的基本配置和中止响应流程。希望这篇基于战舰V3的原创教程能帮你在嵌入式的道路上更进一步。如果你在实验过程中遇到问题,欢迎留言交流!