Электронная библиотека книг Александра Фролова и Григория Фролова.
Shop2You.ru Создайте свой интернет-магазин
Библиотека
Братьев
Фроловых

Аппаратное обеспечение персонального компьютера

© Александр Фролов, Григорий Фролов
Том 33, М.: Диалог-МИФИ, 1997, 304 стр.

[Назад] [Содеожание] [Дальше]

Таймер и музыка

Одно из наиболее распространенных применений таймера - генерация звуковых сигналов и проигрывание музыки. Таймер позволяет проигрывать музыку в фоновом режиме, то есть во время работы программы может звучать музыка.

Настройка таймера для проигрывания музыки

Как мы уже говорили, канал 2 микросхемы 8254 связан с громкоговорителем компьютера. Однако громкоговоритель не просто соединен с выходом OUT канала 2. Порт вывода 61h также используется для управления громкоговорителем. Младший бит порта 61h подключен ко входу GATE канала 2 таймера. Этот бит при установке в 1 разрешает работу канала, то есть генерацию импульсов для громкоговорителя.

Дополнительно для управления громкоговорителем используется бит 1 порта 61h. Если этот бит установлен, импульсы от канала 2 таймера смогут проходить на громкоговоритель.

Таким образом, для включения звука надо выполнить следующие действия:

·       запрограммировать канал 2 таймера на нужную частоту (загрузить регистр счетчика канала нужным значением);

·       для включения звука установить два младших бита порта 61h.

Так как остальные 6 битов порта 61h используются для других целей, установка младших битов должна выполняться таким образом, чтобы значения остальных битов не были изменены. Для этого вначале надо считать байт из порта 61h в рабочую ячейку памяти, установить там нужные биты, затем вывести новое значение байта в порт 61h.

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

Одноголосая мелодия состоит из нот, разделенных или не разделенных паузами. При проигрывании мелодии необходимо для каждой ноты программировать соответствующим образом канал 2 таймера и включать громкоговоритель (с помощью порта 61h) на определенное время, равное длительности ноты. Затем программа должна выключить динамик и выдержать паузу перед проигрыванием следующей ноты, если такая пауза требуется.

Второй способ проигрывания музыки

Программа может генерировать звуки и другим способом, не используя таймер. Для этого нужно сбросить младший бит порта 61h и, управляя битом 1 этого порта, формировать импульсы для громкоговорителя. Иными словами, программа должна периодически то устанавливать этот бит, то сбрасывать. Высота генерируемого звука будет соответствовать периоду изменения состояния указанного бита.

Можно также комбинировать описанные способы, получая разнообразные звуковые эффекты.

Проигрывание музыки в фоновом режиме

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

Основная идея заключается в использовании прерывания INT 1Ch, которое вырабатывается таймером с частотой примерно 18,2 Гц. Ваш обработчик этого прерывания осуществляет контроль за выборкой нот из массива, содержащего мелодию, и программирование микросхемы 8254. Например, один раз в полсекунды обработчик проверяет, не пора ли прекратить звучание одной ноты и начать проигрывание следующей ноты. Если пора, он выключает громкоговоритель и перепрограммирует канал 8254 на новую частоту, соответствующую следующей ноте.

Основное преимущество использования таймера для проигрывания мелодии - независимость констант, используемых для программирвания канала таймера от производительности системы. Ваша мелодия будет звучать одинаково и на медленной IBM PC/XT и на совеменном компьютера с процессором Pentium, но при условии, что вы будете использовать таймер и для организации задержек при исполнении мелодии.

Для определения значения, которое должно быть записано в регистр счетчика канала 2 таймера, надо разделить число 1193180 на частоту ноты в герцах.

Для подготовки таблиц частот по нотам вам поможет список частот для нот второй октавы:

Нота

Частота, Гц

До

261,7

До-диез

277,2

Ре

293,7

Ре-диез

311,1

Ми

329,6

Фа

349,2

Фа-диез

370,0

Соль

392,0

Соль-диез

415,3

Ля

440,0

Ля-диез

466,2

Си

493,9

Для других октав при понижении или повышении тона значения частот надо умножать (при повышении тона) или делить (при понижении тона) на 2.

Программа TMSOUND

Программа TMSOUND (листинг 5.2) проигрывает мелодию с помощью системного таймера.

