341 lines
15 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "sai.h"
#include "delay.h"
//////////////////////////////////////////////////////////////////////////////////
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK STM32H7开发板
//SAI驱动代码
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//创建日期:2017/8/17
//版本V1.0
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2014-2024
//All rights reserved
//////////////////////////////////////////////////////////////////////////////////
SAI_HandleTypeDef SAI1A_Handler; //SAI1 Block A句柄
SAI_HandleTypeDef SAI1B_Handler; //SAI1 Block B句柄
DMA_HandleTypeDef SAI1_TXDMA_Handler; //DMA发送句柄
DMA_HandleTypeDef SAI1_RXDMA_Handler; //DMA接收句柄
//SAI Block A初始化,I2S,飞利浦标准
//mode:工作模式,可以设置:SAI_MODEMASTER_TX/SAI_MODEMASTER_RX/SAI_MODESLAVE_TX/SAI_MODESLAVE_RX
//cpol:数据在时钟的上升/下降沿选通可以设置SAI_CLOCKSTROBING_FALLINGEDGE/SAI_CLOCKSTROBING_RISINGEDGE
//datalen:数据大小,可以设置SAI_DATASIZE_8/10/16/20/24/32
void SAIA_Init(u32 mode,u32 cpol,u32 datalen)
{
HAL_SAI_DeInit(&SAI1A_Handler); //清除以前的配置
SAI1A_Handler.Instance=SAI1_Block_A; //SAI1 Bock A
SAI1A_Handler.Init.AudioMode=mode; //设置SAI1工作模式
SAI1A_Handler.Init.Synchro=SAI_ASYNCHRONOUS; //音频模块异步
SAI1A_Handler.Init.OutputDrive=SAI_OUTPUTDRIVE_ENABLE; //立即驱动音频模块输出
SAI1A_Handler.Init.NoDivider=SAI_MASTERDIVIDER_ENABLE; //使能主时钟分频器(MCKDIV)
SAI1A_Handler.Init.FIFOThreshold=SAI_FIFOTHRESHOLD_1QF; //设置FIFO阈值,1/4 FIFO
SAI1A_Handler.Init.MonoStereoMode=SAI_STEREOMODE; //立体声模式
SAI1A_Handler.Init.Protocol=SAI_FREE_PROTOCOL; //设置SAI1协议为:自由协议(支持I2S/LSB/MSB/TDM/PCM/DSP等协议)
SAI1A_Handler.Init.DataSize=datalen; //设置数据大小
SAI1A_Handler.Init.FirstBit=SAI_FIRSTBIT_MSB; //数据MSB位优先
SAI1A_Handler.Init.ClockStrobing=cpol; //数据在时钟的上升/下降沿选通
//帧设置
SAI1A_Handler.FrameInit.FrameLength=64; //设置帧长度为64,左通道32个SCK,右通道32个SCK.
SAI1A_Handler.FrameInit.ActiveFrameLength=32; //设置帧同步有效电平长度,在I2S模式下=1/2帧长.
SAI1A_Handler.FrameInit.FSDefinition=SAI_FS_CHANNEL_IDENTIFICATION;//FS信号为SOF信号+通道识别信号
SAI1A_Handler.FrameInit.FSPolarity=SAI_FS_ACTIVE_LOW; //FS低电平有效(下降沿)
SAI1A_Handler.FrameInit.FSOffset=SAI_FS_BEFOREFIRSTBIT; //在slot0的第一位的前一位使能FS,以匹配飞利浦标准
//SLOT设置
SAI1A_Handler.SlotInit.FirstBitOffset=0; //slot偏移(FBOFF)为0
SAI1A_Handler.SlotInit.SlotSize=SAI_SLOTSIZE_32B; //slot大小为32位
SAI1A_Handler.SlotInit.SlotNumber=2; //slot数为2个
SAI1A_Handler.SlotInit.SlotActive=SAI_SLOTACTIVE_0|SAI_SLOTACTIVE_1;//使能slot0和slot1
HAL_SAI_Init(&SAI1A_Handler); //初始化SAI
__HAL_SAI_ENABLE(&SAI1A_Handler); //使能SAI
}
//SAI Block B初始化,I2S,飞利浦标准
//mode:工作模式,可以设置:SAI_MODEMASTER_TX/SAI_MODEMASTER_RX/SAI_MODESLAVE_TX/SAI_MODESLAVE_RX
//cpol:数据在时钟的上升/下降沿选通可以设置SAI_CLOCKSTROBING_FALLINGEDGE/SAI_CLOCKSTROBING_RISINGEDGE
//datalen:数据大小,可以设置SAI_DATASIZE_8/10/16/20/24/32
void SAIB_Init(u32 mode,u32 cpol,u32 datalen)
{
HAL_SAI_DeInit(&SAI1B_Handler); //清除以前的配置
SAI1B_Handler.Instance=SAI1_Block_B; //SAI1 Bock B
SAI1B_Handler.Init.AudioMode=mode; //设置SAI1工作模式
SAI1B_Handler.Init.Synchro=SAI_SYNCHRONOUS; //音频模块同步
SAI1B_Handler.Init.OutputDrive=SAI_OUTPUTDRIVE_ENABLE; //立即驱动音频模块输出
SAI1B_Handler.Init.NoDivider=SAI_MASTERDIVIDER_ENABLE; //使能主时钟分频器(MCKDIV)
SAI1B_Handler.Init.FIFOThreshold=SAI_FIFOTHRESHOLD_1QF; //设置FIFO阈值,1/4 FIFO
SAI1B_Handler.Init.MonoStereoMode=SAI_STEREOMODE; //立体声模式
SAI1B_Handler.Init.Protocol=SAI_FREE_PROTOCOL; //设置SAI1协议为:自由协议(支持I2S/LSB/MSB/TDM/PCM/DSP等协议)
SAI1B_Handler.Init.DataSize=datalen; //设置数据大小
SAI1B_Handler.Init.FirstBit=SAI_FIRSTBIT_MSB; //数据MSB位优先
SAI1B_Handler.Init.ClockStrobing=cpol; //数据在时钟的上升/下降沿选通
//帧设置
SAI1B_Handler.FrameInit.FrameLength=64; //设置帧长度为64,左通道32个SCK,右通道32个SCK.
SAI1B_Handler.FrameInit.ActiveFrameLength=32; //设置帧同步有效电平长度,在I2S模式下=1/2帧长.
SAI1B_Handler.FrameInit.FSDefinition=SAI_FS_CHANNEL_IDENTIFICATION;//FS信号为SOF信号+通道识别信号
SAI1B_Handler.FrameInit.FSPolarity=SAI_FS_ACTIVE_LOW; //FS低电平有效(下降沿)
SAI1B_Handler.FrameInit.FSOffset=SAI_FS_BEFOREFIRSTBIT; //在slot0的第一位的前一位使能FS,以匹配飞利浦标准
//SLOT设置
SAI1B_Handler.SlotInit.FirstBitOffset=0; //slot偏移(FBOFF)为0
SAI1B_Handler.SlotInit.SlotSize=SAI_SLOTSIZE_32B; //slot大小为32位
SAI1B_Handler.SlotInit.SlotNumber=2; //slot数为2个
SAI1B_Handler.SlotInit.SlotActive=SAI_SLOTACTIVE_0|SAI_SLOTACTIVE_1;//使能slot0和slot1
HAL_SAI_Init(&SAI1B_Handler);
SAIB_DMA_Enable(); //使能SAI的DMA功能
__HAL_SAI_ENABLE(&SAI1B_Handler); //使能SAI
}
//SAI底层驱动引脚配置时钟使能
//此函数会被HAL_SAI_Init()调用
//hsdram:SAI句柄
void HAL_SAI_MspInit(SAI_HandleTypeDef *hsai)
{
GPIO_InitTypeDef GPIO_Initure;
__HAL_RCC_SAI1_CLK_ENABLE(); //使能SAI1时钟
__HAL_RCC_GPIOE_CLK_ENABLE(); //使能GPIOE时钟
//初始化PE2,3,4,5,6
GPIO_Initure.Pin=GPIO_PIN_2|GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6;
GPIO_Initure.Mode=GPIO_MODE_AF_PP; //推挽复用
GPIO_Initure.Pull=GPIO_PULLUP; //上拉
GPIO_Initure.Speed=GPIO_SPEED_FREQ_VERY_HIGH; //高速
GPIO_Initure.Alternate=GPIO_AF6_SAI1; //复用为SAI
HAL_GPIO_Init(GPIOE,&GPIO_Initure); //初始化
}
//SAI Block A采样率设置
//采样率计算公式:
//MCKDIV!=0: Fs=SAI_CK_x/[512*MCKDIV]
//MCKDIV==0: Fs=SAI_CK_x/256
//SAI_CK_x=(HSE/pllm)*PLLSAIN/PLLSAIQ/(PLLSAIDivQ+1)
//一般HSE=25Mhz
//pllm:在Stm32_Clock_Init设置的时候确定一般是25
//PLLSAIN:一般是50~432
//PLLSAIQ:2~15
//PLLSAIDivQ:1~32
//MCKDIV:0~15
//SAI A分频系数表@pllm=25,HSE=25Mhz,即vco输入频率为1Mhz
const u16 SAI_PSC_TBL[][5]=
{
{800 ,344,7,1,12}, //8Khz采样率
{1102,429,2,19,2}, //11.025Khz采样率
{1600,344,7, 1,6}, //16Khz采样率
{2205,429,2,19,1}, //22.05Khz采样率
{3200,344,7, 1,3}, //32Khz采样率
{4410,429,2,19,0}, //44.1Khz采样率
{4800,344,7, 1,2}, //48Khz采样率
{8820,271,2, 3,1}, //88.2Khz采样率
{9600,344,7, 1,1}, //96Khz采样率
{17640,271,6,3,0}, //176.4Khz采样率
{19200,295,6,1,0}, //192Khz采样率
};
//开启SAI的DMA功能,HAL库没有提供此函数
//因此我们需要自己操作寄存器编写一个
void SAIA_DMA_Enable(void)
{
u32 tempreg=0;
tempreg=SAI1_Block_A->CR1; //先读出以前的设置
tempreg|=1<<17; //使能DMA
SAI1_Block_A->CR1=tempreg; //写入CR1寄存器中
}
//开启SAIB的DMA功能,HAL库没有提供此函数
//因此我们需要自己操作寄存器编写一个
void SAIB_DMA_Enable(void)
{
u32 tempreg=0;
tempreg=SAI1_Block_B->CR1; //先读出以前的设置
tempreg|=1<<17; //使能DMA
SAI1_Block_B->CR1=tempreg; //写入CR1寄存器中
}
//设置SAIA的采样率(@MCKEN)
//samplerate:采样率,单位:Hz
//返回值:0,设置成功;1,无法设置.
u8 SAIA_SampleRate_Set(u32 samplerate)
{
u8 i=0;
RCC_PeriphCLKInitTypeDef RCCSAI1_Sture;
for(i=0;i<(sizeof(SAI_PSC_TBL)/10);i++)//看看改采样率是否可以支持
{
if((samplerate/10)==SAI_PSC_TBL[i][0])break;
}
if(i==(sizeof(SAI_PSC_TBL)/10))return 1;//搜遍了也找不到
RCCSAI1_Sture.PeriphClockSelection=RCC_PERIPHCLK_SAI1;
RCCSAI1_Sture.Sai1ClockSelection=RCC_SAI1CLKSOURCE_PLL2;
RCCSAI1_Sture.PLL2.PLL2M=25;
RCCSAI1_Sture.PLL2.PLL2N=(u32)SAI_PSC_TBL[i][1];
RCCSAI1_Sture.PLL2.PLL2P=(u32)SAI_PSC_TBL[i][2];
HAL_RCCEx_PeriphCLKConfig(&RCCSAI1_Sture);
__HAL_SAI_DISABLE(&SAI1A_Handler); //关闭SAI
SAI1A_Handler.Init.AudioFrequency=samplerate; //设置播放频率
HAL_SAI_Init(&SAI1A_Handler); //初始化SAI
SAIA_DMA_Enable(); //开启SAI的DMA功能
__HAL_SAI_ENABLE(&SAI1A_Handler); //开启SAI
return 0;
}
//SAIA TX DMA配置
//设置为双缓冲模式,并开启DMA传输完成中断
//buf0:M0AR地址.
//buf1:M1AR地址.
//num:每次传输数据量
//width:位宽(存储器和外设,同时设置),0,8位;1,16位;2,32位;
void SAIA_TX_DMA_Init(u8* buf0,u8 *buf1,u16 num,u8 width)
{
u32 memwidth=0,perwidth=0; //外设和存储器位宽
switch(width)
{
case 0: //8位
memwidth=DMA_MDATAALIGN_BYTE;
perwidth=DMA_PDATAALIGN_BYTE;
break;
case 1: //16位
memwidth=DMA_MDATAALIGN_HALFWORD;
perwidth=DMA_PDATAALIGN_HALFWORD;
break;
case 2: //32位
memwidth=DMA_MDATAALIGN_WORD;
perwidth=DMA_PDATAALIGN_WORD;
break;
}
__HAL_RCC_DMA1_CLK_ENABLE(); //使能DMA1时钟
__HAL_LINKDMA(&SAI1A_Handler,hdmatx,SAI1_TXDMA_Handler); //将DMA与SAI联系起来
SAI1_TXDMA_Handler.Instance=DMA1_Stream5; //DMA1数据流5
SAI1_TXDMA_Handler.Init.Request=DMA_REQUEST_SAI1_A; //SAI1 Bock A
SAI1_TXDMA_Handler.Init.Direction=DMA_MEMORY_TO_PERIPH; //存储器到外设模式
SAI1_TXDMA_Handler.Init.PeriphInc=DMA_PINC_DISABLE; //外设非增量模式
SAI1_TXDMA_Handler.Init.MemInc=DMA_MINC_ENABLE; //存储器增量模式
SAI1_TXDMA_Handler.Init.PeriphDataAlignment=perwidth; //外设数据长度:16/32位
SAI1_TXDMA_Handler.Init.MemDataAlignment=memwidth; //存储器数据长度:16/32位
SAI1_TXDMA_Handler.Init.Mode=DMA_CIRCULAR; //使用循环模式
SAI1_TXDMA_Handler.Init.Priority=DMA_PRIORITY_HIGH; //高优先级
SAI1_TXDMA_Handler.Init.FIFOMode=DMA_FIFOMODE_DISABLE; //不使用FIFO
SAI1_TXDMA_Handler.Init.MemBurst=DMA_MBURST_SINGLE; //存储器单次突发传输
SAI1_TXDMA_Handler.Init.PeriphBurst=DMA_PBURST_SINGLE; //外设突发单次传输
HAL_DMA_DeInit(&SAI1_TXDMA_Handler); //先清除以前的设置
HAL_DMA_Init(&SAI1_TXDMA_Handler); //初始化DMA
HAL_DMAEx_MultiBufferStart(&SAI1_TXDMA_Handler,(u32)buf0,(u32)&SAI1_Block_A->DR,(u32)buf1,num);//开启双缓冲
__HAL_DMA_DISABLE(&SAI1_TXDMA_Handler); //先关闭DMA
delay_us(10); //10us延时防止-O2优化出问题
__HAL_DMA_ENABLE_IT(&SAI1_TXDMA_Handler,DMA_IT_TC); //开启传输完成中断
__HAL_DMA_CLEAR_FLAG(&SAI1_TXDMA_Handler,DMA_FLAG_TCIF1_5); //清除DMA传输完成中断标志位
HAL_NVIC_SetPriority(DMA1_Stream5_IRQn,0,0); //DMA中断优先级
HAL_NVIC_EnableIRQ(DMA1_Stream5_IRQn);
}
//SAIA TX DMA配置
//设置为双缓冲模式,并开启DMA传输完成中断
//buf0:M0AR地址.
//buf1:M1AR地址.
//num:每次传输数据量
//width:位宽(存储器和外设,同时设置),0,8位;1,16位;2,32位;
void SAIA_RX_DMA_Init(u8* buf0,u8 *buf1,u16 num,u8 width)
{
u32 memwidth=0,perwidth=0; //外设和存储器位宽
switch(width)
{
case 0: //8位
memwidth=DMA_MDATAALIGN_BYTE;
perwidth=DMA_PDATAALIGN_BYTE;
break;
case 1: //16位
memwidth=DMA_MDATAALIGN_HALFWORD;
perwidth=DMA_PDATAALIGN_HALFWORD;
break;
case 2: //32位
memwidth=DMA_MDATAALIGN_WORD;
perwidth=DMA_PDATAALIGN_WORD;
break;
}
__HAL_RCC_DMA1_CLK_ENABLE(); //使能DMA1时钟
__HAL_LINKDMA(&SAI1B_Handler,hdmarx,SAI1_RXDMA_Handler); //将DMA与SAI联系起来
SAI1_RXDMA_Handler.Instance=DMA1_Stream6; //DMA1数据流6
SAI1_RXDMA_Handler.Init.Request=DMA_REQUEST_SAI1_B; //SAI1 Bock B
SAI1_RXDMA_Handler.Init.Direction=DMA_PERIPH_TO_MEMORY; //外设到存储器模式
SAI1_RXDMA_Handler.Init.PeriphInc=DMA_PINC_DISABLE; //外设非增量模式
SAI1_RXDMA_Handler.Init.MemInc=DMA_MINC_ENABLE; //存储器增量模式
SAI1_RXDMA_Handler.Init.PeriphDataAlignment=perwidth; //外设数据长度:16/32位
SAI1_RXDMA_Handler.Init.MemDataAlignment=memwidth; //存储器数据长度:16/32位
SAI1_RXDMA_Handler.Init.Mode=DMA_CIRCULAR; //使用循环模式
SAI1_RXDMA_Handler.Init.Priority=DMA_PRIORITY_MEDIUM; //中等优先级
SAI1_RXDMA_Handler.Init.FIFOMode=DMA_FIFOMODE_DISABLE; //不使用FIFO
SAI1_RXDMA_Handler.Init.MemBurst=DMA_MBURST_SINGLE; //存储器单次突发传输
SAI1_RXDMA_Handler.Init.PeriphBurst=DMA_PBURST_SINGLE; //外设突发单次传输
HAL_DMA_DeInit(&SAI1_RXDMA_Handler); //先清除以前的设置
HAL_DMA_Init(&SAI1_RXDMA_Handler); //初始化DMA
HAL_DMAEx_MultiBufferStart(&SAI1_RXDMA_Handler,(u32)&SAI1_Block_B->DR,(u32)buf0,(u32)buf1,num);//开启双缓冲
__HAL_DMA_DISABLE(&SAI1_RXDMA_Handler); //先关闭接收DMA
delay_us(10); //10us延时防止-O2优化出问题
__HAL_DMA_CLEAR_FLAG(&SAI1_RXDMA_Handler,DMA_FLAG_TCIF2_6); //清除DMA传输完成中断标志位
__HAL_DMA_ENABLE_IT(&SAI1_RXDMA_Handler,DMA_IT_TC); //开启传输完成中断
HAL_NVIC_SetPriority(DMA1_Stream6_IRQn,0,1); //DMA中断优先级
HAL_NVIC_EnableIRQ(DMA1_Stream6_IRQn);
}
//SAI DMA回调函数指针
void (*sai_tx_callback)(void); //TX回调函数
void (*sai_rx_callback)(void); //RX回调函数
//DMA1_Stream5中断服务函数
void DMA1_Stream5_IRQHandler(void)
{
if(__HAL_DMA_GET_FLAG(&SAI1_TXDMA_Handler,DMA_FLAG_TCIF1_5)!=RESET) //DMA传输完成
{
__HAL_DMA_CLEAR_FLAG(&SAI1_TXDMA_Handler,DMA_FLAG_TCIF1_5); //清除DMA传输完成中断标志位
if(sai_tx_callback!=NULL)sai_tx_callback(); //执行回调函数,读取数据等操作在这里面处理
SCB_CleanInvalidateDCache();//清除无效的D-Cache
}
}
//DMA1_Stream6中断服务函数
void DMA1_Stream6_IRQHandler(void)
{
if(__HAL_DMA_GET_FLAG(&SAI1_RXDMA_Handler,DMA_FLAG_TCIF2_6)!=RESET) //DMA传输完成
{
__HAL_DMA_CLEAR_FLAG(&SAI1_RXDMA_Handler,DMA_FLAG_TCIF2_6); //清除DMA传输完成中断标志位
if(sai_rx_callback!=NULL)sai_rx_callback(); //执行回调函数,读取数据等操作在这里面处理
}
}
//SAI开始播放
void SAI_Play_Start(void)
{
__HAL_DMA_ENABLE(&SAI1_TXDMA_Handler); //开启DMA TX传输
}
//关闭SAI播放
void SAI_Play_Stop(void)
{
__HAL_DMA_DISABLE(&SAI1_TXDMA_Handler); //结束播放
}
//SAI开始录音
void SAI_Rec_Start(void)
{
__HAL_DMA_ENABLE(&SAI1_RXDMA_Handler); //开启DMA RX传输
}
//关闭SAI录音
void SAI_Rec_Stop(void)
{
__HAL_DMA_DISABLE(&SAI1_RXDMA_Handler); //结束录音
}