【正点原子STM32连载】 第四十七章 SRAM实验 摘自【正点原子】STM32F103 战舰开发指南V1.2

news2025/3/1 6:36:35

1)实验平台:正点原子stm32f103战舰开发板V4
2)平台购买地址:https://detail.tmall.com/item.htm?id=609294757420
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/thread-340252-1-1.html#

第四十七章 SRAM实验

STM32F103ZET6自带了64K字节的RAM,对一般应用来说,已经足够了,不过在一些对内存要求高的场合,比如做华丽效果的GUI,处理大量数据的应用等,STM32自带的这些内存就可能不太够用了。好在嵌入式方案提供了扩展芯片RAM的方法,本章将介绍我们开发板上使用的RAM拓展方案:使用SRAM芯片,并驱动这个外部SRAM提供程序需要的一部分RAM空间,对其进行读写测试。
本章分为如下几个部分:
47.1 存储器简介
47.2 硬件设计
47.3 程序设计
47.4 下载验证

47.1 存储器简介

使用电脑时,我们会提到内存和内存条的概念,电脑维修的朋友有时候会说加个内存条电脑就不卡了。实际上对于PC来说一些情况下卡顿就是电脑同时运行的程序太多了,电脑处理速度变慢的现象。而程序是动态加载到内存中的,一种解决方法就是增加电脑的内存来增加同时可处理的程序的数量。对于单片机也是一样的,高性能有时候需要通过增加大内存来获得。内存是存储器的一种,由于微机架构设计了不同的存储器放置不同的数据,所以我们也简单来了解一下存储器。
存储器实际上是时序逻辑电路的一种,用来存放程序和数据信息。构成存储器的存储介质主要采用半导体器件和磁性材料。存储器中最小的存储单位就是一个双稳态半导体电路或一个CMOS晶体管或磁性材料的存储元,它可存储一个二进制代码。由若干个存储元组成一个存储单元,然后再由许多存储单元组成一个存储器。按不同的分类方式,存储器可以有表47.1.1所示的分类:
在这里插入图片描述

表47.1.1 存储器的分类
对于上述分类,在我们STM32编程学习中我们常常只关心按读写功能分类的ROM和RAM两种,因为嵌入式程序主要对应到这两种存储器。对于RAM,目前常见的是SRAM和DRAM,它们因工作方式不同而得名,它们主要有以下的特性,如表47.1.2所示:

SRAM	DRAM

描述 静态存储器/Static RAM,存储单元一般为锁存器,只要不掉电,信息就不会丢失 动态存储器/Dynamic RAM,利用MOS(金属氧化物半导体)电容存储电荷来储存信息,保留数据的时间很短,速度也比SRAM慢,每隔一段时间,要刷新充电一次,否则内部的数据即会消失。
特点 存取速度快,工作稳定,不需要刷新电路,集成度不高;集成度较低且价格较高 DRAM的成本、集成度、功耗等明显优于SRAM
常见应用 CPU与主存间的高速缓冲、CPU内部的一级/二级缓存、外部的高速缓存、SSRAM DRAM分为很多种,按内存技术标准可分为FPRAM/FastPage、EDO DRAM、SDRAM、DDR/DDR2/DDR3/DDR4/…、RDRAM、SGRAM以及WRAM等。
表47.1.2 SRAM和DRAM特性
在STM32上,我们编译的程序,编译器一般会根据对应硬件的结构把程序中不同功能的数据段分为ZI\RW\RO这样的数据块,执行程序时分别放到不同的存储器上,这部分参考我们《第九章 STM32启动过程分析》中关于map文件的描述。对于我们编写的STM32程序中的变量,在默认配置下是加载到STM32的RAM区中执行的。而像程序代码和常量等编译后就固定不变的则会放到ROM区。
存储器的知识我们就介绍到这里,限于篇幅只能作简单的引用和介绍,大家可以查找资料拓展对各种存储器作一下加深了解。
47.2 SRAM方案简介
RAM的功能我们已经介绍过了,SRAM更稳定,但因为结构更复杂且造价更高,所以有更大片上SRAM的STM32芯片造价也更高。而且由于SRAM集成度低的原因,MCU也不会把片上SRAM做得特别大,基于以上原因,计算机/微机系统中都允许采用外扩RAM的方式提高性能。
1.SRAM芯片介绍
IS62WV51216 方案
IS62WV51216是ISSI(Integrated Silicon Solution, Inc)公司生产的一颗16位宽512K(512*16,即1M字节)容量的CMOS静态内存芯片。该芯片具有如下几个特点:
高速。具有45ns/55ns访问速度。
低功耗。
TTL电平兼容。
全静态操作。不需要刷新和时钟电路。
三态输出。
字节控制功能。支持高/低字节控制。
IS62WV51216的功能框图如图47.1.1所示:
在这里插入图片描述

