RXマイコンのソフト開発(4)UART送信でHelloWorld

RXマイコン(RX631)のソフト開発の4回目です。今回は、UARTでHello Worldを出力したいと思います。

今回の記事に関しては、ほとんど以下のマウス本を参考にしています。

環境

  • パソコン: Windows10 64 bit
    • 統合開発環境: e2studio 2021-01をインストール
    • コンパイラ: Renesas CCRX v3.03.00をインストール
    • 書き込みソフト: Renesas Flash Programmer V3.08.01(無償版) をインストール
  • ターゲットデバイス: R5F5631PDDFL(RX631 48ピン)

UARTの回路

図1:UARTの回路
図1:UARTの回路

作成したUART部分の回路図は図1です。TXDのポートは P26/TXD1 です。TXD1を使って、RXマイコンからPCにデータを送信したいと思います。

プログラムフロー

プログラムの処理の流れは図2です。

図2:プログラムのフローチャート
図2:プログラムのフローチャート

いつものようにクロックの初期化とコンペアマッチタイマー0(CMT0)の初期化を行っています。次に、シリアルコミュニケーションインターフェース1(SCI1)を初期化します。その後、メインルーチンに入り、スイッチの押し込みを検知したら HelloWorld を UART 送信します。

プログラム

重要なプログラム部分だけ示します。sci.csci.h を除いた全てのソースコードGitHubの「4_uart」フォルダの中にソースコードがありますので、詳細を知りたい場合はご覧ください。

メイン関数

main.c:メイン関数のソースコード

#include "interface.h"
#include "sci.h"
#include "init_rx631.h"

void main(void){

    init_rx631();       // Overall Initialization

    while(1){
        if(g_sw_chg){   // Enter when SW is pressed
            g_sw_chg = 0;
            sci_printf("Hello World\n\r");
        }
    }
}

メイン関数では、RX631の初期化を行い、割り込み関数によってスイッチの押し込みフラグが立ったら、フラグを下げて、sci_printf でHelloWorldをUART送信します。

SCI1の初期化

初期化関数のソースコードが以下です。SCI に関してはユーザーズマニュアルの p.1357「35. シリアルコミュニケーションインターフェース(SCIc、SCId)」に記述があります。

init_rx631.c:初期化関数のソースコード

#include "init_rx631.h"
#include "iodefine.h"

/*** Function Declaration ***/
static void init_clock(void);
static void init_cmt0(void);
static void init_sci1(void);

/*---- RX631 Initialization ----*/
void init_rx631(void){
    SYSTEM.PRCR.WORD = 0xA503;  // Unprotect

    // MainCLK, SUBCLK and RTC Initialization
    init_clock();

    // CMT0 Initialization
    init_cmt0();

    // SCI1 Initialization
    init_sci1();

    SYSTEM.PRCR.WORD = 0xA500;  // Reprotect
}

~略~

static void init_sci1(void){

    MSTP(SCI1) = 0;             // SCI1 Module Stop Release

    MPC.PWPR.BIT.B0WI   = 0;    // PFSWE bit write enable
    MPC.PWPR.BIT.PFSWE  = 1;    // PmnPFS Register write enable
    MPC.P26PFS.BIT.PSEL = 0x0A; // set to TXD1
    MPC.PWPR.BYTE       = 0x80; // Reprotect
    PORT2.PMR.BIT.B6    = 1;    // set to Peripheral

    SCI1.SMR.BYTE       = 0x00; // PCLK/1, asynchronous,
                                // 1 stop bit, no parity bit, data length=8bit
    SCI1.SEMR.BIT.ABCS  = 1;    // 1bit transfer in 8 cycles
    SCI1.BRR            = 80;   // 38400 bps, error=0.47%

    SCI1.SCR.BIT.TE     = 1;    // Enable serial transmission
}

SCI1の初期化では、MTU3の初期化と同様にモジュールストップ状態の解除と端子機能の変更を行います。

その後、SCI1の設定を行います。SCI1.SMR.BYTE=0x00 とすることで以下のように設定されます。

  • クロックの分周比  a:1
  • ストップビットの数:1
  • パリティビットなし
  • データ長:8ビット
  • 動作モード:調歩同期式

ビットレート B については、BRRレジスタを設定することで決定します。SCI1.SEMR.BIT.ABCS=1のとき、BRRの値は以下のように計算されます。

\displaystyle{
BRR=\frac{\rm PCLK}{16 \times a \times B } - 1
}

 B=38400 bpsとしたい場合は、BRRの値は以下のようになります。

\displaystyle{
BRR=\frac{50\times 10^6}{16 \times 1 \times 38400 } - 1 =80.4
}

プログラム中では、小数点以下を四捨五入して、 BRR=80 としています。

最後に、SCI1.SCR.BIT.TE=1 で、シリアル送信を許可しています。

UART送信する関数

UART送信する関数については、マウス本のHPで公開している sci.csci_printf 関数を使わせていただきました。

マウス本のHP:https://takeyuta.wixsite.com/mouse

プログラムの実行

PCとマウスを接続したら、TeraTerm を開いて、図3のように「設定」→「シリアルポート」を実行します。

図3:TeraTermの設定1
図3:TeraTermの設定1

シリアルポートの設定では、ボーレートを38400に設定して、「OK」を左クリックします(図4)。

図4:TeraTermの設定2
図4:TeraTermの設定2

マウスのプッシュスイッチを押すと、図5のように TeraTermHello World が表示されます。

図5:プログラムの実行結果
図5:プログラムの実行結果

おわりに

今回でUART送信ができました。次の記事では、ADCを使ってバッテリーの電圧を測定したいと思います。

参考文献

RXマイコンのソフト開発(3)スピーカーから音を出す

RXマイコン(RX631)のソフト開発の3回目です。今回は、スピーカーから音を出したいと思います。MTUという機能を用いて、端子から矩形波を出力することで音を出します。この機能によって、いちいちプログラムで端子をLOW、HIGHに設定せずとも、自動で矩形波が出力されます。

環境

  • パソコン: Windows10 64 bit
    • 統合開発環境: e2studio 2021-01をインストール
    • コンパイラ: Renesas CCRX v3.03.00をインストール
    • 書き込みソフト: Renesas Flash Programmer V3.08.01(無償版) をインストール
  • ターゲットデバイス: R5F5631PDDFL(RX631 48ピン)

スピーカーの回路

図1:スピーカーの回路図
図1:スピーカーの回路図

作成したスピーカー部分の回路図は図1です。SPEAKERのポートは P14/MTIOC3A です。MTIOC3Aの機能を持った端子からは任意の周波数の矩形波を出力できるので、その機能を使って音を出力したいと思います。

プログラムフロー

プログラムの処理の流れは図2です。レジスタプロテクトの設定は省略しています。

図2:プログラムのフローチャート
図2:プログラムのフローチャート

まず、前回の記事と同様にクロックの初期化とI/Oポートの設定、コンペアマッチタイマー0(CMT0)の初期化を行っています。次に、CMT1の初期化とマルチファンクションパルスユニット2 のチャネル3 (MTU3)を初期化します。その後、メインルーチンに入り、スイッチの押し込みを検知したら音を出力します。音は0.5秒間だけ出力して、音階名でC5からC6を出力するようにします。

プログラム

重要なプログラム部分だけ示します。全てのソースコードGitHubの「3_speaker」フォルダの中にソースコードがありますので、詳細を知りたい場合はご覧ください。

メイン関数

main.c:メイン関数のソースコード

#include "define_wakaba.h"
#include "interface.h"

void main(void){
    unsigned char spk_note_num = 0;

    init_rx631();  // Overall Initialization

    while(1){ 
        if(g_sw_chg){   // Enter when SW is pressed
            g_sw_chg = 0;
            switch(spk_note_num){
            case 0:
                SPK_Soundout(C5, 50);
                break;
            case 1:
                SPK_Soundout(D5, 50);
                break;

            ~略~       

            case 7:
                SPK_Soundout(C6, 50);
                break;
            }
            spk_note_num++;
            spk_note_num = spk_note_num % 8;
        }
    }
}

メイン関数では、RX631の初期化を行い、割り込み関数によってスイッチの押し込みフラグ(g_sw_chg)が立ったら、フラグを下げて、SPK_Soundout で音を出力するようにしています。

CMT1とMTU3の初期化

初期化関数のソースコードが以下です。ここでは MTU3 の初期化について主に説明します。MTU に関してはユーザーズマニュアルの p.765「23. マルチファンクションタイマパルスユニット2(MTU2a)」に記述があります。

init_rx631.c:初期化関数のソースコード

#include "init_rx631.h"
#include "iodefine.h"

/*** Function Declaration ***/
static void init_clock(void);
static void init_cmt0(void);
static void init_cmt1(void);
static void init_mtu3(void);

