Showing posts with label pic. Show all posts
Showing posts with label pic. Show all posts

Monday, 7 September 2015

Reading and writing EEPROM on a PIC

It's amazing that's it's taken 'til now to need to write a routine like this. For ages, we've been using compilers that allow you to simply write

Read address, value
or
Write address, value

and store away byte values to the microcontroller's own internal eeprom.
But the latest Oshonsoft PIC compilers (still the best simulators, if not exactly the best compilers for PIC microcontrollers around) only have limited support for some of the latest PIC range.

Which means that we've had to roll our sleeves up and get dirty with some assembly language, just to write a couple of bytes to/from eeprom. We were at the stage of giving up (having worked almost all weekend getting nowhere with it) and finally made a breakthrough.

There are a couple of gotchas to look out for, so here's a couple of routines for anyone else having similar problems.


read_eeprom:
     EEADRL = eeprom_addr
     EECON1.CFGS = 0
     EECON1.RD = 1 'set the read bit to start reading
     ASM: nop
     eeprom_byte = EEDATL
Return


write_eeprom:
     
     'disable interrupts while we write to eeprom
     b = INTCON.GIE
     INTCON.GIE = 0

     'set the eeprom address
     EEADRL = eeprom_addr

     EECON1.CFGS = 0
     EECON1.EEPGD = 0
     EECON1.WREN = 1 'set the eeprom write enable bit to start writing

     'set the eeprom value
     EEDATL = eeprom_byte

     'these commands need to be executed with no other instructions between them
     'so we've written them out in assembly, just to be sure the compiler doesn't
     'add in any extra crap that might stop it from working!
     
     ASM: banksel EECON2
     ASM: movlw 0x55 ;
     ASM: movwf EECON2 ;Write 55h
     ASM: movlw 0xaa ;
     ASM: movwf EECON2 ;Write aah
     ASM: bsf EECON1,WR ;set WR Bit To begin Write
     
     While EECON1.WR = 1
          'wait for it to finish
     Wend
     
     'disable writing to eeprom until this has finished
     EECON1.WREN = 0

     'although we don't use eeprom interrupts, the EEIF flag will still be set.
     PIR2.EEIF = 0 'clear the eeprom write done interrupt flag
     
     'now re-enable global interrupts if they were previously set.
     INTCON.GIE = b
     
     'this shouldn't be necessary since we waited for the write cycle to complete
     WaitMs 4
     
Return

When it comes to reading eeprom, the datasheet makes things nice a clear. Set the address you want to read from, set the "start reading" bit in the corresponding register, and the value you're after appears in the EEDATL register. Nice and easy.

Writing data to eeprom is not so easy.
And until we'd actually proved that our reading routine - which seemed so laughably simple by comparison - was working properly, finding the actual cause of the problems, when nothing seemed to work, was a real headache.

Now the main gotcha when writing to eeprom is that there's a very specific bit sequence that you have to write, in a very specific way, to a very specific register - that doesn't actually exist as a physical register - just in case the chip were to boot up in a peculiar state and go rogue, overwriting eeprom memory (and, in a worst case program) locations with junk.

The critical thing to do, when writing to eeprom, before you can set the "start writing" bit, is send the bit pattern 01010101 (0x55) followed by 10101010 (0xaa) to the virtual register EECON2. Then - and only then - can you set the "start writing" bit WR of EECON1. And you must do it immediately after sending the 0x55, 0xaa pattern.

The instructions have to be written as five consecutive commands, with no other instructions between them. That means no delays or wait commands between each, global interrupts really need to be turned off, and you need to be sure that your compiler isn't adding in any extra junk you don't want.

In fact, if you're not careful writing these instructions, you can easily get extra stuff in there that you didn't even think about. For example, EECON1.WR=1 seems innocuous enough - set the WR bit of the EECON1 register. But how is the compiler doing it?

Maybe it's taking the existing EECON1 value, bitwise OR-ing it with some bitmask that identifies the WR bit (if WR is the bit two, this mask would be 00000100) and then setting EECON1 to this new result. If this is how the compiler works, then there's a whole load of other junk in the middle of the eeprom-specific sequence.

So to make sure that our compiler isn't adding anything else in, we actually hand-crafted some assembly in the write_eeprom routine (it's been a good few years since any of us did any assembly programming, and took a while for the rusty cogs to get spinning again!). The key part is highlighted above

     ASM: movlw 0x55 ;
     ASM: movwf EECON2 ;Write 55h
     ASM: movlw 0xaa ;
     ASM: movwf EECON2 ;Write aah
     ASM: bsf EECON1,WR ;set WR Bit To begin Write

