コンテック社のデジタル入出力ボードを使う(割り込み編)

コンテック社のデジタル入出力ボード PIO-16/16L(LPCI)H または DIO-1616L-LPE を使って、外部機器からコンピュータに対しての割り込み信号を検知する方法を紹介します。

下記の記事は、割り込み信号ではなくてトリガを検知する方法の記事です。本記事では内容の重複を避けるため下記の記事との差分しか解説しませんので、ぜひお読みいただきたく思います。

コンテック社のデジタル入出力ボードを使う(トリガ編)

コンテック社のデジタル入出力ボードを使って、外部機器からコンピュータに対してのトリガ入力を検知する方法を紹介します。

「割り込み」と「トリガ」の概念について

コンテック社のドライバでは、「割り込み」と「トリガ」という別々の概念が存在します。デジタル入出力ボードに対して電気信号を入力するという観点からいうと、この二つは全く同じものです。

私の想像ですが、ドライバ内に隠蔽された検知の仕方の違いで「割り込み」と「トリガ」をわけているようです。

  • 「割り込み」はデジタル入出力ボードに搭載されているマイコンの機能をそのままつかったもの。
  • 「トリガ」はドライバ内部で外部からの信号入力をポーリングループで監視しているもの。

と、予想されます。

下記に「割り込み」と「トリガ」の違いの表を示します。この表をながめると、あきらかに「トリガ」のほうがソフトウェア的な信号監視をやっているような作りであると推測されます。(想像とか推測ばかりで、すいません)

入力検知方式割り込み
CdioConst.DIOM_INTERRUPT
トリガ
CdioConst.DIOM_TRIGGER
最短検知間隔なしあり (ボードによって最短検知間隔がある)
RISEを検知
FALLを検知
RISE と FALL を両方検知×
検知開始命令NotifyInterrupt()NotifyTrg()
検知終了命令不要StopNotifyTrg()
対応するボード使えないものもあるすべて
Fig. 2 割り込みの場合に対応できる信号
Fig. 3 トリガの場合に対応できる信号

前述した「ポーリングループ」とは、ユーザに見えないところでディスパッチタイマを使って InpByte() しているようなものです。NotifyTrg() の引数に最短検知間隔を与えていることがそれを暗示しています。

StopNotifyTrg() というメソッドが存在するのも、監視の必要ないときはポーリングループをとめて必要以上にCPUの計算リソースを消費しないようにするという機能を提供するためだと思われます。

外部回路の接続について

本記事のソースコードを実際に実行して試す場合は、Fig. 4 のように外部回路を接続してください。

「トリガ」の検知のときは、'RISE' と 'FALL' と 'RISEとFALL両方'、を検知したかったのでセンサを3つ接続していましたが、本記事の「割り込み」の場合は 'RISE' と 'FALL' の違いだけを実感できれば良いのでセンサの接続は2個だけでかまいません。

Fig. 4 実体配線図

プログラムの大まかな流れについて

割り込み信号の検知は、トリガの検知とほとんど同じ流れです。トリガ検知では NotifyTrg() というメソッドと StopNotifyTrg() というメソッドを対にして使っていましたが、割り込みの場合は Stop にあたるメソッドが存在しません。

(0) アプリケーションを起動する.
(1) メインウインドウのハンドルを取得する.
(2) メッセージフックができるようにする.
(3) デジタル入出力ボードクラスを生成 new する.
(4) デジタル入出力ボードクラスを初期化 Init() する.
(5) 割り込みを検出する入力ビットを必要な数だけ登録 NotifyInterrupt() する.
(6) そのとき 'RISE' な割り込み信号だけ検知するか、'FALL' な割り込み信号だけ検知するか決める.
( 割り込みは 'RISEとFALLの両方' 検知はできない )
(7) WndProc の中で割り込み信号の検知メッセージ CdioConst.DIOM_INTERRUPT が通知されるのを見張る.
(8) 見張ってメッセージをつかまえたらそれに応じた処理をする.
(9) トリガ検知と違いアプリを終了するまえに割り込み信号検知をとめる必要はない.
( そもそもそれにあたるメソッドが存在しない )
(10) デジタル入出力ボードクラスを終了 Exit() する.
(11) アプリケーションを終了する.

ソースコード解説

XAMLのほうは、トリガ検知のときと同じなので解説は省略します。

