Arduino中Serial.print()与Serial.write()函数的区别,以及串口通信中十六进制与字符串的收发格式问题和转换过程详解

news/2024/10/23 14:32:06/

1、串口通信中十六进制和字符数据的区别

串口收发数据时字符、十六进制、二进制格式详细区分
ASCII码查询表格

在使用串口发送数据时可以选择字符串(ASCII)发送或者十六进制(Hex)发送,通常情况下我们习惯选用字符串发送数据。

在计算机中,数据是以二进制的形式存储的,串口发送的数据,本质上来讲,就是 0 和 1 这样的二进制,但是在编译时,可能使用16进制进行表示。

对于 ASCII码(字符),其本质上也是二进制数据,可以使用16进制表示,可以使用10进制表示,也可以使用字符表示。在串口通讯过程中,是以16进制进行表示,以二进制进行传输的。(即先将字符转化为ASCII码,然后转化为十六进制表示,最后用对应的二进制数进行传输)

下面来对比不同的收发格式:

(1)十六进制:由于 1 位⼗六进制数位宽为 4bits ,那么 2 位十六进制数占有⼀个字节的位宽,所以当以16进制格式收发时,每个字节发送或者接收2位十六进制数, 举个例子 ,当以16进制格式发送⼀组数据 ‘’ 0F3C781A ‘’ 时 , 每个字节对应的数据如下:

发送数据0x0F0x3C0x780x1A
字节数1234

由于计算机传输数据都是以二进制数进行传输的,参照十六进制收发格式的原理 ,每位⼆进制数位宽为 1bit ,那么串⼝每个字节可以传输 8 位⼆进制数,那么,在传输数据 ''0F3C781A ‘’ 时 ,每个字节对应的数据即为上表中十六进制数对应的⼆进制数。

发送数据0000 11110011 11000111 10000001 1010
字节数1234

(2)字符:串⼝在以字符格式收发数据时 ,因为每个字符在 ASCII 码表中对应成⼆进制码都是8bit 宽的⼆进制数 ,正好为⼀个字节,所以默认先将该字符转换为对应的⼆进制数然后发送,相当于每个字节发送⼀个字符。
串⼝接收端如果是⼆进制格式,那么将直接显示;
如果为十六进制,即显示该字符在ASCII码表中对应的2位⼗六进制数 ;
如果串⼝接收端以字符格式显示的话即将接收到的⼆进制数按照 ASCII 码表再转换为对应的字符 (该字符与发送的字符相同) 然后显示。

字符0F3C781A
对应的16进制数0x300x460x330x430x370x380x310x41
对应的2进制数0011_00000100_01100011_00110100_00110011_01110011_10000011_00010100_0001

那么以字符格式发送该段数据后,分别以字符格式、16进制、⼆进制格式接收到的数据为:

接收字节数12345678
接收字符0F3C781A
接收6进制数0x300x460x330x430x370x380x310x41
接收2进制数0011_00000100_01100011_00110100_00110011_01110011_10000011_00010100_0001

总结:
其实在串口收发数据时,不同的数据格式只是数据的一个表现形式,最重要的是要将收发端对数据的解析协议统一。

在发送数据时,我们通常使用字符串的格式来记录数据的,如果接收端要求是字符串格式,那么发送数据直接发送即可(自动将字符对应的ASCII码转换成8位二进制数发送);

如果接收端要求是十六进制格式,比如发送0x0F3C781A,我们在发送端用字符串格式保存即为 “0x0F3C781A”,此时我们需要手动将这段字符串转换成对应的十六进制数然后发送,确保接收端收到的数据就是 0000 1111 0011 1100 0111 1000 0001 1010,即0x0F3C781A,而不是以字符发送的二进制格式 0011_0000 0100_0110 0011_0011 0100_0011 0011_0111 0011_1000 0011_0001 0100_0001。

举例:

在这里插入图片描述

