Arduino GIGA R1 Road Test

View table of contents ...  

RoadTest: Seeking a Tech Enthusiast to Evaluate the Arduino GIGA Display Bundle

Author: dougw

Creation date:

Evaluation Type: Connectors & Cable

Did you receive all parts the manufacturer stated would be included in the package?: False

What other parts do you consider comparable to this product?: The Arduino Giga is the same form factor as an Arduino Mega, however the Giga is significantly improved in a great many ways..

What were the biggest problems encountered?: The biggest problem was trying to test the vast array of features in this kit.

Detailed Review:

Intro

Every once in a while a microcontroller module comes along that stands out as a significant step forward. The Arduino GIGA is one of hose modules. When I read the specifications and features, I knew I wanted to check it out and see what it could do. Fortunately I was chosen to put it through some paces. This blog chronicles that journey. It isn't a standard road test that focusses on explaining features and testing specifications. what I wanted to do was develop a series of mini applications that go beyond what the example sketches actually do, to showcase that when the feature set is comprehensive enough, they synergize to make the sum of the features greater than the individual features. The Arduino Giga exemplifies a platform that is so feature rich it can adapt to implement more complex applications than perhaps any microcontroller available today. I hope to show that saying it has more horsepower, more memory and more IO is insufficient to describe how much better it actually is than microcontrollers that came before.

Here is a picture of the GIGA with its companion LCD displaying a color picture - just to get things started with a splash:

imageimage

You can already see the display is a gorgeous 800x480 LCD, but I'm getting ahead of the journey.

Unboxing

The journey started with unboxing accompanied by a little Giga song I concocted.  This video includes installation of an Arducam GC2145, procured separately from the road test kit. It also includes installation of the whole system in a 3D printed chassis, to make demonstrations a bit more robust.  Finally the first video incudes powering on to display a first image:

One of the really impressive things about this display is how fast it can display a full image. This speed allows it to display video which is problematic on most small microcontroller displays.

Displaying Color Images From Onboard Memory

The second video shows how to display images stored in onboard memory. The GIGA has 2 MB of Flash and 1 MB of RAM.

Full images for the 800x480 display take 750KB, so even though there is lots of onboard memory, it can't store very many full images. 

Image Display Sketch

/*
aball image display
by Doug Wong
*/

#include "Arduino_H7_Video.h"
#include "ArduinoGraphics.h"

// Alternatively, any raw RGB565 image can be included on demand using this macro
// Online image converter: https://lvgl.io/tools/imageconverter (Output format: Binary RGB565)

#define INCBIN_PREFIX
#include "incbin.h"
INCBIN(test, "F:/projects/arduino/GigaR1/demos/aball1.bin");

Arduino_H7_Video Display(800, 480, GigaDisplayShield);

Image img_aball1(ENCODING_RGB16, (uint8_t *) testData, 800, 480);

void setup() {
  Display.begin();

  Display.beginDraw();
  Display.image(img_aball1, (Display.width() - img_aball1.width())/2, (Display.height() - img_aball1.height())/2);
  Display.endDraw();
}

void loop() { }

This program also uses a incbin.h program to load an image into memory.

/**
 * @file incbin.h
 * @author Dale Weiler
 * @brief Utility for including binary files
 *
 * Facilities for including binary files into the current translation unit and
 * making use from them externally in other translation units.
 */
#ifndef INCBIN_HDR
#define INCBIN_HDR
#include <limits.h>
#if   defined(__AVX512BW__) || \
      defined(__AVX512CD__) || \
      defined(__AVX512DQ__) || \
      defined(__AVX512ER__) || \
      defined(__AVX512PF__) || \
      defined(__AVX512VL__) || \
      defined(__AVX512F__)
# define INCBIN_ALIGNMENT_INDEX 6
#elif defined(__AVX__)      || \
      defined(__AVX2__)
# define INCBIN_ALIGNMENT_INDEX 5
#elif defined(__SSE__)      || \
      defined(__SSE2__)     || \
      defined(__SSE3__)     || \
      defined(__SSSE3__)    || \
      defined(__SSE4_1__)   || \
      defined(__SSE4_2__)   || \
      defined(__neon__)
# define INCBIN_ALIGNMENT_INDEX 4
#elif ULONG_MAX != 0xffffffffu
# define INCBIN_ALIGNMENT_INDEX 3
# else
# define INCBIN_ALIGNMENT_INDEX 2
#endif

/* Lookup table of (1 << n) where `n' is `INCBIN_ALIGNMENT_INDEX' */
#define INCBIN_ALIGN_SHIFT_0 1
#define INCBIN_ALIGN_SHIFT_1 2
#define INCBIN_ALIGN_SHIFT_2 4
#define INCBIN_ALIGN_SHIFT_3 8
#define INCBIN_ALIGN_SHIFT_4 16
#define INCBIN_ALIGN_SHIFT_5 32
#define INCBIN_ALIGN_SHIFT_6 64

/* Actual alignment value */
#define INCBIN_ALIGNMENT \
    INCBIN_CONCATENATE( \
        INCBIN_CONCATENATE(INCBIN_ALIGN_SHIFT, _), \
        INCBIN_ALIGNMENT_INDEX)

/* Stringize */
#define INCBIN_STR(X) \
    #X
#define INCBIN_STRINGIZE(X) \
    INCBIN_STR(X)
/* Concatenate */
#define INCBIN_CAT(X, Y) \
    X ## Y
#define INCBIN_CONCATENATE(X, Y) \
    INCBIN_CAT(X, Y)
/* Deferred macro expansion */
#define INCBIN_EVAL(X) \
    X
#define INCBIN_INVOKE(N, ...) \
    INCBIN_EVAL(N(__VA_ARGS__))

/* Green Hills uses a different directive for including binary data */
#if defined(__ghs__)
#  if (__ghs_asm == 2)
#    define INCBIN_MACRO ".file"
/* Or consider the ".myrawdata" entry in the ld file */
#  else
#    define INCBIN_MACRO "\tINCBIN"
#  endif
#else
#  define INCBIN_MACRO ".incbin"
#endif

#ifndef _MSC_VER
#  define INCBIN_ALIGN \
    __attribute__((aligned(INCBIN_ALIGNMENT)))
#else
#  define INCBIN_ALIGN __declspec(align(INCBIN_ALIGNMENT))
#endif

#if defined(__arm__) || /* GNU C and RealView */ \
    defined(__arm) || /* Diab */ \
    defined(_ARM) /* ImageCraft */
#  define INCBIN_ARM
#endif

#ifdef __GNUC__
/* Utilize .balign where supported */
#  define INCBIN_ALIGN_HOST ".balign " INCBIN_STRINGIZE(INCBIN_ALIGNMENT) "\n"
#  define INCBIN_ALIGN_BYTE ".balign 1\n"
#elif defined(INCBIN_ARM)
/*
 * On arm assemblers, the alignment value is calculated as (1 << n) where `n' is
 * the shift count. This is the value passed to `.align'
 */
#  define INCBIN_ALIGN_HOST ".align " INCBIN_STRINGIZE(INCBIN_ALIGNMENT_INDEX) "\n"
#  define INCBIN_ALIGN_BYTE ".align 0\n"
#else
/* We assume other inline assembler's treat `.align' as `.balign' */
#  define INCBIN_ALIGN_HOST ".align " INCBIN_STRINGIZE(INCBIN_ALIGNMENT) "\n"
#  define INCBIN_ALIGN_BYTE ".align 1\n"
#endif

/* INCBIN_CONST is used by incbin.c generated files */
#if defined(__cplusplus)
#  define INCBIN_EXTERNAL extern "C"
#  define INCBIN_CONST    extern const
#else
#  define INCBIN_EXTERNAL extern
#  define INCBIN_CONST    const
#endif

/**
 * @brief Optionally override the linker section into which data is emitted.
 *
 * @warning If you use this facility, you'll have to deal with platform-specific linker output
 * section naming on your own
 *
 * Overriding the default linker output section, e.g for esp8266/Arduino:
 * @code
 * #define INCBIN_OUTPUT_SECTION ".irom.text"
 * #include "incbin.h"
 * INCBIN(Foo, "foo.txt");
 * // Data is emitted into program memory that never gets copied to RAM
 * @endcode
 */
#if !defined(INCBIN_OUTPUT_SECTION)
#  if defined(__APPLE__)
#    define INCBIN_OUTPUT_SECTION         ".const_data"
#  else
#    define INCBIN_OUTPUT_SECTION         ".rodata"
#  endif
#endif

