1、准备材料

开发板(正点原子stm32f407探索者开发板V2.4

STM32CubeMX软件(Version 6.10.0

野火DAP仿真器

keil µVision5 IDE(MDK-Arm

ST-LINK/V2驱动

XCOM V2.6串口助手

2、实验目标

使用STM32CubeMX软件配置STM32F407开发板的FSMC实现以轮询或DMA的方式读写IS62WV51216(SRAM)芯片

3、轮询方式读写SRAM

3.0、前提知识

关于FSMC的内容读者可阅读“STM32CubeMX教程22 FSMC - 8080并行接口TFT-LCD驱动”实验“3.0、前提知识”小节

本实验使用的SRAM芯片为IS62WV51216,其为16位宽512K容量的静态随机存取存储器,开发板使用FSMC Bank 1-NOR/PSRAM3,片选信号为NE3(PG10),地址线A[0:18],数据线D[0:15]来控制该存储芯片,如下图所示为该存储芯片的硬件原理图

FSMC的地址线是按照字节寻址的,因此19根地址线的寻址范围应该为0x0 0000~0x7 FFFF(2^19=524288=0x8 0000)

但是由于该存储器为16位(2个字节),因此其实际容量应该为2*512KB=1024KB,其需要的寻址范围为0x0 0000~0xF FFFF

再考虑到FSMC Bank 1-NOR/PSRAM3起始地址为0x6800 0000,因此如果要访问IS62WV51216芯片全部的1024KB数据的地址范围应该为0x6800 0000~0x680F FFFF

19根地址线做不到,那怎么办?

解决方法就是将16位宽分为高字节和低字节,通过FSMC的字节控制引脚FSMC_NBL0/1实现全部1024KB存储空间的访问

3.1、CubeMX相关配置

3.1.0、工程基本配置

打开STM32CubeMX软件,单击ACCESS TO MCU SELECTOR选择开发板MCU(选择你使用开发板的主控MCU型号),选中MCU型号后单击页面右上角Start Project开始工程,具体如下图所示

开始工程之后在配置主页面System Core/RCC中配置HSE/LSE晶振,在System Core/SYS中配置Debug模式,具体如下图所示

详细工程建立内容读者可以阅读“STM32CubeMX教程1 工程建立

3.1.1、时钟树配置

系统时钟使用8MHz外部高速时钟HSE,HCLK、PCLK1和PCLK2均设置为STM32F407能达到的最高时钟频率,具体如下图所示

3.1.2、外设参数配置

本实验需要需要初始化开发板上KEY1和KEY0两个用户按键,具体配置步骤请阅读“STM32CubeMX教程3 GPIO输入 - 按键响应

本实验需要需要初始化USART1作为输出信息渠道,具体配置步骤请阅读“STM32CubeMX教程9 USART/UART 异步通信

本实验写入SRAM数据时使用到了STM32的随机数RNG功能,读者直接在Security/RNG中将其激活即可

单击Pinout & Configuration页面左边Connectivity/FSMC选项,在右边的Mode下点开NORFIash/PSRAM/SRAM/ROM/LCD3选项卡(因为SRAM使用的是使用FSMC Bank 1-NOR/PSRAM3),然后按照下面顺序配置

  1. 选择片选信号NE3
  2. 内存类型为SRAM
  3. FSMC地址线19位
  4. 数据Data为16位宽度(D0-D15)
  5. 使能字节控制Byte enable

下方的读写时序参数配置可阅读“STM32CubeMX教程22 FSMC - 8080并行接口TFT-LCD驱动”实验“3.0、前提知识”小节,具体配置如下图所示

配置完成之后请读者对照STM32CubeMX中配置的默认功能引脚是否和开发板硬件原理图每个引脚对应,防止出现默认功能引脚与开发板设计的引脚不一致的现象

3.1.3、外设中断配置

轮询方式读写SRAM无需配置任何中断

3.2、生成代码

3.2.0、配置Project Manager页面

单击进入Project Manager页面,在左边Project分栏中修改工程名称、工程目录和工具链,然后在Code Generator中勾选“Gnerate peripheral initialization as a pair of 'c/h' files per peripheral”,最后单击页面右上角GENERATE CODE生成工程,具体如下图所示

详细Project Manager配置内容读者可以阅读“STM32CubeMX教程1 工程建立”实验3.4.3小节

3.2.1、外设初始化调用流程

请阅读“STM32CubeMX教程22 FSMC - 8080并行接口TFT-LCD驱动”实验3.2.1小节

3.2.2、外设中断调用流程

轮询方式读写SRAM无需配置任何中断

3.2.3、添加其他必要代码

在main.c文件中添加SRAM读写测试函数,具体函数源代码如下所示

/*用HAL函数写入数据*/
void SRAM_WriteByFunc(void)
{
//1.写入字符串
uint32_t *pAddr = (uint32_t *)(SRAM_ADDR_BEGIN); //给指针赋值
uint8_t strIn[] = "Moment in UPC";
uint16_t dataLen = sizeof(strIn); //数据长度,字节数,包括最后的结束符'\0’
if(HAL_SRAM_Write_8b(&hsram3, pAddr, strIn, dataLen) == HAL_OK)
{
printf("Write string at 0x6800 0000:");
printf("%s\r\n",strIn);
} //2.写入一个随机数
uint32_t num=0;
pAddr=(uint32_t *)(SRAM_ADDR_BEGIN+256); //指针重新赋值
HAL_RNG_GenerateRandomNumber(&hrng, &num); //产生32位随机数
if(HAL_SRAM_Write_32b(&hsram3, pAddr, &num, 1) == HAL_OK)
{
printf("Write 32b number at 0x6800 0100");
printf("0x%x\r\n", num);
}
printf("-----------------------------------------\r\n");
} /*用HAL函数读取数据*/
void SRAM_ReadByFunc(void)
{
//1.读取字符串
uint32_t *pAddr = (uint32_t *)(SRAM_ADDR_BEGIN); //给指针赋值
uint8_t strOut[30];
uint16_t dataLen = 30;
if(HAL_SRAM_Read_8b(&hsram3, pAddr, strOut, dataLen) == HAL_OK)
{
printf("Read string at 0x6800 0000:");
printf("%s\r\n", strOut);
} //2.读取一个uint32_t数
uint32_t num=0;
pAddr=(uint32_t *)(SRAM_ADDR_BEGIN+256); //指针重新赋值,指向一个新的地址
if(HAL_SRAM_Read_32b(&hsram3, pAddr, &num, 1) == HAL_OK)
{
printf("Read 32b number at 0x6800 0100:");
printf("0x%x\r\n", num);
}
printf("-----------------------------------------\r\n");
}

在main.c文件中添加使用到的SRAM起始/中间/结束地址,并对定义的函数声明,源代码如下所示

/*定义SRAM地址*/
#define SRAM_ADDR_BEGIN 0x68000000UL //Bank1 子区3的 SRAM起始地址
#define SRAM_ADDR_HALF 0x68080000UL //SRAM 中间地址 512K字节
#define SRAM_ADDR_END 0x680FFFFFUL //SRAM 结束地址 1024K字节 /*读写测试函数声明*/
void SRAM_ReadByFunc(void);
void SRAM_WriteByFunc(void);

最后在主函数主循环while(1)中使用用户按键调用SRAM读写测试函数即可,源代码如下所示

/*KEY1被按下*/
if(HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin) == GPIO_PIN_RESET)
{
HAL_Delay(50);
if(HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin) == GPIO_PIN_RESET)
{
//SRAM写测试函数
SRAM_WriteByFunc();
while(!HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin));
}
} /*KEY0被按下*/
if(HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin) == GPIO_PIN_RESET)
{
HAL_Delay(50);
if(HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin) == GPIO_PIN_RESET)
{
//SRAM读测试函数
SRAM_ReadByFunc();
while(!HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin));
}
}

