ITPub博客

首页 > 嵌入式/内核开发 > 嵌入式/内核开发 > 《STM32H7R7开发指南 V1.1 》第二十六章 HyperRAM实验

《STM32H7R7开发指南 V1.1 》第二十六章 HyperRAM实验

原创 嵌入式/内核开发 作者:正点原子 时间:2025-08-22 10:10:43 0 删除 编辑

第二十六章 HyperRAM实验


       STM32H7R7L8H6H自带620K字节的SRAM,对一般应用来说,已经足够了,不过在一些对内存要求高的场合,STM32H7R7自带的这些内存就不够用了。比如使用LTDC驱动RGB 屏、跑算法或者跑GUI等,就可能不太够用,所以STM32H7R7开发板根据不同版本板载了两种不同容量规格的HyperRAM,分别为8MB和32MB,满足大内存使用的需求。

       本章,我们将使用STM32H7R7来驱动开发板板载的HyperRAM,并通过XSPI的内存映射功能,将HyperRAM作为STM32H7R7内存来使用,并测试其容量。本章分为如下几个部分:

       26.1 HyperRAM简介

       26.2 硬件设计

       26.3 程序设计

       26.4 下载验证


        26.1 HyperRAM简介

       本章我们将通过经过STM32H7R7的XSPI接口,来驱动开发板板载的HyperRAM芯片,本节我们将重点介绍HyperRAM相关知识点,关于XSPI的相关知识点,会在后续的章节中进行介绍。


       26.1.1 HyperRAM简介

       HyperRAM是具备HyperBus接口的高速CMOS自刷新DRAM。其存储阵列的内部结构类似于DRAM,而外在行为则与SRAM相似,这意味着HyperRAM能够再外部主机未进行读取或写入操作时,从内部实现DRAM阵列的刷新操作。STM32H7R7的XSPI接口支持HyperBus协议,因此,我们可以外挂 HyperRAM,从而大大降低外扩内存的成本。以开发板板载的32MB容量的HyperRAM为例,其型号为W958D8NBY,其特点如下:

       l 接口:HyperBus

       l 供电电压:1.7V-2.0V

       l 最大时钟频率:250MHz

       l DDR模式下可达500MB/s

       l 支持单端时钟或差分时钟两种模式

       l 具有片选信号

       l 8位数据总线

       l 硬件复位信号

       l 读写数据选通信号

       W958D8NBY的内部结构框图如图26.1.1.1所示:


图26.1.1.1  W958D8NBY内部结构框图


       接下来,我们结合图26.1.1.1,对HyperRAM的几个重要知识点进行介绍。


       (1) 信号线

       HyperRAM使用HyperBus协议来传输,HyperBus是一种低信号计数、双数据速率(DDR)接口,可实现高速读写吞吐量。DDR协议在DQ输入/输出信号上每个时钟周期传输两个数据字节。HyperBus上的读写事务由内部HyperRAM阵列上的16位宽、一个时钟周期的数据传输。

       命令、地址和数据信息通过8个DQ[7:0]信号传输。时钟(CK)用于从接收到的DQ信号上对信息进行捕获。

       HyperRAM信号线如表26.1.1.1所示:


表26.1.1.1 HyperRAM信号线


       (2) 存储空间

       HyperRAM的存储单元是以阵列的形式排列的,阵列大小(密度)可以从行地址和列地址的系统地址位的总数来确定,这些地址由列表中的“行地址位计数”和“列地址位计数”字段表示。

       W958D8NBY 的存储结构为: 256Mb设备内的行数有32768 行,每行有64个半页,每半页有8个字, 每列有512个字(1K字节)。半页地址(Half-Page Address)也称为上列地址,Word of HP Address称为下列地址。如下图26.1.1.2和图26.1.1.3所示:


图26.1.1.2 存储空间地址映射(16位字)


图26.1.1.3 存储空间地址映射(16位字)


       HyperRAM设备的容量可由上图26.1.1.3中行和列的地址总线位数来确定,上图26.1.1.3中,Row Address使用地址总线为A23~A9,共15个行地址位,而Column Address使用地址总线为A8~A0,共9个列地址位,行地址位和列地址位一共为24个地址位,那么HyperRAM设备的容量则为224=16M(16位字)=32MB。


       (3) 寄存器空间

       寄存器空间用于外部主机获取HyperRAM设备信息和配置HyperRAM设备,例如获取W958D8NBY的设备ID或配置W958D8NBY的差分时钟模式等。如下图26.1.1.4所示:


