Friday, May 22, 2020

ATOM Matrix: Using an I2C Keyboard

Besides supporting analog sensors, the ATOM's Grove port can also be used to interface with I2C sensors. This post demonstrates how to do this using an I2C keyboard. First we read keyboard input into a character array. In the second application we filter and transform incoming characters. Finally, we combine that with the scrolling text message code from an earlier post.

The first two applications should work almost unchanged on regular ESP32 (or even ESP8266) dev boards.

Experimenting (i.e. playing) with the keyboard, there is a problem: the Sym + "." key does NOT return the ">" character.


Store Keyboard Input into a Character Array

The CardKB is handy method of getting user input, but for getting sequences of characters, we must build the result one keypress at a time. The following application does that, and in addition:

  • The maximum length of the character array is enforced
  • When the backspace key is pressed, the last character of the array is removed
  • When the escape key is pressed, the character array is cleared
  • When the return key is pressed, input is "finalized" in that no additional characters are added to the result

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
/*
 * CardKB-Keyboard-00.ino
 * 
 * By: Mike Klepper
 * Date: 26 April 2020
 * 
 * Read input from an I2C keyboard and store
 * result in a character array
 * 
 * Demonstrates basic char array usage
 * 
 * See post on patriot-geek.blogspot.com
 * for details
 */

#include "M5Atom.h"

#define CARDKB_ADDR 0x5F

#define BKSP 8
#define CR 13
#define ESC 27

const int maxMessageLength = 16;
char msg[maxMessageLength];
uint8_t charIndex = 0;
bool messageEntered = false;

void setup() 
{
  M5.begin(true, true, true);
  delay(20);
  Wire.begin(26, 32);
  Serial.println("");
}

void loop() 
{
  Wire.requestFrom(CARDKB_ADDR, 1);

  while(Wire.available() && !messageEntered)
  {
    char c = Wire.read();
    
    if(c != 0)
    {
      Serial.print(c);
      Serial.print(F(" - "));
      Serial.println(c, DEC);

      if(charIndex >= 0 && charIndex < maxMessageLength || c == BKSP || c == CR || c == ESC)
      {
        if(c != BKSP && c != CR && c != ESC)
        {
          msg[charIndex++] = c;
        }
        else if(c == BKSP && charIndex > 0)
        {
          msg[--charIndex] = '\0';
          Serial.print(F("Backspace! "));
        }
        else if(c == ESC)
        {
          charIndex = 0;
          memset(msg, 0, sizeof(msg));
          Serial.print(F("Resetting input! "));
        }
        else if(c == CR)
        {
          messageEntered = true;
          Serial.print(F("Done! "));
        }
      }
      else
      {
        Serial.print(F("Maximum message length reached! "));
      }

      Serial.print(F("msg = "));
      Serial.println(msg);
      Serial.print(F("charIndex = "));
      Serial.println(charIndex);
    }
  }
}

First thing to notice is that Wire.h is apparently not included. As it goes, when the second argument of the M5.begin command is true (line 31), the M5 API includes Wire.h for us. Here's a line-by-line explanation of the code:

Line Comment
18 I2C bus address of the CardKB
20 - 22 ASCII code for backspace, enter key, and escape key
25 Array of characters, with length specified in line above
26 Current character (think of it as a cursor position)
27 When messageEntered becomes true, we are done getting input!
31 Second argument is true, so Wire.h will be included
33 Initialize the Wire library using pins 26 and 32 for SCL and SDA, respectively
41 If messageEntered is false
43 Read one byte as a character
45 If we have a non-zero character
47 - 49 Print it in the serial monitor
51 Enforce max length requirement, but let BKSP, CR, and ESC through
53 If character is not one of those three
55 Increment charIndex and add c to end of the char array
57 If incoming character is a backspace
59 Decrement charIndex and fill with null terminator
62 If it is the escape character
64 Reset charIndex
65 Empty out the msg char array
68 - 72 If character is carriage return, we are done!
76 Ignore incoming character as msg is as long as we allow
79 - 82 Display the msg and the charIndex after each keypress


Filtering and Transforming Keyboard Input

In order to display the message entered by the keyboard, we must do the following:

  • Lower-case letters are converted to upper case letters (since that is what the 5 x 5 font has available)
  • Characters other than letters and digits are ignored (for the same reason)

It makes sense to do these just before the incoming character is added to the msg char array.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
/*
 * CardKB-Keyboard-01.ino
 * 
 * By: Mike Klepper
 * Date: 26 April 2020
 * 
 * Read input from an I2C keyboard, filter and 
 * transform it
 * 
 * See post on patriot-geek.blogspot.com
 * for details
 */

#include "M5Atom.h"

#define CARDKB_ADDR 0x5F

#define BKSP 8
#define CR 13
#define ESC 27

const int maxMessageLength = 16;
char msg[maxMessageLength];
uint8_t charIndex = 0;
bool messageEntered = false;

void setup() 
{
  M5.begin(true, true, true);
  delay(20);
  Wire.begin(26, 32);
  Serial.println("");
}

