完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980
第94章 STM32H7的SPI总线应用之双机通信(DMA方式)本章节为大家讲解SPI DMA方式双机通信。
94.1 初学者重要提示
94.2 SPI DMA主从机硬件接线
94.3 SPI DMA主机程序设计
94.4 SPI DMA从机程序设计
94.5 SPI DMA主从机使用注意事项
94.6 SPI DMA主从机驱动移植和使用
94.7 实验例程设计框架
94.8 实验例程说明(MDK)
94.9 实验例程说明(IAR)
94.10 总结
94.1 初学者重要提示
- 学习本章节前,务必优先学习第72章SPI基础和第73章SPI Flash的DMA玩法方式。本章实现的SPI DMA通信方式的主机和从机,跟SPI DMA方式驱动SPI Flash是类似的。
- 本章是采用的SPI DMA全双工通信方式。
- 大家根据自己接线的稳定性,可以适当调节SPI主机和从机的时钟速度,其中从机的时钟速度是可以高于主机速度的,这样通信的容错性更好些。
接线方式如下,使用的两块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。
配置代码实现如下,注释比较详细:
/* ********************************************************************************************************* * 函 数 名: 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数据流同时刷是否处理过来https://www.armbbs.cn/forum.php?mod=viewthread&tid=109765 。
- 由于程序里面开启了数据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。
配置代码实现如下,注释比较详细:
/* ********************************************************************************************************* * 函 数 名: 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双击通信。
配套例子:
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个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.9 实验例程说明(IAR)
配套例子:
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个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.10 总结
本章节就为大家讲解这么多,推荐大家做实际应用测试,实际项目中需要双机通信,可以考虑这种方式,比较实用。
微信公众号:armfly_com 安富莱论坛:www.armbbs.cn 安富莱淘宝:https://armfly.taobao.com 【文章转自:防御服务器 http://www.558idc.com/aqt.html提供,感恩】