#if defined(__APPLE__)
/* The directives are different for Apple branded compilers */
#  define INCBIN_SECTION         INCBIN_OUTPUT_SECTION "\n"
#  define INCBIN_GLOBAL(NAME)    ".globl " INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME "\n"
#  define INCBIN_INT             ".long "
#  define INCBIN_MANGLE          "_"
#  define INCBIN_BYTE            ".byte "
#  define INCBIN_TYPE(...)
#else
#  define INCBIN_SECTION         ".section " INCBIN_OUTPUT_SECTION "\n"
#  define INCBIN_GLOBAL(NAME)    ".global " INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME "\n"
#  if defined(__ghs__)
#    define INCBIN_INT           ".word "
#  else
#    define INCBIN_INT           ".int "
#  endif
#  if defined(__USER_LABEL_PREFIX__)
#    define INCBIN_MANGLE        INCBIN_STRINGIZE(__USER_LABEL_PREFIX__)
#  else
#    define INCBIN_MANGLE        ""
#  endif
#  if defined(INCBIN_ARM)
/* On arm assemblers, `@' is used as a line comment token */
#    define INCBIN_TYPE(NAME)    ".type " INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME ", %object\n"
#  elif defined(__MINGW32__) || defined(__MINGW64__)
/* Mingw doesn't support this directive either */
#    define INCBIN_TYPE(NAME)
#  else
/* It's safe to use `@' on other architectures */
#    define INCBIN_TYPE(NAME)    ".type " INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME ", @object\n"
#  endif
#  define INCBIN_BYTE            ".byte "
#endif

/* List of style types used for symbol names */
#define INCBIN_STYLE_CAMEL 0
#define INCBIN_STYLE_SNAKE 1

/**
 * @brief Specify the prefix to use for symbol names.
 *
 * By default this is `g', producing symbols of the form:
 * @code
 * #include "incbin.h"
 * INCBIN(Foo, "foo.txt");
 *
 * // Now you have the following symbols:
 * // const unsigned char gFooData[];
 * // const unsigned char *const gFooEnd;
 * // const unsigned int gFooSize;
 * @endcode
 *
 * If however you specify a prefix before including: e.g:
 * @code
 * #define INCBIN_PREFIX incbin
 * #include "incbin.h"
 * INCBIN(Foo, "foo.txt");
 *
 * // Now you have the following symbols instead:
 * // const unsigned char incbinFooData[];
 * // const unsigned char *const incbinFooEnd;
 * // const unsigned int incbinFooSize;
 * @endcode
 */
#if !defined(INCBIN_PREFIX)
#  define INCBIN_PREFIX g
#endif

/**
 * @brief Specify the style used for symbol names.
 *
 * Possible options are
 * - INCBIN_STYLE_CAMEL "CamelCase"
 * - INCBIN_STYLE_SNAKE "snake_case"
 *
 * Default option is *INCBIN_STYLE_CAMEL* producing symbols of the form:
 * @code
 * #include "incbin.h"
 * INCBIN(Foo, "foo.txt");
 *
 * // Now you have the following symbols:
 * // const unsigned char <prefix>FooData[];
 * // const unsigned char *const <prefix>FooEnd;
 * // const unsigned int <prefix>FooSize;
 * @endcode
 *
 * If however you specify a style before including: e.g:
 * @code
 * #define INCBIN_STYLE INCBIN_STYLE_SNAKE
 * #include "incbin.h"
 * INCBIN(foo, "foo.txt");
 *
 * // Now you have the following symbols:
 * // const unsigned char <prefix>foo_data[];
 * // const unsigned char *const <prefix>foo_end;
 * // const unsigned int <prefix>foo_size;
 * @endcode
 */
#if !defined(INCBIN_STYLE)
#  define INCBIN_STYLE INCBIN_STYLE_CAMEL
#endif

/* Style lookup tables */
#define INCBIN_STYLE_0_DATA Data
#define INCBIN_STYLE_0_END End
#define INCBIN_STYLE_0_SIZE Size
#define INCBIN_STYLE_1_DATA _data
#define INCBIN_STYLE_1_END _end
#define INCBIN_STYLE_1_SIZE _size

/* Style lookup: returning identifier */
#define INCBIN_STYLE_IDENT(TYPE) \
    INCBIN_CONCATENATE( \
        INCBIN_STYLE_, \
        INCBIN_CONCATENATE( \
            INCBIN_EVAL(INCBIN_STYLE), \
            INCBIN_CONCATENATE(_, TYPE)))

/* Style lookup: returning string literal */
#define INCBIN_STYLE_STRING(TYPE) \
    INCBIN_STRINGIZE( \
        INCBIN_STYLE_IDENT(TYPE)) \

/* Generate the global labels by indirectly invoking the macro with our style
 * type and concatenating the name against them. */
#define INCBIN_GLOBAL_LABELS(NAME, TYPE) \
    INCBIN_INVOKE( \
        INCBIN_GLOBAL, \
        INCBIN_CONCATENATE( \
            NAME, \
            INCBIN_INVOKE( \
                INCBIN_STYLE_IDENT, \
                TYPE))) \
    INCBIN_INVOKE( \
        INCBIN_TYPE, \
        INCBIN_CONCATENATE( \
            NAME, \
            INCBIN_INVOKE( \
                INCBIN_STYLE_IDENT, \
                TYPE)))

/**
 * @brief Externally reference binary data included in another translation unit.
 *
 * Produces three external symbols that reference the binary data included in
 * another translation unit.
 *
 * The symbol names are a concatenation of `INCBIN_PREFIX' before *NAME*; with
 * "Data", as well as "End" and "Size" after. An example is provided below.
 *
 * @param NAME The name given for the binary data
 *
 * @code
 * INCBIN_EXTERN(Foo);
 *
 * // Now you have the following symbols:
 * // extern const unsigned char <prefix>FooData[];
 * // extern const unsigned char *const <prefix>FooEnd;
 * // extern const unsigned int <prefix>FooSize;
 * @endcode
 */
#define INCBIN_EXTERN(NAME) \
    INCBIN_EXTERNAL const INCBIN_ALIGN unsigned char \
        INCBIN_CONCATENATE( \
            INCBIN_CONCATENATE(INCBIN_PREFIX, NAME), \
            INCBIN_STYLE_IDENT(DATA))[]; \
    INCBIN_EXTERNAL const INCBIN_ALIGN unsigned char *const \
    INCBIN_CONCATENATE( \
        INCBIN_CONCATENATE(INCBIN_PREFIX, NAME), \
        INCBIN_STYLE_IDENT(END)); \
    INCBIN_EXTERNAL const unsigned int \
        INCBIN_CONCATENATE( \
            INCBIN_CONCATENATE(INCBIN_PREFIX, NAME), \
            INCBIN_STYLE_IDENT(SIZE))

/**
 * @brief Include a binary file into the current translation unit.
 *
 * Includes a binary file into the current translation unit, producing three symbols
 * for objects that encode the data and size respectively.
 *
 * The symbol names are a concatenation of `INCBIN_PREFIX' before *NAME*; with
 * "Data", as well as "End" and "Size" after. An example is provided below.
 *
 * @param NAME The name to associate with this binary data (as an identifier.)
 * @param FILENAME The file to include (as a string literal.)
 *
 * @code
 * INCBIN(Icon, "icon.png");
 *
 * // Now you have the following symbols:
 * // const unsigned char <prefix>IconData[];
 * // const unsigned char *const <prefix>IconEnd;
 * // const unsigned int <prefix>IconSize;
 * @endcode
 *
 * @warning This must be used in global scope
 * @warning The identifiers may be different if INCBIN_STYLE is not default
 *
 * To externally reference the data included by this in another translation unit
 * please @see INCBIN_EXTERN.
 */
#ifdef _MSC_VER
#define INCBIN(NAME, FILENAME) \
    INCBIN_EXTERN(NAME)
#else
#define INCBIN(NAME, FILENAME) \
    __asm__(INCBIN_SECTION \
            INCBIN_GLOBAL_LABELS(NAME, DATA) \
            INCBIN_ALIGN_HOST \
            INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(DATA) ":\n" \
            INCBIN_MACRO " \"" FILENAME "\"\n" \
            INCBIN_GLOBAL_LABELS(NAME, END) \
            INCBIN_ALIGN_BYTE \
            INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(END) ":\n" \
                INCBIN_BYTE "1\n" \
            INCBIN_GLOBAL_LABELS(NAME, SIZE) \
            INCBIN_ALIGN_HOST \
            INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(SIZE) ":\n" \
                INCBIN_INT INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(END) " - " \
                           INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(DATA) "\n" \
            INCBIN_ALIGN_HOST \
            ".text\n" \
    ); \
    INCBIN_EXTERN(NAME)

#endif
#endif

Displaying Color Images From External USB Memory

In keeping with my goal to go beyond what the example sketches can do, this sketch can load images from a USB memory stick by touching the screen.

This is a complete slide show app.

Display Images From USB Memory Sketch

// Arduino GIGA R1 program to display images stored on a USB memory device
// by Doug Wong 2025
// Touching the screen displays the next image
// The image will roll over to thr first image after the last image
// Image format should be 800 x 480 in RGB565 format - binary

