PUYA USART — 串口轮询收发与 printf 重定向(HAL 库)

PUYA PY32F0xx_Firmware V1.5.0 USART 串口轮询收发(USART_HyperTerminal_Polling)与 printf 重定向示例教学

USART — 串口轮询收发与 printf 重定向(HAL 库)

固件库: PY32F0xx_Firmware V1.5.0
芯片: PY32F030
源文件: PUYA/PY32F0xx_Firmware_V1.5.0/Projects/PY32F030-STK/Example/USART/USART_HyperTerminal_Polling/Src/main.c
串口重定向: Drivers/BSP/PY32F030xx_Start_Kit/py32f030xx_Start_Kit.c_write / __io_putchar


功能简介

USART(通用同步异步收发器)即常说的“串口”,是 MCU 与电脑/模块通信、打印调试信息最常用的通道。本示例做两件事:

  1. 轮询收发:串口先发送欢迎语,再用 HAL_UART_Receive 阻塞等待接收 12 字节,收到后原样回显,最后发结束语、点亮 LED。
  2. printf 重定向(本库特色):BSP 里已实现 _write / __io_putchar,把 C 库 printf 重定向到 USART2,于是在 ADC 等示例里能直接 printf("adc:%u\r\n", val) 打印。

任务要求的 “UART_Printf(printf 重定向)”在 PUYA 固件里不是独立示例,而是由 BSP_USART_Config() + _write 统一提供。下面先讲官方轮询示例,再专门讲 printf 重定向怎么用。

硬件准备

  • USB 转串口模块(CH340 / CP2102 等)
  • 接线:开发板 PA2(TX) → 模块 RXPA3(RX) → 模块 TX共地
  • 电脑端串口助手(SSCOM / XCOM / PuTTY),波特率 115200-8-N-1

完整代码(官方轮询示例)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include "main.h"

#define COUNTOF(__BUFFER__)     (sizeof(__BUFFER__) / sizeof(*(__BUFFER__)))
#define TXSTARTMESSAGESIZE      (COUNTOF(aTxStartMessage) - 1)
#define TXENDMESSAGESIZE        (COUNTOF(aTxEndMessage) - 1)

UART_HandleTypeDef UartHandle;
uint8_t aTxStartMessage[] = "\r\n UART Hyperterminal communication based on Polling\r\n Enter 12 characters using keyboard :\r\n";
uint8_t aTxEndMessage[]   = "\r\n Example Finished\r\n";
uint8_t aRxBuffer[12]      = {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};

int main(void)
{
  HAL_Init();
  BSP_LED_Init(LED_GREEN);

  /* 串口参数:USART2,115200,8 位,1 停止位,无校验,无流控,收发全开 */
  UartHandle.Instance           = USART2;
  UartHandle.Init.BaudRate      = 115200;
  UartHandle.Init.WordLength    = UART_WORDLENGTH_8B;
  UartHandle.Init.StopBits      = UART_STOPBITS_1;
  UartHandle.Init.Parity        = UART_PARITY_NONE;
  UartHandle.Init.HwFlowCtl     = UART_HWCONTROL_NONE;
  UartHandle.Init.Mode          = UART_MODE_TX_RX;
  UartHandle.Init.OverSampling  = UART_OVERSAMPLING_16;
  UartHandle.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
  HAL_UART_Init(&UartHandle);

  /* 1) 发送欢迎语 */
  if (HAL_UART_Transmit(&UartHandle, aTxStartMessage, TXSTARTMESSAGESIZE, 5000) != HAL_OK)
    APP_ErrorHandler();

  /* 2) 阻塞接收 12 字节(串口助手输入 12 个字符) */
  if (HAL_UART_Receive(&UartHandle, aRxBuffer, 12, 5000) != HAL_OK)
    APP_ErrorHandler();

  /* 3) 把收到的 12 字节原样回显 */
  if (HAL_UART_Transmit(&UartHandle, aRxBuffer, 12, 5000) != HAL_OK)
    APP_ErrorHandler();

  /* 4) 发送结束语 */
  if (HAL_UART_Transmit(&UartHandle, aTxEndMessage, TXENDMESSAGESIZE, 5000) != HAL_OK)
    APP_ErrorHandler();

  BSP_LED_On(LED_GREEN);   /* 成功:绿灯常亮 */
  while (1) {}
}

