记STM32外扩SDRAM

SDRAM和SRAM的区别?SRAM是TTL触发器构成的静态电路,就是数电里学的那种触发器寄存器,每个存储单元由多个晶体管组成。成本高,容量密度小,但功耗低速度快。SDRAM是靠CMOS电容存储数据的,但寄生电容会自漏电,就需要不断充电,所以成本低,容量密度大,但功耗高,而且速度相比SRAM理论上也要慢一点。

本次实验硬件是STM32H723ZG配W9825G6KH,因为第一次外扩RAM,肯定优先是抄作业。仔细一看这东西其实不难,stm32cubemx里配置FMC使能SDRAM模式,并且根据芯片的数据和地址宽度选择对应的模式,cubemx就把IO给选好了,而且单片机引脚和芯片引脚直接一一对应,连连看就行,再给电源都撒上104电容,原理图就出锅了。

PCB设计才是难题,13根地址线16根数据线加上几个控制信号一共39根线,更重要的是,这39根线原则上全部要等长走线,因为FMC的频率可以上百M,已经来到了高速PCB的范畴。在走线的过程中,会发现有些数据线不可避免地会出现交叉,而且可能会存在很多交叉,这时候可以对信号线进行一些调整,理论上数据线D0到D15可以任意互换,如下图,我们输出1011,交换D0和D1之后,SDRAM实际存入的是0111,但如果把数据再读出,0111经过交叉的数据线依然会还原成1011,SDRAM存放的数据物理上是什么根本不重要,只要单片机能把存入的数据原样取出就行。

信号线交叉示意图

但是,一般情况下只能D0~D7内对调,D8~D15内对调 ,因为FMC存在一种按byte访问的模式,如果单片机只操作八位类型的数据比如char/u8,而数据宽度是16位的,FMC就会屏蔽高8bit或者低8bit来实现8bit读写,所以高位和低位的信号不能对调,否则就不支持八位操作了。事实上笔者在画原理图的时候就没想到这一点,精准的把D0和D8对调了,导致无法使用八位操作,其实问题也不大,SDRAM容量如此之大,即便把char按照short去存也完全够用。

这时候有聪明的网友就要问了,那A0到A13是不是也可以任意对调,毕竟数据实际存放在什么位置也不重要,只要能原样取回就行。但这个其实不行,因为SDRAM芯片还存在一些控制命令,这些命令就是通过地址线去发送的。而且物理地址对读写其实是有影响的,对硬盘稍微了解点的都知道硬盘的随机读写和顺序读写性能不同,对于SDRAM也是如此,如果数据线乱了,明明是顺序读写却都会变成随机读写,性能会打折扣。

历经千辛万苦拖完了39根登场走线,下单,拿到了PCB,写代码。不得不说stm32cubemx是真的太方便了,图形化配置完了生成代码,再从网上随便找个例程复制过来,即便单片机型号不同,代码也成功跑起来。初始化完成后直接往0xC0000000开始的地址里面写入数据再读出来就知道有没有成功,如果卡死跳入硬件错误中断就是失败,如果读出的数据和写入的不同,多半是因为频率过高等长误差太大,降低频率再试试。笔者的PCB全是自己摸爬滚打自学的,第一次画等长虽然跑不满137.5M,可以跑到108M已经比较满意。初始化代码是从CSDN别的博主下面拷贝过来的就不分享了,分享一下写的测试代码

