Search This Blog

Sunday, November 21, 2010

Working Bootloader

Finally! I got my bootloader code working.  It's pretty quick, too.  The basic outline ended up looking like this:
The Pic code is written in Asm.  The PC code is written in Python, making use of USPP as ascribed earlier.  The tinyBld code I looked at was an ok starting point.  I ended up rewriting a lot of it.  It ended up using 224 bytes of space for a 198 word program.

The startup sequence goes like this: On power up or reset, the first four words pageselect and go to the bootloader at the bottom of the program space.  This space initializes the USART and immediately sends a start signal ( 0xC1 ) at 9600 baud.  It waits for a little over a one second.  If it does not receive the start signal back, it drops to one byte before the user's first four space.  This is in the last 32 bytes of program space and holds the first four words of the last loaded program.  The byte before the user's space does a pageselect back to page zero.  The loaded program then executes.

Assuming you want to load code, you wire up TX, RX, VCC, and GND from an RS232 shifter hooked to the PC.  I use a USB cable hooked to a serial to USB converter to the shifter.  Fire up the board.  Wait 2 seconds. Launch the PC program, called icsp.py.  I found I had to set the userid bit on python for this to work.  ( Yes, I know the risk and unset it when done ).  The icsp program takes a few arguments.  The first is the path to the serial port.  For my linux machines with the serial to USB, this is /dev/ttyUSB0 ( or one, or two, etc ).  The second argument is the location of the hex file you wish to load.  The last argument is the debug.  This prints out verbose logging as to what is occurring.

The program will load the file and create packets for transmission.  This involves "undoubling" a lot of the values the hex file creates.  It computes a new checksum value for each line/packet.  It keeps the user's program from overwriting the bootloader.  It intercepts the first four instructions if they are in 0x00-0x04.  It rewrites them in the user's first four area.  If there are additional instructions on this line, it creates a new line with address 0x0004 and a new checksum.  When this load is complete, the command line lets you know it is listening for the start signal from the Pic.

Hit the reset button ( or power off and on ).  The Pic will jump to the bootloader code and send the start signal.  The icsp receives this, echoes it back, and goes into and endless wait for the 'Ok' signal ( 'k' ).  The Pic, meanwhile, prepares for a new program.  It copies the first four instructions from its program space to a buffer.  This is the code that jumps to the bootloader.  It then erases the program space up to the bootloader. It also rewrites the 32 bytes from the user's first four space.  The 16f88 I'm working with has to erase blocks of 32 and wastes 28 bytes of space because of it.  Once the erase is done, it rewrites the bootload first four to 0x00-0x04.  It then sends the 'k' to the PC and goes into a one second receive delay, waiting for the first line.

The PC loops through the list of packets and sends each to the Pic.  The Pic receives each byte, storing the address in the EEADR and EEADRH registers.  It stores the data in a buffer.  It knows how many data packets are coming because the number of instructions is sent as part of the packet.  When the read is complete, it writes the packet to the program space at the address indicated.  Wash, rinse, repeat.  The pic calculates the checksum.  If it doesn't match the sent checksum, it sends a 'n' to the PC for another attempt.  The PC will retry three times per line.  When the last line has been sent, the Pic will time out waiting for the next line and go to the pageselect and user's first four.

Now I can reprogram my robot in circuit without a lot of rewiring!!! Huzzah!

I'll post code after I convert it.

Thursday, October 28, 2010

Pics of My Bot

Here is my robot with a little makeover. I call him CDR ( Compact Disc Robot ). Got a new smartphone this week, so I can finally post some pics and later vids.

Front View:

Bottom view ( Power supplies and gear motor ):

Circuit Board( reusable as dev board ):

Saturday, October 23, 2010

Random Ramblings

   Hmmm... Finally got some parts from Sparkfun. I can now program my chips from my laptop thanks to the usb to serial dongle. Hoorah! I got the Sharp IR receiver working by reading over the ADC channel on AN1.
   Worked on the Python code some more. I now have a class to load the hex file and start the serial port. The next step is to write the code to communicate with the pic. from the pic side, I'll write my program and the bootloader_test all at once. The bootloader_test will get called on reset, listen for the start and timeout after 1 sec. If it gets the start signal, it will read in each line and spit it back to the PC.
   If that goes well, I'll move on to making it write the new program *shudder*. For some reason, this makes me a bit nervous.
   Other parts I got from Sparkfun will allow me to wire up the LCD board. I'll let it be powered by another board. I think I'll use the 16C84 chip I have laying around. I now have a 10MHz ceramic that I can use for it. I just need to convert the 16F84A code at 20MHz to work with it.
   I should also wire up a board for USART from the PC. Before I do that, I'll probably get a DB9 part that has right angles to a board. Forgot to get that on my last order...

Sunday, August 1, 2010

