TFT Graphics - Scrolling ruler scale tied to Enc position

Hi Guys

I am building a stepper driven cross slide for my lathe utilising a KY-040 encoder and Nema17 stepper with a 2mm lead screw. So basically every revolution of the stepper (200 steps) moves the slide 2mm

Each encoder position moves the cross slide .01mm without microstepping.

I then have a 2.25" TFT LCD Screen Module ST7789 76 * 284 Display , SPI Screen and a few buttons to control the stepper

On the display I would like to have a scrolling ruler scale that moves in conjunction with the encoder wheel. The ruler scale will represent 1mm with 0.1mm gradient ticks, with major ticks every 10th tick, medium tick every 5th tick and minor tick every tick. The “10” ticks will cover 200 pixels, so each tick is 20pixels

cross slide disp

So, going by the pic, as I advance the encoder wheel forward, “ 4.0” & the tick should advance, along with all the other ticks ( ignore the other text or mistakes ) . The 2 red triangles remain fixed, probably a .bmp background

I am familiar with AdfruitGFX , TFT-espi , I am just trying to think of a starting point.

Stepper control is straight forward using acelstepper

What is the question to forum?

Your display is 284 pixels wide? A strange number, they are usually 240, 320 etc.

But anyway, at 20 pixels per tick mark, that's going to be 14.2 ticks, or 7.1 ticks (0.71mm) either side of the centre.

Let's say the current position is 4.49mm, similar to your drawing. That will be the centre of the display. The left edge of the display will represent 4.49 - 0.71 = 3.78mm. The right edge represents 4.49 + 0.71 = 5.20mm.

With me so far?

OOpps.. I actually forgot the question bit…:woozy_face:

The question is, does anyone have an idea on how to achieve a scrolling ruler tied to the encoder position.

The nearest tick marks to the left edge of the display will be 3.7mm and 3.8mm. But the 3.7mm will be off the display, so forget that.

The nearest tick marks to the right edge of the display will be 5.2mm and 5.3mm. But the 5.3mm will be off the display, so forget that also.

So you want to draw every tick mark from 3.8mm to 5.2mm. You can do this with a for-loop:

for (float t = 3.8; t <= 5.2; t += 0.1) { ...

For each tick mark, you need to translate the mm position into a pixel position from the left edge of the display to give to one of the drawing functions. Let's take 4.1mm as an example. That's 4.1 - 3.78 = 0.32mm or 3.2 tick marks or 3.2 x 20 pixels = 64 pixels from the left edge of the display.

  int x = (t - 3.78) * 200; // 200 pixels per mm

So... Why do you need the encoder?

Since the tics are an even number of pixels apart, probably easier to work in pixels.

Start by determining the first tic position on the left, and what measurement that corresponds to, then draw each tic mark across the screen, and as you go check to see if the tic mark needs to be the longer mark for an even or half mm.

When the ruler moves, you will first need to go through and redraw the current tic marks using the background color, then draw the new tic marks with the foreground color.

Leave the base line intact, only drawing the portion of the tic above the line, and possibly needing to redraw the thin red center line as the tic marks go past it.

Personally I would use integers instead of floats, having the integer represent the position in 10ths or 100ths of a mm, whichever is more appropriate with the rest of the code.

The encoder sets the position/speed for the stepper….

Thats lines I am thinking along. I want nice simple code that I can use on other projects. I have already perfected round dial gauges/meters with swept needle and rainbow gradients, .bmp backgrounds, graphics..etc

So I want to keep the pixel/scale/measurement in easy units…hence 200 pixels wide ruler scale ( leaves me 80 pixels for some other info.)

Basically 1 encoder pulse will move the slide by 0.01mm and on a 200 pixel scale that is ever 2 pixels…I am also thinking of micro-stepping so that each encoder pulse moves .005mm, which is 1 pixel over a 200 pixel scale., and makes coding a bit easier

I only need to redraw the tick marks and the numeric value. The numeric value does not need to be absolute,I have a DRO for that. Mostly it well be set at “Zero” for relative measurements

Ah, sorry, for some reason I thought the encoder sensed the position of the cross slide.

I had the same thoughts, but also conscious of the dangers of "premature optimisation".

yes, there is nothing like spending hrs on code and tinkering only to find you where wrong a he begining.

The hardest bit I see is the actual tracking/moving/location of the numerical value

BTW, the numerical value needs to fall as the position moves forward, as you are advancing forward to “Zero” . This is because on a lathe, you move forward/advance into the work with the center being “Zero”

Not too difficult. I tried a function that only needs the numeric value in millimeters for the center of the scale. First the value is converted to an integer in units of 1/200mm to match the pixels per mm. From this, the value for the first pixel in the scale is calculated. Then you only need to go across the entire scale, one pixel at a time, putting a full height tic mark and numeric label when the value is divisible evenly by 200, a mid-length tic mark when evenly divisible by 100 (but not 200), and a short tic mark when evenly divisible by 20 (but not 100 or 200).

@DucatiboyStu I tried a method similar to the one mentioned by @david_2018, the way I controlled the animation was by referencing the left edge with a for loop and drawing the tick marks after each encoder count. My main concern was whether every count could be read between updating the animation, my text wasn't aligned that great either. Still here is something to play with done on a ILI9341 320 x 240 and the Adafruit_GFX library.

#define canvasWidth  200
#define canvasHeight 48
#define resolution 1

Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);
GFXcanvas16 canvas(canvasWidth, canvasHeight);

