by Floris Wouterlood – Leiden, the Netherlands – March 24, 2017
Summary
A screen with things that move attracts attention. Here we discuss a radar scope with a sweeping beam simulated on a 2.8 inch color TFT display shield plugged onto an Arduino Uno. The Arduino sketch contains two elements: a static part that deals with the construction of the scope and the CRT, and a dynamic part dealing the creation of a beam that continuously rotates 360 degrees sweeping across the simulated CRT. The TFT display is ILI9341 compatible.
Introduction
Several attractive and inexpensive shields are available for the Arduino microcontroller platform. One of these is the color TFT display shield. Shields of this type are available with various screen diagonals, for instance the small 2.4 inch 320×240 pixel screen and the big 3.95 inch 320×480 pixel display. These shields simply attach to the Arduino by plugging their male pin headers into the female pin headers on the Arduino. Usually a TFT display is purchased with a particular application in mind. Apart from such purposes they are perfect toys to create colorful, moving things with, just to please the eye or to materialize one’s own creative ideas.
The goal here was to mimic an air controller radar scope, a representation of the old CRTs with an endlessly sweeping beam. In the real world this device is known as ‘plan position indicator’ (https://en.wikipedia.org/wiki/Radar_display). I wanted to create one just for fun and to prove that it is possible to make such a device with an Arduino, a TFT display shield and with my modest programming skills.
Parts: Arduino Uno and a 2.8 inch 320×240 color TFT shield.
Figure 1. Components of my Arduino radar scope: Arduino Uno (A) and a 2.8″ color TFT shield: front view (B), back view (C).
The TFT display used here is a nameless 2.8 inch 320×240 pixel 16-bit color screen equipped with an ILI9341 controller. Officially this shield is also touch sensitive, but this functionality was not used. The shield carries a memory card slot and the pin connectivity to address this slot. The shield is plugged with its male connectors onto the Arduino Uno and then is ready for use. No soldering at all. A disadvantage is that, elegantly and simply as this shield is applied, it covers the entire pin header of the Arduino. The Uno’s free pins are because of this not directly available to connect with extra devices like sensors, servos, leds and the like.
Radar scope sketch
Let’s go through the sketch and discuss the instructions. The initial part of the sketch calls the libraries that we need to successfully compile the instructions for the Arduino’s microcontroller:
Libraries
#include <SPI.h> // f.k. for Arduino-1.5.2
#include <Adafruit_GFX.h> // hardware-specific library
#include <MCUFRIEND_kbv.h> // hardware-specific library
Pin and color assignments for the TFT display
#define LCD_CS A3 // Chip Select goes to Analog 3
#define LCD_CD A2 // Command/Data goes to Analog 2
#define LCD_WR A1 // LCD Write goes to Analog 1
#define LCD_RD A0 // LCD Read goes to Analog 0
#define LCD_RESET A4 // alternatively just connect to Arduino reset pin
As colors are defined hexadecimally in RGB565 format it is useful to define the most commonly used colors in human language. The sketch includes for this purpose the following:
#define BLACK 0x0000
#define BLUE 0x001F
#define RED 0xF800
#define GREEN 0x07E0
#define CYAN 0x07FF
#define MAGENTA 0xF81F
#define YELLOW 0xFFE0
#define WHITE 0xFFFF
#define SCOPE 0x3206 // SCOPE is the CRT screen color
A RGB565 color picker can be found at www.barth-dev.de/online/rgb565-color-picker/
Center coordinates of the scope
We are using a 320×240 screen and we have to instruct the Arduino which coordinates to use for drawing the center of the scope. This position is identical to the ‘pivot’: the fixed position around which the beam will sweep. For the 320×240 TFT display the center position is pixel x=120 and y=160 which is closest to the physical center of the screen. Because the display is made up of an even number of pixels the scope will be drawn one pixel off in x and y from the physical center of the display!
int center_x=120; // center x of radar scope on 320×240 TFT display
int center_y=160; // center y of radar scope on 320×240 TFT display
All coordinates used for drawing the static and dynamic parts of the scope will be related in this sketch to the center x and y coordinates. This makes it easier to migrate the scope to a larger display of, say, 320×480 pixels. Compared with hard coordinate programming the price tag for relative coordinate programming is about 1,000 bytes more memory required.
Once we have defined the center coordinates for the scope and the pivot for the beam it is time to introduce a few more parameters to store additional screen coordinate values.
About the beam
The beam is a green line segment with a fixed origin (center_x and center_y) while its radial end runs just inside the rim of the scope’s CRT screen in 360 steps over a circular trajectory. The function ‘drawLine’ recognized by the mcufriend_kbv library requires five parameters: x-y coordinates of the start and end of the line segment, and a line color. Notice that line thickness is always one pixel. If you want a thicker line, use two lines, a rectangle, or two triangles.
figure 2. Scheme explaining the selection of variables that control the scope’s center, the beam and the scale markers at the scope’s rim.
Variables need to be declared that will hold coordinates for the beam. The beam is a special kind of line segment, in the sense that one end is fixed at the pivot while the radial end moves with fixed angular increments over a circular path.
As the coordinates belonging to the radial end of the beam (the ‘edges’) will be calculated with cosine and sine functions in the dynamic part of the sketch, the variables holding these values need to be ‘floats’. The pivot coordinates of the beam (center_x and center_y) in the mean time can be economically defined using an ‘int’ variable. The sketch must be designed such to stop the drawing of the beam one pixel short of the scope’s rim to keep the color of the rim intact.
Scale markers
The scale decorating the scope’s rim consists of a series of 36 scale marker line segments positioned neatly along the rim of the scope, 10 degrees equidistant from each other. The start of each segment coincides with the rim of the scope and is hence named ‘edge’, while the end of each segment pointing outward is called ‘edge_out’. These marker line segments can be hard programmed one by one, but it is much more elegant and less memory consuming to calculate their ‘edge’ and ‘edge_out’ coordinates using the same formula as used for calculating the ‘edge’ coordinates of the beam. The variables that hold the coordinates for the scale marker line segments are:
float edge_x=0; // edge x coordinate
float edge_y=0; // edge y coordinate
Note that in the sketch the variables ‘edge_x’ and ‘edge_y’ are used twice: in the static part when the scope is constructed, and in the dynamic part where they hold the x and y coordinates of the radial end of the beam.
float edge_x_out=0;
float edge_y_out=0;
In terms of animation a ‘rotating’ beam is a process that is rapidly repeated 360 times. In each cycle a green radial line segment is drawn at a new angle. After drawing the new position of the beam its previous position needs to be erased because not doing so will fill the scope screen very fast with green beam lines. Before calculating the new position of the beam we store the previous position of the ‘edge’ position of the beam in memory (remember that the center coordinate of the beam is a stable, ‘hard’ coordinate). For this we need variables:
float edge_x_old=0; // remember previous edge x coordinate
float edge_y_old=0; // remember previous edge y coordinate
Beam updating is achieved through overwriting the previous position of the beam with a line segment that has the same color as the background.
And of course there is the beam angle because the beams rotates 360 degrees. The variable ‘radius’ is introduced here to make anticipated adaptation to 320×480 TFT displays easier. The variable ‘j’ is necessary to declare the memory location that keeps track of the amount of beam angle. The beam sweeps 360 degrees, so ‘j’ will increase from 0 to 359 in increments of 10 and then will be reset to 0. We will use the availability of ‘j’ to create also the ring of scale marker line segments.
int j; // for the 360 degrees for-next loops
float angle=0; // holds angular information of the beam
int radius=110; // for 320×240 TFT screens, portrait
Extra variables are defined here to draw lines, circles and rectangles to embellish the scope. The label ‘scope’ is used here to indicate that ‘scope’ coordinates deal with the static part, i.e., the construction of the scope. The x- and y-scope coordinates are relative to the center_x and center_y coordinates to make the sketch compatible with TFT displays with different dimensions and, in addition, to run this process with a minimum of hard coordinate programming:
int scope_x = 0;
int scope_y = 0;
The only thing left to do in this part of the sketch is to tell the mcufriend_kbv library that there is a compatible display attached:
MCUFRIEND_kbv tft;
Setup
After having established all variables we can continue with the static part of the script. It is always handy to have Serial Monitor output at hand, so the serial monitor is activated and will display a short message in the Serial Monitor screen. Next the TFT must be initialized. Successful initialization is reported on the TFT display with the message “radar scope initializing . . . . . . . OK” that is displayed for 1 second.
void setup(void) {
uint16_t g_identifier;
Serial.begin(9600);
Serial.println (“starting radar scope . . “);
// provide identification TFT controller to library
g_identifier = 0x9341; // this if for the 2.8 inch display ILI9341 controller
// uncomment this line and comment the previous if an ILI9481 is on board
// g_identifier = 0x9481; // this if for the 3.5 inch display ILI9481 controller
tft.begin(g_identifier);
// ========== initialization message TFT screen ========
tft.fillScreen(BLACK);
tft.setCursor(10, 10);
tft.setTextColor(WHITE);
tft.setTextSize(1);
tft.print (“radar scope initializing. . . . . . . “);
delay(900);
tft.println (” OK”);
delay(100);
tft.fillScreen(BLACK);
Static part: constructing the scope
This is a cool, modest scope on a humble Arduino, drawn with a minimum of extras. All multiple components are drawn in subroutines: contours, screws and scale marker segments. All elements are drawn relative to center_x and center_y. The subroutines ‘screw’, ‘draw_contour’ and ‘draw_scale’ do the hard work. The scope is built up from top to bottom.
Drawing the upper contour and upper screws
The ‘contours’ are the two horizontal lines with a rectangle in their middle, one contour above and one below the CRT. The ‘screws’ are the circles with a horizontal and a vertical line through it at four corner positions. These components are drawn relative to the scope center:
// upper contour
scope_x=(center_x-115);scope_y=(center_y-145);
// right upper screw
scope_x=(center_x+100); scope_y=(center_y-110);
screw();
// left upper screw
scope_x=(center_x-100); scope_y=(center_y-110);
screw();
Now it is time to draw the CRT with its scale markers
Drawing the CRT
The scope window consists of two red circles that together form the two-pixel thick CRT rim. This is a workaround because the drawCircle function can only draw one-pixel thick circles. We fill the scope window with a drab green color ‘0x3206’ defined earlier as ‘SCOPE’. A small bright green circle marks the center of the CRT, the pivot for the beam. The distance reference circles at radius 60 and 90 are just for decoration. As the beam sweeps over these circles the erasing of the previous beam position will remove part of these circles with the SCOPE color, leaving a nice radar clutter behind!
// scope CRT
tft.drawCircle (center_x,center_y, (radius+1),RED);
tft.drawCircle (center_x,center_y, (radius+2),RED);
tft.fillCircle (center_x,center_y, radius,SCOPE); // drab green CRT background
tft.fillCircle (center_x,center_y,2,GREEN); // beam pivot
tft.drawCircle (center_x,center_y,60,GREEN); // distance reference circle
tft.drawCircle (center_x,center_y,90,GREEN); // distance reference circle
Drawing the scale marker segments
As we have delared the edge_x and edge_y coordinates with pixel values we can outsource this job to the suboutine named ‘draw_scale’
draw_scale();
Drawing the lower contour and lower screws
This section is quite similar to the drawing of the upper contour and screws:
// lower contour
scope_x=(center_x-115);scope_y=(center_y+140);
draw_contour();
// left lower screw
scope_x=(center_x-100); scope_y=(center_y+120);
screw();
// right lower screw
scope_x=(center_x+100); scope_y=(center_y+120);
screw();
angle=0;
}
Dynamic part: managing the beam
Every move of the radar beam is a rotation of one degree clockwise around the pivot. The radial end of the beam must trace a circle just inside the rim of the scope. A small hurdle here is that the microprocessor must be addressed in radians instead of degrees. Hence here an instruction that converts degrees into radians. The edge coordinates are calculated by the subroutine ‘edge_coord’ while the actual sweep is done by the subroutine ‘sweep_beam’
void loop (){
for (j=0;j<360;j++)
{
angle=(j*0.01745331); // angle in radians – 1 degree = 0,01745331 radians
edge_coord(); // calculate beam edge coordinates
sweep_beam(); // sweep the beam
Because the beam is written to screen after the bright green center spot has been drawn as a static element, the center spot would be wiped out in one cycle because the previous beam erasing subroutine will overwrite the spot with the SCOPE color. A solution that addresses this unwanted phenomenon is redrawing the center spot every time the beam has moved one degree. Therefore this instruction must occur within ‘void loop’.
tft.fillCircle (center_x,center_y,2,GREEN); // restore pivot
}
}
As all the nitty gritty work is performed by subroutines the main loop is short and clean.
Subroutines
• Subroutine #1: edge_coord
‘edge_coord’ calculates the next position of the radial end of the beam on its endless circular sweep over the CRT. To do its job the formula needs the length of the beam (radius) and the cosine of the beam angle to determine the edge_x coordinate while it needs the sine of the angle and the radius to determine the edge_y coordinate. The angle value is supplied by the loop and the radius value is hard declared in the setup section. This subroutine also remembers the previous position of the beam.
void edge_coord ()
{
edge_x_old=edge_x;
edge_y_old=edge_y;
edge_x=(center_x+(radius*cos(angle)));
edge_y=(center_y+(radius*sin(angle)));
}
The new beam must be written to screen and then the old position must be erased.
The subroutine ‘sweep_beam’ just does that.
• Subroutine #2: sweep_beam
// refresh beam by overdrawing the old beam with SCOPE color and then draw a beam with the new radial edge coordinates.
void sweep_beam ()
{
tft.drawLine (center_x,center_y,edge_x_old,edge_y_old,SCOPE); // wipe previous
tft.drawLine (center_x,center_y,edge_x,edge_y,GREEN); // draw the beam
}
The next three subroutines draw static parts of the scope: contour, screws and scale marker segments.
• Subroutine #3: draw_contour
// draw contour subroutine
void draw_contour()
{
tft.drawLine (scope_x,scope_y,(scope_x+105),scope_y,RED);
tft.drawLine ((scope_x+135),scope_y,(scope_x+230),scope_y,RED);
tft.drawRect ((scope_x+105),(scope_y-3),30,9,SCOPE);
}
• Subroutine #4: screw
// draw the four screws
void screw ()
{
tft.drawCircle (scope_x,scope_y, 6,RED);
tft.drawLine ((scope_x-11),scope_y,(scope_x+11),(scope_y),RED);
tft.drawLine (scope_x,(scope_y-11),scope_x,(scope_y+11),RED);
}
• Subroutine #5: draw_scale
The actual drawing of the scale marker segments that dfecorate the CRT’s rim is a “do …. while” operation: draw a line segment along the rim every 10 degrees until 36 markers have been drawn, then return to the point where this subroutine was called.
// draw the scale marker segments
void draw_scale ()
{
j=0;
do {
angle=(j*0.01745331); // express angle in radians: 1deg = 0,01745331 radians
edge_x=(center_x+(radius*cos(angle)));
edge_y=(center_y+(radius*sin(angle)));
edge_x_out=(center_x+((radius+8)*cos(angle)));
edge_y_out=(center_y+((radius+8)*sin(angle)));
tft.drawLine (edge_x,edge_y, edge_x_out, edge_y_out,RED);
j=j+10; // increase the angle with 10 degrees
}while (j<360);
}
// end of sketch
Results
With this sketch uploaded to the Arduino a passive radar scope appears on the TFT display with a rotating beam, ready to report airplanes or UFO’s in the controller’s airspace. The sketch is very simple and there is no input of actual objects being detected and reported. The next step is to add an ultrasonic distance sensor device. I found a very interesting sketch by Dejan Nedelkovski on howtomechanotronic.com in which a servo motor sweeps an ultrasonic distance sensor left-right, detects an object and reports on a display. A similar project has been published by Ujash Patel at uu-machinetool.blogspot.nl. And there are more projects, for example Siraj Msm’s radar device which reports via Serial Monitor. This makes me confident that in the future I may be able to develop a similar complex device.
Some links
Dejan Nedelkovski’s radar:
http://howtomechatronics.com/projects/arduino-radar-project/
https://hackaday.io/project/14871-make-a-radar-station-with-arduino-and-processing
You Tube movie: https://youtu.be/kQRYIH2HwfY
Ujash Patel’s radar:
http://uu-machinetool.blogspot.nl/2014/08/ultrasonic-radar-system-with-arduino.html
Siraj Msm’s radar:
https://create.arduino.cc/projecthub/siraj-msm/radar-project-7694
Radar scope sketch: download here (arduino_radar.zip)