Arduino Serial使用以及各种读写函数区别

之前有一篇文章总结了Python的串口使用,自认为已经深得要领。直到最近开始研究Arduino,用的串口传输数据,心里还是有很多问题,时常在运用相关函数的时候想起之前Python上的串口知识点,两者有着千丝万缕的关系。从头开始理一下思路,原理都是一样的,计算机只懂得二进制,而人类懂得ASCII文本,如何理解两者之间架起的这座桥梁甚是关键….

1. 前言

常见的串口调试是将Arduino与PC相连,然后利用PC上的串口助手与其通信调试,但是有一个注意点是,调试Arduino串口时,用串口助手调试与跟程序语言(比如Python,Arduino语言)调试的传输的数据有区别,比如串口助手输入框中输入97时你可能输入的是两个字符97,而在用Arduino语法(Serial.write(97))写的时候就是代表ASCII的a,或者在Python中是ser.write(b'a')也可写成ser.write([97])),两者调试方法有明显的差别性。

2. 两个重要的写函数(write,print)

2.1 Serial.write()

串口中通信的一定是byte(字节),也就是八位Bit(二进制位),可以为一个字节,也可为多个字节;如果想发送代表数字的字符,应该使用print()函数(这个后面说);该函数返回有几个字节被发送了。

Serial.write()一共有三种语法

  • Serial.write(val)

发送单个字节,比如发送97,那么等于发送十进制ASCII码值为97的字符,也就是字母a

  • Serial.write(str)

发送字符串作为一系列的字节,比如int bytesSent = Serial.write(“hello”);

注意,这里发送字符串一定要用双引号,我一开始习惯了Python的用法,单双引号看心情来用,直接只收到一个字节,问题很大

  • Serial.write(buf, len)

发送一个长度为len的数组(一般为char类型数组),跟上面Serial.write(str)作用是一致的,只是传入参数不一样

2.2 Serial.print()

Prints data to the serial port as human-readable ASCII text.

println与print类似,只是多了换行符,这里只介绍print函数。
上面这行英文已经把它跟read()函数的关系分的很清楚了。这个命令可以有多种形式。每个数字都使用ASCII字符打印。浮点数类似地打印为ASCII数字,默认为两位小数。字节作为单个字符发送。字符和字符串按原样发送。例如:

1
2
3
4
5
6
7
8
Serial.print(78) gives "78"

Serial.print(1.23456) gives "1.23"

Serial.print('N') gives "N"

Serial.print("Hello world.") gives "Hello world."

还有第二个可选参数format是具体的格式,允许二进制八进制十六进制等,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
Serial.print(78, BIN) gives "1001110"

Serial.print(78, OCT) gives "116"

Serial.print(78, DEC) gives "78"

Serial.print(78, HEX) gives "4E"

Serial.print(1.23456, 0) gives "1"

Serial.print(1.23456, 2) gives "1.23"

Serial.print(1.23456, 4) gives "1.2346"

2.3 Serial.write与Serial.print函数的区别

谢邀,我觉得print,printf是开发出来专门针对pc端显示的,write则是用来与串口设备通信的,当然在老手眼里怎么用都行 ——————from逼乎某大佬回答

按照arduino官网reference的解释,Serial. print()print data to the serial port as human-reading ASC II text ,Serial. write()write binary data to the serial port ,一个转化为文本输出,一个是数据输出。

我在测试的时候刚开始并没有发现两者的区别。起初我认为传数据就该用Serial.write,而对于Serial.print这是在用串口助手的时候用,打印一些格式啥的,好利于调试辨认啥的等等(事实确实如此,但不仅于此)

于是我就将这两者的区别与Python中sys.stdout.write与内置的print函数作比较,前者不会写换行符然后是写到缓冲区,而print函数每次都有换行符,清空了缓冲区,然后就打印到屏幕上了,而且print函数自带了很多格式,可以跟format函数配合啥的,倒是跟Arduino的print有几分相似……但是其实这并不是重点~

我认为Arduino中的writeprint还是存在缓冲区这个区别的,比如是在write中设置了timeout等操作,达到了与print类似的操作,这里只是猜测下,不做深究

重点来了!

我在这里Arduino 的 Serial.write() 和 Serial.print() 的区别在哪里?看到了答案?

  • 在输出字符或字符串时,没有任何区别
  • 在输出数值时,write会直接输出数据本身,而print会将其转化为可显示的ASCII字符(其实我觉得不妥,转换是串口助手来转换的,应该是算是说转换成可以传输的ASCII码值)

当时我的想法跟amazing814答主一样,而跟最高赞同者那位相反,后来发现我们概念理解错了
他“错误”的回答如下:

1, print 出来的是真实数值,
2, write出来的是ascii码表对应的值(或者是说”对应的图形”)

错在了串口助手会自动将数据转换程ASCII文本! 反正,你要知道的是,串口发送或者读取到的数据都是字节!这一点用Python串口模块自己尝试一下就行!