/*---- RX631 Initialization ----*/
void init_rx631(void){
    SYSTEM.PRCR.WORD = 0xA503;  // Unprotect

    // MainCLK, SUBCLK and RTC Initialization
    init_clock();

    // CMT0 and CMT1 Initialization
    init_cmt0();
    init_cmt1();

    // MTU3 Initialization
    init_mtu3();

    SYSTEM.PRCR.WORD = 0xA500;  // Reprotect
}

~略~

/*---- CMT1 Initialization ----*/
void init_cmt1(void){

    MSTP(CMT1) = 0;          // CMT1 Module Stop Release

    CMT.CMSTR0.BIT.STR1 = 0; // CMT1 Stop
    CMT1.CMCR.WORD = 0x00C1; // interrupt enabled and PCLK/32
    CMT1.CMCOR = 15624;      // Set interrupt cycle to 10ms

    IPR(CMT1,CMI1) = 9;      // Priority Level 9
    IEN(CMT1,CMI1) = 1;      // Interrupt enabled
}

/*---- MTU3 Initialization ----*/
void init_mtu3(void){

    MSTP(MTU) = 0;              // MTU Module Stop Release

    MPC.PWPR.BIT.B0WI   = 0;    // PFSWE bit write enable
    MPC.PWPR.BIT.PFSWE  = 1;    // PmnPFS Register write enable
    MPC.P14PFS.BIT.PSEL = 0x01; // set to MTIOC3A
    MPC.PWPR.BYTE       = 0x80; // Reprotect
    PORT1.PMR.BIT.B4    = 1;    // set to Peripheral
    
    MTU3.TMDR.BIT.MD   = 0x2;   // PWM mode1
    MTU3.TIORH.BIT.IOA = 0x5;   // Initial output HIGH, Compare match LOW
    MTU3.TIORH.BIT.IOB = 0x6;   // Initial output HIGH, Compare match HIGH
    MTU3.TCR.BIT.TPSC  = 0x1;   // PCLK/4
    MTU3.TCR.BIT.CKEG  = 0x0;   // count by riging edge
    MTU3.TCR.BIT.CCLR  = 0x2;   // Clear by compare match TGRB
}

CMT1の初期化はCMT0の初期化とほとんど変わらないです。変化している点は割り込み周期が10msである点とカウンタを動作させていない点です。

MTU3の初期化では、まず、モジュールストップ機能によって MTU が停止しているため、モジュールストップ状態を解除します。

端子機能の変更

MPC.PWPR.BIT.B0WI   = 0;    // PFSWE bit write enable
MPC.PWPR.BIT.PFSWE  = 1;    // PmnPFS Register write enable
MPC.P14PFS.BIT.PSEL = 0x01; // set to MTIOC3A
MPC.PWPR.BYTE       = 0x80; // Reprotect
PORT1.PMR.BIT.B4    = 1;    // set to Peripheral

端子の機能を変更します。MTU3のPWMモード1で矩形波を出力したいので、P14端子の機能をMTIOC3Aに変更します。ただ、端子の機能を変更するレジスタがプロテクトされているので解除します。その解除は少し面倒で、MPC.PWPR.BIT.B0WI=0 で書き込みを可能にするビットの書き込みを可能にして、MPC.PWPR.BIT.PFSWE=1 で書き込みを可能にします。

P14 端子の選択できる機能は p.723「22.2.3 P1n 端子機能制御レジスタ(P1nPFS)(n=0 ~ 7)」に記述してあります。機能がMTIOC3Aとなるように、MPC.P14PFS.BIT.PSEL=0x01 とします。機能を選択できたら、MPC.PWPR.BYTE=0x80 で再度プロテクトします。また、端子を周辺機能として使用できるようにPORT1.PMR.BIT.B4=1でポートモードレジスタを変更します。

MTU3の設定

MTU3.TMDR.BIT.MD   = 0x2;   // PWM mode1
MTU3.TIORH.BIT.IOA = 0x5;   // Initial output HIGH, Compare match LOW
MTU3.TIORH.BIT.IOB = 0x6;   // Initial output HIGH, Compare match HIGH
MTU3.TCR.BIT.TPSC  = 0x1;   // PCLK/4
MTU3.TCR.BIT.CKEG  = 0x0;   // count by riging edge
MTU3.TCR.BIT.CCLR  = 0x2;   // Clear by compare match TGRB

MTU3の設定を変更します。今回は、PWMモード1で端子からPWM波形を出力したいので、MTU3.TMDR.BIT.MD=0x2を実行します。

図3のようにPWM波形を出力したいので、TIORH レジスタを変更して、初期出力HIGH、TGRAとコンペアマッチでLOW出力、TGRBとコンペアマッチでHIGH出力に設定します。

また、TCR レジスタの CCLR ビットを変更することで、TGRBとコンペアマッチでカウントクリアするようにします。

図3:PWMモード1の動作例
図3:PWMモード1の動作例

あとは、MTU3.TCR.BIT.TPSC = 0x1で分周比を4に設定して、MTU3.TCR.BIT.CKEG = 0x0でクロックの立ち上がりエッジでカウントするようにします。

音を出力する関数

interface.c:インターフェース用の関数のソースコード

#include "define_wakaba.h"
#include "interface.h"
#include "iodefine.h"

volatile unsigned char g_sw_chg;
static volatile unsigned char spk_cnt;

~略~

// CMT1 CMI1 Interrupt function
// Control soundout time
void SPK_CtrlCnt(void){

    // Reduce the count
    spk_cnt--;
    // Stop square wave output
    if(spk_cnt==0){
        MTU.TSTR.BIT.CST3 = 0;
        MTU3.TCNT = 0;
        CMT.CMSTR0.BIT.STR1 = 0;
        CMT1.CMCNT = 0;
    }
}

// Speaker Sound output
void SPK_Soundout(NOTE spk_note, unsigned char l_spk_cnt){
    spk_cnt = l_spk_cnt;
    MTU3.TGRA = spk_note>>2;
    MTU3.TGRB = spk_note;
    MTU.TSTR.BIT.CST3 = 1;
    CMT.CMSTR0.BIT.STR1 = 1;
}

SPK_CtrlCnt 関数は、割り込み関数の中に書かれている関数です。そのため、実質割り込み関数です。カウンタを減らして、カウンタがゼロになれば、MTU3を停止させて、カウンタTCNTをクリアさせています。また、CMT1も停止させて、カウンタCMCNTもクリアさせています。

SPK_Soundout 関数は、カウンタの初期値をまず設定してます。l_spk_cnt=50 とすれば、音を500 ms 間出力します。それから、TGRAやTGRBの値を設定して、周波数を設定します。spk_note の変数型がNOTEになっていますが、これはinterface.hに以下のように記述されたものです。

/*** type define ***/
typedef enum{
    C5 = 23900,  // 523Hz
    D5 = 21282,  // 587Hz
    E5 = 18960,  // 659Hz
    F5 = 17896,  // 698Hz
    G5 = 15943,  // 783Hz
    A5 = 14204,  // 880Hz
    B5 = 12654,  // 987Hz
    C6 = 11944   // 1046Hz
} NOTE;

各値は以下の式で決定しています。

\displaystyle{
TGRB=\frac{\rm PCLK}{分周比}\times \frac{1}{周波数} - 1
}

最後に、MTU.TSTR.BIT.CST3=1でMTU3を動作、CMT.CMSTR0.BIT.STR1 = 1でCMT1を動作させています。

プログラムの実行

プログラムを書き込むとスイッチを押すたびに以下のように音が変化します。

おわりに

今回でスピーカーから音が無事出力できました。次の記事では、デバッグを行いやすくするために、UARTで送信を行いたいと思います。

参考文献

RXマイコンのソフト開発(2)スイッチのチャタリング対策

RXマイコン(RX631)のソフト開発の2回目です。今回は、プッシュスイッチの入力を受けて、フルカラーLEDの点灯色を変更したいと思います。回路ではチャタリング除去をしていないため、ソフト側でチャタリング対策をしたいと思います。前回と同様にRX631のユーザーズマニュアル ハードウェア編を参照することをおススメします。

図1:基板上のプッシュスイッチ
図1:基板上のプッシュスイッチ

環境

  • パソコン: Windows10 64 bit
    • 統合開発環境: e2studio 2021-01をインストール
    • コンパイラ: Renesas CCRX v3.03.00をインストール
    • 書き込みソフト: Renesas Flash Programmer V3.08.01(無償版) をインストール
  • ターゲットデバイス: R5F5631PDDFL(RX631 48ピン)

スイッチの回路

図2:スイッチ部分の回路図
図2:スイッチ部分の回路図

作成したスイッチ部分の回路図は図2です。SWのポートはP35です。回路にはチャタリング除去するためのローパスフィルタがないので、24msごとにタイマー割り込みをしてスイッチの状態を確認したいと思います。スイッチオフからスイッチオンになったときにフルカラーLEDの点灯色を変えます。

プログラムのフロー

プログラムの処理の流れは図3です。レジスタプロテクトの設定は省略しています。

図3:プログラムのフロー
図3:プログラムのフロー

