IOポートを操作するデバイスドライバ(32bit)を作成しBEEP音を制御する

icon 項目のみ表示/展開表示の切り替え

概要

IOポートの操作する命令は特権命令なのでカーネルモードでしか動作しません。
IOポートを操作する32bitデバイスドライバ(beeptest.sys)をインストールした状態で、別のプログラム(beeptestctl.exe)からデバイスドライバを制御しBEEP音(屋台のラーメン屋でおなじみのチャルメラ音)を発生させます。
BEEP音はヘッドホンを使用してもパソコンのスピーカーから音が出る機種がありますので、テストする環境には気を付けてください。
IOポートの操作は、コンパイラの内部関数を使用しています。
使用するには、まずデバイスドライバをインストールし、その後beeptestctl.exeを実行します。
チャルメラの楽譜を以下に示します。
なお、純粋にビープ音を発生させるのが目的であれば、Beep APIを使用すれば簡単に済みます。
本ページは、IOポート操作の例として標準的なIOであり動作確認が容易なビープ音を選択しました。
Windows 7等のBeep APIでは、本ページの様にタイマーICを操作するのではなく、サウンドカードによってビープ音を発生させるように変更されています。(音量調整ができる)

テスト環境

コンパイラ

ドライバの開発 WDK7
ドライバを制御するソフトの開発 Visual C++ 2013 Express

実行環境

Windows 7 Professional Service Pack 1 32bit
Windows Vista Ultimate Service Pack 2 32bit
Windows XP Professional Service Pack 3 32bit

ドライバソースファイル等のフォルダー構成

d:¥beeptest
│  
└─beeptest
       beeptest.cpp
       makefile
       sources

ドライバ開発環境の構築とビルドの方法

デバイスドライバの開発にWDK7を使用します。以下のサイトよりWDK 7.1.0をダウンロードし、インストールします。
Windows Driver Kit (WDK)https://msdn.microsoft.com/ja-jp/library/windows/hardware/ff557573(v=vs.85).aspx
WDK7にはコンパイラも含まれています。(Visual C++ 2008の英語版相当)ただし、コマンドラインでしか使用できません。
文法チェックやインテリセンス機能を使用したいので、Visual Studioでソースコードの編集を行いWDK7でデバイスドライバを開発することとします。

Visual Studio C++でプロジェクトを作成

Visual Studioで新規プロジェクトを作成を開始し、テンプレートはメイクファイルプロジェクトを選択し後は適当に操作して完了させます。
ここではd:¥ドライブにbeeptest64プロジェクトを作成しました。
プロジェクトのプロパティを表示し、インクルードの検索パスにDDKのインクルードファイルが格納されているフォルダー名を指定します。(ディフォルトではC:¥WinDDK¥7600.16385.1¥inc¥ddk)
これによりVisual Studioのエディタ上で関数名の色が変わったり、インテリセンスが使えるようになり便利です。

makefileとsourcesファイルの作成

上記のファイルを作成します。保存先は、d:¥beeptest¥beeptestフォルダとします。本プログラムに必要なファイルは本ページの最後のダウンロードのリンク先に含まれています。
makefileとsourcesファイル名には拡張子はついていないので注意してください。

makefile

!INCLUDE $(NTMAKEENV)¥makefile.def

sources

TARGETNAMEで作成するデバイスドライバのファイル名を指定します。本例ではbeeptest.sysとなります。
SOURCESでソースファイル名を指定します。複数のソースファイルがある場合はスペースで区切ります。
TARGETNAME=beeptest
TARGETTYPE=DRIVER
TARGETPATH=obj
SOURCES=beeptest.cpp

WDK7によるデバイスドライバの作成

スタートメニューからWindows Driver Kits・WDK 7600.16385.1・Build Environments・Windows XP¥x86 Free Build Environmentを実行します。
メニューで選択するOSは開発しているパソコンのOSではなく、ドライバを実行するOSです。XPを選択すればXP,Vista,7で使用可能です。
コマンドプロンプトのウィンドウが開くので、CDコマンドを使用してソースファイルが存在するフォルダーに移動します。
buildコマンドでコンパイルできます。
cd d:¥beeptest¥beeptest
build
デバイスドライバは、d:¥beeptest¥beeptest¥objfre_wxp_x86¥i386 に作成されます。

デバイスドライバのインストール

ドライバのインストールは、ドライバのインストール/アンインストールするプログラムを参照指定ください。