图41.1.1 IS62WV51216功能框图
图中A018为地址线,总共19根地址线(即2^19=512K,1K=1024);IO015为数据线,总共16根数据线。CS2和CS1都是片选信号,不过CS2是高电平有效CS1是低电平有效;OE是输出使能信号(读信号);WE为写使能信号;UB和LB分别是高字节控制和低字节控制信号;
XM8A51216方案
国产替代一直是国内嵌入式领域的一个话题,国产替代的优势一般是货源稳定,售价更低,也有专门研发对某款芯片作Pin to Pin兼容的厂家,使用时无需修改PCB,直接更换元件即可,十分方便。
正点原子开发板目前使用的一款替代IS62WV51216的芯片是XM8A5121,它与IS62WV51216一样采用TSOP44封装,引脚顺序也与前者完全一致。
XM8A51216是星忆存储生产的一颗16位宽512K(512*16,即1M位)容量的CMOS静态内存芯片。采用异步SRAM接口并结合独有的XRAM免刷新专利技术,在大容量、高性能和高可靠及品质方面完全可以匹敌同类SRAM,具有较低功耗和低成本优势,可以与市面上同类型SRAM产品硬件完全兼容,并且满足各种应用系统对高性能和低成本的要求,XM8A51216也可以当做异步SRAM使用,该芯片具有如下几个特点:
⚫高速。具有最高访问速度10/12/15ns。
⚫低功耗。
⚫TTL电平兼容。
⚫全静态操作。不需要刷新和时钟电路。
⚫三态输出。
⚫字节控制功能。支持高/低字节控制。
该芯片与IS62WV51216引脚和完全兼容,控制时序也类似,大家可以方便地直接替换。
本章,我们使用FSMC的BANK1区域3来控制SRAM芯片,关于FSMC的详细介绍,我们在学习LCD的章节已经介绍过,我们采用的是读写不同的时序来操作TFTLCD模块(因为TFTLCD模块读的速度比写的速度慢很多),但是在本章,因为IS62WV51216/XM8A51216的读写时间基本一致,所以,我们设置读写相同的时序来访问FSMC。关于FSMC的详细介绍,请大家看《TFT LCD实验》和《STM32F10xxx参考手册_V10(中文版).pdf》。
47.3 硬件设计

  1. 例程功能
    本章实验功能简介:开机后,显示提示信息,然后按下KEY0按键,即测试外部SRAM容量大小并显示在LCD上。按下KEY1按键,即显示预存在外部SRAM的数据。LED0指示程序运行状态。
  2. 硬件资源
    1)LED灯
    LED0 – PB5
    2)按键:
    KEY0:PE4
    KEY1:PE3
    3)SRAM芯片:
    XM8A51216/IS62WV51216
    4)串口1(PA9/PA10连接在板载USB转串口芯片CH340上面)
    5)正点原子 2.8/3.5/4.3/7/10寸TFTLCD模块(仅限MCU屏,16位8080并口驱动)
  3. 原理图
    SRAM芯片与STM32的连接关系,如下图所示:

