当前位置: 首页 > news >正文

stm32 hal库 SPI使用(二)硬件SPI的HAL库函数调用

使用硬件SPI1,开启DMA,软件NSS。

1.使用硬件spi后,spi.c文件里会自动生成SPI_HandleTypeDef hspi1句柄,并且在main.c中自动使用MX_SPI1_Init()函数对hsp1句柄赋值和SPI初始化

void MX_SPI1_Init(void)
{hspi1.Instance = SPI1;//spi1hspi1.Init.Mode = SPI_MODE_MASTER;//主机模式hspi1.Init.Direction = SPI_DIRECTION_2LINES;//双线双向hspi1.Init.DataSize = SPI_DATASIZE_8BIT;//8位数据传输hspi1.Init.CLKPolarity = SPI_POLARITY_HIGH;//时钟极性hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;//始终相位hspi1.Init.NSS = SPI_NSS_HARD_OUTPUT;//硬件NSS(输出)hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8;//prescalerhspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;//MSBhspi1.Init.TIMode = SPI_TIMODE_DISABLE;//使用摩托罗拉帧格式hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_ENABLE;//开启CRC校验hspi1.Init.CRCPolynomial = 10;//CRC多项式if (HAL_SPI_Init(&hspi1) != HAL_OK)//调用HAL_StatusTypeDef HAL_SPI_Init(SPI_HandleTypeDef \*hspi)函数进行SPI初始化,在这个函数里主要调用了HAL_SPI_MspInit这个虚函数(虽然是虚函数但是内容已经在spi.c里写好了^^){Error_Handler();}
}

Info

补充解释一下 hspi1.Init.Direction = SPI_DIRECTION_2LINES;//双线双向 的意思。
双线指的是MOSI和MISO两条线 一般默认是双线双向传输
也可以通过软件改成双线单向或者单线。
双线单向就是同一时间两条线都向同一个设备发数据 所以传输速率能加快一倍^^
单线就是,单线(禁用一条线),虽然速率慢但是硬件上能省下一个管脚。
这部分在上一篇原理部分寄存器位那里具体解释过如何设置(通过控制寄存器标志位)。

2.在正式发送和接受之前先要进行NSS的接收和配置。

3.先介绍一下主要用到的hal库函数:

HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, const uint8_t *pData, uint16_t Size, uint32_t Timeout);//阻塞式发送
HAL_StatusTypeDef HAL_SPI_Receive(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);//阻塞式接收
HAL_StatusTypeDef HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi, const uint8_t *pTxData, uint8_t *pRxData, uint16_t Size, uint32_t Timeout);//阻塞式发送+接收(因为spi的发送和接收是同时进行的^^)HAL_StatusTypeDef HAL_SPI_Transmit_IT(SPI_HandleTypeDef *hspi, const uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_SPI_Receive_IT(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_SPI_TransmitReceive_IT(SPI_HandleTypeDef *hspi, const uint8_t *pTxData, uint8_t *pRxData,uint16_t Size);//中断式发送和接收HAL_StatusTypeDef HAL_SPI_Transmit_DMA(SPI_HandleTypeDef *hspi, const uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_SPI_Receive_DMA(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_SPI_TransmitReceive_DMA(SPI_HandleTypeDef *hspi, const uint8_t *pTxData, uint8_t *pRxData,uint16_t Size);//SPI的dma发送接收

如果要用中断式发送/接收需要配套加上中断接收回调