所以Serial.writeSerial.print最大的区别就是传进函数中的数据,前者是真正的原始数据(int型或者说二进制位的数值),得到的是数据本身,而使用print函数的时候会以多个字节的形式向串口传递括号中数值,会将它看成一个字符串,传递其中每一个字符的ASCII码。例如你举的例子“78”会向串口传递“7”和“8”的ASCII码的值。

比如 Serial.write(78) ,发送的是N(换算成二进制就是01001110),得到的也是N的字节(即二进制位),虽然串口助手上显示的是N
与Serial.print(78),发送的78两个字节,内部先把78转换成5556的原始数据,在发送出去,得到也是55,56

所以那位错误的答主,想说的其实并不是“真实”,而是“相同”! 真正的计算机数据是二进制位(为了方便,转换成十进制了,也就是上面的55,56)

print主要用于”给人看”的地方. 电脑内部通讯, 如果程序内不需要用人眼再检查, 用 write 会比较好, 直接快捷.

3. Serial.read 相关函数

3.1 Serial.read()

读取即将来的串口数据,但是只读取第一个字节,且这个字节的数据类型为int,即ASCII码值。

通常与Serial.available()一起用,例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int incomingByte = 0; // for incoming serial data

void setup() {
Serial.begin(9600); // opens serial port, sets data rate to 9600 bps
}

void loop() {
// send data only when you receive data:
if (Serial.available() > 0) {
// read the incoming byte:
incomingByte = Serial.read();

// say what you got:
Serial.print("I received: ");
Serial.println(incomingByte, DEC);
}
}

串口发送数据与读到数据都为字节,所以该函数return回来int 型数据很正常,但是我们可以直接拿来跟字符比较或者用到某些话函数中,这得益于在C语言中单引号括起一个字符实际上代表一个整数,即ASCII码值

,从Arduino内置的例子就能看到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
void setup() {
// Open serial communications and wait for port to open:
Serial.begin(9600);
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
}

// send an intro:
Serial.println("send any byte and I'll tell you everything I can about it");
Serial.println();
}

void loop() {
// get any incoming bytes:
if (Serial.available() > 0) {
int thisChar = Serial.read();

// say what was sent:
Serial.print("You sent me: \'");
Serial.write(thisChar);
Serial.print("\' ASCII Value: ");
Serial.println(thisChar);

// analyze what was sent:
if (isAlphaNumeric(thisChar)) {
Serial.println("it's alphanumeric");
}
if (isAlpha(thisChar)) {
Serial.println("it's alphabetic");
}
if (isAscii(thisChar)) {
Serial.println("it's ASCII");
}
if (isWhitespace(thisChar)) {
Serial.println("it's whitespace");
}
if (isControl(thisChar)) {
Serial.println("it's a control character");
}
if (isDigit(thisChar)) {
Serial.println("it's a numeric digit");
}
if (isGraph(thisChar)) {
Serial.println("it's a printable character that's not whitespace");
}
if (isLowerCase(thisChar)) {
Serial.println("it's lower case");
}
if (isPrintable(thisChar)) {
Serial.println("it's printable");
}
if (isPunct(thisChar)) {
Serial.println("it's punctuation");
}
if (isSpace(thisChar)) {
Serial.println("it's a space character");
}
if (isUpperCase(thisChar)) {
Serial.println("it's upper case");
}
if (isHexadecimalDigit(thisChar)) {
Serial.println("it's a valid hexadecimaldigit (i.e. 0 - 9, a - F, or A - F)");
}

// add some space and ask for another byte:
Serial.println();
Serial.println("Give me another byte:");
Serial.println();
}
}

3.2 Serial.readBytes(buffer, length)

Serial.readBytes从串行端口读取字符到缓冲区,返回放入缓冲区的字节数,如果超时该函数将中断

这个函数我看到的用法是放在字符数组中,然后再利用数组的特性进行一些操作,例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
char mystr[10]; //Initialized variable to store recieved data

void setup() {
// Begin the Serial at 9600 Baud
Serial.begin(9600);
}

void loop() {
Serial.readBytes(mystr,5); //Read the serial data and store in var
Serial.println(mystr); //Print data on Serial Monitor
for (byte i = 0; i < 5; i = i + 1) {
Serial.println(mystr[i]);
delay(1000);
}

3.3 Serial.readString()

Serial.readString将串行缓冲区中的字符读入String,如果超时该函数将中断

1
2
3
4
5
6
7
8
9
10
11
12
String a;

void setup() {
Serial.begin(9600); // opens serial port, sets data rate to 9600 bps
}
void loop() {

while(Serial.available()) {
a= Serial.readString();// read the incoming data as string
Serial.println(a);
}
}

当时这个函数与上面的搞不太清楚,所以试了下。其实这个函数是读取发送过来的字符串,存到一个String类型的变量里,用来打印,或者处理(Serial.write()无法字节发送String类型的变量)再发出去等等,可以看一下String类型的变量很多个方法比如toCharArray()toInt()之类的

4. 总结

经历了两天的疑惑解惑过程,发现自己关于串口的基本功还是不够扎实,这个过程中需要自己不断反复地尝试与验证,剥开代码语言的皮囊,看到更下面真实的骨架,其实也挺开心的,故再花点时间整理在这里留作总结以及反思!