1. Arduino串口通讯基础入门

第一次接触Arduino的串口通讯时,我也被那些专业术语搞得一头雾水。直到后来才发现,串口通讯其实就是Arduino和电脑"聊天"的一种方式。想象一下,你的Arduino板子就像个话痨,而串口通讯就是它说话的嘴巴和听声音的耳朵。

Serial.begin()就像是给这个话痨打开嘴巴和耳朵。我刚开始玩Arduino时,经常忘记写这行代码,结果板子就像个哑巴一样,怎么喊都不答应。后来才明白,这个函数有两个重要作用:一是初始化串口硬件,二是设置通讯速度。常见的波特率有9600、19200、57600和115200等,就像两个人聊天要约定好说话的速度一样,太快了会听不清,太慢了又浪费时间。

这里有个小技巧:波特率设置要匹配。我在项目中最常使用的是9600,因为这个速度对于大多数调试需求来说已经足够,而且兼容性最好。记得有一次用115200的波特率调试,结果串口监视器里全是乱码,折腾了半天才发现是波特率没设对。

void setup() {
  Serial.begin(9600); // 我习惯在setup里初始化串口
}

2. Serial.print和Serial.println的区别详解

刚开始用Arduino时,我总搞不清print和println的区别,直到有一天在调试一个传感器数据时,发现所有数值都挤在一起,完全分不清谁是谁。这才明白println最后的"ln"代表"line",会自动在输出后加换行符。

print()就像用笔在纸上写字,写完一个字接着写下一个;而println()则是写完一个字后换一行。这个区别在输出多个变量时特别重要。比如要输出温度和湿度两个值:

float temp = 25.5;
float humidity = 60.0;

// 不推荐的写法 - 两个值会连在一起
Serial.print(temp);
Serial.print(humidity); // 输出:25.560.0

// 推荐的写法
Serial.print("Temp: ");
Serial.print(temp);
Serial.print(" Humidity: ");
Serial.println(humidity); // 输出:Temp: 25.5 Humidity: 60.0

实际项目中,我更喜欢在println里直接拼接字符串,代码更简洁:

Serial.println("Temp: " + String(temp) + " Humidity: " + String(humidity));

3. 多格式输出技巧实战

Arduino的串口输出最强大的功能之一就是支持多种数据格式。记得第一次需要查看二进制数据时,我还在傻傻地手动转换,后来发现Serial.print()本身就支持多种格式输出。

常用的格式有:

  • DEC:十进制(默认)
  • HEX:十六进制
  • OCT:八进制
  • BIN:二进制

这个功能在调试通信协议时特别有用。比如分析I2C设备地址:

byte deviceAddress = 0x68;

void setup() {
  Serial.begin(9600);
  Serial.print("Device address in DEC: ");
  Serial.println(deviceAddress, DEC);
  Serial.print("Device address in HEX: ");
  Serial.println(deviceAddress, HEX);
  Serial.print("Device address in BIN: ");
  Serial.println(deviceAddress, BIN);
}

void loop() {}

输出结果会显示:

Device address in DEC: 104
Device address in HEX: 68
Device address in BIN: 1101000

对于浮点数输出,还可以控制小数位数。比如要显示温度值保留两位小数:

float temperature = 25.5678;
Serial.println(temperature, 2); // 输出:25.57

4. 串口调试实战技巧

在实际项目中,串口调试是最常用的排错手段。经过多个项目的磨练,我总结出几个实用技巧:

结构化输出:给数据加上标签和时间戳,方便后期分析。比如:

void logData(float temp, float humi) {
  Serial.print(millis());
  Serial.print("ms - Temp: ");
  Serial.print(temp);
  Serial.print("C, Humi: ");
  Serial.println(humi);
  Serial.println("------------------");
}

条件输出:通过定义调试宏来控制输出,正式发布时可以关闭调试信息:

#define DEBUG 1

#if DEBUG
  #define DEBUG_PRINT(x) Serial.println(x)
#else
  #define DEBUG_PRINT(x)
#endif

多传感器数据采集:用表格形式输出多个传感器数据,便于观察关联性:

