关键词:库函数,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