图47.3.1 STM32和SRAM连接原理图(XM8A51216/IS62WV51216封装相同)
SRAM芯片直接是接在STM32F1的FSMC外设上,具体的引脚连接关系如下表47.3.1所示。
战舰 SRAM
A[0:18] FMSC_A[0:18]
(为了布线方便交换了部分IO)
D[0:15] FSMC_D[0:15]
UB FSMC_NBL1
LB FSMC_NBL0
OE FSMC_OE
WE FSMC_WE
CS FSMC_NE3
表47.3.1 STM32和SRAM芯片的连接原理图
在上面的连接关系中,SRAM芯片的A[0:18]并不是按顺序连接STM32F1的FMSC_A[0:18],这样设计的好处,就是可以方便我们的PCB布线。不过这并不影响我们正常使用外部SRAM,因为地址具有唯一性,只要地址线不和数据线混淆,就可以正常使用外部SRAM。
47.4 程序设计
操作SRAM时要通过多个地址线寻址,然后才可以读写数据,在STM32上可以使用FSMC来实现,在TFT_LCD一节我们也已经讲解过FSMC接口的驱动,与之前的用法类似,关于HAL库的部分我们这里就不重复介绍了。
使用SRAM的配置步骤:
1)使能FSMC时钟,并配置FSMC相关的IO及其时钟使能。
要使用FSMC,当然首先得开启其时钟。然后需要把FSMC_D015,FSMCA018等相关IO口,全部配置为复用输出,并使能各IO组的时钟。
2)设置FSMC BANK1区域3的相关寄存器。
此部分包括设置区域3的存储器的工作模式、位宽和读写时序等。本章我们使用模式A、16位宽,读写共用一个时序寄存器。
3)使能BANK1区域3。
最后,需要通过FSMC_BCR寄存器使能BANK1的区域3,使FSMC工作起来。
通过以上几个步骤,我们就完成了FSMC的配置,初始化FSMC后就可以访问SRAM芯片时行读写操作了,这里还需要注意,因为我们使用的是BANK1的区域3,所以HADDR[27:26]=10,故外部内存的首地址为0X68000000。
47.4.1 程序流程图

在这里插入图片描述

图47.4.1.1 SRAM实验程序流程图
47.4.2 程序解析

  1. SRAM驱动
    这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。SRAM驱动源码包括两个文件:sram.c和sram.h。
    为方便修改,我们在sram.h中使用宏定义SRAM的读写控制和片选引脚,它们定义如下:
#define SRAM_WR_GPIO_PORT        	GPIOD
#define SRAM_WR_GPIO_PIN           	GPIO_PIN_5
#define SRAM_WR_GPIO_CLK_ENABLE()	do{ __HAL_RCC_GPIOD_CLK_ENABLE();}while(0)   

#define SRAM_RD_GPIO_PORT       		GPIOD
#define SRAM_RD_GPIO_PIN           	GPIO_PIN_4
#define SRAM_RD_GPIO_CLK_ENABLE() 	do{ __HAL_RCC_GPIOD_CLK_ENABLE(); }while(0)  

/* SRAM_CS(需要根据SRAM_FSMC_NEX设置正确的IO口) 引脚 定义 */
#define SRAM_CS_GPIO_PORT           	GPIOG
#define SRAM_CS_GPIO_PIN            	GPIO_PIN_10
#define SRAM_CS_GPIO_CLK_ENABLE() 	do{ __HAL_RCC_GPIOG_CLK_ENABLE();}while(0)  

根据stm32f1参考手册,SRAM可以选择FSMC对应的存储块1上的4个区域之一作为访问地址,它上面有四块相互独立的64M的连续寻址空间,为了能灵活根据不同的计算出使用的地址空间,我们定义了以下的宏:

/* FSMC相关参数 定义 
 * 注意: 我们默认是通过FSMC块3来连接SRAM, 块1有4个片选: FSMC_NE1~4
 *
 * 修改SRAM_FSMC_NEX, 对应的SRAM_CS_GPIO相关设置也得改
 */
#define SRAM_FSMC_NEX        	3 	/* 使用FSMC_NE3接SRAM_CS,取值范围只能是: 1~4 */
     
/*****************************************************************/
/* SRAM基地址, 根据 SRAM_FSMC_NEX 的设置来决定基址地址
 * 我们一般使用FSMC的块1(BANK1)来驱动SRAM, 块1地址范围总大小为256MB,均分成4块:
 * 存储块1(FSMC_NE1)地址范围: 0X6000 0000 ~ 0X63FF FFFF
 * 存储块2(FSMC_NE2)地址范围: 0X6400 0000 ~ 0X67FF FFFF
 * 存储块3(FSMC_NE3)地址范围: 0X6800 0000 ~ 0X6BFF FFFF
 * 存储块4(FSMC_NE4)地址范围: 0X6C00 0000 ~ 0X6FFF FFFF
 */