4、常用函数

/*向SRAM中写8位缓存数据*/
HAL_StatusTypeDef HAL_SRAM_Write_8b(SRAM_HandleTypeDef *hsram, uint32_t *pAddress, uint8_t *pSrcBuffer, uint32_t BufferSize) /*从SRAM中读8位缓存数据*/
HAL_StatusTypeDef HAL_SRAM_Read_8b(SRAM_HandleTypeDef *hsram, uint32_t *pAddress, uint8_t *pDstBuffer, uint32_t BufferSize) /*向SRAM中写16位缓存数据*/
HAL_StatusTypeDef HAL_SRAM_Write_16b(SRAM_HandleTypeDef *hsram, uint32_t *pAddress, uint8_t *pSrcBuffer, uint32_t BufferSize) /*从SRAM中读16位缓存数据*/
HAL_StatusTypeDef HAL_SRAM_Read_16b(SRAM_HandleTypeDef *hsram, uint32_t *pAddress, uint8_t *pDstBuffer, uint32_t BufferSize) /*向SRAM中写32位缓存数据*/
HAL_StatusTypeDef HAL_SRAM_Write_32b(SRAM_HandleTypeDef *hsram, uint32_t *pAddress, uint8_t *pSrcBuffer, uint32_t BufferSize) /*从SRAM中读32位缓存数据*/
HAL_StatusTypeDef HAL_SRAM_Read_32b(SRAM_HandleTypeDef *hsram, uint32_t *pAddress, uint8_t *pDstBuffer, uint32_t BufferSize) /*向SRAM中写32位缓存数据*/
HAL_StatusTypeDef HAL_SRAM_Write_DMA(SRAM_HandleTypeDef *hsram, uint32_t *pAddress, uint8_t *pSrcBuffer, uint32_t BufferSize) /*从SRAM中读32位缓存数据*/
HAL_StatusTypeDef HAL_SRAM_Read_DMA(SRAM_HandleTypeDef *hsram, uint32_t *pAddress, uint8_t *pDstBuffer, uint32_t BufferSize)

