このページは「24環境」の情報です

第7回 ハードウェア入門1.DIOの基礎の基礎

海老原祐太郎
2002/9/1

最近ロボットコンテストが盛んですネ。
自分のプログラムでメカを自由自在に動かすってのは男のロマンですよね。(違うか?笑)
メカを動かすためにはハードの知識が必要になってきますので簡単にハードの勉強をしてみます。

0と1をハードウェアで表す

タイトルの「DIOの基礎の基礎」のDIOとはデジタルI/Oの略です。
ソフト屋さんにはおなじみの 0と1は、デジタル回路では電圧が高い(H)か、低い(L)かで
あらわします。電圧が高いほう(Hレベル)を1、低いほう(Lレベル)を0とあらわします。

0V〜0.8Vを「L」、2V〜5Vを「H」を意味すると取り決めたものを「TTLレベル」と呼びます。
TTLレベルの理解がハードウェア入門の第一歩です。

TTLレベル出力

dio1.gif
デジタル出力ポート(TTLレベル出力)
出力端子から電流が流れ出ます(吐き出し電流)
出力がLのときは0V(に近い)電圧が出ます。
出力端子に向かって電流が流れ込みます(吸い込み電流)

出力なのに「L出力の時は電流が入ってくる」、というのは今はピンと来ないと思いま すが、
のちのち説明しますので今はそーゆーもんだ。と読んでおけばいいです。

じゃ、早速実験してみます。CAT68701にはTTLレベル出力ポートが13ビット分あります。

paraport.gif

出力ポート0の電圧(上記緑印)をテスターで測ってみます。
出力をHやLに切り替えるデバイスドライバは後で説明します。

dio3.jpg
「L」出力の電圧 0.1mv
dio4.jpg
「H」出力の電圧 5.11V
(電圧が5Vを超えてるのはそもそも電源が5.11V位なのだろう。汗)

TTLレベル入力

dio2.gif
デジタル入力ポート(TTLレベル入力)

入力端子に 2V〜5Vの電圧を入力すると「H」と認識されます。
入力端子に 0V〜0.8Vの電圧を入力すると「L」と認識されます。  
paraport2.gif
dio6.jpg
IP-0に「L」レベルを入力する
入力ポート表示プログラム(後述)の出力

1111011111111110
              ^ここが0になってる
dio5.jpg
IP-0に「H」レベルを入力する
入力ポート表示プログラム(後述)の出力

1111011111111111
                ^ここが1になってる

ソフトウェアとハードウェアの連結

ここでもう一度下の図を見てください。真ん中の 2列になっている端子の図は今までと同じです。

paraport3.gif

出力ポートは0xb4007400番地に割り当てられています。
0xb4007400番地のビット0〜10に 0あるいは1の値を書き込むことで、
出力ポートからL/Hレベルの電圧を出力することができます。
C言語的に書くとこうなります。