#define SRAM_BASE_ADDR         (0X60000000 + (0X4000000 * (SRAM_FSMC_NEX - 1)))

上述定义SRAM_FSMC_NEX的值为3,即使用FSMC存储块1的第3个地址范围,上面的SRAM_BASE_ADDR则根据我们使用的存储块计算出SRAM空间的首地址,存储块3对应的是0X68000000 ~ 0X6BFFFFFF的地址空间。
sram_init的类似于LCD,我们需要根据原理图配置SRAM的控制引脚,复用连接到SRAM芯片上的IO作为FSMC的地址线,根据SRAM芯片上的进序设置地址线宽度、等待时间、信号极性等,则sram的初始化函数我们编写如下:

void sram_init(void)
{
    GPIO_InitTypeDef GPIO_Initure;
    FSMC_NORSRAM_TimingTypeDef fsmc_readwritetim;

    SRAM_CS_GPIO_CLK_ENABLE();   	/* SRAM_CS脚时钟使能 */
    SRAM_WR_GPIO_CLK_ENABLE();    	/* SRAM_WR脚时钟使能 */
    SRAM_RD_GPIO_CLK_ENABLE();    	/* SRAM_RD脚时钟使能 */
    __HAL_RCC_FSMC_CLK_ENABLE();  	/* 使能FSMC时钟 */
    __HAL_RCC_GPIOD_CLK_ENABLE(); 	/* 使能GPIOD时钟 */
    __HAL_RCC_GPIOE_CLK_ENABLE(); 	/* 使能GPIOE时钟 */
    __HAL_RCC_GPIOF_CLK_ENABLE(); 	/* 使能GPIOF时钟 */
    __HAL_RCC_GPIOG_CLK_ENABLE(); 	/* 使能GPIOG时钟 */

    GPIO_Initure.Pin = SRAM_CS_GPIO_PIN;
    GPIO_Initure.Mode = GPIO_MODE_AF_PP;       
    GPIO_Initure.Pull = GPIO_PULLUP;           
    GPIO_Initure.Speed = GPIO_SPEED_FREQ_HIGH; 
    HAL_GPIO_Init(SRAM_CS_GPIO_PORT, &GPIO_Initure); /* SRAM_CS引脚模式设置 */

    GPIO_Initure.Pin = SRAM_WR_GPIO_PIN;
    HAL_GPIO_Init(SRAM_WR_GPIO_PORT, &GPIO_Initure); /* SRAM_WR引脚模式设置 */

    GPIO_Initure.Pin = SRAM_RD_GPIO_PIN;
    HAL_GPIO_Init(SRAM_RD_GPIO_PORT, &GPIO_Initure); /* SRAM_CS引脚模式设置 */
    /* PD0,1,4,5,8~15 */
GPIO_Initure.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_8 | GPIO_PIN_9 | 
GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13 
GPIO_PIN_14 | GPIO_PIN_15;
    GPIO_Initure.Mode = GPIO_MODE_AF_PP;      	/* 推挽复用 */
    GPIO_Initure.Pull = GPIO_PULLUP;           	/* 上拉 */
    GPIO_Initure.Speed = GPIO_SPEED_FREQ_HIGH;	/* 高速 */
    HAL_GPIO_Init(GPIOD, &GPIO_Initure);

    /* PE0,1,7~15 */
GPIO_Initure.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_7 | GPIO_PIN_8 |
 GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12 | 
GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15;
    HAL_GPIO_Init(GPIOE, &GPIO_Initure);
    /* PF0~5,12~15 */
GPIO_Initure.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 |
 GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_12 | GPIO_PIN_13 | 
GPIO_PIN_14 | GPIO_PIN_15;
    HAL_GPIO_Init(GPIOF, &GPIO_Initure);
    /* PG0~5,10 */
GPIO_Initure.Pin = GPIO_PIN_0 | GPIO_PIN_1 | 
GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5;
    HAL_GPIO_Init(GPIOG, &GPIO_Initure);
    g_sram_handler.Instance = FSMC_NORSRAM_DEVICE;
    g_sram_handler.Extended = FSMC_NORSRAM_EXTENDED_DEVICE;
    g_sram_handler.Init.NSBank = (SRAM_FSMC_NEX == 1) ? FSMC_NORSRAM_BANK1 : \
                                  (SRAM_FSMC_NEX == 2) ? FSMC_NORSRAM_BANK2:\
(SRAM_FSMC_NEX == 3) ? FSMC_NORSRAM_BANK3:\ 
                                  FSMC_NORSRAM_BANK4; /* 根据配置选择FSMC_NE1~4 */
     /* 地址/数据线不复用 */
    g_sram_handler.Init.DataAddressMux = FSMC_DATA_ADDRESS_MUX_DISABLE;
g_sram_handler.Init.MemoryType = FSMC_MEMORY_TYPE_SRAM;   /* SRAM */
/* 16位数据宽度 */
g_sram_handler.Init.MemoryDataWidth = SMC_NORSRAM_MEM_BUS_WIDTH_16;
/* 是否使能突发访问,仅对同步突发存储器有效,此处未用到 */
g_sram_handler.Init.BurstAccessMode = FSMC_BURST_ACCESS_MODE_DISABLE;
/* 等待信号的极性,仅在突发模式访问下有用 */
g_sram_handler.Init.WaitSignalPolarity = FSMC_WAIT_SIGNAL_POLARITY_LOW;
/* 存储器是在等待周期之前的一个时钟周期还是等待周期期间使能NWAIT */
g_sram_handler.Init.WaitSignalActive = FSMC_WAIT_TIMING_BEFORE_WS;
/* 存储器写使能 */
g_sram_handler.Init.WriteOperation = FSMC_WRITE_OPERATION_ENABLE;
/* 等待使能位,此处未用到 */
g_sram_handler.Init.WaitSignal = FSMC_WAIT_SIGNAL_DISABLE;
/* 读写使用相同的时序 */
g_sram_handler.Init.ExtendedMode = FSMC_EXTENDED_MODE_DISABLE;
/* 是否使能同步传输模式下的等待信号,此处未用到 */
    g_sram_handler.Init.AsynchronousWait = FSMC_ASYNCHRONOUS_WAIT_DISABLE; 
    g_sram_handler.Init.WriteBurst = FSMC_WRITE_BURST_DISABLE; /* 禁止突发写 */
/* FMC读时序控制寄存器 */
/* 地址建立时间(ADDSET)为1个HCLK 1/72M=13.8ns */
    fsmc_readwritetim.AddressSetupTime = 0x00; 
    fsmc_readwritetim.AddressHoldTime = 0x00;/* 地址保持时间(ADDHLD)模式A未用到 */
    fsmc_readwritetim.DataSetupTime = 0x01;/* 数据保存时间为3个HCLK=4*13.8=55ns */
    fsmc_readwritetim.BusTurnAroundDuration = 0X00;
    fsmc_readwritetim.AccessMode = FSMC_ACCESS_MODE_A;      /* 模式A */
    HAL_SRAM_Init(&g_sram_handler,&fsmc_readwritetim,&fsmc_readwritetim);
}
初始化成功后,FSMC控制器就能根据扩展的地址线访问SRAM的数据,于是我们可以直接根据地址指针来访问SRAM,我们定义SRAM的写函数如下;
void sram_write(uint8_t *pbuf, uint32_t addr, uint32_t datalen)
{
    for (; datalen != 0; datalen--)
    {
        *(volatile uint8_t *)(SRAM_BASE_ADDR + addr) = *pbuf;
        addr++;
        pbuf++;
    }
}
同样地,也是利用地址,可以构造出一个SRAM的连续读函数:
void sram_read(uint8_t *pbuf, uint32_t addr, uint32_t datalen)
{
    for (; datalen != 0; datalen--)
    {
        *pbuf++ = *(volatile uint8_t *)(SRAM_BASE_ADDR + addr);
        addr++;
    }
}

