Double Stepper Motor Control

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

Hello Freaks!

I am in the last stanges of making my first auto balancing robot. It is made out of 2 stepper motors, 2 stepper motor drivers, mpu6050 and ATmega8A as brain.

I have already achieved balancing however I am currently trying to achieve control from phone by bluetooth (HC05). Here is short vid:

 

https://www.youtube.com/watch?v=...

 

To make it possible I need to be able to control 2 stepper motors simultaneously (so they can rotate at different speed when robot turn.

currently I control them by using ISR. Algorithm is simple:

 

1. Mpu6050 reads gyro and accel

2. Complementary filter calculates current angle

3. PID calculates required acceleration

4. currentSpeed = reqAccel*dt + currentSpeed;

5. calculate required freqency of 0,1 signal generated on I/O port to achieve currentSpeed (stepper motor driver makes motor make step each time 0 changes to 1)

6. generate interrupts that change state of the port at required freqency. (CTC)

 

on a single timer (timer2) this works perfectly as we can see on a video. However I have found it difficult to do the same thing when using 2 timers (one for each motor) simultaneously, so when, for example, I send from smartphone 'turn' signal which is simply difference between rotation speed of two motors ( half of this difference should be added to motor1 speed and half should be deducted from motor2 speed) I have 2 different interrupt frequencies one for each motor. 

 

My tests prove that on small frequencies this routine works, however over certain frequency program just stopps working. 

 

So there is time for some codes. I will paste just the interrupt test codes to keep this post as clear as possible.

interrupt test is aplication sending letters with bluetooth from 3 different sources.

 

- '_' is sent with main loop of program

- 'A' is sent with TIMER1 CTC interrupt

- 'B' is sent with TIMER2 CTC interrupt

 

 

#ifndef	F_CPU
#define F_CPU 4210000UL
#endif

#include <avr/io.h>
#include "USART.h"
#include <util/delay.h>
#include <avr/interrupt.h>

double extraTime1=0;
double extraTime2=0;
int main(void)
{
	//TIMER2
	TIMSK |= (1 << OCIE2);	//Output Compare Interrupt Enable 2
	TCCR2 |= (1 << WGM21) | (1 << CS22); //CTC, prescaler 64
	OCR2 = 125;
	sei(); 

	//TIMER1
	_delay_ms(1);
	TCCR1B |= (1<<CS11)|(1<<CS10)|(1 << WGM12); //CTC, prescaler 64
	OCR1A = 125;
	TIMSK |= (1 << OCIE1A); //Output Compare Interrupt Enable 1A
	sei();

	usart_Init();

    while (1) {
		_delay_ms(1000);
		UDR = '_';
	}
}
ISR(TIMER1_COMPA_vect){
	extraTime1++;
		if(extraTime1 >= 526){ //occurs every ~1 s
			UDR = 'A';
			extraTime1=0;
		}
}

ISR(TIMER2_COMP_vect){
extraTime2++;
	if(extraTime2 >= 526){ ///occurs every ~1 s
                UDR = 'B';
		extraTime2=0;
	}
}

Result is 3 signs every 1 second (sometimes I lose '_' which is acceptable for me at this rate as PID control is rather robust and could probably handle this)

when I increase interrupt frequency for both timers:

	OCR2 = 25;  //frequency increased 5 times
	OCR1A = 25;

	if(extraTime1 >= 2630){ //increased 5 times so sign is sent still every 1s
	if(extraTime2 >= 2630){

Result is 'rather unacceptable' (too often I lose main loop of program):

and finally when I again increase frequency 5 times the result is absolutely unaccaptable.

 

 

Additional test I made includes 1 timer (timer2) but with increased frequency AGAIN 5 times

I receive just B letter from time to time (not in 1s delay). EDIT: '_' shows up once per few min :)

 

I think it is worth to mention that the usart commends might be slowing down the work (however its easiest to share with you way of debugging in my opinion, tell me if I am wrong) as in the original balacning robot program the interrupt on timer occurs with this last frequency.

here is this code:

	TIMSK |= (1 << OCIE2);	//Output Compare Interrupt Enable 2
	TCCR2 |= (1 << WGM21) | (1 << CS22)|(1 <<CS21); // Clear Timer on Compare 64
	OCR2 = 1; //Output Compare Register 2 (8 bit -> up to 256)
	sei(); //Set Enable for Interrupt

	[...]

	ISR(TIMER2_COMP_vect){
	extraTime++;
	if (extraTime>=halfPeriod)
	{
		PORTB ^= (1 << PORTB0)|(1<<PORTB2);	//connected to step inputs of driver
		extraTime=0;
	}

}

Only thing that comes to my mind that could perhaps solve this problem is adding external oscilator and increasing F_CPU to 16Mhz for example.

the question is, if it is possible to do it on this frequency or perhaps there is a better way to control two motors. I am quite new to AVR enviroment and still learning a lot so I can be doing it completly wrong way and in this case please correct me. 

 

Chris

 

This topic has a solution.
Last Edited: Tue. Sep 26, 2017 - 03:43 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

1) F_CPU = 4210000??? Are you sure?

 

2) You write to UDR without checking it is empty so you may well be overwriting a value just written by the other ISR.

