具有I2C总线的Arduino操作员面板(HMI)

作为使用某些与arduino兼容的设备(最后是关于它的部分)的一部分,我需要一个带有按钮的屏幕来控制和显示当前信息。 也就是说,需要操作员面板,它也是HMI。

决定独立制作HMI,并使用“方形” i2c总线作为接口。



如果您对此类设备的开发和编程过程感兴趣,欢迎联系cat。

特点

  • 显示1602,单色16x2字符
  • 5个按钮:向上,向下,取消,输入,编辑
  • I2c接口
  • DB9F连接器
  • 尺寸155x90x44毫米

有明显的问题:

为什么不买现成的盾牌呢?
当然,也可以用显示屏和键盘从同一中文购买现成的盾牌,例如:

您可以在此屏蔽层上焊接2条FC-113围巾,其功能与我的屏蔽层相同:带i2c键盘的显示器。 套装的价格从$ 4。

但是在此板上,按钮的尺寸不适合我,但我希望大型按钮能够安装多色盖。 我不想使用普通的DB9F连接器将Arduino连接到HMI,但是有必要制作一个连接板。 在这种情况下,制作一两个板有什么区别? 此外,我已经有几台1602显示器库存,因此我只需要花费1.02美元就可以在Aliexpress上购买FC-113(0.55美元)和PCF8574P端口扩展器(0.47美元)。

好吧,最重要的是,如果您要处理的是Arduino,那么为他独立制造屏蔽罩是理所当然的,对吗?

为什么使用i2c总线,直接连接按钮是否更容易?
在我工作的工业控制系统领域,HMI使用数字数据传输接口RS-232,RS-485,CAN等与设备进行通信。 因此,对于我而言,我自己的HMI 可以在数据接口(在本例中为i2c)上工作,这是合乎逻辑的。

如果我制造了一种显示器可以在方形总线上工作的设备,并且按钮直接进入了Arduino的输入,那会让我感到非常不满意。 正如我将要呈现的这张图片:一条单独的电线从面板伸出到接口,分开的电线接到输入,brrrr ...

此外,直接进入Arduino输入的按钮板与具有i2c接口的按钮板之间的区别仅在于PCF8574P芯片(0.47美元),一个电容器和两个电阻器。

为什么按钮排列成这样而不是其他?
从左到右的按钮具有以下功能:上,下,取消,输入,编辑。
将“编辑”按钮从其他按钮移开一点,以强调其功能-更改逻辑参数的值(打开/关闭),或在数字参数的情况下切换到编辑模式。

尽管键盘上的芯片最多可以连接8个部件,但总共有5个按钮。
使用四个按钮就足够了,并且功能不会受到影响-“输入”和“编辑”可以组合在一个按钮中。 但是,我只是感到遗憾,端口扩展器微电路的8条支路中有一半没有参与。
如果我决定在一行中显示多个参数,则另一个单独的“编辑”按钮可能很有用。 然后,使用此按钮可以在参数之间切换,以指示需要更改的参数。 这就是流行的中文HMI OP320中“ SET”按钮的工作方式。

如果前两个按钮是向上和向下的意思,那么为什么不将它们垂直放置,例如在上面的中国盾牌中那样呢?

就我个人而言,将所有按钮都水平放置会更方便,然后在操作过程中手指只能在一个平面上移动。






1.带有DB9F连接器的自制底板。 因此,当我们为Arduino的端口和显示扩展器使用+ 5V电源时,我在板上放置了0.1 A保险丝

2.众所周知,众所周知的1602显示器带有FC-113焊接板,可将显示器连接到i2c总线。

3.带有PCF8574P芯片的自制键盘板,它将读取按钮的状态并通过i2c总线进行传输。 顺便说一句,FC-113“显示”板也基于PCF8574芯片,仅具有T索引,即 平面,而不是DIP,例如PCF8574P。

我在按钮上放了12h12mm的方形按钮;可以在上面戴上彩色的大号帽。