CR和LF 起源于机械打字机时代。
CR(Carriage Return)表示回车,使光标到行首,用符号 \r表示,十进制ASCII代码是13,十六进制代码为0x0D;
LF(Line Feed)表示换行,使光标下移一格,使用\n符号表示,ASCII代码是10,十六制为0x0A。
Dos和Windows采用回车+换行(CR+LF,\r\n)表示下一行 而UNIX/Linux采用换行符(LF,\n)表示下一行
苹果机(MAC OS系统)则采用回车符(CR,\r)表示下一行

2、Arduino中Serial.print()与Serial.write()函数的区别

首先需要知道:
Serial.print() 发送的是字符
Serial.write() 发送的字节

在发送不同类型的数据时存在不一样的区别:

1、发送十进制的数据

void setup() {Serial.begin(9600);int i = 97;Serial.print(i);delay(50);Serial.write(i);
}
  • 用Serial.print() 发送的过程

十进制 int 97 —— 转化为字符型数据 char 9、和char 7—— 发送 9 和 7 的ASCII码 (00111001 00110111),对应十六进制数为 39 37.

若串口监视器以ASCII格式接收,则直接显示 97;
若以十六进制Hex格式接收,则显示39 37。
  • 用Serial.write() 发送的过程

十进制 int 97 —— Serial.write() 发送十进制数 97 对应的ASCII码 (二进制10010111)

若串口监视器以ASCII格式接收,则显示 a;
若以十六进制Hex格式接收,则显示61。

ASCII: 97a
Hex: 39 37 61

2、发送十六进制的数据

void setup() {Serial.begin(9600);int i = 0x7A;Serial.print(i);delay(50);Serial.write(i);
}
  • 用Serial.print() 发送的过程

十六进制 int 0x7A —— 默认转换为十进制 122 —— 转化为字符型数据 ‘1’ ‘2’ ‘2’—— Serial.print() 发送 ‘1’ ‘2’ ‘2’ 的ASCII码 (00110001 00110010 00110010),对应十六进制数为 31 32 32.

若串口监视器以ASCII格式接收,则直接显示 122;
若以十六进制Hex格式接收,则显示 31 32 32。
  • 用Serial.write() 发送的过程

十六进制 int 0x7A —— Serial.write() 发送十六进制数 0x7A 对应的ASCII码 (二进制01111010)

若串口监视器以ASCII格式接收,则显示 z;
若以十六进制Hex格式接收,则显示 7A。

ASCII: 122z
Hex: 31 32 32 7A

3、发送二进制的数据

void setup() {Serial.begin(9600);int i = B01101101;Serial.print(i);delay(50);Serial.write(i);
}
  • 用Serial.print() 发送的过程*

二进制 int B01101101 —— 默认转换为十进制 109 —— 转化为字符型数据 ‘1’ ‘0’ ‘9’—— Serial.print() 发送 ‘1’ ‘0’ ‘9’ 的ASCII码 (00110001 00110000 00111001),对应十六进制数为 31 30 39.

若串口监视器以ASCII格式接收,则直接显示 109;
若以十六进制Hex格式接收,则显示 31 30 39。
  • 用Serial.write() 发送的过程

二进制 int B01101101 —— Serial.write() 发送二进制数 0xB01101101 对应的ASCII码 (十六进制为6D)

若串口监视器以ASCII格式接收,则显示 m;
若以十六进制Hex格式接收,则显示 6D。

ASCII: 109m
Hex: 31 30 39 6D

4、发送字符型数据时没有区别

void setup() {Serial.begin(9600);char i[] = "char";Serial.print(i);delay(50);Serial.write(i);
}
  • 用Serial.print() 发送的过程

字符串 “char” —— Serial.print() 发送 ‘c’ ‘h’ ‘a’ ‘r’ 的ASCII码 (01100011 01101000 01100001 01110010),对应十六进制数为 63 68 61 72.

若串口监视器以ASCII格式接收,则显示 char;
若以十六进制Hex格式接收,则显示 63 68 61 72。
  • 用Serial.write() 发送的过程

字符串 “char” —— Serial.write() 发送 ‘c’ ‘h’ ‘a’ ‘r’ 字符对应的ASCII码 (01100011 01101000 01100001 01110010),对应十六进制数为 63 68 61 72.

