Start a new topic

» Nextion Waveforms

This tutorial will go through the basics of creating Nextion Waveforms.

 ( No promises of completely ready to use code, for any MCU / language )


The Nextion Waveform component 

  - can accept byte range values from 0 to 255.

  - can not accept values 256 and over

  - is a local component, accessible only when page is current

  - can not be a global component, is inaccessible from other HMI pages.

  - can have up to 4 channels per waveform

  - an HMI page can have up to 4 waveform components per page.

  - can have left to right, right to left and right aligned data

  - is not 100% perfect in every combination of available option

  - uses 1 column 1 pixel wide per data point

  - number of data points is the width of waveform in pixels

  - when filled will scroll one pixel and add new data at the waveform edge

  - has two commands useful from Nextion Instruction Set add and addt

  - recently in v0.46 allowed for Nextion side .val attribute as value parameter.

As with any Nextion side component, the attributes in green are dynamic

 - bco/picc/pic, gdc, gdw, gdh, pco0, pco1, pco2 and pco3 can be changed at runtime

 - design position of the waveform x, y, h, w is static - can not change at runtime

 - direction .dir and .sta (image/crop/solidcolor) is static - can not change at runtime.

The Nextion Instruction Set defines three useful commands for waveforms

    add - adds a single value to the waveform channel

    addt - adds multiple values at once to a waveform channel

    sendme - I will argue knowing you are on the waveform page in HMI is useful.

    add and addt are the only two commands that allow new values to be included


So next we will go through creating a functional waveform

2 people like this idea

So let's start a new two page HMI

  - Page 0 of mine has a logo

      - Page Preinitialization Event has the command sendme

      - Page Touch Release Event has the command page 1

  - Page 1 of mine has a waveform .id 1 and Hotspot

      - Page Preinitialization Event has the command sendme

      Waveform wxh is 361x257 at x,y (60,30) with .id of 1

      - this is a single channel waveform, so .ch is 1

      - .dir is right to left, and .sta is solid color

      - .bco of 0, .gdc of 512, .gdw of 30, .gdh of 32, and .pco0 of 64528

      Finally the Hotspot is on left side of page with

         - Touch Released Event has the command page 0

The HMI is very simple in design

  - it allows for changing to and from the Waveform Page

  - the waveform has the one channel for data

  - provides MCU update as to current page with sendme

But wait ....

   as stated above, the waveform is local and not a global component.

   - as we leave the waveform page

        any waveform add/addt commands become invalid

   - as we re-enter the waveform page

        our waveform is empty after loading the page as per design

So how should we deal with this?

  The Nextion sends a 0x66 Return Data on each sendme (as page changes)

   - 0x66 0x00 0xFF 0xFF 0xFF is sent in response to page 0

   - 0x66 0x01 0xFF 0xFF 0xFF is sent in response to page 1

  When we receive these, we know which page is current by the second byte

   - only if current page is 1, should any add commands be sent from the MCU

   - on entry into page 1, we should consider backfilling our waveform with data.

  With our waveform backfilled with data,

      we only really need to be concerned with adding the most current value

      using the single value add command.

  So how do we backfill our waveform with data

     - create a circular byte buffer for these shadow values     

     - we will need s0.w quantity of values stored MCU side

     when backfilling we use the multi value addt command (above)

       and send values from array head to array tail

  Then as each new reading comes in to the MCU

     - store it in the tail of the circular array and then only if the current page

       is the waveform page, then send single value add command (above)

As we see in this basic example,

   a basic Nextion HMI design makes the waveform accessible for use

   the remaining requires MCU side coding of the topics discussed here

   - catching the 0x66 Return Codes - current_page is second byte

   - on change to current_page = 1 - function to backfill with addt

   - on each sensor reading, append to tail of circular array

       and conditionally, if current_page = 1, send value with add

To identify the sendme,

  modify to add the following condition inside NexHardware.cpp's nexLoop();  


   if (nexSerial.available() >= 4)
      __buffer[0] = c;  
      for (i = 1; i < 5; i++)
         __buffer[i] =;
      __buffer[i] = 0x00;
      if (0xFF==__buffer[2] && 0xFF==__buffer[3] && 0xFF==__buffer[4])
         // add call to function for when sendme 0x66 comes in
         // current page is __buffer[1]


Now what happens when a value exceeds a user threshold

 - lets consider some ways to provide a warning

  (these are perhaps only a few of many many possibilities)

As .pco0 is a runtime changeable attribute,

  we can consider toggling .pco0 to red when threshold exceeds

  - when threshold is breached s0.pco0=32768ÿÿÿ

  - when alert has ended and returns to normal s0.pco0=64495ÿÿÿ


As .bco is also a runtime changeable attribute,

  we can consider toggling .bco to red when threshold exceeds

  - when threshold is breached s0.bco=32768ÿÿÿ

  - when alert has ended and returns to normal s0.bco=0ÿÿÿ



See attached .mp4 videos for concepts presented so far.

  - waveform backfilled upon entering into page 1 triggered by sendme

  - new values updated to waveform as new values come in

  - channel color changes when threshold exceed/returns within range - clip0022.mp4

  - background changes when threshold exceed/returns within range - clip0026.mp4

Of course, we can also use other screen real-estate to have other means of status and

   indicators.  Adding a Text component called msg can allow for a more detailed status

   message about an alert condition msg.txt="10:14a My Coffee is just too cold"ÿÿÿ