Bootloader - Send and Receive Protocol

    Worked on the bootloader a bit this weekend. This was my first time using the built in AUSART modules of the 16f88 rather than bit-banging routines. Got the pic to transmit pretty quickly at 8MHz internal clock with data rate of 19,200. Then I wrote a simple python program to simulate the protocol I wanted for the bootloader. The program fires up and starts sending the start bit, 0xC1, over and over. After each send, it listens for 20ms for a response of 'K'. It times out after one minute. On the pic side, the program fires up the USART in ansynch mode and listens for 1 second for the start code. If it hears it, it validates that it is the right code. It sends 'K' back, and it then transmits its message ( 'Hello, world!', of course ).
    The programs looked great, but they were not working. My most bone-headed move of late, I forgot to hook up the receiving wire! Incidentally, I use some transistors, resistors, and a diode for my converter. I tried the Max232 chip for this, but I got way too much noise on the circuit. I really need to figure that out sometime....
Anyway, the program works great. Below is the code for posterity. I hope it comes through clean this time. As mentioned in previous posts, the Python code uses the uspp code for serial transmission. Great bit of work. I am indebted to the developer for saving me the trouble of learning raw serial transmission in Linux. And it will work with Windoze.
    Edit:  Looking over past posts, I realized I changed direction again. I am writing my bootloader from whole cloth, stealing from the tinyBld code. I am not going to bit bang. I'll use the built in modules. Robot code will shift the motors to RB0 and 1 and RB7 and 6, leaving RB5(TX) and 2(RX) free for ICSP bootloading.
Pic Code:

;******************************************************************************
;
; Filename: periph.asm

; Date: 20010.07.31

; File Version: 1.0.0

;

; Author: Tom Hunt
;

;******************************************************************************

; NOTES: Echo some ascii over serial to PC usart.
; Use the built-in usart module.
;
; Pic 16f88 running at 8MHz
; data rate: 19200 with spbrg high
;
;******************************************************************************
; CHANGE LOG:
; Backed by a python program (talk.py), the python program sends the start
; char every 1ms and then listens for ~20ms. The pic fires up and listens
; for the start char. When it hears it and validates that it is the
; start char, it sends back a 'K'. The python prog hears the 'K' and
; goes into eternal listen mode. The pic sends the message, 'Hello, world!'
; The python prog prints this to the screen.
;
;******************************************************************************



list R=DEC, p=16f88 ; list directive to define processor

#include <p16f88.inc> ; processor specific variable definitions



;Program Configuration Register 1
__CONFIG _CONFIG1, _CP_OFF & _CCP1_RB0 & _DEBUG_OFF & _WRT_PROTECT_OFF & _CPD_OFF & _LVP_OFF & _BODEN_OFF & _MCLR_ON & _PWRTE_ON & _WDT_OFF & _INTRC_IO

;Program Configuration Register 2
__CONFIG _CONFIG2, _IESO_OFF & _FCMEN_OFF


errorlevel 1, -302 ; suppress Register in operand not in bank 0 -- Get rid of this during development


;******************************************************************************
; VARIABLE DEFINITIONS
;******************************************************************************

#define STARTSIGNAL 0xC1

CBLOCK 0x20
count
buffer:20
i
tIndex
ENDC

CBLOCK 0x70 ; interupt context-saving vars here see datasheet chp 15
w_temp
status_temp
pclath_temp
ENDC

;******************************************************************************
; MACRO DEFINITIONS *
;******************************************************************************

bank0 MACRO
bcf STATUS, RP1
bcf STATUS, RP0
ENDM

bank1 MACRO
bcf STATUS, RP1
bsf STATUS, RP0
ENDM


;******************************************************************************
; RESET VECTOR *
;******************************************************************************

ORG 0x000 ; processor reset vector
nop
nop
nop

goto main ; go to beginning of program



;******************************************************************************
; INTERRUPT VECTOR
;******************************************************************************

ORG 0x004 ; interrupt vector location
MOVWF w_temp ;Copy W to TEMP register
SWAPF STATUS, W ;Swap status to be saved into W
CLRF STATUS ;bank 0, regardless of current bank, Clears IRP,RP1,RP0
MOVWF status_temp ;Save status to bank zero STATUS_TEMP register
MOVF PCLATH, W ;Only required if using page 1
MOVWF pclath_temp ;Save PCLATH into W
CLRF PCLATH ;Page zero, regardless of current page
;
;(ISR) ;(Insert user code here)
;
MOVF pclath_temp, W ;Restore PCLATH
MOVWF PCLATH ;Move W into PCLATH
SWAPF status_temp, W ;Swap STATUS_TEMP register into W
;(sets bank to original state)
MOVWF STATUS ;Move W into STATUS register
SWAPF w_temp, F ;Swap W_TEMP
SWAPF w_temp, W ;Swap W_TEMP into W


