TWELITE で SPI を試す

前回の
「TWELITE で Lチカの続き(シリアルUART出力)」
https://qiita.com/nanbuwks/items/7194db5680b65c9cc2bd
を元に、SPI 接続の ADコンバータ、MCP3008 を接続してみます。

無線通信などは行わず、先の Lチカ＋シリアル UART 出力プログラムに SPI 接続に必要な最低限の記述のみ加えます。


# 接続


![IMG_20200824_065326144.jpg]
(https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139524/f535e4ed-ee39-7590-4187-fdeae662da5f.jpeg)


![IMG_20200824_063529806.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139524/8ab8d6a6-86fe-e383-01ee-af00f083d998.jpeg)

MCP3008 はアナログ入力ポートが CH0 〜 CH7 の 8チャンネルあります。それぞれ 10 bit の分解能で AD 変換を行い、SPI でマイコンなどに取り込むことができます。テストのため、CH0 に 1kΩ の抵抗をつけて、VDD につないでいます。
右側に配置しているスイッチは TWELITE の書き込みのために PRG と R (RESET) につなげています。

また、ブレッドボード外にLEDを接続する配線として、ピン番号15番(シルク表記12)とGNDにLEDを接続しています。

|SPI信号名|MCP3008 PIN|MCP3008 信号名|ケーブル|TWELITE DIP基板ピン番号| TWELITE DIP基板上のシルク表記|TWELITE IO名|
|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|VDD|16|VDD |オレンジ |28|VCC|VCC|
||15|VREF |VDDに接続 ||||
||14|AGND|DGNDに接続||||
|SCK|13|CLK |黄色|6|C|DO0|
|MISO|12|DOUT |紫|7|I|DO1|
|MOSI|11|DIN |灰|5|18|DIO18|
|CS|10|CS/SHDN |緑|8|19|DIO19|
|GND|9|DGND |黒|GND|GND|GND|




# TWELITE の SPI関数


以下のサイトから、必要な設定をピックアップします。


https://sdk.twelite.info/hw-api-rifurensu/perifuraru/spi
https://mono-wireless.com/jp/tech/Programming/PERIPH_SPI.html

ピックアップした主要関数は以下の通りです。

- vAHI_SpiConfigure() 
- vAHI_SpiSelect()
- vAHI_SpiStartTransfer()
- bAHI_SpiPollBusy()
- u32AHI_SpiReadTransferXX()
- vAHI_SpiStop
- vAHI_SpiDisable();


# SPI 主要関数を MCP3008 に合わせて使う

MCP3008についての詳細は
「ESP32 と MCP3008 で SPI の勉強」
https://qiita.com/nanbuwks/items/9a9169a2afbad95ae7ca
を参照してください。


## vAHI_SpiConfigure

SPIリソースを確保し、初期化します。
必要な引数について、設定値は以下のようにします。


||設定値|設定値の意味|
|:---:|:---:|:---:|
| u8SlaveEnable | 1 | SPI_CSEL として DIO19 のみ使用する(SPI_CSEL0のみの使用) |
| bLsbFirst |FALSE|MSBから転送開始||
| bPolarity |FALSE|クロックを反転しない(SPI MODE0)|アイドルLo パルスHigh|
| bPhase |TRUE|立ち上がりエッジを用いる(SPI MODE0)||
| u8ClockDiviser |8| クロックを1MHzで駆動|MCP3008は10kHz〜1.35MHzまで対応|
| bInterruptEnable |FALSE|SPI転送終了時の割り込みを使用しない|
| bAutoSlaveSelect |FALSE|CSELピンの制御を vAHI_SpiSelect を使って行う|

## vAHI_SpiSelect , vAHI_SpiStop

SPIのCSEL は TWELITE では SPI_CSEL0,SPI_CSEL1,SPI_CSEL2 の3つが使え、それぞれ DIO19, DIO14(DIO0に変更可能),DIO15(DIO1 に変更可能)に割り当てられています。
今回は SPI_CSEL0 を使うことにし、必要な引数について、設定値は以下のようにします。

||設定値|設定値の意味|
|:---:|:---:|:---:|
| u8SlaveEnable | 1 | SPI_CSEL0 ( DIO19 ) ピンを使ってSPIをセレクト状態にする |

また、セレクト解除には以下のようにするか、vAHI_SpiStop を呼び出します。

||設定値|設定値の意味|
|:---:|:---:|:---:|
| u8SlaveEnable | 0 | SPI_CSELピンの解除 |

## vAHI_SpiStartTransfer , bAHI_SpiPollBusy()