printf 重定向(重点)

py32f030xx_Start_Kit.c 中已实现如下重定向,使得任意示例调用 DEBUG_USART_Config() 后即可使用 printf

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
/* printf 最终调用到这里,把每个字符从 USART2 发出 */
int __io_putchar(int ch)
{
  /* 阻塞等待发送数据寄存器空,再写 1 字节 */
  while (!(DEBUG_USART->ISR & USART_ISR_TXE));
  DEBUG_USART->TDR = (ch & 0xFF);
  return ch;
}

/* C 库 write 系统调用:把整个缓冲区逐个字符送出 */
int _write(int file, char *ptr, int len)
{
  int i;
  for (i = 0; i < len; i++)
  {
    __io_putchar(*ptr++);
  }
  return len;
}

使用步骤

  1. main() 里调用 DEBUG_USART_Config();(它内部配置 PA2/PA3 为 USART2、115200-8-N-1 并使能时钟)。

  2. 工程需勾选 Use MicroLIB(Keil)或链接 libc(GCC/iar),否则 _write 不生效。

  3. 之后即可:

    1
    
    printf("系统启动 ok,温度=%d℃\r\n", temp);
    

    注意:重定向版是阻塞发送,仅适合调试打印;高吞吐场景请用 HAL_UART_Transmit_DMA 或中断。

代码分段讲解

  • 波特率与时钟:USART2 挂在 APB1,时钟来自 HSI/PLL;波特率 115200 需系统时钟足够且分频匹配,否则乱码。
  • 轮询发送 HAL_UART_Transmit:内部等 TXE 标志直到整帧发完或超时,简单但占用 CPU。
  • 轮询接收 HAL_UART_Receive:阻塞直到收满指定长度或超时;若串口助手发的字节数不足,会一直等(超时 5000ms 后报错)。
  • 重定向原理:C 库的 printffputc/_write__io_putchar → 写 USART 的 TDR,从而形成“打印即串口输出”。

实验现象

  • 串口助手收到欢迎语,提示输入 12 个字符;
  • 输入 12 个字符后,开发板原样回显,再发“Example Finished”,绿灯常亮。

常见问题

  1. 串口助手全是乱码?

    • 波特率必须与代码一致(115200);系统时钟(HSI/PLL)设置要匹配库里 DEBUG_USART 的时钟来源。
    • 确认 printf 工程勾选了 MicroLIB / 正确链接 libc。
  2. printf 没输出?

    • 忘记调用 DEBUG_USART_Config();或引脚不是 PA2/PA3(被改过需同步改 BSP)。
    • 确认 HAL_UART_MODULE_ENABLED 宏已开(否则 printf 重定向分支被编译掉)。
  3. HAL_UART_Receive 卡住?

    • 它要收满 12 字节才返回;串口助手发的字符数不够或没勾选“发送新行”都会超时。教学建议先固定长度,或用中断/不定长(见 USART_HyperTerminal_IT)。
  4. TX/RX 接反?

    • 必须交叉:板 TX → 模块 RX,板 RX → 模块 TX,并共地。

小结

  • 串口配置三要素:波特率、数据格式(8-N-1)、时钟来源
  • 轮询收发最简单但阻塞 CPU;正式项目用中断/DMA。
  • 通过 _write/__io_putchar 重定向 printf,是最高效的调试打印手段。

拓展应用

  • printf 打印变量/状态,做在线调试,替代单步。
  • 升级为 HAL_UART_Transmit_IT/DMA 做高吞吐日志或命令解析。
  • 接收中断 + 环形缓冲实现“串口命令行”(解析 AT 指令等)。
  • 双 USART:一个接调试、一个接 WiFi/BLE 模块。
世界是你们
使用 Hugo 构建
主题 StackJimmy 设计