0 1

明天就两岁了

按照妈妈家乡的习惯,小孩一出生就算一岁,过一个年就算两岁了。日本以阳历过年,所以可以说明天我就两岁了。这个月我的变化是十分显著的。就让我从头到脚做一个总结。

我的头发依旧没有增长多少,不过这倒是让爸爸妈妈方便了,因为他们不需要为我剃头而发愁。

眼睛的变化很大,也是争论最多的地方。记得刚刚出生的时候,大家都一致认为我的眼睛很大,继承了妈妈的特点。但是随着我渐渐地长胖,眼睛所占据的面积逐渐相对缩小,以至于妈妈说我的眼睛很小,像爸爸,甚至比爸爸还显得小。爸爸一直坚持我眼睛大的观点,可是终究也有些动摇了。而且我曾一度是单眼皮,让都是双眼皮的爸爸妈妈甚是疑惑。这两天我的眼睛有渐大的趋势,而且也开始出现双眼皮了,不过两只眼睛同时出现的概率还不太大。
脸上的湿疹已经好了,最近改用强生婴儿护肤产品,感觉皮肤滋润多了。

由于每天的奶水很充足,我的体重日益增加,同时也出现了许多层下巴。

我已经养成了吃手的好习惯,白天衣服太多不容易吃到手,因此晚上睡觉前,一旦棉袄一脱,我的左右手就开始排队进嘴巴了。
我的脖子已经可以自立了,听说平均大约都得3个月左右才能自立脖子,我最多只花了两个半月,这归功于我平时十分注意锻炼。每天睡觉之前我都会左右晃动脑袋N次,直到头发昏为止。

现在我已经可以十分熟练地说婴儿语了。有时我会和妈妈咿咿呀呀好久,不过她发音不太准,老得我纠正才行。有时我高兴的时候,还会笑呵呵地逗爸爸妈妈开心,他们最喜欢看我笑了,我一笑他们也跟着笑,生活就是这么开心。

今天就说到这里,我们明年再见。

满两个月了

昨天,12月4 日,我满两个月了。我现在一个月一个样儿,所以爸爸说每个月4号都要给我庆祝一下满月。所谓庆祝,实际上和我也没有太大关系,就是爸爸妈妈姑奶奶三个人饱 餐两顿(午饭和晚饭)。上次我满一个月的时候,他们吃的是火锅,爸爸说这次换个口味,自己包饺子吃。但是妈妈和姑奶奶都觉得包饺子麻烦,不能马上进嘴,于 是还是改为火锅了。
 
虽然我是一口也捞不到,不过毕竟还是给我庆祝满月,于是我也很配合,他们大吃特吃的时候,我就呼呼大睡,一点儿也没有给他们添麻烦。妈妈说我会 看火候,有眼力劲儿。这几句称赞的话儿我就笑纳了。说实话我的确给他们面子,每次家里来客人或者他们有什么活动,我都是尽量配合的。下次庆祝满月就是明年 的1月4日了,我希望他们在自己大吃大喝之余,也给我来一顿丰盛的...

C5510DSK例子程序的DMA设置问题

RY 隐藏 2005/12/27

今天研究C5510DSK所带的例子dsk_app(examples\dsk5510\bsl\dsk_app)的DMA设置,结果发现两个问题,这两个问题将在程序运行瞬间产生噪声。

先简要介绍一下dsk_app设置DMA的方法。

在DSP/BIOS Config中为DMA0设置handle名为hDmaXmt,DMA1的handle名为hDmaRcv,这两个DMA分别传输数据到McBSP和从McBSP接收数据。

这两个DMA对应的配置分别为dmaCfgTransmit和dmaCfgReceive,可在dsk_app2.cdb中的Chip Support Library -> DMA -> DMA Configuration Manager中找到。

