Panel de operador (HMI) con bus I2C para Arduino

Como parte de trabajar con algunos equipos compatibles con arduino (al final sobre esto), necesitaba una pantalla con botones para controlar y mostrar la información actual. Es decir, se necesitaba el panel del operador, también es HMI.

Se decidió hacer HMI de forma independiente y utilizar el bus i2c "cuadrado" como interfaz.



Si está interesado en el proceso de desarrollo y programación de dichos dispositivos, bienvenido a cat.

Caracteristicas

  • Pantalla 1602, caracteres monocromáticos de 16x2
  • 5 botones: arriba, abajo, cancelar, ingresar, editar
  • Interfaz I2c
  • Conector DB9F
  • Dimensiones 155x90x44 mm

Hay preguntas obvias:

¿Por qué no comprar un escudo confeccionado?
Por supuesto, era posible comprar un escudo confeccionado del mismo chino con una pantalla y un teclado, y así:

Puede soldar 2 bufandas FC-113 a este escudo y resultará funcionalmente igual que el mío: una pantalla con un teclado que funciona en i2c. El precio del conjunto será de $ 4.

Pero en este tablero, el tamaño de los botones no me conviene, pero quería grandes con la capacidad de instalar tapas multicolores. No quería conectar el Arduino a la HMI usando el conector DB9F normal, pero era necesario hacer una placa de conexión. Y en este caso, ¿cuál es la diferencia, hacer una o dos tablas? Además, ya tenía varias pantallas 1602 en stock, así que solo tuve que gastar $ 1.02 para comprar FC-113 ($ 0.55) y el expansor de puerto PCF8574P ($ 0.47) en Aliexpress.

Bueno, y lo más importante, si se trata de Arduino, entonces la fabricación independiente de escudos para él es algo natural, ¿verdad?

¿Por qué es el bus i2c? ¿No es más fácil conectar los botones directamente?
En el campo de los sistemas de control industrial, donde trabajo, las HMI utilizan interfaces de transmisión de datos digitales RS-232, RS-485, CAN, etc. para comunicarse con los dispositivos. Por lo tanto, es lógico para mí que mi HMI casera funcione en una interfaz de datos, en este caso, en i2c.

Si creara un dispositivo donde la pantalla funciona en un bus cuadrado y los botones van directamente a la entrada de Arduino, me sentiría profundamente insatisfecho. Como presentaré esta imagen: un cable separado sobresale del panel a la interfaz, se conecta por separado a la entrada, brrrr ...

Además, la diferencia entre la placa de botones que va directamente a las entradas de Arduino y la placa de botones con la interfaz i2c está solo en el chip PCF8574P ($ 0.47), un condensador y dos resistencias.

¿Por qué los botones están dispuestos así y no de otra manera?
Los botones de izquierda a derecha tienen las siguientes funciones: arriba, abajo, cancelar, ingresar, editar.
El botón "editar" se aleja un poco de los demás para enfatizar su función: cambiar los valores de los parámetros lógicos (activar / desactivar) o cambiar al modo de edición en el caso de parámetros numéricos.

Hay 5 botones en total, aunque el chip en la placa del teclado le permite conectar hasta 8 piezas.
Sería suficiente con cuatro botones y la funcionalidad no se vería afectada: "entrada" y "edición" se pueden combinar en un solo botón. Pero lamentaba que la mitad de las 8 patas del microcircuito expansor de puerto no estuvieran involucradas.
Otro botón "editar" por separado puede ser útil si decido mostrar varios parámetros en una línea. Luego, con este botón, puede cambiar entre los parámetros, indicando cuál necesita ser cambiado. Así es como funciona el botón "SET" en la popular HMI OP320 china.

Si los dos primeros botones significan arriba y abajo, ¿por qué no colocarlos verticalmente, como, por ejemplo, se hace en el escudo chino anterior?

Para mí personalmente, es más conveniente cuando todos los botones están horizontalmente, luego, durante la operación, los dedos se mueven solo en un plano.

