声明

  文中内容为观看 BiliBili 视频【STM32入门教程-2023版 细致讲解 中文字幕】后学习并扩展总结。
  本文章为个人学习使用,版面观感若有不适请谅解,文中知识仅代表个人观点,若出现错误,欢迎各位批评指正。

一、数据模式及数据包

1.1 数据模式

  在数字通信系统中,发送方与接收方通过编码 - 译码的双向映射实现字符与二进制数据的转换,这一过程构成了信息可靠传输的核心基础。以字符 'A'为例,发送方将其编码为十六进制值 0x41(对应二进制 01000001),并以原始比特流形式传输至接收方;接收方则通过译码操作将 0x41还原为字符 'A',完成信息的完整闭环传递。
  在此过程中,数据的显示模式直接影响对传输内容的解读维度:十六进制模式(HEX 模式)/ 二进制模式以原始数据形式呈现,直观展示通信链路中传输的比特序列或其十六进制等价形式,可精准定位传输过程中的比特错误与数据完整性;文本模式(字符模式)则以编码后的字符形式呈现,更贴近人类可读的信息语义,便于理解传输内容的实际含义。二者分别从物理层数据传输应用层语义表达两个层面,共同构成了数字通信系统中数据观测与分析的完整视角,为协议调试、故障排查及性能优化提供了互补的技术手段。
在这里插入图片描述

1.2 HEX 数据包 及 数据包接收
  • 固定包长 HEX 数据包格式
      固定包长 HEX 数据包采用帧头 - 数据 - 帧尾的定长封装结构,以特定字节作为帧边界标识:帧头固定为 0xFF,帧尾固定为 0xFE,中间承载 4 字节定长有效载荷。该格式通过帧头 0xFF与帧尾 0xFE的字节序列实现帧同步与边界划分,有效载荷长度严格固定为 4 字节,无需额外长度字段即可完成帧解析,适用于对传输时延确定性要求较高的场景。典型数据包序列为 0xFF 0x01 0x02 0x03 0x04 0xFE,其中 0xFF为帧起始标识, 0x01~0x04为有效数据, 0xFE为帧结束标识。

  • 可变包长 HEX 数据包格式
      可变包长 HEX 数据包同样采用帧头 - 数据 - 帧尾的封装范式,帧头 0xFF与帧尾 0xFE的边界标识规则与固定包长格式保持一致,但有效载荷长度可动态变化。该格式通过帧头 0xFF标记帧起始,帧尾 0xFE标记帧结束,有效载荷长度由应用层逻辑或协议约定动态调整,无需在帧结构中显式携带长度字段,仅依赖帧尾 0xFE完成帧边界判定。典型数据包序列为 0xFF 0x01 0x02 0x03 0x04 0xFE(4 字节载荷)或 0xFF 0x05 0x06 0x07 0xFE(3 字节载荷),适用于数据长度可变的异步通信场景。

  • HEX 数据包接收状态机流程
      HEX 数据包接收采用 有限状态机(FSM) 实现帧同步与解析,核心状态定义如下:
      状态 S=0(等待包头): 系统初始状态,持续监听接收字节流,仅当检测到帧头字节 0xFF时,状态迁移至 S=1(接收数据);若收到其他字节,则保持 S=0 状态。
      状态 S=1(接收数据): 进入数据接收阶段,累计接收有效载荷字节。对于固定包长格式,需累计接收 4 字节有效数据;对于可变包长格式,持续接收直至触发帧尾检测。若未收满约定长度的有效数据或收到非帧尾字节,则保持 S=1 状态;当收满约定长度数据时,状态迁移至 S=2(等待包尾)。
      状态 S=2(等待包尾): 等待帧尾字节 0xFE的到来,若检测到 0xFE,则判定当前帧接收完成,状态重置为 S=0(等待包头),并向上层提交完整数据包;若收到其他字节,则保持 S=2 状态,直至检测到0xFE或超时丢弃当前帧。
      该状态机通过字节级别的状态跃迁,实现了对固定 / 可变包长 HEX 数据包的可靠同步与解析,有效避免了帧黏连与帧丢失问题,保障了串行通信的帧完整性。