注意以上两个函数是操作unsigned char类型的指针,当使用其它类型的指针时需要注意指针的偏移量。难点主要是根据SRAM芯片上的时序来初始化FSMC控制器,大家参考芯片手册上的时序结合代码来理解这部分初始化的过程。
2. main.c代码
初始化好了SRAM,我们就可以使用SRAM中的存储进行编程了,我们利用ARM编译器的特性:可以在某一绝对地址定义变量。为方便测试,我们直接定义一个与SRAM容量大小类似的数组,由于是1M位的RAM,我们定义了uint32_t类型后,大小要除4,故定义的测试数组如下:
/* 测试用数组, 起始地址为: SRAM_BASE_ADDR */

#if (__ARMCC_VERSION >= 6010050)
uint32_t g_test_buffer[250000] __attribute__((section(".bss.ARM.__at_0x68000000")));
#else
uint32_t g_test_buffer[250000] __attribute__((at(SRAM_BASE_ADDR)));
#endif

这里的__attribute__(())是ARM编译器的一种关键字,它有很多种用法,可以通过特殊修饰指定变量或者函数的属性。大家可以去MDK的帮助文件里查找这个关键字的其它用法。这里我们要用这个关键字把变量放到指定的位置,而且用了条件编译,因为MDK的AC5和AC6下的语法不同。
通过前面的描述,我们知道SRAM的访问基地址是0x68000000,如果我们定义一个与SRAM空间大小相同的数组,而且数组指向的位置就是0x68000000的话,则这通过数组就可以很方便直接操作这块存储空间。所以回来前面所说的__attribute__这个关键字。对于AC5,它可以用__attribute__((at(地址)))的方法来修饰变量,而且这个地址可以是一个算式,这样编译器在编译时就会通过这个关键字判断并把这个数组放到我们定义的空间,如果硬件支持的情况下,我们就可以访问这些指定空间的变量或常量了。但是对于AC6,同样指定地址,需要用__attribute__((section(“.bss.ARM.__at_地址”)))的方法,指定一个绝对地址才能把变量或者常量放到我们所需要定义的位置。这里这个地址就不支持算式了,但是这个语法对于相对而言更加地通用,其它平台的编译器如gcc也有类似的语法,而且AC5下也可以用AC6的这种语法来达到相同效果,两者之间的差异,大家可以多实践以进行区分。
完成SRAM部分的代码,main函数只要实现对SRAM的读写测试即可,我们。加入按键和LCD显示来辅助显示,在main函数中编写代码如下:

int main(void)
{
    uint8_t key;
    uint8_t i = 0;
uint32_t ts = 0;

    HAL_Init();                             		/* 初始化HAL库 */
    sys_stm32_clock_init(RCC_PLL_MUL9);		/* 设置时钟, 72Mhz */
    delay_init(72);                       		/* 延时初始化 */
    usart_init(115200);                   		/* 串口初始化为115200 */
    usmart_dev.init(72);                  		/* 初始化USMART */
    led_init();                             		/* 初始化LED */
    lcd_init();                             		/* 初始化LCD */
    key_init();                             		/* 初始化按键 */
sram_init();                           		/* SRAM初始化 */

    lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
    lcd_show_string(30, 70, 200, 16, 16, "SRAM TEST", RED);
    lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
    lcd_show_string(30, 110, 200, 16, 16, "KEY0:Test Sram", RED);
lcd_show_string(30, 130, 200, 16, 16, "KEY1:TEST Data", RED);

    for (ts = 0; ts < 250000; ts++)
    {
        g_test_buffer[ts] = ts; 		/* 预存测试数据 */
}

    while (1)
    {
        key = key_scan(0); 			/* 不支持连按 */

        if (key == KEY0_PRES)
        {
            fsmc_sram_test(30, 150);	/* 测试SRAM容量 */
        }
        else if (key == KEY1_PRES) 	/* 打印预存测试数据 */
        {
            for (ts = 0; ts < 250000; ts++)
            {   /* 显示测试数据 */
                lcd_show_xnum(30, 170, g_test_buffer[ts], 6, 16, 0, BLUE); 
            }
        }
        else
        {
            delay_ms(10);
        }

        i++;
        if (i == 20)
        {
            i = 0;
            LED0_TOGGLE(); /* LED0闪烁 */
        }
    }
}

47.4 下载验证
在代码编译成功之后,我们通过下载代码到开发板上,得到如图47.4.1所示界面:
在这里插入图片描述