Hierro






1. Placa posterior casera con conector DB9F. Entonces, mientras tomamos una fuente de alimentación de + 5V para los expansores de puertos y pantallas de Arduino, puse un fusible de 0.1 A en la placa

2. Todos conocemos la conocida pantalla 1602 con una placa soldada FC-113 que conecta la pantalla al bus i2c.

3. Una placa de teclado hecha en casa con un chip PCF8574P, que leerá el estado de los botones y los transmitirá a través del bus i2c. Por cierto, la placa de "pantalla" FC-113 también se basa en el chip PCF8574, solo con el índice T, es decir planar, no DIP, como PCF8574P.

Botones que pongo 12h12mm con un empujador cuadrado; en ellos puedes usar grandes gorras multicolores.

Fotos y esquemas de circuitos caseros.






Vale la pena decir algunas palabras sobre el chip PCF8574P, en base al cual hice una placa de teclado.
El PCF8574P es un extensor de puerto i2c. Hay 8 puertos en total, cada uno de los cuales se puede configurar para funcionar como entrada o salida. Para este chip y el flejado como tal no es necesario (recuerde, por ejemplo, max232), en caso de que coloque un condensador para la alimentación.

La dirección del chip PCF8574P se establece utilizando las patas de dirección A0, A1, A2, que se empujan hacia el suelo o hacia la fuente de alimentación a través de una resistencia de 10 kΩ.

En la placa del teclado, coloco todas las patas de dirección del PCF8574P en el suelo, por lo que la dirección está codificada como 0x20 y no puede cambiarla.

Como ya escribí, elegí DB9F como conector para la HMI. Arduino recibe señales de él +5 V, GND, SDA, SCL.



El cable para la comunicación a través de i2c Arduino y HMI hizo 1,4 m de largo, funciona sin fallas.

Dibujé los tableros en Sprint Layout 6, los transferí a textolita usando el método LUT y los grabé en una solución de peróxido y ácido cítrico.

Un poco sobre decapado
Hay muchas recetas para grabar tableros de ácido cítrico en láminas de fibra de vidrio.

Hice esta solución: 100 ml de peróxido de hidrógeno al 3%, 50 g de ácido cítrico, 3 cucharaditas de sal. Calentó la jarra de peróxido en una sartén con agua a una temperatura de aproximadamente 70 grados.

Sumergimos el tablero en la solución con el patrón hacia abajo, como se recomienda cuando se graba con peróxido.
Después de un par de decenas de segundos, comienza un proceso tormentoso. Se libera mucho vapor, que no se recomienda inhalar. Probablemente



Entonces el proceso disminuye. Dale la vuelta al tablero.



Listo



La caja fue hecha de un amigo de plexiglás de 4 mm en una máquina de corte por láser.

Digresión lírica sobre el cuerpo
¿Comprar una caja terminada o hágalo usted mismo? Después de pensar un poco, decidí hacerlo yo mismo. Los que vi a la venta no me convenían ni por precio ni por razones estéticas, o estaban en un riel DIN, que tampoco me convenía.

Inicialmente, el cuerpo quería cortar madera contrachapada. Pero luego recordé que tenía un amigo maravilloso y, con gran alegría, el director de una compañía de premios deportivos. Tiene todo tipo de máquinas allí, incluso para corte por láser.

Pidió ayuda y un amigo no se negó, por un par de minutos las partes fueron cortadas con un láser.

Aprovecho esta oportunidad para decir gracias, Kolya! De lo contrario, tendría que cortar y moler madera contrachapada todo el día, y el resultado difícilmente sería tan brillante.

Programacion


Desde el punto de vista de Arduino, esta HMI consta de 2 dispositivos que funcionan en el bus i2c: una pantalla (LCD) con la dirección 0x27 y un teclado con la dirección 0x20. En consecuencia, Arduino trabajará por separado con el teclado y por separado con la pantalla LCD.