DMA 配置为Auto init。这里稍微解释一下Auto Init的工作原理。DMA的寄存器分为配置寄存器和工作寄存器,用户设置配置寄存器,在启动DMA的时候DMA控制器将配置寄存器的内容复制到工作寄存 器中,然后按照工作寄存器的设置工作。若设置了Auto Init则当DMA传输完毕的时候,自动载入当时的配置寄存器的内容到工作寄存器,并按照其设置工作。因此Auto Init可以方便的让DMA不间断地工作,这在语音输入输出时非常有用。

声音的输入输出缓存都采用PingPong缓存,当DMA写 Ping缓存的时候,系统处理Pong缓存的数据,写Pong缓存的时候,处理Ping缓存的数据。输入DMA(hDmaRcv)一开始设置为写输入 Ping缓存(gBufferRcvPingL,gBufferRcvPingR),而输出DMA则读输出Ping缓存。由于此时输出缓存中没有实际数 据,因此在启动DMA之前应先将输出PingPong缓存清零,但是例子程序中并没有这么做,这是造成程序运行瞬间产生噪声的第一个原因。

程 序初始设置存取输入输出的Ping缓存,当DMA开始时,DMA控制器将设置读入工作寄存器,此时应该继续设置配置寄存器为存取Pong缓存,这样当输入 Ping缓存满(而输出Ping缓存空)的时候,Auto Init将自动载入存取Pong缓存的设置,从而继续存取Pong缓存。但是程序中并没有这么做,这样,程序启动时首先存取Ping缓存,Ping缓存满 了之后,载入的设置仍然是存取Ping缓存。下一次载入的仍然是Ping缓存的设置,直到第3次载入设置的时候,才会切换到Pong缓存,因此程序存取 PingPong缓存的顺序是 Ping Ping Ping Pong Ping Pong …,而正确的顺序应该是:Ping Pong Ping Pong …,即第二个Ping应当为Pong。由于第二次传输的时候仍然使用Ping缓存,而声音处理程序处理已传输完毕的缓存也是Ping,这样就造成了数据的 冲突,以至产生噪声。

具体的修改办法也很简单,把processBuffer函数中设置DMA存取Pong缓存的程序复制到main函数中启动DMA的语句之后即可:

void main(){
…
    // Start the DMA
    DMA_start(hDmaRcv);
    DMA_start(hDmaXmt);
    // 添加下面的设置,DMA传输完毕,自动载入配置寄存器时,将开始存取Pong缓存。    
    // Configure the receive channel for pong input data
    addr = ((Uint32)gBufferRcvPongL) << 1;
    DMA_RSETH(hDmaRcv, DMACDSAL, addr & 0xffff);
    DMA_RSETH(hDmaRcv, DMACDSAU, (addr >> 16) & 0xffff);
    // Configure the transmit channel for pong output data
    addr = ((Uint32)gBufferXmtPongL) << 1;    
    DMA_RSETH(hDmaXmt, DMACSSAL, addr & 0xffff);
    DMA_RSETH(hDmaXmt, DMACSSAU, (addr >> 16) & 0xffff);    
}

虽然这两个问题只会在程序启动瞬间产生噪声,作为一般的演示程序并无大碍,但是如果把它写进产品了的话,就有些麻烦了。


MP3播放器的快退问题

RY DSP开发 2005/12/22

在开发MP3播放器的DSP程序的时候,我曾经遇到过这样一个问题:播放大文件时(100M以上)快退的速度很慢,但是快进没有问题。一般MP3文件很少大于100M,但是那个MP3播放器可以录制WAVE文件,一不小心就可能录制一个几百兆的大文件出来。

个 播放器采用Compact Flash卡存储文件,最新的CF卡的大小已经达到4G,而且有一种和CF兼容的Micro Drive已经做到了6G,所以用户甚至可以录制1G以上的大文件。CF卡采用FAT16或FAT32的文件系统,在DSP的程序中有一个库专门用来存取 FAT文件系统的数据。它提供的接口函数和C语言的fread,fwrite,fseek类似。而快进快退是调用fseek函数重新定位文件指针来实现 的,所以快退的速度取决于fseek函数往回拨文件指针的速度。

