Go Down

Topic: PID Controller and Hall Effect Switch Problem (Read 1 time) previous topic - next topic

5alad

HI all

    I am trying to run a Brush DC motor at constant rpm. I am implementing a PID controller with a hall effect switch.

The code i am using to get the hall effect readings is from http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1274834441

I am using the PID beta library for my PID controller.


The problem i am having is that while im tuning my PID controller following http://mbed.org/cookbook/PID

Somehow the hall switch code interferes with my PID loop. When the motor is away from the switch.. and the switch is giving an rpm reading of 0. The duty cycle jumps up like its supposed to... But as soon as i start getting readings on the hall sensor...even if they are below my setpoint.. the arduino stops sending the PWM signal. When the motor stops the sensor reads 0 rpm again and then pwm shoots up to high.. then when the hall sensor reads the rotation the pwm signal stops again.

This happens in a loop. What could be the problem? Also my rpm reading is not always accurate it varies by 60rpm. is there a way to set a range of acceptable values so that the chip doesnt constantly try to focus on 1 specific rpm readout value?


Here is the code given below:

Code: [Select]

                                                                   
//-------------------------------------------------------------------------------------
// ArduECU version 0.01 alpha
// Compiled with Arduino v0018
//
// Thanks to the members of the arduino.cc forum for their assistance

// Many comments removed to fit sketch into forum posting box...

#include <PID_Beta6.h>

double Input, Output, Setpoint;
PID pid(&Input, &Output, &Setpoint, 6, 0, 0);

extern volatile unsigned long timer0_overflow_count;  // Record the most recent timer ticks
volatile boolean ticks_valid;                         // Indicates a valid reading
volatile unsigned long ticks_per_rev;                 // Number of ticks counted in the current revolution
unsigned long msSinceRPMReading;                      // Number of mSec since the last rpm_sense (used to spot zero RPM)
float lastRPM, peakRPM;                               // Most recent RPM reading, and highest RPM recorded since last reset

const float __TICKS_TO_RPMS = 15e6;                   // Convert clock ticks to RPM by dividng ticks into this number
                                                     // The number will change if there are more magnets in an rpm
                                                     //   (e.g. 2 magnets = 29296.875)
const unsigned int __WAIT_BEFORE_ZERO_RPM = 2000;     // Number of mS to wait for an rpm_sense before assunming RPM = 0.
const int __REV_GAUGE_PIN = 3;                        // 100uA meter on this pin, with 50K current-limiting resistor


unsigned long msSinceSend;                            // mSec when the last data was sent to the serial port
const unsigned int __WAIT_BETWEEN_SENDS = 1000;       // Number of mS to wait before sending a batch of data.

int outputPsuB = 10;  // Timer1-B
int dutycycle = 85; //50% duty cycle

void setup()
{

 Serial.begin(9600);                                 // Initialise serial comms.
 
 pid.SetOutputLimits(0, 500);
 Setpoint = 7300;
 pid.SetMode(AUTO);

 msSinceSend = millis();                             // Data sent counter

 attachInterrupt(0, rpm_sense, RISING);              // RPM sense will cause an interrupt on pin2
 msSinceRPMReading = 0;                              // If more than 2000 (i.e. 2 seconds),
                                                     // then RPMs can be assumed to be zero (< 15rpm
                                                     // at most, with a single magnet, no small IC
                                                     // can run that slowly).
 lastRPM = 0;                                        // Current RPM to zero
 peakRPM = 0;                                        // Max recorded RPM to zero
 pinMode(__REV_GAUGE_PIN,OUTPUT);                    // Set pin 3 to be output
 
 pinMode(outputPsuB, OUTPUT);  // select Pin as ch-B
 
 TCCR1A = B00100001; // PWM, Phase and frequency correct - change at OCR1A
 TCCR1B = B10010;  // prescaling by 8 the system clock

}

// ------------------------------------------------------------------------------------------------
// FUNCTION: RPM-SENSE
//
// Called whenever the RPM sense signal rises. In my setup, the hall effect switch is normally
// high, goes low when a south pole is introduced to the sensor, and rises back to high as the
// magnet goes away. Thus, the RPM sense is called on the trailing edge of the magnet.
//
// Version Date        By  Comment
// -------|-----------|---|------------------------------------------------------------
//   0.01a 26-May-2010 JAV Created (with an assist from BenF from the arduino.cc forum
//
// ------------------------------------------------------------------------------------------------
void rpm_sense()
{
 static unsigned long pre_count;               // Last timer0_overflow_count value
 unsigned long ticks_now;                      //

 ticks_now = timer0_overflow_count;            // Read the timer

 byte t = TCNT0;
 if ((TIFR0 & _BV(TOV0)) && (t<255))
   ticks_now++;
 ticks_now = (ticks_now << 8) + t;

 if (pre_count == 0) {                         // First time around the loop?
   pre_count = ticks_now;                      // Yes - set the precount, don't use this number.
 } else {
   ticks_per_rev = ticks_now - pre_count;      // No - calculate the number of ticks...
   pre_count = ticks_now;                      // ...reset the counter...
   ticks_valid = true;                         // ...and flag the change up.
 }
}

