Software

Getting Code Onto M0 Feather

In this portion of the guide, we'll get code uploaded to the Adafruit Feather M0 micro-controller. If you don't write / understand code, don't to worry! You don't need to be a programmer to be able to upload prewritten code :-) 

We'll walk you through the whole process. 

First, visit the Feather M0 tutorial page by clicking the button below. Follow the instructions to download & setup the Arduino IDE and install drivers.

Make sure you are able to get sketches compiled and uploaded, especially the blink example in the tutorial. Once you are comfortable with using the Feather M0, you can continue!

Download Adafruit_IS31FL3731 library

Before uploading the code,you will need to download Adafruit_IS31FL3731 from our github repository. You can do that by visiting the github repo and manually downloading or, easier, just click this button to download the zip.

Rename the uncompressed folder Adafruit_IS31FL3731 and check that the Adafruit_IS31FL3731folder contains Adafruit_IS31FL3731.cpp and Adafruit_IS31FL3731.h

Place the Adafruit_IS31FL3731 library folder your arduinosketchfolder/libraries/ folder.
You may need to create the libraries subfolder if its your first library. Restart the IDE.

We also have a great tutorial on Arduino library installation at:
http://learn.adafruit.com/adafruit-all-about-arduino-libraries-install-use

Rename the uncompressed folder Adafruit_GFX and check that the Adafruit_GFX folder contains Adafruit_GFX.cpp and Adafruit_GFX.h

Place the Adafruit_GFX library folder your arduinosketchfolder/libraries/ folder like you did with the IS31FL library

Lines of Code, The Matrix, and Game of Life

Written by Phillip Burgess, this sketch cycles between different effects: typing code, Conway's Game of Life, The Matrix effect, and a blank screen w/blinking cursor. It doesn't require the GFX or IS31FL3731 library but instead  addresses the CharliePlex driver chip directly to achieve smooth full-screen animation.

//--------------------------------------------------------------------------
// Animated Commodore PET plaything. Uses the following parts:
//   - Feather M0 microcontroller (adafruit.com/products/2772)
//   - 9x16 CharliePlex matrix (2972 is green, other colors avail.)
//   - Optional LiPoly battery (1578) and power switch (805)
//
// This is NOT good "learn from" code for the IS31FL3731. Taking a cue from
// our animated flame pendant project, this code addresses the CharliePlex
// driver chip directly to achieve smooth full-screen animation.  If you're
// new to graphics programming, download the Adafruit_IS31FL3731 and
// Adafruit_GFX libraries, with examples for drawing pixels, lines, etc.
//
// Animation cycles between different effects: typing code, Conway's Game
// of Life, The Matrix effect, and a blank screen w/blinking cursor (shown
// for a few seconds before each of the other effects; to imply "loading").
//--------------------------------------------------------------------------

#include <Wire.h>

#define I2C_ADDR 0x74        // I2C address of Charlieplex matrix
#define WIDTH      16        // Matrix size in pixels
#define HEIGHT      9
#define GAMMA     2.5        // Gamma-correction exponent
uint8_t img[WIDTH * HEIGHT], // 8-bit buffer for image rendering
        bitmap[((WIDTH+7)/8) * HEIGHT], // 1-bit buffer for some modes
        gamma8[256],         // Gamma correction (brightness) table
        page  = 0,           // Double-buffering front/back control
        frame = 0;           // Frame counter used by some animation modes
// More globals later, above code for each animation, and before setup()


// UTILITY FUNCTIONS -------------------------------------------------------

// Begin I2C transmission and write register address (data then follows)
uint8_t writeRegister(uint8_t n) {
  Wire.beginTransmission(I2C_ADDR);
  Wire.write(n); // No endTransmission() - left open for add'l writes
  return 2;      // Always returns 2; count of I2C address + register byte n
}

// Select one of eight IS31FL3731 pages, or the Function Registers
void pageSelect(uint8_t n) {
  writeRegister(0xFD); // Command Register
  Wire.write(n);       // Page number (or 0xB = Function Registers)
  Wire.endTransmission();
}

// Set bit at (x,y) in the bitmap buffer (no clear function, wasn't needed)
void bitmapSetPixel(int8_t x, int8_t y) {
  bitmap[y * ((WIDTH + 7) / 8) + x / 8] |= 0x80 >> (x & 7);
}

// Read bit at (x,y) in bitmap buffer, returns nonzero (not always 1) if set
uint8_t bitmapGetPixel(int8_t x, int8_t y) {
  return bitmap[y * ((WIDTH + 7) / 8) + x / 8] & (0x80 >> (x & 7));
}


// BLINKING CURSOR / LOADING EFFECT ----------------------------------------
// Minimal animation - just one pixel in the corner blinks on & off,
// meant to suggest "program loading" or similar busy effect.

void cursorLoop() {
  img[0] = (frame & 1) * 255;
}


// TERMINAL TYPING EFFECT --------------------------------------------------
// I messed around trying to make a random "fake code generator," but it
// was getting out of hand. Instead, the typed "code" is just a bitmap!

