Further 'Newbie' learning using TFT_eSPI on ESP32 DevKit -V1 3.2 and TFT 240x320 Display

This project started out watching some YouTube ‘Ux-Radar’ demonstrations that I thought I’d like to move onto. Sure enough (as usual I’ve found) most of the code I tried had ‘issues’ and didn’t work and the code was very poorly documented.

So, as I’m teaching myself, the basis of a Radar screen background is very well provided within the Bodmer examples, 320x240 and Sprite folders. I used Rotated_Sprite_2 as my teach guide. Implementing the addition of a Ux device and servo for rotation should hopefully be easy to complete my project.

The code below is for any ‘Newbie’ getting to grips with Sprites which hopefully I’ve fully documented that you can try out and play around, with the various functions I’ve put together.

BUT, I have an issue that’s fried my brain! At lines 150 to 160 in the code inside this function,

void drawDegsEmptyGraticule(String label, int32_t val)

once the numerical angle value is outside of the defined sprite, it screws itself up by a factor of decimal 10 once the return angle is less than 100 on the first return sweep?

Can any one please offer advice/solution to correct the issue, be critical if you find something blatantly obvious.

TIA

#include <Arduino.h>
// This example plots a rotated Sprite into another Sprite and then the resultant composited
// Sprite is pushed to the TFT screen. This example is for a 240 x 320 screen.

// The motivation for developing this capability is that animated dials can be drawn easily
// and the complex calculations involved are handled by the TFT_eSPI library. To create a dial
// with a moving needle a graphic of a meter needle is plotted at a specified angle into another
// Sprite that contains the dial face. When the needle Sprite is pushed to the dial Sprite the
// plotting ensures two pivot points for each Sprite coincide with pixel level accuracy.

// Two rotation pivot points must be set, one for the first Sprite and one for the second
// Sprite using setPivot(). These pivot points do not need to be within the Sprite boundaries.

// In this example a needle graphic is also be plotted direct to a defined TFT pivot point.

// The rotation angle is in degrees, an angle of 0 means no Sprite rotation.

// The pushRotated() function works with 1, 8 and 16-bit per pixel (bpp) Sprites.

// For 1 bpp Sprites the foreground and background colours are defined with the
// member function setBitmapColor(foregroundColor, backgroundColor).

// Created by Bodmer 6/1/19 as an example to the TFT_eSPI library:
// https://github.com/Bodmer/TFT_eSPI

#include <SPI.h>
#include <TFT_eSPI.h> // Hardware-specific library

#define Radar_WIDTH  317 
#define Radar_HEIGHT 237 
#define Radar_BeamWidth  17
#define Radar_BeamHeight 140 //128
#define OuterScreenRadius 140
#define InnerScreenRadius 132
#define PixelCentrePtx 150
#define PixelCentrePty 215


TFT_eSPI tft = TFT_eSPI();       // Invoke custom library
//Create a 'background' sprite BODMER version
//TFT_eSprite dial   = TFT_eSprite(&tft); // Sprite object for dial
//TFT_eSprite needle = TFT_eSprite(&tft); // Sprite object for needle

//Create a 'background' sprite DEGS version
TFT_eSprite Radarback = TFT_eSprite(&tft); // Sprite object for Radarback
TFT_eSprite RadarbackG = TFT_eSprite(&tft); // Sprite object for Radarback

TFT_eSprite needleBeam = TFT_eSprite(&tft);// Sprite object for needleBeam
TFT_eSprite RadarBeam = TFT_eSprite(&tft); // Sprite object for Wedge type needle
TFT_eSprite RadarBeamG = TFT_eSprite(&tft); // Sprite object for Wedge type needle


const int MAX_RANGE_CM = 50;

uint32_t startMillis;

// =======================================================================================

// =======================================================================================
// Create the Radar graticule, the graticule outer and place scale markers
//          ****** Developed from Bodmer Example Rotated_Sprite_2 ******
// =======================================================================================
/*
void createDegsDialScale(int16_t start_angle, int16_t end_angle, int16_t increment)
{
  // Create the dial Sprite
  Radarback.setColorDepth(8);       // Size is odd (i.e. 91) so there is a centre pixel at 45,45
  //Radarback.setSwapBytes(true); // We need to swap the colour bytes (endianess)
  Radarback.createSprite(91, 91);   // 8bpp requires 91 * 91 = 8281 bytes
  Radarback.setPivot(45,45);       // set pivot in middle of dial Sprite

  // Draw dial outline
  Radarback.fillSprite(TFT_TRANSPARENT);           // Fill with transparent colour
  Radarback.fillCircle(45,45,43,TFT_DARKGREY);  // Draw dial outer

  // Hijack the use of the needleBeam Sprite since that has not been used yet!
  needleBeam.createSprite(3, 3);     // 3 pixels wide, 3 high
  needleBeam.fillSprite(TFT_WHITE);  // Fill with white
  needleBeam.setPivot(1, 43);        //  Set pivot point x to the Sprite centre and y to marker radius
  
  for (int16_t angle = start_angle; angle <= end_angle; angle += increment) {
    needleBeam.pushRotated(&Radarback, angle); // Sprite is used to make scale markers
    yield(); // Avoid a watchdog time-out
  }

  needleBeam.deleteSprite(); // Delete the hijacked Sprite

}
*/

void createDegsGraticule(int16_t start_angle, int16_t end_angle, int16_t increment){
  // Create the dial Sprite
  RadarbackG.setColorDepth(8);       // Size is odd (i.e. 91) so there is a centre pixel at 45,45
  //Radarback.setSwapBytes(true); // We need to swap the colour bytes (endianess)
  //RadarbackG.createSprite(301, 301);   // 8bpp requires 91 * 91 = 8281 bytes
  //RadarbackG.setPivot(150,150);       // set pivot in middle of dial Sprite
  RadarbackG.createSprite(301, 301);   // 8bpp requires 91 * 91 = 8281 bytes
  RadarbackG.setPivot(150,150);       // set pivot in middle of dial Sprite

  // Draw dial outline
  RadarbackG.fillSprite(TFT_TRANSPARENT);           // Fill with transparent colour
  //RadarbackG.fillCircle(150,150,147,TFT_DARKGREY);  // Draw dial outer
  //RadarbackG.fillCircle(PixelCentrePtx,PixelCentrePty,PixelCentrePtx-3,TFT_DARKGREY);  // Draw dial outer
  RadarbackG.fillCircle(PixelCentrePtx,150,PixelCentrePtx-3,TFT_DARKGREY);  // Draw dial outer

  // Hijack the use of the needleBeam Sprite since that has not been used yet!
  RadarBeamG.createSprite(3, 12);     // 3 pixels wide, 3 high
  RadarBeamG.fillSprite(TFT_WHITE);  // Fill with white
  RadarBeamG.setPivot(1, 147);       //  Set pivot point x to the Sprite centre and y to marker radius
  
  for (int16_t angle = start_angle; angle <= end_angle; angle += increment) {
    RadarBeamG.pushRotated(&RadarbackG, angle); // Sprite is used to make scale markers
    yield(); // Avoid a watchdog time-out
  }

  RadarBeamG.deleteSprite(); // Delete the hijacked Sprite

}

// ===========================================================================//
// Add the empty (Bodmer Dial Face) or Graticule face with a label and value =//
// ===========================================================================//
/*
void drawDegsEmptyDial(String label, int32_t val)
{  
  
  // Draw black face
  Radarback.fillCircle(45, 45, 40, TFT_BLACK);
  Radarback.drawPixel(45, 45, TFT_WHITE);        // For demo only, mark pivot point with a white pixel

  Radarback.setTextDatum(TC_DATUM);              // Draw dial text
  Radarback.drawString(label, 45, 15, 2);
  Radarback.drawNumber(val, 45, 60, 2);
  //tft.drawString(label, 5, 15, 2);
  //tft.drawNumber(val, 45, 15, 2);   
  
}
*/

