Il codice di prova e funzionante per metterlo alla prova è suffuciente un motore dc, un L293NE, un encoder in quadratura. Io ho usato una stampante dove nel carrello di stampa c'è un sensore ottico che tramite un timing strip (un nastro) realizzano un Encoder in quadratura da 150LPI.

Codice:

/*
Copyright 2010 Maurilio Pizzurro <maurotec@libero.it

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

/*
######################################################################################
#  Programma per il controllo di posizione tramite motore dc, encoder in             #  
#  quadratura da 150LPI con algortitmo PID.                                          #
#  --------------------------------------------------------------------------------- #
#  Risoluzione encoder in quadratura 150LPI x 4 = 600LPI                             #
#  Risoluzione in Inch 0.0016666                                                     #   
#  Risoluzione in mm   0.0042333                                                     #
#  Spostamento ncount = (mm / 0.0042333) / 10. Unità millimetri                      #
#  Spostamento ncount = (mill inch / 0.0016666) / 1000. Unità millesimi di pollice   #
#  Esempio:                                                                          #  
#  ncount = (254mm / 0.0042333) / 10 = 6000.047244466491862                          #      
#  ncount = (10.000mmInch / 0.0016666) / 1000 = 6000.240009600384015                 #
######################################################################################
Pin impegnati:

                PC6  1|    |28  PC5 (AI 5)
          (D 0) PD0  2|    |27  PC4 (AI 4)
          (D 1) PD1  3|    |26  PC3 (AI 3)
  ->      (D 2) PD2  4|    |25  PC2 (AI 2)      
  -> PWM+ (D 3) PD3  5|    |24  PC1 (AI 1)
  ->      (D 4) PD4  6|    |23  PC0 (AI 0)
                VCC  7|    |22  GND
                GND  8|    |21  AREF
                PB6  9|    |20  AVCC
                PB7 10|    |19  PB5 (D 13)
  -> PWM+ (D 5) PD5 11|    |18  PB4 (D 12)
     PWM+ (D 6) PD6 12|    |17  PB3 (D 11) PWM
          (D 7) PD7 13|    |16  PB2 (D 10) PWM
          (D 8) PB0 14|    |15  PB1 (D 9) PWM    <-
*/

#include "pins_arduino.h"
#include <PID_Beta6.h>

// Contains EEPROM.read() and EEPROM.write()
#include <EEPROM.h>

// ID of the settings block
#define CONFIG_VERSION "V01"

// Tell it where to store your config data in EEPROM
#define CONFIG_START 32

// Example settings structure
struct StoreStruct {
  // This is for mere detection if they are your settings
  char version[4];
  // The variables of your settings
  float Kc;
  float TauI;
  float TauD;
  float velocity;
} storage = {
  CONFIG_VERSION,
  // The default values
  0.0,    // Kc
  0.0,    // TauI
  0.0,    // TauD
  5.0     // velocity  
};

// **** Caricati da EEprom **** 
//Define Variables we'll be connecting to
double Setpoint, Input, Output;
// Parametri P I D, Kc, TauI, TauD
double Kc;
double TauI;
double TauD;
double velocity;
// ***** Caricati da EEprom ***** 

/* LoadAndSaveSettings
 * Joghurt 2010
 * Demonstrates how to load and save settings to the EEPROM
 */
void loadConfig() {
    // To make sure there are settings, and they are YOURS!
    // If nothing is found it will use the default settings.
    if (EEPROM.read(CONFIG_START + 0) == CONFIG_VERSION[0] &&
        EEPROM.read(CONFIG_START + 1) == CONFIG_VERSION[1] &&
        EEPROM.read(CONFIG_START + 2) == CONFIG_VERSION[2])
    {  
        for (unsigned int t=0; t<sizeof(storage); t++)
        *((char*)&storage + t) = EEPROM.read(CONFIG_START + t);
    }
}

void saveConfig() {
    noInterrupts();                 // disable IRQ
    for (unsigned int t=0; t<sizeof(storage); t++)
        EEPROM.write(CONFIG_START + t, *((char*)&storage + t));
    interrupts();                   // enable IRQ
}


// Software major and minor version
#define majorVersion 0
#define minorVersion 4
// version 0.3 introdotto il PID
// verione 0.4 ora il PID e la velocità sono impostabile da seriale 