const uint16_t codeBits[] = {
  0b1110111110100000,
  0b0011101100000000,
  0b0011011111101000,
  0b0000111111011100,
  0b0000111011100000,
  0b0010000000000000,
  0b0011011111000000,
  0b1000000000000000,
  0b0000000000000000,
  0b1111011010000000,
  0b0011110111110110,
  0b1000000000000000,
  0b0000000000000000,
  0b1110111101000000,
  0b0011101011010000,
  0b0011110111111000,
  0b0011101101110000,
  0b0011011111101000,
  0b0000111011111100,
  0b0010000000000000,
  0b1000000000000000,
  0b0000000000000000,
  0b1110110100000000,
  0b0011011111110100,
  0b0000111101100000,
  0b0010000000000000,
  0b0011110111110000,
  0b0011101111011000,
  0b1000000000000000,
  0b0000000000000000
};

uint8_t cursorX, cursorY, line;

void typingSetup() {
  cursorX = cursorY = line = 0;
}

void typingLoop() {
  img[cursorY * WIDTH + cursorX] = // If bit set, "type" random char
    ((codeBits[line] << cursorX) & 0x8000) ? random(32, 128) : 0;

  cursorX++;
  if(!(uint16_t)(codeBits[line] << cursorX)) { // End of line reached?
    cursorX = 0;
    if(cursorY >= HEIGHT-1) { // Cursor on last line?
      uint8_t y;
      for(y=0; y<HEIGHT-1; y++) // Move img[] buffer up one line
        memcpy(&img[y * WIDTH], &img[(y+1) * WIDTH], WIDTH);
      memset(&img[y * WIDTH], 0, WIDTH); // Clear last line
    } else cursorY++;
    if(++line >= (sizeof(codeBits) / sizeof(codeBits[0]))) line = 0;
  }
  img[cursorY * WIDTH + cursorX] = 255; // Draw cursor in new position
}


// MATRIX EFFECT -----------------------------------------------------------
// Inspired by "The Matrix" coding effect -- 'raindrops' travel down the
// screen, their 'tails' twinkle slightly and fade out.

#define N_DROPS 15
struct {
  int8_t  x, y; // Position of raindrop 'head'
  uint8_t len;  // Length of raindrop 'tail' (not incl head)
} drop[N_DROPS];

void matrixRandomizeDrop(uint8_t i) {
  drop[i].x   = random(WIDTH);
  drop[i].y   = random(-18, 0);
  drop[i].len = random(9, 18);
}

void matrixSetup() {
  for(uint8_t i=0; i<N_DROPS; i++) matrixRandomizeDrop(i);
}

void matrixLoop() {
  uint8_t i, j;
  int8_t  y;

  for(i=0; i<N_DROPS; i++) { // For each raindrop...
    // If head is onscreen, overwrite w/random brightness 20-80
    if((drop[i].y >= 0) && (drop[i].y < HEIGHT))
      img[drop[i].y * WIDTH + drop[i].x] = random(20, 80);
    // Move pos. down by one. If completely offscreen (incl tail), make anew
    if((++drop[i].y - drop[i].len) >= HEIGHT) matrixRandomizeDrop(i);
    for(j=0; j<drop[i].len; j++) {     // For each pixel in drop's tail...
      y = drop[i].y - drop[i].len + j; // Pixel Y coord
      if((y  >= 0) && (y < HEIGHT)) {  // On screen?
        // Make 4 pixels at end of tail fade out.  For other tail pixels,
        // there's a 1/10 chance of random brightness change 20-80
        if(j < 4)            img[y * WIDTH + drop[i].x] /= 2;
        else if(!random(10)) img[y * WIDTH + drop[i].x] = random(20, 80);
      }
    }
    if((drop[i].y >= 0) && (drop[i].y < HEIGHT)) // If head is onscreen,
      img[drop[i].y * WIDTH + drop[i].x] = 255;  // draw w/255 brightness
  }
}


// CONWAY'S GAME OF LIFE ---------------------------------------------------
// The rules: if cell at (x,y) is currently populated, it stays populated
// if it has 2 or 3 populated neighbors, else is cleared.  If cell at (x,y)
// is currently empty, populate it if 3 neighbors.

void lifeSetup() { // Fill bitmap with random data
  for(uint8_t i=0; i<sizeof(bitmap); i++) bitmap[i] = random(256);
}

