三十二、STM32的USART(串口收发文本数据包)

前言:串口(USART)是嵌入式工程中使用频率最高的通信接口。不论是调试输出、模块控制、板间通信,都离不开串口。本篇文章带你从零实现一个 串口文本数据包协议收发案例

  • 数据包格式: @MSG\r\n

  • 通过中断 + 状态机解析完整数据包

  • 将收到的文本指令显示到 OLED

  • 收到如 "LED_ON" 指令自动执行动作,并回传执行结果

目录

一、接线图

二、功能说明

三、串口文本数据包格式设计

[四、串口初始化(9600 8N1)](#四、串口初始化(9600 8N1))

[五、串口接收数据包 ------ 状态机解析核心](#五、串口接收数据包 —— 状态机解析核心)

[六、OLED 显示收发内容](#六、OLED 显示收发内容)

[七、根据指令执行动作(LED 控制)](#七、根据指令执行动作(LED 控制))

[八、完整 main.c](#八、完整 main.c)

九、上位机调试方法

十、总结


一、接线图

二、功能说明

本项目实现如下功能:

  1. 上位机发送文本指令

格式:

@LED_ON\r\n

@LED_OFF\r\n

  1. STM32 接收完整数据包并解析

解析内容存入:

char Serial_RxPacket[100];

  1. OLED 显示收到的文本命令

LED_ON

LED_OFF

  1. 执行对应操作
  1. 串口回传执行结果

LED_ON_OK

LED_OFF_OK

ERROR_COMMAND

三、串口文本数据包格式设计

我们采用一个简单、稳定、安全的文本协议格式:

@内容\r\n

例如:

@LED_ON\r\n

为什么选 @

  • 特殊字符,不易混淆

  • 便于状态机识别

  • 文本协议常用格式

四、串口初始化(9600 8N1)

以下代码完成 GPIO、USART、NVIC 中断初始化:

复制代码
void Serial_Init(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    USART_InitTypeDef USART_InitStructure;
    USART_InitStructure.USART_BaudRate = 9600;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
    USART_InitStructure.USART_Parity = USART_Parity_No;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    USART_Init(USART1, &USART_InitStructure);

    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);

    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_Init(&NVIC_InitStructure);

    USART_Cmd(USART1, ENABLE);
}

五、串口接收数据包 ------ 状态机解析核心

状态机三阶段:

完整中断解析代码:

复制代码
void USART1_IRQHandler(void)
{
    static uint8_t RxState = 0;
    static uint8_t pRxPacket = 0;

    if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
    {
        uint8_t RxData = USART_ReceiveData(USART1);

        if (RxState == 0)
        {
            if (RxData == '@' && Serial_RxFlag == 0)
            {
                RxState = 1;
                pRxPacket = 0;
            }
        }
        else if (RxState == 1)
        {
            if (RxData == '\r')
            {
                RxState = 2;
            }
            else
            {
                Serial_RxPacket[pRxPacket] = RxData;
                pRxPacket++;
            }
        }
        else if (RxState == 2)
        {
            if (RxData == '\n')
            {
                RxState = 0;
                Serial_RxPacket[pRxPacket] = '\0';
                Serial_RxFlag = 1;
            }
        }

        USART_ClearITPendingBit(USART1, USART_IT_RXNE);
    }
}

为什么需要状态机?

  • 串口数据是一个字节一个字节来的

  • 不能保证一次收到全部数据

  • 协议解析必须严格依靠状态机实现

六、OLED 显示收发内容

OLED_ShowString(1, 1, "TxPacket");

OLED_ShowString(3, 1, "RxPacket");

显示接收到的内容:

OLED_ShowString(4, 1, " ");

OLED_ShowString(4, 1, Serial_RxPacket);

七、根据指令执行动作(LED 控制)

复制代码
if (strcmp(Serial_RxPacket, "LED_ON") == 0)
{
    LED1_ON();
    Serial_SendString("LED_ON_OK\r\n");
}
else if (strcmp(Serial_RxPacket, "LED_OFF") == 0)
{
    LED1_OFF();
    Serial_SendString("LED_OFF_OK\r\n");
}
else
{
    Serial_SendString("ERROR_COMMAND\r\n");
}

并在 OLED 显示执行结果:

OLED_ShowString(2, 1, " ");

OLED_ShowString(2, 1, "LED_ON_OK");

八、完整 main.c

复制代码
#include "stm32f10x.h"
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
#include "LED.h"
#include "string.h"

int main(void)
{
    OLED_Init();
    LED_Init();
    Serial_Init();

    OLED_ShowString(1, 1, "TxPacket");
    OLED_ShowString(3, 1, "RxPacket");

    while (1)
    {
        if (Serial_RxFlag == 1)
        {
            OLED_ShowString(4, 1, "                ");
            OLED_ShowString(4, 1, Serial_RxPacket);

            if (strcmp(Serial_RxPacket, "LED_ON") == 0)
            {
                LED1_ON();
                Serial_SendString("LED_ON_OK\r\n");
                OLED_ShowString(2, 1, "LED_ON_OK");
            }
            else if (strcmp(Serial_RxPacket, "LED_OFF") == 0)
            {
                LED1_OFF();
                Serial_SendString("LED_OFF_OK\r\n");
                OLED_ShowString(2, 1, "LED_OFF_OK");
            }
            else
            {
                Serial_SendString("ERROR_COMMAND\r\n");
                OLED_ShowString(2, 1, "ERROR_COMMAND");
            }

            Serial_RxFlag = 0;
        }
    }
}

九、上位机调试方法

打开串口助手,设置:

  • 9600 波特率

  • 文本模式(发送字符串)

  • 必须发送结束符 \r\n

@LED_ON\r\n
@LED_OFF\r\n

即可看到:

  • LED 点亮 / 熄灭

  • OLED 显示命令

  • STM32 回传 LED_ON_OK

十、总结

本项目完整展示了:

  • GPIO + USART + NVIC 初始化

  • 串口文本协议设计

  • 状态机解析数据包

  • OLED 显示系统

  • 文本指令控制 LED

  • 串口回传执行结果

相关推荐
传感器与混合集成电路1 天前
210℃与175℃高温存储器选型研究:LHM256MB与LDMF4GA-H架构与可靠性对比(上)
嵌入式硬件·能源
时光找茬1 天前
【瑞萨AI挑战赛-FPB-RA6E2】+ 从零开始:FPB-RA6E2 开箱测评与 e2 studio 环境配置
c++·单片机·边缘计算
17(无规则自律)1 天前
【CSAPP 读书笔记】第二章:信息的表示和处理
linux·嵌入式硬件·考研·高考
@good_good_study1 天前
FreeRTOS内存管理
单片机
Hello_Embed1 天前
libmodbus 移植 STM32(基础篇)
笔记·stm32·单片机·学习·modbus
qq_397562311 天前
QT工程 , 生成别的电脑运行的exe程序
嵌入式硬件·qt
qqssss121dfd1 天前
STM32H750XBH6的ETH模块移植LWIP
网络·stm32·嵌入式硬件
想放学的刺客1 天前
单片机嵌入式试题(第27期)设计可移植、可配置的外设驱动框架的关键要点
c语言·stm32·单片机·嵌入式硬件·物联网
天昊吖1 天前
stc8H启用DMA发送后 卡住【踩坑日志】
单片机
李永奉1 天前
杰理芯片SDK开发-ENC双麦降噪配置/调试教程
人工智能·单片机·嵌入式硬件·物联网·语音识别