Qwiic Digital Desk Sign with MicroMod

Pages
Contributors: bboyho
Favorited Favorite 2

Introduction

"Where's Bobby?" A question that comes up when at work. While I'm usually at my desk, there are times that I need to walk away for lunch, take a 15-minute break, head into a meeting, or check inventory. To help notify others of where I may be, I made the Qwiic-enabled digital desk sign using the SAMD51's USB host and a USB keyboard to type short custom messages while I am away!

Qwiic Digital Desk Sign with MicroMod

Required Materials

To follow along with this tutorial, you will need the following materials. You may not need everything though depending on what you have. Add it to your cart, read through the guide, and adjust the cart as necessary.

DC Barrel Jack Adapter - Female

DC Barrel Jack Adapter - Female

PRT-10288
$3.50
1
SparkFun 20x4 SerLCD - RGB Backlight (Qwiic)

SparkFun 20x4 SerLCD - RGB Backlight (Qwiic)

LCD-16398
$26.95
9
Wall Adapter Power Supply - 5VDC, 2A (Barrel Jack)

Wall Adapter Power Supply - 5VDC, 2A (Barrel Jack)

TOL-15312
$6.50
2
SparkFun USB Type A Female Breakout

SparkFun USB Type A Female Breakout

BOB-12700
$4.95
6
SparkFun MicroMod SAMD51 Processor

SparkFun MicroMod SAMD51 Processor

DEV-16791
$18.95
1
Qwiic Cable - 100mm

Qwiic Cable - 100mm

PRT-14427
$1.50
Reversible USB A to C Cable - 0.3m

Reversible USB A to C Cable - 0.3m

CAB-15426
$4.50
1
SparkFun MicroMod ATP Carrier Board

SparkFun MicroMod ATP Carrier Board

DEV-16885
$19.95
1

You Will Also Need

You will also need a USB keyboard. The following keyboards were tested.

  • Dell KB216t
  • Logitech K120

Tools

You will need a soldering iron, solder, and general soldering accessories.

Break Away Headers - Straight

Break Away Headers - Straight

PRT-00116
$1.75
20
Female Headers

Female Headers

PRT-00115
$1.75
8
Soldering Iron - 60W (Adjustable Temperature)

Soldering Iron - 60W (Adjustable Temperature)

TOL-14456
$16.50
16
SparkFun Mini Screwdriver

SparkFun Mini Screwdriver

TOL-09146
$1.05
3
Solder Lead Free - 15-gram Tube

Solder Lead Free - 15-gram Tube

TOL-09163
$3.95
4
Hook-up Wire - Black (22 AWG)

Hook-up Wire - Black (22 AWG)

PRT-08022
$2.95
1
Wire Strippers - 20-30AWG

Wire Strippers - 20-30AWG

TOL-15220
$12.95
1
Hook-up Wire - Yellow (22 AWG)

Hook-up Wire - Yellow (22 AWG)

PRT-08024
$2.95

Suggested Reading

If you aren't familiar with the MicroMod ecosystem, we recommend reading here for an overview. We recommend reading here for an overview if you decide to take advantage of the Qwiic connector.

MicroMod Logo Qwiic Connect System
MicroMod EcosystemQwiic Connect System

We also recommend checking out these tutorials before continuing. If you are using a Qwiic PIR motion sensor with the project, you could also look at its associated tutorial.

Installing an Arduino Library

How do I install a custom Arduino library? It's easy! This tutorial will go over how to install an Arduino library using the Arduino Library Manager. For libraries not linked with the Arduino IDE, we will also go over manually installing an Arduino library.

Installing Arduino IDE

A step-by-step guide to installing and testing the Arduino software on Windows, Mac, and Linux.

I2C

An introduction to I2C, one of the main embedded communications protocols in use today.

AVR-Based Serial Enabled LCDs Hookup Guide

The AVR-based Qwiic Serial Enabled LCDs are a simple and cost effective solution to include in your project. These screens are based on the HD44780 controller, and include ATmega328P with an Arduino compatible bootloader. They accept control commands via Serial, SPI and I2C (via PTH headers or Qwiic connector). In this tutorial, we will show examples of a simple setup and go through each communication option.

MicroMod SAMD51 Processor Board Hookup Guide

This tutorial covers the basic functionality of the MicroMod SAMD51 and highlights the features of the ARM Cortex-M4F development board.

MicroMod All The Pins (ATP) Carrier Board

Access All The Pins (ATP) of the MicroMod Processor Board with the Carrier Board!

Qwiic PIR Hookup Guide

Get started passively monitoring motion using the Panasonic EKMC and EKMB sensors with the SparkFun Qwiic PIR.

Hardware Assembly

If you have not already, make sure to check out the Getting Started with MicroMod: Hardware Hookup for information on inserting your Processor Board to your Carrier Board.

Getting Started with MicroMod

October 21, 2020

Dive into the world of MicroMod - a compact interface to connect a microcontroller to various peripherals via the M.2 Connector!

Your board should look like the image below after connecting the MicroMod SAMD51 Processor Board to the MicroMod ATP. To program, insert the USB-C cable.

Processor Board inserted into Carrier Board

Since the SAMD51 uses the USB connector for USB host, we'll strip and solder wires to the board to connect an external 5V wall adapter.

Solder Wire to 5V and GND

Then we'll strip the other end of the wires and insert them into a female barrel jack adapters with "+" to VIN and "" to GND. We'll secure the wires by twisting them together.

Wires Inserted into Barrel Jack with Screw Terminals

Due to the design of the MicroMod SAMD51's host pins, we'll need to solder another USB Type A connector breakout to the ATP's host pins using male headers. If you have an adapter to convert the USB Type C to Type A, you can also use that as well.

Solder Header Pins to USB Breakout Board

While I could solder the breakout directly to the ATP's host pins, I decided to solder female headers to the board to be able to easily disconnect the USB keyboard.

Solder Female Header Pins to ATP Carrier Board's USB Pins

Add a Qwiic cable between the Qwiic SerLCD and MicroMod's Qwiic connector labeled as I2C

Qwiic Cable Between ATP Carrier Board and 20x4 RGB Character LCD

Insert a keyboard to the USB breakout board.

Insert Keyboard into USB breakout board

When you have finished programming the SAMD51, you can insert a 5V wall adapter for power.

5V Wall Adapter connected to Barrel Jack for Power

To save power and the screen, you can also add a PIR motion sensor or distance sensor to toggle the screen on and off. An additional Qwiic cable and Qwiic PIR motion sensor was added between the MicroMod ATP and Qwiic SerLCD.

Qwiic PIR Motion Sensor Added to Setup

The 20x40 SerLCD was hard to see any messages with it flat on a table so a panel was eventually cut out from a SparkFun cardboard box to mount the project. for the scope of this tutorial, won't go over the specifics of cutting the cardboard in this tutorial or mounting the electronics to the panel.

Cardboard panel to mount the electronics

Arduino Example Code

Note: If this is your first time using Arduino IDE or board add-on, please review the following tutorials.

The example code can be found in the following GitHub repository. The example includes the original host keyboard code from Arduino. The code was modified to work with a 20x4 SerLCD and eventually the 16x2. To save power and the screen, the code was further modified to be used with the Qwiic PIR motion sensor in order to toggle the RGB LED and screen. For the scope of this project tutorial, we will be using examples 1b and 2.

Arduino SAMD Board Add-Ons

Since we are using the SAMD51, you will need to install the board add-on. Head over to the tutorial for instructions on installing the board definitions.

MicroMod SAMD51 Processor Board Hookup Guide

October 21, 2020

This tutorial covers the basic functionality of the MicroMod SAMD51 and highlights the features of the ARM Cortex-M4F development board.

Additional Libraries

If you are using the Qwiic PIR motion sensor, make sure to download and install the library as stated in its tutorial.

Qwiic PIR Hookup Guide

March 25, 2021

Get started passively monitoring motion using the Panasonic EKMC and EKMB sensors with the SparkFun Qwiic PIR.

The Arduino Library Manager is the easiest way to install the library. Open the Library Manager, search for "SparkFun Qwiic PIR Arduino Library" and click the "Install" button to download the latest version. If you prefer manually installing the library from the GitHub repository, you can download it here:

Example 1b: Qwiic Digital Desk Sign

