Программирование SoundBlaster


Сразу скажу, что программировать SoundBlaster непросто. Но не всё так плохо, если не касаться программирования ЧМ-синтезатора. Поэтому в данном разделе приведено руководство только по программированию цифрового канала SoundBlaster.

Для начала некоторые сведения. Сердцем SoundBlaster (далее SB) является процессор для обработки цифровых сигналов (DSP - Digital Signal Processor). Этот чип позволяет воспроизводить и записывать звуки, и содержит в себе два основных блока - аналогово-цифровой преобразователь (АЦП) и цифро-аналоговый преобразователь (ЦАП). Первый блок позволяет преобразовывать аналоговый сигнал (например, с микрофона) в цифровой сигнал. Второй блок преобразует цифровой сигнал в аналоговый, благодаря чему можно что-то услышать из колонок. Как и любое другое периферийное устройство, SB имеет IRQ (InteRrupt Request - сигнал запроса прерывания), канал прямого доступа к памяти (DMA - Direct Memory Access) и базовый порт ввода-вывода. IRQ нужен для того, чтобы программа могла реагировать на ответ SB по окончании операции воспроизведения или записи. DMA нужен для быстрой передачи данных между SB и памятью, не загружая при этом процессор. Базовый порт ввода-вывода нужен для того, чтобы программа могла посылать в SB команды (например, команду воспроизведения) и читать состояние SB (типа готов он, например, к записи в него команды или нет).

SB имеет несколько регистров для работы с цифровым каналом:

  1. Регистр сброса данных (используется для инициализации DSP) - BASE + 0x00;
  2. Регистр для чтения состояния SB - BASE + 0x0A;
  3. Регистр для записи команд в SB - BASE + 0x0C;
  4. Регистр, для определения состояния готовности данных - BASE + 0x0E.

Доступ к этим регистрам осуществляется посредством чтения или записи в соответствующий порт. BASE означает адрес базового порта ввода-вывода, а выражение BASE + 0xYY означает конкретный порт для данного регистра, относительно базового порта.

Для того, чтобы начать работу с SB нужно сбросить DSP (reset DSP) для этого нужно проделать следующие операции:

  1. записать 1 в порт сброса данных (BASE + 0x00);
  2. осуществить задержку в 3 микросекунды;
  3. записать 0 в порт сброса данных;
  4. через 3 мкс. прочитать байт из порта состояния данных (BAE + 0x0E), он должен содержать 1 в седьмом разряде.
  5. прочитать байт из порта состояния (BASE + 0x0A). Если он равен 0xAA, то сброс DSP закончен и он (DSP) готов к работе. Если же этот байт не равен 0xAA, или байт из порта состояния данных не содержит 1 в седьмом разряде, то произошла ошибка, или неправильно задан базовый порт ввода-вывода.

Пример, демонстрирующий вышесказанное приведён ниже:

  
  unsigned int RESET_DSP(unsigned port_)
  {
    outportb(port_ + 6, 1);
    delay(10);
    outportb(port_ + 6, 0);
    delay(10);

    if (((inportb(port_ + 0x0E) & 0x80) == 0x80) &
    (inportb(port_ + 0x0A) == 0xAA))
      return port_;

    return 0;
  }
  

В данном примере параметр port_ задаёт базовый порт ввода-вывода.

Для записи команды в DSP можно применить следующий алгоритм:

  1. читать порт для записи команд, пока седьмой бит содержит 1;
  2. записать нужную команду в этот же порт.

Следующий пример иллюстрирует эти действия:


  void WRITE_DSP(char value)
  {
    while (inportb(BASE + 0x0C) & 0x80) {}
    outportb(BASE + 0x0C, value);
  }

В данном случае глобальная переменная BASE задаёт базовый адрес порта ввода-вывода, а параметр value определяет нужную команду, для записи в DSP.

И, наконец, SB должен знать, чего от него хотят. Для воспроизведения или записи через SB используются такие действия:

  1. установить частоту записи/воспроизведения. Обычно от 4КГц до 44КГц. Для этого необходимо вычислить константу частоты: TimeConst = 256 - 1000000 / frequency, где frequency - требуемая частота в герцах.
  2. Записать в DSP (используя WRITE_DSP) команду 0x40 для установки частоты;
  3. записать в DSP (опять используя WRITE_DSP) саму константу частоты (TimeConst);
  4. записать в DSP команду записи/воспроизведения (см. табл. 1);
  5. записать в DSP младший байт значения длины записываемых/воспроизводимых данных.
  6. записать в DSP старший байт значения длины записываемых/воспроизводимых данных.

