Sensory inputs: (i) Raspberry message and (ii) Joystick position.
i.- Raspberry message For communication with a Raspberry Pi we use four-bit messages carried over 4 input pins. For each axis of motion we need two bits and therefore we need two digital pins. To move the X axis “up” (to higher angle values) or “down” (to lower angle values), we use PIN 2 and PIN 3. On these digital input pins, we can send in the following possible messages: a (0,0) message corresponds to stay-put while moving up and moving down messages corresponds to (1,0) and (0,1) respectively. Similarly for the Y axis we use PIN 4 and PIN 5 to transmite similar messanges: stay put (0,0), moving up (1,0) and moving down (0,1).
ii.- Joystick position To read the Joystick, we use two analog input pins, PIN 0 (A0) and PIN 1 (A1); these pins can have values ranging from 0 to 1023. At the center position, for each degree of freedom (XY) the stick is resting vertically at 90 degrees and the Joystick analog value is around 512. We use a threshold value of 64 to detect a meaningful deflection and trigger actuation.
Actuation outputs: (i) XY stage movement (servo control), (ii) signal back to the Raspberry Pi (one bit), and (iii) LCD display of angle coordinates (using the i2c protocol).
i.- XY stage movement For actuation, the Arduino controls XY movement via two servos. For this, we use the Servo library and define two PWM capable pins as servo output pins. We use PIN 9 and PIN 10 for X and Y servos respectively.
ii.- Signal back to Raspberry In order for the Raspberry Pi to know if the Arduino is listening (to Joystick or Raspberry 4-bit messages) or bussy actuating a servo movement, we mirror the Arduino two-state machine state of execution to PIN 12 (signal_pin) to inform the Raspberry Pi the Arduino is busy moving servos.
iii.- LCD display of angle coordinates For user info, the Arduino uses the i2c protocol (2 pins) to control a 16×2 LCD where it displays the angle coordinates of the servos. For i2c communication with the LCD, we use PIN A4 for SDA and PIN A5 for SCL as well as the Wire and LiquidCrystal_I2C libraries.
The code in the file Arduino_XY.ino controls the XY OpenStage via two servo motors while listening to a Joystick and a Raspberry Pi 4-bit message acting as descriptor of XY movement wishes. This Arduino also displays servo motor coordinates (from 0 to 180 degrees) in a 16×2 LCD controlled via i2c. The Arduino_XY.ino code shown here coordinates sensing input and actuating output as a two-state automata. The code, listens for a message (either from the Joystick or the Raspberry) and then upon its detection actuates stage movement. We first include theServo, Wire, and LiquidCrystal_I2C libraries,
#include <Servo.h> #include <Wire.h> #include <LiquidCrystal_I2C.h>
Then we define the input pin variables needed, to listen to the Raspberry’s movement wishes (PINS 2,3,4,5), and to write servo angles to the motors over PINS 9 and 10. We also set up the signal_pin (PIN 12) needed so the Arduino can send out a signal back to the Raspberry Pi communicating that actuation is taken place.
// declare pins for digital input from the RASPBERRY PI int x_up_pin = 2; int x_down_pin = 3 ; int y_up_pin = 4; int y_down_pin = 5 ; // declare pins for servo output int servo_x_Pin = 9; int servo_y_Pin = 10; // declare flag pin to send digital output to the RASPBERRY PI int signal_pin = 12;
We create two servo objects using the Servo.h library
// declare 2 servos to control the degrees of freedom of the XY Stage Servo servo_x; Servo servo_y;
We also create a LCD controller lcdusing the LiquidCrystal_I2C.h library,
//declare an i2c LCD to display the servo coordinated LiquidCrystal_I2C lcd(0x27, 16, 2);
Then we define variables servo_x_Angle and servo_y_Angle to keep track of coordinates in the microscope’s viewing area. As servos have a range between 0 and 180, we set our starting position (at start up) in the middle of their range.
// set up the default position of the servos: in the middle int servo_x_Angle = 90; int servo_y_Angle = 90;
We initialize the two-state machine not actuating but listening.
// actuation flag: not moving int actuating = false; int moving_x = 0; int moving_y = 0;
Finally, we set up some variables to later define analog pin identities JoyStick_X and JoyStick_Y needed to read the Joystick state and also set up the Joystick sensitivity by setting up a threshold value where to consider a movement has to be actuated in the direction the Joystick has bend.
// Joystick sensitivity int threshold =64; // Analog input pins for Joystick XY values int JoyStick_X =0;// pin number for x int JoyStick_Y =1;// pin number for y
After setting all these definitions in the global declarations section of the code we proceed with coding the setup and the loop functions. First, in the setup function we define the signal_pin needed to communicate to the Raspberry that the Arduino two-state machine is in the listening or actuating state; then we set the mode of signal_pin (PIN 12) as an output pin and write LOW state to it. Next we proceed to attach the servo controllers servo_x and servo_x to the corresponding pins servo_x_Pin and servo_x_Pin (PIN 9 and PIN 10), initialize the LCD library (and print “OpenStage” to the display), center the stage (writing the default angle to the servos), and setting up the 4 bit digital input pins (2,3,4,5) to code for the Raspberry Pi stage-movement wishes. Finally, a small delay is introduced and the function exits leaving the Arduino ready to execute the loop function ad infinitum (provided with power).
void setup() { // communicate to RASPBERRY if actuating a stage move or listening pinMode(signal_pin, OUTPUT); // to send 1 bit message to pi digitalWrite(signal_pin, LOW);// not actuating // prepare the servos Serial.begin(9600);// 9600 bps servo_x.attach(servo_x_Pin); servo_y.attach(servo_y_Pin); // prepare the LCD lcd.begin(); // Turn on the blacklight and print a message. lcd.backlight(); lcd.setCursor(0,0); lcd.print("OpenStage"); // position at the center of the scanning area (x,y)=(90,90) servo_x.write(servo_x_Angle); servo_y.write(servo_y_Angle); // RASPBERRY communication pins (4 bits) pinMode(x_up_pin, INPUT); // GUI button x.step-up pinMode(x_down_pin, INPUT); // GUI button x.step-down pinMode(y_up_pin, INPUT); // GUI button y.step-up pinMode(y_down_pin, INPUT); // GUI button y.step-down delay(50); }
After initiating the LCD, we write the servos with the default XY position; that is located at the center of the viewing area at the coordinate value = (90, 90). Finally we set up four input pins to hear for the Raspberry Pi messages.
To finish up, we need to specify the loop function. The loop executes for as long as power is provided to the Arduino. These function is divided into two parts: (a) listens to inputs and (b) executes output. While listening to inputs, the looplooks for up or down movement instructions for each degree of freedom (X and Y). These instructions might come from the Joystick or the four-bit Raspberry Pi input message. First, we evaluate if there is a request to actuate a x.down movement, then x.up, y.down, y.up respectively. On each of these four if statements actuation can be trigger by either the Joystick bending beyond the threshold towards one particular direction or by the Raspberry Pi four bit input message. If any of the conditions above is met, the loop decides a stage move must be executed in a particular degree of freedom (X or Y) and in a particular direction (up or down). After the input is detected, a new movement angle is communicated to the corresponding servo and the Raspberry Pi is notified that a movement has occurred via the signal bit PIN 12. Thus, loop consist of four if statements; two per degree of freedom checking for movement messages. The other four “nested” if statements present take care of the exceptions needed to deal with the endpoints of the range (servos only go from 0 to 180 degrees).
void loop() { // X movement (signal detection) // x.up if((analogRead(JoyStick_X) > (512 + threshold) && actuating == false) || (digitalRead(x_up_pin)== HIGH && actuating == false)) { if(servo_x_Angle < 180) servo_x_Angle++; actuating = true; } //x.down if((analogRead(JoyStick_X) < (512 - threshold) && actuating==false) ||(digitalRead(x_down_pin)== HIGH && actuating == false)) { if(servo_x_Angle > 0)servo_x_Angle--; actuating = true; } // Y movement (signal detection) //y.up if((analogRead(JoyStick_Y) > (512 + threshold)&& actuating==false) ||(digitalRead(y_up_pin)== HIGH && actuating==false)) { if(servo_y_Angle < 180) servo_y_Angle++; actuating= true; } //y.down if((analogRead(JoyStick_Y) < (512 - threshold)&& actuating==false) ||(digitalRead(y_down_pin)== HIGH && actuating==false)) { if(servo_y_Angle > 0) servo_y_Angle--; actuating = true; } // Move if(actuating == true ) { digitalWrite(signal_pin, HIGH); // actuating servo_x.write(servo_x_Angle); servo_y.write(servo_y_Angle); delay(500); actuating = false; digitalWrite(signal_pin, LOW); // not actuating } // display pos info lcd.setCursor(0,1); lcd.print("position:"); lcd.print(servo_x_Angle); lcd.print(","); lcd.print(servo_y_Angle); lcd.print(" "); delay(50); }