RX651用のマウス基板の設計とRX631との違い

いままでRX631を載せたマウス基板を用いて、デバイスの動作確認をおこなっていましたが、AD変換が上手くいかなかったので、マウス基板をまた作りなおしました。

RX631-48ピンが手に入りづらくなっていたことに加えて、RAMが多い高性能のマイコンを使いたいと思ったので、今回はRX651-64ピン用のマウス基板を設計しました。

RX651用のマウス基板の回路設計

赤外線センサーのAD変換結果がおかしな値となっていたため、RX651のユーザーズマニュアル ハードウェア編 を参考にして回路を再設計をしました。

回路図の変更点

再設計した回路図は図1です。

図1:再設計した回路図
図1:再設計した回路図
回路図PDF版

今回の回路図で前回の回路図から変更した主な箇所は以下の3つです。

  1. マイコンをRX631からRX651に変更
  2. 1つのFETで1つの赤外線LEDをスイッチング
  3. マウス基板とAS5047P基板をつなげるコネクタを4x2から3x2に変更

冒頭で述べたようにRX631からRX651に変更しています。

加えて、いままで1つのFETで2つの赤外線LEDをスイッチングしていましたが、プログラムが書きにくかったので、1つのFETで1つの赤外線LEDをスイッチングするようにしました。

あとは、AS5047PのA端子とB端子はマイコンと接続しなくてもよいことがわかりましたので、コネクタを4x2から3x2に変更しています。

DRV8836やMPU6000も手に入りづらくなっているので、変更しようかなと思いましたが、使い方を再び調査しないといけないので辞めときました。

配線図の変更点

再設計した配線図は以下です。

図2:再設計した配線図
図2:再設計した配線図

配線図PDF版(縮尺:2.5倍)

今回の配線図で前回の配線図から変更した主な箇所は以下の3つです。

  1. マイコンのAVSSや赤外線センサにつながっているGNDはレギュレータ付近のコンデンサのGNDと一点接続する
  2. 赤外線センサーの出力信号とデジタル信号線を交差させない
  3. モータドライバーのGNDはレギュレータ付近のコンデンサのGNDと一点接続する

変更点1と2についてはRX651のマニュアルp.2548にある「53.6.12 ボード設計上の注意」に書いてあることを参考にしています。いままでは、プリント基板の2層ともベタGNDにしていましたが、1層だけベタGNDにして、GNDを1点接続できるようにしました。

また、モータドライバーからのノイズで問題が生じるというブログ記事をいくつか拝見しましたので、モータドライバーのGNDについてもレギュレータ付近のコンデンサのGNDと一点接続させました。

届いたマウス基板

Elecrowに発注して届いたマウス基板が図3です。

図3:届いたマウス基板
図3:届いたマウス基板

文字は少し乱れていましたが、フットプリントがとても綺麗にできていて良かったです。

ハンダ付けして組み立てたマウスが図4となります。

図4:組み立てたマウス
図4:組み立てたマウス

前回のマウス基板では、センサーの出力はオシロコープで正常だったにもかかわらず、AD変換の結果がずっと4095になっていたり、電圧が上がると逆にAD変換の結果が下がったりして、謎の動作をしていました。

今回のマウス基板では、AD変換の結果が正常な値でしたので、とても良かったです。

RX651とRX631の違い

RXマイコン(RX631)のソフト開発で記述したプログラムを書き換えました。その過程で気づいたRX631と違った点を述べていきます。

性能

前回購入したRX631と今回購入したRX651の性能の違いは以下の表のようになっています。システムクロック(ICLK)というのはRXマイコンのCPU動作クロックの呼び名です。

RX631 RX651
型名 R5F5631PDDFL R5F5651EHDFM
ピンの数 48 64
RAM容量 64KB 640KB
ROM容量 512KB 2MB
ICLK周波数 100MHz 120MHz

全体的にメモリの容量が多くなっているので、プログラムや計算結果などを多く保存できます。

また、 ICLK周波数も120MHzに上がっているので、計算速度が速くなってます。

書き込みにメインクロックがいらない

以下の記事で説明しましたが、RX631では書き込みにメインクロックが必要でした。

しかし、RX651ではSCI (UART) で書き込む場合、メインクロックがいらなくなりました。試しにセラロックをつけずに書き込んだところ、無事書き込めました。

また、PLL回路の入力クロックソースに高速オンチップオシレータ(HOCO)を選択できるようになりましたので、メインクロックがなくてもICLKを最大周波数で動作できます。

これでRXマイコンの書き込みのハードルも下がったかなと思います。

プログラム的な変更点

RX631からRX651でプログラムを書き換えたときの主な変更点を説明します。

ROMWTレジスタの設定

RX651からICLKを50MHz以上で動かす場合、ROM ウェイトサイクル設定レジスタ (ROMWT)の設定が必要となりました(マニュアルp.312「9.2.2 ROM ウェイトサイクル設定レジスタ (ROMWT)」を参照)。

そのため、メインクロックの設定とICLKの設定の間にROMWAITレジスタの設定を以下のように加えました。

/*---- MainCLK, SUBCLK and RTC Initialization ----*/
void init_clock(void){
     ~略~
    // 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

    // ROMWAIT setting
    SYSTEM.ROMWT.BIT.ROMWT  = 2;
    while(SYSTEM.ROMWT.BIT.ROMWT!=2);  // ROM wait cycle = 2cycle

    // SYSTEM CLK setting
    SYSTEM.PLLCR.BIT.PLIDIV = 0x0;  // Main CLK divided by 1
    SYSTEM.PLLCR.BIT.STC    = 0x17; // Main CLK multiplied by 12
     ~略~
}

MTU3とRSPIがPCLKAを使用

RX631ではMTU3とRSPIはクロックにPCLKBを使用していましたが、RX651ではPCLKAを使用しています。それなので、MTU3とRSPIがPCLKBで動いていると勘違いしないように注意する必要があります。

スピーカーを鳴らしたときに音が高くなっていたので、気づくことができました。

センサーホルダーでの失敗

図5のセンサーホルダーを3Dプリンタで作成したのですが、失敗したので報告します。

