Continue to Site

Welcome to EDAboard.com

Welcome to our site! EDAboard.com is an international Electronics Discussion Forum focused on EDA software, circuits, schematics, books, theory, papers, asic, pld, 8051, DSP, Network, RF, Analog Design, PCB, Service Manuals... and a whole lot more! To participate you need to register. Registration is free. Click here to register now.

[AVR] Controlling DC-Motor from incremental Encoder pulse signals using ATmega88, C code

Status
Not open for further replies.

hsj17

Newbie
Newbie level 2
Joined
Jun 21, 2021
Messages
2
Helped
0
Reputation
0
Reaction score
0
Trophy points
1
Activity points
29
I have given the following C-Code to test a 60V DC motor with [gear box ratio 16:1 and incremental encoder to see if the code calculates the speed by reading the Pulse signal as feedback for µcontroller from encoder. The µcontroller is ATmega88A-UA. The encoder RE56-3-1000-TI signals 1000 ppr. The DC motor on special occasions needs to be run at 3-4 RPM for specific task. At lower speed such as less than 5V where motor has starting voltage of 1.8V, the software seems to calculating the incorrect speed values. When at low speeds the mode is TCCR1B == 0x02, the speed value is relatively higher ranging from 255 - 1071 at 2V-5V and frequency 751Hz -3.6kHz accordingly.

When the mode is switched to TCCR1B == 0x01, the speed values calculated/observed (by debugging) ranges from 471 - 1140 at 8V - 20V and frequency from 6.2kHz - 17.4kHz.

What I have also observed that the mode TCCR1B keep switching between 0x01 & 0x02 specially at frequency between 2kHz and 4kHz.

Does any one understands what might be the trouble with incorrect value calculations. Is there some sort of timing problem? The speed variable used is an unsigned int. I need your help. There is a great possibility that things might not be well explained. I'll try my best to give right answers. The prodcut links are included in the description. Any kind of help/suggestions would be very much appreciated. Thanks



60V DC motor: **broken link removed**
gear box ratio 16:1: **broken link removed**
Encoder RE56-3-1000 TI: https://www.dunkermotoren.com/en/products/brakes-and-encoders/encoders/detail/8871005210/

"C-Code"
C:
#include "iom88.h"
#include "main.h"

 unsigned int Test_Speed_low;       //For testing only!!
 unsigned int Test_Speed_1;       //For testing only!!
 unsigned int Test_Speed_2;       //For testing only!!
 unsigned int Test_Value_Timer_2;       //For testing only!!

int main(void)
{
  // 1 rpm    ->    1000 / 60 = 16,6 Pulse/s  -> 1000 / 16,6 = 60,25 ms/Pulse
  // 10 rpm   ->   10000 / 60 = 166  Pulse/s  -> 1000 / 166  = 6,025 ms/Pulse
  // 120 rpm  ->  120000 / 60 = 2000 Pulse/s  -> 1000 / 2000 = 0,5   ms/Pulse
  // 2000 rpm -> 2000000 / 60 = 33333 Pulse/s -> 1000 / 33333 = 0,03 ms/Pulse
  // 8 Mhz -> 0,125 us / Takt
  // Timer1 CLK/8 -> 1 us / Timer cycle -> 65535 * 1 us = 62 ms -> ~ 1 rpm
  // If pulse is faster than 0.5 ms then measure 10 im pulses with
  // Timer1 CLK/1 -> 0,125 us / Timer cycle -> 65535 * 0,125 us = 8192 us = 8 ms
  // 120 rpm  ->  120000 / 60 = 2000 Pulse/s  -> 10 Pulse = 5 ms -> 5000 us / 0,125 us = 40000 Timerticks
  // 2000 rpm -> 2000000 / 60 = 33333 Pulse/s -> 10 Pulse = 0,3 ms -> 300 us / 0,125 us = 2400 Timerticks
      
  PORTB = 0x00;           // No Pullups
  DDRB = 0x03;            // PORTB_Bit0 = 1k3, Bit1 = 0R

  PORTC = 0;
  DDRC = 0x1F;            // Out_0-4 Outputs 47k, 22k, 11k, 5k6, 2k7

  PORTD = 0x20;           // Pull-Up T1 (Bit5)
  DDRD = 0x00;                               
 
  ACSR = 0x80;            // Disable Analog Comparator
  DIDR1 = 0x03;           // Disable input register on AIN0/AIN1
  ADCSRA = 0xC7;          // Clock / 128 slower is not possible, no INT 
  ADMUX = 0xC5;           // Int 1,1V Ref, ADC5
  DIDR0 = 0x20;           // Disable input register on ADC5
 
  TCCR0A = 0x02;          // T0 CRC mode
  TCCR0B = 0x06;          // T0 External clock source on falling edge
  OCR0A = 1;              // CRC int after 1 clocktick
  TIMSK0 = 0x02;          // Timer 0 Compare match A int

  TCCR1A = 0x00;          // T1 normal mode
  TCCR1B = 0x02;          // clk/8 source on T1
  TIMSK1 = 0x01;          // Timer 1 Overflow int 
  Value_TCCR1B = TCCR1B;
  Value_Timer1 = 60000;
 
  Min_Timer1 = 60000;
  Max_Timer1 = 0;
  Counter_Timer1 = 0;
 
  Test_Speed_1 = 0;       //For testing only!!
  Test_Speed_2 = 0;       //For testing only!!
  Test_Speed_low = 0;       //For testing only!!
 
  EAL = 1;   
    
  while(1)
  {
    if (TCCR1B == 0x02) {
      if (Value_Timer1 <= 500) {   // < 0,5 ms / Pulse
        OCR0A = 8;                 // CRC int after 8 clocktick 
        TCNT0 = 0;                // Reset timer 0 value
        TCCR1B = 0;               // stop T1
        TCNT1 = 0;                // Reset timer 1 value
        TCCR1B = 1;               // clk/1 source on T1
        Value_TCCR1B = TCCR1B;
      } 
    } else {   
      if (Value_Timer1 > 40000) {  // > 0,5 ms / 10 Pulse     //Slow Mode
        OCR0A = 1;                // CRC int after 1 clocktick 
        TCNT0 = 0;                // Reset timer 0 value
        TCCR1B = 0;               // stop T1
        TCNT1 = 0x1FF;
        TCCR1B = 2;               // clk/8 source on T1
        Value_TCCR1B = TCCR1B;
      } 
    }
    
    if (Counter_Timer1 > 2) {
     if (Value_Timer1 < Min_Timer1) {
        Min_Timer1 = Value_Timer1;
      }
      if (Value_Timer1 > Max_Timer1) {
        Max_Timer1 = Value_Timer1;
      }
      if (Counter_Timer1 > 1000) {
        Counter_Timer1 = 0;
        Min_Timer1 = 60000;
        Max_Timer1 = 0;
      }
    }     

    if (TCCR1B == 0x02)
    {                     
      //Speed = 60000 / (Value_Timer1 / 10); // 0,1 RPM,  at 60000 uS -> 1 RPM
      
      Test_Value_Timer_2 = (Value_Timer1/10);         //For testing
      Speed = (60000/Test_Value_Timer_2); // 0,1 RPM,  at 60000 uS -> 1 RPM
      Test_Speed_low = Speed;
    }
    else
    {
      if (Value_Timer1 > 10000)
      {
        //Speed = 48000 / (Value_Timer1 / 100); // 1 RPM,  at 5000 uS -> 120 RPM
        
        Test_Value_Timer_2 = (Value_Timer1/100);      //For Testing
        Speed = (48000/Test_Value_Timer_2); // 1 RPM,  at 5000 uS -> 120 RPM
        Test_Speed_1 = Speed;           //For testing only!!
      }
      else
      {
        //Speed = 48000 / (Value_Timer1 / 10);  // 1 RPM,  at 5000 uS -> 120 RPM  ... 120RPM????
        //Speed *= 10;
        
        Test_Value_Timer_2 = (Value_Timer1/10);         //For testing
        Speed = (48000/Test_Value_Timer_2);  // 1 RPM,  at 5000 uS -> 120 RPM
        Speed *= 10;
        Test_Speed_2 = Speed;       //For testing only!!
      } 
    }
  }
}

#pragma vector=INT0_vect

__interrupt void INT0_int(void)   
{
}

#pragma vector=INT1_vect

__interrupt void INT1_int(void)   
{
}

#pragma vector=TIMER0_COMPA_vect

__interrupt void TIMER0_COMPA_int(void)
{
  TCCR1B = 0;                   // Timer1 Stop
  Value_Timer1 = TCNT1;
  TCNT1 = 0;                    // Reset timer 1 value
  TCCR1B = Value_TCCR1B; 
  Counter_Timer1++;   
  Counter_Int = 1;
}

#pragma vector=TIMER1_OVF_vect

__interrupt void TIMER1_OVF_int(void)
{
 if (Timer1_OVF == 2)           // Speed < 1,0 RPM
 {   
   Speed = 0;
 }
 else
 {
    Timer1_OVF++;
 }
}
 

Hi,

I see no declaration of the variable "Speed". What type, bit width and signedness is it? I guess you need it to be a float.

value_TCCR1B is set in MAIN, but read in INT. Then it should be declared volatile. (although not essential here, because it is modified in main only)
But indeed I see no reason for usage in MAIN. Declare it in INT, read the TCCR1B into it before stopping TMR1.

All the MAIN and it´s calculations are not timed. Maybe they trun 100 times per second, maybe 100,000 times per second.
I guess this will influence behaviour.

What´s the idea behind counter_timer1 variable? It is read in MAIN, but set in INT. Then it should be declared volatile.

Klaus
--- Updated ---

Hi,

I see a couple of issues:
* you want a dynamic from 3 RPM to 2000 RPM. That´s huge. Thus you need careful design over the whole range.
* If I understand right you want at high speed an average of 10 pulses. I can´t see how this is done.
* at low speed you want to measure the time for one pulse. The time_value is big, but then you use "divide" into an "unsigned int". With this you distroy all the good resolution.
* I see no measurement_finished or similar signal to synchronize INT measurement with MAIN.
* According comments speed sometimes has the unit "0.1 RPM" and sometimes "1 RPM".

******
Usually for
* low frequency measuremnt: you use time_mesurement method
* high frequency measurement: you use pulse_count method

****
If I understand right, then with
* 10 RPM you expect a SPEED update every 6.02 ms
* 100 RPM you expect a SPEED update every 0.602 ms
* 1000 RPM you expect a SPEED update every 0.602 ms, too

For a predictable, precise and stable regulation loop, you need a constant output update rate. This update rate needs to be slower than the slowest of your input (speed) update rate. Mind not to generate undersampling effects. Be sure that your algorithm can fulfil this.

Klaus
 
Last edited:
Hi Klaus,
first of all thank you very much for a detailed reply and pointing at the concerned points. Due to lack of experience I am unable to understand the code properly and find it too complexedly written. Also I do not have such detailed knowledge controlling motors with encoders and PWM.
Do you have any similar example where such thing is implemented? Can it be shared with me so that I can take a look and try to implement something similar that would work. For your better understanding I have attached the code files that I was provided for testing. Can you please take a look at it. I am in need of help here. :)
 

