使用硬件SPI1,开启DMA,软件NSS。
1.使用硬件spi后,spi.c文件里会自动生成SPI_HandleTypeDef hspi1句柄,并且在main.c中自动使用MX_SPI1_Init()函数对hsp1句柄赋值和SPI初始化
cs
void MX_SPI1_Init(void)
{
hspi1.Instance = SPI1;//spi1
hspi1.Init.Mode = SPI_MODE_MASTER;//主机模式
hspi1.Init.Direction = SPI_DIRECTION_2LINES;//双线双向
hspi1.Init.DataSize = SPI_DATASIZE_8BIT;//8位数据传输
hspi1.Init.CLKPolarity = SPI_POLARITY_HIGH;//时钟极性
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;//始终相位
hspi1.Init.NSS = SPI_NSS_HARD_OUTPUT;//硬件NSS(输出)
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8;//prescaler
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;//MSB
hspi1.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库函数:
cs
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发送接收
如果要用中断式发送/接收需要配套加上中断接收回调
cs
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi) {
if (hspi == &hspi1) {
// 处理接收到的数据
}
}
阻塞式中断式和DMA任选一种使用即可,因为开了DMA所以用DMA 的接收和发送^^
先看下hal库里提供的发送函数:
cs
/**
* @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 */
//如果启用CRC
if (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和channel
if (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;
}
用这个的话需要补上:
cs
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi) {
if (hspi == &hspi1) {
// 发送完成处理
}
}
接收差不多同理。
虽然有把接收和发送塞在一起的函数但还是建议发送和接收拆开,一个是方便看懂现在在干嘛,一个是有些复杂外设需要更灵活的配置。
但是既然只是个样例所以就把用接收发送一起的即可:
(这个函数要自己写)
cs
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: 启动全双工 DMA
HAL_SPI_TransmitReceive_DMA(&hspi1, txBuf, rxBuf, len);
}
4.进行BSY==0的判断 通过(即确认总线空闲)之后才能升高NSS(其实这一步一般来说没什么必要 可加可不加的)
5.在(自己写的)DMA传输完成回调中恢复NSS和SSI:
cs
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