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

  • 串口回传执行结果

相关推荐
别了,李亚普诺夫6 小时前
DMA学习笔记
笔记·stm32
v先v关v住v获v取7 小时前
天然气管道内检测机器人检测节设计14张cad+三维图+设计说明书
科技·单片机·51单片机
无垠的广袤7 小时前
【上海晶珩睿莓 1 单板计算机】物联网环境监测终端
linux·python·嵌入式硬件·物联网·mqtt·home assistant
先知后行。7 小时前
ModBus协议
嵌入式硬件
IT阳晨。15 小时前
【STM32】天气预报项目
stm32·单片机·嵌入式硬件
IT阳晨。17 小时前
【STM32】智能台灯项目
stm32·单片机·嵌入式硬件
炸膛坦客18 小时前
Cortex-M3-STM32F1 开发:(三十九)DMA详细介绍(3):相关寄存器、库函数介绍,配置步骤,以及内存到内存和内存到外设的实例
stm32·单片机·嵌入式硬件
BMS小旭20 小时前
CubeMx-GPIO学习
单片机·学习
清风6666661 天前
基于单片机的PID调节脉动真空灭菌器上位机远程监控设计
数据库·单片机·毕业设计·nosql·课程设计·期末大作业
polarislove02141 天前
9.6 [定时器]超声波测距实验-嵌入式铁头山羊STM32笔记
笔记·stm32·嵌入式硬件