retfie ; return from interrupt


;******************************************************************************
; INITIALIZATION CODE
;******************************************************************************
; no interrupts, all digital i/o, 8MHz internal clock
; usart configured for 19,200 data rate
; brgh set to high speed
; spbrg = clock / ( dataRate * 16 * ( 4^0 ) ) - 1
; spbrg = 8e6 / ( 19200 * 16 * 1 ) - 1 = 25.042
init
bank0
clrf INTCON
clrf ADCON0 ; all digital
bank1
movlw 0x00
movwf ANSEL ; go all digital
movlw b'01110010' ; bits for OSCCON internal clock at 8MHz 0111 0000
movwf OSCCON^0x080
movlw 0x19 ; decimal 25
movwf SPBRG^0x080
; txsta gets b'00100100' enables asynch transmit and fast(brgh)
movlw 0x00 ^ ( ( 1 << TXEN ) + ( 1 << BRGH ) )
movwf TXSTA ^ 0x080
; tmr0 set up
movlw 0x00 ^ ( ( 1 << PS2 ) + ( 1 << PS1 ) + ( 1 << PS0 ) ) ; b'00000111' tmr0 prescaler 1:128 ( and the 2x factor )=256
movwf OPTION_REG ^ 0x080
bank0
; rcsta gets b'10010000' enables serial port and continuous receive
movlw 0x00 ^ ( ( 1 << SPEN ) + ( 1 << CREN ) )
movwf RCSTA
return

;******************************************************************************
; RS232 CODE
;******************************************************************************
; receive tries to receive a byte for one second from RCREG
receive
movlw 0x28 ; count = 40
movwf count
timeIt
; tmr0 will give ~25ms @ 8MHz -> 256*195= 49920 instrs
movlw 0x03D ; 256-195=61
movwf TMR0
testRcReg
btfsc PIR1,RCIF ; if set, new byte in RCREG
goto gotAByte
movf TMR0,W
btfss STATUS,Z ; wait for timeout
goto testRcReg
decrementCount
decfsz count,F
goto timeIt
; if we get here, we've timed out after 1 second
goto errorAndExit
gotAByte
movf RCREG,W
return

sendByte
movwf TXREG
bank1
btfss TXSTA,TRMT ; when TRMT is set, transmission is completed
goto $ - 1
bank0
return

;******************************************************************************
; MAINLINE CODE
;******************************************************************************

main
call init
bank1
movlw 0x00 ^ ( 1 << 2 )
movwf TRISB ^0x080 ; enable all PORTB for output except PORTB,2 ( RX )
movlw b'11111011' ; set PORTA to Input except for A2
movwf TRISA ^ 0x080
bank0
waitForSignal
movlw 0x00 ; init W reg
call receive
sublw STARTSIGNAL
btfss STATUS,Z
goto errorAndExit
movlw 'K'
call sendByte
sendMsg
movlw 0
movwf i
sayHi
call table
iorlw 0
btfsc STATUS, Z ; if end of table, end
goto done
call sendByte
incf i, F
movf i, W ; increment index
goto sayHi

errorAndExit ; placeholder
bank0
bcf RCSTA,SPEN ; desactivate UART

clrf PCLATH
done
goto $

;******************************************************************************
; TABLE DATA
;******************************************************************************

table
movwf tIndex ; save table index
movlw HIGH tableEntries ; get the current high page
movwf PCLATH ; put this in PCLATH to move us to the right page
movf tIndex, W ; move the index asked for into w
addlw LOW tableEntries ; compute the offset for the block
btfsc STATUS, C ;
incf PCLATH, F ; if in next page, increment PCLATH
movwf PCL ; put the correct addr in PCL
tableEntries
dt "Hello, world!\0"

END

Python Code:

#!/usr/bin/python

from uspp import *
from time import sleep as sleep

startByte = 0xC1
ok = 'K'

def init( tty ):
tty = SerialPort("/dev/ttyS0", 0, 19200 )
tty.flush() # discard unread bytes
return tty

def sendStart( tty ):
print 'Sending start byte ', 0xC1
gotStart = False
ch = ''
count=800
while count > 0:
tty.write( chr( startByte ) )
tries = 20
while tries > 0:
ch = tty.read()
if ok == ch:
gotStart = True
return True
sleep(.001)
tries = tries - 1
print count, ' ch=\'', ch, '\''
count = count - 1
print ''
if True == gotStart:
print 'Got ok from pic'
else:
print 'Never got ok back from pic'
return gotStart



def listen( tty ):
# ignore timeouts and read
print 'Endless listen:'
ch = ''
try:
while True:
ch = tty.read()
if ch != '':
print ch
except SerialPortException:
pass

