3PI Demo Code on arduino

Hi,

I have a 3PI and wanted to be able to load the original demo code for arduino. I wasn’t able to find an official version of the code, but I figured it would be a good excersize in learning to code to copy and paste it into the arduino environment and then make it work again. I changed the function calls to the Library::function format since it makes it orange in the code for me.

I have the code here if anyone else wants it:

/*
 * 3pi-demo-program - demo code for the Pololu 3pi Robot arduino
 * 
 *
 * https://www.pololu.com/docs/0J20
 * https://www.pololu.com
 * https://forum.pololu.com
 *
 */

// The following libraries will be needed by this demo
#include <Pololu3pi.h>
#include <PololuQTRSensors.h>
#include <OrangutanMotors.h>
#include <OrangutanAnalog.h>
#include <OrangutanLEDs.h>
#include <OrangutanLCD.h>
#include <OrangutanPushbuttons.h>
#include <OrangutanBuzzer.h>

Pololu3pi robot;

// This include file allows data to be stored in program space.  The
// ATmega168 has 16k of program space compared to 1k of RAM, so large
// pieces of static data should be stored in program space.
#include <avr/pgmspace.h>

// Introductory messages.  The "PROGMEM" identifier causes the data to
// go into program space.
const char welcome_line1[] PROGMEM = " Pololu";
const char welcome_line2[] PROGMEM = "3\xf7 Robot"; // \xf7 is a greek
                          // pi character
const char demo_name_line1[] PROGMEM = "Demo";
const char demo_name_line2[] PROGMEM = "Arduino";

const char instructions_line1[] PROGMEM = "Use B to";
const char instructions_line2[] PROGMEM = "select.";
const char instructions_line3[] PROGMEM = "Press B";
const char instructions_line4[] PROGMEM = "-try it!";

const char thank_you_line1[] PROGMEM = " Thank ";
const char thank_you_line2[] PROGMEM = "  you!";

const char main_menu_intro_line1[] PROGMEM = "  Main";
const char main_menu_intro_line2[] PROGMEM = "  Menu";

const char menu_bat_test[] PROGMEM = "Battery";
const char menu_led_test[] PROGMEM = "LEDs";
const char menu_lcd_test[] PROGMEM = "LCD";
const char menu_ir_test[] PROGMEM = "Sensors";
const char menu_motor_test[] PROGMEM = "Motors";
const char menu_music_test[] PROGMEM = "Music";
const char menu_pot_test[] PROGMEM = "Trimpot";
const char menu_time_test[] PROGMEM = "Timer";

const char menu_line2[] PROGMEM = "\x7f" "A \xa5" "B C\x7e";
const char back_line2[] PROGMEM = "\6B";


const char fugue[] PROGMEM = 
  "! T120O5L16agafaea dac+adaea fa<aa<bac#a dac#adaea f"
  "O6dcd<b-d<ad<g d<f+d<gd<ad<b- d<dd<ed<f+d<g d<f+d<gd<ad"
  "L8MS<b-d<b-d MLe-<ge-<g MSc<ac<a MLd<fd<f O5MSb-gb-g"
  "ML>c#e>c#e MS afaf ML gc#gc# MS fdfd ML e<b-e<b-"
  "O6L16ragafaea dac#adaea fa<aa<bac#a dac#adaea faeadaca"
  "<b-acadg<b-g egdgcg<b-g <ag<b-gcf<af dfcf<b-f<af"
  "<gf<af<b-e<ge c#e<b-e<ae<ge <fe<ge<ad<fd"
  "O5e>ee>ef>df>d b->c#b->c#a>df>d e>ee>ef>df>d"
  "e>d>c#>db>d>c#b >c#agaegfe fO6dc#dfdc#<b c#4";

const char fugue_title[] PROGMEM = "       \7 Fugue in D Minor - by J.S. Bach \7       ";


void bat_test();
void led_test();
void lcd_test();
void ir_test();
void motor_test();
void music_test();
void time_test();
void pot_test();

typedef void (*function)();
const function main_menu_functions[] = { bat_test, led_test, pot_test, ir_test, motor_test, music_test, time_test };
const char *main_menu_options[] = { menu_bat_test, menu_led_test, menu_pot_test, menu_ir_test, menu_motor_test, menu_music_test, menu_time_test };
const char main_menu_length = sizeof(main_menu_options)/sizeof(main_menu_options[0]);

// A couple of simple tunes, stored in program space.
const char welcome[] PROGMEM = ">g32>>c32";
const char thank_you_music[] PROGMEM = ">>c32>g32";
const char beep_button_a[] PROGMEM = "!c32";
const char beep_button_b[] PROGMEM = "!e32";
const char beep_button_c[] PROGMEM = "!g32";
const char timer_tick[] PROGMEM = "!v8>>c32";