図5:作成したセンサーホルダー
図5:作成したセンサーホルダー

赤外線LEDを光らせて、センサーホルダーを付けたときのAD変換値をみてください。

表:センサーホルダーありのAD変換値
障害物 左前 左横 右前 右横
3607 3634 3587 3564
3689 3747 3661 3673

障害物がセンサーの前に無いときと有るときでAD変換値の差がほとんどありません。

おそらく、原因としてはセンサーホルダーによる散乱光だと考えられます。それにしても、散乱光が強すぎると思いますが....。

センサーホルダーを使う人で、散乱光の対策はどうしているのかと気になったところ以下の記事が見つかりました。

見つけた記事うむ夫の歩み Nucleo-32boardを使ったクラシックマウスの開発その8 ~センサーホルダー~

どうやら、熱収縮チューブを付けてホルダーに挿入すれば良いみたいです。ただ、作成したホルダーの穴の大きさが熱収縮チューブの分を考慮していないので、作り直す必要があります。

試しにセンサーホルダーを外して、赤外線LEDに熱収縮チューブを付けた場合のAD変換値は以下です。

表:センサーホルダーなし、熱収縮チューブありのAD変換値
障害物 左前 左横 右前 右横
350 343 278 574
1916 2078 2316 2951

AD変換の値に差があっていい感じです。

センサーホルダーを付けなくてもいいかなと少し思いましたが、基板にホルダーを差し込む穴もあるので、もう一度作成しなおそうと思います。

おわりに

今回、RX651用のマウス基板の設計とRX651とRX631の違いについて話しました。これで全てのデバイスの動作確認ができましたので、次回から本格的にマイクロマウスのプログラムを作成していきたいと思います。

参考文献

RXマイコンのソフト開発(8)磁気式エンコーダーAS5047

RXマイコン(RX631)のソフト開発の8回目です。今回は、磁気式エンコーダーのAS5047を動作させたいと思います。AS5047によって、タイヤの回転角度を求めます。

環境

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

AS5047の周辺回路

作成したAS5047の周辺回路図は図1です。xxxxxx_Hxxxxxx_HRはコネクタでつながっています。

図1:AS5047の周辺回路図
図1:AS5047の周辺回路図

今回使用するネットの RX631 への接続先は以下です。今回は右側のエンコーダーしか使いません。

  • CS1 → PB5
  • RSPCKA → PC5/RSPCKA
  • MOSIA → PC6/MOSIA
  • MISOA → PC7/MISOA

はじめ、SPI通信で初期設定をして、R_ENC_AR_ENC_BからA相とB相の矩形波を受け取ろうと思ったのですが、SPI通信だけでも角度情報を受け取れるようなので、R_ENC_AR_ENC_Bは使いません。

補足:初期状態でA相とB相の矩形波が出力されるように設定されている(オシロスコープで出力を確認)ので、SPIの通信線がなくても角度情報は受け取れるそうです。このため、レジスタ設定をしない人は、SPIの通信線を省略してみてもいいかもしれません。

作成した機体の足回り

足回りの構造は図2のようになっています。

図2:足回りの構造
図2:足回りの構造

ギアホイールにシャフトを圧入して、カラーにシャフトを圧入して、カラーの中に磁石を入れることで、ギアホイールと磁石が同じように回転します。磁気式エンコーダーで磁石の角度がわかることで、ギアホイールの回転角度もわかります。

プログラムフロー

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

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

クロックの初期化とCMT0、SCI1の初期化を行って、RSPI0を初期化します。その後、メインルーチンに入り、1ms経過したら、磁石の角度を取得して、UARTで送信します。

プログラム

磁気式エンコーダーに関係するプログラム部分だけ示します。sci.csci.h を除いた全てのソースコードGitHubの「8_as5047」フォルダの中にあります。

メイン関数

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

#include "sci.h"
#include "spi.h"
#include "init_rx631.h"
#include "define_wakaba.h"

extern volatile unsigned char g_flag;

void main(void){
    unsigned short rd_data;
    unsigned short data;

    init_rx631();       // Overall Initialization

    while(1){
        if(g_flag){
            g_flag = 0;  // Clear flag

            // Read angular velocity
            rd_data = SPI_ReadAS5047(AS5047_ANGLECOM);
            data = (rd_data & 0x3FFF) * 360 / 16384;
            sci_printf("%u\r\n", data);
        }
    }
}

メイン関数では、RX631の初期化を行い、メインルーチンに入ります。CMT0の割り込み関数によって1ms 経過フラグ(g_flag)が立ったら、フラグを下げて、角度を読み、その値をsci_printfでUART送信します。

補足:角度データが入っているレジスタANGLEANGLECOMがありますが、今回はエラー補正したANGLECOMから角度データを取り出します。

RSPI0の初期化

初期化関数のソースコードが以下です。RSPI に関してはユーザーズマニュアルの p.1619「38. シリアルペリフェラルインタフェース(RSPI)」に記述があります。ほとんどRXマイコンのソフト開発(6)RSPIでMPU6000から角速度取得の初期化と同じです。

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

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

