STM32 SPI DMA驱动SRAM LY68L6400SLIT应用指南

关键词:库函数,STM32F407,SPI+DMA ,SPI-DMA,SRAM , LY68L6400SLIT,STM32CubeMX

编 辑:大黄蜂

说明:本笔记记录 基于 STM32F407 + RT RTOS 采用 SPI接口和 SPI+DMA接口 调试 SRAM LY68L6400SLIT (8M 字节 SRAM)

重点:STM32 HAL SPI 库函数;STM32CubeMX SPI 配置;STM32CubeMX SPI 配置+DMA配置;SPI+DMA 读写SRAM

项目测试平台:STM32F407核心板 + LY68L6400SLIT (8M 字节 SRAM)+ rt-thread4.0.2

调试心得:

1:调试 SRAM 时最开始用的是 STM32CubeMX 配置的 SPI 配置代码和 SPI 库函数,先期调试还算顺利,但测试时发现代码在读写大数据量,例如写满8M 耗时不记得了,读完8M耗时在14s左右。

2:希望优化 SRAM的读写速度,所以使用 SPI + DMA 读写,这个调试过程从最开始的读写错误,到后面发现有部分数据是准确的,再到读写不稳定,最后读写彻底稳定,连续读写15小时无错误。

3:发生SPI + DMA 调试不顺的问题主要是忽略了 DMA 发送数据的机制,DMA发送是需要时间的,一开始的测试代码都是执行了 DMA发送代码就立即 关掉了SRAM的使能。代码从执行了DMA读写后就立刻关掉了使能,这时DMA实际上正在执行读写操作,从而导致了DMA读写错误。

4:优化,在发现 DMA 收发都需要时间这一问题后,对此做了判断 DMA 状态的优化,读写DMA都先判断DMA就绪后再进行下一步操作,从而完成了SPI + DMA 对 SARM的读写测试。

5:在配置了 SPI + DMA 配置情况下,SPI直接读写函数和SPI+DMA函数都可以直接读写。

6:SPI + DMA 测试结果,SPI 时钟频率 40 M, 连续写8M数据耗时:1784ms ;连续读8M数据耗时:2070ms ;连续读8M数据 每次读 1K + CRC校验 耗时:14245ms。以上测试数据都是读写函数按每次 512字节读写情况下测试结果。

7:SRAM 读写在MCU上电前先复位一下 SRAM,避免因 MCU 重启而 SRAM 没有复位 导致出现读写异常的问题。

/* 调试遇到读写问题时的代码 */
rt_pin_write(SPI_SRAM_CS  , 0);                    /*使能SRAM 用RT函数控制*/
HAL_SPI_Transmit_DMA(&hspi1, pData, 5);            /*发送数据*/
HAL_SPI_Receive_DMA(&hspi1, ReadData, read_size);  /*接收数据*/
rt_pin_write(SPI_SRAM_CS  , 1);                    /*去使能SRAM 用RT函数控制*/

/* 优化后正常测试的代码 */
while(hdma_spi1_rx.State != HAL_DMA_STATE_READY ); /*等待DMA就绪*/
while(hdma_spi1_tx.State != HAL_DMA_STATE_READY ); /*等待DMA就绪*/
rt_pin_write(SPI_SRAM_CS  , 0);                    /*使能SRAM 用RT函数控制*/
HAL_SPI_Transmit_DMA(&hspi1, pData, 5);            /*发送数据*/
while(hdma_spi1_tx.State != HAL_DMA_STATE_READY ); /*等待DMA发送完成*/
HAL_SPI_Receive_DMA(&hspi1, ReadData, read_size);  /*接收数据*/
while(hdma_spi1_rx.State != HAL_DMA_STATE_READY ); /*等待DMA接收完成*/
rt_pin_write(SPI_SRAM_CS  , 1);             /*去使能SRAM 用RT函数控制*/

1:硬件图纸、连接

核心板资料:STM32F407VxT6 Board – LCD wiki

如图:将SRAM直接焊接到箭头指的FLASH封装上, 例程测试时是把 SRAM直接背到FLASH,再把CS管脚单独连接到LED0的驱动脚。

 

 

 2:STM32CubeMX SPI 配置流程、工程包

2.1配置调试接口

 2.2配置时钟

 

2.3配置SPI 

 2.4配置SPI 的 DMA

 2.5工程输出设置

 工程输出设置

 2.6生成代码

 生成后的文件路径,截图是压缩包压缩后的参考路径,实际路径根据工程保存路径查找。

 

3:STM32CubeMX SPI 代码移植、代码注释

