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.
Search This Blog
Wednesday, March 24, 2010
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 ).
Subscribe to:
Posts (Atom)