LoginSignup
0
0

More than 1 year has passed since last update.

Longan Nanoを使ってみる 5 ~ゲームのプロジェクトを作成~

Last updated at Posted at 2022-02-16

前の記事

Longan Nanoを使ってみる 4 ~printfを使ったデバッグ~

全体の目次

Longan Nanoを使ってみる 1 ~ビルド環境の構築~
Longan Nanoを使ってみる 2 ~デバッガの環境設定~
 Sipeed RISC-V Debugger
Longan Nanoを使ってみる 3 ~デバッガの使用方法~
Longan Nanoを使ってみる 4 ~printfを使ったデバッグ~
Longan Nanoを使ってみる 5 ~ゲームのプロジェクトを作成~
Longan Nanoを使ってみる 6 ~文字出力~
 Longan Nanoを使ってみる ~FONTX2ファイルを作る~
Longan Nanoを使ってみる 7 ~外枠とブロックを書く~
 Longan Nanoを使ってみる ~謎の画像表示関数~
Longan Nanoを使ってみる 8 ~ボールを動かす~
Longan Nanoを使ってみる 9 ~A/Dコンバータから入力~
Longan Nanoを使ってみる 10 ~パドルを動かす~
Longan Nanoを使ってみる 11 ~ボタンの入力
Longan Nanoを使ってみる 12 ~ボールのロス~
Longan Nanoを使ってみる 13 ~ステージの遷移とゲームオーバー~
Longan Nanoを使ってみる 14 ~PWMとサウンド~
Longan Nanoを使ってみる 15 ~音楽を鳴らす~
Longan Nanoを使ってみる 16 ~とりあえずのまとめ~

どうやって作るのか決める

ここまでで、開発環境を構築し、Platform IOでビルドから実行、デバッグまでを完結させることができた。
実際に、環境やハードウェアに慣れるには、何か作ってみるのが一番ということで、ブロック崩しゲームでも作ってみることにする。

  • 簡単に開発を行うため、可能な限り、Sipeedが提供したAPIを使用する
  • 表示は本体付属のLCDを使う
  • 入力は可変抵抗器を使い、ADコンバーターを使ってパドルの位置を決める。(ADCの使い方)
  • ボタンは1つ用意する。チャタリングの防止もソフトウェアで行う。
  • スピーカ(圧電スピーカー)を使用し、音を出す。音階は矩形波でPWMを使用する。(PWM出力の使い方)

注意

このページは、quiita.com で公開されています。URLがqiita.com以外のサイト、例えばjpdebug.comなどのページでご覧になっている場合、悪質な無許可転載サイトで記事を見ています。正しいURLは、https://qiita.com/BUBUBB/items/7ce85ada67a3f6d1944d です。
無許可転載サイトでの権利表記(CC BY SA 2.5、CC BY SA 3.0とCC BY SA 4.0など)は、不当な表示です。
正確な内容は、qiitaのページで参照してください。

APIを使用する

そもそも、GD32VF103CBのドキュメントを見ても直接ハードウェアを弄ることに消極的な印象がある。(気のせいかも) その割には、APIは妙に充実しており大概のことはAPIでできそうだ。APIもソースコードが公開されており、APIを使ったからと言って、ハードウェアについて全く理解できないまま進むということもなさそう。

表示は本体付属のLCD

せっかくついているのだから使わない手はない。LCDはLH096TIF09という型番で、ST7735Sをコントローラーに使用している。資料も豊富。接続はSPIだが、LCDについてもSipeedは汎用性の高いサンプルを提供しており、直接プロトコルを意識する必要は少なそうだ。(実際はそうでもなかった)

LCDのライブラリは、日本語表示ができないが、なんと素晴らしい、Sipeed Longan NanoでFONTX2形式のフォントを利用するというページがあった。これを利用させてもらうことにする。

フォントについては、FONTX2形式という古い(DOS/V時代)のもの。いくつかフリーの日本語フォントも提供されているが、小さい液晶画面に表示するのには向いていないものも多い。好きなフォントを使うためにはフォントの変換ツールが必要になる。しかし、フォント関連のツールは今のWindows では動作しなくなっているものも多い。何かツールの作成が必要だろう。

入力は可変抵抗器

