通过串行读取 utf8 时,Arduino 行为异常

Posted

技术标签:

【中文标题】通过串行读取 utf8 时,Arduino 行为异常【英文标题】:Arduino behaves strangely when reading utf8 via Serial 【发布时间】:2017-08-07 10:20:56 【问题描述】:

我在向我的 arduino 发送一些 uft8 编码的字符串时遇到了一些奇怪的行为。我从this 示例开始并对其进行了修改。我的程序如下:

我正在根据示例监听 SerialEvent。通过这种方式,我不是将字符添加到字符串,而是以循环方式添加到 char[500] 数组。有两个指针变量,一个是读的,一个是写的,都递增到 499 再设置回 0。

void serialEvent() 
    while (Serial.available()) 
        // get the new byte:
        char inChar = (char)Serial.read();

        // add it to the inputString:
        if (inChar != '\r') 
            if (inChar == '\n') 
                commandsInQueue++;
                inChar = NULL;
            
            inputBuffer[ptrInputWrite] = inChar;
            if (ptrInputWrite < 499) ptrInputWrite++; else ptrInputWrite = 0;
        
    

然后在循环函数内部有一个阅读器。它在那里从该数组创建一个字符串,并通过引用将其交给解码器函数。在那里,我使用 String 的 indexOf 函数将命令拆分为各个部分。

if (commandsInQueue > 0) 
    String tmp = "";
    tmp.reserve(500);
    do 
        tmp += inputBuffer[ptrInputRead];
        if (ptrInputRead < 499) ptrInputRead++; else ptrInputRead = 0;
     while (inputBuffer[ptrInputRead] != NULL);

    rxInterpreter(tmp);

    if (ptrInputRead < 499) ptrInputRead++; else ptrInputRead = 0;
    commandsInQueue--;

解码器:

void rxInterpreter(String cmd) 
    unsigned int lastSem = 2, nextSem = NULL;

    Serial.println("Processing command: " + cmd);

    if (connectionOpen) 
        switch (cmd[0])
        
        // ...
        case 'T':
            textRefreshTime = millis();
            refreshBar = true;
            currentPos = 0;
            displayShift = 0;
            currentlyShowing = 0;

            
                unsigned short len = cmd.length();
                for (short i = 0; i < len; i++) 
                    if ((unsigned long)cmd[i] > 255) 
                        unsigned long chr = (unsigned short)cmd[i] << 8 | (byte)cmd[i + 1];
                        switch (chr) 
                        case 0xc384: //Ä
                        case 0xc3a4: //ä
                            cmd[i] = (uint8_t)B11100001; break;
                        case 0xc396: //Ö
                        case 0xc3b6: //ö
                            cmd[i] = (uint8_t)B11101111; break;
                        case 0xc39c: //Ü
                        case 0xc3bc: //ü
                            cmd[i] = (uint8_t)B11110101; break;
                        case 0xc39f: //ß
                            cmd[i] = (uint8_t)B11100010; break;
                        default:
                            Serial.println("Unbekanntes Zeichen " + String(chr, HEX));
                        

                        cmd.remove(i + 1, 1);
                        len--;
                    
                
            
// ...

该过程的一部分是解释 utf8:我的第一种方法是通过检查当前字符的值是否大于 0xC0 来查找 utf8 字符的 bit signature。这没有用。经过一番研究,我意识到,出于某种原因,从该位置返回的字符不是 8 位,而是 16 位长。所以 cmdString[i] 通常会返回一个字节,但是如果字符是一个 uft8 字符,那么它将连续返回有符号的短裤,其中高两个八位字节被填充。

例如变音符号 (ä),其 utf8 代码为 0xc3a4。我希望 cmdString[i] 为 0xc3,cmdString[i+1] 为 0xa4。然而,实际上,[i] 返回 0xffc3(十进制的-61)和 [i+1] 0xffa4(十进制的-92)。由于它将这些值解释为有符号变量,因此检查它们是否大于 0xc0 自然会失败。

我通过分别打印 Serial.println(String(cmdString[i], HEX)); 来测试它。 BINARY。它总是返回 4/16 位,而不是 2/8。

谁能给我解释一下,为什么会这样?这与我(分别是 arduino 库)从字节数组创建字符串对象的方式有关吗?我一直认为,在任何情况下 char 都是无符号字节。这甚至是我发现的错误吗?

我已经在我的代码中解决了这个问题。但我仍然想知道为什么会这样。

【问题讨论】:

a char 不是无符号的 8 位值,也不是有符号的 8 位值,因为这取决于实现。大多数编译器甚至为此提供了命令行开关。 我建议你使用unsigned char。如果您使用char,则从 0x80 到 0xFF 的值被管理为负值。 char 不可能超过 8 位,如果它大于 8 位,则可能是您正在使用 wchar "[i] 返回 0xffc3" 你怎么知道的?这是您从调试器获得的值还是您使用 printf 之类的函数打印的值?更有可能的是,该函数实际上确实分别返回了0xC30xA4。如果您的编译器将char 视为已签名,这些将被解释为负十进制值。如果实际值很重要,则不应使用char,而应使用uint8_t 如果char(由于某些原因)提升为int,则“大于”0x80 的char 值将显示为0xFFnn(其中nn 是字符的十六进制值)。 如何将utf8值插入变量cmdString 【参考方案1】:

您可能会遇到 C 语言的一些怪癖(这里并没有真正涉及 C++,它继承了这些规则)。

char 类型与signed charunsigned char 不同,它是由实现定义的普通char 是否可以具有负值(如果可以,是哪个)。显然你的系统允许-91

第二个怪癖是char 类型的表达式经常被提升为int,例如当您比较((char)0xC0) &lt; 0 时,右侧是0,一个int,所以左侧也提升为int

这不是 UTF-8 特有的。 ISO-8859-x 也有同样的问题。

【讨论】:

这里不是问题 感谢您的澄清。我会考虑到这一点。

以上是关于通过串行读取 utf8 时,Arduino 行为异常的主要内容,如果未能解决你的问题,请参考以下文章

通过串行Arduino和XBee读取数据

修改 Arduino 代码以读取串行以控制移位寄存器

Arduino Uno Raspberry Pi 串行通信双读数

读取 Arduino 的串口

Python 串行读取线是不是会中断 Arduino 循环?

Arduino - Unity串行通信