ドライバ ソースの概要

beeptest.cpp

各関数名が勝手に修飾されないように、extern "C"で定義します。
インクルードファイルconio.hはIOポートを操作する関数の定義のために使用しています。
各関数は、コンパイル時にOACRにより警告が出ないように独特の型でプロトタイプ宣言を行います。
#pragma alloc_textにより各関数をメモリ上の扱いを定義します。この定義を行った場合、各関数の入口にPAGED_CODE();を記述する必要があります。
INITを指定した場合、ページアウトしません。PAGEをした場合、ページアウト可能です。
in/out命令を実行するためにコンパイラの組み込み関数_inpと_outpを使用します。

DriverEntry関数

デバイスドライバが起動されたときの1度だけ実行される関数です。
デバイスドライバの名称の定義と、 デバイスドライバを使用するプログラムで実行されるCreateFile関数・CloseFile関数・DeviceIoControl関数に対する処理先等を定義します。
以後、使用する側から呼び出される場合は、定義した処理先が呼び出されます。

DeviceControlFunction関数

DriverEntry関数で登録されたデバイスドライバを使用する上のDeviceIoControl関数から呼び出されるプログラムで実行される関数である。
直接呼び出されるのではなく、カーネルがバッファ処理等を行ってから呼び出されます。
引数より入力・出力バッファのアドレス・サイズ等が含まれる構造体をIoGetCurrentIrpStackLocation関数により呼び出しますす。
アクセスIDを調べ該当する処理に振り分けます。本ドライバはIOCTL_BEEP_CODEのみサポートしています。
入力バッファより、BEEPのカウンタ値とスピーカのon/offの指定値を取得します。
入力バッファの0~3バイトがカウンタ値(下位2バイトのみ有効)、4~7バイトがスピーカの制御値です。
カウント値(16bit)により分周されます。
スピーカー制御値が0以外の時はBEEP音を連続発生、0の時はBEEP音を停止させます。
IoCompleteRequestにより処理したコマンドを消去します。
IO操作の概要
実際に出力されるBEEP音の基本周波数は、1193180/カウント値で決まります。
1193180はPC/AT互換機に使われているタイマーICの8254(チップセット等に互換品が集積されている)のCLK2への入力周波数であり、CLK2の立下り時に2ずつ減算されます。カウンタが0になるとカウンタに初期値がロードされます。
スピーカはカウント2を使用しており、モード3に設定しています。
OUT2端子はカウンタが0になるたびにLow、hiが交互に出力されます。カウンタ値が偶数の場合はLow、Hiとも同間隔、奇数の場合はHiがクロックの1個分増えます。
GATE2端子がHiの時、CLK2が有効になります。
GATE2端子がLowからHiになるとカウントの初期値がロードされます。
GATE2端子は出力ポート(0x61)のビット0に割り当てられているので、出力ポートによりカウンタを停止させることができます。
スピーカは、OUT2端子からANDゲートを経由して接続されています。ANDゲートのもう一方の入力は出力ポート(0x61)のビット1に接続されています。
出力ポート(0x61)のビット1をLowにするとスピーカへの出力を停止させることができます。
SVGの代替画像 GATE1 GATE1 GATE1 CLK0 CLK1 CLK2 OUT0 OUT1 OUT2 IRQ DRAM リフレッシュ スピーカーへ 8254 (0x61) PORTB XD0 XD1 XD5 1193180Hz Vcc

UnloadDriverFunction関数

ドライバがアンロードされるときに呼び出されます。
IoDeleteDevice関数によりドライバを消去します。

ドライバ(beeptest.sys) ソースコード

beeptest.cpp

//      WDMドライバサンプル(周波数を指定してBeep音を鳴らす/止める)
//      Windows XP Vista 7 32bit OS限定

