全部例程

USART - 串口通讯

W55MH32

更新于 2026年8月13日

本篇总结

使用 USART + DMA,实现串口数据收发回传。

🔑 KEIL MDK、UART调试、USART、DMA

本示例演示 W55MH32 的 USART1 外设配合 DMA(直接存储器访问)实现异步串口收发。与阻塞式收发不同,DMA 可在不占用 CPU 的情况下自动搬运数据:USART 收到数据时,DMA 自动将其从 USART_DR 寄存器搬到内存缓冲区(接收),反之亦然(发送)。
全程无需 CPU 逐字节轮询,大幅提升系统效率。

串口通讯协议简介

串口通讯(Serial Communication)是设备间常用的串行通讯方式,简单便捷,大部分电子设备都支持,电子工程师调试时也常用来输出调试信息。

通讯协议可分为两层:物理层规定机械、电子功能特性(如 RS-232 电平标准、DB9 接口),确保原始数据在物理媒体上传输;协议层规定通讯逻辑,统一收发双方的数据打包、解包标准(波特率、起始/停止位、数据帧格式、校验位等)。

本例程关键参数:异步通讯(无时钟线) / 波特率 115200 / 8 位数据 / 1 停止位 / 无校验(8N1)/ 全双工。USART1 使用 PA9(TX)、PA10(RX),通过板载 USB 转串口芯片与 PC 通讯。

前置工具准备

开始本例程前,请确认以下工具已安装:

  • WIZ UartTool V1.0:串口收发测试工具:点击下载

硬件连接

USART1 的 TX(PA9) 和 RX(PA10) 通过板载 USB 转串口芯片连接到 DEBUG USB 口。仅需一根 Micro USB 线连接开发板 DEBUG USB 口到 PC 即可。

引脚功能方向模式
PA9USART1_TX开发板 → PC复用推挽输出 (AF_PP)
PA10USART1_RXPC → 开发板浮空输入 (IN_FLOATING)

工程结构

工程位于 SDK 内 1.SDK\ModuleDemo\USART\USART_Asyn\,核心文件如下:

USART_Asyn/
├── USER/
│   ├── main.c            ← 外设初始化 + DMA收发循环 + printf 重定向
│   ├── w55mh32_conf.h    ← 外设头文件集中包含
│   ├── w55mh32_it.c/h    ← 中断服务函数
│   ├── system_w55mh32.c/h← 系统初始化(时钟配置)
│   └── startup_w55mh32.s ← 启动文件
└── SYSTEM/
    └── delay/
        └── delay.c/h      ← SysTick 延时函数

步骤1:头文件、宏定义与全局变量

文件:USER/main.c 顶部 — 引入标准库与 W55MH32 头文件,定义 USART_TEST 宏指向 USART1,声明三个配置函数原型,定义缓冲区大小 BUFFSIZE=8 与收发共用数组 Buff


#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "delay.h"
#include "w55mh32.h"

USART_TypeDef *USART_TEST = USART1;

void UART_Configuration(uint32_t bound);
void DMA_RecvConfiguration(void);
void DMA_SendConfiguration(void);

#define BUFFSIZE 8
uint8_t Buff[BUFFSIZE];

步骤2:USART 初始化

文件:USER/main.c | 函数:UART_Configuration() — 使能 USART1 与 GPIOA 时钟,PA9 配置为复用推挽输出(TX),PA10 配置为浮空输入(RX),USART1 初始化为 115200 / 8N1 / 收发模式。


void UART_Configuration(uint32_t bound)
{
    GPIO_InitTypeDef  GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;

    /* 开启外设时钟 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    /* PA9: USART1_TX → 复用推挽输出 */
    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);

    /* PA10: USART1_RX → 浮空输入 */
    GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    /* USART 参数:115200 / 8N1 / 全双工 */
    USART_InitStructure.USART_BaudRate            = bound;
    USART_InitStructure.USART_WordLength          = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits            = USART_StopBits_1;
    USART_InitStructure.USART_Parity              = USART_Parity_No;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode                = USART_Mode_Rx | USART_Mode_Tx;

    USART_Init(USART_TEST, &USART_InitStructure);
    USART_Cmd(USART_TEST, ENABLE);
}

