/* ************************************************************** 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(); } } }