3pi Robot --- Problem with pololu_3pi_init function

I just got my 3pi robot recently, and it has been fairly easy to work with and straightforward with C programming. However, I recently encountered a strange reaction.

I used the sample line-following code, but made a few edits to it to make a wider spectrum of turn sharpness so it followed more smoothly. The line-following code worked fine after that. Later, I added a sort of menu before the line-following code was run, which resulted in no malfunctions of the code. However, after I input several songs for the buzzer to run (which could be played by a certain selection in the “menu”), I found that running the default line-following code resulted in a reset of the system.

I there were no errors with compilation, etc. and each other function on my menu worked, including voltage check and music. I recharged my batteries, recompiled the program, and tried again with the same results. By commenting out different segments to debug, I found that the line that made the system reset was:

pololu_3pi_init(2000);

which, of course, had been there the entire time. It is impossible to remove, as it is necessary to set up the reflectance sensors, but I am unable to run the line-following segment of code, as it resets the robot.

Is it possible that, with the robot’s memory filled more, it can result in unexpected behavior? After compilation, the build output said that the robot’s memory was 98.8% full. Could this somehow cause unexpected problems for the pololu_3pi_init(2000) function?

After commenting out one sample of music (which apparently occupies 20% of the robot’s memory!) I found that the line-tracking section works again. Is there any way to prevent this malfunction caused by low remaining memory?

Hello.

I don’t expect filling up the program memory to cause problems, but you could definitely have issues if too much RAM is used. If you post your code, I could take a look and see if there is anything obvious that might cause problem.

- Grant

Here’s the code that caused reset. Later, it worked fine when I deleted the really long line that plays “Canon” (line 341 in the program).

I don’t think it would cause RAM issues, as the music is not playing when you run the line-following segment, but I’m not an expert on these things. Thanks for helping!

/*
 * 3pi-linefollower - demo code for the Pololu 3pi Robot (MODIFIED)
 * 
 * This code will follow a black line on a white background, using a
 * very simple algorithm.  It demonstrates auto-calibration and use of
 * the 3pi IR sensors, motor control, bar graphs using custom
 * characters, and music playback, making it a good starting point for
 * developing your own more competitive line follower.
 *
 * https://www.pololu.com/docs/0J21
 * https://www.pololu.com
 * https://forum.pololu.com
 *
 */
 
// The 3pi include file must be at the beginning of any program that
// uses the Pololu AVR library and 3pi.
#include <pololu/3pi.h>
 
// This include file allows data to be stored in program space.  The
// ATmegaxx8 has 16x more program space than 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";
const char demo_name_line1[] PROGMEM = "  Line ";
const char demo_name_line2[] PROGMEM = "Tracking";
 
// A couple of simple tunes, stored in program space.
const char welcome[] PROGMEM = ">g32>>c32";
const char go[] PROGMEM = "L16 cdegreg4";
 
// 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 function loads custom characters into the LCD.  Up to 8
// characters can be loaded; we use them for 7 levels of a bar graph.
void load_custom_characters()
{
    lcd_load_custom_character(levels+0,0); // no offset, e.g. one bar
    lcd_load_custom_character(levels+1,1); // two bars
    lcd_load_custom_character(levels+2,2); // etc...
    lcd_load_custom_character(levels+3,3);
    lcd_load_custom_character(levels+4,4);
    lcd_load_custom_character(levels+5,5);
    lcd_load_custom_character(levels+6,6);
    clear(); // the LCD must be cleared for the characters to take effect
}
 
// This function displays the sensor readings using a bar graph.
void display_readings(const unsigned int *calibrated_values)
{
    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.
        const char display_characters[10] = {' ',0,0,1,2,3,4,5,6,255};
 
        // The variable c will have values from 0 to 9, since
        // calibrated values are in the range of 0 to 1000, and
        // 1000/101 is 9 with integer math.
        char c = display_characters[calibrated_values[i]/101];
 
        // Display the bar graph character.
        print_character(c);
    }
}
 