图26.1.1.4 寄存器空间地址映射


       上图中寄存器的功能如下:

       Identification Register 0:读取ID寄存器0

       Identification Register 1:读取ID寄存器1

       Configuration Register 0 Read:读取配置寄存器0

       Configuration Register 0 Write:写入配置寄存器0

       Configuration Register 1 Read:读取配置寄存器1

       Configuration Register 1 Write:写入配置寄存器1

       了解了寄存器的功能之后,那图26.1.1.4中寄存器的地址怎么看呢?图26.1.1.4中的System Address表示的就是外部主机访问HyperRAM设备时,需要访问的地址,该地址是一个32位的地址,这么一来可以由图26.1.1.4总结出各个寄存器对应的32位系统地址,如下表26.1.1.2所示:


表26.1.1.2 HyperRAM寄存器32位系统地址


       可以看到,读取配置寄存器0和写入配置寄存器0的32位系统地址都是0x00000800,因此,外部主机可以通过0x00000800对配置寄存器0进行读取和写入的操作,同理,外部主机可通过0x00000801对配置寄存器1进行读取和写入操作。

       但是需要特别注意的是,如果在STM32H7R7上直接使用表26.1.1.2中的32位系统地址访问HyperRAM是无法得到想要的结果的,其原因在STM32H7R7的参考手册中有说明,如下图所示:


图26.1.1.5 STM32H7R7 XSPI访问HyperRAM注意事项


       如图26.1.1.5所示,某些存储器会规定每个地址对应1个16位的值,但是STM32H7R7的XSPI规定每个地址对应1个8位的值,因此,在STM32H7R7使用XSPI访问HyperRAM的寄存器时,需要将对应寄存器的系统地址在软件上做乘以2的处理。

       了解了寄存器的地址及其注意事项后,下面来看看各个寄存器的作用。

       读取ID寄存器0和读取ID寄存器1,这两个寄存器用于读取HyperRAM的ID,如下图26.1.1.6和图26.1.1.7所示:


图26.1.1.6 读取ID寄存器0描述


图26.1.1.7 读取ID寄存器1描述


       由上图26.1.1.6和图26.1.1.7可知,HyperRAM的ID0因该为0x0E86,HyperRAM的ID1应该为0x0001。

       配置寄存器0可用于配置HyperRAM的深度睡眠模式、驱动能力、初始化时序、突发长度等,在本章实验中用不到配置寄存器0,因此这里不过多介绍。

       配置寄存器1可用于配置HyperRAM的时钟模式、混合睡眠模式、阵列刷新模式、刷新间隔等参数,在本章实验中主要使用该寄存器配置使能HyperRAM的差分时钟模式,其余的内容本章不过多介绍。HyperRAM的差分时钟模式由配置寄存器1的bit6进行配置,当bit6被置1时,HyperRAM使用单端时钟模式,这也是HyperRAM复位后的默认模式,当bit6被清0时,HyperRAM使用差分始终模式,差分时钟模式有着更好的抗干扰能力,但需要多使用1个引脚和外部主机的支持。


       26.1.2 XSPI接口简介

       HyperRAM的驱动使用的是STM32H7R7的XSPI接口,对于该接口的详细介绍,请读者跳转到后续的《XSPI实验》小节中进行查阅,本小节不重复介绍。


        26.2 硬件设计 

       本章实验功能简介:开机后,显示提示信息,然后按下 KEY0 按键,即测试外部 SDRAM容量大小并显示在 LCD 上。按下 KEY1 按键,即显示预存在外部 SDRAM 的数据。DS0 指示程序运行状态。

       本实验用到的硬件资源有:

       1) 指示灯 DS0

       2) KEY0 和 KEY1 按键

       3) 串口

       4) TFTLCD 模块

       5) W958D8NBY

       这些我们都已经介绍过( W958D8NBY 与 STM32H7R7 的各 IO 对应关系,请参考光盘原理图),接下来我们开始软件设计。


        26.3 程序设计 


       26.3.1 程序解析


       1. HyperRAM驱动代码 

       这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。HyperRAM驱动源码包括两个文件:hyperram.c和hyperram.h。

       hyperram.h头文件只有函数声明,就不解释了。下面我们直接解析hyperram.c的程序,该函数的实现代码如下:

/**
 * @brief 初始化HyperRAM
 * @param 无
 * @retval 初始化结果
 * @arg0:  初始化成功
 * @arg1:  初始化失败
 */
uint8_t hyperram_init(void)
{
    RCC_PeriphCLKInitTypeDef rcc_periph_clk_init_struct = {0};
    GPIO_InitTypeDef gpio_init_struct = {0};
    XSPIM_CfgTypeDef xspim_cfg_struct = {0};
    XSPI_HyperbusCfgTypeDef xspi_hyperbus_cfg_struct = {0};
    
    /* 配置XSPI时钟源 */
    rcc_periph_clk_init_struct.PeriphClockSelection = RCC_PERIPHCLK_XSPI2;
    rcc_periph_clk_init_struct.Xspi2ClockSelection = RCC_XSPI2CLKSOURCE_PLL2S;
    if (HAL_RCCEx_PeriphCLKConfig(&rcc_periph_clk_init_struct) != HAL_OK)
    {
     return 1;
    }
    
    /* 使能时钟 */
    __HAL_RCC_XSPIM_CLK_ENABLE();
    __HAL_RCC_XSPI2_CLK_ENABLE();
    __HAL_RCC_GPION_CLK_ENABLE();
    
    /* 初始化XSPIM引脚 */
gpio_init_struct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 | 
GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9
| GPIO_PIN_10 | GPIO_PIN_11;
    gpio_init_struct.Mode = GPIO_MODE_AF_PP;
    gpio_init_struct.Pull = GPIO_NOPULL;
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    gpio_init_struct.Alternate = GPIO_AF9_XSPIM_P2;
    HAL_GPIO_Init(GPION, &gpio_init_struct);
    
    /* 初始化复位引脚 */
    gpio_init_struct.Pin = GPIO_PIN_12;
    gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;
    gpio_init_struct.Pull = GPIO_PULLUP;
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(GPION, &gpio_init_struct);
    
    /* 初始化XSPI */
    g_xspi_handle.Instance = XSPI2;
    g_xspi_handle.Init.FifoThresholdByte = 4;
    g_xspi_handle.Init.MemoryMode = HAL_XSPI_SINGLE_MEM;
    g_xspi_handle.Init.MemoryType = HAL_XSPI_MEMTYPE_HYPERBUS;
    g_xspi_handle.Init.MemorySize = HAL_XSPI_SIZE_256MB;
    g_xspi_handle.Init.ChipSelectHighTimeCycle = 2;
    g_xspi_handle.Init.FreeRunningClock = HAL_XSPI_FREERUNCLK_DISABLE;
    g_xspi_handle.Init.ClockMode = HAL_XSPI_CLOCK_MODE_0;
    g_xspi_handle.Init.WrapSize = HAL_XSPI_WRAP_32_BYTES;
    g_xspi_handle.Init.ClockPrescaler = 2 - 1;
    g_xspi_handle.Init.SampleShifting = HAL_XSPI_SAMPLE_SHIFT_NONE;
    g_xspi_handle.Init.DelayHoldQuarterCycle = HAL_XSPI_DHQC_DISABLE;
    g_xspi_handle.Init.ChipSelectBoundary = HAL_XSPI_BONDARYOF_NONE;
    g_xspi_handle.Init.MaxTran = 0;
    g_xspi_handle.Init.Refresh = 0;
    g_xspi_handle.Init.MemorySelect = HAL_XSPI_CSSEL_NCS1;
    if (HAL_XSPI_Init(&g_xspi_handle) != HAL_OK)
    {
     return 1;
    }
    
    /* 配置XSPIM */
    xspim_cfg_struct.nCSOverride = HAL_XSPI_CSSEL_OVR_NCS1;
    xspim_cfg_struct.IOPort = HAL_XSPIM_IOPORT_2;
if (HAL_XSPIM_Config(&g_xspi_handle, &xspim_cfg_struct, 
HAL_XSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    {
     return 1;
    }
    
    /* 配置HyperBus参数 */
    xspi_hyperbus_cfg_struct.RWRecoveryTimeCycle = 7;
    xspi_hyperbus_cfg_struct.AccessTimeCycle = 7;
    xspi_hyperbus_cfg_struct.WriteZeroLatency = HAL_XSPI_LATENCY_ON_WRITE;
    xspi_hyperbus_cfg_struct.LatencyMode = HAL_XSPI_FIXED_LATENCY;
if (HAL_XSPI_HyperbusCfg(&g_xspi_handle, &xspi_hyperbus_cfg_struct, 
HAL_XSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    {
     return 1;
    }
    
    /* 硬件复位HyperRAM */
    hyperram_hardware_reset();
    
    /* 检查HyperRAM的ID */
    if (hyperram_check_identification() != 0)
    {
     return 1;
    }
    
    /* 配置HyperRAM差分时钟模式 */
    if (hyperram_config_differential_clock() != 0)
    {
     return 1;
    }
    
    /* 配置HyperRAM内存映射 */
    if (hyperram_memory_mapped() != 0)
    {
     return 1;
    }
    
    return 0;
}

       该部分是HyperRAM的初始化函数,可以看到,整个初始化流程主要分为四个步骤。分别为:初始化XSPI接口、复位HyperRAM、校验HyperRAM的ID、配置HyperRAM的差分时钟模式、配置XSPI的内存映射模式,其中关于XSPI的初始化和使用在后续的章节中会进行讲解,本章主要介绍HyperRAM的使用。

       复位HyperRAM的函数如下所示:

/**
 * @brief 硬件复位HyperRAM
 * @param 无
 * @retval 无
 */
static void hyperram_hardware_reset(void)
{
    HAL_GPIO_WritePin(GPION, GPIO_PIN_12, GPIO_PIN_RESET);
    HAL_Delay(1);
    HAL_GPIO_WritePin(GPION, GPIO_PIN_12, GPIO_PIN_SET);
    HAL_Delay(1);
}

       该函数比较简单,就是对HyperRAM进行引脚复位。

       接下来是校验HyperRAM的设备ID,其函数如下所示:

/**
 * @brief 检查HyperRAM的ID
 * @param 无
 * @retval 检查结果
 * @arg  0: 检查成功
 * @arg  1: 检查失败
 */
static uint8_t hyperram_check_identification(void)
{
    uint16_t data;
    
    if (hyperram_read_register(HYPERRAM_IDENTIFICATION_REGISTER_0, &data) != 0)
    {
     return 1;
    }
    
    if (data != HYPERRAM_IDENTIFICATION_0)
    {
     return 1;
    }
    
    if (hyperram_read_register(HYPERRAM_IDENTIFICATION_REGISTER_1, &data) != 0)
    {
     return 1;
    }
    
    if (data != HYPERRAM_IDENTIFICATION_1)
    {
     return 1;
    }
    
    return 0;
}

       前面的章节中也说到,读取ID寄存器0和读取ID寄存器1可以读取HyperRAM设备的ID,该函数就是读取这两个寄存器检查HyperRAM设备的ID的。

       接下来是配置HyperRAM的差分时钟模式,我们的开发板是支持HyperRAM的差分时钟模式的,如果不想使用HyperRAM的差分时钟模式,也可以跳过此步骤,使用HyperRAM默认的单端时钟模式,配置HyperRAM差分时钟模式的函数如下所示:

/**
 * @brief 配置HyperRAM差分时钟模式
 * @param 无
 * @retval 配置结果
 * @arg  0: 配置成功
 * @arg  1: 配置失败
 */
static uint8_t hyperram_config_differential_clock(void)
{
    uint16_t data;
    
    if (hyperram_read_register(HYPERRAM_CONFIGURATION_REGISTER_1, &data) != 0)
    {
     return 1;
    }
    
    data &= ~(1UL << 6);
    if (hyperram_write_register(HYPERRAM_CONFIGURATION_REGISTER_1, data) != 0)
    {
     return 1;
    }
    
    return 0;
}

       前面的章节中也说到,配置寄存器1的bit6可用于配置HyperRAM设备的时钟模式,该函数就是通过读-修改-写的方式将HyperRAM配置寄存器1的bit6清0来使能HyperRAM设备的差分时钟模式的。

       最后便是配置XSPI来开启内存映射模式,在开启内存映射模式后,STM32H7R7访问HyperRAM便可像访问内存SRAM一样通过寻址的方式进行访问,我们开发板上的HyperRAM连接到STM32H7R7的XSPI2接口上,XSPI2开启内存映射后的基地址为0x70000000,因此,只要访问0x70000000地址中的数据就相当于访问HyperRAM中的数据了。


       2. main.c代码

       在main函数之前,我们添加了hyperram_test这个函数,代码如下:

/**
 * @brief   测试HyperRAM容量
 * @param   x: LCD上显示提示信息的起始X坐标
 * @param   y: LCD上显示提示信息的起始Y坐标
 * @retval  无
 */
void hyperram_test(uint16_t x, uint16_t y)
{
    uint32_t i;
    uint32_t temp = 0;
    uint32_t sval = 0;
    
    lcd_show_string(x, y, 180, y + 16, 16, "HyperRAM Test:      KB", RED);
    
    /* 每间隔16KB写入1字数据 */
    for (i = 0; i < HYPERRAM_SIZE; i += 0x4000UL)
    {
        *(volatile uint32_t *)(HYPERRAM_BASE_ADDR + i) = temp++;
    }
    
    /* 每间隔16KB读取1字数据进行校验 */
    for (i = 0; i < HYPERRAM_SIZE; i += 0x4000UL)
    {
        temp = *(volatile uint32_t *)(HYPERRAM_BASE_ADDR + i);
        
        if ((temp != 0) && (temp <= sval))
        {
            break;
        }
        else
        {
            sval = temp;
        }
        
        lcd_show_num(x + 15 * 8, y, (temp + 1) * 16, 5, 16, BLUE);
        printf("HyperRAM Capacity: %dKB\r\n", (temp + 1) * 16);
    }
}

       该函数用于测试外部 HyperRAM的容量大小,并显示其容量。

       main函数代码如下:

int main(void)
{
    uint8_t t = 0;
    uint8_t key;
    uint32_t i;
    
    sys_mpu_config();                   /* 配置MPU */
    sys_cache_enable();                 /* 使能Cache */
    HAL_Init();                         /* 初始化HAL库 */
    sys_stm32_clock_init(300, 6, 2);    /* 配置时钟,600MHz */
    delay_init(600);                    /* 初始化延时 */
    usart_init(115200);                 /* 初始化串口 */
    led_init();                         /* 初始化LED */
    key_init();                         /* 初始化按键 */
    lcd_init();                         /* 初始化LCD */
    hyperram_init();                    /* 初始化HyperRAM */
    
    lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
    lcd_show_string(30, 70, 200, 16, 16, "HyperRAM TEST", RED);
    lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
    lcd_show_string(30, 110, 200, 16, 16, "KEY0: Test HyperRAM", RED);
    lcd_show_string(30, 130, 200, 16, 16, "KEY1: Test Data", RED);
    
    /* 准备测试数据 */
    for (i = 0; i < (sizeof(buffer) / sizeof(uint32_t)); i++)
    {
        buffer[i] = i;
    }
    
    while (1)
    {
        key = key_scan(0);
        if (key == KEY0_PRES)
        {
            /* 测试HyperRAM容量 */
            hyperram_test(30, 170);
        }
        else if (key == KEY1_PRES)
        {
            /* 读取测试数据 */
            for (i = 0; i < (sizeof(buffer) / sizeof(uint32_t)); i++)
            {
                lcd_show_num(30, 190, buffer[i], 7, 16, BLUE);
                printf("buffer[%d]: %d\r\n", i, buffer[i]);
            }
        }
        
        if (++t == 20)
        {
            t = 0;
            LED0_TOGGLE();
        }
        
        delay_ms(10);
    }
}

       此段代码,我们定义了一个超大数组 buffer,我们指定该数组定义在外部 HyperRAM 起始地址(__attribute__((section(".bss.ARM.__at_0x70000000")))),该数组用来测试外部 HyperRAM 数据的读写。注意该数组的定义方法,是我们推荐的使用外部 HyperRAM 的方法。

       另外, hyperram_test 函数和 main 函数,我们都加入了 printf 输出结果,对于没有 MCU屏模块的朋友来说,可以打开串口调试助手,观看实验结果,软件部分就给大家介绍到这里。


        26 .4 下载验证

       在代码编译成功之后,我们通过下载代码到STM32开发板,得到如图26.4.1所示:


图26.4.1 程序运行效果图


       此时,我们按下KEY0,就可以在LCD上看到内存测试的画面,同样,按下KEY1,就可以看到LCD显示存放在数组buffer里面的测试数据,如图26.4.2所示:


图26.4.2 外部HyperRAM测试界面


       对于没有MCU屏模块的朋友,我们可以用串口来检查测试结果,如图26.4.3所示:


图26.4.3 串口观看测试结果


来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70040860/viewspace-3094013/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2024-08-30

  • 博文量
    588
  • 访问量
    765088