STM32H7的SPI总线应用之双机通信(DMA方式)

Posted Simon223

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STM32H7的SPI总线应用之双机通信(DMA方式)相关的知识,希望对你有一定的参考价值。

完整教程下载地址:链接

第94章       STM32H7的SPI总线应用之双机通信(DMA方式)

本章节为大家讲解SPI DMA方式双机通信。

目录

94.1 初学者重要提示

94.2 SPI DMA主从机硬件接线

94.3 SPI DMA主机程序设计

94.3.1 第1步:SPI总线配置

94.3.2 第2步:SPI DMA配置

94.3.3 第3步:SPI DMA传输设置和MPU配置

94.3.4 第4步:应用代码设计

94.4 SPI DMA从机程序设计

94.4.1 第1步:SPI总线配置

94.4.2 第2步:SPI DMA配置

94.4.3 第3步:SPI DMA传输设置和MPU配置

94.4.4 第4步:应用代码设计

94.5 SPI DMA主从机使用注意事项

94.6 SPI DMA主从机驱动移植和使用

94.7 实验例程设计框架

94.8 实验例程说明(MDK)

94.9 实验例程说明(IAR)

94.10   总结


94.1 初学者重要提示

  1.   学习本章节前,务必优先学习第72章SPI基础和第73章SPI Flash的DMA玩法方式。本章实现的SPI DMA通信方式的主机和从机,跟SPI DMA方式驱动SPI Flash是类似的。
  2.   本章是采用的SPI DMA全双工通信方式。
  3.   大家根据自己接线的稳定性,可以适当调节SPI主机和从机的时钟速度,其中从机的时钟速度是可以高于主机速度的,这样通信的容错性更好些。

94.2 SPI DMA主从机硬件接线

接线方式如下,使用的两块V7板子,一块板子做主机,一块板子做从机。

 对应的引脚信息如下:

 实际项目中使用,推荐大家务必比将硬件片选引脚NSS接上,实现全程硬件控制收发。如果大家不使用硬件片选,而使用下面的方式:

 这种方式有个比较明显的缺点,主从机上电次序不同,很容易造成从机CLK识别错误,即高低电平变化导致数据传输错位。改成加入硬件SPI片选NSS引脚后,完美解决了这个问题

94.3 SPI DMA主机程序设计

SPI DMA主机程序实现和本教程72的SPI DMA配置是一样的,只是多了SPI硬件片选引脚NSS配置。

94.3.1 第1步:SPI总线配置

SPI总线配置通过如下两个函数实现:

/*
*********************************************************************************************************
*	函 数 名: bsp_InitSPIBus
*	功能说明: 配置SPI总线。
*	形    参: 无
*	返 回 值: 无
*********************************************************************************************************
*/
void bsp_InitSPIBus(void)
	
	g_spi_busy = 0;
	
	bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_16, SPI_PHASE_1EDGE, SPI_POLARITY_LOW);


/*
*********************************************************************************************************
*	函 数 名: bsp_InitSPIParam
*	功能说明: 配置SPI总线参数,时钟分频,时钟相位和时钟极性。
*	形    参: _BaudRatePrescaler  SPI总线时钟分频设置,支持的参数如下:
*                                 SPI_BAUDRATEPRESCALER_2    2分频
*                                 SPI_BAUDRATEPRESCALER_4    4分频
*                                 SPI_BAUDRATEPRESCALER_8    8分频
*                                 SPI_BAUDRATEPRESCALER_16   16分频
*                                 SPI_BAUDRATEPRESCALER_32   32分频
*                                 SPI_BAUDRATEPRESCALER_64   64分频
*                                 SPI_BAUDRATEPRESCALER_128  128分频
*                                 SPI_BAUDRATEPRESCALER_256  256分频
*                                                        
*             _CLKPhase           时钟相位,支持的参数如下:
*                                 SPI_PHASE_1EDGE     SCK引脚的第1个边沿捕获传输的第1个数据
*                                 SPI_PHASE_2EDGE     SCK引脚的第2个边沿捕获传输的第1个数据
*                                 
*             _CLKPolarity        时钟极性,支持的参数如下:
*                                 SPI_POLARITY_LOW    SCK引脚在空闲状态处于低电平
*                                 SPI_POLARITY_HIGH   SCK引脚在空闲状态处于高电平
*
*	返 回 值: 无
*********************************************************************************************************
*/
void bsp_InitSPIParam(uint32_t _BaudRatePrescaler, uint32_t _CLKPhase, uint32_t _CLKPolarity)

	/* 提高执行效率,只有在SPI硬件参数发生变化时,才执行HAL_Init */
	if (s_BaudRatePrescaler == _BaudRatePrescaler && s_CLKPhase == _CLKPhase && s_CLKPolarity == _CLKPolarity)
			
		return;
	

	s_BaudRatePrescaler = _BaudRatePrescaler;	
	s_CLKPhase = _CLKPhase;
	s_CLKPolarity = _CLKPolarity;
	
	
	/* 设置SPI参数 */
	hspi.Instance               = SPIx;                   /* 例化SPI */
	hspi.Init.BaudRatePrescaler = _BaudRatePrescaler;     /* 设置波特率 */
	hspi.Init.Direction         = SPI_DIRECTION_2LINES;   /* 全双工 */
	hspi.Init.CLKPhase          = _CLKPhase;              /* 配置时钟相位 */
	hspi.Init.CLKPolarity       = _CLKPolarity;           /* 配置时钟极性 */
	hspi.Init.DataSize          = SPI_DATASIZE_8BIT;      /* 设置数据宽度 */
	hspi.Init.FirstBit          = SPI_FIRSTBIT_MSB;       /* 数据传输先传高位 */
	hspi.Init.TIMode            = SPI_TIMODE_DISABLE;     /* 禁止TI模式  */
	hspi.Init.CRCCalculation    = SPI_CRCCALCULATION_DISABLE; /* 禁止CRC */
	hspi.Init.CRCPolynomial     = 7;                       /* 禁止CRC后,此位无效 */
	hspi.Init.CRCLength         = SPI_CRC_LENGTH_8BIT;     /* 禁止CRC后,此位无效 */
	hspi.Init.NSS               = SPI_NSS_SOFT;               /* 使用软件方式管理片选引脚 */
	hspi.Init.FifoThreshold     = SPI_FIFO_THRESHOLD_01DATA;  /* 设置FIFO大小是一个数据项 */
	hspi.Init.NSSPMode          = SPI_NSS_PULSE_DISABLE;      /* 禁止脉冲输出 */
	hspi.Init.MasterKeepiostate = SPI_MASTER_KEEP_IO_STATE_ENABLE; /* 禁止SPI后,SPI相关引脚保持当前状态 */  
	hspi.Init.Mode 		    = SPI_MODE_MASTER;            /* SPI工作在主控模式 */

	/* 设置SPI参数 */
	hspi.Instance               = SPIx;                   		/* 例化SPI */
	hspi.Init.BaudRatePrescaler = _BaudRatePrescaler;     		/* 设置波特率 */
	hspi.Init.Direction         = SPI_DIRECTION_2LINES;         /* 全双工 */
	hspi.Init.CLKPhase          = _CLKPhase;             		/* 配置时钟相位 */
	hspi.Init.CLKPolarity       = _CLKPolarity;           		/* 配置时钟极性 */
	hspi.Init.DataSize          = SPI_DATASIZE_8BIT;      	 	/* 设置数据宽度 */
	hspi.Init.FirstBit          = SPI_FIRSTBIT_MSB;         	/* 数据传输先传高位 */
	hspi.Init.TIMode            = SPI_TIMODE_DISABLE;     		/* 禁止TI模式  */
	hspi.Init.CRCCalculation    = SPI_CRCCALCULATION_DISABLE; 	/* 禁止CRC */
	hspi.Init.CRCPolynomial     = 7;                       		/* 禁止CRC后,此位无效 */
	hspi.Init.CRCLength         = SPI_CRC_LENGTH_8BIT;     		/* 禁止CRC后,此位无效 */
	hspi.Init.FifoThreshold     = SPI_FIFO_THRESHOLD_16DATA;	/* 设置FIFO大小是一个数据项 */
	
	hspi.Init.NSS         = SPI_NSS_HARD_OUTPUT;         		/* 片选引脚 */
	hspi.Init.NSSPMode    = SPI_NSS_PULSE_DISABLE;    			/* 禁止脉冲输出 */
	hspi.Init.NSSPolarity = SPI_NSS_POLARITY_LOW;               /* 低电平有效 */
	hspi.Init.MasterSSIdleness        = SPI_MASTER_SS_IDLENESS_00CYCLE; /* MSS, 插入到NSS有效边沿和第一个
数据开始之间的额外延迟,单位SPI时钟周期个数 */

	hspi.Init.MasterInterDataIdleness = SPI_MASTER_INTERDATA_IDLENESS_10CYCLE; /* MIDI, 两个连续数据帧之间
插入的最小时间延迟,单位SPI时钟周期个数 */
	
	hspi.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_ENABLE; /* 禁止SPI后,SPI相关引脚保持当前状态 */  
	hspi.Init.Mode 			 	= SPI_MODE_MASTER;          	  

	/* 复位配置 */
	if (HAL_SPI_DeInit(&hspi) != HAL_OK)
	
		Error_Handler(__FILE__, __LINE__);
		

	/* 初始化配置 */
	if (HAL_SPI_Init(&hspi) != HAL_OK)
	
		Error_Handler(__FILE__, __LINE__);
		

