Friday, May 22, 2020

ATOM Matrix: Basic Display Usage

One of the best things about the boards from M5Stack is their API, which handles display, button debounce, and other hardware interfaces. Documents and source code for the ATOM's API can be found here: https://github.com/m5stack/M5Atom

To include the API in a sketch, use the following:

#include "M5Atom.h"
This will define a class called M5Atom, and create an instance of that class called M5.

Here are the most common API methods we'll be using:

To initialize the ATOM, use this:

M5.begin(bool SerialEnable , bool I2CEnable , bool DisplayEnable)
The first argument should be set to true when we want to do serial output
The second argument enables I2C, so would be set to true when using some Grove sensors
The third argument, when set to true, enables the 5x5 RGB LED display.

We will be using four methods from that API for displaying data:

  • M5.dis.setBrightness(uint8_t brightness)
  • M5.dis.drawpix(uint8_t xpos, uint8_t ypos, CRGB Color)
  • M5.dis.drawpix(uint8_t Number, CRGB Color)
  • M5.dis.clear()

There are other display-related methods that we'll not be using.

Finally, use this method to poll for button clicks:

M5.update()


Activating a Single Pixel

To introduce the M5Atom API, here is a simple sketch that causes the RGB LED in the top-left corner to blink. Thrilling, no?

 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
/*
 * SinglePixel.ino
 * 
 * By: Mike Klepper
 * Date: 16 May 2020
 * 
 * Introduces the M5Atom API
 */

#include "M5Atom.h"

void setup() 
{
  M5.begin(true, false, true);
  delay(20);
}

void loop() 
{
  M5.dis.drawpix(0, 0, 0xffffff);
  Serial.println(F("Pixel is on"));
  delay(250);
  M5.dis.clear();
  Serial.println(F("Pixel is off"));
  delay(250);
  M5.update();
}
Line Comment
10 Include the M5Atom API, which creates an object called M5
14 Initialize the M5 object, enabling serial output, disabling I2C, and enabling the display
15 This delay is not needed, but apps that use the IMU seem to require it
20 Set the pixel at (0, 0) to be white - so we know where the origin is!
23 Clear the display
26 Call update method, which polls for button events (unnecessary for this particular sketch)

Before uploading this program, choose the proper serial port from the Tools > Port menu, and set the board to be "M5Stick-C".

Something to notice is what is missing from this program:

Serial.begin(115200)
This line is included in the M5.begin method when the first argument is set to true.


Color Order

When you attempt to change the color of the pixel's color in line 20, you'll get unexpected results! For example, if you change the line to read:

M5.dis.drawpix(0, 0, 0xff0000);
instead of getting blinking red, you'll get a green blinking pixel!

As it goes, the color order that these LEDs use isn't the RRGGBB found in HTML, but GGRRBB. To demonstrate this (and to demonstrate the axis orientation), this program will plot a red pixel in the top-right corner, a green pixel at bottom-left, and a blue pixel at the bottom-right corner.

 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
/*
 * FourPixels.ino
 * 
 * By: Mike Klepper
 * Date: 16 May 2020
 * 
 * Demonstrates GRB color order for the LEDs as well as axis orientation
 */

#include "M5Atom.h"

void setup() 
{
  M5.begin(true, false, true);
  delay(20);
}

void loop() 
{
  M5.dis.drawpix(0, 0, 0xffffff); // White
  M5.dis.drawpix(4, 0, 0x00ff00); // Red
  M5.dis.drawpix(0, 4, 0xff0000); // Green
  M5.dis.drawpix(4, 4, 0x0000ff); // Blue
  Serial.println(F("Pixels are on"));
  delay(250);
  
  M5.dis.clear();
  Serial.println(F("Pixels are off"));
  delay(250);
  
  M5.update();
}

So we see that the pixels are numbered 0 to 4, inclusive, along each axis, and that the y-axis points down.


Pixel Order

The programs so far has used the drawpix(x, y, c) method. We will have a need for the other drawpix method in the API, the drawpix(n, c). This method counts from the top-left (n = 0) to the bottom-right (n = 24), but are the pixels in the middle ordered like words in an English paragraph (line-by-line, top-to-bottom) or in a zig-zag manner? Let's find out!

 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
/**
 * LED-Order.ino
 *
 * By: Mike Klepper
 * Date: 26 April 2020
 *
 * Demonstrates order that pixels are addressed
 *
 * See blog post on patriot-geek.blogspot.com
 * for instructions.
 */


