Go Down

Topic: Yellowjacket + PS2/You LED sign + WiServer = *almost AWESOME* (Out of memory?) (Read 907 times) previous topic - next topic

Catfang

I have a PS2You LED message board that I decided would make a great wall clock/calendar/weather station/etc. and connected it to a Rugged Circuits Yellowjacket. The intent is to update the LED message board over wifi using a simple REST-like interface. After some experimentation, I can get the REST services working over wifi, or I can get the LED message board to update, but I can't seem to do both. This appears to be an issue with the Yellowjacket running out of SRAM while attempting to use both the WiServer library and the DisplayMatrix library at the same time.

I've posted my code below (which could probably be refactored some to decrease the SRAM memory usage) but I suspect that I'm going to need to refactor one or both libraries to get enough free memory to make everything work properly. Can anyone offer any help or suggestions on the best way to approach this? I'm afraid its been a long time since I've had to do any optimization of this type... :)

Code: [Select]
/*
* A simple sketch that uses WiServer to recieve a web call and update the display
*/
// for the wifi:
#include <EEPROM.h>
#include <TinyREST.h>
#include <WiServer.h>
// for the display:
#include <string.h>
#include "MatrixDisplay.h"
#include "DisplayToolbox.h"
#include "font.h"

#define WIRELESS_MODE_INFRA 1
#define WIRELESS_MODE_ADHOC 2

// Wireless configuration parameters ----------------------------------------
unsigned char local_ip[] = {192,168,1,20}; // IP address of WiShield
unsigned char gateway_ip[] = {192,168,1,1}; // router or gateway IP address
unsigned char subnet_mask[] = {255,255,255,0}; // subnet mask for the local network
const prog_char ssid[] PROGMEM = {"TESTSSID"}; // max 32 bytes

unsigned char security_type = 3; // 0 - open; 1 - WEP; 2 - WPA; 3 - WPA2

// WPA/WPA2 passphrase
const prog_char security_passphrase[] PROGMEM = {"testpassphrase"}; // max 64 characters

// WEP 128-bit keys
// sample HEX keys
prog_uchar wep_keys[] PROGMEM = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, // Key 0
 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Key 1
 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Key 2
 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // Key 3
};

// setup the wireless mode
// infrastructure - connect to AP
// adhoc - connect to another WiFi device
unsigned char wireless_mode = WIRELESS_MODE_INFRA;

unsigned char ssid_len;
unsigned char security_passphrase_len;
// End of wireless configuration parameters ----------------------------------------

// Matrix display configuration parameters ----------------------------------------

const int numLines = 6;        // Max number of lines
const int displayTime = 2500;  // Length of time in milliseconds to display each line
const int gapTime = 10;        // Time gap between lines
const int maxLineLength = 100; // Maximum length of a line in characters

String textLines[numLines];     // Array of text lines

int textLength;          // Holds current line length in pixels
int strLength;           // Holds current line length in characters
int currentLine = 0;     // Index of current line


char defaultText[numLines][30] = {{"Awaiting input..."}};

// Character lookup string -- used to map the characters from the keyboard onto the font data array
// (This should probably be moved to program memory in some form in the future)
String charLookup  = " 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*(),-.?></\\|[]_=+:'\"{}";

// Macro to make it the initDisplay function a little easier to understand
#define setMaster(dispNum, CSPin) initDisplay(dispNum,CSPin,true)
#define setSlave(dispNum, CSPin) initDisplay(dispNum,CSPin,false)

// Initialize matrix
//MatrixDisplay disp(3,16,15, true);
//DisplayToolbox toolbox(&disp);

// Prepare boundaries
uint8_t X_MAX = 0;
uint8_t Y_MAX = 0;

#define is_printable(c) (!(c&0x80))  

int line = 0;
const unsigned int MAX_INPUT = 100;

// End of matrix display configuration parameters ----------------------------------------