3.1 把STM32CubeMX生成的 SPI 和 DMA 代码复制到 RT 工程的 board.c 文件后面

如:代码有两个模式 SPI 和 SPI + DMA 模式,根据宏定义选择

/*
 * Copyright (c) 2006-2021, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2021-09-15     RealThread   first version
 */

//#include <rtthread.h>     /* 这里把头文件转移到 user_cfg.h 文件下面了 */
//#include <board.h>        /* 这里把头文件转移到 user_cfg.h 文件下面了 */
//#include <drv_common.h>   /* 这里把头文件转移到 user_cfg.h 文件下面了 */
#include "user_cfg.h"

RT_WEAK void rt_hw_board_init()
{
    extern void hw_board_init(char *clock_src, int32_t clock_src_freq, int32_t clock_target_freq);

    /* Heap initialization */
#if defined(RT_USING_HEAP)
    rt_system_heap_init((void *) HEAP_BEGIN, (void *) HEAP_END);
#endif

    hw_board_init(BSP_CLOCK_SOURCE, BSP_CLOCK_SOURCE_FREQ_MHZ, BSP_CLOCK_SYSTEM_FREQ_MHZ);

    /* Set the shell console output device */
#if defined(RT_USING_DEVICE) && defined(RT_USING_CONSOLE)
    rt_console_set_device(RT_CONSOLE_DEVICE_NAME);
#endif

    /* Board underlying hardware initialization */
#ifdef RT_USING_COMPONENTS_INIT
    rt_components_board_init();
#endif

}


/* SPI 模式 */

#if SRAM_LY64_MODE_SPI
SPI_HandleTypeDef hspi1;

