Skip to content
Snippets Groups Projects
Select Git revision
  • master default protected
1 result

vel_control.c

Blame
  • vel_control.c 11.98 KiB
    /*
    **************************************************************
    
     Current regulation - Pontus Giselsson, Per-Ola Larsson 18/02/09
               for LTH - reglerteknik
    
    ***************************************************************
    */
    #include <util/twi.h>
    #include <avr/io.h>
    #include <avr/interrupt.h>
    #include <inttypes.h>
    
    #include "vel_control.h"
    #include "serialio.h"
    
    /*
     * Serial I/O assignments
     *
     *   AO 0 -- Axis 1  motor voltage
     *   A0 1 -- Axis 1  velocity reference !
     *
     *   EI 0 -- Axis 1  position !
     *   EI 1 -- Axis 1  filtered velocity !
     *   EI 5 -- Axis 1  position with predicted fraction
     *   EI 6 -- Axis 1  position unquantized
     *
     *   AI 2 -- Axis 1  current
     *   AI 3 -- Pendulum angle !
     *
     *   AI 4 -- Axis 1  motor voltage (actual)
     *
     *   DI 0 -- Axis 1  endpoint sensor
     */
    
    #define POLL_AXIS1_POSITION      0x01
    #define POLL_AXIS1_VELOCITY      0x02
    #define POLL_PEND_ANGLE          0x04
    #define POLL_CURRENT_REFERENCE   0x08
    //#define POLL_AXIS1_RESET         0x0008
    #define POLL_CONFIG              0x80
    
    static volatile uint8_t pccom_poll=0;
    
    
    // reference variables
    struct {
      volatile uint16_t samples; // nbr of samples since last ctrl-ref update
      struct {
        volatile unsigned char pending;
        volatile int32_t next;
        volatile int32_t value;
      } vel;
      struct {
        volatile int32_t value;
      } acc;
    } ref = {
      .samples = 0,
      .vel.pending = 0,
      .vel.next = 0,
      .vel.value = 0,
      .acc.value = 0,
    };
    volatile int32_t refCtrl = 0;    // ref used in ctrl-loop (=ref sent from simulink)
    
    // velocity control variables
    volatile int32_t u = 0;          // 11 frac bits
    volatile int8_t brake = 0;       // brake variable if pos-sample missed
    volatile int32_t I = 0;          // 11 frac bits
    
    volatile int32_t K = 807;        // 6 frac bits, prop constant
    volatile int32_t Ke = 13;        // 6 frac bits, integral constant
    volatile int8_t fr_comp = (10<<3);
    #define V_MAX (120<<4)
    #define V_MIN (-120<<4)
    
    
    // encoder variables
    #define ENCODERY  (PIND&0x04)        //Positional encoder pins
    #define ENCODERX  ((PINB&0x02)<<1)   //Positional encoder pins (shift one step for faster comparison with Y)
    
    // position variables
    volatile int16_t pos = 0;        // position in tics
    volatile int16_t posCtrl = 0;    // position used in ctrl-loop
    volatile int16_t oldPos = 0;     // previous pos used for velocity estimation
    volatile int8_t newX;            // encoder signal
    volatile int8_t newY;            // encoder signal
    volatile int8_t oldX;            // previous encoder signal
    volatile int8_t oldY;            // previous encoder signal
    
    // velocity estimation parameters
    volatile int32_t velEst = 0;     // vel-estimate, 5 frac bits
    int16_t a = 116;                 // 7 frac bits (parameter in velocity estimate)
    int16_t b = 152;                 // 5 frac bits (parameter in velocity estimate)
    
    // adc measurement variables
    volatile int16_t low;            // temporary variable for ad-reading
    volatile int16_t high;           // temporary variable for ad-reading
    volatile int16_t angleOffset = 0;
    
    /* return position (in tics) */
    int32_t getPosition() {
      int16_t result = 0;
      cli();
      result = pos;
      sei();
      return (result);
    }
    
    
    /* return velocity (in mm/s) */
    int32_t getVelocity() {
      int32_t result;
      cli();
      result = velEst;
      sei();
      return result;
    }
    
    
    /* return last angle measurement */
    int16_t getAngle() {
      int16_t adc = ADC;
      while (adc != ADC) {
        adc = ADC;
      }
      return adc - 512 - angleOffset;
    }
    
    
    /* return current-reference */
    int32_t getCurrentRef() {
      int32_t result;
      cli();
      result = u;
      sei();
      return result;
    }
    
    
    /* Set new acceleration reference value */
    void setAccRef(int32_t newAccRef) {
      // Called from serial interrupt, so should be atomic by itself
      if (ref.vel.pending) {
        ref.vel.pending = 0;
        ref.vel.value = ref.vel.next;
        ref.acc.value = newAccRef;
        ref.samples = 0;
      } else {
        // TODO: report error
      }
    }
    
    
    /* Set new velocity reference value */
    void setVelRef(int32_t newVelRef) {
      // Called from serial interrupt, so should be atomic by itself
      ref.vel.pending = 1;
      ref.vel.next = newVelRef;
    }
    
    /* Routine used to initialize the positional encoders */
    void initPos()
    {
      oldX = ENCODERX;
      oldY = ENCODERY;
    }
    
    /* Timer 2, Encoder counter, 73 kHz updates position variable */
    SIGNAL(TIMER2_COMP_vect) {
      // Update position from encoder
      newX = ENCODERX;
      newY = ENCODERY;
      if((newX != oldX) || (newY != oldY)) { //Check if any value changed
        /*
          sum = (oldX<<2)+oldY+newX+(newY>>2);  
          if (sum == 2 || sum == 4 || sum == 11 || sum == 13) {
          pos = pos+1;
          } else if (sum == 1 || sum == 7 || sum ==  8 || sum == 14) {
          pos = pos-1;
          } else {
          brake = 1; // emergency brake
          }
        */
        // Works like if-clauses above, but faster!
        if ((oldX == newY) && (oldY != newX)) {
          pos = pos+1;
        } else if ((oldX != newY) && (oldY == newX)) {
          pos = pos-1;
        } else {
          brake = 1;
        }
        oldX = newX;
        oldY = newY;
      }
    }
    
    SIGNAL(USART_RXC_vect) {
      char ch = UDR;
      switch (serialio_RXC(ch)) {
        case serialio_clearbit: {
          switch (serialio_channel) {
          }
        } break;
        case serialio_setbit: {
          switch (serialio_channel) {
          }
        } break;
        case serialio_pollbit: {
          switch (serialio_channel) {
            //case 0: { addPoll(POLL_AXIS1_RESET); } break;
          }
        } break;
        case serialio_pollchannel: {
          switch (serialio_channel) {
            case 0:  pccom_poll |= POLL_AXIS1_POSITION;    break;
            case 1:  pccom_poll |= POLL_AXIS1_VELOCITY;    break;
            case 2:  pccom_poll |= POLL_PEND_ANGLE;        break;
            case 3:  pccom_poll |= POLL_CURRENT_REFERENCE; break;
            case 31: pccom_poll |= POLL_CONFIG;            break;
          }
        } break;
        case serialio_setchannel: {
          switch (serialio_channel) {
            case 0: { 
              setVelRef(serialio_value - (1L<<12));
            } break;
            case 1: {
              setAccRef(serialio_value - (1L<<15));
            } break;
          }
        } break;
        case serialio_more: {
        } break;
        case serialio_error: {
        } break;
      }
    }
    
    SIGNAL(TWI_vect) {
      unsigned char twsr = TWSR;
      switch (twsr) {
        case 0x08: {
          TWDR = 0x02; // slave is 0x02 (sla+w)
          TWCR = _BV(TWINT)|_BV(TWEN)|_BV(TWIE);
        } break;
        case 0x18:
        case 0x20: {
          TWDR = u;
          TWCR = _BV(TWINT)|_BV(TWEN)|_BV(TWIE);
        } break;
        case 0x28:
        case 0x30: {
          TWCR = _BV(TWINT)|_BV(TWEN)|_BV(TWSTO)|_BV(TWIE);
        } break;
        default: {
          // This should never happen
          TWCR = (_BV(TWINT)|_BV(TWEN)|_BV(TWIE));
        } break;
      }
    }
    
    /* Timer 0, control loop , 1 kHz */
    SIGNAL(TIMER1_COMPA_vect) {
      if (ref.samples <= 500) {
        ref.samples++;
      }
      posCtrl = pos; // store pos to use in this loop
      int32_t vel_ref = ref.vel.value;
      int32_t acc_ref = ref.acc.value;
      int16_t samples = ref.samples;
      sei(); // to enable interupts from encoder counter, serial and TWI
    
      // velocity estimation in mm/s
      velEst = (((a*velEst+64)>>7)+b*(posCtrl-oldPos));  // 5 fracbits on velEst
      oldPos = posCtrl;
    
      if (samples > 500) {
        // Communication lost, stop and reset
        I = 0;
        // Protect u and TWCR for concurrent updates
        cli();
        u = 0;
      } else {
        // store velEst and ref to be sent/used here
        refCtrl = vel_ref + ((acc_ref*samples)>>10);  //shift nbrSamples 10 steps (= nbrSamples*h)
    
        // control error
        int32_t e = refCtrl-((velEst+16)>>5);  // mm/s
    
        // temporary ctrl-signal
        int32_t v = (((K*e+(1<<5))>>6)+((I+(1<<3))>>4));
    
        // friction compensation
        if (refCtrl > 0) {
          v = v+fr_comp;
        } else if (refCtrl < 0) {
          v = v-fr_comp;
        }
    
        // variable that decides if I-part should be updated
        int8_t intCond = 1;
      
        // saturation of v
        int32_t vSat;
        if (v > V_MAX) {
          vSat = V_MAX;
          if (e > 0) { intCond = 0; }
        } else if (v < V_MIN) {
          vSat = V_MIN;
          if (e < 0) { intCond = 0; }
        } else {
          vSat = v;
        }
      
        if (intCond) {
          I = I + (((Ke*e)+(1<<1))>>2);
        }
    
        // Protect u and TWCR for concurrent updates
        cli();
        
        // scale ctrl-signal to send over twi
        u = (vSat+8)>>4; // u=127 gives current = 6.75 A, vSat makes u saturate at 114
      }
    
      // TWI-communication to set current reference on the other atmel
      // send start command
      if (TWCR == (_BV(TWEN)|_BV(TWIE)) || TWCR == 0) {
        TWCR = _BV(TWEN)|_BV(TWSTA)|_BV(TWIE);
      }
    }
    
    
    int main()
    {
      cli();
      
      //Port directions
      PORTD = 0x40;  // pull up on reset switch
    
    
      /* Timer section */
      // Enable timer1, timer2 compare match interrupts
      TIMSK = _BV(OCIE1A)|_BV(OCIE2);
      
      /* Timer 2, 73 kHz Prescaler 1, encoder counter for position measurement */
      TCCR2 = _BV(WGM21)|_BV(CS20);
      OCR2 = 200;
    
      /* Reset timer 2 */
      TCNT2 = 0;
      
      /* Timer 1, 1 kHz , prescaler 1, ctrl-loop */
      TCCR1B = _BV(WGM12)|_BV(CS10);
      OCR1AH = 0x38;
      OCR1AL = 0x3f;
      TCNT1H = 0;
      TCNT1L = 0;
      
    
      /* Timer 0, 40 kHz, Prescaler 8, serial communication */
      TCCR0 = _BV(WGM01)|_BV(CS01);
      //OCR0 = 184-1; // 10 kHz
      OCR0 = 45-1; // 40 kHz
       /* Reset timer 0 */
      TCNT0 = 0;
    
    
      // syncronization (ctrl interrupt 34 micros before communication interrupt)
      TCNT1 = TCNT0*8+500;
      
      
      //Serial communication initialization
      UCSRA = 0x00;     // USART: 
      UCSRB = 0x98;     // USART: RxIntEnable|RxEnable|TxEnable 
      UCSRC = 0x86;     // USART: 8bit, no parity
      UBRRH = 0x0;      // USART: 115200 @ 14.7456MHz
      UBRRL = 7;        // USART: 115200 @ 14.7456MHz
      
      
      /* AREF (AREF is 5V) pin external capacitor, ADC3 for pendulum angle */
      ADMUX = _BV(REFS0)|_BV(MUX0)|_BV(MUX1);
      
      // Enable ADC on adc3, start first conversion, prescaler 128, free running mode
      ADCSRA = _BV(ADEN)|_BV(ADSC)|_BV(ADATE)|_BV(ADPS2)|_BV(ADPS1)|_BV(ADPS0);
    
    
      // Initialize Master TWI
      TWBR = 0x10;  // set SCL-frequency CPU-freq/(16+2*16)
      TWCR = _BV(TWEN)|_BV(TWIE);
    
      // initialize position measurements
      initPos();
    
      // initialize pc-communication
      serialio_init();
      pccom_poll = 0;
      
      //Enable interrupts
      sei();
      
    
      // loop
      while (1) {
        unsigned char to_poll;
        // reset position, velocity estimate and integral part of ctrl if reset button pushed
        if (!(PIND & 0x40)) {
          cli();
          low = ADCL;
          high = ADCH;
          pos = 0; // reset position
          angleOffset =  ((int16_t) ((high<<8) | low) - 512);
          oldPos = 0;
          velEst = 0; // reset velocity estimate
          I = 0; // reset integral part of controller
          u = 0; // reset ctrl signal
          sei();
        }
        cli();
        to_poll = pccom_poll;
        pccom_poll = 0;
        sei();    
        if (to_poll & POLL_AXIS1_POSITION) {
          serialio_putchannel(0, getPosition()+(1L<<15)); 
        }
        if (to_poll & POLL_AXIS1_VELOCITY) {
          serialio_putchannel(1, getVelocity()+(1L<<17)); 
        }
        if (to_poll & POLL_PEND_ANGLE) {
          serialio_putchannel(2, getAngle()+(1L<<10)); 
        }
        if (to_poll & POLL_CURRENT_REFERENCE) {
          serialio_putchannel(3, getCurrentRef()+(1L<<7)); 
        }
        if (to_poll & POLL_CONFIG) {
          CONF_ANALOG_IN(0, CONF_RESOLUTION(16));     // Position (now reference)
          CONF_ANALOG_IN(0, CONF_MIN(CONF_NEGATIVE_VOLT(1)));
          CONF_ANALOG_IN(0, CONF_MAX(CONF_POSITIVE_VOLT(1)));
    
          CONF_ANALOG_IN(1, CONF_RESOLUTION(18));     // Velocity estimate
          CONF_ANALOG_IN(1, CONF_MIN(CONF_NEGATIVE_VOLT(1)));
          CONF_ANALOG_IN(1, CONF_MAX(CONF_POSITIVE_VOLT(1)));
    
          CONF_ANALOG_IN(2, CONF_RESOLUTION(11));     // Pend angle measurement
          CONF_ANALOG_IN(2, CONF_MIN(CONF_NEGATIVE_VOLT(1)));
          CONF_ANALOG_IN(2, CONF_MAX(CONF_POSITIVE_VOLT(1)));
    
          CONF_ANALOG_IN(3, CONF_RESOLUTION(8));     // Current reference
          CONF_ANALOG_IN(3, CONF_MIN(CONF_NEGATIVE_VOLT(1)));
          CONF_ANALOG_IN(3, CONF_MAX(CONF_POSITIVE_VOLT(1)));
    
          CONF_ANALOG_OUT(0, CONF_RESOLUTION(13));    // Reference to vel-ctrl
          CONF_ANALOG_OUT(0, CONF_MIN(CONF_NEGATIVE_VOLT(1)));
          CONF_ANALOG_OUT(0, CONF_MAX(CONF_POSITIVE_VOLT(1)));
    
          CONF_ANALOG_OUT(1, CONF_RESOLUTION(16));    // Reference to acc-ctrl
          CONF_ANALOG_OUT(1, CONF_MIN(CONF_NEGATIVE_VOLT(1)));
          CONF_ANALOG_OUT(1, CONF_MAX(CONF_POSITIVE_VOLT(1)));
    
          CONF_END();
        }
      }
    }