STM32F7 SAI驱动
使用的是开发板上面的SAI2A,连接的WM8994,使用的DMA双缓冲传输(WM8994的初始化见之前的文章)
/************************************************************************************************************** 文件名 : stm32f7_sai.c* 功能 : STM32F7 SAI接口驱动* 作者 : cp1300@139.com* 创建时间 : 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与Slot10, //此位字段中设置的值定义 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:失败;
* 依赖 : 底层宏定义
* 作者 : cp1300@139.com
* 时间 : 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:失败
* 依赖 : 底层宏定义
* 作者 : cp1300@139.com
* 时间 : 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;}//先获取BlockSAI_Block_X = SAI_GetBlock(pHandle, block); //获取Block配置结构SAI_SetBlockEnable(pHandle->ch, block, FALSE); //关闭Block//先设置CR1temp = 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;//设置CR2SAI_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 SlotSAI_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
* 返回 : 无
* 依赖 : 底层宏定义
* 作者 : cp1300@139.com
* 时间 : 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;}//先获取BlockSAI_Block_X = SAI_GetBlock(pHandle, block); //获取Block配置结构//设置CR1SAI_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:失败
* 依赖 : 底层宏定义
* 作者 : cp1300@139.com
* 时间 : 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;}//先获取BlockSAI_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:无效;其他:地址
* 依赖 : 底层宏定义
* 作者 : cp1300@139.com
* 时间 : 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;}//先获取BlockSAI_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:失败
* 依赖 : 底层宏定义
* 作者 : cp1300@139.com
* 时间 : 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;}//先获取BlockSAI_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:数据位宽
* 返回 : 无
* 依赖 : 底层宏定义
* 作者 : cp1300@139.com
* 时间 : 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:要传输的数据长度;
* 返回 : 无
* 依赖 : 底层宏定义
* 作者 : cp1300@139.com
* 时间 : 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选择;
* 返回 : 无
* 依赖 : 底层宏定义
* 作者 : cp1300@139.com
* 时间 : 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
* 依赖 : 底层宏定义
* 作者 : cp1300@139.com
* 时间 : 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:失败
* 依赖 : 底层宏定义
* 作者 : cp1300@139.com
* 时间 : 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)/256const 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; //8KHzcase (11025):index = 1;break; //11.025KHzcase (12000):index = 2;break; //12KHzcase (16000):index = 3;break; //16KHzcase (22050):index = 4;break; //22.05KHzcase (24000):index = 5;break; //24KHzcase (32000):index = 6;break; //32KHzcase (44100):index = 7;break; //44.1KHzcase (48000):index = 8;break; //48KHzcase (88200):index = 9;break; //88.2KHzcase (96000):index = 10;break; //96KHzdefault:{DEBUG("不支持的采样率:%dHz\r\n", SampleRate);return FALSE;}}//配置RCC->CR&=~(1<<26); //先关闭PLLI2S tempreg|=(u32)SAI_CLOCK_CONFIG_TAB[index][1]<<6; //设置PLLI2SNtempreg|=(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; //设置MCKDIVSAI_Block_X->CR1=tempreg; //配置MCKDIVreturn TRUE;
}
/************************************************************************************************************** 文件名 : stm32f7_sai.h* 功能 : STM32F7 SAI接口驱动* 作者 : cp1300@139.com* 创建时间 : 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/4SAI_FIFO_THR_1_2 = 2, //FIFO 1/2SAI_FIFO_THR_3_4 = 3, //FIFO 3/4SAI_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 Slotu8 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, //SAI1SAI_CH2 = 1, //SAI2
}SAI_CH_TYPE;//SAI Block选择
typedef enum
{SAIx_BLOCK_A = 0, //Block ASAIx_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
* 依赖 : 底层宏定义
* 作者 : cp1300@139.com
* 时间 : 2020-02-17
* 最后修改时间 : 2020-02-17
* 说明 : SAI1AMCK_A PE2 AF6FS_A PE4 AF6 SCK_A PE5 AF6SD_A PE6 AF6SD_A PC1 AF6SD_A PB2 AF6SD_A PD6 AF6SAI1B SD_B PF6 AF6MCLK_B PF7 AF6SCK_B PF8 AF6FS_B PF9 AF6SD_B PE3 AF6SAI2ASD_A PD11 AF10FS_A PD12 AF10SCK_A PD13 AF10MCK_A PE0 AF10MCK_A PI4 AF10SCK_A PI5 AF10SD_A PI6 AF10FS_A PI7 AF10SAI2B MCK_B PE6 AF10FS_B PC0 AF8SD_B PA0 AF10MCK_B PA1 AF10SCK_B PA2 AF8SCK_B PH2 AF10MCK_B PH3 AF10SD_B PF11 AF10SD_B PE11 AF10SCK_B PE12 AF10FS_B PE13 AF10MCK_B PE14 AF10FS_B PA12 AF8FS_B PG9 AF10SD_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,PE6SYS_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,PF9SYS_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,PI7SYS_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,PE13SYS_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); //关闭blockSAI_BlockConfig(SAI_CH2, SAIx_BLOCK_A, &cg_SAI_MTXD_I2S_CONFIG1); //配置blockif(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使能DMASAI_SetDMAConfig(SAI_CH2, SAIx_BLOCK_A, TRUE, DMA_SIZE_16BIT); //SAI DMA配置-发送模式SAI_SetBlockEnable(SAI_CH2, SAIx_BLOCK_A, TRUE); //使能BLOCKWM8994_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);}
}
STM32F7 SAI驱动相关推荐
- WM8994驱动 STM32 WM8994驱动( STM32f746gdiscovery WM8994驱动)
描述 WM8994 是一款高度集成的超低功耗高保真 CODEC,专为智能手机和其他具有丰富多媒体功能的便携式设备而设计. 集成的立体声 D/AB 扬声器驱动器和 W 类耳机驱动器,可最大限度降低音频播 ...
- STM32MP157驱动开发——Linux 音频驱动
STM32MP157驱动开发--Linux 音频驱动 一.简介 1.CS42L51 简介 2.I2S总线 3.STM32MP1 SAI 总线接口 二.驱动开发 1.音频驱动 1)修改设备树 i2c 接 ...
- 基于NXP iMX6ULL 扩展音频解码器 MAX98357A
By Toradex胡珊逢 Colibri iMX6ULL 是 Toradex 面向低成本设备推出的 Arm 计算机模块.该产品没有音频编解码器,无法直接输出模拟音频信号.本文将介绍如何使用模块的数字 ...
- STM32H750获取OV5640摄像头图像及上位机解码(一维码二维码)
STM32H750获取OV5640摄像头图像及上位机解码(一维码&二维码) 1. 目的 针对静止拍摄图像场景,实现STM32H750对500万像素OV5640摄像头进行图像捕获,并通过串口将数 ...
- STM32H750获取OV2640摄像头图像及上位机解码(一维码二维码)
STM32H750获取OV2640摄像头图像及上位机解码(一维码&二维码) 1. 目的 针对静止拍摄图像场景,实现STM32H750对200万像素OV2640摄像头进行图像捕获,并通过串口将数 ...
- STM32H750获取OV7670摄像头图像及上位机解码(一维码二维码)
STM32H750获取OV7670摄像头图像及上位机解码(一维码&二维码) 1. 目的 针对静止拍摄图像场景,实现STM32H750对30万像素OV7670摄像头进行图像捕获,并通过串口将数据 ...
- 【正点原子MP157连载】第二十三章 Linux设备树-摘自【正点原子】STM32MP1嵌入式Linux驱动开发指南V1.7
1)实验平台:正点原子STM32MP157开发板 2)购买链接:https://item.taobao.com/item.htm?&id=629270721801 3)全套实验源码+手册+视频 ...
- Linux 音频驱动
Linux 音频驱动 硬件介绍 WM8960与IMX6ULL之间有两个通信接口:I2C和I2S 其中I2C用于配置WM8960 I2S用于音频数据传输 修改设备树文件 编写I2C子节点设备树 code ...
- 进入方法内快捷键_肝货|驱动安装流程驱动amp;快捷键设置(一)
| 这里是博学多识可爱无敌的课代表可可嘚吧嘚 笔记疯狂输出时间 某个灵机一动(摸鱼摸虾)的下午 突然发现(摸鱼有理了~) 写了这么多的教程我竟然遗漏了一个最最最最最······重要的手绘板/屏的驱动安 ...
- Linux驱动开发中的中间件:设备树
Linux设备树 设备树的产生是为了解决内核源码的arch/arm目录下代码混乱和臃肿的问题(过去每个厂商出个板子就要提供外设硬件和平台硬件信息,这些信息以.c和.h文件的形式呈现).在使用设备树之后 ...
最新文章
- 电脑连接电视方法详解_查看电脑配置的几种方法(图文详解)
- 研发应该懂的binlog知识(下)
- visio如何扩大画布的大小. 鼠标移到画布的边界按住Ctrl,就可以拉大
- HashSet中是如何判断元素是否重复的
- c语言link错误什么原因,C语言 OpenCV错误:“LINK:致命错误LNK1104:无法打开文件’opencv_core231d.lib’”...
- 公网对讲机修改对讲机程序_更少的对讲机,对讲机-更多专心,专心
- 2018/7/17-纪中某C组题【jzoj4024,jzoj4025,jzoj2136,jzoj2137】
- Extjs 数据代理
- Jboss未授权访问漏洞记录(影响版本:全版本,端口:80,8080)
- 利用hutool工具类导出Excel
- 14英寸电脑长宽多少_华为MateBook 14 2020款 14英寸轻薄笔记本王者升级
- java-多线程安全问题
- awk去除行首行尾空格
- JDK64位安装与JDK环境配置图文教程
- 安卓系统的电视机_天猫魔盒强刷机教程,把天猫魔盒刷成安卓系统教程?
- 华为服务器找不到阵列卡_DELL 服务器R230 加载阵列卡驱动安装Server 2012R2操作系统...
- 基于模型的约束排序,并探究OTUs对pH的响应特征——单峰or线性?
- ORA-20011, KUP-11024 外部表引发报错
- Linux目录结构,命令,文件类型学习
- 求矩阵伪逆的matlab方法,手把手教学