void loop() 
{
  Wire.requestFrom(CARDKB_ADDR, 1);

  while(Wire.available() && !messageEntered)
  {
    char c = Wire.read();
    
    if(c != 0)
    {
      Serial.print(c);
      Serial.print(F(" - "));
      Serial.println(c, DEC);

      if(charIndex >= 0 && charIndex < maxMessageLength || c == BKSP || c == CR || c == ESC)
      {
        if(c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == ' ')
        {
          msg[charIndex++] = c;
        }
        else if(c >= 'a' && c <= 'z')
        {
          msg[charIndex++] = c - 32;
        }
        else if(c == BKSP && charIndex > 0)
        {
          msg[--charIndex] = '\0';
          Serial.print(F("Backspace! "));
        }
        else if(c == ESC)
        {
          charIndex = 0;
          memset(msg, 0, sizeof(msg));
          Serial.print(F("Resetting input! "));
        }
        else if(c == CR)
        {
          messageEntered = true;
          Serial.print(F("Done! "));
        }
      }
      else
      {
        Serial.print(F("Maximum message length reached! "));
      }

      Serial.print(F("msg = "));
      Serial.println(msg);
      Serial.print(F("charIndex = "));
      Serial.println(charIndex);
    }
  }
}

The only real difference between this code and the previous app's is that instead of indiscriminately adding the incoming character to the end of msg (lines 53-56 in first application) we do the following:

Line Comment
51 If the incoming character is either a digit, an upper-case letter, or a space
53 Increment charIndex and add it to the end of msg
55 Else if the incoming character is a lower-case letter
57 Increment charIndex, convert it to uppercase, and add it to the end of msg


Displaying Input

This last application combines the above keyboard input code with the code for displaying scrolling text from an earlier post, each wrapped in its own function.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
/*
 * CardKB-Keyboard-02.ino
 * 
 * By: Mike Klepper
 * Date: 26 April 2020
 * 
 * Read input from an I2C keyboard and display it!
 * 
 * See post on patriot-geek.blogspot.com
 * for details
 */

#include "M5Atom.h"
#include <Adafruit_GFX.h>
#include <Adafruit_NeoMatrix.h>
#include <Adafruit_NeoPixel.h>

#define DISPLAY_PIN 27
#define CARDKB_ADDR 0x5F

#define BKSP 8
#define CR 13
#define ESC 27

int directionAndOrientation = NEO_MATRIX_TOP + NEO_MATRIX_LEFT + NEO_MATRIX_ROWS;
int pixelType = NEO_GRB + NEO_KHZ800;

Adafruit_NeoMatrix matrix = Adafruit_NeoMatrix(5, 5, DISPLAY_PIN,
  directionAndOrientation + NEO_MATRIX_PROGRESSIVE,
  pixelType);

int xPos  = matrix.width();

const int maxMessageLength = 16;
char msg[maxMessageLength];
uint8_t charIndex = 0;
bool messageEntered = false;

void setup() 
{
  M5.begin(true, true, true);
  delay(20);
  Wire.begin(26, 32);
  Serial.println("");

  matrix.begin();
  matrix.setTextWrap(false);
  matrix.setBrightness(60);
  matrix.setTextColor(matrix.Color(80, 0, 80));
}

void loop()
{
  if(!messageEntered)
  {
    getMessageFromCardKB();
  }
  else
  {
    displayMessage(msg);
  }
}


void getMessageFromCardKB() 
{
  Wire.requestFrom(CARDKB_ADDR, 1);

  while(Wire.available() && !messageEntered)
  {
    char c = Wire.read();
    
    if(c != 0)
    {
      Serial.print(c);
      Serial.print(F(" - "));
      Serial.println(c, DEC);

      if(charIndex >= 0 && charIndex < maxMessageLength || c == BKSP || c == CR || c == ESC)
      {
        if(c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == ' ')
        {
          msg[charIndex++] = c;
        }
        else if(c >= 'a' && c <= 'z')
        {
          msg[charIndex++] = c - 32;
        }
        else if(c == BKSP && charIndex > 0)
        {
          msg[--charIndex] = '\0';
          Serial.print(F("Backspace! "));
        }
        else if(c == ESC)
        {
          charIndex = 0;
          memset(msg, 0, sizeof(msg));
          Serial.print(F("Resetting input! "));
        }
        else if(c == CR)
        {
          messageEntered = true;
          Serial.print(F("Done! "));
        }
      }
      else
      {
        Serial.print(F("Maximum message length reached! "));
      }

      Serial.print(F("msg = "));
      Serial.println(msg);
      Serial.print(F("charIndex = "));
      Serial.println(charIndex);
    }

    delay(1);
  }
}


void displayMessage(char message[])
{
  Serial.print("Displaying ");
  Serial.println(msg);

  String msgString = String(message);
  int msgLength = msgString.length();
  int charWidth = 5;
  int numTrailingSpaces = 3;
  int maxLeftPosition = (msgLength + numTrailingSpaces) * (charWidth + 1);
  
  int scrollDelay = 100;

  while(true)
  {
    matrix.fillScreen(0);
    matrix.setCursor(xPos, 0);
    matrix.print(msgString);
  
    if(--xPos < -maxLeftPosition)
    {
      xPos = matrix.width();
    }
    
    matrix.show();
    delay(scrollDelay);
  }
}

Click here to go to the table of contents for this series.

No comments:

Post a Comment