我们知道FAT文件系统的用文件分区表(FAT)来保存文件在磁盘上的存储 位置的信息的。每个文件对应一条在文件分区表中的链表。由于这个链表是单向的,所以只能从前往后遍历它,用fseek快进的时候,从链表的当前位置往后搜 索就可以了。但是若要从当前位置往前跳(快退),由于是单向链表,无法从当前位置往前搜索,因此只能从链表的头开始数起,一直数到指定的位置。当文件很 大,而且当前位置已经很靠后的时候,每快退一次,都要从链表头开始遍历几乎整个链表。链表储存在FAT中,而FAT在CF卡上,因此每次读取链表都需要访 问CF卡,这样做速度当然会很慢。

DSP的内存很小,也无法把整个链表读入内存中,因此我想了一个折中的办法:每隔50M保存一个链表的位 置,这样往回跳的时候就不需要从链表头开始搜索,而只需要从最近的一个保存点开始就行了。最后我让程序动态地决定两个保存点之间的距离,每个文件最多保存 20个点(需要20个long)。一开始每两个保存点的间隔为1M,当文件播放到21M需要保存第21个点的时候,就把前面的20个点压缩成10个,这样 就变为每2M保存一个点了。随着文件的播放,两个保存点之间的长度成倍地增加。

为了让已经写好的播放程序不经修改就能直接使用上面这些功能,我修改了CF卡的库函数fseek和fread,而用来保存那20个点的数组也直接放到文件结构体中(相当于C语言的FILE结构)。这样做,对于调用库函数的应用程序来说,就完全透明了。

调试正在的运行的程序

RY DSP开发 2005/12/22

昨天遇到一个比较棘手的调试问题。一台5416芯片的电压监视仪器,以不到百分之一的概率会在开机后工作不正常。通常这种问题多半是硬件故障导致的,但是为了确定是何处的硬件故障,必须在不正常运转时能够调试程序。

我们知道一般调试DSP程序的时候,都是采用JTAG直接把out文件下载到DSP上,然后设置断点运行。现在的问题是电压监视仪器的程序从flash加载,而且必须开关电源很多次才有可能出现故障。这就必须在DSP工作不正常之后,连接JTAG并启动CCS,并且继续运行CCS启动时中断了的程序。

下面以我调试5416芯片为例,简单介绍一下调试步骤:

  1. 修改GEL文件。GEL文件是CCS的批处理程序,它可以命令CCS经由JTAG对DSP进行操作。由于缺省情况下启动CCS会RESET DSP,这样就无法对DSP上正在运行的程序进行调试了。连接JTAG,打开硬件电源,启动CCS。在Files树中,最上面一个就是GEL files,里面有对应芯片的GEL文件,我的文件名为c5416.gel。找到C5416_Init函数,把里面的寄存器设置语句都注释掉。例如:GEL_Reset();PMST = PMST_VAL;等等。然后关闭CCS。
  2. 开关电源N次,一直到问题出现。
  3. 启动CCS,打开flash中程序对应的工程,选择CCS的File->Load Symbols->Load Symbols Only。找到flash中的程序所对应的out文件。现在不加载out文件,而只是让CCS加载out文件中的符号,便于调试。
  4. 查看DSP寄存器,若XPC的值不为0,则修改PC寄存器的值,让其高位等于XPC。例如PC=0xABCD,XPC=0x2,则修改PC为0x2ABCD。(PC寄存器本为16位的,它和XPC一起构成完整的程序地址指针,可能是由于CCS的问题,载入符号之后,并没有显示XPC + PC地址的程序,因此设置一次PC寄存器强制其显示)
  5. 按F5继续运行程序。剩下的调试就和从JTAG加载程序一样了。
经过调试,我发现数据从扩展内存中读出之后出现了错误,因此我要用CCS查看一下扩展内存,于是我在GEL文件中添加了一些GEL_MapAdd。有了对应地址的GEL_MapAdd,CCS才能查看其内容。我发现程序暂停的状态下,每次刷新读取扩展内存中的数据所得到的结果不一样,于是初步确定问题出在扩展内存。为了进一步地确认,我写了个小程序校样扩展内存中的数据,果然发现其工作不正常。

