Solar drawing machine - FabAcademy 2017

Table of contents

Our solar tracker uses sunlight to engrave a substrate with a low melting temperature [wax]. It is sensed by four LDR; it uses stepper motors and a servo to control the build plates positioning towards maximum light intensity. A fresnel lens will bundle the incoming solar rays which are reflected by a mirror perpendicularly towards the machine bed. A second lens will allow final focusing of the beam. The bed runs on two axes, enabling the 2D solar engraving of the substrate.

image postermtm Machine building project
1 minute video

Idea and sketches

By all

It is a machine composed of two mini-systems:

  1. The first one is to track the sunlight and focus it on one beam.
  2. The second is the machine bed on which the focused beam either engraves or draws a material that can be burnt, melt or cut.

As the sketch show, the solar tracker will follow the sun in order to keep the lens oriented in the right direction. It will be driven by servo or stepper motor. The machine be will use the Gestalt nodes for x and y movements. These movement will be controlled with a mouse to enable drawing. You will basically draw with the mouse and the light beam on a piece of wax.

image ConnectionsStepperMotor The idea

Initial experiments with lenses proved the feasibility of the concept. We used a lens-focussed sunbeam to cut paper and to engrave wax.

First test
Test with wax
Test with wax - 2

What are you testing

image TestBelt Test mechanism

Machine bed

By Cesar, Konstantin, Ahmed

Bed prototype

By Konstantin.

A rough sketch of a 2-axis bed was completed in Rhino. The design uses the motors of the MTM kit. Measurements are taken carefully from the motor threads and rods available in the fablab.

image bed-sketch The idea

Glide bearings are added too to the design for 3d printing.

image glide The idea

Bed design and bed assembly

By Konstantin.

Design is transferred to Fusion360 where joints are tested.

Assembly testing

Design is ready for laser cutting:

image fusion360_bed_layout Design laid flat

Bed is assembled.

image bed_assembled Bed assembled

There were no problems at this stage and everything fit snuggly.

image bed_assembled_2 Bed assembled

Testing and programming

By Cesar

Gestalt library is used to test the x-y movement of the bed. X-Y Plotter example is used to test the machine movement:

# Two stage example Virtual Machine file
# moves get set in Main
# usb port needs to be set in initInterfaces
# Nadya Peek Dec 2014

#------IMPORTS-------
from gestalt import nodes
from gestalt import interfaces
from gestalt import machines
from gestalt import functions
from gestalt.machines import elements
from gestalt.machines import kinematics
from gestalt.machines import state
from gestalt.utilities import notice
from gestalt.publish import rpc	#remote procedure call dispatcher
import time
import io

#------VIRTUAL MACHINE------
class virtualMachine(machines.virtualMachine):
	
	def initInterfaces(self):
		if self.providedInterface: self.fabnet = self.providedInterface		#providedInterface is defined in the virtualMachine class.
		else: self.fabnet = interfaces.gestaltInterface('FABNET', interfaces.serialInterface(baudRate = 115200, interfaceType = 'ftdi', portName = '/dev/ttyUSB0'))
		
	def initControllers(self):
		self.xAxisNode = nodes.networkedGestaltNode('X Axis', self.fabnet, filename = '086-005a.py', persistence = self.persistence)
		self.yAxisNode = nodes.networkedGestaltNode('Y Axis', self.fabnet, filename = '086-005a.py', persistence = self.persistence)

		self.xyNode = nodes.compoundNode(self.xAxisNode, self.yAxisNode)

	def initCoordinates(self):
		self.position = state.coordinate(['mm', 'mm'])
	
	def initKinematics(self):
		self.xAxis = elements.elementChain.forward([elements.microstep.forward(4), elements.stepper.forward(1.8), elements.leadscrew.forward(8), elements.invert.forward(True)])
		self.yAxis = elements.elementChain.forward([elements.microstep.forward(4), elements.stepper.forward(1.8), elements.leadscrew.forward(8), elements.invert.forward(False)])		
		self.stageKinematics = kinematics.direct(2)	#direct drive on all axes
	
	def initFunctions(self):
		self.move = functions.move(virtualMachine = self, virtualNode = self.xyNode, axes = [self.xAxis, self.yAxis], kinematics = self.stageKinematics, machinePosition = self.position,planner = 'null')
		self.jog = functions.jog(self.move)	#an incremental wrapper for the move function
		pass
		
	def initLast(self):
		#self.machineControl.setMotorCurrents(aCurrent = 0.8, bCurrent = 0.8, cCurrent = 0.8)
		#self.xNode.setVelocityRequest(0)	#clear velocity on nodes. Eventually this will be put in the motion planner on initialization to match state.
		pass
	
	def publish(self):
		#self.publisher.addNodes(self.machineControl)
		pass
	
	def getPosition(self):
		return {'position':self.position.future()}
	
	def setPosition(self, position  = [None]):
		self.position.future.set(position)

	def setSpindleSpeed(self, speedFraction):
		#self.machineControl.pwmRequest(speedFraction)
		pass