<Window x:Class="aaa.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:aaa"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="480">

    <DockPanel>

        <Menu DockPanel.Dock="Top">

            <MenuItem x:Name="menuApplication" Header="Application">
                <MenuItem x:Name="menuApplicationQuit" Header="Quit" Click="menuApplicationQuit_Click"/>
            </MenuItem>

        </Menu>

        <ToolBarTray DockPanel.Dock="Top">
            <ToolBar>

                <Border BorderBrush="DarkViolet" BorderThickness="1" >
                    <Button x:Name="tbnApplicationQuit" Content="Quit" Width ="48" Height="48" Click="menuApplicationQuit_Click"></Button>
                </Border>

            </ToolBar>
        </ToolBarTray>

        <StatusBar DockPanel.Dock="Bottom" Height="30">
            <Label x:Name="stbLabel000"/>
        </StatusBar>

        <Border BorderBrush="DarkViolet" BorderThickness="1" >
            <ScrollViewer x:Name="TheScv" Background="DarkViolet" ScrollViewer.HorizontalScrollBarVisibility="Visible" ScrollViewer.VerticalScrollBarVisibility="Visible">
                <Grid x:Name="TheGrid">
                    <Image x:Name="TheImage" Stretch="Uniform" />
                    <Canvas x:Name="TheCanvas" Background="Transparent">
                        <TextBlock x:Name="TheTbk" Foreground="White" Background="Transparent"/>
                    </Canvas>
                </Grid>
            </ScrollViewer>
        </Border>

    </DockPanel>

</Window>

つぎに MainWindow.xaml.cs のソースコードを解説します。

124行目、デジタル入出力ボードを初期化 Init() します。

144行目、このウインドウで生じるメッセージのフックができるように Windows に依頼します。

152、157行目、外部の信号入力を「割り込み信号」として検知するようにドライバに依頼 NotifyInterrupt() します。このメソッドは NotifyTrg() と違って引数に最短検知間隔を与える必要がないことに注目してください。

また、NotifyInterrupt() の引数には、RISE、または、FALL、どちらかの信号変化検知しか与えられないことに注目してください。ためしに ( RISE | FALL ) としてみると戻り値はエラーになります。

また、「割り込み」の検知に対応していないボードなども、NotifyInterrupt() を実行するとエラーになります。たとえば DIO-16/16(USB)や、DIO-0808LY-USBといったコンテック社のデジタル入出力デバイスではエラーになります。

下記の名前空間の追加をお忘れなく.
using CdioCs;
using System.Windows.Interop;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

// この using を忘れないように. Don't forget to add this sentence.
using CdioCs;
using System.Windows.Interop;

namespace aaa
{

	public partial class MainWindow : Window
	{

		// 割り込みのカウンタを記録するリスト.
		List<int> ListCounter = new List<int>(0);

		// 割り込みを検出したいビット番号.
		public const short INTERRUPT_BIT_NUMBER_INP00 = 0;
		public const short INTERRUPT_BIT_NUMBER_INP01 = 1;

		// デバイスマネージャでデジタル入出力ボードのデバイス名称を調べること.
		public const String DEV_NAME_DIO = "DIO000";

		public Cdio MyDio;
		public short MyId;

		/////////////////////////////////////////////////////////////
		/////////////////////////////////////////////////////////////
		/////////////////////////////////////////////////////////////

		public IntPtr Handle
		{
			get
			{
				Window wnd = Window.GetWindow( this );
				var wih = new System.Windows.Interop.WindowInteropHelper( wnd );
				return wih.Handle;
			}
		}

		private IntPtr WndProc( IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled )
		{

			// コンテックのライブラリが発生させたメッセージかどうか調べる.
			if ( msg == (int)( CdioConst.DIOM_INTERRUPT ) )
			{

				uint zero_mask_upper = 0x0000ffff;
				uint zero_mask_lower = 0xffff0000;
				int sft = 16;

				int v = lParam.ToInt32();

				// 割り込みを検出したビット.
				int interrupt_bit = (int)( ( v & zero_mask_upper ) );

				// 割り込みの種類、ライズかフォールか.
				int interrupt_kind = (int)( ( v & zero_mask_lower ) >> sft );

				// ライズな割り込みの場合.
				int msk_rise = (int)( CdioConst.DIO_INT_RISE );
				if ( ( interrupt_kind & msk_rise ) == msk_rise )
				{
					if (( 0 <= interrupt_bit ) && ( interrupt_bit < ListCounter.Count )) 
					{
						// 該当のビットのカウンタをインクリメントする.
						( ListCounter[interrupt_bit] )++;
					}
				}

				// フォールな割り込みの場合.
				int msk_fall = (int)( CdioConst.DIO_INT_FALL );
				if ( ( interrupt_kind & msk_fall ) == msk_fall )
				{
					if (( 0 <= interrupt_bit ) && ( interrupt_bit < ListCounter.Count )) 
					{
						// 該当のビットのカウンタをインクリメントする.
						( ListCounter[interrupt_bit] )++;
					}
				}

				// 再描画を要請する.
				this.InvalidateVisual();

			}

			return IntPtr.Zero;

		}