图47.4.1 程序运行效果图
此时,我们按下KEY0,就可以在LCD上看到内存测试的画面,同样,按下KEY1,就可以看到LCD显示存放在数组g_test_buffer里面的测试数据,我们把数组的下标直接写到SRAM中,可以看到这个数据在不断地更新,SRAM读写操作成功了,如图47.4.2所示:
在这里插入图片描述

图47.4.2 外部SRAM测试界面
该实验我们还可以借助USMART来测试,如图47.4.3 所示:
在这里插入图片描述

图47.4.3 借助USMART测试外部SRAM读写

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/703927.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用inno打包程序流程

1:配置iss文件 ​​​​​​​ 2编译 3.生成安装包文件安装

浅谈建筑项目中的智能照明系统的设计与研究

【摘要】&#xff1a;建筑智能照明工程中智能照明控制系统发展迅速&#xff0c;具有舒适性和节能性两方面优势。智能照明控制系统已经处于模块化高速发展阶段&#xff0c;如今更好的控制方案成为制约系统发展的瓶颈。文章在研究了国内外智能照明系统的基础上&#xff0c;从照明…

【CSS】nth:children以及浏览器内核webkit使用(滚动条样式修改)

&#x1f609;博主&#xff1a;初映CY的前说(前端领域) ,&#x1f4d2;本文核心&#xff1a;nth:children以及浏览器中的webkit使用 前言&#xff1a;在页面的编写中使用了多个标签通常有需求去处理下特殊的样式&#xff0c;我们常见做法是给我们的标签加上一个类或者通过标签选…

【微服务】什么是微服务?-- 全面了解微服务架构

What is Microservices — Edureka 您有没有想过&#xff0c;什么是微服务以及扩展行业如何与它们集成&#xff0c;同时构建应用程序以满足客户的期望&#xff1f; 要了解什么是微服务&#xff0c;您必须了解如何将单体应用程序分解为独立打包和部署的小型微型应用程序。本文将…

力扣 -- 91.解码方法

题目链接&#xff1a;91. 解码方法 - 力扣&#xff08;LeetCode&#xff09; 以下是用动态规划的思想解决这道题目&#xff0c;如果对动态规划五部曲的含义还不是很清楚的老铁可以看看本专栏的第一题动规(10条消息) 力扣 -- 746. 使用最小花费爬楼梯_KOBE 0824 BRYANT的博客-…

软件测试编写文档模板【附文档模板】

一、测试岗位必备的文档 在一个常规的软件测试流程中&#xff0c;会涉及到测试计划、测试方案、测试用例、测试报告的编写&#xff0c;这些文档也是软件测试岗位必须掌握的文档类型。 1、测试计划 测试计划是组织管理层面的文件&#xff0c;从组织管理的角度对一次测试活动进…

华为OD机试真题 Python实现【最小的调整次数】【2023Q1 100分】

目录 一、题目描述二、输入描述三、输出描述四、补充说明五、解题思路六、Python算法源码七、效果展示1、输入2、输出3、说明 一、题目描述 有一个特异性的双端队列&#xff0c;该队列可以从头部或尾部添加数据&#xff0c;但是只能从头部移出数据。 小A依次执行2n个指令往队…

uboot详解(嵌入式学习)

uboot详解 概念详解扩展Windows的“uboot” 概念 U-Boot&#xff08;Unified Bootloader&#xff09;是一个开源的嵌入式系统引导加载程序&#xff0c;也是一种通用的引导加载程序。它主要用于嵌入式系统的启动过程&#xff0c;负责初始化硬件设备、加载操作系统内核和启动应用…

Android Studio 使用 Build Variants 配置测试/正式环境域名等字段

拿测试环境域名和正式环境域名举例&#xff1a;在项目调试和发版过程中可以通过频繁地注释和解开注释来切换正式环境域名和测试环境域名&#xff0c;但此方法过于繁琐&#xff1b;所以可以使用Android Studio的Build Variants根据切换环境来替我们执行切换环境的操作。 在项目…

《HelloGitHub》第 87 期

兴趣是最好的老师&#xff0c;HelloGitHub 让你对编程感兴趣&#xff01; 简介 HelloGitHub 分享 GitHub 上有趣、入门级的开源项目。 https://github.com/521xueweihan/HelloGitHub 这里有实战项目、入门教程、黑科技、开源书籍、大厂开源项目等&#xff0c;涵盖多种编程语言 …