These five lines have to appear exactly as written in order to write to the eeprom register(s). Yet despite writing this assembly directly into the write_eeprom routine, we still couldn't get our values committed to eeprom between power-cycles (the sure-fire way of testing that the value has been written to non-volatile memory, rather than pluck a value from RAM).

It has taken us nearly three days, copious amounts of coffee and a massive "a-ha!" when the answer was revealed - before accessing the EECON2 register, we have to make sure that the correct bank is selected on the PIC.

This is a gotcha from over twelve years ago, when a few of us were starting out, writing simple (and sometimes not-so-simple) routines in assembly on the trusty old PIC16F877A range. Quite often we'd spend hours debugging assembly routines, only to find we'd not switched to the correct "bank" before trying to access a register.

And - over a decade later - the same problem once-again bit at least one of us cleanly on the arse. And, having remembered about bank-switching, our read and write routines suddenly started working properly! So here they are, for anyone else having a similar problem. Whatever language you're working in, here are a couple of routines for writing to onboard eeprom, old-school....

Saturday, 13 October 2012

Hardware PWM PIC/AVR

PWM is one of those things that we've always done in software. It allows us to set the frequency and duty cycle easily but at a cost of cpu cycles. In fact, until recently we didn't actually know what frequency and duty cycle meant and because our PWM experience was mostly with servo- and motor-control, trying to understand them in these terms was more confusing than it needed to be.

Consider how a servo works.
We have to send a continuous series of pulses between 1ms and 2ms wide (in practice, between 0.6ms and 2.4ms) at least every 20ms. This is where frequency, duty cycle and comparing to hardware pwm gets confused.

With a servo, you need send only one pulse, up to 2ms wide, every 20ms.
This means that for 18 out of 20 milliseconds, the output doesn't actually need to do anything:


So the first thing we need to do is to forget all about servo control.
With that out of the way, we need to understand how PWM is produced inside our microcontroller. If we were doing it in software, this is probably how we'd do it:

First, set up a timer. Every time the timer rolls over, switch on an output pin.
Secondly, set up a CCP (compare, capture, pwm) interrupt.
Put simply, in compare mode, we give this register a value. Whenever the timer value reaches this "milestone" or "waypoint" or whatever term you want to give it, an interrupt occurs. When this interrupt occurs, we turn the output pin off.


The end result is a PWM square wave.
The frequency is determined by the timer1 interrupt. In this example, we're assuming a 16-bit timer1 so the timer counts up to 65,535 and raises the interrupt every time the timer rolls over to zero.
If we pre-loaded timer1 with a value each time, we could reduce the number that timer1 counts up to (actually, we'd start it from a higher value; it always raises an interrupt on rollover to zero, but the principle is the same). If timer1 is only counting up to, say, 20,000 instead of 65,535 (so we'd start the timer1 off at 65535-20000 = 45,535 to get it to count up to 20,000) then the frequency (number of times something occurs) is increased.

Duty cycle is a whole other thing and is dependent on which "value we're counting up to".
In terms of servo control, duty cycle doesn't actually mean anything - we're dealing with definite lengths of time. Turn on a pwm pin, then after 1.5ms, turn it off again. This 1.5ms isn't actually a percentage of a frequency, it's just an amount of time to leave a pin on for.

But if we're creating a continuous, repeating square wave, duty cycle (as a percentage) depends on which value we're counting up to.

If we wanted a 50% duty cycle (half the square wave is high, half low) we need to know what value we count up to, in order to create the timer1/reset interrupt. Then we set our CCP (turn-output-pin-off) interrupt to half this value.

It seems that this is where confusion creeps in when working with hardware PWM.
The datasheets talk about frequency, duty cycle, resolution and all this sort of crazy stuff that just gets confusing - especially when you're trying to load a value to 25% into a register to set the duty cycle.

In order to create a 25% duty cycle, you need to first know the "timebase" for the PWM signal. This is the same as the number the timer is counting up to. Take 25% of this and put that value into the duty cycle register. So if we have an 8-bit PWM cycle (timer is counting from 0-255) in order to create a 25% duty cycle, we need to set the CCP value to 256*25% = 256/4 = 64.

BUT
If we're using a 16-bit timer (counting from 0-65,535) then a 25% duty cycle would need a CCP value of 65536*25% = 65535/4 = 16,384

