Go Down

Topic: Motor PID control (Read 7 times) previous topic - next topic

Steve1

Brain fart.  The total pulse time would be the same over the same interval, just more pulses per second.  Duh.  So it may be working.  Doesn't look like I'm going to get the resolution I need, maybe I'll try micros()

Steve1

Allright, we're getting somewhere.
Code: [Select]
#include <TimedAction.h>
long TimeNow;
long LastPulseTime;
long PulseTime;
long TotalPulseTime;
int PulseCount;
long uSPerPulse;

TimedAction timedAction = TimedAction(100,CalculateRPM); // Set up a timed action class that occurs every 100mS
void setup()
{
 Serial.begin(9600); // Set up serial communication

 attachInterrupt(0, TimeStamp, RISING); // Attaches interrupt to Digi Pin 2
 LastPulseTime=micros(); // Give LastPulseTime a value
 PulseCount=0; // Initialize PulseCount
}

void TimeStamp()
{
 TimeNow=micros(); // Gets current time stamp in microseconds
 PulseTime=TimeNow-LastPulseTime; // Takes difference between current timestamp and last timestamp
 LastPulseTime=TimeNow;  //Sets current timestamp to last timestamp to be used next time around for difference
 TotalPulseTime+=PulseTime; //Accumulate total
 PulseCount++;  //Accumulate count
}

void loop()
{
 timedAction.check();
}

void CalculateRPM(){
 uSPerPulse=(TotalPulseTime/PulseCount);
 Serial.print(" TotalPulseTime:");
 Serial.print(TotalPulseTime);
 Serial.print(" PulseCount:");
 Serial.print(PulseCount);
 Serial.print(" Divided:");
 Serial.println(uSPerPulse);
 PulseCount=0;
 TotalPulseTime=0;

}

This works like a DREAM.  It has 1uS resolution and reads accurately as high as I could test it (11kRPM).  However, I can't figure how to calculate RPM without screwing up the math.  I believe it should be RPM = (1/ ((TotalPulseTime*1000000)/PulseCount))*60, but this causes some major overflow problems or needs more bits or something, because the result is garble.  As I said, I'm no programmer, does it show?  Any ideas for how to do the RPM calculation?  

PaulS

If RPM = (1/ ((TotalPulseTime*1000000)/PulseCount))*60,

move the 60 inside the parens:

RPM = 60/(TotalPulseTime*1000000)/PulseCount)

or

RPM = (60 * PulseCount) / (TotalPulseTime * 1000000)

Cancel a 10 top and bottom:

RPM = (6 * PulseCount) / (TotalPulseTime * 100000)

Depending on how large TotalPulseTime gets to be, a long may or may not be large enough to avoid overflow.

Mitch_CA

While PualS's tips are good, recognize in your code you are checking the RPM every 100 ms.
You stated in your first post you get 6 pulses per revolution.
Therefore every 100 ms the number of revolutions can be calculated...
myRevs_per100ms = PulseCount / 6
Note this is not an integer result.

Then RPM directly is [with units]...
myRPM = myRevs_per100ms * 600 [deciseconds per minute]

push it all together and simplify...

RPM = PulseCount * 100;

Note this is an integer result.  I'm sure you're smiling at how simple that equation looks :)

Mitch_CA

I had a second look at the TimedAction library.  It isn't what I thought it was.  It simply relies on polling the millis() function in the main loop and does not rely on an interrupt to trigger an event (in your case CalculateRPM()).  I thought I'd seen a library that better packaged an output compare interrupt but I can't find it, and TimedAction is not it.

Sorry for the mistake.  As a result, when you start putting more meat into your main loop you may find that CalculateRPM() does not run as regularly as you want/need.  You may then need to resort back to the original interrupt setup I recommended.

Glad you're making some headway.  When do I get to start doing some PID stuff here?  :) :)

Steve1

Soon I hope.  I already tried to put PID in the last code I had posted just using uSPerPulse as my Input instead of RPM and scaled my setpoint to go from 100000 to 800 based on an input potentiometer, but I couldn't get it to control the motor at all.  I'll have to do a little more work first, I'll try to implement a second interrupt sometime this week and see if I can figure that out.

Steve1

I just saw your last post about the formula.  I should have though about that, I noticed the convenience when I was measuring the frequency output that I just multiply Hz by 10 to get RPM.  I guess 6 pulses per rev might have been done on purpose, who knew?

I'm backed up big time at work, it'll probably be a week or two till I get some revised code back up for review.

Thanks for all the help

Steve1

