The program.s file

For what is it good and for what not?

Introduced with the Nextion Editor v 1.60.0 in early 2020 as a container for startup code, the program.s file allowed for the very first time to execute code before the startup page (which was always page 0) was loaded. Although we have been using it many times in our demo projects, and since every project compiled using Nextion Editor v 1.60.0 or newer makes use of it, even when you do not touch at it, I think that it’s time to have a deeper look to get most out of it.

Now to the details

When you create a new project in the Nextion Editor, the program.s file is already populated with some comments and a few default settings. Let’s look critically at these, one by one, in a first step. The very first line is a pure comment, just as a short reminder of the purpose:

//The following code is only run once when power on, and is generally used for global variable definition and power on initialization data

Yes, it’s only run once after power on. The simple and short explanation is that a page command, which is always the last to be executed in the program.s file, does not return. That means that if you write code after a page command, it will never be executed. But the code before the page command allows us to do powerful things. Let’s look at the next line as a first example:

int sys0=0,sys1=0,sys2=0     //At present, the definition of global variable only supports 4-byte signed integer (int), and other types of global quantity declaration are not supported. If you want to use string type, you can use variable control in the page to implement

Here we see that the global system variables sys0, sys1, and sys2 which were hardcoded in earlier firmware releases are now defined and at the same time initialized in the program.s file. This allowed to free up some firmware space for other functionalities which have been added or extended since v 1.60.0 while maintaining full backwards compatibility with older HMI projects which made use of these global system variables. If you don’t need these, you can delete this line and thus free up 12 bytes of RAM. You may also define your own numeric globals instead. In terms of RAM use, these globals don’t eat up more or less memory than any other numeric variable component placed on a page with its .vscope set to global. But having all global definitions in one single place makes your code easier to read and to maintain, similar to #define and const definition in a C header file. Thus, for a drawing routine with GUI commands, you may for example define your own constants for a menu bar which will later be used on multiple pages. When re-compiling your project for another screen size, it will be sufficient to change these pseudo-constants once in the program.s file to adapt your menu accordingly:

int menu_h=20,menu_w=480 //set the height and the width of the menu bar

But, didn’t we read that arbitrary code can be executed? Thus, why not let our menu dimensions be calculated automatically, depending on the screen size? Something like

int menu_h=page0.h*8/100,menu_w=page0.w //set the menu to 8% of the page height and to 100% of the width

will unfortunately give us several compile errors and you have perhaps already understood why. First, you can’t refer to page0 before it has been loaded. Second, you can initialize numeric variables only with constants, not with computations.

Solving the second issue is easy: Do it in two steps. First declare the variable without initializing it, and then, initialize it with a computed value as follows:

int obj_h=150,obj_w //declare the width without initializing
obj_w=obj_h*4/3     //initialize the width based on the height with a 4:3 ratio

There is also a solution for the first issue: Just declare our menu dimensions without initializing them immediately and postpone the initialization until the first page is loaded. Thus, write in program.s only

int menu_h,menu_w //declare the height and the width of the menu bar without initialization

and put the following in the Page PreInitialize event of the first page to be loaded:

menu_h=b[0].h*100/8 //set the menu height to 8% of the page height, b[0] refers to the object id 0 of the page which is the page itself, which makes your code independent of the page id and its name
menu_w=b[0].w       //set the menu width to 100% of the page width.

This will give you a menu bar which always fits, independent of the screen size and resolution!

Now to the “true” system variables

By default, three system variables are set in a “virgin” program.s file: baud (serial speed), dim (screen brightness), and recmod (protocol parse mode). And this for several purposes: First, these make sure that an external MCU may properly communicate with your Nextion and that the display doesn’t seem black/dead after a reboot, even if everything went nuts later in in a previous run of your code. And second, these serve you as an example on how to properly initialize system variables which are already declared in the firmware. That’s while these are not preceded by the int keyword.

baud=9600//Configure baudrate
dim=100//Configure backlight
recmod=0//Serial data parsing mode:0-Passive mode;1-Active mode

Feel free to modify them and to add more system variables to this section, for example bkcmd, if your project needs these to be properly initialized. You might even think about deleting these if you are sure that the startup default values like bauds or dims are properly set to save a few microseconds on the boot time. But that’s something I do not recommend for the sake of further compatibility – since the developers of the Nextion Editor and firmware insist of having an initialization in program.s, it might be that in further releases, these startup defaults like bauds and dims will disappear to have more firmware space freed up for new or improved functions to come…

And finally, arbitrary code execution

Before v 1.65.0, every Nextion HMI did send out some status bytes, followed by the 3 0xFF terminator bytes, if you wanted or needed this or not, because this was hardcoded in the firmware, too. This could sometimes be annoying, especially if your project implemented a custom protocol between the Nextion and an external MCU. From now on, since this has been moved to program.s for backwards compatibility, you can either modify or delete these to taste:

printh 00 00 00 ff ff ff 88 ff ff ff//Output power on information to serial port

As explained already above, the last command in program.s is always a page command. Again, for backwards compatibility with the hardcoded startup in the firmware, the default is

page 0                       //Power on start page 0

but you may from now on modify it to better suit the needs of your project. The compiler won’t complain if you write page 25 or page startup instead, as long as these pages exist.

Now that we have understood the basic principles, advantages, and limits of the program.s file, you might see a few advanced things which can be done in program.s in one of my next articles.

Thank you for reading and happy Nextioning!

Questions, comments, critics, suggestions? Just send me an email to thierry (at) itead (dot) cc! 🙂