For the last couple of years I’ve been working on spray-coated solar cells at the University of Sheffield in the EPMM group. This group is doing lots of interesting work on thin-film next-generation low-cost solar cells and spray-coating is a great way to go about making them; it’s fast, versatile and scalable. Instead of using blocks of silicon, active (functional) materials are dissolved in solvents to make inks. Then inks can be processed using conventional printing techniques, or even sprayed, to leave thin-films that can harvest light or transport charge which, through careful optimisation, gives us a solar cell.
I’ve been used to using ultrasonic spray-coaters in my work but these instruments are really bulky and not at all portable. What I wanted was a system that we could use in Sheffield to make solar cells in the lab but would also be portable enough for us to take to an Xray light source. This would enable us to start to understand the way that semiconducting films form in real-time at the nanoscale.
So the brief was to make a portable spray-coater suitable for solar cell fabrication that was programmable and would give reasonably repeatable processes. To address this, my basic idea was to strip out a linear drive system from an inkjet printer and mount a simple artists airbrush on the carriage which I would control with a solenoid valve. With an Arduino microcontroller and some embedded software, I was able to control the drive system and solenoid valve to spray lines of water as you can see in the video. At this point, I finished my contract so I wasn’t able to take this any work any further. But now that the kit is built, it would be possible to do this project. Watch this space. Below I’m going to lay out the schematics and details of how I actually did it!
As a Christmas present to myself, I bought the Ardiuno starter kit. It really kick started things for me and took me from absolute ignorance to a “light-bulb moment”. Originally, I got the idea to use the H-Bridge project (Zeotrope project No. 10) form the kit. In the kit you get an L293 H-Bridge, a circuit and code to drive it among lots of other stuff. In a nutshell, an H-Bridge is an IC composed of several MOSFETs which you can use to control a small DC motor (speed/direction/on-off). I quickly realised that this wasn’t going to cut it however – the load requirement of the motor I was using was too much for the poor little chap. The peak output is only 1.2A and after some digging, I discovered that the motor in the drive unit would draw at least 1A at no load and up to 40A if stalled! You’d need some serious power electronics for this so made the assumption that I need to uprate things a little and I went for the Arduino motor shield which is rated at 2A (per channel). Turned out that this was enough to drive the motor and with pulse width modulation and some logic signals, I could control speed and direction of the motor.

To read the carriage position, you need to know about quadrature encoders. I found a nice tutorial on this here. Amazingly, it’s possible to get to an accuracy of 0.07mm! But if you want to print at high resolution then I guess that’s not all that surprising. For this application, we don’t need anywhere near this accuracy – cm accuracy is probably about good enough but it’s nice to have it all the same. The position is read by a light gate and encoder strip which is a piece of acetate with lots of lines printed really close together. As the carriage moves along, we see a square wave from the light gate as the lines break the beam and if we count these, then we can follow the position of the printer head. Neat! The code is relatively straightforward. So, all that was left was to integrate these two functions and sort out some of the details such as an interface, power supplies, solenoid valve and some hardware to mount everything.
Linear drive system



Final schematics and boards


