7. Program Explanation

Share for us
Share on facebook
Share on twitter
Share on pinterest
Share on whatsapp

Through the explanation previously, you should already have a general understanding of the robot. Next we will come to the most important part – theory and program. Most of the programs provided are compiled as libraries and you just need to call them in the Arduino IDE. So in the following let’s focus on how to compile them.

7.1      Singing with Buzzer

The function of singing with buzzer is to drive the active buzzer to beep by compiling the tunes and the notes so as to play the music. So, what are the procedures?

First, let’s come to some simple musical knowledge. We can compile the code after knowing how the music is played.

1. Play a single note. A piece of music is composed of several notes. A note corresponds to a frequency. We all know that as long as the Arduino outputs a frequency to the buzzer, there will be a corresponding beep. Here is a table for the mapping between note and tune:

2. Adjust the time span of a note playing. After knowing how the music is being played, next we are going to control how long a single note lasts – each note should play for a certain time and a beautiful piece of music can be thus formed, otherwise just a note after another would be boring enough. How to determine the unit time for each note to be played?

As we all know, the rhythm can be one beat, half beat, 1/4 beat and 1/8 beat. We set the time of one beat as 1 in code, half beat as 0.5, 1/4 as 0.25, and 1/8 as 0.125. So we can give each note one such beat and then the music is generated.

Let’s see how to translate the numbered musical notation to the corresponding frequency and beat. Just take For Elise as an example: 

Let’s check the details in the music score. Look at 1=C on the top left corner in the figure above. It means it uses the key of C. Check the C line (in red and bolded) in the frequency table previously. In the first note, there is a dot above 3, so it corresponds to 661 and its corresponding variable is NOTE_CH3. The time is half beat, which is 0.5 in the array duration[]. As for the second note, there is another dot above 2 and it is 589. The time is half beat equaling to 0.5. As for the definition of scale, we’ve already defined it in the underlying library so you can use it directly. NOTE_A1 means DO in the key of A, Note_A2 for RE, NOTE_AH1 for the treble DO, and NOTE_AD1 for the bass DO in key A……

Note that there should be no sound for 0 in the score. And the beat should be 1.

Every “-” added behind a note, it means +1 of the beat (1+1).

The dot behind a note means a half beat – for example, the beat of 3• is 1+0.5.

An easy way for rhythm: If one single note is not underlined, it means one beat (1); one underline = half beat (0.5); two underlines = 1/4 beat (0.25); one “—” (dash) = duplicate beat of the former note (+1).

For pitch: Find the value of the note in the table previously based on the pitch (dot at the top or bottom of the number).

Now let’s check the code for the For Elise in detail. The following two arrays are the tune (tune[]) and frequency ([duration]) of each note.

int tune[] =
{
  NOTE_CH3,NOTE_CH2,NOTE_CH3,NOTE_CH2,NOTE_CH3,NOTE_C7,NOTE_CH2,NOTE_CH1,NOTE_C6,
  NOTE_C1,NOTE_C3,NOTE_C6,NOTE_C7,
  NOTE_C3,NOTE_C5,NOTE_C7,NOTE_CH1,
  NOTE_C3,NOTE_CH3,NOTE_CH2,NOTE_CH3,NOTE_CH2,NOTE_CH3,NOTE_C7,NOTE_CH2,NOTE_CH1,NOTE_C6,
  NOTE_C1,NOTE_C3,NOTE_C6,NOTE_C7,
  NOTE_C3,NOTE_CH1,NOTE_C7,NOTE_C6,
};
float duration[]=
{
  0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,1,
  0.5,0.5,0.5,1,
  0.5,0.5,0.5,1,
  0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,1,
  0.5,0.5,0.5,1,
  0.5,0.5,0.5,1,
};
int tune[] =
{
  NOTE_CH3,NOTE_CH2,NOTE_CH3,NOTE_CH2,NOTE_CH3,NOTE_C7,NOTE_CH2,NOTE_CH1,NOTE_C6,
  NOTE_C1,NOTE_C3,NOTE_C6,NOTE_C7,
  NOTE_C3,NOTE_C5,NOTE_C7,NOTE_CH1,
  NOTE_C3,NOTE_CH3,NOTE_CH2,NOTE_CH3,NOTE_CH2,NOTE_CH3,NOTE_C7,NOTE_CH2,NOTE_CH1,NOTE_C6,
  NOTE_C1,NOTE_C3,NOTE_C6,NOTE_C7,
  NOTE_C3,NOTE_CH1,NOTE_C7,NOTE_C6,
};
float duration[]=
{
  0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,1,
  0.5,0.5,0.5,1,
  0.5,0.5,0.5,1,
  0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,1,
  0.5,0.5,0.5,1,
  0.5,0.5,0.5,1,
};


