しばっち電機
-

110.RTC(リアルタイムカウンタ)を使ってみよう (時計編)

110-1. はじめに

2011/10/02↓

SmartFusionにはタイマー系が3種類搭載されています。
1.Watchdog Timer
2.Real-Time Counter(RTC)
3.System Timer
この中で今回は Real-Time Counterを使って見ることにします。

RTCというと、時計!ってことで、まず最初はデバッガでRTCの動作を確認してから、24H時計を作って最終的にはOLEDに表示してみたいと思います。時刻設定はSW1,SW2もしくはJ22のpinを使ってみます。

資料は以下の3つでよさそうです。
Actel SmartFusion? Microcontroller Subsystem User’s Guide http://www.actel.com/documents/SmartFusion_MSS_UG.pdf
の 16 ? Real-Time Counter (RTC) System の箇所、
SmartFusion: Interfacing with OLED using I2C http://www.actel.com/documents/SmartFusion_Interfacing_with_OLED_using_I2C_AN.pdf
SmartFusion Evaluation Kit User’s Guide A2F_EVAL_KIT_UG.pdf

110-2. RTCを見てみよう

16 ? Real-Time Counter (RTC) System をさらっと読んでみると(英語は苦手なのでさらっとしか読めないという話も...)、
ポイントは
  • 32.768kHzが入力されていて、それを中で128分周して、256Hz単位としている。(32768/128=256)
  • カウントレジスタは40bitあって、4,294,967,296秒まで測れるので136年位測れる。(注:2^32=4,294,967,296ね)
  • カウンタと一致する値を設定する、マッチレジスタがある。
  • カウンタと個別に一致する値を設定する、インディビジュアルマッチレジスタがある。
  • それぞれ40bit(5バイト)であるが、読みだすときはまず最下位のバイトを読みだしてから上位を読んでいくこと。
  • コントロール/ステータスレジスタがあり、RTCやカウンタ有効などの制御ができる。
といったところのようです。
読むときは最下位からとありますが、書くときはどうなのかとか、インディビジュアルマッチレジスタってどういう動作をするのかとかは説明に書いて無いようですので、実際に動かして確かめてみることにします。

110-3. RTCを使った時計の仕様を決定しよう

そんなに機能があるわけでもないのでさくっと決めます。
  • 時計は24時間計とする。
  • OLEDへの表示は 12:34:56 のように秒まで表示する。
  • SW1を押すと時の桁が1秒に1づつカウントアップ、SW2を押すと分の桁が1秒に1づつカウントアップ、SW1,SW2両押しで秒を0にする。
  • MSSはRTCとI2C(OLED)だけで構成する。
  • FablicはSW1,SW2の読取に使用しする。APBのアドレスはFabliceエリアの最初(最下位)、ビット割り当てはbit0にSW1、bit4にSW2を割り当てる。リードオンリーとし、ライトした場合は何もしない。
  • リアルタイムカウンタの値を読み取り、ARMで計算で時分秒に変換しI2CによりOLEDに書きこむ。SW1,SW2の状態もモニタしそれに応じてカウンタの値を更新する。
ざっと、こんなところです。また何かあれば追記します。


110-4. MSSを構成しよう


今回はSW1,SW2の状態を読み取るために少しだけですがFablicを触るので、いつものようにLiberaoIDEでプロジェクトを作るところから始めます。
細かい手順は 8.チュートリアルに沿って試してみよう (UART LiberoIDE フロー)や100.ともかくFablic(FPGA)を使ってみよう (簡単計算機編)をみてください。

2011/10/02↑

2011/10/09↓

ざっと説明すると、ポイントはMSSではFablic InterfaceだけMasterとして残す。クロックは20MHzでFABへの供給も行う。リセットもFABへ供給を行う。といったところです。
  1. LiberoIDEのProject Managerを立ち上げます。
  2. Project->New Projectで、Project name:rtc_clock 、 Project location:C:\Actelprj\rtc_clock\rtc_clock 、 Verilog とします。
  3. Family:SmartFusion、Die:A2F200M3F、Package:484FBGA 、あとはNext
  4. Add Microcontroller Subsystemで、 Design Name:rtc_clock
  5. Smart Design MSS Configurater で Fablic Interface と、I2C_0 だけチェックを残し、他はチェックを外します。 (I2C_0を追記 2011/10/30)
  6. Clock Management を開いて、PLLの右側の3inセレクタを真中を選択、その横のクロクを20.000MHzに変更、FAB_CLK(GLA1)をチェック
  7. Fablic Interfaceを開いて、Interface Type が AMBA APB3、Use Master Interface にチェックを入れ OK
  8. Reset Managementを開いて、Enable MSS To Fablic Reset にチェックを入れOK
  9. Smart Design MSS Configurater で File->Save rtc_clock 後、Design->Generate。完了したらFile->Exit

