M5Stack Core2とMAX31855を使った温度計

## はじめに
自宅のオーブンの温度を測ろうと思ってM5Stack core2を使おうと思ったのですが，意外とM5Stack core2のSPIについての記事がないと思ったので書き留めておきます.

### MAX31855とは
SPI接続で熱電対を利用して温度を測定することができるICです．
今回は秋月電子さんで買えるこちらのモジュールを使用しました．

https://akizukidenshi.com/catalog/g/gM-08218/

端子はそれぞれ，
Vin->5V
3Vo->未接続 
GND->GND
DO->MISO
CS->自分で設定したGPIO(今回は19)
CLK->SCK に接続します．
 
## BUSのGPIOを整理する
まず，M5Stackとcore2でBUSに引き出されているGPIOが変更されているようです.
SPIのSSに指定しようとするピン番号に注意が必要です.
[<img src="https://www.gwendesign.com/kb/m5stack/img/M5StackM5Core2GPIO.png">](https://docs.m5stack.com/ja/core/core2#m5core2-buscompared-to-m5stack)

M5Stack Core2の公式ドキュメントページは以下.

https://docs.m5stack.com/ja/core/core2

## SPIについて
サンプルコードなど色々調べた結果，どうやらBUSに引き出されているSPIのピンは「VSPI」モジュールのようです．
普段UARTばかり使っているので，受信関数は何だろうと疑問に思ったのですが，SPIは`Receive_data = transfer(Send_data);`でデータの送受信を同時に行えるんですね．

SPIの使い方は，
```C
#include <SPI.h> /*SPIヘッダーをインクルード*/
#define MAXCS 19 /*SSピンをGPIO19に*/
static const int spiClk = 1000000; /*クロックを1MHzに*/
SPIClass * thermo_spi = NULL /*SPIクラスを格納するポインタを準備*/

void setup(){
    thermo_spi = new SPIClass(VSPI); /*インスタンスを生成*/
    thermo_spi -> begin(VSPI_SCLK, VSPI_MISO, VSPI_MOSI, MAXCS); /*指定したピンでSPI通信を確立*/
    pinMode(MAXCS, OUTPUT); /*begin()でSSピンを指定しても自動でピンモードを設定してくれないので手動で設定する.*/
}

/*通信コード*/
void measurement(){
    /* beginTransaction()でSPI通信の開始を宣言して，
     * digitalWrite()でSSピンを手動で操作する．
     * transfer()でデータの送受信を行い，
     * digitalWrite()でSSピンを元に戻してから，
     * endTransaction()でSPI通信を終了する．       */
    thermo_spi->beginTransaction(SPISettings(spiClk, MSBFIRST, SPI_MODE0)); 
    digitalWrite(MAXCS, LOW);
    /*今回はMAX31855に合わせて32bit通信用関数を使用．
     *もちろん普通の8bit通信のtransfer()を4回呼び出してもいい．
     *MAX31855は受信機能はないので，送るデータは何でもいい．   */
    receive_data = thermo_spi->transfer32(0x32241608);
    digitalWrite(MAXCS, HIGH);
    thermo_spi->endTransaction();
}
```
出来上がったプログラムの画面はこんな感じです．
「℃」の表示をどうしようと考え，オー'o'とシー'C'を無理やりくっつければいいやと雑に考えた割には，それっぽいものを作ることができて満足しています．
![IMG_20230606_192546.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/253612/b35bbb02-b6fe-22da-893b-063d57ea50ab.jpeg)
バッテリーの残量表示もあったほうがいいな～と作った後から思い立ったのでさりげなく追加してあります（笑）


## おわりに
今回初めてM5Stackを使いました．
コンパクトでケーシングもあって，ディスプレイもついていてすごく便利ですね．
SPIライブラリの中身を見たら，ちょうど32bit転送命令`transfer32()`があったので使用してます．

## 関連リンク
SPIの理解には，こちらのページを参考にさせていただきました．

https://smtengkapi.com/engineer-esp32-wroom32-spi

## ソースコード
作ったプログラムのソース全文は以下です．
サンプリング周期を可変できるようにしようとか，考えていたけどやめたりしていてちょっと無駄な内容とかもありますが…
どなたかのお役に立つことを願って<(_ _)>
```c
#include <SPI.h>
#include <M5Core2.h>
#include <Wire.h>
#include <AXP192.h>

// Define ALTERNATE_PINS to use non-standard GPIO pins for SPI bus
#ifdef ALTERNATE_PINS
  #define VSPI_MISO   2
  #define VSPI_MOSI   4
  #define VSPI_SCLK   0
  #define VSPI_SS     33

  #define HSPI_MISO   26
  #define HSPI_MOSI   27
  #define HSPI_SCLK   25
  #define HSPI_SS     32
#else
  #define VSPI_MISO   MISO  //38
  #define VSPI_MOSI   MOSI  //23
  #define VSPI_SCLK   SCK   //18
  #define VSPI_SS     SS    //4

  #define HSPI_MISO   12
  #define HSPI_MOSI   13
  #define HSPI_SCLK   14
  #define HSPI_SS     15
#endif

#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
#define VSPI FSPI
#endif

static const int spiClk = 1000000; // 1 MHz
SPIClass * thermo_spi = NULL;

//thermocouple
#define MAXCS   19
double temperature;
int32_t receive_data=0;
void measuerment(){
    //use the SPI buses
  thermo_spi->beginTransaction(SPISettings(spiClk, MSBFIRST, SPI_MODE0));
  digitalWrite(MAXCS, LOW); //pull SS slow to prep other end for transfer
  receive_data = thermo_spi->transfer32(0x32241608);
  digitalWrite(MAXCS, HIGH); //pull ss high to signify end of data transfer
  thermo_spi->endTransaction();

  double internal = (recieve_data >> 4) & 0x7FFF;
  internal *= 0.0625;
  if((receive_data >> 4) & 0x800) internal *= -1;

  if( receive_data & 0x80000000){
    receive_data = 0xFFFFC000 | ((receive_data >> 18) & 0x00003FFF);
  }else{
    receive_data >>= 18;
  }

  temperature = receive_data;
  temperature *= 0.25;
}

//timer interrupt
hw_timer_t *timer = NULL;
volatile int interruptCounter;
volatile bool update_admit = 0;
volatile uint16_t intervalTime=10;
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
void IRAM_ATTR onTimer(){
  portENTER_CRITICAL_ISR(&timerMux);
  interruptCounter++;
  update_admit = 1;
  portEXIT_CRITICAL_ISR(&timerMux);
}

//battery
AXP192 power;
float bat=0;

#define LOW_BATTERY 3.65
#define LOW_THRESHOLD 0.53


void update_temperature(){
  M5.Lcd.setTextSize(4);
  M5.Lcd.drawFloat(temperature,2,10,30);
  M5.Lcd.setCursor(270,30);
  M5.Lcd.setTextSize(1);
  M5.Lcd.print('o');
  M5.Lcd.setTextSize(4);
  M5.Lcd.print('C');
  M5.Lcd.setTextSize(2);
  M5.Lcd.setCursor(300,45);
  if(interruptCounter % 2){
    M5.Lcd.print('.');
  }else{
    M5.Lcd.print(' ');
  }
  M5.Lcd.setCursor(0, 0);
}


void setup() {
  // put your setup code here, to run once:
  M5.begin();
  
  M5.Lcd.setTextSize(4);
  //timer
  timer = timerBegin(0,80,true);//1 micro sec
  timerAttachInterrupt(timer, &onTimer, true);
  timerAlarmWrite(timer, 1000000, true);
  timerAlarmEnable(timer);

  //spi
  thermo_spi = new SPIClass(VSPI);
  thermo_spi->begin(VSPI_SCLK, VSPI_MISO, VSPI_MOSI, MAXCS);
  pinMode(MAXCS, OUTPUT);
  
}

void loop() {
  // put your main code here, to run repeatedly:
  M5.update();

  if(update_admit){
    measuerment();
    M5.Lcd.setTextSize(2);
    M5.Lcd.setCursor(240,0);
    bat = power.GetBatVoltage();
    bat = bat - LOW_BATTERY;
    //M5.Lcd.printf("%3.2f V", power.GetBatVoltage());
    if(bat < 0){
      M5.Lcd.print("0 %%");
    }else{
      M5.Lcd.printf("%3.1f %%", (bat / LOW_THRESHOLD)*100);
    }
    update_temperature();
    portENTER_CRITICAL_ISR(&timerMux);
    update_admit = 0;
    portEXIT_CRITICAL_ISR(&timerMux);
  }
   if(interruptCounter > (intervalTime-1)){
    portENTER_CRITICAL_ISR(&timerMux);
    interruptCounter=0;
    portEXIT_CRITICAL_ISR(&timerMux);
  }
  delay(100);

}
```