NerdKits - electronics education for a digital generation

You are not logged in. [log in]

NEW: Learning electronics? Ask your questions on the new Electronics Questions & Answers site hosted by CircuitLab.

Sensors, Actuators, and Robotics » robot with 2 Continuous rotation servos help

March 14, 2012
by lukel
lukel's Avatar

Hi, my name is Luke, and I need help on making a robot. I have 2 Parallax (futaba) continuous servos.

I am trying to get them to work. I want to make a rover that avoids bumping into stuff.

I am also trying to use the Parallax Ping ultrasonic sensor to avoid the stuff.

March 14, 2012
by lukel
lukel's Avatar

Here is my code so far

#define F_CPU 14745600

#include <stdio.h>

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <inttypes.h>

#include "../libnerdkits/delay.h"
#include "../libnerdkits/lcd.h"
#include "../libnerdkits/uart.h"

#define turn_on PORTB |=
#define turn_off PORTB &= ~
#define led (1<<PB3);
#define define_output DDRB |=
#define define_input DDRB &= ~
#define M1 (1<<PB2);
#define M2 (1<<PB1);
#define button (1<<PB4);

void pwm_sety(uint16_t y) {
  OCR1B = y;

void pwm_setx(uint16_t x) {
  OCR1B = x;
}

#define PWM_MIN 1300
#define PWM_MAX 4200
#define PWM_START 2750
void pwm_init() {
  // setup Timer1 for Fast PWM mode, 16-bit
  // COM1B1 -- for non-inverting output
  // WGM13, WGM12, WGM11, WGM10 -- for Fast PWM with OCR1A as TOP value
  // CS10 -- for no clock prescaling    
  ICR1 = 36864;// set for 28.8Khz cycle
  pwm_set(PWM_START);
  TCCR1A = (1<<COM1A1) | (1<<WGM11);
  TCCR1B = (1<<WGM13) | (1<<WGM12) | (1<<CS10);  
}

define_output led
define_output M1 
define_output M2
define_input button

int main() {
  // init LCD
  lcd_init();
  FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);
  lcd_write_string(PSTR(" Motor Control "));

  // init serial port
  uart_init();
  FILE uart_stream = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW);
  stdin = stdout = &uart_stream;

  uint16_t pos1 = PWM_START; 
  uint16_t pos2 = PWM_START;

  while(1) { 
  pwm_setx(pos1); 
  pwm_sety(pos2);

  if(pos1 > PWM_MAX) pos1 = PWM_MAX;
  if(pos1 < PWM_MIN) pos1 = PWM_MIN;
  if(pos2 > PWM_MAX) pos2 = PWM_MAX;
  if(pos2 < PWM_MIN) pos2 = PWM_MIN;
March 14, 2012
by pcbolt
pcbolt's Avatar

Hi lukel -

A few things jump out. You have two "pwm_set()" functions that do exactly the same thing. I guess you plan on using a different port in the future for x and y directions (BTW the pwm_setx() function needs a closing brace). Inside the pwm_init() you use pwm_set() but only the x/y versions are defined (I guess you could use either one or both if you are using two axis). Last thing is inside main() you don't call pwm_init(). I'm guessing some of the end code got chopped off of your post so I'm sure you put in the closing brace of the while (1) loop and a "return 0" in there.

March 14, 2012
by lukel
lukel's Avatar

Thanks pcbolt, I read something about using 2 servos with 1 Atmega168. It said to do this.

March 14, 2012
by pcbolt
pcbolt's Avatar

Yep. That sounds right. Keep posting on your project...sounds really interesting.

March 14, 2012
by lukel
lukel's Avatar

I am now stuck on using the ultrasonic sensor. I need to send a pulse to it 5 us long to send an ultrasonic chirp. Then time it from the pulse to when the chirp comes back.

March 14, 2012
by pcbolt
pcbolt's Avatar

Luke -

I'm sure the guys that post on here will have better suggestions than I will but the way I see it you'd want to have as good a timing resolution as possible. Not so much for the outgoing pulse but for timing the return. Since the oscillator operates at 14,745,600 Hz, the finest resolution would be about 68ns. Your outgoing pulse of 5us would then take about 74 clock "ticks" or cycles. I'd set up a "send_pulse()" function to initialize the MCU timer to trigger an interrupt when it reaches 74, set the pulse pin HI. Then just let the interrupt routine turn the pulse pin LO, turn off the interrupt, reset the timer to 0 and set a new "overflow" interrupt that just counts the number of times the tick count went past 255. Then you'd need a separate "pin change" interrupt to measure when the chirp came back, store the "overflow count", get the current timer value, combine them and you'd have your answer. I wouldn't mind getting into finer detail, but I'd hate to deprive you of the value of discovery.

March 15, 2012
by lukel
lukel's Avatar

Does this look right? I just don't know how to reset the timer.

#define F_CPU 14745600

#include <stdio.h>
#include <math.h>

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <inttypes.h>

#include "../libnerdkits/delay.h"
#include "../libnerdkits/lcd.h"
#include "../libnerdkits/uart.h"

void send_pulse() {

  TCCR0A |= (1<<WGM01);
  // clocked from CLK/1024
  // which is 14745600/1024, or 14400 increments per second
  TCCR0B |= (1<<CS02) | (1<<CS00);
  // set TOP to 143
  // because it counts 0, 1, 2, ... 142, 143, 0, 1, 2 ...
  // so 0 through 143 equals 144 events
  OCR0A = 74;
  // enable interrupt on compare event
  // (14400 / 144 = 100 per second)
  TIMSK0 |= (1<<OCIE0A);
}

SIGNAL(SIG_OUTPUT_COMPARE0A) {
  // when Timer0 gets to its Output Compare value,
  // one one-hundredth of a second has elapsed (0.01 seconds).
  }

  void time_pulse() {

  TCCR0A |= (1<<WGM01);
  // clocked from CLK/1024
  // which is 14745600/1024, or 14400 increments per second
  TCCR0B |= (1<<CS02) | (1<<CS00);
  // set TOP to 143
  // because it counts 0, 1, 2, ... 142, 143, 0, 1, 2 ...
  // so 0 through 143 equals 144 events
  OCR0A = 1;
  // enable interrupt on compare event
  // (14400 / 144 = 100 per second)
  TIMSK0 |= (1<<OCIE0A);
} 
  volatile int32_t the_time;

SIGNAL(SIG_OUTPUT_COMPARE0A) {
  // when Timer0 gets to its Output Compare value,
  // one one-hundredth of a second has elapsed (0.01 seconds).
  the_time++;
  }
March 15, 2012
by lukel
lukel's Avatar

Speaking of servos, can you change the PWM MIN and PWM MAX to 0. Would it make the ranges infinite?

March 15, 2012
by pcbolt
pcbolt's Avatar

Luke -

I may have actually overcomplicated things a bit. I think it would be better just to keep the interrupt the way it is (triggering at 74 ticks) that way every interrupt would be done at 5 usec. You really don't need any better timing than that since the speed of sound in air is about 1000 ft/sec. 5 usec means 0.005' therefore not a problem. I would try something like this then:

volatile uint8_t pulse_flag, timing_flag;
volatile uint32_t pulse_count, pulse_time;

timer_init(){
// Set up interrupt
TCCR0A |= (1<<WGM01);  // sets compare and reset to "top" mode
TCCR0B |= (1<<CS00);   // set clock divider to 1
OCROA = 74;            // set "top" for 5 usec
TIMSK0 |= (1<<OCIE0A); // turn interrupt on

pulse_flag = 0;
timing_flag = 0;
}

send_pulse(){

PORTC |= (1<<PC4);     // start pulse
pulse_flag = 1;

}

SIGNAL(SIG_OUTPUT_COMPARE0A) {

if (pulse_flag == 1){
PORTC &= ~(1<<PC4);    // stop pulse
pulse_flag = 0;
pulse_count = 0;
}
else{
pulse_count++;
timing_flag = 1;
}

ISR(PIN_CHANGE_INTERRUPT) {
if (timing_flag == 1){
pulse_time = pulse_count;
timing_flag = 0;
}
}

Please note, I don't have my guides with me right now so I don't remember the actual name of the pin change interrupt or how to set it up. However, the NK keyboard tutorial and source code has all that in it.

March 15, 2012
by pcbolt
pcbolt's Avatar

Luke -

Big oops here. Inside the "send_pulse()" function place this at the end (at line 19):

TCNT0 = 0;

This will reset the timer to 0.

March 15, 2012
by lukel
lukel's Avatar

Thank you so much. So this gets the time. Would you know how to convert it into inches. I think this is the formula: time ** 178. How would you do that.

March 15, 2012
by pcbolt
pcbolt's Avatar

Speed of sound is 1126 ft/sec in air so that would be about 94 in/sec. The time is actually round trip time so you'd divide the time by 2 then multiply times 94, or just take the time measured and multiply times 47. Since we're measuring the number of times 5 micro seconds elapse, the final formula would be:

double pulse_distance;
pulse_distance = (double)pulse_time * 0.000005 * 47.0;

(You may have to include math.h to do the floating point arithmetic).

If you just want to set a distance limit on when to stop the robot, you could just pre-figure the pulse time and not have to worry about the math.

March 15, 2012
by lukel
lukel's Avatar

Thanks. I tried to upload the code and it had a list of errors. 0

us.c: In function 'main': us.c:18: error: 'PC4' undeclared (first use in this function) us.c:18: error: (Each undeclared identifier is reported only once us.c:18: error: for each function it appears in.) us.c:37: warning: implicit declaration of function 'timer_init' us.c:37: error: expected ';' before '{' token us.c: In function 'SIG_OUTPUT_COMPARE0A': us.c:68: warning: 'PIN_CHANGE_INTERRUPT' appears to be a misspelled signal handl er us.c:68: error: static declaration of 'PIN_CHANGE_INTERRUPT' follows non-static declaration us.c:68: error: previous declaration of 'PIN_CHANGE_INTERRUPT' was here

March 15, 2012
by lukel
lukel's Avatar

Here is my code.

#define F_CPU 14745600

#include <stdio.h>
#include <math.h>

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <inttypes.h>

#include "../libnerdkits/io_328p"
#include "../libnerdkits/delay.h"
#include "../libnerdkits/lcd.h"
#include "../libnerdkits/uart.h"

  int main() {

  DDRC |= (1<<PC4);

  // init lcd
  lcd_init();
  FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);
  lcd_home();

  // init serial port
  uart_init();
  FILE uart_stream = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW);
  stdin = stdout = &uart_stream;

  sei();

  volatile uint8_t pulse_flag, timing_flag;
volatile uint32_t pulse_count, pulse_time;

while(1) {

timer_init(){
// Set up interrupt
TCCR0A |= (1<<WGM01);  // sets compare and reset to "top" mode
TCCR0B |= (1<<CS00);   // set clock divider to 1
OCROA = 74;            // set "top" for 5 usec
TIMSK0 |= (1<<OCIE0A); // turn interrupt on

pulse_flag = 0;
timing_flag = 0;
}

send_pulse(){

PORTC |= (1<<PC4);     // start pulse
pulse_flag = 1;
TCNT0 = 0;

}

SIGNAL(SIG_OUTPUT_COMPARE0A) {

if (pulse_flag == 1){
PORTC &= ~(1<<PC4);    // stop pulse
pulse_flag = 0;
pulse_count = 0;
}
else{
pulse_count++;
timing_flag = 1;
}

ISR(PIN_CHANGE_INTERRUPT) {
if (timing_flag == 1){
pulse_time = pulse_count;
timing_flag = 0;
}
}

    double pulse_distance;
    pulse_distance = (double)pulse_time * 0.000005 * 47.0;

lcd_home();
    fprintf_P(&lcd_stream, PSTR("%16.2f inches"), pulse_time);  
}
}
}
March 15, 2012
by pcbolt
pcbolt's Avatar