#ifdef __cplusplus
        extern "C" {
#endif

#include <wdm.h>
#include <conio.h>

#define IOCTL_BEEP_CODE 0x800

//      エントリーポイント
DRIVER_INITIALIZE DriverEntry;

__drv_dispatchType(IRP_MJ_MAXIMUM_FUNCTION) DRIVER_DISPATCH  UnSupportedFunction;

//      Create
__drv_dispatchType(IRP_MJ_CREATE) DRIVER_DISPATCH CreateFunction;
NTSTATUS CreateFunction(IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp);

//      Close
__drv_dispatchType(IRP_MJ_CLOSE) DRIVER_DISPATCH CloseFunction;
NTSTATUS CloseFunction(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp);

//      DeviceControl
__drv_dispatchType(IRP_MJ_DEVICE_CONTROL) DRIVER_DISPATCH DeviceControlFunction;
NTSTATUS DeviceControlFunction(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp);

//      AddDevice
NTSTATUS AddDeviceFunction(IN PDEVICE_OBJECT DriverObject, IN PDEVICE_OBJECT DeviceObjcet);

//      UnloadDriver
DRIVER_UNLOAD UnloadDriverFunction;

#ifdef ALLOC_PRAGMA
#pragma alloc_text( INIT, DriverEntry )
#pragma alloc_text( PAGE, CreateFunction)
#pragma alloc_text( PAGE, CloseFunction)
#pragma alloc_text( PAGE, DeviceControlFunction)
#pragma alloc_text( PAGE, AddDeviceFunction)
#pragma alloc_text( PAGE, UnloadDriverFunction)
#endif

#ifdef __cplusplus
        }
#endif

//      ドライバーがロードされたときに呼び出される関数
NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject,IN PUNICODE_STRING pRegistryPath){
        UNICODE_STRING usDeviceName,usDosDeviceName;
        PAGED_CODE();
        RtlInitUnicodeString( &usDeviceName, L"¥¥Device¥¥beeptest" );
        RtlInitUnicodeString(&usDosDeviceName, L"¥¥DosDevices¥¥beeptest");

        PDEVICE_OBJECT pDeviceObject = NULL;
        NTSTATUS NtStatus=IoCreateDevice( pDriverObject, 0, &usDeviceName, FILE_DEVICE_SECURE_OPEN, 0, FALSE, &pDeviceObject);
        if( NtStatus != STATUS_SUCCESS){
                return NtStatus;
        }
        pDriverObject->MajorFunction[IRP_MJ_CREATE]=CreateFunction;  //      CreateFile呼び出し時の処理を登録
        pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DeviceControlFunction; //      DeviceControl呼び出し時の処理を登録
        pDriverObject->MajorFunction[IRP_MJ_CLOSE]=CloseFunction;    //      CloseFile呼び出し時の処理を登録
        
        pDriverObject->DriverUnload = UnloadDriverFunction;  //      ドライバがアンロードされる時の処理を登録

        NTSTATUS status = IoCreateSymbolicLink(&usDosDeviceName, &usDeviceName);

        return STATUS_SUCCESS;
}

//      Create
NTSTATUS CreateFunction(IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp){
        PAGED_CODE();
        return STATUS_SUCCESS;
}

//      Close
NTSTATUS CloseFunction(IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp){
        PAGED_CODE();
        return STATUS_SUCCESS;
}

//      DeviceControl   周波数,on/off
NTSTATUS DeviceControlFunction(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp){
        PAGED_CODE();
        int dwDataRead = 0, dwDataWritten = 0;
        NTSTATUS NtStatus = STATUS_NOT_SUPPORTED;
        PIO_STACK_LOCATION pIoStackIrp = IoGetCurrentIrpStackLocation(Irp);
        if (pIoStackIrp){
                // アクセスIDを調べる
                if (pIoStackIrp->Parameters.DeviceIoControl.IoControlCode == IOCTL_BEEP_CODE){
                        // バッファポインタ取得
                        PCHAR pInputBuffer = (PCHAR)Irp->AssociatedIrp.SystemBuffer;
                        PCHAR pOutputBuffer = (PCHAR)Irp->AssociatedIrp.SystemBuffer;
                        // バッファサイズ取得
                        int InSize = pIoStackIrp->Parameters.DeviceIoControl.InputBufferLength;
                        int OutSize = pIoStackIrp->Parameters.DeviceIoControl.OutputBufferLength;
                        DWORD32* dwp = (DWORD32*)pInputBuffer;
                        DWORD32 count = dwp[0]; //      カウント値
                        DWORD32 flag = dwp[1];  //      スピーカーon/off

                        if (flag){      //      bepp音を発生
                                unsigned a;
                                _outp(0x43, 0xb6);
                                _outp(0x42, count & 0xff);
                                _outp(0x42, count / 256);
                                _outp(0x61, _inp(0x61) | 0x3);

                        }
                        else{   //      bepp音を停止
                                _outp(0x61, _inp(0x61) & 0xfc);
                        }

                        NtStatus = STATUS_SUCCESS;
                }
        }
        Irp->IoStatus.Status = NtStatus;
        Irp->IoStatus.Information = dwDataWritten;
        IoCompleteRequest(Irp, IO_NO_INCREMENT);
        return STATUS_SUCCESS;
}