これもAPIにライブラリが用意されており使うのは難しくなさそうだ。リファレンス電圧が必要なのかと思ったが、マニュアルGD32VF103_User_Manual_EN_V1.2.pdf (3.3.2. V DD V DD A power domain) を読むと、そもそもLongan Nanoが使用するパッケージにはVREF自体が出ておらず、VDDに接続されているとのこと。さほど精度も必要ないのでかえって楽になりそうだ。

ゲームとしてみると、入力はボリュームではなく、ロータリーエンコーダーのほうがよさそうだ。そのうちやってみることにする。

ボタンは1つ用意

ボタンは、本体に付属しているものも使えるが、不便なのとRESETと近く誤爆の可能性もある。別のボタンを用意する。

音を出す

音は矩形波でPWM機能を使用、ソフトウェアの実行速度に依存せずに正しい周波数で出力する。
ソフトウェア的に面倒なのは、ゲーム本体を動かしたまま、既定の時間で音を止めないといけない。音の停止を、タイマー割込み処理で行う方法もあるが、今回はゲーム全体を一定の割り込み周期で状態遷移させながら処理し、音を止める、という動作もその割り込みに組み込んでいくことにする。

プログラムの骨格を作る

##必要なファイルのダウンロード
Sipeed Longan NanoでFONTX2形式のフォントを利用するを参照し、Githubからプロジェクト全体をダウンロードしておく。

game4.png

(本当は、このプロジェクトに肉付けしていくほうが楽だが、使い方を覚える意味でゼロから作ることにする)

プロジェクトの新規作成

プログラムの骨格を作ることにする。Platform IOからNew Projectをクリック、表示されるダイアログにプラットフォームとプロジェクト名を入力する。
game1.png
game2.png

プロジェクトの作成が終わったら、platformio.iniを開き、RV-Debuggerを使うように次の2行を追加する。参照:設定ファイルの変更

upload_protocol = sipeed-rv-debugger
debug_tool = sipeed-rv-debugger

ダウンロードしたLonganNanoLCD-master.zipを解凍し、次のファイルを作成したプロジェクトの該当するフォルダにコピーする。

LonganNanoLCD-master\src内の、次の2つのファイル
  led.c
  systick.c
LonganNanoLCD-master\src\lcd フォルダ全体
LonganNanoLCD-master\src\fatfs フォルダ全体

LonganNanoLCD-master\include
 gd32v_pjt_include.h
 led.h
 systick.h
LonganNanoLCD-master\include\lcd フォルダ全体
LonganNanoLCD-master\include\fatfs フォルダ全体

出来上がったプロジェクトのファイルエクスプローラー、srcを右クリック、main関数を追加して最初のメイン関数を追加する。

game3.png

追加する内容はなんでも構わないが、LEDの点滅や、printfデバッグができる程度にはしておく。例えば、次のようなコードを挿入する。

#include "lcd/lcd.h"
#include "gd32vf103.h"
#include "fatfs/tf_card.h"
#include "systick.h"
#include <stdio.h>

void printf_debug_init()
{
    // GPIOのAが、USART0なのでGPIOAにクロックを供給する
    rcu_periph_clock_enable(RCU_GPIOA);
    // USART0にクロックを供給
    rcu_periph_clock_enable(RCU_USART0);

    // TX、RXがGPIOAの9と10に出ているので、それぞれを初期化する。
    gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9); // USART0 TX
    gpio_init(GPIOA, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_10); // USART0 RX

    // TXの設定を行う。デバッグでは、ターミナルへの出力だけを行うので、最低限の設定でよい。
    usart_deinit(USART0);
    usart_baudrate_set(USART0, 115200U);
    usart_word_length_set(USART0, USART_WL_8BIT);
    usart_stop_bit_set(USART0, USART_STB_1BIT);
    usart_parity_config(USART0, USART_PM_NONE);
    usart_hardware_flow_rts_config(USART0, USART_RTS_DISABLE);
    usart_hardware_flow_cts_config(USART0, USART_CTS_DISABLE);
    usart_receive_config(USART0, USART_RECEIVE_ENABLE);
    usart_transmit_config(USART0, USART_TRANSMIT_ENABLE);

    //USARTを有効にする
    usart_enable(USART0);
}


int _put_char(int ch)
{
    usart_data_transmit(USART0, (uint8_t) ch );
    while ( usart_flag_get(USART0, USART_FLAG_TBE)== RESET){
    }
    return ch;
}