// Initializes the 3pi, displays a welcome message, calibrates, and
// plays the initial music.
void initialize()
{
    unsigned int counter; // used as a simple timer
    unsigned int sensors[5]; // an array to hold sensor values
 
    // 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.
    pololu_3pi_init(2000);
    load_custom_characters(); // load the custom characters
     
    // Play welcome music and display a message
    //print_from_program_space(welcome_line1);
    //lcd_goto_xy(0,1);
    //print_from_program_space(welcome_line2);
	//play("!T480 L8 >c>f>g>>crrrr <cgec");
    //delay_ms(1000);
 
 	clear();
 	print_from_program_space(welcome_line1);
 	lcd_goto_xy(0,1);
 	print_from_program_space(welcome_line2);
 	delay_ms(1000);
 
	clear();
    print_from_program_space(demo_name_line1);
    lcd_goto_xy(0,1);
    print_from_program_space(demo_name_line2);
    delay_ms(1000);
 
    // Display battery voltage and wait for button press
    while(!button_is_pressed(BUTTON_B))
    {
        int bat = read_battery_millivolts();
 
        clear();
        print_long(bat);
        print("mV");
        lcd_goto_xy(0,1);
 
		if (bat<4000)
		{
			play("!T480 L8 >c16");
			print("NO JUICE!");
		}
 		else if (bat<4500)
		{
			play("!T480 L8 a16");
			print("Low Battery");
			delay_ms(100);
		}
		else if (bat<4800)
 		{
	 		play("!T480 L8 f16");
			print("Low Battery");
			delay_ms(300);
 		}
		else
		{
			print("Press B");
		}
        delay_ms(100);
    }
 
    // Always wait for the button to be released so that 3pi doesn't
    // start moving until your hand is away from it.
    wait_for_button_release(BUTTON_B);
	play("!T480 L8 >>gr>>g");
    delay_ms(1000);
 
    // Auto-calibration: turn right and left while calibrating the
    // sensors.
    for(counter=0;counter<80;counter++)
    {
        if(counter < 20 || counter >= 60)
            set_motors(40,-40);
        else
            set_motors(-40,40);
 
        // This function records a set of sensor readings and keeps
        // track of the minimum and maximum values encountered.  The
        // IR_EMITTERS_ON argument means that the IR LEDs will be
        // turned on during the reading, which is usually what you
        // want.
        calibrate_line_sensors(IR_EMITTERS_ON);
 
        // Since our counter runs to 80, the total delay will be
        // 80*20 = 1600 ms.
        delay_ms(20);
    }
    
	set_motors(0,0);
 
    // Display calibrated values as a bar graph.
    while(!button_is_pressed(BUTTON_B))
    {
        // Read the sensor values and get the position measurement.
        unsigned int position = read_line(sensors,IR_EMITTERS_ON);
 
        // Display the position measurement, which will go from 0
        // (when the leftmost sensor is over the line) to 4000 (when
        // the rightmost sensor is over the line) on the 3pi, along
        // with a bar graph of the sensor readings.  This allows you
        // to make sure the robot is ready to go.
        clear();
        print_long(position);
        lcd_goto_xy(0,1);
        display_readings(sensors);
 
        delay_ms(100);
    }
    wait_for_button_release(BUTTON_B);
 
    clear();
 
    print("Onward!");       
 
    // Play music and wait for it to finish before we start driving.
	play("!T240 L8 g>eR>e4>d>ca>c");
	while (is_playing());
}
 