前回の記事でおこなったクロックの初期化とI/Oポートの設定を行い、今回用いるコンペアマッチタイマー0(CMT0)の初期化を行っています。その後、メインルーチンに入り、スイッチの押し込みを検知したらLEDの点灯色を変更します。スイッチの押し込みはCMT0を用いて24ms周期で検知します。

補足チャタリングの期間は長くても 10 数msといわれているので、スイッチ検知の周期は余裕をもって24msとしました。

プログラム

スイッチに関連するプログラム部分だけ示します。全てのソースコードGitHubの「2_sw」フォルダの中にソースコードがありますので、詳細を知りたい場合はご覧ください。

メイン関数

main.c:メイン関数のソースコード

#include "define_wakaba.h"
#include "led_and_sw.h"

void main(void){
    // 0:OFF, 1:赤, 2:青, 3:紫, 4:緑, 5:黄, 6:水色, 7:白
    unsigned char led_color = 0;  // フルカラーLEDの色

    init_rx631();  // RX631の初期化
    while(1){
        if(g_sw_chg){
            led_color++;
            led_color = led_color & 0x07;
            Full_color_LED(led_color);   // フルカラーLEDを点灯
            g_sw_chg = 0;  // スイッチの押し込みフラグを下げる
        }
    }
}

メイン関数では、RX631の初期化を行い、割り込み関数によってスイッチの押し込みフラグ(g_sw_chg)が立ったら、フルカラーLEDの点灯色を変更して、フラグを下げるようにしています。

CMT0の初期化

初期化関数のソースコードが以下です。ここではCMT0の初期化について説明したいと思います。CMT0に関してはユーザーズマニュアルのp.1085「28. コンペアマッチタイマ(CMT)」に記述があります。

init_rx631.c:初期化関数のソースコード

#include "init_rx631.h"
#include "iodefine.h"

/*** Function Declaration ***/
static void init_clock(void);
static void init_IO(void);
static void init_cmt0(void);

/*---- RX631 Initialization ----*/
void init_rx631(void){
    SYSTEM.PRCR.WORD = 0xA503;  // プロテクト解除

    // クロックの初期化
    init_clock();

    // I/O ポートの初期化
    init_IO();

    // CMT0 の初期化
    init_cmt0();

    SYSTEM.PRCR.WORD = 0xA500;  // 再プロテクト
}

~略~

/*---- CMT0 初期化 ----*/
void init_cmt0(void){

    MSTP(CMT0) = 0; // CMT0のモジュールストップを解除

    CMT0.CMCR.WORD = 0x00C1; // 割り込み許可とPCLKを32分周
    CMT0.CMCOR = 37499;      // 割り込み周期を24msに設定

    IPR(CMT0,CMI0) = 10; // 優先レベルを10に設定
    IEN(CMT0,CMI0) = 1;  // 割り込み許可

    CMT.CMSTR0.BIT.STR0 = 1; // CMT0 開始
}

モジュールストップ機能の解除

MSTP(CMT0) = 0; // CMT0のモジュールストップを解除

モジュールストップ機能によって CMT0 が停止しているため、モジュールストップ状態を解除する必要があります。モジュールストップ機能の説明についてはユーザーズマニュアルのp.297「11. 消費電力低減機能」にあります。マクロが用意されていますので、MSTP(CMT0)=0 でモジュールストップを解除します。

CMT0の設定

CMT0.CMCR.WORD = 0x00C1; // 割り込み許可とPCLKを32分周
CMT0.CMCOR = 37499;      // 割り込み周期を24msに設定

CMCRレジスタを設定することで、割り込み許可とPCLK (PCLKB)を32分周しています。

CMCORレジスタには割り込みが入るカウント数を設定します。PCLKB:50MHz、分周比:32、割り込み周期を24ms としたいので、CMCORレジスタの値は以下のようになります。

\displaystyle{
{\rm CMT0.CMCOR} = (50\times10^6) / 32 \times 0.024 - 1 = 37499
}

注意:CMCRレジスタを設定する際は、7ビット目の説明に「書く場合、“1”としてください」と記述がありますので、WORD単位で設定する必要があります。ビット単位で設定すると、レジスタが書き換わりません。

割り込みコントローラの設定

IPR(CMT0,CMI0) = 10; // 優先レベルを10に設定
IEN(CMT0,CMI0) = 1;  // 割り込み許可

割り込みコントローラについてはユーザーズマニュアルのp.361「15. 割り込みコントローラ(ICUb)」に説明があります。割り込み要求を許可するために、「15.2.2 割り込み要求許可レジスタ m(IERm)」を設定して、割り込み優先度を設定するために、「15.2.3 割り込み要因プライオリティレジスタ n(IPRn)」を設定します。これらについてもマクロが用意されていますので、

  • IEN(<割り込み要求発生元>,<割り込みの名称>) = 1
  • IPR(<割り込み要求発生元>,<割り込みの名称>) = 10

で割り込み許可と優先度の設定をします。

最後に、CMSTR0レジスタのSTR0ビットに"1"を設定して、「28.2.4 コンペアマッチタイマカウンタ(CMCNT)」をカウントアップさせます。CMCNTとCMCORが一致することで割り込みが発生します。

割り込み関数の登録

割り込み関数のソースコードは以下です。実処理は別の関数(SW_status_check)で行っていますが、割り込み関数の登録はこのソースコードで行っています。

interrupt.c:割り込み関数のソースコード

#include "led_and_sw.h"
// 関数のセクションを割り込み関数の領域に変更
#pragma section P PIntPRG 

/*** 関数宣言 ***/
static void CMT0_CMI0(void);  // CMT0 CMI0

// CMT0 CMI0
// スイッチの状態を確認する割り込み関数
#pragma interrupt (CMT0_CMI0(vect=28)) // CMT0_CMI0を割り込み関数に登録
void CMT0_CMI0(void){
    SW_status_check();
}

#pragma section P PIntPRG という記述は、この記述以下の関数を割り込み関数のメモリ領域に置くというものです。

#pragma interrupt (CMT0_CMI0(vect=28))という記述は、指定した関数を割り込みインターフェースに登録します。vect=<割り込み要因に対応したベクタ番号>で、割り込み要因を指定します。割り込みベクタテーブルに関してはユーザーズマニュアルのpp.386-393に記述があります。

補足#pragma interrupt で宣言した関数内に直接実処理を記述したほうが、処理時間は短くなりますが、可読性がよくなると思って実処理は別の関数に記述しています。少しでも処理を速くしたいひとは、直接記述してもいいと思います。

注意:このまま、ビルドをすると、ベクタ番号28が複数回定義されているとして、エラーが生じます。generate/vect.hに以下のような記述があるので、コメントアウトする必要があります。

// CMT0 CMI0
#pragma interrupt (Excep_CMT0_CMI0(vect=28))
void Excep_CMT0_CMI0(void);

割り込み関数の内容について

led_and_sw.c:LEDとスイッチに関するソースコード

#include "define_wakaba.h"
#include "led_and_sw.h"

/*** Global variables ***/
volatile unsigned char g_sw_chg = 0;  // スイッチの押し込みフラグ