// Data for generating the characters used in load_custom_characters
// and display_readings.  By reading levels[] starting at various
// offsets, we can generate all of the 7 extra characters needed for a
// bargraph.  This is also stored in program space.
const char levels[] PROGMEM = {
  0b00000,
  0b00000,
  0b00000,
  0b00000,
  0b00000,
  0b00000,
  0b00000,
  0b11111,
  0b11111,
  0b11111,
  0b11111,
  0b11111,
  0b11111,
  0b11111
};

// This character is a musical note.
const char note[] PROGMEM = {
  0b00100,
  0b00110,
  0b00101,
  0b00101,
  0b00100,
  0b11100,
  0b11100,
  0b00000,
};

// This character is a back arrow.
const char back_arrow[] PROGMEM = {
  0b00000,
  0b00010,
  0b00001,
  0b00101,
  0b01001,
  0b11110,
  0b01000,
  0b00100,
};

// This function loads custom characters into the LCD.  Up to 8
// characters can be loaded; we use them for 6 levels of a bar graph
// plus a back arrow and a musical note character.
void load_custom_characters()
{
   OrangutanLCD::loadCustomCharacter(levels+0,0); // no offset, e.g. one bar
   OrangutanLCD::loadCustomCharacter(levels+1,1); // two bars
   OrangutanLCD::loadCustomCharacter(levels+2,2); // etc...
   OrangutanLCD::loadCustomCharacter(levels+4,3); // skip level 3
   OrangutanLCD::loadCustomCharacter(levels+5,4);
   OrangutanLCD::loadCustomCharacter(levels+6,5);
   OrangutanLCD::loadCustomCharacter(back_arrow,6);
   OrangutanLCD::loadCustomCharacter(note,7);
   OrangutanLCD::clear();  // the LCD must be cleared for the characters to take effect
}

// 10 levels of bar graph characters
const char bar_graph_characters[10] = {' ',0,0,1,2,3,3,4,5,255};

// Displays the battery voltage.
void bat_test()
{
  int bat =  OrangutanAnalog::readBatteryMillivolts();

  OrangutanLCD::print(bat);
  OrangutanLCD::print("mV");

  delay(100);
}

char wait_for_250_ms_or_button_b()
{
  int i;
  for(i=0;i<25;i++)
  {
    delay(10);
    if(OrangutanPushbuttons::isPressed(BUTTON_B))
      return 1;
  }
  return 0;
}

// Blinks the LEDs
void led_test()
{
  OrangutanBuzzer::play("c32");
  OrangutanLCD::print("Red  ");

  OrangutanLEDs::red(HIGH);
  if(wait_for_250_ms_or_button_b())
    return;
  OrangutanLEDs::red(LOW);
  if(wait_for_250_ms_or_button_b())
    return;

  OrangutanBuzzer::play(">c32");
  OrangutanLCD::gotoXY(0,0);
  OrangutanLCD::print("Green");

  OrangutanLEDs::green(HIGH);
  if(wait_for_250_ms_or_button_b())
    return;
  OrangutanLEDs::green(LOW);
  if(wait_for_250_ms_or_button_b())
    return;
}

void ir_test()
{
  unsigned int sensors[5]; // an array to hold sensor values

  if(OrangutanPushbuttons::isPressed(BUTTON_C))
    read_line_sensors(sensors, IR_EMITTERS_OFF);
  else
    read_line_sensors(sensors,IR_EMITTERS_ON);

  unsigned char i;

  for(i=0;i<5;i++) {
    // Initialize the array of characters that we will use for the
    // graph.  Using the space, an extra copy of the one-bar
    // character, and character 255 (a full black box), we get 10
    // characters in the array.

    // The variable c will have values from 0 to 9, since
    // values are in the range of 0 to 2000, and 2000/201 is 9
    // with integer math.
    char c = bar_graph_characters[sensors[i]/201];

    // Display the bar graph characters.
    OrangutanLCD::print(c);

  }

  // Display an indicator of whether IR is on or off
  if(OrangutanPushbuttons::isPressed(BUTTON_C))
    print("IR-");
  else
    print("  C");

  delay(100);
}

int m1_speed = 0;
int m2_speed = 0;

