element14 Community
element14 Community
    Register Log In
  • Site
  • Search
  • Log In Register
  • About Us
  • Community Hub
    Community Hub
    • What's New on element14
    • Feedback and Support
    • Benefits of Membership
    • Personal Blogs
    • Members Area
    • Achievement Levels
  • Learn
    Learn
    • Ask an Expert
    • eBooks
    • element14 presents
    • Learning Center
    • Tech Spotlight
    • STEM Academy
    • Webinars, Training and Events
    • Learning Groups
  • Technologies
    Technologies
    • 3D Printing
    • FPGA
    • Industrial Automation
    • Internet of Things
    • Power & Energy
    • Sensors
    • Technology Groups
  • Challenges & Projects
    Challenges & Projects
    • Design Challenges
    • element14 presents Projects
    • Project14
    • Arduino Projects
    • Raspberry Pi Projects
    • Project Groups
  • Products
    Products
    • Arduino
    • Avnet Boards Community
    • Dev Tools
    • Manufacturers
    • Multicomp Pro
    • Product Groups
    • Raspberry Pi
    • RoadTests & Reviews
  • Store
    Store
    • Visit Your Store
    • Choose another store...
      • Europe
      •  Austria (German)
      •  Belgium (Dutch, French)
      •  Bulgaria (Bulgarian)
      •  Czech Republic (Czech)
      •  Denmark (Danish)
      •  Estonia (Estonian)
      •  Finland (Finnish)
      •  France (French)
      •  Germany (German)
      •  Hungary (Hungarian)
      •  Ireland
      •  Israel
      •  Italy (Italian)
      •  Latvia (Latvian)
      •  
      •  Lithuania (Lithuanian)
      •  Netherlands (Dutch)
      •  Norway (Norwegian)
      •  Poland (Polish)
      •  Portugal (Portuguese)
      •  Romania (Romanian)
      •  Russia (Russian)
      •  Slovakia (Slovak)
      •  Slovenia (Slovenian)
      •  Spain (Spanish)
      •  Sweden (Swedish)
      •  Switzerland(German, French)
      •  Turkey (Turkish)
      •  United Kingdom
      • Asia Pacific
      •  Australia
      •  China
      •  Hong Kong
      •  India
      •  Korea (Korean)
      •  Malaysia
      •  New Zealand
      •  Philippines
      •  Singapore
      •  Taiwan
      •  Thailand (Thai)
      • Americas
      •  Brazil (Portuguese)
      •  Canada
      •  Mexico (Spanish)
      •  United States
      Can't find the country/region you're looking for? Visit our export site or find a local distributor.
  • Translate
  • Profile
  • Settings
Arduino
  • Products
  • More
Arduino
Blog Fast Track to Arduino Programming - Lesson 3 pt 2, Improved Parsing and memory usage (Optimized)
  • Blog
  • Forum
  • Documents
  • Quiz
  • Events
  • Polls
  • Files
  • Members
  • Mentions
  • Sub-Groups
  • Tags
  • More
  • Cancel
  • New
Join Arduino to participate - click to join for free!
  • Share
  • More
  • Cancel
Group Actions
  • Group RSS
  • More
  • Cancel
Engagement
  • Author Author: Robert Peter Oakes
  • Date Created: 13 Jun 2014 2:48 AM Date Created
  • Views 3014 views
  • Likes 2 likes
  • Comments 10 comments
  • tutorial
  • optimized
  • advanced
  • console
  • ardiono
  • programming
  • fast_track_to_arduino_programming
  • serial
Related
Recommended

Fast Track to Arduino Programming - Lesson 3 pt 2, Improved Parsing and memory usage (Optimized)

Robert Peter Oakes
Robert Peter Oakes
13 Jun 2014

back to intro and main menu Fast Track to Arduino Programming

 

In part 1 of this lesson, you where presented with a fully working Arduino sketch that can read from analog inputs, output PWM and output to the digital lines, please review the code there before going through this as I will be assuming ou have done so

you can find it here Fast Track to Arduino Programming - Lesson 3 pt 1, Improved Parsing and memory usage (Fully working programs)

 