So in summary -
To increase the frequency, we need our PWM module to count up to a lower value.
To create a duty cycle (in percentage terms) we need to know what number we're counting up to, calculate the number to count up to, in order to create the % based duty cycle and put that value into the duty cycle register.

This is nothing like the way we've been creating PWM for servos in code but does provide us with a really quick and easy way of playing PCM based wav files. Here's a snippet from the datasheet for a PIC16F1825


So by loading the value 0x1F into our PWM register, the hardware will automatically reset the PWM output pin 250,000 times per second. Because of this, the maximum "resolution" - maximum number we can set the CCP interrupt to is a 7-bit value (0-127). In order to create a 250khz carrier wave, the timer1 "count-to-this" value (or hardware equivalent) has to be less than 255 to ensure it can be reset enough times during one second. Because "timer1" is counting up to a value less than 255, the CCP interrupt point cannot be an 8-bit value (0-255) so the maximum resolution is a 7-bit value (0-128).

For our audio playback example, we can set the PWM output "carrier wave" to 250khz - an inaudible frequency except for small animals - and then set the "duty cycle" using the value of each sample in the wav file. It needs a little adjustment, since we're reading 8-bit values from the wav file. Luckily these eight-bit values represent duty cycles in binary (so a 50% amplitude in the sound wav is recorded as 128 in the wav file - i.e. 50% of the maximum 256). So if we could convert an 8-bit value to a 7-bit value, we could just load this straight into our CCP value register in PWM hardware.

To convert 8-bits to 7-bits is quite easy.
We simply bit-shift the entire value to the right by one place.
This is the same as dividing the value by two (but bit-shifting is a quicker operation to do).

What this does is makes all max values of 256 a new value 128.
All minimum values of 1 are discarded (or treated as zero) and the new minimum value becomes 2 (which when bit-shifted or divided by two is one). The overall result is a tiny loss of definition in the sound - but given the low-quality hardware we'll be playing it back on, is of little concern.

So there we have it.
A quick round up of how to play sounds from a 22khz using PWM hardware:


  • Set up the "carrier wave" frequency to be 250khz by loading PR1 with the value 0x1F. 
  • Every 1/22050th of a second, load another byte from the wav file, bitshift right by one position (divide by two) and set CCPR1L.


That's it!

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

Monday, 17 September 2012

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

Friday, 27 April 2012

Awesome Farnell

Just a quick post to remind myself as much as anyone else why Farnell are still number one for me...
After chatting with Robot Steve from BuildBrighton, we were talking about creating peripheral devices for mobile phones. We've already made a few USB devices for PCs (USB/HID slave devices) but this was a whole new challenge.

Android have embraced this idea, but in a back-to-front way.
To enable developers to communicate with the widest range of devices already on the market, they've suggested creating a USB device as a host (not a slave as would be the normal way of going about things) and then having the phone act as a slave, just as it would as if it were plugged into your PC.

Sounds straightforward enough, but we've only ever created USB slave devices, not hosts.
A quick look on the Farnell website reveals that the 24F series of PICs with USB host/slave (OTG - on the go) functionality cost as much as, and sometimes less than, the 18F series of PICs we were using for our slave devices. It looks like it's finally time to think about moving up to 16-bit micros and leaving our old 8-bit friends behind....

We ordered one of these PIC development boards at teatime yesterday:
http://uk.farnell.com/jsp/search/productdetail.jsp?SKU=1605296