Листинг 5.2. Файл tmsound\tmsound.с


	// =====================================================
	// Проигрывание музыки с помощью таймера
	//
	// (C) Фролов А.В, 1997
	//
	// E-mail: frolov@glas.apc.org
	// WWW:    http://www.glasnet.ru/~frolov
	//            или
	//         http://www.dials.ccas.ru/frolov
	// =====================================================
	#include <stdio.h>
	#include <conio.h>
	#include <dos.h>
	
	void sound(int, int);
	void tm_sound(int freq, int time);
	void tm_delay(int ticks);
	
	// Массив частот для мелодии
	int mary[] = 
	{
	  330, 294, 262, 294, 330, 330, 330,
	  294, 294, 294, 330, 392, 392,
	  330, 294, 262, 294, 330, 330, 330, 330,
	  294, 294, 330, 294, 262, 0
	};
	
	// Массив длительностей
	int del[] = 
	{
	  5, 5, 5,  5, 5, 5,  10,
	  5, 5, 10, 5, 5, 10,
	  5, 5, 5,  5, 5, 5,  5, 5,
	  5, 5, 5,  5, 20
	};
	
	int main(void) 
	{
	  int i;
	
	  for(i=0 ;mary[i] != 0 ;i++)
	    tm_sound(mary[i], del[i]);
	  
	  return 0;
	}
	
	/**
	*.Name         tm_sound
	*.Title        Формирование тона заданной длительности
	*
	*.Descr        Эта функция предназначена для генерации
	*              на громкоговорителе тона заданной
	*              длительности и частоты
	*
	*.Proto        void tm_sound(int freq, int time);
	*
	*.Params       int freq - частота в герцах;
	*              int time - длительность в периодах работы
	*               таймера
	**/
	void tm_sound(int freq, int time) 
	{
	  int cnt;
	
	  // Задаем режим канала 2 таймера
	  outp(0x43, 0xb6);
	
	  // Вычисляем задержку для загрузки в
	  // регистр счетчика таймера
	  cnt = (int)(1193180L / freq);
	
	  // Загружаем регистр счетчика таймера - сначала
	  // младший, затем старший байты
	  outp(0x42, cnt & 0x00ff);
	  outp(0x42, (cnt &0xff00) >> 8);
	
	  // Включаем громкоговоритель. Сигнал от
	  // канала 2 таймера теперь будет проходить
	  // на вход громкоговорителя
	  outp(0x61, inp(0x61) | 3);
	
	  // Выполняем задержку.
	  tm_delay(time);
	
	  // Выключаем громкоговоритель.
	  outp(0x61, inp(0x61) & 0xfc);
	}
	
	/**
	*.Name         tm_delay
	*.Title        Формирование задержки по таймеру
	*
	*.Descr        Эта функция формирует задержку, используя
	*              системный таймер
	*
	*.Proto        void tm_delay(int ticks)
	*
	*.Params       int ticks - величина задержки в периодах работы таймера
	**/
	
	void tm_delay(int ticks) 
	{
	  _asm 
	  {
	    push si
	
	    mov  si, ticks
	    mov  ah, 0
	    int  1ah
	
	    mov  bx, dx
	    add  bx, si
	
	delay_loop:
	
	    int  1ah
	    cmp  dx, bx
	    jne  delay_loop
	
	    pop  si
	  }
	}

Программа IOSOUND

Приведем исходный текст программы IOSOUND, генерирующую звук без использования таймера (листинг 5.3.). Эта программа формирует импульсы при помощи манипуляций с разрядом 1 порта 61h.

Листинг 5.3. Файл iosound\iosound.с


	// =====================================================
	// Генерация звукового сигнала через порты таймера
	//
	// (C) Фролов А.В, 1997
	//
	// E-mail: frolov@glas.apc.org
	// WWW:    http://www.glasnet.ru/~frolov
	//            или
	//         http://www.dials.ccas.ru/frolov
	// =====================================================
	#include <stdio.h>
	#include <conio.h>
	#include <dos.h>
	
	#define FREQUENCY 200
	#define CYCLES 30000
	
	int main(void) 
	{
	  // Во время генерации звука прерывания должны
	  // быть запрещены.
	  _disable();
	
	  _asm 
	  {
	    // Загружаем количество циклов - периодов
	    // генерируемых импульсов
	    mov   dx, CYCLES
	
	    // Отключаем громкоговоритель от таймера
	    in    al, 61h
	    and   al, 0feh
	
	    // Цикл формирования периода
	sound_cycle:
	
	    // Формируем первый полупериод, подаем
	    // на громкоговоритель уровень 1
	    or    al, 2
	    out   61h, al
	
	    // Формируем задержку
	    mov   cx, FREQUENCY
	
	first: loop  first
	
	    // Формируем второй полупериод, подаем
	    // на громкоговоритель уровень 0
	    and   al,  0fdh
	    out   61h, al
	
	    // Формируем задержку
	    mov   cx, FREQUENCY
	
	second: loop  second
	
	    // Если сформированы не все периоды, переходим
	    // к формированию следующего периода.
	    dec   dx
	    jnz   sound_cycle
	  }
	
	  // Разрешаем прерывания
	  _enable();
	
	  // Выключаем громкоговоритель
	  outp(0x61, inp(0x61) & 0xfc);
	  
	  return 0;
	}