int cnt =100;
bool increase=true;

void setup() {
    Serial.begin(9600);
    tft.begin();
    tft.setRotation(3);
    tft.setSPISpeed(40000000);
    tft.fillScreen(ILI9341_BLUE);
    canvas.setTextColor(ILI9341_RED);
    ticks();
    delay(5000);
}

void ticks(){

  int minor = cnt % 10;
  int major = cnt % 100;
  int cursor = cnt;
  int val = cursor / 100;

  canvas.fillRect(0, 0, canvasWidth, canvasHeight, ILI9341_WHITE);
  canvas.drawLine(0,40,200,40,ILI9341_BLACK);
  canvas.drawLine(100,0,100,25,0xf800);
  for(int idx=minor;idx<200;idx+=10){canvas.drawLine(idx,30,idx,40,ILI9341_BLACK);}
  canvas.drawLine(major,20,major,40,ILI9341_BLACK);
  canvas.setCursor(major-4,10);
  canvas.print(val);
  major += 100;
  canvas.drawLine(major,20,major,40,ILI9341_BLACK);
  canvas.setCursor(major-4,10);
  canvas.print(val - 1);
  
  tft.drawRGBBitmap(60, 40, canvas.getBuffer(), canvas.width(), canvas.height());

}

void loop() {

  if(cnt==2100){increase=false; delay(5000);}
  if(cnt==100 ){increase=true; delay(5000);}
  if(increase){cnt+=resolution;}
  else if(!increase){cnt-=resolution;}
  ticks();

}

MM…..I like the look of that

Was think of using fastVline…something like this, but using

startX = encPosn . But I am not sure how well it will work

// Assuming tft is your TFT_eSPI or Adafruit_GFX instance
// void drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color)

int16_t startX = 10;
int16_t startY = 50;
int16_t tickHeight = 10;
int16_t numTicks = 10;
int16_t spacing = 10;
uint16_t color = TFT_WHITE;

for (int i = 0; i < numTicks; i++) {
    // x = startX + (i * spacing)
    // y = startY
    // h = tickHeight
    tft.drawFastVLine(startX + (i * spacing), startY, tickHeight, color);
}

This guy...

I got this to work.