El trabajo con LCD se realiza a través de una biblioteca especial "LiquidCrystal_I2C.h", debe instalarse en el IDE de Aduino.

El trabajo con el teclado se lleva a cabo a través de la biblioteca estándar Wire.h, que inicialmente está disponible en el IDE de Aduino.

Conectamos HMI a Ardiuno.



1. Primero, verifique si Arduino ve nuestra HMI. Para hacer esto, cargue el programa en él, que buscará dispositivos en el bus i2c.

Boceto 1, escaneo de bus i2c
//i2c_scaner #include <Wire.h> String stringOne; void setup() { Wire.begin(); Serial.begin(9600); while (!Serial); } void loop() { byte error, address; int nDevices; Serial.println("Scanning..."); nDevices = 0; for(address = 1; address < 127; address++ ) { Wire.beginTransmission(address); error = Wire.endTransmission(); if (error == 0) { String stringOne = String(address, HEX); Serial.print("0x"); Serial.print(stringOne); Serial.print(" - "); if(stringOne=="0A") Serial.println("'Motor Driver'"); if(stringOne=="0F") Serial.println("'Motor Driver'"); if(stringOne=="1D") Serial.println("'ADXL345 Input 3-Axis Digital Accelerometer'"); if(stringOne=="1E") Serial.println("'HMC5883 3-Axis Digital Compass'"); if(stringOne=="5A") Serial.println("'Touch Sensor'"); if(stringOne=="5B") Serial.println("'Touch Sensor'"); if(stringOne=="5C") Serial.println("'BH1750FVI digital Light Sensor' OR 'Touch Sensor" ); if(stringOne=="5D") Serial.println("'Touch Sensor'"); if(stringOne=="20") Serial.println("'PCF8574 8-Bit I/O Expander' OR 'LCM1602 LCD Adapter' "); if(stringOne=="21") Serial.println("'PCF8574 8-Bit I/O Expander'"); if(stringOne=="22") Serial.println("'PCF8574 8-Bit I/O Expander'"); if(stringOne=="23") Serial.println("'PCF8574 8-Bit I/O Expander' OR 'BH1750FVI digital Light Sensor'"); if(stringOne=="24") Serial.println("'PCF8574 8-Bit I/O Expander'"); if(stringOne=="25") Serial.println("'PCF8574 8-Bit I/O Expander'"); if(stringOne=="26") Serial.println("'PCF8574 8-Bit I/O Expander'"); if(stringOne=="27") Serial.println("'PCF8574 8-Bit I/O Expander' OR 'LCM1602 LCD Adapter '"); if(stringOne=="39") Serial.println("'TSL2561 Ambient Light Sensor'"); if(stringOne=="40") Serial.println("'BMP180 barometric pressure sensor'" ); if(stringOne=="48") Serial.println("'ADS1115 Module 16-Bit'"); if(stringOne=="49") Serial.println("'ADS1115 Module 16-Bit' OR 'SPI-to-UART'"); if(stringOne=="4A") Serial.println("'ADS1115 Module 16-Bit'"); if(stringOne=="4B") Serial.println("'ADS1115 Module 16-Bit'"); if(stringOne=="50") Serial.println("'AT24C32 EEPROM'"); if(stringOne=="53") Serial.println("'ADXL345 Input 3-Axis Digital Accelerometer'"); if(stringOne=="68") Serial.println("'DS3231 real-time clock' OR 'MPU-9250 Nine axis sensor module'"); if(stringOne=="7A") Serial.println("'LCD OLED 128x64'"); if(stringOne=="76") Serial.println("'BMP280 barometric pressure sensor'"); if(stringOne=="77") Serial.println("'BMP180 barometric pressure sensor' OR 'BMP280 barometric pressure sensor'"); if(stringOne=="78") Serial.println("'LCD OLED 128x64'" ); nDevices++; } else if (error==4) { Serial.print("Unknow error at address 0x"); if (address<16) Serial.print("0"); Serial.println(address,HEX); } } if (nDevices == 0) Serial.println("No I2C devices found\n"); else Serial.println("done\n"); delay(5000); } 


