ILI9341_t3n speed up ideas sought

clinker8

Well-known member
I have an electronic lead screw system running on a Teensy4.1 which has been running for about a year and a half. It's been working amazingly well, far beyond my expectations.

I'm adding some features and finding that I'm writing to the ILI9341 too often. This typically happens on a fast feed on a lathe. It's aggravated by the fact that this is a 1um DRO scale. Every time the position changes, (by 1 micron) the display is updated. Works great at lower speeds.

First I'd like to know, roughly how long a small text area takes to update. I only write to a small block on the screen, just to change the number. If you can suggest something to make this part more streamlined I'd appreciate the advice. I call this in the background, but Zval is updated by an encoder ISR.
C-like:
void updateZ()
{
  if ((Zval != oldZval) || (zz == 0))
  {
    uint16_t w, h;
    int16_t x1, y1;
    oldZval = Zval;
    String newstr = "XXXXXXXXXXXX";
    //tft.setFont(Arial_18_Bold);
    tft.setFont(DroidSansMono_18);
    tft.setTextColor(ILI9341_GREEN, thisGREY);
    tft.getTextBounds(newstr, cxgZ, cygZ, &x1, &y1, &w, &h);
    //Serial.printf("w = %i, h = %i\n", w, h);
    tft.fillRect(x1, y1-1, w, h+1, thisGREY);
    tft.setTextDatum(TL_DATUM);

    tft.drawString("Z:", cxgZ, cygZ);
    x1 = tft.getCursorX();  y1 = tft.getCursorY();
    if (metric) {
      float zval = Zval * 25.4;
      if (zval > 0.0) {
        tft.drawString("+", x1, y1); }
      else if (zval < 0.0) {
        tft.drawString("-", x1, y1); }
      else if (zval == 0.0) {
        tft.drawString(" ", x1, y1); }
      x1 = tft.getCursorX();  y1 = tft.getCursorY();
      tft.drawFloat( fabs(zval), 3, x1, y1 );
      x1 = tft.getCursorX();  y1 = tft.getCursorY();
      tft.drawString("mm", x1, y1);
      //Serial.printf("Zval = %+6.3f mm\n", fabs(zval));
    }
    else {
      if (Zval > 0.0) {
        tft.drawString("+", x1, y1); }
      else if (Zval < 0.0) {
        tft.drawString("-", x1, y1); }
      else if (Zval == 0.0) {
        tft.drawString(" ", x1, y1); }
      x1 = tft.getCursorX();  y1 = tft.getCursorY();
      tft.drawFloat( fabs(Zval), 4, x1, y1 );
      x1 = tft.getCursorX();  y1 = tft.getCursorY();
      tft.drawString("in", x1, y1);
      //Serial.printf("Zval = %+6.4f in\n", Zval);
    }
  }
  zz = zz + 1;
}
Second, I'm pursuing some sort of display rate limiting, but I'm currently thrashing at that. Since the display is updated in the main loop, I need a way to estimate the number of calls generated and make them appropriate to the varying speeds. I haven't implemented the code on the Teensy yet but I modeled it in Python to get an idea how to implement it at runtime, from the time between estimated updates. I also calculated the number of updates per second, if it was updated every time the carriage moved a micron. It's RPM and feed rate dependent. The answer is given by

update rate [calls/sec] = RPM/60 x encoder x N/D [step/sec] x stepsize [mm/step] x updates [calls/mm] where:

RPM is measured by the ELS, encoder is known and fixed, N & D depend on the feed rate selected, stepsize is known and fixed, as are the updates/mm

For 400 RPM, an encoder of 4096 pulses/rev and N=150, D=8128 (for a feed rate of 0.2mm/rev of the spindle) and a step size of 2.65 um, (and a 1000 updates/mm), I get an average rate of 1333.33 calls/sec. As a consequence, at this feed rate the display of the Z axis is blurry, and there's banding. So on average, the call rate has to be toned down, a lot!

From my logged data (courtesy of the great help I got here on data logging) my calculated from data display update rate the estimates are quite noisy, as I'm effectively differentiating the time difference of the updates. Nonetheless, I ran a script to see what it would look like if I implemented an alpha filter on the Teensy on reciprocal of the time difference between updates (Z changed by one micron). The raw logged data is grey and a mess, the colored traces are the output of simple alpha filters that would be easy to implement in a Teensy. (Have such a filter, for RPM filtering.) This graph is from captured data from my lathe (yesterday). During this (not super fast) feed the display (the little box with the DRO value,) couldn't keep up. Feed time was from about 52sec to 70sec. Although it looks complete at 67 seconds, it took another 5 seconds to come to a stop, within 3um of the stop point. The last 5 seconds seem forever...
callspersecond_estimator.pngScreenshot 2025-03-28 at 12.13.45 PM.png
I want the position display update rate to be normal at low call rates. I'm willing to programmatically skip updates at fast feed rates, as long as they smoothly transition to "normal" at low rates. I need a signal to control the rate, that's not too hard to compute. I think the alpha filter is over estimating the rates from 65.2 seconds onward. The carriage is just slowly creeping forward, slowing exponentially, as seen in the second graph.

