PSRAM example
Hello,
I took a look at this demo and wanted to try to interact with the P2 edge's 32MB of Ram:
https://forums.parallax.com/discussion/176083/3d-teapot-demo/p1
I did read some of the code in this thread and referenced the manual. The program does compile but I feel like i'm missing something to write the letter A to the psram and then read it back:
' sets the system clock frequency
_CLKFREQ = 180_000_000
' set the receiving pin for input to the P2 microcontroller
RX_PIN = 63
' set the transmission pin for output from the P2 microcontroller
TX_PIN = 62
' set the baud mode to support 2000000 baud
BAUD_RATE = 2_000_000
' set the serial clock period for the baud rate based on the system clock frequency (_CLKFREQ)
SERIAL_CLOCK = ( _CLKFREQ / BAUD_RATE )
' calculate a bit period value for the serial communication baud rate.
BIT_PERIOD = (( SERIAL_CLOCK << 16 ) & $FFFFFC00 ) + ( 8 - 1 )
' PSRAM Pin Definitions
P_PSRAM_D = 40 ' Data bus (4-bit, P40-P43)
P_PSRAM_CLK = 56 ' PSRAM Clock
P_PSRAM_CE = 57 ' PSRAM Chip Enable
' Constants
PSRAM_ADDR = $000000 ' Address to store 'A' in PSRAM
dat
org 0 ' start assembling code from address 0
asmclk ' use the system clock (_CLKFREQ) for timing-related calculations
' Configure RX smart pin
wrpin ##(P_ASYNC_RX | P_HIGH_15K), #RX_PIN ' configure the RX pin as a smart pin for asynchronous serial reception
wxpin ##BIT_PERIOD, #RX_PIN ' set the correct timing for receiving bits at the correct speed
wypin #1, #RX_PIN ' Enable the smart pin
dirh #RX_PIN ' sets the direction of the RX pin to high
' Configure TX smart pin
wrpin ##(P_ASYNC_TX | P_OE), #TX_PIN ' configure the TX pin as a smart pin for asynchronous serial transmission
wxpin ##BIT_PERIOD, #TX_PIN ' set the correct timing for transmitting bits at the correct speed
wypin #1, #TX_PIN ' Enable the smart pin transmitter
dirh #TX_PIN ' sets the direction of the TX pin to high
.start
' Initialize system
call #.psram_init ' Initialize PSRAM
' Write letter 'A' to PSRAM
mov tmp1, #$41
call #.write_psram
' Read back letter 'A' from PSRAM
call #.read_psram
' Send output to PST
call #.uart_tx_char
' End program loop
jmp #.done
'------------------------------------------------------------
' Initialize PSRAM
'------------------------------------------------------------
.psram_init
' Activate PSRAM to prepare for operations
drvl #P_PSRAM_CE ' Activate PSRAM (low active)
ret
'------------------------------------------------------------
' Write to PSRAM (Full 8-bit mode)
'------------------------------------------------------------
.write_psram
' Set address (PSRAM_ADDR = 0x000000)
mov tmp2, #PSRAM_ADDR
' Enable PSRAM
drvl #P_PSRAM_CE
' Write Data (8-bit split into 4-bit nibbles)
mov tmp0, tmp1 ' Copy the 8-bit value
and tmp1, ##$F ' Mask the lower 4 bits (lower nibble)
mov outa, tmp1
wrpin ##%01, #P_PSRAM_D ' Configure P40-P43 as outputs
drvl #P_PSRAM_CLK
call #.clock_pulse
drvh #P_PSRAM_CLK
' Write second nibble (upper 4 bits)
shr tmp0, #4 ' Shift right by 4 to get the upper nibble
and tmp0, ##$F ' Mask the upper 4 bits
mov outa, tmp0
wrpin ##%01, #P_PSRAM_D ' Configure P40-P43 as outputs
drvl #P_PSRAM_CLK
call #.clock_pulse
drvh #P_PSRAM_CLK
' Disable PSRAM
drvh #P_PSRAM_CE
ret
'------------------------------------------------------------
' Read from PSRAM (Full 8-bit mode)
'------------------------------------------------------------
.read_psram
' Enable PSRAM
drvl #P_PSRAM_CE
' Set Data Bus as Input
wrpin ##%00, #P_PSRAM_D ' Configure P40-P43 as inputs
dirl #P_PSRAM_D ' Set direction to input
' Clock Pulse Before Reading
call #.clock_pulse
' Read lower nibble (first 4 bits)
rdpin tmp3, #P_PSRAM_D ' Read data from P40-P43
and tmp3, ##$F ' Mask only the 4-bit value
' Shift to make room for the upper nibble
shl tmp3, #4 ' Shift left to clear the lower nibble
' Clock Pulse Before Reading Upper nibble
call #.clock_pulse
' Read upper nibble (next 4 bits)
rdpin tmp4, #P_PSRAM_D ' Read data from P40-P43
and tmp4, ##$F ' Mask only the 4-bit value
or tmp3, tmp4 ' Combine the two nibbles (upper + lower)
' Send the read value for debugging
call #.uart_tx_char ' Send value through UART
' Disable PSRAM
drvh #P_PSRAM_CE
ret
.data_error
' Handle the error (e.g., signal failure via UART or LED)
call #.uart_tx_char ' Send an error message
ret
'------------------------------------------------------------
' UART Send Character (t3 contains data)
'------------------------------------------------------------
.uart_tx_char
wypin tmp3, #TX_PIN ' Send character
nop
waitx ##5000 ' Small delay for stability
ret
'------------------------------------------------------------
' Clock Pulse Routine
'------------------------------------------------------------
.clock_pulse
drvl #P_PSRAM_CLK
nop
drvh #P_PSRAM_CLK
ret
.done
'------------------------------------------------------------
' Variable Registers
'------------------------------------------------------------
tmp0 res 1 '
tmp1 res 1 '
tmp2 res 1 '
tmp3 res 1 '
tmp4 res 1 '
Comments
It doesn't work quite so easily:
See also:
I have had great success with psram.spin2 and psram16drv.spin2 found at
https://www.parallax.com/package/p2-solar-panel-code/
This driver pair allows burst reads and writes; longer reads and writes achieve greater efficiency. For example, moving 64 longs at at time yields 100 MB/s. Huge thanks to Michael Mulholland for developing these drivers! (I use flexspin/flexprop, which may be required, not sure.)
Roger Loh wrote those two particular files - https://forums.parallax.com/discussion/171176/memory-drivers-for-p2-psram-sram-hyperram-was-hyperram-driver-for-p2/p1
This was all helpful to get me a little closer.
'------------------------------------------------------------ ' Constants and Pin Definitions '------------------------------------------------------------ _CLKFREQ = 180_000_000 ' Set system clock frequency RX_PIN = 63 ' Set the receiving pin for input to the P2 microcontroller TX_PIN = 62 ' Set the transmission pin for output from the P2 microcontroller BAUD_RATE = 2_000_000 ' Set the baud rate for serial communication SERIAL_CLOCK = (_CLKFREQ / BAUD_RATE) ' Set the serial clock period based on the system clock frequency BIT_PERIOD = ((SERIAL_CLOCK << 16) & $FFFFFC00) + (8 - 1) ' Calculate bit period for baud rate ' PSRAM Pin Definitions P_PSRAM_D = 40 ' Data bus (4-bit, P40-P43) P_PSRAM_CLK = 56 ' PSRAM Clock P_PSRAM_CE = 57 ' PSRAM Chip Enable ' Constants PSRAM_ADDR = $000000 ' Address to store 'A' in PSRAM '------------------------------------------------------------ ' Initialize system and Smart Pin configurations '------------------------------------------------------------ dat org 0 asmclk ' Use the system clock (_CLKFREQ) for timing-related calculations ' Configure RX smart pin wrpin ##(P_ASYNC_RX | P_HIGH_15K), #RX_PIN ' Configure RX pin for asynchronous serial reception wxpin ##BIT_PERIOD, #RX_PIN ' Set correct timing for receiving bits wypin #1, #RX_PIN ' Enable RX pin smart pin dirh #RX_PIN ' Set RX pin as input ' Configure TX smart pin wrpin ##(P_ASYNC_TX | P_OE), #TX_PIN ' Configure TX pin for asynchronous serial transmission wxpin ##BIT_PERIOD, #TX_PIN ' Set correct timing for transmitting bits wypin #1, #TX_PIN ' Enable TX pin smart pin dirh #TX_PIN ' Set TX pin as output .start ' Initialize system call #.psram_init ' Initialize PSRAM mov tmp1, #$38 ' Command to QPI write call #.psram_send_command ' Write letter 'A' to PSRAM mov tmp1, #"A" ' ASCII code for 'A' mov tmp2, #PSRAM_ADDR ' Set address to start writing ' Write the letter to PSRAM call #.write_psram ' Wait for a brief moment waitx ##100000 mov tmp1, #$EB ' Command to enter QPI read call #.psram_send_command ' Read back letter 'A' from PSRAM call #.read_psram ' Send output to PST (UART) call #.uart_tx_char ' End program loop jmp #.done '------------------------------------------------------------ ' Initialize PSRAM to Quad SPI Mode (QPI) '------------------------------------------------------------ .psram_init ' Reset Enable Command (Optional) mov tmp1, #$66 ' Command to enable reset call #.psram_send_command ' Reset Command (Optional) mov tmp1, #$99 ' Command to reset PSRAM call #.psram_send_command ' Enter Quad Mode Command mov tmp1, #$35 ' Command to enter QPI mode (Quad SPI mode) call #.psram_send_command ' Wait for a brief moment to ensure that PSRAM has transitioned waitx ##100000 ret '------------------------------------------------------------ ' Send Command to PSRAM (Uses tmp1 as the command) '------------------------------------------------------------ .psram_send_command ' Activate PSRAM to prepare for command drvl #P_PSRAM_CE ' Enable PSRAM chip (CE low) ' Send the command (this assumes the command is in tmp1) mov tmp2, tmp1 ' Copy command to tmp2 ' Use appropriate smart pin mode to send command ' Send 8-bit command in Serial mode (DIO) wrpin ##$04, #P_PSRAM_D ' Set Data pins for output dirh #P_PSRAM_D ' Set pins as output wypin tmp2, #P_PSRAM_D ' Send the command byte to PSRAM ' Clock Pulse After Sending Command call #.clock_pulse ' Disable PSRAM chip drvh #P_PSRAM_CE ' Disable PSRAM chip (CE high) ret '------------------------------------------------------------ ' Write to PSRAM '------------------------------------------------------------ .write_psram ' Enable PSRAM chip drvl #P_PSRAM_CE ' Write Data to PSRAM wrpin ##$00, #P_PSRAM_D ' Set Data pins for output dirh #P_PSRAM_D ' Set pins as output wypin tmp1, #P_PSRAM_D ' Write the byte to PSRAM ' Clock pulse after write call #.clock_pulse ' Disable PSRAM chip drvh #P_PSRAM_CE ret '------------------------------------------------------------ ' Read from PSRAM '------------------------------------------------------------ .read_psram ' Enable PSRAM chip drvl #P_PSRAM_CE ' Set Data Bus as Input (P40-P43) wrpin ##$00, #P_PSRAM_D ' Configure P40-P43 as inputs dirl #P_PSRAM_D ' Set direction to input ' Clock Pulse Before Reading call #.clock_pulse 'this is a test 'mov tmp3, #"A" ' Read the data 'rdbyte tmp3, #P_PSRAM_D ' Read data from PSRAM rdbyte tmp3, #P_PSRAM_D ' Read data from PSRAM ' Disable PSRAM chip drvh #P_PSRAM_CE ret '------------------------------------------------------------ ' UART Send Character (t3 contains data) '------------------------------------------------------------ .uart_tx_char wypin tmp3, #TX_PIN ' Send character via UART nop waitx ##5000 ' Small delay for stability ret '------------------------------------------------------------ ' Clock Pulse Routine '------------------------------------------------------------ .clock_pulse drvl #P_PSRAM_CLK nop drvh #P_PSRAM_CLK ret .done ' End of program loop '------------------------------------------------------------ ' Variable Registers '------------------------------------------------------------ tmp0 res 1 tmp1 res 1 tmp2 res 1 tmp3 res 1
Right now, i fixed things so the output at least works. I get the character ?, but i still need to do the streamer. Also replaced the rdpin's with some rdbytes an the same for the writing. I tested the read a bit to make sure it at least works with these lines:
'this is a test
'mov tmp3, #"A"
' Read the data rdbyte tmp3, #P_PSRAM_D ' Read data from PSRAM
At this point in the code, it prints A if i uncomment the mov, otherwise I get the ?. The PSRAM's datasheet was extremely helpful. I'll keep plugging away at it though.
RDBYTE is also not the correct instruction for this.
You want
GETBYTE x, INA+(P_PSRAM_D/32),#(P_PSRAM_D/8)&3
. That will grab a byte from the I/O port.Though I would recommend getting a grip on the basics of PASM programming before trying to mess with something harder like the PSRAM.
That might not be a bad idea, will do. Thank you!
@cgracey made a super simple PSRAM driver. He is using it for VGA in this thread:
https://forums.parallax.com/discussion/175725/anti-aliased-24-bits-per-pixel-hdmi/p1
@Rayman
Wow, I’m gonna bookmark this and have a closer look. I took a quick scan of it and noticed it has all the parts in here and even the streamer. I might give it another shot. Thank you!!!
After this though I might go back to some of the simpler add ons and general assembly language as suggested. I think I got ahead of myself when I was able a lot things working so quickly.
It looks like there is a bug with the number of clocks generate in Chip's driver code. I've not tested it but it looks like the
add pb,len
should be amov pb,len
instead. Amusingly it probably doesn't really matter a lot since the clock can probably be left running unbroken anyway.Hey everybody! Firstly, thank you all for all the info. I literally combed over it and my own code. The datasheet for the psram was extremely helpful. I was able to gauge what I was missing, in particular the very first drvh needed before doing anything. The code from psram driver was great too since I saw how i needed to set the clock pulse and such. At the end of the day, I came away with this, still needs some work since I think i have a clock_pulse or two that i don't need. I'm gonna hack away at this a bit more to get rid f the unneeded bits.:
' sets the system clock frequency _CLKFREQ = 180_000_000 ' Serial communication settings RX_PIN = 63 TX_PIN = 62 BAUD_RATE = 2_000_000 SERIAL_CLOCK = ( _CLKFREQ / BAUD_RATE ) BIT_PERIOD = (( SERIAL_CLOCK << 16 ) & $FFFFFC00 ) + ( 8 - 1 ) ' PSRAM Pin Definitions P_PSRAM_D = 40 ' Data bus (4-bit, P40-P43) P_PSRAM_CLK = 56 ' PSRAM Clock P_PSRAM_CE = 57 ' PSRAM Chip Enable ' Constants PSRAM_SIZE = 64 ' PSRAM_ADDR = $000000 ' Starting address to write/read dat org 0 asmclk ' Configure RX smart pin wrpin ##(P_ASYNC_RX | P_HIGH_15K), #RX_PIN wxpin ##BIT_PERIOD, #RX_PIN wypin #1, #RX_PIN dirh #RX_PIN ' Configure TX smart pin wrpin ##(P_ASYNC_TX | P_OE), #TX_PIN wxpin ##BIT_PERIOD, #TX_PIN wypin #1, #TX_PIN dirh #TX_PIN '------------------------------------------------------------ ' Main Execution '------------------------------------------------------------ start ' The PSRAM for the P2 Edge 32MB is required to have CE# high before beginning any operations. ' After the initial drvh: ' drvl will disable the psram so changed can be made. ' drvh will enable the psram so the changes take affect. drvh #P_PSRAM_CE call #reset_psram ' We do not assume the state of the PSRAM at start up call #startup_psram ' Start up the PSRAM for output mov tmp1, #$35 ' "Enter Quad Mode" command (0x35) call #clock_pulse call #fill_psram ' Fill PSRAM call #read_psram ' Read a value from PSRAM call #uart_tx_char ' Send read value via UART jmp #endprog ' End the program '------------------------------------------------------------ ' Reset PSRAM '------------------------------------------------------------ reset_psram drvl #P_PSRAM_CE mov tmp1, #$66 ' Reset Enable command call #clock_pulse mov tmp1, #$99 ' Reset command call #clock_pulse drvh #P_PSRAM_CE call #clock_pulse ret '------------------------------------------------------------ ' Start up PSRAM '------------------------------------------------------------ startup_psram drvl #P_PSRAM_CLK ' Set CLK low dirh #P_PSRAM_CLK ' Set CLK as output to drive it drvh #P_PSRAM_CLK ' Set CLK high call #clock_pulse ret '------------------------------------------------------------ ' Fill PSRAM with 1 (0x0001) in Quad Mode '------------------------------------------------------------ fill_psram mov tmp0, #PSRAM_ADDR ' Start address mov tmp1, ##PSRAM_SIZE ' Stop writing after you reach this size .fill_loop drvl #P_PSRAM_CE mov tmp2, #$38 ' Quad Write Command (0x38) call #clock_pulse ' Send address (24-bit) mov tmp2, tmp0 ' Address call #send_address ' Send the address ' Write value mov tmp2, #66 ' Upper nibble call #send_data mov tmp2, #66 ' Lower nibble call #send_data drvh #P_PSRAM_CE call #clock_pulse add tmp0, #2 ' Increment by 2 (16-bit write) cmp tmp0, tmp1 wz if_ne jmp #.fill_loop ret '------------------------------------------------------------ ' Read from PSRAM '------------------------------------------------------------ read_psram ' Enable PSRAM (CS Low) drvl #P_PSRAM_CE ' Send Quad Read Command (0xEB) mov tmp1, #$EB call #send_command ' Send the address mov tmp2, #PSRAM_ADDR call #send_address ' Read upper byte (first nibble) call #receive_data mov tmp4, tmp2 ' Store the upper byte ' Read lower byte (second nibble) call #receive_data mov tmp5, tmp2 ' Store the lower byte drvh #P_PSRAM_CE ret '------------------------------------------------------------ ' Receive data from PSRAM '------------------------------------------------------------ receive_data ' Set data bus as input wrpin #%00001110, #P_PSRAM_D dirl #P_PSRAM_D ' Ensure data bus is in input mode ' Read upper 4 bits call #clock_pulse rdpin tmp2, #P_PSRAM_D ' Read 4-bit data and tmp2, #$0F ' Ensure only lower 4 bits are used shl tmp2, #4 ' Move to upper half of byte ' Read lower 4 bits call #clock_pulse rdpin tmp3, #P_PSRAM_D ' Read next 4-bit data and tmp3, #$0F ' Ensure only lower 4 bits are used or tmp2, tmp3 ' Merge upper and lower 4 bits ret '------------------------------------------------------------ ' Send Command to PSRAM '------------------------------------------------------------ send_command drvl #P_PSRAM_CE wrpin #$04, #P_PSRAM_D wypin tmp1, #P_PSRAM_D call #clock_pulse drvh #P_PSRAM_CE ret '------------------------------------------------------------ ' Send Address '------------------------------------------------------------ send_address wrpin #%00001100, #P_PSRAM_D ' Set PSRAM data pins to output mode dirh #P_PSRAM_D ' Set the data bus to output ' Send MSB (most significant byte) - Shift by 16 bits mov tmp3, tmp2 shr tmp3, #16 ' Shift address to get the MSB and tmp3, #$FF ' Mask to get only the MSB (8 bits) call #send_data ' Send the MSB ' Send middle byte - Shift by 8 bits mov tmp3, tmp2 shr tmp3, #8 ' Shift address to get the middle byte and tmp3, #$FF ' Mask to get only the middle byte call #send_data ' Send the middle byte ' Send LSB (least significant byte) and tmp2, #$FF ' Mask to get the LSB (8 bits) call #send_data ' Send the LSB ret '------------------------------------------------------------ ' Send Data '------------------------------------------------------------ send_data wrpin #%00000100, #P_PSRAM_D dirh #P_PSRAM_D ' Send upper 4 bits mov tmp3, tmp2 shr tmp3, #4 and tmp3, #$0F wypin tmp3, #P_PSRAM_D ' Send lower 4 bits and tmp2, #$0F wypin tmp2, #P_PSRAM_D call #clock_pulse ret '------------------------------------------------------------ ' Clock Pulse Routine '------------------------------------------------------------ clock_pulse drvl #P_PSRAM_CLK waitx #6 drvh #P_PSRAM_CLK ret '------------------------------------------------------------ ' UART Send Character '------------------------------------------------------------ uart_tx_char add tmp5, "0" wypin tmp5, #TX_PIN call #start_flush_tx ret start_flush_tx rdpin pr2, #TX_PIN wc if_c jmp #start_flush_tx ret endprog '------------------------------------------------------------ ' Variable Registers '------------------------------------------------------------ tmp0 res 1 tmp1 res 1 tmp2 res 1 tmp3 res 1 tmp4 res 1 tmp5 res 1