void drawDegsEmptyGraticule(String label, int32_t val)
{  
  
  // Draw black face
  //RadarbackG.fillCircle(PixelCentrePtx,PixelCentrePty, OuterScreenRadius, TFT_BLACK);
  //RadarbackG.drawPixel(PixelCentrePtx,PixelCentrePty, TFT_WHITE);        // For demo only, mark pivot point with a white pixel
  RadarbackG.fillCircle(PixelCentrePtx,150, OuterScreenRadius, TFT_BLACK);
  RadarbackG.drawPixel(PixelCentrePtx,150, TFT_WHITE);        // For demo only, mark pivot point with a white pixel

  //RadarbackG.setTextDatum(TC_DATUM);              // Draw dial text
  RadarbackG.drawString(label, 110, 25, 2);
  RadarbackG.drawNumber(val, 165, 25, 2);
  RadarbackG.drawString(label, 60, 45, 2);
  RadarbackG.drawNumber(val, 110, 45, 2);
  RadarbackG.drawString("Background Sprite_co-ordinates", 0,0);
  RadarbackG.drawString(label, 0, 10, 2);
  RadarbackG.drawNumber(val, 50, 10, 2); 
  ////RadarbackG.drawNumber(val/10, 50, 10, 2); //Dividing val/10 'partially' works?
  
  tft.drawString("tft_co-ordinates",2,2);
  tft.drawString(label, 0, 15, 2);
  tft.drawNumber(val, 50, 15, 2);
  ////tft.drawNumber(val/10, 50, 15, 2); //Dividing val/10 'partially' works?150
  
  //Draw 12 lines
  float sx, sy, x0, yy0, x1, yy1;
  for(int i = 0; i<210; i+= 30) {
    //sx = cos((i-90)*0.0174532925);
    //sy = sin((i-90)*0.0174532925);
    sx = cos((i+180)*0.0174532925);
    sy = sin((i+180)*0.0174532925);
    x0 = sx*(InnerScreenRadius+4)+PixelCentrePtx;
    //yy0 = sy*(InnerScreenRadius+4)+PixelCentrePty;
    yy0 = sy*(InnerScreenRadius+4)+150;
    x1 = sx*10+PixelCentrePtx;
    //yy1 = sy*10+PixelCentrePty;
    yy1 = sy*10+150;

    //virtual void TFT_eSPI::drawLine(int32_t xs, int32_t ys, int32_t xe, int32_t ye, uint32_t color)
    RadarbackG.drawLine(x0, yy0, x1, yy1, TFT_GREEN);
    RadarbackG.drawCircle(PixelCentrePtx,150,OuterScreenRadius,TFT_GREEN);
    RadarbackG.drawCircle(PixelCentrePtx,150,OuterScreenRadius/3,TFT_GREEN);
    RadarbackG.drawCircle(PixelCentrePtx,150,OuterScreenRadius*2/3,TFT_GREEN);
    Radarback.drawCircle(PixelCentrePtx,150,10,TFT_WHITE);
    //void TFT_eSPI::drawLine(int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint32_t color)
    RadarbackG.drawLine(10, 145, 290, 145, TFT_WHITE);
    RadarbackG.drawLine(10, 155, 290, 155, TFT_WHITE);
    RadarbackG.drawLine(10, 145, 10, 155, TFT_WHITE);
    RadarbackG.drawLine(290, 145, 290, 155, TFT_WHITE);
  }  
  // Draw 12 dots
  RadarbackG.fillCircle(PixelCentrePtx,OuterScreenRadius-122, 4, TFT_WHITE);
  for(int i = 0; i<=360; i+= 30) {
    sx = cos((i-90)*0.0174532925);
    sy = sin((i-90)*0.0174532925);
    x0 = sx*InnerScreenRadius+PixelCentrePtx;
    yy0 = sy*InnerScreenRadius+150;

    // Draw main quadrant dots. NOTE: Angles are CW rotation   
    if(i==30 || i==330) RadarbackG.fillCircle(x0, yy0, 2, TFT_WHITE);
    if(i==60 || i==300) RadarbackG.fillCircle(x0, yy0, 2, TFT_WHITE);
    if(i==90 || i==270) RadarbackG.fillCircle(x0, yy0, 2, TFT_WHITE);
    
  }
  // Draw 60 dots Outer Ring
  for(int i = 0; i<360; i+= 6) {
    sx = cos((i-90)*0.0174532925);
    sy = sin((i-90)*0.0174532925);
    x0 = sx*InnerScreenRadius+PixelCentrePtx;
    yy0 = sy*InnerScreenRadius+150;
    // Draw 'minute' or '6 degree' markers
    RadarbackG.fillCircle(x0, yy0, 1, TFT_WHITE);
  }
  // Draw 60 dots Middle Ring
  for(int i = 0; i<360; i+= 6) {
    //int OuterScreenRadius = 140;
    //int InnerScreenRadius = 137;
    sx = cos((i-90)*0.0174532925);
    sy = sin((i-90)*0.0174532925);
    x0 = sx*(InnerScreenRadius*2/3)+PixelCentrePtx;
    yy0 = sy*(InnerScreenRadius*2/3)+150;
    // Draw minute markers
    RadarbackG.fillCircle(x0, yy0, 1, TFT_WHITE);
  }
  // Draw 60 dots Inner Ring
  for(int i = 0; i<360; i+= 6) {
    //int OuterScreenRadius = 140;
    //int InnerScreenRadius = 137;
    sx = cos((i-90)*0.0174532925);
    sy = sin((i-90)*0.0174532925);
    x0 = sx*(InnerScreenRadius*2/6)+PixelCentrePtx;
    yy0 = sy*(InnerScreenRadius*2/6)+150;
    // Draw minute markers
    RadarbackG.fillCircle(x0, yy0, 1, TFT_WHITE);
  }  
}

// =============================================================================================
// Update the dial of your choice and plot to screen with needle of your choice at defined angle
// =============================================================================================

/*
void plotDegsDialNeedle(int16_t x, int16_t y, int16_t angle, String label, uint16_t val)
{
  // Draw the blank dial in the Sprite, add label and number
  drawDegsEmptyDial(label, val);

  // Push a rotated needle Sprite to the dial Sprite, with black as transparent colour
  needleBeam.pushRotated(&Radarback, angle, TFT_BLACK); // dial is the destination Sprite
  //RadarBeam.pushRotated(&Radarback, angle, TFT_BLACK); // dial is the destination Sprite

  // Push the resultant dial Sprite to the screen, with transparent colour
  Radarback.pushSprite(x, y, TFT_TRANSPARENT);
}
*/

/*
void plotDegsDialRadar(int16_t x, int16_t y, int16_t angle, String label, uint16_t val)
{
  // Draw the blank dial in the Sprite, add label and number
  drawDegsEmptyDial(label, val);

  // Push a rotated needle Sprite to the dial Sprite, with black as transparent colour
  //needleBeam.pushRotated(&Radarback, angle, TFT_BLACK); // dial is the destination Sprite
  RadarBeam.pushRotated(&Radarback, angle, TFT_BLACK); // dial is the destination Sprite

  // Push the resultant dial Sprite to the screen, with transparent colour
  Radarback.pushSprite(x, y, TFT_TRANSPARENT);
}
*/

void plotDegsDialRadarGraticule(int16_t x, int16_t y, int16_t angle, String label, uint16_t val)
{
  // Draw the blank dial in the Sprite, add label and number
  drawDegsEmptyGraticule(label, val);

  // Push a rotated needle Sprite to the dial Sprite, with black as transparent colour
  //needleBeam.pushRotated(&Radarback, angle, TFT_BLACK); // dial is the destination Sprite
  RadarBeamG.pushRotated(&RadarbackG, angle, TFT_BLACK); // dial is the destination Sprite

  // Push the resultant dial Sprite to the screen, with transparent colour
  RadarbackG.pushSprite(x, y, TFT_TRANSPARENT);
}