Durante la ejecución de este programa, Arduino escribirá los resultados del escaneo del bus i2c en el puerto serie. Para ver estos datos, vaya a Arduino IDE Tools-> Port Monitor.



Vemos que Arduino en el bus i2c identificó dos dispositivos con direcciones 0x20 y 0x27, estos son el teclado y la pantalla LCD, respectivamente.

2. Ahora veamos cómo funciona nuestro teclado. Cree un programa que sondeará el estado de los botones y lo mostrará en la pantalla LCD.

Boceto 2, muestra el estado de los botones
 /*   LCD     i2c LCD    FC-113,  0x27      PCF8574P,  0x20 */ #include <LiquidCrystal_I2C.h> #include <Wire.h> #define led 13 #define ADDR_KBRD 0x20 #define ADDR_LCD 0x27 byte dio_in; bool b; bool key[5]; LiquidCrystal_I2C lcd(ADDR_LCD,16,2); //   void setup() { pinMode(led, OUTPUT); // lcd.init(); lcd.backlight();//    // Wire.begin(); Wire.beginTransmission(ADDR_KBRD); Wire.write(B11111111); //   PCF8574P     Wire.endTransmission(); } void loop() { Wire.requestFrom(ADDR_KBRD,1); while (!Wire.available()); byte dio_in = Wire.read(); //   PCF8574P() //      byte mask=1; for(int i=0; i<5;i++) { key[i]=!(dio_in & mask); mask=mask<<1; } b=!b; digitalWrite(led, b); //    //    LCD lcd.setCursor(0, 0); lcd.print(String(key[0])+" "+ String(key[1])+" "+ String(key[2])+" "+ String(key[3])+" "+ String(key[4])+" "); delay(100); } 




El teclado esta funcionando.

3. Finalmente, puede pasar a lo que estaba haciendo todo, crear un menú de varios niveles en Arduino. A través del menú no solo veremos información, sino que también controlaremos las salidas del propio Arduino.





En realidad, hay mucha información sobre cómo crear un menú de varios niveles en C ++, pero para Arduino incluso vi algunas bibliotecas. Pero en mi programa decidí escribir el menú yo mismo. En primer lugar, cuantas menos bibliotecas quedan en el proyecto, más tranquilo. Y en segundo lugar, es simple.

Tengo otra variación del menú del árbol. El menú le permite mostrar en cada línea al mismo tiempo texto estático y el valor de la variable. Por ejemplo, puede mostrar el nombre del parámetro y su valor.

Para mostrar variables en la pantalla, aplico el principio de etiquetas: de cierta manera, diseñé etiquetas de texto en el texto, en lugar de las cuales, cuando el texto se muestra en la pantalla, se muestra un valor.

Los parámetros se pueden cambiar presionando el botón "Editar". Además, la etiqueta de cada parámetro indica si está disponible para edición o solo lectura. Si el parámetro actual es de solo lectura, al comienzo de la línea el puntero será '*'; si la edición está habilitada, el puntero se convertirá en '+'.