#include <Arduino_USBHostMbed5.h>
#include <DigitalOut.h>
#include <FATFileSystem.h>
#include <Arduino_H7_Video.h>
#include <ArduinoGraphics.h>
#include "Arduino_GigaDisplayTouch.h"

Arduino_GigaDisplayTouch touchDetector;

USBHostMSD msd;
mbed::FATFileSystem usb("usb");

const int USB_HOST_ENABLE_PIN = PA_15;
const int MAX_FILES = 50;
const int MAX_NAME_LEN = 128;
char fileNames[MAX_FILES][MAX_NAME_LEN];
int fileCount = 0;
const int MAX_CONNECTION_ATTEMPTS = 10;
int filenum = 0;                        // file number selected

Arduino_H7_Video Display(800, 480, GigaDisplayShield);
const int IMG_WIDTH = 800;
const int IMG_HEIGHT = 480;
const uint32_t EXPECTED_FILE_SIZE = IMG_WIDTH * IMG_HEIGHT * 2;

uint8_t rowBuffer[IMG_WIDTH * 2]; // Static buffer

void setup() {
    Serial.begin(115200);
    touchDetector.begin();
    pinMode(USB_HOST_ENABLE_PIN, OUTPUT);
    digitalWrite(USB_HOST_ENABLE_PIN, HIGH);
    while (!Serial) {}
    delay(1500);

    Serial.println("=== USB File List ===");
    Display.begin();
    forceScreenClear();

    if (!initUSBHost() || !mountUSB()) return;
    listRootDirectory();
    if (fileCount == 0) return;

    printFileList();
    Serial.println("Select a file # to open as 800x480 .bin, or type 'clear' to reset the screen:");
}

void loop() {
    if (fileCount > 0) handleUserInput();
    uint8_t contacts;
    GDTpoint_t points[5];
    
    contacts = touchDetector.getTouchPoints(points);  //capture touches on touchscreen
    if (contacts > 0) {                               //Check for screen touch
        filenum++;                                    //increment filenumber
        if (filenum > fileCount)  filenum = 1;        //handle rollover when filenum exceeds filecount
        displayRawRowByRow(fileNames[filenum-1]);     //call image disply of file number
    }
    contacts = touchDetector.getTouchPoints(points);  //check screen for touches - should br none at this time
    contacts = 0;                                     //clear any touch count anyway
    delay (20);
}

bool initUSBHost() {
    for (int i = 0; i < MAX_CONNECTION_ATTEMPTS; i++) {
        if (msd.connect()) {
            Serial.println("USB mass storage device connected!");
            return true;
        }
        Serial.println("USB device not detected, retrying...");
        delay(1000);
    }
    return false;
}

bool mountUSB() {
    Serial.print("Mounting USB device... ");
    if (usb.mount(&msd)) {
        Serial.println("Failed to mount USB.");
        return false;
    }
    Serial.println("done.");
    return true;
}

void listRootDirectory() {
    fileCount = 0;
    DIR* dir = opendir("/usb/");
    if (!dir) return;
    while (fileCount < MAX_FILES) {
        struct dirent* entry = readdir(dir);
        if (!entry) break;
        strncpy(fileNames[fileCount], entry->d_name, MAX_NAME_LEN - 1);
        fileNames[fileCount][MAX_NAME_LEN - 1] = '\0';
        fileCount++;
    }
    closedir(dir);
}

void printFileList() {
    Serial.print("Found "); Serial.print(fileCount); Serial.println(" file(s) in /usb/:");
    for (int i = 0; i < fileCount; i++) {
        Serial.print(i + 1); Serial.print(") "); Serial.println(fileNames[i]);
    }
}

void handleUserInput() {
    if (Serial.available() > 0) {
        String input = Serial.readStringUntil('\n');
        input.trim();

        if (input.equalsIgnoreCase("clear")) {
            forceScreenClear();
            return;
        }

        int sel = input.toInt();
        if (sel < 1 || sel > fileCount) return;

        Serial.print("Displaying: /usb/");
        Serial.println(fileNames[sel - 1]);

        displayRawRowByRow(fileNames[sel - 1]);
    }
}

bool displayRawRowByRow(const char* fileName) {
    String path = "/usb/" + String(fileName);
    FILE* f = fopen(path.c_str(), "rb");
    if (!f) return false;

    forceScreenClear();
    Display.beginDraw();

    for (int y = 0; y < IMG_HEIGHT; y++) {
        if (fread(rowBuffer, 1, IMG_WIDTH * 2, f) != IMG_WIDTH * 2) break;
        Image rowImage(ENCODING_RGB16, rowBuffer, IMG_WIDTH, 1);
        Display.image(rowImage, 0, y);
    }

    Display.endDraw();
    fclose(f);
    Serial.println("Image displayed successfully!");
    return true;
}

void forceScreenClear() {
    Display.beginDraw();
    Display.fill(0x0000);
    Display.endDraw();
}

Camera Port

Next I moved on to see how the Arducam B0462 worked. The Giga has a connector for a camera which is passed through to the LCD module so it is simply plug and play.

Here is the Arducam displaying what it sees, which in this case is the camera I use to take videos for this blog.

image

Camera Demo

Currently the camera captures 320x240 pixels and upscales to 640x480. Hopefully the driver will get updated to capture a full 800x480.

Actually, I don't think I needed to swap horizontal orientation, but that is an easy fix.

Camera Sketch

//Arduino GIGA R1 displaying an Arducam B0462 (GC2145) on the GIGA Display
// Doug Wong 2025
// The GC2145 image has been transposed properly so it dsplays right side up

#include "arducam_dvp.h"
#include "Arduino_H7_Video.h"
#include "dsi.h"
#include "SDRAM.h"

// This example only works with Greyscale cameras (due to the palette + resize&rotate algo)
#define ARDUCAM_CAMERA_GC2145

#ifdef ARDUCAM_CAMERA_HM01B0
#include "Himax_HM01B0/himax.h"
HM01B0 himax;
Camera cam(himax);
#define IMAGE_MODE CAMERA_GRAYSCALE
#elif defined(ARDUCAM_CAMERA_HM0360)
#include "Himax_HM0360/hm0360.h"
HM0360 himax;
Camera cam(himax);
#define IMAGE_MODE CAMERA_GRAYSCALE
#elif defined(ARDUCAM_CAMERA_OV767X)
#include "OV7670/ov767x.h"
// OV7670 ov767x;
OV7675 ov767x;
Camera cam(ov767x);
#define IMAGE_MODE CAMERA_RGB565
#elif defined(ARDUCAM_CAMERA_GC2145)
#include "GC2145/gc2145.h"
GC2145 galaxyCore;
Camera cam(galaxyCore);
#define IMAGE_MODE CAMERA_RGB565
#endif

// The buffer used to capture the frame
FrameBuffer fb;
// The buffer used to rotate and resize the frame
FrameBuffer outfb;
// The buffer used to rotate and resize the frame
Arduino_H7_Video Display(800, 480, GigaDisplayShield);

void blinkLED(uint32_t count = 0xFFFFFFFF)
{
  pinMode(LED_BUILTIN, OUTPUT);
  while (count--) {
    digitalWrite(LED_BUILTIN, LOW);  // turn the LED on (HIGH is the voltage level)
    delay(50);                       // wait for a second
    digitalWrite(LED_BUILTIN, HIGH); // turn the LED off by making the voltage LOW
    delay(50);                       // wait for a second
  }
}

uint32_t palette[256];

void setup() {
  // Init the cam QVGA, 30FPS
  if (!cam.begin(CAMERA_R320x240, IMAGE_MODE, 30)) {
    blinkLED();
  }

  // Setup the palette to convert 8 bit greyscale to 32bit greyscale
  for (int i = 0; i < 256; i++) {
    palette[i] = 0xFF000000 | (i << 16) | (i << 8) | i;
  }

  Display.begin();

  if (IMAGE_MODE == CAMERA_GRAYSCALE) {
    dsi_configueCLUT((uint32_t*)palette);
  }
  outfb.setBuffer((uint8_t*)SDRAM.malloc(1024 * 1024));

  // clear the display (gives a nice black background)
  dsi_lcdClear(0);
  dsi_drawCurrentFrameBuffer();
  dsi_lcdClear(0);
  dsi_drawCurrentFrameBuffer();
}

#define HTONS(x)    (((x >> 8) & 0x00FF) | ((x << 8) & 0xFF00))