/* SPI1 init function */
void MX_SPI1_Init(void)
{

  /* USER CODE BEGIN SPI1_Init 0 */

  /* USER CODE END SPI1_Init 0 */

  /* USER CODE BEGIN SPI1_Init 1 */

  /* USER CODE END SPI1_Init 1 */
  hspi1.Instance = SPI1;
  hspi1.Init.Mode = SPI_MODE_MASTER;            //主机模式
  hspi1.Init.Direction = SPI_DIRECTION_2LINES;  //全双工
  hspi1.Init.DataSize = SPI_DATASIZE_8BIT;      //数据位为8位
  hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;    //CPOL=0
  hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;        //CPHA为数据线的第一个变化沿
  hspi1.Init.NSS = SPI_NSS_SOFT;                //软件控制NSS
  hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2; //2分频
  hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;                 //最高位先发送
  hspi1.Init.TIMode = SPI_TIMODE_DISABLE;                 //禁用TI模式
  hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; //禁用CRC校验
  hspi1.Init.CRCPolynomial = 10;                          //CRC值计算的多项式
  if (HAL_SPI_Init(&hspi1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN SPI1_Init 2 */

  /* USER CODE END SPI1_Init 2 */

}
INIT_COMPONENT_EXPORT(MX_SPI1_Init);

void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(spiHandle->Instance==SPI1)
  {
  /* USER CODE BEGIN SPI1_MspInit 0 */

  /* USER CODE END SPI1_MspInit 0 */
    /* SPI1 clock enable */
    __HAL_RCC_SPI1_CLK_ENABLE();

    __HAL_RCC_GPIOB_CLK_ENABLE();
    /**SPI1 GPIO Configuration
    PB3     ------> SPI1_SCK
    PB4     ------> SPI1_MISO
    PB5     ------> SPI1_MOSI
    */
    GPIO_InitStruct.Pin = GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

  /* USER CODE BEGIN SPI1_MspInit 1 */

  /* USER CODE END SPI1_MspInit 1 */
  }
}

void HAL_SPI_MspDeInit(SPI_HandleTypeDef* spiHandle)
{

  if(spiHandle->Instance==SPI1)
  {
  /* USER CODE BEGIN SPI1_MspDeInit 0 */

  /* USER CODE END SPI1_MspDeInit 0 */
    /* Peripheral clock disable */
    __HAL_RCC_SPI1_CLK_DISABLE();

    /**SPI1 GPIO Configuration
    PB3     ------> SPI1_SCK
    PB4     ------> SPI1_MISO
    PB5     ------> SPI1_MOSI
    */
    HAL_GPIO_DeInit(GPIOB, GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5);

  /* USER CODE BEGIN SPI1_MspDeInit 1 */

  /* USER CODE END SPI1_MspDeInit 1 */
  }
}

#endif



/* SPI + DMA 模式 */

#if SRAM_LY64_MODE_SPI_DMA
/* USER CODE END 0 */

 SPI_HandleTypeDef hspi1;
 DMA_HandleTypeDef hdma_spi1_rx;
 DMA_HandleTypeDef hdma_spi1_tx;

/* SPI1 init function */
void MX_SPI1_Init(void)
{

  /* USER CODE BEGIN SPI1_Init 0 */

  /* USER CODE END SPI1_Init 0 */

  /* USER CODE BEGIN SPI1_Init 1 */

  /* USER CODE END SPI1_Init 1 */
  hspi1.Instance = SPI1;                                      //SP1
  hspi1.Init.Mode = SPI_MODE_MASTER;                          //设置SPI工作模式,设置为主模式
  hspi1.Init.Direction = SPI_DIRECTION_2LINES;                //设置SPI单向或者双向的数据模式:SPI设置为双线模式
  hspi1.Init.DataSize = SPI_DATASIZE_8BIT;                    //设置SPI的数据大小:SPI发送接收8位帧结构
  hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;                  //串行同步时钟的空闲状态为高/底电平
  hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;                      //串行同步时钟的第1个跳变沿(上升或下降)数据被采样
  hspi1.Init.NSS = SPI_NSS_SOFT;                              //NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制
  hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;     //定义波特率预分频的值
  hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;                     //指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
  hspi1.Init.TIMode = SPI_TIMODE_DISABLE;                     //关闭TI模式
  hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;     //关闭硬件CRC校验
  hspi1.Init.CRCPolynomial = 10;                              //CRC值计算的多项式
  if (HAL_SPI_Init(&hspi1) != HAL_OK)                         //初始化
  {
    Error_Handler();
  }
  /* USER CODE BEGIN SPI1_Init 2 */

  /* USER CODE END SPI1_Init 2 */

}

void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(spiHandle->Instance==SPI1)
  {
  /* USER CODE BEGIN SPI1_MspInit 0 */

  /* USER CODE END SPI1_MspInit 0 */
    /* SPI1 clock enable */
    __HAL_RCC_SPI1_CLK_ENABLE();

    __HAL_RCC_GPIOB_CLK_ENABLE();
    /**SPI1 GPIO Configuration
    PB3     ------> SPI1_SCK
    PB4     ------> SPI1_MISO
    PB5     ------> SPI1_MOSI
    */
    GPIO_InitStruct.Pin = GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    /* SPI1 DMA Init */
    /* SPI1_RX Init */
    hdma_spi1_rx.Instance = DMA2_Stream0;                         //数据流选择
    hdma_spi1_rx.Init.Channel = DMA_CHANNEL_3;                    //通道选择
    hdma_spi1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;           //外设到存储器
    hdma_spi1_rx.Init.PeriphInc = DMA_PINC_DISABLE;               //外设非增量模式
    hdma_spi1_rx.Init.MemInc = DMA_MINC_ENABLE;                   //存储器增量模式
    hdma_spi1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;  //外设数据长度:8位
    hdma_spi1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;     //存储器数据长度:8位
    hdma_spi1_rx.Init.Mode = DMA_NORMAL;                          //外设流控模式
    hdma_spi1_rx.Init.Priority = DMA_PRIORITY_LOW;                //低优先级
    hdma_spi1_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
    if (HAL_DMA_Init(&hdma_spi1_rx) != HAL_OK)
    {
      Error_Handler();
    }

    __HAL_LINKDMA(spiHandle,hdmarx,hdma_spi1_rx);                 //将DMA与SPI1联系起来(发送DMA)

    /* SPI1_TX Init */
    hdma_spi1_tx.Instance = DMA2_Stream3;
    hdma_spi1_tx.Init.Channel = DMA_CHANNEL_3;
    hdma_spi1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
    hdma_spi1_tx.Init.PeriphInc = DMA_PINC_DISABLE;

    hdma_spi1_tx.Init.MemInc = DMA_MINC_ENABLE;
    hdma_spi1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
    hdma_spi1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
    hdma_spi1_tx.Init.Mode = DMA_NORMAL;
    hdma_spi1_tx.Init.Priority = DMA_PRIORITY_LOW;
    hdma_spi1_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
    if (HAL_DMA_Init(&hdma_spi1_tx) != HAL_OK)
    {
      Error_Handler();
    }

    __HAL_LINKDMA(spiHandle,hdmatx,hdma_spi1_tx);

  /* USER CODE BEGIN SPI1_MspInit 1 */

  /* USER CODE END SPI1_MspInit 1 */
  }
}

