An Arduino Uno TFT shield radar scope
by Floris Wouterlood – Leiden, the Netherlands – March 24, 2017
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.
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:
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
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.
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.
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:
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) <
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
// ========== initialization message TFT screen ========
tft.print (“radar scope initializing. . . . . . . “);
tft.println (” OK”);
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
// right upper screw
// left upper 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’
Drawing the lower contour and lower screws
This section is quite similar to the drawing of the upper contour and screws:
// lower contour
// left lower screw
// right lower screw
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’
Arduino UNO Library for a Sampling Scope & Counter © LGPL
Easily include oscilloscope and frequency counter diagnostics into your own project.
After building my sampling scope and frequency counter, I figured it would be neat to be able to include these functions in a new project. That would make it easier to debug it, without the need of a second Arduino (since I only have one). This resulted in the library ScopeOne (for Arduino UNO) that you can easily include in your project. In the minimum configuration, the scope only uses two ports: a trigger input (port 2 or 3) and a signal input (one of A0-A5). Of course, you are still able to use all six channels of the original design if A0-A5 are not in use by your project.
- Slightly higher sampling frequency
- Reduced memory use
- Configurable input pins (a selection of A0-A5, trigger on pin 2 or 3)
- Added the use of the PC keyboard to communicate with Arduino
How to use the library
First install the library. To do this, create a new folder «ScopeOne» in your «Arduino/libraries» folder (this is where the Ardiuno program is stored on your hard disk). Copy the library.properties and keyword.txt files to this folder. Then create a subfolder «src». Copy the ScopeOne.h and ScopeOne.cpp files to the src folder.
See the Arduino code files Oscilloscope.ino to see how to use the library. I’ve also added ScopeLCD.ino with an example of how to combine a project with ScopeOne.
The components listed for this projects are only needed to build the full example that uses an LCD, a wave generator circuit and a few LEDs. The scope itself can operate with the Arduino only. The example project also uses the Arduino LCD library. The project writes the operation mode to the display (Scope or Counter) as well as the first input channel pin and the number of channels. You may want to try and change the configuration to use fewer channels by altering the «ScopeOne::install();» statement into something like e.g. «ScopeOne::install(A2, 2);» and see what happens.
The number in the top right of the display is the contrast level of the LCD (analog output on pin 6), which can be controlled by ‘+’ and ‘-‘ on the PC keyboard. The backlight of the display can be switched on and off using the ‘.’ key.