void loop()
{
 unsigned long thisMillis = millis();          // Read the time once
 Input = lastRPM;
   Output = dutycycle;
   pid.Compute();  
 //Serial.print("dutycycle: ");
 //Serial.println(dutycycle);
 //Generation of PWM Signal
 OCR1A = 500; // 122.0Hz at 8197us|140Hz at 7143us|2kHz at 500us
 OCR1B = Output; // 99.9% DC at 8196|40% DC at 7071


 // Calculate RPM
 if (ticks_valid)
 {                            // Only come here if we have a valid RPM reading
   unsigned long thisTicks;
   
   noInterrupts();
   thisTicks = ticks_per_rev;
   ticks_valid = false;
   interrupts();
   
   lastRPM = __TICKS_TO_RPMS / thisTicks;      // Convert ticks to RPMs
   ticks_valid = false;                        // Reset the flag.
   msSinceRPMReading = thisMillis;             // Set the time we read the RPM.
   if (lastRPM > peakRPM)
     peakRPM = lastRPM;                        // New peak RPM
 }
 else
 {
   // No tick this loop - is it more than X seconds since the last one?
   if (thisMillis - msSinceRPMReading > __WAIT_BEFORE_ZERO_RPM)
   {
     lastRPM = 0;                              // At least 2s since last RPM-sense, so assume zero RPMs
     msSinceRPMReading = thisMillis;           // Reset counter
   }
 }
 

 
 // Is it time to send the data?
 if (thisMillis < msSinceSend)                  // If thisMillis has recycled, reset it
   msSinceSend = millis();
   
 if (thisMillis - msSinceSend > __WAIT_BETWEEN_SENDS)
 {
   Input = lastRPM;
   //dutycycle = Output;
   pid.Compute();

   // Yes: Build the serial output...
   
   // For now, send everything plaintext. Maybe compression would be a good thing, later down the line...
   Serial.print("uECUv0.01|CurrentRPM: ");                  // Send ID - ditch this for something smaller
   Serial.print(lastRPM);                       // Current RPMs
   Serial.print("|");                           // Field Separator
   Serial.print(peakRPM);                       // Peak RPMs
   Serial.print("|DutyCycle: ");                           // Field Separator
   Serial.print(dutycycle);                     // Temperature #1 (water in)
   Serial.print("|PIDOut: ");                           // Field Separator
   Serial.print(Output);                             // Temperature #2 (water out)
   Serial.print("|");                           // Field Separator
   Serial.print(0);                             // Temperature #3 (exhaust gas)
   
    // Debugging
   // Serial.print("|");                           // Field Separator
   // Serial.print(ticks_per_rev);                 // clock ticks in the last rpm

   // That'll do for bnow
   Serial.println();                            // EOL.

   msSinceSend = millis();                      // Reset the timer
   
   
   
   
   if (Serial.available()) {
     char cmd = Serial.read();
     if (char('R') == cmd) {
       peakRPM=0;
     }
     Serial.flush();
   }
   
 }

 // Set the RPM gauge value in real-time...
 if (lastRPM < 1000) {
   // Sub-1000 RPMs, map to between 0-255
   analogWrite(__REV_GAUGE_PIN,map(lastRPM,0,1000,0,255));
 } else {
   // If RPMs > 1000, write 255 (=meter FSD); but we've got bigger problems if the RPMs ever get this high...
   analogWrite(__REV_GAUGE_PIN,255);  
 }

}



Thank you all in advance for your help!!

RIDDICK

#1
Aug 18, 2010, 08:20 pm Last Edit: Aug 18, 2010, 08:22 pm by RIDDICK Reason: 1
hm

i dont know these libraries...

why dont u use analogWrite()?
http://arduino.cc/en/Reference/AnalogWrite

what if u try to do it without the PID library?

u could just add/subtract 1 to the PWM output,
if the RPM is too low/high...

what happens if u do that?

u can use this formula to get a smoother RPM value:
Code: [Select]
float smooth_rpm = 0;
[...]
void loop() {
 [...]
 smooth_rpm = current_rpm * .1 + smooth_rpm * .9;
 [...]
}


-arne
-Arne

Palle B

Did you get this to work?

I am working on a similar project, only i need to control a valve, that control a brake, to hold the rpm below a given value.

So i need a rpm counter and something to control the PWM or PID output.

I am going to make tread of my own, but i think that my project is so close to this, that it might not the original tread?

Btw. I'm brand new to Arduino, and this is my first post.

Thanks in advance

Palle

Go Up