STM32H7的SPI总线应用之双机通信(DMA方式)
Posted Simon223
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STM32H7的SPI总线应用之双机通信(DMA方式)相关的知识,希望对你有一定的参考价值。
完整教程下载地址:链接
第94章 STM32H7的SPI总线应用之双机通信(DMA方式)
本章节为大家讲解SPI DMA方式双机通信。
目录
94.1 初学者重要提示
- 学习本章节前,务必优先学习第72章SPI基础和第73章SPI Flash的DMA玩法方式。本章实现的SPI DMA通信方式的主机和从机,跟SPI DMA方式驱动SPI Flash是类似的。
- 本章是采用的SPI DMA全双工通信方式。
- 大家根据自己接线的稳定性,可以适当调节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方式要特别注意几点:
- 通过本手册第26章的内存块超方便使用方式,将DMA缓冲定义到SRAM4上。因为本工程是用的DTCM做的主RAM空间,这个空间无法使用通用DMA1和DMA2。
- 程序这里SPI DMA方式主控用的是等待传输完成,大家根据自己实际应用可以做修改,详情大家可以看此贴作为拓展:【深入探讨】DMA到底能不能起到加速程序执行的作用,DMA死等操作是否合理,多个DMA数据流同时刷是否处理过来【深入探讨】DMA到底能不能起到加速程序执行的作用,DMA死等操作是否合理,多个DMA数据流同时刷是否处理过来 - STM32H7 - 硬汉嵌入式论坛 - Powered by Discuz! 。
- 由于程序里面开启了数据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双机通信(从机)
实验目的:
- 学习SPI Flash主从机通信实现。
实验操作:
- K1按键按下,主机打印。
- 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方式多通道采样