this sketch compiled to 8434 bytes and had only 693 bytes of RAM left, yet we have not added any more buffers, just more code?, in this lesson video I will take you through the changes I did to the sketch to recover nearly 1K of the RAM and nearly the same for the program space, all without loosing any commands / functionality (Compiled to 7942 byes and had 1619 bytes of ram left). These techniques will be essential once we get to more advanced libraries and simply adding the library can consume 500bytes of RAM before you even use it (Example is the SD card library).

 

I will be introducing the use of PROGMEM which is used to hold  is limited to 2K on the Arduino UNO, I will also be re-arranging the sketch in order to make use of function calls rather than repeating code over and over.

 

Video 1 (Pre Optimization), please watch first., video 2 the Optimized approach, using PROGMEM, Case menu system etc

You don't have permission to edit metadata of this video.
Edit media
x
image
Upload Preview
image
You don't have permission to edit metadata of this video.
Edit media
x
image
Upload Preview
image

 

The attached sketch is the optimized one and has a couple of corrections included to allow for toggling the digital outputs and also handling the second parameter better, also the IsNumeric function has been updated to allow a + or - as the first character of a number

 

/*
Title: Lesson 3b. Improved parsing and reduced SRAM consumption - "Case" Statements

Description:  Reading a line of text from the Serial Port and sending to a command process function.
the processing function performs a real IO function for this lesson
This sketch also demonstrates the use of compiler directives to turn on and off blocks of functionality
Date created: 11th June 2014
Created By: Peter Oakes
Additional Comments:
Feel free to use and abuse as you wish, I imply NO Warranty
NOTE: defines take no program space untill used so use instead of ints etc when it will never change
Lessons Available
lesson 0. How big is an Empty Sketch anyway
Lesson 1. Reading the Serial input a line at time
Lesson 2. Parsing the line using  "if else if" statements and == or strcmp
Lesson 3a. Preperation for Improved parsing and reduced SRAM consumption - "Case" Statements
Lesson 3b. Improved parsing and reduced Flash usage, significantly less SRAM consumption and uses "Case" Statements
*/
/* Include needed Lbraries */
#include <avr/pgmspace.h>
/* End include libraries */
// Bunch of constants in the form of definitions
// 1 = output debug to serial port, 0 = no debug
#define debug 1 
// define the buffer size... 
#define serialbufferSize 50 
#define commandDelimeters "|,. " // '-' removed as it is needed as a value leader
#define progMemBuffer 128
// Define some Digital pins
// Dont use the PWM capable ones
#define D0 0
#define D1 1
#define D2 2
#define D3 4
#define D4 7
#define D5 8
#define D6 12
#define D7 13
// define the PWM Pins
#define PWM0 3
#define PWM1 5
#define PWM2 6
#define PWM3 9
#define PWM4 10
#define PWM5 11
#define MAXPWM 255
#define MINPWM 0
#define analogToVolts 5/1023 // Volts per step in ADC 
// End of Constants
// Now the real varibles
char inputBuffer[serialbufferSize]   ; 
int serialIndex = 0; // keep track of where we are in the buffer
// End of real variables
// Strings to be stored into Program space
//enumeration to make accessing the strings easier
enum {welcomeText0, welcomeText1, welcomeText2, webText0, webText1, webText2, errorText, helloReply, 
goodbyReply, dosomethingReply, invalidValue
#if debug 
, isnumericParam, notNumericParam
#endif
};
//welcome text
PROGMEM prog_char welcomeTxt_0[]  = "Hello, please ask me something\ntype \"Hello\", \"Goodby\", \"web\" or \"dosomething\"\n";
PROGMEM prog_char welcomeTxt_1[]  = "or \"pwmx where x is from 0 to 5\"\nor \"analogx where x is from 0 to 5\"\n";
PROGMEM prog_char welcomeTxt_2[]  = "or \"digitalx where x is from 0 to 7\"\nor make up your own command\n";
// end of welcome text
PROGMEM prog_char webTxt_0[]  = "HTTP/1.1 200 OK\nContent-Type: text/html\nConnection: close\n";
PROGMEM prog_char webTxt_1[]  = "<!DOCTYPE html><html><head><title>Hello from www.thebreadboard.ca</title></head><body>";
PROGMEM prog_char webTxt_2[]  = "</body></html>\n";
// error text
PROGMEM prog_char errorTxt_0[]  = "I dont understand you \nYou said: ";
PROGMEM prog_char helloRepTxt_0[]  = "Hello back at you ";
PROGMEM prog_char goodbyRepTxt_0[]  = "Goodby back at you ";
PROGMEM prog_char dosomethingRepTxt_0[]  = "what would you like me to do?";
PROGMEM prog_char invalidValueRepTxt_0[]  = "Invalid Value, setting unchanged";
#if debug // yes you can even use compiler directives here
PROGMEM prog_char paramIsNumeric_0[]  = "Remaining Param is numeric and = ";
PROGMEM prog_char paramIsNotNumeric_0[]  = "Remaining Param is not numeric or invalid, value = ";
#endif
//array of pointers to the above message strings
PROGMEM const char* Txt_table[] =
{welcomeTxt_0,  welcomeTxt_1,  welcomeTxt_2,  webTxt_0,  webTxt_1,  webTxt_2, errorTxt_0, 
helloRepTxt_0,goodbyRepTxt_0,dosomethingRepTxt_0, invalidValueRepTxt_0, 
#if debug // yes you can even use compiler directives here 
paramIsNumeric_0,paramIsNotNumeric_0
#endif
};
//enumeration to make accessing the command strings easier
enum {hello, goodby, dosomething, web1, web2, web3, web4, web5, analog0, analog1, analog2
, analog3, analog4, analog5, digital0, digital1, digital2, digital3, digital4
, digital5, digital6, digital7, pwm0, pwm1, pwm2, pwm3, pwm4, pwm5};
//Command Strings
PROGMEM prog_char helloCmd[]     = "Hello";
PROGMEM prog_char goodbyCmd[]    = "Goodby";
PROGMEM prog_char dosomethingCmd[]      = "dosomething";
PROGMEM prog_char web1Cmd[]      = "web1";
PROGMEM prog_char web2Cmd[]      = "web2";
PROGMEM prog_char web3Cmd[]      = "web3";
PROGMEM prog_char web4Cmd[]      = "web4";
PROGMEM prog_char web5Cmd[]      = "web5";
PROGMEM prog_char analog0Cmd[]   = "analog0";
PROGMEM prog_char analog1Cmd[]   = "analog1";
PROGMEM prog_char analog2Cmd[]   = "analog2";
PROGMEM prog_char analog3Cmd[]   = "analog3";
PROGMEM prog_char analog4Cmd[]   = "analog4";
PROGMEM prog_char analog5Cmd[]   = "analog5";
PROGMEM prog_char digital0Cmd[]   = "digital0";
PROGMEM prog_char digital1Cmd[]   = "digital1";
PROGMEM prog_char digital2Cmd[]   = "digital2";
PROGMEM prog_char digital3Cmd[]   = "digital3";
PROGMEM prog_char digital4Cmd[]   = "digital4";
PROGMEM prog_char digital5Cmd[]   = "digital5";
PROGMEM prog_char digital6Cmd[]   = "digital6";
PROGMEM prog_char digital7Cmd[]   = "digital7";
PROGMEM prog_char pwm0Cmd[]       = "pwm0";
PROGMEM prog_char pwm1Cmd[]       = "pwm1";
PROGMEM prog_char pwm2Cmd[]       = "pwm2";
PROGMEM prog_char pwm3Cmd[]       = "pwm3";
PROGMEM prog_char pwm4Cmd[]       = "pwm4";
PROGMEM prog_char pwm5Cmd[]       = "pwm5";
//array of pointers to the above command strings
PROGMEM const char* Cmd_table[] =
{helloCmd,goodbyCmd,dosomethingCmd,web1Cmd,web2Cmd,web3Cmd,web4Cmd,web5Cmd,
analog0Cmd,analog1Cmd,analog2Cmd,analog3Cmd,analog4Cmd,analog5Cmd,
digital0Cmd,digital1Cmd,digital2Cmd,digital3Cmd,digital4Cmd,digital5Cmd,digital6Cmd,digital7Cmd,
pwm0Cmd,pwm1Cmd,pwm2Cmd,pwm3Cmd,pwm4Cmd,pwm5Cmd};
int cmdCount = sizeof(Cmd_table) / sizeof(Cmd_table[0]);
// Function that finds the string in prog mem arrays and gets it into usable space
char* getStringfromProgMem(const char* Table[], int i)
{
char buffer[progMemBuffer];
strcpy_P(buffer, (char*)pgm_read_word(&(Table[i])));
return buffer;
};
// Search through the comands untill we find one or run out
int findCommand(char* searchText)
{
  int startCount = 0;
  int foundIndex = -1; // -1 = not found
  while (startCount < cmdCount)
  {
    if(strcmp(searchText,getStringfromProgMem(Cmd_table,startCount))==0)
    {
     foundIndex = startCount;
     break;
    }
    startCount++;
  }
  return foundIndex;
}
void setup() 
{ 
  // initialise all the digital outputs
  pinMode(D0, OUTPUT);      
  pinMode(D1, OUTPUT);      
  pinMode(D2, OUTPUT);      
  pinMode(D3, OUTPUT);      
  pinMode(D4, OUTPUT);      
  pinMode(D5, OUTPUT);      
  pinMode(D6, OUTPUT);      
  pinMode(D7, OUTPUT);   
  // initialise all the PWM outputs
  pinMode(PWM0, OUTPUT);      
  pinMode(PWM1, OUTPUT);      
  pinMode(PWM2, OUTPUT);      
  pinMode(PWM3, OUTPUT);      
  pinMode(PWM4, OUTPUT);      
  pinMode(PWM5, OUTPUT);      
  
  // initialize serial:
  Serial.begin(9600);
  delay(200);
  // do other setup here as needed
  
  // Print some pretty instructions
  Serial.print(getStringfromProgMem(Txt_table,welcomeText0));
  Serial.print(getStringfromProgMem(Txt_table,welcomeText1));
  Serial.print(getStringfromProgMem(Txt_table,welcomeText2));
}
void loop() 
{
  // Notice how the main loop is very simple and the functions 
  // seperate the logic into easily manageable parts
  if (CheckSerial()) DoCommand(inputBuffer); 
  // Do other stuff
}
// Enhanced Command Processor using strtok to strip out command from multi parameter string
boolean DoCommand(char * commandBuffer)
{
  char* Command; // Command Parameter
  char* Parameter; // Additional Parameter
  int analogVal = 0; // additional parameter converted to analog if possible
  
  // Get the command from the input string
  Command = strtok(commandBuffer,commandDelimeters); // get the command
  Parameter = strtok(NULL, commandDelimeters); // get the parameter if any
  //if there are more than one parameter they will be ignored for now
  // Make sure we have an analog value if we are to allow PWM output
  int outparameter = isNumeric (Parameter);
  //if it is a number then convert it
  if (outparameter) 
  {
    analogVal = atoi(Parameter);
  }
  
  // Switch / Case way to handle commands
  int cmdID = findCommand(Command);
  switch( cmdID)
  {
   case  hello :           Serial.println(getStringfromProgMem(Txt_table,helloReply));break;
   case  goodby :          Serial.println(getStringfromProgMem(Txt_table,goodbyReply)); break;
   case  dosomething :     Serial.println(getStringfromProgMem(Txt_table,dosomethingReply)); break;
   case  web1 :            processWebCmd("<H1>Test1</H1>");break;
   case  web2 :            processWebCmd("<H1>Test2</H1>");break;
   case  web3 :            processWebCmd("<H1>Test3</H1>");break;
   case  web4 :            processWebCmd("<H1>Test4</H1>");break;
   case  web5 :            processWebCmd("<H1>Test5</H1>");break;
   case  analog0 :         getAnalog(A0); break;
   case  analog1 :         getAnalog(A1); break;
   case  analog2 :         getAnalog(A2); break;
   case  analog3 :         getAnalog(A3); break;
   case  analog4 :         getAnalog(A4); break;
   case  analog5 :         getAnalog(A5); break;
   case  digital0 :        getsetDigital(D0, analogVal);break;
   case  digital1 :        getsetDigital(D1, analogVal);break;
   case  digital2 :        getsetDigital(D2, analogVal);break;
   case  digital3 :        getsetDigital(D3, analogVal);break;
   case  digital4 :        getsetDigital(D4, analogVal);break;
   case  digital5 :        getsetDigital(D5, analogVal);break;
   case  digital6 :        getsetDigital(D6, analogVal);break;
   case  digital7 :        getsetDigital(D7, analogVal);break;
   case  pwm0 :            setPWM(PWM0, analogVal, outparameter);break;
   case  pwm1 :            setPWM(PWM1, analogVal, outparameter);break;
   case  pwm2 :            setPWM(PWM2, analogVal, outparameter);break;
   case  pwm3 :            setPWM(PWM3, analogVal, outparameter);break;
   case  pwm4 :            setPWM(PWM4, analogVal, outparameter);break;
   case  pwm5 :            setPWM(PWM5, analogVal, outparameter);break;
   default :             {
                           Serial.print(getStringfromProgMem(Txt_table,errorText));
                           Serial.println(commandBuffer);
                           break;
                         }
  }
// debug code after here
#if debug  
if (Parameter != '\0'){
  Serial.print((outparameter)? getStringfromProgMem(Txt_table,isnumericParam) : getStringfromProgMem(Txt_table,notNumericParam) );
  Serial.println(Parameter);  
}
  Serial.print("Free Ram = "); Serial.println(freeRam(), DEC);
#endif  
return true;
}
/*
Checks the serial input for a string, returns true once a '\n' is seen
users can always look at the global variable "serialIndex" to see if characters have been received already
*/
boolean CheckSerial()
{
  boolean lineFound = false;
  // if there's any serial available, read it:
  while (Serial.available() > 0) {
    //Read a character as it comes in:
    //currently this will throw away anything after the buffer is full or the \n is detected
    char charBuffer = Serial.read(); 
      if (charBuffer == '\n') {
           inputBuffer[serialIndex] = 0; // terminate the string
           lineFound = (serialIndex > 0); // only good if we sent more than an empty line
           serialIndex=0; // reset for next line of data
         }
         else if(charBuffer == '\r') {
           // Just ignore the Carrage return, were only interested in new line
         }
         else if(serialIndex < serialbufferSize && lineFound == false) {
           /*Place the character in the string buffer:*/
           inputBuffer[serialIndex++] = charBuffer; // auto increment index
         }
  }// End of While
  return lineFound;
}// End of CheckSerial()
// check to see if value is nomeric.. only dealing with signed integers
int isNumeric (const char * s)
{
  if (s == NULL || *s == '\0' || isspace(*s)) return 0; // extra protection
  if (*s == '-' || *s == '+') s++; // allow a + or - in the first char space
  while(*s)
  {
    if(!isdigit(*s))
      return 0;
    s++;
  }
  return 1;
}
// end of check is numeric
#if debug
// check free ram
int freeRam () 
{
  extern int __heap_start, *__brkval; 
  int v; 
  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); 
}
#endif
// output to PWM Channels
void setPWM(int pin, int value, int IsValid)
{
      // check the analog value is in the correct range if not then make isvalid false
    if (((value < MINPWM )|| (value > MAXPWM)) && IsValid) IsValid = false;
      Serial.print("pwm "); Serial.print(pin); Serial.print(" = ");
      Serial.println(value);    // read the input pin "A1" is already defined
      // dont change value if invalid
      if(IsValid) analogWrite(pin, value); else Serial.println(getStringfromProgMem(Txt_table,invalidValue));
}
// get analog Channels
void getAnalog(int pin)
{
      int readValue = analogRead(pin);
      Serial.print("Analog "); Serial.print(pin); Serial.print(" = ");
      Serial.print(readValue);    // read the input pin "Ax" is already defined
      Serial.print(" Volts = ");
      Serial.println(double(readValue)*analogToVolts);    // read the input pin "Ax"
}
// get Digital Channels
void getsetDigital(int pin, int value)
{
      Serial.print("Digital "); Serial.print(pin); Serial.print(" was = "); Serial.println(digitalRead(pin));
      if(value == -1)digitalWrite(pin, !digitalRead(pin)); // invert value
      else if(value == 0)digitalWrite(pin, LOW); // set value low
      else if(value == 1)digitalWrite(pin, HIGH); // set value high
      else Serial.println(getStringfromProgMem(Txt_table,invalidValue));
}
// get Digital Channels
void processWebCmd(char* webText)
{
    Serial.print(getStringfromProgMem(Txt_table,webText0));
    Serial.print(getStringfromProgMem(Txt_table,webText1));
    Serial.print(webText);    
    Serial.print(getStringfromProgMem(Txt_table,webText2));
}

