三十二、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

  • 串口回传执行结果

相关推荐
沉在嵌入式的鱼2 小时前
linux串口对0X0D、0X0A等特殊字符的处理
linux·stm32·单片机·特殊字符·串口配置
学习路上_write2 小时前
AD5293驱动学习
c语言·单片机·嵌入式硬件·学习
影阴2 小时前
存储器和寄存器
stm32·单片机·嵌入式硬件
吃西瓜的年年3 小时前
3. C语言核心语法2
c语言·嵌入式硬件·改行学it
李洛克073 小时前
RDMA CM UDP 通信完整指南
单片机·网络协议·udp
思茂信息4 小时前
CST电动车EMC仿真——电机控制器MCU滤波仿真
javascript·单片机·嵌入式硬件·cst·电磁仿真
小曹要微笑4 小时前
I2C总线技术解析(纯文字版)
单片机·嵌入式硬件·esp32·iic
我送炭你添花4 小时前
可编程逻辑器件(PLD)的发展历程、原理、开发与应用详解
嵌入式硬件·fpga开发
袖手蹲4 小时前
Arduino UNO Q 从 Arduino Cloud 远程控制闪烁 LED
人工智能·单片机·嵌入式硬件·电脑
平凡灵感码头5 小时前
第一次做蓝牙产品,从零开发 嵌入式开发日志(2)AC63NSDK 完整合并版目录说明
stm32·单片机·嵌入式硬件