void printSensorData() {
  Serial.println("Time\tTemp\tHumidity\tLight");
  Serial.print(millis());
  Serial.print("\t");
  Serial.print(readTemperature());
  Serial.print("\t");
  Serial.print(readHumidity());
  Serial.print("\t");
  Serial.println(readLight());
}

5. 常见问题与解决方案

在长期使用串口通讯的过程中,我踩过不少坑,这里分享几个典型问题:

乱码问题:最常见的原因是波特率不匹配。确保代码中的Serial.begin()和串口监视器的波特率设置一致。如果还不行,尝试降低波特率。

数据丢失:当发送数据过快时,缓冲区可能溢出。解决方法包括:

  1. 增加延迟:Serial.println(data); delay(10);
  2. 检查发送缓冲区:if(Serial.availableForWrite() > 20) { Serial.println(data); }

长时间运行后停止输出:可能是USB接口供电不稳定导致的。可以尝试:

  1. 使用质量更好的USB线
  2. 给Arduino单独供电
  3. 在代码中加入看门狗复位

多设备干扰:当同时连接多个串口设备时,记得每个设备需要不同的串口实例。比如在Arduino Mega上:

void setup() {
  Serial.begin(9600);   // USB串口
  Serial1.begin(9600);  // 硬件串口1
  Serial2.begin(9600);  // 硬件串口2
}

6. 高级应用:自定义串口协议

当项目复杂度增加时,简单的println可能就不够用了。我设计过一个智能家居控制器,需要Arduino和手机APP双向通信。这时就需要定义自己的通信协议。

基本框架包括:

  1. 帧头(标识数据开始)
  2. 命令类型
  3. 数据长度
  4. 数据内容
  5. 校验和
  6. 帧尾

示例代码:

void sendData(byte cmd, byte len, byte* data) {
  byte checksum = cmd + len;
  
  Serial.write(0xAA); // 帧头
  Serial.write(cmd);
  Serial.write(len);
  
  for(int i=0; i<len; i++) {
    Serial.write(data[i]);
    checksum += data[i];
  }
  
  Serial.write(checksum);
  Serial.write(0x55); // 帧尾
}

bool receiveData() {
  if(Serial.available() >= 5) { // 最小帧长度
    if(Serial.read() == 0xAA) {
      byte cmd = Serial.read();
      byte len = Serial.read();
      
      if(Serial.available() >= len + 2) {
        byte data[len];
        byte checksum = cmd + len;
        
        for(int i=0; i<len; i++) {
          data[i] = Serial.read();
          checksum += data[i];
        }
        
        if(Serial.read() == checksum && Serial.read() == 0x55) {
          // 数据接收成功
          processCommand(cmd, len, data);
          return true;
        }
      }
    }
  }
  return false;
}

7. 性能优化技巧

当项目需要高频度串口通信时,性能优化就很重要了。经过多次测试,我发现以下几个方法很有效:

减少字符串操作:字符串拼接会消耗较多内存和时间。比如:

// 不推荐 - 会产生临时字符串对象
Serial.println("Value: " + String(value));

// 推荐 - 直接输出
Serial.print("Value: ");
Serial.println(value);

使用二进制传输:当传输大量数据时,二进制比文本格式更高效。比如传输10个传感器读数:

int sensors[10];

void sendBinaryData() {
  Serial.write((byte*)sensors, sizeof(sensors));
}

缓冲区管理:通过Serial.availableForWrite()检查可用空间,避免阻塞:

void sendWhenReady(const char* data) {
  while(Serial.availableForWrite() < strlen(data)) {
    delay(1);
  }
  Serial.print(data);
}

批量发送:将多个小数据打包后一次性发送:

char buffer[64];
int index = 0;

void appendToBuffer(const char* str) {
  int len = strlen(str);
  if(index + len < sizeof(buffer)) {
    memcpy(buffer + index, str, len);
    index += len;
  }
}

void sendBuffer() {
  if(index > 0) {
    Serial.write(buffer, index);
    index = 0;
  }
}
Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