// =======================================================================================
// Create the needle Sprite and the image of the needle
// =======================================================================================

void createDegsNeedle(void)
{
  needleBeam.setColorDepth(8);
  needleBeam.createSprite(11, 49); // create the needle Sprite 11 pixels wide by 49 high

  needleBeam.fillSprite(TFT_BLACK);          // Fill with black

  // Define needle pivot point
  uint16_t piv_x = needleBeam.width() / 2;   // x pivot of Sprite (middle)
  uint16_t piv_y = needleBeam.height() - 10; // y pivot of Sprite (10 pixels from bottom)
  needleBeam.setPivot(piv_x, piv_y);         // Set pivot point in this Sprite

  // Draw the red needle with a yellow tip
  // Keep needle tip 1 pixel inside dial circle to avoid leaving stray pixels
  needleBeam.fillRect(piv_x - 1, 2, 3, piv_y + 8, TFT_GREEN);
  needleBeam.fillRect(piv_x - 1, 2, 3, 5, TFT_YELLOW);

  // Draw needle centre boss
  needleBeam.fillCircle(piv_x, piv_y, 5, TFT_MAROON);
  needleBeam.drawPixel( piv_x, piv_y, TFT_WHITE);     // Mark needle pivot point with a white pixel
}

////void createRadarGraticule(int16_t start_angle, int16_t end_angle, int16_t increment){
/*
void createRadarGraticule(){
  // Create the dial Sprite of defined size
  Radarback.setColorDepth(8); // VERY IMPORTANT
  //Create the SIZE of the Sprite  
  Radarback.createSprite(Radar_WIDTH, Radar_HEIGHT); //(317,237)
  Radarback.setPivot(PixelCentrePtx,PixelCentrePty); // set pivot in middle of dial Sprite (160, 225)
  //Radarback.setSwapBytes(true); // We need to swap the colour bytes (endianess)

  // Draw dial outline
  Radarback.fillSprite(TFT_TRANSPARENT);           // Fill with transparent colour
  Radarback.fillCircle(PixelCentrePtx+2,PixelCentrePty+2,OuterScreenRadius+2,TFT_BLACK);  // Draw dial outer
  
  //Draw 12 lines
  float sx, sy, x0, yy0, x1, yy1;
  for(int i = 0; i<210; i+= 30) {
    //sx = cos((i-90)*0.0174532925);
    //sy = sin((i-90)*0.0174532925);
    sx = cos((i+180)*0.0174532925);
    sy = sin((i+180)*0.0174532925);
    x0 = sx*(InnerScreenRadius+4)+PixelCentrePtx;
    yy0 = sy*(InnerScreenRadius+4)+PixelCentrePty;
    x1 = sx*10+PixelCentrePtx;
    yy1 = sy*10+PixelCentrePty;

    //virtual void TFT_eSPI::drawLine(int32_t xs, int32_t ys, int32_t xe, int32_t ye, uint32_t color)
    Radarback.drawLine(x0, yy0, x1, yy1, TFT_GREEN);
    Radarback.drawCircle(PixelCentrePtx,PixelCentrePty,OuterScreenRadius,TFT_GREEN);
    Radarback.drawCircle(PixelCentrePtx,PixelCentrePty,OuterScreenRadius/3,TFT_GREEN);
    Radarback.drawCircle(PixelCentrePtx,PixelCentrePty,OuterScreenRadius*2/3,TFT_GREEN);
    Radarback.drawCircle(PixelCentrePtx,PixelCentrePty,10,TFT_WHITE);
    //void TFT_eSPI::drawLine(int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint32_t color)
    Radarback.drawLine(10, 222, 310, 222, TFT_WHITE);
    Radarback.drawLine(10, 228, 310, 228, TFT_WHITE);
    Radarback.drawLine(10, 222, 10, 228, TFT_WHITE);
    Radarback.drawLine(310, 222, 310, 228, TFT_WHITE);
  }  
  // Draw 12 dots
  for(int i = 0; i<360; i+= 30) {
    sx = cos((i-90)*0.0174532925);
    sy = sin((i-90)*0.0174532925);
    x0 = sx*InnerScreenRadius+PixelCentrePtx;
    yy0 = sy*InnerScreenRadius+PixelCentrePty;

    // Draw main quadrant dots. NOTE: Angles are CW rotation
    if(i==0) Radarback.fillCircle(x0, yy0, 2, TFT_WHITE);
    if(i==30 || i==330) Radarback.fillCircle(x0, yy0, 2, TFT_WHITE);
    if(i==60 || i==300) Radarback.fillCircle(x0, yy0, 2, TFT_WHITE);
    if(i==90 || i==270) Radarback.fillCircle(x0, yy0, 2, TFT_WHITE);
  }
  // Draw 60 dots Outer Ring
  for(int i = 0; i<360; i+= 6) {
    sx = cos((i-90)*0.0174532925);
    sy = sin((i-90)*0.0174532925);
    x0 = sx*InnerScreenRadius+PixelCentrePtx;
    yy0 = sy*InnerScreenRadius+PixelCentrePty;
    // Draw 'minute' or '6 degree' markers
    Radarback.fillCircle(x0, yy0, 1, TFT_WHITE);
  }
  // Draw 60 dots Middle Ring
  for(int i = 0; i<360; i+= 6) {
    //int OuterScreenRadius = 140;
    //int InnerScreenRadius = 137;
    sx = cos((i-90)*0.0174532925);
    sy = sin((i-90)*0.0174532925);
    x0 = sx*(InnerScreenRadius*2/3)+PixelCentrePtx;
    yy0 = sy*(InnerScreenRadius*2/3)+PixelCentrePty;
    // Draw minute markers
    Radarback.fillCircle(x0, yy0, 1, TFT_WHITE);
  }
  // Draw 60 dots Inner Ring
  for(int i = 0; i<360; i+= 6) {
    //int OuterScreenRadius = 140;
    //int InnerScreenRadius = 137;
    sx = cos((i-90)*0.0174532925);
    sy = sin((i-90)*0.0174532925);
    x0 = sx*(InnerScreenRadius*2/6)+PixelCentrePtx;
    yy0 = sy*(InnerScreenRadius*2/6)+PixelCentrePty;
    // Draw minute markers
    Radarback.fillCircle(x0, yy0, 1, TFT_WHITE);
  }
}
*/

// =======================================================================================
// Update the Graticule and plot to screen with Radar beam at defined angle
// =======================================================================================
/*
void ScanRadarBeam(int16_t x, int16_t y, int16_t angle, String label, uint16_t val)
{ // Draw the blank dial in the Sprite, add label and number
  drawEmptyGraticule(label, val);

  // Push a rotated RadarBeam Sprite to the Radarback Sprite, with black as transparent colour
  RadarBeam.pushRotated(&Radarback, angle, TFT_BLACK); // dial is the destination Sprite

  // Push the resultant dial Sprite to the screen, with transparent colour
  Radarback.pushSprite(x, y, TFT_TRANSPARENT);

}
*/

