Steve's been working on an rather exciting firmware-update-over-audio idea: uploading Arduino "sketches" using squeaks and farts, rather like a ZX Spectrum used to do, back in the early 80s. It doesn't sound like much when written down, but the possibilities of such an approach are really quite exciting - maybe he'll expand more on it in the future.
In the meantime, Iain had asked for a simple project - a magnet controlled motor controller. We'd already got the PCB made up and the circuit working on a breadboard, so it was time to code up the firmware.
(note the use of a larger 640FET for the motor controller rather than a simple NPN transistor; the reason was two-fold - firstly, more current is available to the motor than if a wimpy 200mA 2N2222A were used and also, this particular FET also includes an internal fly-back diode which we need because we're switching an inductive load)
The actual functionality of the controller is pretty simple - present a magnet over a hall effect sensor, and start the motor running. Present the magnet a second time, and turn the motor off. A two-stage state machine would easily take care of this. But there were a couple of other considerations that we had to take into account:
Firstly, he'd mentioned running a motor of a PP3 battery.
So we needed to make sure that our PIC microcontroller wasn't slurping all the power out of the battery, constantly polling the hall sensor on the input pin. This involves putting the PIC to sleep when not needed. Which means, of course, that we need to use some kind of wake-up interrupt to take it out of sleep.
Here's what we came up with:
Define CONFIG1 = 0x0984
Define CONFIG2 = 0x1cff
Define CLOCK_FREQUENCY = 1
AllDigital
symbols:
declarations:
Dim state As Byte
Dim timer_count As Word
Dim i As Byte
Symbol motor_output = PORTC.3
Symbol magnet_in = PORTA.5
Symbol led = PORTC.2
configpins:
ConfigPin PORTA = Input
ConfigPin PORTC = Output
ConfigPin motor_output = Output
ConfigPin magnet_in = Input
initialise:
'internal oscillator defaults to 500khz internal
WaitMs 100 'wait for internal voltages to settle
'enable pull-up resistors on encoder pins, and pushbuttons
OPTION.7 = 0 'wpuen=7
WPUA = 0xff 'enable pull-ups on all portA
state = 0
timer_count = 0
Low motor_output
Low led
'enable the interrupt on change register
INTCON.IOCIE = 1
IOCAN.5 = 1 'portA raised an interrupt on change
'enable interrupts
INTCON.GIE = 1
loop:
Select Case state
Case 0
'motor is not running
If magnet_in = 0 Then
'debounce the input
WaitMs 10
If magnet_in = 0 Then
High motor_output
High led
WaitMs 200
'wait for the magnet to be removed
While magnet_in = 0
'do nothing
Wend
state = 1
Endif
Endif
Case 1
'motor is already running
If magnet_in = 0 Then
'debounce the input
WaitMs 10
If magnet_in = 0 Then
Low motor_output
Low led
WaitMs 200
'wait for the magnet to be removed
While magnet_in = 0
'do nothing
Wend
state = 0
Endif
Endif
EndSelect
timer_count = timer_count + 1
If timer_count > 5000 Then
'testing: show the going to sleep sequence
'For i = 1 To 3
'High led
'WaitMs 80
'Low led
'WaitMs 80
'Next i
timer_count = 0
ASM: sleep
Endif
Goto loop
End
On Interrupt
If IOCAF.5 = 1 Then
'clear the interrupt
IOCAF.5 = 0
timer_count = 0
Endif
Resume
Define CONFIG2 = 0x1cff
Define CLOCK_FREQUENCY = 1
AllDigital
symbols:
declarations:
Dim state As Byte
Dim timer_count As Word
Dim i As Byte
Symbol motor_output = PORTC.3
Symbol magnet_in = PORTA.5
Symbol led = PORTC.2
configpins:
ConfigPin PORTA = Input
ConfigPin PORTC = Output
ConfigPin motor_output = Output
ConfigPin magnet_in = Input
initialise:
'internal oscillator defaults to 500khz internal
WaitMs 100 'wait for internal voltages to settle
'enable pull-up resistors on encoder pins, and pushbuttons
OPTION.7 = 0 'wpuen=7
WPUA = 0xff 'enable pull-ups on all portA
state = 0
timer_count = 0
Low motor_output
Low led
'enable the interrupt on change register
INTCON.IOCIE = 1
IOCAN.5 = 1 'portA raised an interrupt on change
'enable interrupts
INTCON.GIE = 1
loop:
Select Case state
Case 0
'motor is not running
If magnet_in = 0 Then
'debounce the input
WaitMs 10
If magnet_in = 0 Then
High motor_output
High led
WaitMs 200
'wait for the magnet to be removed
While magnet_in = 0
'do nothing
Wend
state = 1
Endif
Endif
Case 1
'motor is already running
If magnet_in = 0 Then
'debounce the input
WaitMs 10
If magnet_in = 0 Then
Low motor_output
Low led
WaitMs 200
'wait for the magnet to be removed
While magnet_in = 0
'do nothing
Wend
state = 0
Endif
Endif
EndSelect
timer_count = timer_count + 1
If timer_count > 5000 Then
'testing: show the going to sleep sequence
'For i = 1 To 3
'High led
'WaitMs 80
'Low led
'WaitMs 80
'Next i
timer_count = 0
ASM: sleep
Endif
Goto loop
End
On Interrupt
If IOCAF.5 = 1 Then
'clear the interrupt
IOCAF.5 = 0
timer_count = 0
Endif
Resume
The interesting things to note here are
a) the use of inline ASM. Oshonsoft as a compiler is great for just adding in some assembly language right in the middle of your basic code. Just use the tag ASM: and then your assembly instruction. One little gotcha to look out for is there must be a space or a tab between the ASM: statement and the command (otherwise the compiler treats the next word as a label, not an instruction)
b) the use of INTCON.IOCIE = 1 to enable interrupt-on-change. Combined with IOCAN.5 = 1 this means that when pin A5 goes low (the register to monitor rising edges on PORTA is called IOCAP) the interrupt-on-change interrupt is fired. Luckily for us, this interrupt can also bring the device out of a sleep state
c) although commented out, during testing we put a "shutdown" routine (flashed an LED a couple of times) to indicate when the device was going to go to sleep. This proved useful to demonstrate that the device was actually asleep (and that no activity was actually taking place). To see this in action, uncomment the shutdown flashing LED and comment out the ASM: SLEEP statement. You should see that the LED flashes then after a second or two delay, flashes again - this is the behaviour of the firmware. If you now re-enable the SLEEP statement, the LED only flashes once: the sleep statement has suspended the program execution (just as we hoped it would).
Of course, when using interrupt-on-change, it's important that other (unused) input pins are not accidentally firing the change-interrupt because they've been left floating. Although the IOCAN register masks out only the pins that are to be monitored, it doesn't hurt to ensure that the pull-up resistors are enabled on any input pins, just in case.
So there we have it, a quick and simple project made up over a few cups of teas and a few enjoyable hours talking rubbish with the other nerds. With the firmware written and tested, we'd better get this thing in the post so Iain can actually put it to use!
No comments:
Post a Comment