跳到主要内容

串口设备和 Arduino

Serial* 内置函数可以与任何显示为 COM 端口的设备通信:通过 USB 连接的 Arduino、ESP32 或 Raspberry Pi Pico,USB 转串口适配器,GPS 接收器,处于串口模式的条码扫描器,小票或标签打印机,接受 G 代码的 3D 打印机或 CNC 控制器,实验室仪器,或者自制的按钮盒、宏键盘、旋钮或 LED 控制器。设备收发的是一行行文本;您的脚本可以把这些行变成 Input.Observer 能做的任何其他事情。

一些思路:

  • 按钮盒或宏键盘。Arduino 读取其按钮并打印 B1、B2……串口监视器将每一行变成一个快捷键、一个媒体键或一条窗口命令。
  • 音量或滚动旋钮。电位器或旋转编码器打印其位置;监视器据此设置音量或进行滚动。
  • 状态灯。手势或热键向驱动 LED 灯带的开发板发送 LED 255 0 0,用灯光表示正在录制或构建失败。
  • 传感器。计时器向开发板询问温度或光照强度,然后显示该值、将其记录到文件中,或者调暗屏幕。
  • 扫描器和读卡器。读取器发送的每个条码或 RFID 标签都会变成键入的文本、一次搜索,或在文件中的一次查找。
  • 机器。向打印机或绘图仪发送 G 代码行,每条命令一次 SerialWriteTextLine。

查找端口​

Windows 会为每个设备分配一个类似 COM3 的名称。设备管理器在 端口 (COM 和 LPT) 下列出它;Arduino IDE 在其开发板菜单中显示它。脚本也可以列出这些端口:

n = SerialEnumeratePorts();
if (n == 0) UtilityPrint("No COM ports. Is the board plugged in?");
for (i = 0; i < n; i = i + 1) {
UtilityPrint(SerialGetEnumeratedPortAt(i));
}

列表按编号排序,因此 COM3 排在 COM10 之前。当设备换到另一个 USB 插口时,编号可能会改变。

速率、帧格式和行结束符​

  • 速率(baudRate)必须与设备一致:即 Arduino 草图(sketch)中 Serial.begin(...) 里的数字,通常为 9600 或 115200。
  • 帧格式几乎总是 8 个数据位、无校验、1 个停止位:8、SerialParity.None、SerialStopBits.One。请写常量:作为停止位参数的普通数字 1 表示 1.5 个停止位。
  • 行结束符。接收到的一行在您指定的 terminator 处结束,该结束符会从行中移除。空文本表示 CR LF,即 Arduino 的 Serial.println 所发送的内容。对于只以 LF 结束行的设备,请使用 "\n";只以 CR 结束行的设备,请使用 "\r"。SerialWriteTextLine 发送的内容以同一个结束符结尾;如果是该调用自己打开端口,则以 CR LF 结尾。使用 CR LF 时,用 Serial.readStringUntil('\n') 读取的 Arduino 草图得到的行末尾会带有一个 CR,可用 trim() 去除;使用 "\n" 时则没有需要去除的 CR。
  • 信号。打开端口时会开启其 DTR 和 RTS 信号,与 Arduino IDE 的做法相同。这会使大多数 Arduino Uno 和 Nano 开发板以及采用相同复位电路的其他开发板重启。

与设备通信的三种方式​

  • 一问一答。在未打开的端口上调用 SerialWriteTextLine 和 SerialGetTextLine 时,会以该调用的 baudRate、8-N-1 打开该端口,仅用于这一次调用。端口已打开时,将忽略 baudRate 参数。适用于打开端口时不会重启的设备。请参阅向串口设备提问。

  • 保持端口打开。SerialOpenPort 以您的设置打开端口,直到调用 SerialClosePort 为止,并收集设备发送的每一行,供 SerialGetTextLine 读取。大多数 Arduino Uno 和 Nano 开发板在端口每次打开时都会重启,因此请在 Load 全局事件中只打开一次端口,然后让手势和热键向其写入。对于不存在、已拔出或已被其他程序打开的端口,SerialOpenPort 返回 false;对于 Input.Observer 已打开或被监视器占用的端口,它会使操作因错误而停止。

    // Load global event: open the port once, at the sketch's speed. An Uno or Nano
    // restarts every time its port opens, so a port opened per command would lose
    // the command while the board boots.
    if (!SerialOpenPort("COM4", 115200, SerialParity.None, 8, SerialStopBits.One, "")) {
    UtilityPrint("COM4 isn't available: unplugged, or open in the Arduino IDE's Serial Monitor?");
    }

    // Any gesture or hotkey: send a command line and read the board's reply.
    // (The sketch reads a line with Serial.readStringUntil('\n') and answers OK.)
    if (SerialWriteTextLine("COM4", "LED 255 0 0", 115200)) {
    reply = SerialGetTextLine("COM4", 1, 115200);
    if (reply != "OK") UtilityPrint(StringFormat("Board said: {0}", reply, ""));
    }
  • 监视器。SerialMonitorCreate 在后台为设备发送的每一行运行一段脚本,直到调用 SerialMonitorDelete 为止。该行由 ContextGetSerialTextLine() 获取。请参阅将串口设备的按钮映射到媒体键以及:

    // Arduino side: a potentiometer on A0, one reading per line.
    // void setup() { Serial.begin(115200); }
    // void loop() { Serial.println(analogRead(A0)); delay(50); }
    // Empty text as the terminator means CR LF, which Serial.println sends.
    SerialMonitorCreate("knob", "COM4", 115200, SerialParity.None, 8, SerialStopBits.One, "",
    "line = ContextGetSerialTextLine(); if (StringIsNumber(line, true)) MultimediaSetVolume(AudioEndpoint.Playback, StringToNumber(line, true) / 1023.0);");
备注

关于监视器需要了解的事项。

  • 各行按到达顺序逐一运行:上一行的脚本结束后,下一行的脚本才会开始。最多可有 256 行等待;超出后会丢弃最早的行。
  • 停止所有操作会停止正在为某一行运行的脚本,并丢弃等待中的行。监视器本身继续运行。停止所有操作热键则更进一步:它会删除每个监视器并关闭其端口。
  • 任何脚本(包括监视器自身的脚本)都可以用 SerialWriteTextLine 向被监视的端口发送内容,因此监视器可以应答其设备。SerialGetTextLine 无法读取被监视的端口:每一行都交给监视器。
  • 每个监视器名称只属于一个端口。使用该端口当前监视器的名称调用 SerialMonitorCreate 会替换该监视器。如果该名称已在监视另一个端口、端口已用 SerialOpenPort 打开,或另一个监视器占用了该端口,它会使操作因错误而停止。不存在、已拔出或已被其他程序打开的端口会得到 false。
  • 拔下设备会结束监视器,并在控制台的 系统 选项卡中写入一行。设备重新连接时不会自动重启监视器。请再次运行 SerialMonitorCreate(通过热键,或在下次启动时通过 Load 事件)。
注意

同一时间只能有一个程序打开某个 COM 端口。当 Arduino IDE 的串口监视器或串口绘图仪处于打开状态时,Input.Observer 无法打开该端口;而当 Input.Observer 打开了该端口时,IDE 无法上传草图。上传之前请先关闭端口(SerialClosePort 或 SerialMonitorDelete)。