int main( void ) 
{

    printf_debug_init();
        printf("start!");

    led_init();

	while(1){
        led_on(BLUE);
        printf("ON\r\n");
        delay_1ms(1000);
        led_off(BLUE);
        printf("OFF\r\n");
        delay_1ms(1000);
	}
};	

ビルドと実行ができることを確認しておく。

ゲーム本体の処理を作成する

今回、ゲームはサウンドとゲーム本体を含め、タイマーを使って動かしていく。プロジェクトにgame.cとgame.hを追加し、その中に処理を記述していくことにする。最初に実装するのは、タイマー割込みを使ってゲームのメイン処理を回していく。

タイマーの使い方

gd32vf103には、いくつかのタイマーがあるが、松竹梅と機能が分かれている。GD32VF103_User_Manual_EN_V1.2.pdfの15. Timer(TIMERx)に詳しい。

システムアーキテクチャを見ると、TIMER0だけは108MHzのバスに接続されて、それ以外は54Mhzバスに接続されている。

bus1.png
タイマー 機能
TIMER0 Advanced Timer
すべての機能を持っている。APB1に接続されており、最大108MHz。
TIMER1~4 Level0 Timer
おおむねAdvanced Timerと同じ機能を持つが、repetitionやbreakなど、あまり使わない機能が省略されている。最大54MHz
TIMER5~6 BASIC Timer
外部入出力ができない、基本的な機能だけを持ったタイマー

プログラムの制御を行うのであれば、外部入力も何も不要なので、TIMER0~4はもったいない。というわけでTIMER5を使って、定期的にプログラムを動かすことにする。

注意
TIMER1~6は、54MHzのバスにつながっているはずなのだが、実際にタイマーを動かすと108MHzのバスにつながっているような動作をする。調べたのだが、今一つ理由がわからない。私の頭が悪いのだと思うが、まずは現物合わせて、108MHzがプリスケーラに入力される、としてプログラムを書くことにする。

誰か、本当に教えてください!!

タイマーをAPIから使用するには次のような手順となる。

  • rcu_periph_clock_enable(RCU_TIMER5); を実行し、TIMER5にクロックを供給する
  • timer_deinit(TIMER5);を実行し、タイマーをリセットする。
  • timer_parameter_struct 構造体に必要なパラメータを設定する。最初に、timer_struct_para_initを呼び出すことで、基本的な初期化を行うことができ、手間が省ける。
  • timer_init(TIMER5, タイマーパラメータ);を実行し、Timer5にパラメータを指定する。
  • timer_auto_reload_shadow_enable(TIMER5);オートリロードのシャドウレジスタを有効にする。オートリロードレジスタの更新タイミングの調整だが、今回の用途では正確なタイマーを必要としないので別に実行する必要もない。
  • timer_interrupt_enable(TIMER5, TIMER_INT_UP); タイマーの割り込みソースを指定する。 TIMER5/6はチャンネルがないので、TIMER_INT_UP一択。

次に、タイマーにより、プログラムの特定の関数を呼び出すように指定する。これらの処理は、eclic_XXXXという名前のAPIを使用すると、

  • eclic_global_interrupt_enable(); を実行し、割り込みを有効にする
  • eclic_set_nlbits(ECLIC_GROUP_LEVEL3_PRIO1);を呼び出す。サンプルにあるのでそのままだが、ドキュメントに書かれていない・・・
  • eclic_irq_enable(TIMER5_IRQn,1,0);を実行し、割り込みハンドラを有効にする。割り込みハンドラは、 TIMERX_IRQHandler(void) (Xは0~6)という固定の名前になるので、自分でこのメソッドをオーバーライドして使用する。

くどくどと説明するより、ソースコードを見たほうが早いとおもわれる。

game.h

game.hではまだほとんど何もしない。ここでは、ゲームの状態を定義するenumを用意している。game本体は、この状態に応じて処理を行い、状態遷移させる。game.cは、STATE_INITから始まり様々な状態を繰り返していく。

#ifndef __GAME_H__
#define __GAME_H__
extern void Game(void);

enum GAMESTATE {
	STATE_INIT,			// 初期化処理中
	STATE_STARTGAME,	// ゲーム開始(初期化中)
	STATE_INGAME,		// ゲーム中
	STATE_GAMEOVER,		// ゲームオーバー処理中
};

#endif

game.c

