by yosikawa » 2011年2月14日(月) 01:37
さて、PicoBlaze にロータリスイッチを繋いでみる。
以前作ったロータリスイッチの処理回路を再利用する。
しかし良く考えると、rotary_eventは1クロックしか出力されない。
このままポートに接続してもPicoBlaze 側は読み込みが間に合わない。
かといって、単純にフリップフロップに覚えただけだと、2回目のイベントが取れない。
PicoBlaze が読み込んだらクリアする必要がある。ハンドシェークが必要だ。
イベントが発生したら、フリップフロップに覚える。ポート01に何かが書き込まれたら、クリアする。
以下のようにしてみた。
これに合わせて、入出力ポートの接続は以下のようにした。
アセンブラの方は、だんだんプログラムらしくなってきた。
rotary_event が発生すると、カウントを増減させる。rotary_right=1のときは+1、rotary_right=0のときは-1する。
C言語なら簡単に書けるがマシン語だと大変。
定数定義や、レジスタに名前を定義する機能を使って、読みやすくしよう。
以下のようになった。
動かしてみると、、、よしよし。ロータリスイッチに反応してLEDで表現した2進数が増減する。
デバッグがしやすいように、ソフト・リセット機能も付けておこう。
起動後、1回だけhard_reset がアクティブ(ロー)になる。
へんな回路だが、この方がシミュレーションのとき便利だったので。
ロータリ・スイッチ長押しでソフト・リセットする(4秒)
PushButton_LongTerm は長押しを検出する回路。
ついでに、ソフト・リセットがかかったらロータリスイッチを押している間、LEDがフラッシュするようにしよう。
最後に、PicoBlaze の割り込みを使ってみよう。
今回のアプリでは割り込みを使わなくても十分に間に合うが、
今後作成予定のLCDモジュールでは時間待ちの処理などを入れるので、
割り込みで処理したい。
ロータリスイッチのイベントは、くりっ、くりっ、と回転するごとに発生するから、速く回せばそこそこの頻度で発生する。
イベント発生のタイミングで割り込みをかけて、割り込みルーチンの中でロータリスイッチのカウントをするようにする。
では、top.vに割り込みの回路を付ける。
割り込み処理ルーチン付きのPicoBlaze プログラムは最終的に以下のようになった。
これで、入出力と割り込みまでPicoBlaze の使い方を一通りマスターした。と思う。
以前作ったロータリスイッチの処理回路を再利用する。
- コード:
// ロータリスイッチのトリガ信号を作る。
wire rotary_event; // ロータリスイッチが1段階回転したとき、1パルスだけアクティブになる。
wire rotary_right; // rotary_event がアクティブのとき、回転方向を示す。ハイが右方向。
S3E_Rotary S3E_Rotary(clk, reset, ~rotary_a, ~rotary_b, rotary_event, rotary_right);
しかし良く考えると、rotary_eventは1クロックしか出力されない。
このままポートに接続してもPicoBlaze 側は読み込みが間に合わない。
かといって、単純にフリップフロップに覚えただけだと、2回目のイベントが取れない。
PicoBlaze が読み込んだらクリアする必要がある。ハンドシェークが必要だ。
イベントが発生したら、フリップフロップに覚える。ポート01に何かが書き込まれたら、クリアする。
以下のようにしてみた。
- コード:
// ロータリスイッチのトリガ信号を作る。
wire rotary_event; // ロータリスイッチが1段階回転したとき、1パルスだけアクティブになる。
wire rotary_right; // rotary_event がアクティブのとき、回転方向を示す。ハイが右方向。
S3E_Rotary S3E_Rotary(clk, reset, ‾rotary_a, ‾rotary_b, rotary_event, rotary_right);
// ロータリイベントを保持する。
reg [1:0] rotary_latch;
always @(posedge clk or negedge reset) begin
if(!reset) begin
rotary_latch <= 0;
end else if(rotary_event) begin
rotary_latch <= { rotary_event, rotary_right };
end else if(write_strobe & port_id[0]) begin
rotary_latch <= 0;
end
end
これに合わせて、入出力ポートの接続は以下のようにした。
- コード:
// スイッチ入力を入力ポートに接続する
// port 00 : switch, btn
// port 01 : 6'b0, rotary_event, rotary_right
always @(posedge clk or negedge reset) begin
if(!reset) begin
in_port <= 0;
end else begin
case (port_id)
8'h00 : in_port <= { switch, btn };
8'h01 : in_port <= { 6'b0, rotary_latch };
default : in_port <= 0;
endcase
end
end
// 出力ポートから信号を取り出す
reg [7:0] out1;
always @(posedge clk or negedge reset) begin
if(!reset) begin
out1 <= 0;
end else if(write_strobe & ‾port_id[0]) begin
out1 <= out_port;
end
end
アセンブラの方は、だんだんプログラムらしくなってきた。
rotary_event が発生すると、カウントを増減させる。rotary_right=1のときは+1、rotary_right=0のときは-1する。
C言語なら簡単に書けるがマシン語だと大変。
定数定義や、レジスタに名前を定義する機能を使って、読みやすくしよう。
以下のようになった。
- コード:
; files
VHDL "ROM_form.v", "rom.v", "rom"
; input port
i_switch DSIN $00
i_rotary DSIN $01
; output port
o_led DSOUT $00
o_rotary_ack DSOUT $01
; bit pattern
b_rotary_event EQU $02
b_rotary_right EQU $01
; register
s_switch_value EQU s0
s_rotary_count EQU s1
s_rotary_value EQU s2
;
; initialize
;
ORG 0
LOAD s_rotary_count, 0
;
; main loop
;
BEGIN:
; input rotary events and test
IN s_rotary_value, i_rotary
TEST s_rotary_value, b_rotary_event
JUMP Z, SKIP
; clear event flag
OUT s_rotary_value, o_rotary_ack
; rotate right?
TEST s_rotary_value, b_rotary_right
JUMP Z, GOLEFT
; +1 when right
ADD s_rotary_count, 1
JUMP SKIP
GOLEFT:
; -1 when left
SUB s_rotary_count, 1
SKIP:
IN s_switch_value, i_switch
ADD s_switch_value, s_rotary_count
OUT s_switch_value, o_led
JUMP BEGIN
動かしてみると、、、よしよし。ロータリスイッチに反応してLEDで表現した2進数が増減する。
デバッグがしやすいように、ソフト・リセット機能も付けておこう。
- コード:
// リセット信号を作る。
reg [1:0] reset_count;
always @(posedge clk) begin
if(&reset_count == 0)
reset_count <= reset_count + 1;
end
wire hard_reset = reset_count[0]; // ハード・リセット信号。
// 1KHz, 4Hz の内部クロックを作成する。
// 分周比 50MHz / 50000 = 1KHz
parameter integer CLK1MS_SCALE = 50000; // 分周比
defparam prescaler1.SCALE = CLK1MS_SCALE;
wire clk1ms; // 1ms 内部クロック
Prescaler prescaler1(
clk, // 入力クロック
hard_reset, // リセット
clk1ms // 分周結果。シングルパルス。
);
parameter integer CLK250MS_SCALE = 250; // 分周比
defparam prescaler2.SCALE = CLK250MS_SCALE;
wire clk250ms; // 250ms 内部クロック
Prescaler_Enabled prescaler2(clk, hard_reset, clk1ms, clk250ms);
// ロータリ・スイッチ長押しでソフト・リセットする(4秒)
wire soft_reset, nsoft_reset;
defparam PushButton_LongTerm.WAIT = 12; // 0.25s * 12 = 4s
PushButton_LongTerm PushButton_LongTerm(clk, hard_reset, clk250ms, rotary_press, nsoft_reset);
assign soft_reset = ~nsoft_reset; // ソフト・リセット信号
// リセット信号を作る
wire reset = hard_reset & soft_reset;
起動後、1回だけhard_reset がアクティブ(ロー)になる。
へんな回路だが、この方がシミュレーションのとき便利だったので。
ロータリ・スイッチ長押しでソフト・リセットする(4秒)
PushButton_LongTerm は長押しを検出する回路。
- コード:
module PushButton_LongTerm(clk, reset, enable, in, out);
parameter integer WAIT = 1; // 長押し検出時間。(wclkの周期)*WAIT の時間長押しすると反応する。最低1。
input wire clk; // クロック
input wire reset; // リセット
input wire enable; // ウェイト用クロック。1パルスだけアクティブになる信号でなければならない。
input wire in; // 入力信号。アクティブ・ハイ。チャタを含まない同期信号。
output reg out; // inがWAIT時間以上アクティブのときアクティブになり、inの立ち下がりでインアクティブになる。
function integer get_width(input integer value);
integer v2;
begin
v2 = value;
for(get_width = 0; v2 > 0; get_width = get_width + 1) begin
v2 = v2 >> 1;
end
end
endfunction
parameter count_width = get_width(WAIT - 1);
reg [count_width - 1:0] count;
always @(posedge clk or negedge reset) begin
if(!reset) begin
count <= 0;
out <= 0;
end else if(!in) begin
count <= 0;
out <= 0;
end else if(enable) begin
if(count == (WAIT - 1'b1)) begin
count <= 0;
out <= 1;
end else begin
count <= count + 1;
end
end
end
endmodule
ついでに、ソフト・リセットがかかったらロータリスイッチを押している間、LEDがフラッシュするようにしよう。
- コード:
// ソフト・リセット中は点滅させる。
reg flash_timming;
always @(posedge clk or negedge hard_reset) begin
if(!hard_reset) begin
flash_timming <= 0;
end else if(clk250ms) begin
flash_timming <= ~flash_timming;
end
end
assign led = (!soft_reset) ? (flash_timming ? 8'hff : 8'h00) : out1;
最後に、PicoBlaze の割り込みを使ってみよう。
今回のアプリでは割り込みを使わなくても十分に間に合うが、
今後作成予定のLCDモジュールでは時間待ちの処理などを入れるので、
割り込みで処理したい。
ロータリスイッチのイベントは、くりっ、くりっ、と回転するごとに発生するから、速く回せばそこそこの頻度で発生する。
イベント発生のタイミングで割り込みをかけて、割り込みルーチンの中でロータリスイッチのカウントをするようにする。
では、top.vに割り込みの回路を付ける。
- コード:
// ロータリイベントで割り込みをかける。
always @(posedge clk or negedge reset) begin
if(!reset) begin
interrupt = 0;
end else if(rotary_event) begin
interrupt = 1;
end else if(interrupt_ack) begin
interrupt = 0;
end
end
割り込み処理ルーチン付きのPicoBlaze プログラムは最終的に以下のようになった。
- コード:
; files
VHDL "ROM_form.v", "rom.v", "rom"
; input port
i_switch DSIN $00
i_rotary DSIN $01
; output port
o_led DSOUT $00
o_rotary_ack DSOUT $01
; bit pattern
b_rotary_event EQU $02
b_rotary_right EQU $01
; register
s_switch_value EQU s0
s_rotary_count EQU s1
s_rotary_value EQU s2
;
; initialize
;
ORG 0
LOAD s_rotary_count, 0
EINT ; enable interrupt
;
; main loop
;
BEGIN:
IN s_switch_value, i_switch
ADD s_switch_value, s_rotary_count
OUT s_switch_value, o_led
JUMP BEGIN
;
; ISR
;
ISR:
; input rotary events and test
IN s_rotary_value, i_rotary
; clear event flag
OUT s_rotary_value, o_rotary_ack
; rotate right?
TEST s_rotary_value, b_rotary_right
JUMP Z, GOLEFT
; +1 when right
ADD s_rotary_count, 1
JUMP SKIP
GOLEFT:
; -1 when left
SUB s_rotary_count, 1
SKIP:
RETI ENABLE
; interrupt vector
ORG $3FF
JUMP ISR
;
これで、入出力と割り込みまでPicoBlaze の使い方を一通りマスターした。と思う。
0 件のコメント:
コメントを投稿