Вот как всё это делается:


  void PlayRecord(unsigned size, int mode,
       unsigned samplerate)
  {
    WRITE_DSP(0x40);
    WRITE_DSP(256 - 1000000 / samplerate);
    WRITE_DSP(mode);
    WRITE_DSP(size & 0xFF);
    WRITE_DSP(size >> 8);
  }

В этой процедуре параметр size задаёт размер данных для чтения/записи, mode - команда из таблицы 1, samplerate - требуемая частота в герцах.

Этих трёх процедур вполне достаточно для воспроизведения и даже записи звука через SB. Но теперь придётся программировать контроллер DMA, иначе будет невозможно передавать данные в SB или в память из SB. Однако для программирования DMA можно обойтись всего лишь одной процедурой и следующими действиями:

  1. вычислить 20-битный адрес буфера памяти, в котором хранятся данные для воспроизведения или записи: BaseAddress = Segment * 16 + Offset. Segment - сегмент буфера памяти, Offset - смещение.
  2. сформировать маску канала DMA и записать её в порт 0x0A. Маска формируется след. образом: mask = channel | 4, где channel - номер канала DMA;
  3. записать значение 0x00 в порт 0x0C;
  4. записать значение 0x49 в порт 0x0B (для воспроизведения) или 0x45 в порт 0x0B (для записи);
  5. записать младший байт (разряды 0 - 7) 20-битного адреса буфера в порт 0x02;
  6. записать не младший байт (разряды 8 - 15) 20-битного адреса буфера в порт 0x02;
  7. записать страницу (разряды 16 - 19) 20-битного адреса буфера в порт 0x83;
  8. записать младший байт значения длины передаваемых данных (0 - 64К) в порт 0x03;
  9. записать старший байт значения длины передаваемых данных (0 - 64К) в порт 0x03;
  10. записать номер канала DMA в порт 0x0A (снятие маски канала).

Маска канала нужна для того, чтобы в процессе записи всех этих значений в порты DMA не использовался.

Ниже представлена процедура для выполнения всех этих действий:


  void SetupDMA(unsigned channel, void far *buffer,
       unsigned data_size, int mode)
  {
    unsigned page, offs;

    data_size--;

    offs = (FP_SEG(buffer) << 4) + FP_OFF(buffer);
    page = (FP_SEG(buffer) +
           (FP_OFF(buffer) >> 4)) >> 12;
    outportb(0x0A, channel | 4);
    outportb(0x0C, 0);

    if (mode == 1)
      outportb(0x0B, 0x45);
    else
      outportb(0x0B, 0x49);

    outportb(0x02, offs & 255);
    outportb(0x02, offs >> 8);
    outportb(0x83, page);
    outportb(0x03, data_size & 255);
    outportb(0x03, data_size >> 8);
    outportb(0x0A, channel);
  }

В этой процедуре channel - номер канала DMA, buffer - указатель на буфер данных, data_size - размер буфера данных и mode - параметр, задающий режим работы (mode = 0 - воспроизведение, mode = 1 - запись).

Примечание. Контроллер DMA должен программироваться до программирования SB.

Таблица 1 - команды воспроизведения / записи SB.

Описание команды SB Код команды Частотный диапазон
Воспроизведение звука через DMA. 8 бит, моно.

Запись звука через DMA. 8 бит, моно.

0x14

0x24

4Кгц - 44Кгц

4Кгц - 23Кгц

Если удастся найти другие команды (например, для воспроизведения стерео, 16 бит), то таблица будет дополнена.

Примечание. Перед воспроизведением звука необходимо записать в DSP команду включения динамика, иначе ничего не будет слышно.

Таблица 2 - прочие команды SB.

Описание команды SB Код команды
Включение динамика.

Выключение динамика.

Приостановка (пауза) воспроизведения.

Продолжение воспроизведения (снятие паузы).

Установка константы частоты.

0xD1

0xD3

0xD0

0xD4

0x40

Для автоопределения (autoinitialization) базового порта ввода-вывода SB можно просматривать все порты ввода-вывода с 0x220 по 0x280 и с помощью процедуры RESET_DSP определить, есть ли на конкретном порте SB или нет.

 

Copyright © 1999 by HackMaster

Hosted by uCoz