7セグメントLED表示器のデバイスドライバを作成しました

あけましておめでとうございます。
今年も、暇を見つけては少しずつ何か作ったりしてみたいと思っています。
よろしくお願いします。



さて、今回は前回の記事(id:cupnes:20121231:1356955476)で調査していた7セグメントLED表示器の、カソードコモンのものである「R362T」のLinuxデバイスドライバを作成してみました。
といっても、赤色LEDについて試した時(id:cupnes:20121231:1356945578)のものに、少し手を加えた程度です。

I/Oポートの接続について

7セグメント表示器の各LEDには、A、B、C、D、E、F、G、DPと名前がついています。
このLEDの名前と7セグメントLEDのピン番号、T-SH7706LSR側のピン番号とSH7706におけるI/Oポート番号を以下の表に示します。
なお、T-SH7706LSR側のピン番号は、全てCN2 PIOのものです。

LEDの名前 A B C D E F G DP
7セグメントLEDのピン番号 7 6 4 2 1 9 10 5
T-SH7706LSR側のピン番号 39 40 41 42 31 32 33 34
I/Oポート PA3 PA2 PA1 PA0 PB3 PB2 PB1 PB0


抵抗は、今回の実験では300Ωを、7セグメントLED表示器の3番ピンとT-SH7706LSRの44番ピン(GND)の間に繋いでいます。
なお、7セグメント表示器側のGNDは3番ピン以外に8番ピンも使用できます。

デバイスドライバソースコード

ファイルの配置なども以前の記事(id:cupnes:20121231:1356945578)と同様で、今回必要なファイルは以下の2つです。


コンパイル方法なども、ファイルの名前が変わった程度で、以前の記事(id:cupnes:20121231:1356945578)と同様です。

「led7seg.c」
#include <linux/init.h>
#include <linux/module.h>
#include <linux/ioport.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/cdev.h>

#define LED7SEG_MAJOR	243
#define LED7SEG_MINORS	1
#define LED7SEG_NAME		"shminled7seg"

#define PADR 0xA4000120									/*  8bit access */
#define PACR 0xA4000100									/* 16bit access */
#define PBDR 0xA4000122									/*  8bit access */
#define PBCR 0xA4000102									/* 16bit access */

/*
     A
  -------
 |       |
F|       |B
 |   G   |
  -------
 |       |
E|       |C
 |   D   |
  -------  .DP
*/
#define SEG_A	0x80
#define SEG_B	0x40
#define SEG_C	0x20
#define SEG_D	0x10
#define SEG_E	0x08
#define SEG_F	0x04
#define SEG_G	0x02
#define SEG_DP	0x01

static struct cdev led7segdev;

static int
led7seg_open(struct inode *inode, struct file *file)
{
	return 0;
}

static int
led7seg_release(struct inode *inode, struct file *file)
{
	return 0;
}

static void led7seg_set7Seg(unsigned char pattern)
{
		unsigned char pc_pat, pd_pat, tmp;

		pc_pat = (pattern >> 4) & 0x0F;
		pd_pat = pattern & 0x0F;

		tmp = __raw_readb(PADR) & 0xF0;
		__raw_writeb(tmp | pc_pat,PADR);

		tmp = __raw_readb(PBDR) & 0xF0;
		__raw_writeb(tmp | pd_pat,PBDR);
}

static void led7seg_setNumber(unsigned char num)
{
		switch(num){
		case 0:
				led7seg_set7Seg(SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F);
				break;
		case 1:
				led7seg_set7Seg(SEG_B | SEG_C);
				break;
		case 2:
				led7seg_set7Seg(SEG_A | SEG_B | SEG_D | SEG_E | SEG_G);
				break;
		case 3:
				led7seg_set7Seg(SEG_A | SEG_B | SEG_C | SEG_D | SEG_G);
				break;
		case 4:
				led7seg_set7Seg(SEG_B | SEG_C | SEG_F | SEG_G);
				break;
		case 5:
				led7seg_set7Seg(SEG_A | SEG_C | SEG_D | SEG_F | SEG_G);
				break;
		case 6:
				led7seg_set7Seg(SEG_A | SEG_C | SEG_D | SEG_E | SEG_F | SEG_G);
				break;
		case 7:
				led7seg_set7Seg(SEG_A | SEG_B | SEG_C);
				break;
		case 8:
				led7seg_set7Seg(SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F | SEG_G);
				break;
		case 9:
				led7seg_set7Seg(SEG_A | SEG_B | SEG_C | SEG_D | SEG_F | SEG_G);
				break;
		}
}