void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi) {if (hspi == &hspi1) {// 处理接收到的数据}
}

阻塞式中断式和DMA任选一种使用即可,因为开了DMA所以用DMA 的接收和发送^^
先看下hal库里提供的发送函数:

/*** @brief  Transmit an amount of data in non-blocking mode with DMA.* @param  hspi pointer to a SPI_HandleTypeDef structure that contains*               the configuration information for SPI module.* @param  pData pointer to data buffer* @param  Size amount of data to be sent* @retval HAL status*/
HAL_StatusTypeDef HAL_SPI_Transmit_DMA(SPI_HandleTypeDef *hspi, const uint8_t *pData, uint16_t Size)
{/* Check tx dma handle */assert_param(IS_SPI_DMA_HANDLE(hspi->hdmatx));//dma发送句柄验证/* Check Direction parameter */assert_param(IS_SPI_DIRECTION_2LINES_OR_1LINE(hspi->Init.Direction));//spi方向验证——根据设置这里是双线双向if (hspi->State != HAL_SPI_STATE_READY){return HAL_BUSY;}if ((pData == NULL) || (Size == 0U)){return HAL_ERROR;}/* Process Locked */__HAL_LOCK(hspi);//加锁  防止多线程或中断环境下的资源竞争/* Set the transaction information */hspi->State       = HAL_SPI_STATE_BUSY_TX;//更新spi状态并且存数据hspi->ErrorCode   = HAL_SPI_ERROR_NONE;hspi->pTxBuffPtr  = (const uint8_t *)pData;hspi->TxXferSize  = Size;hspi->TxXferCount = Size;/* Init field not used in handle to zero *///接收相关字段(pRxBuffPtr, RxXferSize)置零,因本函数仅处理发送。hspi->pRxBuffPtr  = (uint8_t *)NULL;hspi->TxISR       = NULL;hspi->RxISR       = NULL;hspi->RxXferSize  = 0U;hspi->RxXferCount = 0U;/* Configure communication direction : 1Line *///如果是单线if (hspi->Init.Direction == SPI_DIRECTION_1LINE){/* Disable SPI Peripheral before set 1Line direction (BIDIOE bit) */__HAL_SPI_DISABLE(hspi);SPI_1LINE_TX(hspi);}#if (USE_SPI_CRC != 0U)/* Reset CRC Calculation *///如果启用CRCif (hspi->Init.CRCCalculation == SPI_CRCCALCULATION_ENABLE){SPI_RESET_CRC(hspi);//复位CRC单元}
#endif /* USE_SPI_CRC *//* Set the SPI TxDMA Half transfer complete callback *///半传输完成回调hspi->hdmatx->XferHalfCpltCallback = SPI_DMAHalfTransmitCplt;/* Set the SPI TxDMA transfer complete callback *///传输完成回调hspi->hdmatx->XferCpltCallback = SPI_DMATransmitCplt;/* Set the DMA error callback *///错误回调hspi->hdmatx->XferErrorCallback = SPI_DMAError;/* Set the DMA AbortCpltCallback */hspi->hdmatx->XferAbortCallback = NULL;/* Enable the Tx DMA Stream/Channel *///使能dma的发送stream和channelif (HAL_OK != HAL_DMA_Start_IT(hspi->hdmatx, (uint32_t)hspi->pTxBuffPtr, (uint32_t)&hspi->Instance->DR,hspi->TxXferCount)){/* Update SPI error code *///若DMA启动失败,设置错误码HAL_SPI_ERROR_DMA,解锁资源并返回 HAL_ERROR。SET_BIT(hspi->ErrorCode, HAL_SPI_ERROR_DMA);/* Process Unlocked */__HAL_UNLOCK(hspi);return HAL_ERROR;}/* Check if the SPI is already enabled *///若SPI未启用(SPI_CR1_SPE`位未设置),调用 __HAL_SPI_ENABLE 启动SPI外设。if ((hspi->Instance->CR1 & SPI_CR1_SPE) != SPI_CR1_SPE){/* Enable SPI peripheral */__HAL_SPI_ENABLE(hspi);}/* Process Unlocked */__HAL_UNLOCK(hspi);/* Enable the SPI Error Interrupt Bit */__HAL_SPI_ENABLE_IT(hspi, (SPI_IT_ERR));/* Enable Tx DMA Request */SET_BIT(hspi->Instance->CR2, SPI_CR2_TXDMAEN);return HAL_OK;
}

用这个的话需要补上:

void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi) {if (hspi == &hspi1) {// 发送完成处理}
}

接收差不多同理。
虽然有把接收和发送塞在一起的函数但还是建议发送和接收拆开,一个是方便看懂现在在干嘛,一个是有些复杂外设需要更灵活的配置。
但是既然只是个样例所以就把用接收发送一起的即可:
(这个函数要自己写)

void SPI1_TransmitReceive_DMA_With_CS(uint8_t *txBuf, uint8_t *rxBuf, uint16_t len)
{// Step 1: NSS 拉低HAL_GPIO_WritePin(OLED_CS_GPIO_Port, OLED_CS_Pin, GPIO_PIN_RESET);// Step 2: SSI = 0 表示选中从机hspi1.Instance->CR1 &= ~SPI_CR1_SSI;// Step 3: 启动全双工 DMAHAL_SPI_TransmitReceive_DMA(&hspi1, txBuf, rxBuf, len);
}

4.进行BSY==0的判断 通过(即确认总线空闲)之后才能升高NSS(其实这一步一般来说没什么必要 可加可不加的)

5.在(自己写的)DMA传输完成回调中恢复NSS和SSI:

void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)//这个函数是DMA传输完成 只能保证DMA数据在数据寄存器和内存中搬运完毕  不能保证总线上的数据传输完
{if (hspi->Instance == SPI1){// Step 1: 等待 SPI 硬件传输真正完成(BSY 清零)while (__HAL_SPI_GET_FLAG(hspi, SPI_FLAG_BSY)) {}// Step 2: NSS 拉高,表示通信结束HAL_GPIO_WritePin(OLED_CS_GPIO_Port, OLED_CS_Pin, GPIO_PIN_SET);// Step 3: 设置 SSI = 1,表示 SPI 进入空闲状态hspi->Instance->CR1 |= SPI_CR1_SSI;}
}

下一节:具体例程之SPI读取FLASH

相关文章:

  • spring-- 事务失效原因及多线程事务失效解决方案
  • Flutter——数据库Drift开发详细教程(二)
  • Flutter AppBar 详解
  • “会话技术”——Cookie_(2/2)原理与使用细节
  • 【二叉树】java源码实现
  • 中小企业MES系统概要设计
  • 数字智慧方案6213丨智慧园区规划方案(63页PPT)(文末有下载方式)
  • 【学习笔记】第十章:序列建模:递归神经网络(RNN)
  • Python 数据智能实战 (8):基于LLM的个性化营销文案
  • Redis总结及设置营业状态案例
  • 分发饼干之 双数组匹配问题 (双指针 or 二分)
  • 【质量管理】现代TRIZ中问题识别中的功能分析——相互接触分析
  • 【算法题】荷兰国旗问题[力扣75题颜色分类] - JAVA
  • Rust 学习笔记:关于枚举与模式匹配的练习题
  • 从0搭建Transformer
  • 大学之大:瑞典皇家理工学院2025.5.2
  • 纯原生Java实现:获取整个项目中指定接口所有的实现类
  • 柔性超声耦合剂的选择与设计-可穿戴式柔性超声耦合剂面临的难题
  • [面试]SoC验证工程师面试常见问题(三)
  • 冯·诺依曼体系:现代计算机的底层逻辑与百年传承
  • 民族音乐还能这样玩!这场音乐会由AI作曲
  • 马上评|启动最高层级医政调查,维护医学一方净土
  • 购车补贴、“谷子”消费、特色产品,这些活动亮相五五购物节
  • 中国空间站多项太空实验已取得成果,未来将陆续开展千余项研究
  • 摩根大通任命杜峯为亚太区副主席,加码中国市场业务布局
  • 陕西省副省长窦敬丽已任宁夏回族自治区党委常委、统战部部长