Friday 28 September 2012

Inkjet printer teardown - get at those stepper motors!

At last night's BuildBrighton open evening, we set to work opening up an old HP Inkjet printer, using our favourite opening-tools - hammers and hacksaws!


We were after a few parts from inside the printer - namely the stepper motors and linear rods (that hold the print head carriage). Before setting to work with the hammer, we located which parts of the printer we wanted to avoid hitting (too hard)

There's a stepper motor, tucked away in the corner of the enclosure - best go easy down there!

It didn't take long before the plastic casing was off...


And we had hold of a feast of salvaged goodies:


Of particular interest to us was the dc motor (used to make the print carriage travel left-to-right) the nice chunky steel rod with precision made carriage (actually just some moulded plastic with brass rings that slide along the rod - no fancy linear bearings here!), the rubber paper-pickup wheels (for our future lathe project) and of course, the stepper motors (complete with pulley heads and drive belts).

We started to get to work straight away on the stepper motors. They're five-wire connections so already we're considering uni-polar steppers. To find out exactly what we've got, we drew up a table of connections, put an ohm-meter across each of the pins in turn and noted down the respective resistances across each set of pins.

(Here Justin is testing the resistance between pins 1 and 5 - about 60 ohms)

We noticed that all connections to pin three were roughly half the resistance of the others (about thirty ohms). From this table of resistances we worked out that we had a unipolar stepper motor with a common "centre tap" - i,e the centres of both coils were connected together


To work out the wiring for the other coils, we had to do some trial-and-error investigating. We put power onto our centre tap (pin 3) then put a ground connection onto one of the other wires (pin5, we called this pin d). The motor energised, moved slightly and locked into place.

The idea now is to identify the other three pins, such that we can label them pin a, b, c and energise them in sequence a,b,c,d or d,c,b,a to get the motor to spin in either direction.

Here we can see our centre tap (pin3) connected to power, with pin5 grounded and pin1 at the same time.


With pin d still grounded, we then grounded the other three pins, one at a time. Where the stepper motor moved slightly anticlockwise, we'd found our pin c. When the motor moved slightly clockwise, we'd hit upon pin a. When there was no movement in the motor, we'd grounded the (remaining) pin b.
Using this rough-and-ready approach, we successfully labelled all four pins on our stepper motor.


With the pins sucessfully identified, we replaced our bit of wire that poked at the pins, connecting them to ground with some IRF640 power FETs. These are like big chunky transistors, with a flyback diode built into them. By putting them on the ground side of  the coil and activating them in the correct sequence, we managed to get the stepper motor to spin at quite a reasonable rate:



The driver chip is one of our old friends the PIC 18F2455, configured as a USB/HID device. Our custom software simply sends a "command byte" to tell the board which way to spin the motor.
252 = spin anti-clockwise
253 = spin clockwise
250 = stop spinning
249 = change the delay beween pulses.

We found with these steppers, that they ran quickly and quietly with a delay of 2ms between step pulses. We could increase this delay to slow the rate of rotation, but movement got more and more "jerky". A delay of just 1ms caused the stepper to hum without turning at all.

Define CLOCK_FREQUENCY = 20
Define CONFIG1L = 0x24
Define CONFIG1H = 0x0c
Define CONFIG2L = 0x38
Define CONFIG2H = 0x00
Define CONFIG3L = 0x00
Define CONFIG3H = 0x03
Define CONFIG4L = 0x80
Define CONFIG4H = 0x00
Define CONFIG5L = 0x0f
Define CONFIG5H = 0xc0
Define CONFIG6L = 0x0f
Define CONFIG6H = 0xe0
Define CONFIG7L = 0x0f
Define CONFIG7H = 0x40

UsbSetVendorId 0x1923
UsbSetProductId 0x1694
UsbSetVersionNumber 0x1001
UsbSetManufacturerString "Nerd Club"
UsbSetProductString "USB stepper driver"
UsbSetSerialNumberString "1111111111"
UsbOnIoInGosub input_report_before_sending
UsbOnIoOutGosub output_report_received
UsbOnFtInGosub feature_report_before_sending
UsbOnFtOutGosub feature_report_received

AllDigital
Dim i As Byte
Dim spindir As Byte
Dim state As Byte
Dim dly As Word
Config PORTB = Output
Config PORTA.0 = Input

UsbStart
spindir = 0
state = 0
dly = 10

loop:
      UsbService

      If PORTA.0 = 1 Then state = 0

      Select Case state

            Case 250
            Low PORTB.3
            Low PORTB.2
            Low PORTB.1
            Low PORTB.0

            Case 1
            High PORTB.3
            Low PORTB.2
            Low PORTB.1
            Low PORTB.0
           

            Case 2
            High PORTB.3
            High PORTB.2
            Low PORTB.1
            Low PORTB.0

            Case 3
            Low PORTB.3
            High PORTB.2
            Low PORTB.1
            Low PORTB.0
           
            Case 4
            Low PORTB.3
            High PORTB.2
            High PORTB.1
            Low PORTB.0
                       
            Case 5
            Low PORTB.3
            Low PORTB.2
            High PORTB.1
            Low PORTB.0

            Case 6
            Low PORTB.3
            Low PORTB.2
            High PORTB.1
            High PORTB.0

            Case 7
            Low PORTB.3
            Low PORTB.2
            Low PORTB.1
            High PORTB.0

            Case 8
            High PORTB.3
            Low PORTB.2
            Low PORTB.1
            High PORTB.0

      EndSelect
      WaitMs dly

      Select Case spindir
            Case 1
            state = state - 1
            If state < 1 Then state = 8

            Case 2
            state = state + 1
            If state > 8 Then state = 1

      EndSelect
           