What’s more, the most important step is how to write the compiled tunes and voice frequencies into the buzzer and make it beep accordingly by high and low level and delay.

RollbotBuzzer::RollbotBuzzer(int pin)
{
    pinMode(pin,OUTPUT);//Set the pin to buzzer as output 
    tonePin=pin;
}
void RollbotBuzzer::MiniBuzzer(int *tune,float *duration,int Length)
{
    for(int x=0;x<Length;x++)//the number of notation cycling
    {
      tone(tonePin,tune[x]);//The function plays the arrarys (each notation) in the tune[] list in turn
      delay(400*duration[x]);//duration is the time each notation lasts.
                             //400 means the longer time, the slower tempo, and vice versa
      noTone(tonePin);//Exit the current notation and move on to the next
    }
}

7.2   Smiling Face with LEDs

The smiling face of the robot is displayed with 16 yellow LEDs. They are controlled directly by the 74HC595 chip. In the code, change the parameter in the output function Dataout() to 0xff, so the LEDs forming a smiling face will light up. Change it to 0x00, and the LEDs will go out. 

/*********Output of data control***********/
void RollbotLED::DataOut(int val)
{
  for (int i = 0;i < 8;i++)
  {
      if(val&0x80)
        digitalWrite(dataPin,1);
      else
        digitalWrite(dataPin,0); 
      val<<=1;
      digitalWrite(clockPin, HIGH);
      delayMicroseconds(10);
      digitalWrite(clockPin, LOW);   
  }
  digitalWrite(latchPin,LOW);
  delayMicroseconds(10);
  digitalWrite(latchPin,HIGH);
}

7.3 Sensor Signal Intensity on OLED Screen

The OLED screen can display characters, letters, and patterns. It is used in the robot for your better interaction with the robot. How to use it? Here IIC is used in hardware. First, initialize the OLED screen. For use, two functions are indispensable: WriteCommand() and WriteData(). 

/*********OLED command writing***********/
void RollbotOLED::WriteCommand(unsigned int ins)
{
  Wire.beginTransmission(0x78 >> 1);//0x78 >> 1
  Wire.write(0x00);//0x00
  Wire.write(ins);
  Wire.endTransmission();
}
/*********OLED data writing***********/
void RollbotOLED::WriteData(unsigned int dat)
{
  Wire.beginTransmission(0x78 >> 1);//0x78 >> 1
  Wire.write(0x40);//0x40
  Wire.write(dat);
  Wire.endTransmission();
}

Next, the function for positions on the OLED screen: SetPos(). With this function, you can locate the specific point of the OLED display. 

void RollbotOLED::IIC_SetPos(unsigned int x, unsigned int y)
{
  WriteCommand(0xb0 + y);
  WriteCommand(((x & 0xf0) >> 4) | 0x10); //|0x10
  WriteCommand((x & 0x0f) | 0x00); //|0x01
}

Apart from the three basic functions, you can use more for display on the OLED screen. The current code is adequate for displaying sensor signal intensity easily. For more information, you can refer to the program RollbotOLED.CPP in the library.

7.4  The Bluetooth Control Program

The Bluetooth control is to control the Rollbot with the app on the mobile phone. You can control the robot to go forward and backward, and turn left or right, as well as go for related steps in the dice game.

