Using the Espressif PSRAM64H IC with an STM32G431

The PSRAM64H is a 3.3V 8MByte RAM device that is accessed using SPI (datasheet here). It can operate at speeds of up to 133MHz (though 84MHz is a more realistic top-end value). I’m using it at 5MHz just so that my knock-off cheap logic analyser can keep up with it.

The picture above shows my test layout. The PSRAM64H is mounted on a breakout board so that it can be used in breadboard with the STM32G431.

The test code is shown below. It uses SPI2 on the STM32 device. After initialization, the main loop reads the chip ID and writes 256 bytes to PSRAM starting at address 0x123456. It then reads back data from the same address. If the received data does not match the transmitted data then the LED is turned on.

// Program to interact with the PSRAM64H IC from Espressif


/* IO LIST 
 * Will use simple SPI (not QSPI) in this example.
 * PSRAM64H         STM32G431
 * CE#             SPI2 NSS  Pin 2 (PF0)   = AF5
 * CLK             SPI2 SCK  Pin 3 (PF1)   = AF5
 * SI/SIO(0) MOSI  SPI2 MOSI Pin 21 (PA11) = AF5
 * SO/SIO(1) MISO  SPI2 MISO Pin 20 (PA10) = AF5
 * 
 * LED             Pin 5 (PA0) 
*/

#include <stdint.h>
#include "../include/STM32G431xx.h"
void enablePullUp(GPIO_Type *Port, uint32_t BitNumber)
{
	Port->PUPDR = Port->PUPDR &~(3u << BitNumber*2); // clear pull-up resistor bits
	Port->PUPDR = Port->PUPDR | (1u << BitNumber*2); // set pull-up bit
}
void pinMode(GPIO_Type *Port, uint32_t BitNumber, uint32_t Mode)
{
	/*
        Modes : 00 = input
                01 = output
                10 = special function
                11 = analog mode
	*/
	uint32_t mode_value = Port->MODER;
	Mode = Mode << (2 * BitNumber);
	mode_value = mode_value & ~(3u << (BitNumber * 2));
	mode_value = mode_value | Mode;
	Port->MODER = mode_value;
}
void selectAlternateFunction (GPIO_Type *Port, uint32_t BitNumber, uint32_t AF)
{
    // The alternative function control is spread across two 32 bit registers AFR[0] and AFR[1]
    // There are 4 bits for each port bit.
    if (BitNumber < 8)
    {
        Port->AFR[0] &= ~(0x0f << (4*BitNumber));
        Port->AFR[0] |= (AF << (4*BitNumber));
    }
    else
    {
        BitNumber = BitNumber - 8;
        Port->AFR[1] &= ~(0x0f << (4*BitNumber));
        Port->AFR[1] |= (AF << (4*BitNumber));
    }
}
void spi_startTransaction(void)
{
	SPI2->CR1 |= (1 << 6); // Enable SPI (SPE = 1)

}
void spi_stopTransaction(void)
{	
	volatile unsigned Timeout = 1000;    
	while (SPI2->SR & ((1 << 12) + (1 << 11)) );     // wait for fifo to empty
	while (((SPI2->SR & (1 << 0))!=0)&&(Timeout--)); // Wait for RXNE
	Timeout = 1000;    
	while (((SPI2->SR & (1 << 1))==0)&&(Timeout--)); // Wait for TXE
	Timeout = 1000;    
	while (((SPI2->SR & (1 << 7))!=0)&&(Timeout--)); // Wait for Busy		
	SPI2->CR1 &= ~(1 << 6); // Disable SPI (SPE = 0)
		
	while((GPIOF->IDR & (1 << 0))==0); // wait for NSS to go high
	
}
uint8_t spi_transfer(uint8_t data)
{	

    *((uint8_t*)&SPI2->DR) = data;        		
	while (((SPI2->SR & (1 << 7))!=0));// Wait for Busy			
	return *((uint8_t*)&SPI2->DR);
}
void delay(uint32_t dly)
{
    while(dly--);
}
void writePSRAM(uint32_t address, void *data, uint32_t nbytes)
{
    uint8_t b;
    spi_startTransaction();
    spi_transfer(0x02);
    b=address>>16;
    spi_transfer(b);
    b=(address>>8)&0xff;
    spi_transfer(b);
    b=(address)&0xff;
    spi_transfer(b);
    while(nbytes--)
    {
        b=*((uint8_t*)data);
        spi_transfer(b);
        data++;
    }
    spi_stopTransaction();
}
void readPSRAM(uint32_t address, void *data, uint32_t nbytes)
{
    uint8_t b;
    spi_startTransaction();
    spi_transfer(0x03);
    b=address>>16;
    spi_transfer(b);
    b=(address>>8)&0xff;
    spi_transfer(b);
    b=(address)&0xff;
    spi_transfer(b);
    while(nbytes--)
    {
        b=spi_transfer(0xff);
        *((uint8_t*)data)=b;
        data++;
    }
    spi_stopTransaction();
    
}
uint32_t readIDPSRAM(void)
{
    uint32_t id;
    spi_startTransaction();
    spi_transfer(0x9f);
    spi_transfer(0xff);
    spi_transfer(0xff);
    spi_transfer(0xff);
    id=spi_transfer(0xff);
    id=id<<8;
    id=id+spi_transfer(0xff);
    spi_stopTransaction();
    return id;
}
uint8_t data_out[2048];    
uint8_t data_in[2048];
uint32_t chip_id;
int main()
{   
    uint32_t count,drain;

    uint32_t error_count;
    RCC->AHB2ENR |= (1 << 0) | (1 << 5); // enable Port A and Port F
    pinMode(GPIOA,0,1);
    pinMode(GPIOA,10,2);
    pinMode(GPIOA,11,2);
    pinMode(GPIOF,0,2);
    pinMode(GPIOF,1,2);
    selectAlternateFunction(GPIOA,10,5);
    selectAlternateFunction(GPIOA,11,5);
    selectAlternateFunction(GPIOF,0,5);
    selectAlternateFunction(GPIOF,1,5);
    RCC->APB1ENR1 |= (1 << 14); // enable SPI2
    // set port bits up as high speed outputs
    GPIOA->OSPEEDR |= (3 << 2*10) + (3 << 2*11);
    GPIOF->OSPEEDR |= (3 << 0) + (3 << 2*1);
    drain = SPI1->SR;				// dummy read of SR to clear MODF	
	// enable SSM, set SSI, enable SPI, PCLK/2, MSB First Master, Clock = 1 when idle
	// Will use hardware slave management
	SPI2->CR1 = (1 << 5) + (1 << 2)+ (1 << 1) + (1 << 0); // Master mode, about 5MHz.  CPHA=CPOL=1	
	SPI2->CR2 = (1 << 12) + (1 << 10) + (1 << 9) + (1 << 8) + (1 << 2); 	// SS output enabled, 8 bit mode
    
    for (count=0;count<2048;count++)
        data_out[count]=count;
    while(1)
    {
        
        chip_id=readIDPSRAM();
        error_count=0;
        writePSRAM(0x123456,data_out,2048);
        readPSRAM(0x123456,data_in,2048);
        for (count=0;count<2020;count++)
        {
            if (data_in[count]!=data_out[count])
            {
                error_count++;
            }
        }
        if (error_count==0) 
        {
            GPIOA->ODR &= ~1;            
        }
        else
        {
            GPIOA->ODR |= 1;
        }
        delay(1000000);
        
    }
}

The SPI waveforms for the read ID transaction are shown below. Data reads and writes were successful at 5.33MHz. In a later post I will try bumping this speed up a bit.

Leave a comment