Goto loop

End


feature_report_received:
Return


feature_report_before_sending:
Return


output_report_received:
      'PC has sent us some data - use it here
      i = UsbIoBuffer(7)
      Select Case i

            Case 249
            dly.HB = UsbIoBuffer(1)
            dly.LB = UsbIoBuffer(0)

            Case 250
            state = 250
            spindir = 0

            Case 252
            spindir = 1

            Case 253
            spindir = 2

      EndSelect

Return


input_report_before_sending:
      'we're sending data back to the host
      UsbIoBuffer(0) = spindir
      UsbIoBuffer(1) = state

Return

Tuesday 25 September 2012

Chuck-free lathe idea

We've been considering how to make a simple, but reliable chuck for a desktop lathe, but hitting a blank with both our Longworth and Spiral approaches.
After the recent BuildBrighton PCB making workshop, Matt came up with an idea. Like most half-baked ideas, we're still undecided as to whether it's a stroke of genius or a flash of madness.

What if we could do without a chuck?
To begin with, we're only going to be turning regular (round) shaped objects - mainly acrylic rod. So we don't necessarily need to grip an irregular shape and round it off, like many woodworkers do, when starting with square-section wood and turning it to make furniture legs and the like. We know we're starting off with a smooth, round shape.

So perhaps we could just have a couple of free-spinning wheels (shown in green below) and rest our stock in the "groove" formed by two wheels in close proximity:

The piece should now spin freely in place, without any need for gripping the stock. If the wheels are placed such that a drive wheel can be introduced immediately on top of the piece, to complete an equilateral triangle, there's nowhere for the spinning stock to go.

If we turn the drive wheel (orange wheel in the diagram above) and it's in constant contact with the stock, then the piece should spin and still be held in place by the "triangle of wheels".

By spacing the green wheels further apart, or bringing closer together, we can change the height of the centre of the stock being turned, allowing us to introduce a cutting head to the right of the wheels and carve shapes out of the stock. That's the idea anyway.
Will it work?
We'll hopefully soon find out!

Sunday 23 September 2012

BuildBrighton PCB making workshop

Yesterday (Saturday) was BuildBrighton's PCB making workshop, as part of the Brighton Digital Festival.
From a worryingly quiet start, we ended up with six people taking part in yet another successful practical workshop - 100% success rate!

Over six hours, we introduced the attendees to schematic drawing using ExpressPCB (it's much simpler and easier to get going with than Eagle for anyone new to electronics/PCB layout) and their PCB layout software. A few people had brought along projects that they were working on and we helped them create PCBs for their own creations, while everyone else set about recreating the famous BuildBrighton Drawdio  kit.

(all the tools needed for making your own PCBs, including ExpressPCB software, press-n-peel blue paper, ferric chloride, a sanding block and a heavy duty laminator!)i

Sadly, the workshop was such as success and everyone was having such a lovely time that we forgot to take any more photos! But, for reference, for anyone who attended and would like to know more:

The first step was to create/draw your schematic (the bit with the little squiggles and symbols for electronic components). The most important thing to remember here is to give every component on the drawing a unique Part ID (the name can be anything, it's that part id that's referenced in the PCB layout software)

Then we drew our actual PCB layout using ExpressPCB.
File -> Link schematic to import the list of part IDs used in our circuit. A lot of people found it easier to drop symbols for all components used onto the screen before starting the layout process (menu Component -> Component Manager). Make sure that all components are drawn on the top (red) layer!
The most important thing to remember here is no crossing the lines!
In a few instances, some people ended a trace with a round pad, allowed another trace to pass "through" and started the trace again the other side, with another pad


Where a trace is continued after allowing another to "pass through" we tend to draw a connecting line on the silkscreen (yellow) layer, to act as a reminder when assembling the final board.

Once the PCB layout was complete, it was time to get on with the toner transfer.
For our workshop, we used Press-n-Peel (blue)


Toner transfer paper tends to be quite expensive, so rather than just print straight onto the blue sheet, we printed our PCB layout onto plain paper first (using a laser printer - inkjet just won't work for this!). Then cut out a piece of p-n-p blue just a bit larger, stuck it over the printed image with regular tape (shiny side down, powdery side up) and re-printed the image. This gets the PCB image onto the smallest sized bit of press-n-peel, meaning you've got plenty more left for another go!

With the image on the press-n-peel, we cleaned up some copper clad board (available from various online suppliers - eBay sometimes is a cheap source) using a fine-grit sanding block.
Sanding the board not only cleans it up (it needs to be bright and shiny and all traces of oxidation removed) it also provides a slight "key" for the toner image to stick to when transferred.

Tape the image (face down) onto the shiny copper board using paper-based masking tape, preferably cut to fit into a corner of your copper board (so it takes less cutting later). It's important not to use sellotape or similar plastic backed tape, as this will melt during the transfer process.

We used a heavy-duty laminator, but you could also use a regular household iron.
After three passes through the laminator, the image is transferred onto the copper board. Douse in cold water (carry the board by its edges, it gets very hot!) and carefully peel off the backing.


When done correctly, there should be no black traces left on the backing sheet. Check all traces carefully on the copper board. Where necessary, touch up any missing detail with a fine-tipped permanent (black) pen.
When happy with the transferred image, it's time to actually do some etching!

Ferric Chloride (FeCl) is messy stuff. Make sure you're wearing your old clothes and get ready to clean up any spillage immediately (it stains!). We made up a small batch using hot water and about a quarter of a pack of crystals. Etching in warm solution is quicker than in cold, although the end result(s) are the same.

The Ferric Chloride solution should be a dark brown in colour. If it's pale yellow, you need to add more crystals. If it's an orange-y colour, it will probably work but will take a long time. If you're re-using old solution (that's already had a few boards etched) and it's a dark green-y colour, it's getting a bit old and you should consider refreshing it. A dark brown (which is a golden yellow colour at the edges) is the perfect strength mix.

Submerge the board and either tip the container slightly to wash the mixture over the board (large boards in a small, shallow contianer) or dip the boards in and out of the mixture (smaller boards, suspended in a tall container on bits of wire). It will take 10-15 minutes for the board to etch completely.

The copper board etches in a couple of stages and if you keep checking it regularly, you'll see:
Firstly, the board goes a very vibrant pink.
Then the edges of the board (where the copper is exposed) start to look a little bit green.
Finally, the edges of the board turn a beige colour (the natural colour of the board the copper was stuck to) and the etching continues, usually from the edges, towards the centre of the board)