四步完成:开时钟 → GPIO 引脚模式配置(TX 复用推挽,RX 浮空输入)→ USART 参数配置(115200 / 8N1)→ 使能外设。

如果你想自定义

  • 改波特率 → 修改 UART_Configuration() 的入参,如 UART_Configuration(9600)
  • 换用 USART2/USART3 → 同步修改 GPIO 引脚和对应的时钟使能宏,具体引脚映射请参考数据手册

步骤3:DMA 接收配置(Channel5)

函数:DMA_RecvConfiguration() — 配置 DMA1 Channel5 将 USART1 RX 数据自动搬运到内存数组 Buff。外设地址为 USART1->DR,方向为外设→内存,缓冲区 8 字节。


void DMA_RecvConfiguration(void)
{
    DMA_InitTypeDef DMA_InitStructure;

    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

    /* USART1_RX DMA Config */
    DMA_DeInit(DMA1_Channel5);
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;
    DMA_InitStructure.DMA_MemoryBaseAddr     = (uint32_t)Buff;
    DMA_InitStructure.DMA_DIR                = DMA_DIR_PeripheralSRC;   // 外设→内存
    DMA_InitStructure.DMA_BufferSize         = BUFFSIZE;                // 搬 8 字节后停止
    DMA_InitStructure.DMA_PeripheralInc      = DMA_PeripheralInc_Disable; // 外设地址不变
    DMA_InitStructure.DMA_MemoryInc          = DMA_MemoryInc_Enable;     // 内存地址递增
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStructure.DMA_MemoryDataSize     = DMA_MemoryDataSize_Byte;
    DMA_InitStructure.DMA_Mode               = DMA_Mode_Normal;         // 普通模式(非循环)
    DMA_InitStructure.DMA_Priority           = DMA_Priority_Medium;
    DMA_InitStructure.DMA_M2M                = DMA_M2M_Disable;

    DMA_Init(DMA1_Channel5, &DMA_InitStructure);
    DMA_Cmd(DMA1_Channel5, ENABLE);
}

关键两点:DMA_DIR_PeripheralSRC 表示方向为"外设→内存"(接收);DMA_Mode_Normal 表示搬运完 8 字节后自动停机,需软件重新配置。若需连续接收可改为 DMA_Mode_Circular

如果你想自定义

  • 改缓冲区大小 → 修改 BUFFSIZE 宏定义,然后同步调整 DMA_BufferSize
  • 改为循环模式(连续接收) → 将 DMA_Mode_Normal 改为 DMA_Mode_Circular,并在 main() 中移除每轮循环的手动重配逻辑

步骤4:DMA 发送配置(Channel4)

函数:DMA_SendConfiguration() — 与接收配置镜像对称,仅两处不同:方向改为"内存→外设"(DMA_DIR_PeripheralDST),使用 Channel4。


void DMA_SendConfiguration(void)
{
    DMA_InitTypeDef DMA_InitStructure;

    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

    /* USART1_TX DMA Config */
    DMA_DeInit(DMA1_Channel4);
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;
    DMA_InitStructure.DMA_MemoryBaseAddr     = (uint32_t)Buff;
    DMA_InitStructure.DMA_DIR                = DMA_DIR_PeripheralDST;   // 内存→外设
    DMA_InitStructure.DMA_BufferSize         = BUFFSIZE;
    DMA_InitStructure.DMA_PeripheralInc      = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc          = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStructure.DMA_MemoryDataSize     = DMA_MemoryDataSize_Byte;
    DMA_InitStructure.DMA_Mode               = DMA_Mode_Normal;
    DMA_InitStructure.DMA_Priority           = DMA_Priority_Medium;
    DMA_InitStructure.DMA_M2M                = DMA_M2M_Disable;

    DMA_Init(DMA1_Channel4, &DMA_InitStructure);
    DMA_Cmd(DMA1_Channel4, ENABLE);
}