def main():
print 'Starting...'
tty = None
tty = init( tty )
ready = sendStart( tty )
if True == ready:
listen( tty )
else:
print 'Never got ready. Aborting...'
tty.__del__()

main()

Sunday, July 18, 2010

Schematic

    Well, I tried to get file upload/download working between my laptop and my Motorola Razr.  Unfortunately, my phone isn't supported by the Linux tools.  I'll be dipped before I pay Motorola for tools to upload software.  Anyway, spent a bit of time trying to make a schematic with Eagle from Cadsoft.  Great product, a little wonky to get used to the interface.  With luck, my schematic will be viewable here.  Click on the image for a size you can actually see.



    As you can see, there is not a whole lot to the thing.  The beast was in the code and the board layout.

    Success!!! Now I can post code!. This time I'm replacing the less thans with ampersand pound 60;. Here goes:

;******************************************************************************
;
; Filename: head.asm

; Date: 2010.06.28

; File Version: 1.0.0

;

; Author: Tom Hunt
;

;******************************************************************************

; NOTES: Pic 16f88, running @ 4MHz internal clock.
; Integrate ping ultrasonic sensor and servo "neck"
; Test look ahead, stop on block ( under threshold ), look left and
; take reading, look right and take reading. Then find most open way.
; If no open way ( all under threshold ), do 180 degree turn.
; Motor operation will be simulated with leds.
;
; Eyes on RA0
; Neck on RA6
; Motor1 on RB2-3
; Motor2 on RB4-5
; Speaker on RB7
;
; TODO - Need a cap for maximum range on sensor. If TMR1H > 8, set
; EyesLH to max value, indicating wide open space.
; I'm not sure this will work, but I need something to tell
; if we've gone beyond maximum range. Google time....
;
; Stupid note to self: CARRY SET MEANS POSITIVE YOU IDIOT!!!!
;
;******************************************************************************
; CHANGE LOG:
; 2010.07.04 * Fixed decision logic. Got backwards on the Carry
; flag after subtraction again.
; * Added sound. Before decision, plays Zelda's lullaby.
;
; 2010.07.05 * Added motors. Tweaking values for motors. Went from
; 6V to 3V motors. May need pulsing.
;
;******************************************************************************



list R=DEC, p=16f88 ; list directive to define processor

#include <p16f88.inc> ; processor specific variable definitions

; _INTRC_IO _HS_OSC

;Program Configuration Register 1
__CONFIG _CONFIG1, _CP_OFF & _CCP1_RB0 & _DEBUG_OFF & _WRT_PROTECT_OFF & _CPD_OFF & _LVP_OFF & _BODEN_OFF & _MCLR_OFF & _PWRTE_ON & _WDT_OFF & _INTRC_IO

;Program Configuration Register 2
__CONFIG _CONFIG2, _IESO_OFF & _FCMEN_OFF



; '__CONFIG' directive is used to embed configuration data within .asm file.

; The lables following the directive are located in the respective .inc file.

; See respective data sheet for additional information on configuration word.


;******************************************************************************
; CONSTANT DEFINITIONS
;******************************************************************************

#define TOOCLOSE FLAGS,0
#define UTURNLEFT FLAGS,1
#define TIMEOUT FLAGS,2
#define EYES PORTA,0
#define EYESTRIS TRISA^0x080,0
#define MOTORPORT PORTB
#define MOTORRIGHTONE 5
#define MOTORRIGHTTWO 4
#define MOTORLEFTONE 2
#define MOTORLEFTTWO 3
#define SERVO PORTA,6
#define PULSECOUNT 40
#define PULSEDELAY 0x040
#define UTURN 0
#define CANGOLEFT 1
#define CANGORIGHT 2
#define LEFTORRIGHT 3
#define MAXRANGEH 8
#define MAXRANGEL 143
#define THRESHOLDH 3
#define THRESHOLDL 143 ; threshold equates to ~1 foot

; sound constants
#define SPEAKERTRIS TRISB ^0x080
#define SPEAKERPORT PORTB
#define SPEAKER PORTB,7
#define NOTEE7HIGH 0xFF
#define NOTEE7LOW 0x42
#define NOTED7BHIGH 0xFF
#define NOTED7BLOW 0x13
#define NOTEB6HIGH 0xFF
#define NOTEB6LOW 0x02


;******************************************************************************
; VARIABLE DEFINITIONS
;******************************************************************************

CBLOCK 0x20
EYESLH:2 ; holds the time of a pulse back from eyes
LEFTLH:2 ; holds the time of the pulse back from the "eyes" for left look
RIGHTLH:2 ; holds the time of the pulse back from the "eyes" for right look
DIRECTION ; next direction to take
DELAY:3
PULSE:2
PDELAY:2
NOTEHIGH
NOTELOW
COUNTER
SONGCOUNTER
ENDC

