【朝花夕拾】RT10XX SDK TDM的实现
----作者:火山
一, 文档简介
最近遇到网友在使用RT1020 SDK SAI代码实现TDM模式的时候,发现要么出现时钟问题,要么出现data数据线blocking的问题。TDM的实现主要是调用官方底层API SAI_GetTDMConfig,但是同样的情况,DMA代码工程能够成功发出TDM信号,问题是时钟不准确;终端代码工程TDM的SYNC能发成功,但是数据信号线并无数据。本文就此问题展开讨论分析,并在NXP MIMXRT1020-EVK开发板上,测试TDM, 16kHZ采样率,16bit,2通道,并提供解决方案。
二, TDM原理介绍
TDM(PCM)的波形如下:
最简单的音频接口是PCM(脉冲编码调制)接口,该接口由时钟脉冲(BCLK)、帧同步信号(FS)及接收数据(DR)和发送数据(DX)组成。在FS信号的上升沿,数据传输从MSB(Most Significant Bit)字开始,FS频率等于采样率。FS信号之后开始数据字的传输,单个的数据位按顺序进行传输,1个时钟周期传输1个数据字。发送MSB时,信号的等级首先降到最低,以避免在不同终端的接口使用不同的数据方案时造成MSB的丢失。PCM接口很容易实现,原则上能够支持任何数据方案和任何采样率,但需要每个音频通道获得一个独立的数据队列。这是因为PCM信号,一个声道采集是一个数据队列,多个数据队列在PCM格式中没法融合到一个数据队列上。不像AC97有控制tag可以区分,IIS有LRCLK来区分左右声道数据,这是有差别的。
TDM/PCM 根据SD相对于FSYNC的位置,TDM分两种基本模式:
Mode A :数据在FSYNC有效后,BCLK的第二个上升沿有效;
Mode B: 数据在FSYNC有效后,BCLK的第一个上升沿有效;
PCM:传输单声道数据,比如麦克风;
IIS:传输双声道数据,比如喇叭;
TDM:传输两个以上声道数据,同时区别于IIS特定格式。
不同厂商对于Mode A, Mode B的定义可能有所差别,实际应用中,帧同步SYNC的上升沿表示一次传输的开始。PCM帧同步模式大致分为两种:
长帧同步(Long Frame Sync)和短帧同步(Short Frame Sync)
长帧同步,SYNC脉宽等于1个slot长度。Slot在TDM中表示的是传输单个声道所占用的位数。
短帧同步,SYNC脉冲宽度等于1个BCLK周期长度
由于没有统一标准,不同厂商对SYNC脉冲宽度可能不同。
本文将以短帧同步,也就是SYNC脉冲宽度等于1个BCLK,采样率16Khz,16bit,双通道为例,讲解测试。
三, 问题解决与代码实现
3.1 坑位注意点
这里提到坑位,是因为读了RM之后,总以为SAI_TCR4[CHMOD]=0, TDM mode为TDM模式:
这里注意,TDM的配置实际上是发送数据引脚是三态,另外一个坑位就是,SAI_TX_DATA1-DATA3实际上是和RX复用引脚,如果选择了复用引脚接受数据,并且CHMOD=1,会导致数据引脚拉高电压不够高的问题,比如只有1V左右无法到3.3V,所以这种情况,需要使用CHMOD=0,选择三态去解决。
但是,我们这里的测试,并不能使用TDM,此TDM非彼TDM,小编也被坑了一把。实际上我们要配置为TDM模式,还是使用output mode.
3.2 SDK TDM配置
本文旨在实现SYNC=1clk的短帧同步,16Khz采样率,16bit通道宽度,2通道的TDM发送。
代码用了两种:DMA和interrupt
DMA:
SDK_2_13_0_EVK-MIMXRT1020\boards\evkmimxrt1020\driver_examples\sai\edma_transfer
Interrupt:
SDK_2_13_0_EVK-MIMXRT1020\boards\evkmimxrt1020\driver_examples\sai\interrupt_transfer
调用函数:
SAI_GetTDMConfig(&saiConfig,kSAI_FrameSyncLenOneBitClk,kSAI_WordWidth16bits, 2, 1);
屏蔽掉SAI_GetClassicI2SConfig的配置,也就是使用TDM代替经典I2S配置。
实际上,区别也就是配置了帧同步宽度为1个clk,字长为2,通道1(也就是TX_DATA0)。
为了便于测试查看数据,这里把发送的前面数据改为:
__ALIGN_BEGIN const uint8_t music[] __ALIGN_END =
{
0x55, 0xaa, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00, 0x07, 0x00,
…
}
3.3 硬件测试点准备
查看MIMXRT1020-EVK原理图,相关测试点如下:
实物测试图:
3.4 interrupt TDM测试
evkmimxrt1020_sai_interrupt_transfer工程测试结果:
CH1:BCLK, CH2:SYNC, CH3:DATA
可以看到,BCLK,SYNC都有了,就是数据信号没有.
那么是什么原因导致这个问题的呢?
代码分析,发现和下面两段代码有关:
这里面,如果定义了handle->bitWidth= config->frameSync.frameSyncWidth=1
则,会导致一个问题,datasize=(1/8)*handle->channelNums=0
//handle->bitWidth = config->frameSync.frameSyncWidth;
handle->bitWidth = config->serialData.dataWord0Length;
然后再次测试,波形结果如下:
3.5 DMA TDM测试
代码:evkmimxrt1020_sai_edma_transfer
这里不仅仅要修改TDM的函数调用,还要修改SAI1为主机,codec为从机,否则默认是从机,codec是主机。
#define DEMO_SAI_MASTER_SLAVE kSAI_Master
wm8960_config_t wm8960Config = {
…
.master_slave = false,
…
}
// SAI_GetClassicI2SConfig(&saiConfig, DEMO_AUDIO_BIT_WIDTH, kSAI_Stereo, 1U << DEMO_SAI_CHANNEL);
SAI_GetTDMConfig(&saiConfig, kSAI_FrameSyncLenOneBitClk, DEMO_AUDIO_BIT_WIDTH, 2, 1);
经过修改测试结果如下:
const clock_audio_pll_config_t audioPllConfig = {
.loopDivider = 30, /* PLL loop divider. Valid range for DIV_SELECT divider value: 27~54. */
.postDivider = 1, /* Divider after the PLL, should only be 1, 2, 4, 8, 16. */
.numerator = 200, /* 30 bit numerator of fractional loop divider. */
.denominator = 100, /* 30 bit denominator of fractional loop divider */
};
修改后:
const clock_audio_pll_config_t audioPllConfig = {
.loopDivider = 32, /* PLL loop divider. Valid range for DIV_SELECT divider value: 27~54. */
.postDivider = 1, /* Divider after the PLL, should only be 1, 2, 4, 8, 16. */
.numerator = 77, /* 30 bit numerator of fractional loop divider. */
.denominator = 100, /* 30 bit denominator of fractional loop divider */
};
再次测试结果:
目前的TDM波形是模式B,如果需要模式A,则:
config->frameSync.frameSyncEarly = true;
true: TDM 模式A
false:TDM 模式B
四, 总结
对于TDM的代码修改,调用:
SAI_GetTDMConfig(&saiConfig, kSAI_FrameSyncLenOneBitClk, DEMO_AUDIO_BIT_WIDTH, 2, 1);
其他中断和DMA代码分别如下:
中断代码:handle->bitWidth要选择数据宽度16bit,而不是同步宽度。
DMA代码:SAI配成主机,audio PLL时钟配置为786480000hz.
最终通过测试,可以看到生成的波形满足TDM的要求,并且能够成功发送模式A,B的波形数据。