串口通信学习

不需要校验位就选8位,需要校验位就选9位!

USRT

USART框图

STM32的外设引脚

这是USART的基本结构。

数据帧,八位是

这个公式还是很重要的!

如果在编辑器里面使用printf打印汉字的话,会出现乱码的话,前提是你的编码格式使用的UTF8,就在keil5里面这里加上这个--no-multibyte-chars

HEX 数据包这个概念在不同领域有不同的含义,但核心思想是一样的:
它指的是用十六进制(Hexadecimal)形式表示的一个数据包,数据包包含通信所需的头部、数据区和校验等信息

1.“HEX”是什么意思?

  • HEX十六进制 的缩写。

  • 在计算机和嵌入式通信中,二进制数据通常用十六进制表示,因为它更简洁、人类更容易阅读。

  • 例如:

    • 二进制:1010 1111

    • 十六进制:0xAF

2.“数据包”是什么意思?

  • 数据包(Data Packet)是通信双方传输的完整数据单元

  • 一个数据包通常包含:

    1. 帧头 / 起始标志(Start Byte / Header)
      用来标识一个包的开始,例如 0xAA 0x55

    2. 长度字段(Length)
      表示数据区的字节数

    3. 命令字 / 功能码(Command)
      表示这个包的用途(如读取、写入、状态查询)

    4. 数据区(Data)
      实际要传输的内容

    5. 校验码(Checksum / CRC)
      用来检测数据是否损坏

    6. 帧尾 / 结束标志(End Byte)
      表示包的结束(可选)

3.HEX 数据包的定义示例

假设我们设计一个用于串口通信的 HEX 数据包格式:

[0]   帧头1         1 byte    固定为 0xAA
[1]   帧头2         1 byte    固定为 0x55
[2]   长度           1 byte    数据区+命令字的总长度
[3]   命令字         1 byte    例如 0x01 表示读取数据
[4..n]数据区         N byte    实际数据
[n+1] 校验码         1 byte    所有字节异或和或 CRC
[n+2] 帧尾           1 byte    固定为 0x0D

举例:

AA 55 04 01 10 20 35 0D
  • AA 55 帧头

  • 04 长度(后面 4 个字节:01 10 20 35)

  • 01 命令字(读取数据)

  • 10 20 数据区(两个字节的数据)

  • 35 校验码

  • 0D 帧尾

4. 为什么要用 HEX 表示数据包?

  • 可读性好
    十六进制每两个字符正好表示一个字节

  • 方便调试
    串口调试助手、逻辑分析仪等都用 HEX 格式

  • 跨平台兼容
    HEX 表示的是原始二进制,不受编码格式影响

文本数据包(Text Data Packet)指的是以文本形式(可读字符)来组织和传输的一个完整数据单元,它和 HEX 数据包最大的区别是:

  • HEX 数据包里每个字节是二进制,调试时常用十六进制显示

  • 文本数据包直接用可见字符(ASCII/UTF-8等编码)表示内容,例如 "TEMP=25.6;HUM=78%\n"

1.文本数据包的核心定义

一个文本数据包一般包含以下部分:

  • 起始标志(Start Flag)

    • 用于标识数据包的开始

    • 例如 "$$", "<START>", "#"

  • 数据内容(Payload / Body)

    • 全部是可见字符(字母、数字、符号)

    • 一般使用分隔符分割字段,例如 ,;| 或空格

  • 结束标志(End Flag)

    • 表示数据包结束

    • 常用 \n(换行符)、\r\n(回车换行)、"<END>"

  • 可选校验(Checksum)

    • 校验可以直接用十进制数字或十六进制字符串表示

    • 放在数据末尾,方便检测数据完整性

2. 文本数据包示例

串口发送传感器数据

$TEMP=25.6,HUM=78%,BAT=3.7V*
  • $ 起始标志

  • TEMP=25.6,HUM=78%,BAT=3.7V 数据区(用逗号分隔字段)

  • * 结束标志