#define SDRAM_TEST_TIM  TIM2                  //计时用定时器
#define SDRAM_TEST_ADDRESS_START 0xC0000000    //SDRAM首地址
#define SDRAM_TEST_SIZE 0x1fffffA             //测试空间大小
void sdram_test(void) //SDRAM读写测试
{
  unsigned int temp = 0;
  unsigned short err = 0;
  printf("/***************** SDRAM TEST ****************/\n\r");   //开始测试
  //字节写测试
  printf("             WORD TEST          \n\r");
  printf("writing start\n\r");    //字节写入测试开始
  SDRAM_TEST_TIM->CNT=0;                //清空定时器计数
  LL_TIM_EnableCounter(SDRAM_TEST_TIM);  //开始计时
  for(temp=0; temp<SDRAM_TEST_SIZE; temp+=4)
  {
    *(unsigned int*)(SDRAM_TEST_ADDRESS_START+temp) = temp;
  }
  LL_TIM_DisableCounter(SDRAM_TEST_TIM);  //先停止定时器再输出测试结果
  printf("writing end, cost:%1.2fS\n\r", ((float)(SDRAM_TEST_TIM->CNT)/274375000));   //字节写入测试结束打印耗时
  //字节读测试
  printf("reading start\n\r");    //字节读出测试开始
  SDRAM_TEST_TIM->CNT=0;              //清空定时器
  LL_TIM_EnableCounter(SDRAM_TEST_TIM);  //开始计时
  for(temp=0; temp<SDRAM_TEST_SIZE; temp+=4)  //读出并且比对预期写入值,如果不相符输出错误
  {
    if((*(unsigned int*)(SDRAM_TEST_ADDRESS_START+temp)) != temp)
    {
      printf("find err on 0x%x\n\r", (SDRAM_TEST_ADDRESS_START+temp));
      err++;
    }
  }
  LL_TIM_DisableCounter(SDRAM_TEST_TIM);    //计时结束
  printf("reading end, cost:%1.2fS\n\r", ((float)(SDRAM_TEST_TIM->CNT)/274375000));   //打印耗时
  printf("            HALF WORD TEST        \n\r");
  //半字写测试
  printf("writing start\n\r");    //字节写入测试开始
  SDRAM_TEST_TIM->CNT=0;                //清空定时器计数
  LL_TIM_EnableCounter(SDRAM_TEST_TIM);  //开始计时
  for(temp=0; temp<SDRAM_TEST_SIZE; temp+=4)
  {
    *(unsigned short*)(SDRAM_TEST_ADDRESS_START+temp) = (unsigned short)temp;
  }
  LL_TIM_DisableCounter(SDRAM_TEST_TIM);  //先停止定时器再输出测试结果
  printf("writing end, cost:%1.2fS\n\r", ((float)(SDRAM_TEST_TIM->CNT)/274375000));   //字节写入测试结束打印耗时
  //半字读测试
  printf("reading start\n\r");    //字节读出测试开始
  SDRAM_TEST_TIM->CNT=0;              //清空定时器
  LL_TIM_EnableCounter(SDRAM_TEST_TIM);  //开始计时
  for(temp=0; temp<SDRAM_TEST_SIZE; temp+=4)  //读出并且比对预期写入值,如果不相符输出错误
  {
    if((*(unsigned short*)(SDRAM_TEST_ADDRESS_START+temp)) != (unsigned short)temp)
    {
      printf("find err on 0x%x\n\r", (SDRAM_TEST_ADDRESS_START+temp));
      err++;
    }
  }
  LL_TIM_DisableCounter(SDRAM_TEST_TIM);    //计时结束
  printf("reading end, cost:%1.2fS\n\r", ((float)(SDRAM_TEST_TIM->CNT)/274375000));   //打印耗时
  if(err>0)
  {
    printf("find %d err\n\r", err);
  }
  else
  {
    printf("0x%X BYTE CHECKED, NO ERROR\n\r", SDRAM_TEST_SIZE);
    LL_GPIO_SetOutputPin(LED_GPIO_Port, LED_Pin);
  }

  printf("/************ SDRAM TEST COMPLETE ************/\n\r");
}

#define SDRAM_ADDRESS_START SDRAM_TEST_ADDRESS_START
unsigned short *malloc_16(unsigned int size)
{
  static unsigned int ram_p = 0;
  unsigned int res = SDRAM_ADDRESS_START+ram_p;
  ram_p +=size;
  return (unsigned short *)res;
}