自制电路板的照片和方案






值得一提的是PCF8574P芯片,我以此为基础制作了键盘板。
PCF8574P是i2c端口扩展器。 共有8个端口,每个端口都可以配置为输入或输出。 对于该芯片,不需要这样的捆扎(例如,请记住max232),以防万一我放了一个电容器来供电。

PCF8574P芯片的地址使用地址引脚A0,A1,A2进行设置,这些地址引脚通过10kΩ电阻拉至地面或电源。

在键盘板上,我将PCF8574P的所有地址腿都放在了地上,因此该地址被硬编码为0x20,并且您无法更改。

正如我已经写过的,我选择DB9F作为HMI的连接器。 Arduino从+5 V,GND,SDA,SCL接收信号。



通过Arduino i2c和HMI进行通信的导线长1.4 m,工作时无毛刺。

我在Sprint Layout 6中绘制了木板,并使用LUT方法将其转移到了Textolite上,并在过氧化物和柠檬酸溶液中对其进行蚀刻。

关于腌制的一些知识
在铝箔玻璃纤维上蚀刻柠檬酸板有很多方法。

我配制了这种溶液:100毫升3%的过氧化氢,50克柠檬酸,3茶匙盐。 他用水将锅中的过氧化物罐加热到约70度。

根据过氧化物蚀刻的建议,我们将电路板浸入溶液中,使图案朝下。
几十秒后,一个风暴开始。 释放出大量蒸汽,不建议吸入。 大概吧



然后该过程消退。 翻转板。



做完了



该盒子是由4毫米有机玻璃的朋友用激光切割机制成的。

关于兵团的抒情离题
买完箱子还是自己做? 经过一番思考,我决定自己做。 我在销售中看到的那些由于价格或美学原因而不适合我,或者在DIN导轨上也不适合我。

最初,尸体想从胶合板上切下。 但是后来我想起了我有一个很好的朋友,并且令我高兴的是一家体育颁奖公司的董事。 他在那里拥有各种机器,包括激光切割机。

他寻求帮助,一个朋友没有拒绝,用激光切割了几分钟后,零件被拒绝了。

借此机会,我要说谢谢,科里亚! 否则,我将不得不整天切割和研磨胶合板,其结果将不会如此出色。

程式设计


从Arduino的角度来看,此HMI由在i2c总线上工作的2个设备组成:地址为0x27的显示器(LCD)和地址为0x20的键盘。 因此,Arduino将分别与键盘和LCD配合使用。

通过专用库“ LiquidCrystal_I2C.h”进行LCD的操作,必须将其安装在Aduino IDE中。

键盘的操作通过标准的Wire.h库完成,该库最初在Aduino IDE中可用。

我们将HMI连接到Ardiuno。



1.首先,检查Arduino是否看到我们的HMI。 为此,将程序加载到其中,该程序将扫描i2c总线上的设备。

草图1,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); } 


在执行此程序期间,Arduino将i2c总线的扫描结果写入串行端口。 要查看此数据,请转到Arduino IDE工具->端口监视器。



我们看到i2c总线上的Arduino识别了两个地址为0x20和0x27的设备,分别是键盘和LCD。

2.现在,让我们看看键盘的工作原理。 创建一个程序,该程序将轮询按钮的状态并将其显示在LCD上。