Scrolls the ticks nicely inline with the encoder. It does fill the full width of the screen, but some simple graphics can hide it`

ong posn = -999;

void loop() 

  {
     tft.drawRoundRect(0, 0, 284, 73, 1, GREY);
     tft.fillTriangle(100, 15, 90, 0, 110, 0, RED);
     tft.fillTriangle(100, 25, 90, 40, 110, 40, RED);
  long newposn;
  newposn = encCounter; // derived from  rotary encoder
   if (newposn != posn) {
    Serial.print("posn");
    Serial.print(newposn);
    Serial.println();
    tft.fillRect(50, 100, 40, 40, BLACK);
    tft.setTextColor(WHITE,BLACK);
    tft.setTextSize(4);
    tft.setCursor(50, 100);
    tft.print(encCounter);
    posn = newposn;


int16_t startX = (encCounter - 1000);
int16_t startY = 8;
int16_t minTickHeight = 10;
int16_t numTicks = 200;
int16_t majTickHeight = 30;
int16_t minTickspacing = 20;
int16_t majTickspacing = 200;
int16_t medTickHeight = 20;
int16_t medTickspacing = 100;

int16_t spacing = 20;


for (int i = 0; i < numTicks; i++) {
    // x = startX + (i * spacing)
    // y = startY
    // h = tickHeight
    tft.fillRect(203, 0, 300, 40, BLACK);
    tft.drawRoundRect(0, 2, 202, 40, 20, RED);
    tft.drawFastVLine(startX + (i * minTickspacing)-1, startY+10, minTickHeight, BLACK);
    tft.drawFastVLine(startX + (i * minTickspacing)+1, startY+10, minTickHeight, BLACK);
    tft.drawFastVLine(startX + (i * minTickspacing), startY+10, minTickHeight, WHITE);
    tft.drawFastVLine(startX + (i * medTickspacing)-1, startY, medTickHeight, BLACK);
    tft.drawFastVLine(startX + (i * medTickspacing)+1, startY, medTickHeight, BLACK);
    tft.drawFastVLine(startX + (i * medTickspacing), startY, medTickHeight, YELLOW);
    tft.drawFastVLine(startX + (i * majTickspacing)-1, startY, majTickHeight, BLACK);
    tft.drawFastVLine(startX + (i * majTickspacing)+1, startY, majTickHeight, BLACK);
    tft.drawFastVLine(startX + (i * majTickspacing), startY, majTickHeight, RED);

//tft.drawRoundRect(0, 40, 202, 40, 10, RED);
tft.fillRect(203, 50, 300, 40, BLACK);
}

} 
  }           

Incomplete code.

I go it to work. Has very little screen flicker as well.

NOTE:- this is setup on a 320x480 tft, but the display area is 74x284 pixels

#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ST7796S.h>
#include <RotaryEncoder.h>
#include <Fonts/FreeSansBoldOblique9pt7b.h>
#include <Fonts/FreeSansBoldOblique12pt7b.h>

#define display_CS 10
#define display_RST 8 
#define display_DC 9
#define display_MOSI 11
#define display_CLK 13

#define BLACK 0x0000
#define GRAY 0x8410  //lGRAY = light gray, GREY = dark grey  16bit colour
#define GREY 0x632C  
#define WHITE 0xFFFF
#define RED 0xF800
#define ORANGE 0xFCA0
#define YELLOW 0xFFE0
#define DENIM 0x4CBA
#define GREEN 0x07E0
#define DARKGREEN 0x2BE3
#define LIGHTGREEN 0x97F0
#define CYAN 0x07FF
#define AQUA 0x04FF
#define BLUE 0x001F  
#define MAGENTA 0xF81F
#define PINK 0xF69A
#define GREYGREEN 0x2984

Adafruit_ST7796S display(display_CS, display_DC, display_MOSI, display_CLK, display_RST);
RotaryEncoder Rotary(&RotaryChanged, 2, 3, 28);  // Pins 2 (DT), 3 (CLK), 28 (SW) - 28 is just a null number as the (SW) is not used

int encCounter = 0;
long posn = -999;
long newposn;
char buffer1[10];
char buffer2[10];

void RotaryChanged() {
  const unsigned int state = Rotary.GetState();
  if (state & DIR_CW)
    encCounter++;
  if (state & DIR_CCW)
    encCounter--;
}

void setup() {
  Serial.begin(9600);
  Serial.print(("Oih !"));
  display.init(320, 480, 0, 0, ST7796S_BGR);
  display.fillScreen(BLACK);
  display.invertDisplay(1);
  display.setRotation(1);
  display.setTextColor(WHITE);
  display.setCursor(80, 100);
  display.setTextSize(1);
  display.println("T.I.S.M"); // mum, we are serious.   google it
  delay(500);
  display.fillScreen(WHITE);
  display.fillRect(0, 0, 284, 73, GREY);
  display.fillRoundRect(6, 5, 196, 36, 9, WHITE);
}

void loop() {
  display.fillTriangle(100, 15, 90, 5, 110, 5, RED);
  display.fillTriangle(100, 30, 90, 40, 110, 40, RED);
  float measurment = (encCounter / 200.000);
  float imperial = (measurment / 25.4000);
  dtostrf(measurment, 6, 3, buffer1); 
  dtostrf(imperial, 6, 4, buffer2);
  Serial.println(buffer1);
  Serial.println(encCounter);

  
  newposn = encCounter;  // derived from  rotary encoder

  if (newposn != posn) {
     posn = newposn;
    int16_t startX = (encCounter - 500);
    int16_t startY = 8;
    int16_t numTicks = 200;
    int16_t minTickHeight = 10;
    int16_t medTickHeight = 20;
    int16_t majTickHeight = 30;
    int16_t minTickspacing = 20;
    int16_t medTickspacing = 100;
    int16_t majTickspacing = 200;
  
    for (int i = 0; i < numTicks; i++) {
      display.drawFastVLine(startX + (i * minTickspacing) - 1, startY + 10, minTickHeight, WHITE); // 
      display.drawFastVLine(startX + (i * minTickspacing) + 1, startY + 10, minTickHeight, WHITE);
      display.drawFastVLine(startX + (i * minTickspacing), startY + 10, minTickHeight, BLACK);
      display.drawFastVLine(startX + (i * medTickspacing) - 1, startY, medTickHeight, WHITE);
      display.drawFastVLine(startX + (i * medTickspacing) + 1, startY, medTickHeight, WHITE);
      display.drawFastVLine(startX + (i * medTickspacing), startY, medTickHeight, BLACK);
      display.drawFastVLine(startX + (i * majTickspacing) - 1, startY, majTickHeight, WHITE);
      display.drawFastVLine(startX + (i * majTickspacing) + 1, startY, majTickHeight, WHITE);
      display.drawFastVLine(startX + (i * majTickspacing), startY, majTickHeight, BLACK);
    }
    display.drawRoundRect(1, 1, 204, 44, 11, RED);
    display.drawRoundRect(3, 2, 201, 42, 10, RED);
    display.drawRoundRect(5, 4, 198, 38, 9, RED);
    display.fillRect(205, 0, 300, 40, GREY);
    display.setCursor(206, 25);
    display.setFont(&FreeSansBoldOblique9pt7b);
    display.setTextSize(1);
    display.setTextColor(BLACK);
    display.fillRect(65, 50, 80, 20, GREY);
    display.print("Position");
    display.setCursor(70, 67);
    display.setFont(&FreeSansBoldOblique12pt7b);
    display.print(buffer1);
    display.setCursor(140, 65);
    display.print("mm");
    display.setCursor(200, 65);
    display.fillRect(200, 46, 80, 20, GREY);
    display.fillRect(280, 0, 220, 80, WHITE);
    display.setFont(&FreeSansBoldOblique9pt7b);
    display.print(buffer2);
    display.setCursor(257, 65);
    display.print("in");
  }
}

Its enough code snippet to make it work.