5、烧录验证

烧录程序,开发板上电后打开串口助手,按下KEY0按键从SRAM中指定位置读取数据,发现读取到错误乱码信息;按下KEY1按键将准备好的数据写入SRAM指定位置,再次按下KEY0按键从该指定位置读取数据,发现和我们写入的数据一致,可重复多次尝试,发现读取的信息均和写入信息一致,如下图所示为串口输出详细信息

6、DMA方式读写SRAM

6.1、前提知识

读写SRAM还可以使用DMA的方式进行,但是由于SRAM属于存储设备,因此读写SRAM的DMA方向应该为内存到内存

在“STM32CubeMX教程12 DMA 直接内存读取”我们提到只有DAM2的8个通道可以实现从存储器到存储器这种传输模式,由于该模式比较特殊,因此读者可以发现不同于其他外设DMA的配置,在FSMC选项卡我们配置SRAM的页面中根本没有DMA选项卡可以设置,因此想要设置从存储器到存储器这种传输模式的DMA必须在System Core/DMA选项卡中设置

另外在其他外设设置DMA时,DMA流与外设关联的函数__HAL_LINKDMA()会被自动生成在HAL_xxx_MspInit()函数中

但是如果DMA为存储器到存储器这种传输模式,则生成的工程代码中不会自动关联DMA流与外设(他也不知道该如何关联),因此在DMA及外设均初始化完毕之后,需要用户手动增加__HAL_LINKDMA()函数将外设与DMA流关联

6.2、CubeMX相关配置

请读者先按照本实验“3.1、CubeMX相关配置” 小节配置RCC、SYS、用户按键、串口USART1和FSMC模式和基本参数

如果读者对DMA参数不理解,请阅读“STM32CubeMX教程12 DMA 直接内存读取”实验,然后按下方顺序配置DMA参数

  1. 单击Pinout & Configuration页面左边System Core/DMA选项卡
  2. 在Configuration中选择MemToMem
  3. 单击下方的增加按钮选择增加DMA请求
  4. 单击增加的DMA请求
  5. 在下方对其参数进行配置,模式为Normal
  6. 源/目标内存地址均递增
  7. 数据宽度选择word(因为HAL库提供的DMA写入SRAM函数数据为32位的)

其他参数默认,具体配置参看下图

在Pinout & Configuration页面左边System Core/NVIC中勾选DMA2 Stream0 全局中断,然后选择合适的中断优先级即可

6.3、DMA中断调用流程

DMA触发中断,其回调函数是一个函数指针的形式,在外设使用DMA启动传输的时候会将外设对应的中断回调函数赋值给DMA中断回调函数指针,具体请阅读“STM32CubeMX教程12 DMA 直接内存读取”实验实验3.2.2小节,流程大致一致

这里读者只需知道,在STM32CubeMX开启FSMC配置的SRAM DMA中断之后,在工程代码中使用HAL_SRAM_Read_DMA() / HAL_SRAM_Write_DMA()函数传输完毕之后,都会调用HAL_SRAM_DMA_XferCpltCallback()虚函数,用户重新实现该虚函数即可

由于以DMA方式读和写SRAM传输完成的回调函数为同一个,因此用户可以自己设定标志位,从而可以在中断回调函数中判断是读完成还是写完成

6.4、添加其他必要代码

配置工程并单击页面右上角GENERATE CODE生成工程