When no trace of copper remains (double-check for little bits of pink between the black traces on the board) your PCB is fully etched. Congratulations!

Some people prefer to drill then clean their boards - we did it the other way around.
You can use nail-varnish remover or similar acetone-based cleaner, or simply scrub with a fine-grit sanding block until all traces of the black toner are removed and you're left with a shiny new PCB.


During our workshop, everyone successfully made their PCB, etched and drilled to a finished standard! A few brave souls even populated their boards with the Drawdio components provided - and they worked!!

All  in all, another successful BuildBrighton workshop and a great day making stuff!


Friday 21 September 2012

PIC based stepper driver controller for CNC

Here's the PIC code for our USB-HID device/stepper motor controller. Here's how it works:

Data is sent to the device through an 8-byte wide buffer. The seventh byte in the buffer is a "command byte" and (amongst other things) can be used to


  • set the number of steps to move on the x-axis
  • set the number of steps to move on the y-axis
  • start the head moving
  • plunge the drill
  • set the backlash values for x/y axis
  • set the minimum/maximum drill height
While the board is connected, you can poll the device and it will report

  • if the head is still moving (x/y direction)
  • if the drill is activated (spinning/plunging)
  • if the machine is ready to receive the next command

The code is written for Oshonsoft PIC18 Simulator/compiler and runs on an 18F2455 PIC microcontroller


Define CLOCK_FREQUENCY = 20
Define CONFIG1L = 0x24
Define CONFIG1H = 0x0c
Define CONFIG2L = 0x38
Define CONFIG2H = 0x00
Define CONFIG3L = 0x00
Define CONFIG3H = 0x03
Define CONFIG4L = 0x80
Define CONFIG4H = 0x00
Define CONFIG5L = 0x0f
Define CONFIG5H = 0xc0
Define CONFIG6L = 0x0f
Define CONFIG6H = 0xe0
Define CONFIG7L = 0x0f
Define CONFIG7H = 0x40

UsbSetVendorId 0x1234
UsbSetProductId 0x1234
UsbSetVersionNumber 0x1122
UsbSetManufacturerString "Nerd Club"
UsbSetProductString "CNC drilling machine"
UsbSetSerialNumberString "1111111111"
UsbOnIoInGosub input_report_before_sending
UsbOnIoOutGosub output_report_received
UsbOnFtInGosub feature_report_before_sending
UsbOnFtOutGosub feature_report_received

AllDigital
Dim i As Byte
Dim stepsx As Long
Dim stepsy As Long
Dim dirx As Byte
Dim diry As Byte
Dim domove As Bit
Dim doplunge As Bit
Dim isdrilling As Bit
Dim booting As Bit
Dim atdestination As Bit
Dim snapback As Bit
Dim tmp As Byte
Dim tmpw As Word
Dim tmpl As Long

Dim statex As Byte
Dim statey As Byte
Dim statez As Byte
Dim zaxisvalue As Byte
Dim drillspeedvalue As Byte
Dim zaxisspeed As Byte
Dim zservoon As Bit
Dim dservoon As Bit
Dim holdzaxis As Bit
Dim timeoutcount As Byte
Dim zmin As Byte
Dim zmax As Byte
Dim drilldir As Byte
Dim feedbackvalues As Byte

Config PORTB = Output
Config PORTC.7 = Output
Config PORTC.6 = Output

Symbol coila_1 = PORTB.0
Symbol coilb_1 = PORTB.1
Symbol coilc_1 = PORTB.2
Symbol coild_1 = PORTB.3

Symbol coila_2 = PORTB.4
Symbol coilb_2 = PORTB.5
Symbol coilc_2 = PORTB.6
Symbol coild_2 = PORTB.7

Symbol zaxis = PORTC.7
Symbol drillspeed = PORTC.6

init:
      booting = 1
      domove = 0
      doplunge = 0
      atdestination = 1
      statex = 1
      statey = 1
      statez = 0
      snapback = 1

      zservoon = 1
      zaxisvalue = zmin 'retracted
      dservoon = 1
      drillspeedvalue = 90 'midpoint=stopped
      zaxisspeed = 50
      holdzaxis = 0

      timeoutcount = 0
      zmin = 150
      zmax = 175
      drilldir = 0
      isdrilling = 0

      feedbackvalues = 1

      UsbStart

      'wait 1 second before sending any servo commands
      '(so the servo controller doesn't freak out)
      WaitMs 1000

      'set up a timer1 interrupt to occur every 1/20th second
      '(for servo control commands- particularly motor spin speed)
      '(we use this for servos)
      'enable timer1 interrupts to trigger every 1ms
      INTCON1.GIE = 1 'enable global interrupts
      PIE1.0 = 1 'enable TMR1 interupts
      INTCON.6 = 1 'enable all unmasked interrupts
      INTCON.7 = 1 'enable Global interrupts
      PIR1.0 = 0 'reset interupt flag
      T1CON.TMR1CS = 0 'use fosc/4
      T1CON.TMR1ON = 1 'turn on timer1

      'enable timer0 in 16-bit mode
      T0CON.T08BIT = 0
      T0CON.TMR0ON = 1
      T0CON.T0CS = 0 'use fosc/4

      'connect to the brushless motor controller
      '(it has a specific startup sequence)
      drillspeedvalue = 90

      'make sure the drill is retracted(~200ms)
      zservoon = 1
      zaxisvalue = zmin
      drillspeedvalue = 90