110-5. Fabの回路設計をしよう


今回はSW1,SW2の値を取り込むだけ!。
アドレス割り当ては0番地、ビット割り当てはbit0にSW1、bit4にSW2とします。
回路図は以下のようにあっさりと。デコーダも必要ないかもしれません。SW1,SW2をDFFで叩いてPRDATAでも問題無いとおもいますが、一応キッチりとデコードしました。0番地をリードした時のみSW1,SW2の値が読み出せるようにしています。

   

タイミング図は以下のようになります。
   

これを元にRTLを書くと
注:2011/10/23 リセットを入れ忘れていたのと誤記があったので修正しました。
注:2011/12/25 リード時のPENABLEを削除しました。
///////////////////////////////////////////////////////////////////////////////////////////////////
// Company: Shibatchii Denki
//
// File: rtc_clock
// File history:
//      0.2: 2011/12/25: Modify PENABLE
//      0.2: 2011/10/23: Modify reset
//      0.1: 2011/10/09: New
//      : : 
//
// Description:
//
// RTC Clock
//
// Targeted device: SmartFusion A2F200M3F 484FBGA
// Author: @shibatchii
//
///////////////////////////////////////////////////////////////////////////////////////////////////
// このヘッダは Templates の Verilog->Common constructs -> File Header を使用

module rtl_clock(
    PCLK,
    PRESETn,
    PADDR,
    PSELx,
    PENABLE,
    PWRITE,
    PWDATA,
    PREADY,
    PRDATA,
    PSLVERR,
    SW1,
    SW2
);

// --------------------------------------------------------------------------

    // 入力宣言
    input           PCLK;       // クロック。回路で使用する基準クロック入力
    input           PRESETn;    // リセット。最初に回路を初期化する信号
    input   [7:0]   PADDR;      // アドレス。32bit単位アクセスなので下位2ビットは未使用
    input           PSELx;      // セレクト。この回路が選択されたことを示す
    input           PENABLE;    // イネーブル。データ転送期間であることを示す
    input           PWRITE;     // ライト、ライト時1、リード時0
    input   [31:0]  PWDATA;     // ライトデータ、32bitのうち下8bit使用
    input           SW1;        // SW1
    input           SW2;        // SW2入力

    // 出力宣言
    output          PREADY;     // レディ。今回は1固定で使用
    output  [31:0]  PRDATA;     // リードデータ、未使用のビットは0を出力
    output          PSLVERR;    // スレーブエラー。今回は0固定で使用

// --------------------------------------------------------------------------

    // 内部使用信号(reg)
    reg             w_dec_0;    // 0番地リード有効
    reg             r_sw1;      // SW1 リタイミング1
    reg             r_sw2;      // SW2 リタイミング1
    reg             r_sw1d;     // SW1 リタイミング2
    reg             r_sw2d;     // SW2 リタイミング2
    reg             r_sw1dd;    // SW1 出力選択後1
    reg             r_sw2dd;    // SW2 出力選択後

// --------------------------------------------------------------------------

    // 出力接続
    assign  PREADY          =   1'b1;
    assign  PRDATA[31:0]    =   {27'd0,r_sw2dd,3'd0,r_sw1dd};
    assign  PSLVERR         =   1'b0;

