• Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

16F877A brushed dc motor controll with feedback from encoder disk

Thread starter #1
Hello everyone..

Im going to build a project about controlling brushed dc motor with feedback from encoder disk. im using 16F877A and XC8 Compiler for this project. THe encoder disk is from old printer and it has like 8000 steps. about one year ago ,i build that project via Arduino and it worked fine but now im tring to build that project via PIC microcontroller. the code same as arduino's code but its not working for now !
im using a brushed dc motor(max current is about 0.5 amps) and l293n (max current is 2 Amps) for driving motor. connections of driver and pic is fine. but there is a problam.
for example when i forced to motor's rotor through the CCW direction, the pic is giving expected pwm signal but when i set free the rotor , there are two possible action happening.
first possible action is rotor start turning through the CW direction as i expected but its not stopping.
second possible action is rotor start turning through the CW direction as i expected but its missing steps and stopping somewhere after the setpoint.

but same condition is not valid to other direction. its working fine and stopping expected point everytime.

i found the reason ;
when the rotor turning throught the CW direction , interrupt service routine is stop working or missing steps.

the other thing that i should ask ; when motor turning fast , imean when the frequency of encoder is 35kHz , interrupt service routine is missing steps but i dont want to miss any steps.
what should i do ??


My codes are down below.there are also PWM.c ,PWM.h files and pwm signals from my oscillascope .

need some help here.
thanks for assist and have a great day for all of you guys .

My Main C code is here
Code:
#pragma config FOSC = HS        // Oscillator Selection bits (HS oscillator)
#pragma config WDTE = OFF       // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable bit (PWRT disabled)
#pragma config BOREN = OFF      // Brown-out Reset Enable bit (BOR disabled)
#pragma config LVP = OFF        // Low-Voltage (Single-Supply) In-Circuit Serial Programming Enable bit (RB3 is digital I/O, HV on MCLR must be used for programming)
#pragma config CPD = OFF        // Data EEPROM Memory Code Protection bit (Data EEPROM code protection off)
#pragma config WRT = OFF        // Flash Program Memory Write Enable bits (Write protection off; all program memory may be written to by EECON control)
#pragma config CP = OFF         // Flash Program Memory Code Protection bit (Code protection off)


#include <xc.h>

#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#define _XTAL_FREQ 16000000L

#include "pwm.h"

#define STEP_MARGIN      3    //Allowıng 3 missed steps from encoder disk.

#define sensorStatusA     PORTBbits.RB6    //Photo sensor 1
#define sensorStatusB     PORTBbits.RB7    //Photo sensor 2
#define MotY_DIRECTION   PORTDbits.RD0    //Direction output motor
#define MotY_PWM         PORTCbits.RC2    //PWM output motor

int stepStatus = 0;
//int stepStatusOld = 0;
int oldSensorStatusA = 0;
int dutyCycle = 0;
signed long setPoint = 0;
int actualPoint = 0;