loop:
      booting = 0
     
      While domove = 1

            Select Case statex
                  Case 1
                  High coila_1
                  Low coilb_1
                  Low coilc_1
                  Low coild_1

                  Case 2
                  High coila_1
                  High coilb_1
                  Low coilc_1
                  Low coild_1

                  Case 3
                  Low coila_1
                  High coilb_1
                  Low coilc_1
                  Low coild_1

                  Case 4
                  Low coila_1
                  High coilb_1
                  High coilc_1
                  Low coild_1

                  Case 5
                  Low coila_1
                  Low coilb_1
                  High coilc_1
                  Low coild_1

                  Case 6
                  Low coila_1
                  Low coilb_1
                  High coilc_1
                  High coild_1

                  Case 7
                  Low coila_1
                  Low coilb_1
                  Low coilc_1
                  High coild_1

                  Case 8
                  High coila_1
                  Low coilb_1
                  Low coilc_1
                  High coild_1
                       
            EndSelect

           
            Select Case statey
                  Case 1
                  High coila_2
                  Low coilb_2
                  Low coilc_2
                  Low coild_2

                  Case 2
                  High coila_2
                  High coilb_2
                  Low coilc_2
                  Low coild_2

                  Case 3
                  Low coila_2
                  High coilb_2
                  Low coilc_2
                  Low coild_2

                  Case 4
                  Low coila_2
                  High coilb_2
                  High coilc_2
                  Low coild_2

                  Case 5
                  Low coila_2
                  Low coilb_2
                  High coilc_2
                  Low coild_2

                  Case 6
                  Low coila_2
                  Low coilb_2
                  High coilc_2
                  High coild_2

                  Case 7
                  Low coila_2
                  Low coilb_2
                  Low coilc_2
                  High coild_2

                  Case 8
                  High coila_2
                  Low coilb_2
                  Low coilc_2
                  High coild_2
                       
            EndSelect

                       
            If stepsx > 0 Then
                  If dirx = 1 Then
                        statex = statex + 1
                        If statex > 8 Then statex = 1
                  Else
                        statex = statex - 1
                        If statex < 1 Then statex = 8
                  Endif
                  stepsx = stepsx - 1
            Endif

            If stepsy > 0 Then
                  If diry = 1 Then
                        statey = statey + 1
                        If statey > 8 Then statey = 1
                  Else
                        statey = statey - 1
                        If statey < 1 Then statey = 8
                  Endif
                  stepsy = stepsy - 1
            Endif

            If stepsx = 0 And stepsy = 0 Then
                  'set the flag to say we've arrived at the destination
                  atdestination = 1
                  domove = 0
            Else
                  atdestination = 0
                  'we need a delay for the stepper to respond
                  WaitMs 1
            Endif

            'poll the usb just to keep it alive
            UsbService
      Wend

      'important: only plunge when NOT moving!
      If domove = 0 Then

            If statez > 0 Then
                 
                  domove = 0
                  atdestination = 0
                  isdrilling = 1

                  Select Case statez

                  '--------------------------------
                  Case 1 'start the drill spinning
                  '--------------------------------
                  drillspeedvalue = 255
                  WaitMs 100
                  statez = 2

                  '-------------------
                  Case 2 'moving down
                  '-------------------
                  If zaxisvalue < zmax Then
                        zaxisvalue = zaxisvalue + 1
                        If zaxisspeed > 0 Then WaitMs zaxisspeed
                  Else
                        zaxisvalue = zmax
                        statez = 3
                  Endif

                  '----------------------------------------
                  Case 3 'wait at the bottom of the stroke
                  '----------------------------------------
                  WaitMs 500
                  statez = 4

                  '-------------------
                  Case 4 'moving up
                  '-------------------
                  If zaxisvalue > zmin Then
                        zaxisvalue = zaxisvalue - 1
                        If snapback = 1 Then
                              'don't delay
                        Else
                              If zaxisspeed > 0 Then WaitMs zaxisspeed
                        Endif
                  Else
                        zaxisvalue = zmin
                        statez = 5
                  Endif

                  '-----------------------------------
                  Case 5 'brake (not break) the drill
                  '-----------------------------------
                  drillspeedvalue = 60
                  WaitMs 100
                  statez = 6

                  '-------------------------------
                  Case 6 'stop the drill spinning
                  '-------------------------------
                  drillspeedvalue = 90
                  statez = 0
                  WaitMs 100
                  atdestination = 1
                  isdrilling = 0

                  EndSelect
            Endif
      Endif

      'poll the usb to keep it alive
      UsbService

Goto loop

End


feature_report_received:
Return


feature_report_before_sending:
Return