vAHI_SpiStartTransfer で SPI スレーブにデータを送信します。
必要な引数について、設定値は以下のようにします。

||設定値|設定値の意味|
|:---:|:---:|:---:|
| u8CharLen | 4 | 転送長のビット長(5ビット) - 1 を指定。 |
| u32Out |  0x00000018 | 32bitバッファに送信するデータをLSB詰めで格納する  |

スタートビットとして1ビット、それに続く4ビットでMCP3008の測定モードとチャンネルを設定、合計5ビット送信します。


|bit4|bit3|bit2|bit1|bit0|
|:---:|:---:|:---:|:---:|:---:|
|start bit|SGL/IFF|D2|D1|D0|
|1|1|0|0|0|

MCP3008へ入力するbit3~bitについて、SGL/DIFFは シングルエンド入力と作動入力のビットです。1ならシングルエンド、0なら作動入力となります。
今回はシングルエンド入力を使います。その場合、D2/D1/D0 はチャンネル指定、CH0なら D2=0,D1=0,D0=0。CH1ならD2=0,D1=0,D0=1となります。
スタートビットを含めて 0b11000 となるので、32ビットに直し 0x00000018 として転送します。

転送終了を待たずに制御が帰るので、bAHI_SpiPollBusy() でチェックします。転送完了になると bAHI_SpiPollBusy() が false になります。

## u32AHI_SpiReadTransferXX

SPIスレーブデバイスから読み出します。
vAHI_SpiStartTransfer の結果の読み出しデータのサイズごとに

-  uint32 u32AHI_SpiReadTransfer32(void);
-  uint16 u16AHI_SpiReadTransfer16(void);
-  uint8 u8AHI_SpiReadTransfer8(void);

が用意されています。データが満たない場合はLSB側に詰められます。
今回は以下のように 11bit を読み出すので、


|bit10|bit9|bit8|bit7|bit6|bit5|bit4|bit3|bit2|bit1|bit0|
|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|0|B9|B8|B7|B6|B5|B4|B3|B2|B1|B0|


 uint16 u16AHI_SpiReadTransfer16(void);

を使えばいいのですが、デバッグなどの経緯で

 uint32 u32AHI_SpiReadTransfer32(void);
  
を使い、下位10bitを使うことにしました。


## vAHI_SpiDisable

vAHI_SpiConfigure() で確保した SPI のリソースを開放します。

# コード

上記のコードをまとめて以下のようにしました。


