s0?'mC+p 翻译:shepherd(
zqw100@163.com)
4@-tT;$ 原作信息:
y5j:+2|I 题目:Programming the SoundBlaster 16
DSP OOSf<I*> 作者:by Ethan Brodsky (Version 3.1)
_C/|<Ot: 联系方式:
ericbrodsky@psl.wisc.edu &IUA[{o~e 写作日期:2/10/95
kuH%aM<R 免责声明
gLv+L]BnhH 本免责声明正文如下:
jum"T\ 笔者声明,由于利用或误用本
资料,最终导致的任何经济损失,或必然的/偶然的/其他形式的损失,笔者将不承担任何责任。本文可以被免费发放,但是在发放时请完整地保留本免责声明。
]AY 4bm SB16简介
TRi# 16位声霸卡(Sound Blaster 16,本文简写为SB16)可以处理FM(Frequency Modulation:频率调幅)和数字声音信号。其中数字信号的处理范围是:从8位5000HZ单声道,到16位44000HZ立体声(译者注:另外一种说法是到48000HZ)。这份常见问题文档,是关于SB16 DSP CT1341芯片数字音频信号的录制和回放的。理所当然的,有关更早声霸卡的编程知识必不可少。
ZwMVFC-d 译者补充的背景知识:
kS-BB[T FM合成
技术 5?>4I"ne 它是运用特定的算法来简单
模拟真实乐器声音。 其主要特点是
电路简单、生产成本低,不需要大容量存储器支持即可模拟出多种声音。由于 FM是靠算法来合成某个声音,因此实现方法过于生硬、效果单一,所生成的声音与真实乐器产生的声音距离很大。很容易让人听出来是“
电子音乐”。
uQO5GDuK> DSP
}gv'r
"; DSP(Digital Signal Processor,数字信号处理器)是一种内含微处理器的专用芯片,它为当时的高档16位声卡实现180°环绕立体声再现立下了汗马功劳。
#H~55 ))F SB16的I/O端口
.jQx2O SB16的DSP芯片的可编程I/O服务端口地址,其基址是由主板跳线决定的(译者注:现在一般通过BIOS或者应用程序实现)。在SB16芯片中,有16个I/O端口被用作FM合成音乐,音响混合,DSP编程和CD-ROM访问。而下面列出的五个端口被用做DSP编程:
s9wzN6re 2x6h - DSP 复位
FFw(`[A_ 2xAh - DSP 读
e#;43=/Ia 2xCh - DSP 写(命令/数据),DSP 写缓冲区状态字(第7位)
K:U=Y$ x 2xEh - DSP 读缓冲区状态字(第7位), DSP
中断应答
NRx 7S9W 2xFh - DSP 16位中断应答
;
pBLmm*F 译者补充背景资料:
XE2Un1i}j1 端口中的X为基址,可以取1-6,通常情况下取2,即基址是220h。该基址可以由用户在相关配置文件中指定,也可以在程序中自己检测。
|Gz<I DSP复位
F `:Q 在进行DSP编程之前你必须将DSP复位。复位DSP需要按照以下步骤进行:
QfEJU8/5d 1,在复位端口(2X6)写入1
j_rO_m <8 2,等待3毫秒
`C>h]H( 3,在复位端口(2X6)写入0
SdlO]y9E 4,检测读缓冲区端口(2XE)状态,直到第7位为1。
Wmd@%K 5,检测读数据端口(2XA)状态,直到接受到AA。
R9A:"sJ DSP自身初始化通常需要大约100毫秒。经过这段时间以后如果返回的值仍然不是AA,或者没有任何数据返回,说明SB16卡未被安装,或者I/O地址不正确。
@JlT*:Dz 译者补充知识:
uY~mi9E 这里所说的第7位,是从第0位开始算的。整个复位过程,C语言的参考代码如下:
t[!,puZc# int RestDSP(int Test)
l5w^rj {
Lmjd,t /* 重置DSP */
J8~hIy6] outportb(Test + 0x6, 1);
j4i$2ZT' delay(3);
F4\:9ws outportb(Test + 0x6, 0);
'QE8 delay(80); /* 延时时间可以酌情调整 */
)2).kL> /* 如果重置成功则检查 */
LkJq Bg if ((inportb(Test + 0xE) & 0x80 == 0x80)
TYuP
EVEXZ && (inportb(Test + 0xA) == 0xAA))
_(f@b1O~ {
l^R:W#*+U G_base = Test;
O;VqrO return 1;
8x1!15Wiz }
@].s^ss9_ else
uO1^Q;F {
\)28,` return 0;
3)VO{Cj! }
c= 2E/x? }
9'p| [?]v 写DSP
+jrx;xwot 向SB16写一个字节,需要按照以下步骤进行:
`P\H{ 1,读写缓冲区状态端口2XC直到第7位被清除
R~oY
R,L; 2,将数值写入写端口2XC
eJqx,W5MK] 读DSP
TQeIAy 由SB16读一个字节,需要按照以下步骤进行:
MMa`}wSs 1,读读缓冲区状态端口2XE直到第7位被设置
O8hx}dOjA 2,从读端口2XA读出一个字节
\KJTR0EB:> DMA控制器编程
*><j(uz! DMA(Direct Memory Access,直接内存读取)控制器掌管着I/O设备和内存之间的数据传输,整个过程不需要CPU参与。一个INTEL 8237 DMAC集成电路被用来控制它,而一个IBM兼容机有两个DMA控制器:一个掌管8位另外一个掌管16位。同外部页面寄存器配对的DMA控制器,可以传输大于64KB的数据块。下面是有关I/O端口和必要的关于声卡寄存器的设置:
fQ~~%#z1 *DMA地址和
计数寄存器的I/O端口地址
lg-`zV3 |====================================|
# d"M(nt | 控制器 | I/O 地址 | 功能 |
{!(
htg; |====================================|
!(bYh`Uy | DMA 1 | 00 | 通道0地址 |
C|H`.|Q | 8位 | 01 | 通道0计数 |
KUX6n(u | 从 | 02 | 通道1地址 |
@B{ | | 03 | 通道1计数 |
5Zc | | 04 | 通道2地址 |
Y]R=z*i% | | 05 | 通道2计数 |
,FYA*}[ | | 06 | 通道3地址 |
UV%o&tv|< | | 07 | 通道3计数 |
5D3&E_S |====================================|
q:>`|~MX | DMA 2 | C0 | 通道4地址 |
)`k+Oyvi< | 16位 | C2 | 通道4计数 |
~+ae68{p | 主 | C4 | 通道5地址 |
q:vN3#=^qf | | C6 | 通道5计数 |
fc:87ZR{K | | C8 | 通道6地址 |
6/QWzw.0c | | CA | 通道6计数 |
w2 (}pz: | | CC | 通道7地址 |
.nr%c*JUp | | CE | 通道7计数 |
?>=vKU5 |====================================|
,-d2wzhW *控制寄存器的I/O端口地址
2hntQ1[ |========================================|
5FJ%"5n& | 地址 | 操作 | 功能 |
L) _ VdB |DMAC1 DMAC2 | | |
T8LvdzS |========================================|
lh0G/8+C | 0A D4 | 写 |写单一掩码寄存器 |
Rp
zuSh | 0B D6 | 写 |写模式寄存器 |
M9Z9s11{H | 0C D8 | 写 |清除翻转字节指示器 |
,9:v2=C_ |========================================|
<6N3()A)%1 *页寄存器的I/O端口地址
UGOe(JB |===============================|
UT_t]m | 地址 | 功能 |
UWCm:eRQ |===============================|
GYT0zMMf | 81 | 8位 DMA 通道 2 页面 |
Nde1`W]: | 82 | 8位 DMA 通道 3 页面 |
' z^v}~ | 83 | 8位 DMA 通道 1 页面 |
qk&BCkPT | 87 | 8位 DMA 通道 0 页面 |
VF-[O | 89 | 16位 DMA 通道 6 页面 |
y(Pv1=e | 8A | 16位 DMA 通道 7 页面 |
W70BRXe04D | 8B | 16位 DMA 通道 5 页面 |
&@&^k$du8q |===============================|
w `M/0.)V *模式寄存器的各位含义
$iy(+} |===============================|
\<=.J`o{ | 位/值 |功能 |
FP6JfI8 |===============================|
>vfLlYx |Bits 7:6 |状态选择 |
G~lnX^46" | 00 | 查询模式 |
A.P*@}9 | 01 | 单一模式 |
K!88 Nox( | 10 | 块模式 |
KC\W6|NtGj | 11 | 级联模式 |
\ ]h$8JwV |===============================|
|R Qa.^. | Bit 5 |地址增加/减少位 |
zt
)WX9 | 1 | 地址减少 |
_ZuI x=! | 0 | 地址增加 |
p_sqw~)^% |===============================|
xO
1uHaL | Bit 4 |自动初始化设置位 |
6nk.q|n:g | 1 | 自动初始化DMA |
R<>uCF0 | 0 | 单一周期DMA |
41XXL$ |===============================|
?7*J4. |Bits 3:2 |位传输 |
apm,$Vvjy | 00 | 验证 |
TkjZI}]2 | 01 | 写(到内存) |
Of$gs- | 10 | 读(从内存) |
@v\jL+B+m | 11 | 非法 |
#fe zUU | 忽略 | 如果bits 7:6 = 11 |
~!dO2\X+ |===============================|
dC}4Er |Bits 1:0 |通道选择 |
Fc"+L+h@W | 00 | 通道 0 (4) |
y{qKb:~wv | 01 | 通道 1 (5) |
ViG-tb | 10 | 通道 2 (6) |
}l@7t&T| | 11 | 通道 3 (7) |
FE?^}VH |===============================|
EG!):P DMAC2用于16位I/O,DMAC1用于8位I/O。开始数据传送的过程很复杂,所以对于使用I/O的DMA传输方式,我将列出详细步骤。
cNuBWLG 1)计算你的内存缓冲区的绝对线性地址
)0@&pEObm LinearAddr := Seg(Ptr^)*16 + Ofs(Ptr^));
}D#[yE,=\ 2)设置适当的掩码位,从而禁用对应声卡的DMA通道
K}Pi"Le@W Port[MaskPort] := 1 + (Channel mod 4);
}KL( -Ui$ 3)清除翻转字节指示器
[IuF0$w=dj Port[ClrBytePtr] := AnyValue;
8J#TP7; 4)定义DMA传输模式
cX-)]D 查询模式下,模式选择位应该被置为00,地址增加/减少位应置0。有关自动初始化模式位的恰当设定,我将在稍后讨论。回放模式下,传输位应置为10;而录音模式应置为01。通道选择位应该依照声卡的实际DMA通道设置。这里要注意,“读”意味着从内存读到声卡,“写”意味着从声卡写到系统内存。
Q\v^3u2;m` Port[ModePort] := Mode + (Channel mod 4);
c:z<8#A} 经
常用到的方式如下:
*}`D2_uP 48h+通道号 - 单一周期回放
[U?a %$G> 58h+通道号 - 自动初始化回放
Ja6PX P]' 44h+通道号 - 单一周期录制
'WQ<|(:{ 54h+通道号 - 自动初始化录制
$t$YdleIH 5)写入内存缓冲区的偏移地址,低字节在前,高字节在后。对于16位数据,这个偏移地址应该是WORDS类型,从128KB的页面其实处算起。最早的计算16位参数的方法是:在计算偏移量之前讲线性地址除以2。
c`G~.paY| if SixteenBit
syLpnNx= then
* d[sja+ begin
lilF _y BufOffset := (LinearAddr div 2) mod 65536;
qc`UDD5 Port[BaseAddrPort] := Lo(BufOffset);
}>u<, Port[BaseAddrPort] := Hi(BufOffset);
.1& F p end
e$@a zi1 else
mq~L1<f begin
5;-?qcb^w BufOffset := LinearAddr mod 65536;
CpF&Vy K Port[BaseAddrPort] := Lo(BufOffset);
|yow(2(F@ Port[BaseAddrPort] := Hi(BufOffset);
.9;wJ9Bw[ end;
at `\7YfQp 6)写入传输长度,低字节在前,高字节在后。对于8位传输,写入总字节数-1;对于16位传输,写入WORDS-1。
wNm~H Port[CountPort] := Lo(TransferLength-1);
Tn8GLn Port[CountPort] := Hi(TransferLength-1);
,=kQJ| 7)向DMA页面寄存器写入内存缓冲区页码
.FXn=4l'vV Port[PagePort] := LinearAddr div 65536;
-%x9^oQwY 8)清除适当的掩码位,从而允许声卡DMA通道
WH^rM`9 Port[MaskPort] := DMAChannel mod 4;
L>EC^2\ 设置采样频率
%@Ty,d:;= 同早期的声霸卡不同,SB16使用实际采样频率代替了时间片。在SB16上采用41H和42H的DSP命令来设置采样频率。41H用来设置输出,而42H用来设置输入。我听说在SB16上这两个命令其实做了同样的事情,但是我建议为了同未来的声卡兼容,最好采用指定的指令。设置采样频率的步骤如下:
*6e 5T 1)写入命令字,输出写入41H,输入写入42H
\;smH;m 2)写入采样频率的高字节,例如22050HZ则写入56H
+b]+5! 3)写入采样频率的低字节,例如22050HZ责写入22H
*aF<#m v 数字音频I/O
(GdL(H#IL 为了记录或者播放音频,你应该采用以下的步骤:
6-@n$5W0 1)申请一块不大于64KB的物理内存空间
C7[CfcPA 2)安装一个中断服务程序
)FrXD3p 3)设置DMA控制器为后台传输模式
%v(\;&@ 4)设置采样频率
&<sN(;%0R 5)向DSP写入I/O端口命令
"xV9$m> 6)写DSP写入I/O传输模式
&t\KKsUtd 7)向DSP写入块大小(低/高字节)
M _z-~G 使用DMA的单一周期模式时,在中断中你需要:
+wwK#ocw 1)将DMA控制器设置为下一个数据块
i`1QR@11 2)将DSP指向下一个数据块
`<L6Q2Y>j 3)如果有双缓冲,则复制下一个数据块
lU<n Wf 4)通过读端口对SB进行中断应答,8位是端口2XEH,16位是端口2XFH
{s=$.Kg
5)应答中断完毕,向20H端口写入20H,如果声卡使用的是IRQ8-15,那么你还要向A0H写入20H
L@{5:#- 译者补充背景知识:
QDC]g.x 20H和A0H分别为主片和从片的中断控制器入口地址。IRQ 0-7对应主片,中断号为08H-0FH,;IRQ 8-15对应从片,中断号为70H-7FH。通常情况下IRQ0为定时器中断,IRQ1为键盘中断,IRQ5为声卡中断,RIQ6为键盘中断。用户的可编程中断为IRQ 10,11,12,15,都在从片上,如果使用的话则必须先打开主片的IRQ2。
f`jRLo*L DSP命令
? h$>7| D0-暂停由CXH命令引起的8位DMA模式数字音频初始化。可以应用在单一周期回放和自动初始化模式下。
vO)nqtw D0-继续由D0H命令引起的8位DMA模式数字音频暂停。可以应用在单一周期回放和自动初始化模式下。
3' WS6B+ D5-暂停由BXH命令引起的16位DMA模式数字音频初始化。可以应用在单一周期回放和自动初始化模式下。
H[{ch t
h D6-继续由D5H命令引起的16位DMA模式数字音频暂停。可以应用在单一周期回放和自动初始化模式下。
@"m?
# D9-在本数据块操作之后,退出16位自动初始化模式的数字音频I/O操作。
=y/VrF.bV DA-在本数据块操作之后,退出8位自动初始化模式的数字音频I/O操作。
p&L`C|0 E1-获得DSP版本号。在送出这个命令之后,DSP将会返回两字节的内容。第一个字节是主版本号,第二个字节是副版本号。一个SB16的DSP版本号应该大于等于4.00,在使用SB16专用命令前需要检查版本号。
5[|MO.CB$ BX-数字音频I/O的16位DMA模式
U9KnW]O%" 命令序列:命令,方式,低字节(长度-1),高字节(长度-1)
5"[Qs|VjA6 命令格式:|=======================================|
l }?'U |D7 |D6 |D5 |D4 | D3 | D2 | D1 |D0 |
Q
b5AQf30 |=======================================|
*}\!&Zk" | 1 | 0 | 1 | 1 | A/D | A/I |FIFO | 0 |
ba 3_55] |=======================================|
uL!{xuN |0=D/A |0=SC |0=off |
vTD`Ja#h |1=A/D |1=AI |1=on |
.s_wP |===================|
u=I>DEe@c 常用命令:
L`ZH.fN B8 - 16位单一周期输入
3H%oTgWk B0 - 16位单一周期输出
g|PVOY+|^ BE - 16位自动初始化输入
~mtL\!vaM B6 - 16位自动初始化输出
xOjCF&W 模式:|=============================================|
R0M(e@H~ |D7 |D6 | D5 | D4 |D3 |D2 |D1 |D0 |
_AQ :<0/# |=============================================|
h!f7/)|[o | 0 | 0 | Stereo | Signed | 0 | 0 | 0 | 0 |
:_tsS)Q2m |=============================================|
5vL]Y)l |0=单声道 |0=unsigned |
{O6f1LuH |1=立体声 |1=signed |
:Q\b$=,: |=====================|
w $7*za2 CX-数字音频I/O的8位DMA模式
4b8!LzKS 基本用法与命令BX对应的16位数字音频I/O操作相同
+>oVc\$ 常用命令:
Frt_X % C8 - 8位单一周期输入
GCx]VN3& C0 - 8位单一周期输出
oSt-w{! CE - 8位自动初始化输入
8KD7t&H C6 - 8位自动初始化输出
74%,v| 当声卡在需要时不能得到DMA时,系统将采用FIFO(first in first out,先进先出)算法来消除本采样周期内数据的不一致。如果FIFO被禁止,声卡会尝试使用DMA并立即声明:它需要一个采样。如果拥有较高优先级的其他设备占用了DMA,声卡将等待这个采样并且有可能降低采样频率。FIFO允许采样周期浮动而不影响音频质量,并且每当有信号传入DSP时FIFO将被自动清空。
J%3%l5/ 在单一周期模式下,DSP在不断地改变,清空FIFO时有可能包含还没有来得及输出的数据。为了避免这个问题,在单一周期模式下应该关闭FIFO。
x~}RL-Y2o 在自动初始化模式下,DSP是不会被改写的,可以打开FIFO以提高音质。
#`/KF_a3\> DMA的自动初始化
:JqH.Sqk 在单一周期DMA模式下,音频会在每个数据块的末尾停下,虽然中断程序可以开始另外一次传输,但是在音频输出中将会有一个停顿。这造成了在数据块之间出现滴答声,降低了音频质量。
4ow)vS( 在自动初始化模式下,声音输出在数据缓冲区的末尾循环-DMA控制器保持传输同样的内存数据块,而它们是在DMA初始化的时候就决定的。到达数据块末尾时,系统将依照存储的偏移和计数器寄存器,自动初始化当前偏移和计数寄存器,再次开始传递缓冲区数据。
77OH.E|$ 为减少滴答声而被经常采用的方法是,申请一块内存缓冲区并把它分成两个数据块,将DMA控制器设置为整个缓冲区的长度,但是设置SB16为数据块的长度(也就是缓冲区长度的一半)。一个中断对应一个数据块,所以在播放缓冲区的时候会发生两次中断,一次在缓冲区的中间(就是第二个数据块的开始),一次在缓冲区的最后(就是第一个数据块的开始)。中断服务程序会复制数据到刚刚结束的那个数据区,这样数据在声卡需要输出的时候就准备好了。设置单一周期模式DMA传输的顺序和自动初始化模式下是基本一致的,不过是设置第四位的DMA模式寄存器和第三位的DSP命令不同而已。
<!&&Qd-d6H 自动初始化模式DMA的中断:
4.7ePbk[E 1)复制下一个数据单位到刚刚结束的那个输出数据块
Gu&?Gn oc 2)通过读端口对SB进行中断应答,8位是端口2XEH,16位是端口2XFH
8@!/%"Kt2 3)应答中断完毕,向20H端口写入20H,如果声卡使用的是IRQ8-15,那么你还要向A0H写入20H
)[6H!y5 立即停止音频:
'u$$scGt 8位模式下写入DSP命令D0H,16位模式下写入D5H。这样是立即停止,无需调用中断。
JPgV7+{b[ 在当前数据块播放完毕之后停止音频:
{3C~cK{ 8位模式下写入DSP命令DAH,16位模式下写入D9H。这样要等到当前数据块的末尾,如果系统没有为结束音频输出后的中断做好准备,这样可能会导致错误。
_ 9Tv*@ 你仍然可以为单一周期模式而调整DSP,达到结束自动初始化模式的目的,声卡将在下一次中断后由A/I模式转变为S/C模式。然后它将继续在指定的长度上播放或录音,产生中断,停止。这会让你在数据结束之后正确的关闭输出音频,而不用担心剩余的DMA缓冲区被静音填满。这项技术或许对你有用,但是我建议你使用立即停止的命令,除非别的方法更合乎你的要求。