#include "M5Atom.h"

int GRB_COLOR_WHITE = 0xffffff;
int GRB_COLOR_BLACK = 0x000000;

int delayAmt = 1000;

void setup() 
{
  M5.begin(true, false, true);
  delay(50);
}

void loop() 
{
  for(int i = 0; i < 25; i++)
  {
    M5.dis.drawpix(i, GRB_COLOR_WHITE);
    delay(50);
  }
  delay(delayAmt);

  for(int i = 0; i < 25; i++)
  {
    M5.dis.drawpix(i, GRB_COLOR_BLACK);
    delay(50);
  }
  
  delay(delayAmt);
  M5.update();
}

Thus the pixel order with the drawpix(n, c) method is the same as the reading direction in English.


Controlling Brightness

Something unexpected is that changing the overall brightness of the display does NOT require a redraw! Here's a demo:

 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
/*
 * BrightnessDemo.ino
 * 
 * By: Mike Klepper
 * Date: 16 May 2020
 * 
 * Demonstrates that changing brightness does NOT require a redraw!
 */

#include "M5Atom.h"

void setup() 
{
  M5.begin(true, false, true);
  delay(20);

  for(int i = 0; i < 25; i++)
  {
    M5.dis.drawpix(i, 0xffffff);
  }
}

void loop() 
{
  for(int i = 60; i >= 0; i--)
  {
    M5.dis.setBrightness(i);
    delay(50);
  }

  for (int i = 0; i < 60; i++)
  {
    M5.dis.setBrightness(i);
    delay(50);
  }
  
  M5.update();
}

The documents for the ATOM Matrix state that the maximum brightness for the pixels should be 60, hence the limits on the for-loops in lines 25 and 31.


Displaying Single Characters

In future parts of this series, we will frequently need to display single characters or other patterns. This can be accomplished by specifying the desired colors of the pixels in a 1-dimensional array, and looping over that array. The last program in this post displays a Morse code message, thus demonstrating how to display dash and dot characters. As it goes, this program mostly shows how to handle character arrays and strings in C.

  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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
/**
 * MorseCode.ino
 *
 * By: Mike Klepper
 * Date: 26 April 2020
 *
 * Converts string stored in message into Morse code
 *
 * See blog post on patriot-geek.blogspot.com
 * for instructions.
 */

#include "M5Atom.h"

int GRB_COLOR_WHITE = 0xffffff;
int GRB_COLOR_BLACK = 0x000000;
int GRB_COLOR_RED = 0x00ff00;
int GRB_COLOR_ORANGE = 0xa5ff00;
int GRB_COLOR_YELLOW = 0xffff00;
int GRB_COLOR_GREEN = 0xff0000;
int GRB_COLOR_BLUE = 0x0000ff;
int GRB_COLOR_PURPLE = 0x008080;

int activeColor = GRB_COLOR_RED;

int colorList[] = {GRB_COLOR_BLACK, activeColor};

// Constants used in Farnsworth timing
// See https://morsecode.world/international/timing.html for details
int scale = 200;
int dotDuration = 1 * scale;
int dashDuration = 3 * scale;
int timeBetweenDotsAndDashes = 1 * scale;
int timeBetweenCharacters = 3 * scale;
int spaceDuration = 7 * scale;

String message = F("Trump 2020 Keep America Great");



int dot[25] = 
{
  0,0,0,0,0,
  0,0,0,0,0,
  0,0,1,1,0,
  0,0,1,1,0,
  0,0,0,0,0
};

int dash[25] = 
{
  0,0,0,0,0,
  0,0,0,0,0,
  1,1,1,1,1,
  1,1,1,1,1,
  0,0,0,0,0
};


char* letters[] = {
  ".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..",    // A-I
  ".---", "-.-", ".-..", "--", "-.", "---", ".--.", "--.-", ".-.",  // J-R 
  "...", "-", "..-", "...-", ".--", "-..-", "-.--", "--.."          // S-Z
};

char* digits[] = {
  "-----", ".----", "..---", "...--", "....-", ".....",             // 0-5
  "-....", "--...", "---..", "----."                                // 6-9
};


void setup() 
{
  M5.begin(true, false, true);
  delay(20);
}


void loop() 
{
  displayMessageInMorseCode(message);
  finalAnimation();
  delay(2000);
}

