The purpose of this project is to get you familiar with Netduino.Foundation's GraphicsLibrary, a very intuitive and powerful API that helps you draw shapes, lines, text and images with just a few lines of code.
You will connect an SPI TFT LCD display and go through three parts to help you get familiar with the display graphics library: 1. Drawing basic shapes and lines, 2. Drawing texts with different font sizes, and 3. An example with a combination of both to create an analog watch face.
Netduino.Foundation is a platform for quickly and easily build connected things using the.NET MicroFramework on Netduino. Created by Wilderness Labs, it's completely open source and maintained by the Netduino community.
If you're new in Netduino development, I suggest you go to the Getting started with Netduino project to properly set up your development environment.
Step 1 - Assemble the circuitFor this project, wire up your LCD display to your Netduino as shown in the Fritzing diagram:
Create a Netduino project in Visual Studio 2015 for Windows or the latest Visual Studio for Mac; name the project DisplayGraphicsSPI.
Step 3 - Add the Netduino.Foundation NuGet PackageWindows
Right-click on your DisplayGraphicsSPI project and click Manage NuGet Packages. In the Browse tab, search for Netduino.Foundation; it should be the first search result. Click the Install button.
Also search for Netduino.Foundation.TFTSPI and Netduino.Foundation.GraphicsLibrary and add them to your project.
macOS
Alt-click on your DisplayGraphicsSPI project in the Solution Explorer, and click Add => Add Nuget Package to open the NuGet Package window. Search for the Netduino.Foundation package and click Add Package to add it to your project.
Also search for Netduino.Foundation.TFTSPI and Netduino.Foundation.GraphicsLibrary and add them to your project.
Step 4A - Write the code to draw shapes and linesAdd App Class
For this project, we implement a common App software pattern that manages all the peripherals and main logic.
Add a new App class to your project, and paste the following code to draw Circles, Rectangles and lines:
using Netduino.Foundation.Displays;
using SecretLabs.NETMF.Hardware.Netduino;
using Microsoft.SPOT.Hardware;
using Netduino.Foundation;
using System.Threading;
namespace DisplayGraphicsSPI
{
public class App
{
const int DRAW_TIME = 500;
protected ILI9163 tftDisplay;
protected GraphicsLibrary display;
public App()
{
InitializePeripherals();
}
protected void InitializePeripherals()
{
tftDisplay = new ILI9163
(
chipSelectPin: Pins.GPIO_PIN_D3,
dcPin: Pins.GPIO_PIN_D7,
resetPin: Pins.GPIO_PIN_D6,
width: 128,
height: 160,
spiModule: SPI.SPI_module.SPI1,
speedKHz: 15000
);
tftDisplay.ClearScreen(31);
tftDisplay.Refresh();
display = new GraphicsLibrary(tftDisplay);
}
protected void DrawShapes()
{
display.Clear(true);
// Filled Rectangles
display.DrawRectangle(3, 3, 26, 26, Color.Red, true);
display.DrawRectangle(37, 6, 20, 20, Color.Red, true);
display.DrawRectangle(65, 9, 14, 14, Color.Red, true);
display.DrawRectangle(87, 12, 8, 8, Color.Red, true);
display.DrawRectangle(103, 15, 2, 2, Color.Red, true);
display.Show();
// Empty Rectangles
display.DrawRectangle(3, 32, 26, 26, Color.Red);
display.DrawRectangle(37, 35, 20, 20, Color.Red);
display.DrawRectangle(65, 38, 14, 14, Color.Red);
display.DrawRectangle(87, 41, 8, 8, Color.Red);
display.DrawRectangle(103, 44, 2, 2, Color.Red);
display.Show();
// Filled Circles
display.DrawCircle(16, 73, 13, Color.Green, true);
display.DrawCircle(47, 73, 10, Color.Green, true);
display.DrawCircle(72, 73, 7, Color.Green, true);
display.DrawCircle(91, 73, 4, Color.Green, true);
display.DrawCircle(104, 73, 1, Color.Green, true);
display.Show();
// Empty Circles
display.DrawCircle(16, 103, 13, Color.Green);
display.DrawCircle(47, 103, 10, Color.Green);
display.DrawCircle(72, 103, 7, Color.Green);
display.DrawCircle(91, 103, 4, Color.Green);
display.DrawCircle(104, 103, 1, Color.Green);
display.Show();
// Horizontal, vertical and specific lines
for (int i = 0; i < 9; i++)
display.DrawHorizontalLine(3, 123 + (i * 4), 26, Color.Blue);
for (int i = 0; i < 7; i++)
display.DrawVerticalLine(37 + (i * 4), 123, 33, Color.Blue);
display.DrawLine(70, 131, 94, 147, Color.Blue);
display.DrawLine(70, 123, 94, 155, Color.Blue);
display.DrawLine(78, 123, 86, 155, Color.Blue);
display.DrawLine(86, 123, 78, 155, Color.Blue);
display.DrawLine(94, 123, 70, 155, Color.Blue);
display.DrawLine(94, 131, 70, 147, Color.Blue);
display.DrawLine(70, 139, 94, 139, Color.Blue);
display.Show();
Thread.Sleep(5000);
}
public void Run()
{
DrawShapes();
}
}
}
First thing that happens in this class is calling InitializePeripherals to instantiate all the peripherals that are connected to the Netduino, which in this project, the ILI9163 LCD display and when creating a GraphicLibrary object, note that we pass the display object. This links the display graphics library to the display we're using.
After Initializing everything, in the Run method we call DrawShapes, where you can see all the API methods that display offers to draw shapes and lines, especifying colors, filled or empty, sizes, and so on. It's important to note that when you want to clear the screen you call display.Clear(true);
and when you want to display everything you have in the buffer you call display.Show();
Note: Make sure when drawing shapes you don't exceed the screen size, otherwise you'll get an OutOfBoundsException
.
Program Class
Finally, create a new App class object and invoke the Run method. Your code should look like this:
using System.Threading;
namespace DisplayGraphicsSPI
{
public class Program
{
public static void Main()
{
App app = new App();
app.Run();
Thread.Sleep(Timeout.Infinite);
}
}
}
Step 4B - Run the projectClick the run button in Visual Studio to see your shapes in the LCD display! Feel free to play around with more colors and sizes to have a It should look like to the following gif:
Now you're going to change the App class to display text samples using different sizes. All we're doing replacing the DrawShapes method with DrawTexts and calling it in the App's Run method. The code should look something like this:
using Netduino.Foundation.Displays;
using SecretLabs.NETMF.Hardware.Netduino;
using Microsoft.SPOT.Hardware;
using Netduino.Foundation;
using System.Threading;
namespace DisplayGraphicsSPI
{
public class App
{
const int DRAW_TIME = 500;
protected ILI9163 tftDisplay;
protected GraphicsLibrary display;
public App()
{
InitializePeripherals();
}
protected void InitializePeripherals()
{
tftDisplay = new ILI9163
(
chipSelectPin: Pins.GPIO_PIN_D3,
dcPin: Pins.GPIO_PIN_D7,
resetPin: Pins.GPIO_PIN_D6,
width: 128,
height: 160,
spiModule: SPI.SPI_module.SPI1,
speedKHz: 15000
);
tftDisplay.ClearScreen(31);
tftDisplay.Refresh();
display = new GraphicsLibrary(tftDisplay);
}
protected void DrawTexts()
{
display.Clear(true);
display.CurrentFont = new Font8x12();
display.DrawText(4, 4, "abcdefghijklm", Color.White);
display.DrawText(4, 18, "nopqrstuvwxyz", Color.White);
display.DrawText(4, 32, "`1234567890-=", Color.White);
display.DrawText(4, 46, "~!@#$%^&*()_+", Color.White);
display.DrawText(4, 60, "[]\\;',./", Color.White);
display.DrawText(4, 74, "{}|:\"<>?", Color.White);
display.DrawText(4, 88, "ABCDEFGHIJKLM", Color.White);
display.DrawText(4, 102, "NOPQRSTUVWXYZ", Color.White);
display.CurrentFont = new Font4x8();
display.DrawText(4, 116, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", Color.White);
display.DrawText(4, 126, "abcdefghijklmnopqrstuvwxyz", Color.White);
display.DrawText(4, 136, "01234567890!@#$%^&*()_+-=", Color.White);
display.DrawText(4, 146, "\\|;:'\",<.>/?[]{}", Color.White);
display.Show();
Thread.Sleep(5000);
}
public void Run()
{
DrawTexts();
}
}
}
Inside DrawTexts, notice that before calling the DrawText method, we specify the font size in the CurrentFont property of display. Next we have a series of DrawText calls, where you have to specify the x and y coordinates along with the text string and specify a Color if the display supports it.
Note: The current version of DisplayGraphics doesnt support text wrapping, so make sure the text you want to display fits in your screen, otherwise you'll get an OutOfBoundsException.
Step 5B - Run the projectClick the run button in Visual Studio to see your text samples in the display! Notice how it can also display certain especial characters. It should look like to the following gif:
Now lets combine what you've learned so far and make a watch face as an example. Replace your entire App class code with the following:
using Netduino.Foundation.Displays;
using SecretLabs.NETMF.Hardware.Netduino;
using Microsoft.SPOT.Hardware;
using Netduino.Foundation;
using System.Threading;
namespace DisplayGraphicsSPI
{
public class App
{
readonly Color WatchBackgroundColor = Color.White;
protected int hour, minute, second, tick;
protected ILI9163 tftDisplay;
protected GraphicsLibrary display;
public App()
{
InitializePeripherals();
}
protected void InitializePeripherals()
{
tftDisplay = new ILI9163
(
chipSelectPin: Pins.GPIO_PIN_D3,
dcPin: Pins.GPIO_PIN_D7,
resetPin: Pins.GPIO_PIN_D6,
width: 128,
height: 160,
spiModule: SPI.SPI_module.SPI1,
speedKHz: 15000
);
tftDisplay.ClearScreen(31);
tftDisplay.Refresh();
display = new GraphicsLibrary(tftDisplay);
}
protected void DrawClock()
{
hour = 8;
minute = 54;
DrawWatchFace();
while (true)
{
tick++;
Thread.Sleep(1000);
UpdateClock(second: tick % 60);
}
}
protected void DrawWatchFace()
{
display.Clear();
int xCenter = 64;
int yCenter = 80;
int x, y;
display.CurrentFont = new Font8x12();
display.DrawText(4, 4, "Wilderness Labs");
display.DrawText(7, 144, "Netduino Clock");
display.DrawCircle(xCenter, yCenter, 56, WatchBackgroundColor, true);
for (int i = 0; i < 60; i++)
{
x = (int)(xCenter + 50 * System.Math.Sin(i * System.Math.PI / 30));
y = (int)(yCenter - 50 * System.Math.Cos(i * System.Math.PI / 30));
if (i % 5 == 0)
display.DrawCircle(x, y, 2, Color.Black, true);
else
display.DrawPixel(x, y, Color.Black);
}
}
protected void UpdateClock(int second = 0)
{
int xCenter = 64, yCenter = 80;
int x, y, xT, yT;
if (second == 0)
{
minute++;
if (minute == 60)
{
minute = 0;
hour++;
if (hour == 12)
{
hour = 0;
}
}
}
//remove previous hour
int previousHour = (hour - 1) < -1 ? 11 : (hour - 1);
x = (int)(xCenter + 23 * System.Math.Sin(previousHour * System.Math.PI / 6));
y = (int)(yCenter - 23 * System.Math.Cos(previousHour * System.Math.PI / 6));
xT = (int)(xCenter + 3 * System.Math.Sin((previousHour - 3) * System.Math.PI / 6));
yT = (int)(yCenter - 3 * System.Math.Cos((previousHour - 3) * System.Math.PI / 6));
display.DrawLine(xT, yT, x, y, WatchBackgroundColor);
xT = (int)(xCenter + 3 * System.Math.Sin((previousHour + 3) * System.Math.PI / 6));
yT = (int)(yCenter - 3 * System.Math.Cos((previousHour + 3) * System.Math.PI / 6));
display.DrawLine(xT, yT, x, y, WatchBackgroundColor);
//current hour
x = (int)(xCenter + 23 * System.Math.Sin(hour * System.Math.PI / 6));
y = (int)(yCenter - 23 * System.Math.Cos(hour * System.Math.PI / 6));
xT = (int)(xCenter + 3 * System.Math.Sin((hour - 3) * System.Math.PI / 6));
yT = (int)(yCenter - 3 * System.Math.Cos((hour - 3) * System.Math.PI / 6));
display.DrawLine(xT, yT, x, y, Color.Black);
xT = (int)(xCenter + 3 * System.Math.Sin((hour + 3) * System.Math.PI / 6));
yT = (int)(yCenter - 3 * System.Math.Cos((hour + 3) * System.Math.PI / 6));
display.DrawLine(xT, yT, x, y, Color.Black);
//remove previous minute
int previousMinute = minute - 1 < -1 ? 59 : (minute - 1);
x = (int)(xCenter + 35 * System.Math.Sin(previousMinute * System.Math.PI / 30));
y = (int)(yCenter - 35 * System.Math.Cos(previousMinute * System.Math.PI / 30));
xT = (int)(xCenter + 3 * System.Math.Sin((previousMinute - 15) * System.Math.PI / 6));
yT = (int)(yCenter - 3 * System.Math.Cos((previousMinute - 15) * System.Math.PI / 6));
display.DrawLine(xT, yT, x, y, WatchBackgroundColor);
xT = (int)(xCenter + 3 * System.Math.Sin((previousMinute + 15) * System.Math.PI / 6));
yT = (int)(yCenter - 3 * System.Math.Cos((previousMinute + 15) * System.Math.PI / 6));
display.DrawLine(xT, yT, x, y, WatchBackgroundColor);
//current minute
x = (int)(xCenter + 35 * System.Math.Sin(minute * System.Math.PI / 30));
y = (int)(yCenter - 35 * System.Math.Cos(minute * System.Math.PI / 30));
xT = (int)(xCenter + 3 * System.Math.Sin((minute - 15) * System.Math.PI / 6));
yT = (int)(yCenter - 3 * System.Math.Cos((minute - 15) * System.Math.PI / 6));
display.DrawLine(xT, yT, x, y, Color.Black);
xT = (int)(xCenter + 3 * System.Math.Sin((minute + 15) * System.Math.PI / 6));
yT = (int)(yCenter - 3 * System.Math.Cos((minute + 15) * System.Math.PI / 6));
display.DrawLine(xT, yT, x, y, Color.Black);
//remove previous second
int previousSecond = second - 1 < -1 ? 59 : (second - 1);
x = (int)(xCenter + 40 * System.Math.Sin(previousSecond * System.Math.PI / 30));
y = (int)(yCenter - 40 * System.Math.Cos(previousSecond * System.Math.PI / 30));
display.DrawLine(xCenter, yCenter, x, y, WatchBackgroundColor);
//current second
x = (int)(xCenter + 40 * System.Math.Sin(second * System.Math.PI / 30));
y = (int)(yCenter - 40 * System.Math.Cos(second * System.Math.PI / 30));
display.DrawLine(xCenter, yCenter, x, y, Color.Red);
display.Show();
}
public void Run()
{
DrawClock();
}
}
}
Lets break down the explanation, starting from the DrawClock method that's invoked by the App's Run method:
- DrawClock is in charge of initialize the initial hour and minutes of the time, calls DrawWatchFace, and enters into a infinite while loop that in every 1000ms (or 1s) calls UpdateClock passing the remainder operator of ticks divided by 60.
- DrawWatchFace method is called once, and it draws the watch's background, hour marks and the Wilderness Labs text on the top and Netduino Clock text on the bottom.
- UpdateClock method's purpose is to display the clock's hour, minute and second hand, and its invoked every second to update the time like any regular clock. Notice that before drawing the new arm's position, we are re-drawing the arms current position with the same color as the background. We do this to update all the hands positions without having to clear and redraw the entire display.
Click the run button in Visual Studio to see your clock in action! It should look like to the following gif:
Note that the time on the clock will be displayed based on the initial hour and minute values set in the DrawClock method. You could wire an RTC module to keep the time regardless if the Netduino loses power, or use push buttons to adjust the time anytime you need to.
Check out Netduino.Foundation!This project is only the tip of the iceberg in terms of the extensive exciting things you can do with Netduino.Foundation.
- It comes with a Huge Peripheral Driver Library with drivers for the most common sensors and peripherals available in the market.
- All the peripheral drivers are simplified with built-in functionality, exposed by a clean, modern API.
- This project is backed by a growing community that is constantly working on building cool connected things and always excited to help new-comers and discuss new projects.
Comments