软件调试到此结束,问题转手给硬件担当者。


用电脑实时处理声音

RY DSP开发 2005/12/20

在DSP芯片上写处理声音的程序之前,我一般都先在电脑上演示一下。下面总结一下我在电脑上实现实时声音输入输出的几个方法。

用windows的mmsystem库是最直接的办法,可参见http://www.borg.com/~jglatt/tech/lowaud.htm , 有WaveIn和WaveOut的源程序下载。WaveIn把输入的声音写入WAV文件中。程序使用的是类似于DSP上的PingPong缓存和中断(回 调函数)的方法,所以可以很简单地把两个程序合二为一,来进行实时地声音处理。由于Windows本身并不是实时操作系统,因此输入和输出之间的延时较 大。

使用DirectSound,DirectSound的例子在MSDN中就有不少。DirectSound的延时比mmsystem小,但使用起来也比较麻烦。
使用ASIO声卡,至于什么是ASIO声卡,上网查一下就知道了。ASIO声卡的延时很小,如果用RtAudio库来开发的话,程序也非常简单。有关RtAudio库的信息请访问http://www.music.mcgill.ca/~gary/rtaudio/ , RtAudio不仅支持ASIO和DirectSound,而且它还是跨平台的,支持Linux平台上的ALSA、JACK和OSS(这些是什么我不清楚)。

我 用mmsystem和RtAudio都成功地实现了声音的输入输出,由于电脑的速度比DSP快很多,许多算法无需任何优化,直接使用浮点运算就可以在电脑 上听到了实时处理时的效果,给调试算法带来了很大的方便。我的笔记本上有个ASIO声卡,当我用RtAudio的ASIO模式时,延迟非常小,同样的延时 设定(声音缓存的大小)改为DirectSound的话,声音就断断续续了。


调音器

RY DSP开发 2005/12/16

本文介绍高精度调音器的设计原理。

调音器的功能

调音器能够对输入的声音信号进行分析,找到输入声音的音调。在乐器调音中非常有用。

基本设计思路

调音器的原理并不复杂,对输入信号进行FFT变换,找到频谱中能量最大的频率,然后把频率转换成音调即可。
例如,若输入信号的取样频率为44100Hz,FFT的长度为1024,FFT变换之后能量最大的位置为100的话,那么输入信号的频率就是44100/1024*100=4307Hz。

频率转音调

一般把频率440Hz定为标准音A4。频率增加一倍,音阶增加一。因此880Hz为A5,而220Hz为A3。从A4到A5要经历如下12个音:

A4,A4#,B4,C5,C5#,D5,D5#,E5,F5,F5#,G5,G5#

这12个音的频率组成一个等比数列,因此可以求出每两个音之间的频率比是2^(1/12)= 1.0594630。所以A4#的频率为:440*1.0594630=466.164Hz。
为了提高调音器的精度,在每两个相邻的音之间再插入8个中间音,以A4,A4#为例,用如下形式表示:

A4, A4 +1, A4 +2, A4 +3, A4 +4, A4# -4, A4# -3,A4# -2, A4# -1,A4#

这样每个小刻度提高的频率倍数为:1.05946300^(1/9)= 1.006439。我们可以利用这个最小刻度比来计算出音调和频率的对应表。
若调音器程序能找出输入信号的频率,再通过这张表就可以查得相应的音调了。由于在每两个音调中插入了8个中间值,所以调音器可以很准确地显示出输入声音与标准音调之间的细微差别,以便于用户对乐器进行手工调整。

精度

假 设调音器最低能识别A1,也就是55Hz,那么 A1 +1的频率为1.006439*55=55.354Hz。由等比数列的性质,很容易知道A1 +1和A1之间的频率差0.354Hz是最小的频率差,因此所需的频率精度就是0.354Hz/2=0.18Hz。也就是说为了分辨我们设定的音调的最小 单位,频率计算程序的精度必须达到0.18Hz。