带校验的例子(NMEA GPS 协议风格)

$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47
  • $GPGGA 起始标志+数据类型

  • 逗号分隔的多个字段

  • *47 末尾 * 后是校验值(XOR 校验)

自定义协议例子

<START>ID=001;CMD=READ;TEMP=25.6;HUM=78;<END>
  • <START> 起始标志

  • ID=001;CMD=READ;TEMP=25.6;HUM=78; 数据区,字段以 ; 分隔

  • <END> 结束标志

3.文本数据包的优缺点

优点:

  • 人类可直接阅读、调试方便(用串口助手就能看懂)

  • 跨平台性好,不依赖字节序

  • 可直接使用字符串处理函数解析

缺点:

  • 占用带宽较大(字符比原始二进制长)

  • 解析速度慢于固定结构的 HEX 数据包

  • 对浮点数等类型需要额外转换(ASCII ↔ 数值)

4.文本数据包的典型应用

  • 串口调试协议(如 AT 命令、NMEA GPS 数据)

  • HTTP、MQTT 等网络应用层协议

  • 传感器调试输出

  • 物联网设备日志与命令传输

寄存器

在计算机和单片机(包括 STM32、51 单片机等)中,寄存器(Register)是位于 CPU 内部的一种容量极小、速度极快的存储单元,用来临时保存和控制数据、指令以及硬件状态。

你可以把它想象成 CPU 手边的“超高速便利贴”

  • 内存(RAM)像是在隔壁房间的仓库,取数据需要跑过去

  • 寄存器就在 CPU 旁边,一伸手就能拿到

1.寄存器的分类

寄存器按用途大致分为两大类:

① 通用寄存器

  • 作用:临时保存运算数据、中间结果

  • 例子:x86 架构的 EAXEBX,ARM 架构的 R0 ~ R12

  • 特点:编译器和汇编程序可以自由使用

② 特殊功能寄存器(SFR, Special Function Register)

  • 作用:控制硬件外设、反映状态

  • 这些寄存器直接映射到硬件电路中,通过它们就能控制 GPIO、定时器、串口等功能

  • 在 STM32 中,这些寄存器是内存映射寄存器,用地址访问,比如:

  • GPIOA->ODR = 0x01; // 让 PA0 输出高电平
    

    这里的 ODR(Output Data Register)就是 GPIO 的输出数据寄存器。

2. 寄存器的特点

  • 速度极快(比 RAM 还快)

  • 容量很小(几十到几百个寄存器)

  • 与 CPU/外设直接连接

  • 通过位(bit)控制硬件功能

3. 寄存器在单片机中的例子

以 STM32F103 为例,假设要点亮 PA5 引脚上的 LED:

RCC->APB2ENR |= (1 << 2); // 开启 GPIOA 时钟
GPIOA->CRL &= ~(0xF << 20); // 清空 PA5 模式位
GPIOA->CRL |=  (0x1 << 20); // 设置 PA5 为推挽输出
GPIOA->ODR |=  (1 << 5);    // 置位 PA5 输出高电平
  • RCC->APB2ENR:外设时钟使能寄存器

  • GPIOA->CRL:端口配置寄存器低位(控制 PA0~PA7)

  • GPIOA->ODR:输出数据寄存器

这些寄存器本质上都是内存地址,比如 GPIOA->ODR 实际是:

0x4001080C

往这个地址写 1,就等于给 PA5 脚送高电平。

4.用简单比喻理解

  • 寄存器:CPU 桌上的小便签,拿取速度最快(直接操作)

  • RAM:隔壁房间的文件柜(速度较慢)

  • 硬盘:地下仓库(速度最慢)

C语言可变参数

C 语言可变参数(Variable Arguments)指的是一个函数在声明时参数的数量不固定,可以根据调用时的需要传入不同数量的实参。

最典型的例子就是标准库中的 printf() 函数:

