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
Arduino Forum Having trouble combining sketches, and am not sure if it is memory related or something else
  • Blog
  • Forum
  • Documents
  • Quiz
  • Events
  • Polls
  • Files
  • Members
  • Mentions
  • Sub-Groups
  • Tags
  • More
  • Cancel
  • New
Join Arduino to participate - click to join for free!
Actions
  • Share
  • More
  • Cancel
Forum Thread Details
  • State Verified Answer
  • Replies 14 replies
  • Answers 3 answers
  • Subscribers 394 subscribers
  • Views 1010 views
  • Users 0 members are here
  • arduino uno
Related

Having trouble combining sketches, and am not sure if it is memory related or something else

donfrankrice
donfrankrice over 9 years ago

Hi everyone,

 

I am fairly new to Arduino, and a beginner programmer, and could really use another pair of eyes to help me figure out how to dig my way out of a problem.

 

Here's my setup. I have an Arduino Uno, an Ethernet Shield 2, a magnetic window sensor, an HC-SR04 ultrasonic sensor, and an IP camera. I have the ultrasonic sensor pointing at a small box that I have place to simulate a "window".

 

Here's how things are supposed to work. When the magnetic sensor is pulled apart, the "window" is open. The ultrasonic sensor sends out a ping, and if the return is measured to be at least 15cm, the IP camera is accessed to retrieve a still image. This image is saved to the SD card on the Ethernet shield, and then encoded and sent off as an email attachment via SMTP2GO.

 

At present, I have the call to "encode()" commented out (line 251 below). With everything else in place, my sketch uses up 82% program space and 78% dynamic memory. When run, I successfully retrieve and save the image from the IP camera and the email is successfully sent (albeit with a blank attached file - remember, no encoding takes place). That works well. The second that I remove the comments to the call to "encode()", my program space is used up by 84%, and dynamic memory by 81%. This time, running the sketch results in the image failing to save. The file opens and closes, but no data ever gets written. re-comment the call to "encode()", and all is well again.

 

Thinking that this was a memory problem, I started looking into Arduino memory management. I am new to this, so I am not sure that I understand everything. I already have F() placed around my Serial.println strings, but none around my client.println strings. Putting F() around even one of these client.println strings reduces my dynamic memory use by 1% (good!), but when I run the code my image fails to save again (bad!).

 

I have successfully tested each segment of this (window sensor, ultrasonic sensor, camera access, image retrieval, attachment sent as email) in separate sketches.  My challenge has been in combining them all into a single sketch.  Most of it has gone well - and things have only started to go wrong when I tried adding the email portion of this.

 

Can someone please tell me where I am going wrong? Here is my code that I am using, borrowed from several examples found online.

 

#include <SPI.h>
#include <SD.h>
#include <Ethernet2.h>
#include <NewPing.h>

// Arduino pin tied to trigger pin on the ultrasonic sensor.
#define TRIGGER_PIN  8  

// Arduino pin tied to echo pin on the ultrasonic sensor.
#define ECHO_PIN     7  

// Maximum distance we want to ping for (in centimeters). Maximum sensor 
//distance is rated at 400-500cm.
#define MAX_DISTANCE 200 

// window sensor pin
#define WindowPin  6 

//variable to hold the distance to the "window"
int distance;

// NewPing setup of pins and maximum distance.
NewPing sonar(TRIGGER_PIN, ECHO_PIN, MAX_DISTANCE); 

byte mac[] = { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66 };
IPAddress server(192, 168, 1, 27);
IPAddress ip(192, 168, 1, 100);

char mailServer[] = "mail.smtp2go.com";

EthernetClient client;

boolean currentLineIsBlank = true;
File theFile;
File sendFile;