Sketch 3, menú multinivel
 /*  ,    LCD   i2c LCD    FC-113,  0x27      PCF8574P,  0x20 */ #include <LiquidCrystal_I2C.h> #include <Wire.h> #define led 13 //    ;  ,  ,     #define ADDR_KBRD 0x20 #define ADDR_LCD 0x27 #define PORT_D2 2 #define PORT_D3 3 #define PORT_D4 4 #define POINT_ON_ROOT_MENU_ITEM 0 // 0/1= /   (*  +)     byte dio_in; bool b; byte i; //bool ,      bool BoolVal[9]={0,0,0, 0,0,0, 0,0,0}; #define ValSvet1 BoolVal[0] #define ValSvet2 BoolVal[1] #define ValSvet3 BoolVal[2] #define ValRozetka1 BoolVal[3] #define ValRozetka2 BoolVal[4] #define ValRozetka3 BoolVal[5] #define ValClapan1 BoolVal[6] #define ValClapan2 BoolVal[7] #define ValClapan3 BoolVal[8] // struct STRUCT_KEY{ bool StateCur; //   bool StateOld; //     bool Imp; //   (  0  1) }; // STRUCT_KEY Key[5]={0,0,0, 0,0,0, 0,0,0, 0,0,0, 0,0,0 }; //--- /*   *  , : * '#A1' bool ,  * '#'-   bool, * 'A'- (HEX)    BoolVal, * '1'-    *   ,       */ String StrNull=" "; //  String StrRoot1="COMP-MAN.INFO"; String StrRoot2="PLC-BLOG.COM.UA"; String StrSvet= ""; // String StrSvet1=" 1 #01"; String StrSvet2=" 2 #10"; String StrSvet3=" 3 #21"; String StrRozetka=""; // String StrRozetka1=" 1 #30"; String StrRozetka2=" 2 #40"; String StrRozetka3=" 3 #50"; String StrClapan=""; // String StrClapan1=" 1 #60"; // String StrClapan2=" 2 #70"; String StrClapan3=" 3 #80"; struct MENU_ITEM // (),   2        { byte KeyUp; //№  ,     "" byte KeyDwn; //№  ,     "" byte KeyCancel; //№  ,     ""(cancel) byte KeyEnter; //№  ,     ""(enter) byte KeyEdit; // "edit",  String *pstr1; //    () String *pstr2; //    () }; // MENU_ITEM Menu[]={0,0,0,1,0, &StrRoot1,&StrRoot2, //0   1,8,0,2,0, &StrSvet,&StrRozetka, //1  2,3,1,2,0, &StrSvet1,&StrSvet2, //2 2,4,1,3,0, &StrSvet2,&StrSvet3, //3 3,4,1,4,0, &StrSvet3,&StrNull, //4 0,0,0,0,0, &StrNull,&StrNull, //5  0,0,0,0,0, &StrNull,&StrNull, //6 0,0,0,0,0, &StrNull,&StrNull, //7 1,15,0,9,0, &StrRozetka,&StrClapan, //8  9,10,8,9,0, &StrRozetka1, &StrRozetka2, //9 9,11,8,10,0, &StrRozetka2, &StrRozetka3, //10 10,11,8,11,0, &StrRozetka3, &StrNull, //11 0,0,0,0,0, &StrNull,&StrNull, //12  0,0,0,0,0, &StrNull,&StrNull, //13 0,0,0,0,0, &StrNull,&StrNull, //14 8,15,0,16,0, &StrClapan, &StrNull, //15  16,17,15,0,0, &StrClapan1,&StrClapan2, //16 16,18,15,0,0, &StrClapan2,&StrClapan3, //17 17,18,15,0,0, &StrClapan3,&StrNull, //18 0,0,0,0,0, &StrNull,&StrNull, //19  0,0,0,0,0, &StrNull,&StrNull, //20 0,0,0,0,0, &StrNull,&StrNull, //21 }; byte PosMenu=0; //  LiquidCrystal_I2C lcd(ADDR_LCD,16,2); //   //   void ReadKey(byte dio_in) { //      byte mask=1; for(i=0; i<5; i++) { Key[i].StateCur=!(dio_in & mask); mask=mask<<1; Key[i].Imp=!Key[i].StateOld & Key[i].StateCur; //   (  0  1) Key[i].StateOld=Key[i].StateCur; } } /* *  UTF-8   ( )   LCD *       */ byte MasRus[33][2]= { 144, 0x41, // 145, 0xa0, 146, 0x42, 147, 0xa1, 148, 0xe0, 149, 0x45, 129, 0xa2, 150, 0xa3, 151, 0xa4, 152, 0xa5, 153, 0xa6, 154, 0x4b, 155, 0xa7, 156, 0x4d, 157, 0x48, 158, 0x4f, 159, 0xa8, 160, 0x50, 161, 0x43, 162, 0x54, 163, 0xa9, 164, 0xaa, 165, 0x58, 166, 0xe1, 167, 0xab, 168, 0xac, 169, 0xe2, 170, 0xad, 171, 0xae, 172, 0xc4, 173, 0xaf, 174, 0xb0, 175, 0xb1 // }; String RusStrLCD(String StrIn) { String StrOut=""; byte b1; byte y; byte l=StrIn.length(); for(byte i=0; i<l; i++) { b1=StrIn.charAt(i); if (b1<128) StrOut=StrOut+char(b1); else { if (b1==208) //==208,     2-  .  { b1=StrIn.charAt(i+1); for(y=0; y<33; y++) if(MasRus[y][0]==b1) { StrOut=StrOut+char(MasRus[y][1]); break; } } i++; } } return StrOut; } //--------------------------- //ASCII HEX ---> dec byte StrHexToByte(char val) { byte dec=0; switch (val) { case '0': dec=0; break; case '1': dec=1; break; case '2': dec=2; break; case '3': dec=3; break; case '4': dec=4; break; case '5': dec=5; break; case '6': dec=6; break; case '7': dec=7; break; case '8': dec=8; break; case '9': dec=9; break; case 'A': dec=10; break; case 'B': dec=11; break; case 'C': dec=12; break; case 'D': dec=13; break; case 'E': dec=14; break; case 'F': dec=15; break; default: dec=0; break; } return dec; } //     void WriteLCD(byte num) { String str[]={"*"+*Menu[num].pstr1,*Menu[num].pstr2}; if (num==0 && POINT_ON_ROOT_MENU_ITEM==0) //     ? str[0].setCharAt(0,' '); // ,   //     byte NumVal; byte l; for(byte y=0; y<2; y++) { l=str[y].length(); for(i=0; i<l; i++) { if (str[y].charAt(i)=='#') //# bool,  off/ON { if(StrHexToByte(str[y].charAt(i+2))==1 && y==0) //  ? str[y].setCharAt(0,'+'); NumVal=StrHexToByte(str[y].charAt(i+1)); str[y]=str[y].substring(0,i)+String(NumVal) ; if(BoolVal[NumVal]==0) str[y]=str[y].substring(0,i)+"off" ; if(BoolVal[NumVal]==1) str[y]=str[y].substring(0,i)+"ON" ; } if (str[y].charAt(i)=='$') //$ int,     ,      { ; } if (str[y].charAt(i)=='~') //~ ,     ,      { ; } } } //--- lcd.clear(); lcd.setCursor(0, 0); lcd.print(str[0]); lcd.setCursor(1, 1); lcd.print(str[1]); } //,       byte GoMenu(byte key) { byte PosMenuNew=PosMenu; switch (key) { case 0: PosMenuNew=Menu[PosMenu].KeyUp; break; case 1: PosMenuNew=Menu[PosMenu].KeyDwn; break; case 2: PosMenuNew=Menu[PosMenu].KeyCancel; break; case 3: PosMenuNew=Menu[PosMenu].KeyEnter; break; case 4: ; break; default: break; } return PosMenuNew; } //    "Edit" void Edit(byte posmenu) { byte NumVal; bool *pval; String str=*Menu[posmenu].pstr1; byte l=str.length(); for(i=0; i<l; i++) if (str.charAt(i)=='#') //#- bool,  off/ON { if(StrHexToByte(str.charAt(i+2))==1) //  ? { pval= &(BoolVal[StrHexToByte(str.charAt(i+1))]); // ,    .   *pval=!(*pval); //     } } } //     void ValToPort() { digitalWrite(PORT_D2,ValSvet1); digitalWrite(PORT_D3,ValSvet2); digitalWrite(PORT_D4,ValSvet3); } void setup() { pinMode(led, OUTPUT); //     pinMode(PORT_D2, OUTPUT); pinMode(PORT_D3, OUTPUT); pinMode(PORT_D4, OUTPUT); //    LCD StrSvet=RusStrLCD(StrSvet); StrSvet1=RusStrLCD(StrSvet1); StrSvet2=RusStrLCD(StrSvet2); StrSvet3=RusStrLCD(StrSvet3); StrRozetka=RusStrLCD(StrRozetka); StrRozetka1=RusStrLCD(StrRozetka1); StrRozetka2=RusStrLCD(StrRozetka2); StrRozetka3=RusStrLCD(StrRozetka3); StrClapan=RusStrLCD(StrClapan); StrClapan1=RusStrLCD(StrClapan1); StrClapan2=RusStrLCD(StrClapan2); StrClapan3=RusStrLCD(StrClapan3); // lcd.init(); lcd.backlight();//    WriteLCD(PosMenu); Wire.begin(); Wire.beginTransmission(ADDR_KBRD); Wire.write(B11111111); //   PCF8574P     Wire.endTransmission(); } void loop() { Wire.requestFrom(ADDR_KBRD,1); while (!Wire.available()); byte dio_in = Wire.read(); //   PCF8574P() ReadKey(dio_in); //   //,    ;  ,      int KeyImp=-1; for (i=0; i<5; i++) if(Key[i].Imp==1) { KeyImp=i; Key[i].Imp==0; } if (KeyImp>-1) //  ? { if (KeyImp==4) // "Edit" Edit(PosMenu); PosMenu=GoMenu((KeyImp)); WriteLCD(PosMenu); } b=!b; digitalWrite(led, b); //    ValToPort(); //  delay(50); } 