void loop() {

  // Grab frame and write to another framebuffer
  if (cam.grabFrame(fb, 3000) == 0) {

    // double the resolution and transpose (rotate by 90 degrees) in the same step
    // this only works if the camera feed is 320x240 and the area where we want to display is 640x480
    for (int i = 0; i < 320; i++) {
      int k = 320 - i;                    // this coordinate reversal swaps the image horizontally
      for (int j = 0; j < 240; j++) {
        int l = 240 - j;                  // this coordinate reversal swaps the image vertically
        if (IMAGE_MODE == CAMERA_GRAYSCALE) {
          ((uint8_t*)outfb.getBuffer())[j * 2 + (i * 2) * 480] = ((uint8_t*)fb.getBuffer())[i + j * 320];
          ((uint8_t*)outfb.getBuffer())[j * 2 + (i * 2) * 480 + 1] = ((uint8_t*)fb.getBuffer())[i + j * 320];
          ((uint8_t*)outfb.getBuffer())[j * 2 + (i * 2 + 1) * 480] = ((uint8_t*)fb.getBuffer())[i + j * 320];
          ((uint8_t*)outfb.getBuffer())[j * 2 + (i * 2 + 1) * 480 + 1] = ((uint8_t*)fb.getBuffer())[i + j * 320];
        } else {
          ((uint16_t*)outfb.getBuffer())[l * 2 + (k * 2) * 480] = HTONS(((uint16_t*)fb.getBuffer())[i + j * 320]);
          ((uint16_t*)outfb.getBuffer())[l * 2 + (k * 2) * 480 + 1] = HTONS(((uint16_t*)fb.getBuffer())[i + j * 320]);
          ((uint16_t*)outfb.getBuffer())[l * 2 + (k * 2 + 1) * 480] = HTONS(((uint16_t*)fb.getBuffer())[i + j * 320]);
          ((uint16_t*)outfb.getBuffer())[l * 2 + (k * 2 + 1) * 480 + 1] = HTONS(((uint16_t*)fb.getBuffer())[i + j * 320]);
        }
      }
    }
    dsi_lcdDrawImage((void*)outfb.getBuffer(), (void*)dsi_getCurrentFrameBuffer(), 480, 640, IMAGE_MODE == CAMERA_GRAYSCALE ? DMA2D_INPUT_L8 : DMA2D_INPUT_RGB565);
    dsi_drawCurrentFrameBuffer();
  } else {
    blinkLED(20);
  }
}

Audio - VU Meter

I spent some time exploring audio applications of the Giga. It has a microphone in the display and a stereo audio output jack. Both are unusual features for most microcontroller modules, but the Giga has them built-in.

To demonstrate the microphone I am doing a VU Meter, which also shows that the display is lively, although the software slows down the activity to allow human eyes to keep up.

Audio - Spectrum Analyzer

I also wanted to try a spectrum analyzer because the computer has enough speed to calculate FFTs and the display is also fast enough to display the results. This version doesn't try for a detailed spectrum (to reduce my programming time), although the hardware is certainly capable of it. The MCU is a dual core that includes both a 480 MHz ARM Cortex M7 and a 240 MHz ARM Cortex M4, which is about as fast as a decent PC from 1999.

image

Spectrum Analyzer Demo

Spectrum Analyzer Sketch

/*
	Example of an audio spectrum analyzer display using the Arduino Giga Display and its microphone
  by Doug Wong 2025
  This program uses th FFT library by Enrique Condes
*/

#include "Arduino_GigaDisplay.h"
#include "arduinoFFT.h"
#include "Arduino_GigaDisplay_GFX.h"
#include <PDM.h>

GigaDisplay_GFX display; // Create the display object

#define SAMPLES 128           // Number of audio samples
#define BARS 16               // Number of spectrum bars
#define SAMPLING_FREQUENCY 16000  // Hz
#define GREEN 0x07E0
#define BLACK 0x0000
#define BLUE 0x001E

/*
These values can be changed in order to evaluate the functions
*/
const uint16_t samples = 128; //This value MUST ALWAYS be a power of 2
const double signalFrequency = 1000;
const double sampleFrequency = 16000;
static const int frequency = 16000;
static const char channels = 1;
const uint8_t amplitude = 100;
// static const int channels = 1;

short sampleBuffer[samples];

double vReal[samples];      //audio samples array
double vImag[samples];      //spectrum amplitudes
int iBar = SAMPLES / BARS;  //number of data points per bar
int barWidth = 2 * display.width() / BARS;  //number of pixels per bar
int barHeight;
int BarAmp;

int bytesAvailable = 128;   //in microphone buffer

/* Create FFT object */
ArduinoFFT<double> FFT = ArduinoFFT<double>(vReal, vImag, samples, sampleFrequency);

#define SCL_INDEX 0x00
#define SCL_TIME 0x01
#define SCL_FREQUENCY 0x02
#define SCL_PLOT 0x03

void setup()
{
  delay (400);
 // Serial.begin(115200);
  display.begin();
  display.setRotation(1);
  PDM.setBufferSize(samples);
  delay (400);
  display.fillScreen(BLACK);
  PDM.onReceive(onPDMdata);
    // Start PDM microphone
  if (!PDM.begin(channels, frequency)) {
    //Serial.println("Failed to start PDM!");
    while (1)
      ;
  }
}

void loop()
{
//  PDM.read(sampleBuffer, bytesAvailable);
  for (int i = 0; i < SAMPLES; i++) {
    vReal[i] = sampleBuffer[i];
    vImag[i] = 0.0;
  }

  FFT.windowing(vReal, samples, FFT_WIN_TYP_HAMMING, FFT_FORWARD);	/* Weigh data */
  FFT.compute(vReal, vImag, SAMPLES, FFT_FORWARD); /* Compute FFT */
  FFT.complexToMagnitude(vReal, vImag, SAMPLES); /* Compute magnitudes */

// Draw spectrum bars
  for (int i = 0; i < BARS; i++) {
    BarAmp = 0;
    for(int j = 0; j < iBar; j++) {
//       if (BarAmp < vImag[i * BARS + j]) BarAmp = vImag[i * BARS + j];
      BarAmp = BarAmp + vImag[i * BARS + j];    // total amplitude in each bar
    }
    if (i == 8) BarAmp = BarAmp / 20;           // to reduce an anomolously high bar
    BarAmp =  BarAmp / BARS;                    // average amplitube in this bar
    barHeight = BarAmp / 16;                    // adjustment for overall volume
    if (barHeight > display.height()) barHeight = display.height(); 
    display.fillRect(i * barWidth, 0, barWidth - 2, display.height() - barHeight, BLACK);
    display.fillRect(i * barWidth, display.height() - barHeight, barWidth - 2, barHeight, GREEN);
  }
  delay(80);
}

void onPDMdata() {
  // query the number of bytes available
  int bytesAvailable = PDM.available();

  // read into the sample buffer
  int bytesRead = PDM.read(sampleBuffer, bytesAvailable);

  // 16-bit, 2 bytes per sample
  int samplesRead = bytesRead / 2;
}

Audio Output - WAV Player

Now on to audio output. This requires an amplifier and speakers, so I roped in a single chip audio amp I designed a PCB for, and an orphan Logitech speaker I had kicking around.

I had been spoiled by how much was built-in to the Giga, which made this part of the road test seem a bit kluged, including making up some cables. However the audio sounded pretty good considering it uses a 12 bit DAC.

Wav Player Sketch

/*
 * GIGA WAV Player
 * GIGA R1 - Audio Playback of a wav file store on a USB memory device
 * Simple wav format audio playback via 12-Bit DAC output by reading from a USB drive.
 * This sketch assumes the USB memory device is named "USB"
 * This sketch assumes the audio file is named "GIGAchorus.wav"
 * by Doug Wong 2025
*/

#include <Arduino_AdvancedAnalog.h>
#include <DigitalOut.h>
#include <Arduino_USBHostMbed5.h>
#include <FATFileSystem.h>
#include "Arduino_GigaDisplay_GFX.h"

#define WHITE 0xffff
#define BLACK 0x0000
#define YELLOW 0xFFE0
#define CYAN 0x07FFF
#define PURPLE 0x8010

AdvancedDAC dac0(A12);

USBHostMSD msd;
mbed::FATFileSystem usb("USB");

GigaDisplay_GFX display;

FILE * file = nullptr;
int sample_size = 0;
int samples_count = 0;