FFT的最小分辨频率可以由下面的公式求得
最小分辨频率=取样频率/FFT长度
这就是说输入信号的取样频率为44100Hz时,若要最小分辨频率为0.18Hz的话,那么所需的FFT长度是44100/0.18=245000。而收集这245000个数据需要245000/44100=5.6秒。显然FFT的运算长度和数据收集时间都是不实际的。
下面介绍两个方法来解决这个问题。

降低取样频率

为 了缩短FFT的运算长度,而保持最小分辨频率的精度,只能降低取样频率。假设我们固定FFT长度为4096,为了使精度达到0.18Hz,取样频率应为 4096*0.18=737Hz。即对于44100Hz的输入数据,每60个数据取一个数据的话,就可以用4096的FFT长度来实现0.18Hz的精 度。但香农定理告诉我们,取样频率必须大于输入信号最高频率的两倍,才能够从取样后的信号还原为原信号。如果取样频率是737Hz,那么输入的声音信号的 最高频率就不能超过369Hz。而显然调音器的输入有可能超过这个频率。
解决这个问题的办法就是根据输入信号的频率,动态地决定取样频率。 0.18Hz的精度是按照最低音A1计算的。而当输入频率提高n倍时,所需的最小分辨频率也将增大n倍。例如,如果要区分A4和A4 +1的话,最小分辨频率就是0.18Hz*440/55=1.44Hz,那么设定取样频率为5898Hz即可,也就是每8个数据取一个(44100/8= 5512Hz)。
为了能够动态地确定取样频率,可以先用原取样频率(44100Hz)对输入信号进行粗略地分析。这时的最小分辨频率为44100/4096=10.77Hz。粗略地找到了输入信号的频率之后,根据这个频率调低取样频率,再进行精确计算。

补零

通 过动态地降低取样频率可以缩短FFT的运算长度,但不能解决数据收集时间过长的问题。虽然我们要进行4096点FFT运算,但这并不表明一定要收集 4096个数据。如果只收集512个数据,其余的数据全部填0的话,那么数据收集时间就缩短为原来的1/8。而这并不会降低所得的频率的精度。(当收集的 数据过少的时候,会出现误差。只要收集的数据中有足够多的信号周期,则不会出现频率误差)

处理倍音

大多数乐器所发出的声音都包含有倍音,而且有时倍音的能量可能超过基础音,尤其是频率较低的时候。

处理倍音比较麻烦,大概步骤是
先找出所有的局部最大能量的频率,即局部峰值对应的频率,这些频率很可能是倍音频率。
找出其中成倍的频率,剔除一些不相干的噪音频率。
从一系列倍频推算出基础频率。
另外在确定取样频率的时候,需要考虑倍频的存在,所以取样频率不能仅靠最大能量的频率计算,而需要考虑最大的倍音的频率。这样才不会出现取样频率过低而导致频域重叠。

Block Floating Point

RY DSP开发 2005/12/16

在测试16位定点FFT和IFFT运算的时候,我发现数据经过FFT和IFFT之后,将产生很大的噪声,原因请参见我以前的文章:定点FFT注意事项。

那 么有没有办法在只支持整数运算的芯片上实现高精度FFT呢。我在Google上搜索了一下,没有找到很完美的解决方案。不过倒是找到了一篇文章,它介绍了 在TMS320C54x DSP上用Block Floating Point可以实现比定点小数更高精度的FFT运算。

Block Floating Point介于Fixed point和floating point之间,它有指数位,但是是很多数共享一个指数,所以称之为block(块)。

使用Block Floating Point运算的FFT的精度较16位定点FFT有所提高,但是仍然比32位定点FFT低,但是其运算速度却也介于两者之间,而且我怀疑它经过IFFT之后是否能还原为原来的数据。
C55x系列的Block Floating Point FFT也可以在TI的站点上找到,下面给出一个链接:
http://focus.ti.com/dsp/docs/dspsupporttechdocsc.tsp?sectionId=3&tabId=409&abstractName=spra948