First, about the motor-drive. The variables MOTOR_L_DIR, MOTOR_L_PWM, MOTOR_R_DIR, and MOTOR_R_PWM are to control the direction and speed of the motor rotation. You can change their value for each movement of the Rollbot – the specific parameter for speed, and positive and negative value for rotation direction. The variable speed_dir represents the initial status of the motor, which is aforementioned in 5.3, 5.4, and 5.5.

/*********Motor drive function***********/
void RollbotMotors:: MotorSetSpeed(int speed_dir, int speed_left, int speed_right)
{
    switch(speed_dir)
    {
        //speed_dir = 0 means both motors work well and spin forward
        case 0:
        {
            if (speed_left > 0)
            {
                digitalWrite(MOTOR_L_DIR, LOW);
                analogWrite(MOTOR_L_PWM, speed_left);
            }
            else
            {
                digitalWrite(MOTOR_L_DIR, HIGH);
                analogWrite(MOTOR_L_PWM, (-1)*speed_left);
            }
            if (speed_right > 0)
            {
                digitalWrite(MOTOR_R_DIR, HIGH);
                analogWrite(MOTOR_R_PWM, speed_right);
            }
            else
            {
                digitalWrite(MOTOR_R_DIR, LOW);
                analogWrite(MOTOR_R_PWM, (-1)*speed_right);
            }
        }break;
        //speed_dir = 1 means both motors' rotation are reversed and spin backward
        case 1:
        {
            if (speed_left > 0)
            {
                digitalWrite(MOTOR_L_DIR, HIGH);
                analogWrite(MOTOR_L_PWM, speed_left);
            }
            else
            {
                digitalWrite(MOTOR_L_DIR, LOW);
                analogWrite(MOTOR_L_PWM, (-1)*speed_left);
            }
            if (speed_right > 0)
            {
                digitalWrite(MOTOR_R_DIR, LOW);
                analogWrite(MOTOR_R_PWM, speed_right);
           }
            else
            {
                digitalWrite(MOTOR_R_DIR, HIGH);
                analogWrite(MOTOR_R_PWM, (-1)*speed_right);
            }
        }break;
        //speed_dir = 2 means the left motor works well and the right one spins reversely
        case 2:
        {
            if (speed_left > 0)
            {
                digitalWrite(MOTOR_L_DIR, LOW);
                analogWrite(MOTOR_L_PWM, speed_left);
            }
            else
            {
                digitalWrite(MOTOR_L_DIR, HIGH);
                analogWrite(MOTOR_L_PWM, (-1)*speed_left);
            }
            if (speed_right > 0)
            {
                digitalWrite(MOTOR_R_DIR, LOW);
                analogWrite(MOTOR_R_PWM, speed_right);
            }
            else
            {
                digitalWrite(MOTOR_R_DIR, HIGH);
                analogWrite(MOTOR_R_PWM, (-1)*speed_right);
            }
        }break;
        //speed_dir = 3 means the right motor works well and the left one spins reversely
        case 3:
        {
            if (speed_left > 0)
            {
                digitalWrite(MOTOR_L_DIR, HIGH);
                analogWrite(MOTOR_L_PWM, speed_left);
            }
            else
            {
                digitalWrite(MOTOR_L_DIR, LOW);
                analogWrite(MOTOR_L_PWM, (-1)*speed_left);
            }
            if (speed_right > 0)
            {
                digitalWrite(MOTOR_R_DIR, HIGH);
                analogWrite(MOTOR_R_PWM, speed_right);
            }
            else
            {
                digitalWrite(MOTOR_R_DIR, LOW);
                analogWrite(MOTOR_R_PWM, (-1)*speed_right);
            }
        }break;
    }
}
Next, how to read and save the data collected and transferred via Bluetooth:
   while (Serial.available())
    {
      ReceiveByte = Serial.read();
    }
 

Last, judge the data and thus control the robot to act accordingly. Thus, you can control the robot by the app on your mobile.

7.5    Line Following