在这里插入图片描述

1.3 文本数据包 及 数据包接收
  • 固定包长文本数据包格式
      固定包长文本数据包采用帧头 - 数据 - 帧尾的定长封装范式,以字符 @作为帧起始标识(帧头),以 \r\n(回车换行符)作为帧结束标识(帧尾),中间承载 3 字节固定长度的有效文本载荷。该格式通过 @\r\n的字符序列实现帧同步与边界划分,有效载荷长度严格限定为 3 字节,无需额外长度字段即可完成帧解析,适用于对传输时延确定性、数据完整性要求较高的文本通信场景。典型数据包序列为 @ABC\r\n,其中 @为帧起始标识, A/B/C为有效文本数据, \r\n为帧结束标识。

  • 可变包长文本数据包格式
      可变包长文本数据包同样遵循帧头 - 数据 - 帧尾的封装结构,帧头 @与帧尾 \r\n的边界标识规则与固定包长格式保持一致,但有效载荷长度可动态变化。该格式以 @标记帧起始,以 \r\n标记帧结束,有效载荷长度由应用层业务逻辑动态调整,无需在帧结构中显式携带长度字段,仅依赖 \r\n序列完成帧边界判定。典型数据包序列为 @ABC\r\n(3 字节载荷)或 @DE\r\n(2 字节载荷),适用于数据长度可变的异步文本通信场景,如串口调试、传感器文本数据上报等。

  • 文本数据包接收状态机流程
      文本数据包接收采用 有限状态机(FSM) 实现帧同步与解析,核心状态定义如下::
      状态 S=0(等待包头): 系统初始状态,持续监听接收字符流,仅当检测到帧头字符 @时,状态迁移至 S=1(接收数据等待包尾);若收到其他字符,则保持 S=0 状态。
      状态 S=1(接收数据等待包尾): 进入数据接收阶段,累计接收有效文本载荷。对于固定包长格式,需累计接收 3 字节有效数据;对于可变包长格式,持续接收直至检测到 \r字符。若收到其他字符,则保持 S=1 状态;当检测到 \r字符时,状态迁移至 S=2(等待包尾)。
      状态 S=2(等待包尾): 等待帧尾第二个字符 \n的到来,若检测到 \n,则判定当前帧接收完成,状态重置为 S=0(等待包头),并向上层提交完整文本数据包;若收到其他字符,则保持 S=2 状态,直至检测到 \n或超时丢弃当前帧。
      该状态机通过字符级别的状态跃迁,实现了对固定 / 可变包长文本数据包的可靠同步与解析,有效避免了帧黏连与帧丢失问题,保障了串行文本通信的帧完整性。

在这里插入图片描述

二、串口收发 HEX 及 文本 数据包

2.1 串口收发 HEX 数据包的实现
  • 首先,按下图接线方式,搭建面包板电路连接 OLED 显示屏,并将 USB 转串口的 TXD 和 RXD 分别与 PA2 和 PA3 连接,然后将 DAP-Link / ST-Link 连接到 STM32 最小系统板上,为使 OLED 显示屏的 VCC 和 GND 正确连接正负极,请先连接对应正负极跳线(或直接使用 GPIO 口进行供电)。
    在这里插入图片描述
  • 直接复制先前演示的已有文件目录,重命名并双击后缀名为 .uvprojx 的文件打开工程文件,并对 main.c 进行修改,工程中所使用的全部头文件其详细内容已放于文末。
#include "stm32f10x.h"                  // Device header
#include "Serial_HEX.h"
#include "OLED.h"
#include "Key.h"

uint8_t KeyNum;