LCD 1602 y problema de idioma


Por separado, es necesario plantear la cuestión de la rusificación.

En el generador de caracteres de algunos LCD 1602 no hay letras rusas, y en su lugar se cosen krakozyabry japonesas. Es imposible actualizar el generador de caracteres. Por lo tanto, tendrá que escribir palabras en la pantalla en letras latinas, o en el programa para formar las letras rusas usted mismo, porque LCD 1602 tiene la capacidad de crear y almacenar sus propios caracteres en RAM RAM. Pero, en el último caso, puede mostrar en la pantalla no más de ocho personajes "caseros" a la vez.

Gráficos de caracteres LCD 1602




En principio, está bien escribir palabras rusas en inglés en la pantalla LCD. Allí, incluso la venerable compañía francesa Shneider Electric (la que vendió obuses al zar incluso antes de la revolución) durante una década y media no pudo introducir al ruso en sus famosos relés programables Zelio. Pero esto no les impide comerciar activamente en la inmensidad de toda la CEI. Además, se introdujeron canales, español y portugués.

En muchas de nuestras fábricas, estos Zelio se comunican con el personal con frases como "NASOS 1 VKL".

Cuando no está claro si hay letras rusas en una pantalla LCD en particular, debe mostrar todos los caracteres de su generador de caracteres en la pantalla. Si hay cirílico, comienza desde la posición 160.