printf("Hello %s, age %d\n", "Tom", 18);

printf 的第一个参数是固定的格式化字符串,后面跟多少参数由格式字符串决定,这就是可变参数的用法。

一、、可变参数函数的声明方式

在函数形参列表的末尾使用省略号 ... 表示:

#include <stdarg.h> // 必须包含的头文件void myFunc(int count, ...); // count 表示后面有多少参数
  • 固定参数:省略号前的部分,必须有至少一个固定参数(方便定位可变参数起点)。

  • 可变参数:省略号 ... 表示数量和类型在编译期不固定。

二、可变参数的原理

在 C 语言中,可变参数通过 栈传递stdarg.h 提供了访问它们的宏:

  • va_list —— 保存参数信息的变量类型

  • va_start —— 初始化 va_list,定位到可变参数起点

  • va_arg —— 取出一个参数

  • va_end —— 清理工作

三、可变参数函数实现示例

例如写一个求任意数量整数和的函数:

#include <stdio.h>
#include <stdarg.h>// sum(count, ...): 传入 count 个整数,返回它们的和
int sum(int count, ...) {va_list args;           // 定义参数列表变量va_start(args, count);  // 初始化,从 count 后的参数开始取int total = 0;for (int i = 0; i < count; i++) {total += va_arg(args, int); // 每次取出一个 int 参数}va_end(args);           // 清理return total;
}int main() {printf("%d\n", sum(3, 10, 20, 30)); // 输出 60printf("%d\n", sum(5, 1, 2, 3, 4, 5)); // 输出 15return 0;
}

四、注意事项

  • 类型安全性差
    编译器无法检查可变参数类型是否正确,比如 va_arg(args, int) 和实际类型不匹配会导致错误行为。

  • 必须依赖固定参数来控制读取数量
    否则无法知道何时停止读取。

  • 跨平台注意数据对齐
    参数在栈上的对齐方式可能和平台架构有关。

  • 宏和可变参数
    宏中也能用 ... 表示可变参数(C99 及之后)。

#include <stdio.h>
#include <stdarg.h>void show(int count, ...) {va_list args;va_start(args, count); // 定位到第一个可变参数for (int i = 0; i < count; i++) {int val = va_arg(args, int); // 依次取出一个 intprintf("%d\n", val);}va_end(args);
}int main() {show(3, 10, 20, 30);return 0;
}

内存栈图示(调用 show(3, 10, 20, 30) 时)

假设我们是 x86 栈向下增长 的情况(地址从高到低),函数调用时的栈大致如下:

高地址
┌───────────────────────┐
│    返回地址            │ ← main 调用 show 后返回的地址
├───────────────────────┤
│ count = 3             │ ← 固定参数
├───────────────────────┤
│ 10                    │ ← 第1个可变参数
├───────────────────────┤
│ 20                    │ ← 第2个可变参数
├───────────────────────┤
│ 30                    │ ← 第3个可变参数
└───────────────────────┘
低地址