static void led7seg_setDP(void)
{
		led7seg_set7Seg(SEG_DP);
}

static void led7seg_setLightDown(void)
{
		led7seg_set7Seg(0x00);
}

static ssize_t led7seg_write(struct file *file, const char __user *buf, size_t count, loff_t *offset)
{
	unsigned char led7segdata;

	get_user(led7segdata,buf);

	if('0' <= led7segdata && led7segdata <= '9'){
			led7seg_setNumber((unsigned char)(led7segdata - '0'));
	}else if(led7segdata == '.'){
			led7seg_setDP();
	}else if(led7segdata == '-'){
			led7seg_setLightDown();
	}

	(*offset)++;
	return 1;
}

struct file_operations led7seg_fops = {
	.owner = THIS_MODULE,
	.open = led7seg_open,
	.write = led7seg_write,
	.release = led7seg_release,
};

static int __init led7seg_init(void)
{
	dev_t dev = MKDEV(LED7SEG_MAJOR, 0);
	int ret;

	cdev_init(&led7segdev, &led7seg_fops);

	ret = cdev_add(&led7segdev, dev, LED7SEG_MINORS);
	if (ret){
		printk(KERN_WARNING "shminled7seg: device add failed7seg.\n");
		goto err0;
	}
	__raw_writew((__raw_readw(PACR)&~0x00FF)|0x0055,PACR); /* PA0-3 output */
	__raw_writew((__raw_readw(PBCR)&~0x00FF)|0x0055,PBCR); /* PB0-3 output */

	return 0;

err0:
	unregister_chrdev_region(dev, LED7SEG_MINORS);

	return ret;

}

static void __exit led7seg_exit(void)
{
	dev_t dev = MKDEV(LED7SEG_MAJOR, 0);

	__raw_writeb(__raw_readb(PADR)&~0x0F,PADR); /* PA0DT-PA3DT =0 */
	__raw_writeb(__raw_readb(PBDR)&~0x0F,PBDR); /* PB0DT-PB3DT =0 */

	cdev_del(&led7segdev);

	unregister_chrdev_region(dev, LED7SEG_MINORS);

}

module_init(led7seg_init);
module_exit(led7seg_exit);

MODULE_LICENSE("GPL");
Makefile
KERNELDIR ?= ../linux-2.6.39.4
PWD := $(shell pwd)

obj-m := led7seg.o

all:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules

clean:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) clean

使用方法

カーネルオブジェクトのコピー」などは、以前の記事(id:cupnes:20121231:1356945578)と同様ですので、省略します。

デバイスドライバの追加
# mknod /dev/shminled7seg c 243 0
# insmod led7seg.ko
# echo 0 > /dev/shminled7seg    # 「0」を表示
# echo 1 > /dev/shminled7seg    # 「1」を表示
# echo . > /dev/shminled7seg    # 「.」を表示
# echo - > /dev/shminled7seg    # 消灯


数字の「0」〜「9」までと「.(ドット)」は、それぞれ書き込むとその数字、あるいはドットが表示されます。
また、消灯の際には「-」を書き込んでください。

「test7seg.sh」

デバイスドライバのテスト用に、1秒毎にすべての表示パターンを試すスクリプトを作成しました。
全体を3回のforループで囲んでいますので、1秒毎の全表示パターン確認動作を3回繰り替えして終了します。
ターゲットへコピーしてご使用ください。

#!/bin/sh
for i in 1 2 3; do
        for chr in 0 1 2 3 4 5 6 7 8 9 . -; do
                echo "$chr" > /dev/shminled7seg
                echo "$chr"
                sleep 1s
        done
done


おまけですが、このスクリプトによる動作確認の風景を撮影しました。

T-SH7706LSRのCN2 3番ピンのハイ/ローを制御するデバイスドライバを作成

T-SH7706LSRのCN2の3番ピンのハイ/ローを制御するデバイスドライバを作成しました。


