モノ創りで国造りを

ハード/ソフト問わず知見をまとめてます

MERNことはじめ

背景

MERN 開発の流れを知りたくてyotuube(https://www.youtube.com/watch?v=7CqJlxBYj-M)を見た。
動画は情報量が多くて捗るけど、その分繰り返し見るには時間がかかるので文字に起こした。

MongoDBの説明

  • Database
  • Collection
  • Document
  • Index
  • $lookup
  • Reference

Example

Mongo DBの例

{
    name:" ",
    title:" ",
    address:{
                address1:" ",
                city:" ",
                state: " ",
            },
    topics:[" ", " ", " ",],
    number:
}

MogoDB Atlasの紹介

512MBまで無料、Cluster使えるとか

作成するアプリの説明

Appは以下を含む

  • Exercises
  • Users

全てのエクササイズは一人のユーザーのみ持つ

ディレクトリの作成と環境の整備

特定のディレクトリで、

create-react-app mern-exercise-tracker

数分待って完了したらディレクトリ変更

cd mern-exercise-tracker

バックエンド用のディレクトリを作成し環境もろもろを整備する

mkdir backend
cd backend
npm install express cors mongoose dotenv
npm install -g nodemon

バックエンドとMongoDBの接続

backendディレクトリにserver.jsファイルを新規作成する。

const express = require('express');
const cors = require('cors');
const mongoose = require('mongoose');

require('dotenv').config();

const app = express();
const port = process.env.PORT || 5000;

app.use(cors());
app.use(express.json());

const uri = process.env.ATLAS_URI;
mongoose.connect(uri, {userNewUrlParser: true, userCreateIndex: true });

const connection = mongoose.connection;
connection.once('open', () => {
    console.log("MongoDB database connection established successfully");
})

const exercisesRouter = require('./routes/exercises');
const usersRouter = require('./routes/users');

app.use('/exercises', exercisesRouter);
app.use('/users', usersRouter);


app.listen(port,() => {
    console.log(`Server is runnning on port: ${port}`);
});

backendディレクトリに.envファイルを新規作成する。
MongoDB Atlas clusterのconnection clusterのconnection String OnlyのCopyをクリック

.envに以下を記載。 ATLAS_URI=ペースト(先ほどコピーしたもの) passwordの箇所にパスワードを記載する。

backendディレクトリでnodemon server

以上でMongoDBにアクセスできるようになる。

modelの作成

backendにmodelsフォルダを作成。
modelsフォルダに

  • exercise.model.js
  • user.model.js

を新規作成する。
exercise.model.js

const mongoose = require('monngoose');

const Schema = mongoose.Schema;

const exerciseSchema = new Schema({
    username: { type: String, required: true},
    description: { type: String, required: true},
    duration: { type: Number, required: true},
    date: { type: Date, required: true},
}, {
    timestamps: true,
});

const Exercise = mongoose.model('Exercise', exerciseSchema);

module.exports = Exercise;

user.mode.js

const mongoose = require('monngoose');

const Schema = mongoose.Schema;

const userSchema = new Schema({
    username:{
        type: String,
        required: true,
        unique: true,
        trim: true,
        minlength: 3
    },
}, {
    timestamps: true,
});


const User = mongoose.model('User', userSchema);
module.exports = User;

Routeの作成

backendディレクトリ内にroutesディレクトリを作成する。
routesディレクトリ内に

  • exercises.js
  • users.js

を新規作成する。

exercises.js

const router = require('express').Router();
let Exercise = require('../models/exrecise.model');

router.route('/').get((req, res) => {
    Exercise.find()
        .then(exercises => res.json(exercises))
        .catch(err => res.status(400).json('Error: ' + err));
});

router.route('/add').post((req, res) => {
    const username = req.body.username;
    const description = req.body.description;
    const duration = Number(req.body.duration);
    const date = Date.parse(req.body.date);

    const newExercise = new Exercise({
        username,
        description,
        duration,
        date,
    });

    newExercise.save()
    .then(() => res.json('Exercise added!'))
    .catch(err => res.status(400).json('Error: ' + err));
});

module.exports = router;

user.js

const router = require('express').Router();
let User = require('../models/user.model');

router.route('/').get((req, res) => {
    User.find()
        .then(users => res.json(users))
        .catch(err => res.status(400).json('Error: ' + err));
});

router.route('/add').post((req, res) => {
    const username = req.body.username;
    const newUser = new User({username});

    newUser.save()
        .then(() => res.json('User added!'))
        .catch(err => res.status(400).json('Error: ' + err));
});

module.exports = router;

server.jsにルートを記載する(上述)。
---ここまで動画開始から30分---

データベースの確認

Insomniaでサーバーの動作を確認する。
exercises.jsにIDのルートの処理を追加する(上述)。
get,delete,postなど

---ここまで動画開始から43分。ここからReactの説明開始---

ReactでHello Worldの表示

publicのindex.htmlを確認する
index.jsを確認する
App.jsに移動。

exercise-trackerフォルダで

  • npm install bootstrap
  • npm install reat-router-dom

使用するコンポーネントをあらかじめimportする。
srcディレクトリ下にcomponentディレクトリを作成する。

その後

これ以降のReactの説明に関する情報は少し古い。
Hooksを使用するのが今後のトレンドになるはず。
従ってこれ以降は動画をまとめる必要はないような気がするので
代わりにReact Hooksについて読みかじったことを記載しておく。

参考文献

りあクト!
りあクト! TypeScriptで始めるつらくないReact開発 第2版 - くるみ割り書房 - BOOTH

react training公式ページ
React Training: React Router v5.1

Qiitaの記事

useStateの使い方

import React, {FC, useState } from 'react';
import { Button, Card, Statistic } from 'semantic-ui-react';

import './App.css';

const App:FC = () => {
    const [count, setCount] = usetState(0);
    const increment = () => {
        setCount(count + 1);
    };
    const decremet = () => {
        setCount(count -1);
    };

    return(
        <div className="container">
            <header>
                <h1>カウンター</h1>
            </header>
            <Card>
                <Statistic className="number-board">
                    <Statistic.Label>count</Statistic.Label>
                    <Statistic.value>{count}</Statistic.value>
                </Statistic>
                <Card.Content>
                    <div className="ui two buttons">
                        <Button color="red" onClick={decrement}>-1</Button>
                        <Button color="green" onClick={increment}>+1</Button>
                    </div>
                </Card.Content>
            </Card>
        </div>
    );
};

export default App;

useEffectの使い方

import React, {FC, useEffect, useState} from 'react';
import { Button, Card, Icon, Statistics } from 'semantic-ui-react';

import './App.css';

const LIMIT = 60;

const App:FC = () => {
    const [timeLeft, setTimeLeft] = useState(LIMIT);
    const reset = () => {
        setTimeLeft(LIMIT);
    };

    const tick = () => {
        setTimeLeft(prevTime => (prevTime === 0? LIMIT: prevTime -1));
    };

    useEffect(() => {
        const timerId = setInterval(tick, 1000);
        return () => clearInterval(timerId);
    },[]);

    return (
        <div className="container">
            <header>
                <h1>タイマー</h1>
            </header>
            <Card>
                <Statistic className="number-board">
                    <Statistics.Label>time</Statistics.Label>
                    <Statistics.value>{timeLeft}</Statistics.value>
                </Statistic>
                <Card.content>
                    <Button color="red" fluid on Click={reset}>
                        <Icon name="redo" />
                        Reset
                    </Button>
                </Card.content>
            </Card>
        </div>
    );
};

export default App;

Custom Hooksの例

import React, { FC, useEffect, useState } from 'react';
import AppComponent from ' ../components/App';

//Custom hook "useTimer"
const useTimer = (limitSec: number):[number, () => void] => {
    const [timeLeft, setTimeLeft] = useState(limitSec);

    const reset = () => {
        setTimeLeft(limitSec);
    };

    const tick = () => {
        setTimeLeft(prev => ( prevTime === 0? limitSec : prevTime -1));
    };

    useEffect(() => {
        const timerId = setInterval(tick, 1000);
        return () => clearInterval(timerId);
    },[]);

    return [timeLeft, reset];
}

const AppContainer: FC= () => {
    const LIMIT = 60;
    const [timeLeft, reset] = useTimer(LIMIT);

    return <AppComponent timeLeft={timeLeft} reset={reset} />;
};

export default AppContainer;

react-routerの例

import React from 'react';
import ReactDom from 'react-dom';
import {
    Browsewrrouter as router,
    Route,
    Switch,
    useParams,
    useHistory,
    useLocation,
} from 'react-router-dom';

function Hello(){
    const history = useHistory();
    return(
        <div>
            <h1>Hello</h1>
            <button onClick={() => history.push('/hello/react-router?message=hooks#test')}>Next</button>
        </div>
    );
}

function HelloSomeone(){
    const history = useHistory();
    const location = useLocation();
    const {name} = useParams();
    return(
        <div>
            <h1>Hello {name}</h1>
            <p>pathname: {location.pathname}</p>
            <p>search: {location.search} </p>
            <p>hash: {location.hash}</p>
            <button onClick={() => history.goBack()}>Go Back</button>
        </div>
    );
}

function App(){
    return (
        <Router>
            <Switch>
                <Route path="/" exact>
                    <Hello />
                </Route>
                <Route path="/hello/:name" exact>
                    <HelloSomeone/>
                </Route>
            </Switch>
        </Router>
    );
}

react-reduxの例

Yocto Projectの勉強まとめ(途中)

背景

SoC FPGAを搭載したオリジナル基板を開発できるようになりたい。
Linux使えれば色々便利。
基板毎に最適化された組込みLinux OSを作成できるYocto Projectなるものを知ったので勉強したい。

参考文献

https://www.miraclelinux.com/product-service/total-embedded/emb-mtrls/materials/yocto-project-introduction

Linuxディストリビューション

Linuxカーネル + 各種ライブラリ = Linux OS
各種ライブラリにはOSSプロジェクトも含まれる。

頒布方法

一連の流れ:
ソースコード => ダウンロード => コンパイル => バイナリ => インストール

バイナリーベース:
ディストリビューターコンパイル済のソフトウェアをパッケージとして頒布。
パッケージにはバイナリとインストール方法をまとめられている。
ユーザーはそのパッケージをインストールする。

ソースベース:
ディストリビューターコンパイル方法を頒布する。
ユーザーがコンパイルしインストールする。

Yocto Projectとは

Yocto Projectは組み込み向けOS開発を構築する仕組みを提供するプロジェクト。
ソースコードベース。

プロジェクト体制:

Poky

Pokyとは

レシピとは

設定ファイルとは

  • 拡張子は.conf
  • CPUアーキテクチャなどのターゲットマシン毎の定義
  • ビルド環境などの独自設定

bitbakeとは

  • Pythonで書かれたビルドツール
  • レシピを解析し、定義された動作を実行する

bitbakeの動作

  1. レシピを解析
  2. ソースコードのダウンロード
    • どこからダウンロードするか
    • do_fetch
  3. ソースコードの展開
    • どのように展開するか
    • do_unpack
  4. パッチの適用
    • どのようなパッチを適用するか
    • do_patch
  5. ソフトウェアのコンパイル
    • コンパイル方法の指定
    • 依存関係の記述
    • do_configure
    • do_compile
  6. 生成物をパッケージ化
    • インストール先
    • どのファイルを含むか
    • パッケージング方式の指定
    • do_install
    • do_package

レイヤー機能

まだ。

MAX10で画像処理

背景

FPGAで画像処理をしたい。
SoC FPGAでやろうと思ったけど、学習コストが高く、途中で挫折してしまった。
一旦、FPGAマイコンをハード的に分けることで何かしらの進捗を出し、モチベーションを高める。

やりたいこと

  1. カメラモジュールをマイコン(Arduino)のI2Cで制御。
  2. カメラモジュールの画像データをFPGAに出力。
  3. 画像データをFPGA内部で処理。
  4. FPGAからVGA信号をディスプレイに出力。

各モジュールの簡単な説明

マイコン

言わずと知れたArduino Uno。
f:id:yuji2yuji:20190926065121p:plain

カメラモジュール

OV7675のモジュール品を使用する。
秋月で購入。550円。安い。
OV7675使用30万画素カメラ B0070: カメラ 秋月電子通商-電子部品・ネット通販

供給電源は3.3V、出力形式は選択可能。
画像データはパラレル8出力。
f:id:yuji2yuji:20190926064847p:plain

1~4ピン(電源、GND、I2C)をArduinoに接続する。

MAX10 EVAL Kit

Intelから販売されているMAX10の安価な評価ボード。
FPGAへのデータ書き込みには専用のツールが必要。
https://www.intel.co.jp/content/www/jp/ja/programmable/products/boards_and_kits/dev-kits/altera/kit-max-10-evaluation.html

f:id:yuji2yuji:20190926065603p:plain

f:id:yuji2yuji:20190926065648p:plain

カメラモジュールをマイコンのI2Cで制御

OV7675カメラモジュールのデータは以下。
細かい点は色々あるが、まずはデータを出力できればよい。
真っ黒だとデータが出力されているかどうかが不明なのでつらい。

http://akizukidenshi.com/download/ds/omnivision/ov7675.pdf

データシートから、何かしらの画像データを出力するために以下の設定が必要とわかる。

ENとPWDNは以下の設定が必要。

Pin Level
PWDN L
PEN H

初期化時に各アドレスへ送信するデータは以下。

address data description
0xB8 4'h1 1.8V requirement
0xB8 4'h2 1.8V requirement
0x71 0x01 Test pattern Enable
0x17 Horizontal start
0x18 Horizontal stop
0x32 Horizontal start[2:0] and stop[5:3]
0x19 Vertical start
0x1A Vertical stop
0x03 Vertical start[2:0] and stop[5:3]

スレーブアドレスは、Write0x42、Read0x43。
Write/Readで末尾に1/0を付加されている。
一般的にI2Cで使用される7bitのアドレスは0x21。

Direction b6 b5 b4 b3 b2 b1 b0 W/R
Write 0 1 0 0 0 0 1 1
Read 0 1 0 0 0 0 1 0

OV7675カメラモジュールとArduinoを接続する。

SCCB(I2C)でデータの送受信を行う。
ArduinoはI2Cのポートを有するが、Digital IOポートを使ってSCCBの制御を行っているコードをgithubで見つけた。ただしOV7670。
https://gist.github.com/muhammadyaseen/75490348a4644dcbc70f
これを修正すればよさそう。
Arduino一つで複数カメラの制御もできそうなので、
今後の拡張性を考慮してありがたく拝借して使用する。

OV7675の画素はベイヤパターン。
f:id:yuji2yuji:20190926073254p:plain

しかしレジスタの設定を変えることで、出力Formatは変更可能。
今回はRGB565でやってみる。
RGB565は、R:5bit、G:6bit、B:5bitの合計16bitを1画素のデータとするFormat。

OV7675は画像データの表示領域を選択できる。
0x17~0x1A、0x03、0x32で設定する。
f:id:yuji2yuji:20190926073344p:plain

VGAのデータ出威力ダイアグラムは以下。 f:id:yuji2yuji:20190927073822p:plain
データをディスプレイ上に表示するためには、VGAを使ったアナログ信号だとDAコンバータが必要。
今回はDAコンバーターがないので、デジタルデータを閾値で0/1切り替えて表示する。

とりあえず画像だし

FPGAにデータを書き込み、OV7675を初期化すると画像が出るようになった。
ただし、FPGAによるVGA出力とOV7675のデータ入力は同期をとっていないので、
表示される画像はひどい有様だった。

そこで、OV7675から入力されるPCLK信号(画素データのクロック)を使ってFPGAからVGAデータを出力したところ、
画像が表示されなかった(入力データがない状態)。
波形を観測すると、PCLKは鈍っており十分な信号レベルが出力されていないことがわかった。
コンパレーターかアンプが必要とわかったが、そこまで改修するのも手間なので、今回はここまでとする。

pyxelで遊ぶ

背景

Pythonで簡単にドット絵ゲームが作成できるpyxelなるものを知ったので、
Pythonの復習がてら使ってみる。

pyxle

日本人が開発したPythonによるレトロゲームエンジン。

https://github.com/kitao/pyxel/blob/master/README.ja.md

関数を削減して、覚えるよりも作ることに注力できる。学習コストが極めて低い。

ドット絵やサウンドエフェクトの開発ツールも同梱されている。

なんか作ってみる

とりあえずカラフルなエフェクトを作ってみた。
f:id:yuji2yuji:20190904135838g:plain

from pyxel import init, run, circ, cls 
from math import  sin, cos

class App:
    def __init__(self):
        init(160,120)
        self.i = 0
        run(self.update, self.draw)

    def update(self):
        self.i += 1
        self.i %= 1000
    def draw(self):
        cls(1)
        for j in range(1500):
            circ(80+j/(50.1-2*self.i/4)*sin(j/2),60+j/(50.1-2*self.i/4)*cos(j/2),1,(j+self.i)%15)

App()

何でもない画像だけど、これだけでも楽しい。ドット絵だからなのか。
これがドット絵の魔力・・・

ゲームでも作ってみる

せっかくなのでゲームでも作ってみる。

企画

ざっくりとした仕様

  • 横スクロールアクション
  • 自キャラのアクション
    • 左右への移動
    • ジャンプ
    • 攻撃
  • 敵キャラのアクション
    • 左右への移動

絶賛作成中!!

途中経過

左右に移動+ジャンプ+攻撃(火の玉を出す)

f:id:yuji2yuji:20190905180044g:plain

移動時に画像を変えたいけど、画像を作るのがつらい・・・
昔からゲーム作成の肝は画像作成だと思っています。

ROS2でパッケージ作成

背景

Youtubeのこの動画をみて勉強中。
https://www.youtube.com/watch?v=C2bKwFJ5HEY

動画は後から見返すのに時間がかかるので、まとめる。

内容

この動画はROS Development Studioを使っているので実環境で正常に動作するかを後で要確認。
ターミナル上にて、ホームディレクトリで以下を実行

$ source /opt/dashing/setup.bash  
$ cd ros2_ws/src/  
$ ros2 pkg create ros2_cpp_pkg --build-type ament_cmake --dependencies rclcpp  

src内にros2_cpp_code.cppを作成

#include "rclcpp/rclcpp.hpp"

int main(int argc, char *argv[]){
    rclcpp::init(argc, argv);
    auto node = rclcpp::Node::make_shared("ObiWan");

    RCLCPP_INFO(node->get_logger(), "Help me Obi-Wan Kenobi.");
   
    rclcpp::shutdown();
    return 0;
}

ObiWanノードを作成。INFOで一言発するようにしている。

CMake.listに以下を追加。

add_execurable(cpp_code src/ros2_cpp_code.cpp)
ament_target_dependencies(cpp_code rclcpp)

install(TARGETS
    cpp_code
    DESTINATION lib/${PROJECT_NAME}
)

src/ros2_cpp_code.cppをcpp_codeの名称でコンパイル?するように指示。
ament_targetでcpp_codeがcppで記載されていることを明記している?

ros2_wsにて以下を実行

$ colcon build --symlink-install  
$ source install/setup.bash
$ ros2 run ros2_cpp_pkg cpp_code

FPGAでVGA出力

背景

最近verilogを用いた画像の描画について勉強したので、IntelのSoc FPGA基板、DE10-Nanoを使ってVGA経由でディスプレイに画像を出力しようと思う。

VGA

VGAはR、G、Bのデータをアナログ出力する。
hsyncでスキャンラインを一ピクセル下に移動させ、一画面表示し終えたらvsyncで画面の左上に戻る。

画像サイズの設定

ディスプレイによって対応可能なVGAの画面サイズとリフレッシュレートが異なる。以下の数値が標準。
f:id:yuji2yuji:20190820164252p:plain

今回は640x480、60Hzで画像を表示する。
使用するクロックは25MHzとする。

ソース

DE1-Nanoのクロックは50MHz、HPS用に25MHzもFPGAに入力されているが、
このクロックはFPGAのクロックには使えなかった。
そのため50MHzを2分周して25MHzを生成した。
リセット信号は基板上のスイッチを使用。

`include "hvsync_generator.v"

/*
A simple test pattern using the hvsync_generator module.
*/

module vga_top(clk, reset, hsync, vsync, rgb);

  input clk, reset;
  output hsync, vsync;
  output [2:0] rgb;
  wire display_on;
  wire [9:0] hpos;
  wire [9:0] vpos;
  
  reg clk_divider2;

  hvsync_generator hvsync_gen(
    .clk(clk_divider2),
    .reset(reset),
    .hsync(hsync),
    .vsync(vsync),
    .display_on(display_on),
    .hpos(hpos),
    .vpos(vpos)
  );

  wire r = display_on && (((hpos&4'b1111)==0) || ((vpos&4'b1111)==0));
  wire g = display_on && vpos[5];
  wire b = display_on && hpos[5];
  assign rgb = {b,g,r};
  
  always @(posedge clk)begin
    clk_divider2 <= ~clk_divider2;
  end
  
endmodule
`ifndef HVSYNC_GENERATOR_H
`define HVSYNC_GENERATOR_H

/*
Video sync generator, used to drive a simulated CRT.
To use:
- Wire the hsync and vsync signals to top level outputs
- Add a 3-bit (or more) "rgb" output to the top level
*/

module hvsync_generator(clk, reset, hsync, vsync, display_on, hpos, vpos);

  input clk;
  input reset;
  output reg hsync, vsync;
  output display_on;
  output reg [9:0] hpos;
  output reg [9:0] vpos;

  // declarations for TV-simulator sync parameters
  // horizontal constants
  parameter H_DISPLAY       = 640; // horizontal display width
  parameter H_BACK          =  48; // horizontal left border (back porch)
  parameter H_FRONT         =  16; // horizontal right border (front porch)
  parameter H_SYNC          =  96; // horizontal sync width
  // vertical constants
  parameter V_DISPLAY       = 480; // vertical display height
  parameter V_TOP           =  31; // vertical top border
  parameter V_BOTTOM        =  11; // vertical bottom border
  parameter V_SYNC          =   2; // vertical sync # lines
  // derived constants
  parameter H_SYNC_START    = H_DISPLAY + H_FRONT;
  parameter H_SYNC_END      = H_DISPLAY + H_FRONT + H_SYNC - 1;
  parameter H_MAX           = H_DISPLAY + H_BACK + H_FRONT + H_SYNC - 1;
  parameter V_SYNC_START    = V_DISPLAY + V_BOTTOM;
  parameter V_SYNC_END      = V_DISPLAY + V_BOTTOM + V_SYNC - 1;
  parameter V_MAX           = V_DISPLAY + V_TOP + V_BOTTOM + V_SYNC - 1;

  wire hmaxxed = (hpos == H_MAX) || !reset;  // set when hpos is maximum
  wire vmaxxed = (vpos == V_MAX) || !reset;  // set when vpos is maximum
  
  // horizontal position counter
  always @(posedge clk)
  begin
    hsync <= (hpos>=H_SYNC_START && hpos<=H_SYNC_END);
    if(hmaxxed)
      hpos <= 0;
    else
      hpos <= hpos + 1;
  end

  // vertical position counter
  always @(posedge clk)
  begin
    vsync <= (vpos>=V_SYNC_START && vpos<=V_SYNC_END);
    if(hmaxxed)
      if (vmaxxed)
        vpos <= 0;
      else
        vpos <= vpos + 1;
  end
  
  // display_on is set when beam is in "safe" visible frame
  assign display_on = (hpos<H_DISPLAY) && (vpos<V_DISPLAY);

endmodule

`endif

Pin PlannerでFPGAのピンアサインを設定する。
f:id:yuji2yuji:20190821164227p:plain

セットアップ

DE10-NanoにはVGA端子がないのでVGAケーブルをばらしてGPIOに接続できるようにする。

VGAコネクタのソケットのピン番号と各ピンの用途は以下の通り。
f:id:yuji2yuji:20190821143929p:plain
f:id:yuji2yuji:20190821143854p:plain

RED_RTN、GREEN_RTN、BLUE_RTNは全てGNDに接続する。
SDAとSCLは未接続でよい。
VGAケーブルをバッサリカットし、Red,Green,Blue,Vsync,Hsync,GNDの合計6本を2.54mmピッチの汎用コネクタを半田付けする。
半田付けする際に、R/G/Bのケーブルには直列に300Ωの抵抗を挿入した。
なぜなら、VGAは規格として信号電圧が最大0.7Vであり、ディスプレイ側で75Ωの抵抗がプルダウンされているため、
DE10-Nanoの出力電圧を3.3Vに設定すると、R/G/Bの信号電圧は300Ωと75Ωで分圧されて0.66Vとなるからである。

わかりにくいけど、DE10-Nanoにケーブルを取り付けた写真。
f:id:yuji2yuji:20190821143101j:plain

*このVGAケーブルはNCの9ピンに線が接続されていた。何故??

結果

映像がでた。
f:id:yuji2yuji:20190821142948j:plain
赤と青のピンアサインを間違えて、伊勢丹みたいになってしまった。まぁいいか。

最初はHsyncとVsyncで勝手に表示画像サイズが決まると勘違いし、画像サイズを適当な値にしていたら、ディスプレイに「Out of Range」と表示されて困った。

その後、画素数とクロックを変更することで事なきを得た。

ここ数週間、色々とFPGAの勉強をしていたけど、こういったアウトプットがあるとモチベーションが高まる。

実践は座学の何倍も価値があるというのを改めて実感。

Verilogでゲームを作る --Designing Video Game Hardware in Verilogの簡易和訳-- part3

Verilogビデオゲーム

Verilogの参考書を探してるとアマゾンで以下の本に出会った。

Designing Video Game Hardware in Verilog

Designing Video Game Hardware in Verilog

プログラミング言語の入門書の多くに、ゲーム作製をテーマにしているものがある。
Verilogでは同様のコンセプトの書籍が見つからなかったので、さっそく購入。
まだ全部読んでいないため、良書かどうかの判断はできないが、
せっかくなので、重要な箇所だけかいつまんで和訳しようと思う。
全部翻訳すると著作権に抵触するらしいので。
なるべく最後まで完結できるように頑張る。

目次はこちら

yuji2yuji.hatenablog.com

17章 スプライト

跳ね返るボールは良いけど、もっと複雑なものを描きたい。
11章で学んだ数字の書き方を応用しよう。

最初の例として、白黒のレースカーのスプライトを作成する。
スプライトは16pixel x 16pixelサイズだ。ただし横方向は左右対称なので8pixelを折り返して表示する。
最初にビットマップROMが必要だ。

moudle car_bitmap(
    input [3:0] yofs,
    output [7:0] bits;
);
    reg [7:0] bitarray[16];
    assign bits = bitarray[yofs];
    initial begin
        bitarray[0]  = 8'b00000000; //
        bitarray[1]  = 8'b00001100; //    **  
        bitarray[2]  = 8'b11001100; //**  **  
        bitarray[3]  = 8'b11111100; //******  
        bitarray[4]  = 8'b11101100; //*** **  
        bitarray[5]  = 8'b11100000; //***     
        bitarray[6]  = 8'b01100000; // **     
        bitarray[7]  = 8'b01110000; // ***    
        bitarray[8]  = 8'b00110000; //  **    
        bitarray[9]  = 8'b00110000; //  **    
        bitarray[10] = 8'b00110000; //  **    
        bitarray[11] = 8'b01101110; // ** *** 
        bitarray[12] = 8'b11101110; //*** *** 
        bitarray[13] = 8'b11111110; //******* 
        bitarray[14] = 8'b11101110; //*** *** 
        bitarray[15] = 8'b00101110; //  * *** 
    end
endmodule

次に11章と同様にYのオフセットを用いて8bitのビットマップをスライスする。
ROMモジュールと同様に入力をyofs、出力をbitsにつなげる。
各スキャンラインで以下を実行する。
スプライトのY座標はスキャンラインと等しい場合は、Yカウンタを15にする。
そうでない場合はYカウンタを0になるまでデクリメントする。

reg[3:0] car_sprite_xofs;
reg[3:0] car_sprite_yofs;

wire[7:0] car_sprite_bits;

car_bitmap car(
    .yofs(car_sprite_yofs),
    .bits(car_sprite_bits)
);

always @(posedge hsync)begin
    if(vpos == player_y)
        car_sprite_yofs <= 15;
    else if(car_sprite_yofs != 0)
        car_sprite_yofs <= car_sprite_yofs -1 ;

X座標についても同様のことをすればよい。

always @posedge clk)
    if(hpos == player_x)
        car_sprite_xofs <= 15:
    else if(car_sprite_xofs != 0)
        car_sprite_xofs <= car_sprite_xofs -1;

スプライトのビットを出力するための式は以下の記載となる。
x方向のビットは8で折り返される点を考慮する。

wire[3:0] car_bit = car_sprite_xofs >= 8? car_sprite_xofs ^ 7 : car_sprite_xofs;
wire car_gfx = car_sprite_bits[car_bit[2:0]];