Anyways, that's my question. Any ideas or suggestions would be greatly appreciated. I've probably gone into the weeds, when a simple solution would suffice!
 
Apologies but my brain is not working at it's best today, but it seems to me that you just need to limit the update rate of the display.
How about something like the following:

Code:
elapsedMillis lastVideoUpdate;

#define minNotUpdateTime 15

void VideoDisplay() {
    if (lastVideoUpdate > minNotUpdateTime) {
        // UpdateVideo
        lastVideoUpdate = 0;
    }
}
 
Depending on the refresh rate of the screen (typically 60Hz), it isn't going to update the display pixels from the framebuffer any more frequently than every 1000/60ms (16.67), so any writes to the framebuffer more frequently than that are generally never going to be visible and wasted energy/processing time. The screen refresh rate is independent of the speed of your measurements, so you just need to decouple them by having loop() checking a timestamp and only calling the display refresh when the refresh period has elapsed.
 
Apologies but my brain is not working at it's best today, but it seems to me that you just need to limit the update rate of the display.
How about something like the following:

Code:
elapsedMillis lastVideoUpdate;

#define minNotUpdateTime 15

void VideoDisplay() {
    if (lastVideoUpdate > minNotUpdateTime) {
        // UpdateVideo
        lastVideoUpdate = 0;
    }
}
The tft display is updated normally only if the Z value has changed. So if no movement, there's no update. But if moving fast, I need to throttle talking to the display. And the rate that Z changes is dependent on what parameters the operator set on the touch panel and if the lathe carriage is moving.

So strictly speaking, I'd like the updates based on carriage speed. So if going faster, we update only every 10, or 100 calls, even if there was movement. The actual Z value is being updated via encoder interrupts, so the code knows the position if changed.

Maybe like a displaycount modulo thing. With a variable modulo, that has a minimum value of 1, for always updating, for the low speeds.

I could change everything to being time based, like you indicated, I'll consider it if I can't get the above to work.

The "validity" of the DRO (digital read out) value is paramount. If the operator mistrusts the value, the whole utility of the machine is compromised. Not showing the correct value can result in scrapped machined parts.
 
Depending on the refresh rate of the screen (typically 60Hz), it isn't going to update the display pixels from the framebuffer any more frequently than every 1000/60ms (16.67), so any writes to the framebuffer more frequently than that are generally never going to be visible and wasted energy/processing time. The screen refresh rate is independent of the speed of your measurements, so you just need to decouple them by having loop() checking a timestamp and only calling the display refresh when the refresh period has elapsed.
Thanks for that clear and concise explanation. It's quite helpful. Need to digest that. Currently calling the function too often. I had implemented a counter like scheme for my RPM updates, probably should extend that to the display. My RPM is updated at 50 Hz, (and alpha filtered) which ought to be in the ball park of the display. The period of 60 Hz, is so irrational...
 
Well, thought about this, and came up with something simple. Pretty much what both of you said. I have a variable displaycount that is incremented every 20ms so the simple statement
C-like:
if ( (displaycount % 2 == 0) && ( (menu == 0)||(menu == 6)||(menu == 7) ) ) {
    updateDROs();     // Display positions of linear encoders
  }
seems to work well enough. If I have to, I'll make it 3, instead of 2. (60ms, rather than 40ms) But it's much better now, so that's good. On slow movements you can see it display at the resolution of the DRO encoder. Menu refers to the different screens that I display. Menu 0 is the main menu, and menu 6 is my new "experimental" menu with my feed to stop algorithm. Menu 7 has yet to be written, but is for thread to stop, which is a bit harder.
 
The period of 60 Hz, is so irrational...
No less than QWERTY keyboards are irrational today. Made sense, once :) "Back in the day", analog TV sets used the 60Hz frequency of the electricity supply in the US/Japan as a frame sync.....and as with all old standards, as new things evolved but had to work with existing tech, the standards persist long after they're useful/necessary and long into being a hindrance :)

I don't think you need to be "precise" with the display refresh rate. Maybe I'm slow, but I couldn't read, recognize and digest 50-60 different numbers flashing up on a screen per second, so your display is more of a visual 'indicator', probably giving you the perception of the value decreasing/increasing, and faster or slower, which is all you need, so 40-60fps is more than plenty, just wanted to caution against wasting precious cycles doing more than that rate on what is basically your only execution thread available.
 
