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),然后按照下面顺序配置
选择片选信号NE3
内存类型为SRAM
FSMC地址线19位
数据Data为16位宽度(D0-D15)
使能字节控制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读写测试函数,具体函数源代码如下所示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 void SRAM_WriteByFunc (void ) { uint32_t *pAddr = (uint32_t *)(SRAM_ADDR_BEGIN); uint8_t strIn[] = "Moment in UPC" ; uint16_t dataLen = sizeof (strIn); if (HAL_SRAM_Write_8b(&hsram3, pAddr, strIn, dataLen) == HAL_OK) { printf ("Write string at 0x6800 0000:" ); printf ("%s\r\n" ,strIn); } uint32_t num=0 ; pAddr=(uint32_t *)(SRAM_ADDR_BEGIN+256 ); HAL_RNG_GenerateRandomNumber(&hrng, &num); 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" ); } void SRAM_ReadByFunc (void ) { 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); } 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起始/中间/结束地址,并对定义的函数声明,源代码如下所示
1 2 3 4 5 6 7 8 #define SRAM_ADDR_BEGIN 0x68000000UL #define SRAM_ADDR_HALF 0x68080000UL #define SRAM_ADDR_END 0x680FFFFFUL void SRAM_ReadByFunc (void ) ;void SRAM_WriteByFunc (void ) ;
最后在主函数主循环while(1)中使用用户按键调用SRAM读写测试函数即可,源代码如下所示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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_WriteByFunc(); while (!HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin)); } } 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_ReadByFunc(); while (!HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin)); } }
4、常用函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 HAL_StatusTypeDef HAL_SRAM_Write_8b (SRAM_HandleTypeDef *hsram, uint32_t *pAddress, uint8_t *pSrcBuffer, uint32_t BufferSize) HAL_StatusTypeDef HAL_SRAM_Read_8b (SRAM_HandleTypeDef *hsram, uint32_t *pAddress, uint8_t *pDstBuffer, uint32_t BufferSize) HAL_StatusTypeDef HAL_SRAM_Write_16b (SRAM_HandleTypeDef *hsram, uint32_t *pAddress, uint8_t *pSrcBuffer, uint32_t BufferSize) HAL_StatusTypeDef HAL_SRAM_Read_16b (SRAM_HandleTypeDef *hsram, uint32_t *pAddress, uint8_t *pDstBuffer, uint32_t BufferSize) HAL_StatusTypeDef HAL_SRAM_Write_32b (SRAM_HandleTypeDef *hsram, uint32_t *pAddress, uint8_t *pSrcBuffer, uint32_t BufferSize) HAL_StatusTypeDef HAL_SRAM_Read_32b (SRAM_HandleTypeDef *hsram, uint32_t *pAddress, uint8_t *pDstBuffer, uint32_t BufferSize) HAL_StatusTypeDef HAL_SRAM_Write_DMA (SRAM_HandleTypeDef *hsram, uint32_t *pAddress, uint8_t *pSrcBuffer, uint32_t BufferSize) 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参数
单击Pinout & Configuration页面左边System Core/DMA选项卡
在Configuration中选择MemToMem
单击下方的增加按钮选择增加DMA请求
单击增加的DMA请求
在下方对其参数进行配置,模式为Normal
源/目标内存地址均递增
数据宽度选择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流与外设关联的函数
1 2 __HAL_LINKDMA(&hsram3, hdma, hdma_memtomem_dma2_stream0);
在main.c文件中增加以DMA方式读写SRAM的测试函数,源代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 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_Direction=1 ; DMA_Busy=1 ; uint32_t *pAddr_32b=(uint32_t *)(SRAM_ADDR_BEGIN); HAL_SRAM_Write_DMA(&hsram3, pAddr_32b, txBuffer, COUNT); } 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); HAL_SRAM_Read_DMA(&hsram3, pAddr_32b, rxBuffer, COUNT); }
在main.c文件头部增加函数声明及使用到的一些变量定义,源代码如下
1 2 3 4 5 6 7 8 9 10 void SRAM_WriteDMA (void ) ;void SRAM_ReadDMA (void ) ; #define COUNT 5 uint32_t txBuffer[COUNT]; uint32_t rxBuffer[COUNT]; uint8_t DMA_Direction = 1 ; uint8_t DMA_Busy = 0 ;
最后在主循环中使用用户按键调用SRAM读写测试函数即可,源代码如下所示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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_WriteDMA(); while (!HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin)); } } 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_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高效开发教程(基础篇)