通过串行读取 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 之类的函数打印的值?更有可能的是,该函数实际上确实分别返回了0xC3
和0xA4
。如果您的编译器将char
视为已签名,这些将被解释为负十进制值。如果实际值很重要,则不应使用char
,而应使用uint8_t
。
如果char
(由于某些原因)提升为int
,则“大于”0x80 的char
值将显示为0xFFnn(其中nn 是字符的十六进制值)。
如何将utf8值插入变量cmdString
?
【参考方案1】:
您可能会遇到 C 语言的一些怪癖(这里并没有真正涉及 C++,它继承了这些规则)。
char
类型与signed char
和unsigned char
不同,它是由实现定义的普通char
是否可以具有负值(如果可以,是哪个)。显然你的系统允许-91
。
第二个怪癖是char
类型的表达式经常被提升为int
,例如当您比较((char)0xC0) < 0
时,右侧是0
,一个int
,所以左侧也提升为int
。
这不是 UTF-8 特有的。 ISO-8859-x 也有同样的问题。
【讨论】:
这里不是问题 感谢您的澄清。我会考虑到这一点。以上是关于通过串行读取 utf8 时,Arduino 行为异常的主要内容,如果未能解决你的问题,请参考以下文章
Arduino Uno Raspberry Pi 串行通信双读数