```

#include <AppHardwareApi.h>
#include "utils.h"
#include "ToCoNet.h"
#include "ToCoNet_mod_prototype.h"
#include "serial.h" 
#include "fprintf.h"
#define LED 12

tsFILE sSerial0Stream;
static tsSerialPortSetup sSerial0;


static void vProcessEvCore(tsEvent *pEv, teEvent eEvent, uint32 u32evarg)
{
    static int counter=0;
    uint32 mcp3008result;
    int SPIresult=0;
    if (eEvent == E_EVENT_TICK_TIMER) {      // 4ms timer
        counter++;
        if (  0 == counter % 500 ) {
            vPortSetHi(LED);
            vfPrintf(&sSerial0Stream, "LED High\n\r");
            // SPI リソース初期化
            vAHI_SpiConfigure(1,     // use SPISEL0
		    		    	  FALSE, // MSB FIRST
			        		  FALSE, // SPI mode 0
			    	    	  FALSE,  // SPI mode 0
		    			      8,     // 1MHz clock
	    				      FALSE, // no use interrupt SPI transfer end
    					      FALSE);  // CSEL control by vAHI_SpiSelect
        	vAHI_SpiSelect(1); // SPISEL0 スタート
            vAHI_SpiStartTransfer(
		            			4, // 5bit - 1 
			    		        0x0000018L ); // 0b1 (スタートビット) + 0b1000 (シングルエンド入力、CH0)
            int i;

            for ( i=0;  bAHI_SpiPollBusy(); i++) // timeout 付 Busy チェック
            {
                if ( 1000 < i )
                {
                    vfPrintf(&sSerial0Stream, "error! MCP3008 SPI timeout\n\r");
                    break;
                }
                vWait(1);
            }
            vAHI_SpiStartTransfer(
		            			11, // 12bit - 1 ( 1 : 1 bit + 0 : 2bit + ADvalue : 10bit ) 
			    		        0x00000000L );
            for ( i=0;  bAHI_SpiPollBusy(); i++) // timeout 付 Busy チェック
            {
                if ( 1000 < i )
                {
                    SPIresult=-1;
                    break;
                }
                vWait(1);
            }
            if ( 0 == SPIresult ) {
                mcp3008result = u32AHI_SpiReadTransfer32();
                vfPrintf(&sSerial0Stream, "SPI result=%b ADvalue=%d\n\r",mcp3008result,mcp3008result & 0x3FF);
            } else {
                vfPrintf(&sSerial0Stream, "error! MCP3008 SPI timeout\n\r");
            }
            vAHI_SpiSelect(0); // SPISEL0 処理終了
            vAHI_SpiDisable(); // SPI リソース開放

        }
        if ( 250 == counter % 500 ) {
            vPortSetLo(LED);
            vfPrintf(&sSerial0Stream, "LED Low\n\r");
        }
    }
}


void cbAppColdStart(bool_t bStart)
{
    if (!bStart) {
    } else {
        vPortAsOutput(LED);  // GPIO set
        ToCoNet_Event_Register_State_Machine(vProcessEvCore); // add Event handler
        // serial init

        static uint8 au8SerialBuffTx[32];
        static uint8 au8SerialBuffRx[16];

        sSerial0.pu8SerialRxQueueBuffer = au8SerialBuffRx;
        sSerial0.pu8SerialTxQueueBuffer = au8SerialBuffTx;
        sSerial0.u32BaudRate = 115200;
        sSerial0.u16AHI_UART_RTS_LOW = 0xffff;
        sSerial0.u16AHI_UART_RTS_HIGH = 0xffff;
        sSerial0.u16SerialRxQueueSize = sizeof(au8SerialBuffRx);
        sSerial0.u16SerialTxQueueSize = sizeof(au8SerialBuffTx);
        sSerial0.u8SerialPort = E_AHI_UART_0;
        sSerial0.u8RX_FIFO_LEVEL = E_AHI_UART_FIFO_LEVEL_1;
        SERIAL_vInit(&sSerial0);

        sSerial0Stream.bPutChar = SERIAL_bTxChar;
        sSerial0Stream.u8Device = E_AHI_UART_0;

        vfPrintf(&sSerial0Stream, "Hello World!\n\r");

    }
}

void cbAppWarmStart(bool_t bStart) { return; }
void cbToCoNet_vRxEvent(tsRxDataApp *psRx) { return; }
void cbToCoNet_vTxEvent(uint8 u8CbId, uint8 bStatus) { return; }
void cbToCoNet_vNwkEvent(teEvent eEvent, uint32 u32arg) { return; }
void cbToCoNet_vHwEvent(uint32 u32DeviceId, uint32 u32ItemBitmap) {	return; }
uint8 cbToCoNet_u8HwInt(uint32 u32DeviceId, uint32 u32ItemBitmap) {	return FALSE; }
void cbToCoNet_vMain(void) { return; }


```

最初は vAHI_SpiStartTransfer で 5bit 送信した後にすぐに u32AHI_SpiReadTransfer32(void) で読み込みをしたのですが、有効な値が取れませんでした。あれれー？？ と思っていろいろ試したら、


1. vAHI_SpiStartTransfer で 5bit 送信して動作モードやチャンネルなどを送る
1. bAHI_SpiPollBusy()で処理が終わるのを待つ
1. vAHI_SpiStartTransfer で 12bit の 0 データを送信する
1. bAHI_SpiPollBusy()で処理が終わるのを待つ
1. u32AHI_SpiReadTransfer32() で 12bit 取得する

というようにしないといけませんでした。



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139524/2acc8932-1a92-cf03-eec0-50f8084e4314.png)

MCP3008のデータシートでは10bits のAD変換データの頭に0が追加されて11bitsになるはずですが、TWELITEでは10が頭に追加されて12bitになっていました。

なお、bAHI_SpiPollBusy() の処理は Timeout 判定を入れています。SPIライブラリがブロッキングしっぱなしになる可能性があるかどうかまだ確認ができていないので、必要ないかも知れませんが念の為。


# 結果

```

LED Hello World!
LED Low
LED High
SPI result=101111111111 ADvalue=1023
LED Low
LED High
SPI result=101111111111 ADvalue=1023
LED Low
LED High
SPI result=101111111111 ADvalue=1023
LED Low
LED High
SPI result=101110110101 ADvalue=949
LED Low
LED High
SPI result=101111010101 ADvalue=981
LED Low
LED High
SPI result=100000000001 ADvalue=1
LED Low
LED High
SPI result=100000000000 ADvalue=0
LED Low
LED High
```
LEDの点滅と同時に CH0 の電圧をADの変換した値を出力します。CH0に接続した抵抗を、VDDからGNDに繋ぎ変えた時の値です。