在main.c文件中FSMC初始化完毕之后添加DMA流与外设关联的函数

//将外设与DMA流关联
__HAL_LINKDMA(&hsram3, hdma, hdma_memtomem_dma2_stream0);

在main.c文件中增加以DMA方式读写SRAM的测试函数,源代码如下

/*以DMA方式写入数据*/
void SRAM_WriteDMA(void)
{
printf("Write 32bit array by DMA:");
uint32_t Value=3000;
for(uint8_t i=0; i<COUNT; i++)
{
txBuffer[i] = Value;
printf("%d,",Value);
Value += 6;
}
printf("\r\n");
//DMA传输方向,1=write, 0=read
DMA_Direction=1;
//表示DMA正在传输,1=working, 0=idle
DMA_Busy=1;
//给指针赋值
uint32_t *pAddr_32b=(uint32_t *)(SRAM_ADDR_BEGIN);
//DMA方式写入SRAM
HAL_SRAM_Write_DMA(&hsram3, pAddr_32b, txBuffer, COUNT);
} /*以DMA方式读取数据*/
void SRAM_ReadDMA(void)
{
printf("Read 32bit array by DMA\r\n");
DMA_Direction=0;
DMA_Busy=1;
uint32_t *pAddr_32b=(uint32_t *)(SRAM_ADDR_BEGIN);
//以DMA方式读取SRAM
HAL_SRAM_Read_DMA(&hsram3, pAddr_32b, rxBuffer, COUNT);
}

在main.c文件头部增加函数声明及使用到的一些变量定义,源代码如下

/*函数声明*/
void SRAM_WriteDMA(void);
void SRAM_ReadDMA(void); /*变量定义*/
#define COUNT 5 //缓存区数据个数
uint32_t txBuffer[COUNT]; //DMA发送缓存区
uint32_t rxBuffer[COUNT]; //DMA接收缓存区
uint8_t DMA_Direction = 1; //DMA传输方向,1=write, 0=read
uint8_t DMA_Busy = 0; //DMA工作状态,1=busy, 0=idle

最后在主循环中使用用户按键调用SRAM读写测试函数即可,源代码如下所示

/*KEY1被按下*/
if(HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin) == GPIO_PIN_RESET)
{
HAL_Delay(50);
if(HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin) == GPIO_PIN_RESET)
{
//SRAM DMA写测试函数
SRAM_WriteDMA();
while(!HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin));
}
} /*KEY0被按下*/
if(HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin) == GPIO_PIN_RESET)
{
HAL_Delay(50);
if(HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin) == GPIO_PIN_RESET)
{
//SRAM DMA读测试函数
SRAM_ReadDMA();
while(!HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin));
}
}

6.5、实验现象

烧录程序,开发板上电后打开串口助手,按下KEY0按键以DMA方式从SRAM中指定位置读取数据,发现读取到错误的5个数据;按下KEY1按键将3000,3006,3012,3018,3024五个数据以DMA方式写入SRAM指定位置,再次按下KEY0按键以DMA方式从SRAM中指定位置读取数据,发现和我们写入的数据一致,整个过程读者也可以发现每次以DMA方式写入/读取SRAM完成之后都会进入DMA传输完成回调函数中,如下图所示为整个过程串口输出的详细信息

参考资料

STM32Cube高效开发教程(基础篇)