I found the actual name of the pin change interrupt, so on line 68 above, change

ISR(PIN_CHANGE_INTERRUPT){

to

ISR(PCINT1_vect){

Next you have to place all the function blocks above "int main(){". Also the global variables (the ones with "volatile" in front) should go above "main()" as well. Inside the "main()" program block, keep lines 18 thru 28 the way they are. Add timer_init(); and move "double pulse_distance;" to the next line, then the line after that will be sei(); It should look like this:

int main() {

 DDRC |= (1<<PC4);

 // init lcd
 lcd_init();
 FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);
 lcd_home();

 // init serial port
 uart_init();
 FILE uart_stream = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW);
 stdin = stdout = &uart_stream;

 timer_init();
 pin_init();
 double pulse_distance;
 sei();

Now you can start your infinite "while(1)" loop. All you need here is:

while(1){
    start_pulse();
    delay_ms(500);
    pulse_distance = (double)pulse_time * 0.000005 * 47.0;
    lcd_home();
    fprintf_P(&lcd_stream, PSTR("%10.2f inches"), pulse_distance);
}

You'll notice I added a call to a new function named "pin_init()". This will setup the interrupt for pin changes and it should look like:

void pin_init(){

  //Enable PIN Change Interrupt 1 - This enables interrupts on pins
  //PCINT14...8 see p70 of datasheet
  PCICR |= (1<<PCIE1);

  //Set the mask on Pin change interrupt 1 so that only PCINT12 (PC4) triggers
  //the interrupt. see p71 of datasheet
  PCMSK1 |= (1<<PCINT12);
  return;
}

That reminds me, you'll have to add "void" in front of the "start_pulse(){" and "timer_init(){". Then add "return" at the end of each of these functions. It also looks like you need a closing brace for the SIGNAL function.

I think we'd need to change the port direction of PC4 after sending the pulse...but first things first. See if the code changes will compile. Fingers crossed of course.

March 16, 2012
by lukel
lukel's Avatar

Is this how you do it?

#define F_CPU 14745600

#include <stdio.h>
#include <math.h>

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <inttypes.h>

#include "../libnerdkits/io_328p.h"
#include "../libnerdkits/delay.h"
#include "../libnerdkits/lcd.h"
#include "../libnerdkits/uart.h"

void timer_init(){
// Set up interrupt
TCCR0A |= (1<<WGM01);  // sets compare and reset to "top" mode
TCCR0B |= (1<<CS00);   // set clock divider to 1
OCROA = 74;            // set "top" for 5 usec
TIMSK0 |= (1<<OCIE0A); // turn interrupt on

pulse_flag = 0;
timing_flag = 0;
}

void send_pulse(){

PORTC |= (1<<PC4);     // start pulse
pulse_flag = 1;
TCNT0 = 0;

}

SIGNAL(SIG_OUTPUT_COMPARE0A) {

if (pulse_flag == 1){
PORTC &= ~(1<<PC4);    // stop pulse
pulse_flag = 0;
pulse_count = 0;
}
else{
pulse_count++;
timing_flag = 1;
}

ISR(PCINT1_vect){
if (timing_flag == 1){
pulse_time = pulse_count;
timing_flag = 0;
}
}

void pin_init(){

  //Enable PIN Change Interrupt 1 - This enables interrupts on pins
  //PCINT14...8 see p70 of datasheet
  PCICR |= (1<<PCIE1);

  //Set the mask on Pin change interrupt 1 so that only PCINT12 (PC4) triggers
  //the interrupt. see p71 of datasheet
  PCMSK1 |= (1<<PCINT12);
  return;
}

int main() {

 DDRC |= (1<<PC4);

 // init lcd
 lcd_init();
 FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);
 lcd_home();

 // init serial port
 uart_init();
 FILE uart_stream = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW);
 stdin = stdout = &uart_stream;

 timer_init();
 pin_init();
 double pulse_distance;
 sei();

 while(1){
    start_pulse();
    delay_ms(500);
    pulse_distance = (double)pulse_time * 0.000005 * 47.0;
    lcd_home();
    fprintf_P(&lcd_stream, PSTR("%10.2f inches"), pulse_distance);
}
}
March 16, 2012
by lukel
lukel's Avatar

