Hi everyone. I made a change to my code resulting in what appears to be an unrelated error, so I am assuming I am encountering a memory allocation issue, but I have no idea why. Here's the story....
My 9 year old son conceived of a "smart hoodie" that he could wear, and that had a display screen built into the front. He wanted it to be able to display his school name, twinkle some lights, play rock paper scissors, and answer yes or no to a question. I got all this working. I wrote a function CheerText to display text in "cheer" format, where one letter is displayed at a time like cheerleaders might call them out. That function takes a color, some text, and a speed (for the delay between letters) as input, and then displays the resultant cheer. I also created a Twinkle routine to twinkle 20 LEDs at a time. When I showed it to my son, one of his pieces of feedback is that he wanted more LEDs to twinkle at a time. So I upped the number to 50. The weird thing is that once I did that, the Twinkle() routine DID show 50 LEDs twinkling instead of 20, BUT the CheerText() routine appears to get skipped, and there are some random LEDs that get lit up instead. But I literally changed nothing else. My code is pasted below. I'm using an adafruit Flora with an adafruit 16x16 neopixel matrix, and I have a button wired up for control. The matrix is separately powered by a 9v battery connected through a UBEC DC/DC Step-Down (Buck) Converter (also from adafruit). Whenever I change MAXSTARS to 50, and then use the array initialization for the 50 sized array, the CheerText routine craps out. If I change it back to 20, everything works as intended. This one has me stumped. Here's my code:
#include <gfxfont.h>
#include <Adafruit_NeoPixel.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SPITFT.h>
#include <Adafruit_SPITFT_Macros.h>
#include <Adafruit_NeoMatrix.h>
#include <gamma.h>
// NeoPixel Information
#define MATRIXPIN 6 // pin the NeoPixel Matrix is connected to
#define LEDPIN 7 // pin for the on-board LED on the Flora
#define PIXELPIN 8 // pin for the on-board single NeoPixel on the Flora
#define NUMPIXELS 256
#define GRID_COLS 16
#define GRID_ROWS 16
#define MAXSTARS 20 // set to number of desired stars to appear at once in Twinkle routine below
#define DEFBRIGHTNESS 25 // Default brightness level unless otherwise specified.
Adafruit_NeoPixel thePixel(1, PIXELPIN, NEO_GRB + NEO_KHZ800);
Adafruit_NeoMatrix matrix = Adafruit_NeoMatrix(GRID_COLS, GRID_ROWS, MATRIXPIN,
NEO_MATRIX_BOTTOM + NEO_MATRIX_RIGHT +
NEO_MATRIX_COLUMNS + NEO_MATRIX_ZIGZAG,
NEO_GRB + NEO_KHZ800);
#define OFF matrix.Color(0, 0, 0)
#define BLUE matrix.Color(0, 0, 255)
#define GREEN matrix.Color(0, 255, 0)
#define RED matrix.Color(255, 0, 0)
#define GREY matrix.Color(128, 15, 0)
#define WHITE matrix.Color(255, 255, 255)
#define CYAN matrix.Color(0, 255, 255)
#define PURPLE matrix.Color(255, 0, 255)
#define YELLOW matrix.Color(255, 255, 0)
#define ORANGE matrix.Color(255, 165, 0)
// global constants
int mode = 0;
int buttonPin = 10; // pin the bushbutton is connected to
const int maxMode = 5; // set to the max mode number coded below
int buttonState = 0; // variable for reading the pushbutton status
int offset = 0; // scrolling offset placeholder
/*****************************************************************************************
This next section was copied from here for the flame animation:
https://github.com/mic159/NeoFire/blob/master/NeoFire.ino
*****************************************************************************************/
//these values are substracetd from the generated values to give a shape to the animation
const unsigned char valueMask[GRID_ROWS][GRID_COLS]={
{16 , 0 , 0 , 0 , 0 , 0 , 0 , 16 , 16 , 0 , 0 , 0 , 0 , 0 , 0 , 16 },
{32 , 0 , 0 , 0 , 0 , 0 , 0 , 32 , 32 , 0 , 0 , 0 , 0 , 0 , 0 , 32 },
{48 , 0 , 0 , 0 , 0 , 0 , 0 , 48 , 48 , 0 , 0 , 0 , 0 , 0 , 0 , 48 },
{64 , 8 , 0 , 0 , 0 , 0 , 8 , 64 , 64 , 8 , 0 , 0 , 0 , 0 , 8 , 64 },
{80 , 16 , 0 , 0 , 0 , 0 , 16 , 80 , 80 , 16 , 0 , 0 , 0 , 0 , 16 , 80 },
{96 , 32 , 8 , 0 , 0 , 8 , 32 , 96 , 96 , 32 , 8 , 0 , 0 , 8 , 32 , 96 },
{112, 64 , 16 , 0 , 0 , 16 , 64 , 112, 112, 64 , 16 , 0 , 0 , 16 , 64 , 112},
{128, 80 , 32 , 8 , 8 , 32 , 80 , 128, 128, 80 , 32 , 8 , 8 , 32 , 80 , 128},
{144, 96 , 48 , 16 , 16 , 48 , 96 , 144, 144, 96 , 48 , 16 , 16 , 48 , 96 , 144},
{160, 112, 64 , 32 , 32 , 64 , 112, 160, 160, 112, 64 , 32 , 32 , 64 , 112, 160},
{176, 128, 80 , 48 , 48 , 80 , 128, 176, 176, 128, 80 , 48 , 48 , 80 , 128, 176},
{192, 144, 96 , 64 , 64 , 96 , 144, 192, 192, 144, 96 , 64 , 64 , 96 , 144, 192},
{208, 160, 112, 80 , 80 , 112, 160, 208, 208, 160, 112, 80 , 80 , 112, 160, 208},
{224, 176, 128, 96 , 96 , 128, 176, 224, 224, 176, 128, 96 , 96 , 128, 176, 224},
{255, 192, 144, 112, 112, 144, 192, 255, 255, 192, 144, 112, 112, 144, 192, 255},
{255, 208, 160, 128, 128, 160, 208, 255, 255, 192, 160, 128, 128, 160, 208, 255}
};
//these are the hues for the fire,
//should be between 0 (red) to about 25 (yellow)
const unsigned char hueMask[GRID_ROWS][GRID_COLS]={
{1 , 11, 19, 25, 25, 22, 11, 1 , 1 , 11, 19, 25, 25, 22, 11, 1 },
{1 , 11, 19, 25, 25, 22, 11, 1 , 1 , 11, 19, 25, 25, 22, 11, 1 },
{1 , 8 , 13, 19, 25, 19, 8 , 1 , 1 , 8 , 13, 19, 25, 19, 8 , 1 },
{1 , 8 , 13, 19, 25, 19, 8 , 1 , 1 , 8 , 13, 19, 25, 19, 8 , 1 },
{1 , 8 , 13, 16, 19, 16, 8 , 1 , 1 , 8 , 13, 16, 19, 16, 8 , 1 },
{1 , 8 , 13, 16, 19, 16, 8 , 1 , 1 , 8 , 13, 16, 19, 16, 8 , 1 },
{1 , 5 , 11, 13, 13, 13, 5 , 1 , 1 , 5 , 11, 13, 13, 13, 5 , 1 },
{1 , 5 , 11, 13, 13, 13, 5 , 1 , 1 , 5 , 11, 13, 13, 13, 5 , 1 },
{1 , 5 , 11, 11, 11, 11, 5 , 1 , 1 , 5 , 11, 11, 11, 11, 5 , 1 },
{1 , 5 , 11, 11, 11, 11, 5 , 1 , 1 , 5 , 11, 11, 11, 11, 5 , 1 },
{0 , 1 , 5 , 8 , 8 , 5 , 1 , 0 , 0 , 1 , 5 , 8 , 8 , 5 , 1 , 0 },
{0 , 1 , 5 , 8 , 8 , 5 , 1 , 0 , 0 , 1 , 5 , 8 , 8 , 5 , 1 , 0 },
{0 , 0 , 1 , 5 , 5 , 1 , 0 , 0 , 0 , 0 , 1 , 5 , 5 , 1 , 0 , 0 },
{0 , 0 , 1 , 5 , 5 , 1 , 0 , 0 , 0 , 0 , 1 , 5 , 5 , 1 , 0 , 0 },
{0 , 0 , 0 , 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 0 , 0 , 0 },
{0 , 0 , 0 , 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 0 , 0 , 0 }
};
unsigned char matrixValue[GRID_ROWS][GRID_COLS];
unsigned char line[GRID_COLS];
int pcnt = 0;
//Converts an HSV color to RGB color
uint16_t HSVtoRGB(uint8_t ih, uint8_t is, uint8_t iv) {
float r, g, b, h, s, v; //this function works with floats between 0 and 1
float f, p, q, t;
int i;
h = (float)(ih / 256.0);
s = (float)(is / 256.0);
v = (float)(iv / 256.0);
//if saturation is 0, the color is a shade of grey
if(s == 0.0) {
b = v;
g = b;
r = g;
}
//if saturation > 0, more complex calculations are needed
else
{
h *= 6.0; //to bring hue to a number between 0 and 6, better for the calculations
i = (int)(floor(h)); //e.g. 2.7 becomes 2 and 3.01 becomes 3 or 4.9999 becomes 4
f = h - i;//the fractional part of h
p = (float)(v * (1.0 - s));
q = (float)(v * (1.0 - (s * f)));
t = (float)(v * (1.0 - (s * (1.0 - f))));
switch(i)
{
case 0: r=v; g=t; b=p; break;
case 1: r=q; g=v; b=p; break;
case 2: r=p; g=v; b=t; break;
case 3: r=p; g=q; b=v; break;
case 4: r=t; g=p; b=v; break;
case 5: r=v; g=p; b=q; break;
default: r = g = b = 0; break;
}
}
return matrix.Color(r * 255.0, g * 255.0, b * 255.0);
}
/**
* Randomly generate the next line (matrix row)
*/
void generateLine(){
for(uint8_t x=0; x<GRID_COLS; x++) {
line[x] = random(64, 255);
}
}
/**
* shift all values in the matrix up one row - Original version.
*/
void shiftUp() {
for (uint8_t y=GRID_ROWS-1; y>0; y--) {
for (uint8_t x=0; x<GRID_COLS; x++) {
matrixValue[y][x] = matrixValue[y-1][x];
}
}
for (uint8_t x=0; x<GRID_COLS; x++) {
matrixValue[0][x] = line[x];
}
}
/**
* draw a frame, interpolating between 2 "key frames"
* @param pcnt percentage of interpolation
*/
void drawFrame(int pcnt) {
int nextv;
//each row interpolates with the one before it
for (unsigned char y=GRID_ROWS-1; y>0; y--) { // original
// for (unsigned char y=0; y<GRID_ROWS-1; y++) { // flipped vertically
for (unsigned char x=0; x<GRID_COLS; x++) { // original
// for (unsigned char x=GRID_COLS-1; x>=0; x--) { // flipped forizontally
nextv =
(((100.0-pcnt)*matrixValue[y][x]
+ pcnt*matrixValue[y-1][x])/100.0)
- valueMask[y][x];
uint16_t color = HSVtoRGB(
hueMask[y][x], // H
255, // S
(uint8_t)max(0, nextv) // V
);
//matrix.drawPixel(x, y, color);
matrix.drawPixel(x, GRID_ROWS-1-y, color);
}
}
//first row interpolates with the "next" line
for(unsigned char x=0; x<GRID_COLS; x++) {
uint16_t color = HSVtoRGB(
hueMask[0][x], // H
255, // S
(uint8_t)(((100.0-pcnt)*matrixValue[0][x] + pcnt*line[x])/100.0) // V
);
// matrix.drawPixel(x, 0, color);
matrix.drawPixel(x, GRID_ROWS-1, color);
}
}
/************************************************************************************
CheerText - This routine will print one letter at a time in the received text using
the received color. I plan on adding a third input defining the speed or time to
delay on each letter, but that hasn't been implemented yet
************************************************************************************/
void CheerText(String myText, unsigned int color, int speed) {
Serial.println("Starting CheerText.");
matrix.setTextSize(2);
int ArrayLength=myText.length()+1; //The +1 is for the 0x00h Terminator
char CharArray[ArrayLength];
myText.toCharArray(CharArray,ArrayLength);
matrix.setTextColor(color);
for (int i=0; i<ArrayLength-1; i++){
matrix.fillScreen(0);
matrix.show();
matrix.setCursor(3,1);
matrix.print(CharArray[i]);
matrix.show();
delay(speed);
}
//matrix.setTextSize(1);
//Serial.println("Leaving CheerText.");
}
/************************************************************************************
Twinkle - This routine will twinkle random colors on randomly selected NeoPixels in
the matrix, up to MAXSTARS at a time, where MAXSTARS is defined above.
************************************************************************************/
void Twinkle() {
Serial.println("Starting Twinkle.");
// MAXSTARS = 20
int starArray[MAXSTARS][2] = {{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}};
// MAXSTARS = 50
/*
int starArray[MAXSTARS][2] = {
{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}
};
*/
int arrayIndex=0;
matrix.fillScreen(0);
matrix.show();
// read the state of the button:
// buttonState = digitalRead(buttonPin); //commented out for testing
buttonState = digitalRead(buttonPin); // initial button read
// until the button is pressed, add a star and remove a star each iteration, with a short delay.
while (buttonState == HIGH) {
matrix.drawPixel(starArray[arrayIndex][0], starArray[arrayIndex][1], 0); // black out old X,Y pixed
starArray[arrayIndex][0]=random(1,GRID_COLS+1); // set new X coordinate
starArray[arrayIndex][1]=random(1,GRID_ROWS+1); // set new Y coordinate
matrix.drawPixel(starArray[arrayIndex][0], starArray[arrayIndex][1], random(1,65535)); // draw new pixel in random color
matrix.show();
delay(100);
// increment to next position in array
arrayIndex++;
if (arrayIndex >= MAXSTARS) {
arrayIndex=0;
}
// read the state of the button:
buttonState = digitalRead(buttonPin); // commented out for testing
}
mode = 0;
matrix.fillScreen(0); // clear screen when done
matrix.show();
delay(500);
Serial.println("Leaving Twinkle.");
}
/************************************************************************************
YesOrNo - This routine randomly selects yes or no, and outputs an indication of
the result
************************************************************************************/
void YesOrNo() {
Serial.println("Starting YesNo.");
int decision = random(0,2);
matrix.setTextSize(2);
matrix.fillScreen(0);
matrix.show();
matrix.setCursor(3,1);
switch (decision) {
case 0: // Yes
matrix.setTextColor(GREEN);
matrix.print(F("!"));
matrix.show();
break;
case 1: // no
matrix.setTextColor(RED);
matrix.print(F("X"));
matrix.show();
break;
default: // error
CheerText("?", WHITE, 1000);
break;
}
delay(3000);
matrix.fillScreen(0);
matrix.show();
Serial.println("Leaving YesNo.");
}
/************************************************************************************
FireIce - This routine makes an icy fireplace of BlueFlame- to be written
************************************************************************************/
void FireIce() {
if (pcnt >= 100) {
shiftUp();
generateLine();
pcnt = 0;
}
drawFrame(pcnt);
matrix.show();
pcnt+=30;
}
/************************************************************************************
RockPaperScissors - This routine randomly selects either Rock, Paper, or Scissors,
and displays an image of the selection
************************************************************************************/
void RockPaperScissors() {
int choice;
// read the state of the button:
buttonState = digitalRead(buttonPin); // initial button read
// until the button is pressed, add a star and remove a star each iteration, with a short delay.
while (buttonState == HIGH) {
choice = random(1,4);
matrix.fillScreen(0);
matrix.show();
Serial.println("RPS selection: " + String(choice));
matrix.setCursor(3,3);
matrix.setTextSize(1);
matrix.print("Go");
matrix.show();
delay(500);
CheerText("123", YELLOW, 600);
matrix.fillScreen(0);
matrix.show();
delay(100);
switch (choice) {
case 1: // Rock
matrix.drawCircle(7, 7, 7, YELLOW);
matrix.drawCircle(4,4,2,YELLOW);
break;
case 2: // Paper
matrix.drawRect(2, 0, 12, 16, YELLOW);
matrix.drawFastHLine(4,3,8,YELLOW);
matrix.drawFastHLine(4,6,8,YELLOW);
matrix.drawFastHLine(4,9,8,YELLOW);
matrix.drawFastHLine(4,12,8,YELLOW);
break;
case 3: // Scissors
matrix.drawLine(0,3,15,12,YELLOW);
matrix.drawLine(0,12,15,3,YELLOW);
matrix.drawCircle(2,2,2,YELLOW);
matrix.drawCircle(2,13,2,YELLOW);
break;
default:
break;
}
matrix.show();
delay(2000);
matrix.fillScreen(0);
matrix.show();
buttonState = digitalRead(buttonPin);
}
}
/***************************************************************************************
Setup - Executed Once
***************************************************************************************/
void setup() {
Serial.begin(9600);
pinMode(buttonPin, INPUT_PULLUP);
randomSeed(analogRead(12));
thePixel.setBrightness(50);
thePixel.begin();
thePixel.setPixelColor(0, 255, 255, 255); // pixel number, then RGB values
thePixel.show(); // Initialize the onboard neopixel to off
matrix.begin();
matrix.setBrightness(DEFBRIGHTNESS);
matrix.setTextWrap(false);
matrix.setTextColor(WHITE);
matrix.setTextSize(1);
matrix.fillScreen(0);
matrix.show();
matrix.setCursor(2,3);
matrix.print(F("ON"));
matrix.show();
delay(1000);
matrix.fillScreen(0);
matrix.show();
// Some setup stuff for the flame animation
randomSeed(analogRead(0));
generateLine();
memset(matrixValue, 0, sizeof(matrixValue)); //init all pixels to zero
Serial.println("Setup Complete.");
delay(100);
}
/***************************************************************************************
Main Loop - Executed repeatedly until system reset or turned off.
***************************************************************************************/
void loop() {
// ***CHECK AND GET INPUT***
// read the state of the button:
buttonState = digitalRead(buttonPin);
Serial.print("Button state: ");
Serial.println(buttonState);
// if the pushbutton is pressed (buttonState is LOW, because using a pull-up input), change modes.
while (buttonState == LOW) {
// Turn on LED to indicate button push
digitalWrite(LEDPIN, HIGH);
mode++; // increment to next mode
if (mode > maxMode) mode = 0; // cycle back to first mode when max mode is reached.
matrix.setTextSize(1);
matrix.fillScreen(0);
matrix.show();
switch (mode) {
case 0:
matrix.setCursor(0,4);
matrix.setTextColor(BLUE);
matrix.print(F("O"));
matrix.setCursor(5,4);
matrix.print(F("f"));
matrix.setCursor(10,4);
matrix.print(F("f"));
matrix.show();
thePixel.setPixelColor(0, 0, 0, 0); // pixel number, then RGB values
thePixel.show();
break;
case 1:
matrix.setCursor(5,4);
matrix.setTextColor(ORANGE);
matrix.print(F("P"));
matrix.show();
thePixel.setPixelColor(0, 0, 0, 255); // pixel number, then RGB values
thePixel.show();
break;
case 2:
matrix.setCursor(2,4);
matrix.setTextColor(GREEN);
matrix.print(F("YN"));
matrix.show();
thePixel.setPixelColor(0, 255, 165, 0); // pixel number, then RGB values
thePixel.show();
break;
case 3:
matrix.setCursor(5,4);
matrix.setTextColor(PURPLE);
matrix.print(F("*"));
matrix.show();
thePixel.setPixelColor(0, 255, 0, 255); // pixel number, then RGB values
thePixel.show();
break;
case 4:
matrix.setCursor(2,4);
matrix.setTextColor(RED);
matrix.print(F("^^"));
matrix.show();
thePixel.setPixelColor(0, 255, 0, 0); // pixel number, then RGB values
thePixel.show();
break;
case 5:
matrix.drawCircle(2,7,2,YELLOW);
matrix.drawRect(6, 4, 4, 7, YELLOW);
matrix.setTextColor(YELLOW);
matrix.setCursor(11,3);
matrix.print(F("x"));
matrix.show();
thePixel.setPixelColor(0, 255, 255, 0); // pixel number, then RGB values
thePixel.show();
default:
break;
}
delay(1000);
matrix.fillScreen(0);
matrix.show();
buttonState = digitalRead(buttonPin); // check to see if button still pressed
}
digitalWrite(LEDPIN, LOW); // turn off LED once button is let go
// ***DO THE THING BASED ON CURRENT MODE***
Serial.println("Starting mode " + String(mode));
// do something based on the current mode
switch (mode) {
case 0: // Screen Off
thePixel.setPixelColor(0, 0, 0, 0); // pixel number, then RGB values
thePixel.show();
matrix.fill(0);
matrix.show();
delay(500);
break;
case 1: // POTOMAC Cheer mode
CheerText("POTOMAC",BLUE, 900);
matrix.setTextSize(1);
matrix.setBrightness(100);
for (offset=15; offset > -70; offset--) {
matrix.fillScreen(0);
matrix.setCursor(offset, 3);
matrix.print("GO POTOMAC!");
matrix.show();
delay(50);
}
matrix.setBrightness(DEFBRIGHTNESS);
matrix.show();
delay(500);
mode=0;
break;
case 2: // yes-no mode
CheerText("321", PURPLE, 750);
delay(400);
YesOrNo();
mode=0;
break;
case 3: // Twinkle
Twinkle();
break;
case 4: // Fire and Ice
FireIce();
break;
case 5: // rock-paper-scissors
RockPaperScissors();
break;
default:
break;
}
Serial.println();
}
/***************************END*********************************/
Any help appreciated.