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