Attachments

  • Encoder.zip
    9.4 KB · Views: 89

Hi,

although I have done motor control loops, I can´t say I´m a specialist for this.

But the motor problem is common to most regulation problems (like temperature regulation, voltage regulation, position regulation, ...)
You have input:
* RPM setpoint
* actual RPM
you have an output
* PWM

Now you need to build a loop to make the "actual RPM" according the "RPM setpoint".
For the loop to be stable you need to follow general stability requirements of feedbacked loops. (it´s similar to a feedbacked Opamp)
The key parameters for stability are:
* phase (signal delay)
* gain

Less phase shift = less delay = faster regulation loop --> gets a more stable regulation loop
and you can increase gain to get more precise motor RPM.

But the regulation loop should not be stable only at one PM but it should be stable over the whole range of RPM form 15RPM to 2000RPM.
This also means that you should try to keep
* delay (phase) small over the whole range. No jumps in delay.
* gain constant over the whole range of RPM.

Also a good idea: if your system is slow then make the setpoint even slower.
What does this mean? Evn if the motor gets connected to a DC voltage it may take 500ms for the motor the reach the RPM.
So if your setpoint "jumps" from 0 to 100% within 10ms the motor RPM has no chance to follow this and the regulation loop will get out of regulation, inputs and outputs may saturate, the integral part of PID regulation loops will become big... it will take a long time for the loop (for the RPM) to stabilize.
Besides of this the motor will draw a huge current for the mass to be accelerated.
--> so it´s a good idea to decide a limited RPM rise /fall rate.

I´m a friend of push-pull DC motor drivers (half bridge, slow decay). Not a single low side MOSFET.
The single MOSFET solution is cheaper, but the duty_cycle to RPM ratio is way from being linear.
Whereas the push-pull solution gives a rather good linear behaviour. ( this means: constant loop gain)

We don´t know about the mechanical load. Is it known, is it linear, or not predictable at all?
In some cases where he load (torque) is unknown you may use the averaged motor current as additional (positive) feedback for your regulation loop.
This will help to to stabilize RPM for a wide range of torque.

Klaus
 

Status
Not open for further replies.

Part and Inventory Search

Welcome to EDABoard.com

Sponsor

Back
Top