void HAL_SPI_MspDeInit(SPI_HandleTypeDef* spiHandle)
{

  if(spiHandle->Instance==SPI1)
  {
  /* USER CODE BEGIN SPI1_MspDeInit 0 */

  /* USER CODE END SPI1_MspDeInit 0 */
    /* Peripheral clock disable */
    __HAL_RCC_SPI1_CLK_DISABLE();

    /**SPI1 GPIO Configuration
    PB3     ------> SPI1_SCK
    PB4     ------> SPI1_MISO
    PB5     ------> SPI1_MOSI
    */
    HAL_GPIO_DeInit(GPIOB, GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5);

    /* SPI1 DMA DeInit */
    HAL_DMA_DeInit(spiHandle->hdmarx);
    HAL_DMA_DeInit(spiHandle->hdmatx);
  /* USER CODE BEGIN SPI1_MspDeInit 1 */

  /* USER CODE END SPI1_MspDeInit 1 */
  }
}

/* USER CODE BEGIN 1 */


void MX_DMA_Init(void)
{

  /* DMA controller clock enable */
  __HAL_RCC_DMA2_CLK_ENABLE();

  /* DMA interrupt init */
  /* DMA2_Stream0_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA2_Stream0_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA2_Stream0_IRQn);
  /* DMA2_Stream3_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA2_Stream3_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA2_Stream3_IRQn);

}

void MX_GPIO_Init(void)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOH_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, GPIO_PIN_RESET);

  /*Configure GPIO pin : PB14 */
  GPIO_InitStruct.Pin = GPIO_PIN_14;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

}

int SPI_Init_User()
{
    MX_GPIO_Init();
    MX_DMA_Init();
    MX_SPI1_Init();
    return 1;
}
INIT_COMPONENT_EXPORT(SPI_Init_User);


/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

/******************************************************************************/
/* STM32F4xx Peripheral Interrupt Handlers                                    */
/* Add here the Interrupt Handlers for the used peripherals.                  */
/* For the available peripheral interrupt handler names,                      */
/* please refer to the startup file (startup_stm32f4xx.s).                    */
/******************************************************************************/
extern DMA_HandleTypeDef hdma_spi1_rx;
extern DMA_HandleTypeDef hdma_spi1_tx;
/**
  * @brief This function handles DMA2 stream0 global interrupt.
  */
void DMA2_Stream0_IRQHandler(void)
{
  /* USER CODE BEGIN DMA2_Stream0_IRQn 0 */

  /* USER CODE END DMA2_Stream0_IRQn 0 */
  HAL_DMA_IRQHandler(&hdma_spi1_rx);
  /* USER CODE BEGIN DMA2_Stream0_IRQn 1 */

  /* USER CODE END DMA2_Stream0_IRQn 1 */
}

/**
  * @brief This function handles DMA2 stream3 global interrupt.
  */
void DMA2_Stream3_IRQHandler(void)
{
  /* USER CODE BEGIN DMA2_Stream3_IRQn 0 */

  /* USER CODE END DMA2_Stream3_IRQn 0 */
  HAL_DMA_IRQHandler(&hdma_spi1_tx);
  /* USER CODE BEGIN DMA2_Stream3_IRQn 1 */

  /* USER CODE END DMA2_Stream3_IRQn 1 */
}

#endif

4:HAL SPI 相关函数

参考1:以下内容摘录:https://www.cnblogs.com/xingboy/p/9555708.html

HAL库里的硬件SPI主要有以下几个库函数:

  /*  hspi1:spi1 硬件通道,temp_val:发送的数据,re_val:接收的数据,1:数据长度,1000:超时时间  */
  HAL_SPI_TransmitReceive(&hspi1,  &temp_val, &re_val, 1, 1000);   // 一边接受一边发送数据
  HAL_SPI_Transmit(&hspi1,&temp,sizeof(temp),10);  //发送数据
  HAL_SPI_Receive(&hspi1,&sc1161y_sel_re,sizeof(sc1161y_sel_re),10); //接收数据
  HAL_SPI_TransmitReceive_DMA();  //以DMA方式发送数据
  HAL_SPI_Receive_DMA();  //以DMA方式接收数据
  HAL_SPI_TransmitReceive_IT();  // 以中断方式同时接收发送数据
  HAL_SPI_Transmit_IT();  // 以中断方式发送数据
  HAL_SPI_Receive_IT();  // 以中断方式接收数据

具体使用哪个HAL库函数看项目需求。

在使用硬件SPI过程中,会出现的问题可以总结为以下几点:

  1.发送数据不成功;

  2.接收数据不成功;

  3.发送的数据有误;

  4.接收的数据有误;

  5.交互的数据一部分是对的,一部分有误;

  6.SPI时钟没有启动。

