/* * ECE 476 * Spring 2005 * * Final Project: Solar Tracker * * Toby Peterson * Justin Rice * Jeff Valane * * Port Assignments: * * Port A: input from ADC * Port B: output to stepper driver 1 (altitude) * Port C: output to stepper driver 2 (azimuth) * #ifdef USE_LEDS * Port D: output to LEDs * #else * Port D: input from STK500 buttons * #endif * * Notes: * * See page 14 of the LMD18245T datasheet for explanation of step functions. * * x is forward/backward (east/west) * y is up/down (north/south) */ /* * X-axis: 2 steps = 3 degrees -> 240 steps/360 degrees * Y-axis: 5 steps = 9 degrees -> 200 steps/360 degrees */ #include #include #include #define HIGH_TORQUE //#define USE_TERM /**** Definitions ****/ #define DEFAULT_STEP_PERIOD (25) #define FAST_STEP_PERIOD (3) #define RECORD_STEP_PERIOD (10) #define TRACK_PERIOD (1000) #define CIRCLE_RADIUS (4) #define MOVEMENT_THRESHOLD (20) #define MOVEMENT_PAUSE (10000) #define TRACK_C_SAMPLES (5) /**** Prototypes ****/ #ifndef USE_TERM void HandleButtons(void); // Get button input for selecting tracking mode. #endif void ResetTracker(void); // Resets global state. void TrackSun(void); // Main tracking function, calls TrackE or TrackD. void Reposition(void); // Repositioning method. void Calibrate(void); // Initial calibration function. void TrackE(void); // Elementary tracking, just does circles and goes to the best recorded point and does another circle void TrackD(void); // Derivative tracking, uses value of dV/Dtheta to predict how far to go to get to best spot void TrackC(void); // More realistic tracking, based on the predicted path of the sun. void MoveStep(int direction, int step_period); // Moves one step in given direction at the given speed; updates local_position. void MoveCircle(void); // Using MoveStep commands, move in a square, taking data all the time using RecordData(). void RecordData(void); // Called from MoveCircle(), uses ADC to get voltage from PV. void Pause(void); // Toggle the relays to turn the motor off. int AtLimit(int direction); // Determine if movement limit has been reached. int GetPeriod(int min_period, int step, int max); // Calculate step speed. struct data_entry *SolarPath(); // Gather data for TrackC. void SolarFind(struct data_entry *data); // Find next point for TrackC, and move. /**** Other Definitions ****/ // Directions, for MoveStep(). enum { STEP_NONE = 0, STEP_NORTH, STEP_SOUTH, STEP_EAST, STEP_WEST }; // Tracking modes. enum { TRACK_NONE = 0, TRACK_E, TRACK_D, TRACK_C }; // Position structure. struct position { int x; int y; }; // Data gathered by TrackC(). struct data_entry { double altitude; double azimuth; }; // Result data from TrackC algorithm. struct result_entry { double a; double b; int time; double error_altitude; double error_azimuth; }; /**** Global Variables ****/ double phi_ew, phi_ns; // Constants. unsigned long global_timer; // Global timer. int track_mode; // Tracking mode. struct position global_position; // Global position. long localhigh; // Current local high reading. struct position local_position; // Current (local) position. struct position best_position; // Best position, for repositioning. int corner_ne, corner_se, corner_nw, corner_sw; // Corner values, for TrackD /**** Interrupts ****/ /* timer0_compare * 1 ms interrupt. */ interrupt [TIM0_COMP] void timer0_compare(void) { global_timer++; } /**** Main Program ****/ /* main() * Main program loop. */ void main(void) { /* Millisecond interrupt. */ TIMSK = 2; OCR0 = 250; TCCR0 = 0b00001011; /* Enable the ADC. */ ADMUX = 0b00100000; ADCSR = 0b11000111; #ifdef USE_TERM /* Set up HyperTerm. */ UCSRB = 0x08; UBRRL = 103; printf("Starting...\r\n"); #endif DDRB = 0xff; /* Output to stepper driver 1. */ PORTB = 0x00; /* Driver 1 off. */ DDRC = 0xff; /* Output to stepper driver 2. */ PORTC = 0x00; /* Driver 2 off. */ #ifndef USE_TERM DDRD = 0x00; /* Input from push buttons. */ #endif /* Initialize variables. */ track_mode = TRACK_NONE; global_position.x = 0; global_position.y = 0; phi_ew = (CIRCLE_RADIUS * 2.0) * 1.5 * PI / 180.0; phi_ns = (CIRCLE_RADIUS * 2.0) * 1.8 * PI / 180.0; #asm ("sei"); /* Enable interrupts. */ /* Make sure motors are off. */ Pause(); #ifndef USE_TERM /* Wait for button 0 to be pressed. */ while (PIND.0 == 1); #endif /* Run initial calibration. */ Calibrate(); Pause(); #ifdef USE_TERM /* Default to TrackD for debugging. */ track_mode = TRACK_D; #else /* Wait for a tracking mode selection. */ while (track_mode == TRACK_NONE) { HandleButtons(); } #endif /* Run main loop. */ while (1) { global_timer = 0; if (track_mode == TRACK_C) { /* TrackC is special. */ TrackC(); } else { /* Otherwise, reset, track, and reposition, ad infinitum. */ ResetTracker(); TrackSun(); Reposition(); } } } /* Calibrate() * Initial calibration method; moves all around, finds (roughly) the best spot. */ void Calibrate(void) { int i; /* Move 360 degrees around (at a 45 degree tilt), then reposition. */ ResetTracker(); for (i = 0; i < 25; i++) { MoveStep(STEP_NORTH, GetPeriod(FAST_STEP_PERIOD, i, 25)); } for (i = 0; i < 120; i++) { MoveStep(STEP_WEST, GetPeriod(RECORD_STEP_PERIOD, i, 120)); RecordData(); } for (i = 0; i < 240; i++) { MoveStep(STEP_EAST, GetPeriod(RECORD_STEP_PERIOD, i, 240)); RecordData(); } for (i = 0; i < 25; i++) { MoveStep(STEP_SOUTH, GetPeriod(FAST_STEP_PERIOD, i, 25)); } Pause(); best_position.y = 0; Reposition(); Pause(); /* Move up and down (y-axis), then reposition. */ ResetTracker(); for (i = 0; i < 30; i++) { MoveStep(STEP_NORTH, GetPeriod(RECORD_STEP_PERIOD, i, 30)); RecordData(); } for (i = 0; i < 60; i++) { MoveStep(STEP_SOUTH, GetPeriod(RECORD_STEP_PERIOD, i, 60)); RecordData(); } Pause(); best_position.x = 0; Reposition(); Pause(); } /**** Primary Functions ****/ #ifndef USE_TERM /* HandleButtons * Get button input; used only for initial selection of a tracking method. */ void HandleButtons(void) { if (PIND == 0x7f) { track_mode = TRACK_E; } else if (PIND == 0xbf) { track_mode = TRACK_D; } else if (PIND == 0xdf) { track_mode = TRACK_C; } } #endif /* ResetTracker * Resets various state variables used by the tracking functions. */ void ResetTracker(void) { localhigh = 0; local_position.x = 0; local_position.y = 0; best_position.x = 0; best_position.y = 0; corner_ne = corner_nw = corner_se = corner_sw = 0; } /* TrackSun * Waits for a period of time, then runs selected tracking function. */ void TrackSun(void) { /* Skip if we aren't tracking. */ if (track_mode != TRACK_NONE) { global_timer = 0; /* Wait a while, then run tracking function. */ while (1) { if (global_timer >= TRACK_PERIOD) { if (track_mode == TRACK_E) { TrackE(); } else if (track_mode == TRACK_D) { TrackD(); } break; } } } } /* Reposition() * Use values of best_position to move to the next best spot. * While not yet in best position, reposition as many times as is necessary. */ void Reposition(void) { int x_movement, y_movement; /* Block for TRACK_PERIOD. */ global_timer = 0; while (global_timer < TRACK_PERIOD); /* Calculate total movement in each direction. */ x_movement = abs(best_position.x - local_position.x); y_movement = abs(best_position.y - local_position.y); #ifdef USE_TERM printf("local_position: %d %d\r\n", local_position.x, local_position.y); printf("repositioning to: %d %d\r\n", best_position.x, best_position.y); #endif /* Move north (if necessary). */ while (best_position.y > local_position.y) { if (AtLimit(STEP_NORTH)) { break; } MoveStep(STEP_NORTH, GetPeriod(FAST_STEP_PERIOD, best_position.y - local_position.y - 1, y_movement)); } /* Move south (if necessary). */ while (best_position.y < local_position.y) { if (AtLimit(STEP_SOUTH)) { break; } MoveStep(STEP_SOUTH, GetPeriod(FAST_STEP_PERIOD, local_position.y - best_position.y - 1, y_movement)); } /* Move east (if necessary). */ while (best_position.x > local_position.x) { if (AtLimit(STEP_EAST)) { break; } MoveStep(STEP_EAST, GetPeriod(FAST_STEP_PERIOD, best_position.x - local_position.x - 1, x_movement)); } /* Move west (if necessary). */ while (best_position.x < local_position.x) { if (AtLimit(STEP_WEST)) { break; } MoveStep(STEP_WEST, GetPeriod(FAST_STEP_PERIOD, local_position.x - best_position.x - 1, x_movement)); } /* Turn off the motors. */ Pause(); /* If movement was small, pause for a while. */ if (x_movement + y_movement < MOVEMENT_THRESHOLD) { global_timer = 0; while (global_timer < MOVEMENT_PAUSE); } /* Block for TRACK_PERIOD. */ global_timer = 0; while (global_timer < TRACK_PERIOD); } /**** Tracking Functions ****/ /* TrackE() * Nearly braindead tracking method. Finds best reading, moves to it. */ void TrackE(void) { MoveCircle(); Pause(); } /* TrackD() * Smarter method of tracking (compared to TrackE). */ void TrackD(void) { int e_total, w_total, n_total, s_total; int movement_ew; int movement_ns; int v1, v2; double theta_ew; double theta_ns; int steps_ew; int steps_ns; /* Move in a circle, then turn motors off. */ MoveCircle(); Pause(); #ifdef USE_TERM printf("se: %d\r\n", corner_se); printf("nw: %d\r\n", corner_nw); printf("sw: %d\r\n", corner_sw); printf("ne: %d\r\n", corner_ne); #endif /* Figure out what direction we want to go. */ e_total = corner_se + corner_ne; w_total = corner_sw + corner_nw; n_total = corner_nw + corner_ne; s_total = corner_sw + corner_se; movement_ew = (e_total != w_total) ? (e_total > w_total ? STEP_EAST : STEP_WEST) : STEP_NONE; movement_ns = (n_total != s_total) ? (n_total > s_total ? STEP_NORTH : STEP_SOUTH) : STEP_NONE; /* Calculate the numnber of steps to take along the east-west axis. */ if (movement_ew != STEP_NONE) { v2 = ((movement_ew == STEP_EAST) ? e_total : w_total) / 2.0; v1 = ((movement_ew == STEP_EAST) ? w_total : e_total) / 2.0; #ifdef USE_TERM printf("v1 = %d, v2 = %d\r\n", v1, v2); #endif theta_ew = atan(((double)v2 - (double)v1 * cos(phi_ew)) / ((double)v1 * sin(phi_ew))); steps_ew = (int)(theta_ew * 180.0 / PI / 1.5); } else { steps_ew = 0; } /* Calculate the numnber of steps to take along the north-south axis. */ if (movement_ns != STEP_NONE) { v2 = ((movement_ns == STEP_NORTH) ? n_total : s_total) / 2; v1 = ((movement_ns == STEP_NORTH) ? s_total : n_total) / 2; #ifdef USE_TERM printf("v1 = %d, v2 = %d\r\n", v1, v2); #endif theta_ns = atan(((double)v2 - (double)v1 * cos(phi_ns)) / ((double)v1 * sin(phi_ns))); steps_ns = (int)(theta_ns * 180.0 / PI / 1.8); } else { steps_ns = 0; } #ifdef USE_TERM printf("st_ew: %d\r\n", steps_ew); printf("st_ns: %d\r\n", steps_ns); #endif /* Calculate best position to move to (Reposition() will do the work). */ best_position.x = (movement_ew == STEP_EAST) ? steps_ew : -steps_ew; best_position.y = (movement_ns == STEP_NORTH) ? steps_ns : -steps_ns; } /* TrackC * More realistic tracking method which tries to predict the sun's path. */ void TrackC(void) { struct data_entry *data; data = SolarPath(); SolarFind(data); } /* WaitAnHour() * Used by TrackC, waits for an hour (the sun moves slowly). */ void WaitAnHour(void) { int m, s; for (m = 0; m < 60; m++) { for (s = 0; s < 60; s++) { global_timer = 0; while (global_timer < 1000); } } } /* SolarPath() * Gathers data for TrackC, using TrackD. */ struct data_entry * SolarPath() { struct data_entry data[TRACK_C_SAMPLES]; int i; for (i = 0; i < TRACK_C_SAMPLES; i++) { /* Move to best spot, calculate azimuth and altitude, then wait for an hour. */ TrackD(); data[i].altitude = (global_position.y + 50) * PI / 180.0 * 1.8; data[i].azimuth = global_position.x * PI / 180.0 * 1.5; WaitAnHour(); } return data; } /* SolarFind() * Analyzes data found in SolarPath(), moves to predicted positions. * Detailed explanation available in lab report. */ void SolarFind(struct data_entry *data) { double alpha; int k, k_limit; double a, a_start, a_good, a_end, a_range; double b, b_start, b_good, b_end, b_range; int t, t_start, t_good, t_end; int i; /* Initial starting values. */ alpha = 5.0; k_limit = 3; a_start = 0.0; a_end = PI / 2.0; a_good = a_end; b_start = -1.0; b_end = 1.0; b_good = b_end; t_start = 0; t_end = 24; t_good = t_end; /* Zoom in three times. */ for (k = 1; k <= k_limit; k++) { struct result_entry result; double lowest_error; lowest_error = -1.0; /* Over range of a values... */ for (a = a_start; a <= a_end; a += (a_end - a_start) / alpha) { /* ...and over range of b values... */ for (b = b_start; b <= b_end; b += (b_end - b_start) / alpha) { /* ...and over range of t values... */ for (t = t_start; t <= t_end; t++) { double altitude_error; double azimuth_error; double total_error; altitude_error = 0.0; azimuth_error = 0.0; /* ...and over all samples... */ for (i = 0; i < TRACK_C_SAMPLES; i++) { double altitude, altitude_error_squared; double azimuth, azimuth_error_squared; double arg; /* Calculate the altitude. */ altitude = asin(sin(a) * sin(b) + cos(a) * cos(b) * cos((t + i - 12) * PI / 12)); /* Sometimes we'll get invalid data; ignore it. */ arg = (cos(a) * sin(b) - sin(a) * cos(b) * cos((t + i - 12) * PI / 12)) / cos(altitude); if (abs(arg) >= 1.0) { continue; } /* Calculate the azimuth. */ azimuth = acos(arg); /* Adjust, to keep the range valid and usable. */ if (t + i > 12) { azimuth = azimuth - PI; } else { azimuth = PI - azimuth; } /* Calculate error squared, accumulate total error. */ altitude_error_squared = pow(altitude - data[i].altitude, 2.0); azimuth_error_squared = pow(azimuth - data[i].azimuth, 2.0); altitude_error += altitude_error_squared; azimuth_error += azimuth_error_squared; } total_error = altitude_error + azimuth_error; /* If this error is the best (i.e. lowest), then store it. */ if (total_error < lowest_error || lowest_error < 0.0) { lowest_error = total_error; result.a = a; result.b = b; result.time = t; result.error_altitude = altitude_error; result.error_azimuth = azimuth_error; } } } } /* Store new values. */ a_good = result.a; b_good = result.b; t_good = result.time; a_range = a_end - a_start; a_start = a_good - (a_range / alpha / 2.0); a_end = a_good + (a_range / alpha / 2.0); b_range = b_end - b_start; b_start = b_good - (b_range / alpha / 2.0); b_end = b_good + (b_range / alpha / 2.0); t_start = t_good - 2; t_end = t_end + 2; } /* Based on the data we analyzed above, move to the next three predicted locations. */ for (i = 0; i < 3; i++) { int next_time; double next_altitude; double next_azimuth; struct position next_position; double arg; next_time = t_good + TRACK_C_SAMPLES + i; next_altitude = asin(sin(a) * sin(b) + cos(a) * cos(b) * cos((next_time - 12) * PI / 12)); /* Same as above, ignore bad values. */ arg = (cos(a) * sin(b) - sin(a) * cos(b) * cos((next_time - 12) * PI / 12)) / cos(next_altitude); if (abs(arg) >= 1.0) { continue; } next_azimuth = acos(arg); /* Convert back to steps, from radians. */ next_position.x = next_azimuth / 1.5 * 180.0 / PI; next_position.y = (next_altitude / 1.8 * 180.0 / PI) - 50; /* Use Reposition() to do the dirty work here. */ local_position = global_position; best_position = next_position; Reposition(); /* Wait for an hour... */ WaitAnHour(); } } /**** Other Helper Functions ****/ /* MoveStep * Moves one step in given direction at given speed. */ void MoveStep(int direction, int step_period) { int driver_forward[4] = { #ifdef HIGH_TORQUE 0b00010110, // 00010110 0b00000110, // 00000110 0b00001110, // 00001110 0b00011110 // 00011110 #else 0b00010100, // 00010100 0b00000010, // 00000010 0b00001100, // 00001100 0b00011010 // 00011010 #endif }; int driver_back[4] = { #ifdef HIGH_TORQUE 0b00001110, // 00001110 0b00000110, // 00000110 0b00010110, // 00010110 0b00011110 // 00011110 #else 0b00001010, // 00001010 0b00000100, // 00000100 0b00010010, // 00010010 0b00011100 // 00011100 #endif }; int index; int driver_value; /* Skip if we can't move. */ if (direction == STEP_NONE || AtLimit(direction)) { PORTB = 0x00; PORTC = 0x00; return; } /* Update position. */ if (direction == STEP_EAST) { global_position.x++; local_position.x++; } else if (direction == STEP_WEST) { global_position.x--; local_position.x--; } else if (direction == STEP_NORTH) { global_position.y++; local_position.y++; } else if (direction == STEP_SOUTH) { global_position.y--; local_position.y--; } global_timer = 0; index = 0; /* Sequentially drive the four values necessary to control the motor. */ while (1) { if (global_timer > step_period) { index++; global_timer = 0; } if (index >= 4) { break; } /* Determine value to drive with. */ if (direction == STEP_NORTH || direction == STEP_EAST) { driver_value = driver_forward[index]; } else if (direction == STEP_SOUTH || direction == STEP_WEST) { driver_value = driver_back[index]; } /* Drive the ports. */ if (direction == STEP_NORTH || direction == STEP_SOUTH) { PORTB = driver_value; PORTC = 0x00; } else if (direction == STEP_EAST || direction == STEP_WEST) { PORTB = 0x00; PORTC = driver_value; } } PORTB = 0x00; PORTC = 0x00; } /* MoveCircle() * Move in a circle (a square, actually), and gather data. * Moves like this, sort of: * * <--<--^ * | | * | <->^ * | | * v-->--> * */ void MoveCircle(void) { int i; local_position.x = 0; local_position.y = 0; RecordData(); for (i = 0; i < CIRCLE_RADIUS; i++) { MoveStep(STEP_EAST, DEFAULT_STEP_PERIOD); RecordData(); } for (i = 0; i < CIRCLE_RADIUS; i++) { MoveStep(STEP_NORTH, DEFAULT_STEP_PERIOD); RecordData(); } for (i = 0; i < (CIRCLE_RADIUS * 2); i++) { MoveStep(STEP_WEST, DEFAULT_STEP_PERIOD); RecordData(); } for (i = 0; i < (CIRCLE_RADIUS * 2); i++) { MoveStep(STEP_SOUTH, DEFAULT_STEP_PERIOD); RecordData(); } for (i = 0; i < (CIRCLE_RADIUS * 2); i++) { MoveStep(STEP_EAST, DEFAULT_STEP_PERIOD); RecordData(); } for (i = 0; i < CIRCLE_RADIUS; i++) { MoveStep(STEP_NORTH, DEFAULT_STEP_PERIOD); RecordData(); } for (i = 0; i < CIRCLE_RADIUS; i++) { MoveStep(STEP_WEST, DEFAULT_STEP_PERIOD); RecordData(); } } /* RecordData() * Records current input from PV panels. */ void RecordData(void) { int input; /* Get all 10 bits of the sample and start another conversion. */ input = ADCL >> 6; input += ADCH << 2; ADCSR.6 = 1; /* Store the values at the four corners; for TrackD() */ if ((local_position.x == CIRCLE_RADIUS || AtLimit(STEP_EAST)) && (local_position.y == CIRCLE_RADIUS || AtLimit(STEP_NORTH))) { corner_ne = input; } if ((local_position.x == -CIRCLE_RADIUS || AtLimit(STEP_WEST)) && (local_position.y == CIRCLE_RADIUS || AtLimit(STEP_NORTH))) { corner_nw = input; } if ((local_position.x == CIRCLE_RADIUS || AtLimit(STEP_EAST)) && (local_position.y == -CIRCLE_RADIUS || AtLimit(STEP_SOUTH))) { corner_se = input; } if ((local_position.x == -CIRCLE_RADIUS || AtLimit(STEP_WEST)) && (local_position.y == -CIRCLE_RADIUS || AtLimit(STEP_SOUTH))) { corner_sw = input; } /* Store the local high; used for TrackE(). */ if (input > localhigh) { localhigh = input; best_position = local_position; } } /* Pause() * Toggles the relays, to make the motors stop grinding. */ void Pause(void) { global_timer = 0; while (global_timer < 50) { PORTB.0 = 1; PORTC.0 = 1; } } /* AtLimit() * Returns true if we are at the movement limit for the given direction. */ int AtLimit(int direction) { return ((direction == STEP_EAST && global_position.x >= 180) || (direction == STEP_WEST && global_position.x <= -180) || (direction == STEP_NORTH && global_position.y >= 30) || (direction == STEP_SOUTH && global_position.y <= -30)); } /* GetPeriod() * Calculate period (step speed) for given parameters. * Allows the motors to accelerate/decelerate when taking many steps at once. */ int GetPeriod(int min_period, int step, int max) { int p; int start_speed; int halfway; start_speed = DEFAULT_STEP_PERIOD; halfway = max >> 1; if (step < halfway) { p = start_speed - step; } else { p = start_speed - (max - step - 1); } if (p < min_period) { p = min_period; } #ifdef USE_TERM //printf("%d\r\n", p); #endif return p; }