步骤5:主函数逻辑

文件:USER/main.c | 函数:main() — 初始化延时与 UART(115200),打印时钟频率与 USART/DMA 配置参数(引脚、波特率、DMA 通道、缓冲区大小),提示主机发送 8 字节。主循环:打印等待输入提示 → 启动 DMA 接收 → 等待接收完成 → 以 HEX 和 CHAR 双格式打印 → 启动 DMA 发送回显 → 等待发送完成。


int main(void)
{
    uint32_t          i;
    RCC_ClocksTypeDef clocks;

    delay_init();
    UART_Configuration(115200);
    RCC_GetClocksFreq(&clocks);

    printf("\n");
    printf("SYSCLK: %3.1fMhz, HCLK: %3.1fMhz, PCLK1: %3.1fMhz, "
           "PCLK2: %3.1fMhz, ADCCLK: %3.1fMhz\n",
           (float)clocks.SYSCLK_Frequency / 1000000,
           (float)clocks.HCLK_Frequency / 1000000,
           (float)clocks.PCLK1_Frequency / 1000000,
           (float)clocks.PCLK2_Frequency / 1000000,
           (float)clocks.ADCCLK_Frequency / 1000000);

    printf("USART Asyn DMA Test.\n");
    printf("USART1: TX=PA9, RX=PA10, Baudrate=115200, 8N1\n");
    printf("DMA1 Channel5(RX) / Channel4(TX), Buffer Size=%d bytes\n", BUFFSIZE);
    printf("Please send %d bytes from the host serial tool, "
           "the board will echo them back via DMA.\n", BUFFSIZE);

    while (1)
    {
        printf("\n--- Waiting for %d bytes input via UART1 ---\n", BUFFSIZE);

        /* ---- RX ---- */
        DMA_Cmd(DMA1_Channel4, DISABLE);
        USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE);
        DMA_RecvConfiguration();
        while (DMA_GetFlagStatus(DMA1_FLAG_TC5) == RESET);

        printf("DMA RX Complete (Channel5), received %d bytes:\n", BUFFSIZE);
        printf("  HEX : ");
        for (i = 0; i < BUFFSIZE; i++)
            printf("%02X ", Buff[i]);
        printf("\n  CHAR: ");
        for (i = 0; i < BUFFSIZE; i++)
            printf("%c", Buff[i]);
        printf("\n");

        /* ---- TX ---- */
        printf("DMA TX start (Channel4), echoing back %d bytes...\n", BUFFSIZE);
        DMA_Cmd(DMA1_Channel5, DISABLE);
        USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);
        DMA_SendConfiguration();
        while (DMA_GetFlagStatus(DMA1_FLAG_TC4) == RESET);
        printf("DMA TX Complete (Channel4).\n");
    }
}

RX 和 TX 分时复用,每轮循环先关闭对方 DMA 通道避免冲突。DMA 传输完成后用 DMA_GetFlagStatus() 轮询等待 TC(Transfer Complete)标志。整改后整个 DMA 收发链路的配置参数、等待状态、收发结果均清晰可见。

如果你想自定义

  • 避免永久阻塞 → 在 while(DMA_GetFlagStatus(...)) 后加超时计数器,例如超时 1 秒则跳过本次收发
  • 改为中断通知而非轮询 → 启用 DMA 的 TC 中断(DMA_ITConfig(..., DMA_IT_TC, ENABLE)),在 w55mh32_it.c 的 DMA 中断中处理完成回调

步骤6:printf 重定向

SER_PutChar() 等待 USART1 的 TC(发送完成)标志置位后发送一个字节。fputc() 重定向标准输出到 USART1,遇到 \n 时先补发 \r,确保串口工具正确换行。