对于以上解决方法,我总结了一个自己调试时的方法:

  1. 先确认自己的SPI配置是否正确,是否满足项目需求;

  2. 确认电路与通信IC无误,注意信号线不要接错;

  3. 重点:调节延时,第一第二步确认无误后,很多时候不成功是由于延时原因造成的,

      主要是一个数据交互之间的延时,一帧数据发送后跟接收的延时,IC片选的延时,

   每个数据发送间的延时,IC与MCU交互间的延时。

5:SPI SRAM读写测试代码

/*
 * Copyright (c) 2006-2020, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2021-09-16     yl       the first version
 * .程序功能LY68L6400 读写
 *
 */

#include "user_cfg.h"
//#include <rtthread.h>
//#include <board.h>
//#include <rtdevice.h>
//#include "drv_spi.h"

#define SPI_SRAM_NEME                   "spi11"
#define SPI_SRAM_CS                     GET_PIN(A, 1)
#define LY64_CS(X)                      X? (GPIOA->ODR |= (0X0001<<1)):(GPIOA->ODR &=~ (0X0001<<1))   /* IO 直接控制控制模式 PA1 输出1 或者 0 */
#define SPI_FLASH_CS                    GET_PIN(B, 14)
#define SRAM_LY64_CMD_READ              0x0B        /*LY68L6400 快速读取命令 */
#define SRAM_LY64_CMD_WRITE             0x02        /*LY68L6400 写命令 */
#define SRAM_LY64_CMD_READ_ID           0x9F        /*LY68L6400 读ID命令*/
#define SRAM_LY64_CMD_Reset_Enable      0x66        /*LY68L6400 复位使能*/
#define SRAM_LY64_CMD_Reset             0x99        /*LY68L6400 复位*/

#define SRAM_LY64_SIZE                  1024*1024*8 /*LY68L6400 空间大小 字节*/

#define SRAM_LY64_ID_LEN                8           /*LY68L6400 ID长度 字节*/
#define SRAM_LY64_READ_LEN              512         /*LY68L6400 读取函数单次读取的最大长度,如果超过这个数量函数自动分多次读取*/
#define WR_SIZE                         1024        /*LY68L6400 写入函数单次写取的最大长度,如果超过这个数量函数自动分多次写入*/

#define READ_DATA_CRC                   1           /*LY68L6400 读取数据测试时 CRC 校验开关,需要计算读取时间时可以关闭CRC,1开启  0关闭*/
#define READ_DATA_PRINTF                0           /*LY68L6400 打印读取的SRAM数据 ,1开启  0关闭*/
/**
  * @brief   描  述:读取SRAM的ID
  * @param   参数1:uint32_t Size 读取数据量.
  * @param   参数2:uint8_t *ReadData 读取的数据存储地址.
  * @retval  回  复:无.
  * @author  作  者:YL
  * @date    日  期:2021.10.07
  * @version 版 本:V1.0
  * @warning 警 告:
  */
void Sram_LY64_Read_ID(uint32_t Size,uint8_t *ReadData)
{
        uint8_t pData[4];

        pData[0] = SRAM_LY64_CMD_READ_ID;
        pData[1] = 0;
        pData[2] = 0;
        pData[3] = 0;

        rt_enter_critical();/*调度器上锁*/

        rt_pin_write(SPI_SRAM_CS  , 0);             /*使能SRAM*/
        rt_hw_us_delay(2);
        HAL_SPI_Transmit(&hspi1, pData, 4, 2);      /*发送数据*/
        HAL_SPI_Receive(&hspi1, ReadData, Size,  2);/*接收数据*/

        rt_pin_write(SPI_SRAM_CS  , 1);             /*去使能SRAM*/
        rt_exit_critical();/*调度器解锁*/
}

/**
  * @brief   描  述:复位SRAM
  * @param   参数1:
  * @retval  回  复:无.
  * @author  作  者:YL
  * @date    日  期:2021.10.13
  * @version 版 本:V1.0
  * @warning 警 告:
  */
void Sram_LY64_Reset(void)
{
        uint8_t pData[2];

        pData[0] = SRAM_LY64_CMD_Reset_Enable;
        pData[1] = SRAM_LY64_CMD_Reset;

        rt_enter_critical();/*调度器上锁*/

        rt_pin_write(SPI_SRAM_CS  , 0);             /*使能SRAM*/
        rt_hw_us_delay(2);
        HAL_SPI_Transmit(&hspi1, pData, 2, 2);      /*发送数据*/

        rt_pin_write(SPI_SRAM_CS  , 1);             /*去使能SRAM*/
        rt_exit_critical();/*调度器解锁*/
}