关于这两个函数有以下四点要做个说明:

  •   函数bsp_InitSPIBus里面的配置是个初始设置。实际驱动芯片时,会通过函数bsp_InitSPIParam做再配置。
  •   函数bsp_InitSPIParam提供了时钟分频,时钟相位和时钟极性配置。一般主机和从机此处设置为一样即可,但推荐
  •   SPI硬件片选NSS设置为SPI_NSS_HARD_OUTPUT。
  •   这里特别注意主机是hspi.Init.Mode = SPI_MODE_MASTER。

94.3.2 第2步:SPI DMA配置

配置代码实现如下,注释比较详细:

/*
*********************************************************************************************************
*	函 数 名: bsp_InitSPIParam
*	功能说明: 配置SPI总线时钟,GPIO,中断,DMA等
*	形    参: SPI_HandleTypeDef 类型指针变量
*	返 回 值: 无
*********************************************************************************************************
*/
void HAL_SPI_MspInit(SPI_HandleTypeDef *_hspi)

	/* 配置 SPI总线GPIO : SCK MOSI MISO */
	
		GPIO_InitTypeDef  GPIO_InitStruct;
			
		/* SPI和GPIP时钟 */
		SPIx_SCK_CLK_ENABLE();
		SPIx_MISO_CLK_ENABLE();
		SPIx_MOSI_CLK_ENABLE();
		SPIx_CLK_ENABLE();

		/* SPI SCK */
		GPIO_InitStruct.Pin       = SPIx_SCK_PIN;
		GPIO_InitStruct.Mode      = GPIO_MODE_AF_PP;
		GPIO_InitStruct.Pull      = GPIO_PULLUP;
		GPIO_InitStruct.Speed     = GPIO_SPEED_FREQ_HIGH;
		GPIO_InitStruct.Alternate = SPIx_SCK_AF;
		HAL_GPIO_Init(SPIx_SCK_GPIO, &GPIO_InitStruct);

		/* SPI MISO */
		GPIO_InitStruct.Pin = SPIx_MISO_PIN;
		GPIO_InitStruct.Alternate = SPIx_MISO_AF;
		HAL_GPIO_Init(SPIx_MISO_GPIO, &GPIO_InitStruct);

		/* SPI MOSI */
		GPIO_InitStruct.Pin = SPIx_MOSI_PIN;
		GPIO_InitStruct.Alternate = SPIx_MOSI_AF;
		HAL_GPIO_Init(SPIx_MOSI_GPIO, &GPIO_InitStruct);
        
        /* SPI NSS */
        GPIO_InitStruct.Pin = SPIx_NSS_PIN;
        GPIO_InitStruct.Alternate = SPIx_NSS_AF;
        HAL_GPIO_Init(SPIx_NSS_GPIO, &GPIO_InitStruct);
	

	/* 配置DMA和NVIC */
	#ifdef USE_SPI_DMA
	
		/* 使能DMA时钟 */
		DMAx_CLK_ENABLE();      

		/* SPI DMA发送配置 */		
		hdma_tx.Instance                = SPIx_TX_DMA_STREAM;    /* 例化使用的DMA数据流 */
		hdma_tx.Init.FIFOMode           = DMA_FIFOMODE_ENABLE;   /* FIFO*/
		hdma_tx.Init.FIFOThreshold      = DMA_FIFO_THRESHOLD_FULL;/* 禁止FIFO此位不起作用,用于设置阀值 */
		hdma_tx.Init.MemBurst            = DMA_MBURST_SINGLE;	/* 禁止FIFO此位不起作用,用于存储器突发 */
		hdma_tx.Init.PeriphBurst         = DMA_PBURST_SINGLE;	 /* 禁止FIFO此位不起作用,用于外设突发 */
		hdma_tx.Init.Request             = SPIx_TX_DMA_REQUEST;     /* 请求类型 */  
		hdma_tx.Init.Direction           = DMA_MEMORY_TO_PERIPH;    /* 传输方向是从存储器到外设 */  
		hdma_tx.Init.PeriphInc           = DMA_PINC_DISABLE;        /* 外设地址自增禁止 */ 
		hdma_tx.Init.MemInc              = DMA_MINC_ENABLE;         /* 存储器地址自增使能 */  
		hdma_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;     /* 外设数据传输位宽选择字节,即8bit */ 
		hdma_tx.Init.MemDataAlignment    = DMA_MDATAALIGN_BYTE;     /* 存储器数据传输位宽选择字节,即8bit */    
		hdma_tx.Init.Mode                = DMA_NORMAL;              /* 正常模式 */
		hdma_tx.Init.Priority            = DMA_PRIORITY_LOW;        /* 优先级低 */

		 /* 复位DMA */
		if(HAL_DMA_DeInit(&hdma_tx) != HAL_OK)
		
			Error_Handler(__FILE__, __LINE__);     
		
		
		 /* 初始化DMA */
		if(HAL_DMA_Init(&hdma_tx) != HAL_OK)
		
			Error_Handler(__FILE__, __LINE__);     
		

		/* 关联DMA句柄到SPI */
		__HAL_LINKDMA(_hspi, hdmatx, hdma_tx);	

		/* SPI DMA接收配置 */	
		hdma_rx.Instance                = SPIx_RX_DMA_STREAM;     /* 例化使用的DMA数据流 */
		hdma_rx.Init.FIFOMode           = DMA_FIFOMODE_ENABLE;    /* FIFO*/
		hdma_rx.Init.FIFOThreshold      = DMA_FIFO_THRESHOLD_FULL;/* 禁止FIFO此位不起作用,用于设置阀值 */
		hdma_rx.Init.MemBurst            = DMA_MBURST_SINGLE;	/* 禁止FIFO此位不起作用,用于存储器突发 */
		hdma_rx.Init.PeriphBurst         = DMA_PBURST_SINGLE;	/* 禁止FIFO此位不起作用,用于外设突发 */
		hdma_rx.Init.Request             = SPIx_RX_DMA_REQUEST;    /* 请求类型 */  
		hdma_rx.Init.Direction           = DMA_PERIPH_TO_MEMORY;   /* 传输方向从外设到存储器 */  
		hdma_rx.Init.PeriphInc           = DMA_PINC_DISABLE;       /* 外设地址自增禁止 */   
		hdma_rx.Init.MemInc              = DMA_MINC_ENABLE;        /* 存储器地址自增使能 */   
		hdma_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;    /* 外设数据传输位宽选择字节,即8bit */ 
		hdma_rx.Init.MemDataAlignment    = DMA_MDATAALIGN_BYTE;    /* 存储器数据传输位宽选择字节,即8bit */   
		hdma_rx.Init.Mode                = DMA_NORMAL;             /* 正常模式 */
		hdma_rx.Init.Priority            = DMA_PRIORITY_HIGH;      /* 优先级高 */

		 /* 复位DMA */
		if(HAL_DMA_DeInit(&hdma_rx) != HAL_OK)
		
			Error_Handler(__FILE__, __LINE__);     
		
		
		 /* 初始化DMA */
		if(HAL_DMA_Init(&hdma_rx) != HAL_OK)
		
			Error_Handler(__FILE__, __LINE__);     
		

		/* 关联DMA句柄到SPI */
	   __HAL_LINKDMA(_hspi, hdmarx, hdma_rx);	

		/* 配置DMA发送中断 */
		HAL_NVIC_SetPriority(SPIx_DMA_TX_IRQn, 1, 0);
		HAL_NVIC_EnableIRQ(SPIx_DMA_TX_IRQn);
		
		/* 配置DMA接收中断 */
		HAL_NVIC_SetPriority(SPIx_DMA_RX_IRQn, 1, 0);
		HAL_NVIC_EnableIRQ(SPIx_DMA_RX_IRQn);
		
		/* 配置SPI中断 */
		HAL_NVIC_SetPriority(SPIx_IRQn, 1, 0);
		HAL_NVIC_EnableIRQ(SPIx_IRQn);
	
	#endif
	
	#ifdef USE_SPI_INT
		/* 配置SPI中断优先级并使能中断 */
		HAL_NVIC_SetPriority(SPIx_IRQn, 1, 0);
		HAL_NVIC_EnableIRQ(SPIx_IRQn);
	#endif