int main(void)
{
	OLED_Init();
    Key_Init();
    Serial_Init();
    
    OLED_ShowString(1, 1, "TxPacket");
    OLED_ShowString(3, 1, "RxPacket");
    
    Serial_TxPacket[0] = 0x01;
    Serial_TxPacket[1] = 0x02;
    Serial_TxPacket[2] = 0x03;
    Serial_TxPacket[3] = 0x04;
                
	while (1)
	{   
        KeyNum = Key_GetNum();
        if (KeyNum == 1){            
            Serial_TxPacket[0] ++;
            Serial_TxPacket[1] ++;
            Serial_TxPacket[2] ++;
            Serial_TxPacket[3] ++;
            
            Serial_SendPacket();
            
            OLED_ShowHexNum(2, 1, Serial_TxPacket[0], 2);
            OLED_ShowHexNum(2, 4, Serial_TxPacket[1], 2);
            OLED_ShowHexNum(2, 7, Serial_TxPacket[2], 2);
            OLED_ShowHexNum(2, 10, Serial_TxPacket[3], 2);
        }
        
        if (Serial_GetRxFlag() == 1){
            OLED_ShowHexNum(4, 1, Serial_RxPacket[0], 2);
            OLED_ShowHexNum(4, 4, Serial_RxPacket[1], 2);
            OLED_ShowHexNum(4, 7, Serial_RxPacket[2], 2);
            OLED_ShowHexNum(4, 10, Serial_RxPacket[3], 2);
        }
	}
}

  • 打开串口助手(串口助手软件 请在【江协科技】视频下方下载),按照预设的数据包格式,在串口助手的发送区域编辑数据 “FF 11 22 33 44 FE” 并点击发送后,即可在 RxPacket 区域下方查看到所发送的数据包内容。
    在这里插入图片描述
    在这里插入图片描述
2.2 串口收发文本数据包的实现
  • 首先,按下图接线方式,搭建面包板电路连接 OLED 显示屏,并将 USB 转串口的 TXD 和 RXD 分别与 PA2 和 PA3 连接,然后将 DAP-Link / ST-Link 连接到 STM32 最小系统板上,为使 OLED 显示屏的 VCC 和 GND 正确连接正负极,请先连接对应正负极跳线(或直接使用 GPIO 口进行供电)。
    在这里插入图片描述
  • 直接复制先前演示的已有文件目录,重命名并双击后缀名为 .uvprojx 的文件打开工程文件,并对 main.c 进行修改,工程中所使用的全部头文件其详细内容已放于文末。
    注:文末 LED 代码中保留了 void LED_Turn(void);功能的实现,感兴趣的可以尝试将 LED 灯状态转换添加进来,并通过设定 LED 初始状态以及 FLAG 标记位返回当前 LED 灯的状态信息(即:当前 LED 是开还是关)。
#include "stm32f10x.h"                  // Device header
#include "Serial_TEXT.h"
#include "OLED.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){
                LED_ON();
                Serial_SendString("LED_ON_OK\r\n");
                OLED_ShowString(2, 1, "                ");
                OLED_ShowString(2, 1, "LED_ON_OK");
            } else if (strcmp(Serial_RxPacket, "LED_OFF") == 0){
                LED_OFF();
                Serial_SendString("LED_OFF_OK\r\n");
                OLED_ShowString(2, 1, "                ");
                OLED_ShowString(2, 1, "LED_OFF_OK");
            } else {
                Serial_SendString("ERROR_COMMAND\r\n");
                OLED_ShowString(2, 1, "                ");
                OLED_ShowString(2, 1, "ERROR_COMMAND");
            }
            
            Serial_RxFlag = 0;
            
        }
	}
}

  • 打开串口助手(串口助手软件 请在【江协科技】视频下方下载),按照预设的数据包格式,在串口助手的发送区域编辑数据 “@LED_ON + 换行” 并点击发送后,即可观察到 LED 灯亮。
    在这里插入图片描述
    在这里插入图片描述
    注:文末 LED.h中保留了void LED_Turn(void);功能的实现,可尝试完善 LED_TURN 部分内容(输入 @LED_TURN 转换当前 LED 状态,输入 @GET_STATE 获取当前 LED 开关状态。)。
    在这里插入图片描述

