当前位置:网站首页>Arduino中Serial.print()与Serial.write()函数的区别,以及串口通信中十六进制与字符串的收发格式问题和转换过程详解
Arduino中Serial.print()与Serial.write()函数的区别,以及串口通信中十六进制与字符串的收发格式问题和转换过程详解
2022-06-10 19:49:00 【chenyfan_】
1、串口通信中十六进制和字符数据的区别
在使用串口发送数据时可以选择字符串(ASCII)发送或者十六进制(Hex)发送,通常情况下我们习惯选用字符串发送数据。
在计算机中,数据是以二进制的形式存储的,串口发送的数据,本质上来讲,就是 0 和 1 这样的二进制,但是在编译时,可能使用16进制进行表示。
对于 ASCII码(字符),其本质上也是二进制数据,可以使用16进制表示,可以使用10进制表示,也可以使用字符表示。在串口通讯过程中,是以16进制进行表示,以二进制进行传输的。(即先将字符转化为ASCII码,然后转化为十六进制表示,最后用对应的二进制数进行传输)
下面来对比不同的收发格式:
(1)十六进制:由于 1 位⼗六进制数位宽为 4bits ,那么 2 位十六进制数占有⼀个字节的位宽,所以当以16进制格式收发时,每个字节发送或者接收2位十六进制数, 举个例子 ,当以16进制格式发送⼀组数据 ‘’ 0F3C781A ‘’ 时 , 每个字节对应的数据如下:
| 发送数据 | 0x0F | 0x3C | 0x78 | 0x1A |
|---|---|---|---|---|
| 字节数 | 1 | 2 | 3 | 4 |
由于计算机传输数据都是以二进制数进行传输的,参照十六进制收发格式的原理 ,每位⼆进制数位宽为 1bit ,那么串⼝每个字节可以传输 8 位⼆进制数,那么,在传输数据 ''0F3C781A ‘’ 时 ,每个字节对应的数据即为上表中十六进制数对应的⼆进制数。
| 发送数据 | 0000 1111 | 0011 1100 | 0111 1000 | 0001 1010 |
|---|---|---|---|---|
| 字节数 | 1 | 2 | 3 | 4 |
(2)字符:串⼝在以字符格式收发数据时 ,因为每个字符在 ASCII 码表中对应成⼆进制码都是8bit 宽的⼆进制数 ,正好为⼀个字节,所以默认先将该字符转换为对应的⼆进制数然后发送,相当于每个字节发送⼀个字符。
串⼝接收端如果是⼆进制格式,那么将直接显示;
如果为十六进制,即显示该字符在ASCII码表中对应的2位⼗六进制数 ;
如果串⼝接收端以字符格式显示的话即将接收到的⼆进制数按照 ASCII 码表再转换为对应的字符 (该字符与发送的字符相同) 然后显示。
| 字符 | 0 | F | 3 | C | 7 | 8 | 1 | A |
|---|---|---|---|---|---|---|---|---|
| 对应的16进制数 | 0x30 | 0x46 | 0x33 | 0x43 | 0x37 | 0x38 | 0x31 | 0x41 |
| 对应的2进制数 | 0011_0000 | 0100_0110 | 0011_0011 | 0100_0011 | 0011_0111 | 0011_1000 | 0011_0001 | 0100_0001 |
那么以字符格式发送该段数据后,分别以字符格式、16进制、⼆进制格式接收到的数据为:
| 接收字节数 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|---|---|---|---|---|---|---|---|---|
| 接收字符 | 0 | F | 3 | C | 7 | 8 | 1 | A |
| 接收6进制数 | 0x30 | 0x46 | 0x33 | 0x43 | 0x37 | 0x38 | 0x31 | 0x41 |
| 接收2进制数 | 0011_0000 | 0100_0110 | 0011_0011 | 0100_0011 | 0011_0111 | 0011_1000 | 0011_0001 | 0100_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; // 0x31
ByteSend[2] = (i >> 8) & 0xFF; // 0x5A
ByteSend[3] = i & 0xFF; // 0x7A
for(int n = 0; n < 4; ++n){
Serial.write(ByteSend[n]); // 发送一个字节(十六进制数对应的ASCII码)
delay(50);
}
}
| 十六进制接收 | 30 | 31 | 5A | 7A |
|---|---|---|---|---|
| 实际传输数据 | 00110000 | 00110001 | 01011010 | 01111010 |
| ASCII对应字符 | 0 | 1 | Z | z |
- 2、发送端的数据格式为表示十六进制数的字符串
void setup() {
Serial.begin(9600);
char i[] = "30315A7A";
Serial.write(i); // 与 Serial.print(i) 等效
}
若想以上代码一样,直接以字符串格式进行发送,ASCII 对应字符串虽然为 “30315A7A”,但是十六进制数却完全不同,而且传输了8个字节的数据(比十六进制多一倍),内容对比如下:
| 实际传输数据 | 00110011 | 00101111 | 00110011 | 00110001 | 00110101 | 01000001 | 00110111 | 01000001 |
|---|---|---|---|---|---|---|---|---|
| 十六进制接收 | 33 | 30 | 33 | 31 | 35 | 41 | 37 | 41 |
| ASCII对应字符 | 3 | 0 | 3 | 1 | 5 | A | 7 | A |
因此,需要先将字符串转换为十六进制数,再通过串口以字节形式进行发送。
// 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’.
边栏推荐
- [enter textbook latex record]
- hidden danger? Limited meaning? Can't stop the real cooking Mini kitchenware hot 618
- PDU session flow
- 传音 Infinix 新机现身谷歌产品库,TECNO CAMON 19 预装 Android 13
- Mysql database foundation
- Electronic bidding procurement mall system: optimize traditional procurement business and speed up enterprise digital upgrading
- Networkx usage and nx Draw() related parameters
- Fs4100 lithium battery charging management IC input 12V to 8.4v charging IC
- AttributeError: module ‘collections‘ has no attribute ‘MutableMapping‘
- LeetCode:1037. 有效的回旋镖————简单
猜你喜欢
随机推荐
H5 van-popup全屏弹窗,模拟页面回退效果,支持左上角返回按钮,适用物理返回,侧滑与底部返回键
力扣1082,1084题解_sql查询类型的题目
轻便型FDW框架 for pb
In depth learning experience and tools
View play and earn will lead crypto games astray
pytorch深度学习——神经网络卷积层Conv2d
Microsoft Word tutorial, how to change page orientation and add borders to pages in word?
国庆期间给大家推荐一个可能会成为2019最佳的CRUD工具
What are the conditions for opening an account for agricultural futures? How much is the service charge for opening an account now?
【Educational Codeforces Round 120 (Rated for Div. 2)】C. Set or Decrease
传音 Infinix 新机现身谷歌产品库,TECNO CAMON 19 预装 Android 13
Microsoft Word tutorial "5", how to change the margins and create a newsletter column in word?
Cloud native community boss blog
【legendre】多项式
When can Flink support the SQL client mode? When can I specify the applicati for submitting tasks to yarn
35岁被裁员,还能拥有美妙人生吗?
Fs4100 lithium battery charging management IC input 12V to 8.4v charging IC
Solve the problem that the idea automatically becomes * when there are more than 5 identical packages
Error code 1129, state HY000, host 'xxx' is blocked because of many connection errors
P5723 【深基4.例13】质数口袋









