【改訂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つの理由
今回の場合は3.
の組込み開発が容易に該当するのであろうか。
2.1. ps2if_ip
まずは、デバイスのPS/2通信を受け取ってAvalonバスでCPUとデータをやり取りする回路を作る。
このモジュールでは値の送受信処理を行う。
キーボード入力を文字に変換する処理や、マウス入力からXY軸の移動量を取出す処理はソフトウェア側で行う。
レジスタ表
回路のレジスタを以下のように定義する
-
状態レジスタ(PS2STATUS) 以下のbitを持つ
- PS2EMPTY: 送信レジスタが空のとき1
- PS2VALID: 受信データが有効なら1
-
受信データ(PS2RDATA)
-
送信データ(PS2WDATA)
送受信の仕様
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に出力させたいのでそこの値を管理するレジスタを追加する。
- 以下の表を参照
レジスタ部分
上記のレジスタ表に従い、アドレスによって読み書きするレジスタを切り替える。
レジスタ部分
ヘッダファイル
#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
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のデータ送信できているかチェック。
細かいので拡大
4. まとめ
FPGAでキーボードやマウスの入力を受け取った。
- PS/2インタフェース回路の作成
- 受け取ったデータを表示するために、前記事で作った回路を拡張
- 単色赤色の表示を追加した
次回はテキストの第7章のVGA文字表示回路を作ってみる。