// Encoder Pin
#define encoder0PinA 4  //PIND 0b00010000    channel A
#define encoder0PinB 5  //PIND 0b00100000    channel B

volatile long encoder0Pos = 0;                  // posizione corrente

volatile unsigned int stateEncoder0PinA;        // variabile di stato 
volatile unsigned int stateEncoder0PinB;        // variabile di stato

volatile unsigned int npulse = 0;
volatile unsigned int isrna = 0;

unsigned long startTime;
long period;                                    // periodo  

int position;

// modalità opertative 
#define operative 0
#define settings 1

int mode;                                       // mode = operative | settings
boolean mstop = false;                          // motor stop
boolean unsaved = false;
// routine interrupt
ISR(PCINT2_vect) 
{
    // Pin Arduino 4 (PD4 PIN6 PCINT20)
    // se lo stato di encoder0PinA è cambiato, registra lo stato corrente 
    // e chiama encoder0ChA()
    if ((PIND & 0b00010000) != stateEncoder0PinA)           //if (digitalRead(encoder0PinA) != stateEncoder0PinA) 
    {
        stateEncoder0PinA = (PIND & 0b00010000);            //stateEncoder0PinA = digitalRead(encoder0PinA);
        // misura il periodo della frequenza generata dall'encoder 
        isrna++;          // contatore di eventi 
        if (isrna == 3)
        {    
            period = micros() - startTime;       
            //velocity = 0.0211665 / (micros() - startTime) / 1000;
            Input = 0.0211665 / (period / 1000000.0);
            npulse = isrna;
            isrna = 1;
        }
        if (isrna == 1)            // salva il valore del timer in microsecondi
        {    
            startTime = micros();
        }
        encoder0ChA();
    }
    else
    {
        // Pin Arduino 5 (PD5 PIN11 PCINT21)
        // se lo stato di encoder0PinB è cambiato, registra lo stato corrente 
        // e chiama encoder0ChB()
        //if ((PIND & 0b00100000) != stateEncoder0PinB) 
        //if (digitalRead(encoder0PinB) != stateEncoder0PinB) 

        stateEncoder0PinB = (PIND & 0b00100000);            //stateEncoder0PinB = digitalRead(encoder0PinB);
        encoder0ChB();
    }
    if (encoder0Pos == position) mstop = true;
}


void encoder0ChA()
{
    if (stateEncoder0PinA) { 

        // check channel B to see which way encoder is turning
        if (PIND & 0b00100000)                              //if (digitalRead(encoder0PinB) == HIGH) 
        {  
            encoder0Pos--;         // CCW
        } 
        else 
        {
            encoder0Pos++;         // CW
        }
    }
    else
    { 
        // check channel B to see which way encoder is turning  
        if (PIND & 0b00100000)                              //if (digitalRead(encoder0PinB) == HIGH) 
        {   
            encoder0Pos++;          // CW
        } 
        else 
        {
            encoder0Pos--;          // CCW
        }
    }

}

void encoder0ChB()
{
    // check state changed on channel B
    if (stateEncoder0PinB) 
    {   
        // check channel A to see which way encoder is turning
        if (PIND & 0b00010000)                          //if (digitalRead(encoder0PinA) == HIGH) 
        {  
            encoder0Pos++;         // CW
        } 
        else 
        {
            encoder0Pos--;         // CCW
        }
    }
    else
    {
        // check channel A to see which way encoder is turning  
        if (PIND & 0b00010000)                      //if (digitalRead(encoder0PinA) == HIGH) 
        {   
            encoder0Pos--;          // CCW
        } 
        else 
        {
            encoder0Pos++;          // CW
        }
    }
}

#define ledPin 13
#define pwmPin 9
#define motor_N 2
#define motor_P 3

////Specify the links and initial tuning parameters
PID myPID(&Input, &Output, &Setpoint, Kc, TauI, TauD);


