Need design advice for servo control

Go To Last Post
14 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi everyone :)

I am trying to control 9 servos. I have read up on Binary Code Modulation (or Bit Angle Modulation), and I think this would be the wise path to take in case of controlling this many things. I also found out that a signal needs to be constantly sent or the servo goes limp (is this true?).

My microcontroller is ATMEGA1248P, so it doesn't have enough PWM ports. I was thinking of implementing it with the method above, but that will require quite a few interrupts, right? I have no experience using interrupts (I have avoided them until now), but I think I could try to figure it out.

The microcontroller also does receiving of data from a serial port, processing it, and updating servo duty cycles. And maybe updating an LCD, but we'll see about that one. With a single servo cycle of about 20ms (50Hz), and a duty cycle of maximum 2.5ms, that would leave about 17.5ms of work time for the processor. Could this be a problem with UART pins? I can't imagine so since I just write output value to a register, but you never know...

Of course there is also an option for me to get a servo controller IC and just let it worry about all of the overhead. What would you recommend? I would prefer to save board room and have fewer wires, as well as learn how to use interrupts finally. But if this would improve performance of my system, I'd do it.

Thanks for the read!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Since you mention 20ms I guess you¨re talking about RC servos.
Lots of prior threads how to handle many servos with software.
Use the "Search" function at top of page.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

It's definitely time to get comfortable with interrupts. Use "volatile" on shared variables, and make your interrupts as short and fast as possible (set flags to do further work in your main loop).

You can control 9 servos on 9 separate GPIO output pins via software PWM. Essentially, using a timer compare interrupt, you'll

a) turn on servo(x) (GPIO(x) HI)
b) set next compare val to servo(x) time (1-2ms).
c) on compare interrupt, turn off servo(x) and
set next compare val to 18ms
d) advance x to next servo, wrapping back to 0 if
needed
e) on next compare interrupt, goto (a)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Alright, so I wrote the code with 2 interrupts. However, since the outputs didn't work (they're just zero), I started debugging with an oscilloscope. I discovered that my interrupts never trigger.

I included #define sei() at the top of my code (didn't know this was needed). Is that all? I'm quite confused as to whether I need to include interrupt function definitions somewhere? Right now I just have ISR(blahblah) as a function. I looked at this: http://www.nongnu.org/avr-libc/u... , but got confused :(

I will keep reading and maybe I'll figure something out. But if in the meanwhile someone could give me a hint, that'd be great.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You could have a look at the "Tutorial" forum on this site.
Excellent Timer tutorial by Dean to be found there.
And yes you need to initialize/set up your Timer(s) to make ISR work as expected.
The Datasheet for your model have descriptions of registers that belong to each Timer.
But start with the tutorial...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

I included #define sei() at the top of my code (didn't know this was needed)

Which C compiler are you talking about. If this is avr-gcc the very last thing you want to do is:

#define sei()

If you want to enable interrupts then simply use:

#include 

int main(void) {
  sei();
...
}

Perhaps you should post the code you have so that we can run an eye over it?

BTW I posted the very simplest example of "soft PWM" to this tutorial thread:

https://www.avrfreaks.net/index.p...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Alright, I put sei() at the beginning of my main. Here is my code for main() and the procedure that initializes (should do this) counters:

int main (void){

	// Establish an array of 9 servos, 8 bits each
	//ouble given_angles[9];
		sei();	
		double req_delay;
	DDRA = (1<<0);
	double given_angles[9] = {1,2,4,8,16,32,64,128,128};

	// Calculation for number of cycles
	for (int i=0; i<9; i++){
		req_delay = 1000 + 1000*(given_angles[i]/180);
		servo_count[i]=(req_delay/0.05) - 1;	
	}
	int temp = initialize_Servos();
	
	while(1){
	
	}

return 1;
}
int initialize_Servos()
{	

	SERVO_PINS_L1 = 0xFF;
	SERVO_PINS_L2 = (1 << PIN_S_NINE);	
	current_line = 0;

	// start timer 1 with no prescaler in CTC mode
	TCCR1A = 0x00;
    TCCR1B |= (1 << CS10)|(1 << WGM12);
	
    OCR1A = servo_count[0];
	OCR1B = 39999;
      
    // Enable interrupts: Comparator A and B
    TIMSK1 = (1 << OCIE1A)|(1 << OCIE1B)|(1 << ICIE1);

 	SERVO_L1 = (1 << current_line);
		
	// Start counter
	TCNT1=0;

	current_line++;

	return 1;
} 

I started playing around with the simulator, but now it hangs up on a seemingly innocent line:

SERVO_L1 = (1 << current_line);

I have a feeling I'm doing something illegal. Previously (until it started hanging on the above line), it would reset to beginning of main. Which of course leads to everything reinitializing.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