Boceto 4, que muestra todos los caracteres de la tabla del generador de caracteres LCD 1602
 /*   LCD     * LCD    i2c */ #include <LiquidCrystal_I2C.h> LiquidCrystal_I2C lcd(0x27,16,2); //   void setup() { // put your setup code here, to run once: lcd.init(); lcd.clear(); } void loop() { int i,y; while(1) { for (i=0; i < 16; i++) { lcd.clear(); lcd.setCursor(0,0); lcd.print(String(i*16)+" - "+String(i*16+15)); lcd.setCursor(0,1); for(y=0;y<16;y++) lcd.print(char(i*16+y)); delay(3000); } } } 


Pero incluso si su LCD 1602 está rusificado, mostrar palabras en ruso no es tan simple. Al menos, usando la biblioteca LiquidCrystal_I2C.h cuando se trabaja con LCD en el bus i2c.

Si simplemente imprime el texto en ruso, por ejemplo, con la instrucción lcd.print ("¡Hola!"), En lugar de "¡Hola!" aparecerá algo de basura en la pantalla.

Esto se debe a que el IDE de Arduino traduce las letras rusas en código UTF-8 de dos bytes, y en la pantalla LCD todos los caracteres son de un solo byte.

El mismo problema, por cierto, se observa al transferir textos rusos de Arduino al monitor de puerto Arduino IDE. Arduino envía letras rusas en la codificación de doble byte UTF-8 al puerto serie, y el monitor del puerto Arduino IDE intenta leerlas en la codificación de un solo byte Windows-1251 (cp1251). Aunque cp1251 también es de 8 bits, como la codificación LCD 1602, no coincide con él.