void lifeLoop() {
  static const int8_t xo[] = { -1,  0,  1, -1, 1, -1, 0, 1 },
                      yo[] = { -1, -1, -1,  0, 0,  1, 1, 1 };
  int8_t              x, y;
  uint8_t             i, n;

  // Modify img[] based on old contents (dimmed) + new bitmap
  for(i=y=0; y<HEIGHT; y++) {
    for(x=0; x<WIDTH; x++, i++) {
      if(bitmapGetPixel(x, y)) img[i]  = 255;
      else if(img[i] > 28)     img[i] -= 28;
      else                     img[i]  = 0;
    }
  }

  // Generate new bitmap (next frame) based on img[] contents + rules
  memset(bitmap, 0, sizeof(bitmap));
  for(y=0; y<HEIGHT; y++) {
    for(x=0; x<WIDTH; x++) {
      for(i=n=0; (i < sizeof(xo)) && (n < 4); i++)
        n += (img[((y+yo[i])%HEIGHT) * WIDTH + ((x+xo[i])%WIDTH)] == 255);
      if((n == 3) || ((n == 2) && (img[y * WIDTH + x] == 255)))
        bitmapSetPixel(x, y);
    }
  }

  // Every 32 frames, populate a random cell so animation doesn't stagnate
  if(!(frame & 0x1F)) bitmapSetPixel(random(WIDTH), random(HEIGHT));
}


// MORE GLOBAL STUFF - ANIMATION STATES ------------------------------------

struct { // For each of the animation modes...
  void    (*setup)(void); // Animation setup func (run once on mode change)
  void    (*loop)(void);  // Animation loop func (renders one frame)
  uint8_t maxRunTime;     // Animation run time in seconds
  uint8_t fps;            // Frames-per-second for this effect
} anim[] = {
  NULL       , cursorLoop,  3,  4,
  typingSetup, typingLoop, 15, 15,
  lifeSetup  , lifeLoop  , 12, 30,
  matrixSetup, matrixLoop, 15, 10,
};

uint8_t  seq[] = { 0, 1, 0, 2, 0, 3 }, // Sequence of animation modes
         idx   = sizeof(seq) - 1;      // Current position in seq[]
uint32_t modeStartTime = 0x7FFFFFFF;   // micros() when current mode started


// SETUP - RUNS ONCE AT PROGRAM START --------------------------------------

void setup() {
  uint16_t i;
  uint8_t  p, bytes;

  randomSeed(analogRead(A0));              // Randomize w/unused analog pin
  Wire.begin();                            // Initialize I2C
  Wire.setClock(400000L);                  // 400 KHz I2C = faster updates

  // Initialize IS31FL3731 directly (no library)
  pageSelect(0x0B);                        // Access the Function Registers
  writeRegister(0);                        // Starting from first...
  for(i=0; i<13; i++) Wire.write(10 == i); // Clear all except Shutdown
  Wire.endTransmission();
  for(p=0; p<2; p++) {                     // For each page used (0 & 1)...
    pageSelect(p);                         // Access the Frame Registers
    for(bytes=i=0; i<180; i++) {           // For each register...
      if(!bytes) bytes = writeRegister(i); // Buf empty? Start xfer @ reg i
      Wire.write(0xFF * (i < 18));         // 0-17 = enable, 18+ = blink+PWM
      if(++bytes >= SERIAL_BUFFER_SIZE) bytes = Wire.endTransmission();
    }
    if(bytes) Wire.endTransmission();      // Write any data left in buffer
  }

  for(i=0; i<256; i++) // Initialize gamma-correction table:
    gamma8[i] = (uint8_t)(pow(((float)i / 255.0), GAMMA) * 255.0 + 0.5);
}


// LOOP - RUNS ONCE PER FRAME OF ANIMATION ---------------------------------

uint32_t prevTime  = 0x7FFFFFFF; // Used for frame-to-frame animation timing
uint32_t frameUsec = 0L;         // Frame interval in microseconds

void loop() {
  // Wait for FPS interval to elapse (this approach is more consistent than
  // delay() as the animation rendering itself takes indeterminate time).
  uint32_t t;
  while(((t = micros()) - prevTime) < frameUsec);
  prevTime = t;

  // Display frame rendered on prior pass.  This is done immediately
  // after the FPS sync (rather than after rendering) to ensure more
  // uniform animation timing.
  pageSelect(0x0B);    // Function registers
  writeRegister(0x01); // Picture Display reg
  Wire.write(page);    // Page #
  Wire.endTransmission();
  page ^= 1;           // Flip front/back buffer index

  anim[seq[idx]].loop();                     // Render next frame
  frameUsec = 1000000L / anim[seq[idx]].fps; // Frame hold time

  // Write img[] array to matrix thru gamma correction table
  uint8_t i, bytes; // Pixel #, Wire buffer counter
  pageSelect(page); // Select background buffer
  for(bytes=i=0; i<WIDTH*HEIGHT; i++) {
    if(!bytes) bytes = writeRegister(0x24 + i);
    Wire.write(gamma8[img[i]]);
    if(++bytes >= SERIAL_BUFFER_SIZE) bytes = Wire.endTransmission();
  }
  if(bytes) Wire.endTransmission();

  // Time for new mode?
  if((t - modeStartTime) > (anim[seq[idx]].maxRunTime * 1000000L)) {
    if(++idx >= sizeof(seq)) idx = 0;
    memset(img, 0, sizeof(img));
    if(anim[seq[idx]].setup) anim[seq[idx]].setup();
    modeStartTime = t;
    frame = 0;
  } else frame++;
}
This guide was first published on Nov 30, 2016. It was last updated on Nov 18, 2018. This page (Software) was last updated on Nov 29, 2016.