void setup()
{
  Serial.begin(115200);
  while (!Serial);

  display.begin();
  display.setRotation(1); // Landscape mode
  display.fillScreen(PURPLE);
  display.setTextColor(YELLOW);
  display.setTextSize(8);
  display.setCursor(35, 200);
  display.println("GIGA WAV Player");
  delay(4000);
  display.fillScreen(PURPLE);
  display.setTextSize(4);
  display.setCursor(10, 10);
  display.println("Reading USB...");

  /* Enable power for HOST USB connector. */
  pinMode(PA_15, OUTPUT);
  digitalWrite(PA_15, HIGH);

  if (!msd.connect()) {
    display.println("Insert USB memory device");
    while (!msd.connect()) delay(100);
  }
  display.println("Mounting USB...");
  int const rc_mount = usb.mount(&msd);
  if (rc_mount)
  {
    display.println("Error mounting USB device ");
    display.println(rc_mount);
    return;
  }

  display.println("Opening audio file ...");

  /* 16-bit PCM Mono 16kHz realigned noise reduction */
  file = fopen("/USB/GigaChorus.wav", "rb");
  if (file == nullptr)
  {
    display.print("Error opening audio file: ");
    display.println(strerror(errno));
    return;
  }

  display.println("Reading audio header ...");

  delay(200);
  display.fillScreen(PURPLE);
  display.setTextSize(3);
  display.setCursor(10, 1);

  struct wav_header_t
  {
    char chunkID[4]; //"RIFF" = 0x46464952
    unsigned long chunkSize; //28 [+ sizeof(wExtraFormatBytes) + wExtraFormatBytes] + sum(sizeof(chunk.id) + sizeof(chunk.size) + chunk.size)
    char format[4]; //"WAVE" = 0x45564157
    char subchunk1ID[4]; //"fmt " = 0x20746D66
    unsigned long subchunk1Size; //16 [+ sizeof(wExtraFormatBytes) + wExtraFormatBytes]
    unsigned short audioFormat;
    unsigned short numChannels;
    unsigned long sampleRate;
    unsigned long byteRate;
    unsigned short blockAlign;
    unsigned short bitsPerSample;
  };

  wav_header_t header;
  fread(&header, sizeof(header), 1, file);

  display.println("WAV File Header read:");
  char msg[64] = {0};
  snprintf(msg, sizeof(msg), "File Type: %s", header.chunkID);
  display.println(msg);
  snprintf(msg, sizeof(msg), "File Size: %ld", header.chunkSize);
  display.println(msg);
  snprintf(msg, sizeof(msg), "WAV Marker: %s", header.format);
  display.println(msg);
  snprintf(msg, sizeof(msg), "Format Name: %s", header.subchunk1ID);
  display.println(msg);
  snprintf(msg, sizeof(msg), "Format Length: %ld", header.subchunk1Size);
  display.println(msg);
  snprintf(msg, sizeof(msg), "Format Type: %hd", header.audioFormat);
  display.println(msg);
  snprintf(msg, sizeof(msg), "Number of Channels: %hd", header.numChannels);
  display.println(msg);
  snprintf(msg, sizeof(msg), "Sample Rate: %ld", header.sampleRate);
  display.println(msg);
  snprintf(msg, sizeof(msg), "Sample Rate * Bits/Sample * Channels / 8: %ld", header.byteRate);
  display.println(msg);
  snprintf(msg, sizeof(msg), "Bits per Sample * Channels / 8: %hd", header.blockAlign);
  display.println(msg);
  snprintf(msg, sizeof(msg), "Bits per Sample: %hd", header.bitsPerSample);
  display.println(msg);

  /* Find the data section of the WAV file. */
  struct chunk_t
  {
    char ID[4];
    unsigned long size;
  };

  chunk_t chunk;
  snprintf(msg, sizeof(msg), "id\t" "size");
  display.println(msg);
  /* Find data chunk. */
  while (true)
  {
    fread(&chunk, sizeof(chunk), 1, file);
    snprintf(msg, sizeof(msg), "%c%c%c%c\t" "%li", chunk.ID[0], chunk.ID[1], chunk.ID[2], chunk.ID[3], chunk.size);
    display.println(msg);
    if (*(unsigned int *) &chunk.ID == 0x61746164)
      break;
    /* Skip chunk data bytes. */
    fseek(file, chunk.size, SEEK_CUR);
  }

  /* Determine number of samples. */
  sample_size = header.bitsPerSample / 8;
  samples_count = chunk.size * 8 / header.bitsPerSample;
  snprintf(msg, sizeof(msg), "Sample size = %i", sample_size); display.println(msg);
  snprintf(msg, sizeof(msg), "Samples count = %i", samples_count); display.println(msg);

  /* Configure the advanced DAC. */
  if (!dac0.begin(AN_RESOLUTION_12, header.sampleRate * 2, 256, 16))
  {
    display.println("Failed to start DAC1 !");
    return;
  }

  delay(1500);
  display.fillScreen(PURPLE);
  display.setCursor(50, 50);
  display.setTextSize(5);
  display.println("Playing ......");
  display.setCursor(50, 200);
  display.setTextSize(7);
  display.println("GIGAchorus.WAV");
}

void loop()
{
  if (dac0.available() && !feof(file))
  {
    /* Read data from file. */
    uint16_t sample_data[256] = {0};
    fread(sample_data, sample_size, 256, file);

    /* Get a free buffer for writing. */
    SampleBuffer buf = dac0.dequeue();

    /* Write data to buffer. */
    for (size_t i = 0; i < buf.size(); i++)
    {
      /* Scale down to 12 bit. */
      uint16_t const dac_val = ((static_cast<unsigned int>(sample_data[i])+32768)>>4) & 0x0fff;
      buf[i] = dac_val;
    }

    /* Write the buffer to DAC. */
    dac0.write(buf);
  }
}

Touch HID Keyboard

When I realized the Giga had a USB port capable of implementing a HID protocol, I had to try and implement a full HID QWERTY touch keyboard.

image

Touch Keyboard Demo

HID Keyboard Sketch

/*
HID Keyboard using arduino Giga Touch Display
by Doug Wong
2025
*/

//#include "ArduinoGraphics.h"
#include "PluggableUSBHID.h"
#include "USBKeyboard.h"
#include "Arduino_GigaDisplayTouch.h"
#include "Arduino_GigaDisplay_GFX.h"
#include "incbin.h"

#define BLACK 0x0000
#define WHITE 0xFFFF
#define RED 0xF800
#define YELLOW 0xFFE0
#define PURPLE 0xFB00
#define BLUE 0x001F

USBKeyboard Keyboard;
GigaDisplay_GFX display;
Arduino_GigaDisplayTouch touchDetector;

int ROW;
int COL;
int KEY;
char qwerty [41] = "1234567890QWERTYUIOPASDFGHJKL\nZXCVBNM .\b";   //keyboard characters

void setup() {
  display.begin();
  display.fillScreen(WHITE);
  display.setRotation(1);
  display.setTextSize(5);
  display.setTextColor(BLUE);
  display.setCursor(170, 10);
  display.print("GIGA HID KEYBOARD");
  display.setTextColor(BLACK);
  for (int i = 160; i <= 480; i+= 80) {   //draw hoizontal lines
  display.fillRect(0, i, 800, 2, BLUE);
  }
  for (int j = 0; j < 800; j+= 80) {      //draw vertical lines and display the keyboard characters
  display.fillRect(j, 160, 2, 320, BLUE);
  KEY = j / 80;
  display.setCursor(j+30, 180);
  display.print(qwerty[KEY]);
  KEY = j / 80 + 10;
  display.setCursor(j+30, 260);
  display.print(qwerty[KEY]);
  KEY = j / 80 + 20;
  display.setCursor(j+30, 340);
  display.print(qwerty[KEY]);
  KEY = j / 80 + 30;
  display.setCursor(j+30, 420);
  display.print(qwerty[KEY]);
  }

  display.setCursor(20, 60);        // set up to display which key was touched
  display.setTextColor(RED); // red text
  touchDetector.begin();
}

void loop() {
  uint8_t contacts;
  GDTpoint_t points[5];
  contacts = touchDetector.getTouchPoints(points);      //read the touch screen

  if (contacts > 0) {           // figure out the keaboard row that was touched
    if (points[0].x < 320) {
      if (points[0].x < 80) {
        ROW = 3;
    } else if (points[0].x < 160) {
        ROW = 2;
      } else if (points[0].x < 240) {
        ROW = 1;
    } else {
      ROW = 0;
    }
    if (points[0].y < 80) COL = 0;    // figure out the keaboard column that was touched
    else if (points[0].y < 160) COL = 1;
    else if (points[0].y < 240) COL = 2;
    else if (points[0].y < 320) COL = 3;
    else if (points[0].y < 400) COL = 4;
    else if (points[0].y < 480) COL = 5;
    else if (points[0].y < 560) COL = 6;
    else if (points[0].y < 640) COL = 7;
    else if (points[0].y < 720) COL = 8;
    else COL = 9;
    KEY = (ROW * 10 + COL);       // calculate which key was touched
    display.fillRect(0, 30, 100, 80, 0xFFFF);
    display.setCursor(20, 60);
    display.print(qwerty[KEY]);
    char tempCharString[2];
    tempCharString[0] = qwerty[KEY];
    tempCharString[1] = '\0';
    Keyboard.printf("%s", tempCharString);
  }

  for (int x = 0; x < 10; x++) {       //debounce touch
     contacts = touchDetector.getTouchPoints(points);
     if (contacts > 0) {
      x = 0;}   //you may want to time out to avoid a potential endless loop
     delay(10);
  }
  delay(100);
  contacts = 0;
 }
}

Giga MQTT Client

I have wanted to demonstrate a wireless application and this allowed me to explore the idea of using MQTT as a text messaging system. The idea is for users to publish and subscribe to a topic of mutual interest - forming an interest group that can text each other without needing a cell phone.

