本記事は以下に移動しました。
RX651用のマウス基板の設計とRX631との違い
いままでRX631を載せたマウス基板を用いて、デバイスの動作確認をおこなっていましたが、AD変換が上手くいかなかったので、マウス基板をまた作りなおしました。
RX631-48ピンが手に入りづらくなっていたことに加えて、RAMが多い高性能のマイコンを使いたいと思ったので、今回はRX651-64ピン用のマウス基板を設計しました。
RX651用のマウス基板の回路設計
赤外線センサーのAD変換結果がおかしな値となっていたため、RX651のユーザーズマニュアル ハードウェア編 を参考にして回路を再設計をしました。
回路図の変更点
再設計した回路図は図1です。
今回の回路図で前回の回路図から変更した主な箇所は以下の3つです。
- マイコンをRX631からRX651に変更
- 1つのFETで1つの赤外線LEDをスイッチング
- マウス基板とAS5047P基板をつなげるコネクタを4x2から3x2に変更
冒頭で述べたようにRX631からRX651に変更しています。
加えて、いままで1つのFETで2つの赤外線LEDをスイッチングしていましたが、プログラムが書きにくかったので、1つのFETで1つの赤外線LEDをスイッチングするようにしました。
あとは、AS5047PのA端子とB端子はマイコンと接続しなくてもよいことがわかりましたので、コネクタを4x2から3x2に変更しています。
DRV8836やMPU6000も手に入りづらくなっているので、変更しようかなと思いましたが、使い方を再び調査しないといけないので辞めときました。
配線図の変更点
再設計した配線図は以下です。
配線図PDF版(縮尺:2.5倍)
今回の配線図で前回の配線図から変更した主な箇所は以下の3つです。
- マイコンのAVSSや赤外線センサにつながっているGNDはレギュレータ付近のコンデンサのGNDと一点接続する
- 赤外線センサーの出力信号とデジタル信号線を交差させない
- モータドライバーのGNDはレギュレータ付近のコンデンサのGNDと一点接続する
変更点1と2についてはRX651のマニュアルp.2548にある「53.6.12 ボード設計上の注意」に書いてあることを参考にしています。いままでは、プリント基板の2層ともベタGNDにしていましたが、1層だけベタGNDにして、GNDを1点接続できるようにしました。
また、モータドライバーからのノイズで問題が生じるというブログ記事をいくつか拝見しましたので、モータドライバーのGNDについてもレギュレータ付近のコンデンサのGNDと一点接続させました。
届いたマウス基板
Elecrowに発注して届いたマウス基板が図3です。
文字は少し乱れていましたが、フットプリントがとても綺麗にできていて良かったです。
ハンダ付けして組み立てたマウスが図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プリンタで作成したのですが、失敗したので報告します。
赤外線LEDを光らせて、センサーホルダーを付けたときのAD変換値をみてください。
障害物 | 左前 | 左横 | 右前 | 右横 |
---|---|---|---|---|
無 | 3607 | 3634 | 3587 | 3564 |
有 | 3689 | 3747 | 3661 | 3673 |
障害物がセンサーの前に無いときと有るときでAD変換値の差がほとんどありません。
おそらく、原因としてはセンサーホルダーによる散乱光だと考えられます。それにしても、散乱光が強すぎると思いますが....。
センサーホルダーを使う人で、散乱光の対策はどうしているのかと気になったところ以下の記事が見つかりました。
見つけた記事:うむ夫の歩み Nucleo-32boardを使ったクラシックマウスの開発その8 ~センサーホルダー~
どうやら、熱収縮チューブを付けてホルダーに挿入すれば良いみたいです。ただ、作成したホルダーの穴の大きさが熱収縮チューブの分を考慮していないので、作り直す必要があります。
試しにセンサーホルダーを外して、赤外線LEDに熱収縮チューブを付けた場合のAD変換値は以下です。
障害物 | 左前 | 左横 | 右前 | 右横 |
---|---|---|---|---|
無 | 350 | 343 | 278 | 574 |
有 | 1916 | 2078 | 2316 | 2951 |
AD変換の値に差があっていい感じです。
センサーホルダーを付けなくてもいいかなと少し思いましたが、基板にホルダーを差し込む穴もあるので、もう一度作成しなおそうと思います。
おわりに
今回、RX651用のマウス基板の設計とRX651とRX631の違いについて話しました。これで全てのデバイスの動作確認ができましたので、次回から本格的にマイクロマウスのプログラムを作成していきたいと思います。
参考文献
Pythonで正弦波モデルを実装
本記事は以下に移動しました。
RXマイコンのソフト開発(8)磁気式エンコーダーAS5047
- RXマイコンのソフト開発(1)クロック設定とLチカ
- RXマイコンのソフト開発(2)スイッチのチャタリング対策
- RXマイコンのソフト開発(3)スピーカーから音を出す
- RXマイコンのソフト開発(4)UART送信でHelloWorld
- RXマイコンのソフト開発(5)ADCでバッテリーの電圧測定
- RXマイコンのソフト開発(6)RSPIでMPU6000から角速度取得
- RXマイコンのソフト開発(7)モータドライバーDRV8836の動作
- RXマイコンのソフト開発(8)磁気式エンコーダーAS5047
RXマイコン(RX631)のソフト開発の8回目です。今回は、磁気式エンコーダーのAS5047を動作させたいと思います。AS5047によって、タイヤの回転角度を求めます。
環境
- パソコン: Windows10 64 bit
- ターゲットデバイス: R5F5631PDDFL(RX631 48ピン)
AS5047の周辺回路
作成したAS5047の周辺回路図は図1です。xxxとxxx_H、xxxとxxx_HRはコネクタでつながっています。
今回使用するネットの RX631 への接続先は以下です。今回は右側のエンコーダーしか使いません。
- CS1 → PB5
- RSPCKA → PC5/RSPCKA
- MOSIA → PC6/MOSIA
- MISOA → PC7/MISOA
はじめ、SPI通信で初期設定をして、R_ENC_AとR_ENC_BからA相とB相の矩形波を受け取ろうと思ったのですが、SPI通信だけでも角度情報を受け取れるようなので、R_ENC_AとR_ENC_Bは使いません。
補足:初期状態でA相とB相の矩形波が出力されるように設定されている(オシロスコープで出力を確認)ので、SPIの通信線がなくても角度情報は受け取れるそうです。このため、レジスタ設定をしない人は、SPIの通信線を省略してみてもいいかもしれません。
作成した機体の足回り
足回りの構造は図2のようになっています。
ギアホイールにシャフトを圧入して、カラーにシャフトを圧入して、カラーの中に磁石を入れることで、ギアホイールと磁石が同じように回転します。磁気式エンコーダーで磁石の角度がわかることで、ギアホイールの回転角度もわかります。
プログラムフロー
プログラムの処理の流れは図3です。
クロックの初期化とCMT0、SCI1の初期化を行って、RSPI0を初期化します。その後、メインルーチンに入り、1ms経過したら、磁石の角度を取得して、UARTで送信します。
プログラム
磁気式エンコーダーに関係するプログラム部分だけ示します。sci.c と sci.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送信します。
補足:角度データが入っているレジスタはANGLEとANGLECOMがありますが、今回はエラー補正した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のようになります。
コマンドフレーム(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を見たところ、回転角度は問題なく取得できてそうです。
おわりに
今回で磁気式エンコーダーのAS5047からタイヤの回転角度を取得できました。これまでで、ほとんどのデバイスを動作させることができましたが、赤外線センサーだけ上手く動作できなかったので、もう一度基板を作りなおそうかと思います。
参考文献
RXマイコンのソフト開発(7)モータドライバーDRV8836の動作
- RXマイコンのソフト開発(1)クロック設定とLチカ
- RXマイコンのソフト開発(2)スイッチのチャタリング対策
- RXマイコンのソフト開発(3)スピーカーから音を出す
- RXマイコンのソフト開発(4)UART送信でHelloWorld
- RXマイコンのソフト開発(5)ADCでバッテリーの電圧測定
- RXマイコンのソフト開発(6)RSPIでMPU6000から角速度取得
- RXマイコンのソフト開発(7)モータドライバーDRV8836の動作
- RXマイコンのソフト開発(8)磁気式エンコーダーAS5047
RXマイコン(RX631)のソフト開発の7回目です。今回は、モータドライバーのDRV8836を動作させたいと思います。制御はしないです。ただ動かすだけです。ギアが噛み合うかも確かめたいと思います。
環境
- パソコン: Windows10 64 bit
- ターゲットデバイス: R5F5631PDDFL(RX631 48ピン)
DRV8836の周辺回路
作成したDRV8836の周辺回路図は図1です。
各ネットの 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です。
クロックの初期化と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のようにモーターを回転させることができました(写真ではわかりにくいですが...)。
おわりに
今回でDRV8836を動作させることができました。次回は磁気式エンコーダーを動かしたいと思います。
参考文献
RXマイコンのソフト開発(6)RSPIでMPU6000から角速度取得
- RXマイコンのソフト開発(1)クロック設定とLチカ
- RXマイコンのソフト開発(2)スイッチのチャタリング対策
- RXマイコンのソフト開発(3)スピーカーから音を出す
- RXマイコンのソフト開発(4)UART送信でHelloWorld
- RXマイコンのソフト開発(5)ADCでバッテリーの電圧測定
- RXマイコンのソフト開発(6)RSPIでMPU6000から角速度取得
- RXマイコンのソフト開発(7)モータドライバーDRV8836の動作
- RXマイコンのソフト開発(8)磁気式エンコーダーAS5047
RXマイコン(RX631)のソフト開発の6回目です。今回は、RSPIを使ってジャイロ(MPU6000)から角速度を取得して、計算した角度をTeraTermで確認したいと思います。情報が少なかったので、実装するのに苦労しました。ユーザーズマニュアルや本、他の人のブログ、MPU6000のデータシートを参考に実装を行いました。
以下の本を少しだけ参考にしました。ただ、基本的にはユーザーズマニュアルや他の人のブログを参考にしています。
環境
- パソコン: Windows10 64 bit
- ターゲットデバイス: R5F5631PDDFL(RX631 48ピン)
ジャイロの回路
作成したMPU6000の周辺回路図は図1です。
各ネットの RX631 への接続先は以下です。
- MOSIA → PC6/MOSIA
- RSPCKA → PC5/RSPCKA
- CS2 → P16
- MISOA → PC7/MISOA
MPU6000とはSPIとI2Cの通信方法でデータをやり取りできますが、作成した回路はSPIで通信する回路となっています。また、RX631の端子機能であるSSLAxを使わず、汎用入出力でCSを制御しています。
MPU6000の読み書き方法
MPU6000のデータシートを読んでも、読み書きの仕方がいまいちわからなくて苦労しました。そのため、読み書きの仕方を説明します。
1バイトのデータを読む
1バイトのデータを読むときは図2のようにデータをやり取りします。
CS2を”L”にして、最初に送信する1ビットを"1"とすることで、読み込み動作となります。読み込むレジスタは次に送信する7ビットのアドレス [A6]-[A0] で指定できます。アドレス送信後、 指定したアドレスのデータが MPU6000 から送信されますので、これを受信することで1バイト読むことができます。
アドレス送信後に送信するデータ [D7]-[D0] はダミーデータで、RSPCKAを動作させるためのデータです。RSPIの仕様上必要となります。ダミーデータの値はなんでもいいですが、私は 0x00 を送信しています。
1バイトのデータを書く
1バイトのデータを書くときは図3のようにデータを送信します。
CS2を”L”にして、最初に送信する1ビットを"0"とすることで、書き込み動作となります。書き込むレジスタは次に送信する7ビットのアドレス [A6]-[A0] で指定できます。アドレス送信後、 書き込むデータ [D7]-[D0] を送信することで、指定したアドレスのレジスタにその値が書き込まれます。
2バイトのデータを読む
角速度のデータは2バイトの符号付整数のため、2バイトを一気に読み込む必要があります。1バイトずつ読み込んだ場合、上位8ビットと下位8ビットが異なる時間の値となってしまいます。
2バイトのデータを読むときは図4のようにデータをやり取りします。
1バイトのデータを読み込んだあとに、再びダミーデータを送信することで、指定したアドレスの次のアドレスのデータがMPU6000から送信されます。
3バイト以上を読むときも、ダミーデータを何度も送信することでさらに次のアドレスのデータが送信されて、読むことができます。
注意:SPIを使用するデバイスがMPU6000だけの場合、CSをずっと”L”にしておけばいいと思っていましたが、上記のバースト転送の仕様のため、SPI送信後にCSを"H"にしないといけないみたいです。ずっと”L”の場合、時間を空けたとしても、前回受信した次のアドレスのデータがMPU6000から送信されてしまいます。CSを”H”から"L"にして送信した最初のデータがR/Wとアドレスとなるようです。これに気づかなくて、最初に読みだした1バイトだけ正しい現象が起きて、数時間悩みました。
プログラムフロー
プログラムの処理の流れは図5です。
クロックの初期化とCMT0、SCI1の初期化を行って、RSPIとMPU6000を初期化します。その後、メインルーチンに入り、1ms 経過するたびに角速度をMPU6000から取得して、足し合わせていきます。また、100ms経過するたびに足し合わせた値を角度(deg)に変換して、UART 送信します。
プログラム
MPU6000に関係するプログラム部分だけ示します。sci.c と sci.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)については、長方形近似による数値積分で以下のように求めています。
angle_Z は読み取った値の総和、FS はフルスケールの値に対応する角速度です。MPU6000から読み取るデータは2バイトの符号付整数なので、フルスケールの値は32768となります。また、1ms ごとにサンプリングしているため、1000で割っています。
MPU6000の初期化で、 としているため、約分してソースコードの計算式になります。
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 はほとんど違いはなく、以下の手順でプログラムが実行されます。
- 送信するパケットの作成
- 送信するデータ数を設定
- SPIで送受信を行う
読み書きで異なる点は以下です。
- 送信するパケットのMSBが、読みでは"1"、書きでは"0"
- 読みではダミーデータを送信、書きでは書き込みデータを送信
- 送信データ数が、読みでは24ビット、書きでは16ビット
SPIの送受信関数SPI_SendRecvMPU6000では以下の手順でプログラムが実行されます。
- MPU6000のCSをアサート("L"にする)
- 送信割り込み要求の許可とRSPI機能を有効化する
- 送信バッファが空になったら、フラグを下げる
- 送受信バッファSPDRにパケットを書き、SPI送信する
- 送信割り込み要求を禁止して、アイドル割り込み要求と受信割り込み要求を許可する
- アイドル状態になったら、フラグを下げる
- 受信バッファに書き込まれたら、フラグを下げる
- 送受信バッファSPDRから、受信データを取り出す
- 全ての割り込み要求を禁止して、RSPI機能を無効化する
- MPU6000のCSをネゲート("H"にする)
プログラムの実行
プログラムを実行して、基板の角度をだいたい 度から 度に変えたところ、図6のようにデータを取得できました。
上手くできてそうです。
おわりに
今回でMPU6000のデータを取得できました。今回の記事は赤外線LEDと赤外線センサーについて書こうと思ったのですが、所望の値が得られなかったため記事が書けませんでした。ハードウェアの問題の可能性が高いので、もう一度基板を作りなおすかもしれません。次回はモータドライバー(DRV8836)の記事を書こうと思います。
参考文献
RXマイコンのソフト開発(5)ADCでバッテリーの電圧測定
- RXマイコンのソフト開発(1)クロック設定とLチカ
- RXマイコンのソフト開発(2)スイッチのチャタリング対策
- RXマイコンのソフト開発(3)スピーカーから音を出す
- RXマイコンのソフト開発(4)UART送信でHelloWorld
- RXマイコンのソフト開発(5)ADCでバッテリーの電圧測定
- RXマイコンのソフト開発(6)RSPIでMPU6000から角速度取得
- RXマイコンのソフト開発(7)モータドライバーDRV8836の動作
- RXマイコンのソフト開発(8)磁気式エンコーダーAS5047
RXマイコン(RX631)のソフト開発の5回目です。今回は、ADCでバッテリーの電圧を測定して、TeraTermで値を確認したいと思います。
今回の記事は、以下の資料を主に参考にしました。
環境
- パソコン: Windows10 64 bit
- ターゲットデバイス: R5F5631PDDFL(RX631 48ピン)
バッテリーチェックの回路
作成したバッテリーチェックの回路図は図2です。
SEN_BAT のポートは PE1/AN009 です。Lipoバッテリーの電圧を抵抗によって半分に分圧しています。その半分になった電圧をADCで測定します。
プログラムフロー
プログラムの処理の流れは図3です。
クロックの初期化とCMT0とSCI1の初期化を行って、ADCを初期化します。その後、メインルーチンに入り、スイッチの押し込みを検知したら バッテリーの電圧値 を UART 送信します。
プログラム
重要なプログラム部分だけ示します。sci.c と sci.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のようにバッテリーの電圧値が表示されるようになりました。
おわりに
今回でAD変換ができました。次の記事では、赤外線LEDと赤外線センサーを使ってみたいと思います。