void motor_test()
{
  static char m1_back = 0, m2_back = 0;
  char m1_char, m2_char;

  if(OrangutanPushbuttons::isPressed(BUTTON_A))
  {
    if(m1_speed == 0)
    {
      delay(200);

      // If the button is pressed quickly when the motor is off,
      // reverse direction.
      if(!OrangutanPushbuttons::isPressed(BUTTON_A))
        m1_back = !m1_back;
    }
    
    m1_speed += 10;
  }
  else
    m1_speed -= 20;

  if(OrangutanPushbuttons::isPressed(BUTTON_C))
  {
    if(m2_speed == 0)
    {
      delay(200);

      // If the button is pressed quickly when the motor is off,
      // reverse direction.
      if(!OrangutanPushbuttons::isPressed(BUTTON_C))
        m2_back = !m2_back;
    }

    m2_speed += 10;
  }
  else
    m2_speed -= 20;

  if(m1_speed < 0)
    m1_speed = 0;

  if(m1_speed > 255)
    m1_speed = 255;

  if(m2_speed < 0)
    m2_speed = 0;

  if(m2_speed > 255)
    m2_speed = 255;

  // 255/26 = 9, so this gives values in the range of 0 to 9
  m1_char = bar_graph_characters[m1_speed / 26];
  m2_char = bar_graph_characters[m2_speed / 26];
  OrangutanLCD::print(m1_char);
  OrangutanLCD::print(m1_back ? 'a' : 'A');
  OrangutanLCD::print(m1_char);
  OrangutanLCD::gotoXY(5, 0);
  OrangutanLCD::print(m2_char);
  OrangutanLCD::print(m2_back ? 'c' : 'C');
  OrangutanLCD::print(m2_char);

  OrangutanMotors::setSpeeds(m1_speed * (m1_back ? -1 : 1), m2_speed * (m2_back ? -1 : 1));
  delay(50);
}


void music_test()
{
  static char fugue_title_pos = 0;
  static long last_shift = 0;
  char c,i;

  if(millis() - last_shift > 250)
  {
    for(i=0;i<8;i++)
    {
      c = pgm_read_byte(fugue_title + fugue_title_pos + i);
      OrangutanLCD::print(c);
    }
    last_shift = millis();

    fugue_title_pos ++;
    if(fugue_title_pos + 8 >= sizeof(fugue_title))
      fugue_title_pos = 0;
  }

  if(!OrangutanBuzzer::isPlaying())
  {
     OrangutanBuzzer::playFromProgramSpace(fugue);
  }

  delay(100);
}

void pot_test()
{
  long start = millis();
  char elapsed_ms;
  int value;

  OrangutanAnalog::setMode(MODE_10_BIT);
  OrangutanLCD::print(OrangutanAnalog::readTrimpot());
  OrangutanLCD::print("   "); // to clear the display

  while((elapsed_ms = millis() - start) < 100)
  {
    value = OrangutanAnalog::readTrimpot();
    OrangutanBuzzer::playFrequency(value, 200, 15);
    
    if(value < elapsed_ms*10)
    {
      OrangutanLEDs::red(LOW);
      OrangutanLEDs::green(HIGH);
    }
    else
    {
      OrangutanLEDs::red(HIGH);
      OrangutanLEDs::green(LOW);
    }
  }
}

void time_test()
{
  static long elapsed_time = 0;
  static long last_read = 0;
  static long is_ticking = 0;
  static char a_is_pressed = 0;
  static char c_is_pressed = 0;
  static char last_seconds = 0;

  long current_time = millis();
  if(is_ticking)
    elapsed_time += current_time - last_read;

  last_read = current_time;

  if(OrangutanPushbuttons::isPressed(BUTTON_A) && !a_is_pressed)
  {
    // reset
    a_is_pressed = 1;
    is_ticking = 0;
    elapsed_time = 0;
    if(!OrangutanBuzzer::isPlaying()) // only play once
      OrangutanBuzzer::playFromProgramSpace(beep_button_a);
  }

  // find the end of the button press without stopping
  if(!OrangutanPushbuttons::isPressed(BUTTON_A))
    a_is_pressed = 0;

  if(OrangutanPushbuttons::isPressed(BUTTON_C) && !c_is_pressed)
  {
    // start/stop
    c_is_pressed = 1;
    is_ticking = !is_ticking;
    OrangutanBuzzer::playFromProgramSpace(beep_button_c);
  }

  // find the end of the button press without stopping
  if(!OrangutanPushbuttons::isPressed(BUTTON_C))
    c_is_pressed = 0;

  OrangutanLCD::print((elapsed_time/1000/60/10)%10); // tens of minutes
  OrangutanLCD::print((elapsed_time/1000/60)%10); // minutes
  OrangutanLCD::print(':');
  OrangutanLCD::print((elapsed_time/1000)%60/10); // tens of seconds
  char seconds = ((elapsed_time/1000)%60)%10;
  OrangutanLCD::print(((elapsed_time/1000)%60)%10); // seconds
  OrangutanLCD::print('.');
  OrangutanLCD::print((elapsed_time/100)%10); // tenths of seconds
  OrangutanLCD::print((elapsed_time/10)%10); // hundredths of seconds

  // beep every second
  if(seconds != last_seconds && elapsed_time != 0 && !is_playing())
    OrangutanBuzzer::playFromProgramSpace(timer_tick);
  last_seconds = seconds;
}