CBLOCK 0x70 ; interupt context-saving vars here see datasheet chp 15
FLAGS
w_temp
status_temp
pclath_temp
ENDC



;******************************************************************************
; MACRO DEFINITIONS
;******************************************************************************

bank0 MACRO
bcf STATUS, RP1
bcf STATUS, RP0
ENDM

bank1 MACRO
bcf STATUS, RP1
bsf STATUS, RP0
ENDM


;******************************************************************************
; RESET VECTOR
;******************************************************************************

ORG 0x000 ; processor reset vector
goto main ; go to beginning of program



;******************************************************************************
; INTERRUPT VECTOR
;******************************************************************************

ORG 0x004 ; interrupt vector location
MOVWF w_temp ;Copy W to TEMP register
SWAPF STATUS, W ;Swap status to be saved into W
CLRF STATUS ;bank 0, regardless of current bank, Clears IRP,RP1,RP0
MOVWF status_temp ;Save status to bank zero STATUS_TEMP register
MOVF PCLATH, W ;Only required if using page 1
MOVWF pclath_temp ;Save PCLATH into W
CLRF PCLATH ;Page zero, regardless of current page

; **********************************************
; TMR1 Interrupt
; **********************************************
btfss PIR1,TMR1IF ; see if we timed out
goto finishInt ; no timer 1 overflow, finish interrupt
; handle timeout
bsf TOOCLOSE
bsf TIMEOUT
finishInt
bcf PIR1,TMR1IF ; clear the interrupt flag

MOVF pclath_temp, W ;Restore PCLATH
MOVWF PCLATH ;Move W into PCLATH
SWAPF status_temp, W ;Swap STATUS_TEMP register into W
;(sets bank to original state)
MOVWF STATUS ;Move W into STATUS register
SWAPF w_temp, F ;Swap W_TEMP
SWAPF w_temp, W ;Swap W_TEMP into W

retfie ; return from interrupt


;******************************************************************************
; Initialization Code
;******************************************************************************

init
bank0
clrf TMR1L
clrf TMR1H
movlw 0x00 ^ ( (1<<GIE) + (1<<PEIE) )
movwf INTCON ; enable global & peripheral interrupts
call initEyes
clrf ADCON0 ; all digital
bank1
movlw 0x00
movwf ANSEL ; go all digital
movlw b'01100010' ; bits for OSCCON internal clock at 4MHz 0110 0000
movwf OSCCON^0x080
movlw 0x00 ^ ( (1<<TMR1IE) ) ; enable TMR1 interrupts
movwf PIE1^0x080
bank0
return

initEyes
movlw 0x00 ^ ( (1<<T1CKPS0) + (1<<TMR1ON) ) ; (1<<T1CKPS0) + try at 1:2 prescale
movwf T1CON ; prescale 1:8 and enable tmr1
return

initSong
movlw 0x00 ^ ((1<<TMR1ON) ) ; prescaler 1:1
movwf T1CON
return

;******************************************************************************
; Sound Code
;******************************************************************************

playNote
movlw 250
movwf COUNTER
playItAgain
bsf SPEAKER
call playOnOff
bcf SPEAKER
call playOnOff
decfsz COUNTER,F
goto playItAgain
return

playOnOff
bcf PIR1,TMR1IF
bcf TIMEOUT
bsf T1CON,TMR1ON
movf NOTEHIGH,W
movwf TMR1H
movf NOTELOW,W
movwf TMR1L
waitForTimeout
btfss TIMEOUT
goto waitForTimeout

return

playE7
; play E7
movlw NOTEE7HIGH
movwf NOTEHIGH
movlw NOTEE7LOW
movwf NOTELOW
call playNote
call songDelay
return

playD7B
; play D7b
movlw NOTED7BHIGH
movwf NOTEHIGH
movlw NOTED7BLOW
movwf NOTELOW
call playNote
call songDelay
return

playB6
; play B6
movlw NOTEB6HIGH
movwf NOTEHIGH
movlw NOTEB6LOW
movwf NOTELOW
call playNote
call songDelay
call songDelay
return

; Zelda's Song ... kinda
playSong
bcf SPEAKER
call initSong ; tmr1 prescaler set to 1:1

movlw 3
movwf SONGCOUNTER
fromTop
call playE7
call playD7B
call playB6

decfsz SONGCOUNTER,F
goto fromTop

call playD7B
call playB6

call initEyes ; change prescaler back to 1:8
return


;******************************************************************************
; Ultrasonic Ranging Routines
;******************************************************************************
; This code takes a reading and stores the result in EyesLH. It also sets
; flag tooclose if there is a bad reading/under threshold

; after cueing the pulse, we have a total wait of 750us