The line following function is the most important function of the robot. It distinguishes the black and white surfaces through the infrared sensor module and applies the PD algorithm to realize line following. It collects and saves the AD value of the 5 pins: A0, A1, A2, A3, and A7. 

/*********Read data of the sensors***********/
void RollbotReadSensor::Read_Data()
{
  data[0] = analogRead(A0);              
  data[1] = analogRead(A1);
  data[2] = analogRead(A2);
  data[3] = analogRead(A3);
  data[4] = analogRead(A7);                
  OLED_Flag[0] = map(data[0], 50, 500, 3, 6);   
  OLED_Flag[1] = map(data[1], 50, 500, 3, 6);
  OLED_Flag[2] = map(data[2], 50, 500, 3, 6);
  OLED_Flag[3] = map(data[3], 50, 500, 3, 6);
  OLED_Flag[4] = map(data[4], 50, 500, 3, 6);   
}

Compare the collected AD value with the given value and generate a result which indicates the position of the robot to the black line, as shown below. 

int RollbotReadSensor::Read_BlackFlag()
{
    while(1)
    {
      Read_Data();
      if ((data[0] < threshold) && (data[1] > threshold) && (data[2] > threshold) && (data[3] > threshold) && (data[4] > threshold)) return -4;
      else if ((data[0] < threshold) && (data[1] < threshold) && (data[2] > threshold) && (data[3] > threshold) && (data[4] > threshold)) return -3;
      else if ((data[0] > threshold) && (data[1] < threshold) && (data[2] > threshold) && (data[3] > threshold) && (data[4] > threshold)) return -2;
      else if ((data[0] > threshold) && (data[1] < threshold) && (data[2] < threshold) && (data[3] > threshold) && (data[4] > threshold)) return -1;   
      else if ((data[0] > threshold) && (data[1] > threshold) && (data[2] < threshold) && (data[3] > threshold) && (data[4] > threshold)) return 0;    
      else if ((data[0] > threshold) && (data[1] > threshold) && (data[2] < threshold) && (data[3] < threshold) && (data[4] > threshold)) return 1; 
      else if ((data[0] > threshold) && (data[1] > threshold) && (data[2] > threshold) && (data[3] < threshold) && (data[4] > threshold)) return 2;
      else if ((data[0] > threshold) && (data[1] > threshold) && (data[2] > threshold) && (data[3] < threshold) && (data[4] < threshold)) return 3;
      else if ((data[0] > threshold) && (data[1] > threshold) && (data[2] > threshold) && (data[3] > threshold) && (data[4] < threshold)) return 4;
      else if ((data[0] < threshold) && (data[1] < threshold) && (data[2] < threshold) && (data[3] < threshold)) return 5;
      else if ((data[1] < threshold) && (data[2] < threshold) && (data[3] < threshold) && (data[4] < threshold)) return 5;
      else if ((data[0] > threshold) && (data[1] > threshold) && (data[2] > threshold) && (data[3] > threshold) && (data[4] > threshold)) return 6;
    }
}

Finally, we just need to read the position of the robot and adjust the related parameters via the PD formula below, thus realizing the function of following lines.

MotorSpeed = P * SignalValue + D * (SignalValue – LastError)

SignalValue is the gap between the position of Rollbot detected and that of the black line. LastError is the error value accumulated till the last time. SignalValue – LastError means the rate of change of the relative position. 

Motorpeed = int(P * SignalValue + D * (SignalValue - LastError));
    LastError = 0;
    LastError = SignalValue;
    Speed_L = BASE_SPEED + Motorpeed;
    Speed_R = BASE_SPEED - Motorpeed;
    if (Speed_R > MAX_SPEED ) Speed_R = MAX_SPEED;
    if (Speed_L > MAX_SPEED )  Speed_L = MAX_SPEED;
    if (Speed_R < MIN_SPEED) Speed_R = MIN_SPEED;
    if (Speed_L < MIN_SPEED)   Speed_L = MIN_SPEED;
    Motor.Motordrive(Speed_Dir, Speed_L, Speed_R);