Saber codificación del puerto serie

Hola:

Cuando envío datos al puerto serie, en este caso, el Monitor serie de Arduino IDE 1.8.13 lee bien las tildes.

Captura.PNG

El problema está, que no se cual codificación usa Arduino sobre los datos que envía al puerto serie.

Es bueno saberlo ya que si envío letras con tilde a pesar que el Monito serie lo lee bien, en otro lado como el Pc, creando tu propio Monitor serie, no lo lee bien. Hay que saber que datos envía para poder codificarlo.

Un ejemplo tipo monitor serie hecho con la consola C#.

Captura 2.PNG

Tengo que saber si es en ASCII, Unicode, UTF8 o algo de eso. O como lo envía exactamente para recibirlo e interpretarlo.

Lo comento para tener un mayor control sobre Arduino, el PC y la comunicación por el puerto serie/USB en muchos dispositivos. Que no haya conflicto.

¿Alguna idea?

Saludos.

Captura.PNG

Captura 2.PNG

ASCII

Buenas:

No, no es ASCII lo que lee el Monitor Serie.

Ya lo averigüé, es UTF-8.

Código C#:

        // Detecta cualquier dato entrante.
        private static void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
        {
            SerialPort sp = (SerialPort)sender;
            sp.Encoding = Encoding.GetEncoding("utf-8");
            string entradaDatos = sp.ReadExisting(); // Almacena los datos recibidos en la variable tipo string.
            Console.WriteLine("Datos recibido desde Arduino: " + entradaDatos); // Muestra en pantalla los datos recibidos.

        }

Gracias de todas formas campeón.

Veo que tienes razón, pero ahora resulta que tengo más dudas, y yo siempre pensando en ASCII...

He hecho un programilla:

void setup() {
  Serial.begin(9600);
  Serial.println('ñ');
  Serial.println('ñ', HEX);
}

void loop() {
  
}

Y he visto lo que se recibe en el puerto serie, y la salida es:

-15439
FFFFC3B1

Buscando los código de la 'ñ' resulta que para UTF-8 es 0xC3B1 y para UTF-16 es FEFF00F1, así que me mosquea el hecho de que arduino mande dos FF mas...

El -15439 tiene lógica, su representación en binario, complemento a dos, es 1100001110110001, es decir C3B1 en hexa.

Hola:

-15439
FFFFC3B1

¿Te lo soltó en el Monitor serie o en otro programa?

Saludos.

Esto es interesante

void setup() {
  Serial.begin(9600);
 char myChar[2] = "ñ";
Serial.println(myChar[0],HEX);
Serial.println(myChar[1],HEX);
 Serial.write(myChar,2 );
  
}

void loop() {

}

Salida

 FFFFFFC3
FFFFFFB1
ñ

Al parecer, el problema es Serial.println que no puede aceptar un char sin completar con ff.

Hola:

Me he dado cuenta que la ñ y Ñ no me la coje, entonces no es SOLUCINADO como puse arriba, pero si me coje las tildes.
Captura.PNG

Saludos.

Captura.PNG

En este código del post Guardar y leer EEPROM de un array y lee cosas raras:

#include <EEPROM.h>

const unsigned char PROGMEM ALFANUMERICO[] =
{
  'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'Ñ', 'O', 'P',
  'Q', 'R', 'S', 'T', 'U', 'V', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
  'i', 'j', 'k', 'l', 'm', 'n', 'ñ', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'x', 'y',
  'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', ',', '-', '_', ':', ';',
  '¿', '?', '(', ')', '[', ']', '{', '}', '=', '

Hemos declarado el array como unsigned char, por lo tanto es un byte. A la hora de leerlo y grabarlo en memoria lo interpreta como byte. Como el valor UTF-8 de 'ñ' es 0x3CB1, solo lee y guarda la parte baja, es decir, 0xB1.

Se me ocurre que el compilador trata de una manera especial la codificación UTF8. Así cuando ponemos un caracter fuera del ASCII de 7 bits (que es un conjunto de UTF-8), el compilador lo interpreta como un unsinged long (32 bits). A partir que aqui, dependiendo de como lo tratemos lo interpretará de manera diferente:

  • Si le preguntamos el tamaño con sizeof('ñ') este será 2.

  • Si lo imprimimos como un caracter nos dará FFFFC3B1 o -15439. dependiendo de si usamos HEX o no, usando los 32 bits.

  • Si lo usamos en una cadena, por ejemplo "gañan", el tamaño de la cadena es 6. Al imprimirlo por serial sustitye la 'ñ' por dos bytes (utf-8 de la 'ñ') y muestra dos caracteres raros.

Ahora bien, cuando el proceso es al revés, lo enviamos a través del terminal del IDE hacia al Arduino, ocurre algo maravilloso y desconcertante: sólo recibimos un caracter, si mostramos el valor en hex nos da que para la 'ñ' recibimos 0xF1 y para la 'Ñ' nos da 0xD1, que resulta ser el valor UNICODE.

Da que pensar el tema...

, '&', '"', ' '
};