At 8am, a hefty thump-thump-thump on the front door (our postie isn't afraid to get you out of bed) and it arrived at Nerd Towers:


That's about fourteen hours from deciding to give this stuff a try to it turning up on the doorstep, ready to give it a whirl. Farnell have once again excelled themselves and are proving to be really hacker/maker friendly! We've always preferred Farnell over their rivals, because their website makes finding components really, really easy. They're not always the cheapest, but when the difference between components can be measured in pence, it's service like this that keeps customers loyal.

Wednesday, 30 November 2011

Communication between two PIC microcontrollers

We're having a bit of a nightmare getting two PICs to talk to each other.
The master is an 18F4550 which will do most of the grunt work and talks to the PC host via USB (we use Oshonsoft's excellent USB/HID library for our USB work).

The slave is a 16F877A (no reason for this particular model other than we had quite a few of these lying around). This basic idea is that the 16F reads 28 input pins and writes a four-byte value out to the serial port.
The master periodically polls the slave, reading the serial data. It looks for two specific bytes which signify the start of the message, then reads in the next four bytes that make up the message (the fifth byte is the XOR sum of the previous four bytes so we have some sort of checksum).

The problem is that while we can check the 16F using the PICKit2 UART tool - and it successfully reads the message being "broadcast" by the slave - when we try to read data using the hardware UART port on the 18F master chip, the USB comms lock up and our PC app stops working.

It seems that we aren't the first people to run into this sort of problem and there are lots of queries across the 'net asking for help with USB and serial comms together. Here's just one example:


So it looks like USB and UART together isn't going to work.
Which is a bit of a bummer.
Also, like the author of the article above, we were also hoping to use the timer1 interrupt to keep a simple clock running, which makes software-based bit-banging and no-go either. Getting a PIC to work as an I2C slave looks quite complicated too, so we're running out of ideas.

The only thing left for us to try is getting two PICs talking to either other using SPI hardware on both the master and slave chips.

Hopefully this will work with both USB and timer1 interrupts running on the master chip. But until we try, we're not going to know......

PIC16F877A resetting

This is just a general post to remind anyone like me who often forgets the basics and spends far too long trying to find faults with hardware/firmware that just aren't there. Although in my case, it's specific to my 18F877A, the same rules apply to 16F628A chips and pretty much any of the 16F range.

Here's why I'm so stupid......
I've been working a lot lately with 18F series chips.
These are great because they have loads of different "fuses" which let you set up the chip in a particular way. One of the fuses on the 18F range allows you to reclaim pin one - the MCLR/PGV (programming voltage) pin and set it to an input. I do this with pretty much all my 18F chips.

So when I came back to the old 16F series chips (the 16F877A is an old favourite that first got me interested in PIC programming) I completely forgot that you need to apply 5v to the MCLR pin to stop the chip from resetting (pulling this pin low causes the PIC to reset completely).

So during testing, while trying to read the serial message(s) being "broadcast" from my 16F chip, I kept getting blackouts - periods when the serial stream would stop. Wiggle a few wires around on the breadboard and it would come back to life. For a little while.
Everything was pointing at a hardware problem - either a dodgy wire or a loose crystal or something. I re-wrote the firmware on both the slave and master chips, stripping each down to just the bare essentials, and used a really handy tool in the PICKit2 software to monitor the serial stream



You can connect anything that uses serial comms, and set the baud rate to any of the common bps values - 1200, 2400, 4800, 9600, 19200, 38400 etc as well as your own custom baud rate (ideal for debugging MIDI devices at 31250)


Anyway, after connecting everything up, setting it running and not touching a thing, the 16F would periodically shut down. I stripped out all the wires, rewired the board with a brand new set of jumper wires, put LEDs on all kinds of output pins and so on. I checked and double-checked the fuses, made sure the watchdog timer was disabled (and even tried enabling it and littering the firmware with watchdog reset commands) and checked the brown-out option wasn't selected. I even put a few different capacitors on the power rail of the 16F, just in case the input voltage was getting a bit wobbly.

The whole thing was suddenly a lot more complicated than any other PIC project I'd ever worked on and still it kept resetting.....

.... that's when I remembered that pin one (MCLR) has to be pulled high to stop the, erm, PIC from resetting all the time. Bugger.

It was as simple as that!
A single wire from pin one to 5V and everything worked beautifully.
If I had remembered about the MCLR pin earlier, instead of assuming it was always set to behave as an in input pin I think I'd have saved myself about two hours this evening.

Hopefully this post will help others in future to avoid wasting a similar amount of time on something so trivial!

PICKit2 clone programmer alternatives

Something has happened to our faithful old PIC programmer - it was (was being the key word) a PICKit2 clone, iCP01 from piccircuit.com and had worked tirelessly for about three years.
But for some reason, it has suddenly stopped working.
No puff of smoke or funny smell - it just stopped working.


At first it had problems recognising a 16F877A DIP, so - as we always do before wiggling wires - it was removed from the USB port (removing any power from the breadboard) and then when it was plugged in there was no "bing-bong" or any indication that the PC had recognised the USB device.

That's it. Despite stripping off the yellow shrink wrap and inspecting the components on the board (nothing seems to have blown and there's continuity across the connections where you'd expect there to be) the device simply refuses to work.

The PICKit2 software doesn't even recognise that the device is plugged in, and Device Manager fails to identify it too. So it's officially dead. The question is, do we get another one (they're great little programmers and really fast too) or try something else?

Thursday, 24 November 2011

Multi-lights controller for toy cars

As one of the younger members of HackLlan Jason was busy making LED traffic lights for his younger brother last Thursday. This week, we've been working on taking that idea to the next level.

Instead of a simple pin-on, pin-off with delay approach, we've changed the code in the microcontroller to use a single Timer0 interrupt (raising an "event" every 1ms) and a number of counters and state machines to control lots of different devices all at the same time.

Instead of the smaller 16F628A chip (used in the original prototype) we've gone to the other end of the scale and put in a massive 40-pin 18F4550. This gives us plenty of i/o options. So far, we've kept PORTB for inputs (using the internal pull-ups to reduce the final component count), put the traffic lights on PORTD and used PORTC for the servo and LED controller for the level crossing.


The what???

That's right. This controller now simultaneously handles:

  • two sets of inter-dependent traffic lights
  • two sets of alternate flashing level crossing lights
  • two servos (level crossing gates)
  • a one-shot fire-on-release ultra-bright LED (speed camera)


There are still plenty of spare i/o pins to add more to this project in the future (pelican crossing, belisha beacon etc) but we need to think about finishing off what we've got here, otherwise it'll be Xmas and we'll still have everything on a loose-wired breadboard (which let's be honest, won't be much of a final gift!)

Here's a video showing the breadboard, controlling all the different lights and servos at the same time -
well done Jason!




Note how the traffic lights continue to work independently of the other things on the board - the alternating lights work separately to the traffic lights, and the servo signals are being sent every 20ms to update the servo horn/gate positions. And all the while the speed camera goes off at periodic intervals!

Monday, 14 November 2011

PIC microcontrolled traffic lights

This is a set of traffic lights, created by the newest (and youngest) member of HackLlan, Llangollen's hackspace. Jason is also a member of our nerd club and his first microcontroller project was this funky set of traffic lights


He's used a PIC16F628A - the perfect "beginner" chip for starting out with PIC programming, although he's not really done much with the myriad of interfaces available on this chip (I2C, SPI, serial UART etc). Jason wired up the board, created the state machine for the lighting sequence and coded the Oshonsoft BASIC program to make the lights play out the same sequence as a real set of traffic lights.

The final result is quite impressive, with the 5mm LEDs mounted onto tiny little home-made PCBs, ready for fitting into their final enclosure.

Friday, 12 August 2011

Audio triggering device

After another late night at BuildBrighton, Harry and I finally got a working version of his audio triggering device. It's following the requirements from this earlier post and I think we've pretty much covered everything that was asked for.

It's not the prettiest thing to look at, but it does function as requested:



The device is yet another PIC-based 18F2455 USB/HID device.
The state of the device is reported back to the host PC over USB, as well as the audio input level (after being converted through the built-in A-to-D convertor).

Although relatively straight forward to design, we did have problems with one little bit, but it turned out to be a major headache and took a few hours to resolve. The problem we hit was the input voltage level was in quite a low range - not more than about 1 volt. This meant that when put onto an A-to-D pin that converts voltages from between 0V-5V into a value in the range 0-255, our input values were only ever in the region of 0-50 (20% of 5V ~ 20% of 255). Complicating things further, the audio in from the PC had a background noise level, so instead of a value range of 0-50, we were getting a value range of about 30-80.

But this was only at absolute peak volume, which didn't really occur very often, so our input value range hovered around a relatively low 30-50. If we turned the volume up on the PC, to get a higher peak voltage, we also increased the background hiss, so the value range went up to 50-80, but the size of the range didn't actually increase.

Although this relatively small range did create a working trigger threshold, we were uncomfortable about it being so narrow. We didn't want to mess about with op-amps and extra components, so set about finding a way of increasing the voltage range on the A-to-D output.

The answer was to provide an alternative reference voltage onto pin RA3 on the PIC (also labelled VREF+). To achieve this, we put the wiper of a potentiometer onto the input pin and connected the other two pins to power and ground (creating a voltage divider and allowing us to create a variable reference voltage of 0V-5V)



Another addition to the circuit was to include a 0.1uF capacitor in series with the left and right audio inputs. The idea being that as we're detecting white noise rather than regular, relatively smooth speech or sound, the wild fluctuations in the input voltage can be negated through the use of the capacitor. As we get a spike in the input voltage, this can charge the capacitor and when the white noise voltage level goes low, the input signal into the PIC can be sustained.

In practice this worked well, and coupled with the variable reference voltage gave us input values of 0-200+ but meant that after the audio was stopped, the input on the analogue pin retained a relatively high value. We added some "bleed" resistors across the capacitor pins of about 10K, so that when the audio signal is removed, the capacitors discharge relatively quickly (after about a second) and the input signal always returns to zero.



Frustratingly, we also spent about an hour trying to solve a problem that wasn't really present. With the extra capacitor in place, we noticed that there was a slight delay between starting the movie off (that contained the white noise soundtrack) and the device recognising the noise and triggering it's outputs.
After an hour of changing capacitor values, altering resistor values, changing analogue reference voltages, introducing software thresholds and so on, we unplugged the device and listened to the soundtrack as the movie played.
Sure enough, after hitting "play" on the movie player, the images moved instantly, but the sound only came in a fraction of a second later - the problem wasn't with the device at all, but the movie player software used to start and stop the audio signal!

Here's the latest, updated schematic, including last night's changes:

Audio Input Circuit_v2
note some input/output pins may have changed from the original design

Tuesday, 9 August 2011

Audio level triggering device


This is Dr Harry Witchel. A doctor no less. He's a scientist specialising in Neuroscience and Psychobiology. We've always found stuff to do with thinking and the brain to be quite fascinating (hey, this isn't called Nerd Club for nothing!) so were thrilled when one of us met up with him recently to discuss helping out with a project he's working on.

The full brief is here but the basic idea is this:
Harry needs a device that he can connect to the audio out of a PC and will trigger a number of outputs when it detects a loud sound. The threshold volume of the input sound needs to be selectable via a potentiometer or similar rotary dial. The same device needs to display what is happening through a series of coloured LEDs and have a number of operational modes selectable through a series of buttons.

It's just crying out for a microcontroller.
And not just any microcontroller, but our old friend the PIC 18F2455.
Using a number of analogue-to-digital pins, digital inputs and outputs and a few discrete components, we could even create a USB-compatible device that can display exactly what is going on, in a nice onscreen display - not just a few twinkling LEDs!

Here's Harry's original spec with our comments in red:
Specifications for Audio Trigger


Here's the first version of the circuit diagram to acheive what he's after:
Audio Input Circuit

We're hoping to have a working version of this circuit (on a breadboard at least) in time for next Thursday's BuildBrighton meeting. More details (and photos) will be posted here in good time.....

SOIC Test clip


Our homebrew ICSP programming header didn't quite work out as planned.
We're not sure why, but we just couldn't get PICKit2 to recognise the device held under the programming header - maybe the pins aren't quite making contact, or perhaps they're routing to the wrong programming pins (we checked and double-checked the connections before making the PCB though!)

It all seems a bit convoluted anyway and if we're going to have to program a few SMT/SOIC PIC chips in place, we'll need something a bit more robust than a wonky home-made board, so we found this:

http://uk.farnell.com/3m/923660-24/test-clip-soic-24way/dp/178283

It'll take a day or two to arrive (Farnell are usually pretty good with delivery times, but recently we've had a few orders split into two or more parts, and they don't always all arrive on the same day!) but we've other stuff to be getting on with in the meantime....

Sunday, 1 May 2011

PIC programmer with ZIF socket

Just a quick shared post - I work a lot with the 18F2455, 18F4550 and 16F628A series of PIC microcontrollers (if you really want to know why, I'll post reasons later!). I usually have a dedicated breadboard made up with the programming pins mapped to a 5-pin header, that works with my PICKit2 Clone iCP01 programmer (from piccircuit.com).



I recently ran out of breadboards and had to dismantle my programming board and immediately regretted it! I can never remember which wires go where, so I made up a ZIF-based PIC programmer. This board has been tested with and works with the chip mentioned above, but will probably work with a whole load more as lots of PICs share the same pinouts for programming data, voltage and clock lines:

Zif Pic Programmer
Print at 100% no scaling in landscape orientation to create your own press-n-peel transfer to make this PIC programmer


Note the yellow bars at the bottom - these are wires connecting two pads/vias.



Below the ZIF socket is a 5-pin header to connect the PIC programmer to.
At the top of the socket is a three-pin header, onto which you should place a jumper, to connect either of the outer-most pins to the central pin. This is how to change between 18F and 16F microchips. (the 18F position is marked on the board).



To program a PIC microcontroller, place it into the ZIF socket ensuring that pin1 is always in the top-left hand corner.