1、配置I2S
我们的有效数据是32位的,使用飞利浦格式。
2、配置DMA
**这里需要注意:**i2s的DR寄存器是16位的,如果需要发送32位的数据,是需要写两次DR寄存器的,所以DMA的外设数据宽度设置16位,而不是32位。
3、完善I2S文件
i2s.c和i2s.h文件都是MX自动生成的,并且生成MX_I2S3_Init 函数进行了初始化,MX_I2S3_Init 函数里面其实依次调用了HAL_I2S_Init库函数(和MCU不相关的初始化)和HAL_I2S_MspInit库函数(是个weak函数,和MCU相关的初始化)。所以,我们自己要写的代码也加到这个文件中。
c
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file i2s.c
* @brief This file provides code for the configuration
* of the I2S instances.
******************************************************************************
* @attention
*
* Copyright (c) 2024 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "i2s.h"
/* USER CODE BEGIN 0 */
#include "stdio.h"
#define TX_DATA_16 (0x1234)
#define TX_DATA_32 (0x12345678)
#define HALF_TX_BUFFER_SIZE (256)
#define TX_BUFFER_SIZE (HALF_TX_BUFFER_SIZE*2)
uint32_t tx_buffer[TX_BUFFER_SIZE];
/* USER CODE END 0 */
I2S_HandleTypeDef hi2s3;
DMA_HandleTypeDef hdma_spi3_tx;
/* I2S3 init function */
void MX_I2S3_Init(void)
{
/* USER CODE BEGIN I2S3_Init 0 */
//这里有一点需要注�???,i2s的DR�???16位的,�???以如果想发�??32位的数据,得写两次,
//如果想发�???0x12345678,就得先发�???0x1234,再发�???0x5678(标准飞利浦格式是高位在前)
//但是32位数组是小端�???,�???以就�???要重组一�???
for(int i=0;i<TX_BUFFER_SIZE;i++)
{
*(tx_buffer+i)= (TX_DATA_32<<16)|(TX_DATA_32>>16);
}
/* USER CODE END I2S3_Init 0 */
/* USER CODE BEGIN I2S3_Init 1 */
/* USER CODE END I2S3_Init 1 */
hi2s3.Instance = SPI3;
hi2s3.Init.Mode = I2S_MODE_MASTER_TX;
hi2s3.Init.Standard = I2S_STANDARD_PHILIPS;
hi2s3.Init.DataFormat = I2S_DATAFORMAT_32B;
hi2s3.Init.MCLKOutput = I2S_MCLKOUTPUT_ENABLE;
hi2s3.Init.AudioFreq = I2S_AUDIOFREQ_48K;
hi2s3.Init.CPOL = I2S_CPOL_HIGH;
hi2s3.Init.ClockSource = I2S_CLOCK_PLL;
hi2s3.Init.FullDuplexMode = I2S_FULLDUPLEXMODE_DISABLE;
if (HAL_I2S_Init(&hi2s3) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN I2S3_Init 2 */
/* USER CODE END I2S3_Init 2 */
}
void HAL_I2S_MspInit(I2S_HandleTypeDef* i2sHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};
if(i2sHandle->Instance==SPI3)
{
/* USER CODE BEGIN SPI3_MspInit 0 */
/* USER CODE END SPI3_MspInit 0 */
/** Initializes the peripherals clock
*/
PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_I2S;
PeriphClkInitStruct.PLLI2S.PLLI2SN = 192;
PeriphClkInitStruct.PLLI2S.PLLI2SR = 2;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
{
Error_Handler();
}
/* I2S3 clock enable */
__HAL_RCC_SPI3_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
/**I2S3 GPIO Configuration
PA4 ------> I2S3_WS
PC7 ------> I2S3_MCK
PC10 ------> I2S3_CK
PC12 ------> I2S3_SD
*/
GPIO_InitStruct.Pin = WCK_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = GPIO_AF6_SPI3;
HAL_GPIO_Init(WCK_GPIO_Port, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_7|BCK_Pin|DI_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = GPIO_AF6_SPI3;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
/* I2S3 DMA Init */
/* SPI3_TX Init */
hdma_spi3_tx.Instance = DMA1_Stream5;
hdma_spi3_tx.Init.Channel = DMA_CHANNEL_0;
hdma_spi3_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
hdma_spi3_tx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_spi3_tx.Init.MemInc = DMA_MINC_ENABLE;
hdma_spi3_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
hdma_spi3_tx.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
hdma_spi3_tx.Init.Mode = DMA_CIRCULAR;
hdma_spi3_tx.Init.Priority = DMA_PRIORITY_LOW;
hdma_spi3_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
if (HAL_DMA_Init(&hdma_spi3_tx) != HAL_OK)
{
Error_Handler();
}
__HAL_LINKDMA(i2sHandle,hdmatx,hdma_spi3_tx);
/* USER CODE BEGIN SPI3_MspInit 1 */
/* USER CODE END SPI3_MspInit 1 */
}
}
void HAL_I2S_MspDeInit(I2S_HandleTypeDef* i2sHandle)
{
if(i2sHandle->Instance==SPI3)
{
/* USER CODE BEGIN SPI3_MspDeInit 0 */
/* USER CODE END SPI3_MspDeInit 0 */
/* Peripheral clock disable */
__HAL_RCC_SPI3_CLK_DISABLE();
/**I2S3 GPIO Configuration
PA4 ------> I2S3_WS
PC7 ------> I2S3_MCK
PC10 ------> I2S3_CK
PC12 ------> I2S3_SD
*/
HAL_GPIO_DeInit(WCK_GPIO_Port, WCK_Pin);
HAL_GPIO_DeInit(GPIOC, GPIO_PIN_7|BCK_Pin|DI_Pin);
/* I2S3 DMA DeInit */
HAL_DMA_DeInit(i2sHandle->hdmatx);
/* USER CODE BEGIN SPI3_MspDeInit 1 */
/* USER CODE END SPI3_MspDeInit 1 */
}
}
/* USER CODE BEGIN 1 */
int I2S_DMA_Start_Transmit()
{
return HAL_I2S_Transmit_DMA(&hi2s3, (uint16_t *)tx_buffer, TX_BUFFER_SIZE);
}
int I2S_DMA_Stop()
{
return HAL_I2S_DMAStop(&hi2s3);
}
/**
* @brief Tx Transfer Half completed callbacks
* @param hi2s pointer to a I2S_HandleTypeDef structure that contains
* the configuration information for I2S module
* @retval None
*/
void HAL_I2S_TxHalfCpltCallback(I2S_HandleTypeDef *hi2s)
{
printf("%s\r\n",__func__);
/* Prevent unused argument(s) compilation warning */
UNUSED(hi2s);
for(int i=0;i<HALF_TX_BUFFER_SIZE;i++)
{
*(tx_buffer+i)+=1;
}
/* NOTE : This function Should not be modified, when the callback is needed,
the HAL_I2S_TxHalfCpltCallback could be implemented in the user file
*/
}
/**
* @brief Tx Transfer completed callbacks
* @param hi2s pointer to a I2S_HandleTypeDef structure that contains
* the configuration information for I2S module
* @retval None
*/
void HAL_I2S_TxCpltCallback(I2S_HandleTypeDef *hi2s)
{
printf("%s\r\n",__func__);
/* Prevent unused argument(s) compilation warning */
UNUSED(hi2s);
for(int i=0;i<HALF_TX_BUFFER_SIZE;i++)
{
*(tx_buffer+HALF_TX_BUFFER_SIZE+i)-=1;
}
/* NOTE : This function Should not be modified, when the callback is needed,
the HAL_I2S_TxCpltCallback could be implemented in the user file
*/
}
/**
* @brief I2S error callbacks
* @param hi2s pointer to a I2S_HandleTypeDef structure that contains
* the configuration information for I2S module
* @retval None
*/
void HAL_I2S_ErrorCallback(I2S_HandleTypeDef *hi2s)
{
/* Prevent unused argument(s) compilation warning */
printf("HAL_I2S_Error\r\n");
/* NOTE : This function Should not be modified, when the callback is needed,
the HAL_I2S_ErrorCallback could be implemented in the user file
*/
}
/* USER CODE END 1 */
- 其实这里使用了DMA双buffer的思路,但是我没有使用双buffer,而是一个buffer的前后部分。当TxHalfCplt 的时候,我们去更新buffer前半部分数据,当TxCplt的时候,我们去更新buffer的后半部分数据。HAL库没有很好封装DMA双buffer的配置函数。
- 关于DMA的buffer填充问题,I2S的DR寄存器是15位的,所以配置DMA的数据宽度也是16位的,如果I2S是32位的数据格式,那么需要写两次DR寄存器才能组一帧I2S数据,例如I2S想发送0x12345678,那么就得先发送0x1234,再发送0x5678(I2S飞利浦格式就是这样,高位在前),所以填充buffer的时候,也得按该顺序填充。
- 关于全双工DMA的封装,HAL好像也没有很好的支持,等下次再介绍。。。
4、I2S实现DMA双buffer发送
我们如果看过HAL库接口的话,就应该知道。在dma_ex文件中封装了DMA双buffer的接口,但是在i2s文件或者i2s_ex中没有封装双buffer的接口。所以,我们打算仿照HAL_I2S_Transmit_DMA库函数实现一个函数。
c
/* USER CODE BEGIN 1 */
static void I2S_DMAM0TxHalfCplt(DMA_HandleTypeDef *hdma)
{
I2S_HandleTypeDef *hi2s = (I2S_HandleTypeDef *)((DMA_HandleTypeDef *)hdma)->Parent; /* Derogation MISRAC2012-Rule-11.5 */
/* Call user Tx half complete callback */
printf("%s\r\n",__func__);
}
static void I2S_DMAM0TxCplt(DMA_HandleTypeDef *hdma)
{
I2S_HandleTypeDef *hi2s = (I2S_HandleTypeDef *)((DMA_HandleTypeDef *)hdma)->Parent; /* Derogation MISRAC2012-Rule-11.5 */
/* Call user Tx complete callback */
printf("%s\r\n",__func__);
}
static void I2S_DMAM1TxHalfCplt(DMA_HandleTypeDef *hdma)
{
I2S_HandleTypeDef *hi2s = (I2S_HandleTypeDef *)((DMA_HandleTypeDef *)hdma)->Parent; /* Derogation MISRAC2012-Rule-11.5 */
/* Call user Tx half complete callback */
printf("%s\r\n",__func__);
}
static void I2S_DMAM1TxCplt(DMA_HandleTypeDef *hdma)
{
I2S_HandleTypeDef *hi2s = (I2S_HandleTypeDef *)((DMA_HandleTypeDef *)hdma)->Parent; /* Derogation MISRAC2012-Rule-11.5 */
/* Call user Tx complete callback */
printf("%s\r\n",__func__);
}
static void I2S_DMA_Error(DMA_HandleTypeDef *hdma)
{
I2S_HandleTypeDef *hi2s = (I2S_HandleTypeDef *)((DMA_HandleTypeDef *)hdma)->Parent; /* Derogation MISRAC2012-Rule-11.5 */
printf("%s\r\n",__func__);
}
static HAL_StatusTypeDef HAL_I2S_Transmit_DMA_DBuffer(I2S_HandleTypeDef *hi2s, uint16_t *pData0,uint16_t *pData1, uint16_t Size)
{
uint32_t tmpreg_cfgr;
if ((pData0 == NULL) || (Size == 0U))
{
return HAL_ERROR;
}
/* Process Locked */
__HAL_LOCK(hi2s);
if (hi2s->State != HAL_I2S_STATE_READY)
{
__HAL_UNLOCK(hi2s);
return HAL_BUSY;
}
/* Set state and reset error code */
hi2s->State = HAL_I2S_STATE_BUSY_TX;
hi2s->ErrorCode = HAL_I2S_ERROR_NONE;
hi2s->pTxBuffPtr = pData0;
tmpreg_cfgr = hi2s->Instance->I2SCFGR & (SPI_I2SCFGR_DATLEN | SPI_I2SCFGR_CHLEN);
if ((tmpreg_cfgr == I2S_DATAFORMAT_24B) || (tmpreg_cfgr == I2S_DATAFORMAT_32B))
{
hi2s->TxXferSize = (Size << 1U);
hi2s->TxXferCount = (Size << 1U);
}
else
{
hi2s->TxXferSize = Size;
hi2s->TxXferCount = Size;
}
/* Set the I2S Tx DMA Half transfer complete callback */
hi2s->hdmatx->XferHalfCpltCallback = I2S_DMAM0TxHalfCplt;
/* Set the I2S Tx DMA transfer complete callback */
hi2s->hdmatx->XferCpltCallback = I2S_DMAM0TxCplt;
hi2s->hdmatx->XferM1HalfCpltCallback=I2S_DMAM1TxHalfCplt;//callback
hi2s->hdmatx->XferM1CpltCallback=I2S_DMAM1TxCplt;//callback
/* Set the DMA error callback */
hi2s->hdmatx->XferErrorCallback = I2S_DMA_Error;
/* Enable the Tx DMA Stream/Channel */
if (HAL_OK != HAL_DMAEx_MultiBufferStart_IT(hi2s->hdmatx,
(uint32_t)hi2s->pTxBuffPtr,
(uint32_t)&hi2s->Instance->DR,
(uint32_t)pData1,
hi2s->TxXferSize))
{
/* Update SPI error code */
SET_BIT(hi2s->ErrorCode, HAL_I2S_ERROR_DMA);
hi2s->State = HAL_I2S_STATE_READY;
__HAL_UNLOCK(hi2s);
return HAL_ERROR;
}
/* Check if the I2S is already enabled */
if (HAL_IS_BIT_CLR(hi2s->Instance->I2SCFGR, SPI_I2SCFGR_I2SE))
{
/* Enable I2S peripheral */
__HAL_I2S_ENABLE(hi2s);
}
/* Check if the I2S Tx request is already enabled */
if (HAL_IS_BIT_CLR(hi2s->Instance->CR2, SPI_CR2_TXDMAEN))
{
/* Enable Tx DMA Request */
SET_BIT(hi2s->Instance->CR2, SPI_CR2_TXDMAEN);
}
__HAL_UNLOCK(hi2s);
return HAL_OK;
}
注意几点:
- 这些函数还是写在i2s.c文件中。
- 相比原函数,在函数参数上多了一个buffer地址。
- 原函数中的回调函数都是本地函数,不供其他文件调用,所以全部重新定义在我们的本文件中。并且多了m1 buffer的回调函数。
- DMA_Start函数更换为双buffer函数接口。其他的地方都没有改变。
5、全双工I2S实现
配置全双工Master模式,其实看源码就知道,当配置全双工的时候,用到了两个i2s外设。
如果我们配置i2s为Master_Tx,那么i2s_ex就会自动被配置为Slave_Rx。当然,这些在HAL库中都已经封装好了,我们使用起来还是不麻烦的。
但是,HAL库封装的函数很奇怪,所以我们仿照原库函数自己修改了一下。
c
static void I2S_DMARxHalfCplt(DMA_HandleTypeDef *hdma)
{
printf("%s\r\n",__func__);
}
static void I2S_DMARxCplt(DMA_HandleTypeDef *hdma)
{
printf("%s\r\n",__func__);
}
static void I2S_DMATxHalfCplt(DMA_HandleTypeDef *hdma)
{
printf("%s\r\n",__func__);
}
static void I2S_DMATxCplt(DMA_HandleTypeDef *hdma)
{
printf("%s\r\n",__func__);
}
static HAL_StatusTypeDef HAL_I2SEx_TransmitReceive_DMA_Modify(I2S_HandleTypeDef *hi2s,
uint16_t *pTxData,
uint16_t *pRxData,
uint16_t Size)
{
uint32_t *tmp = NULL;
uint32_t tmp1 = 0U;
HAL_StatusTypeDef errorcode = HAL_OK;
if (hi2s->State != HAL_I2S_STATE_READY)
{
errorcode = HAL_BUSY;
goto error;
}
if ((pTxData == NULL) || (pRxData == NULL) || (Size == 0U))
{
return HAL_ERROR;
}
/* Process Locked */
__HAL_LOCK(hi2s);
hi2s->pTxBuffPtr = pTxData;
hi2s->pRxBuffPtr = pRxData;
tmp1 = hi2s->Instance->I2SCFGR & (SPI_I2SCFGR_DATLEN | SPI_I2SCFGR_CHLEN);
/* Check the Data format: When a 16-bit data frame or a 16-bit data frame extended
is selected during the I2S configuration phase, the Size parameter means the number
of 16-bit data length in the transaction and when a 24-bit data frame or a 32-bit data
frame is selected the Size parameter means the number of 16-bit data length. */
if ((tmp1 == I2S_DATAFORMAT_24B) || (tmp1 == I2S_DATAFORMAT_32B))
{
hi2s->TxXferSize = (Size << 1U);
hi2s->TxXferCount = (Size << 1U);
hi2s->RxXferSize = (Size << 1U);
hi2s->RxXferCount = (Size << 1U);
}
else
{
hi2s->TxXferSize = Size;
hi2s->TxXferCount = Size;
hi2s->RxXferSize = Size;
hi2s->RxXferCount = Size;
}
hi2s->ErrorCode = HAL_I2S_ERROR_NONE;
hi2s->State = HAL_I2S_STATE_BUSY_TX_RX;
/* Set the I2S Rx DMA Half transfer complete callback */
hi2s->hdmarx->XferHalfCpltCallback = I2S_DMARxHalfCplt;
/* Set the I2S Rx DMA transfer complete callback */
hi2s->hdmarx->XferCpltCallback = I2S_DMARxCplt;
/* Set the I2S Rx DMA error callback */
hi2s->hdmarx->XferErrorCallback = NULL;
/* Set the I2S Tx DMA Half transfer complete callback as NULL */
hi2s->hdmatx->XferHalfCpltCallback = I2S_DMATxHalfCplt;
/* Set the I2S Tx DMA transfer complete callback as NULL */
hi2s->hdmatx->XferCpltCallback = I2S_DMATxCplt;
/* Set the I2S Tx DMA error callback */
hi2s->hdmatx->XferErrorCallback = NULL;
tmp1 = hi2s->Instance->I2SCFGR & SPI_I2SCFGR_I2SCFG;
/* Check if the I2S_MODE_MASTER_TX or I2S_MODE_SLAVE_TX Mode is selected */
if ((tmp1 == I2S_MODE_MASTER_TX) || (tmp1 == I2S_MODE_SLAVE_TX))
{
/* Enable the Rx DMA Stream */
tmp = (uint32_t *)&pRxData;
HAL_DMA_Start_IT(hi2s->hdmarx, (uint32_t)&I2SxEXT(hi2s->Instance)->DR, *(uint32_t *)tmp, hi2s->RxXferSize);
/* Enable Rx DMA Request */
SET_BIT(I2SxEXT(hi2s->Instance)->CR2, SPI_CR2_RXDMAEN);
/* Enable the Tx DMA Stream */
tmp = (uint32_t *)&pTxData;
HAL_DMA_Start_IT(hi2s->hdmatx, *(uint32_t *)tmp, (uint32_t)&hi2s->Instance->DR, hi2s->TxXferSize);
/* Enable Tx DMA Request */
SET_BIT(hi2s->Instance->CR2, SPI_CR2_TXDMAEN);
/* Check if the I2S is already enabled */
if ((hi2s->Instance->I2SCFGR & SPI_I2SCFGR_I2SE) != SPI_I2SCFGR_I2SE)
{
/* Enable I2Sext(receiver) before enabling I2Sx peripheral */
__HAL_I2SEXT_ENABLE(hi2s);
/* Enable I2S peripheral after the I2Sext */
__HAL_I2S_ENABLE(hi2s);
}
}
else
{
/* Check if Master Receiver mode is selected */
if ((hi2s->Instance->I2SCFGR & SPI_I2SCFGR_I2SCFG) == I2S_MODE_MASTER_RX)
{
/* Clear the Overrun Flag by a read operation on the SPI_DR register followed by a read
access to the SPI_SR register. */
__HAL_I2S_CLEAR_OVRFLAG(hi2s);
}
/* Enable the Tx DMA Stream */
tmp = (uint32_t *)&pTxData;
HAL_DMA_Start_IT(hi2s->hdmatx, *(uint32_t *)tmp, (uint32_t)&I2SxEXT(hi2s->Instance)->DR, hi2s->TxXferSize);
/* Enable Tx DMA Request */
SET_BIT(I2SxEXT(hi2s->Instance)->CR2, SPI_CR2_TXDMAEN);
/* Enable the Rx DMA Stream */
tmp = (uint32_t *)&pRxData;
HAL_DMA_Start_IT(hi2s->hdmarx, (uint32_t)&hi2s->Instance->DR, *(uint32_t *)tmp, hi2s->RxXferSize);
/* Enable Rx DMA Request */
SET_BIT(hi2s->Instance->CR2, SPI_CR2_RXDMAEN);
/* Check if the I2S is already enabled */
if ((hi2s->Instance->I2SCFGR & SPI_I2SCFGR_I2SE) != SPI_I2SCFGR_I2SE)
{
/* Enable I2Sext(transmitter) before enabling I2Sx peripheral */
__HAL_I2SEXT_ENABLE(hi2s);
/* Enable I2S peripheral before the I2Sext */
__HAL_I2S_ENABLE(hi2s);
}
}
error :
__HAL_UNLOCK(hi2s);
return errorcode;
}
相比原函数,我们就修改了一个地方,那就是把TX的回调函数也赋值了,其实这里不理解的地方有两个:
- 为什么不给TX的回调函数赋值
- 为什么RX的回调函数命名TxRx的回调函数
我们在实验中直接将DI接在DO上了,最后看看调试结果
最后,如果想在全双工中使用DMA双buffer,可以仿照上文中函数的修改即可,这里就不做示范了。