game.cはゲームの本体を実装していくところになる。現時点では、タイマーの初期化と割り込み処理、それに連携して処理を行うだけになっている。game()内のswitch文が、ゲームのメイン処理となり、基本的にはここに実装をしていくことになる。ボールを動かし、衝突判定をして…と、処理は細分化して一回のタイマーで1つだけ実行する。

今のところ、LEDを点滅させる処理だけを挿入してある。

こうすることで、音を自由なタイミングで(といってもタイマー割込みがかかるタイミングで)止めることができる。

volatile u8 WakeFlag = 0; という行がある。volatileなんて知らない、という人がいるかもしれないが、組み込みシステムなどではよく使われる修飾子で、最適化からこの変数が除外される。 例えば、while(WakeFlag == 0) {} という行は、wakeFlag が変更されることがない。そのためwakeFlagは最適化により削除され、 while(1){} と同じコードが生成される。こうなると、割り込み処理内で wakeFlag=1と設定しても、whileループを抜けることがなくなってしまう。こうした、非同期な変数の変更を使うとき、最適化から守るのがvolatile。volatileをつけると、この変数は最適化により削除されることはなくなる。

#include "lcd/lcd.h"
#include "led.h"
#include "memory.h"
#include "gd32vf103.h"
#include "game.h"


enum GAMESTATE gameState;	    // ゲームの状態
volatile u8 WakeFlag = 0;       // このフラグが1になると、処理が開始される

//
// 割り込みハンドラ。タイマーにより指定した周期で非同期に呼び出される
//
void TIMER5_IRQHandler(void)
{
    if(SET == timer_interrupt_flag_get(TIMER5, TIMER_INT_FLAG_UP)){ // 割り込みフラグが立っていたら
        timer_interrupt_flag_clear(TIMER5, TIMER_INT_FLAG_UP);  // 割り込みフラグを下す
    	WakeFlag = 1;                                           // ウェイクフラグを立てて、メイン処理を進める
    }
}

//
// タイマーの初期化
//
void timer5_config(int Cnt)
{
    // タイマーのパラメータを設定する。
	// タイマーの16ビットプリスケーラには54MHzが入力される・・・はずだが、108MHzが入力されるように振舞っている。
	// それを、10000分周(timer_initpara.prescaler = 10000-1)すると、おおよそ108,000,000/10000= 10.8Khz
    // それを、引数の cnt回数えて(timer_initpara.period = Cnt;)タイマーの周期を決める。
	// 例えば、cntが30の時は、10,800 / 30 =360で、360Hzとなる。
    timer_parameter_struct timer_initpara;
    rcu_periph_clock_enable(RCU_TIMER5);
    timer_deinit(TIMER5);
    timer_struct_para_init(&timer_initpara);
    timer_initpara.prescaler         = 10000 - 1;
    timer_initpara.alignedmode       = TIMER_COUNTER_EDGE;
    timer_initpara.counterdirection  = TIMER_COUNTER_UP;
    timer_initpara.period            = Cnt;
    timer_initpara.clockdivision     = TIMER_CKDIV_DIV1;
    timer_init(TIMER5, &timer_initpara);
    timer_auto_reload_shadow_enable(TIMER5);
    timer_interrupt_enable(TIMER5, TIMER_INT_UP);

    // 割り込みを有効にして、タイマー5を設定する
    eclic_global_interrupt_enable();
	eclic_set_nlbits(ECLIC_GROUP_LEVEL3_PRIO1);
	eclic_irq_enable(TIMER5_IRQn,1,0);
    
    // タイマーを開始する
    timer_enable(TIMER5);
}

//
// メイン処理
//
void Game(void) 
{
    // 初期化処理
    gameState = STATE_INIT;             // ステータスを初期化にする
    timer5_config(30);                  // タイマーの初期化を行う
    u16 tick = 0;                       // LEDを点滅させるためのカウンターを初期化
    gameState = STATE_STARTGAME;        //初期化処理が終了したのでゲーム開始処理を行う
    
    while (TRUE) {   // 今のところはゲームを終了しない
		timer_enable(TIMER5);               // タイマーを有効にする
        // タイマーのウェイト処理。wakeFlagが割り込みルーチン内で1になるまで無限ループする
		while(WakeFlag == 0) {
		}
        tick = (tick + 1) & 0x8FFF;     // LEDの点滅用カウンタのインクリメント
		WakeFlag = 0;                   // タイマーのウエイトフラグを初期化する
        
        switch (gameState) {
            case STATE_STARTGAME:{
                /*ゲームの開始処理 */
                gameState = STATE_INGAME;
                break;
            }
            case STATE_INGAME:{
                /*ゲームのメイン処理*/
                /*ゲームオーバーになったら、 gameState = STATE_GAMEOVER; を実行*/
                if ((tick % 100) < 50) {
                    led_on(LED_R);
                } else {
                    led_off(LED_R);
                }                
                break;                
            }
            case STATE_GAMEOVER:{
                /*ゲーム-オーバー処理*/
                break;
            }
        }
    }
}

