Don't like ads? PRO users don't see any ads ;-)
Guest

Untitled

By: a guest on Jun 17th, 2012  |  syntax: None  |  size: 7.41 KB  |  hits: 16  |  expires: Never
download  |  raw  |  embed  |  report abuse  |  print
Text below is selected. Please press Ctrl+C to copy to your clipboard. (⌘+C on Mac)
  1. #define F_CPU 20000000
  2. #include <avr/io.h>
  3. #include <avr/interrupt.h>
  4. #include <util/delay.h>
  5. #include <stdio.h>
  6. #include <math.h>
  7.  
  8. /*
  9.     -----------------------
  10.     Self-Balancing Unicycle
  11.     Stephan Boyer, 2011
  12.     boyers@mit.edu
  13.     -----------------------
  14.  
  15.     This should be burned onto an ATMEGA328P.
  16.     To set the clock source to be an external
  17.     20MHz crystal, set the low fuse bit to 0x67
  18.     like this:
  19.  
  20.     avrdude -c usbtiny -p atmega328p -P com1 -U lfuse:w:0x67:m
  21.  
  22.     pins:
  23.  
  24.     PC0 - analog angular acceleration
  25.     PC1 - analog y-acceleration
  26.     PC2 - analog z-acceleration
  27.     PD5 - motor controller (forward)
  28.     PD6 - motor controller (backward)
  29.     PD7 - kill switch (high: "killed")
  30.  
  31. */
  32.  
  33. ////////////////////////////////////////////////////////////////////////////////
  34. // utilities
  35. ////////////////////////////////////////////////////////////////////////////////
  36.  
  37. // read from an analog input, returns value in range [0.0f, 1.0f] (corresponding to [0.0, 5.0] V)
  38. float adc_read(uint8_t channel) {
  39.     ADMUX = 0b01000000|(channel&0b00001111);
  40.     ADCSRB = 0b00000000;
  41.     ADCSRA = 0b11000111;
  42.     while (ADCSRA&0b01000000);
  43.     uint8_t low = ADCL;
  44.     return ((float)((ADCH<<8)|low))/1023.0f;
  45. }
  46.  
  47. // wrap an angle to the range [-pi/2, pi/2)
  48. float wrap_angle(float angle) {
  49.     while (angle >= M_PI)
  50.         angle -= 2.0f*M_PI;
  51.     while (angle < -M_PI)
  52.         angle += 2.0f*M_PI;
  53.     return angle;
  54. }
  55.  
  56. ////////////////////////////////////////////////////////////////////////////////
  57. // motor controller interface
  58. ////////////////////////////////////////////////////////////////////////////////
  59.  
  60. // set up the motor
  61. void motor_init(void) {
  62.     // make sure the motor starts out off
  63.     PORTD &= ~(1<<5);
  64.     PORTD &= ~(1<<6);
  65.    
  66.     // set the pin modes for the motor outputs
  67.     DDRD |= (1<<5);
  68.     DDRD |= (1<<6);
  69.  
  70.     // set up TIMER0 to PWM at 312500 Hz exactly (PWM frequency: 1220.703125 Hz)
  71.     TCCR0A = 0b00000011;
  72.     TCCR0B = 0b00000010;
  73.     TIMSK0 = 0b00000000;
  74. }
  75.  
  76. // set the speed of the motor from -1.0f to 1.0f
  77. void set_motor_speed(float speed) {
  78.     // forward
  79.     if (speed > 0.0f) {
  80.         // disable PWM output on both motor pins
  81.         TCCR0A = 0b00000011;
  82.  
  83.         // turn off the reverse output
  84.         PORTD &= ~(1<<6);
  85.  
  86.         // set the PWM frequency
  87.         OCR0A = (uint8_t)(speed*255.5f);
  88.  
  89.         // enable PWM output on the forward pin
  90.         TCCR0A = 0b10000011;
  91.     }
  92.  
  93.     // reverse
  94.     if (speed < 0.0f) {
  95.         // disable PWM output on both motor pins
  96.         TCCR0A = 0b00000011;
  97.  
  98.         // turn off the forward output
  99.         PORTD &= ~(1<<5);
  100.  
  101.         // set the PWM frequency
  102.         OCR0B = (uint8_t)(-speed*255.5f);
  103.  
  104.         // enable PWM output on the reverse pin
  105.         TCCR0A = 0b00100011;
  106.     }
  107.  
  108.     // coast
  109.     if (speed == 0.0f) {
  110.         // disable PWM output on both motor pins
  111.         TCCR0A = 0b00000011;
  112.  
  113.         // turn off both outputs
  114.         PORTD &= ~(1<<5);
  115.         PORTD &= ~(1<<6);
  116.     }
  117. }
  118.  
  119. ////////////////////////////////////////////////////////////////////////////////
  120. // IMU interface
  121. ////////////////////////////////////////////////////////////////////////////////
  122.  
  123. // the offset to subtract from the accelerometer angle (depends on the desired orientation and how the sensor is mounted)
  124. #define OFFSET_ANGLE    (-M_PI-0.1f)
  125.  
  126. // the gyro neutral point (adjusted over time)
  127. volatile float gyro_neutral;
  128.  
  129. // initialize the IMU
  130. void imu_init(void) {
  131.     // set the gyro pin to input mode
  132.     DDRC &= ~(1<<0);
  133.  
  134.     // set the accelerometer pins to input mode
  135.     DDRC &= ~(1<<1);
  136.     DDRC &= ~(1<<2);
  137. }
  138.  
  139. // compute the angle from the accelerometer (radians)
  140. float get_accelerometer_angle(void) {
  141.     // read the voltages on the accelerometer pins
  142.     float y_acceleration = (adc_read(1)-0.386f)*163.333333f; // m/sec^2
  143.     float z_acceleration = (adc_read(2)-0.386f)*163.333333f; // m/sec^2
  144.  
  145.     // compute the angle from the accelerometer readings
  146.     return wrap_angle(atan2(z_acceleration, y_acceleration)-OFFSET_ANGLE);
  147. }
  148.  
  149. // get the angular velocity from the gyro (rad/sec)
  150. float get_gyro_angular_velocity(void) {
  151.     // read the voltage on the gyro pin
  152.     float adc_val = adc_read(0);
  153.  
  154.     // low pass filter it (this will need to be adjusted if you change how often this function is called)
  155.     gyro_neutral = gyro_neutral*0.9999f+adc_val*0.0001f;
  156.  
  157.     // do the units conversion and return the result
  158.     return (adc_val-gyro_neutral)*9.58972116f; // rad/sec
  159. }
  160.  
  161. ////////////////////////////////////////////////////////////////////////////////
  162. // main logic
  163. ////////////////////////////////////////////////////////////////////////////////
  164.  
  165. // how actively the motor corrects for tilt
  166. #define MOTOR_GAIN      260.0f
  167.  
  168. // the maximum angle before the motor shuts off
  169. #define MAX_ANGLE       (M_PI*0.35f)
  170.  
  171. // our current estimate of the angle
  172. volatile float angle;
  173.  
  174. // the current power value (starts at 0 and ramps up to 1)
  175. volatile float power_level;
  176.  
  177. // whether the motor is ready
  178. volatile uint8_t motor_ready;
  179.  
  180. // TIMER1 interrupt handler
  181. ISR(TIMER1_COMPA_vect) {
  182.     // get the angular velocity
  183.     float angular_vel = get_gyro_angular_velocity();
  184.  
  185.     // integrate the value from the gyro
  186.     angle += angular_vel*0.0016f;
  187.    
  188.     // compute the angle delta due to the accelerometer angle
  189.     float angle_delta = wrap_angle(get_accelerometer_angle()-angle);
  190.    
  191.     // complementary filter
  192.     angle = wrap_angle(angle+0.001f*angle_delta);
  193.  
  194.     // set the motor speed to compensate for tilt
  195.     float motor_speed = 0.0f;
  196.  
  197.     // proportional term
  198.     if (angle > 0.0f)
  199.         motor_speed += angle*angle*MOTOR_GAIN;
  200.     else
  201.         motor_speed -= angle*angle*MOTOR_GAIN;
  202.  
  203.     // derivative term
  204.     motor_speed += angular_vel*0.3f;
  205.    
  206.     // get the absolute value of the angle
  207.     float abs_angle = angle;
  208.     if (abs_angle < 0.0f)
  209.         abs_angle *= -1.0f;
  210.  
  211.     // ramp the power level up if the angle is reasonable, down otherwise
  212.     if (abs_angle < MAX_ANGLE && !(PIND&(1<<7)) && motor_ready)
  213.         power_level += 0.0005f;
  214.     else
  215.         power_level -= 0.003f;
  216.  
  217.     // clamp the power level
  218.     if (power_level > 1.0f)
  219.         power_level = 1.0f;
  220.     if (power_level < 0.0f)
  221.         power_level = 0.0f;
  222.    
  223.     // cap the motor speed
  224.     if (motor_speed > 1.0f)
  225.         motor_speed = 1.0f;
  226.     if (motor_speed < -1.0f)
  227.         motor_speed = -1.0f;
  228.  
  229.     // apply the power level
  230.     motor_speed *= power_level;
  231.    
  232.     // set the motor speed to compensate for tilt
  233.     set_motor_speed(motor_speed);
  234. }
  235.  
  236. // program entrypoint
  237. int main(void) {
  238.     // set the system clock prescalar to 1
  239.     CLKPR = 0x80;
  240.     CLKPR = 0x00;
  241.  
  242.     // give a reasonable initial neatural point for the gyro
  243.     gyro_neutral = 0.27f;
  244.  
  245.     // disable motor output during startup
  246.     motor_ready = 0;
  247.  
  248.     // assume we start upright at the neutral angle
  249.     angle = 0.0f;
  250.  
  251.     // start the power level at zero
  252.     power_level = 0.0f;
  253.  
  254.     // enable interrupts
  255.     sei();
  256.  
  257.     // initialize the motor
  258.     motor_init();
  259.    
  260.     // initialize the IMU
  261.     imu_init();
  262.  
  263.     // set the kill switch pin to input mode
  264.     DDRD &= ~(1<<7);
  265.    
  266.     // set up TIMER1 to go off at 625 Hz exactly
  267.     OCR1A = 0x007C;
  268.     TCCR1A = 0b00000000;
  269.     TCCR1B = 0b00001100;
  270.     TIMSK1 = 0b00000010;
  271.  
  272.     // give the capacitors on the power rails some time to charge
  273.     _delay_ms(1000);
  274.  
  275.     // start the motor
  276.     motor_ready = 1;
  277.    
  278.     ///////////////////////////////////
  279.     // run forever
  280.     ///////////////////////////////////
  281.  
  282.     // loop forever
  283.     while (1);
  284.  
  285.     // never reached
  286.     return 0;
  287. }