		/////////////////////////////////////////////////////////////
		/////////////////////////////////////////////////////////////
		/////////////////////////////////////////////////////////////

		public MainWindow()
		{

			InitializeComponent();

			// 割り込みを検知したらカウントアップするリスト.
			for ( int k = 0; k < 16; k++ )
			{
				// 全要素をゼロリセット.
				const int INIT_VALUE = 0;
				ListCounter.Add( INIT_VALUE );
			}

			// コンテックのDIOクラスを生成する.
			MyDio = new Cdio();

			// DIOを初期化する.
			int iret = MyDio.Init( DEV_NAME_DIO, out MyId );
			if ( iret != (int)( CdioConst.DIO_ERR_SUCCESS ))
			{
				MessageBox.Show( "Error: MyDio.Init();" );
			}

			// ウインドウがロードされたら実行するイベントハンドラを登録する.
			// イベントハンドラの名前はなんでもいい.
			// MainWindowLoaded とか MyMainWindowLoaded とか.
			Loaded += MainWindow_Loaded;

			this.Title = "GazoYaro";

		}

		private void MainWindow_Loaded(object sender, RoutedEventArgs e)
		{

			// ウインドウズのメッセージフックが使えるようにする. 
			HwndSource source = HwndSource.FromHwnd( new WindowInteropHelper( this ).Handle );
			source.AddHook( new HwndSourceHook( WndProc ) );

			// NotifyInterrupt() に使う引数.
			int hwnd = (int)( this.Handle );

			// 論理ライズ割り込み(アップエッジ)の検出を開始する.
			// 論理電源電圧 → ゼロボルト.
			short interrupt_kind0 = (short)( CdioConst.DIO_INT_RISE );
			int iret0 = MyDio.NotifyInterrupt( MyId, INTERRUPT_BIT_NUMBER_INP00, interrupt_kind0, hwnd );

			// 論理フォール割り込み(ダウンエッジ)の検出を開始する.
			// ゼロボルト → 論理電源電圧.
			short interrupt_kind1 = (short)( CdioConst.DIO_INT_FALL );
			int iret1 = MyDio.NotifyInterrupt( MyId, INTERRUPT_BIT_NUMBER_INP01, interrupt_kind1, hwnd );

			if ( iret0 != (int)( CdioConst.DIO_ERR_SUCCESS ))
			{
				MessageBox.Show( "Error: MyDio.NotifyInterrupt(INTERRUPT_BIT_NUMBER_INP0); rise." );
			}

			if ( iret1 != (int)( CdioConst.DIO_ERR_SUCCESS ))
			{
				MessageBox.Show( "Error: MyDio.NotifyInterrupt(INTERRUPT_BIT_NUMBER_INP1); fall." );
			}

		}

		protected override void OnRender( DrawingContext dc )
		{

			// カウンタの全要素の内容を参照して表示用ストリングビルダに追加する.
			StringBuilder sb = new StringBuilder();

			sb.AppendLine( "INTERRUPT" );

			foreach( var v in ListCounter.Select(( value, index ) => new { value, index })) 
			{
				int idx = v.index;
				int tmp = v.value;
				String s = String.Format( "[{0:d2}]\t{1}", idx, tmp );
				sb.AppendLine( s );
			}

			// テキストブロックにすべてのビットのカウンタを表示する.
			String str = sb.ToString().Trim();
			TheTbk.Text = str;

			this.stbLabel000.Content = DEV_NAME_DIO;

		}

		private void menuApplicationQuit_Click( object sender, RoutedEventArgs e )
		{

			// DIOを終了する.
			MyDio.Exit( MyId );

			// ウインドウを閉じてアプリケーション終了する.
			this.Close();

		}

	}

}

入力 INP0(35番ピン)、INP1(34番ピン) に接続されたセンサが反応したら、上から2つの数値が増えていくはずです。

INP0 は、センサがオンした瞬間に数値が増えます。これは割り込み信号の RISE に当たります。
INP1 は、センサがオフした瞬間に数値が増えます。これは割り込み信号の FALL に当たります。

「トリガ」の記事と同様に、本記事の「割り込み」のプログラムをZIPアーカイブで用意しました。ぜひ、下記のページからダウンロードしてビルド実行して動作確認することをお勧めします。パソコンと外の世界がつながると楽しいですよ。