/**
  * @brief   描  述:读取指定地址开始指定数量的 SRAM数据.
  * @param   参数1:uint32_t addr 开始地址.
  * @param   参数2:uint32_t Size 读取数据量.
  * @param   参数3:uint8_t *ReadData 读取的数据存储地址.
  * @retval  回  复:无.
  * @author  作  者:YL
  * @date    日  期:2021.10.07
  * @version 版 本:V1.0
  * @warning 警 告:如果读取数量超过 SRAM_LY64_READ_LEN 程序会自动按设置好的单次读取最大数量分多次读取。
  */
void Sram_LY64_Read(uint32_t addr,uint32_t Size,uint8_t *ReadData)
{
    uint8_t pData[5];
    uint16_t read_size;

    do
    {
        pData[0] = SRAM_LY64_CMD_READ;
        pData[1] = addr>>16;
        pData[2] = addr>>8;
        pData[3] = addr;
        pData[4] = 0x00; /* SPI Fast Read ‘h0B 命令的时序要求要等待8个时钟周期。 */

        if (Size > SRAM_LY64_READ_LEN)
        {
            read_size = SRAM_LY64_READ_LEN;
            Size = Size - SRAM_LY64_READ_LEN;
        }
        else
        {
            read_size = Size;
            Size = 0;
        }

#if SRAM_LY64_MODE_SPI
        //rt_enter_critical();/*调度器上锁*/
        rt_pin_write(SPI_SRAM_CS  , 0);                   /*使能SRAM 用RT函数控制*/
        //LY64_CS(0);                                     /*使能SRAM 寄存器直接控制*/
        rt_hw_us_delay(2);

        HAL_SPI_Transmit(&hspi1, pData, 5, 2);            /*发送数据*/
        HAL_SPI_Receive(&hspi1, ReadData, read_size, 2);  /*接收数据*/
#endif

#if SRAM_LY64_MODE_SPI_DMA
        while(hdma_spi1_rx.State != HAL_DMA_STATE_READY ); /*等待DMA就绪*/
        while(hdma_spi1_tx.State != HAL_DMA_STATE_READY ); /*等待DMA就绪*/
        rt_pin_write(SPI_SRAM_CS  , 0);                    /*使能SRAM 用RT函数控制*/
        HAL_SPI_Transmit_DMA(&hspi1, pData, 5);            /*发送数据*/
        while(hdma_spi1_tx.State != HAL_DMA_STATE_READY ); /*等待DMA发送完成*/
        HAL_SPI_Receive_DMA(&hspi1, ReadData, read_size);  /*接收数据*/
        while(hdma_spi1_rx.State != HAL_DMA_STATE_READY ); /*等待DMA接收完成*/
#endif

        rt_pin_write(SPI_SRAM_CS  , 1);             /*去使能SRAM 用RT函数控制*/
        //LY64_CS(1);                                   /*去使能SRAM 寄存器直接控制*/
        //rt_exit_critical();/*调度器解锁*/
        addr = addr + read_size;
        ReadData = ReadData + read_size;
    }while(Size);

}


/**
  * @brief   描  述:写指定数量数据到 SRAM数据.
  * @param   参数1:uint32_t addr 开始地址.
  * @param   参数2:uint32_t Size 数据量.
  * @param   参数3:uint8_t *WriteData 待写入数据存储地址.
  * @retval  回  复:无.
  * @author  作  者:YL
  * @date    日  期:2021.10.07
  * @version 版 本:V1.0
  * @warning 警 告:
  */

void Sram_LY64_Write(uint32_t addr,uint32_t Size,uint8_t *WriteData)
{
    uint8_t *test = WriteData-4;
    /* 连续写入 */

#if SRAM_LY64_MODE_SPI
    uint8_t pData[5];
    rt_pin_write(SPI_SRAM_CS  , 0);
    rt_hw_us_delay(2);
    pData[0] = SRAM_LY64_CMD_WRITE;
    pData[1] = addr>>16;
    pData[2] = addr>>8;
    pData[3] = addr;
    pData[4] = *WriteData;

    HAL_SPI_Transmit(&hspi1, pData, 4, 2);        /* 发送写命令 */
    HAL_SPI_Transmit(&hspi1, WriteData, Size, 2); /* 发送待写入数据 */
#endif

#if SRAM_LY64_MODE_SPI_DMA                                /* 把写命令放入待发送数据中 */
    *(WriteData - 4) = SRAM_LY64_CMD_WRITE;               /* 这里是待写入数据的首地址,在线程中定义了结构体,这个地址前4个地址是预留存放写命令用的 */
    *(WriteData - 3) = addr>>16;
    *(WriteData - 2) = addr>>8;
    *(WriteData - 1) = addr;

    while(hdma_spi1_rx.State != HAL_DMA_STATE_READY );    /* 等待DMA就绪 */
    while(hdma_spi1_tx.State != HAL_DMA_STATE_READY );    /* 等待DMA就绪 */
    rt_pin_write(SPI_SRAM_CS  , 0);
    HAL_SPI_Transmit_DMA(&hspi1, test, Size + 4);         /* 发送待写入数据 */
    while(hdma_spi1_tx.State != HAL_DMA_STATE_READY );    /* 等待DMA发送完成 */

#endif

    //rt_kprintf("SIZE = %d,addr = %08x, %02x , %02x , %02x , %02x , %02x \r\n",Size,addr,pData[0],pData[1],pData[2],pData[3],pData[4]);
    rt_pin_write(SPI_SRAM_CS  , 1);

}

