Issue Interfacing with AP memory.
- Greeting
- what want to achive
- The current state.
- What we have tried.
Hey there Community,
We are running a project were we need to interface with an AP memory on a custom made board. We intend on using the PSRAM for image capture for a 5mpx image dumped by the dcmipp peripheral. To test the PSRAM separately, we came up with a test to confirm that we get no errors while interacting with it.
We are failing to communicate with the memory at frequencies higher than 100Mhz, and seeing a large number of errors at 100Mhz and zero errors at 50Mhz. This is a big hinderance because the memory should be able to be set all the way to 250Mhz and we do also see these errors appear in the images taken.
We have attached the specifications we are working with as well as the configurations we are using.
Specifications
- STM32N657X0HxQ
- APS256XXN-OBx9
- XSPI interface (x16) (xspi1)
Other Specifications
- Memory mapped mode
- Schematic similar to the STM32N6 DK.
Below are the materials used to aid with the configuration.
- AP memory datasheet.
- Getting started with Octo-SPI, Hexadeca-SPI, and XSPI interfaces on STM32 MCUs.
- Cube MX example (XSPI_PSRAM_MemoryMapped) for the n6 series
Our Current configurations.
XSPI interface configuration code.
...
/* Reset Command **************************************************************/
/**
* @brief Reset the memory
* @PAram Ctx Component object pointer
* @retval error status
*/
int32_t APS256XX_Reset(XSPI_HandleTypeDef *Ctx)
{
XSPI_RegularCmdTypeDef sCommand = {0};
/* Initialize the command */
sCommand.OperationType = HAL_XSPI_OPTYPE_COMMON_CFG;
sCommand.InstructionMode = HAL_XSPI_INSTRUCTION_8_LINES;
sCommand.InstructionWidth = HAL_XSPI_INSTRUCTION_8_BITS;
sCommand.InstructionDTRMode = HAL_XSPI_INSTRUCTION_DTR_DISABLE;
sCommand.Instruction = APS256XX_RESET_CMD;
sCommand.AddressMode = HAL_XSPI_ADDRESS_8_LINES;
sCommand.AddressWidth = HAL_XSPI_ADDRESS_24_BITS;
sCommand.AddressDTRMode = HAL_XSPI_ADDRESS_DTR_DISABLE;
sCommand.Address = 0;
sCommand.AlternateBytesMode = HAL_XSPI_ALT_BYTES_NONE;
sCommand.DataMode = HAL_XSPI_DATA_NONE;
sCommand.DataLength = 0;
sCommand.DummyCycles = 0;
sCommand.DQSMode = HAL_XSPI_DQS_DISABLE;
#if defined (XSPI_CCR_SIOO)
sCommand.SIOOMode = HAL_XSPI_SIOO_INST_EVERY_CMD;
#endif
/* Configure the command */
if (HAL_XSPI_Command(Ctx, &sCommand, HAL_XSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
{
return APS256XX_ERROR;
}
/* Need to wait tRST */
HAL_Delay(1);
return 0/*APS256XX_OK*/;
}
....
void MX_XSPI1_Init(void)
{
/* USER CODE BEGIN XSPI1_Init 0 */
/* USER CODE END XSPI1_Init 0 */
XSPIM_CfgTypeDef sXspiManagerCfg = {0};
/* USER CODE BEGIN XSPI1_Init 1 */
/* USER CODE END XSPI1_Init 1 */
hxspi1.Instance = XSPI1;
hxspi1.Init.FifoThresholdByte = 4;
hxspi1.Init.MemoryMode = HAL_XSPI_SINGLE_MEM;
hxspi1.Init.MemoryType = HAL_XSPI_MEMTYPE_APMEM_16BITS;
hxspi1.Init.MemorySize = HAL_XSPI_SIZE_256MB;
hxspi1.Init.ChipSelectHighTimeCycle = 5;
hxspi1.Init.FreeRunningClock = HAL_XSPI_FREERUNCLK_DISABLE;
hxspi1.Init.ClockMode = HAL_XSPI_CLOCK_MODE_0;
hxspi1.Init.WrapSize = HAL_XSPI_WRAP_NOT_SUPPORTED;
hxspi1.Init.ClockPrescaler = 0;
hxspi1.Init.SampleShifting = HAL_XSPI_SAMPLE_SHIFT_NONE;
hxspi1.Init.DelayHoldQuarterCycle = HAL_XSPI_DHQC_DISABLE;
hxspi1.Init.ChipSelectBoundary = HAL_XSPI_BONDARYOF_2KB;
hxspi1.Init.MaxTran = 0;
hxspi1.Init.Refresh = 0;
hxspi1.Init.MemorySelect = HAL_XSPI_CSSEL_NCS1;
if (HAL_XSPI_Init(&hxspi1) != HAL_OK)
{
Error_Handler();
}
sXspiManagerCfg.nCSOverride = HAL_XSPI_CSSEL_OVR_NCS1;
sXspiManagerCfg.IOPort = HAL_XSPIM_IOPORT_1;
sXspiManagerCfg.Req2AckTime = 1;
if (HAL_XSPIM_Config(&hxspi1, &sXspiManagerCfg, HAL_XSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN XSPI1_Init 2 */
APS256XX_Reset(&hxspi1);
/* USER CODE END XSPI1_Init 2 */
}
void HAL_XSPI_MspInit(XSPI_HandleTypeDef* xspiHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};
if(xspiHandle->Instance==XSPI1)
{
/* USER CODE BEGIN XSPI1_MspInit 0 */
/* XSPI power enable */
__HAL_RCC_PWR_CLK_ENABLE();
HAL_PWREx_EnableVddIO2(); // change, IO3 for XSPI2
HAL_PWREx_ConfigVddIORange(PWR_VDDIO2, PWR_VDDIO_RANGE_1V8);
/* USER CODE END XSPI1_MspInit 0 */
/** Initializes the peripherals clock
*/
PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_XSPI1;
PeriphClkInitStruct.Xspi1ClockSelection = RCC_XSPI1CLKSOURCE_IC4;
PeriphClkInitStruct.ICSelection[RCC_IC4].ClockSelection = RCC_ICCLKSOURCE_PLL1;
PeriphClkInitStruct.ICSelection[RCC_IC4].ClockDivider = 10;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
{
Error_Handler();
}
/* XSPI1 clock enable */
HAL_RCC_XSPIM_CLK_ENABLED++;
if(HAL_RCC_XSPIM_CLK_ENABLED==1){
__HAL_RCC_XSPIM_CLK_ENABLE();
}
__HAL_RCC_XSPI1_CLK_ENABLE();
__HAL_RCC_GPIOP_CLK_ENABLE();
__HAL_RCC_GPIOO_CLK_ENABLE();
/**XSPI1 GPIO Configuration
PP7 ------> XSPIM_P1_IO7
PP6 ------> XSPIM_P1_IO6
PP0 ------> XSPIM_P1_IO0
PP4 ------> XSPIM_P1_IO4
PP1 ------> XSPIM_P1_IO1
PP15 ------> XSPIM_P1_IO15
PP5 ------> XSPIM_P1_IO5
PP12 ------> XSPIM_P1_IO12
PP3 ------> XSPIM_P1_IO3
PP2 ------> XSPIM_P1_IO2
PP13 ------> XSPIM_P1_IO13
PO2 ------> XSPIM_P1_DQS0
PP11 ------> XSPIM_P1_IO11
PP8 ------> XSPIM_P1_IO8
PP14 ------> XSPIM_P1_IO14
PO3 ------> XSPIM_P1_DQS1
PO0 ------> XSPIM_P1_NCS1
PP9 ------> XSPIM_P1_IO9
PP10 ------> XSPIM_P1_IO10
PO4 ------> XSPIM_P1_CLK
*/
GPIO_InitStruct.Pin = GPIO_PIN_7|GPIO_PIN_6|GPIO_PIN_0|GPIO_PIN_4
|GPIO_PIN_1|GPIO_PIN_15|GPIO_PIN_5|GPIO_PIN_12
|GPIO_PIN_3|GPIO_PIN_2|GPIO_PIN_13|GPIO_PIN_11
|GPIO_PIN_8|GPIO_PIN_14|GPIO_PIN_9|GPIO_PIN_10;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF9_XSPIM_P1;
HAL_GPIO_Init(GPIOP, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_2|GPIO_PIN_3|GPIO_PIN_0|GPIO_PIN_4;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF9_XSPIM_P1;
HAL_GPIO_Init(GPIOO, &GPIO_InitStruct);
/* XSPI1 interrupt Init */
HAL_NVIC_SetPriority(XSPI1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(XSPI1_IRQn);
/* USER CODE BEGIN XSPI1_MspInit 1 */
/* USER CODE END XSPI1_MspInit 1 */
}
....
void MX_EXTMEM_MANAGER_Init(void)
{
....
/* EXTMEMORY_2 */
extmem_list_config[1].MemType = EXTMEM_PSRAM;
extmem_list_config[1].Handle = (void*)&hxspi1;
extmem_list_config[1].ConfigType = EXTMEM_LINK_CONFIG_16LINES;
extmem_list_config[1].PsramObject.psram_public.MemorySize = HAL_XSPI_SIZE_256MB;
extmem_list_config[1].PsramObject.psram_public.FreqMax = 100 * 1000000u;
extmem_list_config[1].PsramObject.psram_public.NumberOfConfig = 3u;
/* Config */
extmem_list_config[1].PsramObject.psram_public.config[0].WriteMask = 0x40u;
extmem_list_config[1].PsramObject.psram_public.config[0].WriteValue = 0x40u;
extmem_list_config[1].PsramObject.psram_public.config[0].REGAddress = 0x08u;
extmem_list_config[1].PsramObject.psram_public.config[1].WriteMask = 0x1Cu;
extmem_list_config[1].PsramObject.psram_public.config[1].WriteValue = 0x04u;
extmem_list_config[1].PsramObject.psram_public.config[1].REGAddress = 0x00u;
extmem_list_config[1].PsramObject.psram_public.config[2].WriteMask = 0xE0u;
extmem_list_config[1].PsramObject.psram_public.config[2].WriteValue = 0x80u;
extmem_list_config[1].PsramObject.psram_public.config[2].REGAddress = 0x04u;
/* Memory command configuration */
extmem_list_config[1].PsramObject.psram_public.ReadREG = 0x40u;
extmem_list_config[1].PsramObject.psram_public.WriteREG = 0xC0u;
extmem_list_config[1].PsramObject.psram_public.ReadREGSize = 2u;
extmem_list_config[1].PsramObject.psram_public.REG_DummyCycle = 4u;
extmem_list_config[1].PsramObject.psram_public.Write_command = 0xA0u;
extmem_list_config[1].PsramObject.psram_public.Write_DummyCycle = 3u;
extmem_list_config[1].PsramObject.psram_public.Read_command = 0x20u;
extmem_list_config[1].PsramObject.psram_public.WrapRead_command = 0x00u;
extmem_list_config[1].PsramObject.psram_public.Read_DummyCycle = 3u;
EXTMEM_Init(EXTMEMORY_2, HAL_RCCEx_GetPeriphCLKFreq(RCC_PERIPHCLK_XSPI1));
/* USER CODE BEGIN MX_EXTMEM_Init_PostTreatment */
// Map the memory
....
if (EXTMEM_OK != EXTMEM_MemoryMappedMode(EXTMEMORY_2 , EXTMEM_ENABLE))
{
Error_Handler();
}
/* USER CODE END MX_EXTMEM_Init_PostTreatment */
}
The PSRAM Test code.
The test aims to sweep over the entire memory doing a:
- An address test.
- A walking bit test.
- and lastly an pattern test.
And return the number of Errors or a success message over USART.
#include "main.h"
#include <stdio.h>
#include <stdint.h>
#ifdef PSRAM_TEST
/* ===== CONFIG ===== */
#define KB (1024U)
#define MB (1024U * 1024U)
#define PSRAM_SIZE_BYTES (32U * MB)
#define PSRAM_SIZE_WORDS (PSRAM_SIZE_BYTES / 2U) /* 16M words */
#define PAGE_SIZE_WORDS (1024U) /* 1K words per page in X16 mode */
#define PAGE_SIZE_BYTES (PAGE_SIZE_WORDS * 2U) /* 2KB per page */
/* BASE_ADDR is now 16-bit pointer */
#define BASE_ADDR ((volatile uint16_t *)XSPI1_BASE)
/* ===== GLOBAL ===== */
static uint32_t errorCount = 0;
/* ===== UTIL ===== */
static void report_error(uint32_t word_idx, uint16_t expected, uint16_t actual) {
// printf("ERR @word[0x%08lX] byte_addr=0x%08lX: exp=0x%04X got=0x%04X\r\n",
// word_idx, word_idx * 2U, expected, actual);
errorCount++;
}
/* =========================================================
1. ADDRESS LINE TEST
========================================================= */
static void psram_address_test(void) {
printf("\r\n[Address Test]\r\n\r\n");
/* max offset in words */
uint32_t max_offset = PSRAM_SIZE_WORDS - 1U;
BASE_ADDR[0] = 0xAAAA;
for (uint32_t offset = 1U; offset <= max_offset; offset <<= 1U) {
BASE_ADDR[offset] = 0x5555;
printf("Testing address line: 0x%08lX (byte addr: 0x%08lX)\r\n",
offset, offset * 2U);
if (BASE_ADDR[0] != 0xAAAA) {
printf("Address fault at word offset: 0x%08lX\r\n", offset);
errorCount++;
}
}
HAL_Delay(1);
}
/* =========================================================
2. WALKING BIT TEST (DATA LINES)
========================================================= */
static void psram_walking_bit_test(void) {
printf("\r\n[Walking Bit Test]\r\n\r\n");
/* 16 bits now so walk all 16 bits */
for (uint8_t bit = 0U; bit < 16U; bit++) {
uint16_t pattern = (uint16_t)(1U << bit);
printf("\r\nTesting bit idx: %u", bit);
printf("\r\n ---- normal pattern ----\r\n\r\n");
for (uint32_t i = 0U; i < PSRAM_SIZE_WORDS; i++) {
BASE_ADDR[i] = pattern;
}
HAL_Delay(1);
for (uint32_t i = 0U; i < PSRAM_SIZE_WORDS; i++) {
uint16_t actual = BASE_ADDR[i];
if (actual != pattern) {
report_error(i, pattern, actual);
}
}
printf("\r\n ---- inverse pattern ----\r\n\r\n");
uint16_t inv_pattern = (uint16_t)(~(1U << bit));
for (uint32_t i = 0U; i < PSRAM_SIZE_WORDS; i++) {
BASE_ADDR[i] = inv_pattern;
}
HAL_Delay(1);
for (uint32_t i = 0U; i < PSRAM_SIZE_WORDS; i++) {
uint16_t actual = BASE_ADDR[i];
if (actual != inv_pattern) {
report_error(i, inv_pattern, actual);
}
}
}
}
/* =========================================================
3. PATTERN TEST
========================================================= */
static void psram_pattern_test(void) {
printf("\r\n[Pattern Test]\r\n\r\n");
/* ---- write ---- */
for (uint32_t i = 0U; i < PSRAM_SIZE_WORDS; i++) {
/* pattern uses both bytes of the 16-bit word */
uint16_t pattern = (uint16_t)(
((i & 0xFFU) ^ ((i >> 8U) & 0xFFU)) | /* low byte */
((((i + 1U) & 0xFFU) ^ ((i >> 7U) & 0xFFU)) << 8U) /* high byte */
);
BASE_ADDR[i] = pattern;
}
HAL_Delay(1);
/* ---- read ---- */
for (uint32_t i = 0U; i < PSRAM_SIZE_WORDS; i++) {
uint16_t expected = (uint16_t)(
((i & 0xFFU) ^ ((i >> 8U) & 0xFFU)) |
((((i + 1U) & 0xFFU) ^ ((i >> 7U) & 0xFFU)) << 8U)
);
uint16_t actual = BASE_ADDR[i];
if (actual != expected) {
report_error(i, expected, actual);
}
}
}
/* =========================================================
MAIN TEST ENTRY
========================================================= */
void psram_test(void) {
printf("\r\n==== PSRAM TEST START ====\r\n");
printf("Mode: X16 | Size: %lu MB | Pages: %lu x %u bytes\r\n",
PSRAM_SIZE_BYTES / MB,
PSRAM_SIZE_BYTES / PAGE_SIZE_BYTES,
PAGE_SIZE_BYTES);
errorCount = 0;
psram_address_test();
psram_walking_bit_test();
psram_pattern_test();
if (errorCount == 0) {
printf("\r\n==== SUCCESS: NO ERRORS ====\r\n");
} else {
printf("\r\n==== FAIL: %lu ERRORS ====\r\n", errorCount);
}
}
#endifOrder of function calls
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_RAMCFG_Init();
MX_I2C1_Init();
MX_USART1_UART_Init();
MX_DCMIPP_Init();
MX_XSPI2_Init();
MX_XSPI1_Init();
MX_JPEG_Init();
MX_FileX_Init();
MX_UART4_Init();
MX_EXTMEM_MANAGER_Init();
/* USER CODE BEGIN 2 */
SystemIsolation_Config();
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
#ifdef PSRAM_TEST
psram_test();
while(1){
if(reRunpsram_test){
//Rerun the test
reRunpsram_test = 0;
psram_test();
}
}
#endif
...The XSPI input clock is 200MHz.
Observation
- The psram test, runs with variable number of error counts in the thousands on each run but in a similar range.
What has been tried (In regards to removing error count).
- We experimented at different frequencies while setting up the memory to utilize the correct latency as per the frequency we intended to communicate.
The system fails if we request to operate the PSRAM at max frequencies above 100MHz in the MX_EXTMEM_MANAGER_Init and lose connection to the debug session on pausing to investigate
extmem_list_config[1].PsramObject.psram_public.MemorySize = HAL_XSPI_SIZE_256MB;
extmem_list_config[1].PsramObject.psram_public.FreqMax = 200 * 1000000u;
extmem_list_config[1].PsramObject.psram_public.NumberOfConfig = 3u;
/* Config */
extmem_list_config[1].PsramObject.psram_public.config[0].WriteMask = 0x40u;
extmem_list_config[1].PsramObject.psram_public.config[0].WriteValue = 0x40u;
extmem_list_config[1].PsramObject.psram_public.config[0].REGAddress = 0x08u;
extmem_list_config[1].PsramObject.psram_public.config[1].WriteMask = 0x1Cu;
extmem_list_config[1].PsramObject.psram_public.config[1].WriteValue = 0x10u;
extmem_list_config[1].PsramObject.psram_public.config[1].REGAddress = 0x00u;
extmem_list_config[1].PsramObject.psram_public.config[2].WriteMask = 0xE0u;
extmem_list_config[1].PsramObject.psram_public.config[2].WriteValue = 0x20u;
extmem_list_config[1].PsramObject.psram_public.config[2].REGAddress = 0x04u;
/* Memory command configuration */
extmem_list_config[1].PsramObject.psram_public.ReadREG = 0x40u;
extmem_list_config[1].PsramObject.psram_public.WriteREG = 0xC0u;
extmem_list_config[1].PsramObject.psram_public.ReadREGSize = 2u;
extmem_list_config[1].PsramObject.psram_public.REG_DummyCycle = 4u;
extmem_list_config[1].PsramObject.psram_public.Write_command = 0xA0u;
extmem_list_config[1].PsramObject.psram_public.Write_DummyCycle = 6u;
extmem_list_config[1].PsramObject.psram_public.Read_command = 0x20u;
extmem_list_config[1].PsramObject.psram_public.WrapRead_command = 0x00u;
extmem_list_config[1].PsramObject.psram_public.Read_DummyCycle = 6u;However at Max Freq of 66MHz, we are able to see No Errors returned in the PSRAM test. Though this gives us a distorted image when we set the dcmipp to dump with a memory running at such a low frequency.
extmem_list_config[1].PsramObject.psram_public.MemorySize = HAL_XSPI_SIZE_256MB;
extmem_list_config[1].PsramObject.psram_public.FreqMax = 50 * 1000000u;
extmem_list_config[1].PsramObject.psram_public.NumberOfConfig = 3u;
/* Config */
extmem_list_config[1].PsramObject.psram_public.config[0].WriteMask = 0x40u;
extmem_list_config[1].PsramObject.psram_public.config[0].WriteValue = 0x40u;
extmem_list_config[1].PsramObject.psram_public.config[0].REGAddress = 0x08u;
extmem_list_config[1].PsramObject.psram_public.config[1].WriteMask = 0x1Cu;
extmem_list_config[1].PsramObject.psram_public.config[1].WriteValue = 0x00u;
extmem_list_config[1].PsramObject.psram_public.config[1].REGAddress = 0x00u;
extmem_list_config[1].PsramObject.psram_public.config[2].WriteMask = 0xE0u;
extmem_list_config[1].PsramObject.psram_public.config[2].WriteValue = 0x00u;
extmem_list_config[1].PsramObject.psram_public.config[2].REGAddress = 0x04u;
/* Memory command configuration */
extmem_list_config[1].PsramObject.psram_public.ReadREG = 0x40u;
extmem_list_config[1].PsramObject.psram_public.WriteREG = 0xC0u;
extmem_list_config[1].PsramObject.psram_public.ReadREGSize = 2u;
extmem_list_config[1].PsramObject.psram_public.REG_DummyCycle = 4u;
extmem_list_config[1].PsramObject.psram_public.Write_command = 0xA0u;
extmem_list_config[1].PsramObject.psram_public.Write_DummyCycle = 2u;
extmem_list_config[1].PsramObject.psram_public.Read_command = 0x20u;
extmem_list_config[1].PsramObject.psram_public.WrapRead_command = 0x00u;
extmem_list_config[1].PsramObject.psram_public.Read_DummyCycle = 2u;We reduced the drive strength from the default 25 ohms to 50 ohms, this had no effect.
extmem_list_config[1].PsramObject.psram_public.config[0].WriteMask = 0x41u;
extmem_list_config[1].PsramObject.psram_public.config[0].WriteValue = 0x41u;
extmem_list_config[1].PsramObject.psram_public.config[0].REGAddress = 0x08u;
- Lowered and increased the page boundary and refresh values. These did have a slight effect, but did not clear all the errors.
hxspi1.Init.ChipSelectBoundary = ..;
hxspi1.Init.Refresh = ..;What we need help with
- What configurations are available when interacting with AP memory that we haven't tried or we got wrong that could reduce the errors in the PSRAM test, as these errors appear in the images we take in a different test.
- There is a section on calibration in the application note AN5050 that speaks of an automated PHY calibration process for the STM32N6, that is enabled when the PRESCALER is set and the BUSY flag is set. To our understanding, these appear to be already taking place in the EXTMEM_Init. Is this understanding correct or do we need more steps to start the calibration.
