趁热打铁!STM32F103串口通信入门实战:以正点原子战舰V3为例(基于标准库)
一、前言
处理器与外部设备通信的两种方式:
并行通信
-传输原理:数据各个位同时传输。
-优点:速度快
-缺点:占用引脚资源多
串行通信
-传输原理:数据按位顺序传输。
-优点:占用引脚资源少
-缺点:速度相对较慢
| 通讯标准 | 引脚说明 | 通讯方式 | 通讯方式 |
| UART (通用异步收发器) | TXD:发送端 RXD:接受端 GND:公共地 | 异步通信 | 全双工 |
| 单总线 (1-wire) | DQ:发送/接受端 | 异步通信 | 半双工 |
| SPI | SCK:同步时钟 MISO:主机输入,从机输出 MOSI:主机输出,从机输入 | 异步通信 | 全双工 |
| I2C | SCL:同步时钟 SDA:数据输入/输出端 同步通信 | 异步通信 | 半双工 |
| 串口号 | RXD | TXD |
| 串口1 | PA10 | PA9 |
| 串口2 | PA3 | PA2 |
| 串口3 | PB11 | PB10 |
| 串口4 | PC11 | PC10 |
| 串口5 | PD2 | PC12 |
串口(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)正是通过这里的跳线帽连接的。
| 引脚 | 功能 | 连接外设 |
| PA9 | USART1_TX (发送) | CH340G 的 RXD |
| PA10 | USART1_RX (接收) | CH340G 的 TXD |
三、代码编写与原理解析
我们不直接复制例程,而是手动建立一个核心逻辑。代码基于STM32标准库。
3.1 编程目标
初始化串口1,波特率设置为115200。
实现printf重定向,方便打印调试信息。
开启接收中断。当收到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);
}这是串口通信的灵魂。当串口收到数据时,硬件会自动触发此函数。
// 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)
}
}在主函数中,我们只需要完成初始化,然后让程序空转。所有收发的逻辑都交给中断处理。
// 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;
}四、实验现象与验证
编译下载:将代码编译无误后,通过FlyMcu或者ST-Link下载到战舰V3开发板。
打开串口助手:推荐使用正点原子的XCOM。选择对应的COM口,设置波特率115200,数据位8,停止位1,无校验。
复位观察:
此时XCOM应该会收到两行开头的提示信息。
在发送区输入任意字符串,如Hello STM32,点击发送。
观察现象:接收区立刻显示同样的Hello STM32。同时,开发板上的DS0(红色LED)每收到一个字节就会闪烁一次,与发送的字数对应。
如果发现没反应,请按照以下顺序排查:
P4跳帽是否插紧?这是最常见的疏忽。
波特率是否一致?
下载的程序是否进入了BOOT0=0的模式正常启动?
五、进阶思考
本次实验实现了简单的单字节中断处理。但在实际项目中,我们往往需要处理不定长度的数据帧。这时候,仅靠上面的简单中断是不够的,通常需要引入空闲中断(IDLE)来判断一帧数据的结束,或者构建环形缓冲区来存储数据。
对于战舰V3开发板,如果你想实现更复杂的串口协议解析(比如连接ESP8266模块发送AT指令),建议使用串口2或串口3,这样可以避免与调试用的串口1冲突,也方便模块化管理。
六、结语
串口通信看似简单,但却是嵌入式开发的“任督二脉”。通过本次实战,相信你已经掌握了STM32标准库下串口的基本配置和中止响应流程。希望这篇基于战舰V3的原创教程能帮你在嵌入式的道路上更进一步。如果你在实验过程中遇到问题,欢迎留言交流!
评论 (0)
登录后即可发表评论