void print_two_lines_delay_1s(const char *line1, const char *line2)
{
  // Play welcome music and display a message
  OrangutanLCD::clear();
  OrangutanLCD::printFromProgramSpace(line1);
  OrangutanLCD::gotoXY(0,1);
  OrangutanLCD::printFromProgramSpace(line2);
  delay(1000);
}

// waits for a button, plays the appropriate beep, and returns the
// button or buttons that were pressed
char wait_for_button_and_beep()
{
  char button = wait_for_button_press(ANY_BUTTON);
  
  if(button & BUTTON_A)
    OrangutanBuzzer::playFromProgramSpace(beep_button_a);
  else if(button & BUTTON_B)
    OrangutanBuzzer::playFromProgramSpace(beep_button_b);
  else
    OrangutanBuzzer::playFromProgramSpace(beep_button_c);

  wait_for_button_release(button);
  return button;
}

// Initializes the 3pi, displays a welcome message, calibrates, and
// plays the initial music.
void setup()
{
  
  // This must be called at the beginning of 3pi code, to set up the
  // sensors.  We use a value of 2000 for the timeout, which
  // corresponds to 2000*0.4 us = 0.8 ms on our 20 MHz processor.
  
  robot.init(2000);
  load_custom_characters(); // load the custom characters
  
  OrangutanBuzzer::playFromProgramSpace(welcome);
  
  print_two_lines_delay_1s(welcome_line1,welcome_line2);
  print_two_lines_delay_1s(demo_name_line1,demo_name_line2);
  print_two_lines_delay_1s(instructions_line1,instructions_line2);

  OrangutanLCD::clear();
  OrangutanLCD::printFromProgramSpace(instructions_line3);
  OrangutanLCD::gotoXY(0,1);
  OrangutanLCD::printFromProgramSpace(instructions_line4);

  while(!(wait_for_button_and_beep() & BUTTON_B));

  OrangutanBuzzer::playFromProgramSpace(thank_you_music);

  print_two_lines_delay_1s(thank_you_line1,thank_you_line2);
}

void menu_select()
{
  static int menu_index = 0;

  print_two_lines_delay_1s(main_menu_intro_line1,main_menu_intro_line2);

  while(1)
  {
    OrangutanLCD::clear();
    OrangutanLCD::gotoXY(0,1);
    OrangutanLCD::printFromProgramSpace(menu_line2);
    OrangutanLCD::gotoXY(0,0);
    OrangutanLCD::printFromProgramSpace(main_menu_options[menu_index]);
    OrangutanLCD::showCursor(CURSOR_BLINKING);
    // the cursor will be blinking at the end of the option name
  
    // wait for all buttons to be released, then a press
    while(OrangutanPushbuttons::isPressed(ANY_BUTTON));
    char button = wait_for_button_press(ANY_BUTTON);

    if(button & BUTTON_A)
    {
      OrangutanBuzzer::playFromProgramSpace(beep_button_a);
      menu_index --;
    }
    else if(button & BUTTON_B)
    {
       OrangutanLCD::hideCursor();
      OrangutanLCD::clear();

      OrangutanBuzzer::playFromProgramSpace(beep_button_b);
      wait_for_button_release(button);

      while(!OrangutanPushbuttons::isPressed(BUTTON_B))
      {
        OrangutanLCD::gotoXY(0,1);
        OrangutanLCD::printFromProgramSpace(back_line2);
        OrangutanLCD::gotoXY(0,0);
        main_menu_functions[menu_index]();
      }

      OrangutanMotors::setSpeeds(0,0);
      OrangutanBuzzer::stopPlaying();
      m1_speed = 0;
      m2_speed = 0;
      OrangutanLEDs::red(0);
      OrangutanLEDs::green(0);
      OrangutanBuzzer::playFromProgramSpace(beep_button_b);

      return;
    }
    else if(button & BUTTON_C)
    {
     OrangutanBuzzer::playFromProgramSpace(beep_button_c);
      menu_index ++;
    }

    if(menu_index < 0)
      menu_index = main_menu_length-1;
    if(menu_index >= main_menu_length)
      menu_index = 0;
  }
}

// This is the main function, where the code starts.  All C programs
// must have a main() function defined somewhere.
void loop()
{

    menu_select();

}

// Local Variables: **
// mode: C **
// c-basic-offset: 4 **
// tab-width: 4 **
// indent-tabs-mode: t **
// end: **

Hello.

I tested your code on a 3pi - everything seems to be working fine! I’d consider it a successful exercise. Thanks for sharing!

-Jon