LoginSignup
1
0

NSLで回路を作ろう#03: キーボード,マウスの入力を受け取る。

Last updated at Posted at 2024-01-10

【改訂2版】FPGAボードで学ぶ 組込みシステム開発入門[Intel FPGA編]の第7章に載っているPS/2通信でキーボードやマウス入力を受け取る回路をハードウェア記述言語のNSLで作ってみよう。

1. 回路について

キーボードとマウス入力を受け取るインターフェースとしてPS/2コネクタ - Wikipediaがある、この規格はシンプルなので簡単に実装できるそうだ。

そこで、キーボードとマウス入力を受け取り前記事で扱った、NiosII CPUを使ったプログラムで受け取った入力を表示してみる。

例によってハードウェアとソフトウェア、そしてハードウェアではNSLのコーディングとNiosIIのシステム構築にわかれる。

ハードウェア

  • ps2if_ip
    • PS/2通信とAvalonバスの回路を実装し、NiosII CPUとPS/2通信の中継を担う。
  • mypio_ip
    • CPUから送られたキーボードやマウス入力の値を7セグメントLEDや単色赤色LEDに出力させる。
    • 前記事で作成した回路の拡張
  • NiosII のシステム構築
    • 以下のコンポーネントを使用する
      • NiosII CPU
      • On Chip Memory
      • JTAG UART
      • System ID
      • ps2if_ip
      • mypio_ip
  • 最上位層
    • PS2の入出力端子を接続するのに一工夫が必要。

ソフトウェア

  • 1 キーボード入力
    • キーボード入力を受け取り、文字を出力するプログラム。
  • 2 マウス入力その1
    • マウスの左右のクリックと、縦横の移動量を受け取るプログラム。
  • 3 マウス入力その2
    • 2で作成したプログラムを拡張してスクロールの入力を受け取るプログラム。

2. 作ってみる

  • 雑談
    PS/2は最近のPCだと接続口がない場合が多いが、このサイトによると、
    細々とした需要があるため、完全に廃れたコンテンツ、というわけではないようだ。

