在使用arduino uno与多个传感器和上位机进行串口通信时,受制于arduino羸弱的性能,常常无法发挥传感器的全部性能.因此,我将简述一些提高arduino串口效率的技巧.
查阅官网后,我们可以看到,目前(2021/1/1),硬串口Serial共有如下函数:
Functions | |
---|---|
if(Serial) | print() |
available() | println() |
availableForWrite() | read() |
begin() | readBytes() |
end() | readBytesUntil() |
find() | readString() |
findUntil() | readStringUntil() |
flush() | setTimeout() |
parseFloat | write() |
parseInt() | serialEvent() |
peek() |
串口函数基础
串口初始化阶段常用函数简介
if(Serial)
常常被用于在void setup()
阶段等待串口设置完成.官方示例代码如下:
void setup() {
//Initialize serial and wait for port to open:
Serial.begin(9600);
while (!Serial) {
; // wait for serial port to connect. Needed for native USB
}
}
void loop() {
//proceed normally
}
Serial.begin()
和Serial.end()
则分别被用于初始化串口以及关闭串口.注意初始化过程运行开销较大,因此如非特殊情况,请尽量不要在void setup()
以外的地方使用.
Serial.available()
可以获取到接收缓冲区Rx还有多少个字符,它返回的实际上是接收缓冲区的头尾指针的差值:
int HardwareSerial::available(void)
{
return ((unsigned int)(SERIAL_RX_BUFFER_SIZE + _rx_buffer_head - _rx_buffer_tail)) % SERIAL_RX_BUFFER_SIZE;
}
从代码中我们可以看出,当串口接收缓冲区Rx为空,那么Serial.available()
的返回值为0.
Serial.availableForWrite()
函数则能够在不影响write()
这一过程的前提下,获取串口写缓存TX中能够写的字符的数量.
Serial.flush()
目前的作用是等待串口完成write()
这一过程.当然在不考虑具体输出,而纸上谈兵又要确保兼容性的情况下,write()
函数的优化空间并不算大.
接下来最需要强调的就是void serialEvent()
,这是串口中断函数,在每次循环之间,如果有数据传入硬串口的Rx端,那么就会调用这个函数.在中断里执行的代码由于不再出现在void loop()
当中,因此可以省去调用Serial.available()
这类函数进行判断的时间.以下面的代码为例:
String inputString="";
void setup(){
Serial.begin(9600);
}
void loop(){
while(Serial.available()){
inputString=inputString+char(Serial.read());
delay(2);
}
if(inputString.length()>0){
Serial.println(inputString);
inputString="";
}
} //项目使用了 3220 字节,占用了 (9%) 程序存储空间.最大为 32256 字节.
//全局变量使用了204字节,(9%)的动态内存,余留1844字节局部变量.最大为2048字节.
String inputString = ""; // a String to hold incoming data
bool stringComplete = false; // whether the string is complete
void setup() {
Serial.begin(9600);
}
void loop() {
if (stringComplete) {
Serial.println(inputString);
inputString = "";
stringComplete = false;
}
}
void serialEvent() {
while (Serial.available()) {
char inChar = (char)Serial.read();
inputString += inChar;
if (inChar == '\n') {
stringComplete = true;
}
} //项目使用了 3080 字节,占用了 (9%) 程序存储空间.最大为 32256 字节.
} //全局变量使用了205字节,(10%)的动态内存,余留1843字节局部变量.最大为2048字节.
通过把回车符设为字符串的终止符,我们把代码执行的速度和代码量都做到了一定的优化.
Serial.read()与Serial.peek()
接下来,我们来对比一下HardwareSerial中的最后两个自带函数peek()
和read()
.先放代码:
int HardwareSerial::peek(void)
{
if (_rx_buffer_head == _rx_buffer_tail) {
return -1;
} else {
return _rx_buffer[_rx_buffer_tail];
}
}
int HardwareSerial::read(void)
{
// if the head isn't ahead of the tail, we don't have any characters
if (_rx_buffer_head == _rx_buffer_tail) {
return -1;
} else {
unsigned char c = _rx_buffer[_rx_buffer_tail];
_rx_buffer_tail = (rx_buffer_index_t)(_rx_buffer_tail + 1) % SERIAL_RX_BUFFER_SIZE;
return c;
}
}
由此可见,peek()
相比read()
在C语言代码的层面省去了创建变量和Rx缓存尾指针自增这两个过程.在实际代码中,使用peek()
会比read()
更快吗?不同于我们朴素的直觉,实际效率取决于具体的使用场景.
串口优化实例
有时串口传输的数据具有多种格式,我们来看一个激光测距传感器的例子:
D=1.314m,520#<CR><LF> | 表示距离为1.314米,回光量为520 |
---|---|
E=258<CR><LF> | 表示超出量程,错误码为258 |
让我们暂且放下吐槽这些谐音梗的想法,如果需要让arduino提取出测距得到的距离信息,并且把发生错误时的距离返回值设为-1,我们可以使用如下写法:
if(Serial.read()=='D')
distance = GetDistance();//GetDistance指用于获取距离的一段伪代码
else
distance = -1;
我们可以把上述代码修改为
if(