94.3.3 第3步:SPI DMA传输设置和MPU配置

SPI DMA方式主要通过函数bsp_spiTransfer实现数据传输(代码里面的查询和中断方式请忽略):

/*
*********************************************************************************************************
*	                             选择DMA,中断或者查询方式
*********************************************************************************************************
*/
#define USE_SPI_DMA    /* DMA方式  */
//#define USE_SPI_INT    /* 中断方式 */
//#define USE_SPI_POLL   /* 查询方式 */

/* 查询模式 */
#if defined (USE_SPI_POLL)

uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];  
uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];

/* 中断模式 */
#elif defined (USE_SPI_INT)

uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];   
uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];

/* DMA模式使用的SRAM4 */
#elif defined (USE_SPI_DMA)
    #if defined ( __CC_ARM )    /* IAR *******/
        __attribute__((section (".RAM_D3"))) uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];   
        __attribute__((section (".RAM_D3"))) uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];
    #elif defined (__ICCARM__)   /* MDK ********/
        #pragma location = ".RAM_D3"
        uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];   
        #pragma location = ".RAM_D3"
        uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];
    #endif
#endif

/*
*********************************************************************************************************
*	函 数 名: bsp_spiTransfer
*	功能说明: 启动数据传输
*	形    参: 无
*	返 回 值: 无
*********************************************************************************************************
*/
void bsp_spiTransfer(void)

	if (g_spiLen > SPI_BUFFER_SIZE)
	
		return;
	
	
	/* DMA方式传输 */
#ifdef USE_SPI_DMA
	wTransferState = TRANSFER_WAIT;
	
	if(HAL_SPI_TransmitReceive_DMA(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen) != HAL_OK)	
	
		Error_Handler(__FILE__, __LINE__);
	
	
	while (wTransferState == TRANSFER_WAIT)
	
		;
	
#endif

	/* 中断方式传输 */	
#ifdef USE_SPI_INT
	wTransferState = TRANSFER_WAIT;

	if(HAL_SPI_TransmitReceive_IT(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen) != HAL_OK)	
	
		Error_Handler(__FILE__, __LINE__);
	
	
	while (wTransferState == TRANSFER_WAIT)
	
		;
	
#endif

	/* 查询方式传输 */	
#ifdef USE_SPI_POLL
	if(HAL_SPI_TransmitReceive(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen, 1000000) != HAL_OK)	
	
		Error_Handler(__FILE__, __LINE__);
		
#endif

DMA方式要特别注意几点:

  •   由于程序里面开启了数据Cache,会造成DMA和CPU访问SRAM4数据不一致的问题,特此将SRAM4空间关闭Cache。
	/* 配置SRAM4的MPU属性为Non-cacheable */
	MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
	MPU_InitStruct.BaseAddress      = 0x38000000;
	MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;
	MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
	MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
	MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
	MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
	MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
	MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
	MPU_InitStruct.SubRegionDisable = 0x00;
	MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

	HAL_MPU_ConfigRegion(&MPU_InitStruct);

分散加载设置:

 

94.3.4 第4步:应用代码设计

应用部分的代码设计如下:

/*
*********************************************************************************************************
*	函 数 名: DemoSpiMaster
*	功能说明: SPI 主机通信
*	形    参:无
*	返 回 值: 无
*********************************************************************************************************
*/
void DemoSpiMaster(void)

	uint8_t count = 0;
    uint8_t ucKeyCode;		/* 按键代码 */
    
    /***************设置SPI Flash片选上拉,防止影响 ***************/
    
        GPIO_InitTypeDef gpio_init;

        /* 打开GPIO时钟 */
        __HAL_RCC_GPIOD_CLK_ENABLE();
        
        gpio_init.Mode = GPIO_MODE_OUTPUT_PP;	
        gpio_init.Pull = GPIO_NOPULL;		
        gpio_init.Speed = GPIO_SPEED_HIGH;  	
        gpio_init.Pin = GPIO_PIN_13;	
        HAL_GPIO_Init(GPIOD, &gpio_init);

        GPIOD->BSRR = GPIO_PIN_13;
    
    
	sfDispMenu();		/* 打印命令提示 */
	
	bsp_StartAutoTimer(0, 100);	/* 启动1个100ms的自动重装的定时器 */
	
	while(1)
	
		bsp_Idle();		/* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */
		
		/* 判断定时器超时时间 */
		if (bsp_CheckTimer(0))	
		
			/* 每隔100ms 进来一次 */  
			bsp_LedToggle(2);
		
        
        /* 按键滤波和检测由后台systick中断服务程序实现,我们只需要调用bsp_GetKey读取键值即可。 */
		ucKeyCode = bsp_GetKey();	/* 读取键值, 无键按下时返回 KEY_NONE = 0 */
		if (ucKeyCode != KEY_NONE)
		
			switch (ucKeyCode)
			
				case KEY_DOWN_K1:			/* K1键按下,发送数据给从机*/
                    g_spiTxBuf[0] = count++;
                    g_spiTxBuf[1] = count++;
                    g_spiTxBuf[2] = count++;
                    g_spiTxBuf[3] = count++;
                    g_spiLen = 4;
                    printf("SPI主机发送数据:%d,%d,%d,%d\\r\\n",
 g_spiTxBuf[0],g_spiTxBuf[1],g_spiTxBuf[2],g_spiTxBuf[3]);

                    bsp_spiTransfer();
                    printf("SPI主机接收数据:%d,%d,%d,%d\\r\\n", 
g_spiRxBuf[0],g_spiRxBuf[1],g_spiRxBuf[2],g_spiRxBuf[3]);
					break;
				default:
					/* 其它的键值不处理 */
					break;
			
		
		
	

这部分代码比较好理解,大家按下K1按键后,会打印发送的数据并打印SPI从机设备返回的数据。

 

94.4 SPI DMA从机程序设计

SPI DMA从机设计程序如下,与主机不同的是部分配置选项要设置为从机方式。

94.4.1 第1步:SPI总线配置

SPI总线配置通过如下两个函数实现:

/*
*********************************************************************************************************
*	函 数 名: bsp_InitSPIBus
*	功能说明: 配置SPI总线。
*	形    参: 无
*	返 回 值: 无
*********************************************************************************************************
*/
void bsp_InitSPIBus(void)
	
	g_spi_busy = 0;
	
	bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_16, SPI_PHASE_1EDGE, SPI_POLARITY_LOW);


/*
*********************************************************************************************************
*	函 数 名: bsp_InitSPIParam
*	功能说明: 配置SPI总线参数,时钟分频,时钟相位和时钟极性。
*	形    参: _BaudRatePrescaler  SPI总线时钟分频设置,支持的参数如下:
*                                 SPI_BAUDRATEPRESCALER_2    2分频
*                                 SPI_BAUDRATEPRESCALER_4    4分频
*                                 SPI_BAUDRATEPRESCALER_8    8分频
*                                 SPI_BAUDRATEPRESCALER_16   16分频
*                                 SPI_BAUDRATEPRESCALER_32   32分频
*                                 SPI_BAUDRATEPRESCALER_64   64分频
*                                 SPI_BAUDRATEPRESCALER_128  128分频
*                                 SPI_BAUDRATEPRESCALER_256  256分频
*                                                        
*             _CLKPhase           时钟相位,支持的参数如下:
*                                 SPI_PHASE_1EDGE     SCK引脚的第1个边沿捕获传输的第1个数据
*                                 SPI_PHASE_2EDGE     SCK引脚的第2个边沿捕获传输的第1个数据
*                                 
*             _CLKPolarity        时钟极性,支持的参数如下:
*                                 SPI_POLARITY_LOW    SCK引脚在空闲状态处于低电平
*                                 SPI_POLARITY_HIGH   SCK引脚在空闲状态处于高电平
*
*	返 回 值: 无
*********************************************************************************************************
*/
void bsp_InitSPIParam(uint32_t _BaudRatePrescaler, uint32_t _CLKPhase, uint32_t _CLKPolarity)

	/* 提高执行效率,只有在SPI硬件参数发生变化时,才执行HAL_Init */
	if (s_BaudRatePrescaler == _BaudRatePrescaler && s_CLKPhase == _CLKPhase && s_CLKPolarity == _CLKPolarity)
			
		return;
	

	s_BaudRatePrescaler = _BaudRatePrescaler;	
	s_CLKPhase = _CLKPhase;
	s_CLKPolarity = _CLKPolarity;
	
	
	/* 设置SPI参数 */
	hspi.Instance               = SPIx;                   /* 例化SPI */
	hspi.Init.BaudRatePrescaler = _BaudRatePrescaler;     /* 设置波特率 */
	hspi.Init.Direction         = SPI_DIRECTION_2LINES;   /* 全双工 */
	hspi.Init.CLKPhase          = _CLKPhase;              /* 配置时钟相位 */
	hspi.Init.CLKPolarity       = _CLKPolarity;           /* 配置时钟极性 */
	hspi.Init.DataSize          = SPI_DATASIZE_8BIT;      /* 设置数据宽度 */
	hspi.Init.FirstBit          = SPI_FIRSTBIT_MSB;       /* 数据传输先传高位 */
	hspi.Init.TIMode            = SPI_TIMODE_DISABLE;     /* 禁止TI模式  */
	hspi.Init.CRCCalculation    = SPI_CRCCALCULATION_DISABLE; /* 禁止CRC */
	hspi.Init.CRCPolynomial     = 7;                       /* 禁止CRC后,此位无效 */
	hspi.Init.CRCLength         = SPI_CRC_LENGTH_8BIT;     /* 禁止CRC后,此位无效 */
	hspi.Init.NSS               = SPI_NSS_SOFT;               /* 使用软件方式管理片选引脚 */
	hspi.Init.FifoThreshold     = SPI_FIFO_THRESHOLD_01DATA;  /* 设置FIFO大小是一个数据项 */
	hspi.Init.NSSPMode          = SPI_NSS_PULSE_DISABLE;      /* 禁止脉冲输出 */
	hspi.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_ENABLE; /* 禁止SPI后,SPI相关引脚保持当前状态 */  
	hspi.Init.Mode 		    = SPI_MODE_MASTER;            /* SPI工作在主控模式 */

	/* 设置SPI参数 */
	hspi.Instance               = SPIx;                   		/* 例化SPI */
	hspi.Init.BaudRatePrescaler = _BaudRatePrescaler;     		/* 设置波特率 */
	hspi.Init.Direction         = SPI_DIRECTION_2LINES;         /* 全双工 */
	hspi.Init.CLKPhase          = _CLKPhase;             		/* 配置时钟相位 */
	hspi.Init.CLKPolarity       = _CLKPolarity;           		/* 配置时钟极性 */
	hspi.Init.DataSize          = SPI_DATASIZE_8BIT;      	 	/* 设置数据宽度 */
	hspi.Init.FirstBit          = SPI_FIRSTBIT_MSB;         	/* 数据传输先传高位 */
	hspi.Init.TIMode            = SPI_TIMODE_DISABLE;     		/* 禁止TI模式  */
	hspi.Init.CRCCalculation    = SPI_CRCCALCULATION_DISABLE; 	/* 禁止CRC */
	hspi.Init.CRCPolynomial     = 7;                       		/* 禁止CRC后,此位无效 */
	hspi.Init.CRCLength         = SPI_CRC_LENGTH_8BIT;     		/* 禁止CRC后,此位无效 */
	hspi.Init.FifoThreshold     = SPI_FIFO_THRESHOLD_16DATA;	/* 设置FIFO大小是一个数据项 */
	
	hspi.Init.NSS         = SPI_NSS_HARD_INPUT;         		/* 片选引脚 */
	hspi.Init.NSSPMode    = SPI_NSS_PULSE_DISABLE;    			/* 禁止脉冲输出 */
	hspi.Init.NSSPolarity = SPI_NSS_POLARITY_LOW;               /* 低电平有效 */
	hspi.Init.MasterSSIdleness        = SPI_MASTER_SS_IDLENESS_00CYCLE; /* MSS, 插入到NSS有效边沿和第一个
数据开始之间的额外延迟,单位SPI时钟周期个数 */

	hspi.Init.MasterInterDataIdleness = SPI_MASTER_INTERDATA_IDLENESS_10CYCLE; /* MIDI, 两个连续数据帧之间
插入的最小时间延迟,单位SPI时钟周期个数 */
	
	hspi.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_ENABLE; /* 禁止SPI后,SPI相关引脚保持当前状态 */  
	hspi.Init.Mode 			 	= SPI_MODE_SLAVE;          	  

	/* 复位配置 */
	if (HAL_SPI_DeInit(&hspi) != HAL_OK)
	
		Error_Handler(__FILE__, __LINE__);
		

	/* 初始化配置 */
	if (HAL_SPI_Init(&hspi) != HAL_OK)
	
		Error_Handler(__FILE__, __LINE__);
		