No less than QWERTY keyboards are irrational today. Made sense, once :) "Back in the day", analog TV sets used the 60Hz frequency of the electricity supply in the US/Japan as a frame sync.....and as with all old standards, as new things evolved but had to work with existing tech, the standards persist long after they're useful/necessary and long into being a hindrance :)

I don't think you need to be "precise" with the display refresh rate. Maybe I'm slow, but I couldn't read, recognize and digest 50-60 different numbers flashing up on a screen per second, so your display is more of a visual 'indicator', probably giving you the perception of the value decreasing/increasing, and faster or slower, which is all you need, so 40-60fps is more than plenty, just wanted to caution against wasting precious cycles doing more than that rate on what is basically your only execution thread available.
Understood about syncing to the power line and all.

10 Million ticks of the 600 MHz processor clock is 16.6666 ms.

However, in my case I have a pre-existing 20ms timer, so two counts are 40ms. Good enough. 40ms is 25 Hz, which is a pretty fast update and more importantly, convenient to create.
 
10 Million ticks of the 600 MHz processor clock is 16.6666 ms.
Yep, CPU is fast. ILI9341 not so much and, if you're using blocking SPI transactions to update the screen, SPI runs at a fraction of that 600MHz clock, so depending on your screen resolution, screen update size and wiring, you can burn up several milliseconds per refresh.
 
I'm going to reopen this thread. I have an intermittent fault, which may or may not be display related. I have implemented a thread to stop algorithm, on my electronic lead screw. I am getting an intermittent delay after synchronization, which manifests itself as a ruined thread. I can get 5 or 6 perfect cuts, and then kablooey, the screw is ruined. This is not good.

At a top level, the code pauses the stepper motor at a precise repeatable location, and waits for the spindle angle to be correct. To the best of my testing, it always syncs on the correct angle. But sometimes the stepper motor doesn't start on time.

All the important things are on interrupts, such as the rotary encoder, two linear encoders, the stepper pulser one shot timer and the RPM update. If any of them occur, they are too short to materially affect sync.

However, updating the display, occasionally can result in a significant delay. Based on the rotation rate of the lathe, (240 RPM) it appears the start of the motor was delayed by nearly 125 ms. This resulted in a new thread being cut in between the original thread, but being shifted by about 180 degrees. (Later, I'd like to do that on purpose, but I need to get simple threads to work first!). It's supposed to cut a 1.5mm thread, and it did, but the last cut was also 1.5mm pitch, but the start point was rotated by about 180 degrees. When not in this mode, it cuts perfect on dimension threads, not this ugly looking stuff you see here.
PXL_20250426_202754255.jpg
When this error occurs, there are no missed encoder counts, nor missing DRO counts. Everything looks normal. No parameters are out of place, there are no errors logged. Here is a picture of the screen. Only the RPM, and Z axis are updated periodically. All the other items are static.
PXL_20250426_200740809.jpg
Three questions

1) Would a single DMA update of a clip window help with this problem? (The clip window would have the RPM value, and the Z DRO position.) Or does DMA not help me at all? Using @KurtE 's ili9341_t3n library. I'll need a little hand holding, but can muddle through it. Why are the "t" shifted upwards by a pixel? The white text with the crazy t's is Arial_10.

2) Is there a way to lower the priority of the screen update (or SPI) compared to all the other interrupts? Kurt? I am using 60MHz for the write, on a PJRC ILI9341. 20MHz for any reads. A rotary encoder interrupt is processed and starts an stepper pulse in the ISR. A 2us one shot timer, turns off the stepper pulse.

3) Where can I find some resources on changing the interrupt priorities for the T4.1? Specifically for the encoders and timers. What I have found is scattered, and not intelligible to me. I am using EncoderTool and TimerTool and I don't even see a place where one could change their priority. Are all interrupts at the same level? 128?
 
Some of my issues were self induced. I introduced a bug in my code which prevented an exponential ramp to stop. Fixed that. Found that doing a getTextBounds was quite expensive, to do every change, so I eliminated that along with a fillRect of that spot. Of course, that means artifacts, but I will put up with it temporarily to find this sync issue. It's possible that my loop time is too long to capture the exact moment of sync (the first time around). Still plugging away at it. This is a tough nut to solve.

Still need some insight to determine if using SPI with DMA is blocking. I'm unclear on this. If I can update a small buffer and then write to the display via DMA without waiting, that would be awesome. My display refresh is 160ms in this mode. I only need to write the RPM and the position information, the rest of the display is currently static.
 
Back
Top