output_report_received:

      Select Case UsbIoBuffer(7)
            '--------------------------------------
            Case 1 'set X step count/direction
            '--------------------------------------
            dirx = UsbIoBuffer(4)
            tmpw.HB = UsbIoBuffer(3)
            tmpw.LB = UsbIoBuffer(2)
            tmpl.HW = tmpw
            tmpw.HB = UsbIoBuffer(1)
            tmpw.LB = UsbIoBuffer(0)
            tmpl.LW = tmpw
            stepsx = tmpl
            domove = 0

            '--------------------------------------
            Case 2 'set Y step count/direction
            '--------------------------------------
            diry = UsbIoBuffer(4)
            tmpw.HB = UsbIoBuffer(3)
            tmpw.LB = UsbIoBuffer(2)
            tmpl.HW = tmpw
            tmpw.HB = UsbIoBuffer(1)
            tmpw.LB = UsbIoBuffer(0)
            tmpl.LW = tmpw
            stepsy = tmpl
            domove = 0

            '---------------------------------
            Case 240 'reset the flag buffers
            '---------------------------------
            zaxisvalue = zmin
            domove = 0
            statez = 0
            drillspeedvalue = 90
            WaitMs 300
            isdrilling = 0
            atdestination = 1

            '----------------------------------------------------------
            Case 241 'set which values you want to read back from usb
            '----------------------------------------------------------
            feedbackvalues = UsbIoBuffer(0)

            '--------------------------------------
            Case 247 'zmin value
            '--------------------------------------
            zmin = UsbIoBuffer(0)

            '--------------------------------------
            Case 248 'zmax value
            '--------------------------------------
            zmax = UsbIoBuffer(0)

            '--------------------------------------
            Case 249 'set the z-axis step speed
            '--------------------------------------
            zaxisspeed = UsbIoBuffer(0)

            '--------------------------------------
            Case 250 'set the drill speed
            '--------------------------------------
            drillspeedvalue = UsbIoBuffer(0)

            '--------------------------------------
            Case 251 'set the zaxis servo depth
            '--------------------------------------
            zaxisvalue = UsbIoBuffer(0)
            statez = 0

            '--------------------------------------
            Case 253 'plunge the drill
            '--------------------------------------
            statez = 1
            domove = 0

            '--------------------------------------
            Case 254 'start moving
            '--------------------------------------
            atdestination = 0
            domove = 1

      EndSelect
Return


input_report_before_sending:
      'tell the PC our current status
      '(so when PC sees we're at our destination, for example
      'it can move onto the next command in the script)
      tmp = 0
      tmp.0 = domove
      tmp.1 = atdestination
      tmp.2 = isdrilling
      tmp.7 = booting
     
      Select Case feedbackvalues

            Case 0
            UsbIoBuffer(0) = 0
            UsbIoBuffer(1) = 0
            UsbIoBuffer(2) = 0
            UsbIoBuffer(3) = statex
            UsbIoBuffer(4) = zaxisvalue
            UsbIoBuffer(5) = drillspeedvalue
            UsbIoBuffer(6) = tmp
            UsbIoBuffer(7) = 255

            Case 1
            tmpw = stepsx.LW
            UsbIoBuffer(0) = tmpw.LB
            UsbIoBuffer(1) = tmpw.HB
            tmpw = stepsy.LW
            UsbIoBuffer(2) = tmpw.LB
            UsbIoBuffer(3) = tmpw.HB
            UsbIoBuffer(4) = zaxisvalue
            UsbIoBuffer(5) = drillspeedvalue
            UsbIoBuffer(6) = tmp
            UsbIoBuffer(7) = 255

      EndSelect

Return


preloadtimer1:
      'pre-load to 15535 (65,535-50,000 where 1ms=5000)
      'so this causes a timeout interrupt every 10m/s
      TMR1H = 50 '216 (2ms)
      TMR1L = 175 '239 (2ms)
Return


On High Interrupt

      'if timer1 has rolled over (hit 65535) then
      If PIR1.TMR1IF = 1 Then

            'save system state/working address etc
            Save System

            'reset the timer1 interrupt flag
            PIR1.TMR1IF = 0

            'preload timer1 with a number to count to
            'so that it rolls over (hits 65535) after
            '2ms. As it happens, at 20Mhz, we need to
            'count up to 5000 for 1ms to elapse.
            Gosub preloadtimer1

            timeoutcount = timeoutcount + 1
            If timeoutcount > 1 Then
     
                  'send the servo position control
                  If zservoon = 1 Then
                        ServoOut zaxis, zaxisvalue
                  Endif

                  'send the drill speed control
                  If dservoon = 1 Then
                        ServoOut drillspeed, drillspeedvalue
                  Endif
                  timeoutcount = 0
            Endif
     
      Endif
     
Resume



CNC drill schematic

CNC drilling machine - FINISHED!

With the new path finding routines in our CNC drilling software, and changes to the firmware to allow the user to manually jog the drill head up and down to get more precise alignment during initial set-up, we're finally happy to say that our cheapo CNC drilling machine is finally complete.
Or at least, complete to a state that we're happy to release for anyone else to have a go at making their own.

The last little part of the test was making sure that the machine could drill a PCB even when placed on the cutting bed at an angle. Here's a video showing exactly that:


The drilling is accurate enough for us now (there's still a little bit of play in the x-axis, but we seem to have done enough to remove/reduce any play in the y axis, even when the bed still needs to travel up-and-down in order to correct for the board being at an angle)

Here's the final board, as drilled in the video:


The holes on the bottom-right-hand edge may not be absolutely bang on, but they're good enough to make the board usable, and are about as accurate as you could get by drilling the board by hand. The photo seems to emphasise the amount of drift - it's probably less than 0.5mm away from the centre of the hole.

Having successfully completed a cnc drill test, we put the machine away!


We reckon our machine fulfills all the criteria set in the CNC drill challenge.

  • You can load NC compatible drill files to operate the machine
  • It cost less than £50 to build, completely from scratch with new (no salvaged) parts
  • It's accurate to within 0.5mm (despite the dodgy cheapo stepper motors)
  • The footprint is less than a sheet of A4 (in storage mode, it's about 210mm x 160mm)


It's been a long (and sometimes painful) journey, but it feels great to finally complete a project, not just to a point where it's working, but to be able to compare it to a list of criteria drawn up at the start, and to be able to tick every one off the list!

Along the way we were introduced to brushless motors and their servo-protocol control boards, created our own stepper motor control boards and our own USB-based protocol for moving them, and proved that rack-and-pinion gearing can be used just as successfully for CNCs as belt drives and leadscrews.

If we were to do the whole thing again?
Probably it'd look pretty similar. Maybe instead of a travelled bed-on-wheels we might use rails (similar to the x-axis) because there is a lot of play in the y-axis. But then again maybe not?

Any improvements?
Of course. Better stepper motors would be a great start. When energised, a stepper motor should have no movement in it at all - our steppers have 1mm-2mm of play because of the internal gearing. But then again, this does give us simple movement commands - no messing about with micro-stepping or any of that tricky stuff! We love the simplicity of the rack-and-pinion approach: belt-drives and leadscrews may be more popular, but we reckon we'd stick with ours.

The custom software is enough to make the device usable, but the protocol for sending x- and y- axis values is so simple that allow anyone else can write their own controller software. The latest firmware not only allows you to set a the number of steps to move in both x- and y- axes, but you can also now provide a "ratio" (for every three steps in x, move one in y for example). This means that if a vector line is broken down into enough parts, the machine could be modified to do simple milling - an idea that Justin from BuildBrighton is already working on!

Monday 17 September 2012

CNC lathe alternative chuck designs

After we made a sort-of successful Longworth chuck out of acrylic, we started scouring the internet for alternative chuck designs. The problem with our Longworth chuck is that while it works quite well, there's no locking mechanism.

A spiral based chuck has natural locking, just due to the friction in the system.
Last night we got busy with the laser cutter, but using MDF instead of acrylic, for the first time. The theory being that if we made a chuck from mdf, there would naturally have more friction against any moving parts, which should help aid the locking action in our chuck.

(MDF is not good stuff for laser cutting. It stinks. And fills the place with smoke!)

We made some jaws with three little pegs on the bottom. These will sit inside the curves of a spiral.


The jaws have little "plates" slotted into them at 90 degrees, which slide inside the larger of the two cutouts


The spiral is mounted onto the plain disc. The jaws also sit on top of the spiral such that the three teeth sit between the curves of the spiral.


By mounting the slotted pieces over the jaws, we can restrict their movement so that as the spiral turns, the jaws move in and out, along their slots


The final sprial-based chuck assembly


The idea is that the working stock can be inserted into the chuck and pass right through it, the spiral disc on the back is turned, and the jaws slide along their grooves and clamp the work piece to stop it slipping when the chuck is spinning.

So, does it work?
Erm. No.
Not this version anyway.
Maybe MDF is too, erm, grippy. Or maybe the spiral isn't regular all the way around. Or maybe the design just doesn't work. The theory should be sound - it's how commercial chucks are made anyway. But perhaps we're now seeing why they sell for £30-£40 each and you don't see many made out of cheap materials for a couple of quid at the local Poundstretcher store!

How to drive a stepper motor with a PIC microcontroller

We've made a few posts about this before, while investigating how stepper motors work, but with our recent CNC drilling machine competition entry, we're getting a few questions once again about how it works. So here's a brief outline of what's happening when you control your stepper motor.

If we were to build our stepper driver from discrete components, it'd look something like this:

Stepper Darlington


The stepper motor has a simple operation. By energising coils in a specific sequence, we can make the motor turn clockwise or anti-clockwise. (energise them out of sequence, however, and the motor simply chatters or makes a buzzing noise!)

What often confuses people is that we're not switching the power rail, but the 0v/ground rail. Let's take a look at the layout above.

Ignore the diodes. We'll come on to those in a minute.
Those funny transistors are basically one transistor feeding the gate of another. It allows teeny-tiny currents to be amplified quite a lot. Basically, it means we can power really beefy loads (motors) with really small signals (microcontroller outputs). Such transistors are often called darlington transistors.

Now, every coil is permanently connected to the power rail. It's only by allowing current to flow through each transistor connecting the 0v/ground wire to ground that the coil can be energised. So when we make PORTB.7 high on our micrcontroller, it allows current to flow from the power rail, through coil 4 and down to ground (through Q1), effectively energising coil four. If we send PORTB.7 low and bring PORTB.6 high, current can now flow from the power rail, through coil 1 and down to ground (through Q2). This energises coil one. This causes the stepper motor to start to rotate anti-clockwise. Triggering the other coils in the correct sequence causes the motor to rotate.

Now, those diodes. What are they for?
When a coil is energised, it creates an electro-magnetic field. When you turn the coil off and the magnetic field collapses, it can create what's known as "back-emf" in the wires. Basically, we could get a spike of voltage - if we leave this hanging around, it could easily zap some of our sensitive components. So we use a diode to direct any harmful back-emf safely to ground.

Luckily, we're using a ULN2003A darlington array.
Unfortunately, this is often were the confusion comes in!


Those free-wheeling diodes shown on the datasheet can cause confusion. Trying to work out how to wire them up without a reference schematic like the one above can hurt your brain, but the trick is to remember that power is always connected and we're switching the 0v rail.

So the common pin is not 0v/ground as would be expected, but the power supply. The internal "free-wheeling" or feedback diodes simply ensure that any back-emf is handled safely.
So to connect a microcontroller to a stepper motor:


Following this logic, we've connected two stepper motors and two servos to our PIC to create a CNC drill controller board:

CNC drill schematic

Laser cutting MDF

Last night BuildBrighton member Colleen tried laser cutting MDF for the first time on our laser cutter. The results were quite impressive. Cutting a single sheet of 3mm MDF at about 30mm/sec (we tend to stick with 16mm/sec for acrylic although it can be pushed to 20mm/sec) the only downside was the terrible smell and amount of smoke!

MDF doesn't half smoke when you laser cut it. Maybe it was the type of wood we were using - an 8' x 4' sheet from Wickes costs about£6. Not the "proper" laser-able MDF, just regular (cheap) stuff from a home supplies store. Despite the smoke, it's certainly a cheaper way of trying out designs than carving everything out of acrylic at £4/A4 sheet.

Colleen was making a simple jewelry stand for necklaces. Although originally using MDF just to make a prototype to see how the finished result would look, a quick coat of black acrylic paint, and it didn't look too bad at all - in fact, a coat of PVA glue and a dusting of glitter and it might look quite nice as it is!



(the later revision was made slightly longer and with hooks to stop the necklaces slipping off the stand when in use)




Necklace Stand

Saturday 15 September 2012

CNC software update

One of the main problems with getting absolute accuracy with our CNC drilling machine is overcoming backlash. We've loads of it. Not only between the cogs and the gears on our gantries, but inside the cheap little stepper motors too. Because the motors themselves are geared down inside, there's loads of backlash/slack on the actual motor spindle - and that's before any slack we introduce with our plastic push-fit rack-and-pinion arrangement to drive the axes.

All that said, we're still getting pretty good results, by compensating for backlash whenever we change direction in each axis (for example, whenever the x-axis gantry is travelling left-to-right, and we need to reverse it, to move right-to-left, we add in an extra number of steps to the move command, to take up any slack in the system).

Looking over our software, the "move-to-nearest-point" may make pretty efficient use of time, but because of the number of times we change direction over the course of drilling a board, there are plenty of opportunities for backlash/slack to affect our accuracy.

We've changed our driver software to use calculate different paths now.
Previously, it started at one point and simply picked the nearest undrilled point to the current location:


Now, we're going to move the head to the top-left-most point on the board, and drill them in a strict left-to-right, top-to-bottom sequence:


Although not shown on the screenshot, we're also planning on "overshooting" any point when moving the gantry from right-to-left, before moving to the correct location. The reason?

Doing things this way, we should help reduce (if not eliminate) backlash/slack in the y-axis, since the bed will only ever be travelling in the top-to-bottom direction. By overshooting each point when travelling from right-to-left, we're also ensuring that every hole is approached from the top-left corner so the system should be tight when travelling from top-left to bottom-right.

Hopefully this will help reduce the amount of backlash in the system, and produce a more accurate drilling action.

That's the theory taken care of.
The software is almost ready for testing.
There's only one thing left to do - and that's try it out!

CNC drill - more testing

Another Thursday, another post-Midnight testing session at BuildBrighton - this time we're trying to handle play in our cnc drilling machine which is still causing some of our boards to be drilling slightly off.
We managed to get the drill head spinning the right way, and slow down the servo movement (rather than just ram the drill bit into the cutting board at full speed) and it's certainly helped.

Here's the latest test board fresh off the cnc:


We started in the top right hand corner. Around the centre of the board, the drill isn't quite 100% bang on, but is about as accurate as we usually get when drilling these things by hand, with a little Dremel and snake-head attachment, so it should be good enough.

The three holes at the bottom left of the board were totally way out with the printed PCB, but did actually marry up with the on-screen display


So either the PCB layout has changed between printing our test boards onto paper (not impossible) or the software is reading the dxf/nc drill file incorrectly and putting the holes for the transistor at the bottom-right corner in the wrong place!
It shouldn't take a massive amount of work to find out what went wrong and why, and maybe even tighten up some of the inaccuracies around some of the holes.

At the minute, our path-finding is still doing "nearest-non-drilled-hole"


Given that we start in the top-right hand corner, the y-axis travels "downwards" for a total of nine holes, while the x-axis moves left to hit hole 5 and then right onto hole 6 (the x-axis doesn't change in order to then drill holes 7,8 and 9). At hole 10 the y-axis moves in the opposite direction (upwards) and it's around the centre holes that there's a lot of backwards and forwards movements. It's also around these centre holes that we're seeing most inaccuracies.

The next plan is to alter the path-finding in the software so that it starts at the top of the board, and only ever travels in one direction on the y-axis. This should eliminate any backlash in at least one axis and hopefully give us a level of accuracy that we'd be happy to live with, and then we can get on with the exciting stuff like publishing it all online for everyone else to have a go with!

EDIT: Do'h. We were indeed using two different boards for testing! Here's a screenshot of the PCB layout used to load the holes into our CNC. Note how the transistor in the bottom right hand corner has moved down in relation to the rest of the board. If we had printed this board and drilled it, it's looking quite likely that we'd have had a successful test much earlier on!


Tuesday 11 September 2012

Simple lathe chuck ideas

Having made a Longworth chuck from acrylic, we're now investigating a cheap and easy way to grip material for turning on a miniature desktop sized lathe.

The Longworth chuck works well in principle but locking it could be difficult. It's a nice bit of engineering and sliding the jaws open with a simple turning action is quite satisfying, but getting it to stay locked so that it's strong enough to grip some material while spinning in a lathe might be quite tricky.
So far Robot Steve has come up with some sort of torsion spring to keep the jaws clamped around the piece. We'll give it a try, but we'd also like to try a lower-tech solution:




At BuildBrighton, we've access to a Myford lathe. So machining a piece of rubber with a number of decreasing sized circles shouldn't be too difficult. Exactly the same "chuck" could be mounted on the motor part of a miniature lathe, and also on the free-spinning end, and the two ends drawn together using a threaded rod (or similar) on the base.


By turning the threaded rod, the two ends can be pulled together or separated to fit the length of material to be turned. And by using exactly the same "rubber cups" for both ends, we shouldn't need to worry about centring the piece - so long as we're starting off with a piece of round material (or at least, regular material, consistently thick along it's length) by simply pushing the piece into the appropriately sized cup cut-out, it should automatically be centred.

That's the theory anyway.


Monday 10 September 2012

Make your own Longworth Chuck (any size) in Inkscape

There are loads of instructions all over the 'net about how to draw (and rout out) a Longworth chuck for a lathe, using a pencil and compass, for woodworkers; but very little about how to draw one for a laser cutter.

In this (not so) brief tutorial, we hope to put that right. The first thing to try was to take an existing PDF and simply rescale it. This worked, except it took a lot of trial and error in re-scaling by degrees until the slots were exactly 3mm wide (we're using M3 bolts for our grippy finger things). But in doing this, we ended up with a chuck that was far too big (in diameter) for what we wanted).

What we needed to do was draw one entirely from scratch: This tutorial uses Inkscape - because it's freely available and is used by lots of people for drawing/preparing vector drawings for laser cutting. Until quite recently, we did all our drawing in Flash, then imported it into Inkscape to save as a .dxf (our laser cutter uses NewlyDraw which likes dxf files for cutting). Inkscape is an ok drawing package in it's own right - so we'll stick with it for this tutorial:

The first thing to do is draw some guidelines. Not just for seeing how things are progressing by eye, but Inkscape automatically uses them for snapping. This is really handy, later on. So start off by clicking on the rulers on the outer edge of the screen and dragging them into the centre. As one guide approaches another, they will try to snap together.


Draw another guideline and release. Place your mouse pointer over the node in the middle of the line and hold down the shift key. The arrow pointer should change to allow you to rotate the guideline. With the shift key held down, also hold down the control (ctrl) key. This constrains the guides to 90, 60, 45 and 30 degrees only. Draw two more guides at 45 degrees and snap them all to a single centre point


Select the circle tool. Place the mouse pointer over the centre of the guidelines until a snapping guide appears. Hold down the shift key (this forces Inkscape to draw the centre of the circle from the current mouse position, rather than the top left corner) and draw a circle of your chosen size. We drew ours about 50mm across.


Duplicate the newly drawn circle (select it then hit Ctrl+D on the keyboard) and set the size to about 20mm by 20mm. Place the smaller circle inside the larger one and call up the alignment dialogue (hit Ctrl+Shift+A on the keyboard).


Click the icons to align to the centre both vertically and horizontally. Repeat with another circle in between the two. Since we've drawn a 50mm circle and a 20mm circle inside it, the difference between the two diameters is 30mm. So make the third circle 35mm across (20 + half 30 = 20+15 = 35) and centre in the same way.


Now this is where things could get complicated, so pay attention!
We need to draw a circle with the centre-point on the middle circle, and the outer edge on the far side of the smaller circle. As you place your mouse pointer near the middle circle, on the horizontal guidline, Inkscape tries to snap the "handle to guide". This is good. With the circle tool selected, hold down the shift key and draw a circle (shift+circle causes the circle to be drawn with the centre point at the current mouse position)


With the shift key held down, also hold down the control (ctrl) key. This causes the otherwise-ellipse to be constrained to a few set sizes. Drag the mouse diagonally and with the control key pressed, Inkscape should draw a perfect circle.


Repeat this process at all four points where the guidelines intersect the middle circle (both sides of the horizontal and vertical guides). In this example, we've coloured them pale to make them easier to see!


Select a circle and hit the "node select" tool (second from top on the tool bar). Click on one of the round-shaped nodes


Drag the round-shaped node and Inkscape converts the circle into an arc. Shorten the arc so that it travels only from the inner circle to the outer one.


Repeat for all four arcs. These are the lines we'll need to cut out. So all we need to do now is make them the right size. Remove the inner and middle circles - we're done with these now.


With one of the arcs selected, right-click on the stroke dialogue in the bottom left corner of the screen and make sure the units are mm (not pixels)


We're using M3 bolts so we set the stroke width to 3mm.


Repeat this for all four arcs


With all four arcs selected, choose Path -> Stroke to Path


With the four arc shapes selected, edit the stroke and fill colours - set the stroke to black, 0.1mm and set to no fill colour. Now we just need to stop the arcs reaches to the very edges of the outer disk.


Select the outer circle and duplicate (ctrl+D). Modify and scale to about 90% (or make a specific height/width and centre to the outer circle using the alignment dialogue - ctrl+shift+A - as before)
Now select this slightly-smaller circle and copy (to the clipboard, ctrl+C). This will be needed in a minute!


Select one arc, hold the shift key and select the inner circle, so that both are selected. From the Path menu, select intersection.

The inner circle disappears and the selected arc is shortened.


Now from the Edit menu, Paste in Place. This puts the inner circle back to exactly where it was. Select this inner circle, another arc and repeat Path -> Intersection


Repeat this process for all arcs. We're done!


The resulting shape is one side of the Longworth Chuck.
You'll need to cut two of these shapes. Flip one over and place them face to face. Slot the M3 bolts between the gaps and as the two disks rotate against each other, the bolts slide up and down....


Should you require more pegs, you can also repeat this entire process on the diagonal guides, creating an eight-peg chuck (or maybe just duplicate the arcs and rotate through 45 degrees?)