va_start(args, count)

  • va_start 的作用是:
    args 指针指向 count 后面的第一个可变参数(10)

  • 底层会用 count 在栈上的地址 + 它的大小(sizeof(count)) 来得到可变参数的起点。

  • args ──► 10
    

    va_arg(args, int)

  • va_arg 做了两件事:

    1. 取出 args 当前指向位置的值(比如第一次是 10)

    2. args 移动到下一个参数的位置(加上 sizeof(int)

  • 取值过程:

  • 第1次:args=10 → 返回10 → args指向20
    第2次:args=20 → 返回20 → args指向30
    第3次:args=30 → 返回30 → args指向结束位置
    

    va_end(args)

  • va_end 主要是做清理,防止野指针问题(实际可能什么都不做,但必须写)

总结:

  • va_start:定位到第一个可变参数

  • va_arg:取值并移动指针

  • va_end:结束可变参数处理

  • 栈上参数是连续存放的,所以可以用指针依次取出

定时器中断

定时器中断其实就是利用单片机(或 CPU)里的定时器硬件模块,在设定的时间间隔自动触发中断服务函数,让你在固定时间做某件事。

它结合了两个东西:

  1. 定时器(硬件计时器)

  2. 中断机制(硬件事件触发 CPU 自动跳到某段代码执行)

1.基本原理

可以把它想成一个厨房的闹钟

  • 你在闹钟上设定“10分钟”

  • 闹钟(定时器硬件)开始计时

  • 时间一到,闹钟“叮”一下(产生中断信号)

  • 你(CPU)放下手里的事,去处理闹钟(执行中断函数)

  • 处理完再继续原来的工作

在 STM32 或 51 单片机中:

  • 定时器寄存器 控制定时周期

  • 中断控制器(NVIC)接收到定时器溢出事件后调用中断服务函数(ISR)

2.定时器中断的触发流程

  • 配置定时器参数

    • 预分频器(Prescaler):降低时钟频率

    • 自动重装值(ARR):定时器计数到这个值时溢出

  • 使能定时器中断

    • 设置定时器的 UIE(更新中断使能)位

    • NVIC 使能对应的中断通道

  • 启动定时器

  • 计数溢出 → 触发中断请求(IRQ)

  • 执行中断服务函数(ISR)

    • 在 ISR 中处理任务(如 LED 翻转、计时器变量++ 等)

  • 清除中断标志

    • 防止中断反复触发

3. STM32 定时器中断示例

#include "stm32f10x.h"void TIM2_IRQHandler(void) {if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) {TIM_ClearITPendingBit(TIM2, TIM_IT_Update); // 清除中断标志GPIOA->ODR ^= (1 << 5); // 翻转 PA5}
}void Timer2_Init(void) {RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);GPIO_InitTypeDef gpio;gpio.GPIO_Pin = GPIO_Pin_5;gpio.GPIO_Mode = GPIO_Mode_Out_PP;gpio.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &gpio);TIM_TimeBaseInitTypeDef tim;tim.TIM_Period = 9999; // ARRtim.TIM_Prescaler = 7199; // PSCtim.TIM_ClockDivision = TIM_CKD_DIV1;tim.TIM_CounterMode = TIM_CounterMode_Up;TIM_TimeBaseInit(TIM2, &tim);TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);NVIC_EnableIRQ(TIM2_IRQn);TIM_Cmd(TIM2, ENABLE);
}int main(void) {Timer2_Init();while (1) {// 主循环可做其他事}
}

上面例子里:

  • 定时器频率 = 72MHz / (PSC+1) / (ARR+1) = 72MHz / 7200 / 10000 = 1Hz

  • 每秒进一次中断,ISR 里翻转一次 LED

4.定时器中断的应用

  • 周期性任务调度(实时操作系统里的节拍)

  • LED 闪烁

  • 传感器采样定时

  • 电机 PWM 更新

  • 超时检测

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/92880.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/92880.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

面试经典150题[001]:合并两个有序数组(LeetCode 88)

合并两个有序数组&#xff08;LeetCode 88&#xff09; https://leetcode.cn/problems/merge-sorted-array/?envTypestudy-plan-v2&envIdtop-interview-150 1. 题目背景 你有两个已经排好序的数组&#xff1a; nums1&#xff1a;前面是有效数字&#xff0c;后面是空位&…

快速安装达梦8测试库

计划&#xff1a;数据库名实例名PORT_NUMMAL_INST_DW_PORTMAL_HOSTMAL_PORTMAL_DW_PORTDMDWDBINST_1533615101192.168.207.612510135101*****[2025-08-11 15:14:34]***** Last login: Fri Jul 25 17:36:04 2025 from 192.168.88.48 [rootdm01 ~]# ip a 1: lo: <LOOPBACK,UP,…

Hive中优化问题

一、小文件合并优化Hive中的小文件分为Map端的小文件和Reduce端的小文件。(1)、Map端的小文件优化是通过CombineHiveInputFormat操作。相关的参数是&#xff1a;set hive.input.formatorg.apache.hadoop.hive.ql.io.CombineHiveInputFormat;(2)、Reduce端的小文件合并Map端的小…

tlias智能学习辅助系统--Maven高级-继承

目录 一、打包方式与应用场景 二、父子工程继承关系 1. 父工程配置 2. 子工程配置 三、自定义属性与引用属性 1. 定义属性 2. 在 dependencyManagement 中引用 3. 子工程中引用 四、dependencyManagement 与 dependencies 的区别 五、项目结构示例 六、小结 在实际开…

把 AI 押进“小黑屋”——基于 LLM 的隐私对话沙盒设计与落地

标签&#xff1a;隐私计算、可信执行环境、LLM、沙盒、内存加密、TEE、SGX、Gramine ---- 1. 背景&#xff1a;甲方爸爸一句话&#xff0c;“数据不能出机房” 我们给某三甲医院做智能问诊助手&#xff0c;模型 70 B、知识库 300 GB。 甲方只给了两条铁律&#xff1a; 1. 患者…

Java 大视界 -- Java 大数据在智能教育学习效果评估指标体系构建与精准评估中的应用(394)

Java 大视界 -- Java 大数据在智能教育学习效果评估指标体系构建与精准评估中的应用&#xff08;394&#xff09;引言&#xff1a;正文&#xff1a;一、传统学习评估的 “数字陷阱”&#xff1a;看不全、说不清、跟不上1.1 评估维度的 “单行道”1.1.1 分数掩盖的 “学习真相”…

Dubbo 3.x源码(33)—Dubbo Consumer接收服务调用响应

基于Dubbo 3.1&#xff0c;详细介绍了Dubbo Consumer接收服务调用响应 此前我们学习了Dubbo Provider处理服务调用请求的流程&#xff0c;现在我们来学习Dubbo Consumer接收服务调用响应流程。 实际上接收请求和接收响应同属于接收消息&#xff0c;它们的流程的很多步骤是一样…

栈和队列:数据结构中的基础与应用​

栈和队列&#xff1a;数据结构中的基础与应用在计算机科学的领域中&#xff0c;数据结构犹如大厦的基石&#xff0c;支撑着各类复杂软件系统的构建。而栈和队列作为两种基础且重要的数据结构&#xff0c;以其独特的特性和广泛的应用&#xff0c;在程序设计的舞台上扮演着不可或…

服务端配置 CORS解决跨域问题的原理

服务端配置 CORS&#xff08;跨域资源共享&#xff09;的原理本质是 浏览器与服务器之间的安全协商机制。其核心在于服务器通过特定的 HTTP 响应头声明允许哪些外部源&#xff08;Origin&#xff09;访问资源&#xff0c;浏览器根据这些响应头决定是否放行跨域请求。以下是详细…

Unity笔记(五)知识补充——场景切换、退出游戏、鼠标隐藏锁定、随机数、委托

写在前面&#xff1a;写本系列(自用)的目的是回顾已经学过的知识、记录新学习的知识或是记录心得理解&#xff0c;方便自己以后快速复习&#xff0c;减少遗忘。主要是C#代码部分。十七、场景切换和退出游戏1、场景切换场景切换使用方法&#xff1a; SceneManager.LoadScene()&a…

用 Spring 思维快速上手 DDD——以 Kratos 为例的分层解读

用 Spring 思维理解 DDD —— 以 Kratos 为参照 ​ 在此前的学习工作中&#xff0c;使用的开发框架一直都是 SpringBoot&#xff0c;对 MVC 架构几乎是肌肉记忆&#xff1a;Controller 接请求&#xff0c;Service 写业务逻辑&#xff0c;Mapper 操作数据库&#xff0c;这套套路…

docspace|Linux|使用docker完全离线化部署onlyoffice之docspace文档协作系统(全网首发)

一、 前言 书接上回&#xff0c;Linux|实用工具|onlyoffice workspace使用docker快速部署&#xff08;离线和定制化部署&#xff09;-CSDN博客&#xff0c;如果是小公司或者比如某个项目组内部使用&#xff0c;那么&#xff0c;使用docspace这个文档协同系统是非常合适的&…

【教程】如何高效提取胡萝卜块根形态和颜色特征?

胡萝卜是全球不可或缺的健康食材和重要的经济作物&#xff0c; 从田间到餐桌&#xff0c;从鲜食到深加工&#xff0c;胡萝卜在现代人的饮食和健康中扮演着极其重要的角色&#xff0c;通过量化块根形态和色泽均匀性&#xff0c;可实现对高产优质胡萝卜品种的快速筛选。工具/材料…

Python初学者笔记第二十四期 -- (面向对象编程)

第33节课 面向对象编程 1. 面向对象编程基础 1.1 什么是面向对象编程面向过程&#xff1a;执行者 耗时 费力 结果也不一定完美 面向对象&#xff1a;指挥者 省时 省力 结果比较完美面向对象编程(Object-Oriented Programming, OOP)是一种编程范式&#xff0c;它使用"对象&…

Go 语言 里 `var`、`make`、`new`、`:=` 的区别

把 Go 语言 里 var、make、new、: 的区别彻底梳理一下。1️⃣ var 作用&#xff1a;声明变量&#xff08;可以带初始值&#xff0c;也可以不带&#xff09;。语法&#xff1a; var a int // 声明整型变量&#xff0c;默认值为 0 var b string // 默认值 ""…

计算机网络---IP(互联网协议)

一、IP协议概述 互联网协议&#xff08;Internet Protocol&#xff0c;IP&#xff09;是TCP/IP协议族的核心成员&#xff0c;位于OSI模型的网络层&#xff08;第三层&#xff09;&#xff0c;负责将数据包从源主机传输到目标主机。它是一种无连接、不可靠的协议&#xff0c;提供…

DataFun联合开源AllData社区和开源Gravitino社区将在8月9日相聚数据治理峰会论坛

&#x1f525;&#x1f525; AllData大数据产品是可定义数据中台&#xff0c;以数据平台为底座&#xff0c;以数据中台为桥梁&#xff0c;以机器学习平台为中层框架&#xff0c;以大模型应用为上游产品&#xff0c;提供全链路数字化解决方案。 ✨杭州奥零数据科技官网&#xff…

【工具】通用文档转换器 推荐 Markdown 转为 Word 或者 Pdf格式 可以批量或者通过代码调用

【工具】通用文档转换器 推荐 可以批量或者通过代码调用 通用文档转换器 https://github.com/jgm/pandoc/ Pandoc - index 下载地址 https://github.com/jgm/pandoc/releases 使用方法: 比如 Markdown 转为 Word 或者 Pdf格式 pandoc -s MANUAL.txt -o example29.docx …

【UEFI系列】Super IO

文章目录一、什么是Super IO二、Super IO的作用常见厂商三、逻辑设备控制如何访问SIO逻辑设备的配置寄存器具体配置数值四、硬件监控&#xff08;hardware monitor&#xff09;一、什么是Super IO Super Input/Output超级输入输出控制器。 通过LPC&#xff08;low pin count&a…

飞算 JavaAI 2.0.0 测评:自然语言编程如何颠覆传统开发?

一、前言 在AI技术高速发展的今天&#xff0c;编程方式正在经历一场革命。传统的“手写代码”模式逐渐被AI辅助开发取代&#xff0c;而飞算JavaAI 2.0.0的推出&#xff0c;更是让自然语言编程成为现实。 作为一名长期使用Java开发的程序员&#xff0c;我决定深度体验飞算Java…