The Arduino GIGA with its display is an excellent platform to implement this functionality. It helped that I had already implemented a touch keyboard. Note the crude reflector on the right that allows me to see what the LED is doing.

image

 MQTT Demo

You will want to head over to the separate blog I did on this application for a more complete explanation of how to implement this capability.

MQTT Client Sketch

//Arduino GIGA MQTT client example
//Touch keyboard publishes text messages to an MQTT broker
//It also subscribes to the a text chat topic
//It also subscribes to the LED topic, which controls the GIGA LED
//by Doug Wong 2025

#include "PluggableUSBHID.h"
#include "USBKeyboard.h"
#include "Arduino_GigaDisplayTouch.h"
#include "Arduino_GigaDisplay_GFX.h"
#include <WiFi.h>
#include <PubSubClient.h>
#include "creds.h"  // the following commented out code is included in creds.h

/* replace the capitallized text (only) with your own info & uncomment
const char* ssid = "WIFIACCESSPOINTNAME";
const char* pass = "ACCESSPOINTPASSWORD";
const char* username = "MQTTUSERNAME";
const char* password = "MQTTPASSWORD";
const char* mqtt_server = "MQTTBROKERURL";

const char* topic0 = "MQTTUSERNAME/feeds/TOPICNAME";
const char* topic1 = "MQTTUSERNAME/feeds/TOPICNAME";
*/

#define BLACK 0x0000
#define WHITE 0xFFFF
#define RED 0xF800
#define YELLOW 0xFFE0
#define PURPLE 0xFB00
#define BLUE 0x001F

USBKeyboard Keyboard;
GigaDisplay_GFX display;
Arduino_GigaDisplayTouch touchDetector;

WiFiClient espClient;
PubSubClient client(espClient);

int ROW;                //row number of the key being touched
int COL;                //column number of the key being touched
int KEY;                // KEY character index for the qwerty array
int curp = 0;           //cursor position on the display
char qwerty [41] = "1234567890QWERTYUIOPASDFGHJKL\nZXCVBNM .\b";   //keyboard characters
char mtxt [20];         //message txt to publish
bool sendt = false;     //publish flag to send text message
bool sendf = false;     //send flag to deal with broker echo
bool txt = false;       //flag to determine if the received message is text or LED

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);     // Initialize the LED_BUILTIN pin as an output
  Serial.begin(115200);
  setup_wifi();
  client.setServer(mqtt_server, 1883);
  client.setCallback(callback);

  display.begin();
  display.fillScreen(WHITE);
  display.setRotation(1);             //set landscape orientation
  display.setTextSize(5);
  display.setTextColor(BLUE);
  display.setCursor(170, 6);
  display.print("GIGA MQTT CLIENT");
  display.setTextColor(BLACK);
  for (int i = 160; i <= 480; i+= 80) {   //draw hoizontal lines for the keyboard
  display.fillRect(0, i, 800, 2, BLUE);
  }
  for (int j = 0; j < 800; j+= 80) {      //draw vertical lines and display the keyboard characters
  display.fillRect(j, 160, 2, 320, BLUE);
  KEY = j / 80;
  display.setCursor(j+30, 180);
  display.print(qwerty[KEY]);
  KEY = j / 80 + 10;
  display.setCursor(j+30, 260);
  display.print(qwerty[KEY]);
  KEY = j / 80 + 20;
  display.setCursor(j+30, 340);
  display.print(qwerty[KEY]);
  KEY = j / 80 + 30;
  display.setCursor(j+30, 420);
  display.print(qwerty[KEY]);
  }

  display.setTextColor(RED); // red text
  touchDetector.begin();

  mtxt[19] = '\0';          //null character in case we want to use this array as a string
}

void setup_wifi() {

  delay(1500);
  //start by connecting to a WiFi network
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.begin(ssid, pass);

  while (WiFi.status() != WL_CONNECTED) {
    delay(800);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}

void callback(char* topic, byte* payload, unsigned int length) { //when a message is received from a broker
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.println();

   char lc = topic[strlen(topic) -1];
  if (lc == 't') txt = true;                  //look at the last character of the topic to determine what to do with the payload
  else txt = false;
  if (!txt && (char)payload[0] == '1') {      //if it is not a text message, use the payload to control the LED
    digitalWrite(LED_BUILTIN, LOW);   // Turn the LED on
  } else if (!txt && (char)payload[0] == '0') {
    digitalWrite(LED_BUILTIN, HIGH);  // Turn the LED off
  }
  if (txt) {
    if (sendf) {                              //if it is a txt message echo from a published message, display it in the transmit line
      sendf = false;
      display.fillRect(0, 105, 799, 50, 0xFFD0);
      display.setCursor(10, 114);
      for (int i = 0; i < length; i++) {
        display.print((char)payload[i]);
      }
    }
    else {                                    //if it is a fresh txt message, display it in the receive line
      display.fillRect(0, 50, 799, 50, 0x0FFF);
      display.setCursor(10, 59);
      for (int i = 0; i < length; i++) {
        display.print((char)payload[i]);
      }
      
    }
  }

}

void reconnect() {              // connect or reconnect to the MQTT broker  

  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Create a random client ID
    String clientId = "ArduinoClient-";
    clientId += String(random(0xffff), HEX);
    // Attempt to connect
    if (client.connect(clientId.c_str(), username, password)) {
      Serial.println("connected");
      // Once connected, publish an announcement...
      //client.publish(topic0, "hello world");
      // ... and resubscribe
      client.subscribe(topic0);
      client.subscribe(topic1);
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 8 seconds");
      // Wait 10 seconds before retrying
      delay(2000);
    }
    delay(1000);
  }
}

void loop() {                 //loop to read keyboard touches and display text
  uint8_t contacts;
  GDTpoint_t points[5];

  if (!client.connected()) {
    reconnect();
  }

  client.loop();

  if (sendt) {
    sendt = false;
    sendf = true;
    Serial.print("Publish message: ");
    Serial.println(mtxt);
    client.publish(topic0, mtxt);
  }

 contacts = touchDetector.getTouchPoints(points);      //read the touch screen
  if (contacts > 0) {           // figure out the keaboard row that was touched
  if (points[0].x > 320) {
    points[0].x = 400;
    sendt = true;
    display.fillRect(0, 105, 799, 50, 0xFFEE);
    display.setCursor(10, 114);
    display.print(mtxt);
    }

  else if (points[0].x < 320) {
      if (points[0].x < 80) {
        ROW = 3;
    } else if (points[0].x < 160) {
        ROW = 2;
      } else if (points[0].x < 240) {
        ROW = 1;
    } else {
      ROW = 0;
    }
    if (points[0].y < 80) COL = 0;    // figure out the keaboard column that was touched
    else if (points[0].y < 160) COL = 1;
    else if (points[0].y < 240) COL = 2;
    else if (points[0].y < 320) COL = 3;
    else if (points[0].y < 400) COL = 4;
    else if (points[0].y < 480) COL = 5;
    else if (points[0].y < 560) COL = 6;
    else if (points[0].y < 640) COL = 7;
    else if (points[0].y < 720) COL = 8;
    else COL = 9;
    KEY = (ROW * 10 + COL);       // calculate which key was touched
    if (KEY == 39) {
      curp--;
      mtxt[curp] = 32;
      if (curp < 0) curp = 0;
    }
    else {
      mtxt[curp] = qwerty[KEY];
      curp++;
      if (curp > 18) curp = 18;
    }
      display.fillRect(0, 105, 799, 50, 0xFFE0);
      display.setCursor(10, 114);
      display.print(mtxt);
      display.setCursor(760, 114);
      display.print(">");

  }

  for (int x = 0; x < 10; x++) {       //debounce touch
     contacts = touchDetector.getTouchPoints(points);
     if (contacts > 0) {
      x = 0;}   //you may want to time out to avoid a potential endless loop
     delay(10);
  }
  delay(100);
  contacts = 0;
 }
}

Giga Paint

I wanted to include an application that is a fun, creative tool and a finger paint program fills that requirement.

image

Giga Paint Demo

Giga Paint Sketch

// Giga Paint
//uses Arduino Giga and Giga Touch Display
// by Doug Wong 2025

#include "Arduino_GigaDisplay_GFX.h"
#include "Arduino_GigaDisplayTouch.h"

// --- Global Objects ---
// Create the GFX object for drawing
GigaDisplay_GFX display;
// Create the Touch object for reading touch input
Arduino_GigaDisplayTouch touch;

// --- Configuration Constants ---
// Use the display object to get dimensions
#define SCREEN_WIDTH  480  // GFX width is 480 (portrait by default)
#define SCREEN_HEIGHT 800  // GFX height is 800

// --- Palette and Control Bar Layout ---
const int CONTROL_BAR_HEIGHT = 100;
const int DRAWING_AREA_START_Y = CONTROL_BAR_HEIGHT;
const int PALETTE_COUNT = 5;
const int NIB_SIZES_COUNT = 4;
const int ERASER_INDEX = PALETTE_COUNT; // Index for the Eraser button
const int NIB_SELECTOR_START_X = 350;