Так как в программе IOSOUND для формирования полупериодов используется задержка с помощью команды LOOP, высота генерируемого тона будет зависеть от производительности системы.

Такой зависимости можно избежать, если перед началом работы измерять производительность и соответствующим образом корректировать константу, загружаемую в регистр CX перед вызовом команды LOOP. Измерение производительности лучше всего выполнять с помощью таймера, определяя время выполнения команды LOOP.

Программа RANDOM

Последнее, что мы сделаем с таймером - научимся получать от него случайные числа.

Для генерации случайных чисел лучше всего использовать канал 2 в режиме 3. В регистр счетчика канала мы занесем значение, равное диапазону нужных нам случайных чисел. Например, если мы запишем в регистр число 80 и запустим канал таймера, получаемые случайные числа будут лежать в диапазоне от 0 до 79.

Программа RANDOM (листинг 5.4) получает случайные числа и отображает их в наглядном виде с помощью столбчатой диаграммы.

Листинг 5.4. Файл random\random.с


	// =====================================================
	// Генерация случайных чисел
	//
	// (C) Фролов А.В, 1997
	//
	// E-mail: frolov@glas.apc.org
	// WWW:    http://www.glasnet.ru/~frolov
	//            или
	//         http://www.dials.ccas.ru/frolov
	// =====================================================
	#include <stdio.h>
	#include <conio.h>
	#include <dos.h>
	
	void rnd_set(int bound);
	int rnd_get(void);
	
	int main(void) 
	{
	  int i, j;
	
	  printf("\nГенератор случайных чисел."
	    "\nНажмите любую клавишу,"
		"\nдля завершения работы нажмите <Control+C>"
		"\n");
	
	  for(;;) 
	  {
	    // Устанавливаем диапазон генерации случайных
	    // чисел и инициализируем таймер
	    rnd_set(80);
	
	    // Ожидаем нажатия клавиши
	    getch();
	
	    // После нажатия на клавишу получаем
	    // случайное число
	    j = rnd_get();
	
	    // Выводим на экран строку символов "-",
	    // длина которой равна полученному случайному числу
	    for(i=0; i < j; i++) 
	      putchar(219);
	    printf("\n");
	  }
	  return 0;
	}
	
	/**
	*.Name         rnd_set
	*.Title        Инициализация генератора случайных чисел
	*
	*.Descr        Эта функция инициализирует канал 2 таймера
	*              для использования в качестве генератора
	*              случайных чисел
	*
	*.Proto        void rnd_set(int bound)
	*
	*.Params       int bound - верхняя граница для генерируемых
	*                          случайных чисел.
	**/
	
	void rnd_set(int bound) 
	{
	  // Устанавливаем режим 3 для второго канала таймера
	  outp(0x43, 0xb6);
	
	  // Загружаем регистр счетчика таймера - сначала
	  // младший, затем старший байты
	  outp(0x42, bound & 0x00ff);
	  outp(0x42, (bound &0xff00) >> 8);
	
	  // Разрешаем работу канала
	  outp(0x61, inp(0x61) | 1);
	}
	
	/**
	*.Name         rnd_get
	*.Title        Получение от таймера случайного числа
	*
	*.Descr        Эта функция получает случайное число от
	*              таймера, который был предварительно
	*              проинициализирован функцией rnd_set
	*
	*.Proto        int rnd_get(void)
	*
	*.Params       Отсутствуют.
	*
	*.Return       Случайное число в диапазоне от 0, до
	*              уменьшенного на 1 значения, заданного в
	*              качестве параметра функции rnd_set().
	**/
	
	int rnd_get(void) 
	{
	  int i;
	  
	  // Выдаем команду CLC для фиксирования
	  // текущего значения регистра канала 2 таймера
	  outp(0x43, 0x86);
	
	  // Вводим младший и старший байты счетчика
	  i = inp(0x42);
	  i = (inp(0x42) << 8) + i;
	  
	  return(i);
	}
[Назад] [Содеожание] [Дальше]