PIC write to SRAM

Discussion in 'Embedded Systems and Microcontrollers' started by peter_morley, Aug 11, 2012.

  1. peter_morley

    Thread Starter Member

    Mar 12, 2011
    Hello I am using a PIC 18F45K20 to send address information to an external SRAM AS6C4008 (I have attached both datasheets below). I thought I was writing data correctly to my SRAM device because I was able to display any 8 bit .bmp file to a screen with vga output. I usually would write data in 64 byte blocks ie an 8x8 byte map. Now I have implemented a transparent color that when is detected I skip drawing to that memory location. This would allow me to draw an 8x8 pixel object but not mess with the background I have in SRAM. Another words I was writing memory say 40 times instead of 64 times because of the transparent byte detection.
    So now to the real information. My instructions are executing at 16MIPS or 16MHz because I have a 64 MHz clock and each instruction takes 4 cycles. I am writing to memory in this fashion...
    Code ( (Unknown Language)):
    2.         bsf     PORTE,WE
    3.         bsf     PORTE,CE
    4.         nop
    5.         bcf     PORTE,CE
    6.         bcf     PORTE,WE
    7.         nop
    8.         bsf     PORTE,OE
    9.         nop
    10.         nop
    12.         movf    TABLAT,W
    13.         movwf   PORTC
    14.         nop
    16.         bsf     PORTE,WE
    17.         bsf     PORTE,CE
    18.         nop
    19.         nop
    20.         bcf     PORTE,OE
    The address is set to PORTD and PORTB previously before any of the above instructions are run. I have been stuck on this problem for awhile and I'm not too sure what to do. I am following the write cycle 1 method in the SRAM datasheet.
    Last edited: Aug 11, 2012
  2. JohnInTX


    Jun 26, 2012
    You should always do bcf/bsf to LATx never PORTx. Write to LATx, read from PORTx.

    Presumably the RAM databus is PORTC. You'll need to change the port from output to input via TRISC before dropping OE. You have some bus contention where you drop CE before raising OE.

    I think would go along the lines of leaving CE low during a block write/read and controlling writes with WE, reads with OE after changing port direction. Raise CE when done with the RAM or selecting another.
  3. peter_morley

    Thread Starter Member

    Mar 12, 2011
    I originally thought that whenever a write to a port register is done ie movwf PORTD, LATD stores that data I did not think it was necessary to write it to the latch. Are you saying whenever I write to a port I should write to the latch and whenever I read a port I should read from the port?

    I am quite confused about the TRISC part. I am sending data via PORTC to an external SRAM chip so techincally why would i need to change TRISC to input if I can just control the input output characteristics of the SRAM.

    One BIG question is does the OE bit directly affect Dout and Din in that if I set OE the data port of the SRAM is set to low impedance (output mode) and when i drop OE the data port of the SRAM is set to high impedance (input enable)?

    Here is my changes of the write procedure. Now I am only getting blocks of black written to RAM ie 8x8 blocks of 0x00 bytes to SRAM.
    Code ( (Unknown Language)):
    2.         clrf    TRISC
    3.         clrf    PORTC
    4.         bsf     LATE,OE
    5.         bsf     LATE,WE
    6.         bsf     LATE,CE
    8.         nop
    9.         nop
    10.         nop
    12.         bcf     LATE,CE
    13.         bcf     LATE,WE
    14.         setf    TRISC
    15.         bcf     LATE,OE
    16.         nop
    17.         nop
    19.         movf    TABLAT,W
    20.         movwf   LATC
    21.         nop
    23.         bsf     LATE,WE
    24.         bsf     LATE,CE
    25.         nop
    26.         nop
    27.         bsf     LATE,OE
    28.         clrf    TRISC
  4. JohnInTX


    Jun 26, 2012
    Exactly what I am saying, especially with r-m-w operations like bsf/bcf. Its true that writing to PORTx propagates to LATx but r-m-w on PORT reads the values on the pins, not the last value stored. That frequently causes problems so yes, writes to LATx, reads from PORTx unless you have some compelling reason not to.

    You have to set PORTC to INPUT to accurately read the bus. If you don't you'll have the both PIC output and the SRAM driving the pins. While you can read the port all day if you want, but you won't be getting good data. So for reads, you have to set PORTC to input and for writes set the port to output using the TRIS reg.

    OE must be dropped to enable the SRAMs output drivers (presumably after you set PORTC to input). The chip's logic always is in 'read' mode when WE is high but you can't see the data until you enable the outputs by dropping OE. Similarly, you want OE high before dropping WE to write so that the data goes into the SRAM, not out of it.

    You can see one way of handling the SRAM in the code snippet below. CE is used to select the chip for the whole read or write operation. OE us used to read, WE is used to write. Note how TRISC must be changed between reads and writes.

    BTW: this is untested, just a guideline..
  5. peter_morley

    Thread Starter Member

    Mar 12, 2011
    So I tried using your guidelines and it works fine when i write to every memory location in the 64 byte loop but when I skip a byte due to my designated transparency byte 0x49 I get errors that really don't make sense to me. Vertically (memory locations of 8 byte offsets) if I encounter a skip byte (transparency byte) it doesn't write and all is well. BUT if I happen to encounter a non-transparency byte the rest of the vertical line is colored by that color if I only get transparency bytes after. Here is an example...
    Code ( (Unknown Language)):
    2. ;SINVADER1      8x8 byte map
    4. Char_097        db  0x38, 0xFF, 0x49, 0x49, 0x49, 0x49, 0xFF, 0x49 
    5. Char_098        db  0x49, 0x49, 0xFF, 0x49, 0x49, 0xFF, 0x49, 0x49 
    6. Char_099        db  0x49, 0x49, 0xFF, 0xFF, 0xFF, 0xFF, 0x49, 0x49 
    7. Char_100        db  0x49, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x49 
    8. Char_101        db  0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0xFF 
    9. Char_102        db  0x49, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF  
    10. Char_103        db  0x49, 0x49, 0xFF, 0x49, 0x49, 0xFF, 0x49, 0xFF  
    11. Char_104        db  0x49, 0x49, 0x49, 0xFF, 0xFF, 0x49, 0x49, 0x49
    The left column should technically have 0x38(Green), background,background,background,0xFF(white),background,background,background. What is happening is that the background bytes are being overwritten always by the previous column index. So all 0x49 bytes before the 0xFF and after the 0x38 are being written as 0x38. And all the 0x49 bytes after 0xFF are being written as 0xFF. I have also attached a picture to show you what happens. Look at the left most column of the space invader. Here is the code of my loop...
    Code ( (Unknown Language)):
    2. ;8x8 pixels
    3. drawSprite
    5.         tblrd*+             ; read into TABLAT and increment
    6.         movlw   0x49
    7.         cpfseq  TABLAT
    8.         call    writemem
    9.         incf    PORTD
    11.         tblrd*+             ; read into TABLAT and increment
    12.         movlw   0x49
    13.         cpfseq  TABLAT
    14.         call    writemem
    15.         incf    PORTD
    17.         tblrd*+             ; read into TABLAT and increment
    18.         movlw   0x49
    19.         cpfseq  TABLAT
    20.         call    writemem
    21.         incf    PORTD
    23.         tblrd*+             ; read into TABLAT and increment
    24.         movlw   0x49
    25.         cpfseq  TABLAT
    26.         call    writemem
    27.         incf    PORTD
    29.         tblrd*+             ; read into TABLAT and increment
    30.         movlw   0x49
    31.         cpfseq  TABLAT
    32.         call    writemem
    33.         incf    PORTD
    35.         tblrd*+             ; read into TABLAT and increment
    36.         movlw   0x49
    37.         cpfseq  TABLAT
    38.         call    writemem
    39.         incf    PORTD
    41.         tblrd*+             ; read into TABLAT and increment
    42.         movlw   0x49
    43.         cpfseq  TABLAT
    44.         call    writemem
    45.         incf    PORTD
    47.         tblrd*+             ; read into TABLAT and increment
    48.         movlw   0x49
    49.         cpfseq  TABLAT
    50.         call    writemem
    52.         movlw   0x07
    53.         subwf   PORTD,1
    54.         incf    PORTB
    56.         decfsz  bitmapcount
    57.         goto    drawSprite
    58.         nop
    59.         movlw   0x04
    60.         movwf   bitmapcount
    61.         movff   playery,PORTB
    62.         bsf LATE,CE
    63.         return
    65. writemem
    67.         movff TABLAT,LATC ; write data to the port LATch
    68.         bcf LATE,WE ; strobe the SRAM to write the data
    69.         bsf LATE,WE
    71.         return
    • SI.jpg
      File size:
      153.2 KB
  6. JohnInTX


    Jun 26, 2012
    All of the PORTx references in the code snippet must be to LATx, especially when you are incrementing the address. That's a r-m-w operation and you can't do that reliably on PORTs, only LATs.

    Make sure OE is high throughout.
    Raise CE before changing the address on LATB (for safety).

    During writemem, you may need a nop for settling time after setting LATC before strobing. (shouldn't but I'd try it for grins).

    Stylistic BTWs:
    All the instructions that can refer a destination should be qualified with W or F i.e. incf LATD,F and subwf LATD,F for readability and to remind you (and me!) of what you are doing. I don't like leaving these things to defaults.

    Using colons after a label helps you find it when you search as opposed to all of the things that refer to it.

    Looks cute!
  7. takao21203

    Distinguished Member

    Apr 28, 2012
    I am using a SRAM having a PIC 16f59 only (means there is only PORT).
    It works 100%.

    I suggest to get rid of assembler, it is a big effort actually to go through such source codes. Even if I am able to do it (I used assembler for years).

    Relying to read back from PORT can indeed cause issues. If the TRIS is set correctly, it will work.

    Here is C code that I use for SRAM:

    Code ( (Unknown Language)):
    1. void restore_led_data()
    2. {
    3.   PORTC=bck_PORTC;
    4.   TRISB=0x00;
    5.   PORTD=bck_PORTD;
    6.   PORTB=bck_PORTB;
    7.   TRISC=0x00;
    8. }
    10. void store_ram(unsigned char addr, unsigned char data)
    11. {
    12.     // /OE E5
    13.     // /CS E4
    14.     // WE E6
    15.     // data : RB
    16.     // addr : RD
    17.     PORTC=0xff;
    18.     TRISC=0xff;
    20.     PORTD=addr;
    21.     PORTB=data;
    22.     PORTE=0b00100000;
    23.     PORTE=0b01110000;
    25.     restore_led_data();
    26. }
    28. unsigned char load_ram(unsigned char addr)
    29. {unsigned char d;
    30.   PORTC=0xff;
    31.   TRISC=0xff;
    32.   TRISB=0xff;
    34.   PORTE=0b01000000;
    35.   PORTD=addr;
    36.   d=PORTB;
    37.   PORTE=0b01110000;
    39.   restore_led_data();
    40.   return(d);
    41. }
    restore_led_data is used since the port is multiplexed with a LED matrix.
    And also inside this function TRIS is set for writing.

    It is not directly straightforward but it was optimized for faster access, since the chip works at 125 KHz only.

    However only one byte a time, address is set up each time.

    If you continue to use assembler and also add for instance external LATCH for the address, the resulting source code will become lengthy and messy.

    I have done this once in assembler, having 3 latch registers altogether on the data bus.
  8. JohnInTX


    Jun 26, 2012
    You're not doing any r-m-w on the PORTs at least here. That's a good idea.

    I don't want to get into a big deal about IO methods/limitations etc. But since the 18F eliminates the r-m-w issues that you have to watch in the midrange, why not use it as intended?
  9. peter_morley

    Thread Starter Member

    Mar 12, 2011
    Found out my problem wasn't the writing part. Everything was being written byte by byte as I told the program to execute I just didn't think the problem through well enough. I realized when I wasn't moving my space invader that the picture appeared fine and the background was not being overwritten. Simply, I was forgetting that I had to recover the background bytes always in order to display the background correctly. Once I had realized the mistake it was a quick fix. Thanks for sticking this through, I probably would have given up on this for a week if I didn't get some great suggestions from you guys.
  10. Droidrellik

    New Member

    Apr 7, 2013
    Funny, I suggest the opposite.
    Is there some special ROM code the C compiler unlocks and grants extra special opcodes for the end assembler machine code?
  11. t06afre

    AAC Fanatic!

    May 11, 2009
    I would not say writing to PORTx instead of LATx is that kind of a big NO-NO. Writing to PORTx does not normally lead to problems. Many PIC16F do not have LATx registers. But in some rare situations where pins are loaded beyond their specifications or being quickly changed into a capacitive load, the physical voltage on a pin may not match its primed logic state. In this circumstance, when being read, a nominally logic 1 bit may read as a 0, or vice versa. Thus when written back, more than one bit target bit may change. However it does not hurt get to learn good practice from the beginning. And it will certainly not hurt to get the "Write to LATx, read from PORTx" rule into your fingers from the start
  12. takao21203

    Distinguished Member

    Apr 28, 2012
    No I don't think there is such code.

    If you want fast external RAM, use the parallel Master port.
  13. JohnInTX


    Jun 26, 2012
    I can't disagree with anything you've said but would like to expand on some of the points.

    First, a clarification, writing to PORTx also writes LATx (when present) so either will work just fine.

    Your discussion about r-m-w into less than ideal loads is quite accurate and things like overloaded pins / capacitive loads will indeed cause problems. However, like reading the uCHIP databooks, its too easy to draw the conclusion that if you avoid these and similar conditions you are safe. I disagree.

    A better way to look at it is that during the r-m-w, for one Q time, the entire port is wide open to the big, nasty world. Even if you are driving nothing but clean logic, when changing one pin on the port, the other 7 are also subject to being changed by whatever shows up at that instant on their pin. Got a bit of ground-bounce, some contact noise, or did someone turn on the room lights and generate some conducted noise on the power line? Keep in mind that at 20MHz we are talking a 50ns window. If you have the bad luck to be in the -r- of the r-m-w when that tiny junk hits the pin, you are screwed. Not only are these failures hard to find, they are hard to predict as well.

    Personally, when I am forced to work in midrange/baseline without the LATx, I always shadow the ports (keep a copy, modify the copy then write that to the PORT). I realize that many will disagree but all I can say is that I discovered these unexpected routes to I/O failure the hard way and incurred the not inconsiderable expense of fixing them. Many others have hit the same wall. The idea that it 'works mostly' turned out to be not enough and I don't like solving the same problem twice. When it comes to r-m-w on PORTx, I say don't do it.