ここで、タイマーパラメータ構造体を設定しているが、意味は次のようになる。

  • timer_initpara.prescaler = 10000 - 1;
    プリスケーラの指定。54MHz / 10000 がタイマーの周期になるはずだが、108Mhz / 10000という動作になる。何か私が勘違いしているのだろう。カウントはクロックの立ち上がりと立下りで両方カウントされるのかな?だとしたらTimer0は216MHzになっちゃうが・・・ 
  • timer_initpara.alignedmode = TIMER_COUNTER_EDGE;
    タイマーのアラインメントモードをEDGEにする。タイマーの最大値を100(後で出てくる .periodで指定)とすると、のカウンタは、1,2,3,…99と進み、次に0に戻る。カウンタが0になった時に、割り込みがかかる。
  • timer_initpara.counterdirection = TIMER_COUNTER_UP;
    タイマーのカウンタは増加。
  • timer_initpara.period = Cnt;
    タイマーカウンタの最大値
  • timer_initpara.clockdivision = TIMER_CKDIV_DIV1;
    デッドタイムで使用する。
  • timer_initpara.repetitioncounter = 0;
    リピートカウンタ。この値が2だったら、さらにタイマーカウンタが2回ゼロになって初めて割り込みが発生する。プリスケーラとタイマーカウンタだけで足りないときに使用される。

下の左図は、TIMER_COUNTER_UPを指定したタイマーに関するタイミングチャート。(プリスケーラで指定された)一定の時間間隔でカウンタは階段状に上がっていく。カウンタはCAR(timer_initpara.period )で指定された値まで行くと、0に戻る。

pwm2.png

main.c

main.cは、最初に用意したものに対して、game.hをincludeして Game();を呼び出す処理を追加する。

#include "lcd/lcd.h"
#include "gd32vf103.h"
#include "fatfs/tf_card.h"
#include "systick.h"
#include <stdio.h>
#include "game.h"

void printf_debug_init()
{
    // GPIOのAが、USART0なのでGPIOAにクロックを供給する
    rcu_periph_clock_enable(RCU_GPIOA);
    // USART0にクロックを供給
    rcu_periph_clock_enable(RCU_USART0);

    // TX、RXがGPIOAの9と10に出ているので、それぞれを初期化する。
    gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9); // USART0 TX
    gpio_init(GPIOA, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_10); // USART0 RX

    // TXの設定を行う。デバッグでは、ターミナルへの出力だけを行うので、最低限の設定でよい。
    usart_deinit(USART0);
    usart_baudrate_set(USART0, 115200U);
    usart_word_length_set(USART0, USART_WL_8BIT);
    usart_stop_bit_set(USART0, USART_STB_1BIT);
    usart_parity_config(USART0, USART_PM_NONE);
    usart_hardware_flow_rts_config(USART0, USART_RTS_DISABLE);
    usart_hardware_flow_cts_config(USART0, USART_CTS_DISABLE);
    usart_receive_config(USART0, USART_RECEIVE_ENABLE);
    usart_transmit_config(USART0, USART_TRANSMIT_ENABLE);

    //USARTを有効にする
    usart_enable(USART0);
}


int _put_char(int ch)
{
    usart_data_transmit(USART0, (uint8_t) ch );
    while ( usart_flag_get(USART0, USART_FLAG_TBE)== RESET){
    }
    return ch;
}



int main( void ) 
{

    printf_debug_init();
    led_init();
    Game();
    
	while(1){
	}
};	

将来のために関数を用意しておく

プログラムが大きくなった時のため、あらかじめソースコードとヘッダを用意しておく。ブロック崩しなので、確実にブロックとボールとパドルと壁が存在する。事前に、これらのファイルを追加しておく。ファイルの追加は、エクスプローラで追加した場所(.cなら src、.hならinclude)を右クリック、[新しいファイル]を指定する。