可视化对讲广播电话可以用在哪里

可视化对讲广播电话可以用在哪里 可视化对讲广播电话&#xff1a;无处不在的沟通利器 【工地现场】 在矗立的高楼上&#xff0c;工地上忙碌的工人们使用着可视化对讲广播电话。借助高清画面和清晰音频&#xff0c;工作人员可以实时观察工地情况&#xff0c;更好地协调工作&a…

【【51单片机实现LED点阵屏幕和动画显示】】

LED点阵屏幕和驱动代码 我们先搞清楚&#xff17;&#xff14;HC&#xff15;&#xff19;&#xff15;的原理 &#xff53;&#xff46;&#xff52; 特殊功能寄存器声明 &#xff53;&#xff42;&#xff49;&#xff54; 特殊位声明 就举个例子&#xff0c;我们在之前的…

Linux后台运行Python脚本

Linux后台运行Python脚本命令&#xff1a; nohup python webui.py > myout.file 2>&1 &nohup&#xff1a;nohup命令用于不挂断地运行命令python&#xff1a;执行python代码的命令webui.py: python程序脚本源代码>: 打印程序输出信息到指定日志文件中myout.fi…

[vue]使用Element--Tree 树形控件使用props解决自定义slot-scope=“{ node, data }“的对象问题

在未用props定义的情况下&#xff0c;slot-scope"{ node, data }"解析data对象只有data含有lable和children才能识别出内容和子节点 当我获取的数据如下&#xff0c;没有lable和children&#xff0c;使用的是name和childList&#xff0c;如下图所示&#xff1a; 注意…

设计必备的矢量插画素材,这几个网站超齐全。

分享几个设计必备的矢量插画、插图素材网站&#xff0c;建议收藏~ 菜鸟图库 https://www.sucai999.com/searchlist/bianpingchahua----all-0-1.html?vNTYxMjky 菜鸟图库是一个素材非常丰富的网站&#xff0c;这里面涵盖了平面、电商、UI、图片、高清背景、插画、视频、音频等…

【新星计划】技术博客写作技巧经验分享

序言 写技术博客需要一定的专业知识和写作技巧&#xff0c;它是一个很好的方式来分享你的经验和知识&#xff0c;同时也是一个展示你的专业能力和建立自己品牌的机会。 以下是一些准备和建议&#xff0c;希望可以帮助你写出有用和有吸引力的技术博客&#xff1a; 写在前面 我报…

使用Elasticsearch进行word,excel,PDF的全文检索 windows实现 超完整(ingest-attachment实现)

首先要明确的一点就是Elasticsearch的版本要和ingest-attachment的版本一致&#xff0c;要不然没办法安装。然后还有一点JAVA版本要在11以上 先说说原理吧&#xff0c;其实就是将文件base64编码&#xff0c;然后再用插件读取文件内容并保存到es中。 1.如果你的版本是JAVA1.8的…

win11系统升级后QT的程序识别套件失败解决

解决方法&#xff1a; QTCreator的配置文件存放位置:C:\Users\%USERNAME%\AppData\Roaming\QtProject。删除这个文件夹下的文件&#xff0c;重启QT就会自动识别kits套件初始化信息等 就是QT我配置的颜色也消失了按照如下步骤重新配置后再次重启QT即可 这个方法导致我QT 上配…

苹果开发者账号续费流程及苹果开发者账号续费流程及下架处理

当开发者的苹果开发者账号即将到期时&#xff0c;可能会面临以下问题&#xff1a;“如何查看开发者账号剩余时间&#xff1f;”和“如何进行续费&#xff1f;”本文将详细解答这些问题。 如何查询账号过期时间&#xff1f; 如果距离开发者账号过期时间不到一个月&#xff0c;您…

KNIME工作流和节点比较功能

KNIME工作流和节点比较功能是一个在 << KNIME 视觉化数据分析 >> 中没有讲到的知识点。 KNIME工作流和节点比较功能在以下几种情况下非常有用&#xff1a; 版本控制&#xff1a;此功能可以跟踪工作流和节点中的更改。如果需要返回到之前的工作流或节点版本&#xf…