还可以下载到源程序,等有时间我测试一下到底精度比16位定点FFT提高多少。


简单混响效果

RY DSP开发 2005/12/15

混响效果的算法并不是太复杂,但是由于它需要大量的内存,在DSP上实现的时候只能靠扩展内存来实现。扩展内存的存取速度比DSP内部内存的速度慢很多,因此每个取样值需要多少次内存存取很大程度上影响混响算法的MIPS。

这里介绍一种简单的混响效果,适用于MIPS比较紧张的情况。本混响效果由三个全通滤波器(all pass filter)和一个低通滤波器组成。三个全通滤波器串连产生多重回声,然后通过低通滤波器滤掉回声中的高频噪声。全通滤波器的传递函数公式如下:

H(Z) = Y(Z)/X(Z) = (-k + Z^(-m)) / ( 1 – k*Z^(-m))

其中m为回声延时取样数,k为反馈系数。
用直接1型表示这个传递函数则为:

y(n)=-k*x(n) + x(n-m) + k*y(n-m)

可见其实际上是一个简单的IIR滤波器,时间n的输出由时间n的输入和m点之前的输入与输出计算而得。由于这个IIR滤波器的频率响应为水平直线,所以被称作全通滤波器。

每计算一个输出需要从内存中读取x(n-m)和y(n-m),并把y(n)和x(n)保存进内存。因此需要四次内存存取操作。双声道输入,每个声道通过三个 这样的滤波器,则计算一个取样点的输出需要 2*3*4 = 24次存取操作。若采用频率为200MHz的C5510进行计算,扩展内存的存取时间为70ns,取样频率为44100Hz的话,则计算一秒的数据需要占 用7.4%,14.8MIPS以上的CPU时间。扩展内存速度越慢,需要的时间越长。

仔细研究一下,可以把全通滤波器写成如下公式:

w(n) = x(n) - k*w(n-m)
y(n) = w(n-m) + k*w(n-m)

请读者自行验证之。如果使用这个公式的话,那么内存中需要保存的就只是w(n)了,每次计算存取2次,比较上面直接计算性能提高一倍。再加上微不足道的加法 乘法运算,可以在大约8MIPS左右搞定回响中的全通滤波器部分。而低通滤波器如果使用16位二次IIR滤波器的话,计算量不会超过2MIPS,这样整个 回响效果在10MIPS之内实现。


优化扩展内存存取

RY DSP开发 2005/12/14

今天在把回响程序的运算从16位升级到32位时发现,MIPS从40上升到50左右。本来我想整个程序的处理量全部增加一倍,MIPS应该达到80 左右的。由于回响算法需要大量使用内存存储声音,因此只能使用扩展内存,而扩展内存的存取速度比内部内存要慢很多,因此回响程序大部分的时间都用在了存取 扩展内存上。若数据从16位升级到32位,那么扩展内存的存取量也增加一倍,可是为什么MIPS没有增加一倍呢?

我仔细看了一下硬件设计图,原来DSP芯片和扩展内存之间的数据线是32位的,因此一次存取16位数据或者32位数据所需要的时间都是一样的。所以即使升级到32位,存取扩展内存所需要的时间也不会增加。那增加的10MIPS主要是运算部分。

既然存取32位和16位所需要的时间相同,那么16位的回响程序如果也使用32位存取的话,速度应当有相当大的提高。于是我就动手修改了一下16位回响程序,结果MIPS一下子降低到28,真让人激动呀。

回 响算法中左右声道的延时缓存的大小不一样,因此一次32位存取不能是左右声道的取样数据,我修改为一次存取某个声道的两个取样数据,为了让程序不至于太过 复杂,只好规定所有延时缓存的大小是偶数个取样数据,也就是偶数个short型整数。这样输出结果就不能和原来的程序一模一样,但是听上去没有区别。如果 将来功能增加,MIPS不够的话,就可以用这个抵挡一下了。

0 1