addfile1.png

追加するファイルは、とりあえず.c を4本、対応する.hを4本。

ball.h、block.h、wall.h、paddle.h

ヘッダファイルには、定番の二重インクルードを防止する処理を記述する。ソースコード中、__ball_h__の部分は、ファイル名に応じて書き換えること。

#ifndef __ball_h__
#define __ball_h__
// ここにこれから何かが書かれていく
#endif

ball.c、block.c、wall.c、paddle.c

ソースコードは最初はすべて同じで構わない。

#include "lcd/lcd.h"
#include "led.h"
#include "memory.h"
#include "gd32vf103.h"
#include "game.h"
#include "ball.h"
#include "wall.h"
#include "block.h"
#include "paddle.h"

// ここから下にプログラムが追加されていく

プログラムを実行する

ビルドしてプログラムを実行すると、LEDが点滅する。「結局Lチカかよ」と思うかもしれないが、このLチカはそれなりに複雑?な点滅をしているわけで、普通のLチカとは同じではない・・・と思うこと。

ここまででできたこと

開発に必要なファイルを追加したプロジェクトを作成できた。タイマーによる割り込み処理を実装できた。

次の記事

Longan Nanoを使ってみる 6 ~文字出力~

今回の修正を反映したソースコード

main.c

#include "lcd/lcd.h"
#include "gd32vf103.h"
#include "fatfs/tf_card.h"
#include "systick.h"
#include <stdio.h>

void printf_debug_init()
{
    // GPIOのAが、USART0なのでGPIOAにクロックを供給する
    rcu_periph_clock_enable(RCU_GPIOA);
    // USART0にクロックを供給
    rcu_periph_clock_enable(RCU_USART0);

    // TX、RXがGPIOAの9と10に出ているので、それぞれを初期化する。
    gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9); // USART0 TX
    gpio_init(GPIOA, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_10); // USART0 RX

    // TXの設定を行う。デバッグでは、ターミナルへの出力だけを行うので、最低限の設定でよい。
    usart_deinit(USART0);
    usart_baudrate_set(USART0, 115200U);
    usart_word_length_set(USART0, USART_WL_8BIT);
    usart_stop_bit_set(USART0, USART_STB_1BIT);
    usart_parity_config(USART0, USART_PM_NONE);
    usart_hardware_flow_rts_config(USART0, USART_RTS_DISABLE);
    usart_hardware_flow_cts_config(USART0, USART_CTS_DISABLE);
    usart_receive_config(USART0, USART_RECEIVE_ENABLE);
    usart_transmit_config(USART0, USART_TRANSMIT_ENABLE);

    //USARTを有効にする
    usart_enable(USART0);
}


int _put_char(int ch)
{
    usart_data_transmit(USART0, (uint8_t) ch );
    while ( usart_flag_get(USART0, USART_FLAG_TBE)== RESET){
    }
    return ch;
}

int main( void ) 
{

    printf_debug_init();
        printf("start!");

    led_init();

	while(1){
        led_on(BLUE);
        printf("ON\r\n");
        delay_1ms(1000);
        led_off(BLUE);
        printf("OFF\r\n");
        delay_1ms(1000);
	}
};	

game.h

#ifndef __GAME_H__
#define __GAME_H__
extern void Game(void);

enum GAMESTATE {
	STATE_INIT,			// 初期化処理中
	STATE_STARTGAME,	// ゲーム開始(初期化中)
	STATE_INGAME,		// ゲーム中
	STATE_GAMEOVER,		// ゲームオーバー処理中
};

#endif

game.c

#include "lcd/lcd.h"
#include "led.h"
#include "memory.h"
#include "gd32vf103.h"
#include "game.h"


enum GAMESTATE gameState;	    // ゲームの状態
volatile u8 WakeFlag = 0;       // このフラグが1になると、処理が開始される

//
// 割り込みハンドラ。タイマーにより指定した周期で非同期に呼び出される
//
void TIMER5_IRQHandler(void)
{
    if(SET == timer_interrupt_flag_get(TIMER5, TIMER_INT_FLAG_UP)){ // 割り込みフラグが立っていたら
        timer_interrupt_flag_clear(TIMER5, TIMER_INT_FLAG_UP);  // 割り込みフラグを下す
    	WakeFlag = 1;                                           // ウェイクフラグを立てて、メイン処理を進める
    }
}