void setup()
{
    // prescaler 8 credo?
    TCCR1B |= (1<<CS11);

    Serial.begin(115200);
    Serial.flush();
    pinMode(ledPin, OUTPUT);
    pinMode(encoder0PinA, INPUT);
    pinMode(encoder0PinB, INPUT);
    pinMode(motor_N, OUTPUT);
    pinMode(motor_P, OUTPUT);
    pinMode(pwmPin, OUTPUT);
    stateEncoder0PinA = (PIND & 0b00010000);    //stateEncoder0PinA = digitalRead(encoder0PinA);
    stateEncoder0PinB = (PIND & 0b00100000);    //stateEncoder0PinB = digitalRead(encoder0PinB);

    loadConfig();                               // load data from EEprom
    Kc = storage.Kc;
    TauI = storage.TauI;
    TauD = storage.TauD;
    velocity = storage.velocity;
    // set PID 
    myPID.SetInputLimits(0, 100);
    myPID.SetOutputLimits(10, 255);
    myPID.SetSampleTime(500);
    myPID.SetTunings(Kc, TauI, TauD);           // necessario in questo caso
    myPID.SetMode(AUTO);                        //turn the PID on

    // PIN CHANGED ENABLED
    PCICR |= (1 << PCIE2);                      // Abilita l'iterrupt su PORTD
    PCMSK2 |= (1 << PCINT20);                   // Abbilita il vettore PCINT20 PD4 ARDUINO(4)
    PCMSK2 |= (1 << PCINT21);                   // Abbilita il vettore PCINT21 PD5 ARDUINO(5)

    mode = operative;
}


int byteCommand;            // byte ricevuti dalla seriale interpretati come comandi
#define onWards 1           // motore gira in senso orario
#define backWards -1        // motore gira in senso antiorario
#define motorOff 0          // motore spento
#define motorOn 1           // motore acceso 
int startSlow;              // partenza lenta
int goMotor = 0;            // goMotore può essere onWard o backWards
int motorPower = motorOff;  // motorPower può essere motorOff (default) o motorOn
double scaled;

char valueBuffer[9];
int target;
int action;
void operativeMode(int comm, char *value) 
{
    if (comm == 'g')
    {
        target = atoi(value);
        if (target < encoder0Pos)
        {
            action = 'I';
            position = target;
        }
        if (target > encoder0Pos)
        {
            action = 'A';
            position = target;
        }
    }

    if (action == 'I') 
    {

        action = 0;
        delay(2);
        startSlow = 270;
        Setpoint = velocity;
        scaled = Setpoint / 67;
        goMotor = backWards;
        motorPower = motorOn;
        isrna = 0;                      // azzera il contatore di interrupt del canale A
        digitalWrite(motor_N, HIGH);
        digitalWrite(motor_P, LOW);

    }
    if (action == 'A') 
    {
        action = 0;
        delay(2);
        startSlow = position - 270;
        Setpoint = velocity;
        scaled = Setpoint / 67;
        goMotor = onWards;
        motorPower = motorOn;
        isrna = 0;                      // azzera il contatore di interrupt del canale A
        digitalWrite(motor_P, HIGH);
        digitalWrite(motor_N, LOW);
    }
    if (comm == 'v') printVersion();
    if (comm == 'S') mode = settings;   // enter in setting mode
    comm = 0;
    value = "";
}

void settingsMode(int comm, char *value)
{
    if (comm == 'v') printVersion();        // print software version

    if (comm == 'x')                        // exit from settings mode return in operative mode
    {
        mode = operative;
        myPID.SetTunings(Kc, TauI, TauD);
    }

    if (comm == 'P')                        // imposta il valore per il guadagno del PID 
    {
        double old_kc = Kc;
        Kc = atof(value);
        if (Kc != old_kc) unsaved = true;
    }

    if (comm == 'I')                        // imposta il valore per l'ntegrale del PID
    {
        double old_TauI = TauI;
        TauI = atof(value);
        if (TauI != old_TauI) unsaved = true;
    }

    if (comm == 'D')                        // imposta il valore la derivata del PID
    {
        double old_TauD = TauD;
        TauD = atof(value);
        if (TauD != old_TauD) unsaved = true;
    }

    if (comm == 'V')                        // imposta la velocità
    {
        double old_velocity = velocity;
        velocity = atof(value);
        if (velocity != old_velocity) unsaved = true;
    }

    if (comm == 'Z')
    {
        Serial.println("");
        Serial.println("Uman readable PID data dump:");
        Serial.print("Kc   = ");
        Serial.println(Kc);
        Serial.print("TauI = ");
        Serial.println(TauI);
        Serial.print("TauD = ");
        Serial.println(TauD);
        Serial.println("--------------");
        Serial.print("Current position = ");
        Serial.println(encoder0Pos);
        Serial.print("Current setpoint = ");
        Serial.println(Setpoint);
        Serial.print("Velocity = ");
        Serial.println(velocity);
        if (unsaved)
        {
            Serial.println("** Unsaved **");
        }
        else
        {
            Serial.println("Saved Ok");
        }
        Serial.println("");
    }

    if (comm == 'z')
    {
        Serial.println("begin");
        Serial.println(Kc);
        Serial.println(TauI);
        Serial.println(TauD);
        Serial.println(encoder0Pos);
        Serial.println(Setpoint);
        Serial.println(velocity);
        Serial.println("end");
    }

    if (comm == 's')
    {
        storage.Kc = Kc;
        storage.TauI = TauI;
        storage.TauD = TauD;
        storage.velocity = velocity;
        saveConfig();
        myPID.SetTunings(Kc, TauI, TauD);
        mode = operative;
        unsaved = false;
    }
    comm = 0;
    value = "";
}