//      AddDevice
NTSTATUS AddDeviceFunction(IN PDEVICE_OBJECT DriverObject, IN PDEVICE_OBJECT DeviceObjcet){
        PAGED_CODE();
        return STATUS_SUCCESS;
}

//      UnloadDriver
VOID UnloadDriverFunction(IN PDRIVER_OBJECT DriverObject){
        PAGED_CODE();
        IoDeleteDevice(DriverObject->DeviceObject);
}

制御ソフト ソースの概要

beeptestctl.cpp

WinMain関数

最初に呼び出される関数です。
CreateFile関数によりデバイスドライバをオープンします。
デバイスドライバが見つからない場合は、エラーメッセージを出力しプログラムを終了します。
周波数と音の長さを指定して、TestBeep 関数を呼び出しビープ音を発生させます。
CloseHandle関数を呼び出してデバイスドライバをクローズします。
オクターブ4の各音階の周波数は以下のとおりです。
オクターブを1つ上げる場合は周波数を倍に、下げる場合は周波数を半分にします。
音階 ラ# ド# レ# ファ ファ# ソ#
周波数(Hz) 220 233 247 262 277 293 311 330 349 370 392 415 440
1オクターブの中に12個の音階がありますが、以下の式の様に等倍となっています。
\displaystyle f={220}^{\frac{N}{12}}
上式はオクターブ4を示している。Nは0~11で音階を示している。

TestBeep関数

第1引数が周波数、第2引数がビープ音の発生時間です。時間の単位は千分の1秒です。
DeviceIoControl経由でデバイスドライバのDeviceControlFunction関数を呼び出し、ビープ音のスタートさせSleep関数で待機させた後、ビープ音を止めます。
#include <windows.h>
#include <tchar.h>

#define IOCTL_BEEP_CODE 0x800


void TestBeep(HANDLE h, int f, int w){
        DWORD dwReturn;
        DWORD buf[2];
        buf[0] = 1193180 / f;
        buf[1] = 0xffffffff;
        DeviceIoControl(h, IOCTL_BEEP_CODE, buf, sizeof(buf),
                buf, sizeof(buf), &dwReturn, NULL);

        Sleep(w);

        buf[1] = 0;
        DeviceIoControl(h, IOCTL_BEEP_CODE, buf, sizeof(buf),
                buf, sizeof(buf), &dwReturn, NULL);
}


int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst, LPSTR lpsCmdLine, int nCmdShow){

        HANDLE hFile;
        hFile = CreateFile(_TEXT("¥¥¥¥.¥¥beeptest"),
                GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
        if (hFile == INVALID_HANDLE_VALUE){
                MessageBox(0,_TEXT("CreateFileが失敗しました"),_TEXT("エラー"),MB_OK);
                return 1;
        }
//      チャルメラ
        TestBeep(hFile, 440, 143);      //      ラ
        TestBeep(hFile, 494, 143);      //      シ
        TestBeep(hFile, 277 * 2, 286 * 2);      //      ド#
        TestBeep(hFile, 494, 143);      //      シ
        TestBeep(hFile, 440, 143);      //      ラ
        Sleep(286 * 4);

        TestBeep(hFile, 440, 143 );     //      ラ
        TestBeep(hFile, 494, 143 );     //      シ
        TestBeep(hFile, 277 * 2, 143 ); //      ド#
        TestBeep(hFile, 494, 143 );     //      シ

        TestBeep(hFile, 440, 143);      //      ラ
        TestBeep(hFile, 494, 246);      //      シ

        CloseHandle(hFile);

        return 0;
}

実行ファイルとソースファイルのダウンロード

実行ファイルとソースファイルのダウンロード beeptest.zipの内容は以下のとおりです。
    beeptest
    ├─beeptest
    │  └─beeptest
    │      │  beeptest.cpp
    │      │  makefile
    │      │  sources
    │      │  
    │      └─objfre_wxp_x86
    │          └─i386
    │                  beeptest.sys
    │                  
    └─beeptestctl
            beeptestctl.cpp
            beeptestctl.exe