'This forum helps those who help themselves.'

 

pragmatic  adjective dealing with things sensibly and realistically in a way that is based on practical rather than theoretical consideration.

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

1) I have measured this frequency, as I am not using external oscilator I have checked what is my accual frequency when the fuse bits are set for 4 mhz.

 

2) true, however I have tested it with my own, self written funtions that include checking if UDR jis empty and results were the same, however the program was a bit slower. I replaced them for UDR and repeated tests to just make it more 'clear' for other users of the forum.

 

EDIT: I have checked it once again. with the whole 'checking' ,for the OCR = 5 and less seems like the program does not respond or works very very slowly. on OCR value equal 25 however and higher results are the same

Last Edited: Thu. Sep 21, 2017 - 01:46 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Chris_Irathil wrote:
1) I have measured this frequency, as I am not using external oscilator I have checked what is my accual frequency when the fuse bits are set for 4 mhz.

Here: http://www.avrfreaks.net/comment...

 

So what happened to using the crystal, then?

 

Note that you have an outstanding question awaiting your reply in that thread: http://www.avrfreaks.net/comment...

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

@awneil I am sorry. I meant external crystal when I was mentioning external oscilator. I am still waiting for it, hopefully it will arrive today :)

Also thank you, I missed somehow these last posts!

 

Chris

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

Why are you using double variables when you are doing integer operations? Apart from double actually only being single precision with avrgcc, doing any floating point is going to suck cpu cycles needlessly. The AVR is an 8bitcpu, so choose your variable types carefully if you are concerned about code size and/or performance.

Last Edited: Thu. Sep 21, 2017 - 10:29 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

There are a number of techniques you can use for driving multiple stepper motors. The highest performant would be to use a timer for each motor and have it toggle it's associated output pin.
Another technique is to use one timer in CTC mode to generate an interrupt at a constant rate and use a DDS technique to generate the timing for each motor. This technique gives you very fine control of the motor speed.

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

@Kartman, thank you, good point about these doubles, I've been testing if there are some changes (not according to this problem) and mindlessly left them there to be.

I updated this to code and it significantly reduced 'skip' rate. Here are results (1st line is with double, the second is with ints). 

I assume my solution (idea to make them work) is the second  you mention. I use 2 timers in CTC mode and generate steps with calculated frequency. However so far I suppose my problem would be that interrupts overlaps each other, or just the MEGA cant handle this amount of interrupts per second. The ints help a lot however they dont solve it completly.

 

@EDIT: I have realised the skipping is result of mega spending big amounts of time calculating things and slowing down accual 'main' loop.

 

As stepper motors do step of 1.8* and my wheels are quite small, I need to generate pretty high frequencies.  

What do you think? I am going to make some tests today and update whenever I can.

 

Chris

Last Edited: Fri. Sep 22, 2017 - 09:05 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Chris_Irathil wrote:
I need to generate pretty high frequencies.  

You do realise that UART transmission is pretty slow - so it's probably not surprising that your serial output can't keep up...

 

Quantify, "pretty high" frequencies.

 

What baud rate are you using?

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

Chris_Irathil wrote:

...that include checking if UDR jis empty and results were the same...

 

But what do you do if UDR is NOT empty? If you wait in one ISR then you may well miss the other one. Or rather it will be delayed so that the 1ms offset you put in at the start will instantly disappear.

'This forum helps those who help themselves.'

 

pragmatic  adjective dealing with things sensibly and realistically in a way that is based on practical rather than theoretical consideration.

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

Chris_Irathil wrote:

... just the MEGA cant handle this amount of interrupts per second...

 

A mega can handle LOTS of interrupts per second IF USED RIGHT. I have application running with a combined ISR rate of one every 10usecs.

 

 