草图2,显示按钮的状态
 /*   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); } 




键盘正在工作。

3.最后,您可以继续进行所有操作;在Arduino中创建一个多级菜单。 通过菜单,我们不仅可以观看信息,还可以控制Arduino本人的输出。





在nete中,有很多有关在C ++中创建多级菜单的信息,但是对于Arduino,我什至看到了一些库。 但是我决定在程序中自己编写菜单。 首先,项目中剩余的图书馆越少越安静。 其次,这很简单。

我得到了树形菜单的另一种变化。 该菜单允许您同时在每一行中显示静态文本和变量值。 例如,您可以显示参数的名称及其值。

为了在屏幕上显示变量,我应用了标记的原理-以某种方式在文本中设计了文本标签,而不是在屏幕上显示文本时显示值。

可以通过按“编辑”按钮来更改参数。 此外,每个参数的标签指示它是否可用于编辑或只读。 如果当前参数是只读的,则在行的开头,指针将为“ *”;如果启用了编辑,则指针将变为“ +”。

草图3,多层菜单
 /*  ,    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和语言问题


另外,有必要提出俄罗斯化问题。

在某些LCD 1602的字符生成器中,没有俄文字母,而是用日文krakozyabry代替了。 无法刷新字符发生器。 因此,您将不得不在屏幕上用拉丁字母写单词,或者在程序中自己编写俄语字母,因为 LCD 1602能够在RAM RAM中创建并存储您自己的字符。 但是,在后一种情况下,您一次只能在屏幕上显示八个“自制”字符。

字符表LCD 1602




原则上,可以在液晶显示屏上用英语写俄语单词。 在那里,即使是久负盛名的法国公司施耐德电气公司(甚至在革命之前就曾向沙皇出售榴弹炮的公司)都无法将俄国人引入其著名的Zelio可编程继电器。 但这并不能阻止他们在独联体的广阔区域中积极交易。 此外,还引入了运河,西班牙文和葡萄牙文。

在我们的许多工厂中,这些Zelio用诸如“ NASOS 1 VKL”之类的短语与员工进行沟通。

当不清楚特定的LCD中是否有俄语字母时,您需要在屏幕上显示其字符发生器的所有字符。 如果有西里尔字母,则从位置160开始。

草图4,显示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); } } } 


但是,即使您的LCD 1602生锈了,显示俄语单词也不是那么简单。 至少在i2c总线上使用LCD时,请使用LiquidCrystal_I2C.h库。

例如,如果仅使用指令lcd.print(“ Hello !!!”)输出俄语文本,则代替“ Hello !!!” 屏幕上会出现一些垃圾。

这是因为Arduino IDE将俄语字母转换为两字节的UTF-8代码,而在LCD中,所有字符均为单字节。

顺便说一句,当将俄语文本从Arduino传输到Arduino IDE端口监视器时,也会观察到相同的问题。 Arduino将UTF-8双字节编码的俄语字母发送到串行端口,而Arduino IDE端口监视器尝试以单字节Windows-1251编码(cp1251)读取它们。 尽管cp1251也是8位的,但与LCD 1602编码一样,它也不匹配。

您可以通过字符代码形成俄语文本。 例如,Russified LCD上的“ LCD display”行将显示为:

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

但是我不喜欢这种方法。

为了在Russified LCD 1602上正确显示俄语文本,为Arduino发明了几个库。 但是在阅读评论后,我发现许多人抱怨使用它们时出现了故障。

因此,我自己在多级菜单程序中编写了一个简单的函数,用于将UTF-8转换为LCD代码。 没错,我只用大写的俄语字母做过,非常适合我。

用于将UTF-8大写俄语字母转换为单字节LCD 1602代码的功能
 /* *  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; } 


带有i2c总线的自制HMI就这些了。

哦,是的,在本文的开头,我写道我不是在为Arduino做HMI,而是为与Arduino兼容的设备做HMI。 这是关于CONTROLLINO MAXI的 ,这是从Arduino IDE(以及许多其他产品)编程的。



CONTROLLINO MAXI实际上是Arduino +一堆屏蔽板,所有组件均设计为工业PLC。 但是下次呢。

参考文献

存档包含lay6格式的图表,草图和印刷电路板
Arduino兼容的PLC CONTROLLINO ,这项工作启发了HMI i2c的创建
PCF8574端口扩展器并将其连接到Arduino
→用于通过i2c总线进行LCD 1602操作的FC-113板及其与Arduino的连接
多层树菜单 ,用C创建的一般原理
编码UTF-8
编码Windows-1251

Source: https://habr.com/ru/post/zh-CN401587/


All Articles