2 – Arduino, where to start with it and my embedded code.
Instalment two has arrived! So this naturally means that I haven’t been busted by the boss for writing about non-work related stuff…and yes, I shall stop banging on about the boss now.
As mentioned in the previous blogg, the Human Tracking camera system consists of various different shall we say, modules.
The control module is tasked with controlling the servos of the pan-tilt system, receiving feedback system data and receiving data via serial port from the software on the computer to then convert into servo movements. Some other functions include a manual override to control the pan-tilt system with a joystick and LEDs used as indicators for the feedback system and for the manual override. As you can probably visualise, this control platform is a hub for all the hardware in the system.
For my control board, I have chosen to use the Arduino Nano V3.0 As mentioned in the last blogg, there is an enormous amount of support available on the internet for Arduino products.
If you’re a beginner with regards to electronics or embedded software, please familiarise yourself with some basic concepts before going any further. The best place to get a basic understanding of most of the applied concepts in this project, is the Arduino website’s learning environment:
or
The Arduino link above will actually show you how to install the IDE and provides the absolute novice with a starting point and guidance to learning more about their product and thus micro controllers in a very hands-on way.
The same can be said for Sparkfun. Both companies have based their product lines on ATmel’s ATmega controllers.
Arduino has adapted what looks like a Processing IDE to make it very easy to write, compile and upload your code to whichever Arduino board you’re using. The IDE is packed full of well commented examples which always help. A word of caution if you want to be of any use as an embedded software engineer at some point in the future…..don’t follow the examples blindly. Try to change variables to see what happens when you have changed them. Try to understand the code as you go along. The language used in the Arduino IDE is based on C, so if you have any experience in C, you will feel like you’re mum is talking to you…..it’s that easy. If you have a little experience with C, you may be wondering what nonsense I’m talking, because the words don’t look the same? Well, if you do feel like Arduino is speaking “Greek” to you, it’s possibly because of the many libraries that have been written to somewhat change C to look like Arduino’s own language. Don’t worry, as you work through the examples, you will get used to what to tell the micro controller in order to do as you say.
So….I can go on about Arduino and what you need to know and what you should probably know and all that stuff, but if you are really interested in learning embedded software programming, this is by far the easiest way to start and probably the cheapest. All you need is a PC, an Arduino board and a USB cable. You probably have a PC and a USB cable, so you need only spend a tenner.
Here is the Arduino code that I used for this project:
/*This version aims to incorporate a manual override and manual joystick control of the system to incorporate feedback. The manual override and manual control in this example works well with the feedback system.*/ //Include the Servo and interrupt libraries #include<Servo/Servo.h> #include<avr/interrupt.h> void establishContact(); void LimitCheck(); void Scan(); //Initialize the myservo objects Servoxservo,yservo; /*The volatile variables are used in the interrupt routine*/ volatile int icountX=0,icountY=0;//counts every time a pin goes high or low volatile int data1=-1,data2=-1,a=-1,b=-1;//use -1, because -1 does not appear in the data1 added combination volatile int data3=-1,data4=-1,k=-1,l=-1;//for y interrupt volatile int statusX=0x0,statusY=0x0;//interrupt status int ServoIn,move_checkX=0,move_checkY=0,x=90,y=90,x1,y1,idle=0;//servo input and position variables bool scan=false,upLimit=false,lowLimit=false; bool xUpLimInd=false,xLowLimInd=false,doScan=false,yUpLimInd=false,yLowLimInd=false; bool SendSerial=false,received=false,dataReceived=false,readyToReceive=false; /*Character arrays are used to store the received serial data bytes*/ char val[5],val1[5];//data received from serial port int z=0,i=0; /*Manual control variables*/ const int joyH=5; // L/R Parallax Thumbstick const int joyV=4; // U/D Parallax Thumbstick const int buttonPin=7; //Push Button Parallax Thumbstick const int buttonLED=12; int state=HIGH; // the current state of the output pin int NowButton; // the current reading from the input pin int PrevButton=LOW; // the previous reading from the input pin boolbutton=false; // the follow variables are long’s because the time, measured in miliseconds, // will quickly become a bigger number than can be stored in an int. long time=0; // the last time the output pin was toggled long debounce=200; // the debounce time, increase if the output flickers //Setup the environment (this procedure only runs once) void setup() { /*Attach the servos to the relevant pins and “zero” them at 90 degrees.*/ yservo.attach(11); yservo.write(y); xservo.attach(10); xservo.write(x); delay(100);//wait for the servos to move into position /*Set pin 9 and 12 up as outputs, used for debugging mainly*/ pinMode(9,OUTPUT);//x Feedback indicator pinMode(8,OUTPUT);//y Feedback Indicator pinMode(buttonLED,OUTPUT);//Manual Override indicator Serial.begin(57600); // initialize the pushbutton pin as an input: pinMode(buttonPin,INPUT); digitalWrite(buttonPin,HIGH);//turn on the pull-up resistor for the button-pin /* Configure Pin Change Interrupts*/ cli(); // disable global interrupts PCICR=0x04;//Set Pin Change Interrupt Control Register for timer 2 // Enable D3,D4,D5, D6 – PCINT19:22 PCMSK2=0x78; PCIFR|=0x00;//reset the interrupt flag sei();//enable all interrupts establishContact(); //establish initial contact with the software icountX=90;//set icount to be the same as the servo “zero” position icountY=90; } //The void loop runs continuously void loop() { /*Local variables are only used while the loop runs, they are reset every time the loop starts again*/ intcount=0; intfeedbackX,feedbackY; intservoValX,servoValY;// variables to read the value from the analog pin NowButton=digitalRead(buttonPin);// read the state of the pushbutton value: if(NowButton==HIGH&&PrevButton==LOW&&millis()-time>debounce) { if(state==HIGH) { state=LOW; button=false; digitalWrite(buttonLED,LOW); //delay(10); } else { state=HIGH; button=true; digitalWrite(buttonLED,HIGH); //delay(10); } time=millis(); } /*If the push button has been pressed, the coordinates do not have to be received from Serial.*/ if(button==true) { // Read the horizontal joystick value (value between 0 and 1023) servoValX=analogRead(joyH); x=map(servoValX,0,1023,3,179);// scale it to use it with the servo (result between 0 and 180) //xservo.write(servoValX); // sets the servo position according to the scaled value // Read the horizontal joystick value (value between 0 and 1023) servoValY=analogRead(joyV); y=map(servoValY,0,1023,30,140);// scale it to use it with the servo (result between 70 and 180) //yservo.write(servoValY); // sets the servo position according to the scaled value // Display Joystick values using the serial monitor //outputJoystick(); delay(15); // waits for the servo to get there } else { /*Check for incoming serial data*/ if(Serial.available()>0)//if the port is open { //delay(10); Serial.readBytesUntil(‘\n’,val,5);//read the data until an LF character is detected and store in val if(val[0]==’A’)//check that the data is valid { idle=0; doScan=false; // read the rest of the stream that now starts with x and ends with ‘T’ dataReceived=true;//Set the indicator that new data was received /*Bytes are sent in from the software. Byte values naturally range from 0-127, but a byte can technically store any binary number ranging from 0 – 255. Thus the byte range becomes -127 to +127 and hence the necessity convert values larger than 127, because they are assumed to be in the -127 to 0 range.*/ if(val[1]<0)//if the byte is negative (a char larger than 127) { x1=x;//New value for x is available, so store old value in x1 x=127+val[1]+129; count=1; } else { x1=x;//New value for x is available, so store old value in x1 x=val[1]; count=1; } if(val[2]<0)//if the byte is negative (a char larger than 127) { y1=y;//New value for y is available, so store old value in y1 y=127+val[2]+129; count=2; } else { y1=y;//New value for y is available, so store old value in y1 y=val[2]; count=2; } /*Copy val into val1 for later comparison*/ val1[0]=val[0]; val1[1]=val[1]; val1[2]=val[2]; /*Reset val to contain character 32 or ‘space’*/ val[0]=val[1]=val[2]=val[3]=val[4]=’ ‘; Serial.write(‘T’);//Send ‘T’ to indicate a successful transmission Serial.write(‘\n’);//Send LF character to indicate end of stream } } /*If no serial data is at the port for reception and the port is closed Arduino will keep sending ‘T’ to indicate that it is ready to receive data. If no data is sent for a finite time, the Scan function will run.*/ else if(Serial.available()<=0) { idle++; //Serial.print(“idle:”); //Serial.println(idle); if(idle==100) { doScan=true; } /*After the scan function has completed a full limit-to-limit scan, the camera y position is reset. For Y scanning, comment this if statement out.*/ if(idle>=360) { y1=89; y=90; } if(doScan==true) { Scan(); } Serial.write(‘T’);//keep sending T to indicate Arduino ready to receive //Serial.write(“No data available”); Serial.write(‘\n’); //Serial.write(NULL); val[0]=val[1]=val[2]=val[3]=val[4]=’ ‘; delay(10);//delay to allow the software to read the port } } if(x!=x1)//if a new x-value has been sent { xservo.write(x); //Serial.print(x); } if(y!=y1)//if a new y-value has been sent { yservo.write(y); } /*If the interrupt routine has run between the last loop and the current loop status1 will be larger than 1*/ if(statusX==1) { move_checkX+=icountX; icountX=0; statusX=0; } if(statusY==1) { move_checkY+=icountY; icountY=0; statusY=0; } /*move_check is where the calculated position value is stored as per the feedback system. If this value is within +/- 10 degrees of the x-value, then the feedback system reads successful, ie. no errors. When the feedback is positive, an LED is switched on at pin 9, if the feedback is negative, the LED is switched off*/ if(move_checkX<=(x+10)&&move_checkX>=(x-10)) { feedbackX=1; digitalWrite(9,HIGH); //Serial.print(“Xin:”); //Serial.print(x); //Serial.print(” Xout:”); //Serial.println(move_checkX); } else { feedbackX=0; digitalWrite(9,LOW); //Serial.print(“Xin:”); //Serial.print(x); //Serial.print(” Xout:”); //Serial.println(move_checkX); } if(move_checkY<=(y+10)&&move_checkY>=(y-10)) { feedbackY=1; digitalWrite(8,HIGH); //Serial.print(“Yin:”); //Serial.print(y); //Serial.print(” Yout:”); //Serial.println(move_checkY); } else { feedbackY=0; digitalWrite(8,LOW); //Serial.print(“Yin:”); //Serial.print(y); //Serial.print(” Yout:”); //Serial.println(move_checkY); } PrevButton=NowButton; } /*The interrupt routine currently only caters for one of the two feedback systems. When Pin 3 input changes either from high to low or from low to high, the interrupt will occur.*/ ISR(PCINT2_vect) { PCIFR|=0x00;//reset the interrupt flag data3=(digitalRead((5))<<1)+digitalRead(6);//combine the data from the two y sensors data1=(digitalRead((3))<<1)+digitalRead(4);//combine the data from the two x sensors b=a; k=l; a=(data2<<4)+data1;//combine data1 and data2 k=(data3<<4)+data4;//combine data3 and data4 if(a!=b)//if no change occurred on y int pins { if(a==0x01||a==0x13||a==0x32||a==0x20)//check if moving Left or right {//if moving left, subtract count twice (1 count = 2 degrees) icountX–; icountX–; } elseif(a==0x02||a==0x23||a==0x31||a==0x10) {//if moving right, add count twice icountX++; icountX++; } data2=data1;//copy data1 into data2 statusX=1;//set the interrupt status to 1 to show that interrupt has occurred } if(k!=l) { if(k==0x01||k==0x13||k==0x32||k==0x20)//check if moving Left or right {//if moving left, subtract count twice (1 count = 2 degrees) icountY–; icountY–; } elseif(k==0x02||k==0x23||k==0x31||k==0x10) {//if moving right, add count twice icountY++; icountY++; } data4=data3;//copy data1 into data2 statusY=1;//set the interrupt status to 1 to show that interrupt has occurred } } /*The establishContact function runs during the start procedure. while the serial port is closed, a ‘T”\n’ is sent every 300ms to indicate that Arduino is ready to communicate. This serves as a type of synchronisation function. If Arduino is switched off or for some reason not ready to receive data, the Processing software will not progress onto the Draw function. The same is also true the other way around, if Arduino is switched on, but the software is not running, the program will not progress past this function.*/ void establishContact() { while(Serial.available()<=0) { Serial.write(‘T’); // send a capital T Serial.write(‘\n’); delay(300); } readyToReceive=true; } /*LimitCheck is used to check that the values that are sent to the servos stay within pre-defined limits.*/ void LimitCheck() { intxUpLimit=179; intxLowLimit=3; intyUpLimit=141; intyLowLimit=31; if(x>=xUpLimit) { x=xUpLimit; xUpLimInd=true; xLowLimInd=false; } if(x<=xLowLimit) { x=xLowLimit; xLowLimInd=true; xUpLimInd=false; } if(y>=yUpLimit) { y=yUpLimit; yUpLimInd=true; yLowLimInd=false; } if(y<=yLowLimit) { y=yLowLimit; yLowLimInd=true; yUpLimInd=false; } } /*The Scan function is called when no incoming data had been received from the control software for a set period of time. The function changes the x-value slowly without polling to allow for any incoming serial data to interrupt the scanning process.*/ void Scan() { //x scanning if(x>=x1) { x1=x; x++; LimitCheck(); if(xUpLimInd==true) { xUpLimInd=false; x–; x–; } } elseif(x<x1) { x1=x; x–; LimitCheck(); if(xLowLimInd==true) { xLowLimInd=false; x++; x++; } } ////y scanning //if(y>=y1) //{ //y1 = y; //y++; //LimitCheck(); //if(yUpLimInd==true) //{ //yUpLimInd=false; //y–; //y–; //} //} //else if(y<y1) //{ //y1 = y; //y–; //LimitCheck(); //if(yLowLimInd==true) //{ //yLowLimInd=false; //y++; //y++; //} //} delay(50);//this delay function serves as a type of speed control for the Scan function }The code above might seem quite complex to anyone that didn’t write it, but with a bit of determination, I’m sure you’ll figure it out. Also to note, is the fact that I didn’t actually use the Arduino IDE to write my code. I prefer Atmel Studio 6, because it is based on Visual studio and I learned to program in that. If you use the Arduino IDE, the include paths need to be changed.
As we cover various parts of the project, I shall refer back to the relevant code snippets for the topic so that in the end, you should know what all of it means and how it fits together in the project.
As you may have spotted, there is also a lot of commented code to help the debugging and some bits to help test the hardware.
For now, I shall step through the main functions and what their roles are in the software:
First off, for those of you new to embedded software, one characteristic of embedded, is that there is always a continuous loop somewhere in the program. Remember, there is normally no screen and keyboard connected to an embedded system, so whatever it does, has to happen over and over again. So the basic layout of an Arduino program, consists of a Setup function that runs only at the start of the program, say when the board is plugged in for the first time or when the reset button has been pressed, and a Loop function. This loop function is where the magic normally happens after the board has been set up.
Apart from the two main functions, I also have:
void establishContact();
This function is used to help set up a successful serial communication link with the software on the PC.
void LimitCheck();
The limit check function is used to check whether the x and y coordinates that the board had received are within the specified range for my servos. For a start, the servos can only move from 0-180 degrees. Also, the servo that controls up and down movement (y-servo) I only want to move from 30-140 degrees. We don’t want to see sky and we don’t want to see floor on the camera, remember?
void Scan();
This function is called upon when my camera has not detected anyone in the frame to track. When there’s no-one, I want the camera to move from side to side to see if it can find someone to track. Also, if you jumped out of the camera’s view, the scan function would scan towards the side that you jumped, first.
So for now you have enough to ponder about and a lot of background reading to do.
Next week, I shall talk about the feedback system, which is a quadrature encoder. That part took me 3 months to figure out, be it around Christmas when my full attention was diverted to the man in the red suit, family, plenty of booze and a lot of friends.