TinyREST rest = TinyREST();
static char cmd_read_string[] = {"string_read"};
static char cmd_write_string[] = {"string_write"};

// This is our page serving function that generates web pages
boolean sendMyPage(char* URL) {
 
 return rest.handleURL(URL);
 
}


void setup() {
 // Initialize WiServer and have it use the sendMyPage function to serve pages
 rest.init();
 rest.addCommand(cmd_read_string, 2, testCallback);
 rest.addCommand(cmd_write_string, 2, testCallback);
 WiServer.init(sendMyPage);
 
 // Enable Serial output and ask WiServer to generate log messages (optional)
 Serial.begin(57600);
 WiServer.enableVerboseMode(true);
 
 // Enable matrix display
//  for(int i=0; i<numLines; i++){
//    textLines[i] = defaultText[i];
//    Serial.println(defaultText[i]);
//  }  
//
//  // Fetch display bounds
//  X_MAX = disp.getDisplayCount() * (disp.getDisplayWidth()-1)+3;
//  Y_MAX = disp.getDisplayHeight()-1;
//
//  // Prepare displays
//  disp.setMaster(0,4);
//  disp.setSlave(1,5);
//  disp.setSlave(2,6);
}

void loop(){

 // Run WiServer
 WiServer.server_task();
 rest.loop();
 delay(10);
}

//This is where the lines being displayed on the message board can be updated (or read). To be implemented when memory issues are resolved.
static int testCallback(TinyREST *srv, char *cmd, int len, char **args, void *blind){
 if (strcmp(cmd, cmd_read_string)==0) {
   //read the string
   WiServer.print("<html>");
   WiServer.print("***Pretending to read string!");
   WiServer.print("</html>");
   Serial.println("***Pretending to read string!");
   return RESPONSE_INLINE_OK;
 } else if (strcmp(cmd, cmd_write_string)==0) {
   //write the string
   WiServer.print("<html>");
   WiServer.print("***Pretending to write string!");
   WiServer.print("</html>");
   Serial.println("***Pretending to write string!");
   return RESPONSE_INLINE_OK;
 }
 return RESPONSE_OK;
}

pylon

Get rid of every occurrence of the String class in your code. Use C-like character arrays instead.

The String class is not programmed in an embedded-friendly style and therefore clobbers your memory in no time. Additionally the IDEs (at least up to 1.0.3) have a bug which results in a memory leak when the String class is used.

Then use the F() macro when you have a string literal in a call to print() or println() methods.

Catfang

Okay, I've removed all the String instances, and converted as much as I can to PROGMEM as well. After all this optimization, the board is still unable to correctly run both the web server and the display. Using a memory check function I found, I appear to still have anywhere from 200 to 400 bytes free of SRAM, so although I'm near maxed, this should be working.

Strange thing is, if you comment out the "Update Display" part of the loop() function, the web server works. If you keep the code present, then the web server doesn't respond to requests and only one of the three LED panels appears to work. I'm getting closer, but I'm still missing something! Can anyone help??

Code: [Select]

/*
*    REST-driven LED Display board
*   
*
*/


// for the wifi:
#include <EEPROM.h>
#include <TinyREST.h>
#include <WiServer.h>
// for the display:
#include <string.h>
#include "MatrixDisplay.h"
#include "DisplayToolbox.h"
#include "font.h"


#define WIRELESS_MODE_INFRA 1
#define WIRELESS_MODE_ADHOC 2

// Wireless configuration parameters ----------------------------------------
unsigned char local_ip[] = {192,168,1,20}; // IP address of WiShield
unsigned char gateway_ip[] = {192,168,1,1}; // router or gateway IP address
unsigned char subnet_mask[] = {255,255,255,0}; // subnet mask for the local network
const prog_char ssid[] PROGMEM = {"SSID"}; // max 32 bytes

unsigned char security_type = 3; // 0 - open; 1 - WEP; 2 - WPA; 3 - WPA2

