/* (C) Eitan Sherer & Kenji Hashimoto ECE476 Final Project: Inverted Pendulum Balancer BALANCER W/PID CONTROL Code */ #include #define init_motor_period 300 // initial PWM signal period #define init_motor_duty 150 // initial PWM duty-cycle period #define init_sensor_index 128 // initial sensor index #define max_motor_duty 95 // MAX motor duty-cycle percentage #define min_motor_duty 10 // MIN motor duty-cycle percentage #define safety_timer 200 // safety feature timer #define safety_speed 90 // safety feature minimum speed #define safety_angle 115 // saftey angle offset /* PID Controller Constants */ #define kp 21.2 // Proportional #define ki 0 // Integral #define kd 0.001 // Derivative /* * Global Variable Declarations: */ unsigned char motor_power; // motor power bit unsigned int motor_PWM_duty; // PWM signal duty-cycle length unsigned int motor_PWM_period; // PWM signal period unsigned char motor_break_bit; // motor break bit unsigned char motor_timer; // motor timer unsigned char sensor_index; // sensor index (+ CWR; - CCWR) unsigned char sensor_channel; // sensor channel (bit 1=Ch.B; bit 0=Ch.A) char pid_prev_error; // previous error for derivative calc int pid_sum_error; // sum of errors for intergral calc unsigned char safety_forward; // forward direc counter for safety unsigned char safety_backward; // backward direc counter for safety /* * Function Declarations: */ void initialize(); // initializes ports, timers, interrupts, and global vars void pid_controller(); // PID controller void safety_check(char); // safety check feature void motor_updateDuty(); // sets motor_PWM_duty value to OCR1AH and OCR1AL void motor_updatePeriod(); // sets motor_PWM_period value to ICR1H and ICR1L void motor_break(char); // controls the motor's break signal void motor_direction(char); // controls the motor's direction signal void motor_controller(int,char); // center of motor control /* * Interrupt Service Routines: */ // Timer 0 Interrupt triggered by Overflow interrupt [TIM0_OVF] void timer0_ovfl(void) { if (motor_timer > 0) { motor_timer--; } } // External Interrupt triggered by Encoder's Channel A interrupt [EXT_INT0] void ext_int0_isr(void) { sensor_channel ^= 0x01; if (sensor_channel == 1) { sensor_index--; } } // External Interrupt triggered by Encoder's Channel B interrupt [EXT_INT1] void ext_int1_isr(void) { sensor_channel ^= 0x02; if (sensor_channel == 2) { sensor_index++; } } /* * FUNCTION: main() */ void main(void) { char yellow_led; yellow_led = 0; initialize(); while(1) { // Yellow LED to indicates that the reset of MCU if (sensor_index > 120 && sensor_index < 136 && yellow_led == 0) { PORTA |= 0b00000100; // yellow LED } else { yellow_led = 1; PORTA &= 0b11111011; // yellow LED } pid_controller(); } } /* * CONTROLLER FUNCTION: pid_controller() */ void pid_controller() { char error; float prop_term; float integ_term; float deriv_term; float sum_terms; prop_term = 0; integ_term = 0; deriv_term = 0; sum_terms = 0; int velocity; char direction; // calculate error error = sensor_index - init_sensor_index; // Propotional term calc of PID prop_term = kp*(float)error; sum_terms += prop_term; // Integeral term calc of PID if (ki != 0) { pid_sum_error += error; integ_term = ki*(float)pid_sum_error; sum_terms += integ_term; } // Derivative term calc of PID if (kd != 0) { deriv_term = kd*(float)(error-pid_prev_error); pid_prev_error = error; sum_terms += deriv_term; } // Take the absolute value and set direction bit if (sum_terms > 0) { direction = 0; } else { direction = 1; sum_terms *= -1; } // Update Green LED's if (direction == 0) { PORTA |= 0b00010000; PORTA &= 0b11111110; } else { PORTA |= 0b00000001; PORTA &= 0b11101111; } // check max threshold of the duty-cycle perentage if (sum_terms > max_motor_duty) { velocity = max_motor_duty; } else { velocity = (int)sum_terms; } if(motor_power == 1) motor_controller(velocity,direction); safety_check(direction); } /* * CONTROLLER FUNCTION: safety_check() */ void safety_check(char dir) { if (motor_PWM_duty > safety_speed) { if (dir == 0) { safety_forward++; safety_backward = 0; } else if (dir == 1) { safety_backward++; safety_forward = 0; } } else { safety_forward = 0; safety_backward = 0; } if (safety_forward == safety_timer || safety_backward == safety_timer || sensor_index < init_sensor_index-safety_angle || sensor_index > init_sensor_index+safety_angle) { motor_power = 0; PORTA = 0b10000000; } } /* * MOTOR FUNCTION: motor_controller(char vel, char dir) */ void motor_controller(int vel, char dir) { // SPEED motor_PWM_duty = vel*motor_PWM_period/100; motor_updateDuty(); // DIRECTION motor_direction(dir); // TIME motor_timer = 2; // 0.008 sec interval, 122Hz // break OFF if (motor_PWM_duty > min_motor_duty) motor_break(0); // Wait until timer runs out while (motor_timer > 0) {} // break ON motor_break(1); } /* * MOTOR FUNCTION: motor_break(char param) * param: 0 - break OFF * 1 - break ON */ void motor_break(char param) { switch (param) { case 0: // break OFF PORTD = PORTD & 0b11101111; break; case 1: // break ON PORTD = PORTD | 0b00010000; break; default: break; } } /* * MOTOR FUNCTION: motor_direction(char param) * param: 0 - FORWARD * 1 - BACK * 2 - toggle */ void motor_direction(char param) { motor_break(1); switch (param) { case 0: // FORWARD PORTD = PORTD & 0b10111111; break; case 1: // BACK PORTD = PORTD | 0b01000000; break; case 2: // toggle PORTD = PORTD ^ 0b01000000; break; default: break; } motor_break(motor_break_bit); } /* * MOTOR FUNCTION: motor_updateDuty() */ void motor_updateDuty() { char lowByte; char highByte; lowByte = (char)(motor_PWM_duty); highByte = (char)(motor_PWM_duty >> 8); OCR1AH = highByte; OCR1AL = lowByte; } /* * MOTOR FUNCTION: motor_updatePeriod() */ void motor_updatePeriod() { char lowByte; char highByte; lowByte = (char)(motor_PWM_period); highByte = (char)(motor_PWM_period >> 8); ICR1H = highByte; ICR1L = lowByte; } /* * FUNCTION: initialize() */ void initialize() { // Ports //D.2 Ch A input of Encoder --> triggers INT0 //D.3 Ch B input of Encoder --> triggers INT1 //D.4 output for motor's break signal //D.5 OC1A output for motor's PWM signal //D.6 output for motor's direction signal DDRD = 0b01110000; // LED's //A.0 Green LED to indicate direction //A.2 Yellow LED for debugging purposes //A.4 Green LED to indicate direction //A.7 Red LED to indicate stop of motor operations DDRA = 0xff; PORTA = 0x00; // Timer 0 /* TCCR0: [7] Force Output Compare = 0 [6,3] Waveform Generation Mode = 00 (normal) [5,4] Compare Match Output Mode = 00 (nornaml operation, OC0 disconnected) [2,1,0] Clock Select = 100 (clk/256) */ TCCR0 = 0b00000100; // Timer 1 /* TCCR1A: [7,6] Compare Output Mode for Channel A = 10 (clear on Comp Match, set at TOP) [5,4] Compare Output Mode for Channel B = 00 (disconnected) [3,2] Force Output Compare for Channel A/B = 00 [1,0] Waveform Generation Mode bits 11 & 10 = 10 (Fast PWM w/ TOP=ICR1) */ TCCR1A = 0b10000010; /* TCCR1B: [7] Input Capture Noise Canceler = 0 [6] Input Capture Edge Select = 0 [5] Reserved Bit = 0 [4,3] Waveform Generation Mode bits 13 & 12 = 11 (Fast PWM w/TOP=ICR1) [2,1,0] Clock Select = 100 (clk/256) */ TCCR1B = 0b00011100; /* TIMSK: [7] Timer/Counter2, Output Compare Match Interrupt Enable = 0 [6] Timer/Counter2, Overflow Interrupt Enable = 0 [5] Timer/Counter1, Input Capture Interrupt Enable = 0 [4] Timer/Counter1, Output Compare A Match Interrupt Enable = 0 [3] Timer/Counter1, Output Compare B Match Interrupt Enable = 0 [2] Timer/Counter1, Overflow Interrupt Enable = 0 [1] Timer/Counter0, Output Compare Match Interrupt Enable = 0 [0] Timer/Counter0, Overflow Interrupt Enable = 1 */ TIMSK = 0b00000001; // External Interrupt /* MCUCR - MCU Control Register: [3,2] Interrupt Sense Control 1 = 01 (ISR on logical change) [1,0] Interrupt Sense Control 0 = 01 (ISR on logical change) */ MCUCR = 0b00000101; /* GICR - General Interrupt Control Register: [7] External Interrupt Request 1 Enable = 1 [6] External Interrupt Request 0 Enable = 1 [5] External Interrupt Request 2 Enable = 0 */ GICR = 0b11000000; // Global Variables motor_PWM_period = init_motor_period; motor_updatePeriod(); motor_PWM_duty = init_motor_duty; motor_updateDuty(); motor_break_bit = 1; motor_break(motor_break_bit); motor_power = 1; motor_timer = 0; sensor_index = init_sensor_index; sensor_channel = (PIND & 0b00001100) >> 2; pid_prev_error = 0; pid_sum_error = 0; safety_forward = 0; safety_backward = 0; // Enable Interrupts #asm ("sei"); }