STM32CubeMX教程23 FSMC - IS62WV51216(SRAM)驱动的更多相关文章

  1. [译]Vulkan教程(23)暂存buffer

    [译]Vulkan教程(23)暂存buffer Staging buffer 暂存buffer Introduction 入门 The vertex buffer we have right now ...

  2. [SQL基础教程] 2-3 逻辑运算符

    [SQL基础教程] 2-3 逻辑运算符 NOT AND OR 优先级 ( )改变优先级 AND 优先级高于 OR NULL 引入三值逻辑

  3. Directx教程(23) 简单的光照模型(2)

    原文:Directx教程(23) 简单的光照模型(2)    在工程myTutorialD3D11_16中,我在文件light.vs中定义了一个材质光源属性常量缓冲. //const buffer最好 ...

  4. 迅为4412开发板Linux驱动教程——总线_设备_驱动注册流程详解

    本文转自:http://www.topeetboard.com 视频下载地址: 驱动注册:http://pan.baidu.com/s/1i34HcDB 设备注册:http://pan.baidu.c ...

  5. STM32CubeMX的串口配置,以及驱动代码

    1.STM32CubeMX的配置没啥子好说的,使能然后改一下波特率和字长,然后在将中断勾选,把中断等级调到1(一定要比systick的优先级垃圾!!!) 2.驱动代码 在生成的it.c文件中,例如用的 ...

  6. 迅为4412开发板Linux驱动教程——编写简单应用调用驱动

    Linux驱动教程:http://pan.baidu.com/s/1c0hljUS 编写简单应用调用驱动--头文件 • 打印头文件 – include <stdio.h>调用打印函数pri ...

  7. 迅为4412开发板Linux驱动教程——总线_设备_驱动注冊流程具体解释

    视频下载地址: 驱动注冊:http://pan.baidu.com/s/1i34HcDB 设备注冊:http://pan.baidu.com/s/1kTlGkcR 总线_设备_驱动注冊流程具体解释 • ...

  8. 【工业串口和网络软件通讯平台(SuperIO)教程】四.开发设备驱动

    SuperIO相关资料下载:http://pan.baidu.com/s/1pJ7lZWf 1.1    开发准备 把“开发包”内的所有文件复制到项目的“bin”目录下,或项目下的专用生成目录.开发包 ...

  9. Arduino入门教程--课前准备--Arduino驱动安装及1.0 IDE菜单介绍

    编译器版本:Arduino 1.0实验器件:ocrobot mango控制板(Arduino兼容)一块 Arduino控制板到手后,首先需要在电脑上把驱动装上,这样才可以进行各种实验. 第一步需要把A ...

  10. Spring Cloud架构教程 (六)消息驱动的微服务【Dalston版】

    Spring Cloud Stream是一个用来为微服务应用构建消息驱动能力的框架.它可以基于Spring Boot来创建独立的.可用于生产的Spring应用程序.它通过使用Spring Integr ...

随机推荐

  1. 子类Dog根据自己的需要,重写了Animal方法

    子类Dog根据自己的需要,重写了Animal方法 package com.guoba.method; class Animal{ public void move(){ System.out.prin ...

  2. ElasticSearch之健康状态

    参考Cluster health API. 命令样例,如下: curl -X GET "https://localhost:9200/_cluster/health?wait_for_sta ...

  3. 从零玩转系列之微信支付实战PC端支付微信退款接口搭建 | 技术创作特训营第一期

    一.前言 从零玩转系列之微信支付实战PC端支付微信退款接口搭建 | 技术创作特训营第一期 继前文章取消订单接口和查询订单接口此篇为申请退款流程,此篇文章过长我将分几个阶段的文章发布(项目源码都有,小程 ...

  4. .NET周刊 【12月第3期 2023-12-24】

    国内文章 CAP 8.0 版本发布通告 - CAP 7岁生日快乐! https://www.cnblogs.com/savorboard/p/cap-8-0.html 今天宣布CAP 8.0版本正式发 ...

  5. JavaFx之场景交互(二十一)

    JavaFx之场景交互(二十一) 有parent.son两个父子窗口,父窗口可以操作子窗口,父子可以相互调用对方的对象,下面我给出两种方案,我推荐使用第二种 一.构造传参 参数比较多的话代码不优雅.而 ...

  6. 新版的Django中的path不能使用正则表达式

    新版的path 虽然 取代了 之前的url,但是在写路由的时候不能在路由中直接写正则表达式,不然会找不到页面. 解决方法使用 re_path from django.urls import re_pa ...

  7. Android开发之账号密码登录跳转、固定时间显示进度条实现

    登陆界面.登陆跳转和进度条功能实现 首先打开Android studio新建一个空项目,打开layout文件夹下的activity_main.xml文件,来设置登陆界面的布局.登陆界面需要两个输入框, ...

  8. Python借助企业微信群机器人发送消息、图片和文件

    企业微信功能日益强大,腾讯官方也提供了丰富的API供开发者实现企业微信的很多自动化场景.比如,如何给某个企业微信群发送消息.图片或者文件,甚至@指定群用户?别急,看小爬君轻松教会大家. Step1:在 ...

  9. Prometheus 快速入门

    Prometheus&Grafana快速入门 一.prometheus简介 prometheus是监控多个大数据组件的监控系统.Prometheus是由SoundCloud开发的开源监控报警系 ...

  10. 打通Rasa Action Server和LLM接口的尝试方法

      本文使用最简单的方法对打通 Rasa Action Server 和 LLM 接口进行了尝试,即当 Rasa 对话 intent 为 out_of_scope 时,调用 action_gpt_fa ...