Components and supplies
Plastic or wooden cross-comparted "head" (to build form scratch)
Buzzer
plastic cap
Nylon Spacer
X113647 Stepper Motor Driver
Aluminum Strip - 2 x 2 inches (see instructions)
28BYJ48 stepper motor
Photo resistor
Resistor 1k ohm
LED (generic)
15mm Plywood board (around 30cm x 50cm)
Arduino UNO
Tools and machines
Hand saw
Hole Saw for Drill
Project description
Code
Full Arduino sketch
arduino
The full sketch with all the needed explanations as comments
1/* 2 3Light Tracking Turret 4 5Circuit and comments: 6See http://www.cesarebrizio.it/Arduino/Light_Tracking_Turret.html 7 8 created 27 Sep 2014 9 modified ---- 10 by Cesare Brizio 11 12This example code is in the public domain. 13 14This sketch controls a light-tracking turret based on two stepper motors. 15The turret can rotate about 90Deg in the vertical and 180Deg in the 16horizontal plane, and so tracking directions include up-down and left-right. 17The turret hasn't rotation limit switches and needs manual centering before 18starting the software, that implements soft rotation limits. 19A 4-partition head, each partition containing a photoresistor, is continuously 20aimed at light by minimizing the delta among the four photoresistor, by moving 21in the direction of the most illuminated, minimum resistance photoresistor 22(the one that will give the highest reading). 23Sources of information: 24Manual start/stop switch: http://arduino.cc/en/Tutorial/Debounce 25Speaker: http://arduino.cc/en/Tutorial/Tone 26Small stepper control: http://arduino-info.wikispaces.com/SmallSteppers 27Photoresistors: https://learn.adafruit.com/photocells/using-a-photocell 28 29 30*/ 31 32/*-----( Import needed libraries )-----*/ 33#include "pitches.h" 34#include <AccelStepper.h> 35 36/*-----( Declare Constants and Pin Numbers )-----*/ 37/* NEVER PUT ; AFTER A #define statement!!!! */ 38#define FULLSTEP 4 39#define HALFSTEP 8 40// motor pins 41#define motorPin1 4 // Blue - 28BYJ48 pin 1 42#define motorPin2 5 // Pink - 28BYJ48 pin 2 43#define motorPin3 6 // Yellow - 28BYJ48 pin 3 44#define motorPin4 7 // Orange - 28BYJ48 pin 4 45 // Red - 28BYJ48 pin 5 (VCC) 46 47#define motorPin5 8 // Blue - 28BYJ48 pin 1 48#define motorPin6 9 // Pink - 28BYJ48 pin 2 49#define motorPin7 10 // Yellow - 28BYJ48 pin 3 50#define motorPin8 11 // Orange - 28BYJ48 pin 4 51 // Red - 28BYJ48 pin 5 (VCC) 52 53/*-----( Objects for stepper control )-----*/ 54// NOTE: The sequence 1-3-2-4 is required for proper sequencing of 28BYJ48 55AccelStepper stepper1(HALFSTEP, motorPin1, motorPin3, motorPin2, motorPin4); 56AccelStepper stepper2(HALFSTEP, motorPin5, motorPin7, motorPin6, motorPin8); 57 58/* 59Constant for azimuth control 60------------------------------------ 61Turret will allow an horizontal rotation of 180Deg 62from 0Deg (leftmost) to 180Deg (rightmost) position, 63and a vertical rotation of 90Deg from 0Deg (downmost) 64to 90Deg (upmost) position. It will be centered 65manually before program start. 66Tentatively, I assume that the stepper 67will be rotated in 4,5Deg increments (50 steps), so that 180Deg will require 6840 increments (the real number of increments will be empirically 69determined depending from stepper features). Heading 70is expressed by increment number. Thus, the initial neutral 71horizontal heading will correspond to increment 21 (90Deg), while 72the initial neutral vertical heading will correspond to 73increment 11 (45Deg). 74*/ 75#define leftLimit 0 // leftmost increment no. 76#define neutralHorHeading 21 // mid range increment no., tentatively set to 21 (90Deg) 77#define rightLimit 40 // leftmost increment no., tentatively set to 40 (180Deg) 78#define downLimit 0 // leftmost increment no. 79#define neutralVerHeading 11 // mid range increment no., tentatively set to 11 (45Deg) 80#define upLimit 20 // leftmost increment no., tentatively set to 20 (90Deg) 81 82// 4 photoresitors on analog lines 0-3 83#define photoResUp A0 84#define photoResDown A1 85#define photoResRight A2 86#define photoResLeft A3 87// 2 LED's on digital lines 2-3 88#define ledUp 2 // If moving right or left ... 89#define ledDown 2 // ... set Led 2 on 90#define ledRight 3 // If moving up or down ... 91#define ledLeft 3 // ... set Led 3 on 92// a "SLEEP" pin to store last activation state 93#define SLEEP 12 // PIN 12 = SLP 94#define switchPin 13 //define switch to pin 13 95// constants and variables declaration and setup 96int threshold = 250; // light threshold derived form experiments 97boolean lastButton = LOW; //part of debounce function 98boolean currentButton = LOW; //part of debounce function 99boolean work = LOW; //low puts driver into sleep mode, high turns it on 100boolean firstTimeEver = HIGH; //used just once to perform a full rotation cycle 101// notes in the melody: 102int melody[] = {NOTE_C4, NOTE_G3,NOTE_G3, NOTE_A3, NOTE_G3,0, NOTE_B3, NOTE_C4}; 103// note durations: 4 = quarter note, 8 = eighth note, etc.: 104int noteDurations[] = {4,8,8,4,4,4,4,4 }; 105// variables to define the sequence of execution of the notes 106int note = 1; 107/* Variables currXxxHeading contain increment number 108 and are used to check the rotation limits */ 109int currHorHeading = 0; 110int currVerHeading = 0; 111/* Variables currXxxPosition contain step number 112 and are used to control the stepper */ 113int currHorPosition = 0; 114int currVerPosition = 0; 115 116/* Not all photoresistors were born equal... 117to avoid resistance readings affected by differences 118among the photoresistors, the turret head was 119put under direct, perpendicular, even illumination 120and one of the photoresistors was chosen as reference. 121Thus, a normalization factor was empyrically determined 122as the multiplying factor to obtain an even reading by 123all the photoresistors */ 124float normalizeResUp=1.08; 125float normalizeResDown=1; // reference for the other photoresistors 126float normalizeResRight=1.02; 127float normalizeResLeft=0.93; 128 129/*-----( Allowed delta between opposite photoresistors 130readings - if exceeded, triggers stepper )----- 131declared as float just to be safe - it 132will be compared with floating point values 133*/ 134float allowedDelta=50; 135 136void play_note(int thisNote) { 137 // to calculate the note duration, take one second 138 // divided by the note type. 139 //e.g. quarter note = 1000 / 4, eighth note = 1000/8, etc. 140 int noteDuration = 1000/noteDurations[thisNote]; 141 tone(12, melody[thisNote],noteDuration); 142 // to distinguish the notes, set a minimum time between them. 143 // the note's duration + 30% seems to work well: 144 int pauseBetweenNotes = noteDuration * 1.30; 145 delay(pauseBetweenNotes); 146 // stop the tone playing: 147 noTone(12); 148 } 149 150void play_tune_1() { 151 // plays a tune in direct direction 152 Serial.println("motivo 1"); 153 for (note = 0; note < 8; note = note + 1) { 154 play_note(note); 155 } 156 } 157 158void play_tune_2() { 159 // plays a tune in reverse direction 160 Serial.println("motivo 2"); 161 for (note = 8; note > 0; note = note - 1) { 162 play_note(note); 163 } 164 } 165 166boolean debounce(boolean last) //debounce function for switch 167{ 168 boolean current = digitalRead(switchPin); 169 if (last != current) 170 { 171 delay(5); 172 current = digitalRead(switchPin); 173 } 174 return current; 175} 176 177 178void rotateAzimuth(char direction) { 179 180 currHorPosition=stepper1.currentPosition(); // Horizontal (Azimuth) position is managed by Stepper 1 181 //Serial.println("Current horizontal position (steps)"); 182 //Serial.println(currHorPosition, DEC); // currHorPosition is printed in decimal format 183 //Serial.println("Current horizontal heading (4,5Deg increments)"); 184 //Serial.println(currHorHeading, DEC); // currHorHeading is printed in decimal format 185 186 switch (direction) { 187 case 'L': 188 //Attempt rotation left 189 if(currHorHeading>leftLimit) // check if left rotation limit is reached 190 { 191 Serial.println("Performing left rotation"); 192 digitalWrite(ledLeft,HIGH); // put the left led on 193 stepper1.moveTo(currHorPosition-50); // 50 microsteps left 194 while (stepper1.currentPosition() != currHorPosition-50) 195 stepper1.runSpeedToPosition(); 196 currHorHeading--; 197 digitalWrite(ledLeft,LOW); // put the left led off 198 return; 199 } 200 else 201 { 202 Serial.println("Left rotation limit reached!"); 203 play_note(1); // cannot move further to the left - buzz! 204 } 205 break; 206 case 'R': 207 //Attempt rotation to the right 208 if(currHorHeading<rightLimit) // check if right rotation limit is reached 209 { 210 Serial.println("Performing right rotation"); 211 digitalWrite(ledRight,HIGH); // put the right led on 212 stepper1.moveTo(currHorPosition+50); // 50 microsteps right 213 while (stepper1.currentPosition() != currHorPosition+50) 214 stepper1.runSpeedToPosition(); 215 currHorHeading++; 216 digitalWrite(ledRight,LOW); // put the right led off 217 } 218 else 219 { 220 Serial.println("Right rotation limit reached!"); 221 play_note(2); // cannot move further to the right - buzz! 222 } 223 break; 224 } 225} 226 227void rotateZenith(char direction) { 228 229 currVerPosition=stepper2.currentPosition(); // Vertical (Zenith) position is managed by Stepper 2 230 //Serial.println("Current vertical position (steps)"); 231 //Serial.println(currVerPosition, DEC); // currVerPosition is printed in decimal format 232 //Serial.println("Current vertical heading (4,5Deg increments)"); 233 //Serial.println(currVerHeading, DEC); // currVerHeading is printed in decimal format 234 235 switch (direction) { 236 case 'U': 237 //Attempt rotation up 238 if(currVerHeading<upLimit) // check if upper rotation limit is reached 239 { 240 Serial.println("Performing up rotation"); 241 digitalWrite(ledUp,HIGH); // put the up led on 242 stepper2.moveTo(currVerPosition+50); // 50 microsteps up 243 while (stepper2.currentPosition() != currVerPosition+50) 244 stepper2.runSpeedToPosition(); 245 currVerHeading++; 246 digitalWrite(ledUp,LOW); // put the up led off 247 } 248 else 249 { 250 Serial.println("Up rotation limit reached!"); 251 play_note(3); // cannot move further up - buzz! 252 } 253 break; 254 case 'D': 255 //Attempt rotation down 256 if(currVerHeading>downLimit) // check if down rotation limit is reached 257 { 258 Serial.println("Performing down rotation"); 259 digitalWrite(ledDown,HIGH); // put the down led on 260 stepper2.moveTo(currVerPosition-50); // 50 microsteps down 261 while (stepper2.currentPosition() != currVerPosition-50) 262 stepper2.runSpeedToPosition(); 263 currVerHeading--; 264 digitalWrite(ledDown,LOW); // put the down led off 265 } 266 else 267 { 268 Serial.println("Down rotation limit reached!"); 269 play_note(4); // cannot move further down - buzz! 270 } 271 break; 272 } 273} 274 275void operate_turret() 276{ 277 /* Which resistor is getting most light? The one with the HIGHEST READING! The analog line 278 reads a voltage, and this voltage INCREASES as resistance DECREASES as light intensity 279 INCREASES - so there is a direct relation between light intensity and voltage: I should 280 rotate towards the photoresistance givving the HIGHEST reading! See also 281 https://learn.adafruit.com/photocells/using-a-photocell */ 282 283 float valLeft = analogRead(photoResLeft); // valLeft is used to save the reading from photoresistor A0 Left 284 // Serial.println("photoresistor Left - "); 285 // Serial.println(valLeft, DEC); // valLeft is printed in decimal format 286 valLeft=valLeft*normalizeResLeft; 287 // Serial.println("Normalized output Left - "); 288 // Serial.println(valLeft, DEC); // normalizeResLeft is printed in decimal format 289 290 291 float valRight = analogRead(photoResRight); // valRight is used to save the reading from photoresistor A1 Right 292 // Serial.println("photoresistor Right - "); 293 // Serial.println(valRight, DEC); // valRight is printed in decimal format 294 valRight=valRight*normalizeResRight; 295 // Serial.println("Normalized output Right - "); 296 // Serial.println(valRight, DEC); // normalizeResRight is printed in decimal format 297 298 float deltaH = abs(valLeft-valRight); 299 300 float valUp = analogRead(photoResUp); // valUp is used to save the reading from photoresistor A2 Up 301 // Serial.println("photoresistor Up - "); 302 // Serial.println(valUp, DEC); // valUp is printed in decimal format 303 valUp=valUp*normalizeResUp; 304 // Serial.println("Normalized output Up - "); 305 // Serial.println(valUp, DEC); // normalizeResUp is printed in decimal format 306 307 float valDown = analogRead(photoResDown); // valDown is used to save the reading from photoresistor A3 Down 308 // Serial.println("photoresistor Down - "); 309 // Serial.println(valDown, DEC); // valDown is printed in decimal format 310 valDown=valDown*normalizeResDown; 311 Serial.println("Normalized output Down - "); 312 Serial.println(valDown, DEC); // normalizeResUp is printed in decimal format 313 314 float deltaV = abs(valUp-valDown); 315 316 /* Check if horizontal rotation is needed */ 317 if (deltaH <= allowedDelta) // horizontal rotation NOT needed 318 {Serial.println("Azimuth correct - no rotation required");} 319 /* Check if vertical rotation is needed */ 320 if (deltaV <= allowedDelta) // vertical rotation NOT needed 321 {Serial.println("Zenith correct - no rotation required");} 322 323 /* Now I have the two deltas, horizontal and vertical. 324 Due to the rudimentary construction of the 4-photoresistors head, 325 it seems to me that heading correction should be applied one axis 326 at a time, choosing the axis where the delta is higher. Otherwise, 327 if movement on both axes is performed in the same cycle, correction 328 on the second axis may adversely affect the correction just performed 329 on the first axis */ 330 331 332 333 /* AS LONG AS MOST OPINIONS ADVISE AGAINST THE USE OF NESTED IFs, I CHANGED THE CODE 334 THAT NOW USES ITERATED IFs WITH MULTIPLE CONDITIONS IN PLACE OF NESTED IFs*/ 335 336 // Note: the probability of getting valLeft=valRight or valUp=valDown or deltaV=deltaH is very 337 // marginal, and I don't check that condition via <= or >=, the worst possible consequence being 338 // one inactive loop cycle (and surely the readings of the next cycle will show some difference 339 // between the values...) 340 341 // Horizontal rotation is performed if: 342 // 1) deltaH is above the threshold 343 // 2) deltaV is less than deltaH (otherwise I should correct deltaH first!) 344 345 if (deltaH > allowedDelta && deltaH > deltaV && valLeft>valRight) // left rotation required 346 { 347 // Serial.println("Attempting left rotation"); 348 rotateAzimuth('L'); // I do not need a string / double quotes but just a char 349 } 350 if (deltaH > allowedDelta && deltaH > deltaV && valLeft<valRight) // right rotation required 351 { 352 // Serial.println("Attempting right rotation"); 353 rotateAzimuth('R'); // I do not need a string / double quotes but just a char 354 } 355 356 // Vertical rotation is performed if: 357 // 1) deltaV is above the threshold 358 // 2) deltaH is less than deltaV (otherwise I should correct deltaV first!) 359 360 if (deltaV > allowedDelta && deltaV > deltaH && valUp>valDown) // up rotation required 361 { 362 // Serial.println("Attempting up rotation"); 363 rotateZenith('U'); // I do not need a string / double quotes but just a char 364 } 365 if (deltaV > allowedDelta && deltaV > deltaH && valUp<valDown) // down rotation required 366 { 367 // Serial.println("Attempting down rotation"); 368 rotateZenith('D'); // I do not need a string / double quotes but just a char 369 } 370 371} 372 373void fullCycle(){ 374 /* 375 On first activation I want to perform a full test swing 376 both horizontal and vertical. 377 */ 378 play_note(6); // buzz! 379 play_note(6); // buzz! 380 play_note(6); // buzz! 381 382 // eleven increments up, so that I am sure 383 // to engage the higher limit 384 for (int increm = 1; increm < 12; increm++) { 385 // Serial.println("Attempting up rotation"); 386 rotateZenith('U'); 387 } 388 // twentyone increments down, so that I am sure 389 // to engage the lower limit 390 for (int increm = 1; increm < 22; increm++) { 391 // Serial.println("Attempting down rotation"); 392 rotateZenith('D'); 393 } 394 // back to starting position 395 for (int increm = 1; increm < 11; increm++) { 396 // Serial.println("Attempting up rotation"); 397 rotateZenith('U'); 398 } 399 // eleven increments left, so that I am sure 400 // to engage the left limit 401 for (int increm = 1; increm < 22; increm++) { 402 // Serial.println("Attempting left rotation"); 403 rotateAzimuth('L'); 404 } 405 // twentyone increments right, so that I am sure 406 // to engage the right limit 407 for (int increm = 1; increm < 42; increm++) { 408 // Serial.println("Attempting right rotation"); 409 rotateAzimuth('R'); 410 } 411 // back to starting position 412 for (int increm = 1; increm < 21; increm++) { 413 // Serial.println("Attempting left rotation"); 414 rotateAzimuth('L'); 415 } 416} 417 418 419void setup() 420{ 421 /* Pin operation mode setup */ 422 pinMode(photoResLeft,INPUT); // Analog input of the photoresistor values 423 pinMode(photoResRight,INPUT); 424 pinMode(photoResUp,INPUT); 425 pinMode(photoResDown,INPUT); 426 pinMode(ledRight,OUTPUT); // Digital output for turning LED's on 427 pinMode(ledLeft,OUTPUT); // now there is only one LED for left + right so this line is redundant 428 pinMode(ledUp,OUTPUT); 429 pinMode(ledDown,OUTPUT); // now there is only one LED for up + down so this line is redundant 430 pinMode(switchPin, INPUT); // set pin 13 to input 431 pinMode(SLEEP, OUTPUT); // set pin 12 to output 432 433 stepper1.setMaxSpeed(1000.0); 434 stepper1.setAcceleration(100.0); 435 stepper1.setSpeed(1000); 436 437 stepper2.setMaxSpeed(1000.0); 438 stepper2.setAcceleration(100.0); 439 stepper2.setSpeed(1000); 440 441 /* Initialize serial communications */ 442 Serial.begin(9600); // Initialize serial communications 443 444 445 /* Initialize turret to neutral staring position */ 446 /* I enforce a rotation limit based on increment 447 number. The turret is manually set to neutral 448 position, it suffices to declare that the 449 current position at setup is the neutral 450 starting position on both axes */ 451 currHorHeading = neutralHorHeading; 452 currVerHeading = neutralVerHeading; 453} 454 455 456// Reminder: void loop() cannot be omitted even if 457// you put all the code in operate_turret() 458// you would need a void function called loop: 459 460void loop(){ 461 462currentButton = debounce(lastButton); 463 464// I use a button switch to enter a While loop 465// inside the main loop() 466 467 if (lastButton == LOW && currentButton == HIGH) 468 { 469 work = !work; //work is boolean variable for switch on/off 470 play_tune_1(); // play a melody every time the switch is pressed 471 } 472 lastButton = currentButton; 473 digitalWrite(SLEEP, work); //set SLEEP pin to value of work variable 474 475 476 while(work == HIGH){ 477 // I must check the button also inside the while loop.. 478 // otherwise it will be endless 479 currentButton = debounce(lastButton); 480 if (lastButton == LOW && currentButton == HIGH) 481 { 482 work = !work; //work is boolean variable for switch on/off 483 play_tune_2(); // play a melody every time the switch is pressed 484 } 485 lastButton = currentButton; 486 digitalWrite(SLEEP, work); //set SLEEP pin to value of work variable 487 488 Serial.println("Execution enabled - switch is ON"); 489 490 /* 491 if (firstTimeEver == HIGH) 492 { 493 firstTimeEver = !firstTimeEver; //used just once to call fullCycle() 494 fullCycle(); 495 } 496 */ 497 498 operate_turret(); 499 } 500 Serial.println("Execution disabled - Manually Center the turret to neutral position, and then press the switch on the breadboard"); 501}
Downloadable files
Fritzing diagram (circuit only)
Fritzing diagram (circuit only)
Fritzing diagram (circuit only)
Fritzing diagram (circuit only)
Comments
Only logged in users can leave comments