#------IF RUN DIRECTLY FROM TERMINAL------
if __name__ == '__main__':
	# The persistence file remembers the node you set. It'll generate the first time you run the
	# file. If you are hooking up a new node, delete the previous persistence file.
	stages = virtualMachine(persistenceFile = "examples.vmp")

	# You can load a new program onto the nodes if you are so inclined. This is currently set to 
	# the path to the 086-005 repository on Nadya's machine. 
	#stages.xyNode.loadProgram('../../../086-005/086-005a.hex')
	
	# This is a widget for setting the potentiometer to set the motor current limit on the nodes.
	# The A4982 has max 2A of current, running the widget will interactively help you set. 
	#stages.xyNode.setMotorCurrent(0.7)

	# This is for how fast the 
	stages.xyNode.setVelocityRequest(2)	
	
	
	# Some random moves to examples with
	#moves = [[x,0],[x,y],[0,y],[x,0]]
	

	moves = [
			
			[0,0],
			[10,0],
			[10,10],
			[0,10],
			[0,0],

			[0,0],
			[10,0],
			[10,10],
			[0,10],
			[0,0],
			
			[0,0],
			[10,0],
			[10,10],
			[0,10],
			[0,0],

			]

	
	
	# Move!
	for move in moves:
		stages.move(move, 0)
		status = stages.xAxisNode.spinStatusRequest()
		# This checks to see if the move is done.
		while status['stepsRemaining'] > 0:
			time.sleep(0.001)
			status = stages.xAxisNode.spinStatusRequest()	

The bed test ran successfully.

Mouse control functionality

By Cesar

Code is made from scratch for cursor-control functionality. Tkinter library is used; as well as some scaling conversions.

#Created on APRIL 2017
#@author: J.C. Mariscal

from gestalt import interfaces
from gestalt import machines
from gestalt import functions
from gestalt.machines import elements
from gestalt.machines import kinematics
from gestalt.machines import state
from gestalt.utilities import notice
from gestalt.publish import rpc    #remote procedure call dispatcher
import time
import io
import Tkinter as tk

size_bed_machine = 220.0
size_window = 500.0
scale_ratio = size_window/size_bed_machine
x_old = 0
y_old = 0
threshold = 0.1
machine_speed = 10

def motion(event):
    global x_old
    global y_old
    global threshold
    x, y = event.x, event.y
    x_machine = x/scale_ratio
    y_machine = y/scale_ratio
    print('window{}, {}'.format(x, y))
    print('machine:{}, {}'.format(x_machine, y_machine))
    if abs(x_machine - x_old) > threshold or abs(y_machine - y_old) > threshold:
        print('go')
        x_old = x_machine
        y_old = y_machine
        move = [-x_old, y_old] 
        stages.move(move, 0)
        status = stages.xAxisNode.spinStatusRequest()
        # This checks to see if the move is done.
        #while status['stepsRemaining'] > 0:
            #time.sleep(0.001)
            #status = stages.xAxisNode.spinStatusRequest()