If you are using an external 5V wall adapter, make sure to disconnect it before inserting the USB C cable to your computer's COM port to upload the project's code. Let's upload a project's sketch to the board. Copy and paste the following code in the Arduino IDE. Head to Tools > Board to select the correct board definition (in this case, SparkFun MicroMod SAMD51. Select the correct COM port that the board enumerated to. Hit upload.

language:c
/******************************************************************************
  Host Keyboard Controller with Qwiic Serial LCD 16x2 and 20x4
  Date Modified: 5 Aug 2021
  Modified by
  Ho Yun "Bobby" Chan
  Keyboard Controller Code Originally created 8 Oct 2012
  by Cristian Maglie

  ========== DESCRIPTION==========

  This project code takes input from a USB keyboard with
  the SAMD51's USB host pins and outputs characters to
  the Qwiic RGB Serial Enabled LCD 20x4. Leave a message
  behind as you walk away from your desk!

  Note: Not all keyboards are compatible. This code was tested on the following
  keyboards.

      - Dell KB216t
      - Logitech K120

  This example also builds off the example from Arduino which
  originally showed the output of a USB Keyboard connected to
  the Native USB port on an Arduino Due (SAMD21) Board.

  http://arduino.cc/en/Tutorial/KeyboardController

  This code is part of the public domain.

******************************************************************************/

#include <KeyboardController.h> // Require keyboard control library
#include <Wire.h> //Needed for I2C to SerLCD

#define SerLCD_Address 0x72  //If using SerLCD with I2C
#define SERIAL_PORT_MONITOR Serial1 //debug via hardware UART pins

//assuming that we are using a SerLCD 20x4 screen
//these variables keep track of cursor location
int row = 0;
int remappedRow = row;
int column = 0;

//depending on what screen you are using, we are counting 0 as well
//int maxRow = 1;     // for 16x2
int maxRow = 3;        //for 20x4
//int maxColumn = 15; // for 16x2
int maxColumn = 19;    //for 20x4

boolean rgb_backlight = true; //used for function keys to immediately turn on/off backlight
boolean blink_box = true; //used to keep track of blink box as a "cursor"

//values to keep track of rgb backlight
int rVal = 157; //128 = Off, 157 = 100%
int gVal = 187; //158 = Off, 187 = 100%
int bVal = 217; //188 = Off, 217 = 100%

//values used to turn off rgb in Power Save Mode
int rVal_OFF = 128;
int gVal_OFF = 158;
int bVal_OFF = 188;

int lcdContrast = 40; //used to keep track of contast: Range is 255 to 0, 40 is default


// Initialize USB Controller
USBHost usb;

// Attach keyboard controller to USB
KeyboardController keyboard(usb);

//boolean capsLock = false; //used to keep track of capsLock, this is not used in this code
boolean numLock = false; //display numbers if numLock on, we'll assume that the keyboard resets every time so it's off by default





// This function intercepts key press
void keyPressed() {
  SERIAL_PORT_MONITOR.print("Pressed:  ");
  //printKey(); //disabled so we are not sending two key presses to the SerLCD
}




// This function intercepts key release
void keyReleased() {
  SERIAL_PORT_MONITOR.print("Released: ");
  printKey();
}




void printKey() {
  // getOemKey() returns the OEM-code associated with the key
  int tempKey = keyboard.getOemKey();
  SERIAL_PORT_MONITOR.print(" key:");
  SERIAL_PORT_MONITOR.print(tempKey);

  // getModifiers() returns a bits field with the modifiers-keys
  int mod = keyboard.getModifiers();
  SERIAL_PORT_MONITOR.print(" mod:");
  SERIAL_PORT_MONITOR.print(mod);

  SERIAL_PORT_MONITOR.print(" => ");

  if (mod & LeftCtrl)
    SERIAL_PORT_MONITOR.print("L-Ctrl ");
  if (mod & LeftShift)
    SERIAL_PORT_MONITOR.print("L-Shift ");
  if (mod & Alt)
    SERIAL_PORT_MONITOR.print("Alt ");
  if (mod & LeftCmd)
    SERIAL_PORT_MONITOR.print("L-Cmd ");
  if (mod & RightCtrl)
    SERIAL_PORT_MONITOR.print("R-Ctrl ");
  if (mod & RightShift)
    SERIAL_PORT_MONITOR.print("R-Shift ");
  if (mod & AltGr)
    SERIAL_PORT_MONITOR.print("AltGr ");
  if (mod & RightCmd)
    SERIAL_PORT_MONITOR.print("R-Cmd ");

  // getKey() returns the ASCII translation of OEM key
  // combined with modifiers.
  SERIAL_PORT_MONITOR.write(keyboard.getKey());
  SERIAL_PORT_MONITOR.println();

  /*
    USB SCAN CODES TO KEYCAP (US LAYOUT)
    https://www.win.tue.nl/~aeb/linux/kbd/scancodes-10.html

      CHARACTER KEYS
      USB SCAN CODE = KEYCAP

      4 to 29 = 'a' to 'z' and 'A' to 'Z' with Shift/CapsLock
      30 to 39 = '1' to '9', '0', and '!@#$%^&*()' with Shift
      40 = 'Enter'
      42 = 'Backspace'
      44 = 'Space'
      45 = '-' or '_' with Shift
      46 = '=' or '+' with Shift
      47 = '[' or '{' with Shift
      48 = ']' or '}' with Shift
      49 = '\' or '|' with Shift
      51 = ';' or ':' with Shift
      52 = `'` or '"' with Shift
      53 = '`' or '~' with Shift
      54 = ',' or '<' with Shift
      55 = '.' or '>' with Shift
      56 = '/' or '?' with Shift

      CURSOR CONTROL D-PAD (US LAYOUT)
      79 = `→` (e.g. right)
      80 = `←` (e.g. left)
      81 = `↓` (e.g. down)
      82 = `↑` (e.g. up)

      NUMERIC KEYPAD (US LAYOUT), NUMLOCK MUST BE ENABLED
      83 = 'NumLock'
      84 = '/'
      85 = '*'
      86 = '-'
      87 = '+'
      88 = 'Enter'
      89 = 'End'             or '1' w/ NumLock
      90 = '↓' (e.g. down)   or '2' w/ NumLock
      91 = 'Page Down'       or '3' w/ NumLock
      92 = '←' (e.g. left)   or '4' w/ NumLock
      93 =                      '5' w/ NumLock
      94 = '→' (e.g. right)  or '6' w/ NumLock
      95 = 'Home'             or '7' w/ NumLock
      96 = '↑' (e.g. up)      or '8' w/ NumLock
      97 = 'Page Up'          or '9' w/ NumLock
      98 = 'Insert'           or '0' w/ NumLock
      99 = 'Delete'           or '.' w/ NumLock

      OTHER
      41 = 'Escape'
      73 = 'Insert'
      74 = 'Home'
      75 = 'Page Up'
      76 = 'Delete'
      77 = 'End'
      78 = 'Page Down'

      FUNCTION KEYS
      58 = `F1`
      59 = `F2`
      60 = `F3`
      61 = `F4`
      62 = `F5`
      63 = `F6`
      64 = `F7`
      65 = `F8`
      66 = `F9`
      67 = `F10`
      68 = `F11`
      69 = `F12`



  */

  //----------TYPEWRITER AND KEYPAD KEYS (QWERTY, US LAYOUT) ----------
  if ((tempKey >= 4 && tempKey <= 39) ||
      (tempKey >= 44 && tempKey <= 49) ||
      (tempKey >= 51 && tempKey <= 56) ||
      (tempKey >= 84 && tempKey <= 87) ||
      ((tempKey >= 89 && tempKey <= 97) && numLock == true) ||
      tempKey == 73 ||
      tempKey == 98 ||
      (tempKey == 99 && numLock == true)) {

    Wire.beginTransmission(SerLCD_Address);

    if ( (tempKey == 49) && ((mod == 2) || (mod == 32)) ) {
      //Note: When sending the pipeline character(`|`),
      //we'll need to send 2x since the character
      //is also used as a setting character.
      Wire.write(keyboard.getKey());
      delay(50);//short delay before sending next line
      Wire.write(keyboard.getKey());
    }
    else if ((tempKey == 49) && (mod == 0)) {
      //Note: When sending back slash (`\`),
      //we will load the custom character using
      //printCustomChar() through I2C
      printCustomChar(0);
    }
    else if ( (tempKey == 53) && ((mod == 2) || (mod == 32)) ) {
      //Note: When sending tilde (`~`),
      //we will load the custom character using
      //printCustomChar() through I2C
      printCustomChar(1);
    }
    else if (tempKey == 73) {
      //Note: When sending 'insert',
      //we will load the custom character '♥' using
      //printCustomChar() through I2C
      printCustomChar(2);
    }
    else if (tempKey == 98 && numLock == false) {
      //Note: When sending keypad insert,
      //we will load the custom character '♡' using
      //printCustomChar() through I2C
      printCustomChar(3);
    }
    else {
      Wire.write(keyboard.getKey());
    }


    //after typing the cursor will move automatically to
    //next position so let's keep track of it
    if (column < maxColumn) {
      //if we are not at the end of the row, move cursor to next position
      column = column + 1;
    }
    else {
      //if we are at the end of the row, reset cursor to the beginning of the row
      column = 0;

      if (row < maxRow) {
        //if we are not on the last line, move cursor to the next line
        row = row + 1;
      }
      else {
        //if we are on the last line, move cursor to the first line
        row = 0;
      }
    }

    //remap according to SerLCD's line number
    if (row == 0) {
      remappedRow = 0;
    }
    else if (row == 1) {
      remappedRow = 64;
    }
    else if (row == 2) {
      remappedRow = 20;
    }
    else if (row == 3) {
      remappedRow = 84;
    }

    Wire.write(254); //Send command character
    Wire.write(128 + remappedRow + column); //update cursor position
    Wire.endTransmission(); //Stop I2C transmission

  }





  /*NOTE: These keys move the cursor so it is kept
    as separate condition statements. We will
    also include most of the numLock keys when 'NUMLOCK'
    is disabled.*/
  else if ((tempKey >= 79 && tempKey <= 82) ||
           tempKey == 40 ||
           tempKey == 74 ||
           tempKey == 77 ||
           tempKey == 75 ||
           tempKey == 78 ||
           tempKey == 83 ||
           tempKey == 88 ||
           ((tempKey >= 89 && tempKey <= 97) && numLock == false) ) {


    //move cursor based on d-pad or Enter key

    //----------NUMLOCK KEY----------
    if (tempKey == 83) {
      numLock = !numLock;// change state of numLock if pressed by inverting it
    }




    //----------LEFT KEY----------
    else if ((tempKey == 80) || (tempKey == 92 && numLock == false)) {

      if (column > 0) {
        //if we are at the beginning of the row
        column = column - 1;
      }
      else {
        //if we are at the end of the row
        column = maxColumn;

        if (row > 0) {
          //if we are not on the last line move up a line
          row = row - 1;
        }
        else {
          //if we are on the last line
          row = maxRow;
        }
      }
    }



    //----------RIGHT KEY----------
    else if ( (tempKey == 79) || (tempKey == 94 && numLock == false) ) {

      if (column < maxColumn) {
        //if we are at the before the end of the row
        column = column + 1;
      }
      else {
        //if we are at the end of the row, reset cursor position
        column = 0;

        if (row < maxRow) {
          //move to next line if we are before the last line
          row = row + 1;
        }
        else {
          //move to the first line if we have reached the end
          row = 0;
        }
      }
    }



    //----------UP KEY----------
    else if ( (tempKey == 82) || (tempKey == 96 && numLock == false) ) {
      if (row > 0) {
        //if we are not on the first line
        row = row - 1;
      }
      else {
        //if we are on the first line, move cursor to the last line
        row = maxRow;
      }
    }



    //----------DOWN KEY----------
    else if ( (tempKey == 81) || (tempKey == 90 && numLock == false) ) {
      if (row < maxRow) {
        //if we are not on the first line
        row = row + 1;
      }
      else {
        //if we are on the last line, move cursor to the first line
        row = 0;
      }
    }



    //----------ENTER KEY----------
    else if (tempKey == 40 || tempKey == 88) {
      //move to the first position of the next line
      column = 0;
      if (row < maxRow) {
        //if we are not on the first line, move cursor to the next line
        row = row + 1;
      }
      else {
        //if we are on the last line, move cursor to the first line
        row = 0;
      }
    }

    //----------HOME KEY----------
    else if (tempKey == 74 || (tempKey == 95 && numLock == false) ) {
      //move to the first position of the next line
      column = 0;
    }

    //----------END KEY----------
    else if (tempKey == 77 || (tempKey == 89 && numLock == false)) {
      //move to the first position of the next line
      column = maxColumn;
    }

    //----------PAGE UP KEY----------
    else if (tempKey == 75 || (tempKey == 97 && numLock == false)) {
      //move to the top row
      row = 0;
    }

    //----------PAGE DOWN KEY----------
    else if (tempKey == 78 || (tempKey == 91 && numLock == false)) {
      //move to the bottom row
      row = maxRow;
    }

    else if (tempKey == 93 && numLock == false ) {
      blink_box = !blink_box;// change state of blink_box if pressed by inverting it

      Wire.beginTransmission(SerLCD_Address);
      Wire.write(254); //Send command character

      if (blink_box == false) {
        Wire.write( (1 << 3) | (1 << 2) ); //Cursor off, blinking box off
      }
      else {
        //if blink_box == true

        Wire.write( (1 << 3) | (1 << 2) | (1 << 0) ); //Cursor off, blinking box on
      }

      Wire.endTransmission(); //Stop I2C transmission

    }


    //remap according to SerLCD's line number
    if (row == 0) {
      remappedRow = 0;
    }
    else if (row == 1) {
      remappedRow = 64;
    }
    else if (row == 2) {
      remappedRow = 20;
    }
    else if (row == 3) {
      remappedRow = 84;
    }

    Wire.beginTransmission(SerLCD_Address);
    Wire.write(254); //Send command character
    Wire.write(128 + remappedRow + column); //update cursor position
    Wire.endTransmission(); //Stop I2C transmission

  }




  //----------ESCAPE KEY----------
  else if (tempKey == 41) {
    //Make the escape key clear screen and reset cursor position.
    Wire.beginTransmission(SerLCD_Address);
    Wire.write('|'); //Put LCD into setting mode
    Wire.write('-'); //Send clear display command
    //Control the cursor
    Wire.write(254); //Send command character
    Wire.write( (1 << 3) | (1 << 2) | (1 << 0) ); //Cursor on, blinking box on
    blink_box = true;
    // reset cursor position to (0,0) after using clear display command
    row = 0;
    remappedRow = row;
    column = 0;

    Wire.write(254); //Send command character
    Wire.write(128 + remappedRow + column); //update cursor position
    Wire.endTransmission(); //Stop I2C transmission

  }



  //----------BACKSPACE KEY----------
  else if (tempKey == 42) {
    //Backspace
    //- 'Delete' by replacing with 'Space',
    //- move backward,
    //- then move backward again since cursor moves after sending character


    for (int i = 0 ; i < 2; i++) {

      if (i == 0) {
        //"Delete character" by replacing character with a space
        //and then moving cursor backwards after this condition statement.
        //We do this only once.

        Wire.beginTransmission(SerLCD_Address);
        Wire.write(32); //Send 'Space' Character
        Wire.endTransmission(); //Stop I2C transmission

        //cursor moved automatically to next position so let's keep track of it
        if (column < maxColumn) {
          column = column + 1;
        }
        else {
          column = 0;

          if (row < maxRow) {
            row = row + 1;
          }
          else {
            row = 0;
          }
        }
      }

      //For backspace, we are replacing the current position
      //with a space. This moves the cursor forward once space
      //To correct this, we are going to move backward twice
      //with the help of the for() loop.
      if (column > 0) {
        column = column - 1;
      }
      else {
        column = maxColumn;

        if (row > 0) {
          row = row - 1;
        }
        else {
          row = maxRow;
        }
      }


      //remap according to SerLCD's line number
      if (row == 0) {
        remappedRow = 0;
      }
      else if (row == 1) {
        remappedRow = 64;
      }
      else if (row == 2) {
        remappedRow = 20;
      }
      else if (row == 3) {
        remappedRow = 84;
      }

      Wire.beginTransmission(SerLCD_Address);
      Wire.write(254); //Send command character
      Wire.write(128 + remappedRow + column); //update cursor position
      Wire.endTransmission(); //Stop I2C transmission
      delay(50);




    }//end for() loop for controlling cursor with 'Backspace'
  }//end condition statement for 'Backspace'



  //----------DELETE KEY----------
  else if (tempKey == 76 || (tempKey == 99 && numLock == false) ) {
    //'Delete'
    //- 'Delete' by replacing with 'Space',
    //- move backward again since cursor moves after sending character
    //Note: Delete for the SerLCD is not like the traditional
    //forward delete key. For the SerLCD, we are simply
    //going to delete the character at the cursor position.
    //The characters ahead of the cursor will not be shifted
    //to the left.

    Wire.beginTransmission(SerLCD_Address);
    Wire.write(32); //Send 'Space' Character

    //cursor moved automatically to next position so let's keep track of it
    if (column < maxColumn) {
      column = column + 1;
    }
    else {
      column = 0;

      if (row < maxRow) {
        row = row + 1;
      }
      else {
        row = 0;
      }
    }

    //move cursor back to where it was
    if (column > 0) {
      column = column - 1;
    }
    else {
      column = maxColumn;

      if (row > 0) {
        row = row - 1;
      }
      else {
        row = maxRow;
      }
    }


    //remap according to SerLCD's line number
    if (row == 0) {
      remappedRow = 0;
    }
    else if (row == 1) {
      remappedRow = 64;
    }
    else if (row == 2) {
      remappedRow = 20;
    }
    else if (row == 3) {
      remappedRow = 84;
    }

    Wire.write(254); //Send command character
    Wire.write(128 + remappedRow + column); //update cursor position
    Wire.endTransmission(); //Stop I2C transmission


  }//end condition statement for 'Delete'



  else if (tempKey == 58) {
    //`F1`
    //rVal-

    if (rVal > 128) {
      rVal = rVal - 1;
    }
    else {
      rVal = 128;
    }

    Wire.beginTransmission(SerLCD_Address);
    Wire.write('|'); //Put LCD into setting mode
    Wire.write(rVal); //Update red value
    Wire.endTransmission(); //Stop I2C transmission

  }


  else if (tempKey == 59) {
    //`F2`
    //rVal+

    if (rVal < 157 ) {
      rVal = rVal + 1;
    }
    else {
      rVal = 157;
    }

    Wire.beginTransmission(SerLCD_Address);
    Wire.write('|'); //Put LCD into setting mode
    Wire.write(rVal); //Update red value
    Wire.endTransmission(); //Stop I2C transmission
  }


  else if (tempKey == 60) {
    //`F3`
    //gVal-

    if (gVal > 158 ) {
      gVal = gVal - 1;
    }
    else {
      gVal = 158;
    }

    Wire.beginTransmission(SerLCD_Address);
    Wire.write('|'); //Put LCD into setting mode
    Wire.write(gVal); //Update green value
    Wire.endTransmission(); //Stop I2C transmission

  }


  else if (tempKey == 61) {
    //`F4`
    //gVal+

    if (gVal < 187 ) {
      gVal = gVal + 1;
    }
    else {
      gVal = 187;
    }

    Wire.beginTransmission(SerLCD_Address);
    Wire.write('|'); //Put LCD into setting mode
    Wire.write(gVal); //Update green value
    Wire.endTransmission(); //Stop I2C transmission

  }

  else if (tempKey == 62) {
    //`F5`
    //bVal-

    if (bVal > 188 ) {
      bVal = bVal - 1;
    }
    else {
      bVal = 188;
    }

    Wire.beginTransmission(SerLCD_Address);
    Wire.write('|'); //Put LCD into setting mode
    Wire.write(bVal); //Update blue value
    Wire.endTransmission(); //Stop I2C transmission
  }

  else if (tempKey == 63) {
    //`F6`
    //bVal+

    if (bVal < 217 ) {
      bVal = bVal + 1;
    }
    else {
      bVal = 217;
    }

    Wire.beginTransmission(SerLCD_Address);
    Wire.write('|'); //Put LCD into setting mode
    Wire.write(bVal); //Update blue value
    Wire.endTransmission(); //Stop I2C transmission

  }

  else if (tempKey == 64) {
    //`F7`
    //backlight ON/OFF

    if (rVal == 128 && gVal == 158 &&  bVal == 188)
    { //if all values are off, we will turn it all ON in the next condition statement
      rgb_backlight = 0;
    }
    else {
      //if any of the LEDs is partially on, we will turn it all OFF in the next condition statement
      rgb_backlight = 1;
    }


    if (rgb_backlight == 0)
    { // rgb_backlight == false //OFF, so turn ON

      rgb_backlight = 1;// set it ON
      rVal = 157;
      gVal = 187;
      bVal = 217;


      Wire.beginTransmission(SerLCD_Address);

      Wire.write('|'); //Put LCD into setting mode
      Wire.write(rVal); //Set red backlight

      Wire.write('|'); //Put LCD into setting mode
      Wire.write(gVal); //Set green backlight

      Wire.write('|'); //Put LCD into setting mode
      Wire.write(bVal); //Set blue backlight

      Wire.endTransmission(); //Stop I2C transmission

    }
    else
    { // rgb_backlight == true //ON, so turn OFF

      rgb_backlight = 0;// set it OFF
      rVal = 128;
      gVal = 158;
      bVal = 188;

      Wire.beginTransmission(SerLCD_Address);

      Wire.write('|'); //Put LCD into setting mode
      Wire.write(rVal); //Set red backlight

      Wire.write('|'); //Put LCD into setting mode
      Wire.write(gVal); //Set green backlight

      Wire.write('|'); //Put LCD into setting mode
      Wire.write(bVal); //Set blue backlight

      Wire.endTransmission(); //Stop I2C transmission

    }


  }


  else if (tempKey == 65) {
    //`F8`
    //set custom color, let's set it to cyan (0%, 100%, 100%)

    rVal = 128;
    gVal = 187;
    bVal = 217;

    Wire.beginTransmission(SerLCD_Address);

    Wire.write('|'); //Put LCD into setting mode
    Wire.write(rVal); //Set red backlight

    Wire.write('|'); //Put LCD into setting mode
    Wire.write(gVal); //Set green backlight

    Wire.write('|'); //Put LCD into setting mode
    Wire.write(bVal); //Set blue backlight
    Wire.endTransmission(); //Stop I2C transmission

  }




  else if (tempKey == 66) {
    //`F9`
    //Custom Message 1

    Wire.beginTransmission(SerLCD_Address);
    Wire.write('|'); //Put LCD into setting mode
    Wire.write('-'); //Send clear display command

    // reset cursor position to (0,0) after using clear display command
    row = 0;
    remappedRow = row;
    column = 0;


    if (maxRow == 3 && maxColumn == 19) {
      //Note: We'll send the lines in separate I2C transmissions
      //instead of all at once. Sending several lines freezes the SerLCD.

      Wire.print("Gone dancing! I'll  ");
      Wire.endTransmission(); //Stop I2C transmission
      delay(50);//short delay before sending next line

      Wire.beginTransmission(SerLCD_Address);
      Wire.print("be back at 12:30pm! ");
      Wire.endTransmission(); //Stop I2C transmission
      delay(50);//short delay before sending next line

      Wire.beginTransmission(SerLCD_Address);
      Wire.print("         =)         ");
      Wire.endTransmission(); //Stop I2C transmission
      delay(50);//short delay before sending next line

      Wire.beginTransmission(SerLCD_Address);
      Wire.print("SparkFun Electronics");
      Wire.endTransmission(); //Stop I2C transmission
      delay(50);//short delay before sending next line


      //move cursor position so it's out of the way of the text;
      row = 2;
      remappedRow = 20;
      column = 19;
    }
    else {
      //Note: We'll send the lines in separate I2C transmissions
      //instead of all at once. Sending several lines freezes the SerLCD.

      Wire.print("Gone dancing!   ");
      Wire.endTransmission(); //Stop I2C transmission
      delay(50);//short delay before sending next line

      Wire.beginTransmission(SerLCD_Address);
      Wire.print("BRB at 12:30pm! ");
      Wire.endTransmission(); //Stop I2C transmission
      delay(50);//short delay before sending next line

      //move cursor position so it's out of the way of the text;
      row = 1;
      remappedRow = 64;
      column = 15;
    }

    Wire.beginTransmission(SerLCD_Address);

    Wire.write(254); //Send command character
    Wire.write(128 + remappedRow + column); //update cursor position
    Wire.endTransmission(); //Stop I2C transmission
    delay(50);//short delay before sending next line

  }




  else if (tempKey == 67) {
    //`F10`
    //Custom Message 2

    Wire.beginTransmission(SerLCD_Address);
    Wire.write('|'); //Put LCD into setting mode
    Wire.write('-'); //Send clear display command

    // reset cursor position to (0,0) after using clear display command
    row = 0;
    remappedRow = row;
    column = 0;

    if (maxRow == 3 && maxColumn == 19) {

      //Note: We'll send the lines in separate I2C transmissions
      //instead of all at once. Sending several lines freezes the SerLCD.

      Wire.print("Out for lunch! I'll ");
      Wire.endTransmission(); //Stop I2C transmission
      delay(50);//short delay before sending next line

      Wire.beginTransmission(SerLCD_Address);
      Wire.print("be back at  2:00pm! ");
      Wire.endTransmission(); //Stop I2C transmission
      delay(50);//short delay before sending next line

      Wire.beginTransmission(SerLCD_Address);
      Wire.print("        ^_^         ");
      Wire.endTransmission(); //Stop I2C transmission
      delay(50);//short delay before sending next line

      Wire.beginTransmission(SerLCD_Address);
      Wire.print("SparkFun Electronics");
      Wire.endTransmission(); //Stop I2C transmission
      delay(50);//short delay before sending next line


      //move cursor position so it's out of the way of the text;
      row = 2;
      remappedRow = 20;
      column = 19;
    }
    else {
      //Note: We'll send the lines in separate I2C transmissions
      //instead of all at once. Sending several lines freezes the SerLCD.

      Wire.print("Out for lunch!  ");
      Wire.endTransmission(); //Stop I2C transmission
      delay(50);//short delay before sending next line

      Wire.beginTransmission(SerLCD_Address);
      Wire.print("BRB at  2:00pm! ");
      Wire.endTransmission(); //Stop I2C transmission
      delay(50);//short delay before sending next line

      //move cursor position so it's out of the way of the text;
      row = 1;
      remappedRow = 64;
      column = 15;
    }

    Wire.beginTransmission(SerLCD_Address);

    Wire.write(254); //Send command character
    Wire.write(128 + remappedRow + column); //update cursor position
    Wire.endTransmission(); //Stop I2C transmission
    delay(50);//short delay before sending next line

  }




  else if (tempKey == 68) {
    //`F11`
    //Custom Message 3

    Wire.beginTransmission(SerLCD_Address);
    Wire.write('|'); //Put LCD into setting mode
    Wire.write('-'); //Send clear display command

    // reset cursor position to (0,0) after using clear display command
    row = 0;
    remappedRow = row;
    column = 0;


    if (maxRow == 3 && maxColumn == 19) {

      //Note: We'll send the lines in separate I2C transmissions
      //instead of all at once. Sending several lines freezes the SerLCD.

      Wire.print("In a meeting! I'll  ");
      Wire.endTransmission(); //Stop I2C transmission
      delay(50);//short delay before sending next line

      Wire.beginTransmission(SerLCD_Address);
      Wire.print("be back at  2:00pm! ");
      Wire.endTransmission(); //Stop I2C transmission
      delay(50);//short delay before sending next line

      Wire.beginTransmission(SerLCD_Address);
      Wire.print("                    ");
      Wire.endTransmission(); //Stop I2C transmission
      delay(50);//short delay before sending next line

      Wire.beginTransmission(SerLCD_Address);
      Wire.print("SparkFun Electronics");
      Wire.endTransmission(); //Stop I2C transmission
      delay(50);//short delay before sending next line


      //move cursor position so it's out of the way of the text;
      row = 2;
      remappedRow = 20;
      column = 19;
    }
    else {
      //Note: We'll send the lines in separate I2C transmissions
      //instead of all at once. Sending several lines freezes the SerLCD.

      Wire.print("In a meeting!   ");
      Wire.endTransmission(); //Stop I2C transmission
      delay(50);//short delay before sending next line

      Wire.beginTransmission(SerLCD_Address);
      Wire.print("BRB at  2:00pm! ");
      Wire.endTransmission(); //Stop I2C transmission
      delay(50);//short delay before sending next line

      //move cursor position so it's out of the way of the text;
      row = 1;
      remappedRow = 64;
      column = 15;
    }

    Wire.beginTransmission(SerLCD_Address);

    Wire.write(254); //Send command character
    Wire.write(128 + remappedRow + column); //update cursor position
    Wire.endTransmission(); //Stop I2C transmission
    delay(50);//short delay before sending next line

  }



  else if (tempKey == 69) {
    //`F12`
    //Custom Message 4

    Wire.beginTransmission(SerLCD_Address);
    Wire.write('|'); //Put LCD into setting mode
    Wire.write('-'); //Send clear display command

    // reset cursor position to (0,0) after using clear display command
    row = 0;
    remappedRow = row;
    column = 0;

    if (maxRow == 3 && maxColumn == 19) {

      //Note: We'll send the lines in separate I2C transmissions
      //instead of all at once. Sending several lines freezes the SerLCD.

      Wire.print("Bobby's desk is here");
      Wire.endTransmission(); //Stop I2C transmission
      delay(50);//short delay before sending next line

      Wire.beginTransmission(SerLCD_Address);
      Wire.print("   Working remote!  ");
      Wire.endTransmission(); //Stop I2C transmission
      delay(50);//short delay before sending next line

      Wire.beginTransmission(SerLCD_Address);
      Wire.print(" See you virtually! ");
      Wire.endTransmission(); //Stop I2C transmission
      delay(50);//short delay before sending next line

      Wire.beginTransmission(SerLCD_Address);
      Wire.print("SparkFun Electronics");
      Wire.endTransmission(); //Stop I2C transmission
      delay(50);//short delay before sending next line


      //move cursor position so it's out of the way of the text;
      row = 2;
      remappedRow = 20;
      column = 19;
    }
    else {
      //Note: We'll send the lines in separate I2C transmissions
      //instead of all at once. Sending several lines freezes the SerLCD.

      Wire.print("Bobby's desk is ");
      Wire.endTransmission(); //Stop I2C transmission
      delay(50);//short delay before sending next line

      Wire.beginTransmission(SerLCD_Address);
      Wire.print("here!GoneVirtual");
      Wire.endTransmission(); //Stop I2C transmission
      delay(50);//short delay before sending next line

      //move cursor position so it's out of the way of the text;
      row = 1;
      remappedRow = 64;
      column = 15;
    }

    Wire.beginTransmission(SerLCD_Address);

    Wire.write(254); //Send command character
    Wire.write(128 + remappedRow + column); //update cursor position
    Wire.endTransmission(); //Stop I2C transmission
    delay(50);//short delay before sending next line

  }



  delay(50); //The maximum update rate of OpenLCD is about 100Hz (10ms). A smaller delay will cause flicker

}





//Set Custom Characters for 5x8 Character Position
//'\'
//0x0,0x10,0x8,0x4,0x2,0x1,0x0,0x0
//%0,%10000,%1000,%100,%10,%1,%0,%0
byte back_slash[8] = {
  0b00000,
  0b10000,
  0b01000,
  0b00100,
  0b00010,
  0b00001,
  0b00000,
  0b00000
};





//'~'
//0x0,0x0,0x0,0x8,0x15,0x2,0x0,0x0
//%0,%0,%1000,%10101,%10,%0,%0,%0
byte tilde[8] = {
  0b00000,
  0b00000,
  0b01000,
  0b10101,
  0b00010,
  0b00000,
  0b00000,
  0b00000
};





//'♥'
//0x0,0x0,0xa,0x1f,0x1f,0xe,0x4,0x0
//%0,%0,%1010,%11111,%11111,%1110,%100,%0
byte heart[8] = {
  0b00000,
  0b00000,
  0b01010,
  0b11111,
  0b11111,
  0b01110,
  0b00100,
  0b00000
};





//'♡'
//0x0,0x0,0xa,0x15,0x11,0xa,0x4,0x0
//%0,%0,%1010,%10101,%10001,%1010,%100,%0
byte empty_heart[8] = {
  0b00000,
  0b00000,
  0b01010,
  0b10101,
  0b10001,
  0b01010,
  0b00100,
  0b00000
};





//Given a character number (0 to 7 is valid)
//Given an 8 byte array
//Record this data as a custom character to CGRAM
void loadCustomCharacter(byte charNumber, byte charData[])
{
  if (charNumber > 7) charNumber = 7; //Error correction

  Wire.write('|'); //Send setting character
  Wire.write(27 + charNumber); //27 is the first custom character spot

  for (byte x = 0 ; x < 8 ; x++) //There are 8 bytes of data we need to load
    Wire.write(charData[x]); //Write 8 bytes of graphic data to display
}





//Display a given custom character that was previously loaded into CGRAM
void printCustomChar(byte charNumber)
{
  if (charNumber > 7) charNumber = 7; //Error correction

  Wire.write('|'); //Send setting character
  Wire.write(35 + charNumber); //Tell LCD to display custom char # 0-7
}





void setup() {
  SERIAL_PORT_MONITOR.begin(115200);
  //while (!SERIAL_PORT_MONITOR); // Wait for serial port to connect - used on Leonardo, Teensy and other boards with built-in USB CDC serial connection
  SERIAL_PORT_MONITOR.println("Keyboard Controller Program started");

  if (usb.Init() == -1)
    SERIAL_PORT_MONITOR.println("OSC did not start.");

  delay(3000);//wait a second for the SerLCD to initialize before setting it up

  Wire.begin(); //Join the I2C bus
  Wire.setClock(400000);   // Set clock speed to be the fastest for better communication (fast mode)

  Wire.beginTransmission(SerLCD_Address);
  //Send custom characters to display
  //These are recorded to SerLCD and are remembered even after power is lost
  //There is a maximum of 8 custom characters that can be recorded
  loadCustomCharacter(0, back_slash);
  delay(50);
  loadCustomCharacter(1, tilde);
  delay(50);
  Wire.endTransmission(); //Stop I2C transmission

  Wire.beginTransmission(SerLCD_Address);
  loadCustomCharacter(2, heart);
  delay(50);
  loadCustomCharacter(3, empty_heart);
  delay(50);

  Wire.write('|'); //Put LCD into setting mode
  Wire.write(rVal); //Set red backlight

  Wire.write('|'); //Put LCD into setting mode
  Wire.write(gVal); //Set green backlight

  Wire.write('|'); //Put LCD into setting mode
  Wire.write(bVal); //Set blue backlight

  Wire.endTransmission(); //Stop I2C transmission

  delay(50);//short delay before sending next line

  Wire.beginTransmission(SerLCD_Address);
  Wire.write('|'); //Put LCD into setting mode
  Wire.write('-'); //Send clear display command
  //Control the cursor
  Wire.write(254); //Send command character
  Wire.write( (1 << 3) | (1 << 2) | (1 << 0) ); //Cursor on, blinking box on
  blink_box = true;
  Wire.endTransmission(); //Stop I2C transmission

  delay(50); //short delay before sending next line

}





void loop() {
  // Process USB tasks
  usb.Task();

}





//Given a number, i2cSendValue chops up an integer into four values and sends them out over I2C
void i2cSendValue(int value)
{
  Wire.beginTransmission(SerLCD_Address); // transmit to device #1

  Wire.write('|'); //Put LCD into setting mode
  Wire.write('-'); //Send clear display command

  Wire.endTransmission(); //Stop I2C transmission
}

What You Should See

Unplug the USB cable from your computer and insert it into the MicroMod ATP's host port. Power up the board with the 5V wall adapter. Type a custom message using a USB keyboard to see the output on the Qwiic SerLCD!

Message about 15 minute break and when I'll be back

Use the d-pad to move the cursor around and adjust the message.

Gone away to lunch

Example 2: Qwiic Digital Desk Sign w/ PIR

If you are using an external 5V wall adapter, make sure to disconnect it before inserting the USB C cable to your computer's COM port to upload the project's code. Let's upload a project's sketch to the board. Copy and paste the following code in the Arduino IDE. Head to Tools > Board to select the correct board definition (in this case, SparkFun MicroMod SAMD51. Select the correct COM port that the board enumerated to. Hit upload.

language:c
/******************************************************************************
  Host Keyboard Controller with Qwiic Serial LCD and Qwiic PIR
  Date Modified: 5 Aug 2021
  Modified by
  Ho Yun "Bobby" Chan
  Keyboard Controller Code Originally created 8 Oct 2012
  by Cristian Maglie

  ========== DESCRIPTION==========

  This project code takes input from a USB keyboard with
  the SAMD51's USB host pins and outputs characters to
  the Qwiic RGB Serial Enabled LCD 4x20. Leave a message
  behind as you walk away from your desk!

  Note: Not all keyboards are compatible. This code was tested on the following
  keyboards.

      - Dell KB216t
      - Logitech K120

  This example also builds off the example from Arduino which
  originally showed the output of a USB Keyboard connected to
  the Native USB port on an Arduino Due (SAMD21) Board.

  http://arduino.cc/en/Tutorial/KeyboardController

  This code is part of the public domain.

******************************************************************************/

#include <KeyboardController.h> // Require keyboard control library
#include <SparkFun_Qwiic_PIR.h>
QwiicPIR pir;

//#include <Wire.h> //Was needed for I2C to SerLCD and Qwiic PIR but it is defined in the Qwiic PIR library so we comment it out

#define SerLCD_Address 0x72  //If using SerLCD with I2C
#define SERIAL_PORT_MONITOR Serial1 //debug via hardware UART pins

//assuming that we are using a SerLCD 20x4 screen
//these variables keep track of cursor location
int row = 0;
int remappedRow = row;
int column = 0;

//depending on what screen you are using, we are counting 0 as well
//int maxRow = 1;     // for 16x2
int maxRow = 3;        //for 20x4
//int maxColumn = 15; // for 16x2
int maxColumn = 19;    //for 20x4

boolean rgb_backlight = true; //used for function keys to immediately turn on/off backlight
boolean blink_box = true; //used to keep track of blink box as a "cursor"

//values to keep track of rgb backlight
int rVal = 157; //128 = Off, 157 = 100%
int gVal = 187; //158 = Off, 187 = 100%
int bVal = 217; //188 = Off, 217 = 100%

//values used to turn off rgb in Power Save Mode
int rVal_OFF = 128;
int gVal_OFF = 158;
int bVal_OFF = 188;

int lcdContrast = 40; //used to keep track of contast: Range is 255 to 0, 40 is default


boolean activity = true; //used to keep track of movement or keyboard presses
const int noActivityMillis = 10000; //used to compare amount of time when no activity to turn of screen
int currentMillis = 0;  //get time based on how long the Arduino has been running
int lastActivityMillis = 0;





// Initialize USB Controller
USBHost usb;

// Attach keyboard controller to USB
KeyboardController keyboard(usb);

//boolean capsLock = false; //used to keep track of capsLock, this is not used in this code
boolean numLock = false; //display numbers if numLock on, we'll assume that the keyboard resets every time so it's off by default




// This function intercepts key press
void keyPressed() {
  SERIAL_PORT_MONITOR.print("Pressed:  ");
  //printKey(); //disabled so we are not sending two key presses to the SerLCD
}




// This function intercepts key release
void keyReleased() {
  SERIAL_PORT_MONITOR.print("Released: ");
  printKey();
}




void printKey() {
  activity = true;

  // getOemKey() returns the OEM-code associated with the key
  int tempKey = keyboard.getOemKey();
  SERIAL_PORT_MONITOR.print(" key:");
  SERIAL_PORT_MONITOR.print(tempKey);

  // getModifiers() returns a bits field with the modifiers-keys
  int mod = keyboard.getModifiers();
  SERIAL_PORT_MONITOR.print(" mod:");
  SERIAL_PORT_MONITOR.print(mod);

  SERIAL_PORT_MONITOR.print(" => ");

  if (mod & LeftCtrl)
    SERIAL_PORT_MONITOR.print("L-Ctrl ");
  if (mod & LeftShift)
    SERIAL_PORT_MONITOR.print("L-Shift ");
  if (mod & Alt)
    SERIAL_PORT_MONITOR.print("Alt ");
  if (mod & LeftCmd)
    SERIAL_PORT_MONITOR.print("L-Cmd ");
  if (mod & RightCtrl)
    SERIAL_PORT_MONITOR.print("R-Ctrl ");
  if (mod & RightShift)
    SERIAL_PORT_MONITOR.print("R-Shift ");
  if (mod & AltGr)
    SERIAL_PORT_MONITOR.print("AltGr ");
  if (mod & RightCmd)
    SERIAL_PORT_MONITOR.print("R-Cmd ");

  // getKey() returns the ASCII translation of OEM key
  // combined with modifiers.
  SERIAL_PORT_MONITOR.write(keyboard.getKey());
  SERIAL_PORT_MONITOR.println();

  /*
    USB SCAN CODES TO KEYCAP (US LAYOUT)
    https://www.win.tue.nl/~aeb/linux/kbd/scancodes-10.html

      CHARACTER KEYS
      USB SCAN CODE = KEYCAP

      4 to 29 = 'a' to 'z' and 'A' to 'Z' with Shift/CapsLock
      30 to 39 = '1' to '9', '0', and '!@#$%^&*()' with Shift
      40 = 'Enter'
      42 = 'Backspace'
      44 = 'Space'
      45 = '-' or '_' with Shift
      46 = '=' or '+' with Shift
      47 = '[' or '{' with Shift
      48 = ']' or '}' with Shift
      49 = '\' or '|' with Shift
      51 = ';' or ':' with Shift
      52 = `'` or '"' with Shift
      53 = '`' or '~' with Shift
      54 = ',' or '<' with Shift
      55 = '.' or '>' with Shift
      56 = '/' or '?' with Shift

      CURSOR CONTROL D-PAD (US LAYOUT)
      79 = `→` (e.g. right)
      80 = `←` (e.g. left)
      81 = `↓` (e.g. down)
      82 = `↑` (e.g. up)

      NUMERIC KEYPAD (US LAYOUT), NUMLOCK MUST BE ENABLED
      83 = 'NumLock'
      84 = '/'
      85 = '*'
      86 = '-'
      87 = '+'
      88 = 'Enter'
      89 = 'End'             or '1' w/ NumLock
      90 = '↓' (e.g. down)   or '2' w/ NumLock
      91 = 'Page Down'       or '3' w/ NumLock
      92 = '←' (e.g. left)   or '4' w/ NumLock
      93 =                      '5' w/ NumLock
      94 = '→' (e.g. right)  or '6' w/ NumLock
      95 = 'Home'             or '7' w/ NumLock
      96 = '↑' (e.g. up)      or '8' w/ NumLock
      97 = 'Page Up'          or '9' w/ NumLock
      98 = 'Insert'           or '0' w/ NumLock
      99 = 'Delete'           or '.' w/ NumLock

      OTHER
      41 = 'Escape'
      73 = 'Insert'
      74 = 'Home'
      75 = 'Page Up'
      76 = 'Delete'
      77 = 'End'
      78 = 'Page Down'

      FUNCTION KEYS
      58 = `F1`
      59 = `F2`
      60 = `F3`
      61 = `F4`
      62 = `F5`
      63 = `F6`
      64 = `F7`
      65 = `F8`
      66 = `F9`
      67 = `F10`
      68 = `F11`
      69 = `F12`



  */

  //----------TYPEWRITER AND KEYPAD KEYS (QWERTY, US LAYOUT) ----------
  if ((tempKey >= 4 && tempKey <= 39) ||
      (tempKey >= 44 && tempKey <= 49) ||
      (tempKey >= 51 && tempKey <= 56) ||
      (tempKey >= 84 && tempKey <= 87) ||
      ((tempKey >= 89 && tempKey <= 97) && numLock == true) ||
      tempKey == 73 ||
      tempKey == 98 ||
      (tempKey == 99 && numLock == true)) {

    Wire.beginTransmission(SerLCD_Address);

    if ( (tempKey == 49) && ((mod == 2) || (mod == 32)) ) {
      //Note: When sending the pipeline character(`|`),
      //we'll need to send 2x since the character
      //is also used as a setting character.
      Wire.write(keyboard.getKey());
      delay(50);//short delay before sending next line
      Wire.write(keyboard.getKey());
    }
    else if ((tempKey == 49) && (mod == 0)) {
      //Note: When sending back slash (`\`),
      //we will load the custom character using
      //printCustomChar() through I2C
      printCustomChar(0);
    }
    else if ( (tempKey == 53) && ((mod == 2) || (mod == 32)) ) {
      //Note: When sending tilde (`~`),
      //we will load the custom character using
      //printCustomChar() through I2C
      printCustomChar(1);
    }
    else if (tempKey == 73) {
      //Note: When sending 'insert',
      //we will load the custom character '♥' using
      //printCustomChar() through I2C
      printCustomChar(2);
    }
    else if (tempKey == 98 && numLock == false) {
      //Note: When sending keypad insert,
      //we will load the custom character '♡' using
      //printCustomChar() through I2C
      printCustomChar(3);
    }
    else {
      Wire.write(keyboard.getKey());
    }


    //after typing the cursor will move automatically to
    //next position so let's keep track of it
    if (column < maxColumn) {
      //if we are not at the end of the row, move cursor to next position
      column = column + 1;
    }
    else {
      //if we are at the end of the row, reset cursor to the beginning of the row
      column = 0;

      if (row < maxRow) {
        //if we are not on the last line, move cursor to the next line
        row = row + 1;
      }
      else {
        //if we are on the last line, move cursor to the first line
        row = 0;
      }
    }

    //remap according to SerLCD's line number
    if (row == 0) {
      remappedRow = 0;
    }
    else if (row == 1) {
      remappedRow = 64;
    }
    else if (row == 2) {
      remappedRow = 20;
    }
    else if (row == 3) {
      remappedRow = 84;
    }

    Wire.write(254); //Send command character
    Wire.write(128 + remappedRow + column); //update cursor position
    Wire.endTransmission(); //Stop I2C transmission

  }





  /*NOTE: These keys move the cursor so it is kept
    as separate condition statements. We will
    also include most of the numLock keys when 'NUMLOCK'
    is disabled.*/
  else if ((tempKey >= 79 && tempKey <= 82) ||
           tempKey == 40 ||
           tempKey == 74 ||
           tempKey == 77 ||
           tempKey == 75 ||
           tempKey == 78 ||
           tempKey == 83 ||
           tempKey == 88 ||
           ((tempKey >= 89 && tempKey <= 97) && numLock == false) ) {


    //move cursor based on d-pad or Enter key

    //----------NUMLOCK KEY----------
    if (tempKey == 83) {
      numLock = !numLock;// change state of numLock if pressed by inverting it
    }




    //----------LEFT KEY----------
    else if ((tempKey == 80) || (tempKey == 92 && numLock == false)) {

      if (column > 0) {
        //if we are at the beginning of the row
        column = column - 1;
      }
      else {
        //if we are at the end of the row
        column = maxColumn;

        if (row > 0) {
          //if we are not on the last line move up a line
          row = row - 1;
        }
        else {
          //if we are on the last line
          row = maxRow;
        }
      }
    }



    //----------RIGHT KEY----------
    else if ( (tempKey == 79) || (tempKey == 94 && numLock == false) ) {

      if (column < maxColumn) {
        //if we are at the before the end of the row
        column = column + 1;
      }
      else {
        //if we are at the end of the row, reset cursor position
        column = 0;

        if (row < maxRow) {
          //move to next line if we are before the last line
          row = row + 1;
        }
        else {
          //move to the first line if we have reached the end
          row = 0;
        }
      }
    }



    //----------UP KEY----------
    else if ( (tempKey == 82) || (tempKey == 96 && numLock == false) ) {
      if (row > 0) {
        //if we are not on the first line
        row = row - 1;
      }
      else {
        //if we are on the first line, move cursor to the last line
        row = maxRow;
      }
    }



    //----------DOWN KEY----------
    else if ( (tempKey == 81) || (tempKey == 90 && numLock == false) ) {
      if (row < maxRow) {
        //if we are not on the first line
        row = row + 1;
      }
      else {
        //if we are on the last line, move cursor to the first line
        row = 0;
      }
    }



    //----------ENTER KEY----------
    else if (tempKey == 40 || tempKey == 88) {
      //move to the first position of the next line
      column = 0;
      if (row < maxRow) {
        //if we are not on the first line, move cursor to the next line
        row = row + 1;
      }
      else {
        //if we are on the last line, move cursor to the first line
        row = 0;
      }
    }

    //----------HOME KEY----------
    else if (tempKey == 74 || (tempKey == 95 && numLock == false) ) {
      //move to the first position of the next line
      column = 0;
    }

    //----------END KEY----------
    else if (tempKey == 77 || (tempKey == 89 && numLock == false)) {
      //move to the first position of the next line
      column = maxColumn;
    }

    //----------PAGE UP KEY----------
    else if (tempKey == 75 || (tempKey == 97 && numLock == false)) {
      //move to the top row
      row = 0;
    }

    //----------PAGE DOWN KEY----------
    else if (tempKey == 78 || (tempKey == 91 && numLock == false)) {
      //move to the bottom row
      row = maxRow;
    }

    else if (tempKey == 93 && numLock == false ) {
      blink_box = !blink_box;// change state of blink_box if pressed by inverting it

      Wire.beginTransmission(SerLCD_Address);
      Wire.write(254); //Send command character

      if (blink_box == false) {
        Wire.write( (1 << 3) | (1 << 2) ); //Cursor off, blinking box off
      }
      else {
        //if blink_box == true

        Wire.write( (1 << 3) | (1 << 2) | (1 << 0) ); //Cursor off, blinking box on
      }

      Wire.endTransmission(); //Stop I2C transmission

    }


    //remap according to SerLCD's line number
    if (row == 0) {
      remappedRow = 0;
    }
    else if (row == 1) {
      remappedRow = 64;
    }
    else if (row == 2) {
      remappedRow = 20;
    }
    else if (row == 3) {
      remappedRow = 84;
    }

    Wire.beginTransmission(SerLCD_Address);
    Wire.write(254); //Send command character
    Wire.write(128 + remappedRow + column); //update cursor position
    Wire.endTransmission(); //Stop I2C transmission

  }




  //----------ESCAPE KEY----------
  else if (tempKey == 41) {
    //Make the escape key clear screen and reset cursor position.
    Wire.beginTransmission(SerLCD_Address);
    Wire.write('|'); //Put LCD into setting mode
    Wire.write('-'); //Send clear display command
    //Control the cursor
    Wire.write(254); //Send command character
    Wire.write( (1 << 3) | (1 << 2) | (1 << 0) ); //Cursor on, blinking box on
    blink_box = true;
    // reset cursor position to (0,0) after using clear display command
    row = 0;
    remappedRow = row;
    column = 0;

    Wire.write(254); //Send command character
    Wire.write(128 + remappedRow + column); //update cursor position
    Wire.endTransmission(); //Stop I2C transmission

  }



  //----------BACKSPACE KEY----------
  else if (tempKey == 42) {
    //Backspace
    //- 'Delete' by replacing with 'Space',
    //- move backward,
    //- then move backward again since cursor moves after sending character


    for (int i = 0 ; i < 2; i++) {

      if (i == 0) {
        //"Delete character" by replacing character with a space
        //and then moving cursor backwards after this condition statement.
        //We do this only once.

        Wire.beginTransmission(SerLCD_Address);
        Wire.write(32); //Send 'Space' Character
        Wire.endTransmission(); //Stop I2C transmission

        //cursor moved automatically to next position so let's keep track of it
        if (column < maxColumn) {
          column = column + 1;
        }
        else {
          column = 0;

          if (row < maxRow) {
            row = row + 1;
          }
          else {
            row = 0;
          }
        }
      }

      //For backspace, we are replacing the current position
      //with a space. This moves the cursor forward once space
      //To correct this, we are going to move backward twice
      //with the help of the for() loop.
      if (column > 0) {
        column = column - 1;
      }
      else {
        column = maxColumn;

        if (row > 0) {
          row = row - 1;
        }
        else {
          row = maxRow;
        }
      }


      //remap according to SerLCD's line number
      if (row == 0) {
        remappedRow = 0;
      }
      else if (row == 1) {
        remappedRow = 64;
      }
      else if (row == 2) {
        remappedRow = 20;
      }
      else if (row == 3) {
        remappedRow = 84;
      }

      Wire.beginTransmission(SerLCD_Address);
      Wire.write(254); //Send command character
      Wire.write(128 + remappedRow + column); //update cursor position
      Wire.endTransmission(); //Stop I2C transmission
      delay(50);




    }//end for() loop for controlling cursor with 'Backspace'
  }//end condition statement for 'Backspace'



  //----------DELETE KEY----------
  else if (tempKey == 76 || (tempKey == 99 && numLock == false) ) {
    //'Delete'
    //- 'Delete' by replacing with 'Space',
    //- move backward again since cursor moves after sending character
    //Note: Delete for the SerLCD is not like the traditional
    //forward delete key. For the SerLCD, we are simply
    //going to delete the character at the cursor position.
    //The characters ahead of the cursor will not be shifted
    //to the left.

    Wire.beginTransmission(SerLCD_Address);
    Wire.write(32); //Send 'Space' Character

    //cursor moved automatically to next position so let's keep track of it
    if (column < maxColumn) {
      column = column + 1;
    }
    else {
      column = 0;

      if (row < maxRow) {
        row = row + 1;
      }
      else {
        row = 0;
      }
    }

    //move cursor back to where it was
    if (column > 0) {
      column = column - 1;
    }
    else {
      column = maxColumn;

      if (row > 0) {
        row = row - 1;
      }
      else {
        row = maxRow;
      }
    }


    //remap according to SerLCD's line number
    if (row == 0) {
      remappedRow = 0;
    }
    else if (row == 1) {
      remappedRow = 64;
    }
    else if (row == 2) {
      remappedRow = 20;
    }
    else if (row == 3) {
      remappedRow = 84;
    }

    Wire.write(254); //Send command character
    Wire.write(128 + remappedRow + column); //update cursor position
    Wire.endTransmission(); //Stop I2C transmission


  }//end condition statement for 'Delete'



  else if (tempKey == 58) {
    //`F1`
    //rVal-

    if (rVal > 128) {
      rVal = rVal - 1;
    }
    else {
      rVal = 128;
    }

    Wire.beginTransmission(SerLCD_Address);
    Wire.write('|'); //Put LCD into setting mode
    Wire.write(rVal); //Update red value
    Wire.endTransmission(); //Stop I2C transmission

  }


  else if (tempKey == 59) {
    //`F2`
    //rVal+

    if (rVal < 157 ) {
      rVal = rVal + 1;
    }
    else {
      rVal = 157;
    }

    Wire.beginTransmission(SerLCD_Address);
    Wire.write('|'); //Put LCD into setting mode
    Wire.write(rVal); //Update red value
    Wire.endTransmission(); //Stop I2C transmission
  }


  else if (tempKey == 60) {
    //`F3`
    //gVal-

    if (gVal > 158 ) {
      gVal = gVal - 1;
    }
    else {
      gVal = 158;
    }

    Wire.beginTransmission(SerLCD_Address);
    Wire.write('|'); //Put LCD into setting mode
    Wire.write(gVal); //Update green value
    Wire.endTransmission(); //Stop I2C transmission

  }


  else if (tempKey == 61) {
    //`F4`
    //gVal+

    if (gVal < 187 ) {
      gVal = gVal + 1;
    }
    else {
      gVal = 187;
    }

    Wire.beginTransmission(SerLCD_Address);
    Wire.write('|'); //Put LCD into setting mode
    Wire.write(gVal); //Update green value
    Wire.endTransmission(); //Stop I2C transmission

  }

  else if (tempKey == 62) {
    //`F5`
    //bVal-

    if (bVal > 188 ) {
      bVal = bVal - 1;
    }
    else {
      bVal = 188;
    }

    Wire.beginTransmission(SerLCD_Address);
    Wire.write('|'); //Put LCD into setting mode
    Wire.write(bVal); //Update blue value
    Wire.endTransmission(); //Stop I2C transmission
  }

  else if (tempKey == 63) {
    //`F6`
    //bVal+

    if (bVal < 217 ) {
      bVal = bVal + 1;
    }
    else {
      bVal = 217;
    }

    Wire.beginTransmission(SerLCD_Address);
    Wire.write('|'); //Put LCD into setting mode
    Wire.write(bVal); //Update blue value
    Wire.endTransmission(); //Stop I2C transmission

  }

  else if (tempKey == 64) {
    //`F7`
    //backlight ON/OFF

    if (rVal == 128 && gVal == 158 &&  bVal == 188)
    { //if all values are off, we will turn it all ON in the next condition statement
      rgb_backlight = 0;
    }
    else {
      //if any of the LEDs is partially on, we will turn it all OFF in the next condition statement
      rgb_backlight = 1;
    }


    if (rgb_backlight == 0)
    { // rgb_backlight == false //OFF, so turn ON

      rgb_backlight = 1;// set it ON
      rVal = 157;
      gVal = 187;
      bVal = 217;


      Wire.beginTransmission(SerLCD_Address);

      Wire.write('|'); //Put LCD into setting mode
      Wire.write(rVal); //Set red backlight

      Wire.write('|'); //Put LCD into setting mode
      Wire.write(gVal); //Set green backlight

      Wire.write('|'); //Put LCD into setting mode
      Wire.write(bVal); //Set blue backlight

      Wire.endTransmission(); //Stop I2C transmission

    }
    else
    { // rgb_backlight == true //ON, so turn OFF

      rgb_backlight = 0;// set it OFF
      rVal = 128;
      gVal = 158;
      bVal = 188;

      Wire.beginTransmission(SerLCD_Address);

      Wire.write('|'); //Put LCD into setting mode
      Wire.write(rVal); //Set red backlight

      Wire.write('|'); //Put LCD into setting mode
      Wire.write(gVal); //Set green backlight

      Wire.write('|'); //Put LCD into setting mode
      Wire.write(bVal); //Set blue backlight

      Wire.endTransmission(); //Stop I2C transmission

    }


  }


  else if (tempKey == 65) {
    //`F8`
    //set custom color, let's set it to cyan (0%, 100%, 100%)

    rVal = 128;
    gVal = 187;
    bVal = 217;

    Wire.beginTransmission(SerLCD_Address);

    Wire.write('|'); //Put LCD into setting mode
    Wire.write(rVal); //Set red backlight

    Wire.write('|'); //Put LCD into setting mode
    Wire.write(gVal); //Set green backlight

    Wire.write('|'); //Put LCD into setting mode
    Wire.write(bVal); //Set blue backlight
    Wire.endTransmission(); //Stop I2C transmission

  }




  else if (tempKey == 66) {
    //`F9`
    //Custom Message 1

    Wire.beginTransmission(SerLCD_Address);
    Wire.write('|'); //Put LCD into setting mode
    Wire.write('-'); //Send clear display command

    // reset cursor position to (0,0) after using clear display command
    row = 0;
    remappedRow = row;
    column = 0;


    if (maxRow == 3 && maxColumn == 19) {
      //Note: We'll send the lines in separate I2C transmissions
      //instead of all at once. Sending several lines freezes the SerLCD.

      Wire.print("Gone dancing! I'll  ");
      Wire.endTransmission(); //Stop I2C transmission
      delay(50);//short delay before sending next line

      Wire.beginTransmission(SerLCD_Address);
      Wire.print("be back at 12:30pm! ");
      Wire.endTransmission(); //Stop I2C transmission
      delay(50);//short delay before sending next line

      Wire.beginTransmission(SerLCD_Address);
      Wire.print("         =)         ");
      Wire.endTransmission(); //Stop I2C transmission
      delay(50);//short delay before sending next line

      Wire.beginTransmission(SerLCD_Address);
      Wire.print("SparkFun Electronics");
      Wire.endTransmission(); //Stop I2C transmission
      delay(50);//short delay before sending next line


      //move cursor position so it's out of the way of the text;
      row = 2;
      remappedRow = 20;
      column = 19;
    }
    else {
      //Note: We'll send the lines in separate I2C transmissions
      //instead of all at once. Sending several lines freezes the SerLCD.

      Wire.print("Gone dancing!   ");
      Wire.endTransmission(); //Stop I2C transmission
      delay(50);//short delay before sending next line

      Wire.beginTransmission(SerLCD_Address);
      Wire.print("BRB at 12:30pm! ");
      Wire.endTransmission(); //Stop I2C transmission
      delay(50);//short delay before sending next line

      //move cursor position so it's out of the way of the text;
      row = 1;
      remappedRow = 64;
      column = 15;
    }

    Wire.beginTransmission(SerLCD_Address);

    Wire.write(254); //Send command character
    Wire.write(128 + remappedRow + column); //update cursor position
    Wire.endTransmission(); //Stop I2C transmission
    delay(50);//short delay before sending next line

  }




  else if (tempKey == 67) {
    //`F10`
    //Custom Message 2

    Wire.beginTransmission(SerLCD_Address);
    Wire.write('|'); //Put LCD into setting mode
    Wire.write('-'); //Send clear display command

    // reset cursor position to (0,0) after using clear display command
    row = 0;
    remappedRow = row;
    column = 0;

    if (maxRow == 3 && maxColumn == 19) {

      //Note: We'll send the lines in separate I2C transmissions
      //instead of all at once. Sending several lines freezes the SerLCD.

      Wire.print("Out for lunch! I'll ");
      Wire.endTransmission(); //Stop I2C transmission
      delay(50);//short delay before sending next line

      Wire.beginTransmission(SerLCD_Address);
      Wire.print("be back at  2:00pm! ");
      Wire.endTransmission(); //Stop I2C transmission
      delay(50);//short delay before sending next line

      Wire.beginTransmission(SerLCD_Address);
      Wire.print("        ^_^         ");
      Wire.endTransmission(); //Stop I2C transmission
      delay(50);//short delay before sending next line

      Wire.beginTransmission(SerLCD_Address);
      Wire.print("SparkFun Electronics");
      Wire.endTransmission(); //Stop I2C transmission
      delay(50);//short delay before sending next line


      //move cursor position so it's out of the way of the text;
      row = 2;
      remappedRow = 20;
      column = 19;
    }
    else {
      //Note: We'll send the lines in separate I2C transmissions
      //instead of all at once. Sending several lines freezes the SerLCD.

      Wire.print("Out for lunch!  ");
      Wire.endTransmission(); //Stop I2C transmission
      delay(50);//short delay before sending next line

      Wire.beginTransmission(SerLCD_Address);
      Wire.print("BRB at  2:00pm! ");
      Wire.endTransmission(); //Stop I2C transmission
      delay(50);//short delay before sending next line

      //move cursor position so it's out of the way of the text;
      row = 1;
      remappedRow = 64;
      column = 15;
    }

    Wire.beginTransmission(SerLCD_Address);

    Wire.write(254); //Send command character
    Wire.write(128 + remappedRow + column); //update cursor position
    Wire.endTransmission(); //Stop I2C transmission
    delay(50);//short delay before sending next line

  }




  else if (tempKey == 68) {
    //`F11`
    //Custom Message 3

    Wire.beginTransmission(SerLCD_Address);
    Wire.write('|'); //Put LCD into setting mode
    Wire.write('-'); //Send clear display command

    // reset cursor position to (0,0) after using clear display command
    row = 0;
    remappedRow = row;
    column = 0;


    if (maxRow == 3 && maxColumn == 19) {

      //Note: We'll send the lines in separate I2C transmissions
      //instead of all at once. Sending several lines freezes the SerLCD.

      Wire.print("In a meeting! I'll  ");
      Wire.endTransmission(); //Stop I2C transmission
      delay(50);//short delay before sending next line

      Wire.beginTransmission(SerLCD_Address);
      Wire.print("be back at  2:00pm! ");
      Wire.endTransmission(); //Stop I2C transmission
      delay(50);//short delay before sending next line

      Wire.beginTransmission(SerLCD_Address);
      Wire.print("                    ");
      Wire.endTransmission(); //Stop I2C transmission
      delay(50);//short delay before sending next line

      Wire.beginTransmission(SerLCD_Address);
      Wire.print("SparkFun Electronics");
      Wire.endTransmission(); //Stop I2C transmission
      delay(50);//short delay before sending next line


      //move cursor position so it's out of the way of the text;
      row = 2;
      remappedRow = 20;
      column = 19;
    }
    else {
      //Note: We'll send the lines in separate I2C transmissions
      //instead of all at once. Sending several lines freezes the SerLCD.

      Wire.print("In a meeting!   ");
      Wire.endTransmission(); //Stop I2C transmission
      delay(50);//short delay before sending next line

      Wire.beginTransmission(SerLCD_Address);
      Wire.print("BRB at  2:00pm! ");
      Wire.endTransmission(); //Stop I2C transmission
      delay(50);//short delay before sending next line

      //move cursor position so it's out of the way of the text;
      row = 1;
      remappedRow = 64;
      column = 15;
    }

    Wire.beginTransmission(SerLCD_Address);

    Wire.write(254); //Send command character
    Wire.write(128 + remappedRow + column); //update cursor position
    Wire.endTransmission(); //Stop I2C transmission
    delay(50);//short delay before sending next line

  }



  else if (tempKey == 69) {
    //`F12`
    //Custom Message 4

    Wire.beginTransmission(SerLCD_Address);
    Wire.write('|'); //Put LCD into setting mode
    Wire.write('-'); //Send clear display command

    // reset cursor position to (0,0) after using clear display command
    row = 0;
    remappedRow = row;
    column = 0;

    if (maxRow == 3 && maxColumn == 19) {

      //Note: We'll send the lines in separate I2C transmissions
      //instead of all at once. Sending several lines freezes the SerLCD.

      Wire.print("Bobby's desk is here");
      Wire.endTransmission(); //Stop I2C transmission
      delay(50);//short delay before sending next line

      Wire.beginTransmission(SerLCD_Address);
      Wire.print("   Working remote!  ");
      Wire.endTransmission(); //Stop I2C transmission
      delay(50);//short delay before sending next line

      Wire.beginTransmission(SerLCD_Address);
      Wire.print(" See you virtually! ");
      Wire.endTransmission(); //Stop I2C transmission
      delay(50);//short delay before sending next line

      Wire.beginTransmission(SerLCD_Address);
      Wire.print("SparkFun Electronics");
      Wire.endTransmission(); //Stop I2C transmission
      delay(50);//short delay before sending next line


      //move cursor position so it's out of the way of the text;
      row = 2;
      remappedRow = 20;
      column = 19;
    }
    else {
      //Note: We'll send the lines in separate I2C transmissions
      //instead of all at once. Sending several lines freezes the SerLCD.

      Wire.print("Bobby's desk is ");
      Wire.endTransmission(); //Stop I2C transmission
      delay(50);//short delay before sending next line

      Wire.beginTransmission(SerLCD_Address);
      Wire.print("here!GoneVirtual");
      Wire.endTransmission(); //Stop I2C transmission
      delay(50);//short delay before sending next line

      //move cursor position so it's out of the way of the text;
      row = 1;
      remappedRow = 64;
      column = 15;
    }

    Wire.beginTransmission(SerLCD_Address);

    Wire.write(254); //Send command character
    Wire.write(128 + remappedRow + column); //update cursor position
    Wire.endTransmission(); //Stop I2C transmission
    delay(50);//short delay before sending next line

  }



  delay(50); //The maximum update rate of OpenLCD is about 100Hz (10ms). A smaller delay will cause flicker

}





//Set Custom Characters for 5x8 Character Position
//'\'
//0x0,0x10,0x8,0x4,0x2,0x1,0x0,0x0
//%0,%10000,%1000,%100,%10,%1,%0,%0
byte back_slash[8] = {
  0b00000,
  0b10000,
  0b01000,
  0b00100,
  0b00010,
  0b00001,
  0b00000,
  0b00000
};





//'~'
//0x0,0x0,0x0,0x8,0x15,0x2,0x0,0x0
//%0,%0,%1000,%10101,%10,%0,%0,%0
byte tilde[8] = {
  0b00000,
  0b00000,
  0b01000,
  0b10101,
  0b00010,
  0b00000,
  0b00000,
  0b00000
};





//'♥'
//0x0,0x0,0xa,0x1f,0x1f,0xe,0x4,0x0
//%0,%0,%1010,%11111,%11111,%1110,%100,%0
byte heart[8] = {
  0b00000,
  0b00000,
  0b01010,
  0b11111,
  0b11111,
  0b01110,
  0b00100,
  0b00000
};





//'♡'
//0x0,0x0,0xa,0x15,0x11,0xa,0x4,0x0
//%0,%0,%1010,%10101,%10001,%1010,%100,%0
byte empty_heart[8] = {
  0b00000,
  0b00000,
  0b01010,
  0b10101,
  0b10001,
  0b01010,
  0b00100,
  0b00000
};





//Given a character number (0 to 7 is valid)
//Given an 8 byte array
//Record this data as a custom character to CGRAM
void loadCustomCharacter(byte charNumber, byte charData[])
{
  if (charNumber > 7) charNumber = 7; //Error correction

  Wire.write('|'); //Send setting character
  Wire.write(27 + charNumber); //27 is the first custom character spot

  for (byte x = 0 ; x < 8 ; x++) //There are 8 bytes of data we need to load
    Wire.write(charData[x]); //Write 8 bytes of graphic data to display
}





//Display a given custom character that was previously loaded into CGRAM
void printCustomChar(byte charNumber)
{
  if (charNumber > 7) charNumber = 7; //Error correction

  Wire.write('|'); //Send setting character
  Wire.write(35 + charNumber); //Tell LCD to display custom char # 0-7
}





void setup() {
  SERIAL_PORT_MONITOR.begin(115200);
  //while (!SERIAL_PORT_MONITOR); // Wait for serial port to connect - used on Leonardo, Teensy and other boards with built-in USB CDC serial connection
  SERIAL_PORT_MONITOR.println("Keyboard Controller Program started");

  if (usb.Init() == -1)
    SERIAL_PORT_MONITOR.println("OSC did not start.");

  delay(3000);//wait a second for the SerLCD to initialize before setting it up

  Wire.begin(); //Join the I2C bus
  Wire.setClock(400000);   // Set clock speed to be the fastest for better communication (fast mode)

  Wire.beginTransmission(SerLCD_Address);
  //Send custom characters to display
  //These are recorded to SerLCD and are remembered even after power is lost
  //There is a maximum of 8 custom characters that can be recorded
  loadCustomCharacter(0, back_slash);
  delay(50);
  loadCustomCharacter(1, tilde);
  delay(50);
  Wire.endTransmission(); //Stop I2C transmission

  Wire.beginTransmission(SerLCD_Address);
  loadCustomCharacter(2, heart);
  delay(50);
  loadCustomCharacter(3, empty_heart);
  delay(50);

  Wire.write('|'); //Put LCD into setting mode
  Wire.write(rVal); //Set red backlight

  Wire.write('|'); //Put LCD into setting mode
  Wire.write(gVal); //Set green backlight

  Wire.write('|'); //Put LCD into setting mode
  Wire.write(bVal); //Set blue backlight

  Wire.endTransmission(); //Stop I2C transmission

  delay(50);//short delay before sending next line

  Wire.beginTransmission(SerLCD_Address);
  Wire.write('|'); //Put LCD into setting mode
  Wire.write('-'); //Send clear display command
  //Control the cursor
  Wire.write(254); //Send command character
  Wire.write( (1 << 3) | (1 << 2) | (1 << 0) ); //Cursor on, blinking box on
  blink_box = true;
  Wire.endTransmission(); //Stop I2C transmission

  delay(50); //short delay before sending next line

  //check if pir will acknowledge over I2C
  if (pir.begin() == false) {
    SERIAL_PORT_MONITOR.println("Device did not acknowledge! Freezing.");
    Wire.beginTransmission(SerLCD_Address);
    Wire.print("PIR !Connected");
    Wire.endTransmission(); //Stop I2C transmission
    while (1);
  }

  SERIAL_PORT_MONITOR.println("PIR acknowledged. Waiting 30 Seconds while PIR warms up");
  Wire.beginTransmission(SerLCD_Address);
  Wire.print("Waiting 30 secs for PIR up");
  Wire.endTransmission(); //Stop I2C transmission
  for (uint8_t seconds = 0; seconds < 30; seconds++)
  {
    SERIAL_PORT_MONITOR.println(seconds);

    delay(1000);
  }

  SERIAL_PORT_MONITOR.println("PIR warm!");

  //Use this function call to change the debounce time of the PIR sensor
  //The parameter is the debounce time in milliseconds
  pir.setDebounceTime(500);

  Wire.beginTransmission(SerLCD_Address);
  Wire.write('|'); //Put LCD into setting mode
  Wire.write('-'); //Send clear display command
  Wire.endTransmission(); //Stop I2C transmission
  delay(50); //short delay before sending next line

  Wire.beginTransmission(SerLCD_Address);
  Wire.write('|'); //Send command character
  Wire.write('/'); //disable system messages since current buffer on the SerLCD does not save what is on screen or custom characters correctly
  Wire.endTransmission(); //Stop I2C transmission
  delay(50); //short delay before sending next line




}





void loop() {
  if (activity == true) {

    lastActivityMillis = millis(); //save time when event occurred

    if (lcdContrast != 5) {

      //turn on screen once
      Wire.beginTransmission(SerLCD_Address);
      //Control the cursor and display
      Wire.write('|'); //Put LCD into setting mode
      Wire.write(24); //Value to change contrast
      Wire.write(5); //Contrast from 255 to 0; 5 is default

      //turn on backlight to previous value
      Wire.write('|'); //Put LCD into setting mode
      Wire.write(rVal); //Set red backlight

      Wire.write('|'); //Put LCD into setting mode
      Wire.write(gVal); //Set green backlight

      Wire.write('|'); //Put LCD into setting mode
      Wire.write(bVal); //Set blue backlight

      Wire.endTransmission(); //Stop I2C transmission

      lcdContrast = 5;
    }
    activity = false; //reset flag
  }
  else {
    //check time to see if we are over 10s
    currentMillis = millis();

    if ( ( currentMillis - lastActivityMillis ) < noActivityMillis ) {
      //do nothing since t < 10s
    }
    else {
      //it's been over 10s then let's turn off screen

      if (lcdContrast != 255) {
        //turn off screen once
        Wire.beginTransmission(SerLCD_Address);
        //display off by turning contrast off
        Wire.write('|'); //Put LCD into setting mode
        Wire.write(24); //Value to change contrast
        Wire.write(255);//Contrast from 255 to 0; 5 is default

        delay(50);

        Wire.write('|'); //Put LCD into setting mode
        Wire.write(rVal_OFF); //Set red backlight

        Wire.write('|'); //Put LCD into setting mode
        Wire.write(gVal_OFF); //Set green backlight

        Wire.write('|'); //Put LCD into setting mode
        Wire.write(bVal_OFF); //Set blue backlight

        Wire.endTransmission(); //Stop I2C transmission
        lcdContrast = 255;
      }
    }
  }

  delay(50);




  // Process USB tasks
  usb.Task();

  //check if there is an available PIR event, and tell us what it is!
  if (pir.available()) {
    if (pir.objectDetected()) {
      SERIAL_PORT_MONITOR.println("Object Detected");
      activity = true;
    }
    if (pir.objectRemoved()) {
      SERIAL_PORT_MONITOR.println("Object Removed");
    }
    pir.clearEventBits();
  }

}





//Given a number, i2cSendValue chops up an integer into four values and sends them out over I2C
void i2cSendValue(int value)
{
  Wire.beginTransmission(SerLCD_Address); // transmit to device #1

  Wire.write('|'); //Put LCD into setting mode
  Wire.write('-'); //Send clear display command

  Wire.endTransmission(); //Stop I2C transmission
}

What You Should See

Unplug the USB cable from your computer and insert it into the MicroMod ATP's host port. Power up the board with the 5V wall adapter. You should see something similar to Example 1b. However, the RGB Character LCD turn off whenever there is no activity from the keyboard or motion. This will save a little power as well as grab someone's attention when the Character LCD lights up and displays a custom message.

Making It Better

There’s always room for improvement. After the project was completed, I realized that the project could be improved. Below are a list of possible upgrades and improvements that could be implemented for future builds.

  • Efficient Arduino Code
    • After the finishing the code to display a message on the serial character LCD, I found out there was also an SerLCD Arduino Library to control the serial character LCD. The examples used in this tutorial could be updated with latest SerLCD Arduino Library functions to reduce redundancies in the code.
    • I could also create modular function to move the cursor on the serial character LCD to reduce redundancies in code.
    • To make the code more efficient, run slightly faster, and save space on the processor, I could use #ifdef's use Serial.print() only when I am debugging the code. The trade off is that this would create more lines of code. *The display would react slowly to the button press. Adjusting the delay between each I2C transmission and using the SerLCD Arduino Library could increase amount of characters sent to improve the performance.
  • More macros to include more predefined messages
    • The predefined messages were limited to just the function keys. I could include more predefined messages by using button combinations.
  • Alternative Processors
    • When writing the code, the only processor that had USB host was the MicroMod SAMD51 Processor Board. Now that the MicroMod Teensy Processor Board is available, the code could be modified to use with PJRC's USB Host Library, which can has additional keyboard support. There's also the MicroMod RP2040 Processor Board also has host support through the Raspbbery Pi Pico SDK.
  • Alternative Displays
    • The 16x2 and 20x4 Character LCDs only provide a limited number of characters on the display. Using a different display from the SparkFun catalog could allow for more space to add longer messages or add intricate animations depending on the type of display.

Troubleshooting

  • USB Keyboard Not Compatible
    • Not all USB keyboards are compatible with the USBHost Arduino Library. Try using a different keyboard. Another alternative is to use the MicroMod Teensy Processor Board, which has additional keyboard support.

Resources and Going Further

For more information, check out the resources below regarding this project!

Looking for more inspiration related to USB host? Try switching out the MicroMod SAMD51 Processor with another processor board that is capable of USB host. Of course, you'll need to check the processor's specifications and hookup guide to see if the feature is available. To name a few, the Teensy, STM32, and RP2040 are capable of USB host. However, you'll need to adjust the code with the USB host library depending on the processor board. Or try using mbed's USB host library with the mbed Starter Kit!

mbed Starter Kit Experiment Guide

This Experiment Guide will get you started with the wonderful world of mbed microcontrollers. Need to find that next step after mastering the Arduino? This a great place to take those skills to the next level.

MicroMod Teensy Processor Hookup Guide

Add the processing power and versatility of the Teensy to your MicroMod project following this guide for the SparkFun MicroMod Teensy Processor.

MicroMod RP2040 Processor Board Hookup Guide

This tutorial covers the basic functionality of the MicroMod RP2040 Processor Board and highlights the features of the dual-core ARM Cortex-M0+ processors development board. Get started with the first microcontroller from the Raspberry Pi Foundation!

MicroMod STM32 Processor Hookup Guide

Get started with the MicroMod Ecosystem and the STM32 Processor Board!