分别以word(2*16bit)和hafl word(16bit)把全片写入并且读出,判断读出的数据和写入是否相同,如果不同抛出错误数量。并且使用定时器2(32bit不分频)进行计时

代码运行结果​​​​

 这里遇到一个奇怪的问题,读的速度居然比写要慢得多,查看了读写对应位置的汇编代码,发现读和写都是那么两三句操作寄存器(R0~R15,不是外设寄存器)的汇编,通信的过程应该都包括在这些汇编语句里面由内核自动完成了,耗时原因无从考证。有懂的网友希望留言交流一下

最后附上实物照片,姑且算是比较满意的作品,为了实现这么小的体积用了六层板都差点走线走不下。

实物照片​​​​​
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 在将STM32F407外扩SDRAM时,需要注意以下几个步骤: 首先,确认所选用的SDRAM芯片与STM32F407兼容。确保SDRAM芯片与STM32F407的电压要求和接口规范相匹配。 接着,将SDRAM芯片连接到STM32F407的外部总线接口。一般情况下,SDRAM芯片会通过地址线、数据线、控制线和时钟线与STM32F407相连。 在连接之前,需要根据SDRAM芯片的规格书来配置STM32F407的总线控制器。这包括设置时序参数(如读写时序、预充电时间等)和控制寄存器的值。 在进行总线控制器的配置时,还需要根据SDRAM芯片的引脚分配,设置STM32F407的GPIO引脚模式和配置寄存器。这样才能正确与SDRAM芯片进行数据交换。 完成这些设置后,还需要对SDRAM芯片进行初始化。这包括SDRAM的时钟使能、初始化流程和刷新设置等。 最后,可以进行SDRAM的读写操作。通过将数据存储到SDRAM芯片的指定地址或从指定地址读取数据,与SDRAM进行交互。 需要注意的是,SDRAM的使用可能需要消耗较多的系统资源和功耗。在设计时,要考虑系统的总电源和总线带宽,并根据应用需求进行相应的优化。 总的来说,将STM32F407外扩SDRAM需要注意兼容性、引脚配置、时序参数设置和初始化流程等步骤。通过正确的连接和配置,可以实现更大容量和更高速度的存储器扩展。 ### 回答2: STM32F407是一款高性能的微控制器,具有大量的外设和丰富的存储空间。如果想要在STM32F407上扩展SDRAM,可以按照以下步骤进行操作: 首先,需要连接SDRAM芯片和STM32F407。这可以通过将SDRAM的数据、地址和控制线连接到STM32F407的外部总线接口来实现,例如FMC(外部存储器控制器)。接线之前,请确保使用合适的电平转换电路来匹配STM32F407和SDRAM之间的电压电平。 然后,需要在STM32F407的硬件配置中进行相应的设置。可以使用STM32的CubeMX软件来生成初始化代码,并选择相应的外设和引脚连接。在配置FMC时,需要设置正确的存储区域大小、存储器类型、存储器宽度和时序参数等。 接下来,通过编程来初始化和配置SDRAM。可以使用STM32的Cube HAL库或者标准外设库来编写初始化代码,以便正确地设置SDRAM控制器的寄存器,以及初始化SDRAM的时序和模式。 完成上述步骤后,就可以使用SDRAM了。可以通过读写操作来访问扩展的存储空间,将数据存储到SDRAM中,或从SDRAM中读取数据。在访问SDRAM时,需要根据配置好的时序参数和控制信号进行操作。 需要注意的是,扩展SDRAM可能会增加系统的复杂性和功耗。因此,在设计中要充分考虑到电源和信号完整性的问题,并选择合适的SDRAM芯片以满足设计需求。 总结起来,通过连接、配置和编程等步骤,可以在STM32F407上成功外扩SDRAM,从而获得更大的存储空间和更高的性能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值