# Two stage example Virtual Machine file
# moves get set in Main
# usb port needs to be set in initInterfaces
# Nadya Peek Dec 2014
#-----------------------------------------------------
class virtualMachine(machines.virtualMachine):

        
    def initInterfaces(self):
        if self.providedInterface: self.fabnet = self.providedInterface        #providedInterface is defined in the virtualMachine class.
        else: self.fabnet = interfaces.gestaltInterface('FABNET', interfaces.serialInterface(baudRate = 115200, interfaceType = 'ftdi', portName = '/dev/ttyUSB0'))
        
    def initControllers(self):
        self.xAxisNode = nodes.networkedGestaltNode('X Axis', self.fabnet, filename = '086-005a.py', persistence = self.persistence)
        self.yAxisNode = nodes.networkedGestaltNode('Y Axis', self.fabnet, filename = '086-005a.py', persistence = self.persistence)

        self.xyNode = nodes.compoundNode(self.xAxisNode, self.yAxisNode)

    def initCoordinates(self):
        self.position = state.coordinate(['mm', 'mm'])
    
    def initKinematics(self):
        self.xAxis = elements.elementChain.forward([elements.microstep.forward(4), elements.stepper.forward(1.8), elements.leadscrew.forward(8), elements.invert.forward(True)])
        self.yAxis = elements.elementChain.forward([elements.microstep.forward(4), elements.stepper.forward(1.8), elements.leadscrew.forward(8), elements.invert.forward(False)])        
        self.stageKinematics = kinematics.direct(2)    #direct drive on all axes
    
    def initFunctions(self):
        self.move = functions.move(virtualMachine = self, virtualNode = self.xyNode, axes = [self.xAxis, self.yAxis], kinematics = self.stageKinematics, machinePosition = self.position,planner = 'null')
        self.jog = functions.jog(self.move)    #an incremental wrapper for the move function
        pass
        
    def initLast(self):
        #self.machineControl.setMotorCurrents(aCurrent = 0.8, bCurrent = 0.8, cCurrent = 0.8)
        #self.xNode.setVelocityRequest(0)    #clear velocity on nodes. Eventually this will be put in the motion planner on initialization to match state.
        pass
    
    def publish(self):
        #self.publisher.addNodes(self.machineControl)
        pass
    
    def getPosition(self):
        return {'position':self.position.future()}
    
    def setPosition(self, position  = [None]):
        self.position.future.set(position)

    def setSpindleSpeed(self, speedFraction):
        #self.machineControl.pwmRequest(speedFraction)
        pass
#---------------------------------------------------------------------------

if __name__ == '__main__':
    stages = virtualMachine(persistenceFile = "examples.vmp")
    stages.xyNode.setVelocityRequest(machine_speed)   
    
    #move machine to initialize it
    stages.move([0.1, 0.1], 0)
    status = stages.xAxisNode.spinStatusRequest()
    
    # This checks to see if the move is done.
    while status['stepsRemaining'] > 0:
        time.sleep(0.001)
        status = stages.xAxisNode.spinStatusRequest()

    #global scale_ratio
    #global movement
    print('scale:{}',format(scale_ratio))
    root = tk.Tk()
    root.resizable(width=False, height=False)
    root.geometry('{}x{}'.format(500, 500))
    root.bind('> Motion>', motion)
    root.mainloop() #always end your script by calling the mainloop method of the root window. 
  

Code worked as expected.

There were no problems at this stage and everything fit snuggly.

Optics (fresnel lens/beam control)

By Dima, Ahmed, Konstantin & Joern

Fresnel lens

By Dima

Beam concentration

By Joern

This part of the machine design did not make it into the final machine, as it was to error prone and complicated to integrate it in the race against the clock. The idea was to make the beam hit the bed perpendicularly at any time by reflecting it by a small mirror after crossing the Fresnel lens. To create a sharp focus, a second converging lens was supposed to concentrate the beam again on the bed.