// WPA/WPA2 passphrase
const prog_char security_passphrase[] PROGMEM = {"passphrase"}; // max 64 characters

// WEP 128-bit keys
// sample HEX keys
prog_uchar wep_keys[] PROGMEM = {};

// setup the wireless mode
// infrastructure - connect to AP
// adhoc - connect to another WiFi device
unsigned char wireless_mode = WIRELESS_MODE_INFRA;

unsigned char ssid_len;
unsigned char security_passphrase_len;
// End of wireless configuration parameters ----------------------------------------

// Matrix display configuration parameters ----------------------------------------

const int numLines = 1;        // Max number of lines
const int displayTime = 2500;  // Length of time in milliseconds to display each line
const int gapTime = 10;        // Time gap between lines
const int maxLineLength = 50; // Maximum length of a line in characters

char lines[numLines][maxLineLength];
char text[maxLineLength];

int textLength;          // Holds current line length in pixels
int strLength;           // Holds current line length in characters
int currentLine = 0;     // Index of current line

const prog_char defaultText[] PROGMEM = {"Awaiting input..."};
const prog_char msgRecvText[] PROGMEM = {"<html><p>Message Recieved By Server</p></html>"};
const prog_char msgRecvStartText[] PROGMEM = {"<html><p>"};
const prog_char msgRecvEndText[] PROGMEM = {"</p></html>"};

// Character lookup string -- used to map the characters from the keyboard onto the font data array
const prog_char charLookup[] PROGMEM = " 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*(),-.?></\\|[]_=+:'\"{}";

// Memory test
extern int __bss_end;
extern int *__brkval;


// Macro to make it the initDisplay function a little easier to understand
#define setMaster(dispNum, CSPin) initDisplay(dispNum,CSPin,true)
#define setSlave(dispNum, CSPin) initDisplay(dispNum,CSPin,false)

// Initialize matrix
MatrixDisplay disp(3,16,15, false);
DisplayToolbox toolbox(&disp);

// Prepare boundaries
uint8_t X_MAX = 0;
uint8_t Y_MAX = 0;

// End of matrix display configuration parameters ----------------------------------------

TinyREST rest = TinyREST();
static char cmd_read_string[] = {"string_read"};
static char cmd_write_string[] = {"string_write"};

int lineNum;

// This is our page serving function that generates web pages
boolean sendMyPage(char* URL) {
  return rest.handleURL(URL);
}


void setup() {
  // Initialize WiServer and have it use the sendMyPage function to serve pages
  rest.init();
  rest.addCommand(cmd_read_string, 2, testCallback);
  rest.addCommand(cmd_write_string, 2, testCallback);
  WiServer.init(sendMyPage);
 
  // Enable Serial output and ask WiServer to generate log messages (optional)
  Serial.begin(57600);
  //WiServer.enableVerboseMode(true);
  Serial.println("PROGRAM START");
 
  // Enable matrix display
  for(int i=0; i<numLines; i++){
    strcpy_P(lines[i], defaultText);
  }
 
  // Fetch display bounds
  X_MAX = disp.getDisplayCount() * (disp.getDisplayWidth()-1)+3;
  Y_MAX = disp.getDisplayHeight()-1;

  // Prepare displays
  disp.setMaster(0,4);
  disp.setSlave(1,5);
  disp.setSlave(2,6);
}

void loop(){

  // Run WiServer
  WiServer.server_task();
  rest.loop();
 
  // Update Display
  strLength = strlen(lines[currentLine]); // Number of characters in the string
  textLength = (strLength*6); // Width of string in pixels
 
  strcpy(text, lines[currentLine]);
 
  //** Uncommenting this section causes the web server to not respond!
 
//  if(strLength > 1){
//     if(textLength < (X_MAX +1)){
//       fixedText(text);
//       delay(displayTime);
//       disp.clear();
//       disp.syncDisplays();
//       delay(gapTime);
//     }else{
//       scrollText(text);
//       delay(gapTime);
//     }
//   }
//   nextLine();
   
   //get_free_memory();
}