主に、前回の記事(id:cupnes:20121229:1356786112)で参考にした、
http://wave2.iobb.net/doc/summary/sh3wiki/wifky.cgi?p=LED%C0%A9%B8%E6%A5%C9%A5%E9%A5%A4%A5%D0%A4%CE%BA%EE%C0%AE
を元に作成しました。T-SH7706LSRにおいてCN2の3番ピンへつながっているポートD1を制御します。

ソースコード

必要なものは、以下の2つです。

「ptd1.c」
#include <linux/init.h>
#include <linux/module.h>
#include <linux/ioport.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/cdev.h>


#define PTD1_MAJOR		242
#define PTD1_MINORS	1
#define PTD1_NAME		"shminptd1"

#define PDDR 0xA4000126									/*  8bit access */
#define PDCR 0xA4000106									/* 16bit access */

static struct cdev ptd1dev;

static int
ptd1_open(struct inode *inode, struct file *file)
{
	return 0;
}

static int
ptd1_release(struct inode *inode, struct file *file)
{
	return 0;
}

static ssize_t ptd1_write(struct file *file, const char __user *buf, size_t count, loff_t *offset)
{
	unsigned char ptd1data;

	get_user(ptd1data,buf);

	if(ptd1data=='0') 
		__raw_writeb(__raw_readb(PDDR)&~0x02,PDDR); /* PD1DT=0 */
	if(ptd1data=='1') 
		__raw_writeb(__raw_readb(PDDR)|0x02,PDDR); /* PD1DT=1 */
	(*offset)++;
	return 1;
}

struct file_operations ptd1_fops = {
	.owner = THIS_MODULE,
	.open = ptd1_open,
	.write = ptd1_write,
	.release = ptd1_release,
};

static int __init ptd1_init(void)
{
	dev_t dev = MKDEV(PTD1_MAJOR, 0);
	int ret;

	cdev_init(&ptd1dev, &ptd1_fops);

	ret = cdev_add(&ptd1dev, dev, PTD1_MINORS);
	if (ret){
		printk(KERN_WARNING "shminptd1: device add faiptd1.\n");
		goto err0;
	}
	__raw_writew((__raw_readw(PDCR)&~0x000C)|0x0004,PDCR); /* PD1 output */

	return 0;

err0:
	unregister_chrdev_region(dev, PTD1_MINORS);

	return ret;

}

static void __exit ptd1_exit(void)
{
	dev_t dev = MKDEV(PTD1_MAJOR, 0);

	__raw_writeb(__raw_readb(PDDR)&~0x02,PDDR); /* PD1DT=0 */

	cdev_del(&ptd1dev);

	unregister_chrdev_region(dev, PTD1_MINORS);

}

module_init(ptd1_init);
module_exit(ptd1_exit);

MODULE_LICENSE("GPL");
Makefile
KERNELDIR ?= ../linux-2.6.39.4
PWD := $(shell pwd)

obj-m := ptd1.o

all:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules

clean:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) clean

コンパイル

これら2つのファイルを以下の様に配置します。「linux-2.6.39.4/」はカーネルのソースディレクトリです。


以下の様にコマンドを実行します。なお、ここでは上記した「linux-2.6.39.4」と「ptd1」の2つのディレクトリが「~/work/」にあるものとします。

$ cd ~/work/ptd1/
$ make ARCH=sh CROSS_COMPILE=sh3-linux-


何も問題が無ければ、

ptd1.ko

というファイルが生成されるはずです。

使用方法

生成した「ptd1.ko」をT-SH7706LSRで起動するLinuxのためのファイルシステムを作成したSDカードへコピーします。
MES(Micro Embeded System)
にある「SH3/Linux」の「メモリカードセットアップ」にある通りの作業が終了しているものとします。(SDカードの第2パーティションファイルシステムがインストール済みであるとします。)

カーネルオブジェクトをターゲットへコピー

「/dev/sdb2」がSDカードの第2パーティションを指しているとすると、以下の様に作業してください。また、マウントポイント「/mnt/storage/」は例です、適宜作成、読み替えなど行ってください。

$ sudo mount /dev/sdb2 /mnt/storage
$ sudo cp ~/work/ptd1/ptd1.ko /mnt/storage/root/
$ sudo umount /mnt/storage
デバイスドライバの使用

「ptd1.ko」のコピーが完了した後、SDカードを取り外し、再度T-SH7706LSRへ挿入して、ターゲットでLinuxを起動、ログインしてください。