takeRange
; clear TOOCLOSE flag
movlw 0x00 ; init EYESLH to zero
movwf EYESLH + 1
movwf EYESLH
bcf TOOCLOSE
; first, send init pulse to EYES
bank1
bcf EYESTRIS ; make EYES output enabled
bank0
bsf EYES
goto $ + 1 ; five ops at 4MHz gives 5us
goto $ + 1 ; we use 11us
goto $ + 1
goto $ + 1
goto $ + 1
nop
bcf EYES
bank1
bsf EYESTRIS ; make EYES input enabled
bank0
; now set everything up and wait for total of 750us
call delay750us

clrf TMR1L ; reset tmr1
clrf TMR1H
bcf PIR1,TMR1IF ; clear the overflow flag for tmr1
bcf TOOCLOSE ; clear tooclose flag
bsf T1CON,TMR1ON ; start the timer1

waitForRange
; get range and set TOOCLOSE flag, or return EYESLH.
; If max range, set EYESLH to max range value.
btfsc TOOCLOSE ; flag set by interrupt if we time out
goto gotTooClose
; TRIAL CODE - No idea if this works. Want to set allclear
; if TMR1H goes above 8
btfss TMR1H,7
goto keepChecking
btfsc TMR1H,0
goto setAllClear

keepChecking
btfsc EYES ; wait for send to be done
goto waitForRange
; if we get here, we have a range
; if not TOOCLOSE, set EYESLH
bcf T1CON,TMR1ON ; turn off timer1
movf TMR1L,W ; capture timer1 values in EyesLH
movwf EYESLH
movf TMR1H,W
movwf EYESLH + 1

; Test for reading less than THRESHOLD
; if high greater than THRESHOLDH(3), we're good, return
; if eyes greater than threshhold, result is negative, carry is clear, return value
movf EYESLH + 1,W
sublw THRESHOLDH ; test Eyes > Thresh by Thresh - Eyes
btfss STATUS,C ; if carry is clear, EYESLH is greater than THRESHOLDH
return ; return the good reading

; if high less than THRESHOLDH(3), gotTooClose
; if eyes less than threshold, set flag and return
movlw THRESHOLDH
subwf EYESLH + 1,W ; test Eyes < Thresh by Eyes - Thresh
btfss STATUS,C ; if carry set, EYESLH is less than THRESHOLDH
goto gotTooClose

; if eyes high equals THRESHOLDH(3), check THRESHOLDL
; if eyes low is less than threshold low, set flag and return
movlw THRESHOLDL
subwf EYESLH,W ; test EYESL < THRESHL by EYESL - THRESHL
btfss STATUS,C ; if carry is set, EYESL is less than THRESHL
goto gotTooClose
return

setAllClear
; we hit max range. set EyesLH to max value
movlw MAXRANGEH
movwf EYESLH + 1
movlw MAXRANGEL
movwf EYESLH
return

gotTooClose
;redo this for straight, left, and right reading
; set EYESLH to zero and set TOOCLOSE flag
bsf TOOCLOSE
movlw 0x00
movwf EYESLH + 1
movwf EYESLH
return

;******************************************************************************
; Servo Neck Code
;******************************************************************************
; Generic code to move the servo "Neck"

moveServo
movlw PULSEDELAY
movwf DELAY
movf PULSE,W
movwf PULSE + 1
call pulseOut
decfsz DELAY,F
goto $ - 4
return

; pulses the servo. 20ms total cycle, duty cycle determines position.
; value in pulse will determine time pulse is high
; value of one = .5 ms, 2 = 1ms, etc
; servo needs 6 delay values:
; 10 o'clock needs 2.0ms on and 18.0ms off pulse = 4 2000 and 18000 instructions
; 12 o'clock needs 1.5ms on and 18.5ms off pulse = 3 1500 and 18500 instructions
; 2 o'clock needs 1.0ms on and 19.0ms off pulse = 2 1000 and 19000 instructions
pulseOut
movlw PULSECOUNT
movwf PDELAY + 1
; cheezy hack til I redo this: add one to pulse
incf PULSE + 1,F
bsf SERVO ; start sending high to servo

pulseLoop
decf PULSE + 1,F ; every 500us, decrement pulse
btfsc STATUS,Z
bcf SERVO ; if zero send low to servo

movlw 99
movwf PDELAY
halfMs ; 500us delay inner loop
decf PDELAY,F
nop
btfss STATUS,Z
goto $ - 3
decfsz PDELAY + 1,F
goto pulseLoop
return

;******************************************************************************
; Look Code
;******************************************************************************
; This code looks around, moving the neck and taking readings

lookAhead
; move to 12 o'clock
movlw 3
movwf PULSE
call moveServo
; take reading
call takeRange ; puts reading in EYESLH
return

lookLeft
; move to 10 o'clock
movlw 4
movwf PULSE
call moveServo
; take reading and store in LEFTLH
call takeRange
movf EYESLH + 1,W
movwf LEFTLH + 1
movf EYESLH,W
movwf LEFTLH
return

