使用的是开发板上面的SAI2A,连接的WM8994,使用的DMA双缓冲传输(WM8994的初始化见之前的文章)
/*************************************************************************************************************
* 文件名 : stm32f7_sai.c
* 功能 : STM32F7 SAI接口驱动
* 作者 : [email protected]
* 创建时间 : 2020-02-17
* 最后修改时间 : 2020-02-17
* 详细:
*************************************************************************************************************/
#include "stm32f7_sai.h"
#include "system.h"
#include "dma.h"
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//常用配置列表
//默认配置1:主发送模式,标准I2S协议,立体声模式
const SAI_BLOCK_CONFIG cg_SAI_MTXD_I2S_CONFIG1 =
{
SAI_BLOCK_MODE_MTXD, //音频模块模式-主模式
SAI_PROTOCOL_FREE, //音频协议配置-自由协议
SAI_DATA_SIZE_16BIT, //数据大小
SAI_ASYNC, //同步模式设置
SAI_FIFO_THR_1_4, //FIFO阈值
FALSE, //MSB优先
TRUE, //在 SCK 下降沿更改 SAI 生成的信号
FALSE, //使能立体声
TRUE, //使能立即输出,否则当 SAIXEN 置 1 时驱动音频模块输出-一直输出MCLK时钟
FALSE, //使能DMA-先不开启
64-1, //帧长度(实际长度为FrameLenght+1),左右通道各32位
32-1, //帧同步有效电平长度(实际长度为FrameSyncLenght+1),在I2S模式下=1/2帧长(代表左右声道)
TRUE, //使能 FS 信号为 SOF 信号 + 通道识别信号
FALSE, //FS 为低电平有效(下降沿)
TRUE, //使能 在 Slot 0 第一位的前一位上使能 FS;兼容飞利浦I2S标准
SAI_SLOT_SIZE_DS, //Slot 大小-自动
BIT0|BIT1, //Slot 使能,2个,Slot0与Slot1
0, //此位字段中设置的值定义 Slot 中第一个数据传输位的位置。它表示一个偏移值。在发送模式下,此数据字段以外的位将强制清零。在接收模式下,将丢弃额外接收的位。
2-1, //音频帧中的 Slot 数 (实际数量为:SlotNumber + 1)
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//SAI DMA选择
static const DMAxSx_CH_TYPE scg_SAI_Channel[2][2] = {{DMA2S1_SAI1_A, DMA2S5_SAI1_B}, {DMA2S4_SAI2_A, DMA2S6_SAI2_B}}; //发送通道
//SAI外设结构指针
static const SAI_TypeDef * const SAI_TYPE_BUFF[SAI_CH_COUNT] = {SAI1, SAI2};
static const SAI_Block_TypeDef * const SAIx_Block_TYPE_BUFF[SAI_CH_COUNT*2] = {SAI1_Block_A, SAI1_Block_B, SAI2_Block_A, SAI2_Block_B};
//IIC句柄
typedef struct
{
SAI_CH_TYPE ch; //当前通道
SAI_TypeDef *SAIx; //当前通道外设结构体
SAI_Block_TypeDef *SAI_Block_A; //当前SAI Block A 外设结构体
SAI_Block_TypeDef *SAI_Block_B; //当前SAI Block B 外设结构体
}SAI_HANDLE;
//IIC通道句柄定义
static SAI_HANDLE sg_IIC_Handle[SAI_CH_COUNT];
//通过SAI句柄以及block设置,获取Block操作结构
__inline static SAI_Block_TypeDef *SAI_GetBlock(SAI_HANDLE *pHandle, SAI_BLOCK_TYPE block)
{
if(block == SAIx_BLOCK_A) //A
{
return pHandle->SAI_Block_A;
}
else //B
{
return pHandle->SAI_Block_B;
}
}
//获取SAI句柄
__inline static SAI_HANDLE *SAI_GetHandle(SAI_CH_TYPE ch)
{
if(ch > (SAI_CH_COUNT-1))
{
DEBUG("获取句柄失败,无效的SAI通道%d\r\n", ch);
return NULL;
}
return &sg_IIC_Handle[ch];
}
/*************************************************************************************************************************
* 函数 : bool SAI_Init(SAI_CH_TYPE ch)
* 功能 : SAI初始化(基本时钟使能,外设复位)
* 参数 : ch:SAI选择
* 返回 : TRUE:成功;FALSE:失败;
* 依赖 : 底层宏定义
* 作者 : [email protected]
* 时间 : 2020-02-17
* 最后修改时间 : 2020-02-17
* 说明 :
*************************************************************************************************************************/
bool SAI_Init(SAI_CH_TYPE ch)
{
SYS_DEV_CLOCK DevClock;
SAI_HANDLE *pHandle;
switch(ch)
{
case SAI_CH1: //SAI1(SAIA)
{
DevClock = DEV_SAI1;
}break;
case SAI_CH2: //SAI2(SAIB)
{
DevClock = DEV_SAI2;
}break;
default:
{
DEBUG("初始化SAI失败:无效的SAI通道%d\r\n", ch);
return FALSE;
}
}
pHandle = &sg_IIC_Handle[ch]; //获取相关通道的句柄
if(pHandle == NULL)
{
DEBUG("初始化IIC失败:无效的IIC句柄\r\n");
return FALSE;
}
SYS_DeviceClockEnable(DevClock, TRUE); //使能SAIx时钟
SYS_DeviceReset(DevClock); //复位SAIx
pHandle->ch = ch; //记录通道
pHandle->SAIx = (SAI_TypeDef *)SAI_TYPE_BUFF[ch]; //外设指针
pHandle->SAI_Block_A = (SAI_Block_TypeDef *)SAIx_Block_TYPE_BUFF[ch*2+0]; //当前SAI Block A 外设结构体
pHandle->SAI_Block_B= (SAI_Block_TypeDef *)SAIx_Block_TYPE_BUFF[ch*2+1]; //当前SAI Block B 外设结构体
//可以开始寄存器设置了
pHandle->SAIx->GCR = 0; //无同步
return TRUE;
}
/*************************************************************************************************************************
* 函数 : bool SAI_BlockConfig(SAI_CH_TYPE ch, SAI_BLOCK_TYPE block, const SAI_BLOCK_CONFIG *pConfig)
* 功能 : SAI Block配置(会关闭Block,并且不会使能Block)
* 参数 : ch:SAI选择;block:block选择;pConfig:配置结构,见:SAI_BLOCK_CONFIG
* 返回 : TRUE:成功;FALSE:失败
* 依赖 : 底层宏定义
* 作者 : [email protected]
* 时间 : 2020-02-17
* 最后修改时间 : 2020-02-17
* 说明 : 注意: 会禁用SAI Block,并且配置完成后不会使能Block,会清除主时钟分频器值,当NODIV=0时才会提供MCLK时钟
CR1寄存器-不会使能SAI相应block;
CR2寄存器-不会开启压扩模式,只会设置阈值以及刷新FIFO
*************************************************************************************************************************/
bool SAI_BlockConfig(SAI_CH_TYPE ch, SAI_BLOCK_TYPE block, const SAI_BLOCK_CONFIG *pConfig)
{
u32 temp;
SAI_Block_TypeDef *SAI_Block_X;
SAI_HANDLE *pHandle;
pHandle = SAI_GetHandle(ch); //获取SAI句柄
if(pHandle == NULL || pConfig == NULL)
{
DEBUG("无效的参数\r\n");
return FALSE;
}
//先获取Block
SAI_Block_X = SAI_GetBlock(pHandle, block); //获取Block配置结构
SAI_SetBlockEnable(pHandle->ch, block, FALSE); //关闭Block
//先设置CR1
temp = 0;
temp |= (pConfig->BlockMode & 0x03) << 0; //设置SAIx 音频模块模式
temp |= (pConfig->BlockProtocol & 0x03) << 2; //设置SAIx 音频协议
temp |= (pConfig->DataSize & 0x07) << 5; //设置数据大小
if(pConfig->isLSB) //优先传输数据的 LSB,否则优先传输数据的 MSB
{
temp |= BIT8;
}
if(pConfig->isFalEdgeSCK) //在 SCK 下降沿更改 SAI 生成的信号,否则上升沿
{
temp |= BIT9;
}
temp |= (pConfig->Sync & 0x03) << 10; //同步模式设置
if(pConfig->isMono) //使能单声道,否则为立体声
{
temp |= BIT12;
}
if(pConfig->isStraiOutput) //使能立即输出,否则当 SAIXEN 置 1 时驱动音频模块输出
{
temp |= BIT13;
}
if(pConfig->isEnableDMA) //使能DMA
{
temp |= BIT17;
}
SAI_Block_X->CR1 = temp;
//设置CR2
SAI_Block_X->CR2 &= ~0x07; //清除FIFO阈值
SAI_Block_X->CR2 |= pConfig->FIFO_Thr&0x07; //设置FIFO阈值
SAI_Block_X->CR2|=1<<3; //FIFO刷新
//设置FRCR 帧配置寄存器
temp = 0;
temp |= pConfig->FrameLenght << 0; //帧长度(实际长度为FrameLenght+1)
temp |= (pConfig->FrameSyncLenght & 0x7F) << 8; //帧同步有效电平长度(实际长度为FrameSyncLenght+1)
if(pConfig->isFSisChannelId) //使能 FS 信号为 SOF 信号 + 通道识别信号,否则FS信号为帧起始信号
{
temp |= BIT16; //FS 信号为 SOF 信号 + 通道识别信号
}
if(pConfig->isFS_HighLevel) //使能 FS 为高电平有效(上升沿),否则为FS 为低电平有效(下降沿)
{
temp |= BIT17; //FS 为高电平有效(上升沿)
}
if(pConfig->isFS_Front) //使能 在 Slot 0 第一位的前一位上使能 FS;否则在 Slot 0 的第一位上使能 FS。
{
temp |= BIT18; //在 Slot 0 第一位的前一位上使能 FS。
}
SAI_Block_X->FRCR = temp;
//Slot配置
temp = 0;
temp |= (pConfig->FistBitOffset & 0x1F) << 0; //第一个位偏移
temp |= (pConfig->SlotSize & 0x03) << 6; //Slot 大小
temp |= (pConfig->SlotNumber & 0x0F) << 8; //音频帧中的 Slot 数(实际数量为:SlotNumber + 1)
temp |= (pConfig->SlotEableBit & 0xFFFF) << 16; //Slot 使能,没1bit对应一个Slot,0-15对应1-16 Slot
SAI_Block_X->SLOTR = temp;
return TRUE;
}
/*************************************************************************************************************************
* 函数 : void SAI_BlockDataSize(SAI_CH_TYPE ch, SAI_BLOCK_TYPE block, SAI_DATA_SIZE DataBitSize)
* 功能 : SAI Block配置音频数据大小(采样深度)
* 参数 : ch:SAI选择;block:block选择;DataSize:音频数据大小(采样深度)设置,见SAI_DATA_SIZE
* 返回 : 无
* 依赖 : 底层宏定义
* 作者 : [email protected]
* 时间 : 2020-02-20
* 最后修改时间 : 2020-02-20
* 说明 : 注意:需要关闭block后设置
*************************************************************************************************************************/
void SAI_BlockDataSize(SAI_CH_TYPE ch, SAI_BLOCK_TYPE block, SAI_DATA_SIZE DataBitSize)
{
u32 temp;
SAI_Block_TypeDef *SAI_Block_X;
SAI_HANDLE *pHandle;
pHandle = SAI_GetHandle(ch); //获取SAI句柄
if(pHandle == NULL)
{
DEBUG("无效的参数\r\n");
return;
}
//先获取Block
SAI_Block_X = SAI_GetBlock(pHandle, block); //获取Block配置结构
//设置CR1
SAI_Block_X->CR1 &= ~(0x07 << 5); //清除之前的配置
SAI_Block_X->CR1 |= (DataBitSize&0x07) << 5; //重新配置
}
/*************************************************************************************************************************
* 函数 : bool SAI_SetBlockEnable(SAI_CH_TYPE ch, SAI_BLOCK_TYPE block, bool isEnable)
* 功能 : SAI 开启或者关闭Block
* 参数 : ch:SAI选择;block:block选择;isEnable:TRUE使能block;FALSE:关闭block
* 返回 : TRUE:成功;FALSE:失败
* 依赖 : 底层宏定义
* 作者 : [email protected]
* 时间 : 2020-02-17
* 最后修改时间 : 2020-02-17
* 说明 : 注意: 关闭不会立即生效,会等当前slot传输完成后关闭
*************************************************************************************************************************/
bool SAI_SetBlockEnable(SAI_CH_TYPE ch, SAI_BLOCK_TYPE block, bool isEnable)
{
u16 delay = 1000;
SAI_HANDLE *pHandle;
SAI_Block_TypeDef *SAI_Block_X;
pHandle = SAI_GetHandle(ch); //获取SAI句柄
if(pHandle == NULL)
{
DEBUG("无效的句柄\r\n");
return FALSE;
}
//先获取Block
SAI_Block_X = SAI_GetBlock(pHandle, block); //获取Block配置结构
//使能或者禁用
if(isEnable) //需要使能
{
if(SAI_Block_X->CR1 & BIT16) return TRUE; //开启的,无需重复开启
SAI_Block_X->CR1 |= BIT16; //开启
}
else //需要关闭
{
if((SAI_Block_X->CR1 & BIT16) == 0) return TRUE; //是关闭的,无需重复关闭
SAI_Block_X->CR1 &= ~BIT16; //关闭
while(SAI_Block_X->CR1 & BIT16) //等待关闭完成
{
delay --;
Delay_US(1);
if(delay == 0)
{
DEBUG("关闭BLOCK超时\r\n");
return FALSE;
}
}
}
return TRUE;
}
/*************************************************************************************************************************
* 函数 : u32 SAI_GetBlockDataAddr(SAI_CH_TYPE ch, SAI_BLOCK_TYPE block)
* 功能 : SAI 获取Block 的收发数据地址
* 参数 : ch:SAI选择;block:block选择
* 返回 : 0:无效;其他:地址
* 依赖 : 底层宏定义
* 作者 : [email protected]
* 时间 : 2020-02-17
* 最后修改时间 : 2020-02-17
* 说明 : 获取的是SAI_DR寄存器地址
*************************************************************************************************************************/
u32 SAI_GetBlockDataAddr(SAI_CH_TYPE ch, SAI_BLOCK_TYPE block)
{
SAI_HANDLE *pHandle;
SAI_Block_TypeDef *SAI_Block_X;
pHandle = SAI_GetHandle(ch); //获取SAI句柄
if(pHandle == NULL)
{
DEBUG("无效的句柄\r\n");
return NULL;
}
//先获取Block
SAI_Block_X = SAI_GetBlock(pHandle, block); //获取Block配置结构
return (u32)&SAI_Block_X->DR;
}
/*************************************************************************************************************************
* 函数 : bool SAI_SetEnableDMA(SAI_CH_TYPE ch, SAI_BLOCK_TYPE block,bool isEnable)
* 功能 : SAI使能DMA
* 参数 : ch:SAI选择;block:block选择;isEnable:TRUE使能DMA;FALSE:关闭DMA
* 返回 : TRUE:成功;FALSE:失败
* 依赖 : 底层宏定义
* 作者 : [email protected]
* 时间 : 2020-02-17
* 最后修改时间 : 2020-02-17
* 说明 :
*************************************************************************************************************************/
bool SAI_SetEnableDMA(SAI_CH_TYPE ch, SAI_BLOCK_TYPE block,bool isEnable)
{
SAI_HANDLE *pHandle;
SAI_Block_TypeDef *SAI_Block_X;
pHandle = SAI_GetHandle(ch); //获取SAI句柄
if(pHandle == NULL)
{
DEBUG("无效的句柄\r\n");
return FALSE;
}
//先获取Block
SAI_Block_X = SAI_GetBlock(pHandle, block); //获取Block配置结构
//使能或者禁用
if(isEnable) //需要使能
{
SAI_Block_X->CR1 |= BIT17;
}
else //需要关闭
{
SAI_Block_X->CR1 &= ~BIT17;
}
return TRUE;
}
/*************************************************************************************************************************
* 函数 : void SAI_SetDMAConfig(SAI_CH_TYPE ch, SAI_BLOCK_TYPE block,bool isTxMode, DMA_SIZE_TYPE DataBitSize)
* 功能 : SAI DMA配置
* 参数 : ch:SAI选择;block:block选择;isTxMode:TRUE:发送模式,FALSE:接收模式;DataBitSize:数据位宽
* 返回 : 无
* 依赖 : 底层宏定义
* 作者 : [email protected]
* 时间 : 2020-02-20
* 最后修改时间 : 2020-02-20
* 说明 : 不会启动传输,不开启中断
*************************************************************************************************************************/
void SAI_SetDMAConfig(SAI_CH_TYPE ch, SAI_BLOCK_TYPE block,bool isTxMode, DMA_SIZE_TYPE DataBitSize)
{
DAM_CONFIG mDMA_Config;
DMAxSx_CH_TYPE DMAxSx_Ch = scg_SAI_Channel[ch&0x01][block&0x01]; //DMA通道选择
//DMA配置
mDMA_Config.Mem_BurstSize = DMA_BURST_INCR1; //存储器的突发传输长度配置:单次传输
mDMA_Config.Per_BurstSize = DMA_BURST_INCR1; //外设的突发传输长度配置:单次传输
mDMA_Config.PrioLevel = DAM_PRIO_LEVEL_2; //传输优先级:高
mDMA_Config.Mem_Size = DataBitSize; //存储器数据大小
mDMA_Config.Pre_Size = DataBitSize; //外设数据大小
if(isTxMode) //发送模式:播放音乐
{
mDMA_Config.DataTranDir = DMA_MEM_TO_PRE; //数据传输方向:存储器到外设
}
else //接收模式:录音
{
mDMA_Config.DataTranDir = DMA_PRE_TO_MEM; //数据传输方向:外设到存储器
}
mDMA_Config.PerAddr = SAI_GetBlockDataAddr(ch, block); //外设地址
mDMA_Config.Mem0Addr = NULL; //存储器0地址-启动的时候进行配置
mDMA_Config.Mem1Addr = NULL; //存储器1地址-启动的时候进行配置
mDMA_Config.FIFO_Thres = DMA_FIFO_THRES_FULL; //非直接模式下,FIFO阈值容量选择;直接模式:忽略此设置
mDMA_Config.TranDataCount = 0; //传输数据长度-外设到存储器是流控无需设置长度-启动的时候进行配置
mDMA_Config.isDoubleBuffer = TRUE; //使能双缓冲模式:使能双缓冲
mDMA_Config.isPerIncPsizeAuto = TRUE; //外设偏移量自动根据PSIZE设置(TRUE:偏移量与 PSIZE 相关;否则4字节对齐,如果位 PINC =“0”,则此位没有意义)
mDMA_Config.isMemInc = TRUE; //存储器地址自增:自增
mDMA_Config.isPerInc = FALSE; //外设地址自增:不自增
mDMA_Config.isCircMode = TRUE; //循环模式,循环模式
mDMA_Config.isPreFlowCtrl = FALSE; //外设是流控制设备:非流控
mDMA_Config.isEnableTranComplInt = FALSE; //传输完成中断使能:不开启
mDMA_Config.isDirectMode = TRUE; //直接模式:使能直接模式
mDMA_Config.isEnableStream = FALSE; //是否使能数据流-开始传输:不开启
DMA_Config(DMAxSx_Ch, &mDMA_Config); //DMA配置
}
/*************************************************************************************************************************
* 函数 : void SAI_SetDMAStartTrans(SAI_CH_TYPE ch, SAI_BLOCK_TYPE block,void *pBuff1, void *pBuff2, u16 DataCount)
* 功能 : SAI DMA 启动传输
* 参数 : ch:SAI选择;block:block选择;pBuff1,pBuff2:双缓冲模式下的2个buff指针;DataCount:要传输的数据长度;
* 返回 : 无
* 依赖 : 底层宏定义
* 作者 : [email protected]
* 时间 : 2020-02-20
* 最后修改时间 : 2020-02-20
* 说明 : 重新设置缓冲区地址与要传输的数据数量,并启动传输
*************************************************************************************************************************/
void SAI_SetDMAStartTrans(SAI_CH_TYPE ch, SAI_BLOCK_TYPE block,void *pBuff1, void *pBuff2, u16 DataCount)
{
DMAxSx_CH_TYPE DMAxSx_Ch = scg_SAI_Channel[ch&0x01][block&0x01]; //DMA通道选择
DMA_StartTrans(DMAxSx_Ch, (u32)pBuff1, (u32)pBuff2, DataCount); //启动DMA 传输
}
/*************************************************************************************************************************
* 函数 : void SAI_SetDMAStopTrans(SAI_CH_TYPE ch, SAI_BLOCK_TYPE block)
* 功能 : SAI DMA 停止传输
* 参数 : ch:SAI选择;block:block选择;
* 返回 : 无
* 依赖 : 底层宏定义
* 作者 : [email protected]
* 时间 : 2020-02-20
* 最后修改时间 : 2020-02-20
* 说明 : 停止DMA传输
*************************************************************************************************************************/
void SAI_SetDMAStopTrans(SAI_CH_TYPE ch, SAI_BLOCK_TYPE block)
{
DMAxSx_CH_TYPE DMAxSx_Ch = scg_SAI_Channel[ch&0x01][block&0x01]; //DMA通道选择
DMA_StopTrans(DMAxSx_Ch); //停止DMA 传输
}
/*************************************************************************************************************************
* 函数 : int SAI_WaitDMATransCompl(SAI_CH_TYPE ch, SAI_BLOCK_TYPE block, u32 TimeOutMs)
* 功能 : SAI DMA 等待传输完成
* 参数 : ch:SAI选择;block:block选择;
* 返回 : -1:超时了,0:可以填充缓冲区0;1:可以填充缓冲区1
* 依赖 : 底层宏定义
* 作者 : [email protected]
* 时间 : 2020-02-20
* 最后修改时间 : 2020-02-20
* 说明 : 会清除中断状态
*************************************************************************************************************************/
int SAI_WaitDMATransCompl(SAI_CH_TYPE ch, SAI_BLOCK_TYPE block, u32 TimeOutMs)
{
DMAxSx_CH_TYPE DMAxSx_Ch = scg_SAI_Channel[ch&0x01][block&0x01]; //DMA通道选择
while(((DMA_GetIntStatus(DMAxSx_Ch) & DMA_INT_TRANS_COMPL) == 0) && TimeOutMs) //等待传输完成
{
SYS_DelayMS(1);
TimeOutMs --;
}
DMA_ClearIntStatus(DMAxSx_Ch, DMA_INT_ALL); //清除DMA中断
if(TimeOutMs == 0) return -1;
//传输完成中断有效
if(DMA_GetCurrentTargetBuffIndex(DMAxSx_Ch))//获取双缓冲模式下当前使用的存储器缓冲区索引(0,1)
{
return 0; //缓冲区1正在被使用,返回0可以填充数据
}
else
{
return 1; //缓冲区0正在被使用,返回1可以填充数据
}
}
/*************************************************************************************************************************
* 函数 : bool SAI_SetSampleRate(SAI_CH_TYPE ch, SAI_BLOCK_TYPE block,u32 SampleRate)
* 功能 : SAI 设置采样时钟频率(使用的I2S PLL提供时钟,所有SAI共用)
* 参数 : ch:SAI选择;block:block选择;SampleRate:所需要的采样频率(单位Hz)
* 返回 : TRUE:成功;FALSE:失败
* 依赖 : 底层宏定义
* 作者 : [email protected]
* 时间 : 2020-02-17
* 最后修改时间 : 2020-02-20
* 说明 : 使用的是PLLI2S,所有的SAI共用,此时钟是到达SAI的时钟频率,没有经过SAI MCKDIV分频的时钟频率
注意:系统时钟初始化后,使用的是HSE=25MHz,VCO输入频率为1MHz;ratio固定为256
必须先关闭Block才能设置
*************************************************************************************************************************/
bool SAI_SetSampleRate(SAI_CH_TYPE ch, SAI_BLOCK_TYPE block,u32 SampleRate)
{
SAI_HANDLE *pHandle;
SAI_Block_TypeDef *SAI_Block_X;
u8 index=0;
u32 tempreg=0;
u32 TimeOut = 100000;
//SAI采样率设置表
//vco输入频率为1Mhz
//计算的SAI频率为采样频率的256倍
//[1]:VCO 192~432;[2]:PLLI2SQ:2~15;[3]:PLLI2SDIVQ:0~31+1;[4]:MCKDIV:0~15*2
//采样率计算=VCO/PLLI2SQ/(PLLI2SDIVQ+1)/(MCKDIV*2)/256
const u16 SAI_CLOCK_CONFIG_TAB[][5]=
{
{800, 344, 7, 1-1, 24/2}, //8Khz采样率≈7.998KHz (计算方法344/1/24/256)
{1102, 429, 2, 19-1, 4/2 }, //11.025Khz采样率≈11.024KHz
{1200, 307, 2, 25-1, 2/2 }, //12Khz采样率≈11.992KHz
{1600, 344, 7, 1-1, 12/2}, //16Khz采样率≈15.997KHz
{2205, 429, 2, 19-1, 2/2 }, //22.05Khz采样率≈22.049KHz
{2400, 344, 2, 14-1, 2/2 }, //24Khz采样率≈23.995KHz
{3200, 344, 7, 1-1, 6/2 }, //32Khz采样率≈31.994KHz
{4410, 429, 2, 19-1, 0 }, //44.1Khz采样率≈44.099KHz
{4800, 344, 7, 1-1, 4/2 }, //48Khz采样率≈47.991KHz
{8820, 271, 2, 3-1, 2/2 }, //88.2Khz采样率≈88.216KHz
{9600, 344, 7, 1-1, 2/2 }, //96Khz采样率≈95.982KHz
{17640, 271, 6, 1-1, 0 }, //176.4Khz采样率≈176.432KHz-WM8994不支持
{19200, 295, 6, 1-1, 0 }, //192Khz采样率≈192.810KHz-WM8994不支持
};
pHandle = SAI_GetHandle(ch); //获取SAI句柄
if(pHandle == NULL)
{
DEBUG("无效的句柄\r\n");
return FALSE;
}
SAI_Block_X = SAI_GetBlock(pHandle, block); //获取Block配置结构
switch(SampleRate) //由于目前应用中使用的是WM8994,只获取WM8994所支持的采样率
{
case (8000): index = 0;break; //8KHz
case (11025):index = 1;break; //11.025KHz
case (12000):index = 2;break; //12KHz
case (16000):index = 3;break; //16KHz
case (22050):index = 4;break; //22.05KHz
case (24000):index = 5;break; //24KHz
case (32000):index = 6;break; //32KHz
case (44100):index = 7;break; //44.1KHz
case (48000):index = 8;break; //48KHz
case (88200):index = 9;break; //88.2KHz
case (96000):index = 10;break; //96KHz
default:
{
DEBUG("不支持的采样率:%dHz\r\n", SampleRate);
return FALSE;
}
}
//配置
RCC->CR&=~(1<<26); //先关闭PLLI2S
tempreg|=(u32)SAI_CLOCK_CONFIG_TAB[index][1]<<6; //设置PLLI2SN
tempreg|=(u32)SAI_CLOCK_CONFIG_TAB[index][2]<<24; //设置PLLI2SQ
tempreg|=2<<28; //适用于 I2S 时钟的 PLLI2S 分频系数-不能为0,1
RCC->PLLI2SCFGR=tempreg; //设置I2SxCLK的频率
tempreg=RCC->DCKCFGR1;
tempreg&=~(0X1F); //清空SAI PLLI2SDIVQ设置.
tempreg|=SAI_CLOCK_CONFIG_TAB[index][3]<<0; //设置SAI PLLI2SDIVQ
if(ch == SAI_CH1) //SAI1
{
tempreg&=~(0X03<<20); //清空SAI1 SRC设置
tempreg|=1<<20; //设置SAI1时钟来源为PLLI2SQ
}
else
{
tempreg&=~(0X03<<22); //清空SAI2 SRC设置
tempreg|=1<<22; //设置SAI2时钟来源为PLLI2SQ
}
RCC->DCKCFGR1=tempreg; //设置DCKCFGR寄存器
RCC->CR|=1<<26; //开启PLLI2S
while((RCC->CR&1<<27)==0) //等待PLLI2S 时钟开启成功
{
TimeOut --;
Delay_US(1);
if(TimeOut == 0) return FALSE;
}
tempreg=SAI_Block_X->CR1;
tempreg&=~(0X0F<<20); //清除MCKDIV设置
tempreg|=(u32)SAI_CLOCK_CONFIG_TAB[index][4]<<20; //设置MCKDIV
SAI_Block_X->CR1=tempreg; //配置MCKDIV
return TRUE;
}
/*************************************************************************************************************
* 文件名 : stm32f7_sai.h
* 功能 : STM32F7 SAI接口驱动
* 作者 : [email protected]
* 创建时间 : 2020-02-17
* 最后修改时间 : 2020-02-17
* 详细:
*************************************************************************************************************/
#ifndef __STM32F7_SAI_H_
#define __STM32F7_SAI_H_
#include "system.h"
#include "DMA.h"
#define SAI_CH_COUNT 2 //SAI 数量
#if(SAI_CH_COUNT==0)
#error("SAI数量不能为0");
#endif //SAI_CH_COUNT
//SAIx 音频模块模式(必须在 SAIx 音频模块禁止的情况下配置)
typedef enum
{
SAI_BLOCK_MODE_MTXD = 0, //主发送模式
SAI_BLOCK_MODE_MRXD = 1, //主接收模式
SAI_BLOCK_MODE_STXD = 2, //从发送模式
SAI_BLOCK_MODE_SRXD = 3, //从接收模式
}SAI_BLOCK_MODE;
//SAIx 音频协议配置
typedef enum
{
SAI_PROTOCOL_FREE = 0, //自由协议 由协议允许用户使用音频模块这一强大的配置功能来处理特定的音频协议(如 I2S、LSB/MSB 对齐、TDM、PCM/DSP...)。
SAI_PROTOCOL_SPDIF = 1, //SPDIF协议
SAI_PROTOCOL_AC97 = 2, //AC97协议
}SAI_PROTOCOL_TYPE;
//数据大小(采样深度)
typedef enum
{
SAI_DATA_SIZE_8BIT = 2, //8位
SAI_DATA_SIZE_10BIT = 3, //10位
SAI_DATA_SIZE_16BIT = 4, //16位
SAI_DATA_SIZE_20BIT = 5, //20位
SAI_DATA_SIZE_24BIT = 6, //24位
SAI_DATA_SIZE_32BIT = 7, //32位
}SAI_DATA_SIZE;
//同步设置
typedef enum
{
SAI_ASYNC = 0, //异步模式
SAI_SYNC_IN = 1, //音频子模块与另一个内部音频子模块同步。这种情况下,必须将该音频子模块配置为从模式
SAI_SYNC_OUT = 2, //音频子模块与外部 SAI 的嵌入式外设同步。这种情况下,应将音频子模块配置为从模式。
}SAI_SYNC_TYPE;
//FIFO阈值设置
typedef enum
{
SAI_FIFO_THR_NULL = 0, //FIFO空
SAI_FIFO_THR_1_4 = 1, //FIFO 1/4
SAI_FIFO_THR_1_2 = 2, //FIFO 1/2
SAI_FIFO_THR_3_4 = 3, //FIFO 3/4
SAI_FIFO_THR_FULL = 4, //FIFO 满
}SAI_FIFO_THR;
//Slot大小
typedef enum
{
SAI_SLOT_SIZE_DS = 0, //Slot大小由SAI_DATA_SIZE(DataSize)决定
SAI_SLOT_SIZE_16BIT = 1, //16位
SAI_SLOT_SIZE_32BIT = 2, //32位
}SAI_SLOT_SIZE;
//SAI的block配置
typedef struct
{
//基本配置
SAI_BLOCK_MODE BlockMode; //音频模块模式(必须在 SAIx 音频模块禁止的情况下配置)
SAI_PROTOCOL_TYPE BlockProtocol; //音频协议配置
SAI_DATA_SIZE DataSize; //数据大小
SAI_SYNC_TYPE Sync; //同步模式设置
SAI_FIFO_THR FIFO_Thr; //FIFO阈值
bool isLSB; //使能数据的 LSB 位优先,否则MSB优先
bool isFalEdgeSCK; //在 SCK 下降沿更改 SAI 生成的信号,在上升沿采样,否则上升沿
bool isMono; //使能单声道,否则为立体声
bool isStraiOutput; //使能立即输出,否则当 SAIXEN 置 1 时驱动音频模块输出
bool isEnableDMA; //是否使能DMA
//帧配置
u8 FrameLenght; //帧长度(实际长度为FrameLenght+1)
u8 FrameSyncLenght;//帧同步有效电平长度(实际长度为FrameSyncLenght+1)
bool isFSisChannelId;//使能 FS 信号为 SOF 信号 + 通道识别信号,否则FS信号为帧起始信号
bool isFS_HighLevel; //使能 FS 为高电平有效(上升沿),否则为FS 为低电平有效(下降沿)
bool isFS_Front; //使能 在 Slot 0 第一位的前一位上使能 FS;否则在 Slot 0 的第一位上使能 FS。
//Slot配置
SAI_SLOT_SIZE SlotSize; //Slot 大小
u16 SlotEableBit; //Slot 使能,每1bit对应一个Slot,0-15对应1-16 Slot
u8 FistBitOffset; //此位字段中设置的值定义 Slot 中第一个数据传输位的位置。它表示一个偏移值。在发送模式下,此数据字段以外的位将强制清零。在接收模式下,将丢弃额外接收的位。
u8 SlotNumber; //音频帧中的 Slot 数 (实际数量为:SlotNumber + 1)
}SAI_BLOCK_CONFIG;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//常用配置列表
//默认配置1:主发送模式,标准I2S协议,立体声模式
extern const SAI_BLOCK_CONFIG cg_SAI_MTXD_I2S_CONFIG1;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//SAI选择
typedef enum
{
SAI_CH1 = 0, //SAI1
SAI_CH2 = 1, //SAI2
}SAI_CH_TYPE;
//SAI Block选择
typedef enum
{
SAIx_BLOCK_A = 0, //Block A
SAIx_BLOCK_B = 1, //Block B
}SAI_BLOCK_TYPE;
//相关接口
bool SAI_Init(SAI_CH_TYPE ch);//SAI初始化(基本时钟使能,外设复位)
bool SAI_BlockConfig(SAI_CH_TYPE ch, SAI_BLOCK_TYPE block, const SAI_BLOCK_CONFIG *pConfig); //SAI Block配置(会关闭Block,并且不会使能Block)
bool SAI_SetBlockEnable(SAI_CH_TYPE ch, SAI_BLOCK_TYPE block, bool isEnable); //开启或者关闭Block
bool SAI_SetSampleRate(SAI_CH_TYPE ch, SAI_BLOCK_TYPE block,u32 SampleRate); //SAI 设置采样时钟频率(使用的I2S PLL提供时钟,所有SAI共用)
bool SAI_SetEnableDMA(SAI_CH_TYPE ch, SAI_BLOCK_TYPE block,bool isEnable); //SAI使能DMA
u32 SAI_GetBlockDataAddr(SAI_CH_TYPE ch, SAI_BLOCK_TYPE block); //SAI 获取Block 的收发数据地址
void SAI_BlockDataSize(SAI_CH_TYPE ch, SAI_BLOCK_TYPE block, SAI_DATA_SIZE DataBitSize); //SAI Block配置音频数据大小(采样深度)
//SAI DMA相关
void SAI_SetDMAConfig(SAI_CH_TYPE ch, SAI_BLOCK_TYPE block,bool isTxMode, DMA_SIZE_TYPE DataBitSize); //SAI DMA配置
void SAI_SetDMAStartTrans(SAI_CH_TYPE ch, SAI_BLOCK_TYPE block,void *pBuff1, void *pBuff2, u16 DataCount); //SAI DMA 启动传输
void SAI_SetDMAStopTrans(SAI_CH_TYPE ch, SAI_BLOCK_TYPE block); //SAI DMA 停止传输
int SAI_WaitDMATransCompl(SAI_CH_TYPE ch, SAI_BLOCK_TYPE block, u32 TimeOutMs); //SAI DMA 等待传输完成
/*************************************************************************************************************************
* 函数 : __inline void SAI_IO_Init(SAI_CH_TYPE ch, SAI_BLOCK_TYPE block)
* 功能 : SAI IO 初始化(由于IO口复用较多,请根据实际使用进行配置)
* 参数 : ch:SAI选择;block:block选择;
* 返回 : IIC_ERROR
* 依赖 : 底层宏定义
* 作者 : [email protected]
* 时间 : 2020-02-17
* 最后修改时间 : 2020-02-17
* 说明 : SAI1A
MCK_A PE2 AF6
FS_A PE4 AF6
SCK_A PE5 AF6
SD_A PE6 AF6
SD_A PC1 AF6
SD_A PB2 AF6
SD_A PD6 AF6
SAI1B
SD_B PF6 AF6
MCLK_B PF7 AF6
SCK_B PF8 AF6
FS_B PF9 AF6
SD_B PE3 AF6
SAI2A
SD_A PD11 AF10
FS_A PD12 AF10
SCK_A PD13 AF10
MCK_A PE0 AF10
MCK_A PI4 AF10
SCK_A PI5 AF10
SD_A PI6 AF10
FS_A PI7 AF10
SAI2B
MCK_B PE6 AF10
FS_B PC0 AF8
SD_B PA0 AF10
MCK_B PA1 AF10
SCK_B PA2 AF8
SCK_B PH2 AF10
MCK_B PH3 AF10
SD_B PF11 AF10
SD_B PE11 AF10
SCK_B PE12 AF10
FS_B PE13 AF10
MCK_B PE14 AF10
FS_B PA12 AF8
FS_B PG9 AF10
SD_B PG10 AF10
*************************************************************************************************************************/
__inline void SAI_IO_Init(SAI_CH_TYPE ch, SAI_BLOCK_TYPE block)
{
if(ch == SAI_CH1) //SAI1
{
if(block == SAIx_BLOCK_A) //SAI1_A
{
//PE2,PE4,PE5,PE6
SYS_DeviceClockEnable(DEV_GPIOE, TRUE); //IO时钟使能
SYS_GPIOx_Init(GPIOE, BIT2|BIT4|BIT5|BIT6, AF_PP, SPEED_100M); //速度给最大
SYS_GPIOx_SetAF(GPIOE, 2, AF6_SAI1);
SYS_GPIOx_SetAF(GPIOE, 4, AF6_SAI1);
SYS_GPIOx_SetAF(GPIOE, 5, AF6_SAI1);
SYS_GPIOx_SetAF(GPIOE, 6, AF6_SAI1);
}
else //SAI1_B
{
//PF6,PF7,PF8,PF9
SYS_DeviceClockEnable(DEV_GPIOF, TRUE); //IO时钟使能
SYS_GPIOx_Init(GPIOF, BIT6|BIT7|BIT8|BIT9, AF_PP, SPEED_100M); //速度给最大
SYS_GPIOx_SetAF(GPIOF, 6, AF6_SAI1);
SYS_GPIOx_SetAF(GPIOF, 7, AF6_SAI1);
SYS_GPIOx_SetAF(GPIOF, 8, AF6_SAI1);
SYS_GPIOx_SetAF(GPIOF, 9, AF6_SAI1);
}
}
else //SAI2
{
if(block == SAIx_BLOCK_A) //SAI2_A
{
//PI4,PI5,PI6,PI7
SYS_DeviceClockEnable(DEV_GPIOI, TRUE); //IO时钟使能
SYS_GPIOx_Init(GPIOI, BIT4|BIT5|BIT6|BIT7, AF_PP, SPEED_100M); //速度给最大
SYS_GPIOx_SetAF(GPIOI, 4, AF10_SAI2);
SYS_GPIOx_SetAF(GPIOI, 5, AF10_SAI2);
SYS_GPIOx_SetAF(GPIOI, 6, AF10_SAI2);
SYS_GPIOx_SetAF(GPIOI, 7, AF10_SAI2);
}
else //SAI2_B
{
//PE6,PE11,PE12,PE13
SYS_DeviceClockEnable(DEV_GPIOE, TRUE); //IO时钟使能
SYS_GPIOx_Init(GPIOE, BIT6|BIT11|BIT12|BIT13, AF_PP, SPEED_100M); //速度给最大
SYS_GPIOx_SetAF(GPIOE, 6, AF10_SAI2);
SYS_GPIOx_SetAF(GPIOE, 11, AF10_SAI2);
SYS_GPIOx_SetAF(GPIOE, 12, AF10_SAI2);
SYS_GPIOx_SetAF(GPIOE, 13, AF10_SAI2);
}
}
}
#endif //__STM32F7_SAI_H_
//测试代码,初始化WM8994,SAI后,初始化随机数发生器,使用随机数填充SAI的发送缓冲区,完成最简单的测试,不涉及到DMA,文件系统,wav解析等,到这一步了后续的调试就简单了(后面我会写一个完整的wav播放例子)
//测试噪声播放
void SAI_Test(void)
{
WM8994_Init(&g_SysGlobal.mWM8994_Handle, 0x34, WM8994_IIC_ReadReg, WM8994_IIC_WriteReg);//WM8994初始化-提前初始化IIC接口
SAI_Init(SAI_CH2); //SAI2初始化(基本时钟使能,外设复位)
SAI_IO_Init(SAI_CH2, SAIx_BLOCK_A); //初始化SIA2的block A
//Block配置
SAI_SetBlockEnable(SAI_CH2, SAIx_BLOCK_A, FALSE); //关闭block
SAI_BlockConfig(SAI_CH2, SAIx_BLOCK_A, &cg_SAI_MTXD_I2S_CONFIG1); //配置block
if(SAI_SetSampleRate(SAI_CH2, SAIx_BLOCK_A, 44100) == FALSE) //SAI 设置采样时钟频率(使用的I2S PLL提供时钟,所有SAI共用)
{
uart_printf("SAI时钟采样率设置失败!\r\n");
}
WM8994_SetFrequency(&g_SysGlobal.mWM8994_Handle, 44100); //WM8994 采样率设置
SAI_SetEnableDMA(SAI_CH2, SAIx_BLOCK_A, TRUE); //SAI使能DMA
SAI_SetDMAConfig(SAI_CH2, SAIx_BLOCK_A, TRUE, DMA_SIZE_16BIT); //SAI DMA配置-发送模式
SAI_SetBlockEnable(SAI_CH2, SAIx_BLOCK_A, TRUE); //使能BLOCK
WM8994_EnableDAC(&g_SysGlobal.mWM8994_Handle, TRUE); //使能WM8994 DAC输出
//初始化随机数发生器
SYS_DeviceClockEnable(DEV_RNG, TRUE);
RNG->CR |= BIT2; //使能随机数发生器
while(1)
{
while(SAI2_Block_A->SR & BIT3)
{
SAI2_Block_A->DR = RNG->DR;
}
SYS_DelayMS(1);
}
}