Chris_Irathil wrote:

...skipping is result of mega spending big amounts of time calculating things...

 

But your code in post #1 doesn't do any calculations.

'This forum helps those who help themselves.'

 

pragmatic  adjective dealing with things sensibly and realistically in a way that is based on practical rather than theoretical consideration.

Last Edited: Fri. Sep 22, 2017 - 09:33 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

@awneil

Yes, I have realised that, however no better way of debugging this comes to my mind.

 

The max of this code I use is 62500 interrupts per secons per timer and I want to use 2 timers. At the same time main loop(5ms) is supposed to calculate angle from mpu6050 measurements, set control signal from PID and calculate required freqency.

I have absolutely no idea how many interrupts atmega can manage in a 1s time window. The code for each interrupt looks this way and does not include UART transmission:

 

ISR(TIMER1_COMPA_vect){
	extraTime1++;
		if(extraTime1 >= halfperiod){
			PORTC ^= (1<< PORTC2); // example
			extraTime1=0;
		}
}

halfPeriod is the half of the period ( -_-' ) of the one step of the calculated freqency.

BAUDRATE is 9600

 

Chris

Last Edited: Fri. Sep 22, 2017 - 10:14 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

@Brian Fairchild

 

Sorry, I dont know how to call it. Operations I guess... ? UART tranmission slows it down, using doubles for integer operations slows it down...

The UART was never my target for being used in the interrupt. I jsut use it for debugging to have some results on the screen. 

 

A mega can handle LOTS of interrupts per second IF USED RIGHT

Then my major question for this topic is how to use RIGHT 2 timers on CTC mode with similar interrupt frequencies in a way where they dont slow down the work or overlap each other?

The code I aim to execute in the interrupts is: (for each timer)

ISR(TIMER1_COMPA_vect){
	extraTime1++;
		if(extraTime1 >= halfperiod){
			PORTC ^= (1<< PORTC2); // example
			extraTime1=0;
		}
}

 

Chris

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

Chris_Irathil wrote:
BAUDRATE is 9600

So each character takes about 1ms to transmit!

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

WTF does extratime1 do? What are you wanting to achieve? The timers have associated output ports that can be toggled by the timer without isr or software intervention. You are constrained to using specific port bits. Thus you avoid slow down or issues with overlap.

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

@awneil

OK, I'll make some tests including that knowleadge and update as soon as possible

 

@Kartman

extratime is variable that deciedes how often the main event of the interrupt has to occur. Which means it determines current frequency.

 

 

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

The general idea is you vary the compare value of the timer to set the required frequency.

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

@Kartman

I've been accually testing it. I ended up at the current settings mainly because I can have longer time between next interrupts. The Robot requires some very low frequencies around the angle 0*. It is impossible to achieve this as the register that stores the compare value has 8 bits so can't store bigger numbers.

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

Chris_Irathil wrote:

...I can have longer time between next interrupts...as the register that stores the compare value has 8 bits so can't store bigger numbers.

 

1) Use Timer 1 and you'll have two 16-bit compare registers.

 

2) You don't need the interrupts to be constantly running. Use one of the CTC modes that toggles the output pin to generate your squarewave, no interrupts required.

'This forum helps those who help themselves.'

 

pragmatic  adjective dealing with things sensibly and realistically in a way that is based on practical rather than theoretical consideration.

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

@Brian Fairchild

1) I use both timer1 and timer 2 as I have 2 motors.

 

2) Thanks, haven't found that option yet! Will try it out for sure

 

This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Okay, I have found the solution. 

Apparently the most of the problems were coming ( as usual ) from my lack of brain. I have left comment about prescaler "64" on a timer2 with prescaler accually set to 256. This amount was the limit set for this task to be performed properly. Nothing surprising that with the other timer (timer1) set to prescaler 4 times lower, the program did not want to work. 

 

Also, as I can't paste exact code, for anyone that will have similar problem, I'd advice to pay a lot of attention during tests if you are absolutely sure that everything you are currently testing is set in a mode you think it is.

 

I'm saying it because I also wasted a lot of time and didnt accually recognise easly that is was prescaler problem (even after lowering down compare value where everything begun to work) as I left unproper command in the interrupt function and even changing prescaler value didn't make things work instantly.

 

I got the lesson to keep not only code clean but also comments up to date!

 

 

Chris

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

Chris_Irathil wrote:
 I can't paste exact code

Why not?