【朝花夕拾】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)的波形如下:
在这里插入图片描述

图1 TDM基本波形

最简单的音频接口是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的第一个上升沿有效;
在这里插入图片描述

图2 TDM 模式A

在这里插入图片描述

图3 TDM 模式B

PCM:传输单声道数据,比如麦克风;
IIS:传输双声道数据,比如喇叭;
TDM:传输两个以上声道数据,同时区别于IIS特定格式。
不同厂商对于Mode A, Mode B的定义可能有所差别,实际应用中,帧同步SYNC的上升沿表示一次传输的开始。PCM帧同步模式大致分为两种:
长帧同步(Long Frame Sync)和短帧同步(Short Frame Sync)
长帧同步,SYNC脉宽等于1个slot长度。Slot在TDM中表示的是传输单个声道所占用的位数。
在这里插入图片描述

图 4

短帧同步,SYNC脉冲宽度等于1个BCLK周期长度
由于没有统一标准,不同厂商对SYNC脉冲宽度可能不同。
本文将以短帧同步,也就是SYNC脉冲宽度等于1个BCLK,采样率16Khz,16bit,双通道为例,讲解测试。

三, 问题解决与代码实现

3.1 坑位注意点

这里提到坑位,是因为读了RM之后,总以为SAI_TCR4[CHMOD]=0, TDM mode为TDM模式:
在这里插入图片描述

图 5

这里注意,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原理图,相关测试点如下:
在这里插入图片描述

图 6

实物测试图:
在这里插入图片描述

图 7

3.4 interrupt TDM测试

evkmimxrt1020_sai_interrupt_transfer工程测试结果:

在这里插入图片描述

图 8

CH1:BCLK, CH2:SYNC, CH3:DATA
可以看到,BCLK,SYNC都有了,就是数据信号没有.
那么是什么原因导致这个问题的呢?
代码分析,发现和下面两段代码有关:
在这里插入图片描述

图 9

在这里插入图片描述

图 10

这里面,如果定义了handle->bitWidth= config->frameSync.frameSyncWidth=1
则,会导致一个问题,datasize=(1/8)*handle->channelNums=0
在这里插入图片描述

图 11
所以这个肯定是不对的,我们需要发送单通道为16bit,这样datasize将会是2字节,也就是一个通道的大小。 所以需要改图10代码如下:
//handle->bitWidth = config->frameSync.frameSyncWidth;
    handle->bitWidth = config->serialData.dataWord0Length;

然后再次测试,波形结果如下:
在这里插入图片描述

图 12
可以看到数据已经能够成功发送,并且SYNC的同步宽度也是1个CLK。 检查了BCLK也是512Khz,SYNC的频率是16Khz,所以经过修改满足要求。

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);

经过修改测试结果如下:
在这里插入图片描述

图 13

在这里插入图片描述

图 14
如果这里只是查看数据,那么是没有问题的。 但是仔细查看频率,发现频率并不准确,BCLK,SYNC都不准,然后通过调试,发现MCLK就不准,需要786480000hz,但是实际上为768000000hz,所以从audio PLL那边配置就错误了,这里需要修改PLL4如下: 原来代码:
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 */
};

再次测试结果:
在这里插入图片描述

图 15
可以看到,BCLK 512Khz,SYNC 16Khz,已经完全正确。 那么时钟的计算情况是怎样的呢? 24Mhz->PLL4->MCLK->BCLK的计算情况如下: PLL4即Audio PLL=24Mhz*(32+77/100)=786.48Khz MCLK= (CLOCK_GetFreq(kCLOCK_AudioPllClk) / (DEMO_SAI1_CLOCK_SOURCE_DIVIDER + 1U) / (DEMO_SAI1_CLOCK_SOURCE_PRE_DIVIDER + 1U)) =786.48Khz/(15+1)/(3+1)=12.28875Mhz BCLK=MCLK/(DIV+1)*2=16Khz*16bit*2ch=512Khz=》DIV=11

目前的TDM波形是模式B,如果需要模式A,则:
config->frameSync.frameSyncEarly = true;
true: TDM 模式A
false:TDM 模式B

在这里插入图片描述

图 16
下面给出逻辑分析仪,分别对于TDM 模式A,B的配置情况:

在这里插入图片描述

图 17

在这里插入图片描述

图 18
经过修改,中断和DMA方式的TDM都已经可以工作。

四, 总结

对于TDM的代码修改,调用:
SAI_GetTDMConfig(&saiConfig, kSAI_FrameSyncLenOneBitClk, DEMO_AUDIO_BIT_WIDTH, 2, 1);
其他中断和DMA代码分别如下:
中断代码:handle->bitWidth要选择数据宽度16bit,而不是同步宽度。
DMA代码:SAI配成主机,audio PLL时钟配置为786480000hz.
最终通过测试,可以看到生成的波形满足TDM的要求,并且能够成功发送模式A,B的波形数据。

  • 11
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值