It didn't work. here is the list of errors.

us.c: In function 'timer_init': us.c:20: error: 'OCROA' undeclared (first use in this function) us.c:20: error: (Each undeclared identifier is reported only once us.c:20: error: for each function it appears in.) us.c:23: error: 'pulse_flag' undeclared (first use in this function) us.c:24: error: 'timing_flag' undeclared (first use in this function) us.c: In function 'send_pulse': us.c:30: error: 'pulse_flag' undeclared (first use in this function) us.c: At top level: us.c:35: warning: 'SIG_OUTPUT_COMPARE0A' appears to be a misspelled signal handl er us.c: In function 'SIG_OUTPUT_COMPARE0A': us.c:37: error: 'pulse_flag' undeclared (first use in this function) us.c:40: error: 'pulse_count' undeclared (first use in this function) us.c:44: error: 'timing_flag' undeclared (first use in this function) us.c:47: error: static declaration of 'vector_4' follows non-static declaratio n us.c:47: error: previous declaration of 'vector_4' was here us.c: In function '__vector_4': us.c:49: error: 'pulse_time' undeclared (first use in this function) us.c: In function 'SIG_OUTPUT_COMPARE0A': us.c:66: warning: 'main' is normally a non-static function us.c: In function 'main': us.c:86: warning: implicit declaration of function 'start_pulse' us.c:88: error: 'pulse_time' undeclared (first use in this function) us.c: In function 'SIG_OUTPUT_COMPARE0A': us.c:92: error: expected declaration or statement at end of input