void __interrupt () isr (){
    if (RBIF==1){
        
   if(MotYSensor02 == 0 && MotYSensor01 == 0){
        if(MotYStep == 1){
          MotYIsPoint--;
        }
        if(MotYStep == 3){
          MotYIsPoint++;
        }
        MotYStep = 0;
      }
    
      if(MotYSensor02 == 1 && MotYSensor01 == 0){
        if(MotYStep == 0){
          MotYIsPoint++;
        }
        if(MotYStep == 2){
          MotYIsPoint--;
        }
        MotYStep = 1;
      }
    
      if(MotYSensor02 == 1 && MotYSensor01 == 1){
        if(MotYStep == 3){
          MotYIsPoint--;
        }
        if(MotYStep == 1){
          MotYIsPoint++;
        }
        MotYStep = 2;
      }
    
      if(MotYSensor02 == 0 && MotYSensor01 == 1){
        if(MotYStep == 2){
          MotYIsPoint++;
        }
        if(MotYStep == 0){
          MotYIsPoint--;
        }
        MotYStep = 3;
      }


    RBIF=0;
}


void main(void) {

 TRISB=0XF0;
 
 //-----interrupt registers-----//
    
  
    GIE=1;
    INTEDG=1;
    RBIF=0;
    RBIE=1;
    //-------------------------//
    
            while(1){

if(abs(MotYSetPoint - MotYIsPoint) < STEP_MARGIN){  // Actualpoint=Setpoint
                TRISC2=1;
       MotY_DIRECTION= 0;
        MotYStepDone = 1;
        MotYDutyCycle = MIN_DUTYCYCLE;
      }
      else{
        if(MotYIsPoint < MotYSetPoint){ // CW diection
          
        MotY_DIRECTION= 1;
          PWM_Init(0);
    PWM_Start(0);
      PWM_SetDutyCycle(0,100);  //16kHz pwm freq and %40 of dutycycle
 
          }
          
           if(MotYIsPoint > MotYSetPoint){ //CCW direction
            
          MotY_DIRECTION= 0;
            PWM_Init(0);
    PWM_Start(0);
      PWM_SetDutyCycle(0,50);  //16kHz freq of pwm and %20 of dutycycle
        
        }
      }
} }
this is header fie of pwm
Code:
#ifndef _PWM_H_
#define _PWM_H_

#include "stdutils.h"



/*************************************************************************************************
                                Function Prototypes                               
*************************************************************************************************/
void PWM_Init(uint8_t channel);
void PWM_SetDutyCycle(uint8_t channel,uint8_t dutyCycle);
void PWM_Start(uint8_t channel);
void PWM_Stop(uint8_t channel);   
/*************************************************************************************************/
#endif

this is my pwm.c codes

Code:
#include <pic16f877a.h>
#include "pwm.h"

#pragma warning push
#pragma warning disable 752 // Suppress warnings related to size of variables(conversion to shorter data type).



void PWM_Init(uint8_t channel)
{
    switch (channel)
    {
    case 0 :
        CCP1CON = 0x0F; // Select the PWM mode.
        PR2 = 500;      // 16kHz freq of pwm.
        CCPR1L = 50;    // By default set the dutycycle to 50
        TRISC2=0; // Make the PWM pin(PC.2) Output
        break;

    case 1 :
        CCP2CON = 0x0F; // Select the PWM mode.
        PR2 = 100;      // Set the Cycle time to 100 for varying the duty cycle from 0-100
        CCPR2L = 50;    // By default set the dutycycle to 50
        TRISC1=0; // Make the PWM pin(PC.1) Output
        break;
    }
}




void PWM_SetDutyCycle(uint8_t channel,uint8_t dutyCycle)
{
    

    switch (channel)
    {
    case 0 :
        CCPR1L = dutyCycle;
        break;

    case 1 :
        CCPR2L = dutyCycle;
        break;
    }
}


void PWM_Start(uint8_t channel)
{
    TMR2ON = 1; //Start the Timer for PWM generation
}



void PWM_Stop(uint8_t channel)
{
    switch (channel)
    {
    case 0 :
        CCP1CON = 0x00;  //Disable the CCP Module from generating PWM
        break;

    case 1 :
        CCP2CON = 0x00;  //Disable the CCP Module from generating PWM
        break;
    }
}
 

Attachments

Ian Rogers

User Extraordinaire
Forum Supporter
Most Helpful Member
#2
Are you sure that code compiles successfully? I cannot see half the variables definitions... All the motxxxxx variables in main() and the ISR() are the ones in question? Arduino is C++ and these are defined automatically.. The XC8 compiler is just C so they need a definition..
 

MaxHeadRoom78

Well-Known Member
#3
Is this for position control or for just RPM, if so I would consider 8k/rev encoder Way higher than needed.
I don't program in C so can't help there, but have done it in Assembly, also Picmicro has a Pic with Quadrature encoder module, but even that might be overkill.
Also the lower resolution should solve any INT error.
Max.
 
Thread starter #4
Are you sure that code compiles successfully? I cannot see half the variables definitions... All the motxxxxx variables in main() and the ISR() are the ones in question? Arduino is C++ and these are defined automatically.. The XC8 compiler is just C so they need a definition..
Ahh yeah i missed that part sorry. All definations are ok in my codes but i forgot to add these part of the code here. You can be sure all definations fine ;)
 