I have a feeling I'm doing something illegal.

Well this line:

    // Enable interrupts: Comparator A and B
    TIMSK1 = (1 << OCIE1A)|(1 << OCIE1B)|(1 << ICIE1);

enables three interrupts and you already did sei(), so do you have three ISR()s to catch these events?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Mm nope, I only have 2. I didn't originally have ICIE1 in there, but after reading the datasheet this morning I felt convinced I should include it. So sei() does the exact same job?

Edit: Alright, while it no longer freezes on the output to pin line, it now goes to interrupt routine immediately after.
I was watching values in the debugger and TCNT1 is 18 (OCR1A is about 20000), when the program goes to interrupt for Comp A. I thought these 2 were being compared? I'm going to go re-read that tutorial; I must be missing something vital.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

So sei() does the exact same job?

Did you realise that sei() is nothing more than:

# define sei()  __asm__ __volatile__ ("sei" ::)

In other words when you put sei() in your source the compiler does nothing more than output ONE opcode which is SEI. All that does it set bit 7 or SREG, nothing more, nothing less.

The rest of the "magic" involved in interrupts on the AVR comes from various control registers with interrupt enable bits (often with IE in the name). When any of those are set it says "when the condition asscoiated with this interrupt occurs set the interrupt flag bit for this particular interrupt". Further there is a feature of the AVR core that on every opcode fetch it checks the states of all the IF bits and if one or more are set it disables interrupts then sets the program counter to the vector table entry address of the lowest numbered (in vector table order) of all the set IF bits which arranges for a jump to that interrupts vector handler to occur.

It is your job to ensure that the vector table for that interrupt that might occur has been filled in with the address of a C function in your code. You do this using ISR() and each numbered vector entry has a name such as INT0_vect or ADC_vect or whatever to help you do this.

Of course for a call to the interrupt vector to occur (because the IF flag is set) is also reliant on that peripheral being enabled and reaching the condition that would cause that IF bit to be set so, in the example of a timer overflow it also requires that the timer is running (one or more CS bits are set) and that the count has actually reached TOP. Other interrupts have different conditions for why their IF bit might be set.

If you enable an interrupt, its condition occurs and there is not ISR() in the code to capture it then you will hit the "catch-all" described on this page of the manual:

http://www.nongnu.org/avr-libc/u...

In effect the AVR will "reset" as it does a JMP 0 when the interrupt condition occurs and there is no ISR().

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
 // start timer 1 with no prescaler in CTC mode
   TCCR1A = 0x00;
    TCCR1B |= (1 << CS10)|(1 << WGM12);
   
    OCR1A = servo_count[0];
   OCR1B = 39999;
 

In CTC mode the timer is cleared at OCR1A value.
It never reaches OCR1B.
Use Fast Pwm mode (14).

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Visovian wrote:

 // start timer 1 with no prescaler in CTC mode
   TCCR1A = 0x00;
    TCCR1B |= (1 << CS10)|(1 << WGM12);
   
    OCR1A = servo_count[0];
   OCR1B = 39999;
 

In CTC mode the timer is cleared at OCR1A value.
It never reaches OCR1B.
Use Fast Pwm mode (14).

I will fix the clearing problem once I fix what is happening now (going to ISR when the counter is nowhere near the value). I don't think I can use PWM since I wouldn't have enough pins.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks for such a lengthy response!

clawson wrote:
Quote:

So sei() does the exact same job?

Did you realise that sei() is nothing more than:

# define sei()  __asm__ __volatile__ ("sei" ::)

In other words when you put sei() in your source the compiler does nothing more than output ONE opcode which is SEI. All that does it set bit 7 or SREG, nothing more, nothing less.

That's pretty neat (but no, I had no idea how it worked - I knew it set the interrupt bit high in SREG somehow). I don't have much experience with AVR Assembly (although I should probably try it out).

Quote:
It is your job to ensure that the vector table for that interrupt that might occur has been filled in with the address of a C function in your code. You do this using ISR() and each numbered vector entry has a name such as INT0_vect or ADC_vect or whatever to help you do this.

... If you enable an interrupt, its condition occurs and there is not ISR() in the code to capture it then you will hit the "catch-all" described on this page of the manual:

http://www.nongnu.org/avr-libc/u...

In effect the AVR will "reset" as it does a JMP 0 when the interrupt condition occurs and there is no ISR().

So that explains why my code was jumping to beginning of main when I had those 3 enabled interrupts, but only 2 ISRs. Good to know! :D

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Alright, I got it working (extremely satisfying achievement for my birthday :D ). Now I'll focus on optimizing it to make the interrupts shorter.

Thanks a lot for the help everyone!