You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Go多线程读取Arduino串口出现乱码的问题求助

多线程读取Arduino串口出现乱码的问题分析与解决

你的问题核心不是Arduino无输出时的读取,而是多个goroutine同时操作同一个bufio.Scanner导致的线程安全问题——bufio.Scanner本身并不是线程安全的,多个协程同时调用Scan()会打乱它的内部缓冲区状态,直接导致数据解析混乱、出现乱码。

先看你的原代码:

func readFirstLine(scanner *bufio.Scanner, port *serial.Port){
    for scanner.Scan() {
        log.Printf("%q\n", scanner.Text())
    }
    time.Sleep(time.Duration(1)*time.Second)
}

func readSecondLine(scanner *bufio.Scanner, port *serial.Port){
    time.Sleep(time.Duration(1)*time.Second)
    for scanner.Scan() {
        log.Printf("%q\n", scanner.Text())
    }
}

func main() {
    usbRead := &serial.Config{Name: "COM5", Baud: 9600, ReadTimeout: 0}
    port, err := serial.OpenPort(usbRead)
    if err != nil {
        log.Fatal(err)
    }
    scanner := bufio.NewScanner(port)
    for true {
        go readFirstLine(scanner, port)
        go readSecondLine(scanner, port)
        time.Sleep(time.Duration(2)*time.Second)
    }
}

你预期的输出是完整的JSON格式数据:

{"temperature":[26,26],"humidity":[54.2,54.2],"sensor":"DHT22"}
{"temperature":[23.46041,23.46041],"sensor":"LM35DZ"}
{"blink":["true","true"],"actuator":"arduinoLED"}
{"temperature":[26,26],"humidity":[54.2,54.2],"sensor":"DHT22"}
{"temperature":[23.46041,23.46041],"sensor":"LM35DZ"}
{"blink":["true","true"],"actuator":"arduinoLED"}

但实际出现乱码,本质是多个协程同时抢占scanner的读取权限,导致缓冲区数据被拆分、重叠读取。

解决方法

正确的做法是用单个goroutine负责串口的读取工作,然后将读取到的完整行数据通过channel分发给其他需要处理的协程,这样既保证了串口读取的线程安全,也能实现多线程处理数据的需求。

修改后的代码示例:

import (
    "bufio"
    "log"
    "time"

    "github.com/tarm/serial"
)

// 处理读取到的串口数据的协程1
func processLine1(lineChan <-chan string) {
    for line := range lineChan {
        log.Printf("Processor 1 received: %q\n", line)
    }
}

// 处理读取到的串口数据的协程2
func processLine2(lineChan <-chan string) {
    for line := range lineChan {
        log.Printf("Processor 2 received: %q\n", line)
    }
}

// 单独的串口读取协程
func readSerial(port *serial.Port, lineChan chan<- string) {
    defer close(lineChan)
    scanner := bufio.NewScanner(port)
    // 如果你的JSON行可能超过默认缓冲区大小,可以调整缓冲区
    // buf := make([]byte, 1024)
    // scanner.Buffer(buf, bufio.MaxScanTokenSize)
    for scanner.Scan() {
        line := scanner.Text()
        lineChan <- line
    }
    if err := scanner.Err(); err != nil {
        log.Fatalf("Serial read error: %v", err)
    }
}

func main() {
    usbRead := &serial.Config{Name: "COM5", Baud: 9600, ReadTimeout: time.Second * 1} // 设置合理的读取超时
    port, err := serial.OpenPort(usbRead)
    if err != nil {
        log.Fatal(err)
    }
    defer port.Close()

    lineChan := make(chan string, 10) // 带缓冲的channel,避免阻塞读取

    // 启动处理协程
    go processLine1(lineChan)
    go processLine2(lineChan)

    // 启动串口读取协程
    readSerial(port, lineChan)
}

关键说明

  1. 线程安全保障:单个协程负责串口读取,避免多个协程操作同一个bufio.Scanner,从根源上解决竞态条件导致的乱码。
  2. 数据分发:通过channel将完整的行数据传递给处理协程,保证每个处理协程拿到的都是完整、正确的JSON数据。
  3. 超时设置:给串口配置合理的ReadTimeout,避免读取协程无限阻塞在无数据的状态。
  4. 缓冲区调整:如果你的JSON数据行较长,可以调整scanner.Buffer()的大小,避免因缓冲区不足导致的截断问题。

内容的提问来源于stack exchange,提问作者Melissa

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 07:50:09