关于这两个函数有以下三点要做个说明:

  •   函数bsp_InitSPIBus里面的配置是个初始设置。实际驱动芯片时,会通过函数bsp_InitSPIParam做再配置。
  •   函数bsp_InitSPIParam提供了时钟分频,时钟相位和时钟极性配置。一般主机和从机此处设置为一样即可。
  •   SPI硬件片选NSS设置为SPI_NSS_HARD_INPUT。
  •   这里特别注意主机是hspi.Init.Mode = SPI_MODE_SLAVE。

94.4.2 第2步:SPI DMA配置

配置代码实现如下,注释比较详细:

/*
*********************************************************************************************************
*	函 数 名: bsp_InitSPIParam
*	功能说明: 配置SPI总线时钟,GPIO,中断,DMA等
*	形    参: SPI_HandleTypeDef 类型指针变量
*	返 回 值: 无
*********************************************************************************************************
*/
void HAL_SPI_MspInit(SPI_HandleTypeDef *_hspi)

	/* 配置 SPI总线GPIO : SCK MOSI MISO */
	
		GPIO_InitTypeDef  GPIO_InitStruct;
			
		/* SPI和GPIP时钟 */
		SPIx_SCK_CLK_ENABLE();
		SPIx_MISO_CLK_ENABLE();
		SPIx_MOSI_CLK_ENABLE();
		SPIx_CLK_ENABLE();

		/* SPI SCK */
		GPIO_InitStruct.Pin       = SPIx_SCK_PIN;
		GPIO_InitStruct.Mode      = GPIO_MODE_AF_PP;
		GPIO_InitStruct.Pull      = GPIO_PULLUP;
		GPIO_InitStruct.Speed     = GPIO_SPEED_FREQ_HIGH;
		GPIO_InitStruct.Alternate = SPIx_SCK_AF;
		HAL_GPIO_Init(SPIx_SCK_GPIO, &GPIO_InitStruct);

		/* SPI MISO */
		GPIO_InitStruct.Pin = SPIx_MISO_PIN;
		GPIO_InitStruct.Alternate = SPIx_MISO_AF;
		HAL_GPIO_Init(SPIx_MISO_GPIO, &GPIO_InitStruct);

		/* SPI MOSI */
		GPIO_InitStruct.Pin = SPIx_MOSI_PIN;
		GPIO_InitStruct.Alternate = SPIx_MOSI_AF;
		HAL_GPIO_Init(SPIx_MOSI_GPIO, &GPIO_InitStruct);
        
        /* SPI NSS */
        GPIO_InitStruct.Pin = SPIx_NSS_PIN;
        GPIO_InitStruct.Alternate = SPIx_NSS_AF;
        HAL_GPIO_Init(SPIx_NSS_GPIO, &GPIO_InitStruct);
	

	/* 配置DMA和NVIC */
	#ifdef USE_SPI_DMA
	
		/* 使能DMA时钟 */
		DMAx_CLK_ENABLE();      

		/* SPI DMA发送配置 */		
		hdma_tx.Instance                = SPIx_TX_DMA_STREAM;    /* 例化使用的DMA数据流 */
		hdma_tx.Init.FIFOMode           = DMA_FIFOMODE_ENABLE;   /* FIFO*/
		hdma_tx.Init.FIFOThreshold      = DMA_FIFO_THRESHOLD_FULL;/* 禁止FIFO此位不起作用,用于设置阀值 */
		hdma_tx.Init.MemBurst            = DMA_MBURST_SINGLE;	/* 禁止FIFO此位不起作用,用于存储器突发 */
		hdma_tx.Init.PeriphBurst         = DMA_PBURST_SINGLE;	 /* 禁止FIFO此位不起作用,用于外设突发 */
		hdma_tx.Init.Request             = SPIx_TX_DMA_REQUEST;     /* 请求类型 */  
		hdma_tx.Init.Direction           = DMA_MEMORY_TO_PERIPH;    /* 传输方向是从存储器到外设 */  
		hdma_tx.Init.PeriphInc           = DMA_PINC_DISABLE;        /* 外设地址自增禁止 */ 
		hdma_tx.Init.MemInc              = DMA_MINC_ENABLE;         /* 存储器地址自增使能 */  
		hdma_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;     /* 外设数据传输位宽选择字节,即8bit */ 
		hdma_tx.Init.MemDataAlignment    = DMA_MDATAALIGN_BYTE;     /* 存储器数据传输位宽选择字节,即8bit */    
		hdma_tx.Init.Mode                = DMA_NORMAL;              /* 正常模式 */
		hdma_tx.Init.Priority            = DMA_PRIORITY_LOW;        /* 优先级低 */

		 /* 复位DMA */
		if(HAL_DMA_DeInit(&hdma_tx) != HAL_OK)
		
			Error_Handler(__FILE__, __LINE__);     
		
		
		 /* 初始化DMA */
		if(HAL_DMA_Init(&hdma_tx) != HAL_OK)
		
			Error_Handler(__FILE__, __LINE__);     
		

		/* 关联DMA句柄到SPI */
		__HAL_LINKDMA(_hspi, hdmatx, hdma_tx);	

		/* SPI DMA接收配置 */	
		hdma_rx.Instance                = SPIx_RX_DMA_STREAM;     /* 例化使用的DMA数据流 */
		hdma_rx.Init.FIFOMode           = DMA_FIFOMODE_ENABLE;    /* FIFO*/
		hdma_rx.Init.FIFOThreshold      = DMA_FIFO_THRESHOLD_FULL;/* 禁止FIFO此位不起作用,用于设置阀值 */
		hdma_rx.Init.MemBurst            = DMA_MBURST_SINGLE;	/* 禁止FIFO此位不起作用,用于存储器突发 */
		hdma_rx.Init.PeriphBurst         = DMA_PBURST_SINGLE;	/* 禁止FIFO此位不起作用,用于外设突发 */
		hdma_rx.Init.Request             = SPIx_RX_DMA_REQUEST;    /* 请求类型 */  
		hdma_rx.Init.Direction           = DMA_PERIPH_TO_MEMORY;   /* 传输方向从外设到存储器 */  
		hdma_rx.Init.PeriphInc           = DMA_PINC_DISABLE;       /* 外设地址自增禁止 */   
		hdma_rx.Init.MemInc              = DMA_MINC_ENABLE;        /* 存储器地址自增使能 */   
		hdma_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;    /* 外设数据传输位宽选择字节,即8bit */ 
		hdma_rx.Init.MemDataAlignment    = DMA_MDATAALIGN_BYTE;    /* 存储器数据传输位宽选择字节,即8bit */   
		hdma_rx.Init.Mode                = DMA_NORMAL;             /* 正常模式 */
		hdma_rx.Init.Priority            = DMA_PRIORITY_HIGH;      /* 优先级高 */

		 /* 复位DMA */
		if(HAL_DMA_DeInit(&hdma_rx) != HAL_OK)
		
			Error_Handler(__FILE__, __LINE__);     
		
		
		 /* 初始化DMA */
		if(HAL_DMA_Init(&hdma_rx) != HAL_OK)
		
			Error_Handler(__FILE__, __LINE__);     
		

		/* 关联DMA句柄到SPI */
	   __HAL_LINKDMA(_hspi, hdmarx, hdma_rx);	

		/* 配置DMA发送中断 */
		HAL_NVIC_SetPriority(SPIx_DMA_TX_IRQn, 1, 0);
		HAL_NVIC_EnableIRQ(SPIx_DMA_TX_IRQn);
		
		/* 配置DMA接收中断 */
		HAL_NVIC_SetPriority(SPIx_DMA_RX_IRQn, 1, 0);
		HAL_NVIC_EnableIRQ(SPIx_DMA_RX_IRQn);
		
		/* 配置SPI中断 */
		HAL_NVIC_SetPriority(SPIx_IRQn, 1, 0);
		HAL_NVIC_EnableIRQ(SPIx_IRQn);
	
	#endif
	
	#ifdef USE_SPI_INT
		/* 配置SPI中断优先级并使能中断 */
		HAL_NVIC_SetPriority(SPIx_IRQn, 1, 0);
		HAL_NVIC_EnableIRQ(SPIx_IRQn);
	#endif