void setup() {
 Serial.begin(9600);
}

void loop() {
 // Leemos el array de la memoria flash, byte a byte, lo mostramos por el puerto
 // serie y a la vez lo grabamos en la eeprom.
 for (int i=0; i<80; i++) {
   Serial.print((char)pgm_read_byte(ALFANUMERICO+i));
   EEPROM.update(i, (byte)pgm_read_byte(ALFANUMERICO+i));
   Serial.print(" ");

}
 Serial.println();
 delay(5000);

//Leemos el array de la EEPROM y lo mostramos por el puerto serie.
 for (int i=0; i<80; i++) {
   Serial.print((char)EEPROM.read(i));
   Serial.print(" ");
 }
 Serial.println();
}


Hemos declarado el array como unsigned char, por lo tanto es un byte. A la hora de leerlo y grabarlo en memoria lo interpreta como byte. Como el valor UTF-8 de 'ñ' es 0x3CB1, solo lee y guarda la parte baja, es decir, 0xB1.

Se me ocurre que el compilador trata de una manera especial la codificación UTF8. Así cuando ponemos un caracter fuera del ASCII de 7 bits (que es un conjunto de UTF-8), el compilador lo interpreta como un unsinged long (32 bits). A partir que aqui, dependiendo de como lo tratemos lo interpretará de manera diferente:

- Si le preguntamos el tamaño con sizeof('ñ') este será 2.

- Si lo imprimimos como un caracter nos dará FFFFC3B1 o -15439. dependiendo de si usamos HEX o no, usando los 32 bits.

- Si lo usamos en una cadena, por ejemplo "gañan", el tamaño de la cadena es 6. Al imprimirlo por serial sustitye la 'ñ' por dos bytes (utf-8 de la 'ñ') y muestra dos caracteres raros.

Ahora bien, cuando el proceso es al revés, lo enviamos a través del terminal del IDE hacia al Arduino, ocurre algo maravilloso y desconcertante: [u]sólo recibimos un caracter[/u], si mostramos el valor en hex nos da que para la 'ñ' recibimos 0xF1 y para la 'Ñ' nos da 0xD1, que resulta ser el valor UNICODE.

Da que pensar el tema...

Buenas:

Tengo que averiguar estas cosas sea como sea, para no tener problemas en el futuro sobre el control del puerto serie cosas como estas.

Tabla Unicode:

Tabla UTF-8:

En decimal según la tabla UTF-8.
Ñ = 209
ñ = 241.

Mejor convertir sabes en binario desde Arduino antes de pasarlo al puerto serie. Para saber que pasa.

Es el momento de capturar los bytes en binario y ver qué valor está realmente llegando, para buscarlo en las tablas de códigos. Procura capturar simultáneamente en un mismo mensaje a la vez una letra acentuada y la ñ, para asegurarnos de tener más información acerca de qué codificación se está usando.

Lógicamente, hay que capturarlos en binario antes de que el serialport los meta en un string, porque para entonces ya les ha aplicado el Encoding y se ha perdido el valor original. Usa alguno de los métodos del SerialPort que reciben bytes en lugar de strings.

Saludos.

Edito:

si mostramos el valor en hex nos da que para la 'ñ' recibimos 0xF1 y para la 'Ñ' nos da 0xD1, que resulta ser el valor UNICODE.

No, eso no es el valor UNICODE. Esos dos valores que indicas (F1 y D1) son los códigos de ñ y Ñ en ISO-8859-1 o en Windows ANSI.

Tienes que buscar cómo y dónde estás haciendo la lectura y/o conversión de estos valores, para ver por qué está usando ese Encoding y posiblemente cambiarlo.

Hemos declarado el array como unsigned char

Nótese que esto solo existe en C o C++, pero no en C#. El tipo equivalente en C# es byte. Si en C# lo declaras como Char, eso es un carácter Unicode (16 bits), lo mismo que si forma parte de un string. Si estás pasando datos de uno a otro, hay que tener presente que en alguno de los pasos intermedios tiene que hacer una conversión usando el Encoding, y si pones el encoding incorrecto (o lo usa por defecto si no has puesto nada) pues entonces salen los bytes incorrectos a partir de los caracteres originales.