lookRight
; move to 2 o'clock
movlw 2
movwf PULSE
call moveServo
; take reading, store in RightLH
call takeRange
movf EYESLH + 1,W
movwf RIGHTLH + 1
movf EYESLH,W
movwf RIGHTLH
return

;******************************************************************************
; Decision Code
;******************************************************************************
; This code compares readings to determine next direction

determineNewDirection
; figure out which way to turn: left, right, or uturn
call allStop ; stop moving till we know where to go
call playSong
movlw UTURN
movwf DIRECTION
call lookLeft ; if too close, dir stays zero
btfss TOOCLOSE ; else dir is cangoleft
incf DIRECTION,F

call lookRight
btfsc TOOCLOSE
goto pickATurn
movlw CANGORIGHT ; add 2 to direction
addwf DIRECTION,F ; it will be 2 if right only, 3 if both
pickATurn
movf DIRECTION,W
sublw LEFTORRIGHT
btfsc STATUS,Z ; dir = leftorright ? goto compareLeftRight
goto compareLeftRight
movf DIRECTION,W
sublw CANGOLEFT
btfsc STATUS,Z ; dir = left ? turn left
goto turnLeft
btfss STATUS,C ; dir = 0 ? uturn
goto turnRight ; result was 1-2=-1, negative, carry clear
goto uturn ; result was 1-0=0, positive, carry set
return ; safety return

compareLeftRight
movf LEFTLH + 1,W ; right - left, test if negative
subwf RIGHTLH + 1,W ; right >= left high? turn right : turn left
btfss STATUS,C ; if carry set, right is higher
goto turnLeft
goto turnRight
return ; safety return

;******************************************************************************
; Move Code
;******************************************************************************
; This code controls the motors: straight, left turn, right turn, and turnaround

allStop
; stop all motors and figure things out
; debug code: clear all motor leds
movlw 0x00
movwf MOTORPORT
return

moveAhead
; all clear ahead. Move forward
movlw 0x00 ^ ( ( 1 << MOTORLEFTONE ) + ( 1 << MOTORRIGHTONE ) )
movwf MOTORPORT
return

turnLeft
; turn left ~45 degrees?
movlw 0x00 ^ ( ( 1 << MOTORLEFTTWO ) + ( 1 << MOTORRIGHTONE ) )
movwf MOTORPORT
call motorDelay
clrf PORTB
return

turnRight
; turn right ~45 degrees?
movlw 0x00 ^ ( ( 1 << MOTORLEFTONE ) + ( 1 << MOTORRIGHTTWO ) )
movwf MOTORPORT
call motorDelay
clrf PORTB
return

uturn
; turn left or right ~180 degrees. direction alternates on each call
btfss UTURNLEFT
goto uRight

uLeft
bcf UTURNLEFT ; togggle for different uturn next time
call turnLeft
call turnLeft
return

uRight
bsf UTURNLEFT ; togggle for different uturn next time
call turnRight
call turnRight
return

;******************************************************************************
; Delay Code
;******************************************************************************

delay750us
movlw 148 ; 750us less the 5us for set up, above
movwf DELAY
decf DELAY,F
nop
btfss STATUS,Z
goto $ - 3
nop
return

oneSecDelay
movlw 0x3 ; 0x3 is about 1 sec at 4 MHz
movwf DELAY + 2
outerLoop ; (254*5+8) * 255 + 2 = 325892 instructions
movlw 0xff ; at 4MHz is about 1/3 of a second, .065 sec at 20MHz
movwf DELAY
movlw 0xff
movwf DELAY + 1
loop
decf DELAY, F
btfsc STATUS, Z
decfsz DELAY + 1, F
goto loop
decfsz DELAY + 2, F
goto outerLoop
return

songDelay
movlw 0xff ; at 4MHz is about 1/3 of a second, .065 sec at 20MHz
movwf DELAY
movlw 0xB0
movwf DELAY + 1
thirdLoop
decf DELAY, F
btfsc STATUS, Z
decfsz DELAY + 1, F
goto thirdLoop
nop
nop
return

motorDelay ; roughly 1/10 of a sec at 4 MHz
movlw 0xC8
movwf DELAY
movlw 0x0A
movwf DELAY + 1
tenthLoop
decf DELAY, F
btfsc STATUS, Z
decfsz DELAY + 1, F
goto tenthLoop
return

;******************************************************************************
; Mainline Code
;******************************************************************************