// --------------------------------------------------------------------------

    // 入力リタイミング
    always @( posedge PCLK or negedge PRESETn)begin : pr_r_sw
        if(PRESETn==1'b0)begin
            r_sw1 <= 1'b0;
            r_sw2 <= 1'b0;
            r_sw1d <= 1'b0;
            r_sw2d <= 1'b0;
        end
        else
        begin
            r_sw1 <= SW1;
            r_sw2 <= SW2;
            r_sw1d <= r_sw1;
            r_sw2d <= r_sw2;
        end
    end //

    // 0番地リード有効
    always @(
            PADDR
        or  PSELx
        or  PENABLE
        or  PWRITE
    ) begin : pr_w_dec_0
        // アドレス0で、このブロックがセレクト、リードの時
        if((PADDR[7:2] == 6'b0000_00) && (PSELx == 1'b1) && (PWRITE == 1'b0))begin
            w_dec_0 = 1'b1;
        end
        // それ以外
        else
        begin
            w_dec_0 = 1'b0;
        end //
    end


    // 出力有効
    always @( posedge PCLK or negedge PRESETn)begin : pr_r_swdd
        if(PRESETn==1'b0)begin
            r_sw1dd <= 1'b0;
            r_sw2dd <= 1'b0;
        end
        // アドレス0リードの時
        else
        if(w_dec_0 == 1'b1)begin
            r_sw1dd <= r_sw1d;
            r_sw2dd <= r_sw2d;
        end
        // それ以外
        else
        begin
            r_sw1dd <= 1'b0;
            r_sw2dd <= 1'b0;
        end
    end

// --------------------------------------------------------------------------

endmodule
        
こんな感じです。

2011/10/09↑

2011/10/16↓

ここまでできたらあとはSmartDesginでインスタンスします。いままでのcalcとかtutorialとかとほとんど一緒なのでさくっと記録しておきますね。
  1. Project Manager の Project Flow から SmartDesignをクリックします。
  2. SmartDesign Compornetで Name: rtc_clk と入れ OK をクリックします。(すでに作成したプロジェクト名と同じだとNGのようです)
  3. 左ペーンのDesign Exploreでrtc_clkを左クリック後右クリックし、Set As Root します。rtc_clkが太字になります。
  4. 左ペーンのrtc_clock (IPのrtl_clock)をドラッグしてCanvasに持ってきてドロップします。
  5. 左ペーンのrtc_clock (RTLのrtl_clock)をドラッグしてCanvasに持ってきてドロップします。
  6. 右ペーンのCatalogのCoreタブでBus Interfaces のCareAPB3をドラッグドロップします。
  7. Configuring CoreAPB3_0が開きます。
  8.  Direct or Indirect Addressing を Direct Addressing(legacy)にします。
  9.  APB Master Data Bus Width は 32bit
  10.  Enable APB Slave Slots を Slot 0: だけチェックを残し後はチェックを外します。
  11.  APB Slot Sizeは 256 locations にします。
  12.  Testbench:はUserにします。
  13. OKをクリックします。
  14. 右ペーンのCatalogからBus Definisionsタブを選択します。
  15. APB AMBA AMBA2 slave を選択し、真中ペーンのrtl_clock_0 の上にドラッグドロップします。間違えやすいので注意!。
  16. Add Bus Interface to [rtl_clock_0]が開くので Signal Map DefinitionのInstanceのSignalの表部分をクリックするとドロップダウンで信号名が開くので、左のBus DefinitionのSignalにあるにあわせていきます。RTLを作成するときにAPBの信号名で作成したので同じ名前のはずです。
  17. 設定し終えたらOKで閉じます。
  18. 真中ペーンのCanvasで左クリック後右クリックし、AutoConnectを選択します。
  19. Modify Memory Mapが開きます。今回は1モジュールだけなのでそのままOKします。自動的に配線されたはずです。
  20. Canvasで左クリック後右クリックで、Auto Arrange Instances を選びます。きれいに整えられるはずです。
  21. Canvasで左クリック後右クリックで、Auto Arrange Connectionsを選びます。Arrangeできれいにならんだので今回は何も変わらないかもしれません。
  22. SW1,SW2の接続がまだなので、これを外部ピン(ボード上のSW1,SW2が接続されているピン)に割り当てます。
  23. Canvasの灰色の所(インスタンスがある白色では無く、外側)で左クリック後右クリックします。
  24. Add Port を選択します。Add New Port が開くので Name:SW1 Direction:Inputにします。SW2も同様にします。
  25. ツールバーのSmartDesign->Show I/O Attribute Editor View を開きます。これで各ポートのピン割り当てとか、今回は変更しませんがインターフェース電圧とか指定します。
  26. PortNameが3つ示されているはずです。
  27. SW1のPin Number をドロップダウンリストからG19を選択します。Use I/O Regもチェックします。
  28. SW2のPin Number をドロップダウンリストからG20を選択します。Use I/O Regもチェックします。
  29. SW1,SW2の割り当てられいるピンNo.はSmartFusion Evaluation Kit User’s Guide (A2F_EVAL_KIT_UG.pdf)のP17参照です。Use I/O RegはPadのところにあるDFFを使用することを指定します。入力遅延を小さくする事ができます。今回の場合は選択しなくてもあまり影響は無いと思いますが、一応設定しておきます。
  30. Canvasの灰色の部分で左クリック後右クリックで、Edit Connectionsを選択します。Gridペーンが開きます。ここで接続まだ接続されていないSW1,SW2の接続関係を指定します。
  31. rtl_clock_0 の SW1を横に見て行ってrtc_clkとの交点をクリックしドロップダウンからSW1を選択します。
  32. rtl_clock_0 の SW2を横に見て行ってrtl_clkとの交点をクリックしドロップダウンからSW2を選択します。
  33.    
  34. これで接続の指定ができました。PortNameのIのホームベースのような形の所が緑色になりました。
  35. Canvasに戻るとSW1,SW2が接続されています。もしされていなかったら、左クリック後右クリックでAuto Connectします。
  36.    
  37. これで接続できたのでCtrl+Sでセーブします。
  38. ツールバーのSmartDesign->Check Design Rulesを行なって「Design Rules Check of SmartDesgin "rtc_clk" found no errors and no warning.」となったらOKです。もしエラーがあればCanvasに戻り修正します。
  39. Smartdesign->Generate Design を行います。「Design "rtc_clk" was successfully generated.」とでれば完成です。
  40. OKで閉じます。
リスト形式でザクッと書いていますが、この辺りは簡易計算機とかTutorialとかと同じです。ちょっと異なるのは外部ポート(PIN)とそれらと内部回路の接続設定の所です。今回は2本でしたが、数が数十本とかになってきたらドロップダウンで選択するのも大変だろうなと思います。ただ、ポートはグループ化できるようなので、グループでまとめて指定できるのかもしれません。これはまた今度試して見ることにします。

2011/10/16↑

2011/10/23↓

110-6. Cプログラムを設計をしよう

これからSmartFusionの中のCortex-M3のプログラム設計です。まずはフローチャートなど書いてみます。
基本的には、RTC設定、I2C(OLED)の設定をして、後はループを回してRTCのステートを読んで、1秒経過したらSW1,SW2チェックし時刻設定、もしくは1秒カウントアップすれば良いはずです。
   

フローチャートはできたものの、では実際にどのようにRTCとI2S(OLED)にアクセスするかと調べてみます。

まずはRTCから
User's Guideの 16-Real-Time Counter(RTC) System の所をみると
CTRL_STAT_REG(0x40014160)で、RTCの動作を設定し、MATCHREG*_REGに1秒の値を設定し、一致したら0に戻ってカウントという動作をさせればよさそうです。

CTRL_STAT_REGの設定は
bit7:RTC_RST:1でリセット、0で解除。よって初期化の時に1回1にした後0にすればOKです。
bit6:CNTR_EN:1でカウンタ動作、0でカウンタ停止。よって1にしてカウンタ動作です。
bit5:VR_EN_MAT:1でマッチレジスタとカウンタ値が一致したら出力ポートが1、0だと0のまま。うーん、これはどちらでもよさそうですが、1にしてみます。
bit4:3:割り当て無し
bit2:RST_CNT_OMAT:1:マッチレジスタとカウンタが一致したらカウンタクリア、0:そのままカウントアップ。1に設定して1秒カウントしたら0に戻るようにします。
bit1:RST_CNT:1:カウンタ動作、0:カウンタリセット。これは最初に0にした後は1にしておけばよさそうです。
bit0:XTAL_EN:1:クリスタルオシレータON、0:クリスタルオシレーターOFF。これは1に設定です。

MATCHREG0_REGは256回、0から数えて255回、16進でFFhになればマッチとすればよいので、MATCHREG0_REG=0xFFにします。後のMATCHREG1,2,3,4は0x00にします。

MATCHBITS0_REG(Individual)は特にビット毎に見てなにかするわけでは無いのでMATCHBITS0,1,2,3,4_REGは0x00にします。

読み出すときはMATCHREG0_REGを最初に読めばMATCHREG1,2,3,4_REGもラッチされるようですが、今回はMATCHREG0_REGをポーリングして0xFFになったら時計の処理をさせるようにします。
その後0x00に戻るのをポーリング後0xFF待ちポーリングに戻ります。こうしないと、0xFFの期間何回も時計が進むということが起きてしまいます。

2011/10/23↑

2011/10/30↓

先週のフローチャート見てて、何か抜けてる? と思っていたら、表示するところが抜けていることに気が付きました。ですのでちょっと修正しておきました。
折角なので表示に使用するOLEDについて調べて見ました。

SmartFusion Evaluation Kit User's Guide (A2F_EVAL_KIT_UG.pdf) のP19を見るとOLEDの品番はPMO13701と読めます。
SmartFusion: Interfacing with OLED using (I2C Application Note AC347) (SmartFusion_Interfacing_with_OLED_using_I2C_AN.pdf) の P1に SSD0300 とあります。
どうも PMO13701がOLEDそのもので、SSD0300がコントローラのようです。
ちょっとぐぐって探してみると、
pacerpdfs.s3.amazonaws.com/PMO13701.pdf と http://www.eecs.umich.edu/~prabal/teaching/eecs373-f10/readings/SSD0300-Revision%201.4.pdf
でよさそうです。

ここまで、書いてて、ん?MSSでI2C-0入れ忘れてる!っていうのに気づきました。げげっ!
110-4の5.に戻って、I2C_0にもチェックを入れます。Save後Firmwareタグで緑の↓矢印をクリックして、MSS_I2C_Driver_0にチェックを入れます。
その行で右クリックして、Open Documentation -> MSS_I2C_Driver_UG.pdfを開いた後 AcrobatReaderからセーブしておきます。
もう一度右クリックして、Generate Sample Project -> Cortex-M3 -> SoftConsole -> OLED hellow world を選択すると Generate Sample Options と聞いてくるのでそのままOKします。Message で Successfully〜 と出たらOKです。このサンプルはOLEDに表示させる時に参考にします。
MSSでSaveして Generate、ProjectManagerの左ペーンからrtc_clkをクリックしてCanvasを開き、rtc_clockを左クリック->右クリックでUpdateInstance(s) with LatestComponentを選択、Save後、Generateします。

全部完成した後にWebページ書けば良いのですが、やりながら書くと色々忘れてたり後戻りがあったり。イカンですね。

で、今週は風邪ひいてて土日寝ていたのでほとんど進みませんでした(^^;);。

2011/10/30↑

2011/11/27↓
だいぶ間が開いてしまいましたが、今回はプログラミングをします。
いきなり、RTCをアクセスして時計を作るのでは無く、まずはOLDのサンプルを参考に 時計の表示画面を作ってみます。

SoftConsoleを立ち上げて設定です。
スタート->すべてのプログラム->MicrosemiSoftConsoleV3.3->MicorosemiSoftconsoleIDEV3.3.0.14
・Workspace:C:\Actelprj\rtc_clock\rtc_clock
・File->New->C project で Project name:firmware とし Next をクリック、SelectConfigurationsはそのままで Finishをクリック
  もうfirmwareってあるよっていうメッセージが出ますが気にしない。

File -> New -> Source Fileで File name: main.c としてFinish
C:\Actelprj\rtc_clock\rtc_clock\firmware\CM3_GNU_OLED_hello の main.c を参考にコーディングしてみます。

OLEDに 12:34:56 と表示させてみます。
プログラムはこんな感じ
/*
 * main.c
 *
 *  Created on: 2011/11/27
 *      Author: shibatchii
 */
#include "oled.h"
#define DELAY_COUNT   0x01000000
/*==============================================================================
 * main function.
 */
int main()
{
        OLED_init();
        OLED_set_cursor( FIRST_LINE, FIRST_CHARACTER );
        OLED_write_string("12:34:56");  
}
   

さて、コンパイル...の前に、例のリンカの呪文を設定します。
Project->Propatiesを開きます。
C/C++Build -> Setting で GNU C Linker->Miscellaneous の Linker flagsに
-T ../CMSIS/startup_gcc/debug-in-actel-smartfusion-esram.ld
を入力します。これはお約束なので忘れないように。
Apply->OKで閉じます。

コンパイルの前に
C:\Actelprj\rtc_clock\rtc_clock\firmware\CM3_GNU_OLED_hello にある oled.c と oled.h を
C:\Actelprj\rtc_clock\rtc_clock\firmware にコピーします。

Project->Clean でコンパイルします。
ここで、firmwareにあるディレクトリ、ファイルは全部コンパイルしようとしてしまうようで、必要の無いものまでコンパイルしてエラーとなります。
これを防止するためには、必要の無いフォルダやファイルを選択して、左クリック->右クリック->Exclude from build でチェックを入れてOKにします。
CM3_GNU_OLED_hello、DircetC、hal、drivers/mss_iap を指定しました。
問題なくコンパイルできるとBinariesというフォルダが Project Explorerに現れます。

早速実行してみます。Fablic部分はこのテストでは必要無いので、Fablicの部分はまだ合成、フィッティング、書き込みはしません。
Run->Debug Configurations を選択します。
左のペーンで、Microsemi cortex-M3 RAM Target を選択します。
その下の firmware Debug を選択します。Name:firmware Debug、Projectにfirmware、C/C++ Appication:Debug\firmwareと表示されていることを確認して、Debugクリック。
Run->Resume で OLEDに 12:34:56 と表示されます。
   


2011/11/27↑


2011/12/18↓

110-7. Cプログラムをコーディング、デバッグしよう

この項目↑は前回からでも良かったかもしれませんね。
出張やらなんやらでバタバタしてだいぶ間が開いてしまいましたが続きです。

今回はRTCも動かして、時計らしくします。
RTCのプログラムは firmware\CM3_GNU_RTC_time\drivers\mss_rtc にできている、mss_rtcとmss_rtc.h を参考にします。またその関数のアクセス方法は firmware\CM3_GNU_RTC_time にある main.c を参考にします。

もし、こんなのできてないよ〜という時には、SmartDesign MSS Configurator を立ち上げて、Firmwareで MSS_RTC_Driver_0 を左クリック->右クリックして Generate Sample Project -> Cortex-M3 -> SoftConsole -> RTC Time で 作成します。
また、同様に Open Documentation -> MSS_RTC_Driver_UG.pdf も参考にします。

さてコーディングは前に作成したフローチャートに従って作成します。
ただし、今回はまだSW1,SW2による 時計合わせ機能はまだ入っていません。時計っぽくカウントアップさせます。
作成したプログラムはこちら↓。
/*
 * main.c
 *
 *  Created on: 2011/11/27
 *  Modify  on: 2011/12/18
 *      Author: shibatchii
 */
#include <stdint.h>
#include <string.h>
#include "oled.h"
#include "drivers/mss_rtc/mss_rtc.h"
#define CHSIZE 16
/*==============================================================================
 * btoa function.
 */
void btoa(char a[3],int b)
{
    a[0] = (b/10)+0x30;
    a[1] = (b%10)+0x30;
    a[2] = (char)0x00;
}
/*==============================================================================
 * main function.
 */
int main()
{
    uint64_t rtc_count_val;
    uint64_t rtc_count_val_old;
    int loop;
    int hour,minute,second;
    char hour_ch[3],minute_ch[3],second_ch[3];
    char time_disp[CHSIZE];

    rtc_count_val = 0ULL;
    rtc_count_val_old = 0ULL;
    for(loop=0;loop<CHSIZE;loop++)time_disp[loop]=(char)0x00;
    OLED_init();
    MSS_RTC_init();
    MSS_RTC_set_rtc_count(0x0000000000000000ULL);
    MSS_RTC_set_rtc_match(0x00000000000000FFULL);
    MSS_RTC_configure( MSS_RTC_NO_COUNTER_RESET );
    MSS_RTC_start();
    loop=0;hour=0;minute=0;second=0;
    for(;;){
        rtc_count_val = MSS_RTC_get_rtc_count();
        if((rtc_count_val&(uint64_t)0xFFFFFFFFFFFFFF00ULL)!=(rtc_count_val_old&(uint64_t)0xFFFFFFFFFFFFFF00ULL)){
            rtc_count_val_old = rtc_count_val;
            if(second==59){
                if(minute==59){
                    if(hour==23){
                        hour=0;
                    }else{
                        hour++;
                    }
                    minute=0;
                }else{
                    minute++;
                }
                second=0;
            }else{
                second++;
            }
            btoa(hour_ch,hour);
            btoa(minute_ch,minute);
            btoa(second_ch,second);
            strcat(time_disp,hour_ch);
            strcat(time_disp,":");
            strcat(time_disp,minute_ch);
            strcat(time_disp,":");
            strcat(time_disp,second_ch);
            strcat(time_disp,"");
            OLED_set_cursor( FIRST_LINE, FIRST_CHARACTER );
            OLED_write_string(time_disp);
            for(loop=0;loop<CHSIZE;loop++)time_disp[loop]=(char)0x00;
        }
    }
    return(0);
}
最初のフローチャートとはちょっと違ってきていますが、時計っぽくカウントアップしています。
プログラムとして特殊なことはしていません。というか、結構ベタベタに書いています。もっとスマートな書き方もできそうです。
簡単に説明しておきます。
  • 最初の#includeの所で、stdint.hはuint64_tを使用するため。string.hはstrcatを使用するため、oled.hはOLED_*用、mss_rtc.hはMSS_RTC_*用です。
  • bota()の関数は、最初 Cの関数である itoa() を使用しようとしたのですが、リンク時にそんな関数無いと言われた(実装されてない)ようなので整数値を2ケタの文字に変換する関数を作成しました。1桁の整数に0x30を加えるとちょうどASCIIの文字に変換できます。
  • Main関数の最初は変数宣言と変数初期化、及びOLEDとRTCの初期化を行なっています。
  • for(;;)内のループで、RTCからカウンタ値を読み出し、0x100の桁が変化したら時計をカウントアップをさせます。
  • if文がネストしている部分は各桁のカウントアップと最大値59秒->0秒、59分->0分、23時->0時への処理です。
  • btoa関数で各時分秒を文字に直し、strcatでコロンを挟みながら文字列の時刻にします。
  • それをOLEDに表示します。
動作している所をビデオに撮ってみました。転送されるまで少し時間がかかります。ブラウザがハングしたわけでは無いのでご安心を。(MOVファイル 2.4MByte)


2011/12/18↑

2011/12/25↓

今回は、時計のセット機能を追加します。
  • SW1を押すと1秒ごとに時の桁がカウントアップします。
  • SW2を押すと1秒ごとに分の桁がカウントアップします。
  • SW1とSW2を同時に押すと1秒後に秒の桁が00に設定されます。 (最初の仕様にはありませんでしたが、追加してみました。)
すでに設計済みのファブリック部分のSW読み出し用アドレスからAPBリードによりデータを読みだして、SW1,SW2のビット位置の状態を判定し、それにより時、分の桁をカウントアップさせるようにします。
さっそくですが、プログラムは以下の用になります。
注:デバッグ中にRTLの方のミスが見つかって修正しました。RTLの方も参照してください。(リード時のPENABLEデコードを削除しました。)
/*
 * main.c
 *
 *  Created on: 2011/11/27 // disp only
 *  Modify  on: 2011/12/18 // count up
 *  Modify  on: 2011/12/25 // clock ajust
 *      Author: shibatchii
 */
#include <stdint.h>
#include <string.h>
#include "oled.h"
#include "drivers/mss_rtc/mss_rtc.h"
#define CHSIZE                  16          // キャラクタバッファサイズ
#define FPGA_FABRIC_BASE        0x40050000U // ファブリックベース
#define SW_ADD                  0x00U       // APB SW割り当てアドレス
#define SW1                     0x00000001U // SW1 ビット位置
#define SW2                     0x00000010U // SW2 ビット位置
#define ON                      1           // SW ON
#define OFF                     0           // SW OFF

/*==============================================================================
 * btoa function.
 */
void btoa(char a[3],int b)
{
    a[0] = (b/10)+0x30; // 10の桁
    a[1] = (b%10)+0x30; // 1の桁
    a[2] = (char)0x00;  // 区切り
}
/*==============================================================================
 * main function.
 */
int main()
{
    uint64_t rtc_count_val;     // RTCカウンタ値
    uint64_t rtc_count_val_old; // 1秒前のRTCカウンタ値
    int loop;                   // 初期化用ループ変数
    int hour,minute,second;     // 時、分、秒
    char hour_ch[3],minute_ch[3],second_ch[3];  // 時、分、秒 キャラクタ
    char time_disp[CHSIZE];     // 表示用キャラクタバッファ
    volatile unsigned int* CALC_BASE = (unsigned int*)FPGA_FABRIC_BASE; // ファブリック(FPGA)のベース設定

    // 初期化
    rtc_count_val = 0ULL;
    rtc_count_val_old = 0ULL;
    for(loop=0;loop<CHSIZE;loop++)time_disp[loop]=(char)0x00;
    OLED_init();
    MSS_RTC_init();
    MSS_RTC_set_rtc_count(0x0000000000000000ULL);
    MSS_RTC_set_rtc_match(0x00000000000000FFULL);
    MSS_RTC_configure( MSS_RTC_NO_COUNTER_RESET );
    MSS_RTC_start();
    loop=0;hour=0;minute=0;second=0;
    // 時計ループ
    for(;;){
        rtc_count_val = MSS_RTC_get_rtc_count();
        // 1秒経ったか判定、1秒経った時
        if((rtc_count_val&(uint64_t)0xFFFFFFFFFFFFFF00ULL)!=(rtc_count_val_old&(uint64_t)0xFFFFFFFFFFFFFF00ULL)){
            rtc_count_val_old = rtc_count_val;  // 現在のカウンタ値をセーブ
            // 秒00クリア
            if((((*(CALC_BASE + SW_ADD))&SW1)==0)&&(((*(CALC_BASE + SW_ADD))&SW2)==0)){
                second=0;
            // 時セットの時
            }else if(((*(CALC_BASE + SW_ADD))&SW1)==0){
                // 23時の時は0時へ
                if(hour==23){
                    hour=0;
                // それ以外はカウントアップ
                }else{
                    hour++;
                }
            // 分セットの時
            }else if(((*(CALC_BASE + SW_ADD))&SW2)==0){
                // 59分の時は0分へ
                if(minute==59){
                    minute=0;
                // それ以外はカウントアップ
                }else{
                    minute++;
                }
            // 通常の時計動作
            }else{
                // 59秒の時は0秒へ
                if(second==59){
                    // 59分の時は0分へ
                    if(minute==59){
                        // 23時の時は0時へ
                        if(hour==23){
                            hour=0;
                        // それ以外はカウントアップ
                        }else{
                            hour++;
                        }
                        minute=0;
                    // それ以外はカウントアップ
                    }else{
                        minute++;
                    }
                    second=0;
                // それ以外はカウントアップ
                }else{
                    second++;
                }
            }
            btoa(hour_ch,hour);     // 時をASCIIキャラクタへ
            btoa(minute_ch,minute); // 分をASCIIキャラクタへ
            btoa(second_ch,second); // 秒をASCIIキャラクタへ
            strcat(time_disp,hour_ch);      // 時
            strcat(time_disp,":");          // 区切り
            strcat(time_disp,minute_ch);    // 分
            strcat(time_disp,":");          // 区切り
            strcat(time_disp,second_ch);    // 秒
            strcat(time_disp,"");           // 終端
            OLED_set_cursor( FIRST_LINE, FIRST_CHARACTER ); // OLEDのカーソル位置を設定
            OLED_write_string(time_disp);                   // OLEDへ表示
            for(loop=0;loop<CHSIZE;loop++)time_disp[loop]=(char)0x00;   // バッファ初期化
        }
    }
    return(0);
}

前回からの変更分を簡単に説明しておきます。
  • volatile unsigned int* CALC_BASE = (unsigned int*)FPGA_FABRIC_BASE; でAPBバスに割り当てられたファブリックアドレスを設定。volatileを忘れないように。
  • (*(CALC_BASE + SW_ADD))&SW1 でAPBリードによりSW状態を読み出すとともに、 &SW1でSW1の状態のみ判定。SW2も同様。
後は付随した変更ですのでプログラムのコメントを参照してください。

2011/12/25↑



1つ上
1つ上へもどる


トップ
トップページへもどる


mark  Copyright(c) shibatchii 2011 All Rights Reserved.

-