94.4.3 第3步:SPI DMA传输设置和MPU配置

SPI DMA方式主要通过函数bsp_spiTransfer实现数据传输(代码里面的查询和中断方式请忽略):

/*
*********************************************************************************************************
*	                             选择DMA,中断或者查询方式
*********************************************************************************************************
*/
#define USE_SPI_DMA    /* DMA方式  */
//#define USE_SPI_INT    /* 中断方式 */
//#define USE_SPI_POLL   /* 查询方式 */

/* 查询模式 */
#if defined (USE_SPI_POLL)

uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];  
uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];

/* 中断模式 */
#elif defined (USE_SPI_INT)

uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];   
uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];

/* DMA模式使用的SRAM4 */
#elif defined (USE_SPI_DMA)
    #if defined ( __CC_ARM )    /* IAR *******/
        __attribute__((section (".RAM_D3"))) uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];   
        __attribute__((section (".RAM_D3"))) uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];
    #elif defined (__ICCARM__)   /* MDK ********/
        #pragma location = ".RAM_D3"
        uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];   
        #pragma location = ".RAM_D3"
        uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];
    #endif
#endif

/*
*********************************************************************************************************
*	函 数 名: bsp_spiTransfer
*	功能说明: 启动数据传输
*	形    参: 无
*	返 回 值: 无
*********************************************************************************************************
*/
void bsp_spiTransfer(void)

	if (g_spiLen > SPI_BUFFER_SIZE)
	
		return;
	
	
	/* DMA方式传输 */
#ifdef USE_SPI_DMA
	wTransferState = TRANSFER_WAIT;
	
	if(HAL_SPI_TransmitReceive_DMA(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen) != HAL_OK)	
	
		Error_Handler(__FILE__, __LINE__);
	
	
	//while (wTransferState == TRANSFER_WAIT)
	//
		;
	//
#endif

	/* 中断方式传输 */	
#ifdef USE_SPI_INT
	wTransferState = TRANSFER_WAIT;

	if(HAL_SPI_TransmitReceive_IT(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen) != HAL_OK)	
	
		Error_Handler(__FILE__, __LINE__);
	
	
	//while (wTransferState == TRANSFER_WAIT)
	//
		;
	//
#endif

	/* 查询方式传输 */	
#ifdef USE_SPI_POLL
	if(HAL_SPI_TransmitReceive(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen, 1000000) != HAL_OK)	
	
		Error_Handler(__FILE__, __LINE__);
		
#endif

DMA方式要特别注意两点:

  • 通过本手册第26章的内存块超方便使用方式,将DMA缓冲定义到SRAM4上。因为本工程是用的DTCM做的主RAM空间,这个空间无法使用通用DMA1和DMA2。
  • 由于程序里面开启了数据Cache,会造成DMA和CPU访问SRAM4数据不一致的问题,特此将SRAM4空间关闭Cache。
	/* 配置SRAM4的MPU属性为Non-cacheable */
	MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
	MPU_InitStruct.BaseAddress      = 0x38000000;
	MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;
	MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
	MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
	MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
	MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
	MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
	MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
	MPU_InitStruct.SubRegionDisable = 0x00;
	MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

	HAL_MPU_ConfigRegion(&MPU_InitStruct);

分散加载设置:

 

94.4.4 第4步:应用代码设计

应用部分的代码设计如下:

/*
*********************************************************************************************************
*	函 数 名: DemoSpiSlave
*	功能说明: SPI 从机通信
*	形    参:无
*	返 回 值: 无
*********************************************************************************************************
*/
void DemoSpiSlave(void)

    uint8_t count = 0;
    
    /***************设置SPI Flash片选上拉,防止影响 ***************/
    
        GPIO_InitTypeDef gpio_init;

        /* 打开GPIO时钟 */
        __HAL_RCC_GPIOD_CLK_ENABLE();
        
        gpio_init.Mode = GPIO_MODE_OUTPUT_PP;	/* 设置推挽输出 */
        gpio_init.Pull = GPIO_NOPULL;			/* 上下拉电阻不使能 */
        gpio_init.Speed = GPIO_SPEED_HIGH;  	/* GPIO速度等级 */	
        gpio_init.Pin = GPIO_PIN_13;	
        HAL_GPIO_Init(GPIOD, &gpio_init);

        GPIOD->BSRR = GPIO_PIN_13;
    
    
	sfDispMenu();		/* 打印命令提示 */
	
	bsp_StartAutoTimer(0, 100);	/* 启动1个100ms的自动重装的定时器 */
    
    /* 上电后,准备接收主机命令 */
    g_spiTxBuf[0] = count++;
    g_spiTxBuf[1] = count++;
    g_spiTxBuf[2] = count++;
    g_spiTxBuf[3] = count++;
    g_spiLen = 4;
    bsp_spiTransfer();
    
	while(1)
	
		bsp_Idle();		/* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */
		
		/* 判断定时器超时时间 */
		if (bsp_CheckTimer(0))	
		
			/* 每隔100ms 进来一次 */  
			bsp_LedToggle(2);
		

        if (wTransferState != TRANSFER_WAIT)
        
            printf("SPI从机发送数据 = %d,%d,%d,%d\\r\\n", g_spiTxBuf[0], g_spiTxBuf[1], g_spiTxBuf[2],
 g_spiTxBuf[3]);
            printf("SPI从机接收数据 = %d,%d,%d,%d\\r\\n", g_spiRxBuf[0], g_spiRxBuf[1], g_spiRxBuf[2], 
g_spiRxBuf[3]);
            g_spiTxBuf[0] = count++;
            g_spiTxBuf[1] = count++;
            g_spiTxBuf[2] = count++;
            g_spiTxBuf[3] = count++;
            g_spiLen = 4;
            bsp_spiTransfer();
        
	