若串口监视器以ASCII格式接收,则显示 char;
若以十六进制Hex格式接收,则显示 63 68 61 72。

ASCII: charchar
Hex: 63 68 61 72 63 68 61 72

总结一下,
Serial.print() 发送的是字符
Serial.write() 发送的字节

也就是说,
Serial.print() 会经过 其他进制数 -> 默认十进制数 -> 字符的类型转换,最后发送多个字符对应的ASCII码的二进制数据,一个字符对应一个字节(8位)。

此外,Serial.print(val, format)还能通过设置 format 将 数字val 转换为指定进制类型进行发送;Serial.print(val) 等效于Serial.print(val, DEC)

Serial.write()不经过转换,直接发送不同类型数据对应的ASCII码的二进制数据。

3、十六进制接收 以及字符串到十六进制数的转换

  • 1、发送端的数据格式为十六进制数
void setup() {Serial.begin(9600);unsigned long i = 0x30315A7A;		// 4字节,32位char ByteSend[4] = {0};			// 也可以是 int ByteSend[4] = {0};ByteSend[0] = (i >> 24) & 0xFF;	// 0x30,右移后 与操作,得到一个字节(8位)ByteSend[1] = (i >> 16) & 0xFF;	// 0x31ByteSend[2] = (i >> 8) & 0xFF;	// 0x5AByteSend[3] = i & 0xFF;			// 0x7Afor(int n = 0; n < 4; ++n){Serial.write(ByteSend[n]);		// 发送一个字节(十六进制数对应的ASCII码)delay(50);}
}
十六进制接收30315A7A
实际传输数据00110000001100010101101001111010
ASCII对应字符01Zz
  • 2、发送端的数据格式为表示十六进制数的字符串
void setup() {Serial.begin(9600);char i[] = "30315A7A";Serial.write(i); 	// 与 Serial.print(i) 等效 
}

若想以上代码一样,直接以字符串格式进行发送,ASCII 对应字符串虽然为 “30315A7A”,但是十六进制数却完全不同,而且传输了8个字节的数据(比十六进制多一倍),内容对比如下:

实际传输数据0011001100101111001100110011000100110101010000010011011101000001
十六进制接收3330333135413741
ASCII对应字符30315A7A

因此,需要先将字符串转换为十六进制数,再通过串口以字节形式进行发送。

