モノ創りで国造りを

ハード/ソフト問わず知見をまとめてます

VerilogでUART送信

前置き

先日UARTの受信を作成した。今回は送信。

方針

START信号を検出する。 外部データをBuffに入力。
スタートビットを出力。 クロックをカウントし、ボーレートのタイミングで信号を1bitずつ送信する。
信号送信回数をカウントする。 9回目のカウントで、出力を終えて初期化。

ソースコード

Verilog-HDL

module uart_tx(
    input[7:0]      UARTDATA,
    input             CLK,
    input             RST,
    input             START,
    output reg      UARTTX 
);
    reg[7:0] uartTxBuff = 8'b0;
    reg[8:0] clkCnt     = 9'b0;
    reg[3:0] dataCnt    = 4'b0;
    reg       sendEn    = 0;
    
parameter UARTTIMING = 9'd434;//CLK/Baudrate  ex)5oMHz/115200
    
always@(posedge CLK or negedge RST)begin
    if(!RST)begin
        clkCnt  <= 9'b0;
        dataCnt     <= 4'b0;
    end
    else if(sendEn)begin
        if(clkCnt != UARTTIMING)begin
            clkCnt <= clkCnt + 9'b1;
        end
        else begin//clkCnt == UARTTIMING
            clkCnt              <= 9'b0;
            dataCnt             <= dataCnt + 4'b1;
            UARTTX              <= uartTxBuff[7];
            uartTxBuff[7:0]     <= {uartTxBuff[6:0], 1'b1};//"1" means stop bit

            if(dataCnt == 4'b1001)begin
                sendEn  <= 0;
                clkCnt  <= 9'b0;
                dataCnt <= 4'b0;
            end
        end
    end
    else if(START)begin
        sendEn <= 1;
        uartTxBuff[7:0] <= UARTDATA[7:0];//Copy Data
        UARTTX <= 0;//start bit
    end

end
endmodule

テストベンチ

`timescale 1 ns/ 100 ps
module uart_tx_vlg_tst();
// constants                                           
// general purpose registers
reg eachvec;
// test vector input registers
reg CLK;
reg RST;
reg START;
reg [7:0] UARTDATA;
// wires                                               
wire UARTTX;

// assign statements (if any)                          
uart_tx i1 (
// port map - connection between master ports and signals/registers   
    .CLK(CLK),
    .RST(RST),
    .START(START),
    .UARTDATA(UARTDATA),
    .UARTTX(UARTTX)
);

always #10 CLK = ~CLK;

initial          
begin                                                  
CLK = 0;
RST = 1;
START = 0;
UARTDATA = 8'b0101_1100;

#200 RST = 0;
#200 RST = 1;
#200 START = 1;
#200 START = 0;
#100 UARTDATA = 8'b1100_1001;
#90000 START = 1;
#500 START = 0;
$display("Running testbench");                       
end                                                    

always                                                 
begin                                                                          
@eachvec;                                              
end                                                    
endmodule

気づき

verilog-HDLのソースを変更すると、Modelsimを再度立ち上げ直さないと反映されない。
なにか方法あるのかも?

VerilogでUART受信

前置き

Verilogと開発ツールの使い方の勉強のため、Verilogであれこれ作成・シミュレーションしようと思う。

UARTの受信

通信仕様は

  • データ8bit
  • Parityなし
  • Stop1bit

    今までこれ以外のUART通信をみた事がないので、拡張性も持たせない。 ボーレートはparameterで可変とする。 今回は115200bpsで作成。

大方針

スタートビットの検出方法

一般的なスタートビットの検出方法として、 UARTのボーレート用の逓倍のクロックを生成し、数回連続でLだったらスタートビットとみなす方法と、
UARTデータを基板の元クロックでモニターし、数回連続でLだったらスタートビットとみなす方法がある。

今回は後者で検出する。後者のメリットは。ボーレートが多少ずれても許容幅が大きいこと。

手順

  1. スタートビットを立下りのエッジで検出する
  2. スタートビットを検出したら、クロックのカウントを開始する。
  3. ボーレートの半周期だけ待つ。
  4. ボーレートの1周期毎にデータを取得する。
  5. 上記を9回(データ8bit + stop bit)繰り返す。
  6. 9個目のデータ(ストップビット)がHの場合にバッファにデータを送る。
  7. レジスタを初期化する。

ソースコード

Verilog-HDL

module uart_rx(
    input wire CLK,
    input wire RST,
    input wire UARTRX,
    output reg[7:0] UARTRXBUFF
);

parameter UARTTIMING = 9'd434;//CLK/Baudrate    ex)50MHz/115200
parameter WAITTIMING = 9'd217;//CLK/Baudrate/2  ex)50MHz/115200/2

reg[8:0] clkCnt = 9'b0;
reg[8:0] rxData = 9'b0;//8bit + stop bit
reg[3:0] dataCnt = 4'b0;
reg[3:0] startEdge = 4'b1111;

parameter WAITSTATE = 2'b01, RDSTATE = 2'b10, IDLESTATE = 2'b00;
reg[1:0] state = 2'b00;


//UART CLK 
always@(posedge CLK or negedge RST)begin
    if(!RST)begin
        clkCnt          <= 9'b0;
        state           <= IDLESTATE;
        rxData          <= 9'b0;
        dataCnt             <= 4'b0;
        startEdge       <= 4'b1111;
    end
    else begin
        if(state == IDLESTATE)begin
            startEdge[3:0] <= {startEdge[2:0], UARTRX};
            if(startEdge == 4'b1000)begin
                state <= WAITSTATE;
            end
        end
        else if(state == WAITSTATE)begin
            if(clkCnt != WAITTIMING)begin
                clkCnt <= clkCnt + 9'b1;
            end
            else begin//clkCounter == WAITTIMING
                clkCnt  <= 9'b0;
                state   <= RDSTATE;
            end
        end
        else if(state == RDSTATE)begin
            if(clkCnt != UARTTIMING)begin
                clkCnt      <= clkCnt + 9'b1;
            end
            else begin//clkCounter == UARTTIMING
                clkCnt      <= 9'b0;
                rxData[8:0] <= {rxData[7:0], UARTRX};
                dataCnt <= dataCnt + 4'b1;
            end
            
            if(dataCnt == 4'b1001)begin
                
                if(rxData[0])begin
                    UARTRXBUFF[7:0] <= rxData[8:1];
                end
                
                clkCnt          <= 9'b0;
                state           <= IDLESTATE;
                rxData          <= 9'b0;
                dataCnt             <= 4'b0;
                startEdge       <= 4'b1111; 
            end
        end
        else begin//state == OTHER
            clkCnt          <= 9'b0;
            state           <= IDLESTATE;
            rxData          <= 9'b0;
            dataCnt             <= 4'b0;
            startEdge       <= 4'b1111;
        end
    end
end
endmodule

テストベンチ

`timescale 1 ns/ 100 ps
module uart_rx_vlg_tst();
// constants                                           
// general purpose registers
reg eachvec;
// test vector input registers
reg CLK;
reg RST;
reg UARTRX;
// wires                                               
wire [7:0]  UARTRXBUFF;

// assign statements (if any)                          
uart_rx i1 (
// port map - connection between master ports and signals/registers   
    .CLK(CLK),
    .RST(RST),
    .UARTRX(UARTRX),
    .UARTRXBUFF(UARTRXBUFF)
);

always #10 CLK = ~CLK;

initial                                                
begin                                                  
     
CLK = 0;
RST = 1;
UARTRX = 1;

#100 RST = 0;
#100 RST = 1;
#1000 UARTRX = 0;//start
#8680 UARTRX = 1;//1bit
#8680 UARTRX = 0;//2bit
#8680 UARTRX = 1;//3bit
#8680 UARTRX = 1;//4bit
#8680 UARTRX = 0;//5bit
#8680 UARTRX = 0;//6bit
#8680 UARTRX = 1;//7bit
#8680 UARTRX = 0;//8bit
#8680 UARTRX = 1;//stop

#20000 UARTRX = 0;//start
#8680 UARTRX = 1;//1bit
#8680 UARTRX = 1;//2bit
#8680 UARTRX = 1;//3bit
#8680 UARTRX = 0;//4bit
#8680 UARTRX = 0;//5bit
#8680 UARTRX = 0;//6bit
#8680 UARTRX = 1;//7bit
#8680 UARTRX = 0;//8bit
#8680 UARTRX = 1;//stop

// --> end                                             
$display("Running testbench");                       
end 

always                                                  
begin                                                                                                                     
@eachvec;                                              
end                                                    
endmodule

気づき

4bitのレジスタに3bit と1bitのレジスタを連結させ代入する場合、わざわざ4bitであることを明記しないとデータが正しく代入できなかった。
手元にある書籍には明確な記載はないので、別に原因があったのかも?

//正しい
startEdge[3:0] <= {startEdge[2:0], UARTRX};

//間違い
startEdge <= {startEdge[2:0], UARTRX};

テストベンチで、不要と思っていた以下の記述を削除すると、波形が表示されなかった。

always                                                  
begin                                                                                                                     
    @eachvec;                                              
end  

身についたこと

  • parameterの使い方
  • テストベンチの書き方
  • シミュレーションの内部信号の表示方法

FPGAで画像処理

DE10-Nanoボードで画像処理

IntelFPGAボードとイメージセンサ2つで、深度カメラを構築しようと思います。
大まかな手順は以下を想定。

No To Do How
1 イメージセンサとFPGA間の制御信号のIFを作成 I2C, SCCB
2 FPGAとPC間の通信用IFを作成 UART
3 イメージセンサの制御ソフトを作成 ?
4 イメージセンサとFPGA間の画像データ信号のIFを作成 ?
5 FPGAとPC or Display間の画像データ信号用IFを作成 HDMI, VGA, USB
6 イメージセンサ2つのデータを処理するモジュールを作成 ?

部品の選定

FPGAボードはDE10-Nano-SoCを使用。理由は、現在手元にあるから。
MAX10の評価ボードもあるので、ひょっとしたらMAX10にするかも。
今回はverilogの勉強を兼ねているので、ARMもNIOSⅡも使わないで行けるところまで行く予定。
イメージセンサは何が適切か不明。なのでとりあえず秋月で安く売っていたOV7675http://akizukidenshi.com/catalog/g/gM-13201/を使用予定。

SCCB(I2C)IFの作成(作成中)

SCCBはオムニビジョン社のイメージセンサの制御によく用いられるIF。基本はI2Cのはず。
I2Cは昔ながらのIFなのでFPGAでも簡単に記述できるでしょう
という考えはちょっと甘く、I2Cはデバイスによって微妙に動作が異なる。
各デバイスに応じてverilogの記述を変える必要がある。

Intel提供のI2CのIPはコードが膨大。
QsysでI2C搭載してHDL生成したら、verilogファイルがわんさか出てきた・・・
f:id:yuji2yuji:20190809143109p:plain

UART IFの作成(作成中)

I2Cとは打って変わって、UARTは楽。
QsysでUARTのIP搭載してHDL生成しても、ソースコード1つしか出てこない。
920行もあるけど、それはハードウェア言語の宿命。

イメージセンサの制御ソフトを作成(作成中)

FPGAとPCが通信できたので、PCからFPGAのI2C IFを制御するためのソフトを作成。
主要なレジスタの設定だけできるようにする。

FPGA内部でも、PCから送られるUARTのデータとI2Cのデータを連携させる必要がある。
C言語なら簡単なのにと思いながらもverilogでせっせと作成。

イメージセンサとFPGA間の画像データ信号のIFを作成(作成中)

イメージセンサから送信される画像データは、D0-D8のデータをVSYC、HSYNCによって適切なレジスタに格納する。
難しいかと思ったが、他のIFより簡単かも。

FPGAとPC or Display間の画像データ信号用IFを作成(作成中)

HDMIVGA、USBと方法は色々あれど、どのIFを使うにしても方法が謎。
ちょこちょこ勉強するしかない。

イメージセンサ2つのデータを処理するモジュールを作成(作成中)

トラ技かInterfaceにそれっぽい記事があったので、なんとかなると楽観視している。

Intel FPGAのSimulationの手順

Intel FPGAのSimulation

FPGAの設計は時間がかかる。
特にデバッグに時間がかかるので、コードの誤りは早い段階で検知しておきたい。
そのためにはSimulationを使いましょう。
ここではIntel FPGAのModelsim-Alteraの使い方を簡単にまとめる。

流れ

  1. プロジェクトの作成
  2. verilog fileの作成
  3. テストベンチのテンプレートの作成
  4. テストベンチの作成
  5. Modelsimの起動とSimulationの実行

プロジェクトの作成

Quartus PrimeのFile > New Project Wizardをクリック
適当に進めてProjectを作成する
この画面でModelsim-alteraを選択する f:id:yuji2yuji:20190809105131p:plain

verilog fileの作成

  1. Quartus PrimeのFile > Newをクリック
  2. Design Files > Verilog HDL Fileをクリック
    f:id:yuji2yuji:20190809104738p:plain

  3. Verilogで書く。

  4. Start Analysys & Synthesisをクリックし、エラーが出ないことを確認する。
    f:id:yuji2yuji:20190809104944p:plain

テストベンチのテンプレート作成

  1. Quartus PrimeのProcessing > Start > Start Test Bench Template Writerをクリックする。
    f:id:yuji2yuji:20190809105600p:plain

  2. プロジェクトフォルダ/simulation/modelsimフォルダ内に、"モジュール名".vtのファイルが生成される。
    これがテストベンチのテンプレート。
    入出力と内部信号が記載されている。便利。

テストベンチの作成

  1. 生成されたテストベンチテンプレートファイルを編集する。
    ご丁寧にinitialとalwaysまで記載されているので、そこにinput信号の記載。

  2. プロジェクトを右クリックしてSettinsを選択。
    f:id:yuji2yuji:20190809111218p:plain

  3. EDA Tool SettingsのSimulationを選択。
    f:id:yuji2yuji:20190809111121p:plain

  4. NativeLink settingsのCompile test benchをチェックし。Test Benchesをクリック。
    f:id:yuji2yuji:20190809111102p:plain

  5. Newをクリック。
    f:id:yuji2yuji:20190809111615p:plain

  6. テストベンチ名を適当に入力し、Test bench and simulation filesのFile nameに作成したテストベンチを選択して入力してOKをクリック。
    f:id:yuji2yuji:20190809111025p:plain

  7. 再びプロジェクトを右クリックしてSettingsを選択し、以下の画面のFilesをクリック。File名に先ほど作成したテストベンチを選択して追加する。
    f:id:yuji2yuji:20190809112048p:plain

Modelsimの起動とSimulationの実行

  1. ModelSimのPathを設定するため、Quartsu Primeの Tools > Optionをクリック。
    f:id:yuji2yuji:20190809115057p:plain

  2. GeneralのEDA Tool OptionsのMdelSim-AlteraにPathを選択して記入する。
    自分の場合は"C:\intelFPGA_lite\17.1\modelsim_ase\win32aloem\" f:id:yuji2yuji:20190809114958p:plain

  3. Modelsim-alteraを起動するため、Quartus Primeの Tools > Run Simulation Tool > RTL Simulationをクリック。 f:id:yuji2yuji:20190809111855p:plain

  4. workの中にテストベンチがある事を確認する。
    f:id:yuji2yuji:20190809113615p:plain

  5. 記述に誤りがありコンパイルができず表示されない。
    その場合はテストベンチを修正し、Compile > Complieをクリックし、テストベンチをコンパイルする。
    f:id:yuji2yuji:20190809113532p:plain

  6. Simulationを実行するため作成したテストベンチのファイル名をダブルクリックする。
    Waveタブをクリックし波形を表示する。
    f:id:yuji2yuji:20190809114003p:plain

  7. 閲覧したいネットをドラッグしWaveにドロップする(まだ波形は表示されない)。
    f:id:yuji2yuji:20190809114132p:plain

  8. 閲覧したい波形の最大時間を設定し、Runボタンをクリックする。
    f:id:yuji2yuji:20190809114449p:plain

  9. 波形が表示される。
    f:id:yuji2yuji:20190809114514p:plain

まとめ

手順が多いので地味に大変。基本は以下の3つ。

  1. テストベンチファイルを記述
  2. テストベンチの作成
  3. プロジェクトにテストベンチファイルを追加

2と3のいずれかをうっかり忘れてしまいがちなので
上手くいかない場合は、2か3をやり忘れていないか要確認。

I2C通信で温度を測定する

デジタル測温IC

温度を測定するICの種類は大きく2つ。
アナログ出力とデジタル出力。

アナログ出力タイプの測温IC

アナログ出力タイプのICは、温度に応じた電圧の信号を出力する。
例えばアナログデバイセズのTMP3xシリーズだと
f:id:yuji2yuji:20190807150541p:plain
ICに電源供給するだけで信号が出力されるので、ICの使い方自体は極めて簡単。
しかし、アナログ信号なので、システムに用いる場合はADCで受けてデジタル変換する必要がある。

デジタル出力タイプの測温IC

デジタル出力タイプのICは温度に応じたデジタル信号を出力する。
IFとしてI2C、SPIが一般的。
今回はI2CのMCP9808を使用。
MCP9808のデジタル信号出力は以下。
f:id:yuji2yuji:20190807150800p:plain
データとして2バイト出力される。
デジタル出力タイプのICは、電源供給するだけでデジタル信号は出力されない。
デジタル出力タイプのICを使用するには、
IFのプロトコル(今回はI2C)とICそのものについて
多少の理解が必要。

MCP9808の使い方

I2Cはマスターとスレーブがある。
スレーブは複数ある。
スレーブはそれぞれが異なるアドレスを有する。
マスターが全スレーブに対してアドレス付きのデータ送信し、
指定されたアドレスのスレーブがデータを返送することで
データの送受信が行われる。

MCP9808は常にSlaveとして扱われる。
MCP9808は3bitのSlaveアドレス用端子がある。
Slaveアドレスを8つから選択できる。
f:id:yuji2yuji:20190807151424p:plain

マスターがMCP9808へアドレスを書き込む場合は、末尾を0としたデータを送信する。
例えば、アドレスを指定する場合は、以下のデータを送信する。

A6 A5 A4 A3 A2 A1 A0 R/W
0 0 1 1 x x x 0

MCP9808は温度データを出力するだけでなく
アラート信号を出力する温度の閾値や、
出力する温度の分解能なども読み書きできる。
f:id:yuji2yuji:20190807152441p:plain

MCP9808との通信

MCP9808から温度データを取得する場合の通信について述べる。 MCP9808のアドレスは0x18とする(A2:0、A1:0、A0:0)
全体の流れは

  1. マスターがスレーブにアドレスを送信する
  2. マスターがスレーブに読み出したいデータを指定する
  3. スレーブがマスターにデータを返送する
手順

1.マスターからスレーブに0x18の末尾に0を付加したデータを送信する。

A6 A5 A4 A3 A2 A1 A0 R/W
0 0 1 1 0 0 0 0

2.温度情報を読み出すため、マスターからスレーブへ0x05を送信する。
末尾に0を付加する必要はない。

3.スレーブからデータが2バイト返送される。

SoC FPGAでLチカ --DE10-Nanoを例に--

SoC FPGAとは

SoCFPGAとは、FPGAのチップにCPUの類のものが搭載されているもの。

やったこと

今回行ったのは以下

  1. FPGAのハードウェアロジックでLEDを点滅
  2. LinuxFPGAボードのLEDを点滅

今回使用したボード

DE10-Nano(IntelのCyclone Vが実装されたもの)
Digikeyで15,000円くらい。カバーがついてて心なしか安心。
書き込みが専用ツールでなく、USBケーブルで出来るのもうれしい。 f:id:yuji2yuji:20190730161014p:plain

ハードウェアロジックでLEDを点滅

Quartus Primeを立ち上げて新規プロジェクトを作成する。
ボードは選択しないで、FPGA"5CSEBA6U23I7DK"を選択。

Verilogコードの作成

Verilogのコードは以下。
クロックが50MHzなので、LEDの点滅が目で追えないけど、
スイッチ押せば止まるので気にしない。

module LED_TEST(
    input I_CLK_50MHZ,
    input I_SW1,
    output reg O_LED1,
    output reg O_LED2
    );

    reg [15:0] CLKCNT;
    reg [15:0] HALFCNT;
        
    initial begin
        O_LED1 = 1'b0;
        O_LED2 = 1'b1;
        CLKCNT = 16'b0000_0000_0000_0000;
        HALFCNT = 16'b1000_0000_0000_0000;
    end
    
    always@( posedge I_CLK_50MHZ ) begin
        CLKCNT = (I_SW1)? CLKCNT + 1:CLKCNT;
        O_LED1 = (CLKCNT < HALFCNT)? 1:0; 
        O_LED2 = ~O_LED1;
    end
endmodule

ピンアサインの設定

次にVerilogコードのInput、OutputとFPGAのピンを紐づける。
DE10-NanoのUser Manualに記載がある。
とりあえず必要な箇所だけピックアップして以下に示す。
f:id:yuji2yuji:20190730161821p:plain
f:id:yuji2yuji:20190730161908p:plain
f:id:yuji2yuji:20190730161928p:plain

Quattus PrimeのAssignments > Pin Plannerをクリックし、VelirogのInput、outputと各ピンを接続する。
今回は以下のような感じ。
f:id:yuji2yuji:20190730162313p:plain Node NameはVerilogに記載した名称を入力し、LocationにFPGAのピンアサインを入力する。
I/O Standardの箇所で電圧レベルや種類(シングル or 差動等)を選択する。
Directionはコンパイル時に自動で設定される。

コンパイル

Quartus PrimeのProcessing > Start Compilationをクリックし数分待てば書き込みデータが完成。

書き込み

Quartus PrimeのTools > Programmerをクリックすると、Programmerが起動する。
Auto Detectをクリックすると、基板上のメモリとFPGAが表示される。
FPGA上で右クリックし、edit > change fileを選択。
先ほど作成したコンパイルデータを選択する(projectディレクトリ直下のoutput_files内にあるはず)。
Program/Configureにチェックを入れて、Startをクリック。
無事書き込まれると、右上に100%(Successfuss)が表示される。

動作確認

LED0とLED1が点灯している(実際には高速で点滅している)。 Key0スイッチを押すと、一方のみが点灯する。

以上で、ハードウェアロジックでLEDを操作する説明終了。

LinuxからLEDを操作

次はボード上のLinuxからLEDを操作する。

準備

準備として、以下のDE10-Nanoの演習を一通りこなす。
service.macnica.co.jp

上記の5.演習 3は飛ばしてもよい。
ベアメタルアプリケーション開発はライセンス費用が掛かるので、
よほどのプロでない限りは使用しないはず。

Linuxアプリケーションの開発方法

6.演習4でLinuxアプリケーションの演習があるが、
Hello worldを表示できれば、
開発環境が構築されたことを意味する。
ただしHello Worldだけでは物足りないので、
LEDを点灯させてみる。
演習を完了している場合、LED PIOのベースアドレスは0x0001_0040となっている。
実際にはブリッジのベースアドレスが0xFF20_0000なので、
0xFF21_0040である(演習のpdfに記載されている)。
0xFF21_0040の各bitが各LEDの値に対応している。

レジスタの値を変えるにはmmapを使用する。

#include        <stdio.h>
#include        <stdlib.h>
#include        <fcntl.h>
#include        <string.h>
#include        <time.h>
#include        <sys/time.h>
#include        <poll.h>
#include        <sys/types.h>
#include        <sys/mman.h>
#include       <sys/stat.h>
#include       <unistd.h>
#include       <stdint.h>
#include       <errno.h>

#define    BASE_BRIGDE 0xff200000
#define    BASE_LEDPIO 0xff210040

#define DEVNAME "/dev/mem"

int main(int argc, char** argv) {
    int fd;
    uint32_t from;
    uint32_t num;
    uint32_t port;
    volatile uint8_t *iomap;
    printf("this is IOmemory test program\n");

    fd = open(DEVNAME, (O_RDWR|O_SYNC));
    if(fd <= 0){
        perror(DEVNAME);
        exit(1);
    }

    from = BASE_BRIGDE;
    num = 0x20000;
    iomap = mmap(0, num, PROT_READ|PROT_WRITE, MAP_SHARED, fd, from);
    if (iomap == MAP_FAILED) {
        printf("Error : mmap failed. ERRO:%d\n", errno);
        exit(1);
    }

        //LEDを1つだけ点灯させる。プログラム。
    port = BASE_LEDPIO;
        *(uint32_t*)(iomap + port - from) = ~0x01;
    

        munmap((void*)iomap, num);

    close(fd);
    return 0;
}

LEDを右から左、左から右へと順番に消灯したい場合は以下のようなプログラムになる。

        port = BASE_LEDPIO;
    uint8_t ledMask = 0x01;
    uint8_t ledDir = 0;
    int loopCount = 0;
    while(loopCount < 60){
        *(uint32_t*)(iomap + port - from) = ~ledMask;
        usleep(100*1000);
        if(ledDir == 0){
            ledMask <<= 1;
            if(ledMask == (0x01 << 6)){
                ledDir = 1;
            }
        }else{
            ledMask >>= 1;
            if(ledMask == 0x01){
                ledDir = 0;
                loopCount++;
            }
        }
    }

RN4870のIOピンの使い方がよくわかんねーっす

RN4870のデータシートにはこんな記載がある。

f:id:yuji2yuji:20190723000041p:plain
上から三つ目、P0_2の機能としてAD2との記載がある。

一方、User's guideを見ると
f:id:yuji2yuji:20190723000202p:plain
ADコンバータの機能を有するピンにP0_2の記載がない・・・

どういうこと??

とりあえずMicrochipのForumで聞いてみるもなかなか返答がない。
自分の英語がクソすぎるからか?