从机设计这里主要注意两点:

  • 上电后优先准备一次从机数据接收。
  • 从机接收到主机发送的数据后,将接收到的数据打印出来并打印发送的数据。

 

94.5 SPI DMA主从机使用注意事项

大家根据自己接线的稳定性,可以适当调节SPI主机和从机的时钟速度,其中从机的时钟速度是可以高于主机速度的,这样通信的容错性更好些。

94.6 SPI DMA主从机驱动移植和使用

移植步骤如下:

  • 第1步:复制bsp_spi_bus.c,bsp_spi_bus.h到自己的工程目录,并添加到工程里面。
  • 第2步:根据使用的第几个SPI,SPI时钟,SPI引脚和DMA通道等,修改bsp_spi_bus.c文件开头的宏定义
/*
*********************************************************************************************************
*	                            时钟,引脚,DMA,中断等宏定义
*********************************************************************************************************
*/
#define SPIx							SPI1
#define SPIx_CLK_ENABLE()				__HAL_RCC_SPI1_CLK_ENABLE()
#define DMAx_CLK_ENABLE()				__HAL_RCC_DMA2_CLK_ENABLE()

#define SPIx_FORCE_RESET()				__HAL_RCC_SPI1_FORCE_RESET()
#define SPIx_RELEASE_RESET()			__HAL_RCC_SPI1_RELEASE_RESET()

#define SPIx_SCK_CLK_ENABLE()			__HAL_RCC_GPIOB_CLK_ENABLE()
#define SPIx_SCK_GPIO					GPIOB
#define SPIx_SCK_PIN					GPIO_PIN_3
#define SPIx_SCK_AF						GPIO_AF5_SPI1

#define SPIx_MISO_CLK_ENABLE()			__HAL_RCC_GPIOB_CLK_ENABLE()
#define SPIx_MISO_GPIO					GPIOB
#define SPIx_MISO_PIN 					GPIO_PIN_4
#define SPIx_MISO_AF					GPIO_AF5_SPI1

#define SPIx_MOSI_CLK_ENABLE()			__HAL_RCC_GPIOB_CLK_ENABLE()
#define SPIx_MOSI_GPIO					GPIOB
#define SPIx_MOSI_PIN 					GPIO_PIN_5
#define SPIx_MOSI_AF					GPIO_AF5_SPI1

#define SPIx_TX_DMA_STREAM               DMA2_Stream3
#define SPIx_RX_DMA_STREAM               DMA2_Stream2

#define SPIx_TX_DMA_REQUEST              DMA_REQUEST_SPI1_TX
#define SPIx_RX_DMA_REQUEST              DMA_REQUEST_SPI1_RX

#define SPIx_DMA_TX_IRQn                 DMA2_Stream3_IRQn
#define SPIx_DMA_RX_IRQn                 DMA2_Stream2_IRQn

#define SPIx_DMA_TX_IRQHandler           DMA2_Stream3_IRQHandler
#define SPIx_DMA_RX_IRQHandler           DMA2_Stream2_IRQHandler

#define SPIx_IRQn                        SPI1_IRQn
#define SPIx_IRQHandler                  SPI1_IRQHandler
  • 第3步:如果使用DMA方式的话,请不要使用TCM RAM,因为通用DMA1和DMA2不支持。并为了防止DMA和CPU同时访问DMA缓冲造成的数据一致性问题,将这块空间关闭Cache处理,比如使用的SRAM4:
/* 配置SRAM4的MPU属性为Non-cacheable */
MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress      = 0x38000000;
MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

HAL_MPU_ConfigRegion(&MPU_InitStruct);
  • 第4步:初始化SPI。
/* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */
bsp_InitSPIBus();	/* 配置SPI总线 */	
  • 第5步:SPI Flash驱动主要用到HAL库的SPI驱动文件,简单省事些可以添加所有HAL库C源文件进来。
  • 第6步:应用方法看本章节配套例子即可。特别注意分散加载设置:

 

94.7 实验例程设计框架

通过程序设计框架,让大家先对配套例程有一个全面的认识,然后再理解细节,本次实验例程的设计框架如下:

 

第1阶段,上电启动阶段:

  • 这部分在第14章进行了详细说明。

第2阶段,进入main函数:

  • 第1部分,硬件初始化,主要是MPU,Cache,HAL库,系统时钟,滴答定时器和LED。
  • 第2部分,应用程序设计部分,实现SPI双击通信。

94.8 实验例程说明(MDK)

配套例子:

V7-070_SPI DMA双机通信(主机)

V7-071_SPI DMA双机通信(从机)

实验目的:

  1. 学习SPI Flash主从机通信实现。

实验操作:

  1. K1按键按下,主机打印。
  2. SPI从机等待主机消息。

上电后串口打印的信息:

波特率 115200,数据位 8,奇偶校验位无,停止位 1。

主机:

 从机:

 程序设计:

系统栈大小分配:

 RAM空间用的DTCM:

 硬件外设初始化

硬件外设的初始化是在 bsp.c 文件实现:

/*
*********************************************************************************************************
*	函 数 名: bsp_Init
*	功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
*	形    参:无
*	返 回 值: 无
*********************************************************************************************************
*/
void bsp_Init(void)

    /* 配置MPU */
	MPU_Config();
	
	/* 使能L1 Cache */
	CPU_CACHE_Enable();

	/* 
       STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
	   - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
	   - 设置NVIV优先级分组为4。
	 */
	HAL_Init();

	/* 
       配置系统时钟到400MHz
       - 切换使用HSE。
       - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
    */
	SystemClock_Config();

	/* 
	   Event Recorder:
	   - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
	   - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章
	*/	
#if Enable_EventRecorder == 1  
	/* 初始化EventRecorder并开启 */
	EventRecorderInitialize(EventRecordAll, 1U);
	EventRecorderStart();
#endif
	
bsp_InitDWT();      /* 初始化DWT时钟周期计数器 */       
	bsp_InitKey();    	 /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
	bsp_InitTimer();  	 /* 初始化滴答定时器 */
	bsp_InitLPUart();	 /* 初始化串口 */
	bsp_InitExtIO();	 /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */	
	bsp_InitLed();    	 /* 初始化LED */	