main
call init
bank1
; PORTB is for the motors: all output
movlw 0x00
movwf TRISB ^ 0x080 ; enable all PORTB for output
; eyes and neck. All intially output
movlw 0x00
movwf TRISA ^ 0x080 ; enable all PORTA for output
bank0
bcf EYES ; assert low on EYES so no accidental pulse goes out
clrf MOTORPORT ; turn off motors
explore
call lookAhead
btfsc TOOCLOSE
call determineNewDirection ; we got too close, turn to new direction
call lookAhead
call moveAhead ; move forward
goto explore

END

Robot Version 2.2 - Working

    Kids and wife are away this week.  I had a lot of quiet time to play with the robot.  I've decided to name it KiBHuJu ( Kit Bashed Hunk of Junk ).  I added the ( duh ) line of code to stop the motor as soon as the turn delay was finished.  That helped the doughnuts a bit.  Then, I pulled out one of the AA batteries and replaced it with a piece of antenna as a bridge.  Now, with the 3V motors, it moves a lot slower. 
    I went to my sister's for dinner tonight.  My nephew just finished robot camp.  They create robots with the Lego system ( Mindstorm ? Something like that ).  I decided to bring my robot along and show him.  My niece loved it.  She played with it for a good part of the evening.  And then. . . the same stupid contact to the motor sheared off.  I now have one working 6V motor and one 3V. 
    When I got home, I decided to rip the broken 3V motor apart.  The brush for the motor is just a flat piece of copper that nests in a piece of plastic.  I pulled both out.  I stripped and flattened some solid 22 gauge wire and wedged it in the plastic.  Now, my brushes are one with the lead wires.  I did the same with the 6V motor.  Now, all my motors are working again.  I just need to decide if I want to proactively redo the others.  I will probably wait till they break.  At least I now know how to fix them.
    My new board came out great.  Now, instead of a jumble of soldered, one use mess under the board, I have a plugged in mess on top.  But I can use the board for lots of other projects.  I can also put in a header that will allow me to connect other boards to it. 
    My next goal is to put in a header for ICSP.  I'm going back to the bootloader.  I think I will essentially use the tinyBld program I found.  The actual PC code is for Windoze, though.  I'll write my own Python version.  I'll just change the bootloader code enough to work with the Python program.  The problem is that the bootloader code wants to use port B pins 2 and five for the built in USART module.  I may rewrite it ( making it longer ) to use the port A pins.  I'll just use bit banging routines instead of the built in USART.  I don't care if it is longer than 100 lines.  I have yet to come near the max program RAM. 
    Still working on the CAD.  Eagle is a great tool.  I just haven't put the time in.  Tried to get my phone to connect to my laptop.  No dice.  I've got a Razr.  For Linux, moto4lin is the way to go.  My version, V3re, is not supported.  I'd like to be able to take some pictures and post them.  Maybe when my wife gets back, I'll use the camera.  All good. 

Sunday, July 11, 2010

Robot Working... Sort of...

    Well, I blew a lot of time soldering my proto board this weekend.  I learned a lot.  First of all, I really stink at soldering!  I spent some time reviewing my board.  I now have a design for a proto board that will reduce the amount of jumpers I need to solder.  I'm going to buy one of the Radio Shack boards that have some holes connected.  I ordered a slew of female header pins, some chip sockets, etc from Sparkfun.  The result should be a board with two 18 pin sockets, a piezo speaker, a 5 V regulator, and some additional headers.  I'll have 2 2-pin header with 0.1 uF caps for the motor drivers to cancel noise.  I'll have 2 3-pin headers for signal positive and ground.  This board should be able to be reused for multiple projects.  The idea is to have everything connected to female jumpers.  That way, I can just stick wires in where I need them.  Any project specific parts can be either soldered in or I'll add jumpers.  The other nice thing about female jumpers is that I reduce the risk of frying parts.
    I ran into quite a few challenges this weekend.  Other than soldering wires to the wrong spots, I also had a few mechanical failures.  I ordered 2 geared motors from Solarbotics.  Right after I soldered 22 gauge solid wires to them, one of the leads snapped.  It sheared so close to the join that there was no hope of a fix.  Aaaagh!  I dropped back and punted.  I pulled the motors out and replaced them with the only spares I had: two 3 Volt motors with gear teeth from Radio Shack.  When I tested them on my breadboard, they wreaked havoc.  They put a ton of noise on the line.  I put 0.1 uF caps across their positive and negative leads to cancel the noise. 
    Once I got the rest of the problems worked out, I hooked it all up and went for a test ride.  The code worked great.  The only problem was the switch from 6 Volt motors to 3 Volt.  When the robot decided to turn, it peeled out, doing doughnuts!  I need to gear the turn delay time Waaaaaaaaay down. 
    I see that I can upload images here.  I'll take images of the final code when I get it and upload that.  Once I build my good proto board, I'll upload images of that, too.  I downloaded a freeware version of Eagle CAD.  I think I should be able to use that to generate a good schematic.  I am just getting familiar with it.  TAFN!