// --- Colors and State ---
// Colors are defined in 16-bit RGB565 format (used by GFX)
#define RED     0xF800
#define GREEN   0x07E0
#define BLUE    0x001F
#define YELLOW  0xFFE0
#define MAGENTA 0xF81F
#define BLACK   0x0000
#define WHITE   0xFFFF
#define GREY    0x8410 // For control bar background

uint16_t colors[PALETTE_COUNT] = {RED, GREEN, BLUE, YELLOW, MAGENTA};
uint16_t currentColor = colors[0]; // Start with Red
uint16_t currentNibSize = 5;       // Start with a small size
int nibSizes[NIB_SIZES_COUNT] = {5, 10, 15, 20}; // Available nib sizes
bool isDrawing = false;
int lastX = -1;
int lastY = -1;

// --- Palette/Control Button Structure ---
const int PALETTE_BUTTON_WIDTH = (NIB_SELECTOR_START_X) / (PALETTE_COUNT + 1); // +1 for the eraser
struct ControlButton {
    int x;
    int y;
    int w;
    int h;
    uint16_t color;
    const char* label;
};
ControlButton paletteButtons[PALETTE_COUNT + 1]; // 5 colors + 1 eraser

void setup() {
    // Initialize Display and Touch
    display.begin();
    touch.begin();

    // Set default rotation (0 = portrait)
    display.setRotation(0); 
    
    // Fill the screen with white to be the 'canvas'
    display.fillScreen(WHITE);

    // Initialize the control buttons array
    for (int i = 0; i < PALETTE_COUNT; i++) {
        paletteButtons[i] = {
            i * PALETTE_BUTTON_WIDTH, 
            0, 
            PALETTE_BUTTON_WIDTH, 
            CONTROL_BAR_HEIGHT, 
            colors[i], 
            ""
        };
    }
    // Add the Eraser button
    paletteButtons[ERASER_INDEX] = {
        PALETTE_COUNT * PALETTE_BUTTON_WIDTH, 
        0, 
        PALETTE_BUTTON_WIDTH, 
        CONTROL_BAR_HEIGHT, 
        BLACK, 
        "X"
    };

    drawControlBar();
}

// --- Drawing Functions ---

void drawControlBar() {
    // Draw the background of the control bar
    display.fillRect(0, 0, SCREEN_WIDTH, CONTROL_BAR_HEIGHT, GREY);

    // Draw Palette Buttons 
    for (int i = 0; i <= PALETTE_COUNT; i++) {
        ControlButton btn = paletteButtons[i];
        
        // Draw the main button area
        display.fillRect(btn.x + 2, btn.y + 2, btn.w - 4, btn.h - 4, btn.color);

        // Draw the label for the eraser
        if (i == ERASER_INDEX) {
            display.setTextColor(WHITE);
            display.setTextSize(2);
            // Move cursor to center the text
            display.setCursor(btn.x + 10, btn.y + 35); 
            display.print(btn.label);
        }
    }
    
    // Draw Nib Selector area
    drawNibSelector();
}

void drawNibSelector() {
    display.fillRect(NIB_SELECTOR_START_X, 0, SCREEN_WIDTH - NIB_SELECTOR_START_X, CONTROL_BAR_HEIGHT, GREY);

    int nibAreaWidth = SCREEN_WIDTH - NIB_SELECTOR_START_X;
    int nibButtonWidth = nibAreaWidth / NIB_SIZES_COUNT;

    for (int i = 0; i < NIB_SIZES_COUNT; i++) {
        int x_pos = NIB_SELECTOR_START_X + i * nibButtonWidth;
        int y_center = CONTROL_BAR_HEIGHT / 2;
        int radius = nibSizes[i] / 2;
        int btn_margin = 1;
        
        // Draw button border
        display.drawRect(x_pos + btn_margin, btn_margin, nibButtonWidth - 2*btn_margin, CONTROL_BAR_HEIGHT - 2*btn_margin, WHITE);

        // Draw the nib sample circle
        display.fillCircle(x_pos + nibButtonWidth / 2, y_center, radius, BLACK);
        
        // Highlight the currently selected size
        if (currentNibSize == nibSizes[i]) {
            // Draw a selection indicator (e.g., a thick green border around the button)
            display.drawRect(x_pos, 0, nibButtonWidth, CONTROL_BAR_HEIGHT, GREEN);
            display.drawRect(x_pos + 1, 1, nibButtonWidth - 2, CONTROL_BAR_HEIGHT - 2, GREEN);
        }
    }
}

// --- Touch Handling ---

void handleTouch(int x, int y) {
    // 1. Check if touch is in the Control Bar area
    if (y < CONTROL_BAR_HEIGHT) {
        // --- Palette and Eraser Selection ---
        for (int i = 0; i <= PALETTE_COUNT; i++) {
            ControlButton btn = paletteButtons[i];
            if (x >= btn.x && x < (btn.x + btn.w)) {
                if (i < PALETTE_COUNT) {
                    // Color selection
                    currentColor = colors[i];
                    currentNibSize = 5; // Reset nib size to default
                } else {
                    // Eraser selection: Set color to the canvas color (White)
                    currentColor = WHITE; 
                    currentNibSize = nibSizes[NIB_SIZES_COUNT - 1]; // Max size for eraser
                }
                drawControlBar(); // Redraw to update selection highlights
                return;
            }
        }
        
        // --- Nib Size Selection ---
        if (x >= NIB_SELECTOR_START_X) {
            int nibAreaWidth = SCREEN_WIDTH - NIB_SELECTOR_START_X;
            int nibButtonWidth = nibAreaWidth / NIB_SIZES_COUNT;
            
            for (int i = 0; i < NIB_SIZES_COUNT; i++) {
                int x_pos = NIB_SELECTOR_START_X + i * nibButtonWidth;
                if (x >= x_pos && x < (x_pos + nibButtonWidth)) {
                    // Prevent changing nib size when eraser is selected
                    if (currentColor != WHITE) {
                         currentNibSize = nibSizes[i];
                    }
                    drawNibSelector(); // Redraw only the nib section
                    return;
                }
            }
        }
    } 
    // 2. Touch is in the Drawing Area
    else { 
        // We're drawing, update the canvas
        int radius = currentNibSize / 2;
        
        display.fillCircle(x, y, radius, currentColor); // Draw a dot at the current point

        // Draw a line connecting the last point to the current point for smooth drawing
        if (isDrawing && lastX != -1 && lastY != -1) {
            display.drawLine(lastX, lastY, x, y, currentColor);
            // Draw a second, slightly offset line to increase thickness (since GFX's drawLine is only 1 pixel wide by default)
            // For a true thick line, you would need a more advanced algorithm, but this is a simple approximation.
            if (currentNibSize > 5) {
                // Approximate thick line: draw 2-3 lines next to each other
                display.drawLine(lastX + 1, lastY, x + 1, y, currentColor);
                display.drawLine(lastX - 1, lastY, x - 1, y, currentColor);
            }
        }

        // Update the last position
        lastX = x;
        lastY = y;
        isDrawing = true;
    }
}

void loop() {
    // Check if the screen is touched
    uint8_t contacts;
    GDTpoint_t points[5];
    contacts = touch.getTouchPoints(points);      //read the touch screen
    int touch_x = points[0].x;
    int touch_y = points[0].y;
    handleTouch(touch_x, touch_y);
    // Touch is released, reset drawing state
    isDrawing = false;


    // Small delay to prevent reading the touch sensor too quickly
    delay(5);
}

Giga Oscilloscope

Finally, I wanted to make an application the used the ADC and an oscilloscope is a great application that shows how fast the display is as well as exploring the ADC.

image

Giga Scope Demo

The video doesn't look as good as the in-person experience, but hopefully it came out well enough to get a feel for what it looks like in real life.

Scope Sketch

/*
  Giga Display Oscilloscope
  by Doug Wong 2025
  - Continuous plotting of ADC (A0) on 800x480 Landscape.
  - Scanline refresh (Erase old pixel -> Draw new pixel).
  - Touch Controls:
    1. Bottom (0-700px, H=50): Sets Delay (0-100us)
    2. Right (W=50, H=480): Sets Gain (0.1x - 1.0x)
    3. Leftt (W=50, H=480): Sets Trigger (0 - 480)
    Touch the screen to update signal trace - stop touching to freeze the trace
*/

#include "Arduino_GigaDisplay_GFX.h"
#include "Arduino_H7_Video.h"
#include "Arduino_GigaDisplayTouch.h"

GigaDisplay_GFX display;

// --- Configuration ---
#define SCREEN_WIDTH  800
#define SCREEN_HEIGHT 480
#define ADC_PIN       A0

