A Raspmerry Christmas with Miletus

Time flies and we are yet again at the end of the year. With the holidays coming up we thought it would be exciting to put our Miletus technology to the test and see what we can achieve in combination with a Raspberry Pi 4. Christmas is just around the corner so why not have a Christmas tree on a screen with some snowflakes and Jingle Bells playing in the background?

Preparation

With the plan set we need something to show the Christmas tree on. The screen of our choice is an Adafruit PiTFT Plus 320×240 2.8″ TFT. In theory any ILI9431 display should work with this project if it is correctly hooked up to the Raspberry Pi.
We also need a Christmas tree. If we carefully examine the available specification, datasheet and a Python library provided by Adafruit, we can learn that the pixel format is set to 16 bit. This means each pixel’s color is stored in an RGB565 format. With our Christmas tree image converted and stored in a binary file we are ready to get started!
With respect to software needed to create the project, all we need here is the latest TMS WEB Core v1.9.6.0 release.

Let’s get coding

First let’s make the SPI connection. Drop the TMiletusRaspberrySPI component onto the form and leave the default settings. In the form’s OnCreate event set the TMiletusRaspberrySPI.Frequency property (= SPI clock frequency) to 16 MHz. This determines how fast the data can be written, which is important if we want to show a 150 kB image at a reasonable speed. Then open the connection:

procedure TForm1.MiletusFormCreate(Sender: TObject);
begin
  MiletusRaspberrySPI1.Frequency := 16000000;
  MiletusRaspberrySPI1.Open;
end;

Next is screen initialization. We can do this as soon as the SPI connection is ready. By using the TMiletusRaspberrySPI.OnOpen event we can initialize the screen, load the image and after that draw the snowflakes. By using async methods and functions we can wait for each of step to finish before moving onto the next one.

procedure TForm1.MiletusRaspberrySPI1Open(Sender: TObject);
begin
  Await(JSValue, InitScreen);
  Await(JSValue, LoadChristmasTree);
  StartSnowing;
end;

InitScreen is sending a number of command and data combinations through SPI to the screen so it can be set up with the correct settings. It is based on values from the datasheet and the example library. For further information on these values please study the linked sources.

LoadChristmasTree is responsible for loading the Christmas tree onto the screen. While it is possible to embed the binary image as part of the executable, there are a few reasons against it: First of all, it doesn’t make sense to send the whole image to the web application and from there back to the native shell application. Sending 150 Kb of data between these 2 layers on a small device like a Raspberry Pi can be time consuming. Besides, by making it available as a file it gives the flexibility to change it to a different Christmas tree without modifying or recompiling the application!

function TForm1.LoadChristmasTree: TJSPromise;
begin
  Await(Boolean, TMiletusRaspberryMemoryBuffer.LoadFromFile('./xmastree.bin'));

  Await(JSValue, Block(0, 0, 239, 319, []));

  Await(Integer, GPIOWrite(GPIO_DC_PIN, 1));
  Await(Boolean, MiletusRaspberrySPI1.WriteMemBuffer); 

  Result := EmptyPromise;
end;

We added a new class called TMiletusRaspberryMemoryBuffer in TMS WEB Core v1.9.6.0. This makes it possible to load a file directly from the local file system (or to write a buffer of bytes from the web application) to a memory buffer that is inside the shell application. 

The Block call will again send command-data pairs through SPI: These are the X and Y coordinates as well as the width and the height of the area we would like to draw into. We want to draw the tree onto the whole 240×320 screen at (0,0) coordinates.

After sending this drawing area to the screen, we need to indicate that data is arriving next. For this, we set the DC pin to on/high by calling GPIOWrite. The same DC pin is set multiple times during initialization and in the Block function.

To finish the loading, TMiletusRaspberrySPI.WriteMemBuffer will take what is currently available in the memory buffer and send it over SPI without the data being transferred back and forth between our native shell application and web application.

The Christmas tree is now on the screen so the next step is to have some snowing animation too!

procedure TForm1.StartSnowing;
var
  I, J: Integer;
  s: Boolean;
begin
  I := 0;
  while True do
  begin
    J := (I mod 64) * 5;
    s := I mod 2 = 0;

    Await(JSValue, DrawSnowflake(38, J, s));
    Await(JSValue, DrawSnowflake(118, (J + 45) mod 320, s));
    Await(JSValue, DrawSnowflake(78, (J + 90) mod 320, s));
    Await(JSValue, DrawSnowflake(198, (J + 90) mod 320, s));
    Await(JSValue, DrawSnowflake(158, (J + 135) mod 320, s));
    Await(JSValue, DrawSnowflake(38, (J + 180) mod 320, s));
    Await(JSValue, DrawSnowflake(118, (J + 225) mod 320, s));
    Await(JSValue, DrawSnowflake(198, (J + 270) mod 320, s));

    I := (I + 1) mod 320;
  end;
end;

The StartSnowing routine will continuously draw 8 snowflakes in fixed X coordinates. At every iteration the Y coordinate changes which is making the snowflakes moving downwards. Once a snowflake reaches the bottom of the screen it starts again at the top.

function TForm1.DrawSnowflake(X, Y: Word; AShift: Boolean): TJSPromise;
var
  b: TBytes;
begin
  SetLength(b, 2);
  if not AShift then
  begin
    Await(JSValue, DrawPixel(X - 3, 5 + Y, Color565(255, 255, 255)));
    Await(Boolean, TMiletusRaspberryMemoryBuffer.ReadBuffer(b, 2, (Y * 240 + X) * 2));
    Await(JSValue, DrawPixelBytes(X, Y, b));
  end
  else
  begin
    Await(JSValue, DrawPixel(X, 5 + Y, Color565(255, 255, 255)));
    Await(Boolean, TMiletusRaspberryMemoryBuffer.ReadBuffer(b, 2, (Y * 240 + X - 3) * 2));
    Await(JSValue, DrawPixelBytes(X - 3, Y, b));
  end;
  Result := EmptyPromise;
end;

The DrawSnowflake function will draw the next snowflake which is a single white pixel. Encode the white color into 2 bytes by calling Color565 then send this data and the coordinates to the screen. TMiletusRaspberryMemoryBuffer still contains the Christmas tree so we can use TMiletusRaspberryMemoryBuffer.ReadBuffer to read the original pixel color value of the previous snowflake. This enables us to restore that pixel by writing the 2 color bytes along with the coordinates.

The AShift parameter is used to introduce a little sideways movement too. Instead of the snowflakes moving downwards in a single line they will shift to the left/right by 3 pixels.

Finally, the last step is to have Jingle Bells playing in the background. We are keeping this as a separate file so it can be easily changed to a different Christmas music. Let’s extend the form’s OnCreate event:

procedure TForm1.MiletusFormCreate(Sender: TObject);
var
  mbs: TMiletusBinaryDataStream;
  s: string;
begin
  mbs := TMiletusBinaryDataStream.Create;
  mbs.LoadFromFileRequest('./music.mp3', procedure
  begin
    WebMultimediaPlayer1.URL := 'data:audio/mpeg;base64,' + mbs.Base64;
    mbs.Free;
  end);

  MiletusRaspberrySPI1.Frequency := 16000000;
  MiletusRaspberrySPI1.Open;
end;

By using TMiletusBinaryDataStream we can load a local file (in this case the mp3). When the file is loaded just assign TMiletusBinaryDataStream.Base64 to the TWebMultimediaPlayer.URL property with the correct base64 MIME type prefix for mp3 files. 

With this, the Christmas application is ready! Would you like to see all this in action? Watch the video we prepared!

Get started