void createRadarBeam()
{
  RadarBeam.setColorDepth(8);
  //void *TFT_eSprite::createSprite(int16_t width, int16_t height, uint8_t frames = (uint8_t)1U)
  RadarBeam.createSprite(21, 89); //(21, 89)
  // Create the choice of Sprite 21 pixels wide by 89 high in pixels
  // RadarBeam.createSprite(Radar_BeamWidth = 21, Radar_BeamHeight = 89); 
  RadarBeam.fillSprite(TFT_BLACK); // Fill with black

  //======================================================================================================================//
  //--- Define the Sprite pivot point (In my case a wedge derivative from Bodmers examples representing a radar beam) ---//                                                                 */
  //======================================================================================================================//

  // How to calculate these IMPORTANT values applied to Sprites, specially for a wedge variant?                                                       
  // The area (WxH) of the Sprite is specified in the defined variables RadarBeam.createSprite(21, 89);                                                  
  //* It ALWAYS starts at the top left corner co-ordinates (x,y)=(0,0) to (x,y)=(21,89).                                                    
  //* These are in pixels so in a 320x240 display you have to understand exactly what the orietentation of (x,y)=(0,0) is. 
  //* The next task is to then calculate where you want your sprite object to be positioned relative to a background.      
  //* So, for my example uint16_t piv_x (21 / 2) = 10 comes from aligning the (x,y)=(0,0) point on the 320x240 display.            
  //* NOTE: If like myself you create a background thats of the form:-                                                                            
  //* #define Radar_WIDTH  316                                                                                             
  //* #define Radar_HEIGHT 236                                                                                             
  //* void TFT_eSPI::setPivot(int16_t x, int16_t y)                                                                        
  //* Radarback.createSprite(Radar_WIDTH, Radar_HEIGHT);                                                                   
  //* Then uint16_t piv_x = (316/2) [background display/2]                                         
  //* Then uint16_t piv_y = (236/2)-(The START 'pixel' value where you want the END point of the Sprite to be)               
  //* Radarback.setPivot(10, 44-1);                                                                                         

  //* The next IMPORTANT thing to understand here is where the variables (ax, ay, bx, by) lie on the screen you            
  //* are using. In my case 320x240 pixels and tft.setRotation(1). Using #defines for naming variables helps to prevent    
  //* you losing your way around what you are trying to achieve especially when you are trying to produce a visual effect. 

  uint16_t piv_x = Radarback.width()/2;  // x pivot of Sprite (middle pixel) width = 21
  uint16_t piv_y = Radarback.height()/2; // y pivot of Sprite (44 pixels from bottom) height.
  Radarback.setPivot(piv_x, 45);
  //Radarback.setPivot(piv_x, piv_y-1);
   
  //void TFT_eSPI::drawWedgeLine(float ax, float ay, float bx, float by, float ar, float br, uint32_t fg_color, uint32_t bg_color = 0x00FFFFFF)
  RadarBeam.drawWedgeLine(10,12,10,45,8,1,TFT_SKYBLUE);
}

void createRadarBeamG()
{
  RadarBeamG.setColorDepth(8);
  //void *TFT_eSprite::createSprite(int16_t width, int16_t height, uint8_t frames = (uint8_t)1U)
  RadarBeamG.createSprite(21, 280);
  // Create the choice of Sprite 21 pixels wide by 280 high in pixels 
  RadarBeamG.fillSprite(TFT_BLACK); // Fill with black

  uint16_t piv_x = RadarbackG.width()/2;  // x pivot of Sprite (middle pixel) width = 21
  uint16_t piv_y = RadarbackG.height()/2; // y pivot of Sprite (140 pixels from bottom) height.
  RadarbackG.setPivot(piv_x, piv_y-1); // piv_y BECOMES OuterScreenRadius
   
  //void TFT_eSPI::drawWedgeLine(float ax, float ay, float bx, float by, float ar, float br, uint32_t fg_color, uint32_t bg_color = 0x00FFFFFF)
  RadarBeamG.drawWedgeLine(10,12,10,OuterScreenRadius-4,8,1,TFT_VIOLET);
}

void setup(){  
  tft.init();
  tft.setRotation(1);
  tft.fillScreen(TFT_NAVY); 

  /************************************************/
  /*              BODMER EXAMPLE CODE             */
  /************************************************/
  
  // == Original implementation of Rotated_Sprite_2 == //

  /************************************************/
  /*            MY RADAR EXAMPLE CODE             */
  /************************************************/
  //createDegsDialScale(-120, 120, 15);
  //drawDegsEmptyDial("Degs", 54321);
  
  createDegsGraticule(-120,120,30);
  ////drawDegsEmptyGraticule("Degs", 54321); //This is the black blank screen
  
  // ** Push a copy of the dial to the screen so we can see it **//
  //Radarback.pushSprite(210, 140); //Alternative learning version
  ////RadarbackG.pushSprite(0, 70);
  ///delay(1000);

  // Create the needle Sprite
  createDegsNeedle();                // draw the needle graphic
  needleBeam.pushSprite(245, 10);    // push a copy of the needle to the screen so we can see it
  delay(1000);
  // Create the Radar beam Sprite
  createRadarBeam();
  RadarBeam.pushSprite(265, 10);    // push a copy of the needle to the screen so we can see it
  // Create the Radar beam Sprite
  createRadarBeamG();
  RadarBeamG.pushSprite(295, 10);    // push a copy of the needle to the screen so we can see it
      
}