// StrToHex 将字符串转化为对应的十六进制数, pbDest为传出参数, pbSrc为待转换字符串, nLen为字符串长度的一半
// 实质就是以两个字符作为一个字节的高低位,找到这个字节代表的十六进制数在ASCII码中对应的一个字符
void StrToHex(char *pbDest, char *pbSrc, int nLen)
{char h1,h2;unsigned char s1,s2;int i;for (i=0; i<nLen; i++){h1 = pbSrc[2*i];    // 一个字节高四位h2 = pbSrc[2*i+1];  // 一个字节低四位s1 = toupper(h1) - 0x30;  // toupper(h1) 转换为大写字母,数字时则不变if (s1 > 9) s1 -= 7;      // h1非数字, ASCII中 9至A中间隔了7个符号s2 = toupper(h2) - 0x30;if (s2 > 9) s2 -= 7;pbDest[i] = s1*16 + s2;// 对于"30", s1=3, s2=0, s1*16+s2 = 48 = 0x30 = '0'// 对于"5A", h1=0x35, h2=0x41, s1=5, s2=10, s1*16+s2 = 90 = 0x5A = 'Z'}
}
void setup() {Serial.begin(9600);char i[9] = "30315A7A";char out[4] = {0};memset(out, 0 ,sizeof(out));StrToHex(out, i, 4);  // 字符串到十六进制"30315A7A" -> "01Zz"for(int n = 0; n < sizeof(out); ++n){Serial.write(out[n]);}// 以上过程等效于 Serial.print("01Zz");Serial.print("01Zz");
}

StrToHex 将字符串转化为对应的十六进制数, pbDest为传出参数, pbSrc为待转换字符串, nLen为字符串长度的一半。

实质就是以两个字符作为一个字节的高低位,找到这个字节代表的十六进制数在ASCII码中对应的一个字符;

对于 “30” ,
h1 = 0x33 = ‘3’, h2 = 0x30 = ‘0’,
s1 = 3 = 0x3, s2 = 0 = 0x0,
s1 * 16 + s2 = 48 = 0x30 = ‘0’

对于 “5A”,
h1 = 0x35 = ‘5’, h2 = 0x41 = ‘A’,
s1 = 0x35 - 0x30 = 5 = 0x5, s2= 0x41 - 0x30 - 7 = 10 = 0xA, (ASCII中 9 至 A 中间隔了7个符号)
s1 * 16 + s2 = 90 = 0x5A = ‘Z’.


http://www.ppmy.cn/news/574829.html

相关文章

苹果终端date命令_mac 终端 常用指令

开始正式研究ios 应用开发&#xff0c;由于是从C开始学起&#xff0c;所以学习下常用的mac终端指令&#xff0c;方便后续常用操作。 mac 终端 常用指令&#xff1a; 1、ls指令 用途&#xff1a;列出文件 常用参数 -w 以简洁的形式列出所有文件和文件夹信息&#xff0c;-l 详细信…

完美国际真数苹果_苹果手机:Checkm8漏洞永久性破解A5-A11设备 全线旧设备实现完美越狱...

如今使用IT数码设备的小伙伴们是越来越多了&#xff0c;那么IT数码设备当中是有很多知识的&#xff0c;这些知识很多小伙伴一般都是不知道的&#xff0c;就好比最近就有很多小伙伴们想要知道Checkm8漏洞永久性破解A5-A11设备 全线旧设备实现完美越狱 &#xff0c;那么既然现在大…

库克考虑卸任苹果 CEO,谁会是下一任接班人?

作者 | Carol 出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09; 近日&#xff0c;苹果公司现任CEO Tim Cook&#xff08;蒂姆库克&#xff09;在接受采访时表示&#xff0c;目前状态还不错&#xff0c;暂时不会卸任&#xff0c;但10年太久了&#xff0c;不可能再留任…

java关于安卓,苹果输入表情数据库处理

2019独角兽企业重金招聘Python工程师标准>>> 在APP开发中&#xff0c;大多需要涉及表情符号丰富APP的亲和力&#xff0c;但是因为我们的数据库一般是utf8编码&#xff0c;是3个字节&#xff0c;而表情符号基本都是四个字节的unicode编码。以下是通过emoji-Java解决数…

库克考虑卸任苹果CEO,谁会是下一任接班人?

作者 | Carol 出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09; 近日&#xff0c;苹果公司现任CEO Tim Cook&#xff08;蒂姆库克&#xff09;在4月5日接受采访时表示&#xff0c;目前状态还不错&#xff0c;暂时不会卸任&#xff0c;但10年太久了&#xff0c;不可能…

Windows 应用生成MiniDump文件的方法笔记

Windows应用在执行过程中遇到异常等情况默认不会像linux应用那样生成dump文件&#xff0c;可以自己注册一个函数若应用存在Unhandled Exception的时候则执行写入dump文件的操作来排查某些异常情况。 生成dump文件与注册函数的代码如下&#xff1a; // 创建Dump文件 void Cr…

DUMP文件分析1:DUMP文件简介

1.1 DUMP文件类型 Windows下Dump文件分为两大类&#xff0c;内核模式Dump和用户模式Dump。内核模式Dump是操作系统创建的崩溃转储&#xff0c;最经典的就是系统蓝屏&#xff0c;这时候会自动创建内核模式的Dump。用户模式Dump进一步可以分为完整Dump&#xff08;Full Dump&…

How to enable minidumps in Java for Windows

How to enable minidumps in Java for Windows Failed to write core dump. Minidumps are not enabled by default on client versions of Windows 错误信息 # A fatal error has been detected by the Java Runtime Environment: # # EXCEPTION_ACCESS_VIOLATION (0xc000…