三、演示代码关联的头文件与源文件说明

#include "stm32f10x.h"                  // Device header
#include "Delay.h"

void Key_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
}

uint8_t Key_GetNum(void)
{
	uint8_t KeyNum = 0;
	if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)
	{
		Delay_ms(20);
		while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0);
		Delay_ms(20);
		KeyNum = 1;
	}
	
	return KeyNum;
}

  • Key.h
#ifndef __KEY_H
#define __KEY_H

void Key_Init(void);
uint8_t Key_GetNum(void);

#endif

  • LED.c
#include "stm32f10x.h"                  // Device header

void LED_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	GPIO_SetBits(GPIOA, GPIO_Pin_6);
}

void LED_ON(void)
{
	GPIO_ResetBits(GPIOA, GPIO_Pin_6);
}

void LED_OFF(void)
{
	GPIO_SetBits(GPIOA, GPIO_Pin_6);
}

void LED_Turn(void)
{
	if (GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_6) == 0)
	{
		GPIO_SetBits(GPIOA, GPIO_Pin_6);
	}
	else
	{
		GPIO_ResetBits(GPIOA, GPIO_Pin_6);
	}
}

  • LED.h
#ifndef __LED_H
#define __LED_H

void LED_Init(void);
void LED_ON(void);
void LED_OFF(void);
void LED_Turn(void);

#endif

  • Serial_HEX.c
#include "stm32f10x.h"                  // Device header
#include <stdio.h>
#include <stdarg.h>

uint8_t Serial_TxPacket[4];
uint8_t Serial_RxPacket[4];
uint8_t Serial_RxFlag;

void Serial_Init(void){
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);    
    
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
    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_3;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_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(USART2, &USART_InitStructure);
    
    USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);
    
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_Init(&NVIC_InitStructure);
    
    USART_Cmd(USART2, ENABLE);
}

void Serial_SendByte(uint8_t Byte){
    USART_SendData(USART2, Byte);
    while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
}

void Serial_SendArray(uint8_t *Array, uint16_t Length){
    uint16_t i;
    for(i=0; i<Length; i++){
        Serial_SendByte(Array[i]);
    }
}

void Serial_SendString(char *String){
    uint8_t i;
    for(i=0; String[i] != '\0'; i++){
        Serial_SendByte(String[i]);
    }
}

uint32_t Serial_Pow(uint32_t X, uint32_t Y){
    uint32_t Result = 1;
    while(Y--){
        Result *=X;
    }
    return Result;
}

void Serial_SendNumber(uint32_t Number, uint8_t Length){
    uint8_t i;
    for(i=0; i<Length; i++){
        Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + 0x30);
    }
}

int fputc(int ch, FILE *f){
    Serial_SendByte(ch);
    return ch;
}

void Serial_printf(char *format, ...){
    char String[100];
    va_list arg;
    va_start(arg, format);
    vsprintf(String, format, arg);
    va_end(arg);
    Serial_SendString(String);
}

void Serial_SendPacket(void){
    Serial_SendByte(0xFF);
    Serial_SendArray(Serial_TxPacket, 4);
    Serial_SendByte(0xFE);
}

uint8_t Serial_GetRxFlag(void){
    if(Serial_RxFlag==1){
        Serial_RxFlag = 0;
        return 1;
    }
    return 0;
}

void USART2_IRQHandler(void){
    static uint8_t RxState = 0;
    static uint8_t pRxState = 0;
    if(USART_GetITStatus(USART2, USART_IT_RXNE) == SET){
        uint8_t RxData = USART_ReceiveData(USART2);
        
        if (RxState == 0){
            if (RxData == 0xFF){
                RxState =1;
                pRxState = 0;
            }
        }else if (RxState == 1){
            Serial_RxPacket[pRxState] = RxData;
            pRxState ++;
            if (pRxState >= 4){
                RxState = 2;
            }
        }else if (RxState == 2){
            if (RxData == 0xFE){
                RxState =0;
                Serial_RxFlag = 1;
            }
        }
        
        USART_ClearITPendingBit(USART2, USART_IT_RXNE);
    }
}

  • Serial_HEX.h