PS/2キーボードが無くならない3つの理由

  1. ゲーム / ゲーミングPC(USBキーボードでは、7つ以上のキーを同時押しができない、参考
  2. セキュリティ(USBメモリを使わせたくない現場用、参考
  3. 組込み開発が容易(IchigoJamなど、低スペック低価格コンピューターでも使える)

今回の場合は3.の組込み開発が容易に該当するのであろうか。

2.1. ps2if_ip

まずは、デバイスのPS/2通信を受け取ってAvalonバスでCPUとデータをやり取りする回路を作る。
このモジュールでは値の送受信処理を行う。

キーボード入力を文字に変換する処理や、マウス入力からXY軸の移動量を取出す処理はソフトウェア側で行う。

レジスタ表

回路のレジスタを以下のように定義する

  • 状態レジスタ(PS2STATUS) 以下のbitを持つ

    • PS2EMPTY: 送信レジスタが空のとき1
    • PS2VALID: 受信データが有効なら1
  • 受信データ(PS2RDATA)

  • 送信データ(PS2WDATA)

  • 以下の表を参照
    ps2if-regmap01.png

送受信の仕様

NiosII CPUが値を受信するときは

  • PS2VALIDが1になるまで待機。
  • PS2RDATAの値を読込む。
  • PS2VALIDの値を0に設定しなおす。

を行い、送信するときは

  • PS2EMPTY が1になるまで待機。
  • PS2WDATAに値を書き込む。
    を行い、PS2WDATAに値を書き込んだ時に送信が開始される。

内部ではシフトレジスタを用い、
受信時ではMSB側からシフトしていく、
シフトしていくタイミングはPS2CLKの信号が立ち下がったときだが、
信号自体をクロックとして扱うのではなく、立下り検出回路を設けて、イネーブル制御を行う。

内部の制御(ステートマシン)

送受信の動作はステートマシンを用いて制御を行う。
初期状態で以下の入力を受け取ったらそれぞれの処理を行う

  • 受信処理開始: デバイスからスタートbitを受け取った
  • 送信処理開始: 送信レジスタに値が書き込まれた

状態遷移図は以下のような感じ。

各状態では以下の動作を行う

  • HALT
    • 初期状態
  • GETBIT
    • データを受信する
  • SETFLG
    • フラグbit(PS2VALID)を1にする
  • WAITCLK
    • クロックの立下りを待つ
  • CLKLOW
    • クロック立下りを出力
  • STBIT
    • スタートbit送信
  • SENDBIT
    • データ送信

記述ファイル

今回作成したNSLファイルを以下に示す。(長いので折りたたみ)

処理部分

ヘッダファイル

#ifndef _PS2IF_NSH
#define _PS2IF_NSH

declare ps2if {
    
    input  address[2];
    input  write;
    input  read;
    input  writedata[8];
    output readdata[8];

    input  ps2clk;
    input  ps2data;

    output ps2clk_out;
    output ps2data_out;
    output logclk;

    output ps2clken;
    output ps2dataen;
}

#endif // _PS2IF_NSH

サンプルの回路では
ステートマシンのレジスタの値を直接見てassign文を記述していたが
NSLでは値を参照できないので、func信号で代用。

今更思ったこと

今思うと値参照している信号をfunc信号にすれば良かった説

  • 信号毎に設定するのはNSLコーディングらしくない
    • 機能ごとにコーディングできるのがNSLの強みだからね…

モジュール

#include "ps2if.nsh"

module ps2if {
	state_name HALT, CLKLOW, STBIT, SENDBIT, WAITCLK, GETBIT, SETFLG;
	func_self isHALT(), isCLKLOW(), isSTBIT(), isSENDBIT(), isWAITCLK(), isGETBIT(), isSETFLG();
	
	reg ps2clken_reg  = 0;
	reg ps2dataen_reg = 0;

	// シフトレジスタ、受信レジスタ、フラグ類(reg宣言) 
	reg sft[10] = 0;
	reg ps2rdata[8] = 0;
	reg empty = 1;
	reg valid = 0;
	// 送信時のシフトレジスタ書き込み信号 
	wire txregwr;
	// スタートビット送出用100μs計時用カウンタ 
	reg txcnt[13] = 0;

	wire over100us;
	// 受信したPS2CLKの立ち下がり検出および同期化 
	reg  sreg[3] = 0;
	wire clkfall;
	// 送受信ビット数カウンタ 
	reg bitcnt[4] = 0;

	// SignalTap II用クロック(1MHz) 
	reg logcnt[5] = 0;
	reg logclk_reg = 0;

	wire cntend;

	

	txregwr = (address==2'h2) & write;

	// レジスタ読み出し 
	readdata =  if(read == 0) 8'b0 
	else if(address==2'h0)  {6'b0, empty, valid} else ps2rdata;


	// PS2出力のハザード防止 
	ps2clken_reg  := (isCLKLOW || isSTBIT);
	ps2dataen_reg := (isSENDBIT || isSTBIT);

	ps2clken  = ps2clken_reg;
	ps2dataen = ps2dataen_reg;
	// PS2出力 
	ps2clk_out  = if(ps2clken_reg) 1'b0  else 1'bz;
	ps2data_out = if(ps2dataen_reg) sft[0] else 1'bz;

	over100us = (txcnt==13'd4999);
	alt {
		(isHALT):{
			txcnt := 0;
		}
		(over100us):{
			txcnt := 0;
		}
		else:{
			txcnt++;
		}
	}

	sreg := {sreg[1:0], ps2clk};
	clkfall = sreg[2] & ~sreg[1];

	empty := if(isHALT) 1 else 0;
	alt {
		( (address==2'h0) & write ):{
			valid := writedata[0];
		}
		(isSETFLG && clkfall):{
			valid := 1;
		}
	}

	cntend = (logcnt==5'd24);

	logcnt := if(cntend) 0 else logcnt + 1;
	logclk_reg := if(cntend) ~logclk_reg else logclk_reg;
	logclk = logclk_reg;

	state HALT {
		isHALT();
		any {
			( txregwr ):{
				sft := { ~(^writedata), writedata, 1'b0 };
				goto CLKLOW;
			}
			( (ps2data==1'b0) & clkfall ):{
				goto GETBIT;
			}
		}
		bitcnt := 4'b0;
	}

	state CLKLOW {
		isCLKLOW();
		if ( over100us )
		{
			goto STBIT;
		}
	}

	state STBIT {
		isSTBIT();
		if ( over100us )
		{
			goto SENDBIT;
		}
	}

	state SENDBIT {
		isSENDBIT();
		if ( (bitcnt==4'h9) & clkfall ){
			goto WAITCLK;
		}
		if ( clkfall ) {
			bitcnt++;
			sft := {1'b1, sft[9:1]};	
		}
	}

	state WAITCLK {
		isWAITCLK();
		if ( clkfall ){
			goto HALT;
		}
	}

	state GETBIT {
		isGETBIT();
		if ( (bitcnt==4'h7) & clkfall ){
			goto SETFLG;
		}
		if ( clkfall ){ 
			bitcnt++;
			sft := {ps2data, sft[9:1]};
		}
	}

	state SETFLG {
		isSETFLG();
		if ( clkfall ){
			ps2rdata := sft[9:2];
			goto WAITCLK;
		}
	}

}
  • logclkは Signal Tap でデバッグするときに使うクロックらしい。
最上位モジュール

クロック名の差異を埋めるためにinterfaceで実装。

ヘッダファイル

#ifndef _PS2IF_IP_NSH
#define _PS2IF_IP_NSH

#include "ps2if.nsh"

declare ps2if_ip interface {
	input   clk;
    input   reset;
    
    input   avs_address[2];
    input   avs_write;
    input   avs_read;
    input   avs_writedata[8];
    output  avs_readdata[8];

    input   coe_ps2clk;
    input   coe_ps2data;
    output  coe_logclk;

    output  coe_ps2clk_en;
    output  coe_ps2clk_out;
    
    output  coe_ps2data_en;
    output  coe_ps2data_out;

}
	
#endif // _PS2IF_IP_NSH

モジュール

#include "ps2if_ip.nsh"

module ps2if_ip {
    ps2if p;

    p.m_clock = clk;
    p.p_reset = reset;
    
    p.address = avs_address;
    p.write = avs_write;
    p.read = avs_read;
    p.writedata = avs_writedata;
    avs_readdata = p.readdata;

    p.ps2clk    = coe_ps2clk;
    p.ps2data   = coe_ps2data;

    coe_logclk = p.logclk;
    coe_ps2clk_en   = p.ps2clken;
    coe_ps2clk_out  = p.ps2clk_out;
    coe_ps2data_en  = p.ps2dataen;
    coe_ps2data_out = p.ps2data_out;
}

2.2. mypio_ip

次に、デバイスから受け取った値をLEDに出力する回路を作る。わざわざ別モジュールせずとも、ps2if_ipの方からLED出力してもよいと思うが、折角前記事で似たような回路を作ったのだからそれを再利用して効率よく作っていきたい。

レジスタ表

前記事ではスイッチ入力と7セグメントLEDの出力を表示するようにしていたが、
7セグメントLEDの表示オンオフを別アドレスのレジスタで管理するようにした。
また、ボタンクリックの情報を赤色LEDに出力させたいのでそこの値を管理するレジスタを追加する。

  • 以下の表を参照

mypio-regmap01.png

レジスタ部分

上記のレジスタ表に従い、アドレスによって読み書きするレジスタを切り替える。

レジスタ部分

ヘッダファイル

#ifndef _MYPIO_NSH
#define _MYPIO_NSH


declare mypio {

    input   address[2];
    input   write;
    input   read;
    input   writedata[32];
    output  readdata[32];
    
    input   sw_in[13];
    output  hex[32];
    output  hex_en[32];
    output  led_r[32];
}

#endif // _MYPIO_NSH

モジュール

#include "mypio.nsh"

module mypio {
    reg hex_reg[32]  = 32'h0000_0000;
    reg en_reg[32] = 32'h0000_0000;
    reg led_r_reg[32]  = 32'h0000_0000;
    if ( write ) {
        any {
            address==2'b00:{
                hex_reg := writedata;
            }
            address==2'b01:{
                en_reg := writedata;
            }
            address==2'b10:{
                led_r_reg := writedata;
            }
        }
    }
    if (read) {
        any {
            address == 2'b00 : {
                readdata = hex_reg;
            }
            address == 2'b01 : {
                readdata = en_reg;
            }
            address == 2'b10 : {
                readdata = led_r_reg;
            }
            address == 2'b11 : {
                readdata = {19'd0, sw_in};
            }
        }
    }else{
            readdata = 32'h0000_0000;
    }
    hex = hex_reg;
    hex_en = en_reg;
    led_r = led_r_reg;
}

デコーダ回路

CPUのプログラムから書き込まれてくる値は、4bit区切りの値なので
デコーダ回路を挟んで7セグメントLEDに出力する。

デコーダ回路

ヘッダファイル

#ifndef _SEG7DEC_NSH
#define _SEG7DEC_NSH

declare seg7dec {
	input din[4];
	output nHEX[7];
	func_in decode(din): nHEX;
}

#endif // _SEG7DEC_NSH

モジュール

#include "seg7dec.nsh"

module seg7dec {
	func decode {
		any {
			din==4'h0 : return 7'b1000000;
			din==4'h1 : return 7'b1111001;
			din==4'h2 : return 7'b0100100;
			din==4'h3 : return 7'b0110000;
			din==4'h4 : return 7'b0011001;
			din==4'h5 : return 7'b0010010;
			din==4'h6 : return 7'b0000010;
			din==4'h7 : return 7'b1011000;
			din==4'h8 : return 7'b0000000;
			din==4'h9 : return 7'b0010000;
			din==4'ha : return 7'b0001000;
			din==4'hb : return 7'b0000011;
			din==4'hc : return 7'b1000110;
			din==4'hd : return 7'b0100001;
			din==4'he : return 7'b0000110;
			din==4'hf : return 7'b0001110;
			else 	  : return 7'bx;
		}
	}
}

上位モジュール

Avalonバスの入出力や外部のLED出力、
レジスタ部分の回路とデコーダ回路
の間を配線する部分。

上位モジュール

ヘッダファイル

#ifndef _MYPIO_IP_NSH
#define _MYPIO_IP_NSH

#include "seg7dec.nsh"
#include "mypio.nsh"

struct tag_hex{
    w[7];
};

declare mypio_ip interface {
    input clk;
    input reset;

    input   avs_address[2];
    input   avs_write;
    input   avs_read;
    input   avs_writedata[32];
    output  avs_readdata[32];

    input   coe_sw_in[13];

    output  coe_led_r[10];

    output  coe_hex0[7];
    output  coe_hex1[7];
    output  coe_hex2[7];
    output  coe_hex3[7];
    output  coe_hex4[7];
    output  coe_hex5[7];
}

#endif // _MYPIO_IP_NSH

モジュール

  • インスタンスや構造体を配列のように宣言できる
  • 7セグメントLEDの各桁の配線をgenerate文でまとめて実装
#include "mypio_ip.nsh"


module mypio_ip {
    mypio m;
    seg7dec seg[6];

    integer i, lsb, msb;

    tag_hex wire hex[6];

    m.m_clock   = clk;
    m.p_reset   = reset;
    m.address   = avs_address;
    m.write     = avs_write;
    m.read      = avs_read;
    m.writedata = avs_writedata;

    m.sw_in  = coe_sw_in;
    
    avs_readdata  = m.readdata;
    coe_led_r = m.led_r[9:0];

    msb = 3;
    lsb = 0;
    generate(i = 0; i < 6; i++){
        seg[i].m_clock = clk;
        seg[i].p_reset = reset;
        hex[i] = if( m.hex_en[i] ) {seg[i].decode(m.hex[msb:lsb])} else 7'h7f;
        msb = msb + 4;
        lsb = lsb + 4;
    }
    
    coe_hex0 = hex[0];
    coe_hex1 = hex[1];
    coe_hex2 = hex[2];
    coe_hex3 = hex[3];
    coe_hex4 = hex[4];
    coe_hex5 = hex[5];
}

2.3. NiosIIシステムの構築

以下のファイルを用意しps2if_ipフォルダを作成。その中にNSLをVerilogにコンパイルしたファイルと一緒に入れておく。

ps2if_ip_hw.tcl
package require -exact qsys 16.1


# 
# module ps2if_ip
# 
set_module_property DESCRIPTION ""
set_module_property NAME ps2if_ip
set_module_property VERSION 1.0
set_module_property INTERNAL false
set_module_property OPAQUE_ADDRESS_MAP true
set_module_property AUTHOR ""
set_module_property DISPLAY_NAME ps2if_ip
set_module_property INSTANTIATE_IN_SYSTEM_MODULE true
set_module_property EDITABLE true
set_module_property REPORT_TO_TALKBACK false
set_module_property ALLOW_GREYBOX_GENERATION false
set_module_property REPORT_HIERARCHY false


# 
# file sets
# 
add_fileset QUARTUS_SYNTH QUARTUS_SYNTH "" ""
set_fileset_property QUARTUS_SYNTH TOP_LEVEL ps2if_ip
set_fileset_property QUARTUS_SYNTH ENABLE_RELATIVE_INCLUDE_PATHS false
set_fileset_property QUARTUS_SYNTH ENABLE_FILE_OVERWRITE_MODE false
add_fileset_file ps2if.v VERILOG PATH ps2if.v
add_fileset_file ps2if_ip.v VERILOG PATH ps2if_ip.v TOP_LEVEL_FILE


# 
# parameters
# 


# 
# display items
# 


# 
# connection point clock
# 
add_interface clock clock end
set_interface_property clock clockRate 0
set_interface_property clock ENABLED true
set_interface_property clock EXPORT_OF ""
set_interface_property clock PORT_NAME_MAP ""
set_interface_property clock CMSIS_SVD_VARIABLES ""
set_interface_property clock SVD_ADDRESS_GROUP ""

add_interface_port clock clk clk Input 1


# 
# connection point reset
# 
add_interface reset reset end
set_interface_property reset associatedClock clock
set_interface_property reset synchronousEdges DEASSERT
set_interface_property reset ENABLED true
set_interface_property reset EXPORT_OF ""
set_interface_property reset PORT_NAME_MAP ""
set_interface_property reset CMSIS_SVD_VARIABLES ""
set_interface_property reset SVD_ADDRESS_GROUP ""

add_interface_port reset reset reset Input 1


# 
# connection point avalon_slave_0
# 
add_interface avalon_slave_0 avalon end
set_interface_property avalon_slave_0 addressUnits WORDS
set_interface_property avalon_slave_0 associatedClock clock
set_interface_property avalon_slave_0 associatedReset reset
set_interface_property avalon_slave_0 bitsPerSymbol 8
set_interface_property avalon_slave_0 burstOnBurstBoundariesOnly false
set_interface_property avalon_slave_0 burstcountUnits WORDS
set_interface_property avalon_slave_0 explicitAddressSpan 0
set_interface_property avalon_slave_0 holdTime 0
set_interface_property avalon_slave_0 linewrapBursts false
set_interface_property avalon_slave_0 maximumPendingReadTransactions 0
set_interface_property avalon_slave_0 maximumPendingWriteTransactions 0
set_interface_property avalon_slave_0 readLatency 0
set_interface_property avalon_slave_0 readWaitTime 1
set_interface_property avalon_slave_0 setupTime 0
set_interface_property avalon_slave_0 timingUnits Cycles
set_interface_property avalon_slave_0 writeWaitTime 0
set_interface_property avalon_slave_0 ENABLED true
set_interface_property avalon_slave_0 EXPORT_OF ""
set_interface_property avalon_slave_0 PORT_NAME_MAP ""
set_interface_property avalon_slave_0 CMSIS_SVD_VARIABLES ""
set_interface_property avalon_slave_0 SVD_ADDRESS_GROUP ""

add_interface_port avalon_slave_0 avs_address address Input 2
add_interface_port avalon_slave_0 avs_write write Input 1
add_interface_port avalon_slave_0 avs_read read Input 1
add_interface_port avalon_slave_0 avs_writedata writedata Input 8
add_interface_port avalon_slave_0 avs_readdata readdata Output 8
set_interface_assignment avalon_slave_0 embeddedsw.configuration.isFlash 0
set_interface_assignment avalon_slave_0 embeddedsw.configuration.isMemoryDevice 0
set_interface_assignment avalon_slave_0 embeddedsw.configuration.isNonVolatileStorage 0
set_interface_assignment avalon_slave_0 embeddedsw.configuration.isPrintableDevice 0


# 
# connection point conduit_end_0
# 
add_interface conduit_end_0 conduit end
set_interface_property conduit_end_0 associatedClock clock
set_interface_property conduit_end_0 associatedReset reset
set_interface_property conduit_end_0 ENABLED true
set_interface_property conduit_end_0 EXPORT_OF ""
set_interface_property conduit_end_0 PORT_NAME_MAP ""
set_interface_property conduit_end_0 CMSIS_SVD_VARIABLES ""
set_interface_property conduit_end_0 SVD_ADDRESS_GROUP ""

add_interface_port conduit_end_0 coe_ps2clk ps2clk Input 1
add_interface_port conduit_end_0 coe_ps2data ps2data Input 1
add_interface_port conduit_end_0 coe_logclk logclk Output 1
add_interface_port conduit_end_0 coe_ps2clk_en ps2clk_en Output 1
add_interface_port conduit_end_0 coe_ps2clk_out ps2clk_out Output 1
add_interface_port conduit_end_0 coe_ps2data_en ps2data_en Output 1
add_interface_port conduit_end_0 coe_ps2data_out ps2data_out Output 1

Platform Designerで以下のコンポーネントを追加してNiosIIシステム構築をする。詳しい手順は前記事テキスト参照

  • NiosII CPU
  • On Chip Memory
  • JTAG UART
  • System ID
  • ps2if_ip
  • mypio_ip

nios2_components.png

2.4. 最上位層の作成

NSLファイルで入出力端子の接続がうまくいかなかったので、
最上位層でトライステートバッファを実装して接続。

  • 内部では入出力端子の方向を分けてやり取りした。
最上位層
declare ps2if_top {
    

    /* スライドスイッチ、プッシュスイッチ、7セグメントLED */
    input      SW[9];
    input      KEY[4];
    output     HEX0[7], HEX1[7], HEX2[7], HEX3[7], HEX4[7], HEX5[7];

    /* 単体LED、PS/2端子 */
    output     LEDR[10];
    inout           PS2_CLK, PS2_DAT;

    /* VGA */
    output          VGA_CLK;
    output     VGA_R[8],  VGA_G[8], VGA_B[8];
    output          VGA_HS, VGA_VS;
    output          VGA_BLANK_N, VGA_SYNC_N;

    /* SDRAM */
    output          DRAM_CLK, DRAM_CKE;
    output    DRAM_ADDR[13];
    output     DRAM_BA[2];
    output          DRAM_CAS_N, DRAM_RAS_N;
    output          DRAM_CS_N,  DRAM_WE_N;
    output          DRAM_UDQM,  DRAM_LDQM;
    inout     DRAM_DQ[16];

    /* GPIOコネクタ #0 */
    inout     GPIO_0[36];

    /* GPIOコネクタ #1 */
    inout     GPIO_1[36];
}

declare ps2if_qsys interface {
	input	clk_clk;
	input	reset_reset_n;
	output	ps2if_ip_0_conduit_end_0_logclk;
	
    input	ps2if_ip_0_conduit_end_0_ps2clk;
	input	ps2if_ip_0_conduit_end_0_ps2data;

	output  ps2if_ip_0_conduit_end_0_ps2clk_en;
	output  ps2if_ip_0_conduit_end_0_ps2clk_out;
	output  ps2if_ip_0_conduit_end_0_ps2data_en;
	output  ps2if_ip_0_conduit_end_0_ps2data_out;

	input	mypio_ip_0_conduit_end_0_sw_in[13];
	output	mypio_ip_0_conduit_end_0_led_r[10];
    
	output	mypio_ip_0_conduit_end_0_hex0[7];
	output	mypio_ip_0_conduit_end_0_hex1[7];
	output	mypio_ip_0_conduit_end_0_hex2[7];
	output	mypio_ip_0_conduit_end_0_hex3[7];
	output	mypio_ip_0_conduit_end_0_hex4[7];
	output	mypio_ip_0_conduit_end_0_hex5[7];
}


module ps2if_top {
	ps2if_qsys u0;
    
    // u0.ps2if_ip_0_conduit_end_0_logclk;
    u0.clk_clk = m_clock;
    u0.reset_reset_n = ~p_reset;
    PS2_CLK = if (u0.ps2if_ip_0_conduit_end_0_ps2clk_en) u0.ps2if_ip_0_conduit_end_0_ps2clk_out else 1'bz;
    PS2_DAT = if (u0.ps2if_ip_0_conduit_end_0_ps2data_en) 
        u0.ps2if_ip_0_conduit_end_0_ps2data_out else 1'bz;
    
    u0.ps2if_ip_0_conduit_end_0_ps2clk  = PS2_CLK;
    u0.ps2if_ip_0_conduit_end_0_ps2data = PS2_DAT;

    u0.mypio_ip_0_conduit_end_0_sw_in = {SW, KEY};



    LEDR = u0.mypio_ip_0_conduit_end_0_led_r;
    HEX0 = u0.mypio_ip_0_conduit_end_0_hex0;
    HEX1 = u0.mypio_ip_0_conduit_end_0_hex1;
    HEX2 = u0.mypio_ip_0_conduit_end_0_hex2;
    HEX3 = u0.mypio_ip_0_conduit_end_0_hex3;
    HEX4 = u0.mypio_ip_0_conduit_end_0_hex4;
    HEX5 = u0.mypio_ip_0_conduit_end_0_hex5;
}

2.5. ソフトウェア

NiosII CPU でキーボード入力やマウス入力を受け取るプログラムを作成していく。

  • 0 準備
  • 1 キーボード入力
    • キーボード入力を受け取り、文字を出力するプログラム。
  • 2 マウス入力その1
    • マウスの左右のクリックと、縦横の移動量を受け取るプログラム。
  • 3 マウス入力その2
    • その1プログラムを拡張してスクロールの入力を受け取るプログラム。

2.5.0. 準備

効率よくプログラムを作るためにあらかじめ設定しておく。

  • その為に、「構造体のbitフィールド」と「共用体」を用いる。

構造体の定義

今回のプログラムでは、32bitの桁毎に扱い方が違うものがある。

  • bit演算・シフトするなどして、値をやり取りする必要がある。
    • 最後出力するときにORの連続とかで式が長くなる。
    • 例えば7セグメントLEDの表示を普通に実装すると以下のようになると考えられる。
int main() 
{
    int out;
    int hex0, hex1, hex2, hex3, hex4, hex5;
    ...
    while(1){
        ...
        // my_pioに書込む値を、仕様に従うように代入
        out = hex5 << 20 | hex4 << 16
    	    | hex3 << 12 | hex2 <<  8
    	    | hex1 <<  4 | hex0;
        // my_pioに書込み
        IOWR_32DIRECT(MYPIO_0_BASE, 0, out);
        ...
    }
}

bitの扱いは仕様で決まっている事。

  • 関数内の実装部分で調整するのではなくて、構造体や関数等の宣言部分に記述したい。
    • その方がスマート(…だと思う)
// 宣言部分
#include "sys/alt_stdio.h"
~~~
// ここにbitフィールドの仕様に沿った定義を記述したい
~~~
// 実装部分
int main() 
{
    ~~~
}

そこで、my_pioの仕様に合わせたbitフィールド構造体を定義。

  • その構造体と32bit変数で構成した共用体を作成することで。ビットシフトしなくとも、値を直接編集できる。

7セグメントLEDのbitフィールド構造体を以下のように定義。

struct tag_mypio_hex_field{
	unsigned int hex0: 4;
	unsigned int hex1: 4;
	unsigned int hex2: 4;
	unsigned int hex3: 4;
	unsigned int hex4: 4;
	unsigned int hex5: 4;
	unsigned int     : 8;
} field; 

すると、32bit内のそれぞれのbitを別々の変数として扱うことができる。

field.hex0 = 1;
field.hex1 = 2;
field.hex2 = 3;
field.hex3 = 4;
field.hex4 = 5;
field.hex5 = 6;

次に共用体を定義する。

  • 先ほどの構造体等を共用体に組込む。
     - ついでに2桁区切りで設定できるものと、下位24bitを直接編集するbitフィールド構造体を追加してみた。
typedef union tag_mypio_hex_word {
	struct tag_mypio_hex_field{
		unsigned int hex0: 4;
		unsigned int hex1: 4;
		unsigned int hex2: 4;
		unsigned int hex3: 4;
		unsigned int hex4: 4;
		unsigned int hex5: 4;
		unsigned int     : 8;
	} field; 
	struct tag_mypio_hex2x3_field{
		unsigned int bit10: 8;
		unsigned int bit32: 8;
		unsigned int bit54: 8;
		unsigned int      : 8;
	} hex2;
	struct tag_mypio_hex6_field{
		unsigned int bit: 24;
		unsigned int    :  8;
	} hex6;
	unsigned int bit;
} MYPIO_HEX_WORD;

するとそれぞれの桁を編集した32bitの値を取出すことができる。

MYPIO_HEX_WORD hex;
hex.field.hex0 = 1;
hex.field.hex1 = 2;
hex.field.hex2 = 3;
hex.field.hex3 = 4;
hex.field.hex4 = 5;
hex.field.hex5 = 6;

printf("0x%x",hex.bit);//0x00654321 

同様にして7セグメントLEDや単色赤色LEDの表示オンオフとスイッチ入力のbitフィールドを定義する。

長いので畳みます
    余談

    ここの部分はVSCode上で折りたためるように #pragma region...#pragma endregion で囲ったが、NiosII SBT(Eclipse)は未対応のようで警告がでる。

    • ヘッダファイル化して別で管理した方がよかったかもしれない。
typedef union tag_mypio_hex_en_word{
	struct tag_mypio_hex_en_field{
		unsigned int hex0_en:  1;
		unsigned int hex1_en:  1;
		unsigned int hex2_en:  1;
		unsigned int hex3_en:  1;
		unsigned int hex4_en:  1;
		unsigned int hex5_en:  1;
		unsigned int        : 26;
	}field;
	struct tag_mypio_hex_en_bit_field{
		unsigned int bit:  6;
		unsigned int    : 26;
	}en;
	unsigned int bit;
} MYPIO_HEX_EN_WORD;

typedef union tag_mypio_led_r_word{
	struct tag_mypio_led_r_field{
		unsigned int led0: 1;
		unsigned int led1: 1;
		unsigned int led2: 1;
		unsigned int led3: 1;
		unsigned int led4: 1;
		unsigned int led5: 1;
		unsigned int led6: 1;
		unsigned int led7: 1;
		unsigned int led8: 1;
		unsigned int led9: 1;
		unsigned int     : 22;
	}field;
	struct tag_mypio_led_field{
		unsigned int led: 10;
		unsigned int    : 22;
	}led;
	unsigned int bit;
} MYPIO_LED_R_WORD;

typedef union tag_mypio_sw_word{
	struct tag_mypio_sw_field{
		unsigned int key: 4;
		unsigned int  sw: 9;
		unsigned int    : 19;
	}field;
	unsigned int bit;
} MYPIO_SW_WORD;

それぞれのbitフィールドが機能しているか、念のためテストプログラムを実行しておく。

bitフィールド動作チェックプログラム
#include "sys/alt_stdio.h"
#include "io.h"
#include "system.h"

unsigned char chgcode( unsigned char code );
int getdata(void);

#pragma region struct_definition

typedef union tag_mypio_hex_word {
	struct tag_mypio_hex_field{
		unsigned int hex0: 4;
		unsigned int hex1: 4;
		unsigned int hex2: 4;
		unsigned int hex3: 4;
		unsigned int hex4: 4;
		unsigned int hex5: 4;
		unsigned int     : 8;
	} field;
	struct tag_mypio_hex2x3_field{
		unsigned int bit10: 8;
		unsigned int bit32: 8;
		unsigned int bit54: 8;
		unsigned int      : 8;
	} hex2;
	struct tag_mypio_hex6_field{
		unsigned int bit: 24;
		unsigned int    :  8;
	} hex6;
	unsigned int bit;
} MYPIO_HEX_WORD;

typedef union tag_mypio_hex_en_word{
	struct tag_mypio_hex_en_field{
		unsigned int hex0_en:  1;
		unsigned int hex1_en:  1;
		unsigned int hex2_en:  1;
		unsigned int hex3_en:  1;
		unsigned int hex4_en:  1;
		unsigned int hex5_en:  1;
		unsigned int        : 26;
	}field;
	struct tag_mypio_hex_en_bit_field{
		unsigned int bit:  6;
		unsigned int    : 26;
	}en;
	unsigned int bit;
} MYPIO_HEX_EN_WORD;

typedef union tag_mypio_led_r_word{
	struct tag_mypio_led_r_field{
		unsigned int led0: 1;
		unsigned int led1: 1;
		unsigned int led2: 1;
		unsigned int led3: 1;
		unsigned int led4: 1;
		unsigned int led5: 1;
		unsigned int led6: 1;
		unsigned int led7: 1;
		unsigned int led8: 1;
		unsigned int led9: 1;
		unsigned int     : 22;
	}field;
	struct tag_mypio_led_field{
		unsigned int led: 10;
		unsigned int    : 22;
	}led;
	unsigned int bit;
} MYPIO_LED_R_WORD;


typedef union tag_mypio_sw_word{
	struct tag_mypio_sw_field{
		unsigned int key: 4;
		unsigned int  sw: 9;
		unsigned int    : 19;
	}field;
	unsigned int bit;
} MYPIO_SW_WORD;

#pragma endregion

int main()
{
	int ps2in;
	int data;
	int postF0 = 0;
	MYPIO_HEX_WORD hex;
	MYPIO_HEX_EN_WORD en;
	MYPIO_SW_WORD in;
	// MYPIO_LED_R_WORD led;

	while (1)
	{
		hex.bit = 0;
		en.bit = 0;
		in.bit = 0;
		hex.bit = 0;
		in.bit = IORD_32DIRECT(MYPIO_IP_0_BASE, 12);
		for (int i = 0; i < 6; i++)
		{
			unsigned int bit = 1 << i;
			if (in.field.sw & bit)
			{
				en.en.bit |= bit;
				hex.hex6.bit |= (~in.field.key & 0xf) << (i * 4);
			}
			else
			{
				en.en.bit &= ~bit;
			}
		}

		IOWR_32DIRECT(MYPIO_IP_0_BASE, 0, hex.bit);
		IOWR_32DIRECT(MYPIO_IP_0_BASE, 4, en.bit);
	}
	return 0;
}

2.5.1. キーボード入力

キーボード入力を受け取るには、
送られてきた値(keyコード)を文字コード(ascii)へ変換する処理が必要である。
なのでまずは、以下のように配列を定義する。

// keyコード変換用配列
const unsigned char key_code_table[128][2] = {
	{0x16, '1'}, {0x1e, '2'}, {0x26, '3'}, {0x25, '4'},
	{0x2e, '5'}, {0x36, '6'}, {0x3d, '7'}, {0x3e, '8'},
	{0x46, '9'}, {0x45, '0'}, {0x4e, '-'}, {0x55, '^'},
	{0x6a, '\\'},{0x15, 'q'}, {0x1d, 'w'}, {0x24, 'e'},
	{0x2d, 'r'}, {0x2c, 't'}, {0x35, 'y'}, {0x3c, 'u'},
	{0x43, 'i'}, {0x44, 'o'}, {0x4d, 'p'}, {0x54, '@'},
	{0x5b, '['}, {0x1c, 'a'}, {0x1b, 's'}, {0x23, 'd'},
	{0x2b, 'f'}, {0x34, 'g'}, {0x33, 'h'}, {0x3b, 'j'},
	{0x42, 'k'}, {0x4b, 'l'}, {0x4c, ';'}, {0x52, ':'},
	{0x5d, ']'}, {0x1a, 'z'}, {0x22, 'x'}, {0x21, 'c'},
	{0x2a, 'v'}, {0x32, 'b'}, {0x31, 'n'}, {0x3a, 'm'},
	{0x41, ','}, {0x49, '.'}, {0x4a, '/'}, {0x51, '\\'},
	{0x29, ' '} };
// keyコード変換用配列の長さ
#define KEYCODE_LENGTH 49 

そして定義した配列を用いて、keyコードからasciiに変換する関数を定義する。

// keyコード -> ascii 変換 
unsigned char chgcode( unsigned char code )
{
	int i;

	for ( i = 0; i < KEYCODE_LENGTH; i++ ) {
		if ( key_code_table[i][0]==code ){
			break;
		}
	}

	if ( i >= KEYCODE_LENGTH ){
		return 0x00;
	}else{
		return keytbl[i][1];
	}
}

メイン関数では標準出力に受け取った文字を表示させつつ、
受け取ったキーボード入力や、変換後のasciiコードを表示するようにする。

  • また、F0が入力されたときは次の値のkeyが離れたという意味なので、そのような処理になるように条件分岐している。
int main()
{ 
	int ps2in;
	int data;
	int postF0=0;
	MYPIO_HEX_WORD hex;
	MYPIO_HEX_EN_WORD en;

	while(1) {
		hex.bit = 0;
		en.bit  = 0;

		data = getdata();
		if ( data==0xf0 ){
			postF0 = 1;
		}else if ( postF0==1 ){
			postF0 = 0;
			en.en.bit = 0x3f;
			hex.hex2.bit54 = 0xf0;
			hex.hex2.bit32 = data;
			hex.hex2.bit10 = chgcode(data);
		}else{
			en.en.bit = 0x0f;
			hex.hex2.bit32 = data;
			hex.hex2.bit10 = chgcode(data);
			alt_printf("%c", chgcode(data));
		}			
		IOWR_32DIRECT(MYPIO_IP_0_BASE, 0, hex.bit);
		IOWR_32DIRECT(MYPIO_IP_0_BASE, 4, en.bit);
	}
	return 0;
}

プログラム全体

長いので折りたたみ
#include "sys/alt_stdio.h"
#include "io.h"
#include "system.h"

unsigned char chgcode( unsigned char code );
int getdata(void);

#pragma region struct_definition

typedef union tag_mypio_hex_word {
	struct tag_mypio_hex_field{
		unsigned int hex0: 4;
		unsigned int hex1: 4;
		unsigned int hex2: 4;
		unsigned int hex3: 4;
		unsigned int hex4: 4;
		unsigned int hex5: 4;
		unsigned int     : 8;
	} field; 
	struct tag_mypio_hex2x3_field{
		unsigned int bit10: 8;
		unsigned int bit32: 8;
		unsigned int bit54: 8;
		unsigned int      : 8;
	} hex2;
	struct tag_mypio_hex6_field{
		unsigned int bit: 24;
		unsigned int    :  8;
	} hex6;
	unsigned int bit;
} MYPIO_HEX_WORD;

typedef union tag_mypio_hex_en_word{
	struct tag_mypio_hex_en_field{
		unsigned int hex0_en:  1;
		unsigned int hex1_en:  1;
		unsigned int hex2_en:  1;
		unsigned int hex3_en:  1;
		unsigned int hex4_en:  1;
		unsigned int hex5_en:  1;
		unsigned int        : 26;
	}field;
	struct tag_mypio_hex_en_bit_field{
		unsigned int bit:  6;
		unsigned int    : 26;
	}en;
	unsigned int bit;
} MYPIO_HEX_EN_WORD;

typedef union tag_mypio_led_r_word{
	struct tag_mypio_led_r_field{
		unsigned int led0: 1;
		unsigned int led1: 1;
		unsigned int led2: 1;
		unsigned int led3: 1;
		unsigned int led4: 1;
		unsigned int led5: 1;
		unsigned int led6: 1;
		unsigned int led7: 1;
		unsigned int led8: 1;
		unsigned int led9: 1;
		unsigned int     : 22;
	}field;
	struct tag_mypio_led_field{
		unsigned int led: 10;
		unsigned int    : 22;
	}led;
	unsigned int bit;
} MYPIO_LED_R_WORD;


typedef union tag_mypio_sw_word{
	struct tag_mypio_sw_field{
		unsigned int key: 4;
		unsigned int  sw: 9;
		unsigned int    : 19;
	}field;
	unsigned int bit;
} MYPIO_SW_WORD;

#pragma endregion


// keyコード変換用配列
const unsigned char key_code_table[128][2] = {
	{0x16, '1'}, {0x1e, '2'}, {0x26, '3'}, {0x25, '4'},
	{0x2e, '5'}, {0x36, '6'}, {0x3d, '7'}, {0x3e, '8'},
	{0x46, '9'}, {0x45, '0'}, {0x4e, '-'}, {0x55, '^'},
	{0x6a, '\\'},{0x15, 'q'}, {0x1d, 'w'}, {0x24, 'e'},
	{0x2d, 'r'}, {0x2c, 't'}, {0x35, 'y'}, {0x3c, 'u'},
	{0x43, 'i'}, {0x44, 'o'}, {0x4d, 'p'}, {0x54, '@'},
	{0x5b, '['}, {0x1c, 'a'}, {0x1b, 's'}, {0x23, 'd'},
	{0x2b, 'f'}, {0x34, 'g'}, {0x33, 'h'}, {0x3b, 'j'},
	{0x42, 'k'}, {0x4b, 'l'}, {0x4c, ';'}, {0x52, ':'},
	{0x5d, ']'}, {0x1a, 'z'}, {0x22, 'x'}, {0x21, 'c'},
	{0x2a, 'v'}, {0x32, 'b'}, {0x31, 'n'}, {0x3a, 'm'},
	{0x41, ','}, {0x49, '.'}, {0x4a, '/'}, {0x51, '\\'},
	{0x29, ' '} };
// keyコード変換用配列の長さ
#define KEYCODE_LENGTH 49  
// PS2VALIDのbit
#define DVALID 0x01

int main()
{ 
	int ps2in;
	int data;
	int postF0=0;
	MYPIO_HEX_WORD hex;
	MYPIO_HEX_EN_WORD en;
	MYPIO_SW_WORD in;
	// MYPIO_LED_R_WORD led;

	while(1) {
		hex.bit = 0;
		en.bit  = 0;
		in.bit  = 0;

		data = getdata();
		if ( data==0xf0 ){
			postF0 = 1;
		}else if ( postF0==1 ){
			postF0 = 0;
			en.en.bit = 0x3f;
			hex.hex2.bit54 = 0xf0;
			hex.hex2.bit32 = data;
			hex.hex2.bit10 = chgcode(data);
		}else{
			en.en.bit = 0x0f;
			hex.hex2.bit32 = data;
			hex.hex2.bit10 = chgcode(data);
			alt_printf("%c", chgcode(data));
		}			
		IOWR_32DIRECT(MYPIO_IP_0_BASE, 0, hex.bit);
		IOWR_32DIRECT(MYPIO_IP_0_BASE, 4, en.bit);
	}
	return 0;
}

// keyコード -> ascii 変換 
unsigned char chgcode( unsigned char code )
{
	int i;

	for ( i = 0; i < KEYCODE_LENGTH; i++ ) {
		if ( key_code_table[i][0]==code ){
			break;
		}
	}

	if ( i >= KEYCODE_LENGTH ){
		return 0x00;
	}else{
		return key_code_table[i][1];
	}
}


// 受信
int getdata(void)
{
    int ps2in = 0;
    while ( (ps2in & DVALID)==0 ) {
        ps2in=IORD_8DIRECT(PS2IF_IP_0_BASE, 0);
    }
    ps2in &= ~DVALID;
    IOWR_8DIRECT(PS2IF_IP_0_BASE, 0, ps2in);
    return IORD_8DIRECT(PS2IF_IP_0_BASE, 1);
}

2.5.2. マウス入力その1

マウス入力を受け取るにはプログラム側からコマンド入力を行う必要があるので、値を送る関数を定義する。

// 送信
void putdata(int data)
{
	while ((IORD_8DIRECT(PS2IF_IP_0_BASE, 0) & EMPTY) == 0);
	IOWR_8DIRECT(PS2IF_IP_0_BASE, 2, data);
}

また、値の受取が3回に分かれ、なおかつ最初の1byteはbit毎に意味が異なるので、
以下のようにbitフィールドの構造体を作り、charとメモリを共用する。

typedef union tag_ps2mouse_data1{
	struct tag_ps2mouse_data1_field
	{
		unsigned char L: 1;
		unsigned char C: 1;
		unsigned char R: 1;
		unsigned char  : 1;
		unsigned char x_sign: 1;
		unsigned char y_sign: 1;
		unsigned char x_overflow: 1;
		unsigned char y_overflow: 1;
	} field;
	unsigned char bit;
} PS2MOUSE_DATA1;

メイン関数の初期化処理部分に先ほど定義した関数を用いてコマンド入力と、その応答を受け取る。

int main()
{
	PS2MOUSE_DATA1 data1;
	int data2, data3;
	int posx = 0, posy = 0, dx, dy;
	MYPIO_HEX_WORD hex;
	MYPIO_HEX_EN_WORD en;
	MYPIO_LED_R_WORD led;

	// リセットコマンド
	putdata(0xFF);
	data1.bit = getdata();
	data2 = getdata();
	data3 = getdata();

	// イネーブルコマンド
	putdata(0xF4);
	data1.bit = getdata();
	while(1){
		// マウス入力の処理
	}
	return 0;
}

コマンド入力後は、無限ループを開始して、送られてきたマウス入力の値を逐次反映させる。

while(1) {
  hex.bit = 0;
  en.bit  = 0x0f;

  // データ受信
  data1.bit = getdata();
  data2 = getdata();
  data3 = getdata();

  // 移動量の符号判定
  if (data1.field.x_sign){
    dx = data2 | 0xffffff00;
  }else{
    dx = data2;
  }
  if (data1.field.y_sign){
    dy = data3 | 0xffffff00;
  }else{
    dy = data3;
  }

  // 移動量を加算
  posx += dx;
  posy += dy;

  // 座標の表示
  hex.hex2.bit32 = posx & 0xff;
  hex.hex2.bit10 = posy & 0xff;

  // ボタン表示
  led.field.led1 = data1.field.L;
  led.field.led0 = data1.field.R;

  // 左右同時クリックでxyの値を0にする
  if (data1.field.L == 1 && data1.field.R == 1){
    posx = 0;
    posy = 0;
  }
  
  // 7セグメントLEDに表示させたい値やLEDのオンオフを出力
  IOWR_32DIRECT(MYPIO_IP_0_BASE, 0, hex.bit);
  IOWR_32DIRECT(MYPIO_IP_0_BASE, 4, en.bit);
  IOWR_32DIRECT(MYPIO_IP_0_BASE, 8, led.bit);
}

2.5.3. マウス入力その2

先ほどのプログラムではホイール入力を受け取れていない。

ホイール機能を有効にするには入力コマンドが増える。

  • 2.5.2.で入力したコマンド
    • (1) リセット
    • (2) イネーブル
  • ホイール機能を有効するコマンド
    • (1) リセット
    • (2) サンプルレート設定: 200
    • (3) サンプルレート設定: 100
    • (4) サンプルレート設定: 80
    • (5) イネーブル

このようにリセットとイネーブルの間でサンプルレートを200->100->80に設定する必要がある。

コマンド入力
// リセット
putdata(0xFF);
data2 = getdata();
data3 = getdata();
data4 = getdata();

putdata(0xF3);// セットサンプルレート
data2 = getdata();
putdata(0xC8);// 200
data2 = getdata();
putdata(0xF3);// セットサンプルレート
data2 = getdata();
putdata(0x64);// 100
data2 = getdata();
putdata(0xF3);// セットサンプルレート
data2 = getdata();
putdata(0x50);// 80
data2 = getdata();

// ID 読み出し
putdata(0xF2);
data2 = getdata();
data2 = getdata();

// イネーブルコマンド
putdata(0xF4);
data2 = getdata();

そして4byte目にスイッチ入力の情報(Z軸)が送られるようになるので、
マウス入力を受け取る処理に追加する

スクロール機能を追加した、マウス入力処理
while(1) {
	hex.bit = 0;
	en.bit  = 0x3f;

	// データ受信
	data1.bit = getdata();
	data2 = getdata();
	data3 = getdata();
	data4 = getdata();

	// 移動量の符号判定
	if (data1.field.x_sign){
		dx = data2 | 0xffffff00;
	}else{
		dx = data2;
	}
	if (data1.field.y_sign){
		dy = data3 | 0xffffff00;
	}else{
		dy = data3;
	}

	dz = data4;

	// 移動量を加算
	posx += dx;
	posy += dy;
	posz += dz;

	// 座標の表示
	hex.hex2.bit54 = posx & 0xff;
	hex.hex2.bit32 = posy & 0xff;
	hex.hex2.bit10 = posz & 0xff;

	// ボタン表示
	led.field.led1 = data1.field.L;
	led.field.led0 = data1.field.R;
	led.field.led2 = data1.field.C;

	if (data1.field.L == 1 && data1.field.R == 1){
		posx = 0;
		posy = 0;
		posz = 0;
	}

	IOWR_32DIRECT(MYPIO_IP_0_BASE, 0, hex.bit);
	IOWR_32DIRECT(MYPIO_IP_0_BASE, 4, en.bit);
	IOWR_32DIRECT(MYPIO_IP_0_BASE, 8, led.bit);
}

3. 動作チェック

  • : 動作チェックはps2if_ipのみ行う。
    • mypio_ipやソフトウェアのチェックは省略

ModelSim使って波形を見てみる。

  • チェック
    回路がPS/2のデータ受信出来ているかチェック。
    image.png

細かいので拡大

image.png

回路からPS/2のデータ送信できているかチェック。

image.png

細かいので拡大

image.png

4. まとめ

FPGAでキーボードやマウスの入力を受け取った。

  • PS/2インタフェース回路の作成
  • 受け取ったデータを表示するために、前記事で作った回路を拡張
    • 単色赤色の表示を追加した

次回はテキストの第7章のVGA文字表示回路を作ってみる。

参考文献

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0