// This is the main function, where the code starts.  All C programs
// must have a main() function defined somewhere.
int main()
{
	play("!T480 L8 >c>f>g>>crrrr <cgec");
	print("Welcome!");
	delay_ms(1500);
	clear();
	print("  I am");
	lcd_goto_xy(0,1);
	print(" Melvin");
	delay_ms(1500);
	
	while(1)
	{
		int thing = 0;
		clear();
		print("B: Lines");
		lcd_goto_xy(0,1);
		print("C: Music");
		delay_ms(100);
		
		if (button_is_pressed(BUTTON_A))
		{
			play("!T720 >f>a>>c>f");
			while(is_playing());
		    while(thing == 0)
		    {
			    int bat = read_battery_millivolts();
			    
			    clear();
			    print_long(bat);
			    print("mV");
			    lcd_goto_xy(0,1);
			    
			    if (bat<4000)
			    {
				    play("!T480 L8 >c16");
				    print("NO JUICE!");
			    }
			    else if (bat<4500)
			    {
				    play("!T480 L8 a16");
				    print("Low Battery");
				    delay_ms(100);
			    }
			    else if (bat<4800)
			    {
				    play("!T480 L8 f16");
				    print("Low Battery");
				    delay_ms(300);
			    }
			    else
			    {
				    print("B: Exit");
			    }
			    delay_ms(100);
				
				if (button_is_pressed(BUTTON_B))
				{
					play("!T480 L8 >dr>d");
					wait_for_button_release(BUTTON_B);
					thing = 1;
				}
		    }
		}
		
		if(button_is_pressed(BUTTON_C))
		{
			wait_for_button_release(BUTTON_C);
			play("!T480 L4 ML c2dee2<g2<a1");
			while (is_playing());
			int song = 0;
			int songmenu = 1;
			while (songmenu == 1)
			{	
				clear();
				lcd_goto_xy(0,0);
				
				if (song == 0)
				{
					print("Zelda");
					if (button_is_pressed(BUTTON_B))
					{
						play("!T480 L8 cg>c");
						wait_for_button_release(BUTTON_B);
						delay_ms(500);
						play("!T240 L4 ML <b16d16 MS g2d2.gg8a8b8>c8 ML f16a16>c16 MS >d1r>d>d6>e-6>f6 ML b-16>e-16 MS >g1r6>g6>g6>g6>f6>e-6 ML b-16>d16>f3 MS >e-6>d1>d2 ML e-16g16 MS >c>c8>d8>e-1>d>c ML d16g16 MS b-b-8>c8>d1>cb- ML c+16e16 MS aa8b8>c+1>e2 ML f+16a16 MS >dd8d8dd8d8dd8d8d6e-6f6g1");					
						// First verse: ML <b16d16 MS g2d2.gg8a8b8>c8 ML f16a16>c16 MS >d1r>d>d6>e-6>f6 ML b-16>e-16 MS >g1r6>g6>g6>g6>f6>e-6 ML b-16>d16>f3 MS >e-6>d1>d2 ML e-16g16 MS >c>c8>d8>e-1>d>c ML d16g16 MS b-b-8>c8>d1>cb- ML c+16e16 MS aa8b8>c+1>e2 ML f+16a16 MS >dd8d8dd8d8dd8d8ef+ 
					}
				}
				
				if (song == 1)
				{
					print("Ocelot");
					if (button_is_pressed(BUTTON_B))
					{
						play("!T480 L8 cg>c");
						wait_for_button_release(BUTTON_B);
						delay_ms(500);
						play("!T240 L8 MS g >ec>e4a32>d16.>ca>c<f>ca>c>d<g>c4 >ec>e4a32>d16.>ca>c<g>cgece<g<<g >ec>e4a32>d16.>ca>c<f>ca>c>d<g>c4 c4>e4<a16>g16>e>d>c<g>cgec<ccr");
					}
				}

				if (song == 2)
				{
					print("Tetris");
					if (button_is_pressed(BUTTON_B))
					{
						play("!T480 L8 cg>c");
						wait_for_button_release(BUTTON_B);
						delay_ms(500);
						play("!T240 L8 MS >e<eb>c>d>e16>d16>cba<aa>c>e<a>d>cb<g+b>c>d<e>e<e>c<aarae<bc d>d<d>f>ad16d16>g>f>e<cr>c>e<g>d>cb<g+b>c>d<e>e<e>c<aa<aa");
					}
				}
				
				if (song == 3)
				{
					print("Canon");
					if (button_is_pressed(BUTTON_B))
					{
						play("!T480 L8 cg>c");
						wait_for_button_release(BUTTON_B);
						delay_ms(500);
						play("!T80 L8 ML >ddf+a>c+<ac+eb<bdf+a<f+<ac+ g<g<bdf+<d<f+<ag<g<bda<ac+e >f+df+a>e<ac+e>d<bdf+>c+<f+<ac+ b<g<bda<d<f+<ab<g<bd>c+<age f+df+da<a>c+<a>d<b>f+<b>a<f+a<f+ b<gg<ga<da<dg<g>d<g>c+<ag>c+   >d16d16>c+>ddc+16<a16aef+d16<b16>d>c+b>c+16<f+16>f+>a>b   >g16<g16>f+>e>g>f+16d16>e>d>c+b16<g16agf+e16<a16gf+e >a>f+16>g16>a>f+16>g16>a16a16b16>c+16>d16>e16>f+16>g16>f+>d16>e16>f+f+16g16a16b16a16g16a16f+16g16a16 gb16a16gf+16e16f+16e16d16e16f+16g16a16b16gb16a16b>c+16>d16a16b16>c+16>d16>e16>f+16>g16>a16 >f+f+gf+e>e>f+>e>df+dba<a<g<a <bbc+ba<a<g<a<bbab>c+c+bc+ >d4d4a4<a4b4<b4f+4<f+4g4<g4d4<d4g4<g4a4<a.f+32a32>d2");
						// Third verse: >f+df+a>e<age>d<bdf+>c+<f+ec+ b<g<bda<da<ab<gbd>c+<age
					}
				}
				
				if (song == 4)
				{
					print("Bells");
					if (button_is_pressed(BUTTON_B))
					{
						play("!T480 L8 cg>c");
						wait_for_button_release(BUTTON_B);
						delay_ms(500);
						play("!T180 L8 b-<gab-g<gb-<fab-g<fb-<e-ab-g<e-b-<dab-g<d b-<gab-g<gb-<fab-g<fb-<e-ab-g<e-b-<dab-g<d  >d<g>c>db-<g>d<f>c>db-<f>d<e->c>db-<e->d<d>c>db-<d >d<g>c>db-<g>d<f>c>db-<f>d<e->c>db-<e->d<d>c<db-<d  >g<g>g16r16>g>f>e->d<f>d16r16>d>cb->c<e->c16r16>c>d>cb-<dg16r16g16r16g<d  def+gab->c>d>c<db-<d def+gab->c>d>c<db-<d >b-r>a>b->g");
					}
				}
				
				lcd_goto_xy(0,1);
				print("<A ^B C>");
				delay_ms(100);
												
				if (song == 5)
				{
					song = 0;
				}
				
				if (song == -1)
				{
					song = 4;
				}
										
				if (button_is_pressed(BUTTON_C))
				{
					play("!T720 L8 >dr>d");
					wait_for_button_release(BUTTON_C);
					song++;
				}
				if (button_is_pressed(BUTTON_A))
				{
					play("!T720 L8 drd");
					wait_for_button_release(BUTTON_A);
					song = song-1;
				}
			}
		}
	
	while(button_is_pressed(BUTTON_B))
	{
		play("!T480 L8 ML >c>e>g>>c>>d>>e4");
		wait_for_button_release(BUTTON_B);
		clear();
		
	    unsigned int sensors[5]; // an array to hold sensor values
	 
	    // set up the 3pi
	    initialize();
		
		int dop = 0;
		while(dop<20)
		{
			set_motors(5*dop, 5*dop);
			dop++;
			delay_ms(20);
		}
	 
	    while(1)
	    {
	        unsigned int position = read_line(sensors,IR_EMITTERS_ON);
	 
	        if(position < 200)
	        {
	            set_motors(0,100);
	        }
			
			else if(position < 300)
			{
				set_motors(5,100);
			}
			
			else if(position < 400)
			{
				set_motors(10,100);
			}
					
	        else if(position < 800)
	        {
	            set_motors(20,100);
	        }
			
			else if(position < 1200)
			{
				set_motors(40,100);
			}
			
			
			else if(position < 1600)
			{
				set_motors(60,100);
			}
			
			else if(position < 1800)
			{
				set_motors(80,100);
			}
			
			else if(position < 2000)
			{
				set_motors(90,100);
			}
			
			else if(position < 2300)
			{
				set_motors(95,100);
			}
			
			else if(position < 2700)
			{
				set_motors(100,100);
			}
	
			else if(position < 3000)
			{
				set_motors(100,95);
			}
			
			else if(position < 3400)
			{
				set_motors(100,90);
			}
			
			else if(position < 3600)
			{
				set_motors(100,80);
			}
			
			else if(position < 3800)
			{
				set_motors(100,60);
			}
			
			else if(position < 4200)
			{
				set_motors(100,30);
			}
			
			else if(position < 4600)
			{
				set_motors(100,20);
			}
			
			else if(position < 4700)
			{
				set_motors(100,10);
			}
			
			else if(position < 4800)
			{
				set_motors(100,5);
			}
			
			else if(position < 5000)
			{
				set_motors(100,0);
			}
			
	    }
	}
	}
}

If commenting out that line fixes the problem, then the issue is most likely in that line of code. Do the other songs have the same issue?

By the way, when posting code, please use the code tags to display your code nicely in your post. I have edited your post to include them.

- Grant

I don’t think it is any of the songs in particular, as it works fine by removing “Bells” as well. The line-following, music, and voltage check segments all function normally, just not when all 5 music tracks are compiled and loaded onto the robot.

Hello.

Your Canon song is 604 characters long, which means it will take 605 bytes of RAM. The ATmega328P on the 3pi only has 2048 bytes of RAM, so you are taking up a significant chunk of it with just that one song. With all the other songs and libraries you are using, you are probably running out of RAM. I would recommend putting all your song strings in program space instead of RAM; this will save a lot of RAM and will not take up a lot more program space because ultimately even a RAM string has to have its initial value stored in program space.

You can move your songs to program space pretty easily using the PSTR macro from avr-libc. Just change the lines that play the song to something like this:

play_from_program_space(PSTR("abc"));

Also, it looks like you wrote some fun tunes for your 3pi! We have a 3pi Tune Library thread where you can share them and see what others have written.

–David

Okay, thank you for the reply. I’ll be sure to check out the tune library.