Here (see video below), you can see me testing out the printer carriage with the boards that I made. Note that the movement is a bit jerky because I removed a retaining strip from above the carriage so I could have electrical access – this makes the carriage droop downwards and seems to add a bit of friction.
The code
//yellow is Q line? //blue is I line? // Interrupt information // 0 on pin 2 // 1 on pin 3 #define encoderI 2 #define encoderQ 3 // Only use one interrupt in this example volatile int count; void setup() { Serial.begin(9600); count=0; pinMode(encoderI, INPUT); pinMode(encoderQ, INPUT); attachInterrupt(0, handleEncoder, CHANGE); } void loop() { Serial.println(count); delay(10); } void handleEncoder() { if(digitalRead(encoderI) == digitalRead(encoderQ)) { count++; } else { count--; } }
Above is the code that I used to check that I could read positions from the quadrature encoder based on this youtube resource. It worked really nicely and so I moved on to controlling the motor and solenoid valve using a serial interface.
//130217 code for controlling printer head spray coater //commands issued as ascii commands via serial bus (USB) //version uses the motor shield /* COMMANDS AXXXX start position BXXXX finish position VXXX speed DXXXX delay in ms */ //yellow is Q line - pin3 //blue is I line - pin2 // Interrupt information // 0 on pin 2 blue // 1 on pin 3 yellow #define encoderI 2 #define encoderQ 3 // Only use one interrupt in this example volatile int count; //current position index int startPos = 0; //start of dep int finPos = 0; //end of dep int xPos = 0; int myDelay = 0; //delay after starting spray but before move String inputString = ""; //holds serial commands const int stopDistance = 2; //factor to determine stopping distance const int controlPin = 13; // channel B direction const int enablePin = 11; // channel B PWM input const int solenoidPin = 4; //solenoid control pin for starting spray const int solenoidOverride = 7; // manually turn solenoid on const int solenoidLED = 5; const int motorLED = 6; int motorSpeed = 220; int homeSpeed = 220; int approachSpeed = 200; //slower speed for approaching target position so you don't overshoot void setup() { Serial.begin(9600); count=0; pinMode(encoderI, INPUT); pinMode(encoderQ, INPUT); attachInterrupt(0, handleEncoder, CHANGE); // intialize the inputs and outputs pinMode(controlPin, OUTPUT); pinMode(enablePin, OUTPUT); pinMode(solenoidPin, OUTPUT); pinMode(solenoidOverride, INPUT); pinMode(solenoidLED, OUTPUT); pinMode(motorLED, OUTPUT); // pull outputs LOW to start digitalWrite(enablePin, LOW); digitalWrite(solenoidPin, LOW); digitalWrite(controlPin, LOW); digitalWrite(solenoidLED, LOW); digitalWrite(motorLED, LOW); // reserve 200 bytes for the inputString: inputString.reserve(200); } void loop() { int readUserSpeed; //decide which function to do with the switch case switch (inputString.charAt(0)){ case 'X': xPos = inputString.substring(1).toInt(); if (xPos < 0 || xPos > 4100){ Serial.println("Outside allowed range 0-4100"); xPos = 0; } Serial.print("Moving to "); Serial.println(xPos); moveStage(xPos); break; case 'A': startPos = inputString.substring(1).toInt(); if (startPos < 0 || startPos > 4100){ Serial.println("Outside allowed range 0-4100"); startPos = 0; } Serial.print("Start deposition at "); Serial.println(startPos); break; case 'B': finPos = inputString.substring(1).toInt(); if (finPos < 0 || finPos > 4100){ Serial.println("Outside allowed range 0-4100"); finPos = 0; } else { Serial.print("Finish deposition at "); Serial.println(finPos); } break; case 'D': myDelay = inputString.substring(1).toInt(); if (myDelay < 0 || myDelay > 9999){ Serial.println("Outside allowed range 0-9999"); myDelay = 0; } else { Serial.print("delay (ms) "); Serial.println(myDelay); } break; case 'V': readUserSpeed = inputString.substring(1).toInt(); //need to make sure this is between 0-255 if (readUserSpeed < 0 || readUserSpeed > 255){ Serial.println("Outside allowed range 0-255"); } else { motorSpeed = readUserSpeed; } Serial.print("Motor speed set to "); Serial.println(motorSpeed); break; case 'H': homeStage(); break; case 'Q': //query Serial.println("Current settings:"); Serial.print("Motor speed set to "); Serial.println(motorSpeed); Serial.print("Start deposition at "); Serial.println(startPos); Serial.print("Finish deposition at "); Serial.println(finPos); Serial.print("delay (ms) "); Serial.println(myDelay); Serial.print("current position "); Serial.println(count); break; case 'R': //run recipe Serial.println("Run recipe..."); moveStage(startPos); digitalWrite(solenoidPin, HIGH); digitalWrite(solenoidLED, HIGH); delay(myDelay); moveStage(finPos); digitalWrite(solenoidPin, LOW); digitalWrite(solenoidLED, LOW); Serial.println("recipe done."); break; default: digitalWrite(enablePin, LOW); } //override the solenoid when you push the button down if (digitalRead(solenoidOverride)){ digitalWrite(solenoidPin, HIGH); digitalWrite(solenoidLED, HIGH); } else { digitalWrite(solenoidPin, LOW); digitalWrite(solenoidLED, LOW); } inputString = ""; delay(10); } //update position counter void handleEncoder() { if(digitalRead(encoderI) == digitalRead(encoderQ)) { count--; } else { count++; } } //int dirState = 1; // 0/1 for R-to-L (count++)/L-to-R (count--) void motorDirection(int dirState) { // change the direction the motor spins by talking // to the control pins on the H-Bridge if (dirState == 1) { digitalWrite(controlPin, LOW); } else { digitalWrite(controlPin, HIGH); } } //read serial data if there is any void serialEvent() { if (Serial.available() > 1) { inputString = Serial.readString(); Serial.println(inputString); } } //better way to write things is to separate into functions for different things and then call within the serial.event void homeStage(){ //initialise homing - //go left until you stop moving Serial.println("Homing..."); int homePos = count + 1; //force into loop motorDirection(0); analogWrite(enablePin, homeSpeed); digitalWrite(motorLED, HIGH); while (homePos > count) { homePos = count; delay(50); Serial.println(count); } count = 0; analogWrite(enablePin, 0); digitalWrite(motorLED, LOW); Serial.println("Done"); } void moveStage(int targetPos) { //move stage function Serial.print("Moving stage to "); Serial.println(targetPos); while (abs(targetPos-count)>5){ Serial.println(count); // work out direction - 0/1 for R-to-L (count--)/L-to-R (count++) if (targetPos > count) { motorDirection(1); } else if (targetPos < count) { motorDirection(0); } //check how far we are away from the target and set motor speed if (abs(targetPos-count)<((motorSpeed-180)*stopDistance)) { // PWM the enable pin to vary the speed //going faster means you need to slow down earlier //use motorspeed or similar to manage stopping distance analogWrite(enablePin, approachSpeed); digitalWrite(motorLED, HIGH); } else { analogWrite(enablePin, motorSpeed); //otherwise go full speed digitalWrite(motorLED, HIGH); } delay(1); //think the delay is important to stop it checking position too often } analogWrite(enablePin, 0); digitalWrite(motorLED, LOW); Serial.println("Done"); //this doesn't print out. Why? }