void loop(){ 
  
  // Push the needle sprite to the dial Sprite at different angles and then push the dial to the screen
  // Use angle increments in range 1 to 6 for smoother or faster movement.
  /*
  for (int16_t angle = -120; angle <= 120; angle += 2) {
    plotDegsDialNeedle(10,10, angle, "ANGLE", angle + 120);
    delay(20); //Decreasing or increasing this delay also speeds up plotting movement.
    yield(); // Avoid a watchdog time-out
  }

  delay(500);  // Pause

  // Update the dial Sprite with decreasing angle and plot to screen at 0,0, no delay
  for (int16_t angle = 120; angle >= -120; angle -= 4) {
    plotDegsDialRadar(170,10, angle, "ANGLE", angle + 120);
    delay(20);
    yield(); // Avoid a watchdog time-out
  }
  */

  /**************************************************/
  /*      Derived from BODMER EXAMPLE CODE          */
  /* Two things to note:                            */
  /* 1. Decreasing or increasing the size of the    */ 
  /*    sweep angle also adjusts the sweep rotation */
  /*    speed.                                      */
  /* 2. It's important to realise/understand you    */
  /*    create a new composite sprite written over  */
  /*    the previous composite sprite at EXACTLY    */
  /*    the SPECIFIED PIXEL POINTS in your code.    */
  /**************************************************/
  
  for (int16_t angle = -90; angle <= 90; angle += 1) { // angle = sweep angle
    plotDegsDialRadarGraticule(10,70, angle, "ANGLE", angle + 90);
    delay(50); //Decreasing or increasing this delay also speeds up plotting movement.
    //delay(1000);
    yield(); // Avoid a watchdog time-out
  }

  delay(500);  // Pause Pause type RadarBeamG angle sweep

  // Update the dial Sprite with decreasing angle and plot to screen at 0,0, no delay
  for (int16_t angle = 90; angle >= -90; angle -= 1) {
    plotDegsDialRadarGraticule(10,70, angle, "ANGLE", angle + 90);
    delay(50);
    //delay(1000);
    yield(); // Avoid a watchdog time-out
  }

  delay(500);  // Pause Pause type RadarBeamG angle sweep

  //*** TRY THIS OUT BELOW TO SHOW HOW THE SPRITES ARE COMBINED    ***//
  //*** AND DISPLAYED IN A DIFFERENT SCREEN ROTATION DEPENDING ON  ***//
  //*** EXACTLY the SPECIFIED PIXEL POINTS in your code.           ***//
  //*** IT TEACHES YOU HOW BODMER OVERLAYES SPRITES TO BUILD       ***//
  //*** A COMPOSITE DISPLAY WITHOUT FLICKER.                       ***//

  /*/
  // Update the dial Sprite with decreasing angle and plot to screen at 0,0, no delay
  for (int16_t angle = 110; angle >= -110; angle -= 4) {
    plotDegsDialRadarGraticule(170,10, angle, "ANGLE", angle + 110);
    delay(20);
    yield(); // Avoid a watchdog time-out
  }
  */

So, where am I up to in trying to resolve my:-

This ALSO happens now I’ve connected a Ux sensor and display the numerical distance value.

I still observe trailing numbers in the display. Starting with 0-9 is fine kept within that numerical range. Going to 10-99, then bck to 0-9 displays a remaining unit (0-9), going to 100 - 700 and then decreasing (leaves remaining untis and tens making it confusing).

D = distance in cm. I solved the Intellisense warnings by making the variable for ‘d’ in the code consistent as uint16_t.

i.e D: 7 → D: 35 → D: 154 then going back to D:7 the display is 754? Is the problem in TFT_eSPI?

/***************************************************************************************

** Function name: drawNumber

** Description: draw a long integer

***************************************************************************************/

cpp
int16_t TFT_eSPI::drawNumber(long long_num, int32_t poX, int32_t poY)
{
  isDigits = true; // Eliminate jiggle in monospaced fonts
  char str[12];
  ltoa(long_num, str, 10);
  return drawString(str, poX, poY, textfont);
}

int16_t TFT_eSPI::drawNumber(long long_num, int32_t poX, int32_t poY, uint8_t font)
{
  isDigits = true; // Eliminate jiggle in monospaced fonts
  char str[12];
  ltoa(long_num, str, 10);
  return drawString(str, poX, poY, font);
}

'''



I can’t figure out why the numbers remain being displayed when they clearly aren’t there? Is there some level of shifting I have to do? My code to display values is:

cpp
//int16_t TFT_eSPI::drawString(const char *string, int32_t x, int32_t y, uint8_t font)
    //int16_t TFT_eSPI::drawString(const String &string, int32_t x, int32_t y, uint8_t font)
    RadarbackG.drawString("Background Sprite", 0,0,2);
    //explicit String::String(unsigned int, unsigned char base = (unsigned char)10U)

    //int16_t TFT_eSPI::drawString(const String &string, int32_t x, int32_t y, uint8_t font)
    //RadarbackG.drawString("D: " + String(d) + "cm", 0, 20, 2);

    //My alternative code to see what's happening
    //int16_t TFT_eSPI::drawString(const char *string, int32_t x, int32_t y, uint8_t font)
    RadarbackG.drawString("D: ",0, 20, 2);
    //int16_t TFT_eSPI::drawNumber(long intNumber, int32_t x, int32_t y, uint8_t font)
    RadarbackG.drawNumber(d,30, 20, 2);
    //RadarbackG.drawString("cm",60,40,2);
```

TIA

Apologies for anyone reading this,

By back tracing through the code, I found the original author hasn’t been consistent with declaring variables between functions, (both in passing a variable to a function and returning from that function), where a declared variable of type ‘unsigned int d’ should in fact have been declared as type ‘uint16_t d’. Changing this has compiled correctly and the code is working in plotting the fillCircle function.

ORIGINAL code issue:

I now have a IntelliSense issue? This particular part of my Ux project code snippet works in that it plots the Radarback.fillCircle(x, y, 3, color); part of the code on the TFT screen.

cpp

if (d > 10 && d <= MAX_RANGE_CM) { //const int MAX_RANGE_CM = 50
      //unsigned short d;

      /* code copied from start of code block before function definitions

      struct Detection {
      uint8_t angle;
      uint16_t dist_cm;
      };
      //#define MAX_DETECTIONS 200
      Detection detections[MAX_DETECTIONS];
      int det_count = 0;
      */
    
      //From above unsigned int det_count = 0;
      if (det_count < MAX_DETECTIONS) {
         detections[det_count++] =  {(uint8_t)currentAngle, d} ;
      }
        float rad = currentAngle * PI / 180.0;
        int rpx = distToRadius(d);
        int x = CX + (int)(rpx * cos(rad));
        int y = CY - (int)(rpx * sin(rad));
        uint16_t color = (d < 25) ? TFT_RED : (d < 40 ? TFT_YELLOW : TFT_GREEN);
        Radarback.fillCircle(x, y, 3, color);
    }
    yield(); // Avoid a watchdog time-out
  }

'''

BUT IntelliSense reports the following:

invalid narrowing conversion from “unsigned int” to “unsigned short”

narrowing conversion of ‘d’ from ‘unsigned int’ to ‘uint16_t’ {aka ‘short unsigned int’} inside { } [-Wnarrowing]

If I change the code to correct the IntelliSense warning the code stops working and doesn’t plot the fillCircle part of the code?

cpp

 if (d > 10 && d <= MAX_RANGE_CM) { //const int MAX_RANGE_CM = 50
      unsigned short d;

      /* code copied from start of code block before function definitions

      struct Detection {
      uint8_t angle;
      uint16_t dist_cm;
      };
      //#define MAX_DETECTIONS 200
      Detection detections[MAX_DETECTIONS];
      int det_count = 0;
      */
    
      //From above unsigned int det_count = 0;
      if (det_count < MAX_DETECTIONS) {
         detections[det_count++] =  {(uint8_t)currentAngle, d} ;
      }
        float rad = currentAngle * PI / 180.0;
        int rpx = distToRadius(d);
        int x = CX + (int)(rpx * cos(rad));
        int y = CY - (int)(rpx * sin(rad));
        uint16_t color = (d < 25) ? TFT_RED : (d < 40 ? TFT_YELLOW : TFT_GREEN);
        Radarback.fillCircle(x, y, 3, color);
    }
    yield(); // Avoid a watchdog time-out
  }