Sorry it's taken me so long to post again, holidays and etc...

Here's where I'm at.  I can't quite get the CTC to work.  Using this code, it only shows the number 1 over and over.

It should be noted I'm using the 328P.
Code: [Select]
int TimeStamp;
void setup()
{
 Serial.begin(9600); // Set up serial communication
 
TCCR1B = B01001011; // rising edges, CTC mode, prescaler = 64
OCR1A = 25000; // 25000 x 64 / 16 million -> interrupt every 100ms
TCNT1 = 0; // reset timer1
TIMSK1 = B00000010; // turn on Output Compare A Interrupt
 
}


ISR(TIMER1_COMPA_vect) {
TimeStamp=millis();
Serial.println(TimeStamp);
}

void loop()
{

}


Any idea where I've gone wrong?

Thanks for the help, and again sorry for the delay.

Steve

Mitch_CA

#23
Jan 14, 2010, 03:50 am Last Edit: Jan 14, 2010, 03:53 am by mitch_79 Reason: 1
It took me a bit to figure it out, but I did (on my Arduino Mega mind you).

In the Arduino environment it seems that by default TCCR1A = B00000001.  This + your code puts you in Fast PWM, 8-bit mode.  (I figured this out by finally Serial.print(TCCR1A, BIN)'ing).
When dealing with direct register control of the timers be sure to write *all* registers that you want control over (and note that micros() and millis() will no longer work if you interfere with Timer0).

I also as a rule, tend to avoid Serial operations inside of an interrupt routine.  But since it's just a "hello world" type of application, here is a variant of your code that works.  Note the new type used for TimeStamp, and the "DEC" argument to .println.
Code: [Select]
unsigned long TimeStamp;

void setup()
{
 Serial.begin(9600); // Set up serial communication
 TCCR1A = 0;
 TCCR1B = B01001011; // rising edges, CTC mode, prescaler = 64
 OCR1A = 25000; // 25000 x 64 / 16 million -> interrupt every 100ms
 TCNT1 = 0; // reset timer1
 TIMSK1 = B00000010; // turn on Output Compare A Interrupt
}


ISR(TIMER1_COMPA_vect) {
 TimeStamp = millis();
 Serial.println(TimeStamp, DEC);
}

void loop()
{
}


Steve1

Wow, great catch.  Good news, the code works great.  It does exactly what it's supposed to.  Bad news, using either formula,
RPM=PulseCount*100, or
RPM=10000000/uSPerPulse both lead to the same problem:
a frequency resolution of ±100RPM at 10000RPM.  So the way I see it I have two options.  Either build a motor with more pulses per rev, or increase the 100ms interrupt period to get a more accurate count.  I increased it to 250ms and was able to get down to ±50Hz, but that still isn't quite what I was hoping for.  Any longer and I'm worried the PID won't control it as fast as I'd like.  I also came to another realization.  The built-in PWM in the Arduino uses 256 steps if I'm not mistaken.  I don't know if that will be enough resolution to control the RPM down to ±10RPM.  I know there are external PWM chips available, so I may have to go that route when we get there.  I think I may have outgrown my motor I'm using right now.  I'll try to get some code up soon that includes the PID.  

Here's the latest:
Code: [Select]

#include <TimedAction.h>
long TimeNow;
long LastPulseTime;
long PulseTime;
long TotalPulseTime;
int PulseCount;
double uSPerPulse;
double RPM;

void setup()
{
 Serial.begin(9600); // Set up serial communication
 
 TCCR1A = 0;
 TCCR1B = B01001011; // rising edges, CTC mode, prescaler = 64
 OCR1A = 25000; // 25000 x 64 / 16 million -> interrupt every 100ms
 TCNT1 = 0; // reset timer1
 TIMSK1 = B00000010; // turn on Output Compare A Interrupt
 
 attachInterrupt(0, TimeStamp, RISING); // Attaches interrupt to Digi Pin 2
 LastPulseTime=micros(); // Give LastPulseTime a value
 PulseCount=0; // Initialize PulseCount  
}

void TimeStamp()
{
 TimeNow=micros(); // Gets current time stamp in microseconds
 PulseTime=TimeNow-LastPulseTime; // Takes difference between current timestamp and last timestamp
 LastPulseTime=TimeNow;  //Sets current timestamp to last timestamp to be used next time around for difference
 TotalPulseTime+=PulseTime; //Accumulate total
 PulseCount++;  //Accumulate count
}



ISR(TIMER1_COMPA_vect) {
 uSPerPulse=TotalPulseTime/PulseCount;
 RPM=(10000000.00/(uSPerPulse));
 Serial.println(RPM);
 PulseCount=0;
 TotalPulseTime=0;
 uSPerPulse=0;
}

void loop()
{
}

It works, it has better resolution at slow speeds, but it also has a glitch and displays -10000000.00 with the motor stopped.

Here's using the other method Mitch mentioned:
Code: [Select]

#include <TimedAction.h>

int PulseCount;
int RPM;

void setup()
{ Serial.begin(9600); // Set up serial communication
 
 TCCR1A = 0;
 TCCR1B = B01001011; // rising edges, CTC mode, prescaler = 64
 OCR1A = 25000; // 25000 x 64 / 16 million -> interrupt every 100ms
 TCNT1 = 0; // reset timer1
 TIMSK1 = B00000010; // turn on Output Compare A Interrupt

 attachInterrupt(0, CountPulses, RISING); // Attaches interrupt to Digi Pin 2

 PulseCount=0; // Initialize PulseCount  
}

void CountPulses()
{
 PulseCount++;  //Accumulate count
}

ISR(TIMER1_COMPA_vect) {
 RPM=PulseCount*100;
 Serial.println(RPM);
 PulseCount=0;
}

void loop()
{
}


This works fine, but it has a resolution of 100 RPM across the entire band.  I like the top method a little more, but it will be more computing intensive so may skip pulses if I get a motor with more pulses per rev.  The bottom method is pretty stripped down in terms of calculations, but I don't know if I'll get better resolutions with more pulses per rev.  Any thoughts?



Steve1

I forgot to say, the RPM calculation method using time stamps also is somewhat unstable.  The resolution is better, but it jumps around so is unusable.  The pulse count method is stable, just doesn't have the resolution I'm looking for using a 6 pulse/rev motor.

Also, I want to say thanks for all the help.  I have a hard time fixing my own problems, it's really generous of you to help out.  Greatly appreciated.
Steve

Steve1

Just bought a new motor from Hong Kong
http://
http://shop.ebay.com/?_from=R40&_trksid=p3907.m38.l1313&_nkw=brushless+motor+esc+outrunner+5000&_sacat=See-All-Categories



Sounds like the hobbyists hate the esc that comes with this motor, but I don't want to spend $80 for a Castle Creations one just to find out it may not work for what I need.  I don't quite understand how these work, so I'll have to do some experimentation.  I believe it is a "three phase" motor with switched DC, and the switching frequency determines the motor rotation speed.  If this is the case, I could just monitor one of the phase outputs and use that as a tach input.  However, it mentions PWM, so it may not work this way.  In that case I'd have to build an opto-interrupter circuit or something of that nature.  Either way, I'll get back once I tinker with it.

Mitch_CA

Only have time for a quick note right now, but have you considered running the interrupt every 100ms but holding a buffer for the previous n revolutions?

Suppose n = 10.  Every 100ms you calculate the RPM based on the last 10 turns = 60 encoder pulses.  This gives you a running average.  For the controls portion you can start looking at response of the mechanical system.  What's the torque output of the motor when it's at speed?  And what's the rotational mass?  Together this arrives at max. rotational acceleration which can be used to determine how often you need to run the control logic.  And if you know the drag on the rotating mass (bearing friction) you can derive what value to use for n.

And no problem for offering the help.  Like I said, this is interesting to me, plus it helps keep me sharp for my own projects :)

Steve1

I got the new motor in and while it works the outputs from the ESC to the motor are far too noisy for reliable measurements, so that's out.  In addition the control of the motor is more complex than I'd hoped.  It requires a 50Hz signal with a pulse width of 1ms to 2 ms, 1 is no throttle, 2 is full throttle, 1.5 is in-between.  I hate to say it, because you put so much effort into it, but I think I'm just going to go with an open loop system on this one.  I'll get a PWM driver for the motor controlled with a simple 10 turn pot which will vary the signal from full off to full on, and then measure a tach output from the motor and display the RPM.  This way I can sample it for a second or more and get very accurate RPM results.  No closed loop control, no PID, so you'll have to set-and-measure but it'll work for our purposes.  I'll still probably use the Arduino to control the PWM chip and display freq on some 7-segment LED displays for high visibility.  Thanks for all the help on this one everyone, I learned a lot.  I hate to ditch it after all this, but I just don't think I can get where I want to go.

Next up I'm trying to interface a cheap Bluetooth module to the Arduino, a Sure Electronics Bluetooth uart.  We'll see how that goes.

Thanks again,
Steve

Go Up