void printVersion()
{    
      Serial.println("Software Major Versione");  
      Serial.println(majorVersion);
      Serial.println("Software Minor Versione");  
      Serial.println(minorVersion);
}

int nByte;
int nByteSave;
boolean busyState = false;

void ready()              // Invia 254 via seriale 
{
    delay(2);
    busyState = false;
    Serial.print(254);    // ready state
}

void busy()              // Invia 255 via seriale
{
    delay(2);
    busyState = true;
    Serial.print(255);    // busy state    
}

// Loop infinito 

void loop() 
{

    if (encoder0Pos == position) 
    {
        digitalWrite(ledPin, HIGH);
    }
    else
    {
        digitalWrite(ledPin, LOW);
    }  

    if (mstop)                       // il motore deve essere fermato
    {
        motorPower = motorOff;
        digitalWrite(motor_P, LOW);
        digitalWrite(motor_N, LOW);
        analogWrite(pwmPin, 0);
        mstop = false;
    } 

    if (motorPower == motorOn)      // Il motore è acceso
    {
        if (goMotor == onWards)    // il motore deve girare in senso orario
        {   
            if ((encoder0Pos > startSlow) && (npulse == 3))
            {
                npulse = 0; 
                Setpoint = Setpoint - scaled;       // scala la velocità
            }    
            //if (encoder0Pos < 200) i = i + 0.005; 
            myPID.Compute();                        // computa il PID
            analogWrite(pwmPin, int(Output));       // inposta la velocità calcolata dal PID

        }

        if (goMotor == backWards)                   // il motore deve girare in senso antiorario
        {    
            if ((encoder0Pos < startSlow)  && (npulse == 3))
            {    
                npulse = 0; 
                Setpoint = Setpoint - scaled;       // scala la velocità
            }    
            myPID.Compute();                        // computa il PID
            analogWrite(pwmPin, int(Output));       // inposta la velocità calcolata dal PID
        }
    }
    // se il micro non ha molto da fare nByte e sempre 1 anche se sono stati spediti più byte
    // quindi nByte è indicatore di presenza dati nel buffer e non quantità

    /*if ((motorPower == motorOff) && (busyState == true))
    {
        ready();   // ready state
    }*/
    nByte = Serial.available();         
    if (nByte > 0)                         
    {
        if (true)              // ready state 
        //if (busyState == false)               // ready state 
        {
            //busy();                           // busy state impegna il bus
            delay(10);                          // attende 10ms per dare il tempo alla routine di interrupt seriale 
                                                // di accumulare caratteri, forse basta anche busy()  
            nByte = Serial.available();         // ora nByte ha valore quantitativo

            if (nByte == 1) 
            {
                byteCommand = Serial.read();
            }
            else if (nByte > 1)
            {
                byteCommand = Serial.read();
                // Riempi il buffer di comando
                for (int i = 0; i < 9; i++)
                {
                    valueBuffer[i] = Serial.read();
                }

            } 
            Serial.flush();

            if (mode == operative)
            {  
                operativeMode(byteCommand, valueBuffer);

            }
            if (mode == settings)
            {    
                settingsMode(byteCommand, valueBuffer);
                //ready();                                   // ready state
            }
        }
        else
        {
            if (motorPower == motorOn) 
            {
                //busy();          // Il bus è impegnato ed sono arrivati altri dati ma non possono essere processati
                                 // allora il bus risponde occupato
                Serial.flush();  // pulisce il buffer
            }
        }  
    }  

} // end loop

Share