bsp_InitExtSDRAM(); /* 初始化SDRAM */

	/* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */
	bsp_InitSPIBus();	/* 配置SPI总线 */		


MPU配置和Cache配置:

数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区以及SRAM4

/*
*********************************************************************************************************
*	函 数 名: MPU_Config
*	功能说明: 配置MPU
*	形    参: 无
*	返 回 值: 无
*********************************************************************************************************
*/
static void MPU_Config( void )

	MPU_Region_InitTypeDef MPU_InitStruct;

	/* 禁止 MPU */
	HAL_MPU_Disable();

	/* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */
	MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
	MPU_InitStruct.BaseAddress      = 0x24000000;
	MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
	MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
	MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
	MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
	MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
	MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
	MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
	MPU_InitStruct.SubRegionDisable = 0x00;
	MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

	HAL_MPU_ConfigRegion(&MPU_InitStruct);
	
	
	/* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */
	MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
	MPU_InitStruct.BaseAddress      = 0x60000000;
	MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;	
	MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
	MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
	MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;	
	MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
	MPU_InitStruct.Number           = MPU_REGION_NUMBER1;
	MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
	MPU_InitStruct.SubRegionDisable = 0x00;
	MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
	
	HAL_MPU_ConfigRegion(&MPU_InitStruct);

	/* 配置SRAM4的MPU属性为Non-cacheable */
	MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
	MPU_InitStruct.BaseAddress      = 0x38000000;
	MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;
	MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
	MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
	MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
	MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
	MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
	MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
	MPU_InitStruct.SubRegionDisable = 0x00;
	MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

	HAL_MPU_ConfigRegion(&MPU_InitStruct);

	/*使能 MPU */
	HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);


/*
*********************************************************************************************************
*	函 数 名: CPU_CACHE_Enable
*	功能说明: 使能L1 Cache
*	形    参: 无
*	返 回 值: 无
*********************************************************************************************************
*/
static void CPU_CACHE_Enable(void)

	/* 使能 I-Cache */
	SCB_EnableICache();

	/* 使能 D-Cache */
	SCB_EnableDCache();

每10ms调用一次按键处理:

按键处理是在滴答定时器中断里面实现,每10ms执行一次检测。

/*
*********************************************************************************************************
*	函 数 名: bsp_RunPer10ms
*	功能说明: 该函数每隔10ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些处理时间要求
*              不严格的任务可以放在此函数。比如:按键扫描、蜂鸣器鸣叫控制等。
*	形    参: 无
*	返 回 值: 无
*********************************************************************************************************
*/
void bsp_RunPer10ms(void)

	bsp_KeyScan10ms();

主功能:

主机程序实现如下操作:

/*
*********************************************************************************************************
*	函 数 名: DemoSpiMaster
*	功能说明: SPI 主机通信
*	形    参:无
*	返 回 值: 无
*********************************************************************************************************
*/
void DemoSpiMaster(void)

	uint8_t count = 0;
    uint8_t ucKeyCode;		/* 按键代码 */
    
    /***************设置SPI Flash片选上拉,防止影响 ***************/
    
        GPIO_InitTypeDef gpio_init;

        /* 打开GPIO时钟 */
        __HAL_RCC_GPIOD_CLK_ENABLE();
        
        gpio_init.Mode = GPIO_MODE_OUTPUT_PP;	
        gpio_init.Pull = GPIO_NOPULL;		
        gpio_init.Speed = GPIO_SPEED_HIGH;  	
        gpio_init.Pin = GPIO_PIN_13;	
        HAL_GPIO_Init(GPIOD, &gpio_init);

        GPIOD->BSRR = GPIO_PIN_13;
    
    
	sfDispMenu();		/* 打印命令提示 */
	
	bsp_StartAutoTimer(0, 100);	/* 启动1个100ms的自动重装的定时器 */
	
	while(1)
	
		bsp_Idle();		/* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */
		
		/* 判断定时器超时时间 */
		if (bsp_CheckTimer(0))	
		
			/* 每隔100ms 进来一次 */  
			bsp_LedToggle(2);
		
        
        /* 按键滤波和检测由后台systick中断服务程序实现,我们只需要调用bsp_GetKey读取键值即可。 */
		ucKeyCode = bsp_GetKey();	/* 读取键值, 无键按下时返回 KEY_NONE = 0 */
		if (ucKeyCode != KEY_NONE)
		
			switch (ucKeyCode)
			
				case KEY_DOWN_K1:			/* K1键按下,发送数据给从机*/
                    g_spiTxBuf[0] = count++;
                    g_spiTxBuf[1] = count++;
                    g_spiTxBuf[2] = count++;
                    g_spiTxBuf[3] = count++;
                    g_spiLen = 4;
                    printf("SPI主机发送数据:%d,%d,%d,%d\\r\\n",
 g_spiTxBuf[0],g_spiTxBuf[1],g_spiTxBuf[2],g_spiTxBuf[3]);

                    bsp_spiTransfer();
                    printf("SPI主机接收数据:%d,%d,%d,%d\\r\\n", 
g_spiRxBuf[0],g_spiRxBuf[1],g_spiRxBuf[2],g_spiRxBuf[3]);
					break;
				default:
					/* 其它的键值不处理 */
					break;
			
		
		
	

从机实现程序如下:

/*
*********************************************************************************************************
*	函 数 名: DemoSpiSlave
*	功能说明: SPI 从机通信
*	形    参:无
*	返 回 值: 无
*********************************************************************************************************
*/
void DemoSpiSlave(void)

    uint8_t count = 0;
    
    /***************设置SPI Flash片选上拉,防止影响 ***************/
    
        GPIO_InitTypeDef gpio_init;

        /* 打开GPIO时钟 */
        __HAL_RCC_GPIOD_CLK_ENABLE();
        
        gpio_init.Mode = GPIO_MODE_OUTPUT_PP;	/* 设置推挽输出 */
        gpio_init.Pull = GPIO_NOPULL;			/* 上下拉电阻不使能 */
        gpio_init.Speed = GPIO_SPEED_HIGH;  	/* GPIO速度等级 */	
        gpio_init.Pin = GPIO_PIN_13;	
        HAL_GPIO_Init(GPIOD, &gpio_init);

        GPIOD->BSRR = GPIO_PIN_13;
    
    
	sfDispMenu();		/* 打印命令提示 */
	
	bsp_StartAutoTimer(0, 100);	/* 启动1个100

以上是关于STM32H7的SPI总线应用之双机通信(DMA方式)的主要内容,如果未能解决你的问题,请参考以下文章

STM32H7教程第75章 STM32H7的SPI总线应用之驱动DAC8501(双路输出,16bit分辨率,0-5V)

STM32H7教程第93章 STM32H7的SPI总线应用之驱动ADS1256(8通道24bit ADC, 增益可编程)

STM32H7教程第93章 STM32H7的SPI总线应用之驱动ADS1256(8通道24bit ADC, 增益可编程)

STM32H7教程第46章 STM32H7的ADC应用之DMA方式多通道采样

STM32H7教程第45章 STM32H7的ADC应用之定时器触发配合DMA双缓冲

STM32H7教程第60章 STM32H7的DAC应用之定时器触发实现DMA方式双通道波形