'''

Can someone more skilled than myself teach me why this occurs?

TIA

The issue is variable shadowing, not TFT_eSPI. You redeclare d inside the if block.
That creates a new, uninitialized variable. Therefore, your calculations use an invalid distance.
Keep only the original d declaration. Prefer uint16_t d for consistency. This also removes the narrowing conversion warning.

Thank you for this response, I solved it thinking about your explanation and removed declaring variable d again in the function by simply passing the function the correct type of declaration. It’s been good experience learning this way how re-declaring variables inside functions sometimes upsets the code.

The particular function looks like this now:-

cpp
void plotDegsUxRadar(uint16_t Distance, int32_t angle, int count) // Taken from drawDetections()
{   
  RadarbackG.setTextColor(TFT_YELLOW, TFT_DARKCYAN);
  RadarbackG.drawString("D (cm)= " + String(Distance), 125, 20, 2);
  int NewAngle = angle+90;
  
  if (Distance > 10 && Distance <= MAX_RANGE_CM) {
    if (det_count < MAX_DETECTIONS) {
      currentAngle = NewAngle;
      detections[det_count++] =  {currentAngle, Distance} ; //angle and distance values
    }
    currentAngle = NewAngle;
    float rad = currentAngle * PI/ 180.0; //convert currentAngle to radians 
    uint16_t rpx = distToRadius(Distance);
    uint16_t x = Radar_WIDTH/2-4 + (int)(rpx * -cos(rad)); //(317/2)-4
    uint16_t y = Radar_HEIGHT/2+34 - (int)(rpx * sin(rad));//(237/2)+34
    uint16_t color = (Distance < 25) ? TFT_RED : (Distance < 40 ? TFT_YELLOW : TFT_GREEN);
        
    createObjectDetect(x, y, color); //Send the calculated x, y pixel co-ordinates    
  }
}
```

For other ‘Newbies’ like myself I’m close to publishing a ‘different’ perspective of a Ux Radar project using sprites in TFT_eSPI that looks like a scanning radar beam in-conjunction with a servo driven Ux sensor.

Many thanks for help so far.

This has been such an interesting project over the last few weeks. I’ve learned so much through making sprites, writing functions, declaring variables and their impact. I can think of quite a few improvements going forward like using millis() function to improve loop speed for the servo position and Ux detection etc. I hope it helps other newbies picking something like this up.

As always I would welcome any comments/suggestions from more experienced programmers to teach/improve my code.

Onto the next project …using a GY671 compass chip to drive a compass sprite I have on a LilyGo T-Display S3.

cpp
#include <Arduino.h>
#include <SPI.h>
#include <TFT_eSPI.h> // Hardware-specific library
#include <ESP32Servo.h>

//**************************************************************/
// USED BOARD PINS FOR THE 240x320 TFT DISPLAY
// platfomio.ini board = esp32doit-devkit-v1
// SEE - Setup42_ILI9341_ESP32.h (TFT_eSPI/User_Setups)
//
// #define TFT_MISO 19  
// #define TFT_MOSI 23
// #define TFT_SCLK 18
// #define TFT_CS   15  // Chip select control pin
// #define TFT_DC    2  // Data Command control pin
// #define TFT_RST   4  // Reset pin (could connect to RST pin)
// #define LED 5
//**************************************************************/

#define Radar_WIDTH  317 
#define Radar_HEIGHT 237 
#define Radar_BeamWidth  17
#define Radar_BeamHeight 140 
#define OuterScreenRadius 140
#define InnerScreenRadius 132
#define PixelCentrePtx 150
#define PixelCentrePty 215

//define sound speed in cm/uS
#define SOUND_SPEED 0.034

const int TRIG_PIN = 13; // Was 7 but 7 is prohibited on ESP32 DevKit 1 
const int ECHO_PIN = 16; //
const int SERVO_PIN = 14; // Changed from 17, 14 is HSPI CLK

long duration;
float distanceCm;

Servo myServo;  // create servo object to control a servo
// 16 servo objects can be created on the ESP32

TFT_eSPI tft = TFT_eSPI();       // Invoke custom library
//Create a 'background' sprite BODMER version
//TFT_eSprite dial   = TFT_eSprite(&tft); // Sprite object for dial
//TFT_eSprite needle = TFT_eSprite(&tft); // Sprite object for needle

//Create a 'background' sprite DEGS version
TFT_eSprite Radarback = TFT_eSprite(&tft); // Sprite object for Radarback
TFT_eSprite RadarbackG = TFT_eSprite(&tft); // Sprite object for Radarback

TFT_eSprite needleBeam = TFT_eSprite(&tft);// Sprite object for needleBeam
TFT_eSprite RadarBeam = TFT_eSprite(&tft); // Sprite object for Wedge type needle
TFT_eSprite RadarBeamG = TFT_eSprite(&tft); // Sprite object for Wedge type needle
TFT_eSprite ObjectDetect = TFT_eSprite(&tft); // Sprite object for circle detection
TFT_eSprite PreviousObjectDetect = TFT_eSprite(&tft); //Still under development!

int sweepDir = 1;
int pos = 0;    // variable to store the servo position

const int Radar_WIDTH_02 = 320; 
const int Radar_HEIGHT_02 = 240;
const int CX = Radar_WIDTH_02/2;
const int CY = Radar_HEIGHT_02-10;
int prevX = CX;
int prevY = CY;
const int MAX_RADIUS = 150;
const int MAX_RANGE_CM = 50; //2-400
const int ANGLE_MIN = 0;
const int ANGLE_MAX = 180;
const int ANGLE_STEP = 2;
const int MAX_DETECTIONS = 200;

unsigned long lastSweepTime = 0;
unsigned long sweepInterval = 200;
unsigned long lastMeasureTime = 0;
unsigned long measureInterval = 50;

unsigned short currentAngle = ANGLE_MIN;

uint32_t startMillis;

struct Detection {
  unsigned short angle;
  uint16_t dist_cm;
};

//#define MAX_DETECTIONS 200
Detection detections[MAX_DETECTIONS];
int det_count = 0;

uint16_t distToRadius(uint16_t dcm) {
  if (dcm >= MAX_RANGE_CM) return MAX_RADIUS;
  //(Measured value*150)/50 
  return ((dcm * MAX_RADIUS) / MAX_RANGE_CM);
}

unsigned int readUltrasonicCM() {
  // Clears the trigPin
  digitalWrite(TRIG_PIN, LOW);
  delayMicroseconds(2);
  // Sets the trigPin on HIGH state for 10 micro seconds
  digitalWrite(TRIG_PIN, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIG_PIN, LOW);  
  unsigned long duration = pulseIn(ECHO_PIN, HIGH, 30000UL);
  if (duration == 0) return 0;
  return (unsigned int)(duration / 58.0);
}

/****************************************************/
/*  Create a sprite for the Ux detection distance   */
/****************************************************/
void plotObjectDetect(uint16_t x, uint16_t y, uint16_t color) 
{ 
  // Draw the sprite on the background with the radar beam
  // at the calculated pixel co-ordinates.
  ObjectDetect.fillCircle(1, 1, 3, color);
  ObjectDetect.pushSprite(x,y,TFT_TRANSPARENT);
  // A delay is inserted here rather than in the loop() function
  // so the sprite can be observed without being quickly
  // overwritten if a delay is used in the loop() function.
  delay(100);     
}

void createPreviousObjectDetect()// Still under development!
{

  
}

void plotDegsUxRadar(uint16_t Distance, int32_t angle)
{   
  RadarbackG.setTextColor(TFT_YELLOW, TFT_DARKCYAN);
  RadarbackG.drawString("D (cm)= " + String(Distance), 125, 20, 2);
  int NewAngle = angle+90;
  
  if (Distance > 10 && Distance <= MAX_RANGE_CM) {
    if (det_count < MAX_DETECTIONS) {
      uint16_t currentAngle = NewAngle;
      detections[det_count++] =  {currentAngle, Distance} ; //angle and distance values
    }
    currentAngle = NewAngle;
    float rad = currentAngle * PI/ 180.0; //convert currentAngle to radians 
    uint16_t rpx = distToRadius(Distance);
    //uint16_t x = Radar_WIDTH/2-4 + (int)(rpx * -cos(rad)); //(317/2)-4
    //uint16_t y = Radar_HEIGHT/2+34 - (int)(rpx * sin(rad));//(237/2)+34
    uint16_t x = Radar_WIDTH/2 + (int)(rpx * -cos(rad)); //(317/2)-4
    uint16_t y = Radar_HEIGHT/2+100 - (int)(rpx * sin(rad));//(237/2)+34
    uint16_t color = (Distance < 25) ? TFT_RED : (Distance < 40 ? TFT_YELLOW : TFT_GREEN);
    //Simple option WITHOUT a sprite! But need to use Radar_HEIGHT/2+34
    //RadarbackG.fillCircle(x, y, 3, color); //Simple option WITHOUT a sprite!
    plotObjectDetect(x, y, color);    
  }
}

void createDegsGraticule(int16_t start_angle, int16_t end_angle, int16_t increment){
  //THIS IS THE GREY BACKGROUND SPRITE from Bodmer example.
  // Create the dial Sprite
  RadarbackG.setColorDepth(8); // Size is odd (i.e. 91) so there is a centre pixel at 45,45
  RadarbackG.createSprite(301, 301); // 8bpp requires 91 * 91 = 8281 bytes
  RadarbackG.setPivot(150,150);      // set pivot in middle of dial Sprite

  // Draw dial outline
  RadarbackG.fillSprite(TFT_TRANSPARENT); // Fill with transparent colour
  RadarbackG.fillCircle(PixelCentrePtx,150,PixelCentrePtx-3,TFT_DARKGREY);  // Draw dial outer
  // Hijack the use of the needleBeam Sprite since that has not been used yet!
  RadarBeamG.createSprite(3, 12);   // 3 pixels wide, 3 high
  RadarBeamG.fillSprite(TFT_WHITE); // Fill with white
  // Set pivot point x to the Sprite centre and y to marker radius
  RadarBeamG.setPivot(1, 147);     
  
  for (int16_t angle = start_angle; angle <= end_angle; angle += increment) {
    RadarBeamG.pushRotated(&RadarbackG, angle); // Sprite is used to make scale markers
    yield(); // Avoid a watchdog time-out
  }
  RadarBeamG.deleteSprite(); // Delete the hijacked Sprite
}

// ===========================================================================//
// Add the empty (Bodmer Dial Face) or Graticule face with a label and value =//
// ===========================================================================//

 //ORIGINAL MY VERSION - From Bodmer
void drawDegsEmptyGraticuleV01(String label, uint16_t val)
{  
  // Draw black face
  RadarbackG.fillCircle(PixelCentrePtx,150, OuterScreenRadius, TFT_BLACK);
  RadarbackG.drawPixel(PixelCentrePtx,150, TFT_WHITE); // For demo only, mark pivot point with a white pixel

  // Draw dial text
  //int16_t TFT_eSPI::drawString(const String &string, int32_t x, int32_t y, uint8_t font)
  RadarbackG.setTextColor(TFT_YELLOW, TFT_DARKCYAN);
  RadarbackG.drawString(label, 125, 35, 2);
  //int16_t TFT_eSPI::drawNumber(long intNumber, int32_t x, int32_t y, uint8_t font)
  RadarbackG.drawNumber(val, 173, 35, 2); 
  
  //Draw 12 lines
  float sx, sy, x0, yy0, x1, yy1;
  for(int i = 0; i<210; i+= 30) {
    sx = cos((i+180)*0.0174532925);
    sy = sin((i+180)*0.0174532925);
    x0 = sx*(InnerScreenRadius+4)+PixelCentrePtx;
    yy0 = sy*(InnerScreenRadius+4)+150;
    x1 = sx*10+PixelCentrePtx;
    yy1 = sy*10+150;
    //virtual void TFT_eSPI::drawLine(int32_t xs, int32_t ys, int32_t xe, int32_t ye, uint32_t color)
    RadarbackG.drawLine(x0, yy0, x1, yy1, TFT_GREEN);
    RadarbackG.drawCircle(PixelCentrePtx,150,OuterScreenRadius,TFT_GREEN);
    RadarbackG.drawCircle(PixelCentrePtx,150,OuterScreenRadius/3,TFT_GREEN);
    RadarbackG.drawCircle(PixelCentrePtx,150,OuterScreenRadius*2/3,TFT_GREEN);
    Radarback.drawCircle(PixelCentrePtx,150,10,TFT_WHITE);
    //void TFT_eSPI::drawLine(int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint32_t color)
    RadarbackG.drawLine(10, 145, 290, 145, TFT_WHITE);
    RadarbackG.drawLine(10, 155, 290, 155, TFT_WHITE);
    RadarbackG.drawLine(10, 145, 10, 155, TFT_WHITE);
    RadarbackG.drawLine(290, 145, 290, 155, TFT_WHITE);
  }  
  // Draw 12 dots
  RadarbackG.fillCircle(PixelCentrePtx,OuterScreenRadius-122, 4, TFT_WHITE);
  for(int i = 0; i<=360; i+= 30) {
    sx = cos((i-90)*0.0174532925);
    sy = sin((i-90)*0.0174532925);
    x0 = sx*InnerScreenRadius+PixelCentrePtx;
    yy0 = sy*InnerScreenRadius+150;
    // Draw main quadrant dots. NOTE: Angles are CW rotation   
    if(i==30 || i==330) RadarbackG.fillCircle(x0, yy0, 2, TFT_WHITE);
    if(i==60 || i==300) RadarbackG.fillCircle(x0, yy0, 2, TFT_WHITE);
    if(i==90 || i==270) RadarbackG.fillCircle(x0, yy0, 2, TFT_WHITE);    
  }
  // Draw 60 dots Outer Ring
  for(int i = 0; i<360; i+= 6) {
    sx = cos((i-90)*0.0174532925);
    sy = sin((i-90)*0.0174532925);
    x0 = sx*InnerScreenRadius+PixelCentrePtx;
    yy0 = sy*InnerScreenRadius+150;
    // Draw 'minute' or '6 degree' markers
    RadarbackG.fillCircle(x0, yy0, 1, TFT_WHITE);
  }
  // Draw 60 dots Middle Ring
  for(int i = 0; i<360; i+= 6) {
    //int OuterScreenRadius = 140;
    //int InnerScreenRadius = 137;
    sx = cos((i-90)*0.0174532925);
    sy = sin((i-90)*0.0174532925);
    x0 = sx*(InnerScreenRadius*2/3)+PixelCentrePtx;
    yy0 = sy*(InnerScreenRadius*2/3)+150;
    // Draw minute markers
    RadarbackG.fillCircle(x0, yy0, 1, TFT_WHITE);
  }
  // Draw 60 dots Inner Ring
  for(int i = 0; i<360; i+= 6) {
    //int OuterScreenRadius = 140;
    //int InnerScreenRadius = 137;
    sx = cos((i-90)*0.0174532925);
    sy = sin((i-90)*0.0174532925);
    x0 = sx*(InnerScreenRadius*2/6)+PixelCentrePtx;
    yy0 = sy*(InnerScreenRadius*2/6)+150;
    // Draw minute markers
    RadarbackG.fillCircle(x0, yy0, 1, TFT_WHITE);
  }   
}

void plotDegsDialRadarGraticule(int16_t x, int16_t y, int16_t angle, String label, uint16_t val)
{
  // Draw the blank dial in the Sprite, add label and number
  drawDegsEmptyGraticuleV01(label, val); //Puts the radar background on display
  uint16_t d = readUltrasonicCM(); // Go and read the Ux echo 
  uint16_t Distance = d; // Get the distance back from the Ux echo
  
  // Push a rotated needle Sprite to the dial Sprite, with black as transparent colour
  //needleBeam.pushRotated(&Radarback, angle, TFT_BLACK); // dial is the destination Sprite
  
  ////plotDegsUxRadar(Distance,angle, count);
  plotDegsUxRadar(Distance,angle);
  RadarBeamG.pushRotated(&RadarbackG, angle, TFT_BLACK); // dial is the destination Sprite 
  // Push the resultant dial Sprite to the screen, with transparent colour
  RadarbackG.pushSprite(x, y, TFT_TRANSPARENT);
}

// =======================================================================================
// Create the needle Sprite and the image of the needle - BODMER EXAMPLE CODE
// =======================================================================================

void createDegsNeedle(void) //This is the BODMER one
{ // needleBeam is the name of the sprite
  needleBeam.setColorDepth(8);
  needleBeam.createSprite(11, 49);  // create the needle Sprite 11 pixels wide by 49 high
  needleBeam.fillSprite(TFT_BLACK); // Fill with black
  // Define needle pivot point
  uint16_t piv_x = needleBeam.width() / 2;   // x pivot of Sprite (middle)
  uint16_t piv_y = needleBeam.height() - 10; // y pivot of Sprite (10 pixels from bottom)
  needleBeam.setPivot(piv_x, piv_y);         // Set pivot point in this Sprite
  // Draw the red needle with a yellow tip
  // Keep needle tip 1 pixel inside dial circle to avoid leaving stray pixels
  needleBeam.fillRect(piv_x - 1, 2, 3, piv_y + 8, TFT_GREEN);
  needleBeam.fillRect(piv_x - 1, 2, 3, 5, TFT_YELLOW);
  // Draw needle centre boss
  needleBeam.fillCircle(piv_x, piv_y, 5, TFT_MAROON);
  needleBeam.drawPixel( piv_x, piv_y, TFT_WHITE);     // Mark needle pivot point with a white pixel
}

// =======================================================================================
// Create the WedgeLine Sprite and the image of the RadarBeam - MY EXAMPLE CODE
// =======================================================================================

void createRadarBeam()
{ // RadarBeam is the name of the sprite
  RadarBeam.setColorDepth(8);
  //void *TFT_eSprite::createSprite(int16_t width, int16_t height, uint8_t frames = (uint8_t)1U)
  RadarBeam.createSprite(21, 89); //(21, 89)
  // Create the choice of Sprite 21 pixels wide by 89 high in pixels
  // RadarBeam.createSprite(Radar_BeamWidth = 21, Radar_BeamHeight = 89); 
  RadarBeam.fillSprite(TFT_BLACK); // Fill with black

  //======================================================================================================================//
  //--- Define the Sprite pivot point (In my case a wedge derivative from Bodmers examples representing a radar beam) ---//                                                                 */
  //======================================================================================================================//

  // How to calculate these IMPORTANT values applied to Sprites, specially for a wedge variant?                                                       
  // The area (WxH) of the Sprite is specified in the defined variables RadarBeam.createSprite(21, 89);                                                  
  //* It ALWAYS starts at the top left corner co-ordinates (x,y)=(0,0) to (x,y)=(21,89).                                                    
  //* These are in pixels so in a 320x240 display you have to understand exactly what the orietentation of (x,y)=(0,0) is. 
  //* The next task is to calculate where you want your sprite object to be positioned relative to a background.      
  //* So, for my example uint16_t piv_x (21 / 2) = 10 comes from aligning the (x,y)=(0,0) point on the 320x240 display.            
  //* NOTE: If like myself you create a background thats of the form:-                                                                            
  //* #define Radar_WIDTH  316                                                                                             
  //* #define Radar_HEIGHT 236                                                                                             
  //* void TFT_eSPI::setPivot(int16_t x, int16_t y)                                                                        
  //* Radarback.createSprite(Radar_WIDTH, Radar_HEIGHT);                                                                   
  //* Then uint16_t piv_x = (316/2) [background display/2]                                         
  //* Then uint16_t piv_y = (236/2)-(The START 'pixel' value where you want the END point of the Sprite to be)               
  //* Radarback.setPivot(10, 44-1);                                                                                         

  //* The next IMPORTANT thing to understand here is where the variables (ax, ay, bx, by) lie on the screen you            
  //* are using. In my case 320x240 pixels and tft.setRotation(1). Using #defines for naming variables helps to prevent    
  //* you losing your way around what you are trying to achieve especially when you are trying to produce a visual effect. 

  uint16_t piv_x = Radarback.width()/2;  // x pivot of Sprite (middle pixel) width = 21
  uint16_t piv_y = Radarback.height()/2; // y pivot of Sprite (44 pixels from bottom) height.
  Radarback.setPivot(piv_x, 45); // Set the pixel co-ordinates to match display
   
  //void TFT_eSPI::drawWedgeLine(float ax, float ay, float bx, float by, float ar, float br, uint32_t fg_color, uint32_t bg_color = 0x00FFFFFF)
  RadarBeam.drawWedgeLine(10,12,10,45,8,1,TFT_SKYBLUE);
}

// =======================================================================================
// MY EXAMPLE CODE - As used in the final code
// =======================================================================================

void createRadarBeamG() //This is the one I USE in the graphic
{
  RadarBeamG.setColorDepth(8);
  //void *TFT_eSprite::createSprite(int16_t width, int16_t height, uint8_t frames = (uint8_t)1U)
  RadarBeamG.createSprite(21, 280);
  // Create the choice of Sprite 21 pixels wide by 280 high in pixels 
  RadarBeamG.fillSprite(TFT_BLACK); // Fill with black
  uint16_t piv_x = RadarbackG.width()/2;  // x pivot of Sprite (middle pixel) width = 21
  uint16_t piv_y = RadarbackG.height()/2; // y pivot of Sprite (140 pixels from bottom) height.
  RadarbackG.setPivot(piv_x, piv_y-1); // piv_y BECOMES OuterScreenRadius   
  //void TFT_eSPI::drawWedgeLine(float ax, float ay, float bx, float by, float ar, float br, uint32_t fg_color, uint32_t bg_color = 0x00FFFFFF)
  RadarBeamG.drawWedgeLine(10,12,10,OuterScreenRadius-4,8,1,TFT_VIOLET);
}

void createObjectDetect() //Filled circle sprite representing Ux detection
{ 
  ObjectDetect.setColorDepth(8);
  ObjectDetect.createSprite(8,8);
  ObjectDetect.fillSprite(TFT_BLACK);
  // Draw the sprite on the background with the radar beam
  // at the calculated pixel co-ordinates.
  ObjectDetect.fillCircle(1, 1, 3, TFT_RED);
}

void setup(){
  //Serial.begin(115200); //For DEBUG purposes!
  uint16_t x;
  uint16_t y;
  uint16_t r;
  uint16_t color;
  color = TFT_CYAN;
  tft.init();
  tft.setRotation(1);
  tft.fillScreen(TFT_NAVY);

  pinMode(TRIG_PIN, OUTPUT);
  pinMode(ECHO_PIN, INPUT);

  myServo.setPeriodHertz(50);
  // Attaches the servo on pin 14 (MY code) to the servo object
  myServo.attach(SERVO_PIN, 1000, 2000);
  /***********************************************************/
  // I am using a Hi-Tec HS-422 Servo. The data sheet         /
  // suggests a min/max of:                                   /
  // CLOCK WISE/PULSE TRAVELING 1500 TO 1900usec              / 
	// but I found using default:                               /
  // worked better with min/max of 1000us and 2000us          /
	// different servos may require different min/max settings  /
	// for an accurate 0 to 180 sweep                           /
  /***********************************************************/
  myServo.write(currentAngle);

  createDegsGraticule(-120,120,30);
  // Create the needle Sprite
  createDegsNeedle();               // Bodmer version, draw the needle graphic
  needleBeam.pushSprite(245, 10);   // push a copy of the needle to the screen so we can see it
  // Create the Radar beam Sprite
  createRadarBeam();                // My First attempt at a Wedge type needle/beam
  RadarBeam.pushSprite(265, 10);    // push a copy of the needle to the screen so we can see it
  // Create the Radar beam Sprite
  createRadarBeamG();               // The actual one I use in the code
  RadarBeamG.pushSprite(295, 10);   // push a copy of the RadarBeam to the screen so we can see it
  // A new sprite to represent object detection
  createObjectDetect();
  ObjectDetect.pushSprite(230,10);            
  // A new sprite to represent previous object detection - STILL IN DEVELOPMENT
  //createPreviousObjectDetect();      
}

void loop(){ 
  unsigned long now = millis();

  /**************************************************/
  /*      Derived from BODMER EXAMPLE CODE          */
  /* Two things to note:                            */
  /* 1. Decreasing or increasing the size of the    */ 
  /*    sweep angle also adjusts the sweep rotation */
  /*    speed.                                      */
  /* 2. It's important to realise/understand you    */
  /*    create a new composite sprite written over  */
  /*    the previous composite sprite at EXACTLY    */
  /*    the SPECIFIED PIXEL POINTS in your code.    */
  /**************************************************/
  
  if (now - lastSweepTime >= measureInterval) {
    lastSweepTime = now;

    for (int16_t angle = -90; angle <= 90; angle += 1) { // angle = sweep angle
      int i = angle + 90;
      plotDegsDialRadarGraticule(10,70, angle, "Angle> :", angle + 90);
      myServo.write(i);		
      yield(); // Avoid a watchdog time-out  
    }
    delay(100);  // Pause Pause type RadarBeamG angle sweep

    for (int16_t angle = 90; angle >= -90; angle -= 1) {
      int i = angle+90;
      plotDegsDialRadarGraticule(10,70, angle, "Angle< :", angle + 90);
      myServo.write(i);      
      yield(); // Avoid a watchdog time-out  
    }
    delay(100);  //Pause type RadarBeamG angle sweep
  }  
} 
```