Firmware auf dem Nextion HMI unter Linux hochladen
Published by dg5kr on Donnerstag, Juli 23, 2020
Das Nextion HMI (Human Machine Interface) ist ein Touchdisplay der besonderen Art. In diesem Artikel möchte ich gar nicht so sehr auf das Display, deren Eigenschaft und Einsatz eingehen. Hier verweise ich auf die unten angegeben Artikel. Viel mehr möchte ich über das „Bestücken“ der Firmware des HMI schreiben. Nextion bietet dabei zwei Möglichkeiten an:
1. Firmware Upload via SD Karte
2. Firmware Upload via serielle Schnittstelle
Bei 1. Wird die Firmware auf eine FAT32 formatierte mini SD kopiert. Nach dem einlegen im Nextion HMI wird diese Firmware nach dem Einschalten automatisch in das HMI geladen. Allerdings ist diese Methode recht umständlich, da man immer physisch an die Platine des Display ran muss.
Punkt 2 ist wohl meist genutzte Methode. Hier das Display mittels FTDI Adapter (TTL-Serial auf USB) am Rechner angeschlossen und die Firmware kann dann mit der Nextion IDE aufgespielt werden.
Nun, hier ist die Krux. Die IDE gibt es ausschließlich für Windows. Andere native „Upload-Programme“ sind ebenfalls bisher nur unter Windows zu finden.
Nun habe ich aber das Problem das ich das Nextion-Display als „Statusanzeige“ für meinen DMR Jumbospot nutze und in einem Gehäuse fest verbaut ist. Dort läuft ein MMDV auf basis PISTAR. Schnell kam der Wunsch die Firmware von ON7LDS nach meinen Wünschen anzupassen. Leider musste ich dazu jedes Mal das Gehäuse öffnen und den Nextion Anschluss vom Raspberry Pi trennen um ich dann am PC zu verbinden und die Firmware auf das HMI zu laden – ganz schön umständlich.
Meine Recherche fanden für Linux ein Python Programm, was dies tun soll. Leider funktioniert das Programm nicht und schien auch nicht weiter gepflegt zu werden. Also entschied ich mich ein solches Programm selber zu schreiben. Ich entschied mich für C, da ich in Python nicht so bewandert bin.
Raus gekommen ist ein Tool, welches per Befehlszeile eine kompilierte (TFT-File) Firmware für das Nextion-Display unter Linux aufspielt. Im Beispiel von PI-STAR spiele ich die Firmware über das Netzwerk einfach in Home Verzeichnis vom User pi-star und rufen dann über die SSH Konsole der PI-STAR Webseite das Tool auf.
// nextion_upload.c
// Send Firmwareupdates to the Nextiondisplay
//
// Parm1 = Port
// Parm2 = TFT-File
#include <ctype.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
/* ------------------------------------------------------------------------------------------------------------------ */
// Global VARS
char hostname [16];
char *portname = "";
char *filename = "";
int USB;
// See https://nextion.tech/2017/12/08/nextion-hmi-upload-protocol-v1-1/
//Baudrate 2400 4800 9600 19200 38400 57600 115200
//Delay(ms) 447 239 135 83 57 48 39
int TICKWAIT = 135000;
int BAUDCOMM = 9600;
int BAUDUPLOAD = 115200;
char ACKCODE=0x05;
char ENDCODE=0x88;
/* ------------------------------------------------------------------------------------------------------------------ */
void CLS()
{
// CLS
printf("33[3;J33[H33[2J");
}
/* ------------------------------------------------------------------------------------------------------------------ */
void print_usage()
{
// CLS
CLS();
// 01234567890123456789012345678901234567890123456789012345678901234567890123456789
printf("\nNEXTION_UPLOAD, sendet neue Firmware zum Nextiondisplay\n");
printf("(C) 2020 by DG5KR\n\n");
printf("Aufruf: nextion_upload /dev/ttyUSB[n] TFT-Filenamen\n");
exit(0);
}
/* ------------------------------------------------------------------------------------------------------------------ */
void GOTOXY(int x,int y)
{
printf("%c[%d;%df",0x1B,y,x);
}
/* ------------------------------------------------------------------------------------------------------------------ */
void intHandler(int dummy) {
int ANT;
printf("Programm wirklich abbrechen (j/n) ");
ANT=getchar();
if(ANT==106) {
// start MMDVM
// gethostname(hostname,16);
if(strcmp(hostname,"hotspot")==0)
{
system("/usr/bin/sudo /bin/systemctl start mmdvmhost.timer");
system("/usr/bin/sudo /bin/systemctl start mmdvmhost");
usleep(1000000);
}
exit(127);
}
}
/* ------------------------------------------------------------------------------------------------------------------ */
int get_file_size()
{
struct stat st;
stat(filename, &st);
int size = st.st_size;
return size;
}
/* ------------------------------------------------------------------------------------------------------------------ */
int openport_init(char *portname)
{
USB = open( portname, O_RDWR| O_NOCTTY | O_NDELAY );
// Init Port
struct termios tty;
memset (&tty, 0, sizeof tty);
// Set Baud Rate
cfsetospeed (&tty, (speed_t)B9600);
cfsetispeed (&tty, (speed_t)B9600);
// Setting other Port Stuff
tty.c_cflag &= ~PARENB; // Make 8n1
tty.c_cflag &= ~CSTOPB;
tty.c_cflag &= ~CSIZE;
tty.c_cflag |= CS8;
tty.c_cflag &= ~CRTSCTS; // no flow control
tty.c_cc[VMIN] = 10; // read doesn't block
tty.c_cc[VTIME] = 0; // no read timeout
tty.c_cflag |= CREAD | CLOCAL; // turn on READ & ignore ctrl lines
tty.c_iflag &= ~(ICANON | ECHO | ECHOE | ISIG);
// Flush Port, then applies attributes
tcflush( USB, TCIFLUSH );
if ( tcsetattr ( USB, TCSANOW, &tty ) != 0) {
printf("Error flush serial: %s", strerror(errno));
printf("nn");
exit(errno);
}
}
int openport_upload(char *portname)
{
USB = open( portname, O_RDWR| O_NOCTTY | O_NDELAY );
// Init Port
struct termios tty;
memset (&tty, 0, sizeof tty);
// Set Baud Rate
cfsetospeed (&tty, (speed_t)B115200);
cfsetispeed (&tty, (speed_t)B115200);
// Setting other Port Stuff
tty.c_cflag &= ~PARENB; // Make 8n1
tty.c_cflag &= ~CSTOPB;
tty.c_cflag &= ~CSIZE;
tty.c_cflag |= CS8;
tty.c_cflag &= ~CRTSCTS; // no flow control
tty.c_cc[VMIN] = 10; // read doesn't block
tty.c_cc[VTIME] = 0; // no read timeout
tty.c_cflag |= CREAD | CLOCAL; // turn on READ & ignore ctrl lines
tty.c_iflag &= ~(ICANON | ECHO | ECHOE | ISIG);
// Flush Port, then applies attributes
tcflush( USB, TCIFLUSH );
if ( tcsetattr ( USB, TCSANOW, &tty ) != 0) {
printf("Error flush serial: %s", strerror(errno));
printf("nn");
exit(errno);
}
}
/* ------------------------------------------------------------------------------------------------------------------ */
int send_cmd(char *cmd_str)
{
int n = write(USB,cmd_str,strlen(cmd_str));
n = n + write(USB,"xFF",1);
n = n + write(USB,"xFF",1);
n = n + write(USB,"xFF",1);
usleep(TICKWAIT);
return n;
}
/* ------------------------------------------------------------------------------------------------------------------ */
/* ------------------------------------------------------------------------------------------------------------------ */
int main(int argc, char *argv[])
{
/* ------------------------------------------------------------------------------------------------------------------ */
// trap CTRL-C
signal(SIGINT, intHandler);
/* ------------------------------------------------------------------------------------------------------------------ */
int n=0;
/* ------------------------------------------------------------------------------------------------------------------ */
// Es wurde kein Paramter uebergeben
if(argc < 2)
{
print_usage();
}
portname = argv[1];
filename = argv[2];
/* ------------------------------------------------------------------------------------------------------------------ */
// Hole Hostname
gethostname(hostname,16);
/* ------------------------------------------------------------------------------------------------------------------ */
// stop MMDVM
if(strcmp(hostname,"hotspot")==0)
{
system("/usr/bin/sudo /bin/systemctl stop mmdvmhost");
system("/usr/bin/sudo /bin/systemctl stop mmdvmhost.timer");
usleep(1000000);
}
/* ------------------------------------------------------------------------------------------------------------------ */
// Open Port
openport_init(argv[1]);
// Ready zum Daten Empfang
n = send_cmd("");
n = send_cmd("connect");
printf("Bytes Txed %d, ", n);
// Lese Ergebnis
char read_buffer[1024];
int bytes_read = 0;
int i = 0;
bytes_read = read(USB,&read_buffer,256);
printf("Bytes Rxed %dn", bytes_read);
printf("ACKN: %sn", read_buffer);
// Filelänge ?
int flen=get_file_size();
printf("Bytes to send of %s: %d bytesn", filename,flen);
// Befehl um in den Upload mod zu gehen
// whmi-wri filesize,baud,res0ÿÿÿ
char s[80] = "";
sprintf(s,"whmi-wri %d,%d,res0",flen,BAUDUPLOAD);
send_cmd(s);
// Warten auf ACK
char BUF[1];
bytes_read = read(USB,BUF,1);
while (bytes_read <= 0)
{
bytes_read = read(USB,BUF,1);
usleep(TICKWAIT);
}
// Setzen der Upload Rate
close(USB);
openport_upload(argv[1]);
usleep(150000);
// Jetzt schieben wir 4096Byte Packete dahin
int fd = open(filename, O_RDONLY);
char buffer[flen];
int pack_len=4096;
for (i=0 ; i < flen ; i=i+pack_len) {
// process bytes at buffer[i]
read(fd, buffer, pack_len);
write(USB,buffer,pack_len);
GOTOXY(0,5);
printf("Sending firmware to HMI: Block %8d of %d",i,flen);
GOTOXY(0,6);
usleep(TICKWAIT);
// Warten auf ACK
char BUF[1];
bytes_read = read(USB,BUF,1);
while (bytes_read <= 0)
{
bytes_read = read(USB,BUF,1);
usleep(TICKWAIT);
}
usleep(TICKWAIT);
}
// Und den Rest
pack_len=flen-i;
read(fd, buffer, pack_len);
write(USB,buffer,pack_len);
GOTOXY(0,5);
printf("Sending firmware to HMI: Block %8d of %d",i,flen);
GOTOXY(0,6);
close(fd);
close(USB);
/* ------------------------------------------------------------------------------------------------------------------ */
// start MMDVM
if(strcmp(hostname,"hotspot")==0)
{
system("/usr/bin/sudo /bin/systemctl start mmdvmhost.timer");
system("/usr/bin/sudo /bin/systemctl start mmdvmhost");
}
/* ------------------------------------------------------------------------------------------------------------------ */
exit(0);
}
Programm compilieren mit:
gcc nextion_upload.c -o nextionupload -I/usr/local/include -L/usr/local/lib -l ncurses
Informationen und Projekte rund um das Nextion Display:
Nextion
Firmware Upload Protocol
ON7LDS – Nextion Displays
ON7LDS – Nextion Displays
Ein Nextion Serial Client in Python