The complication however was the exact movement of mirror and lens. As we did not want to add more motor and servos. Belts were supposed to drive the correct orientation (not displayed here).

The concept looked like this:

Step 1 - Reflect

image LensMirror Concept for the mirror drive

Step 2 - Concentrate

image LensMirror Concept for the concentrator beam

For design was only implemented partly. For further details, I refer to my personal documentation.

Solar tracker

By Ketlli

The solar tracker is necessary for this system in order to orient the payload towards the sun. The payloads in our system are the fresnel reflector together with the bed of our system. The solar tracker will be helpful for the fresnel reflector to get the maximum beam of light and concentrate it to the bed.

Functional requirements

In order for the LDRs to be connected and work properly, they have to receive the same amount of sunlight, no less no more. To achieve that, something that would be able to give the system that ability had to be designed. So, a structure that when the sun is not perfectly perpendicular to the LDRs would give a shadow to them as a result of getting less sunlight and making them adjusting accordingly was designed and laser cut using a 3mm MDF. Two of the parts of the design were designed to work as a press fit and then were super glued to the base of the design were slots for the placement of the LDRs were also put.

Design Structure LDR Structure Design
LDR Structure Result Design LDR Structure Result Design

PCB Design

The PCB design of the solar tracker is a modified version of the satshakit created by Daniele Ingrassia. This pcb board differs from the original board because of its additional resistors needed for the functionality of the LDRs and its additional pins for VCC and GND for the requirements of the two stepper motors to be used.

PCB of solar tracker on eagle PCB design in Eagle of the Solar Tracker

Software of the solar tracker

Stepper.h library was used for the movement of the stepper motors and mathematical equations for the movement of the whole structure according to the LDR readings. The code was written in an Arduino language.

 
#include < Stepper.h >

#define motorstep 100 

//horizontal stepper
#define hstep 13
#define hdir 12
int step_hdivision = 10;
Stepper stepperh(motorstep, hstep, hdir);

//vertical stepper
#define vstep 10
#define vdir 9
int step_vdivision = 10;
Stepper stepperv(motorstep, vstep, vdir);

int ldrLT = A0; // LDR Left Top
int ldrRT = A1; // LDR Right Top
int ldrLD = A2; // LDR Left Down
int ldrRD = A3; // LDR Right Down

void setup(){
  Serial.begin(9600);
  stepperh.setSpeed(200);
  stepperv.setSpeed(40);
  }

void loop(){
  int lt = analogRead(ldrLT);
  //Serial.println (lt);
  int rt = analogRead(ldrRT);
  //Serial.println (rt);
  int ld = analogRead(ldrLD);
  //Serial.println (ld);
  int rd = analogRead(ldrRD);
  //Serial.println (rd);

  int dtime = 1;
  int tol = 50;

  int avt = (lt + rt)/2; // average value top
  int avd = (ld + rd)/2; // average value down
  int avl = (lt + ld)/2; // average value left
  int avr = (rt + rd)/2; // average value right

  int dvert = avt - avd; // difference of up and down
  int dhoriz = avl - avr; //difference of left and right

  // horizontal stepper motor
  if(-1*tol > dhoriz || dhoriz > tol){
    if(avl > avr){
      stepperh.step(step_hdivision);
      delay(10);
      }
    else if(avl < avr){
      stepperh.step(-step_hdivision);
      delay(10);
      }
    else{
      stepperh.step(0);
      delay(10);
      }    
    }

  // vertical servo motor  
  if(-1*tol > dvert || dvert > tol){
    if (avt > avd){
      stepperv.step(step_vdivision);
      delay(10);
      }
    else if (avt < avd){
      stepperv.step(-step_vdivision);
      delay(10);
      }
    else if (avl == avr){
      //nothing
      }    
    }
    
  delay(dtime);  
  }  
        

Assembly

By all

Download files