#ifndef __SERIAL_HEX_H
#define __SERIAL_HEX_H

#include <stdio.h>

extern uint8_t Serial_TxPacket[];
extern uint8_t Serial_RxPacket[];

void Serial_Init(void);
void Serial_SendByte(uint8_t Byte);
void Serial_SendArray(uint8_t *Array, uint16_t Length);
void Serial_SendString(char *String);
void Serial_SendNumber(uint32_t Number, uint8_t Length);
int fputc(int ch, FILE *f);
void Serial_printf(char *format, ...);

void Serial_SendPacket(void);

uint8_t Serial_GetRxFlag(void);

#endif

  • Serial_TEXT.c
#include "stm32f10x.h"                  // Device header
#include <stdio.h>
#include <stdarg.h>

char Serial_RxPacket[100];
uint8_t Serial_RxFlag;

void Serial_Init(void){
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);    
    
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
    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_3;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_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(USART2, &USART_InitStructure);
    
    USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);
    
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_Init(&NVIC_InitStructure);
    
    USART_Cmd(USART2, ENABLE);
}

void Serial_SendByte(uint8_t Byte){
    USART_SendData(USART2, Byte);
    while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
}

void Serial_SendArray(uint8_t *Array, uint16_t Length){
    uint16_t i;
    for(i=0; i<Length; i++){
        Serial_SendByte(Array[i]);
    }
}

void Serial_SendString(char *String){
    uint8_t i;
    for(i=0; String[i] != '\0'; i++){
        Serial_SendByte(String[i]);
    }
}

uint32_t Serial_Pow(uint32_t X, uint32_t Y){
    uint32_t Result = 1;
    while(Y--){
        Result *=X;
    }
    return Result;
}

void Serial_SendNumber(uint32_t Number, uint8_t Length){
    uint8_t i;
    for(i=0; i<Length; i++){
        Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + 0x30);
    }
}

int fputc(int ch, FILE *f){
    Serial_SendByte(ch);
    return ch;
}

void Serial_printf(char *format, ...){
    char String[100];
    va_list arg;
    va_start(arg, format);
    vsprintf(String, format, arg);
    va_end(arg);
    Serial_SendString(String);
}

void USART2_IRQHandler(void){
    static uint8_t RxState = 0;
    static uint8_t pRxState = 0;
    if(USART_GetITStatus(USART2, USART_IT_RXNE) == SET){
        uint8_t RxData = USART_ReceiveData(USART2);
        
        if (RxState == 0){
            if (RxData == '@' && Serial_RxFlag == 0){
                RxState =1;
                pRxState = 0;
            }
        }else if (RxState == 1){
            if (RxData == '\r'){
                RxState = 2;
            } else {
                Serial_RxPacket[pRxState] = RxData;
                pRxState ++;
            }            
        }else if (RxState == 2){
            if (RxData == '\n'){
                RxState =0;
                Serial_RxPacket[pRxState] = '\0';
                Serial_RxFlag = 1;
            }
        }
        
        USART_ClearITPendingBit(USART2, USART_IT_RXNE);
    }
}

  • Serial_TEXT.h
#ifndef __SERIAL_TEXT_H
#define __SERIAL_TEXT_H

#include <stdio.h>

extern char Serial_RxPacket[];
extern uint8_t Serial_RxFlag;

void Serial_Init(void);
void Serial_SendByte(uint8_t Byte);
void Serial_SendArray(uint8_t *Array, uint16_t Length);
void Serial_SendString(char *String);
void Serial_SendNumber(uint32_t Number, uint8_t Length);
int fputc(int ch, FILE *f);
void Serial_printf(char *format, ...);

#endif


  文中部分知识参考:B 站 —— 江协科技;百度百科

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