打印
[STM32F7]

STM32F7 SAI驱动

[复制链接]
893|11
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
guanjiaer|  楼主 | 2021-8-2 10:41 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
使用的是开发板上面的SAI2A,连接的WM8994,使用的DMA双缓冲传输.


/*************************************************************************************************************
* 文件名                :        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与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:失败;
* 依赖        :                        底层宏定义
* 作者        :                        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;
}


使用特权

评论回复
沙发
guanjiaer|  楼主 | 2021-8-2 10:42 | 只看该作者
/*************************************************************************************************************************
* 函数        :                        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;
        }
        //先获取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;
}



使用特权

评论回复
板凳
guanjiaer|  楼主 | 2021-8-2 10:43 | 只看该作者

/*************************************************************************************************************************
* 函数        :                        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;
        }
        //先获取Block
        SAI_Block_X = SAI_GetBlock(pHandle, block);                        //获取Block配置结构
        //设置CR1
        SAI_Block_X->CR1 &= ~(0x07 << 5);                                        //清除之前的配置
        SAI_Block_X->CR1 |= (DataBitSize&0x07) << 5;                //重新配置
}





使用特权

评论回复
地板
guanjiaer|  楼主 | 2021-8-2 10:46 | 只看该作者
/*************************************************************************************************************************
* 函数        :                        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;
        }
        //先获取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;
}

使用特权

评论回复
5
guanjiaer|  楼主 | 2021-8-2 10:47 | 只看该作者
/*************************************************************************************************************************
* 函数        :                        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;
        }
        //先获取Block
        SAI_Block_X = SAI_GetBlock(pHandle, block);                                //获取Block配置结构
       
        return (u32)&SAI_Block_X->DR;
}



使用特权

评论回复
6
guanjiaer|  楼主 | 2021-8-2 10:47 | 只看该作者
/*************************************************************************************************************************
* 函数        :                        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;
        }
        //先获取Block
        SAI_Block_X = SAI_GetBlock(pHandle, block);                                //获取Block配置结构

        //使能或者禁用
        if(isEnable) //需要使能
        {
                SAI_Block_X->CR1 |= BIT17;
        }
        else //需要关闭
        {
                SAI_Block_X->CR1 &= ~BIT17;
        }
       
        return TRUE;
}



使用特权

评论回复
7
guanjiaer|  楼主 | 2021-8-2 10:48 | 只看该作者
/*************************************************************************************************************************
* 函数        :                        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配置
}



使用特权

评论回复
8
guanjiaer|  楼主 | 2021-8-2 10:50 | 只看该作者
/*************************************************************************************************************************
* 函数        :                        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 传输
}



使用特权

评论回复
9
guanjiaer|  楼主 | 2021-8-2 10:52 | 只看该作者

/*************************************************************************************************************************
* 函数        :                        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可以填充数据
        }
}


使用特权

评论回复
10
guanjiaer|  楼主 | 2021-8-2 10:53 | 只看该作者

/*************************************************************************************************************************
* 函数        :                        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)/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;
}



使用特权

评论回复
11
guanjiaer|  楼主 | 2021-8-2 10:54 | 只看该作者

/*************************************************************************************************************
* 文件名                :        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/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
* 依赖        :                        底层宏定义
* 作者        :                        cp1300@139.com
* 时间        :                        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_


使用特权

评论回复
12
guanjiaer|  楼主 | 2021-8-2 10:55 | 只看该作者
//测试代码,初始化WM8994,SAI后,初始化随机数发生器,使用随机数填充SAI的发送缓冲区,完成最简单的测试,不涉及到DMA,文件系统,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);
        }
}


使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

77

主题

4116

帖子

2

粉丝