Puede formar textos rusos a través de códigos de caracteres. Por ejemplo, la línea 'pantalla LCD' en una pantalla LCD rusificada se mostrará así:

 lcd.print("\243K \343\270c\276\273e\271"); 

Pero no me gusta este enfoque.

Para mostrar correctamente el texto en ruso en la pantalla LCD rusificada 1602, se inventaron varias bibliotecas para Arduino. Pero después de leer los comentarios, vi que muchos se quejan de problemas técnicos al usarlos.

Por lo tanto, en mi programa de menú de niveles múltiples, yo mismo escribí una función simple para convertir UTF-8 a códigos LCD. Es cierto, lo hice solo para letras mayúsculas rusas, lo que me conviene perfectamente.

Función para convertir letras rusas mayúsculas UTF-8 a código LCD 1602 de un solo byte
 /* *  UTF-8   ( )   LCD *       */ byte MasRus[33][2]= { 144, 0x41, // 145, 0xa0, 146, 0x42, 147, 0xa1, 148, 0xe0, 149, 0x45, 129, 0xa2, 150, 0xa3, 151, 0xa4, 152, 0xa5, 153, 0xa6, 154, 0x4b, 155, 0xa7, 156, 0x4d, 157, 0x48, 158, 0x4f, 159, 0xa8, 160, 0x50, 161, 0x43, 162, 0x54, 163, 0xa9, 164, 0xaa, 165, 0x58, 166, 0xe1, 167, 0xab, 168, 0xac, 169, 0xe2, 170, 0xad, 171, 0xae, 172, 0xc4, 173, 0xaf, 174, 0xb0, 175, 0xb1 // }; String RusStrLCD(String StrIn) { String StrOut=""; byte b1; byte y; byte l=StrIn.length(); for(byte i=0; i<l; i++) { b1=StrIn.charAt(i); if (b1<128) StrOut=StrOut+char(b1); else { if (b1==208) //==208,     2-  .  { b1=StrIn.charAt(i+1); for(y=0; y<33; y++) if(MasRus[y][0]==b1) { StrOut=StrOut+char(MasRus[y][1]); break; } } i++; } } return StrOut; } 


Eso es todo por el HMI casero con el bus i2c.

Ah, sí, al comienzo del artículo escribí que estaba haciendo HMI no solo para Arduino, sino para equipos compatibles con Arduino. Este soy yo sobre el PLC CONTROLLINO MAXI , que está programado desde el Arduino IDE (y muchos otros).



CONTROLLINO MAXI es en realidad Arduino + un conjunto de escudos y todo está diseñado como un PLC industrial. Pero sobre él la próxima vez.

Referencias

Archivo con diagramas, bocetos y una placa de circuito impreso en formato lay6
PLC CONTROLLINO compatible con Arduino , el trabajo con el que inspiró la creación del HMI i2c
Expansor de puerto PCF8574 y conectarlo a Arduino
→ Tarjeta FC-113 para operación LCD 1602 a través del bus i2c y su conexión a Arduino
Menú de árbol multinivel , principios generales de creación en C
Codificación UTF-8
Codificación de Windows-1251

Source: https://habr.com/ru/post/es401587/


All Articles