// UI Dimensions
#define BOTTOM_TOUCH_WIDTH 700
#define BOTTOM_TOUCH_HEIGHT 50
#define RIGHT_TOUCH_WIDTH  50
#define RIGHT_TOUCH_HEIGHT 480

// Colors
#define COLOR_BG    0x0000 // Black
#define COLOR_TRACE 0x07E0 // Green
#define COLOR_TEXT  0xFFFF // White
#define COLOR_UI    0x3333 // Dark Grey (for UI guides)

// --- Objects ---
Arduino_H7_Video Display(800, 480, GigaDisplayShield);
Arduino_GigaDisplayTouch Touch;

// --- Global Variables ---
uint16_t signalBuffer[SCREEN_WIDTH]; // Stores Y-coordinates of the previous scan
int currentX = 0;
int rawADC;
int tADC;
int lastADC;

// Oscilloscope Settings
unsigned long sampleDelayMicros = 0; // 0 to 130 microseconds
float gain = 0.1;                    // 0.1 to 1.0
int gaini;
int trigger = 200;

// Helper to track previous touch state to avoid flickering UI updates
int lastTouchZoneDelay = -1;
int lastTouchZoneGain = -1;
uint8_t contacts;
GDTpoint_t points[5];

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);     // Initialize the LED_BUILTIN pin as an output
  // Initialize Serial (Optional for debug)
  Serial.begin(115200);

  // Initialize Display
  Display.begin();
  display.begin();
  display.setRotation(1);
  display.fillScreen(COLOR_BG);

  // Initialize Touch
  Touch.begin();

  // Configure ADC
  analogReadResolution(12); // 0-4095
  pinMode(ADC_PIN, INPUT);

  // Initialize Buffer (Center screen initially)
  for (int i = 0; i < SCREEN_WIDTH; i++) {
    signalBuffer[i] = SCREEN_HEIGHT / 2;
  }
  
  // Draw UI Guidelines (Optional: Visual indicators for touch areas)
  gaini = gain * 10;
//  drawInterfaceGuides();
  updateStatsOnScreen();
}

void loop() {
  // 1. Handle Touch Input (Check strictly before sampling to adjust params)
  handleTouch();

//  tADC = analogRead(ADC_PIN);
//  if (tADC > lastADC && tADC > trigger) {
 for (int X = 0; X < SCREEN_WIDTH; X++) {

  // Perform Trace Update

  // 2. Capture New Sample
  rawADC = analogRead(ADC_PIN);

  // 3. Erase the pixel from the previous scan at this X position
  display.drawPixel(X, signalBuffer[X], COLOR_BG);

  // 4. Calculate Logic
  int newY = SCREEN_HEIGHT - round(rawADC * gain);
  
  // 5. Draw New Pixel
  display.drawPixel(X, newY, COLOR_TRACE);

  // 6. Update Buffer
  signalBuffer[X] = newY;

  // 7. Apply Sample Delay
  if (sampleDelayMicros > 0) {
    delayMicroseconds(sampleDelayMicros);
  }
 }
//}
//  lastADC = tADC;
//  display.fillRect(0, 20, 2, 2, COLOR_BG);     //need to isplay something for the drawPixel activity to be displayed
}

void handleTouch() {

  // Read touch points
  contacts = Touch.getTouchPoints(points);

  if (contacts > 0) {
    int ty = SCREEN_HEIGHT - points[0].x;
    int tx = points[0].y;

    // --- Check Bottom Area (Delay Control) ---
    // Area: x[0-700], y[430-480] (Bottom 50px)
    if (tx > 50 && tx < BOTTOM_TOUCH_WIDTH && ty > (SCREEN_HEIGHT - BOTTOM_TOUCH_HEIGHT)) {
      // 14 Zones, 50px wide each. 
      int zone = (tx - 50) / 50; 
      if (zone > 12) zone = 12;

      // Map zone 0-13 to 0-56 microseconds (step of 4)
      sampleDelayMicros = zone * 10;
    }

    // --- Check Right Area (Gain Control) ---
    // Area: x[750-800], y[0-480]
    else if (tx >= (SCREEN_WIDTH - RIGHT_TOUCH_WIDTH)) {
      // 12 Zones, 40px high each.
      int zone = (SCREEN_HEIGHT - ty) / 40;
      if (zone > 11) zone = 11;

      // Map zone 0-11 to Gain 0.1 - 1.0
      // We have 12 steps to cover range 0.9. 
      // Step size approx 0.081. 
      // Simple linear mapping: 0.1 + (zone / 11.0) * 0.9
      gain = 0.1 + ((float)zone / 11.0) * 0.9;
    }

        // --- Check Leftt Area (Trigger Control) ---
    // Area: x[0-50], y[0-480]
    else if (tx < (50)) {

      trigger = SCREEN_HEIGHT - points[0].x;
    }
      updateStatsOnScreen();
  }
}

// Draws visual markers so you know where to touch
void drawInterfaceGuides() {
    // Divider for Bottom Area
    display.drawLine(0, SCREEN_HEIGHT - BOTTOM_TOUCH_HEIGHT, BOTTOM_TOUCH_WIDTH, SCREEN_HEIGHT - BOTTOM_TOUCH_HEIGHT, COLOR_UI);
    // Ticks for Bottom Area
    for(int i=0; i<=14; i++) {
        display.drawLine(i*50, SCREEN_HEIGHT - BOTTOM_TOUCH_HEIGHT, i*50, SCREEN_HEIGHT, COLOR_UI);
    }

    // Divider for Right Area
    display.drawLine(SCREEN_WIDTH - RIGHT_TOUCH_WIDTH, 0, SCREEN_WIDTH - RIGHT_TOUCH_WIDTH, SCREEN_HEIGHT, COLOR_UI);
    // Ticks for Right Area
    for(int i=0; i<=12; i++) {
        display.drawLine(SCREEN_WIDTH - RIGHT_TOUCH_WIDTH, i*40, SCREEN_WIDTH, i*40, COLOR_UI);
    }
}

// Displays current settings in top left corner
void updateStatsOnScreen() {
    // Simple text overlay to show current settings
    // We draw a black box first to erase old text
    display.fillRect(0, 0, 300, 20, COLOR_BG); 
    
    display.setCursor(0, 0);
    display.setTextSize(2);
    display.setTextColor(COLOR_UI);
    
    display.print("Delay: ");
    display.print(sampleDelayMicros);
    display.print("us | Gain: ");
    gaini = (1.1 - gain) * 10;
    display.print(gaini);
}

Discussion

I had a blast road testing the Arduino Giga with its touch LCD. I was absolutely tickled at how easy it was to implement sophisticated applications using built-in hardware. That isn't to say everything went smoothly and quickly, but problems were mostly to do with me, not the support materials available.

The example code and libraries available from Arduino and online were a big help in developing applications that went a bit beyond what was available.

The feature set of the Giga platform is so extensive, I only just scratched the surface of what it can do. I didn't even get around to using the accelerometers on the LCD module. Actually I had planned to use them to lock the camera video to gravity, but since the camera is mounted to the display, the video is always upright, regardless of camera tilt. For some reason, it wasn't obvious to me until I saw it in action.

Another feature I didn't get to was Bluetooth low energy. My main computer and other Arduino modules do not do BLE, so I gave this one a pass. This road test blog is already too long, but it would be hard to cover this many features in a shorter review. Other things I didn't push into were the large number of I/O pins, the dual core architecture and the large memory. This review required lots of small programs, not one huge program - thankfully.

I did post individual blogs about each of the applications I explored, which may help them be searchable. If you want a little more detail, they are linked below.

In summary, this Giga plus LCD platform is the only platform with this many high-end microcontroller features. It isn't super cheap, but it is good value and it can handle pretty much any microcontroller task with ease. It is actually a full computer platform, with a complete user interface built-in. I could see it being used as a stand-alone system that is used to develop its own software - maybe with a Python IDE. Presumably a USB keyboard could be plugged in to avoid having to use the touch screen as a keyboard.

I really like the fact that the Giga has tons of I/O and I especially like that many peripheral interfaces have their own standard connector, such as camera, LCD, audio and even antenna. The USB host functionality adds a whole world of possibilities and the capacitive touch screen provides a very potent user interface. However, if I had to choose the one feature that surprised or impressed me the most out of all the superlative features, I would say the speed with which it can load and display a 768 kbyte full color image was a total surprise, but a very useful and pleasant one.

I have to say it is both a super powerful and a super fun platform and I am certain I will find lots of uses for it. I am so happy and grateful that I got to participate in this road test. Thanks e14 and Arduino.

Links:

Unboxing and display demo

Touch Screen and USB memory demo

GIGA display of an Arducam video camera

GIGA VU Meter and Spectrum Analyzer

GIGA WAV Player

GIGA HID Touch Keyboard

GIGA MQTT Client

Giga Paint

Giga Scope

Final Giga Road Test Blog

Giga R1 Road Test page

Anonymous