/* LY68L6400 读写测试线程,可以测试读取指定大小数据花费时间、测试读写可靠性 */
void SARM_LY64_Test_entry(void *param)
{
    rt_pin_mode(SPI_SRAM_CS  , PIN_MODE_OUTPUT);
    rt_pin_mode(SPI_FLASH_CS  , PIN_MODE_OUTPUT); /* 测试板FLASH 与 SARM 共用SPI接口 */
    rt_pin_write(SPI_FLASH_CS  , 1);              /* 测试板FLASH 与 SARM 共用SPI接口,这里先把FLASH CS 拉高 */

    rt_thread_mdelay(3000);

    rt_uint8_t id[SRAM_LY64_ID_LEN] = {0};       /* SRAM id 存储 */
    uint32_t data_addr = 0;                      /* 待读写 SRAM 首地址 */

    struct spi_wdata                             /* 待写入数据结构体 */
    {
        uint8_t  cmd[4];         /* 写数据命令 */
        uint8_t  data[WR_SIZE];  /* 待写入SRAM的数据 */
    } spi_wdata_1;

    uint8_t   *wdata = &spi_wdata_1.data[0]; /* 待写入数据指针 */
    uint8_t   rdata[WR_SIZE] = {0};
    uint32_t  stime;                    /* 开始时间 */
    uint32_t  etime;                    /* 结束时间 */
    uint16_t  crc_data_w;               /* 写入数据CRC*/
    uint16_t  crc_data_r;               /* 读出数据CRC*/
    uint32_t  read_crc_ok = 0;          /* 单次读出数据CRC 成功次数*/
    uint32_t  read_crc_err = 0;         /* 单次读出数据CRC 失败次数*/
    uint32_t  read_crc_err_sum = 0;     /* 总读出数据CRC 失败次数*/

    uint16_t test_num = 1;              /* 测试次数 */
    uint8_t i = 1;

    /* 待写入数据初始化 */
    for (uint16_t var = 0; var < WR_SIZE; ++var)
    {
        if (i>100)
        {
           i = 1;
        }
        wdata[var] = i;
        ++i;
    }

    Sram_LY64_Reset(); /* 复位 SRAM */

    while(1)
    {
        rt_thread_mdelay(100);

        /* 读取SRAM ID */
        Sram_LY64_Read_ID(SRAM_LY64_ID_LEN,id);
        rt_kprintf("Read ID is:%02x %02x %02x %02x %02x %02x %02x %02x \n", id[0],id[1],id[2],id[3], id[4], id[5],id[6],id[7]);

        /* SRAM写入数据 */
        data_addr = 0;
        crc_data_w = yl_crc16(wdata,WR_SIZE);  /* 校验待写入数据,以便与读出数据做对比 */
        rt_kprintf("写入数据 CRC = %04x \r\n",crc_data_w);

        /* 往SRAM中写满数据 */
        stime = rt_tick_get();  /* 获取系统时间 */
        do
        {
            Sram_LY64_Write(data_addr,WR_SIZE,wdata);
            data_addr = data_addr +  WR_SIZE;

        } while (data_addr < SRAM_LY64_SIZE);
        etime = rt_tick_get();  /* 获取系统时间 */
        rt_kprintf("写  开始 = %d 结束 = %d 耗时 = %d\r\n",stime,etime,etime-stime); /* 计算写入过程耗时 */

        rt_thread_mdelay(10);

        /* 读取SRAM全部数据 */
        i = 5;  /* 循环读取测试次数 */
        while(i)
        {
            data_addr = 0;
            rt_kprintf("倒数第 %d 次读取测试...\r\n",i);
            stime = rt_tick_get();
            do
            {
                Sram_LY64_Read(data_addr,WR_SIZE,rdata);
                data_addr = data_addr +  WR_SIZE;

#if READ_DATA_CRC
                /* CRC 校验读取的数据 */
                crc_data_r = yl_crc16(rdata,WR_SIZE);
                if (crc_data_r == crc_data_w)
                {
                    //rt_kprintf("读第 %d 次, 校验成功:地址: adr = %08x , CRC写 = %04x , CRC读 = %04x\r\n",i,data_addr-WR_SIZE,crc_data_w,crc_data_r);
                    ++read_crc_ok;
                }
                else
                {
                    rt_kprintf("\r\n\r\n\r\n 起始地址: %08x , %d 校验失败: CRC写 = %04x , CRC读 = %04x \r\n\r\n\r\n",data_addr-WR_SIZE,i,crc_data_w,crc_data_r);
                    ++read_crc_err;
                    ++read_crc_err_sum;
                }
#endif
                /* 打印读取的SRAM数据 */
#if READ_DATA_PRINTF
                rt_kprintf(" 读开始地址: adr = %08x \r\n",data_addr-WR_SIZE);
                if (crc_data_r != crc_data_w)
                {
                    for (uint16_t var = 0; var < WR_SIZE; ++var)
                    {
                        rt_kprintf("读SRAM var= %d Data:%d %d %d %d %d %d %d %d %d %d\n",var, rdata[var+0],rdata[var+1],rdata[var+2],rdata[var+3], rdata[var+4], rdata[var+5],rdata[var+6],rdata[var+7],rdata[var+8], rdata[var+9]);
                        var = var + 9;
                    }
                }

#endif

            } while (data_addr < SRAM_LY64_SIZE);
            etime = rt_tick_get();

            rt_kprintf("读取大小 = %d 字节, 时间统计:开始 = %d 结束 = %d 耗时 = %d\r\n",SRAM_LY64_SIZE,stime,etime,etime-stime);

            rt_memset(rdata, 0, WR_SIZE);  /* 把读数据缓存清零 */
            --i;
        }
        rt_kprintf("第 %d 次测试:写1次,连读 5 次 ;单次全部读取,每次读1K字节:校验成功次数 = %d ,校验失败次数 = %d ;累计校验失败次数 = %d\r\n",test_num,read_crc_ok,read_crc_err,read_crc_err_sum);
        ++test_num;
        read_crc_err = 0;
        read_crc_ok = 0;
        rt_thread_mdelay(1000);
    }

}