Thread starter #5
Is this for position control or for just RPM, if so I would consider 8k/rev encoder Way higher than needed.
I don't program in C so can't help there, but have done it in Assembly, also Picmicro has a Pic with Quadrature encoder module, but even that might be overkill.
Also the lower resolution should solve any INT error.
Max.
Dear Max

As you can see there are two encoder pins for one motor ,which is mean its not for rpm only, its for position controll.
And second ; i can get position information via atmel 168 without missing any (almost) steps but same code is not working with pic although 16MHz Fosc freq. It s so weird !
Im missing something but what !?!?!?
 

Pommie

Well-Known Member
Most Helpful Member
#6
Interrupting at 38k on a 4MHz pic will only allow 103 cycles per interrupt - no wonder it misses a few.
To fix it you can either,
Switch to a faster chip - 16F18857 (48MHz= 12 MHz clock cycles).
Make your ISR more efficient - I have code that may work if you're interested.
Or, only interrupt on 1 inputs positive edge and then your other pin is the direction but at lower resolution.

Mike.
 
Thread starter #7
Interrupting at 38k on a 4MHz pic will only allow 103 cycles per interrupt - no wonder it misses a few.
To fix it you can either,
Switch to a faster chip - 16F18857 (48MHz= 12 MHz clock cycles).
Make your ISR more efficient - I have code that may work if you're interested.
Or, only interrupt on 1 inputs positive edge and then your other pin is the direction but at lower resolution.

Mike.
Dear Mike

Thanks for reply. Yes im fully interrestin your interrupt code you can send it to me if you wish. I cannot switch my device cause i have only these one for now :(
And can you tell me more details about the using one interrupt pin for encoder ? What is the differences between two pins and one pin ?? Is that more efficient ? Teach me please, im hungry and open to knowladge. Thanks :)
 
Thread starter #8
Interrupting at 38k on a 4MHz pic will only allow 103 cycles per interrupt - no wonder it misses a few.
To fix it you can either,
Switch to a faster chip - 16F18857 (48MHz= 12 MHz clock cycles).
Make your ISR more efficient - I have code that may work if you're interested.
Or, only interrupt on 1 inputs positive edge and then your other pin is the direction but at lower resolution.

Mike.
And also max value is 2^15. That means after that value , value is clearing ( 0 ).
I also need extra codes for fix that. Do you have any advice to me ? Thanks.
 

rjenkinsgb

Active Member
#9
Another possibility is to also feed one of the encoder signals to a counter input.

For high speed movements, you can compare counter values at regular intervals to get the amount of movement and speed, then switch back to reading individual edges at lower speeds when absolute position becomes important.

I also agree with just using one signal for the interrupt and reading the other at the start of the interrupt, to get the direction.

The interrupt could do nothing except increment or decrement a position-counter variable and leave all the other calculations in the main program loop, which should keep the interrupt duration to a minimum.
 

Pommie

Well-Known Member
Most Helpful Member
#10
The more I think about this the more I realize that it's impossible to do this without loosing counts. Stuttering (when an output is on an edge) will cause changes as your ISR is executing and cause errors. The technique of only interrupting on rising edges of one output can also loose counts. Does the absolute position have to completely repeatable? If so, then I think your only solution is a lower res encoder or convert your ISR to asm.

Mike.
BTW, the code I use for rotary encoders is,
Code:
  // the two inputs are B0 and B1
  const int8_t lookup_table[] = {0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0};
  rotateState<<=2|(PORTB&3);
  rotateState&=15;
  number+=lookup_table[rotateState];
Edit, if you make the variable "number" 32 bit then it will help with your wrapping problem.
 
Thread starter #11
as you guys said there are two possible solution.
First solution, lower rotor speed
Second is lower resolotion encoder.

What if i use temporary memo. Im not sure but i heard about the tecnique to write pulses into temporary memory and then process it through the motor. I do not know much more than these. Do you guys know that ?

Thanks for all replies :)
 

MaxHeadRoom78

Well-Known Member
#13
Did you look at 8-bit Microchip PIC that has Quadrature Encoder Interface (QEI) feature with noise filters like PIC18F2331, PIC18F2431, PIC18F4331 and PIC18F4431. These configure some of the related registers for position and velocity of the encoder, and can be obtained directly without serving the interrupt routine.
Max.
 

Latest threads

EE World Online Articles

Loading

 
Top