// bot0.c // prototype controller firmware for experimental robot 0 // written by dale wheat - 17 april 2008 // notes: // AVR Studio 4.14, Windows XP Pro, WinAVR 20070525, GCC 4.1.2 // device = ATmega644-20PU // clock = 20 MHz crystal // conventions: // units of measure: // linear: inches // angular: radians // velocity: inches per second // trigonometric notation is used for navigation, i.e.: // angles are in radians // 0 radians is due east // counter-clockwise rotation as viewed from above is positive // motor [+] high and [-] low produces clockwise rotation and positive odometer values // right motor has 0 degree phase; correction factor of cos(0) // left motor has 180 degree phase; correction factor of cos(pi) // USART0 (was) connected to SparkFun Electronics BlueSmirf module (discontinued) (before it died) // default baud rate is 9600 (default is 9600bps, 8 Data Bits, No Parity, 1 Stop Bit, and hardware flow control enabled) // AT commands: // +++ enter command mode // ATMD return to data mode // ATVER,ver1 returns "Ver 2.8.1.4.0" // ATSI,0 returns BlueRadios AT // ATSI,1 returns 00A096127F03 (product ID code, BlueTooth address ID) // ATSI,8 returns 0027,0000,0000 (UART parameters in hex) // ATSW20,1024,0,0,0 sets baud rate to 250 Kbps // module defected on 20 april 2008 - will be replaced with Digi XBee modules // wheel information // motors: faulhaber 1524S006 6V coreless DC motors with precious metal commutation // encoders: magnetic quadrature 1:1 on motor output // quadrature encoding produces ~282 steps per wheel revolution // gearbox: ~141:1 reduction (140.759183:1 per David Cook) // wheels: // right OD = 2.265", 7.116" cir, 0.0252" per odometer tick // revised: 2.2665", 7.120419749 cir, 0.025292914 per odometer tick // left OD = 2.255", 7.084" cir, 0.0251" per odometer tick // revised: 2.2575", 7.092145415" cir, 0.025192479 per odometer tick // wheelbase = 5.528" overall less average wheel thickness 0.280" = 5.248" effective // problems: // *fixed* intermittant power failures - suspect positive battery lead - not seen since power switch installed // *fixed* motor spins during ISP - suggest pull-down resistor on PWM driver pin - fix: 10 K pulldowns on PWM lines // right wheel is wobbly // startup delay - first timer tick is missed, but only after ISP or extended power off - BOR causes this // TODO // *done* add overhead timer to RTC heartbeat service // *done* caster - 3rd wheel // *done* wire bail jack stand // *done* power switch // *done* battery charge connector // *done* battery mount // radio mount // battery voltage input // *done* battery volt meter LCD-DVM // battery thermistor input // command interpreter // I2C interface for Wiimote nunchuk, classic controller // power control for SN754410 // *done* interrupt-driven serial interface // *done* beeper/speaker // *in process* motion control alorithms // add blue LED for radio connection status // *done* add blue LED for general user interface // wiimote triangulation via sensor bar // IR obstacle avoidance + remote control // bump switches // accelerometer // balancing tests // 'return to zero' demo // *done* add red 'error' LED, error() function and codes defined // charge detector circuit & 'submit patiently & monitor charge' behavior // add 'thinking' (CPU load) LED indicator // replace wire bail with more substantial kickstand // *done* increase RTC rate to 16 Hz // *done* add linear+angular objects // determine wheelbase, wheel diameters dynamically // add adjustable PID coefficients // *done* separate motor and motion objects // add error detection & stats to serial communication // *done* change sample frequency to 8 Hz // *done* double encoder resolution // *done* determine cause of reset // *done* multiple operation modes via reset button // *done* move hardware initialization routines to .init3 section // pin-point navigation // add tiered navigation code for 90 degree problem #ifndef F_CPU #define F_CPU 20000000 #endif // #ifdef F_CPU #define sbi(port, pin) (port) |= (1 << (pin)) #define cbi(port, pin) (port) &= ~(1 << (pin)) #include #include #include #include #include #include // mechanical constants #define WHEEL_MOTOR_GEAR_RATIO 140.759183 // to 1 #define ENCODER_TICK_RATIO 4 // number of ticks produced by one complete quadrature encoder cycle #define RIGHT_WHEEL_OD 2.2665 // right wheel outside diameter in inches, including o-ring tire #define RIGHT_WHEEL_CIRCUMFERENCE RIGHT_WHEEL_OD * M_PI // right wheel circumference in inches #define RIGHT_WHEEL_TICK RIGHT_WHEEL_CIRCUMFERENCE / (WHEEL_MOTOR_GEAR_RATIO * ENCODER_TICK_RATIO) // inches per odometer tick #define LEFT_WHEEL_OD 2.2575 // left wheel outside diameter in inches, including o-ring tire #define LEFT_WHEEL_CIRCUMFERENCE LEFT_WHEEL_OD * M_PI // left wheel circumference in inches #define LEFT_WHEEL_TICK LEFT_WHEEL_CIRCUMFERENCE / (WHEEL_MOTOR_GEAR_RATIO * ENCODER_TICK_RATIO) // inches per odometer tick //#define WHEELBASE 5.248 // effective wheelbase dimension in inches - original value - shows early on test //#define WHEELBASE 5.250 // test value slightly larger - still early, but gets there slower #define WHEELBASE 5.252 // in the middle //#define WHEELBASE 5.255 // more bigger -maybe a little too big // program constants #define PWM_PERIOD 100 // for wheel motor power, 16 bit max value #define TICKS_PER_SECOND 8 // RTC & sampling period frequency: 4, 8 or 16 Hz supported #define TPS TICKS_PER_SECOND // shorthand // operational modes typedef enum { MODE_STANDBY, MODE_SHORT_TRIP, MODE_SLALOM, MODE_SLALOM8, MODE_LONG_TRIP, MODE_LONG_RH_SQUARE, MODE_LONG_LH_SQUARE, MODE_DEBUG, MODE_MAX } MODE; // global variables volatile unsigned char reset __attribute__ ((section(".noinit"))); // cause of reset bitmask volatile unsigned char mode __attribute__ ((section(".noinit"))); // operational mode //volatile unsigned char motor_control_flag = 1; // adjust power to motors to achieve desired velocity // real time clock volatile unsigned char ticks; volatile unsigned char seconds; volatile unsigned char minutes; volatile unsigned char hours; // floating-point odometry volatile double x = -12.0, y = 12.0; // absolute coordinates in inches; +x=east, +y=north volatile double theta = 0.0; // absolute orientation in radians, looking down // delay() - user-perceptible delay in 1/4 increments // note: requires interrupts to be enabled - uses RTC tick handler //volatile unsigned char rtc_overhead; // 1/(256 * TPS) second ticks consumed in RTC service routine volatile unsigned int foreground_downcounter; // for timing & delay functions void delay(unsigned int n) { foreground_downcounter = n; while(foreground_downcounter); // variable gets decremented in RTC tick handler every 1/4 second } #define delay_seconds(n) delay(n * TPS) // delay24() - 24 bit delay subroutine: kills 5n + 10 cycles void delay24(unsigned long n) __attribute__ ((naked)); void delay24(unsigned long n) { asm("1: subi r22, 1"); asm(" sbci r23, 0"); asm(" sbci r24, 0"); asm(" brne 1b"); asm(" ret"); } // LED functions #define error_led_on() sbi(PORTC, PC2) #define error_led_off() cbi(PORTC, PC2) typedef enum { ERROR_NONE = 0, // no error ERROR_RESET = 1, // bad reset state - probably caused by software runaway and re-execution of init() function ERROR_EXCEPTION = 2, // unhandled exception encountered - bad interrupt or stack overflow ERROR_TEST = 9, // *** debug *** test error code } ERROR; prog_char error_msg_none[] = ""; prog_char error_msg_reset[] = "bad reset state"; prog_char error_msg_exception[] = "unhandled exception"; PGM_P error_msg[] = { error_msg_none, // no error error_msg_reset, error_msg_exception, }; void error(ERROR e) __attribute__ ((naked, noreturn)); void error(ERROR e) { unsigned char i; cli(); // stop responding to interrupts if(e == ERROR_NONE) { error_led_on(); // no error indicated (this is an error), so turn on error LED and halt while(1); } else { // a specific error has occurred fprintf_P(stderr, PSTR("[%S - system halted]"), error_msg[e]); // tell the world about it // blink the error LED while(1) { for(i = 0; i < e; i++) { error_led_on(); delay24(1000000); // ~1/4 second delay error_led_off(); delay24(1000000); // ~1/4 second delay } delay24(4000000); // ~1 second delay } } } #define user_led_on() sbi(PORTC, PC3) #define user_led_off() cbi(PORTC, PC3) #define user_led_toggle() sbi(PINC, PC3) // speaker/beeper functions void silence(void) { OCR0A = 0; // ahh, blessed silence } void tick(void) { OCR0A = 6; // shrill, piercing tone delay24(10000); // silence(); } void chirp(void) { OCR0A = 6; // shrill, piercing tone delay24(50000); // silence(); } void beep(void) { OCR0A = 6; // shrill, piercing tone delay24(500000); // ~1/8 second silence(); } void buzz(void) { OCR0A = 50; delay24(10000); silence(); } // USART functions #define USART_BUFFER_SIZE 256 char usart_txd_buffer[USART_BUFFER_SIZE]; char usart_rxd_buffer[USART_BUFFER_SIZE]; volatile unsigned char usart_txd_in; volatile unsigned char usart_txd_out; volatile unsigned char usart_rxd_in; volatile unsigned char usart_rxd_out; // usart_putchar() - schedule a single character to be transmitted static int usart_putchar(char c, FILE *stream) { if(c == '\n') usart_putchar('\r', stream); while((unsigned char)(usart_txd_in + 1) == usart_txd_out); // wait for room in the queue usart_txd_buffer[usart_txd_in] = c; // store in buffer usart_txd_in++; sbi(UCSR0B, UDRIE0); // schedule an interrupt when the current transmission (if any) is complete to start this transmission return 0; // report unconditional success } // usart_putchar_polled() - transmit a single character without interrupts static int usart_putchar_polled(char c, FILE *stream) { if(c == '\n') usart_putchar('\r', stream); while(bit_is_clear(UCSR0A, UDRE0)); // wait for USART to be ready to transmit UDR0 = c; // transmit character return 0; // this always works } // usart_puthex() - print 2 hex digits const char hex[] = "0123456789ABCDEF"; void usart_puthex(unsigned char h) { usart_putchar(hex[h >> 4], stdout); // upper 4 bits usart_putchar(hex[h & 0x0f], stdout); // lower 4 bits } // usart_getchar() - receive a single character static int usart_getchar(FILE *stream) { while(usart_rxd_in == usart_rxd_out); // wait for something to arrive return (int) usart_rxd_buffer[usart_rxd_out++]; } // usart_getchar_polled() - receive a single character without buffering or interrupts //static int usart_getchar_polled(FILE *stream) { // // while(bit_is_clear(UCSR0A, RXC0)); // wait for character to arrive // return UDR0; // return it //} // usart_getline signed char usart_getline(FILE *stream, char *buffer, const char *prompt) { signed char result = 0; printf_P(PSTR("\n%S"), prompt); // display prompt strcpy(buffer, "test"); // *** debug *** return string "test" result = 1; // *** debug *** fake return status return result; } static FILE USART0 = FDEV_SETUP_STREAM(usart_putchar, usart_getchar, _FDEV_SETUP_RW); // stdout & stdin //static FILE USART0 = FDEV_SETUP_STREAM(usart_putchar, usart_getchar_polled, _FDEV_SETUP_RW); // stdout (buffered) & stdin (polled) static FILE USART0_POLLED = FDEV_SETUP_STREAM(usart_putchar_polled, NULL, _FDEV_SETUP_WRITE); // stderr (polled) // motor power functions typedef struct { double power; // value between -1.0 (full reverse) and +1.0 (full forward) int pwm_power; // combines sign & magnitude int odometer; // in encoder ticks int previous_odometer; // previous odometer reading in encoder ticks int velocity; // in encoder ticks per sample period } WHEEL; volatile WHEEL right_wheel, left_wheel; // the two powered wheels typedef struct { double odometer; // in inches double previous_odometer; // previous odometer reading in inches double velocity_setpoint; // requested velocity in inches per sample period double velocity; // actual velocity in inches per sample period double p_error; // proportional error double i_error; // integral error double d_error; // derivative error } MOTION; volatile MOTION right, left, angular, linear; // motions void right_wheel_power(double power) { // constrain power value if(power > 1.0) { power = 1.0; // max } else if(power < -1.0) { power = -1.0; // min } if(power > 0.0) { sbi(PORTB, PB7); cbi(PORTD, PD7); right_wheel.pwm_power = power * PWM_PERIOD; } else if(power < 0.0) { cbi(PORTB, PB7); sbi(PORTD, PD7); right_wheel.pwm_power = power * -PWM_PERIOD; } else { cbi(PORTB, PB7); cbi(PORTD, PD7); right_wheel.power = 0; } OCR1A = right_wheel.pwm_power; right_wheel.power = power; } void left_wheel_power(double power) { // constrain power value if(power > 1.0) { power = 1.0; // max } else if(power < -1.0) { power = -1.0; // min } if(power > 0.0) { sbi(PORTB, PB6); cbi(PORTD, PD6); left_wheel.pwm_power = power * PWM_PERIOD; } else if(power < 0.0) { cbi(PORTB, PB6); sbi(PORTD, PD6); left_wheel.pwm_power = power * -PWM_PERIOD; } else { cbi(PORTB, PB6); cbi(PORTD, PD6); left_wheel.pwm_power = 0; } OCR1B = left_wheel.pwm_power; left_wheel.power = power; } // motor_control() - adjust motor power to maintain linear & angular velocities // angular PID coefficients //#define Pangular 0.01 //#define Iangular 0.0002 //#define Dangular 0.02 // 8 Hz #define Pangular 0.25 #define Iangular 0.0010 #define Dangular 0.500 // linear PID coefficients //#define Plinear 0.01 //#define Ilinear 0.0002 //#define Dlinear 0.02 // these work OK @ 16 Hz //#define Plinear 0.015 //#define Ilinear 0.0001 //#define Dlinear 0.2 // 8 Hz #define Plinear 0.2 #define Ilinear 0.0010 #define Dlinear 0.5000 void motor_control(void) { double angular_error, linear_error, previous_error; // if(motor_control_flag) { // calculate angular velocity error previous_error = angular.p_error; angular.p_error = angular.velocity_setpoint - angular.velocity; // proportional error angular.i_error += angular.p_error; // integral error angular.d_error = angular.p_error - previous_error; // derivative error angular_error = (Pangular * angular.p_error) + (Iangular * angular.i_error) + (Dangular * angular.d_error); // wighted sum of error terms // calculate linear velocity error previous_error = linear.p_error; linear.p_error = linear.velocity_setpoint - linear.velocity; // proportional error linear.i_error += linear.p_error; // integral error linear.d_error = linear.p_error - previous_error; // derivative error linear_error = (Plinear * linear.p_error) + (Ilinear * linear.i_error) + (Dlinear * linear.d_error); // weighted sum of error terms right_wheel_power(right_wheel.power + (angular_error + linear_error)); left_wheel_power(left_wheel.power + (angular_error - linear_error)); // } } // navigation functions // hypot() - returns length of a right triangle's hypotenuse given lengths of sides a & b double hypot(double a, double b) { return sqrt(square(a) + square(b)); } // distance() - returns straight-line distance between points (x0,y0) and (x1,y1) double distance(double x0, double y0, double x1, double y1) { return hypot(x0 - x1, y0 - y1); // distance formula always returns positive result } // go() - specify heading in terms of angular & linear velocities, both in inches per second void go(double angular_velocity, double linear_velocity) { angular.velocity_setpoint = angular_velocity / TPS; // suggest angular velocity linear.velocity_setpoint = linear_velocity / TPS; // suggest linear velocity } // normal() - return similar angle in radians in range -pi to +pi double normal(double a) { double result; result = fmod(a, M_PI * 2.0); // 0-2pi if(result > M_PI) result -= M_PI; // -pi to +pi return result; } // target_angle() - returns relative angle in radians to target point (target_x, target_y) double target_angle(double target_x, double target_y) { return atan2(target_y - y, target_x - x); // relative to present position } // target_distance() - returns distance in inches to target point (target_x, target_y) double target_distance(double target_x, double target_y) { return distance(target_x, target_y, x, y); } // go_to() - navigate to target point (target_x, target_y) at velocity in inches per second void go_to(double target_x, double target_y, double velocity) { double navigation_tolerance; double angular_error, linear_error, velocity_error; // calculate navigation tolerance based on suggested velocity and sampling frequency navigation_tolerance = (velocity / TICKS_PER_SECOND) * 1.25; // with some built-in overlap // arrive within initial navigational tolerance do { delay(1); // wait for odometry update printf_P(PSTR("go_to(% 7.3f,% 7.3f, % 6.3f): "), target_x, target_y, velocity); // parameters angular_error = target_angle(target_x, target_y) - theta; // the relative need to turn linear_error = target_distance(target_x, target_y); // how far away velocity_error = velocity - (linear.velocity * TICKS_PER_SECOND); // required change in velocity in inches per second printf_P(PSTR("A% 7.3f L% 7.3f V% 7.3f "), angular_error, linear_error, velocity_error); // errors go(cos(angular_error) * sin(angular_error) * velocity, cos(angular_error) * velocity); printf_P(PSTR("go(% 7.3f,%7.3f)\n"), cos(angular_error) * sin(angular_error) * velocity, cos(angular_error) * velocity); /*if(angular_error > 1.0) { go(1.0, 0.0); // turn left printf_P(PSTR("1 go(% 7.3f, 0)\n"), 1.0); } else if(angular_error < -1.0) { go(-1.0, 0.0); // turn right printf_P(PSTR("2 go(% 7.3f, 0)\n"), -1.0); } else if(velocity_error > 1.0) { go(angular_error, linear.velocity * TPS + 1.0); // ramp forward printf_P(PSTR("3 go(% 7.3f,%7.3f)\n"), angular_error, linear.velocity * TPS + 1.0); } else if(velocity_error < -1.0) { go(angular_error, linear.velocity * TPS - 1.0); // ramp printf_P(PSTR("4 go(% 7.3f,%7.3f)\n"), angular_error, linear.velocity * TPS - 1.0); } else if(linear_error > 1.0) { go(angular_error, velocity); printf_P(PSTR("5 go(% 7.3f,%7.3f)\n"), angular_error, velocity); } else { go(angular_error, velocity * linear_error); printf_P(PSTR("6 go(% 7.3f,%7.3f)\n"), velocity * linear_error); }*/ } while(linear_error > navigation_tolerance); // chirp(); // *** debug *** announce arrival at target coordinates } // *** initialization functions *** // init_reset() - determine cause of reset void init_reset(void) __attribute__ ((naked, section(".init3"))); void init_reset(void) { reset = MCUSR; // collect reset cause MCUSR = 0; // clear reset flags if(reset == 0) { // not supposed to happen, but does // suspect a software malfunction error(ERROR_RESET); // blink error LED } else if(reset & 1<