//This is where the lines being displayed on the message board can be updated (or read).
static int testCallback(TinyREST *srv, char *cmd, int len, char **args, void *blind){
  if (strcmp(cmd, cmd_read_string)==0) {
    //read the string
    //To be implemented if there is space.
   WiServer.print_P(msgRecvText);
    return RESPONSE_INLINE_OK;
  } else if (strcmp(cmd, cmd_write_string)==0) {
    //write the string
    if (len == 2)
    {
      lineNum = atoi(args[0]);
      if (lineNum >= 0 && lineNum < maxLineLength)
      {
        strcpy(lines[lineNum], args[1]);
        WiServer.print_P(msgRecvStartText);
        WiServer.print(F("Wrote "));
        WiServer.print(args[1]);
        WiServer.print(F(" to line "));
        WiServer.print(lineNum);
        WiServer.print_P(msgRecvEndText);
      }
    } else {
      WiServer.print_P(msgRecvText);
    }
    return RESPONSE_INLINE_OK;
  }
  return RESPONSE_OK;
}

void nextLine(){
   if(currentLine < (numLines -1)){
      currentLine++;
   }else{
     currentLine = 0;
   }   
}

void scrollText(char* text){
  int y=1;
  int endPos = 0 - textLength;
  int loopCount = 0;
  for(int Xpos = X_MAX; Xpos > endPos; Xpos--){
    loopCount++;
    disp.clear();
    drawString(Xpos,y,text);
    disp.syncDisplays();
  }
}


// Write a line of static (non-scrolling) text to the display
void fixedText(char* text){
  int y = 1;
  int x = 0;
  disp.clear();
  drawString(x,y,text);
  disp.syncDisplays();
}


// Output a string to the display
void drawString(int x, uint8_t y, char* c){
  for(char i=0; i< strLength; i++){
    drawChar(x, y, c[i]);
    x+=6; // Width of each glyph
  }
}


// Output a single character to the display
void drawChar(int x, int y, char c){
  int dots;
 
  c = getIndex(charLookup, c);
 
  for (char col=0; col< 5; col++) {
    if((x+col+1)>0 && x < X_MAX){ // dont write to the display buffer if the location is out of range
      dots = pgm_read_byte_near(&myfont[c][col]);
      for (char row=0; row < 7; row++) {
        if (dots & (64>>row))         // only 7 rows.
          toolbox.setPixel(x+col, y+row, 1);
        else
          toolbox.setPixel(x+col, y+row, 0);
      }
    }
  }
}

int getIndex(const prog_char* str, char c)
{
  const prog_char* e = strchr_P(str, c);
  if (e) {
    return (int)(e - str);
  }
  else
    return -1;
}

// Memory check
int get_free_memory(){
  int free_memory;
  if((int)__brkval == 0)
    free_memory = ((int)&free_memory) - ((int)&__bss_end);
  else
    free_memory = ((int)&free_memory) - ((int)__brkval);
    Serial.println(free_memory);
  return free_memory;
}


pylon

Sure it doesn't work this way, you're freezing your Arduino for 2.5 seconds which is an incredibly long time in network regards. Take a look at the BlinkWithoutDelay example to find out how you can have timed events in the loop without using delay().

Code: [Select]
//  if(strLength > 1){
//     if(textLength < (X_MAX +1)){
//       fixedText(text);
//       delay(displayTime);
//       disp.clear();
//       disp.syncDisplays();
//       delay(gapTime);
//     }else{
//       scrollText(text);
//       delay(gapTime);
//     }
//   }
//   nextLine();

Catfang

Thanks Pylon! That makes complete sense. I'll work on refactoring that code right now! I believe that will fix the web server not responding issue.

I'm still worried about the display not fully working though, so I guess that will be next to investigate after this...

Go Up