Name:	        Latin Small Letter N with Tilde[1]
Unicode Version:	1.1 (June 1993)[2]
Block:	        Latin-1 Supplement, U+0080 - U+00FF[3]
Plane:	        Basic Multilingual Plane, U+0000 - U+FFFF[3]
Script:	                Latin (Latn) [4]
Category:	                Lowercase Letter (Ll) [1]
Bidirectional Class:	Left To Right (L) [1]
Combining Class:	        Not Reordered (0) [1]
Character is Mirrored:	No [1]
GCGID:	                LN190000[5]
HTML Entity:	

    ñ
    &#xF1;
    &ntilde;

UTF-8 Encoding:	        0xC3 0xB1
UTF-16 Encoding:	0x00F1
UTF-32 Encoding:	0x000000F1
Uppercase Character:	Ñ (U+00D1) [1]
Decomposition:	        n (U+006E) - ◌̃ (U+0303)[1]

La verdad que da mucha guerra lo del encoding, lo mejor: binario. Así todos hablan igual y ya esta, los 0 y 1 es la única verdad multiplataforma.

Hola.

Cierto.
Mejor 0 y 1.
¿Cómo envías formato 1 y 0?

Desde el otro lado.
¿Cómo lo recibe?

El truco de Arduino print(variable, BIN?

Saludos.

Se envían en Byte con Serial.write( )

1 Like

No se aplicación estés intentado programar, pero mejor que trabajar a nivel byte (o binario ) es trabajar con protocolos, son una capa que te aísla del bus de comunicación.

Con parte de tu código modificado, sigue sin sali rla Ñ y ñ.

void loop() 
{
  // Leemos el array de la memoria flash, byte a byte, lo mostramos por el puerto
  // serie y a la vez lo grabamos en la eeprom.
  for (int i=0; i<80; i++) {
    Serial.write((char)pgm_read_byte(ALFANUMERICO+i));
    EEPROM.update(i, (byte)pgm_read_byte(ALFANUMERICO+i));
    Serial.write(" ");

  }
  Serial.println();
  delay(5000);

  // Leemos el array de la EEPROM y lo mostramos por el puerto serie.
  for (int i=0; i<80; i++) {
    Serial.write((char)EEPROM.read(i));
    Serial.write(" ");
  }
  Serial.println();
}

Si que cuesta enteneder esto. Si, lo del tema de protocolo es lo que hay que ver. Quiero saber que es lo que envía Arduino.

Claro, porque ahora estas escribiendo bytes.

Un byte no tiene codificación solo es un número binario. Si lo envias por Serial, dependerá del receptor lo que quiera interpretar. El terminal por ejemplo, escribe caracteres, solo los que pueda imprimir, los que no los dejará en blanco o hará cualquier cosa.

Mira este programa:

#include <EEPROM.h>

void setup() {
  Serial.begin(9600);
  for (int i=0; i<256; i++) {
    Serial.write((unsigned char)i);
    if (i!=0 && i%16==0) Serial.println();
    else Serial.print(' ');
  }
}

void loop() {
 
}

Enviará por el puerto serie los bytes del 0 al 255, he añadido un salto
de línea cada 16 caracteres y he usado el terminal del ide y realTerm, con el siguiente resultado:

codepage.png

RealTerm utiliza ASCII/ANSI, mientras que el terminal usa UTF-8. Como ves el resultado es que cada byte tiene asociado un caracter en el caso de ANSI y los que no tienen un caracter ni los pinta. En el caso del IDE, ocurre tres cuartas de lo mismo, pero en este caso si pinta cuadrados en los caracteres que no tienen esa asociación.

Una cosa buena de las codificaciones es que generalmente los bytes comprendidos entre 0x21 (33) y 0x7F (127) son comunes. No importa si la codificación es tal o cual, será reconocido. Estos caracteres son:

! " # $ % & ' ( ) * + , - . / 0 
1 2 3 4 5 6 7 8 9 : ; < = > ? @
A B C D E F G H I J K L M N O P 
Q R S T U V W X Y Z [ \ ] ^ _ ` 
a b c d e f g h i j k l m n o p 
q r s t u v w x y z { | }

Pero como ves, no hay acentos, ni ñ, ni caracter raro.

Así que si quieres evitar el problema de la codificación hay dos vias:

Usando texto: obvia tildes y caracteres raros. Así en lo "comandos" que propusiste en la imagen del primer post serían: ok, atras, guardar, etc. Sin usar tildes ni nada por el estilo.

Así no importa si es utf-8 lo que haga arduino con los caracteres, o si es ascii, será lo mismo. E igualmente en el lado del emisor, que lo raro es lo del ide, ya que si fuese utf-8 deberia de mandar en el caso de la ñ 0x3cB1 y envia, parece ser, utf-16.

Usando binario. En este caso lo que mandas son valores binarios, asi que tanto como emisor como receptor deben enteder el idioma, lo que Peter ha dicho que son protocolos. Por ejemplo, el arduino recibe valores binarios y un valor 0x34 es el comando "atrás", entonces el emisor solo debe mandar un byte que es 0x34.

codepage.png

Sin duda se pueden escribir protocolos propios, pero cuando me refería a protocolo, quería decir de utilizar alguno ya desarrollado, como Modbus, MQTT y similares ¿ Para que reinventar la rueda?
Saludos.

Buenas explicaciones.

Como han dicho arriba, es también depende de como lo interprete en el otro lado.

Es bueno saber que envía Arduino, para poder saber que hacer desde el PC que recibe esos datos, así de simple.

¿Qué envía Arduino para las ñ, Ñ y las letras con tildes?

¿Qué pongo en el PC con todas las codificaciones que hay?

Ahí está la cuestión.

Hay que tenerlo claro para no haber problemas de comunicaciones.

Saludos.

Arduino no entiende de codificación.

¿Qué ocurre?

Cuando nosotros creamos un sketch, lo que hacemos es escribir un código en un fichero. Ese fichero se guarda con codificación UTF-8. El compilador lee el código fuente con esa codificación, y cuando encuentra un carácter " interpreta que lo hay ahi es una cadena. Así al estar códificado con UTF-8 utiliza los bytes que se corresponden a dicha codificación para crear la cadena.

Cuando se encuentra con un caracter 'ñ', realiza la conversión a entero de 32 bits porque así es como almacena valores enteros.

Pero en realidad, es cosa del compilador. Arduino solo manda valores de 0x00 a 0xFF, un byte.

Así en el lado del PC, debería tener cuidado y leer UTF-8.

En cambio que ocurre si es el PC el que envia.

Pues también dependerá del compilador/programa. Arduino no entiende UTF-8, solo bytes, por lo que cuando le mandas un caracter raro, si ocupa un byte leerá un byte, si ocupa 2 leerá 2. Pero Arduino entenderá solo 1 byte por caracter y si lo reenvias no tendrá codificación ninguna. Aparentemente el terminal del IDE lee UTF-8 pero envia caracteres Unicode (creo que para ser mas exactos ISO-8859-1).

iso8859-1.jpg

Un ejemplo:

void loop() {
  if ( Serial.available() ) {
    String s = Serial.readString();
    Serial.print(s);
  }
}

Ahora teclea en el terminal "áéíóúñ". Dado que el terminal envia Unicode (la ISO dicha), recibes la secuencia de bytes: E1 E9 ED F3 FA F1. Cuando la reenvias al terminal no transforma nada ni hace codificación alguna, solo vuelve a mandar esos bytes, por lo que el terminal muestra ? en todos ellos: envias bytes (aunque sea Unicode) y esperas UTF-8.

Mi recomendación sigue siendo la misma: olvida los caracteres especiales usa solo los ASCII que gracias a dios estan todos presentes en casi cualquier codificación y no tendrás problemas en ningún terminal.

iso8859-1.jpg

https://playground.arduino.cc/Code/UTF-8/
http://www.fileformat.info/info/charset/UTF-8/list.htm
El IDE de arduino crea los ficheros en uft-8.
Los primeros 127 caracteres tanto en ASCCI como en UTF-8 se codifican igual y ocupan un byte.

Los caracteres que no se pueden representar en los primeros 127 caracteres, pasan a ocupar 2, 3 o 4 bytes. Es por eso que en el código de abajo la variable a ocupa 6 bytes, en vez de 4, realmente ocupa 7 ya que las cadenas en C acaban con un byte a cero para indicar el final.

char a[]="ñoño";
Serial.println(sizeof(a));

Si imprimimos la cadena por el puerto serie saldrán 6 caracteres, lo cual no pasa nada si el terminal sobre el que imprimimos soporta utf-8, representará las eñes correctamente.

En el sentido contrario, en la recepción, debes tener en cuenta esto. Los caracteres pueden ser de 1, 2, 3, o 4 bytes.

Saludos.