void displayMessageInMorseCode(String message)
{
  message.toUpperCase();
  
  int msgLength = message.length();

  Serial.println("");

  for(int i = 0; i < msgLength; i++)
  {
    char currentChar = message.charAt(i);
    String morseCodeForCurrentChar;
    Serial.print(currentChar);
    Serial.print(" ");
    
    if(isAlpha(currentChar))
    {
      morseCodeForCurrentChar = letters[currentChar - 65];
      Serial.print(letters[currentChar - 65]);
      Serial.println(" - Alpha");

      displaySingleMorseCodeCharacter(morseCodeForCurrentChar);
    }
    else if(isDigit(currentChar))
    {
      morseCodeForCurrentChar = digits[currentChar - 48];
      Serial.print(digits[currentChar - 48]);
      Serial.println(" - Digit");

      displaySingleMorseCodeCharacter(morseCodeForCurrentChar);
    }
    else if(isSpace(currentChar))
    {
      Serial.println(" - Space");
      delay(spaceDuration);
    }
  }
}

void displaySingleMorseCodeCharacter(String morseCodeCharacter)
{
  int morseCodeLength = morseCodeCharacter.length();

  for(int i = 0; i < morseCodeLength; i++)
  {
    char currentDotOrDash = morseCodeCharacter.charAt(i);
    Serial.println(currentDotOrDash);

    if(currentDotOrDash == '.')
    {
      M5.dis.clear();
      drawArray(dot, colorList);
      delay(dotDuration);
    }
    else
    {
      M5.dis.clear();
      drawArray(dash, colorList);
      delay(dashDuration);
    }

    M5.dis.clear();
    delay(timeBetweenDotsAndDashes);
  }

  delay(timeBetweenCharacters);
  Serial.println("---------------------");
}


void drawArray(int arr[], int colors[])
{
  for(int i = 0; i < 25; i++)
  {
      M5.dis.drawpix(i, colors[arr[i]]);
  }
}

void fillDisplay(int fillColor)
{
  for(int i = 0; i < 25; i++)
  {
      M5.dis.drawpix(i, fillColor);
  }
}

void finalAnimation()
{
  delay(2000);
  
  M5.dis.clear();
  fillDisplay(GRB_COLOR_RED);
  delay(2000);
  fillDisplay(GRB_COLOR_WHITE);
  delay(2000);
  fillDisplay(GRB_COLOR_BLUE);
  delay(2000);
  M5.dis.clear();
}

It is easier to understand the code starting from the bottom...

Line Comment
156 - 162 Draws an array using the colors specified in the array of colors
164 - 170 Fills the display with a single color
172 - 184 Animation that is displayed at the end of the Morse code message
15 - 22 Handy color constants!
24 Color used to display the Morse code message
26 Colors used when displaying a character (both off and on colors)
28 - 35 Constants that control the Morse code speed
37 The message string that will be displayed - MAGA!
41 - 48 Array describing the dot character
50 - 57 Array for the dash character
60 - 69 The Morse code for letters and numbers
72 - 76 Usual setup function
79 - 84 The loop function - display the message, do the animation, pause for two seconds
86 - 124 This function looks-up each character in lines 60 - 69 by ASCII code
94 Loop over all characters in the message
96 Get current character
101 If the character is a letter...
103 Look up character in the letters array, offsetting the ASCII code by 65 (the ASCII code for 'A')
107 Display the single letter in Morse code
109 Else if the character is a digit...
111 Look up character in the digits array, offsetting ASCII code by 48 (the ASCII code for '0')
115 Display the single digit in Morse code
117 - 121 A space is just a pause in Morse code
125 - 149 Function for displaying the Morse code equivalent of a single character
129 Loop over the dots and dashes
131 Get current dot or dash
134 - 139 If it is a dot, show the dot character, and delay
140 - 145 Same for dash
151 Pause between characters
156 - 162 Display a character by looping over its array representation
158 Loop over the given array
160 Look up the color in the colorList and plot it!

Not very memory efficient code, but there it is.

The drawArray function is quite flexible! As used here, it displays characters using only one color for all pixels (and the other pixels are black). In later posts in this series, we will use that same function to show characters or icons using more colors!

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

3 comments:

  1. Great article, well structered and extremly understandable. Highly recommanded to all all who would like to use M5Stack Atom Matrix

    ReplyDelete
  2. Thanks for sharing this useful content. STONE Tech is a manufacturer of HMI display module Intelligent TFT LCD.

    tft color lcd display

    ReplyDelete