*((unsigned volatile short *)0xb4007400L) = data;~ [#c749cc86]

入力ポートはアドレス 0xb4007000番地に割り当てられています。
0xb4007000番地のビット0〜13の値を読み込むことで、
入力ポートのL/Hレベルを0/1として読み取ることできます。
C言語的に書くとこうなります。

data = *((unsigned volatile short *)0xb4007000L);~

入出力デバイスドライバ

単に

*((unsigned volatile short *)0xb4007400L) = data;~ [#u9844ee1]

と書けば 0xb4007400番地にマッピングされている出力ポートにL/Hレベルを
出力できそうなものですが、Linuxでは絶対番地をアクセスするためには
デバイスドライバを書いてカーネル空間で動かす必要があります。

/*
 * CAT68701 internal pararel port driver "para.c"
 *   for linux-2.4.x siries kernel
 *   Copyright Feb/2000 Linux-koubou (si-linux.com)
 *   written by Y.Ebihara
 */
#define MODULE
#define __KERNEL__ 

#define DEFAULT_MAJOR_NUM   242
#define DEVNAME             "para"
#include "paradrv.h"

#include <linux/module.h>
#include <linux/fs.h>
#include <asm/uaccess.h> 

static unsigned int para_major=DEFAULT_MAJOR_NUM;
static unsigned short para_temp=0;


/* -------------------------------------------------------- */
/* file operation */
/* -------------------------------------------------------- */

static int para_open(struct inode *inode, struct file *filp){
  int minor=MINOR(inode->i_rdev);
  if(minor != 0){
    printk("<1>para open() error");
    return -1;
  }
  MOD_INC_USE_COUNT;
  return 0;
}

static int para_close(struct inode *inode, struct file *filp){
  MOD_DEC_USE_COUNT;
  return 0;
}
 

static int para_read(struct file *filp, char *user_buf,size_t count,loff_t *ppos){
  unsigned short data;
  data = *((unsigned volatile short *)0xb4007000L); // <−−ここでハードから入力している
  copy_to_user(user_buf,&data,2);  /* 2byte */
  filp->f_pos += 2;
  return 2;
}

static int para_write(struct file *filp, const char *user_buf,size_t count,loff_t *ppos){
  unsigned short data;
  copy_from_user(&data,user_buf,2);
  *((unsigned volatile short *)0xb4007400L) = data; // <−−ここでハードへ出力している
  para_temp = data;   /* save current output data */
  filp->f_pos += 2;
  return 2;
}

static int para_ioctl(struct inode *inode,
                         struct file *filp,
                         unsigned int request,
                         unsigned long arg){
  int minor=MINOR(inode->i_rdev);
  if(minor != 0){
    printk("ioctl error\n");
    return -1;
  }

  switch(request){
    case PARA_GETOUTPUTDATA:
      return para_temp;
  }
  return 0;
}

static struct file_operations para_fops = {
  open: para_open,      /* open */
  read: para_read,      /* read */
  write: para_write,    /* write */
  ioctl: para_ioctl,    /* ioctl */
  release: para_close   /* close */
};

/* -------------------------------------------------------- */
/* driver module load / unload */
/* -------------------------------------------------------- */

static int init_module(void){
  printk("<1>para hello\n");
  if(register_chrdev(para_major,DEVNAME,& para_fops) != 0){
    printk("<1>para register fail\n");
    unregister_chrdev(para_major,DEVNAME);
    return -1;
  }
  return 0;
}

static void cleanup_module(void){
  printk("<1>para goodby\n");
  if(unregister_chrdev(para_major,DEVNAME) != 0){
    printk("<1>para unregister fail\n");
  }
}

コンパイル

$ sh3-linux-gcc -I/カーネルソースを展開したディレクトリ/include -O2 -c  paradrv.c
 
以下は CAT68701 で
# insmod paradrv.o
# cat /proc/devices
Character devices:
<略>
242 para     キャラクタデバイス242番が確認できればOK
# mknod /dev/cat68701-para c 242 0

ドライバを呼び出すユーザー空間プログラム

入力のサンプル (prog1a.c)

#include <stdio.h>
#include <fcntl.h>
#include "paradrv.h"
int main(){
  int fd;
  unsigned char data[2];
  fd=open("/dev/cat68701-para",O_RDWR);  // ←デバイスドライバを開く
  if(fd<0){
    perror("");
    exit();
  }
  while(1){
    unsigned short s;
    int i;
    read(fd,data,2);  // ← デバイスドライバをコール(2バイト=16bit リード)
    s = (((unsigned short)data[1]<<8)&0xff00) + ((unsigned short)data[0] &  0xff);

    for(i=15; i>=0; i--)
      printf("%d",(s>>i)&1);
    printf("\n");
    sleep(1);
  }
  close(fd);
 }

出力のサンプル (prog2.c)

#include <stdio.h>
#include <fcntl.h>
#include "paradrv.h"

int main(){
  int fd,i;
  unsigned short data,old;

  fd=open("/dev/cat68701-para",O_RDWR);  // ←デバイスドライバを開く
  if(fd<0){
    perror("");
    exit();
  }
  while(1){
    for(i=0; i<16; i++){
      old=ioctl(fd,PARA_GETOUTPUTDATA,0);  // ←ioctlで現在出力中の値をリード
      printf("olddata=%04x ",old);

      data = 1 << i;
      printf("newdata=%04x \n",data);
      write(fd,&data,2); // ← デバイスドライバをコール(2バイト=16bit ライト)
      sleep(3);
    }
  }
  close(fd);
}

prog1a.c や prog2.c は 普通に main() で始まるプログラムですからこれを改造して
好きな動きを与えてください。使い方は open() した時にもらえる fd を使って
read() (入力)や write() (出力)すればOKです。

今回のゴールは、ソフトウェアから出力ポートにH/Lレベルの電圧を出してみたり、
入力ポートの電圧を0/1として取り込んだりできるようになれば目標達成です。
実際にテスターで当たって確認を取ってください。

次回はもうちょびっとハード寄りの話をします。