//
// タイマーの初期化
//
void timer5_config(int Cnt)
{
    // タイマーのパラメータを設定する。
	// タイマーの16ビットプリスケーラには54MHzが入力される・・・はずだが、108MHzが入力されるように振舞っている。
	// それを、10000分周(timer_initpara.prescaler = 10000-1)すると、おおよそ108,000,000/10000= 10.8Khz
    // それを、引数の cnt回数えて(timer_initpara.period = Cnt;)タイマーの周期を決める。
	// 例えば、cntが30の時は、10,800 / 30 =360で、360Hzとなる。

    timer_parameter_struct timer_initpara;
    rcu_periph_clock_enable(RCU_TIMER5);
    timer_deinit(TIMER5);
    timer_struct_para_init(&timer_initpara);
    timer_initpara.prescaler         = 10000 - 1;
    timer_initpara.alignedmode       = TIMER_COUNTER_EDGE;
    timer_initpara.counterdirection  = TIMER_COUNTER_UP;
    timer_initpara.period            = Cnt;
    timer_initpara.clockdivision     = TIMER_CKDIV_DIV1;
    timer_init(TIMER5, &timer_initpara);
    timer_auto_reload_shadow_enable(TIMER5);
    timer_interrupt_enable(TIMER5, TIMER_INT_UP);

    // 割り込みを有効にして、タイマー5を設定する
    eclic_global_interrupt_enable();
	eclic_set_nlbits(ECLIC_GROUP_LEVEL3_PRIO1);
	eclic_irq_enable(TIMER5_IRQn,1,0);
    
    // タイマーを開始する
    timer_enable(TIMER5);
}

//
// メイン処理
//
void Game(void) 
{
    // 初期化処理
    gameState = STATE_INIT;             // ステータスを初期化にする
    timer5_config(30);                  // タイマーの初期化を行う
    u16 tick = 0;                       // LEDを点滅させるためのカウンターを初期化
    gameState = STATE_STARTGAME;        //初期化処理が終了したのでゲーム開始処理を行う
    
    while (TRUE) {   
		timer_enable(TIMER5);               // タイマーを有効にする
        // タイマーのウェイト処理。wakeFlagが割り込みルーチン内で1になるまで無限ループする
		while(WakeFlag == 0) {
		}
        tick = (tick + 1) & 0x8FFF;     // LEDの点滅用カウンタのインクリメント
		WakeFlag = 0;                   // タイマーのウエイトフラグを初期化する
        
        switch (gameState) {
            case STATE_STARTGAME:{
                /*ゲームの開始処理 */
                gameState = STATE_INGAME;
                break;
            }
            case STATE_INGAME:{
                /*ゲームのメイン処理*/
                /*ゲームオーバーになったら、 gameState = STATE_GAMEOVER; を実行*/
                if ((tick % 100) < 50) {
                    led_on(LED_R);
                } else {
                    led_off(LED_R);
                }                
                break;                
            }
            case STATE_GAMEOVER:{
                /*ゲーム-オーバー処理*/
                break;
            }
        }
    }
}

ball.h

#ifndef __ball_h__
#define __ball_h__

#endif

ball.c

#include "lcd/lcd.h"
#include "led.h"
#include "memory.h"
#include "gd32vf103.h"
#include "game.h"
#include "ball.h"
#include "wall.h"
#include "block.h"
#include "paddle.h"

block.h

#ifndef __block_h__
#define __block_h__

#endif

block.c

#include "lcd/lcd.h"
#include "led.h"
#include "memory.h"
#include "gd32vf103.h"
#include "game.h"
#include "ball.h"
#include "wall.h"
#include "block.h"
#include "paddle.h"

wall.h

#ifndef __wall_h__
#define __wall_h__

#endif

wall.c

#include "lcd/lcd.h"
#include "led.h"
#include "memory.h"
#include "gd32vf103.h"
#include "game.h"
#include "ball.h"
#include "wall.h"
#include "block.h"
#include "paddle.h"

paddle.h

#ifndef __paddle_h__
#define __paddle_h__

#endif

paddle.c

#include "lcd/lcd.h"
#include "led.h"
#include "memory.h"
#include "gd32vf103.h"
#include "game.h"
#include "ball.h"
#include "wall.h"
#include "block.h"
#include "paddle.h"
0
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
0
0