/*线程创建函数*/
int SARM_LY64_Test(void)
{
    rt_thread_t tid1;                                          /*创建线程控制块指针来接收线程创建函数的返回值,目的是通过返回值判断线程是否创建ok*/

    /* 创建线程 1,名称是 SARM_LY64_Test,入口是 SARM_LY64_Test_entry*/

    tid1 = rt_thread_create("SARM_LY64_Test",         /*线程名称,系统打印线程时会显示这个线程的名字*/
                            SARM_LY64_Test_entry,     /*线程入口函数,入口函数函数名*/
                            RT_NULL,                  /*入口参数*/
                            2000 + WR_SIZE*2,         /*设置内存堆栈大小*/
                            9,                        /*设置优先级*/
                            200);                     /*时间片参数,时间片是在有多个相同优先级线程时,这个线程每次被执行多少个时间片*/


    /* 如果获得线程控制块,启动这个线程 */
    if (tid1 != RT_NULL)
        rt_thread_startup(tid1);

    return RT_EOK;
}
INIT_APP_EXPORT(SARM_LY64_Test);

6.测试信息输出

 

7:参考资源链接

优质参考资料:STM32 SPI通信协议详细讲解—小白入门_阿乔不想编程的博客-CSDN博客

其他参考:

https://blog.csdn.net/weixin_41294615/article/details/103233374?depth_1-

https://yngzmiao.blog.csdn.net/article/details/80318821

https://www.freesion.com/article/65571428542/

https://my.oschina.net/u/4386235/blog/3937830

https://blog.csdn.net/qq_54747686/article/details/119221405?spm=1001.2014.3001.5501

https://www.cnblogs.com/xuhaojieixbwer/p/14270116.html

https://www.cnblogs.com/xingboy/p/9555708.html

https://xfxuezhang.blog.csdn.net/article/details/108716706

https://blog.csdn.net/as480133937/article/details/104827639/?spm=1001.2101.3001.4242

https://blog.csdn.net/as480133937/article/details/105849607

https://blog.csdn.net/as480133937/article/details/104827639

物联沃分享整理
物联沃-IOTWORD物联网 » STM32 SPI DMA驱动SRAM LY68L6400SLIT应用指南

发表评论