以下の様にコマンドを実行すると、ポートD1の制御が出来ます。

# mknod /dev/shminptd1 c 242 0
# insmod ptd1.ko
# echo 1 > /dev/shminptd1    # ポートD1をハイに設定
# echo 0 > /dev/shminptd1    # ポートD1をローに設定
備考

http://japan.renesas.com/products/mpumcu/superh/sh7700/sh7706/Documentation.jsp#type44Om44O844K244O844K644Oe44OL44Ol44Ki44OrOiDjg4!jg7zjg4njgqbjgqfjgqI=
ルネサスエレクトロニクス株式会社の公開している「SH7706」のハードウェアマニュアルによると、
「表24.2 DC特性」より

  • 全出力端子の出力ハイレベル電圧(V_OH)
    • min 2.4V (@V_CCQ=3.0V、I_OH=-200マイクロA)
    • min 2.0V (@V_CCQ=3.0V、I_OH=-2mA)
  • 全出力端子のローレベル電圧(V_OL)
    • max 0.55V (@V_CCQ=3.6V、I_OL=1.6mA)

また、「表24.3 出力許容電流値」より

  • 出力ローレベル許容電圧(1端子当たり)(I_OL)
    • max 2.0mA
  • 出力ハイレベル許容電圧(1端子当たり)(-I_OH)
    • max 2.0mA

とあるので、順方向電圧2V程の赤色LEDで、300Ωの抵抗を使用しています。

Linux-2.6.39.4のT-SH7706LSR向けコンパイル

家に転がっていた「T-SH7706LSR」のボードでLinuxを動かして、デバイスドライバ等、組込みLinux開発見たいな事をしたいと思い立って、早数ヶ月。
そもそもの、Linuxカーネルコンパイルがうまくいかず、いろいろ試していたのですが、今回、linux-2.6.39.4でクロスコンパイルが出来たので、ここにメモしておきます。

ホスト側の環境

といっても、ウィンドウマネージャなど、いろいろと変えているので、Ubuntuに固有の事柄は特にないと思います。

ホスト側の準備

http://mes.sourceforge.jp/mes26/lin_cross.html
こちらの記事の、1と2、必要であれば4の作業を行いました。
Eclipseは使用しないので、Eclipseに関する作業は特に行っていません。


なお、必要なファイルはこちらから。
http://mes.sourceforge.jp/mes26/lin_file.html


上気の2つのページは、それぞれこちらのページのものです
http://mes.sourceforge.jp/mes26/
左側のフレームの「SH3/Linux」へ行き、
同じく左側フレームの「クロスコンパイラ」、「ダウンロード/新版」からそれぞれのページへ行けます。

必要なファイルのダウンロード

先ほどダウンロードを行ったこちらのページから
http://mes.sourceforge.jp/mes26/lin_file.html
以下の2つのファイルをダウンロードします。

  • linux-2.6.39.4.tar.bz2
  • linux-2.6.39.4-shmin-1.patch
    • リンクの名前は「linux-2.6.39.1-shmin-1.patch」となっていますが、実際に参照しているファイルはこの名前です。


ここでは、これらのファイルを以下の場所へダウンロードしたとします。

~/work/

カーネルソースの展開とパッチの適用

コマンドライン上で以下の作業を行います。

$ cd ~/work/できました。
$ tar jxf linux-2.6.39.4.tar.bz2
$ cd linux-2.6.39.4/
$ patch -p1 < ../linux-2.6.39.1-shmin-1.patch

コンパイル

makeの前に環境変数の設定を行います。

$ export PATH="/usr/sh3-linux/bin:$PATH"
$ export ARCH=sh
$ export CROSS_COMPILE=sh3-linux-


makeします。

$ cd ~/work/linux-2.6.39.4/
$ make


makeが完了すると、
「vmlinux」できました。
というファイルが出来上がっているはずです。

参考

本作業に関して、主に以下の記事を参考にしました。
なお、記事の中ではMakefileの修正を行っていますが、linux-2.6.39.4に関しては、修正を行わずともmakeすることができました。
http://wave2.iobb.net/doc/summary/sh3wiki/wifky.cgi?p=kernel%A4%CE%BA%C6%B9%BD%C3%DB