/*** Function Declaration ***/
static void init_clock(void);
static void init_cmt0(void);
static void init_sci1(void);
static void init_rspi0(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();

    // RSPI0 Initialization
    init_rspi0();

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

~略~

/*---- RSPI0 Initialization ----*/
static void init_rspi0(void){

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

    RSPI0.SPBR = 5;                //  521 kbps when BRDV=3

    RSPI0.SPSCR.BIT.SPSLN = 0;     //  Sequence length = 1
    RSPI0.SPDCR.BIT.SPFC  = 0;     //  Frame Num = 1
    RSPI0.SPDCR.BIT.SPLW  = 1;     //  Long Word Access

    /* RSPI Command Register0 */
    // CPHA = 1  : Data change at odd edges, data sample at even edges
    // CPOL = 0  : RSPCK at idle is LOW
    // BRDV = 3  : 8 divisions of the base bit rate
    // LSBF = 0  : MSB First
    // SPB  = 15 : Data length = 16bit
    RSPI0.SPCMD0.WORD = 0x0F0D;

    MPC.PWPR.BIT.B0WI     = 0;     // PFSWE bit write enable
    MPC.PWPR.BIT.PFSWE    = 1;     // PmnPFS Register write enable
    MPC.PC7PFS.BIT.PSEL   = 13;    // Set to MISOA
    MPC.PC6PFS.BIT.PSEL   = 13;    // Set to MOSIA
    MPC.PC5PFS.BIT.PSEL   = 13;    // Set to RSPCKA
    MPC.PWPR.BYTE         = 0x80;  // Reprotect
    PORTC.PMR.BIT.B7      = 1;     // set to Peripheral
    PORTC.PMR.BIT.B6      = 1;     // set to Peripheral
    PORTC.PMR.BIT.B5      = 1;     // set to Peripheral
    PORTB.PDR.BIT.B5      = 1;     // CS1: OUT
    PORTB.PODR.BIT.B5     = 1;     // CS1: HIGH

    RSPI0.SPCR.BIT.SPMS = 1;       // Clock synchronous operation
    RSPI0.SPCR.BIT.MSTR = 1;       // Master Mode
}

MPU6000のときと変わっている点は、SPCMD0レジスタのCPOLビットを"0"にしている点だけです。これで、アイドル時にRSPCKAが"L"になります。

補足:今回は、ANGLECOMレジスタから角度データを取り出すので、AS5047の初期設定はしませんでした。ANGLEレジスタから角度データを取り出す場合は、SETTINGS1レジスタDAECDESビットやDataselectビットを設定したほうがいいかもしれません。

SPIの送受信

SPIで読み書きするソースコードとヘッダーファイルが以下です。こっちはMPU6000のときと少し変わっています。

spi.c:SPI関連のソースコード

#include "define_wakaba.h"
#include "spi.h"
#include "sci.h"

static u16 SPI_SendRecvAS5047(u32 packet);

/* Write to AS5047 register */
void SPI_WriteAS5047(u16 address, u16 data){
    u16 packet;
    u8  i;
    u8  parity=0;

    CS_AS5047 = ASSERT;  // Assert AS5047

    /* make packet */
    packet = address;
    for(i=0;i<15;i++)
        parity += (packet >> i) & 1;
    packet += (parity % 2) << 15;

    SPI_SendRecvAS5047(packet);  // Send Address

    CS_AS5047 = NEGATE;  // Negate AS5047

    for(i=0;i<10;i++)   // Wait 700ns
        __nop();

    CS_AS5047 = ASSERT;  // Assert AS5047

    /* make packet */
    packet = data;
    parity = 0;
    for(i=0;i<15;i++)
        parity += (packet >> i) & 1;
    packet += (parity % 2) << 15;

    SPI_SendRecvAS5047(packet);  // Send Data

    CS_AS5047 = NEGATE;  // Negate AS5047
}

/* Read AS5047 register */
u16 SPI_ReadAS5047(u16 address){
    u16 packet=0;
    u8  i;
    u8  parity=0;
    u16 data=0;

    CS_AS5047 = ASSERT;  // Assert AS5047

    /* make packet */
    packet = 0x4000 + address;
    for(i=0;i<15;i++)
        parity += (packet >> i) & 1;
    packet += (parity % 2) << 15;

    SPI_SendRecvAS5047(packet);  // Send Address

    CS_AS5047 = NEGATE;  // Negate AS5047

    for(i=0;i<10;i++)   // Wait 700ns
        __nop();

    CS_AS5047 = ASSERT;  // Assert AS5047

    /* make packet */
    packet = 0xC000;

    data = SPI_SendRecvAS5047(packet);  // Receive Data

    CS_AS5047 = NEGATE;  // Negate AS5047

    return data;
}

/* Sending/receiving data */
static u16 SPI_SendRecvAS5047(u32 packet){
    u16 data;

    RSPI0.SPCR.BIT.SPTIE  = 1;  // Enable transmission IRQ
    RSPI0.SPCR.BIT.SPE    = 1;  // Enable RSPI

    /* Wait for the send buffer to be empty */
    while(IR(RSPI0, SPTI0)==0);
    IR(RSPI0, SPTI0)=0;         // Clear Flag

    RSPI0.SPDR.LONG = packet;   // Send data

    RSPI0.SPCR.BIT.SPTIE  = 0;  // Disable transmission IRQ
    RSPI0.SPCR2.BIT.SPIIE = 1;  // Enable idle IRQ
    RSPI0.SPCR.BIT.SPRIE  = 1;  // Enable receive IRQ

    /* Wait for RSPI to idle */
    while(IR(RSPI0, SPII0)==0);
    IR(RSPI0, SPII0)=0;         // Clear Flag

    /* Wait for the receive buffer to be written */
    while(IR(RSPI0, SPRI0)==0);
    IR(RSPI0, SPRI0)=0;         // Clear Flag

    data = RSPI0.SPDR.LONG & 0xFFFF;

    RSPI0.SPCR2.BIT.SPIIE = 0;  // Enable idle IRQ
    RSPI0.SPCR.BIT.SPRIE  = 0;  // Enable receive IRQ
    RSPI0.SPCR.BIT.SPE    = 0;  // Disable RSPI

    return data;
}

spi.h:SPI関連のヘッダーファイル

#ifndef SPI_H_
#define SPI_H_

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

#define LENGTH_16BIT  15
#define LENGTH_24BIT   1

#define AS5047_ERRFL     0x0001
#define AS5047_ZPOSM     0x0016
#define AS5047_ZPOSL     0x0017
#define AS5047_SETTINGS1 0x0018
#define AS5047_SETTINGS2 0x0019
#define AS5047_ANGLE     0x3FFE
#define AS5047_ANGLECOM  0x3FFF

#define ASSERT 0
#define NEGATE 1

#define CS_AS5047  PORTB.PODR.BIT.B5

void SPI_WriteAS5047(u16 address, u16 data);
u16 SPI_ReadAS5047(u16 address);

#endif /* SPI_H_ */

メイン関数ではSPI_WriteAS5047は使っていないので、SPI_ReadAS5047だけ説明したいと思います。SPI_SendRecvAS5047についてはSPI_SendRecvMPU6000と変わっていません。

SPI通信でAS5047のレジスタを読む方法は図4のようになります。

図4:SPI通信によるレジスタの読みかた(AS5047Pのデータシートから引用)
図4:SPI通信によるレジスタの読みかた(AS5047Pのデータシートから引用)

コマンドフレーム(Command)の構造は以下の表のようになります。

ビット 名前 説明
15 PARC 14:0 ビットで計算された偶数パリティビット
14 R/W 0:書き込み、1:読み込み
13:0 ADDR 読み書きするアドレス

パリティビットを作る必要があるので、SPI_ReadAS5047関数の中でパリティビットを作成しています。

データフレーム(Data)の構造は以下の表のようになります。

ビット 名前 説明
15 PARD 14:0 ビットで計算された偶数パリティビット
14 EF 0:コマンドフレームでエラーなし、1:エラーあり
13:0 DATA データ

15:14 ビットについてはデータではないので、メイン関数でマスクをして、データだけ取り出しています。

また、アドレス送信とデータ受信の間に350ns以上、CSを"H"にする必要があるので、NOPを10回実行しています。オシロスコープで測定したところ、NOPを10回実行することで、700nsの間、CSが”H”になっていました。

データを受信するときは、NOPレジスタ(Address=0x0000)を読み込むためのコマンドフレーム(0xC000)を同時送信しています。

プログラムの実行

プログラムを実行して、タイヤを左右に動かすと図5のようになりました(取得したデータから初期値を引いています)。

図5:取得した回転角度
図5:取得した回転角度

図5を見たところ、回転角度は問題なく取得できてそうです。

おわりに

今回で磁気式エンコーダーのAS5047からタイヤの回転角度を取得できました。これまでで、ほとんどのデバイスを動作させることができましたが、赤外線センサーだけ上手く動作できなかったので、もう一度基板を作りなおそうかと思います。

参考文献

RXマイコンのソフト開発(7)モータドライバーDRV8836の動作

RXマイコン(RX631)のソフト開発の7回目です。今回は、モータドライバーのDRV8836を動作させたいと思います。制御はしないです。ただ動かすだけです。ギアが噛み合うかも確かめたいと思います。

環境

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

DRV8836の周辺回路

作成したDRV8836の周辺回路図は図1です。

図1:DRV8836の周辺回路図
図1:DRV8836の周辺回路図

各ネットの RX631 への接続先は以下です。

  • NSLEEP → P15
  • M_PHASE_L → PE4
  • M_ENBL_L → PB1/MTIOC4C
  • M_PHASE_R → PB0
  • M_ENBL_R → PB3/MTIOC4A

DRV8836は制御の仕方に IN/IN モードと PHASE/ENABLE モードがありますが、作成した回路はPHASE/ENABLE モードで使う回路となっています。xIN1(xENABLE)にPWM波形を入れて、DUTY比を変えることで速度を変更します。また、xIN2(xPHASE)にHIGH、LOWを入れて、回転方向を変更します。

プログラムフロー

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

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

クロックの初期化とCMT0の初期化を行って、MTU4を初期化します。その後、メインルーチンに入り、スイッチが押されたら、片方のモータをDUTY比50%で回転させるか、逆回転させるか、停止させるかします。その後、status変数を変更します。

プログラム

重要なプログラム部分だけ示します。全てのソースコードGitHubの「7_drv8836」フォルダの中にあります。

メイン関数

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

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

#define NSLEEP       PORT1.PODR.BIT.B5
#define M_PHASE_R    PORTB.PODR.BIT.B0
#define M_PHASE_L    PORTE.PODR.BIT.B4
#define MOTOR_START  MTU.TSTR.BIT.CST4

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

    init_rx631(); // Overall Initialization
    NSLEEP = 1;   // Sleep mode stop

    while(1){
        if(g_sw_chg){   // Enter when SW is pressed
            g_sw_chg = 0;
            if(status==0){  // Rotate motor
                M_PHASE_R = 0;
                MTU4.TGRA = 249;   // Duty M_ENBL_R
                MTU4.TGRC = 249;   // Duty M_ENBL_L
                MOTOR_START = 1;
                status = 1;
            }
            else if(status==1){  // Reverse rotation of motor
                M_PHASE_R = 1;
                status = 2;
            }
            else if(status==2){  // Motor Stop
                MTU4.TGRA = 500;   // Duty M_ENBL_R
                MTU4.TGRC = 500;   // Duty M_ENBL_L
                status = 0;
            }
        }
    }
}