Attachments:
Arduino_Lesson_3b___Advanced_Parsing_the_commands_from_Serial.ino.zip
  • Sign in to reply

Top Comments

  • Former Member
    Former Member over 10 years ago in reply to clem57 +2
    Just use char, but if you want a string stored into only program (flash) memory, use const PROGMEM char. Here is an example of the change needed to line 87 from Peter's sketch: PROGMEM prog_char goodbyRepTxt_0…
  • Robert Peter Oakes
    Robert Peter Oakes over 9 years ago in reply to Workshopshed

    Nunit for Arduino... an interesting concept

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • Workshopshed
    Workshopshed over 9 years ago

    I just got into this series but there's some great stuff here.

    For desktop software I find a good suite of unit tests a great way to allow you to make changes safely. Have you looked at unit testing for the Arduino?

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • Robert Peter Oakes
    Robert Peter Oakes over 9 years ago in reply to Former Member

    looks like it is time to update the series with the latest IDE etc

     

    Regards

    Peter

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • Former Member
    Former Member over 9 years ago in reply to Former Member

    Hi,  I changed the code as you recommended but had below error messages,

     

     

    Lesson3b_part2-Arduino_Fast_Track_Programming:152: error: cannot convert 'char**' to 'char*' for argument '1' to 'char* strcpy_P(char*, const char*)'

    strcpy_P(buffer, (char*)pgm_read_word(&(Table[i])));

                                                       ^

    Lesson3b_part2-Arduino_Fast_Track_Programming:153: error: cannot convert 'char**' to 'char*' in return

    return buffer;

            ^

    C:\Users\IC\Documents\Arduino\Lesson3b_part2-Arduino_Fast_Track_Programming\Lesson3b_part2-Arduino_Fast_Track_Programming.ino: In function 'int findCommand(char*)':

    Lesson3b_part2-Arduino_Fast_Track_Programming:163: error: invalid conversion from 'const char* const*' to 'const char**' [-fpermissive]

         if(strcmp(searchText,getStringfromProgMem(Cmd_table,startCount))==0)

                                                                       ^

    Lesson3b_part2-Arduino_Fast_Track_Programming:149: error: initializing argument 1 of 'char* getStringfromProgMem(const char**, int)' [-fpermissive]

    char* getStringfromProgMem(const char* Table[], int i)

           ^

     

    There was a lot of same messages....

    Any suggestions?

    If anyone can provide updated source, it would be highly appreciated.

     

    Thanks for a great tutorial!

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • Former Member
    Former Member over 10 years ago in reply to clem57

    Just use char, but if you want a string stored into only program (flash) memory, use const PROGMEM char. Here is an example of the change needed to line 87 from Peter's sketch:

     

    PROGMEM prog_char goodbyRepTxt_0[]  = "Goodby back at you ";


    change to:


    const PROGMEM char goodbyRepTxt_0[]  = "Goodby back at you ";

    • Cancel
    • Vote Up +2 Vote Down
    • Sign in to reply
    • More
    • Cancel
>
element14 Community

element14 is the first online community specifically for engineers. Connect with your peers and get expert answers to your questions.

  • Members
  • Learn
  • Technologies
  • Challenges & Projects
  • Products
  • Store
  • About Us
  • Feedback & Support
  • FAQs
  • Terms of Use
  • Privacy Policy
  • Legal and Copyright Notices
  • Sitemap
  • Cookies

An Avnet Company © 2025 Premier Farnell Limited. All Rights Reserved.

Premier Farnell Ltd, registered in England and Wales (no 00876412), registered office: Farnell House, Forge Lane, Leeds LS12 2NE.

ICP 备案号 10220084.

Follow element14

  • X
  • Facebook
  • linkedin
  • YouTube