I've been pretty busy with work. Had to write some really complex business logic **** Had to remove this rant. Details of work are confidential ... But enough of work.
I put down the chips for far too long. Been working nights or not on the comp at all. Now, I'd like to get back to it. I got sidetracked with the whole SD card read/write thing. Wound up doing a lot of chip math to calculate where to put my bytes or find them again. There are lots of good FAT libraries out there. I, of course, wanted to do the whole thing myself in asm. *martyr* The idea was to have the pic write to a file that would have a directory entry, fat entry, etc., so a computer could mount the SD card and see the file. That has gotten a little too tedious for me. After all, this is just a hobby. If it ain't fun... well... I'm a programmer, I can't give up. I'll just put it aside and do the next self-imposed challenge.
The next self-imposed challenge is actually something I started and got side-tracked: the bootloader. I'm going to gear myself back into that. It would be cool to be able to reprogram my chip without taking it out of the board.
Search This Blog
Friday, May 21, 2010
Wednesday, March 24, 2010
SD Card Working Notes
EDIT: I've seen a lot of people hit this post. Ignore it. It is rubish and random ramblings. See this post for stuff that works: http://thelast16bits.blogspot.com/2011/11/micro-sd-card-low-level-io.html
This is an incoherent blog to capture some notes in progress. I'm trying to figure out how to connect a PIC to an Sd card ( MMC, SD or high capacity SD ). I'll be working with a 2GB micro SD card. Just in case of hard drive crash, here are my notes so far:
What we have so far:
1. polynomial for crc7 is 10001001
2. The only commands for which we need a valid crc7 are cmd0 and cmd8.
Spi mode ignores crc for all others unless you are stupid enough to enable it.
3. If you make the check byte 0x22 for cmd8, you will have the same valid crc as for cmd0,
value = 0x95
4. TODO - It seems that acmd command value should be 0x40 + 41 = 0x69, but
one website reports that the value should be 0xc0 + 41 = 0xE9. The problem
is that this would require setting the start bit to one. Not sure this is right.
5. Need to adjust code to take in 5 bytes for response R7 from cmd8
6. Need to figure out if CS is high or low when pulsing for response. The web seems to say
that that you let it go high to read.
7. Need to figure out if we need the dummy byte that ELF talks about.
8. Adjust messages to post to PC instead of LCD for now. I need all the data I can read!
9. cmd55 precedes all acmd41 calls and gets R1 back. hex = 0x77 bin = 0b01110111
0x95 = 10010101
cmd0 = 01000000
cmd8 = 01001000
cmd55=
acmd41= 01101001
CMD0 01000000 00000000 00000000 00000000 00000000 10010101
CMD8 01001000 00000000 00000000 00000001 00100010 10010101
CMD55 01110111 00000000 00000000 00000000 00000000 10010101
ACMD41 01101001 01000000 00000000 00000000 00000000 10010101
GOT IT!
0X22 IS THE CHECK VALUE THAT WILL MAKE CMD8'S CRC 0X95!!!
cmd8 = 0b01001000
twenty = 00000000000000000000
voltage = 0001 ( for 2.5 - 3.3 Volts )
check = 00100010 ( 0x22 )
0x4800000122 binary = 0b100100000000000000000000000000100100010
cmd 55 = 0x77
acmd41 = 0x69
; polynomial for crc7 = 10001001
; crc for cmd8 should be: 11010101 ( 0xD5 ) when voltage set to 0b0001 for 2.5 - 3.3 V
; crc for cmd8 could be: 11000011 ( 0xC3 ) with no voltage specified
; need crc for cmd8 with check value of 0x1AA...
; NOT RIGHT -- message = 4800000001aa crc = 0x95 10010101 - so that's why they use AA...
; found this for command byte of ACMD41: The ACMD41 is not 0x40 + 41 but 0xc0 + 41
; So, 0xc0 + 41 = 0xE9... maybe...
; crc for cmd0 is: 10010101 ( 0x95 ) this is per the physical spec
; cmd55 is 0bb1110111
; acmd41 is 0b11101001 - The one in bit eight ( 'sposed to always be zero ) bothers me.
This is an incoherent blog to capture some notes in progress. I'm trying to figure out how to connect a PIC to an Sd card ( MMC, SD or high capacity SD ). I'll be working with a 2GB micro SD card. Just in case of hard drive crash, here are my notes so far:
What we have so far:
1. polynomial for crc7 is 10001001
2. The only commands for which we need a valid crc7 are cmd0 and cmd8.
Spi mode ignores crc for all others unless you are stupid enough to enable it.
3. If you make the check byte 0x22 for cmd8, you will have the same valid crc as for cmd0,
value = 0x95
4. TODO - It seems that acmd command value should be 0x40 + 41 = 0x69, but
one website reports that the value should be 0xc0 + 41 = 0xE9. The problem
is that this would require setting the start bit to one. Not sure this is right.
5. Need to adjust code to take in 5 bytes for response R7 from cmd8
6. Need to figure out if CS is high or low when pulsing for response. The web seems to say
that that you let it go high to read.
7. Need to figure out if we need the dummy byte that ELF talks about.
8. Adjust messages to post to PC instead of LCD for now. I need all the data I can read!
9. cmd55 precedes all acmd41 calls and gets R1 back. hex = 0x77 bin = 0b01110111
0x95 = 10010101
cmd0 = 01000000
cmd8 = 01001000
cmd55=
acmd41= 01101001
CMD0 01000000 00000000 00000000 00000000 00000000 10010101
CMD8 01001000 00000000 00000000 00000001 00100010 10010101
CMD55 01110111 00000000 00000000 00000000 00000000 10010101
ACMD41 01101001 01000000 00000000 00000000 00000000 10010101
GOT IT!
0X22 IS THE CHECK VALUE THAT WILL MAKE CMD8'S CRC 0X95!!!
cmd8 = 0b01001000
twenty = 00000000000000000000
voltage = 0001 ( for 2.5 - 3.3 Volts )
check = 00100010 ( 0x22 )
0x4800000122 binary = 0b100100000000000000000000000000100100010
cmd 55 = 0x77
acmd41 = 0x69
; polynomial for crc7 = 10001001
; crc for cmd8 should be: 11010101 ( 0xD5 ) when voltage set to 0b0001 for 2.5 - 3.3 V
; crc for cmd8 could be: 11000011 ( 0xC3 ) with no voltage specified
; need crc for cmd8 with check value of 0x1AA...
; NOT RIGHT -- message = 4800000001aa crc = 0x95 10010101 - so that's why they use AA...
; found this for command byte of ACMD41: The ACMD41 is not 0x40 + 41 but 0xc0 + 41
; So, 0xc0 + 41 = 0xE9... maybe...
; crc for cmd0 is: 10010101 ( 0x95 ) this is per the physical spec
; cmd55 is 0bb1110111
; acmd41 is 0b11101001 - The one in bit eight ( 'sposed to always be zero ) bothers me.
Sunday, March 14, 2010
Serial to Parallel Interface for Hitachi LCD
Success! I actually had this working for quite a while and didn't realize it. I rewrote the code entirely to run on a 16f84a with bit banging. I was trying to send data from the PC to the pic, using Python and a library called uspp. I was iterating through a string ("Hello, world!", of course ) and sending one character at a time. The first one or two characters came through fine, but the rest was garbage. If I introduced a > 1 us delay between characters, it also worked. I eventually want to use this library to write the PC bootloader program, and the extra 1us per character would slow things down ridiculously.
I tried today to send characters raw through bash, using stty -F /dev/ttyS0 ispeed 4800 ospeed 4800 cs8. I then echoed: echo 'Hello, world!' > /dev/ttyS0. It sent beautifully. I went back to the Python code and saw that the uspp function write(s) could take a full string. So I sent:
s = SerialPort("/dev/ttyS0", None, 4800)
s.flush()
str = 'Hello, world!' + chr(0b10000000) + chr(0b11000000) + 'From: Tom'
s.write( str )
And it worked perfectly. I just need to remember to put a 1us delay between full string sends. A note on the above. The pic code listens for 0b10000000. This signals that the next character will be an instruction to the LCD ( like clear screen or next line). The 0b11000000 instruction moves the cursor to the second line. The datasheet for your LCD will show the commands. Mine is: GDM1602K
Here is the pic assembly code. It needs clean up. I want to move all the delays into the macro at least.
;******************************************************************************
; *
; Filename: lcd2.asm *
; Date: 2010.03.13 *
; File Version: 1.0.0 *
; *
; Author: Tom Hunt *
; *
;******************************************************************************
; NOTES: Implements lcd serial interface to Hitachi style lcd *
; pic 16f84a running at 20MHz *
; *
;******************************************************************************
; CHANGE LOG: *
; *
; *
;******************************************************************************
list R=DEC, p=16f84a ; list directive to define processor
#include ; processor specific variable definitions
__CONFIG _CP_OFF & _WDT_OFF & _PWRTE_ON & _HS_OSC
;******************************************************************************
; CONSTANT & VARIABLE DEFINITIONS *
;******************************************************************************
#define RS232 PORTA
#define TX 3
#define RX 4
#define Cntrl PORTA
#define RS 0
#define RW 1
#define E 2
#define Data PORTB
#define Instruction b'10000000' ; will send to indicate an instruction
#define isInstruction 0
CBLOCK 0x0C
Byte ; holds the byte for rs232 and lcd
Count ; holds the counter for start, stop and data bits ( 10 )
Dlay:2 ; 16 bits for long delays
Flags
w_temp
status_temp
ENDC
;******************************************************************************
; MACROS *
;******************************************************************************
; courtesy of Myke Predko - delays from 4 to 771
Delay Macro Value ; Figure Out Delay Values
variable Cnt, Remainder
Cnt = (Value - 4) / 3
Remainder = Value - ((Cnt * 3) + 4)
movlw Cnt + 1
movwf Dlay
decfsz Dlay, f
goto $ - 1
if (Remainder == 2)
goto $ + 1
endif
if (Remainder == 1)
nop
endif
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 ; save off current W register contents
movf STATUS,w ; move status register into W register
movwf status_temp ; save off contents of STATUS register
; TODO: Place ISR (Interrupt Service Routine) here...
movf status_temp,w ; retrieve copy of STATUS register
movwf STATUS ; restore pre-isr STATUS register contents
swapf w_temp,f
swapf w_temp,w ; restore pre-isr W register contents
retfie ; return from interrupt
;************************************************************************************************
; *
; INITIALIZATION CODE *
; *
;************************************************************************************************
initLcd
bcf Flags,isInstruction
call dlay5ms ; wait 15ms+ to start
call dlay5ms
call dlay5ms
call dlay5ms
; This sequence loads values into the D0-7, RS, RW pins and strobes E
bcf Cntrl,RS
bcf Cntrl,RW
bcf Cntrl,E
;call dlay15us ; don't think I need this...
movlw b'00110000' ; first control command to lcd
movwf Data
bsf Cntrl,E
call dlay15us
bcf Cntrl,E ; strobe it into lcd
call dlay5ms ; a wait period is needed. For this, we need 5ms
bsf Cntrl,E
call dlay15us
bcf Cntrl,E ; strobe lcd again with same function set
call dlay150us ; a wait period of +100us is needed
bsf Cntrl,E
call dlay15us
bcf Cntrl,E ; strobe lcd 3rd time with same function set
call dlay5ms
movlw b'00111000' ; 0x38 function set for 2 lines, small font
movwf Data
bsf Cntrl,E
call dlay15us
bcf Cntrl,E
call dlay5ms
; display off
movlw b'00001000' ; 0x08
movwf Data
bsf Cntrl,E
call dlay15us
bcf Cntrl,E
call dlay5ms
; clear display
movlw b'00000001' ; 0x01
movwf Data
bsf Cntrl,E
call dlay15us
bcf Cntrl,E
call dlay5ms
; entry set incr data and no shift
movlw b'00000110'
movwf Data
bsf Cntrl,E
call dlay15us
bcf Cntrl,E
call checkLcdBusy ; now we can poll the busy flag
; turn on display
movlw b'00001110' ; 0xE - display and cursor on, blink off
movwf Data
bsf Cntrl,E
call dlay15us
bcf Cntrl,E
call checkLcdBusy
return
initInput ; count with return = 5
movlw 0xFF
movwf TRISB ^ 0x080
clrf Data ; clear Data port for clean read
return
initOutput ; count = 10
clrf RS232
clrf Data ; clear the lcd data byte/port bits
bsf STATUS, RP0 ; Select Bank 1
movlw 0x00 ^ ( 1 << RX ) ; make all portA output except RX
movwf TRISA^0x080
movlw 0x00
movwf TRISB^0x080 ; PORTB starts as output for lcd data
bcf STATUS, RP0 ; Select Bank 0
return
;************************************************************************************************
;* *
;* LCD ROUTINES *
;* *
;************************************************************************************************
processByte ; takes ~204 if no busy and writing to lcd
btfss Flags,isInstruction ; this is an instruction
goto notInstr ; not an instruction, continue processing
call writeByte ; process instruction
return
notInstr
movf Byte,W
xorlw Instruction
btfss STATUS,Z ; see if we want an instruction
goto regularByte
bsf Flags,isInstruction ; next will be instruction. set flag and return
return
regularByte
call writeByte ; write the byte to lcd
return ; count with return = 204
writeByte ; count = 192 = 104 + 75 + 13
call checkLcdBusy ; total = 104 + 2 for call
bcf Cntrl,RW ; write operation
bsf Cntrl,RS ; default to writing data
btfsc Flags,isInstruction ; are we expecting an instruction?
bcf Cntrl,RS ; then set to write instruction
movf Byte,W
movwf Data
bsf Cntrl,E ; strobe the byte into the lcd
call dlay15us ; total = 75 with call
bcf Cntrl,E
bcf Flags,isInstruction ; clear in case it was an instruction
return
checkLcdBusy ; total with no busy lcd = 104
call initInput ; takes 2 for call and 5 for method = 7
bsf Cntrl,RW ; set for read ( input = 1 )
bcf Cntrl,RS
bsf Cntrl,E ; strobe on
call dlay15us ; total 75 instrs. with call
movf Data,W ; read the contents of the lcd
bcf Cntrl,E ; stobe off
;goto $ + 1 ; debug goto. delete for app
btfsc Data,7 ; test if D7 is busy
goto $ - 4 ; wait for it!
bcf Cntrl,RW ; reset back to output mode
call initOutput ; 2 for call + 10 for method = 12
return
;************************************************************************************************
;* *
;* RS232 ROUTINES *
;* *
;************************************************************************************************
receiveByte
Delay 521 - 3 ; wait 1/2 bit period and test again
btfsc RS232,RX
return ; false read, return and block again
movlw 0x08
movwf Count ; set up to read 8 bits, LSB first
goto $ + 1
receiveBit
Delay 521 - 7 ; 2 half bit delays put us squarely in the middle of the bit send
Delay 521 ;
bcf STATUS,C ; clear the carry bit
btfsc RS232,RX
bsf STATUS,C ; set carry bit to be shifted into received byte var
rrf Byte,F ; shift the bit into Byte ( we get LSb first )
decfsz Count,F
goto receiveBit
Delay 600 ; should put us in the stop bit with 80 instr for error margin
btfsc RS232,F ; check stop bit, don't write if invalid
return
call processByte ; takes ~204 if no blocking on lcdBusy
return ; as soon as possible and block for next byte
;************************************************************************************************
;* *
;* MAINLINE CODE *
;* *
;************************************************************************************************
main
call initOutput
call initLcd
;movlw 'K'
;movwf Byte
;call processByte
processLoop ; btfss Flags,reading
btfss RS232,RX ; block until start bit received
call receiveByte
goto processLoop
;******************************************************************************
; Debugging Routines *
;******************************************************************************
debugging ; call to blink an led on A4 where needed for debug
bsf PORTA,3
movlw d'100' ; c. half second on
movwf Count
call dlay5ms
decfsz Count,F
goto $ - 2
bcf PORTA,3
movlw d'100' ; c. half second off
movwf Count
call dlay5ms
decfsz Count,F
goto $ - 2
return
;************************************************************************************************
;* *
;* DELAY ROUTINES *
;* *
;************************************************************************************************
dlay5ms
movlw 0x19 ; 25 * 1005 + 2 = 25127 ( .0050254 secs at 20 MHz )
movwf Dlay + 1
outer5ms
movlw 0xFA ; 250 decimal
movwf Dlay
inner5ms ; 250 * 5 instructions = 1000
nop
decfsz Dlay,F
goto inner5ms
nop
decfsz Dlay + 1,F
goto outer5ms
nop
return
dlay150us
movlw 250 ; 250 * 3 instrs = 750. 750 / 5e6 = .0015
movwf Dlay
decfsz Dlay,F
goto $ - 1
return
dlay15us ; 15us ( 6 + 23*3 = 75 ) worked
movlw 23
movwf Dlay
decfsz Dlay,F
goto $ - 1
nop
return
END ; directive 'end of program'
Use at your own risk... Enjoy!
As you can see from the defines, this is for a 16f84a, running at 20 MHz. PortB pins are the 8 data line pins ( makes shifting the byte in easier ). PortA,0 is RS, PortA,1 is RW, and PortA,2 is the E for the LCD. PortA,4 is the RS232 receive pin. TX is not used. I actually used this pin for debugging. If you want to use this at different baud rates or different clock speed, you will have to adjust the timings. With slower crystals, beware! Writing to the LCD takes at least 204 instructions. This might be less at 4 MHz. With 20MHz, I had to add in a 15us delay in the strobe of the E pin. I didn't need this at 4 MHz. Removing the delay will save you 75 instructions. In writeByte, this will save you 150 instructions( called for checkLcdBusy and in the write strobe ).
I tried today to send characters raw through bash, using stty -F /dev/ttyS0 ispeed 4800 ospeed 4800 cs8. I then echoed: echo 'Hello, world!' > /dev/ttyS0. It sent beautifully. I went back to the Python code and saw that the uspp function write(s) could take a full string. So I sent:
s = SerialPort("/dev/ttyS0", None, 4800)
s.flush()
str = 'Hello, world!' + chr(0b10000000) + chr(0b11000000) + 'From: Tom'
s.write( str )
And it worked perfectly. I just need to remember to put a 1us delay between full string sends. A note on the above. The pic code listens for 0b10000000. This signals that the next character will be an instruction to the LCD ( like clear screen or next line). The 0b11000000 instruction moves the cursor to the second line. The datasheet for your LCD will show the commands. Mine is: GDM1602K
Here is the pic assembly code. It needs clean up. I want to move all the delays into the macro at least.
;******************************************************************************
; *
; Filename: lcd2.asm *
; Date: 2010.03.13 *
; File Version: 1.0.0 *
; *
; Author: Tom Hunt *
; *
;******************************************************************************
; NOTES: Implements lcd serial interface to Hitachi style lcd *
; pic 16f84a running at 20MHz *
; *
;******************************************************************************
; CHANGE LOG: *
; *
; *
;******************************************************************************
list R=DEC, p=16f84a ; list directive to define processor
#include
__CONFIG _CP_OFF & _WDT_OFF & _PWRTE_ON & _HS_OSC
;******************************************************************************
; CONSTANT & VARIABLE DEFINITIONS *
;******************************************************************************
#define RS232 PORTA
#define TX 3
#define RX 4
#define Cntrl PORTA
#define RS 0
#define RW 1
#define E 2
#define Data PORTB
#define Instruction b'10000000' ; will send to indicate an instruction
#define isInstruction 0
CBLOCK 0x0C
Byte ; holds the byte for rs232 and lcd
Count ; holds the counter for start, stop and data bits ( 10 )
Dlay:2 ; 16 bits for long delays
Flags
w_temp
status_temp
ENDC
;******************************************************************************
; MACROS *
;******************************************************************************
; courtesy of Myke Predko - delays from 4 to 771
Delay Macro Value ; Figure Out Delay Values
variable Cnt, Remainder
Cnt = (Value - 4) / 3
Remainder = Value - ((Cnt * 3) + 4)
movlw Cnt + 1
movwf Dlay
decfsz Dlay, f
goto $ - 1
if (Remainder == 2)
goto $ + 1
endif
if (Remainder == 1)
nop
endif
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 ; save off current W register contents
movf STATUS,w ; move status register into W register
movwf status_temp ; save off contents of STATUS register
; TODO: Place ISR (Interrupt Service Routine) here...
movf status_temp,w ; retrieve copy of STATUS register
movwf STATUS ; restore pre-isr STATUS register contents
swapf w_temp,f
swapf w_temp,w ; restore pre-isr W register contents
retfie ; return from interrupt
;************************************************************************************************
; *
; INITIALIZATION CODE *
; *
;************************************************************************************************
initLcd
bcf Flags,isInstruction
call dlay5ms ; wait 15ms+ to start
call dlay5ms
call dlay5ms
call dlay5ms
; This sequence loads values into the D0-7, RS, RW pins and strobes E
bcf Cntrl,RS
bcf Cntrl,RW
bcf Cntrl,E
;call dlay15us ; don't think I need this...
movlw b'00110000' ; first control command to lcd
movwf Data
bsf Cntrl,E
call dlay15us
bcf Cntrl,E ; strobe it into lcd
call dlay5ms ; a wait period is needed. For this, we need 5ms
bsf Cntrl,E
call dlay15us
bcf Cntrl,E ; strobe lcd again with same function set
call dlay150us ; a wait period of +100us is needed
bsf Cntrl,E
call dlay15us
bcf Cntrl,E ; strobe lcd 3rd time with same function set
call dlay5ms
movlw b'00111000' ; 0x38 function set for 2 lines, small font
movwf Data
bsf Cntrl,E
call dlay15us
bcf Cntrl,E
call dlay5ms
; display off
movlw b'00001000' ; 0x08
movwf Data
bsf Cntrl,E
call dlay15us
bcf Cntrl,E
call dlay5ms
; clear display
movlw b'00000001' ; 0x01
movwf Data
bsf Cntrl,E
call dlay15us
bcf Cntrl,E
call dlay5ms
; entry set incr data and no shift
movlw b'00000110'
movwf Data
bsf Cntrl,E
call dlay15us
bcf Cntrl,E
call checkLcdBusy ; now we can poll the busy flag
; turn on display
movlw b'00001110' ; 0xE - display and cursor on, blink off
movwf Data
bsf Cntrl,E
call dlay15us
bcf Cntrl,E
call checkLcdBusy
return
initInput ; count with return = 5
movlw 0xFF
movwf TRISB ^ 0x080
clrf Data ; clear Data port for clean read
return
initOutput ; count = 10
clrf RS232
clrf Data ; clear the lcd data byte/port bits
bsf STATUS, RP0 ; Select Bank 1
movlw 0x00 ^ ( 1 << RX ) ; make all portA output except RX
movwf TRISA^0x080
movlw 0x00
movwf TRISB^0x080 ; PORTB starts as output for lcd data
bcf STATUS, RP0 ; Select Bank 0
return
;************************************************************************************************
;* *
;* LCD ROUTINES *
;* *
;************************************************************************************************
processByte ; takes ~204 if no busy and writing to lcd
btfss Flags,isInstruction ; this is an instruction
goto notInstr ; not an instruction, continue processing
call writeByte ; process instruction
return
notInstr
movf Byte,W
xorlw Instruction
btfss STATUS,Z ; see if we want an instruction
goto regularByte
bsf Flags,isInstruction ; next will be instruction. set flag and return
return
regularByte
call writeByte ; write the byte to lcd
return ; count with return = 204
writeByte ; count = 192 = 104 + 75 + 13
call checkLcdBusy ; total = 104 + 2 for call
bcf Cntrl,RW ; write operation
bsf Cntrl,RS ; default to writing data
btfsc Flags,isInstruction ; are we expecting an instruction?
bcf Cntrl,RS ; then set to write instruction
movf Byte,W
movwf Data
bsf Cntrl,E ; strobe the byte into the lcd
call dlay15us ; total = 75 with call
bcf Cntrl,E
bcf Flags,isInstruction ; clear in case it was an instruction
return
checkLcdBusy ; total with no busy lcd = 104
call initInput ; takes 2 for call and 5 for method = 7
bsf Cntrl,RW ; set for read ( input = 1 )
bcf Cntrl,RS
bsf Cntrl,E ; strobe on
call dlay15us ; total 75 instrs. with call
movf Data,W ; read the contents of the lcd
bcf Cntrl,E ; stobe off
;goto $ + 1 ; debug goto. delete for app
btfsc Data,7 ; test if D7 is busy
goto $ - 4 ; wait for it!
bcf Cntrl,RW ; reset back to output mode
call initOutput ; 2 for call + 10 for method = 12
return
;************************************************************************************************
;* *
;* RS232 ROUTINES *
;* *
;************************************************************************************************
receiveByte
Delay 521 - 3 ; wait 1/2 bit period and test again
btfsc RS232,RX
return ; false read, return and block again
movlw 0x08
movwf Count ; set up to read 8 bits, LSB first
goto $ + 1
receiveBit
Delay 521 - 7 ; 2 half bit delays put us squarely in the middle of the bit send
Delay 521 ;
bcf STATUS,C ; clear the carry bit
btfsc RS232,RX
bsf STATUS,C ; set carry bit to be shifted into received byte var
rrf Byte,F ; shift the bit into Byte ( we get LSb first )
decfsz Count,F
goto receiveBit
Delay 600 ; should put us in the stop bit with 80 instr for error margin
btfsc RS232,F ; check stop bit, don't write if invalid
return
call processByte ; takes ~204 if no blocking on lcdBusy
return ; as soon as possible and block for next byte
;************************************************************************************************
;* *
;* MAINLINE CODE *
;* *
;************************************************************************************************
main
call initOutput
call initLcd
;movlw 'K'
;movwf Byte
;call processByte
processLoop ; btfss Flags,reading
btfss RS232,RX ; block until start bit received
call receiveByte
goto processLoop
;******************************************************************************
; Debugging Routines *
;******************************************************************************
debugging ; call to blink an led on A4 where needed for debug
bsf PORTA,3
movlw d'100' ; c. half second on
movwf Count
call dlay5ms
decfsz Count,F
goto $ - 2
bcf PORTA,3
movlw d'100' ; c. half second off
movwf Count
call dlay5ms
decfsz Count,F
goto $ - 2
return
;************************************************************************************************
;* *
;* DELAY ROUTINES *
;* *
;************************************************************************************************
dlay5ms
movlw 0x19 ; 25 * 1005 + 2 = 25127 ( .0050254 secs at 20 MHz )
movwf Dlay + 1
outer5ms
movlw 0xFA ; 250 decimal
movwf Dlay
inner5ms ; 250 * 5 instructions = 1000
nop
decfsz Dlay,F
goto inner5ms
nop
decfsz Dlay + 1,F
goto outer5ms
nop
return
dlay150us
movlw 250 ; 250 * 3 instrs = 750. 750 / 5e6 = .0015
movwf Dlay
decfsz Dlay,F
goto $ - 1
return
dlay15us ; 15us ( 6 + 23*3 = 75 ) worked
movlw 23
movwf Dlay
decfsz Dlay,F
goto $ - 1
nop
return
END ; directive 'end of program'
Use at your own risk... Enjoy!
As you can see from the defines, this is for a 16f84a, running at 20 MHz. PortB pins are the 8 data line pins ( makes shifting the byte in easier ). PortA,0 is RS, PortA,1 is RW, and PortA,2 is the E for the LCD. PortA,4 is the RS232 receive pin. TX is not used. I actually used this pin for debugging. If you want to use this at different baud rates or different clock speed, you will have to adjust the timings. With slower crystals, beware! Writing to the LCD takes at least 204 instructions. This might be less at 4 MHz. With 20MHz, I had to add in a 15us delay in the strobe of the E pin. I didn't need this at 4 MHz. Removing the delay will save you 75 instructions. In writeByte, this will save you 150 instructions( called for checkLcdBusy and in the write strobe ).
Saturday, February 20, 2010
AUSART and Max232
Having another nice Saturday. I've been trying to get the built-in AUSART circuit working. Had an odd bug with the RS232 level converter chip. The one I'm using is from Sipex ( courtesy of sparkfun.com ). The docs for all these converters want 4-5 0.1uF caps. When I hook up the caps on V- and V+, it was causing my pic to reset over and over. What I would see was something like:
( trying to send "Hello, world!" to PC ) HHHHHHHHHHHHHHHHHHHHHHHHHeHHHHHHellHHHHHello, world!HHHHHHHHHHHHH
I got this to work in a couple of ways. The first way was to disconnect the caps to V- and V+. I'm really not enough of an electrical engineer type to know if this is harmful. I decided to go Luddite. I found a great diagram of a fast RTL inverter on Free Circuit Diagrams. They work great. Sparkfun has some variations on this. They sell a serial cable with this kind of inverter built in. They add a couple of capacitors to ground, though. This setup produces voltages between 0 and ~5V. This is enough on my PC to trigger the rs232 correctly. It may not work on all PCs, though. I think the threshold is supposed to be between around +/- 3.8 V.
I can now send RS232 chars to my PC and output the same on the LCD. Now I need to create a buffer and receive chars, print to LCD, and confirm each line from a hex file. Not sure if I'll actually do an MD5 checksum calculation on each line or not. Need some way to check if I got what I should without sacrificing too much time. We'll see.
( trying to send "Hello, world!" to PC ) HHHHHHHHHHHHHHHHHHHHHHHHHeHHHHHHellHHHHHello, world!HHHHHHHHHHHHH
I got this to work in a couple of ways. The first way was to disconnect the caps to V- and V+. I'm really not enough of an electrical engineer type to know if this is harmful. I decided to go Luddite. I found a great diagram of a fast RTL inverter on Free Circuit Diagrams. They work great. Sparkfun has some variations on this. They sell a serial cable with this kind of inverter built in. They add a couple of capacitors to ground, though. This setup produces voltages between 0 and ~5V. This is enough on my PC to trigger the rs232 correctly. It may not work on all PCs, though. I think the threshold is supposed to be between around +/- 3.8 V.
I can now send RS232 chars to my PC and output the same on the LCD. Now I need to create a buffer and receive chars, print to LCD, and confirm each line from a hex file. Not sure if I'll actually do an MD5 checksum calculation on each line or not. Need some way to check if I got what I should without sacrificing too much time. We'll see.
Saturday, February 13, 2010
Bug Fix!!!!
Wow! What a day. I reverted back to my old standard, the pic 16f84a to get tmr0 working. Everything worked great after I downloaded the general manual on mid range pics and looked at the code samples for tmr0. I then tried the same stuff for my 16f88. Errors aplenty ensued.
Revelation: I am using a PicStart Plus programmer with the open source software picp on Linux. Turns out, there is a bit of a mistake with the definition file for the 16f88. First, get the latest firmware for PicStart Plus ( I don't know what's relevant for other programmers, sorry ). Then, for picp 0.6.8, you need to modify the picdevrc file ( mine is in /usr/local/bin ). The first part of the definition needs to be:
[16F88]
0 0 4 0 0 0 0 0
0 0 0 0 0 0 0 0
PICSTART WARP JUPIC OLIMEX
Notice the '4' on the 3rd number. This is the word alignment. Yours will read zero.
This undoes my previous posts about needing 4 lines after ORG 0x00. It also undoes all kinds of crazy bugs in your code. I feel like a new man!!! My code now runs as expected.
The only reservation I have is when I start to write my bootloader. What impact, if any, is there on my approach to write to the program space. More research is needed. For now, I'm just glad to know I'm not insane. I hope someone reads this and benefits.
This introduced only one oddity in my existing code. After LCD initilization, I found that I needed to write an set DDRAM address instruction to line one, position zero to get my text writing. I thought the initialization code did that by default. It had when my programmer was buggy. Weird.
Now to tackle AUSART again.... A whole new set of problems, I'm sure. For now, though: \/\/00+!
Revelation: I am using a PicStart Plus programmer with the open source software picp on Linux. Turns out, there is a bit of a mistake with the definition file for the 16f88. First, get the latest firmware for PicStart Plus ( I don't know what's relevant for other programmers, sorry ). Then, for picp 0.6.8, you need to modify the picdevrc file ( mine is in /usr/local/bin ). The first part of the definition needs to be:
[16F88]
0 0 4 0 0 0 0 0
0 0 0 0 0 0 0 0
PICSTART WARP JUPIC OLIMEX
Notice the '4' on the 3rd number. This is the word alignment. Yours will read zero.
This undoes my previous posts about needing 4 lines after ORG 0x00. It also undoes all kinds of crazy bugs in your code. I feel like a new man!!! My code now runs as expected.
The only reservation I have is when I start to write my bootloader. What impact, if any, is there on my approach to write to the program space. More research is needed. For now, I'm just glad to know I'm not insane. I hope someone reads this and benefits.
This introduced only one oddity in my existing code. After LCD initilization, I found that I needed to write an set DDRAM address instruction to line one, position zero to get my text writing. I thought the initialization code did that by default. It had when my programmer was buggy. Weird.
Now to tackle AUSART again.... A whole new set of problems, I'm sure. For now, though: \/\/00+!
Wednesday, February 10, 2010
Control Commands to LCD
Got a minute to play tonight. I added routines to allow the user to indicate that the next instruction will be a command rather than data. The user will send 0x80 ( unused by the character set ). This sets a flag Flag,isInstruction. The next instruction can be any command other than a read ( to read busy flag or DDRAM/CGRAM ). I don't really need that at this point.
I'm obviously still learning. There were a number of pesky bugs. I miss function scope and encapsulation!!!
I wired up the RS232 converter. Next, I'll get the pic to wait endlessly for a byte. When it receives one, it will process it and write to the LCD. Timing will be interesting. With the need to poll the busy flag of the LCD, I can't be certain how long an operation will take. I'm thinking that the read from USART needs to write to a buffer and signal the source if it gets full. I'll need some kind of ready to receive/buffer full protocol. My compiler tends to create lines of up to 8 instructions. Line by line might be the way to go. Not the fastest, but it is controllable.
Looks like another foot or two of snow. Work may even be cancelled. That will provide some time to do more tomorrow ( power lines permitting ). Cabin fever kids may be a challenge, too.
Phrase of the day: Silence is golden, duct tape is silver.
I'm obviously still learning. There were a number of pesky bugs. I miss function scope and encapsulation!!!
I wired up the RS232 converter. Next, I'll get the pic to wait endlessly for a byte. When it receives one, it will process it and write to the LCD. Timing will be interesting. With the need to poll the busy flag of the LCD, I can't be certain how long an operation will take. I'm thinking that the read from USART needs to write to a buffer and signal the source if it gets full. I'll need some kind of ready to receive/buffer full protocol. My compiler tends to create lines of up to 8 instructions. Line by line might be the way to go. Not the fastest, but it is controllable.
Looks like another foot or two of snow. Work may even be cancelled. That will provide some time to do more tomorrow ( power lines permitting ). Cabin fever kids may be a challenge, too.
Phrase of the day: Silence is golden, duct tape is silver.
Sunday, February 7, 2010
\/\/00+!!! LCD is Working!
Snowy here in Pennsyltucky. In between digging out the cars, driveway, mailbox, etc., I managed to get my 16f88 to write to the Hitachi LCD. I soldered a 16 pin female header to the LCD, mangled a 16 pin male header to fit the breadboard, and got the thing printing. On the hardware end ( not my strong suit ), I got the stupid contrast set. Pin 3 on the LCD lets you set the contrast. Had to do some Googling to find the circuit. I played around with the values and ended up with 2 1K resistors, one on vdd and one on vss to a 10K pot. This gave me a nice, crisp display. The LCD I got displays white text on black. Definitely need the backlight to see it. I was able to wire the contrast adjustment and the backlight all onto the same power as the pic and LCD.
Code bugs were aplenty on this one. Had cabin fever kids "helping" me every step of the way. Here is the basic code. When I figure out how to generate circuit diagrams, I'll post those, too. This code just reads a table and prints out "Hi Wiley and Maura" on the LCD. I'm already adding code to allow instructions as well as data. The screen allows 2 16 char lines to print in small font. A lot of the routines ( notably delays ) are not used. This is dev code, folks. Here it is:
;******************************************************************************
; *
; Filename: helloLcd.asm *
; Date: 2010.01.31 *
; File Version: 1.0.0 *
; *
; Author: Tom Hunt *
; *
;******************************************************************************
; NOTES: Get the parallel LCD working. Read a string of characters and *
; print them on the LCD. *
; Uses RA0-4, RB7, RA6-7 for the data lines ( RA5 is input only and *
; I want to use the built in AUSART on RB2&5. *
; *
;******************************************************************************
; CHANGE LOG: *
; *
; *
;******************************************************************************
list R=DEC, p=16f88 ; list directive to define processor
#include ; 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.
;******************************************************************************
; VARIABLE DEFINITIONS *
;******************************************************************************
#define Cntrl PORTB ; Control Register
#define RS 0 ; Register select on lcd 1=Data 0=Instruction
#define RW 1 ; Read/Write select 1=Read 0=Write
#define E 3 ; 1= starts data read/write operation
CBLOCK 0x20
Flags
Count
Byte ; holds the byte to output
i ; basic index counter for loop
tIndex ; table index temp storage
ENDC
CBLOCK 0x70 ; interupt context-saving vars here see datasheet chp 15
w_temp
status_temp
pclath_temp
ENDC
#define isInstruction 0
#define isLineTwo 1
;******************************************************************************
; 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
;******************************************************************************
; MAINLINE CODE *
;******************************************************************************
init
bank0
clrf INTCON ; For now, clear intcon, disables all interrupts. We'll change this and
; use the GIE and PEIE peripheral interupt to get writes from USART
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
clrf PIE1^0x080 ; clear peripheral interrupts for now.
clrf PIE2^0x080
bank0
return
initLcd
call dlay5ms ; wait 15ms+ to start
call dlay5ms
call dlay5ms
call dlay5ms
; This sequence loads values into the D0-7, RS, RW pins and strobes E
bcf Cntrl,RS
bcf Cntrl,RW
bsf PORTB,7
movlw b'00010000' ; value of data 'byte' is 00110000
movwf PORTA
bsf Cntrl,E
bcf Cntrl,E
call dlay5ms ; a wait period is needed. For this, we need 5ms
bsf Cntrl,E
bcf Cntrl,E ; strobe lcd again with same function set
call dlay150us ; a wait period of +100us is needed
bsf Cntrl,E
bcf Cntrl,E ; strobe lcd 3rd time with same function set
call dlay5ms
movlw b'00111000' ; 0x38 function set for 2 lines, small font
movwf PORTA
bsf PORTB,7
bsf Cntrl,E
bcf Cntrl,E
call dlay5ms
; display off
movlw b'00001000' ; 0x08
movwf PORTA
bcf PORTB,7
bsf Cntrl,E
bcf Cntrl,E
call dlay5ms
; clear display
movlw b'00000001' ; 0x01
movwf PORTA
bcf PORTB,7
bsf Cntrl,E
bcf Cntrl,E
call dlay5ms
; entry set incr data and no shift
movlw b'00000110'
movwf PORTA
bcf PORTB,7
bsf Cntrl,E
bcf Cntrl,E
call checkLcdBusy ; now we can poll the busy flag
; turn on display
movlw b'00001110' ; 0xE - display and cursor on, blink off
movwf PORTA
bcf PORTB,7
bsf Cntrl,E
bcf Cntrl,E
call checkLcdBusy
return
initInput
bank1
bsf TRISB^0x080,7
movlw 0xFF ; set PORTA to Input
movwf TRISA ^ 0x080
bank0
clrf PORTA ; clear PORTA for clean read
return
initOutput
bcf Cntrl,RS
bcf Cntrl,RW
bcf Cntrl,E
bcf PORTB,7
bcf PORTB,4
clrf PORTA ; clear all output pins
movlw b'01100100'
bank1
movwf TRISB ^0x080 ; enable B,7 & B0-1,3 for output B4 for debug
movlw b'00100000' ; set PORTA to Output except input only A,5
movwf TRISA ^ 0x080
bank0
return
dlay5ms
movlw b'00000100' ; set up tmr0 with prescaler 16 ( for 5ms delay )
bank1
movwf OPTION_REG^0x080
bank0
movlw 0x64 ; value 100 for tmr0. with prescaler 16 gives 5ms
movwf TMR0
movf TMR0, W
btfss STATUS, Z ; wait for 5ms timeout
goto $ - 2
return
dlay2ms
movlw b'00000010' ; set up tmr0 with prescaler 4 ( for 2ms delay )
bank1
movwf OPTION_REG^0x080
bank0
movlw 0x006 ; value 6 for tmr0. with prescaler 16 gives 5ms
movwf TMR0
movf TMR0, W
btfss STATUS, Z ; wait for 2ms timeout
goto $ - 2
return
dlay150us
movlw b'00000000' ; set prescaler to 1:1
bank1
movwf OPTION_REG^0x080
bank0
movlw 0xB5 ; value of 181 for tmr0 with prescaler 0 gives 150us
movwf TMR0
movf TMR0, W
btfss STATUS, Z
goto $ - 2
return
dlay50us
movlw b'00000000' ; set prescaler to 1:1
bank1
movwf OPTION_REG^0x080
bank0
movlw 0xE7 ; value of 231 for tmr0 with prescaler 0 gives 50us
movwf TMR0
movf TMR0, W
btfss STATUS, Z
goto $ - 2
return
; change ddram address and cursor position to line1 or 2
; based on the isLineTwo flag in Flags
changeLine
call checkLcdBusy
bcf Cntrl,RW ; write operation
bcf Cntrl,RS ; instruction
bcf PORTB,7
clrf PORTA
btfsc Flags,isLineTwo
bsf PORTA,7
btfsc Flags,isLineTwo
bsf PORTA,6
bsf Cntrl,E
bcf Cntrl,E
return
processByte ; for now, this is only a write operation
call checkLcdBusy
bcf Cntrl,RW ; write operation
bsf Cntrl,RS ; data
movf Byte,W
movwf PORTA
bcf PORTB,7
btfsc Byte,5
bsf PORTB,7
bsf Cntrl,E
bcf Cntrl,E
return
checkLcdBusy
call initInput
bsf Cntrl,RW ; set for read ( input = 1 )
bcf Cntrl,RS
bsf Cntrl,E
movf PORTA,W
bcf Cntrl,E
btfsc PORTA,7 ; test if D7 is busy
goto $ - 4
bcf Cntrl,RW
call initOutput
return
debugging ; call to blink an led on B4 where needed for debug
bsf PORTB,4
movlw d'100' ; c. half second on
movwf Count
call dlay5ms
decfsz Count,F
goto $ - 2
bcf PORTB,4
movlw d'100' ; c. half second off
movwf Count
call dlay5ms
decfsz Count,F
goto $ - 2
return
main
call init
call initOutput
call initLcd
movlw 0
movwf i
sayHi
call table
iorlw 0
btfsc STATUS, Z ; if end of table, end
goto done
movwf Byte
call processByte
incf i, F
movf i, W ; increment index
goto sayHi
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 "Hi Wiley & Maura\0"
done
goto $
END ; directive 'end of program'
Now that this is working, the next step is to add instructions. If the code receives 0x80, it knows that the next byte will be an instruction. It will clear the RS to allow an instruction write rather than a data write.
Once that works, I'll start adding code to set up the USART to receive data from the PC. I'll need to configure the pic, wire in a Maxim 3232 facsimile, and write some Python code to send the data. I'm going for a software protocol, here, limiting wires to 2. I'm thinking that the pic will initialize itself and wait for a handshake. The Python code will send a request to send code ( maybe 0x82 ? ). The pic will acknowledge ( maybe 0x83 ? ). Then the Python code will start sending bytes. For the first round, I'll just send text, then change to line 2, then more text, then a finished code ( maybe 0x84 ? ). or just not send any more. A timeout could work. I'm not sure yet.
All this is in preparation to build bootloading code. All this is in preparation to build a bootloader for the 16f88. I want the PC to send a request to send repeatedly up to a timeout. The pic will be fired up, initialize, read the line and see the request to send. It will acknowledge and go into wait to receive mode. The PC will send code and receive an echo of the sent byte. I'll want some error handling and resend code, too. Ahhh, so much to do, so much time to do it. That's the beauty of a hobby.
For now, I'm just dancing about seeing "Hello World" on my LCD. It doesn't seem to take much to make me happy. "Couldn't you just...." It's amazing how much it takes to satisfy those simple words....
Code bugs were aplenty on this one. Had cabin fever kids "helping" me every step of the way. Here is the basic code. When I figure out how to generate circuit diagrams, I'll post those, too. This code just reads a table and prints out "Hi Wiley and Maura" on the LCD. I'm already adding code to allow instructions as well as data. The screen allows 2 16 char lines to print in small font. A lot of the routines ( notably delays ) are not used. This is dev code, folks. Here it is:
;******************************************************************************
; *
; Filename: helloLcd.asm *
; Date: 2010.01.31 *
; File Version: 1.0.0 *
; *
; Author: Tom Hunt *
; *
;******************************************************************************
; NOTES: Get the parallel LCD working. Read a string of characters and *
; print them on the LCD. *
; Uses RA0-4, RB7, RA6-7 for the data lines ( RA5 is input only and *
; I want to use the built in AUSART on RB2&5. *
; *
;******************************************************************************
; CHANGE LOG: *
; *
; *
;******************************************************************************
list R=DEC, p=16f88 ; list directive to define processor
#include
; _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.
;******************************************************************************
; VARIABLE DEFINITIONS *
;******************************************************************************
#define Cntrl PORTB ; Control Register
#define RS 0 ; Register select on lcd 1=Data 0=Instruction
#define RW 1 ; Read/Write select 1=Read 0=Write
#define E 3 ; 1= starts data read/write operation
CBLOCK 0x20
Flags
Count
Byte ; holds the byte to output
i ; basic index counter for loop
tIndex ; table index temp storage
ENDC
CBLOCK 0x70 ; interupt context-saving vars here see datasheet chp 15
w_temp
status_temp
pclath_temp
ENDC
#define isInstruction 0
#define isLineTwo 1
;******************************************************************************
; 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
;******************************************************************************
; MAINLINE CODE *
;******************************************************************************
init
bank0
clrf INTCON ; For now, clear intcon, disables all interrupts. We'll change this and
; use the GIE and PEIE peripheral interupt to get writes from USART
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
clrf PIE1^0x080 ; clear peripheral interrupts for now.
clrf PIE2^0x080
bank0
return
initLcd
call dlay5ms ; wait 15ms+ to start
call dlay5ms
call dlay5ms
call dlay5ms
; This sequence loads values into the D0-7, RS, RW pins and strobes E
bcf Cntrl,RS
bcf Cntrl,RW
bsf PORTB,7
movlw b'00010000' ; value of data 'byte' is 00110000
movwf PORTA
bsf Cntrl,E
bcf Cntrl,E
call dlay5ms ; a wait period is needed. For this, we need 5ms
bsf Cntrl,E
bcf Cntrl,E ; strobe lcd again with same function set
call dlay150us ; a wait period of +100us is needed
bsf Cntrl,E
bcf Cntrl,E ; strobe lcd 3rd time with same function set
call dlay5ms
movlw b'00111000' ; 0x38 function set for 2 lines, small font
movwf PORTA
bsf PORTB,7
bsf Cntrl,E
bcf Cntrl,E
call dlay5ms
; display off
movlw b'00001000' ; 0x08
movwf PORTA
bcf PORTB,7
bsf Cntrl,E
bcf Cntrl,E
call dlay5ms
; clear display
movlw b'00000001' ; 0x01
movwf PORTA
bcf PORTB,7
bsf Cntrl,E
bcf Cntrl,E
call dlay5ms
; entry set incr data and no shift
movlw b'00000110'
movwf PORTA
bcf PORTB,7
bsf Cntrl,E
bcf Cntrl,E
call checkLcdBusy ; now we can poll the busy flag
; turn on display
movlw b'00001110' ; 0xE - display and cursor on, blink off
movwf PORTA
bcf PORTB,7
bsf Cntrl,E
bcf Cntrl,E
call checkLcdBusy
return
initInput
bank1
bsf TRISB^0x080,7
movlw 0xFF ; set PORTA to Input
movwf TRISA ^ 0x080
bank0
clrf PORTA ; clear PORTA for clean read
return
initOutput
bcf Cntrl,RS
bcf Cntrl,RW
bcf Cntrl,E
bcf PORTB,7
bcf PORTB,4
clrf PORTA ; clear all output pins
movlw b'01100100'
bank1
movwf TRISB ^0x080 ; enable B,7 & B0-1,3 for output B4 for debug
movlw b'00100000' ; set PORTA to Output except input only A,5
movwf TRISA ^ 0x080
bank0
return
dlay5ms
movlw b'00000100' ; set up tmr0 with prescaler 16 ( for 5ms delay )
bank1
movwf OPTION_REG^0x080
bank0
movlw 0x64 ; value 100 for tmr0. with prescaler 16 gives 5ms
movwf TMR0
movf TMR0, W
btfss STATUS, Z ; wait for 5ms timeout
goto $ - 2
return
dlay2ms
movlw b'00000010' ; set up tmr0 with prescaler 4 ( for 2ms delay )
bank1
movwf OPTION_REG^0x080
bank0
movlw 0x006 ; value 6 for tmr0. with prescaler 16 gives 5ms
movwf TMR0
movf TMR0, W
btfss STATUS, Z ; wait for 2ms timeout
goto $ - 2
return
dlay150us
movlw b'00000000' ; set prescaler to 1:1
bank1
movwf OPTION_REG^0x080
bank0
movlw 0xB5 ; value of 181 for tmr0 with prescaler 0 gives 150us
movwf TMR0
movf TMR0, W
btfss STATUS, Z
goto $ - 2
return
dlay50us
movlw b'00000000' ; set prescaler to 1:1
bank1
movwf OPTION_REG^0x080
bank0
movlw 0xE7 ; value of 231 for tmr0 with prescaler 0 gives 50us
movwf TMR0
movf TMR0, W
btfss STATUS, Z
goto $ - 2
return
; change ddram address and cursor position to line1 or 2
; based on the isLineTwo flag in Flags
changeLine
call checkLcdBusy
bcf Cntrl,RW ; write operation
bcf Cntrl,RS ; instruction
bcf PORTB,7
clrf PORTA
btfsc Flags,isLineTwo
bsf PORTA,7
btfsc Flags,isLineTwo
bsf PORTA,6
bsf Cntrl,E
bcf Cntrl,E
return
processByte ; for now, this is only a write operation
call checkLcdBusy
bcf Cntrl,RW ; write operation
bsf Cntrl,RS ; data
movf Byte,W
movwf PORTA
bcf PORTB,7
btfsc Byte,5
bsf PORTB,7
bsf Cntrl,E
bcf Cntrl,E
return
checkLcdBusy
call initInput
bsf Cntrl,RW ; set for read ( input = 1 )
bcf Cntrl,RS
bsf Cntrl,E
movf PORTA,W
bcf Cntrl,E
btfsc PORTA,7 ; test if D7 is busy
goto $ - 4
bcf Cntrl,RW
call initOutput
return
debugging ; call to blink an led on B4 where needed for debug
bsf PORTB,4
movlw d'100' ; c. half second on
movwf Count
call dlay5ms
decfsz Count,F
goto $ - 2
bcf PORTB,4
movlw d'100' ; c. half second off
movwf Count
call dlay5ms
decfsz Count,F
goto $ - 2
return
main
call init
call initOutput
call initLcd
movlw 0
movwf i
sayHi
call table
iorlw 0
btfsc STATUS, Z ; if end of table, end
goto done
movwf Byte
call processByte
incf i, F
movf i, W ; increment index
goto sayHi
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 "Hi Wiley & Maura\0"
done
goto $
END ; directive 'end of program'
Now that this is working, the next step is to add instructions. If the code receives 0x80, it knows that the next byte will be an instruction. It will clear the RS to allow an instruction write rather than a data write.
Once that works, I'll start adding code to set up the USART to receive data from the PC. I'll need to configure the pic, wire in a Maxim 3232 facsimile, and write some Python code to send the data. I'm going for a software protocol, here, limiting wires to 2. I'm thinking that the pic will initialize itself and wait for a handshake. The Python code will send a request to send code ( maybe 0x82 ? ). The pic will acknowledge ( maybe 0x83 ? ). Then the Python code will start sending bytes. For the first round, I'll just send text, then change to line 2, then more text, then a finished code ( maybe 0x84 ? ). or just not send any more. A timeout could work. I'm not sure yet.
All this is in preparation to build bootloading code. All this is in preparation to build a bootloader for the 16f88. I want the PC to send a request to send repeatedly up to a timeout. The pic will be fired up, initialize, read the line and see the request to send. It will acknowledge and go into wait to receive mode. The PC will send code and receive an echo of the sent byte. I'll want some error handling and resend code, too. Ahhh, so much to do, so much time to do it. That's the beauty of a hobby.
For now, I'm just dancing about seeing "Hello World" on my LCD. It doesn't seem to take much to make me happy. "Couldn't you just...." It's amazing how much it takes to satisfy those simple words....
Subscribe to:
Posts (Atom)