//variable to use for encoding process
static const char cb64[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

void setup() 
{
  Serial.begin(9600);
  Serial.println(F("Starting the serial monitor"));
  pinMode(WindowPin, INPUT_PULLUP);
  
  // disable Ethernet chip
  pinMode(10, OUTPUT);
  digitalWrite(10, HIGH);
  
  Serial.print(F("Initializing SD card..."));

  if (!SD.begin(4)) 
  {
    Serial.println(F(" initialization failed!"));
    return;
  }
  Serial.println(F(" initialization done."));
  
  // start the Ethernet connection:
  if (Ethernet.begin(mac) == 0) 
  {
    Serial.println(F("Failed to configure Ethernet using DHCP"));
    // try to congifure using IP address instead of DHCP:
    Ethernet.begin(mac, ip);
  }
  // give the Ethernet shield a second to initialize:
  delay(1000);

 // print your local IP address:
  Serial.println(Ethernet.localIP());  

}

void loop() 
{
  //take a read of the WindowPin.  If it reads HIGH, the window is open,
  //and a message can be displayed as such
  if(digitalRead(WindowPin) == HIGH)
  {
    Serial.println(F("the window is open. check to see if person is inside or outside"));
    
    // Wait 50ms between pings (about 20 pings/sec). 29ms should be the 
    //shortest delay between pings.
    delay(50);                     
    Serial.print(F("Ping: "));
    
    // Send ping, get distance in cm and print result (0 = outside set distance range)
    distance = sonar.ping_cm();
    Serial.print(distance); 
    Serial.print(F("cm"));
    
    //if the distance to the "window" is less than 15cm, indicate that it was
    //opened from inside. Otherwise, insicate that it was opened from outside
    if(distance <15)
    {
      Serial.println(F(" --opened from inside"));
      Serial.println();
    }
    else
    {
      Serial.println(F(" --opened from outside"));
      Serial.println(F("Let's take a picture"));
      
     
        Serial.println(F("connecting to camera..."));
      
        // if you get a connection, report back via serial:
        if (client.connect(server, 80)) 
        {
          Serial.println(F("connected"));
          // Make a HTTP request:
          client.println("GET /image.jpg");
          client.println("Authorization: Basic Y=");
          client.println("Host: 192.168.1.27");
          //client.println("Connection: close");
          client.println();
          delay(1000);
        }
        else 
        {
          // kf you didn't get a connection to the server:
          Serial.println(F("connection failed"));
        }
        
   // open the file for writing
  Serial.println(F("Creating file."));
  theFile = SD.open("pic.jpg", FILE_WRITE);  // change file name to write to here
  if (!theFile) {
    Serial.println(F("Could not create file"));
    while (1);
  }
  // from the server, read them and print them:
  while (client.available()) {
    char c = client.read();
    if (c == '\n' && currentLineIsBlank) {
      // end of HTTP header, now save requested file
      while (client.connected()) {
        // stay in this loop until the file has been received
        if (client.available()) {
          c = client.read();  // get file byte
          Serial.print(c);
          theFile.print(c);   // save file byte
        }
      }
    }
    // detect the end of the incoming HTTP header
    if (c == '\n') {
      // starting a new line
      currentLineIsBlank = true;
    }
    else if (c != '\r') {
      // got a character on the current line
      currentLineIsBlank = false;
    }
  }

  // if the server's disconnected, stop the client:
  if (!client.connected()) {
    Serial.println();
    Serial.println(F("disconnecting."));
    client.stop();
    theFile.close();
    Serial.println(F("Finished writing to file"));
    
    if(sendEmail()) Serial.println(F("Email Sent"));
      else Serial.println(F("Email Failed"));
    
    while(true);
    
    // do nothing forevermore:
    //while (true);
  }
  }
  
  //delay for 2 seconds, to avoid incessant scrolling
    delay(2000);
  }
  
  //If the sensor reads LOW, the window is closed, and a message can be
  //displayed as such.  No need to involve the ultrasonic sensor
  else
  {
    Serial.println(F("Window is closed. No need to check distance"));
    
    //delay for 2 seconds, to avoid incessant scrolling
    delay(2000);
  }
  
}


byte sendEmail(){
  byte thisByte = 0;
  byte respCode;
  
  if(client.connect(mailServer, 80) == 1){
    Serial.println(F("Connected"));
  }
  else {
    Serial.println(F("Connection Failed"));
    return 0;
  }
  if(!eRcv()) return 0;

  Serial.println(F("Sending Hello"));
//replace 1.2.3.4 with your Arduino's ip**************************
  client.println("EHLO");
  if(!eRcv()) return 0;

  Serial.println(F("Sending authorizes login"));
  client.println("auth login");
  if(!eRcv()) return 0;

  Serial.println(F("Sending User"));
//Change to your base64 encoded user**************************
  client.println("Y=");
  if(!eRcv()) return 0;

  Serial.println(F("Sending Password"));
//change to your base64 encoded password**************************
  client.println("Y=");
  if(!eRcv()) return 0;

//change to your email address (sender)**************************
  Serial.println(F("Sending From"));
  client.println("MAIL From: <ME@gmail.com>");
  if(!eRcv()) return 0;

//change to recipient address**************************
  Serial.println(F("Sending To"));
  client.println("RCPT To: <YOU@gmail.com>");
  if(!eRcv()) return 0;
  Serial.println(F("Sending DATA"));
  client.println("DATA");
  if(!eRcv()) return 0;
  Serial.println(F("Sending email"));

//change to recipient address**************************
  client.println("To: You <YOU@gmail.com>");

//change to your address**************************
  client.println("From: Me <ME@gmail.com>");
  client.println("Subject: Sent from Donny's Arduino");
  
//Start of Attach File
  sendFile =SD.open("pic.jpg",FILE_READ);
  client.print("Content-Type: image/jpg; name=\"pic.jpg\"\r\n");
  client.write("Content-Disposition: attachment; filename=pic.jpg\r\n");
  client.print("Content-Transfer-Encoding: base64\r\n\r\n");
  
  //encode();
  sendFile.close();
//End of Attach File

  client.print("\r\n.\r\nQUIT\n");
  if(!eRcv()) return 0;
  client.stop();
  Serial.println(F("disconnected"));
  return 1;
}

byte eRcv(){
  byte respCode;
  byte thisByte;
  int loopCount = 0;
  while(!client.available()){
    delay(1);
    loopCount++;
    // if nothing received for 10 seconds, timeout
    if(loopCount > 10000) {
      client.stop();
      Serial.println(F("\r\nTimeout"));
      return 0;
    }
  }
  respCode = client.peek();
  while(client.available()){ 
    thisByte = client.read();   
    Serial.write(thisByte);
  }
  if(respCode >= '4'){
    efail();
    return 0; 
  }
  return 1;
}

void efail(){
  byte thisByte = 0;
  int loopCount = 0;
  client.println(F("QUIT"));
  while(!client.available()) {
    delay(1);
    loopCount++;
    // if nothing received for 10 seconds, timeout
    if(loopCount > 10000) {
      client.stop();
      Serial.println(F("\r\nTimeout"));
      return;
    }
  }
  while(client.available()){ 
    thisByte = client.read();   
    Serial.write(thisByte);
  }
  client.stop();
  Serial.println(F("isconnected"));
}

void encodeblock(unsigned char in[3],unsigned char out[4],int len) {
  out[0]=cb64[in[0]>>2]; out[1]=cb64[((in[0]&0x03)<<4)|((in[1]&0xF0)>>4)];
  out[2]=(unsigned char) (len>1 ? cb64[((in[1]&0x0F)<<2)|((in[2]&0xC0)>>6)] : '=');
  out[3]=(unsigned char) (len>2 ? cb64[in[2]&0x3F] : '=');
}

void encode() {
  unsigned char in[3],out[4];
  int i,len,blocksout=0;
  while (sendFile.available()!=0) {
    len=0;
    for (i=0;i<3;i++){
      in[i]=(unsigned char) sendFile.read();
      if (sendFile.available()!=0) len++;
      else in[i]=0;
    }
    if (len){
      encodeblock(in,out,len);
      for(i=0;i<4;i++) client.write(out[i]);
        blocksout++;
    }
    if (blocksout>=19||sendFile.available()==0){
      if (blocksout) client.print("\r\n");  blocksout=0;
    }
  }
}

 

 

Thanks for your help in advance.  I really appreciate it!

 

Donny

  • Sign in to reply
  • Cancel
Parents
  • Robert Peter Oakes
    0 Robert Peter Oakes over 9 years ago

    With what you have your almost definitely running out of RAM, and you never get warnings for it, what I often do in this situation to test the theory is drop it onto an ATMEGA2560 and try it there without alteration, if it works, case proven.

     

    You have several long strings still in RAM and this is doubled at run time, a way of getting rid of these is to also put them into flash using progmem and having a function to retrieve them

     

    this is an example of one of my tutorials that using FLASH in the way I describe specifically to reduce ram usage, note this sketch is deliberately made large to create the memory issue in order to demonstrate resolving it

     

    /*
    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
    Created By: Peter Oakes
    Date created: 11th June 2014
    
    
    Updated 23 June 2016
    for IDE 1.6.9 by Peter Oakes
    Needed to re-arrange the PROGMEM statements and add some "const char" prefixes to get working again
    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 Libraries */
    #include <avr/pgmspace.h>
    #include <Arduino.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
    // Don't 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
    const char welcomeTxt_0[] PROGMEM = "Hello, please ask me something\ntype \"Hello\", \"Goodbye\", \"web\" or \"dosomething\"\n";
    const char PROGMEM welcomeTxt_1[]  = "or \"pwmx where x is from 0 to 5\"\nor \"analogx where x is from 0 to 5\"\n";
    const char PROGMEM welcomeTxt_2[]  = "or \"digitalx where x is from 0 to 7\"\nor make up your own command\n";
    // end of welcome text
    const char PROGMEM webTxt_0[]  = "HTTP/1.1 200 OK\nContent-Type: text/html\nConnection: close\n";
    const char PROGMEM webTxt_1[]  = "<!DOCTYPE html><html><head><title>Hello from www.thebreadboard.ca</title></head><body>";
    const char PROGMEM webTxt_2[]  = "</body></html>\n";
    // error text
    const char PROGMEM errorTxt_0[]  = "I don't understand you \nYou said: ";
    const char PROGMEM helloRepTxt_0[]  = "Hello back at you ";
    const char PROGMEM goodbyRepTxt_0[]  = "Goodbye back at you ";
    const char PROGMEM dosomethingRepTxt_0[]  = "what would you like me to do?";
    const char PROGMEM invalidValueRepTxt_0[]  = "Invalid Value, setting unchanged";
    #if debug // yes you can even use compiler directives here
    const char PROGMEM paramIsNumeric_0[]  = "Remaining Param is numeric and = ";
    const char PROGMEM paramIsNotNumeric_0[]  = "Remaining Param is not numeric or invalid, value = ";
    #endif
    
    
    //array of pointers to the above message strings
    const char* const Txt_table[] PROGMEM =
    {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
    const char PROGMEM  helloCmd[]     = "Hello";
    const char PROGMEM  goodbyCmd[]    = "Goodbye";
    const char PROGMEM  dosomethingCmd[]      = "dosomething";
    const char PROGMEM  web1Cmd[]      = "web1";
    const char PROGMEM  web2Cmd[]      = "web2";
    const char PROGMEM  web3Cmd[]      = "web3";
    const char PROGMEM  web4Cmd[]      = "web4";
    const char PROGMEM  web5Cmd[]      = "web5";
    const char PROGMEM  analog0Cmd[]   = "analog0";
    const char PROGMEM  analog1Cmd[]   = "analog1";
    const char PROGMEM analog2Cmd[]   = "analog2";
    const char PROGMEM analog3Cmd[]   = "analog3";
    const char PROGMEM analog4Cmd[]   = "analog4";
    const char PROGMEM analog5Cmd[]   = "analog5";
    const char PROGMEM digital0Cmd[]   = "digital0";
    const char PROGMEM digital1Cmd[]   = "digital1";
    const char PROGMEM digital2Cmd[]   = "digital2";
    const char PROGMEM digital3Cmd[]   = "digital3";
    const char PROGMEM digital4Cmd[]   = "digital4";
    const char PROGMEM digital5Cmd[]   = "digital5";
    const char PROGMEM digital6Cmd[]   = "digital6";
    const char PROGMEM digital7Cmd[]   = "digital7";
    const char PROGMEM pwm0Cmd[]       = "pwm0";
    const char PROGMEM pwm1Cmd[]       = "pwm1";
    const char PROGMEM pwm2Cmd[]       = "pwm2";
    const char PROGMEM pwm3Cmd[]       = "pwm3";
    const char PROGMEM pwm4Cmd[]       = "pwm4";
    const char PROGMEM  pwm5Cmd[]       = "pwm5";
    
    
    //array of pointers to the above command strings
    const char* const Cmd_table[] PROGMEM =
    {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 buffer[progMemBuffer];
    char* getStringfromProgMem(const char* const Table[], int i)
    {
    
    
    strcpy_P(buffer, (char*)pgm_read_word(&(Table[i])));
    return buffer;
    };
    
    
    // Search through the commands until 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() 
    { 
      // Initialize 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 Carriage 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));
    }

     

    see the tutorial here :- Fast Track to Arduino Programming  lesson 3B

     

    I updated the sketch to be compatible with the latest IDE

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • Verify Answer
    • Reject Answer
    • Cancel
Reply
  • Robert Peter Oakes
    0 Robert Peter Oakes over 9 years ago

    With what you have your almost definitely running out of RAM, and you never get warnings for it, what I often do in this situation to test the theory is drop it onto an ATMEGA2560 and try it there without alteration, if it works, case proven.

     

    You have several long strings still in RAM and this is doubled at run time, a way of getting rid of these is to also put them into flash using progmem and having a function to retrieve them

     

    this is an example of one of my tutorials that using FLASH in the way I describe specifically to reduce ram usage, note this sketch is deliberately made large to create the memory issue in order to demonstrate resolving it

     

    /*
    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
    Created By: Peter Oakes
    Date created: 11th June 2014
    
    
    Updated 23 June 2016
    for IDE 1.6.9 by Peter Oakes
    Needed to re-arrange the PROGMEM statements and add some "const char" prefixes to get working again
    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 Libraries */
    #include <avr/pgmspace.h>
    #include <Arduino.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
    // Don't 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
    const char welcomeTxt_0[] PROGMEM = "Hello, please ask me something\ntype \"Hello\", \"Goodbye\", \"web\" or \"dosomething\"\n";
    const char PROGMEM welcomeTxt_1[]  = "or \"pwmx where x is from 0 to 5\"\nor \"analogx where x is from 0 to 5\"\n";
    const char PROGMEM welcomeTxt_2[]  = "or \"digitalx where x is from 0 to 7\"\nor make up your own command\n";
    // end of welcome text
    const char PROGMEM webTxt_0[]  = "HTTP/1.1 200 OK\nContent-Type: text/html\nConnection: close\n";
    const char PROGMEM webTxt_1[]  = "<!DOCTYPE html><html><head><title>Hello from www.thebreadboard.ca</title></head><body>";
    const char PROGMEM webTxt_2[]  = "</body></html>\n";
    // error text
    const char PROGMEM errorTxt_0[]  = "I don't understand you \nYou said: ";
    const char PROGMEM helloRepTxt_0[]  = "Hello back at you ";
    const char PROGMEM goodbyRepTxt_0[]  = "Goodbye back at you ";
    const char PROGMEM dosomethingRepTxt_0[]  = "what would you like me to do?";
    const char PROGMEM invalidValueRepTxt_0[]  = "Invalid Value, setting unchanged";
    #if debug // yes you can even use compiler directives here
    const char PROGMEM paramIsNumeric_0[]  = "Remaining Param is numeric and = ";
    const char PROGMEM paramIsNotNumeric_0[]  = "Remaining Param is not numeric or invalid, value = ";
    #endif
    
    
    //array of pointers to the above message strings
    const char* const Txt_table[] PROGMEM =
    {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
    const char PROGMEM  helloCmd[]     = "Hello";
    const char PROGMEM  goodbyCmd[]    = "Goodbye";
    const char PROGMEM  dosomethingCmd[]      = "dosomething";
    const char PROGMEM  web1Cmd[]      = "web1";
    const char PROGMEM  web2Cmd[]      = "web2";
    const char PROGMEM  web3Cmd[]      = "web3";
    const char PROGMEM  web4Cmd[]      = "web4";
    const char PROGMEM  web5Cmd[]      = "web5";
    const char PROGMEM  analog0Cmd[]   = "analog0";
    const char PROGMEM  analog1Cmd[]   = "analog1";
    const char PROGMEM analog2Cmd[]   = "analog2";
    const char PROGMEM analog3Cmd[]   = "analog3";
    const char PROGMEM analog4Cmd[]   = "analog4";
    const char PROGMEM analog5Cmd[]   = "analog5";
    const char PROGMEM digital0Cmd[]   = "digital0";
    const char PROGMEM digital1Cmd[]   = "digital1";
    const char PROGMEM digital2Cmd[]   = "digital2";
    const char PROGMEM digital3Cmd[]   = "digital3";
    const char PROGMEM digital4Cmd[]   = "digital4";
    const char PROGMEM digital5Cmd[]   = "digital5";
    const char PROGMEM digital6Cmd[]   = "digital6";
    const char PROGMEM digital7Cmd[]   = "digital7";
    const char PROGMEM pwm0Cmd[]       = "pwm0";
    const char PROGMEM pwm1Cmd[]       = "pwm1";
    const char PROGMEM pwm2Cmd[]       = "pwm2";
    const char PROGMEM pwm3Cmd[]       = "pwm3";
    const char PROGMEM pwm4Cmd[]       = "pwm4";
    const char PROGMEM  pwm5Cmd[]       = "pwm5";
    
    
    //array of pointers to the above command strings
    const char* const Cmd_table[] PROGMEM =
    {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 buffer[progMemBuffer];
    char* getStringfromProgMem(const char* const Table[], int i)
    {
    
    
    strcpy_P(buffer, (char*)pgm_read_word(&(Table[i])));
    return buffer;
    };
    
    
    // Search through the commands until 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() 
    { 
      // Initialize 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 Carriage 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));
    }

     

    see the tutorial here :- Fast Track to Arduino Programming  lesson 3B

     

    I updated the sketch to be compatible with the latest IDE

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • Verify Answer
    • Reject Answer
    • Cancel
Children
No Data
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