(1.08 MB)
(1.1 MB)

So let's examine the waveform grid lines.  Default .gdh of 40 and .gdw of 40.

Here we know that our width relates to some time scale, as it is in a time interval

that each new data point is added to the waveform.  We know the magnitude of

the data point height is in relation to some scale of our reading.  It makes more

sense to adjust our .gdw to match some unit of our frequency, and .gdh to match

some unit related to our measurement.  Are these adjustable at runtime?

Indeed they are adjustable at runtime, so it is possible to send

 - s0.gdh=60ÿÿÿ to adjust horizontal spacing for height and

 - s0.gdw=60ÿÿÿ to adjust vertical spacing for width

It is important to note that such a 60x60 grid will always start at lower left and be

    patterned up from the bottom, and right from the left.  This means incomplete

    grid blocks (if any) will be always along the top and the right side of the waveform.

There is no means to have a partial grid block at the bottom (using grid .gdh)

It is also important to note, such settings will be lost on waveform page re-entry,

    and as such would have to be handled in code on re-entry.  sendme is valuable.

Now we know that we can fairly quickly backfill a waveform with data using addt,

    and this means that it is more than possible to reuse an existing waveform for

    more than one type data, in more than one scale, over more than one time base.

By adding a few touch events to the outside real-state of our waveform page, we can

    use the components touch Send Component ID checkbox to notify the MCU

    that we want to display our new data, data units and time line. From here it will

    be up to our code to deliver our new waveform data.

It is important to note, that our MCU side code will be required to update array values

    for each new data type, in each scale, for each time base in their own circular buffer.

    It perhaps wouldn't be too long before much MCU side SRAM for the circular buffers,

    and much MCU cycles are consumed to keep all up-to-date for on demand display.

As such, one must really calculate what they desire in balance to available resources.

In Stump the Chump challenge #12, I demonstrated that we could have time scales

    for 1H, 6H, 1D, 1W, 1M and 3M and change the waveform to fit such.  There are

    two key issues in meeting such a change of time scales

    - dynamically reordering time scale under the new grid lines (see xstr command)

    - and having all the data readily available to backfill the waveform.

In the Stump the Chump challenge, showing it was possible was the main goal,

    and not creating an entire application (especially any MCU side coding) with data.

We could also remove gridlines by setting the waveform .gdc to equal the waveform .bco

    s0.gdc=s0.bcoÿÿÿ  making grid lines (actually drawn) indistinguishable from its background

Let's for a moment talk about labeling our waveforms as mentioned in Stump the Chump

 - This is simply not an effective means to attempt to do.

 - Although it can indeed be done, we are restricted by where the labels will be and how big.

     - more than a few calculations would indeed be needed to ensure success

The difficulty here is the amount of horizontal space to fit the label between vertical grid lines.

 - this indeed has to do with the width and height sizing of our used font for labelling.

   when the .gdw is set at 40 and we use a 12x24 font,

      we can use up to 3 chars for label before it looks like it runs together, 4 will start overwriting.

   when the .gdw is set at 30 and we use an 8x16 font, again 3 char and 4 starts overwriting.

 - but should we dynamically change the width of vertical grid lines with .gdw,

      we can not also change the .x positioning of any static Text Components for labelling.

    we must therefore consider if we desire dynamic labelling, and in most cases use xstr.

We have a similar issue in vertically labelling where the height of our gridlines .gdh will influence

    what size fonts to use for our labels.  Again, if dynamically changing .gdh, then use xstr.

Now up to now I have been going over and dealing with (.sta as) solid color backgrounds.

   This configuration is indeed the fastest to render on the Nextion.

But this is not the only configuration the waveform is capable of.

   Indeed .sta has both image and crop image options

With .sta set as image or crop image there is no grid lines (.gdc, .gdh or .gdw)

   Your gridlines will need to be included within your chosen image.

   s0.picc=1ÿÿÿ on a .sta of crop image sets the waveform to crop from pic resource 1

   s0.pic=1ÿÿÿ on a .sta of image sets the waveform to the pic resource 1

With a graphic as the waveform background you have more control over the look and feel

   - including anti-aliasing and font smoothing created by your graphics program

   but this more control is only at the time of making the background graphic

   - there is no changing the graphic at runtime, besides swapping the pic with another

This option will indeed take more time to render as pixels are not compared to a constant

      value, but will need to be fetched from HMI resources and pixels compared.

Now, I want to make a slight correction to no changing the graphic at runtime.

  - The Nextion Instruction Set has GUI commands that can be added nearly anytime.

     this will not allow you to draw within the boundaries of the Waveform,

     but it certainly adds to what can be done - the creativity is up to you.

Although the Waveform component has special access in firmware that user commands do not,

   many of the limits of the Nextion waveform component can be surpassed with GUI commands.

   - the trade off, GUI commands take longer to execute - more clock cycles to process.

The GUI commands can also make a waveform (again, not as quickly, but functional)

    - the 4 channel limit?  no longer a limit, maybe you want 6 channels

    - the 0 to 255 range limit,  no longer a limit, maybe you want full Nextion screen size

    - scrolling, okay slower, you will have to manage your own scroll handling.


So indeed with GUI commands, what can be done is limited by your imagination.

Tutorial thread cleaned

Login or Signup to post a comment