数码之家

 找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 57|回复: 1

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

[复制链接]
发表于 12 小时前 | 显示全部楼层 |阅读模式
第二十六章 HyperRAM实验



STM32H7R7L8H6H自带620K字节的SRAM对一般应用来说,已经足够了不过在一些对内存要求高的场合,STM32H7R7自带的这些内存就不够用了。比如使用LTDC驱动RGB 屏、跑算法或者跑GUI等,就可能不太够用,所以STM32H7R7开发板根据不同版本板载了两种不同容量规格的HyperRAM,分别为8MB32MB,满足大内存使用的需求。
本章,我们将使用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阵列的刷新操作。STM32H7R7XSPI接口支持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位宽、一个时钟周期的数据传输。
命令、地址和数据信息通过8DQ[7:0]信号传输。时钟(CK)用于从接收到的DQ信号上对信息进行捕获。
HyperRAM信号线如表26.1.1.1所示:
信号线
类型
说明
CS#
输入
片选信号,总线事务由高到低转换发起。
CK
输入
时钟使能,禁止时钟时,HyperRAM会进入自刷新模式
DQ[7:0]
输入/输出
在读写事务中,命令、地址和数据信息通过这些信号传递
RWDS
输入/输出
RWDS高表示额外延迟,低表示没有额外延迟
RESET#
输入,内部上拉
当低电平时,从设备将自我初始化并返回备用状态
VCC
电源供应
用于提供CK/CK#,CS#,RESET#,DQ[7:0]RWD内部电路和存储阵列的输入缓冲器
VCCQ
电源供应
用于提供DQ[7:0]RWDS的输出缓冲器
VSS
电源供应
VSS
VSSQ
电源供应
VSSQ的地
RFU
不连接
内部可以连接,也可以不连接
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=16M16位字)=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所示:
寄存器
32位系统地址
读取ID寄存器0
0x00000000
读取ID寄存器1
0x00000001
读取配置寄存器0
0x00000800
写入配置寄存器0
0x00000800
读取配置寄存器1
0x00000801
写入配置寄存器1
0x00000801
26.1.1.2 HyperRAM寄存器32位系统地址
可以看到,读取配置寄存器0和写入配置寄存器032位系统地址都是0x00000800,因此,外部主机可以通过0x00000800对配置寄存器0进行读取和写入的操作,同理,外部主机可通过0x00000801对配置寄存器1进行读取和写入操作。
但是需要特别注意的是,如果在STM32H7R7上直接使用表26.1.1.2中的32位系统地址访问HyperRAM是无法得到想要的结果的,其原因在STM32H7R7的参考手册中有说明,如下图所示:
26.1.1.5 STM32H7R7 XSPI访问HyperRAM注意事项
如图26.1.1.5所示,某些存储器会规定每个地址对应116位的值,但是STM32H7R7XSPI规定每个地址对应18位的值,因此,在STM32H7R7使用XSPI访问HyperRAM的寄存器时,需要将对应寄存器的系统地址在软件上做乘以2的处理。
了解了寄存器的地址及其注意事项后,下面来看看各个寄存器的作用。
读取ID寄存器0和读取ID寄存器1,这两个寄存器用于读取HyperRAMID,如下图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可知,HyperRAMID0因该为0x0E86HyperRAMID1应该为0x0001
配置寄存器0可用于配置HyperRAM的深度睡眠模式、驱动能力、初始化时序、突发长度等,在本章实验中用不到配置寄存器0,因此这里不过多介绍。
配置寄存器1可用于配置HyperRAM的时钟模式、混合睡眠模式、阵列刷新模式、刷新间隔等参数,在本章实验中主要使用该寄存器配置使能HyperRAM的差分时钟模式,其余的内容本章不过多介绍。HyperRAM的差分时钟模式由配置寄存器1bit6进行配置,当bit6被置1时,HyperRAM使用单端时钟模式,这也是HyperRAM复位后的默认模式,当bit6被清0时,HyperRAM使用差分始终模式,差分时钟模式有着更好的抗干扰能力,但需要多使用1个引脚和外部主机的支持。
26.1.2 XSPI接口简介
HyperRAM的驱动使用的是STM32H7R7XSPI接口,对于该接口的详细介绍,请读者跳转到后续的《XSPI实验》小节中进行查阅,本小节不重复介绍。
26.2 硬件设计
本章实验功能简介:开机后,显示提示信息,然后按下 KEY0 按键,即测试外部 SDRAM容量大小并显示在 LCD 上。按下 KEY1 按键,即显示预存在外部 SDRAM 的数据。DS0 指示程序运行状态。
本实验用到的硬件资源有:
1) 指示灯 DS0
2KEY0 KEY1 按键
3) 串口
4TFTLCD 模块
5W958D8NBY
这些我们都已经介绍过( W958D8NBY STM32H7R7 的各 IO 对应关系,请参考光盘原理图),接下来我们开始软件设计。
26.3 程序设计 26.3.1 程序解析1. HyperRAM驱动代码
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。HyperRAM驱动源码包括两个文件:hyperram.chyperram.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();

    /* 检查HyperRAMID */
    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、校验HyperRAMID、配置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 检查HyperRAMID
* @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;
}
前面的章节中也说到,配置寄存器1bit6可用于配置HyperRAM设备的时钟模式,该函数就是通过读-修改-写的方式将HyperRAM配置寄存器1bit60来使能HyperRAM设备的差分时钟模式的。
最后便是配置XSPI来开启内存映射模式,在开启内存映射模式后,STM32H7R7访问HyperRAM便可像访问内存SRAM一样通过寻址的方式进行访问,我们开发板上的HyperRAM连接到STM32H7R7XSPI2接口上,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;
    }

    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, 7, 16, BLUE);
                printf("buffer[%d]: %d\r\n", i, buffer);
            }
        }
        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 串口观看测试结果

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
发表于 10 小时前 | 显示全部楼层
游客请登录后查看回复内容
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

APP|手机版|小黑屋|关于我们|联系我们|法律条款|数码之家-技术知识分享平台

闽公网安备35020502000485号

闽ICP备2021002735号-2

GMT+8, 2026-9-1 21:20 , Processed in 0.202800 second(s), 10 queries , Gzip On, Redis On.

Powered by Discuz!

© MyDigit.Net Since 2006

快速回复 返回顶部 返回列表