int SER_PutChar(int ch)
{
    while (!USART_GetFlagStatus(USART_TEST, USART_FLAG_TC));
    USART_SendData(USART_TEST, (uint8_t)ch);
    return ch;
}

int fputc(int c, FILE *f)
{
    /* Place your implementation of fputc here */
    /* e.g. write a character to the USART */
    if (c == '\n')
    {
        SER_PutChar('\r');         // 补回车,确保串口工具正确换行
    }
    return (SER_PutChar(c));
}

编译与下载

Keil MDK 中打开 USART.uvprojxF7 编译,确认 0 Error(s)。连接 DEBUG USB 口到 PC,F8 下载。下载成功后按 RESET 键启动新固件。

运行验证

打开 WIZ UartTool V1.0,选择开发板对应 COM 口,波特率 115200 / 8N1,打开串口。按 RESET 启动后,预期串口输出配置参数与等待提示:

SYSCLK: 72.0Mhz, HCLK: 72.0Mhz, PCLK1: 36.0Mhz, PCLK2: 72.0Mhz, ADCCLK: 36.0Mhz
USART Asyn DMA Test.
USART1: TX=PA9, RX=PA10, Baudrate=115200, 8N1
DMA1 Channel5(RX) / Channel4(TX), Buffer Size=8 bytes
Please send 8 bytes from the host serial tool, the board will echo them back via DMA.

--- Waiting for 8 bytes input via UART1 ---

在发送区输入 8 个字符(如 ABCDEFGH )并发送。开发板通过 DMA 接收完成后,以 HEX 与 CHAR 双格式打印数据,随后通过 DMA 将数据原样回发:

DMA RX Complete (Channel5), received 8 bytes:
  HEX : 41 42 43 44 45 46 47 48
  CHAR: ABCDEFGH
DMA TX start (Channel4), echoing back 8 bytes...
DMA TX Complete (Channel4).

--- Waiting for 8 bytes input via UART1 ---

可看到 DMA 接收 8 字节成功,HEX 与 CHAR 双格式打印一致,DMA 回发完成后进入下一轮等待。整个 DMA 收发链路清晰可见。

常见问题

Q: 编译报错 "cannot open source file w55mh32.h"

检查 Project → Options → C/C++ → Include Paths,确保 SDK 的 Libraries 目录已添加。

Q: 下载成功但串口无输出

确认 COM 口号正确、波特率 115200,下载后需按 RESET 键启动。

Q: 程序卡在 DMA 等待处不动

确认 PC 端串口工具已发送至少 8 字节数据。可增加超时机制避免永久阻塞。

Q: 串口输出乱码

先看串口打印的时钟频率是否正常(SYSCLK 应为 72.0Mhz)。若频率异常,检查 system_w55mh32.c 中 HSE_VALUE 是否与实际晶振一致。

Q: DMA 接收数据错位或丢失

确认每轮循环先关闭对方 DMA 通道(DMA_Cmd(..., DISABLE))再重配本通道,避免残留数据。Normal 模式下每轮必须重新调用配置函数。

总结

本文基于 W55MH32 的 USART1 + DMA 实现了异步串口收发:

  • USART1 配置为 115200 / 8N1 全双工模式,通过 PA9(TX) 和 PA10(RX) 与 PC 通信
  • DMA1 Channel5 负责接收(外设→内存),Channel4 负责发送(内存→外设),Normal 模式下每轮循环手动重配
  • 主循环打印配置参数与等待提示,接收完成后以 HEX/CHAR 双格式打印数据,再通过 DMA 原样回显
  • 重写 fputc() 实现 printf 重定向,借助 WIZ UartTool V1.0 观察收发数据

下载本章例程

我们提供完整的工程文件以及配套开发板,方便你随时测试,快速完成产品开发:

开发环境: Keil MDK5 配套开发板

Andy

W5500 技术支持专家