Search This Blog

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.

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 ).