March 16, 2012
by pcbolt
pcbolt's Avatar

Ok.

First you'll need to add:

volatile uint8_t pulse_flag, timing_flag;
volatile uint32_t pulse_count, pulse_time;

After line 14.

Then, the "SIG_OUTPUT_COMPARE0A" function needs a closing brace. After that you need to put "return;" on line 32 and after line 14. And anywhere there is OCROA replace with OCR0A (the second "O" is actually the number "0").

March 17, 2012
by lukel
lukel's Avatar
It still didn't work. It says SIG_OUTPUT_COMPARE0A function is spelled wrong. Then there is an issue with "vector 4".

us.c:37: warning: 'SIG_OUTPUT_COMPARE0A' appears to be a misspelled signal handl er us.c: In function 'SIG_OUTPUT_COMPARE0A': us.c:48: error: static declaration of 'vector_4' follows non-static declaratio n us.c:48: error: previous declaration of 'vector_4' was here us.c: In function 'main': us.c:83: warning: implicit declaration of function 'pin_init' us.c:89: warning: implicit declaration of function 'start_pulse'

March 17, 2012
by pcbolt
pcbolt's Avatar

Luke -

You could try replacing:

SIGNAL(SIG_OUTPUT_COMPARE0A){

With:

ISR(TIMER0_COMPA_vect) {

Did you add a closing brace at the end of that function (at line 46)? The closing brace that is there now closes the "else" block not the function (or ISR) block.

March 18, 2012
by lukel
lukel's Avatar

It compiled and worked!:) For some reason it delayed 5 seconds to read.

March 31, 2012
by Ralphxyz
Ralphxyz's Avatar

lukel, what does your final code look like and you should add this to the Nerdkit community Library Project.

Ralph

March 31, 2012
by lukel
lukel's Avatar

How do you post something on the library?

March 31, 2012
by Ralphxyz
Ralphxyz's Avatar

It is basically the same as posting here in the forum, it even uses the stupid/limited Markdown language syntax.

You first need to signin.

If you start on the first (ugly) page and scroll down to the bottom and select edit in the lower right corner you will view the text/code behind the page. Then follow the links to the Projects section and look at the way other Projects are written up and just duplicate them with your code. Of course do not save any of the pages you view, well even if you did save them and didn't make any changes you would not break anything just change the attribute.

It might be easier to work in an editor on your PC rather than working in the actual page on the web.

So you can just do a copy paste.

If you have questions or problems let me know at rhulslander gmail.com

Ralph

March 31, 2012
by lukel
lukel's Avatar

How do you make a code look like a code?

April 01, 2012
by Ralphxyz
Ralphxyz's Avatar

Darn Humberto sent out instruction on specifically doing that, which I no longer have.

You can always indent your code preceded by four spaces that is what the "Indent Selection as Code Block" button does here in the forum.

The Library uses the exact same syntax, both use the Markdown engine.

Also you can look at other pages to see how they did it.

Ahhh followed my own advice:

Before you can use the LCD you must first initialize it.

~~~~~~~~~~.c_avr
#include "../libnerdkits/lcd.h"

int main()
{
    lcd_init();
    ...
}
~~~~~~~~~~

This came from bretm's (whom I really miss) Nerdkit LCD Library page in the Code Library section!

So you put 10 tildes (~) then . language name in this case c_avr you could also do ~~.python for python code I think there are more hopefully someone who actually knows what they are talking about will jump in. And then you close the block with ten tildes ~~. These are not indented!

Again it might be easier to do this in a editor rather than on the web page, using copy paste.

Ralph

Post a Reply

Please log in to post a reply.

Did you know that LEDs (light emitting diodes) only conduct current in one direction, like normal diodes? Learn more...