メイン関数では、RX631の初期化を行い、P15(NSLEEP)をHIGHにすることで、DRV8836のスリープモードを解除します。スイッチが押されてフラグ(g_sw_flag)が立ったら、フラグを下げて、status に応じて、モーターを回転させたり、逆回転させたり、停止させたりします。回転させるときは、M_PHASE_R で回転方向を決めて、MOTOR_START (MTU.TSTR.BIT.CST4) を1に設定して、タイマーを動作させて、PWM波形を出力させます。

補足:モーターを停止させるとき、MTU4.TGRX=500 とさせることで、コンペアマッチさせず、HIGHにならないようにしています。

MTU4の初期化

初期化関数のソースコードが以下です。MTU に関してはユーザーズマニュアルの p.765「23. マルチファンクションタイマパルスユニット 2(MTU2a)」に記述があります。初期化の方法は RXマイコンのソフト開発(3)スピーカーから音を出す とほとんど同じです。

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_mtu4(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();

    // MTU4 Initialization
    init_mtu4();

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

~略~

/*---- MTU4 Initialization ----*/
void init_mtu4(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.PB3PFS.BIT.PSEL = 0x02; // set to MTIOC4A
    MPC.PB1PFS.BIT.PSEL = 0x02; // set to MTIOC4C
    MPC.PWPR.BYTE       = 0x80; // Reprotect
    PORTB.PMR.BIT.B3    = 1;    // set to Peripheral
    PORTB.PMR.BIT.B1    = 1;    // set to Peripheral
    PORTB.PDR.BIT.B0    = 1;    // M_PHASE_R: OUT
    PORTE.PDR.BIT.B4    = 1;    // M_PHASE_L: OUT
    PORT1.PDR.BIT.B5    = 1;    // NSLEEP:    OUT

    MTU4.TMDR.BIT.MD   = 0x2;   // PWM mode1
    MTU.TOER.BIT.OE4A  = 1;     // MTIOC4A Output Enable
    MTU.TOER.BIT.OE4C  = 1;     // MTIOC4C Output Enable
    MTU4.TIORH.BIT.IOA = 0x2;   // Initial output LOW, Compare match HIGH
    MTU4.TIORH.BIT.IOB = 0x1;   // Initial output LOW, Compare match LOW
    MTU4.TIORL.BIT.IOC = 0x2;   // Initial output LOW, Compare match HIGH
    MTU4.TIORL.BIT.IOD = 0x1;   // Initial output LOW, Compare match LOW
    MTU4.TCR.BIT.TPSC  = 0x0;   // PCLK/1
    MTU4.TCR.BIT.CKEG  = 0x0;   // count by riging edge
    MTU4.TCR.BIT.CCLR  = 0x2;   // Clear by compare match TGRB
    MTU4.TGRA          = 249;   // Duty M_ENBL_R
    MTU4.TGRB          = 499;   // PWM period
    MTU4.TGRC          = 249;   // Duty M_ENBL_L
    MTU4.TGRD          = 499;   // PWM period
}

モジュールストップを解除して、端子機能を変更して、MTU4の設定をしています。

スピーカーのときと異なる点は主に以下です。

  • MTIOC4A、MTIOC4Cの出力許可を出す
  • コンペアマッチしたときの出力の仕方を変える
  • PWM波形の周波数とDUTY比の設定をする

MTIOC3Aに関しては出力許可をするためのレジスタはありませんでしたが、MTIOC4xに関してはTOERレジスタを設定しないと出力許可されないので設定します。

また、初期設定のときに端子がLOWになってほしいので、TIORHレジスタTIORLレジスタの設定を変更しています。

最後に、TGRXレジスタを設定して、PWM波形の周波数を100kHz、DUTY比を50%にしています。

プログラムの実行

プログラムを実行させて、スイッチを押したところ、図3のようにモーターを回転させることができました(写真ではわかりにくいですが...)。

図3:モーターが回転している様子
図3:モーターが回転している様子

おわりに

今回でDRV8836を動作させることができました。次回は磁気式エンコーダーを動かしたいと思います。

参考文献

RXマイコンのソフト開発(6)RSPIでMPU6000から角速度取得

RXマイコン(RX631)のソフト開発の6回目です。今回は、RSPIを使ってジャイロ(MPU6000)から角速度を取得して、計算した角度をTeraTermで確認したいと思います。情報が少なかったので、実装するのに苦労しました。ユーザーズマニュアルや本、他の人のブログ、MPU6000のデータシートを参考に実装を行いました。

以下の本を少しだけ参考にしました。ただ、基本的にはユーザーズマニュアルや他の人のブログを参考にしています。

環境

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

ジャイロの回路

作成したMPU6000の周辺回路図は図1です。

図1:MPU6000の周辺回路図
図1:MPU6000の周辺回路図

各ネットの RX631 への接続先は以下です。

  • MOSIA → PC6/MOSIA
  • RSPCKA → PC5/RSPCKA
  • CS2 → P16
  • MISOA → PC7/MISOA

MPU6000とはSPIとI2Cの通信方法でデータをやり取りできますが、作成した回路はSPIで通信する回路となっています。また、RX631の端子機能であるSSLAxを使わず、汎用入出力でCSを制御しています。

MPU6000の読み書き方法

MPU6000のデータシートを読んでも、読み書きの仕方がいまいちわからなくて苦労しました。そのため、読み書きの仕方を説明します。

1バイトのデータを読む

1バイトのデータを読むときは図2のようにデータをやり取りします。

図2:1バイトの読み方
図2:1バイトの読み方

CS2を”L”にして、最初に送信する1ビットを"1"とすることで、読み込み動作となります。読み込むレジスタは次に送信する7ビットのアドレス [A6]-[A0] で指定できます。アドレス送信後、 指定したアドレスのデータが MPU6000 から送信されますので、これを受信することで1バイト読むことができます。

アドレス送信後に送信するデータ [D7]-[D0] はダミーデータで、RSPCKAを動作させるためのデータです。RSPIの仕様上必要となります。ダミーデータの値はなんでもいいですが、私は 0x00 を送信しています。

1バイトのデータを書く

1バイトのデータを書くときは図3のようにデータを送信します。

図3:1バイトの書き方
図3:1バイトの書き方

CS2を”L”にして、最初に送信する1ビットを"0"とすることで、書き込み動作となります。書き込むレジスタは次に送信する7ビットのアドレス [A6]-[A0] で指定できます。アドレス送信後、 書き込むデータ [D7]-[D0] を送信することで、指定したアドレスのレジスタにその値が書き込まれます。

2バイトのデータを読む

角速度のデータは2バイトの符号付整数のため、2バイトを一気に読み込む必要があります。1バイトずつ読み込んだ場合、上位8ビットと下位8ビットが異なる時間の値となってしまいます。

2バイトのデータを読むときは図4のようにデータをやり取りします。

図4:2バイトの読み方
図4:2バイトの読み方

1バイトのデータを読み込んだあとに、再びダミーデータを送信することで、指定したアドレスの次のアドレスのデータがMPU6000から送信されます。

3バイト以上を読むときも、ダミーデータを何度も送信することでさらに次のアドレスのデータが送信されて、読むことができます。

注意:SPIを使用するデバイスがMPU6000だけの場合、CSをずっと”L”にしておけばいいと思っていましたが、上記のバースト転送の仕様のため、SPI送信後にCSを"H"にしないといけないみたいです。ずっと”L”の場合、時間を空けたとしても、前回受信した次のアドレスのデータがMPU6000から送信されてしまいます。CSを”H”から"L"にして送信した最初のデータがR/Wとアドレスとなるようです。これに気づかなくて、最初に読みだした1バイトだけ正しい現象が起きて、数時間悩みました。

プログラムフロー

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

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

クロックの初期化とCMT0、SCI1の初期化を行って、RSPIとMPU6000を初期化します。その後、メインルーチンに入り、1ms 経過するたびに角速度をMPU6000から取得して、足し合わせていきます。また、100ms経過するたびに足し合わせた値を角度(deg)に変換して、UART 送信します。

プログラム

MPU6000に関係するプログラム部分だけ示します。sci.csci.h を除いた全てのソースコードGitHubの「6_mpu6000」フォルダの中にあります。

メイン関数

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

#include "sci.h"
#include "spi.h"
#include "init_rx631.h"
#include "define_wakaba.h"

extern volatile unsigned char g_flag;

void main(void){
    int angle_Z=0;
    u16 cnt=0;
    int send_data;
    short rd_data;

    init_rx631();   // Overall Initialization

    while(1){
        if(g_flag){
            g_flag = 0;  // Clear flag

            // Read angular velocity
            rd_data = SPI_ReadMPU6000(MPU6000_GYRO_ZOUT_H);
            angle_Z += rd_data;

            cnt++;
            if(cnt==100){
                cnt=0;
                send_data = angle_Z / 16384;  // (angle_Z*2000) / (32768*1000)
                sci_printf("angle=%l\r\n", send_data);
            }
        }
    }
}

メイン関数では、RX631の初期化を行い、メインルーチンに入ります。CMT0の割り込み関数によって1ms 経過フラグ(g_flag)が立ったら、フラグを下げて、角速度を読み、いままでの値に足し合わせ、カウント(cnt)をインクリメントします。100msが経過(cnt==100)したら、カウントをクリアして、角度を計算して、その値をsci_printfでUART送信します。

角度(ANGLE)については、長方形近似による数値積分で以下のように求めています。

\displaystyle{
ANGLE=\frac{angle\_Z \times {\rm FS} }{ 32768 \times  1000 } 
}

angle_Z は読み取った値の総和、FS はフルスケールの値に対応する角速度です。MPU6000から読み取るデータは2バイトの符号付整数なので、フルスケールの値は32768となります。また、1ms ごとにサンプリングしているため、1000で割っています。

MPU6000の初期化で、{\rm FS}=2000\ {\rm dps} としているため、約分してソースコードの計算式になります。

RSPI0の初期化

初期化関数のソースコードが以下です。RSPI に関してはユーザーズマニュアルの p.1619「38. シリアルペリフェラルインタフェース(RSPI)」に記述があります。

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

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

/*** Function Declaration ***/
static void init_clock(void);
static void init_cmt0(void);
static void init_sci1(void);
static void init_rspi0(void);
static void init_mpu6000(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();

    // RSPI0 Initialization
    init_rspi0();

    // MPU6000 Initialization
    init_mpu6000();

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

~略~

/*---- RSPI0 Initialization ----*/
static void init_rspi0(void){

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

    RSPI0.SPBR = 5;                //  521kbps when BRDV=3

    RSPI0.SPSCR.BIT.SPSLN = 0;     //  Sequence length = 1
    RSPI0.SPDCR.BIT.SPFC  = 0;     //  Frame Num = 1
    RSPI0.SPDCR.BIT.SPLW  = 1;     //  Long Word Access

    /* RSPI Command Register0 */
    // CPHA = 1  : Data change at odd edges, data sample at even edges
    // CPOL = 1  : RSPCK at idle is High
    // BRDV = 3  : 8 divisions of the base bit rate
    // LSBF = 0  : MSB First
    // SPB  = 15 : Data length = 16bit
    RSPI0.SPCMD0.WORD = 0x0F0F;

    MPC.PWPR.BIT.B0WI     = 0;     // PFSWE bit write enable
    MPC.PWPR.BIT.PFSWE    = 1;     // PmnPFS Register write enable
    MPC.PC7PFS.BIT.PSEL   = 13;    // Set to MISOA
    MPC.PC6PFS.BIT.PSEL   = 13;    // Set to MOSIA
    MPC.PC5PFS.BIT.PSEL   = 13;    // Set to RSPCKA
    MPC.PWPR.BYTE         = 0x80;  // Reprotect
    PORTC.PMR.BIT.B7      = 1;     // set to Peripheral
    PORTC.PMR.BIT.B6      = 1;     // set to Peripheral
    PORTC.PMR.BIT.B5      = 1;     // set to Peripheral
    PORT1.PDR.BIT.B6      = 1;     // CS2: OUT
    PORT1.PODR.BIT.B6     = 1;     // CS2: HIGH

    RSPI0.SPCR.BIT.SPMS = 1;       //  Clock synchronous operation
    RSPI0.SPCR.BIT.MSTR = 1;       //  Master Mode
}

/*---- MPU6000 Initialization ----*/
static void init_mpu6000(void){

    // Sleep mode Release
    SPI_WriteMPU6000(MPU6000_PWR_MGMT_1, 0x00);

    // Disable I2C interface
    SPI_WriteMPU6000(MPU6000_USER_CTRL, 0x10);

    // The full scale range of the gyroscope = +-2000 dps
    SPI_WriteMPU6000(MPU6000_GYRO_CONFIG, 0x18);
}

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

その後、以下のRSPI0の設定を行います。

  • RSPI0.SPBR=5 で、ベースビットレートを4.16Mbpsに設定
  • RSPI0.SPSCR.BIT.SPSLN=0 で、シーケンス長を1に設定
  • RSPI0.SPDCR.BIT.SPFC=0 で、フレーム数を1に設定
  • RSPI0.SPDCR.BIT.SPLW=1 で、SPDR レジスタへのアクセス幅を32ビットに設定
  • RSPI0.SPCMD0.WORD = 0x0F0F で、送信の設定をいろいろする
  • RSPI0.SPCR.BIT.SPMS = 1 で、クロック同期式動作に設定
  • RSPI0.SPCR.BIT.MSTR = 1 で、マスタモードに設定

シーケンス長を1にすることで、SPCMD0で設定した送信の方法でしか、送信しないようにします。SPIで送信するデバイスが複数ある場合は、デバイスの数だけシーケンス長を設定するようです。

SPCMD0を設定することで、送信の仕方を以下のように変更します。

  • CPOLビットを”1”にして、アイドル時のRSPCKをHIGHにする
  • CPHAビットを”1”にして、CPOL=1 のときRSPCKの立ち上がりでサンプリング(図2のような波形にするために、ユーザーズマニュアルのp.1660「38.3.5.2 CPHA ビット= 1 の場合」の図 38.24 にあるように、CPOL=1、CPHA=1とします)
  • BRDVビットを3にして、ベースビットレートを8分周する(4.16Mbpsを8分周することで、512kbpsにします。MPU6000の絶対定格ではSCLKの周波数が1MHzなので、1MHz以下になるように512kbpsにしています。)
  • LSBFビットを”0”にして、MSBファーストで送信
  • SPBビットを15にして、16ビット送信する(送信するたびに変更しますが、初期設定は16ビットで送信するようにします。)

SPCMD0の設定が終わった後は、SSLは使わないので、クロック同期式動作にします。また、マスタモードに設定することで、RXマイコンから送信開始するようにします。

以上でRSPI0の設定は終わりです。

つづいて、MPU6000の初期化を行います。初期設定で以下のことをします。

  • PWR_MGMT_1(アドレス107)のSLEEPビットを"0"にして、スリープモードを解除
  • USER_CTRL(アドレス106)の I2C_IF_DIS ビットを"1"にして、I2Cを無効にしてSPIを有効にする(設定しなくてもSPI通信はできるので、設定する必要はないかもしれない)
  • GYRO_CONFIG(アドレス27)の FS_SELビットを3にして、フルスケールの値に対応する角速度を2000dpsにする

SPIの送受信

SPIで読み書きするソースコードとヘッダーファイルが以下です。ソースコードは主にユーザーズマニュアルのp.1676「(a) 送信処理フロー」とp.1677「(b) 受信処理フロー」を参考に書きました。

spi.c:SPI 関係のソースコード

#include "define_wakaba.h"
#include "spi.h"
#include "sci.h"

static u16 SPI_SendRecvMPU6000(u32 packet);

/* Write to MPU6000 register */
void SPI_WriteMPU6000(u8 address, u8 data){
    u16 packet;

    /* make packet */
    packet = address << 8;
    packet |= data;

    /* Number of data to send = 16bit */
    RSPI0.SPCMD0.BIT.SPB = LENGTH_16BIT;

    SPI_SendRecvMPU6000(packet);  // Write
}

/* Read MPU6000 register */
u16 SPI_ReadMPU6000(u8 address){
    u16 data;
    u32 packet;

    /* make packet */
    packet = 0x800000;
    packet |= address << 16;

    /* Number of data to send = 24bit */
    RSPI0.SPCMD0.BIT.SPB = LENGTH_24BIT;

    data = SPI_SendRecvMPU6000(packet);  // Read

    return data;
}

/* Sending/receiving data to/from MPU6000 */
static u16 SPI_SendRecvMPU6000(u32 packet){
    u16 data;

    CS_MPU6000 = ASSERT;   // Assert MPU6000 CS

    RSPI0.SPCR.BIT.SPTIE  = 1;  // Enable transmission IRQ
    RSPI0.SPCR.BIT.SPE    = 1;  // Enable RSPI

    /* Wait for the send buffer to be empty */
    while(IR(RSPI0, SPTI0)==0);
    IR(RSPI0, SPTI0)=0;         // Clear Flag

    RSPI0.SPDR.LONG = packet;   // Send data

    RSPI0.SPCR.BIT.SPTIE  = 0;  // Disable transmission IRQ
    RSPI0.SPCR2.BIT.SPIIE = 1;  // Enable idle IRQ
    RSPI0.SPCR.BIT.SPRIE  = 1;  // Enable receive IRQ

    /* Wait for RSPI to idle */
    while(IR(RSPI0, SPII0)==0);
    IR(RSPI0, SPII0)=0;         // Clear Flag

    /* Wait for the receive buffer to be written */
    while(IR(RSPI0, SPRI0)==0);
    IR(RSPI0, SPRI0)=0;         // Clear Flag

    data = RSPI0.SPDR.LONG & 0xFFFF;

    RSPI0.SPCR2.BIT.SPIIE = 0;  // Enable idle IRQ
    RSPI0.SPCR.BIT.SPRIE  = 0;  // Enable receive IRQ
    RSPI0.SPCR.BIT.SPE    = 0;  // Disable RSPI

    CS_MPU6000 = NEGATE;  // Negate MPU6000 CS

    return data;
}

spi.h:SPI 関係のヘッダーファイル

#ifndef SPI_H_
#define SPI_H_

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

#define MPU6000_GYRO_CONFIG  27
#define MPU6000_GYRO_ZOUT_H  71
#define MPU6000_GYRO_ZOUT_L  72
#define MPU6000_USER_CTRL   106
#define MPU6000_PWR_MGMT_1  107
#define MPU6000_WHO_AM_I    117

#define LENGTH_16BIT  15
#define LENGTH_24BIT   1

#define ASSERT 0
#define NEGATE 1

#define CS_MPU6000  PORT1.PODR.BIT.B6

void SPI_WriteMPU6000(u8 address, u8 data);
u16 SPI_ReadMPU6000(u8 address);

#endif /* SPI_H_ */

書き込み関数 SPI_WriteMPU6000 と読み込み関数 SPI_WriteMPU6000 はほとんど違いはなく、以下の手順でプログラムが実行されます。

  1. 送信するパケットの作成
  2. 送信するデータ数を設定
  3. SPIで送受信を行う

読み書きで異なる点は以下です。

  • 送信するパケットのMSBが、読みでは"1"、書きでは"0"
  • 読みではダミーデータを送信、書きでは書き込みデータを送信
  • 送信データ数が、読みでは24ビット、書きでは16ビット

SPIの送受信関数SPI_SendRecvMPU6000では以下の手順でプログラムが実行されます。

  1. MPU6000のCSをアサート("L"にする)
  2. 送信割り込み要求の許可とRSPI機能を有効化する
  3. 送信バッファが空になったら、フラグを下げる
  4. 送受信バッファSPDRにパケットを書き、SPI送信する
  5. 送信割り込み要求を禁止して、アイドル割り込み要求と受信割り込み要求を許可する
  6. アイドル状態になったら、フラグを下げる
  7. 受信バッファに書き込まれたら、フラグを下げる
  8. 送受信バッファSPDRから、受信データを取り出す
  9. 全ての割り込み要求を禁止して、RSPI機能を無効化する
  10. MPU6000のCSをネゲート("H"にする)

プログラムの実行

プログラムを実行して、基板の角度をだいたい-45 度から45 度に変えたところ、図6のようにデータを取得できました。

図6:取得した角度データ
図6:取得した角度データ

上手くできてそうです。

おわりに

今回でMPU6000のデータを取得できました。今回の記事は赤外線LEDと赤外線センサーについて書こうと思ったのですが、所望の値が得られなかったため記事が書けませんでした。ハードウェアの問題の可能性が高いので、もう一度基板を作りなおすかもしれません。次回はモータドライバー(DRV8836)の記事を書こうと思います。

参考文献

RXマイコンのソフト開発(5)ADCでバッテリーの電圧測定

RXマイコン(RX631)のソフト開発の5回目です。今回は、ADCでバッテリーの電圧を測定して、TeraTermで値を確認したいと思います。

図1:AD変換の模式図
図1:AD変換の模式図

今回の記事は、以下の資料を主に参考にしました。

環境

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

バッテリーチェックの回路

作成したバッテリーチェックの回路図は図2です。

図2:バッテリーチェックの回路
図2:バッテリーチェックの回路

SEN_BAT のポートは PE1/AN009 です。Lipoバッテリーの電圧を抵抗によって半分に分圧しています。その半分になった電圧をADCで測定します。

プログラムフロー

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

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

クロックの初期化とCMT0とSCI1の初期化を行って、ADCを初期化します。その後、メインルーチンに入り、スイッチの押し込みを検知したら バッテリーの電圧値 を UART 送信します。

プログラム

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

メイン関数

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

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

void main(void){
    unsigned short voltage;

    init_rx631();       // Overall Initialization

    while(1){
        if(g_sw_chg){   // Enter when SW is pressed
            g_sw_chg = 0;
            S12AD.ADCSR.BIT.ADST = 1;         // ADC start
            while(S12AD.ADCSR.BIT.ADST==1);   // wait for ADC completion
            voltage = S12AD.ADDR9*330*2/4095; // Convert to voltage
            sci_printf("voltage = %u.%u\n\r",voltage/100,voltage%100);
        }
    }
}

メイン関数では、RX631の初期化を行い、割り込み関数によってスイッチの押し込みフラグが立ったら、フラグを下げて、AD変換を行います。AD変換を開始したら、AD変換が完了するまで待ち、AD変換が完了したら、AD変換値から電圧値に変換します。その電圧値をsci_printfでUART送信します。

ADCの初期化

初期化関数のソースコードが以下です。ADC に関してはユーザーズマニュアルの p.1769「42. 12 ビット A/D コンバータ(S12ADa)」に記述があります。

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);
static void init_adc(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();

    // ADC Initialization
    init_adc();

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

~略~

static void init_adc(void){

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

    MPC.PWPR.BIT.B0WI     = 0;     // PFSWE bit write enable
    MPC.PWPR.BIT.PFSWE    = 1;     // PmnPFS Register write enable
    MPC.PE1PFS.BIT.ASEL   = 1;     // Used as an analog port
    MPC.PWPR.BYTE         = 0x80;  // Reprotect
    //PORTE.PMR.BIT.B1    = 1;     // set to Peripheral (2021/9/15 comment out)

    S12AD.ADCSR.BIT.CKS   = 3;     // PCLK/1
    S12AD.ADANS0.WORD     = 0x200; // conversion target:AN009
    S12AD.ADCSR.BIT.ADCS  = 0;     // single scan mode
}

ADCの初期化では、UARTの初期化と同様にモジュールストップ状態の解除と端子機能の変更を行います。端子機能の変更でUARTのときと少し違う点はアナログ端子として使用するので、PmnPFSレジスタのPSELではなく、ASELを変更している点です。

その後、以下のADCの設定を行います。

  • ADCSRレジスタのCKSビットを3にして、分周比を1とする。
  • ADANS0レジスタを0x200にして、AN009を変換対象とする。
  • ADCSRレジスタのADCSビットを0にして、シングルスキャンモードにする。

連続スキャンモードというのもありますが、連続でAD変換する必要はないので、シングルスキャンモードにします。

また、ソフトウェアトリガでAD変換を開始するので、ADCSRレジスタのEXTRGビットやTRGEビットは”0”、”1”どちらでも構いません。

補足:はじめ、 ADCSRレジスタのCKSビットを0(分周比:8)にしていたのですが、そうするとAD変換結果が必ず4095になってしまいました。CKSビットを3にすることでこの現象が治りました。原因は不明ですが、基板のほうに問題があるのかもしれません。

追記(2021/9/15):マニュアルp.764「22.4.3 アナログ機能を使う場合の注意事項」に「アナログ機能を使用するときは、ポートモードレジスタ(PMR)の当該ビットを “0”、ポート方向レジスタ(PDR)の当該ビットを “0” にし、当該端子を汎用入力ポートにしてから、Pmn 端子機能制御レジスタの端子機能選択ビット(PmnPFS.ASEL[1:0])を “1” にしてください。」とありますので、PMRレジスタの設定はコメントアウトしました

プログラムの実行

スイッチを押すたびに、図4のようにバッテリーの電圧値が表示されるようになりました。

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

おわりに

今回でAD変換ができました。次の記事では、赤外線LEDと赤外線センサーを使ってみたいと思います。

参考文献

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

© 2021 Setoti All rights reserved.