// フルカラーLEDの点灯
void Full_color_LED(char color){
    switch(color){
        case OFF:
            LED_RED   = LED_OFF;
~~略~~

// 割り込み関数の実処理
// スイッチの状態を確認をする
void SW_status_check(void){
    // 前のスイッチの状態
    static unsigned char pre_sw_status = SW_OFF;

    // スイッチが押し込まれたとき
    if(PUSH_SW==SW_ON && pre_sw_status==SW_OFF){
        g_sw_chg = 1;  // フラグを上げる
    }
    // 前のスイッチの状態に現在の状態を代入
    pre_sw_status = PUSH_SW;
}

割り込み関数の実処理(SW_status_check)では、24ms 前のスイッチの状態をstatic変数(pre_sw_status)で保持して、

  • 現在のスイッチの状態(PUSH_SW) = ON
  • 24ms前のスイッチの状態(pre_sw_status) = OFF

のときに、スイッチの押し込みフラグ(g_sw_chg)を立てます。

最後にpre_sw_statusに現在の状態を代入して、処理を終了します。

補足グローバル変数g_sw_chg)に型修飾子volatileを付けているのは、割り込み関数によるグローバル変数の変更が必ず反映されるようにするためです。「A.1.4 通常時と割り込み時に使用する変数を定義する」によると、コンパイラによってメモリアクセスが省略されることがあるので、割り込み関数がメモリのデータを書き換えても、メイン関数ではメモリにあるデータを取りにいかないことが起きるそうです。

補足:PSWについて

CMT0の初期化では割り込み許可を以下のようにしました。

  • CMCRレジスタのCMIEビットを"1"として、コンペアマッチの割り込み許可
  • IER03.IEN4ビットを”1”として、CMT0に対してCPUへの割り込み要求を許可

最後にCPU自体の割り込み許可が必要です。割り込みを許可するには、ユーザーズマニュアル p.135「2.2.2.4 プロセッサステータスワード(PSW)」の割り込み許可ビットを"1"にしないといけません。しかし、これについては generate/resetprg.c の中にあるスタートアッププログラム PowerON_Reset_PC で設定がされています。スタートアッププログラム中の CC-RXの組み込み関数 set_psw(PSW_init)PSW の割り込み許可ビットを"1"にしています。もし、割り込みが発生しない場合は、PSWを確認してみてもいいかもしれません。

おわりに

プログラムを書き込んだところ、プッシュスイッチを押すとLEDの点灯色が変わるようになりました。 次回はスピーカーから音を鳴らしてみたいと思います。

参考文献

ZYBOでシンセサイザー作成(5)音を出力する

前回と前々回でパラシリ変換器(sin_gen)と正弦波生成 IP(para2serial) が作成できました。今回は、これらを組み合わせて、ZYBOから音を出力したいと思います。

環境

  • パソコン:Windows10 64 bit
    • Vivado 2020.2 をインストール
    • Xilinx Vitis 2020.2 をインストール
  • ボード:Zybo Z7-20
  • 出力デバイス:スピーカー

システムの構成

システムの構成を図1に示します。

図1:システムの構成
図1:システムの構成

データをスピーカーに出力する手順は以下です。

  1. 「AXI GPIO」で正弦波生成 IPに周波数値を与える
  2. 正弦波生成 IPの出力値をパラシリ変換 IPでシリアルに変換する
  3. コーデックでシリアルデータをDA変換してスピーカーに出力

また、MMCMで50MHzから96MHzのクロックを生成して、カウンタを用いて4分周することで24MHzのMCLKを生成します。コーデックが12MHzのBCLKを生成しますので、BCLKに同期してパラシリ変換 IPでデータを出力します。

IP インテグレータの編集

IP インテグレータで IP どうしの接続を行います。ZYBOでシンセサイザー作成(2)オーディオコーデックの使用 のプロジェクトzybo_synthesizerを編集していきます。

まず、プロジェクトのリポジトリリストに「ip_repo_synth」を追加します。「PROJECT MANAGER」→「Settings」で「Settings」画面を開き、「Project Settings」→「IP」→「Repository」の「+」で「ip_repo_synth」を追加して、「OK」を押します(図1)。

図1:IPリポジトリの追加
図1:IPリポジトリの追加

次に、「Add IP」から以下の IP を追加します。

  • sin_gen_v1_0
  • para2serial_v1_0
  • Clocking Wizard
  • AXI GPIO
  • Binary Counter
  • Slice

AXI GPIO は以下のように設定します(図2)。

  • All Outputs:チェックON
  • GPIO Width:15

図2:AXI GPIOの設定
図2:AXI GPIOの設定

Clocking Wizard は以下のように設定します。

  • Input Frequency:50 MHz
  • Output Freq:96 MHz
  • locked:チェックOFF

図3:Clocking Wizardの設定(Clocking Options)
図3:Clocking Wizardの設定(Clocking Options)

図4:Clocking Wizard の設定(Output Clocks)
図4:Clocking Wizard の設定(Output Clocks)

Binary Counter は以下のように設定します。

  • Output Width:2

図5:Binary Counterの設定
図5:Binary Counterの設定

Slice は以下のように設定します。

  • Din Width:2
  • Din From:1
  • Din Down To:1
  • Dout Width:1

図6:Sliceの設定
図6:Sliceの設定

配線は図7のように接続します。

図7:配線後のIPインテグレーション
図7:配線後のIPインテグレーション

配線ができたら、「Validate Design」で回路チェックをして、「Create HDL Wrapper...」でラッパーを更新します。

制約ファイルについては、「ac_bclk」と「ac_mclk」と「ac_pbdat」と「ac_pblrc」の行のコメントアウトを外します。

##Audio Codec
set_property -dict { PACKAGE_PIN R19   IOSTANDARD LVCMOS33 } [get_ports { ac_bclk }]; #IO_0_34 Sch=ac_bclk
set_property -dict { PACKAGE_PIN R17   IOSTANDARD LVCMOS33 } [get_ports { ac_mclk }]; #IO_L19N_T3_VREF_34 Sch=ac_mclk
set_property -dict { PACKAGE_PIN P18   IOSTANDARD LVCMOS33 } [get_ports { ac_muten }]; #IO_L23N_T3_34 Sch=ac_muten
set_property -dict { PACKAGE_PIN R18   IOSTANDARD LVCMOS33 } [get_ports { ac_pbdat }]; #IO_L20N_T3_34 Sch=ac_pbdat
set_property -dict { PACKAGE_PIN T19   IOSTANDARD LVCMOS33 } [get_ports { ac_pblrc }]; #IO_25_34 Sch=ac_pblrc
#set_property -dict { PACKAGE_PIN R16   IOSTANDARD LVCMOS33 } [get_ports { ac_recdat }]; #IO_L19P_T3_34 Sch=ac_recdat
#set_property -dict { PACKAGE_PIN Y18   IOSTANDARD LVCMOS33 } [get_ports { ac_reclrc }]; #IO_L17P_T2_34 Sch=ac_reclrc
set_property -dict { PACKAGE_PIN N18   IOSTANDARD LVCMOS33 } [get_ports { iic_scl_io }]; #IO_L13P_T2_MRCC_34 Sch=ac_scl
set_property -dict { PACKAGE_PIN N17   IOSTANDARD LVCMOS33 } [get_ports { iic_sda_io }]; #IO_L23P_T3_34 Sch=ac_sda

制約ファイルを保存したら、「Generate Bitstream」をします。

非同期クロック間の制約

「Generate Bitstream」が完了すると、「write_bitstream_Complete」が出ると思いますが、「Open Implemented Design」をして、「Timing」タブの Worst Negative Slack を確認すると負の値になっています(図8)。また、Critical Warning もあるので好ましくありません。

図8:Worst Negative Slack の値
図8:Worst Negative Slack の値

原因は、非同期クロック間で不必要なタイミング解析を行っているからです。MMCMに入力される50MHzのクロックとMMCMから出力される96MHzのクロックは非同期ですが、今回は同期しているとしてタイミング解析がされてしまいました。

非同期クロック間でタイミング解析を行わないように制約を与えます。「Timing」タブ→「Inter-Clock Paths」を選択して、クロックのペアを右クリックしてから「Set Clock Groups...」で設定できます(図9)。

図9:「Set Clock Groups」を開く
図9:「Set Clock Groups」を開く

「Set Clock Groups」では、「Group name」を記入して、「OK」を押します。私の場合は、「Group name」を「50MHz_and_96MHz」としました(図10)。

図10:「Set Clock Groups」の設定
図10:「Set Clock Groups」の設定

設定を終えたら、「Timing Constraints」タブの「Apply」で設定を適用します(図11)。

図11:「Timing Constraints」の設定を「Apply」する
図11:「Timing Constraints」の設定を「Apply」する

制約を適用できたら、再び「Generate Bitstream」でビットストリームを作成します。

制約ファイルを保存するか聞かれますので、「Save」を押します(図12)。

図12:制約ファイルの保存のダイアログ
図12:制約ファイルの保存のダイアログ

次に、「論理合成と配置配線が古くなりますよ」という警告が出ますが、そのまま「OK」でいいです(図13)。

図13:論理合成と配置配線が古くなる警告
図13:論理合成と配置配線が古くなる警告

制約ファイルを更新するか、新しく作成するか聞かれますので、「Update」を押して更新します(図14)。

図14:制約ファイルを「Update」する
図14:制約ファイルを「Update」する

「Select an existing file」が選択されていることと制約ファイル名が正しいことを確認して、「OK」を押します(図15)。

図15:既存の制約ファイルに保存する
図15:既存の制約ファイルに保存する

あとはいつもの設定画面が出ますので入力すると、「Generate Bitsteam」が始まります。

「write_bitstream_Complete」が出たら、一応、タイミングを満たしているか確認します。「Reload」を押して、配置配線後のデザインをリロードします(図16)。

図16:配置配線後のデザインを「Reload」する
図16:配置配線後のデザインを「Reload」する

「Timing」タブからWorst Negative Slackを確認すると正の値になっていて、タイミングを満たしていることが確認できます。

図17:Worst Negative Slack の改善
図17:Worst Negative Slack の改善

最後に、「Export Hardware...」でハードウェア情報であるXSAファイルを作成します。以上でVivadoでの作業は終わりです。

Vitisでプログラムの作成

Vivadoで「Launch Vitis IDE」を押して、Vitisを起動します。ハードウェアの更新をVitisに反映させるために、プラットフォームプロジェクトを選択して、マウス右ボタンから「Update Hardware Specification」を実行します。

次に、ソースコードを書き換えます。書き換えたプログラムが以下です。一応、ソースコードGitHubの「5th」にもあります。

#include "xparameters.h"
#include "xiic.h"
#include "xil_printf.h"
#include "xgpio.h"
#include <sleep.h>

enum adauRegisterAddresses {
    R0_LEFT_ADC_VOL     = 0x00,
    R1_RIGHT_ADC_VOL    = 0x01,
    R2_LEFT_DAC_VOL     = 0x02,
    R3_RIGHT_DAC_VOL    = 0x03,
    R4_ANALOG_PATH      = 0x04,
    R5_DIGITAL_PATH     = 0x05,
    R6_POWER_MGMT       = 0x06,
    R7_DIGITAL_IF       = 0x07,
    R8_SAMPLE_RATE      = 0x08,
    R9_ACTIVE           = 0x09,
    R15_SOFTWARE_RESET  = 0x0F,
    R16_ALC_CONTROL_1   = 0x10,
    R17_ALC_CONTROL_2   = 0x11,
    R18_ALC_CONTROL_2   = 0x12
};

int CodecWrite(XIic*, u8 Address, u16 data);
XStatus CodecInit(XIic *Iic);

int main(void){
    XIic Iic;
    XGpio Gpio0, Gpio1;
    int status;

    xil_printf("IIC Start\n");

    // Codec Initialization
    status = CodecInit(&Iic);
    if(status != XST_SUCCESS) {
        xil_printf("Error Codec Initialization");
        return XST_FAILURE;
    }

    // AXI GPIO Initialization
    status = XGpio_Initialize(&Gpio0, XPAR_GPIO_0_DEVICE_ID);
    if(status != XST_SUCCESS) {
        return XST_FAILURE;
    }
    status = XGpio_Initialize(&Gpio1, XPAR_GPIO_1_DEVICE_ID);
    if(status != XST_SUCCESS){
        return XST_FAILURE;
    }

    // Port Direction
    XGpio_SetDataDirection(&Gpio0, 1, 0x0);  // output
    XGpio_SetDataDirection(&Gpio1, 1, 0x00); // output

    // Output Level
    XGpio_DiscreteWrite(&Gpio0, 1, 0xF);   // All lights up
    XGpio_DiscreteWrite(&Gpio1, 1, 0x1B8); // Set to 440Hz

    xil_printf("IIC Finished\n");
    return XST_SUCCESS;
}

// Write Audio Codec Register
int CodecWrite(XIic* Iic, u8 Address, u16 data){
    u8 Device_Address = 0x1A;       // Device ID
    UINTPTR BaseAddress = Iic->BaseAddress; // AXI IIC BaseAddress
    int num;                        // Number of Data sent

    // set write date
    u8 WR_data[2];
    Address = ((Address<<1) & 0xFE);
    WR_data[0] = Address + ((data>>8)&0x01);
    WR_data[1] = (data&0xFF);

    // send data
    num = XIic_Send(BaseAddress, Device_Address, WR_data, 2, XIIC_STOP);
    if(num!=2){
        xil_printf("Writing data Failed\r\n");
        return XST_FAILURE;
    }
    return XST_SUCCESS;
}

// Audio Codec Initialization
XStatus CodecInit(XIic* Iic){
    int status;

    // Initializes XIic instance.
    status = XIic_Initialize(Iic, XPAR_IIC_0_DEVICE_ID);
    if (status != XST_SUCCESS){
        return XST_FAILURE;
    }

    // Codec register settings
    CodecWrite(Iic, R6_POWER_MGMT,    0x77); // power on
    CodecWrite(Iic, R0_LEFT_ADC_VOL,  0x97);
    CodecWrite(Iic, R1_RIGHT_ADC_VOL, 0x97);
    CodecWrite(Iic, R2_LEFT_DAC_VOL,  0x79);
    CodecWrite(Iic, R3_RIGHT_DAC_VOL, 0x79);
    CodecWrite(Iic, R4_ANALOG_PATH,   0x10);
    CodecWrite(Iic, R5_DIGITAL_PATH,  0x00);
    CodecWrite(Iic, R7_DIGITAL_IF,    0x53);
    CodecWrite(Iic, R8_SAMPLE_RATE,   0x41);
    usleep(80000);  // wait for charging capacity on the VMID pin t=C*25000/3.5
    CodecWrite(Iic, R9_ACTIVE,        0x01);  // digital core active
    CodecWrite(Iic, R6_POWER_MGMT,    0x67);
    return XST_SUCCESS;
}

AXI GPIOの設定を追加して、コーデックのレジスタの値を変更しています。コーデックのレジスタ設定で重要な箇所は以下です。

  • DIGITAL AUDIO I/F Register (0x07) の設定
    • MSビットに"1"を設定して、マスターモードにする
    • LRPビットに"1"を設定して、DSP Submode2にする
    • WLビットに"00"を設定して、16ビットにする
    • Formatビットに"11"を設定して、DSPモードにする
  • Sampling Rate Register (0x08) の設定
    • MCLK=24MHzでDACサンプリングレートが48kHzとなるように設定

ビルドして、実行するとLEDは点灯しますが、音はでないと思います。ミュートスイッチによってミュートされていますので、スイッチを図18のようにすることでミュートがOFFになり、音が出力されます。

図18:ミュートをOFF
図18:ミュートをOFF

上手くできると以下のような音が出ます。出力される波形とスペクトログラムは図19のようになります。

図19:出力される波形とスペクトログラム
図19:出力される波形とスペクトログラム

440Hz以外にも成分があると思いますが、おそらく原因は正弦波生成 IP (sin_gen) だと思います。sin_genについてはあとで作りなおしたいと思います。

変な音がでる

上の例だと出力される音が綺麗なのですが、これは良い例です。7,8割は上手くいかず、以下のような音が出ます(イヤホンでは聴かないほうがいいです)。出力される波形とスペクトログラムは図20のようになります。

図20:変なときの音の波形とスペクトログラム
図20:変なときの音の波形とスペクトログラム

原因はいまだわかっていないです。プログラムの実行順番を変えたりしたら治ったり、プログラムを元に戻しても変にはならなかったりして、原因がわかりません。ロジックアナライザで確認しても、シリアルデータはフォーマット通りのため、FPGAの外の問題なのかもしれません。

おわりに

音の出力が上手くいかず気持ち悪いですが、他のことをやりながら原因をつきとめようと思います。次の記事では、フィルタの作成を行いたいと思います。

RXマイコンにプログラムを書き込む方法

RXマイコンにプログラムを書き込む方法を紹介したいと思います。本記事では、ターゲットデバイスにRX631を用いますが、他のRXマイコンでも同様の方法で書き込みができます。統合開発環境にはe2studioを用います。

開発環境

  • パソコン: Windows10 64 bit
    • 統合開発環境: e2studio 2021-01をインストール
    • コンパイラ: Renesas CCRX v3.03.00をインストール
    • 書き込みソフト: Renesas Flash Programmer V3.08.01(無償版) をインストール
  • ターゲットデバイス: R5F5631PDDFL

e2studioでプロジェクトの作成

e2studioを起動するとワークスペースを選択する画面が出ますので、ワークスペースとして使用するディレクトリを選択して、「起動」ボタンを押します(図1)。

図1:ワークスペースの選択画面
図1:ワークスペースの選択画面

新しいワークスペースを選択して起動すると、「ログ/使用状況データ収集」の画面が出てきますが、そのまま「Apply」でいいです(図2)。

図2:ログ/使用状況データ収集画面
図2:ログ/使用状況データ収集画面

e2studioの操作画面が出てきたら、[ファイル]→[新規]→[Renesas C/C++ Project]→[Renesas RX] をクリックしてプロジェクトを新規に作成します(図3)。

図3:プロジェクトの作成
図3:プロジェクトの作成

最初の画面では、CCRX toolchainを使用するため、「Renesas CC-RX C/C++ Executable Project」を選択して、「次へ」をクリックします(図4)。

図4:プロジェクトのテンプレートの選択
図4:プロジェクトのテンプレートの選択

次の画面ではプロジェクト名を記入します。この記事ではプロジェクト名を「1_LED」としました。記入を終えたら、「次へ」をクリックします(図5)。

図5:プロジェクト名の記入
図5:プロジェクト名の記入

最後の画面では、ツールチェインに Renesas CCRX が選択されていることを確認して、ターゲットデバイスを選択します(図6)。私の場合、「R5F5631PDDFL」を使っているため、「R5F5631PDxFL」を選択しました。ツールチェイン・バージョンについては「v2.08.00」でも大丈夫だと思います。選択したら「終了」でプロジェクトが作成されます。

図6:ツールチェーンとデバイスの選択
図6:ツールチェーンとデバイスの選択

プログラムファイルの出力設定

プロジェクトが作成できたら、プログラムファイルが出力されるように設定します。プロジェクト「1_LED」を選択して、[プロジェクト]→[プロパティ] をクリックすると、プロパティ画面が開きます(図7)。

図7:プロパティ画面を開く
図7:プロパティ画面を開く

開いたら、[C/C++ビルド]→[設定] を選択して、[ツール設定] タブ→[Converter]→[出力] をクリックします。「ロードモジュールコンバータを実行する」にチェックを付けて、出力ファイル形式が「モトローラS形式ファイルを出力する」に設定されていることを確認します。設定を終えたら、「適用して閉じる」で画面を閉じます(図8)。

補足. 出力ファイル形式に「インテルHEX形式ファイルを出力する」を選択してもいいです。

図8:プロジェクトファイルを出力するように設定
図8:プロジェクトファイルを出力するように設定

プログラムをビルドして書き込み

次にソースコードをプロジェクトに追加していきます。エクスプローラーでソースコードのコピーを行い、[1_LED]→[src] を右クリックして「貼り付け」でプロジェクトに追加できます。元からある「1_LED.c」は削除します。

ソースコードをプロジェクトに追加したら、「1_LED」を選択して、左上のトンカチマークをクリックすることで、ビルドが始まります。コンソールに「Build Finished. 0 errors」と表示されれば OK です(図9)。

図9:プロジェクトのビルド
図9:プロジェクトのビルド

ビルドが終わっていれば、「HardwareDebug」フォルダの下に「1_LED.mot」というプログラムファイルが作成されています。Renesas Flash Programmer (RFP) を起動して、プログラムファイルに「1_LED.mot」を選択します。前回の記事のRX631と接続する方法でPCとマウスを接続して、「スタート」で書き込みを行います。「操作が成功しました。」と表示されれば書き込みは成功です(図10)。

補足. 出力形式にHEX形式を選択した場合は、プログラムファイルに「1_LED.hex」を選択してください。

図10:RFPによるプログラムの書き込み
図10:RFPによるプログラムの書き込み

シングルチップモードで起動すれば、書き込んだプログラムが実行されると思います。

参考文献

[1] e² studio 2020-04、e² studio v7.8 ユーザーズマニュアル入門ガイド
[2] Renesas Flash Programmer V3.08 フラッシュ書き込みソフトウェア ユーザーズマニュアル

RXマイコンのソフト開発(1)クロック設定とLチカ

今回からRXマイコン(RX631)のソフト開発を行いたいと思います。まずはクロック設定とLチカからはじめたいと思います。主にこの記事は以下のマウス本を参考にしています。また、RX631のユーザーズマニュアル ハードウェア編もかなり参考にしているので、参照することをおススメします。

開発環境

  • パソコン: Windows10 64 bit
    • 統合開発環境: e2studio 2021-01をインストール
    • コンパイラ: Renesas CCRX v3.03.00をインストール
    • 書き込みソフト: Renesas Flash Programmer V3.08.01(無償版) をインストール
  • ターゲットデバイス: R5F5631PDDFL(RX631 48ピン)

プログラム

早速、記述したプログラムが以下です。初期化関数はinit_rx631.cinit_rx631.h、使用するポートの再定義はdefine_wakaba.hに書いています。メインルーチンはmain.cに書いています。GitHubの「1_led」フォルダの中にソースコードがありますので、ダウンロードしてご利用ください。

init_rx631.c:初期化関数のソースファイル

#include "init_rx631.h"
#include "iodefine.h"

void init_rx631(void){
    unsigned short i=0;
    SYSTEM.PRCR.WORD = 0xA503;  // Unprotect

    /*---- SUBCLK and RTC Initialization ----*/
    // SUBCLK Initialization
    SYSTEM.SOSCCR.BIT.SOSTP = 1;    // Sub CLK STOP
    while(SYSTEM.SOSCCR.BIT.SOSTP != 1); // Wait until SOSTP=1
    RTC.RCR3.BIT.RTCEN      = 0;    // Sub CLK STOP
    while(RTC.RCR3.BIT.RTCEN != 0); // Wait until RTCEN=0

    // Main CLK setting
    RTC.RCR4.BIT.RCKSEL     = 1;    // Select main CLK as count source
    SYSTEM.MOSCCR.BIT.MOSTP = 0;    // Main CLK ON
    for(i=0;i<1000;i++);            // Wait about 50ms

    // SYSTEM CLK setting
    SYSTEM.PLLCR.BIT.PLIDIV = 0x0;  // Main CLK divided by 1
    SYSTEM.PLLCR.BIT.STC    = 0x13; // Main CLK multiplied by 20
    SYSTEM.PLLCR2.BIT.PLLEN = 0;    // PLL Enable
    for(i=0;i<1000;i++);            // wait about 50ms
    // FCK: 4div, ICK:  2div, BCK:  stop, SDCLK stop,
    // BCK: 4div, PCKA: 2div, PCKB: 4div
    SYSTEM.SCKCR.LONG       = 0x21C21211;
    SYSTEM.SCKCR3.BIT.CKSEL = 0x4;  // Select PLL as CLK source
    SYSTEM.LOCOCR.BIT.LCSTP = 1;    // LOCO stop

    // RTC stop and reset
    RTC.RCR2.BIT.START      = 0;    // RTC stop
    while(RTC.RCR2.BIT.START != 0); // Wait until START=0
    RTC.RCR2.BIT.RESET      = 1;    // RTC reset
    while(RTC.RCR2.BIT.RESET != 0); // Wait until RESET=0

    // Disable RTC interrupt requests
    RTC.RCR1.BYTE        = 0x00;    // Alarm interrupt disable
    while(RTC.RCR1.BYTE != 0x00);   // wait until RCR1=0x00
    /*---- End of SUBCLK and RTC Initialization ----*/

    // I/O setting
    PORT1.PDR.BIT.B7 = 1;  // LED_Red:   Out
    PORT2.PDR.BIT.B7 = 1;  // LED_Green: Out
    PORTE.PDR.BIT.B3 = 1;  // LED_Blue:  Out

    PORT1.PODR.BIT.B7 = 1; // LED_Red:   HIGH
    PORT2.PODR.BIT.B7 = 1; // LED_Green: HIGH
    PORTE.PODR.BIT.B3 = 1; // LED_Blue:  HIGH

    PORT1.PMR.BIT.B7 = 0;  // LED_Red:   I/O
    PORT2.PMR.BIT.B7 = 0;  // LED_Green: I/O
    PORTE.PMR.BIT.B3 = 0;  // LED_Blue:  I/O

    SYSTEM.PRCR.WORD = 0xA500;  // Reprotect
}

init_rx631.h:初期化関数のヘッダーファイル

#ifndef __INITRX631_HEADER__
#define __INITRX631_HEADER__

void init_rx631(void);

#endif

define_wakaba.h:使用するポートを再定義するヘッダーファイル

#ifndef __DEFINE_WAKABA_H__
#define __DEFINE_WAKABA_H__

#include "iodefine.h"

// reg define
#define LED_RED    PORT1.PODR.BIT.B7
#define LED_GREEN  PORT2.PODR.BIT.B7
#define LED_BLUE   PORTE.PODR.BIT.B3

// others
#define LED_ON   0
#define LED_OFF  1

#endif /* __DEFINE_WAKABA_H__ */

main.c:メインルーチンのソースファイル

#include "define_wakaba.h"

void main(void){
    unsigned short i=0;
    unsigned short j=0;

    init_rx631();
    while(1){
        LED_RED = LED_ON;
        for(i=0;i<10000;i++)
            for(j=0;j<100;j++);
        LED_RED = LED_OFF;
        for(i=0;i<10000;i++)
            for(j=0;j<100;j++);
    }
}

プログラムのフロー

プログラムの流れは図1のようになっています。サブクロックとリアルタイムクロック(RTC)の初期化後に、I/Oポートの設定をして、Lチカさせます。経験的にはサブクロックとRTCは初期化しなくても問題ないのですが、「電源投入時に設定してください」とマニュアルに書いてあるので設定します。

図1:プログラムのフローチャート
図1:プログラムのフローチャート

RTCの初期化の流れは図2のようになっています。RTCは使う予定がないので、RTCの停止や割り込み要求の禁止をします。

図2:リアルタイムクロックの初期化のフローチャート
図2:リアルタイムクロックの初期化のフローチャート

プログラムの解説

プログラムの各処理ごとの説明をしていきます。詳細についてはRX631のユーザーズマニュアル ハードウェア編を参照してほしいと思います。

レジスタライトプロテクトの設定

SYSTEM.PRCR.WORD = 0xA503;  // Unprotect
~略~
SYSTEM.PRCR.WORD = 0xA500;  // Reprotect

RXマイコンにはプログラムが暴走したときに、重要なレジスタの値が書き換わらないようにレジスタライトプロテクション機能があります(参照 マニュアル p.350)。この機能によって、クロック発生回路関連レジスタの値を書き換えることができないため、PRCRのPRC0ビットを”1”としてプロテクトを解除します。また、将来的に消費電力低減機能関連レジスタにも書き込みたいため、PRCRのPRC1ビットを”1”としています。初期化関数の最後には、再度レジスタをプロテクトしてます。

注意点としては、PRCRレジスタを書き換える場合、上位8ビットに”A5h”を書き込むことを忘れないでください。はじめ、私はPRC0ビットだけを書きかえていたので、PRCRが書き換わっていませんでした。マウス本のプログラムを見て、書き換わっていないことに気づきました。

サブクロックの初期化

// SUBCLK Initialization
SYSTEM.SOSCCR.BIT.SOSTP = 1;    // Sub CLK STOP
while(SYSTEM.SOSCCR.BIT.SOSTP != 1); // Wait until SOSTP=1
RTC.RCR3.BIT.RTCEN      = 0;    // Sub CLK STOP
while(RTC.RCR3.BIT.RTCEN != 0); // Wait until RTCEN=0

48ピンではサブクロックを使用できませんが、サブクロック関連のレジスタの中にはリセット後に値が不定となっているものがあるので、ビットを設定する必要があります(参照 マニュアル p.288-p.289)。SOSCCRのSOSTPビットに"1"、RCR3のRTCENビットに"0"を設定することで、サブクロック発振器を停止させています。

プログラムを見ると、while文で意味のないことをやっているように見えます。これは、レジスタの書き込みの完了を待ってから後続の命令を実行するために挿入しています。どうやら、レジスタへの書き込みの場合、レジスタの値はすぐに書き換わらないようです。マニュアルに「書き換えた後は、書き込みの完了を待ってから後続の命令を実行する」という旨の記述があるときは、このようにwhile文で待っています(今回の場合、マニュアル p.283に記載されている)。

リアルタイムクロックの初期化

RTCについても48ピンでは使えませんが、電源投入時にRTC内のレジスタの値は不定となっているため、ビットを設定する必要があります。設定手順の詳細は、マニュアル p.1138の「リアルタイムクロック電源投入時の初期化に関する注意事項」における「(d) RTC を使用しない場合(カウントソースにメインクロックが使用できる場合)」を参照してほしいと思います。ここでは、「メインクロックの設定」と「システムクロックの設定」だけ説明したいと思います。

メインクロックの設定

// Main CLK setting
RTC.RCR4.BIT.RCKSEL     = 1;    // Select main CLK as count source
SYSTEM.MOSCCR.BIT.MOSTP = 0;    // Main CLK ON
for(i=0;i<1000;i++);            // Wait about 50ms

メインクロックの設定手順は以下です。

  1. RCR4のRCKSELビットを"1"にして、カウントソースにメインクロックを選択
  2. MOSCCRのMOSTPビットを"0"にすることで、メインクロックを発振させる
  3. for文でメインクロック発振安定待機時間が経過するまで待つ

経験的にはメインクロックの安定まで待たなくても動作はしますが、念のため待機します。 メインクロック発振安定待機時間  t_{\rm MAINOSCWT} は以下の式で計算できます(参照 マニュアル p.312)。

\displaystyle{
 t_{\rm MAINOSCWT}= t_{\rm MAINOSC} + \frac{n+16384}{f_{MAIN}}
}


私の使用しているセラロック(CSTNE10M0G550000R0)の場合、

  • 発振安定時間: t_{\rm MAINOSC}=15000 [us]
  • 発振周波数: f_{\rm MAIN}=10 [MHz]

であり、MSTSビットは初期値のため、 待機時間は n=262144 サイクルです。

したがって、

\displaystyle{
\begin{align}
 t_{\rm MAINOSCWT}&=15000 [{\rm us} ] + \frac{262144 +16384}{10[{\rm MHz}]}  \\
 &=42853 [{\rm us} ]
\end{align} 
}


になって、余裕をもって 50ms 待っています。

補足1. セラロックの発振安定時間についてはどこにも見当たりませんでしたが、セラロックアプリケーションマニュアルに掲載されていた立ち上がり時間のグラフの最大値が約15ms だったので、発振安定時間: t_{\rm MAINOSC}=15000 [us] としています。

補足2. [for(i=0;i<1000;i++);] だと100msくらい待機しているかもしれません。待機時間が気になる人は繰り返し回数を減らしてもいいと思います。

システムクロックの設定

// SYSTEM CLK setting
SYSTEM.PLLCR.BIT.PLIDIV = 0x0;  // Main CLK divided by 1
SYSTEM.PLLCR.BIT.STC    = 0x13; // Main CLK multiplied by 20
SYSTEM.PLLCR2.BIT.PLLEN = 0;    // PLL Enable
for(i=0;i<1000;i++);            // wait about 50ms
// FCK: 4div, ICK:  2div, BCK:  stop, SDCLK stop,
// BCK: 4div, PCKA: 2div, PCKB: 4div
SYSTEM.SCKCR.LONG       = 0x21C21211;
SYSTEM.SCKCR3.BIT.CKSEL = 0x4;  // Select PLL as CLK source
SYSTEM.LOCOCR.BIT.LCSTP = 1;    // LOCO stop

システムクロックの設定手順は以下です。

  1. PLLCRのPLIDIVビットに"00"を設定して、PLLの入力分周比を1とする
  2. PLLCRのSTCビットに"010011"を設定して、PLLの周波数逓倍率を20倍にする
  3. PLLCR2のPLLENビットに"0"を設定して、PLLを動作
  4. for文でPLLクロック発振安定待機時間が経過するまで待つ
  5. SCKCRを設定して、システムクロックなどを最大動作周波数とする
  6. SCKCR3のCKSELビットに"100"を設定して、クロックソースをPLL出力とする
  7. LOCOCRのLCSTPビットに"1"を設定して、LOCOを停止

PLLクロック発振安定時間の求め方はマニュアル p.315 にあります。計算すると 22126us だったため、メインクロックのときと同じようにfor文で待ちます。

SCKCRの設定では、各クロックの動作周波数が最大動作周波数となるように分周比を決めます(参照 マニュアル p.255)。各クロックの動作周波数と分周比は以下のようになっています。

  • システムクロック (ICLK) = 200 MHz / 2 = 100 MHz
  • 周辺モジュールクロックA (PCLKA) = 200 MHz / 2 = 100 MHz
  • 周辺モジュールクロックB (PCLKB) = 200 MHz / 4 = 50 MHz
  • FlashIFクロック (FCLK) = 200 MHz / 4 = 50 MHz
  • 外部バスクロック (BCLK) = 200 MHz / 2 = 100 MHz

システムクロックと周辺モジュールクロックBだけ設定すればいいのですが、他のクロックも念のため設定しています。 また、SDCLKとBCLKについては使用しないため、端子出力を停止させています。

注意点1. SCKCRレジスタを書き換える場合、下位8ビットに”11h”を書き込むことを忘れないでください。はじめ、私はビットごとに書きかえていたので、SCKCRが書き換わっておらず、そのせいかクロックソースがLOCOのままでした。かふぇルネの質問を見て、書き換わっていないことに気づきました。

注意点2. 設定の順番には気をつけてください。例えば、マニュアル p.261には「PLLCR2.PLLEN ビットが “0”(PLL 動作)のとき、PLLCR レジスタへの書き込みは禁止です。」と書いてあるため、PLLの設定はPLL動作前にする必要があります。

注意点3. マニュアル p.258に「PLL選択時は1分周は設定禁止です」とあることに気をつけてください。私の場合、PLLの周波数逓倍率を20倍にすることでクロックソースを200MHzとして、これを2分周または4分周しています。

I/Oポートの設定

 // I/O setting
PORT1.PDR.BIT.B7 = 1;  // LED_Red:   Out
PORT2.PDR.BIT.B7 = 1;  // LED_Green: Out
PORTE.PDR.BIT.B3 = 1;  // LED_Blue:  Out

PORT1.PODR.BIT.B7 = 1; // LED_Red:   HIGH
PORT2.PODR.BIT.B7 = 1; // LED_Green: HIGH
PORTE.PODR.BIT.B3 = 1; // LED_Blue:  HIGH

PORT1.PMR.BIT.B7 = 0;  // LED_Red:   I/O
PORT2.PMR.BIT.B7 = 0;  // LED_Green: I/O
PORTE.PMR.BIT.B3 = 0;  // LED_Blue:  I/O

LEDを制御する端子 (P17, P27, PE3) の設定手順は以下です。

  1. PDRを設定して、端子を出力に設定する
  2. PODRを設定して、端子をHIGH出力とする
  3. PMRを設定して、端子を汎用入出力ポートとして使用する

LED部分の回路図を図3に示します。

図3:LED部分の回路図
図3:LED部分の回路図

回路図がこのようになっているため、端子をHIGH出力とすることでLEDを消灯させています。

I/OポートのH出力とL出力の繰り返し

while(1){
    LED_RED = LED_ON;
    for(i=0;i<10000;i++)
        for(j=0;j<100;j++);
    LED_RED = LED_OFF;
    for(i=0;i<10000;i++)
        for(j=0;j<100;j++);
}

最後にLEDの点灯と消灯を繰り返すプログラムを書いています。このプログラムでおよそ0.3秒の周期でLEDが点滅します。システムクロックの設定が上手くできていなくてクロックソースにLOCOが用いられている場合、約30秒周期でLEDが点滅しますので、気をつけてください。

ハマったところ

// RTC stop and reset
RTC.RCR2.BIT.START      = 0;    // RTC stop
while(RTC.RCR2.BIT.START != 0); // Wait until START=0
RTC.RCR2.BIT.RESET      = 1;    // RTC reset
while(RTC.RCR2.BIT.RESET != 0); // Wait until RESET=0

はじめ、私はマニュアル p.1964 の「48 ピン製品の使用上の注意事項」をもとにサブクロックとRTCを初期化していましたが、どうしてもRCR2のRESETビットが"0"にならず、while文で動作が止まってしまいました。

原因としては以下の2つがありました。

  • メインクロックが動作していなかった。
  • システムクロックの設定がうまくできていなかった。

マニュアル p.1964 の「48 ピン製品の使用上の注意事項」には、メインクロックを動作させることやシステムクロックを設定することなどは書かれていないので、上記でも述べたように以下を参照するほうがいいと思います。

  • マニュアル p.288-p.289の「サブクロック発振器に関する注意事項」における「(d) サブクロックを使用しない場合
  • マニュアル p.1138の「リアルタイムクロック電源投入時の初期化に関する注意事項」における「(d) RTC を使用しない場合(カウントソースにメインクロックが使用できる場合)

また、マニュアル p.256の「クロック発生回路のブロック図」を見て、RTCで使用するクロックはシステムクロックとは関係ないと思っていましたが、マニュアル p.1140「LOCO クロックの周波数はメインクロックの周波数より低いため、システムクロックを変更する必要があります」やマニュアル p.1134「周辺モジュールクロック(PCLK)周波数はカウントソースの周波数以上に設定する」と記述があるので、ICLKやPCLKも関係するみたいです。

とにかく、メインクロックの設定とシステムクロックの設定をすることで、リセットが動作するようになりました。私と同じようにリセットが完了しない人は、メインクロックの設定とシステムクロックの設定がきちんとできているか確認するべきだと思います。

プログラムの動作

RXマイコンにプログラムを書き込む方法の手順でプログラムを書き込みます。 書き込みが完了すれば、シングルチップモードで動作させることで図4のようにLチカが動作します。

図4:Lチカの様子
図4:Lチカの様子

おわりに

結構長くなってしまいましたが、参考にしていただければ幸いです。次回はスイッチ入力を受けつけるプログラムを書こうと思います。

ZYBOでシンセサイザー作成(4)パラシリ変換 IP の作成


今回は、前回の記事で作成した正弦波生成 IP(sin_gen)の出力をパラシリ変換して、オーディオコーデック(SSM2603)で受け取れるデータ形式にします。

環境

  • パソコン:Windows10 64 bit
    • Vivado 2020.2 をインストール
    • Xilinx Vitis 2020.2 をインストール
  • ボード:Zybo Z7-20

パラシリ変換 IP の作成

Vivadoでパラシリ変換 IP のpara2serialを製作していきます。作成の仕方は前回のIPの作成とほとんど同じです。

パラシリ変換 IP について

コーデックへのデータの与え方はいくつかありますが、今回は図1の方法でシリアル変換します。コーデックから図1のBCLKとPBLRCの波形がくるので、これらに同期させて、PBDATに波形を送ります。

図1:コーデックへのデータの与え方(SSM2603のデータシートから引用)
図1:コーデックへのデータの与え方(SSM2603のデータシートから引用)

パラシリ変換 IPのフォルダの用意

前回と同じように以下のようにフォルダとファイルを配置していきます。para2serial.svはGitHubの「4th」フォルダにあります。

ip_repo_synth
└── para2serial
    └── HDL
        └── para2serial.sv

para2serial.svの内容を以下に示します。para2serial.svの内容については別の記事でしたいと思います。

para2serial.sv

module para2serial(
    input logic clk96M,
    input logic reset,
    input logic bclk,
    input logic pblrc,
    input logic [15:0] din_L,
    input logic [15:0] din_R,
    output logic pbdat
    );
    
    logic [1:0] cnt4;
    logic load;
    logic shift;
    logic [31:0] sreg_ff;
    
    // cnt4
    always_ff @(posedge clk96M) begin
        if(reset)
            cnt4 <= 2'd0;
        else if(bclk)
            cnt4 <= cnt4 + 2'd1;
        else
            cnt4 <= 2'd0;
    end
                  
    // shift register
    assign shift = (cnt4==2'd3);
    assign load = shift & pblrc;
    
    always_ff @ (posedge clk96M) begin
        if(reset)
            sreg_ff <= 32'd0;
        else if(load)
            sreg_ff <= {din_L, din_R};
        else if(shift)
            sreg_ff <= {sreg_ff[31:0], 1'b0};
    end

assign pbdat = sreg_ff[31];      
    
endmodule

VivadoでIPの製作

プロジェクトを以下のようにして作成します。

  • 作業フォルダ:para2serial
  • プロジェクト名:para2serial

このプロジェクトでIPの製作を行い、IPのシミュレーションも行います。

プロジェクトができたら、前回のVivadoでIPの製作の手順通りに途中まで進めて、IP編集用のVivado画面を起動します。

前回は編集用のVivado画面でいろいろとやりましたが、今回は「Package IP」タブ →「Review and Package」の「Package IP」を押すだけでいいです(図3)。

図2:「Package IP」を押す
図2:「Package IP」を押す

以上でIPが作成され、元のVivadoプロジェクト画面に戻ります。

パラシリ変換 IP のシミュレーション

パラシリ変換 IPであるpara2serialのシミュレーションをしていきます。

IP インテグレータで回路作成

IPインテグレータで回路を作っていきます。

  1. 「IP INTEGRATOR」→「Create Block Design」で「Diagram」を作成する。
  2. 「PROJECT MANAGER」→「Settings」で「Settings」画面を開き、「Project Settings」→「IP」→「Repository」の「+」で「sin_gen」IPを追加して、「OK」を押す(図3)。

    図3:プロジェクトにIPを追加する
    図3:プロジェクトにIPを追加する

  3. 「Add IP」で「sin_gen_v1_0」と「para2serial_v1_0」を追加する。

  4. 図4のようにして、回路を作成する。

図4:作成する回路
図4:作成する回路

回路ができたら、「Validate Design」をして、回路のチェックを行います。また、「Create HDL Wrapper」でラッパーを作成します。ラッパーの作成では「Copy generated wrapper to allow user edits」を選択します。

テストベンチの追加

テストベンチsim_para2serial.svを前回の記事のテストベンチの追加と同じように追加します。sim_para2serial.svはGitHubにあり、内容は以下です。(2021/5/29 追記:PBLRCの周期が96kHzだったため、48kHzになるようにテストベンチを修正しました)。sim_para2serial.svの説明についても別の記事で書こうと思います。

sim_para2serial.sv

`timescale 1ps / 1ps

module sim_para2serial;

localparam [63:0] STEP = 10416;
localparam [63:0] CLKNUM = 960000*4;

logic clk96M;
logic [14:0] freq;
logic reset;
logic bclk;
logic pbdat;
logic pblrc;

logic [1:0] cnt4;
logic [8:0] cnt500;

design_1_wrapper design_1_wrapper(
    .bclk(bclk),
    .clk96M (clk96M),
    .freq (freq),
    .pbdat (pbdat),
    .pblrc (pblrc),
    .reset (reset)
    );

// clk96M
always begin
    clk96M = 0; #(STEP/2);
    clk96M = 1; #(STEP/2);
end     

// cnt4
always_ff @(posedge clk96M) begin
    if(reset)
        cnt4 <= 2'd0;
    else
        cnt4 <= cnt4 + 2'd1;
end

// cnt500
always_ff @(posedge clk96M) begin
    if(reset)
        cnt500 <= 9'd0;
    else if(cnt4==2'd2)
        if(cnt500==9'd499)
            cnt500 <= 9'd0;
        else
            cnt500 <= cnt500 + 9'd1;
end

// pblrc
always_ff @(posedge clk96M) begin
    if(reset)
        pblrc <= 1'b0; 
    else if(cnt500==9'd499 | cnt500==9'd498)
        pblrc <= 1'b1;
    else
        pblrc <= 1'b0;
end

// bclk
always_ff @(posedge clk96M) begin
    if(reset)
        bclk <= 1'b0;
    else if(cnt4==2'd3)
        bclk <= ~bclk;
end

initial begin
    reset = 0;
    freq = 15'd440;
    #(STEP*600);
    reset = 1;
    #(STEP*20);
    reset = 0;
    #(STEP*CLKNUM);
    freq = 15'd600;
    #(STEP*CLKNUM);
    $stop;
end

endmodule

シミュレーションの実行

シミュレーションを実行すると、図6のようになっています。

図6:シミュレーションの結果
図6:シミュレーションの結果

PBDATが図1のデータ形式通りになっていることが確認できると思います。

おわりに

今回でパラシリ変換 IP を作成して、シミュレーションができました。次回はいよいよ音を出力したいと思います。

参考文献

[1] SSM2603のデータシート
[2] 小林 優、「FPGAプログラミング大全 Xilinx編 第2版」、秀和システム、2021.

お問い合わせフォーム プライバシーポリシー

© 2021 Setoti All rights reserved.