Seeeduino XIAOに色々繋げて動かしてみる(OLED-LCD INA226 MAX31855)

Arduino/SAMDI2C,INA226,MAX31855,OLED,SAMD21,Seeeduino,SPI,U8g2,XIAO,プルアップ

折角なのでESP32で今迄ちょっと試したモジュールをXIAOに繋げて動かしてみようと思います。

温度計:MAX31855 SPI
OLED-LCD:SSD1306 I2C
電流・電圧計:INA226 I2C

抵抗も何も要らずズブシズブジと挿すだけで済むかなと思ったのですが、、

どうやらsamd21の内部プルアップはINPUT専用で、OUTPUTでは使えない模様。

面倒ですがいくつかのクロックと信号線を、電源から10kΩの抵抗でプルアップしています。

XIAOwithModules01

CHAOS!

いつもながら大変混み合っておりますが、、

特にマイコン外で回路を形成したりもせず、接続の内容は単純です。

ただコネクタを繋いだだけ、みたいな。

回路図は・・・書こうと思ったのですが、なんかモジュールボードを表現するのが大変なので省きます(ぇ

プログラムもライブラリを使うだけの感じですので、ただ配置しただけという感じ。

長いですが、、、

#include <Arduino.h>

// Seeed FreeRTOSライブラリ
#include <Seeed_Arduino_FreeRTOS.h>

// Wireライブラリ
#include <Wire.h>

// MAX31855 k熱電対センサー
#include "MAX31855.h"

// OLEDライブラリ
#include <U8g2lib.h> // グラフィックライブラリ
//#include <U8x8lib.h>	// テキストライブラリ

//====================================================================================
// GPIO I2C Setting
//	マクロでよしなに設定してくれるので、変更する場合以外は不要
//const uint8_t I2C_SCL = 5; // SCL 5
//const uint8_t I2C_SDA = 4; // SDA 4
//====================================================================================

//====================================================================================
// MAX31855
const int csPin = 3; // VSPI SS
const int clPin = 8; // VSPI CLK
const int doPin = 9; // VSPI MISO

// インスタンス
//	今回はハードウェアSPIモードではなくソフトウェアSPIモードでテスト
MAX31855 tc(clPin, csPin, doPin);
// ハードウェアSPIならこれだけ。
//MAX31855 tc(csPin);
//====================================================================================

//====================================================================================
// OLED LCD(I2C)
// I2Cアドレス
volatile const uint8_t ADDRES_OLED = 0x3C; // volatile

// 通信速度
//const uint32_t I2C_Freq = 3400000UL;  // I2C 周波数
const uint32_t I2C_HIGH = 400000UL; // I2C 周波数
//const uint32_t I2C_Normal = 100000UL; // I2C 周波数

// U8g2
// LCD初期設定マクロ
//	\U8g2\src\U8g2lib.h ハードウェアにあったマクロを指定
//	https://github.com/olikraus/u8g2/wiki/u8g2setupcpp#ssd1306-128x64_noname
//
//	コントローラチップ:液晶サイズ:LCD名:バッファサイズ:接続方法
//	モード(u8g2 / u8x8)
//	SSD1306 128x64 HW_I2C F HW_I2C
// U8G2:グラフィックライブラリ
U8G2_SSD1306_128X64_NONAME_F_HW_I2C display(U8G2_R0, /* reset=*/U8X8_PIN_NONE);

// U8X8:テキストライブラリ
//U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8(/* reset=*/ U8X8_PIN_NONE);
//U8X8_SSD1306_128X64_NONAME_HW_I2C display(/* reset=*/ U8X8_PIN_NONE);
//====================================================================================

//====================================================================================
// INA226(I2C)

// I2Cアドレス
volatile const uint8_t ADDRES_INA226 = 0x40; // volatile

// -------------------------------------------------------------------------------
//	シャント抵抗値
//const uint8_t ShuntR = 100; // 単位はmohm(ミリオーム)
volatile uint8_t ShuntR = 100; // 単位はmohm(ミリオーム)volatile指定しておく
// -------------------------------------------------------------------------------
//	INA226 レジスター値
// -------------------------------------------------------------------------------
//	コンフィグ設定値は16bit値らしいので2byteに揃える
//		https://www.tij.co.jp/product/jp/INA226
//		https://www.tij.co.jp/jp/lit/ds/symlink/ina226.pdf?ts=1603963477005&ref_url=https%253A%252F%252Fwww.tij.co.jp%252Fproduct%252Fjp%252FINA226
// -------------------------------------------------------------------------------
// ---------------------------------------------------
// 00h ConfigurationRegister
//	default : 0B:01000001 00100111 0x:4127h
const uint8_t INA226_CONFIG = 0x00;
// -----------------------------------------------
// AVGBit Settings 平均値モードのサンプル数 D11-D9 << 9
//const uint16_t INA226_CONFIG_AVG = 0x0000U; // default 1@000  128@100 MAX:1024@111
//const uint16_t INA226_CONFIG_AVG = 0x0001U; // 8回計測
uint16_t INA226_CONFIG_AVG = 0x0000U; // 可変にするのでconstを外す
// Bus VoltageConversionTime 電圧測定間隔 D8-D6  << 6
//const uint16_t INA226_CONFIG_VCT = 0x0004U; // default:1.1ms@100
const uint16_t INA226_CONFIG_VCT = 0x0000U; // 140us
// ShuntVoltageConversionTime シャント抵抗電圧測定間隔 D5-D3 << 3
//const uint16_t INA226_CONFIG_SVCT = 0x0004U; // default:1.1ms@100
const uint16_t INA226_CONFIG_SVCT = 0x0000U; // 140us
// ModeSettings 動作モード D2-D0 << 0
const uint16_t INA226_CONFIG_MODE = 0x0007U; // default:Shuntand Bus,Continuous@111
// -----------------------------------------------

// ---------------------------------------------------
// 01h ShuntVoltageRegister (ReadOnly)
//	0h と出るけど固定 8000 (1F40h)
//const uint8_t INA226_SHUNTV = 0x01;
volatile const uint8_t INA226_SHUNTV = 0x01; // volatile
// ---------------------------------------------------
// 02h Bus VoltageRegister (ReadOnly)
//	0h と出るけど固定 1.25mV / bit = 9584 (2570h)
//const uint8_t INA226_BUSV = 0x02;
volatile const uint8_t INA226_BUSV = 0x02; // volatile
// ---------------------------------------------------
// 03h PowerRegister (ReadOnly)
//	0h と出るけど固定 Power = CurrentRegister * VoltageRegister / 20000 = 4792 (12B8h)
const uint8_t INA226_POWER = 0x03;
// ---------------------------------------------------
// 04h CurrentRegister (ReadOnly)
//	0h と出るけど固定 10000 (2710h)
const uint8_t INA226_CURRENT = 0x04;
// ---------------------------------------------------0.000002
// 05h CalibrationRegister
//	default 2560 (A00h)
//	CAL(2560) = 0.00512 / Current 0.001A(04h 10000) * Shunt 0.002ohm(2m ohm)
//		1mohm=5120, 2mohm=2560 10mohm=512 25mohm=204.8 50m=102.4 100mohm=51.2 1ohm=5.12
//	CurrentRegister(04h 10000) = ShuntVoltage(01h 8000) * CalibrationRegister / 2048
const uint8_t INA226_CALIB = 0x05;
// ---------------------------------------------------
// 06h Mask/EnableRegister
//	アラートトリガーの設定らしい(今回未接続)
const uint8_t INA226_MASK = 0x06;
// ---------------------------------------------------
// 07h AlertLimitRegister
//	アラートの閾値の設定らしい(今回未接続)
const uint8_t INA226_ALERTL = 0x07;
// ---------------------------------------------------
// FEh ManufacturerID Register (ReadOnly)
const uint8_t INA226_MANU_ID = 0x08;
// ---------------------------------------------------
// FFh Die ID Register (ReadOnly)
const uint8_t INA226_DIE_ID = 0xff;
// ---------------------------------------------------

struct currentVoltageAmps
{
	volatile float shuntVoltage;
	volatile float shuntCurrentAmps;
	volatile int16_t busVoltage;
};
// volatile currentVA, *pcurrentVA;
currentVoltageAmps currentVA;
struct currentVoltageAmps *pcurrentVA = ¤tVA;
//====================================================================================

//====================================================================================
// サブルーチン
// レジスタ書き込み関数
void INA226_write(uint8_t reg, uint16_t val)
{
	Wire.beginTransmission(ADDRES_INA226);
	Wire.write(reg);
	// I2Cは8bitづつ書き込みらしい
	Wire.write(val >> 8);	  // 上位ビット送信
	Wire.write(val & 0x00ff); // ビットマスクして送信
	Wire.endTransmission();
}

// 読み込み
//uint16_t INA226_read(uint8_t reg)
volatile uint16_t INA226_read(uint8_t reg) // volatile
{
	uint16_t ret = 0;
	// リクエストするレジスタを連絡
	Wire.beginTransmission(ADDRES_INA226);
	Wire.write(reg);
	Wire.endTransmission();
	// 2バイトリクエスト
	Wire.requestFrom((uint8_t)ADDRES_INA226, (uint8_t)2);
	// 2バイト取り込み
	while (Wire.available())
	{
		//	初回は0なので下位ビットに埋まる
		//	2回目は下位ビットを上位にずらして、新しく来たものを下位ビットに埋める
		ret = (ret << 8) | Wire.read();
	}
	return ret;
}

// INA226コンフィグ送信
void configureINA226(uint16_t _config_ina)
{
	// INA226 初期設定
	// 引数が無効な場合は初期設定を書き込む
	if (!_config_ina)
	{
		// INA226 初期設定
		_config_ina = 0x4000U; // ベースビット 0B0100000000000000
		_config_ina = _config_ina | (INA226_CONFIG_AVG << 9) | (INA226_CONFIG_VCT << 6) | (INA226_CONFIG_SVCT << 3) | (INA226_CONFIG_MODE);
	}
	// 初期設定書き込み
	INA226_write(INA226_CONFIG, _config_ina);

	// キャリブレーション書き込み
	//	5120 : VAOhm order ((0.0000025V x 2048) / 0.001A ) * 1000(to milli ohm at ShuntR unit order)
	INA226_write(INA226_CALIB, (uint16_t)round(5120 / ShuntR)); // 5120 = 2.5 * 2048
}

// 電流/電圧取得
void getVoltageAmps(struct currentVoltageAmps *ret)
{
	ret->shuntVoltage = INA226_read(INA226_SHUNTV) * 2.5; // 2.5 : ShuntLSB unit 2.5uV to cf
	ret->shuntCurrentAmps = ret->shuntVoltage / ShuntR;	  // mA
	ret->busVoltage = INA226_read(INA226_BUSV) * 1.25;	  // 1.25 : BusLSB unit 1.25mV to cf
}
//====================================================================================

void setup()
{
	// シリアル
	Serial.begin(115200);
	while (!Serial)
		;

	// I2C開始
	Wire.begin();

	// 少しディレイ入れておく
	delay(1000);

	//------------------------------------------------------------------
	// OLED LCD
	//	Wireを初期化していないと動作しない
	display.begin();

	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	// ここは設定しなくても動く
	// I2Cアドレスを設定
	//	なんか知らんけどU8gnはI2Cアドレスを2倍して送るらしい・・・
	display.setI2CAddress(ADDRES_OLED * 2);
	// I2Cバスクロック
	display.setBusClock(I2C_HIGH);
	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

	// 日本語UTF8フォントを有効化
	display.enableUTF8Print();
	// フォントはここ
	//	https://github.com/olikraus/u8g2/wiki/fntgrp
	//	https://github.com/olikraus/u8g2/wiki/fntlistall

	// タイトル出力
	display.setFont(u8g2_font_b16_b_t_japanese1);
	display.setFontDirection(0);
	display.clearBuffer();
	display.setCursor(0, 15);
	display.print("-Seeeduino XIAO-");
	display.sendBuffer();

	Serial.println("Start OLED-LCD");
	//------------------------------------------------------------------

	//------------------------------------------------------------------
	// MAX31855
	// CS/SS ピン 内部プルアップでHIGHを代用も。
	pinMode(csPin, OUTPUT);
	digitalWrite(csPin, HIGH); // 基本アクティブLOWなのでHIGHにしておく

	// CLK OUTPUT
	// XIAOはプルアップだけのレジスタが無いのでとりあえずOUTPUTで。
	// ノイズ等で安定しない場合には回路に抵抗でプルアップをかまして対応
	pinMode(clPin, OUTPUT);

	// MISO MOSI
	pinMode(doPin,INPUT_PULLUP);	// MISO
//	pinMode(diPin,OUTPUT);			// MOSI 今回は使わない 使う場合は外部プルアップを付ける

	// モジュール通信開始
	tc.begin();

	Serial.print("Start max31855: ");
	Serial.println(MAX31855_VERSION);
	//------------------------------------------------------------------

	//------------------------------------------------------------------
	// INA226
	// デフォルト書き込み
	configureINA226(0);

	Serial.println("Start INA226");
	//------------------------------------------------------------------
}

void loop()
{

	// 温度表示
	// ステータスチェック
	int status = tc.read();
	if (status != 0)
	{
		Serial.print("stat:\t\t");
		Serial.println(status);
		// 手抜き
		Serial.println("Module Status Error!");

		// 表示箇所だけクリア
		display.setDrawColor(0);
		display.drawBox(0, 15, 127, 31);
		display.setDrawColor(255);

		display.setCursor(0, 31);
		display.print("MAX31855 Error!");
	}
	else
	{
		// 表示箇所だけクリア
		display.setDrawColor(0);
		display.drawBox(0, 15, 127, 31);
		display.setDrawColor(255);

		display.setCursor(0, 31);
		display.print("温度: ");
		display.print(tc.getTemperature());
		display.print(" 度");
	}

	// 現在のVA取得
	getVoltageAmps(pcurrentVA);

	// 表示箇所のクリア
	display.setDrawColor(0);
	display.drawBox(0, 31, 127, 63);
	display.setDrawColor(255);

	display.setCursor(0, 47);
	display.print("電圧: ");
	display.print(pcurrentVA->busVoltage);
	display.print(" mV");

	display.setCursor(0, 63);
	display.print("電流: ");
	display.print(pcurrentVA->shuntCurrentAmps);
	display.print(" mA");

	// LCDに送信
	display.sendBuffer();

	delay(50); // 50ms毎
}

特にマルチタスクで動かしている訳でもない、単純なものです。

一応各モジュールについて触れておきます。

MAX31855 k熱電対温度測定モジュール(SPI接続)

ESP32で触ったばかりですので特に説明の必要は無いと思いますが、、

マスター側のCS(GPIO3)をHIGHにし、CLKとMISOをプルアップしてあります。

MISO線はINPUTですので、XIAOの内部プルアップで代用。

CLKは10kΩの抵抗を使いました。

MOSI線は今回使いませんので放置状態です。

CSは3番ピンを使い、MAX31855ボードはアクティブローみたいなので、CSピンをHIGHにしてあります。

0.96inch OLED LCD(I2C接続):ライブラリにU8G2を利用

LCDライブラリに以前はAdafruitのものを使いましたが、今回はU8g2ライブラリを使ってみました。

このライブラリはAdafruitのライブラリと並んで有名なライブラリみたいですが、扱いが簡単でよくまとめられているとの事。

使い方は本当に簡単で、簡単3ステップでした。

・ライブラリをインクルード
・LCDに合ったマクロで初期設定
・begin()して描画処理を書く

ハードウェアI2Cを使う場合には、既定の配線をしてあればもうGPIOの番号を指定する必要すら無く、手間と言えばLCDに合ったマクロを探す事位でしょうか。

あとはフォントサイズは用意されているフォントに依存する様ですので、文字の拡大縮小等を行ったりする場合にはAdafruitのものを使った方が良いと思います。

今回は「u8g2_font_b16_b_t_japanese1」のフォントを利用させていただきました。

ちゃんと日本語フォントもありますので用が足りないという事は無く、感想としては、キャラクターLCDを使う感覚で使えるライブラリといった印象。

但し・・・

I2Cアドレスを送信する際には、要注意です。

なんかアドレスを2倍にして送るという謎仕様があるらしく、こんな書き方になってます・・・

display.setI2CAddress(ADDRES_OLED * 2);

I2CをHWで利用しマクロで定義してしまえば勝手にアドレス設定してくれる様なので不要なのですけども、基板上の抵抗の位置を変えてI2Cアドレスを変更した場合には設定が必要そうです。

U8g2には二つのライブラリがあり、U8g2は描画もできるライブラリ、U8x8が描画は出来ないものの軽量で更に簡単に使えるライブラリといった分類になっている様でした。

INA226(I2C接続)

INA226については色々と初期設定が必要ですのでコードが長くなっています。

基本的に8ビットづつ2回に分けて送る、みたいな関数も要ります。

これは過去記事を見てもらった方がいいです。

接続はLCDとMAX31855の電流を計測する様に結線してあります。

動作テストなので何処でもいいでしょって事で。

さてこのINA226もI2Cで繋いでますので、超速でデータを取りまくりたい時には少し工夫が要るかもしれません。

というのも、ESP32の時は何故か知らんI2Cのクロックを物凄い上げても動作したんですが、今回は1MHz位迄しか上がりませぬ。

XIAOの出力が弱いのか、LCDのライブラリにAdafruitのものを使っていた為なのか、定かではありませぬが

抵抗減らしてみたら安定するかもしれまい。

こういう時オシロやロジックアナライザがあると捗るのではないかなぁ・・・

そのうち調べてみます。( ノД`)

とりあえず動くかテストとまとめ

給電はそのままUSBからXIAO経由で引き出しています。

使ってみたモジュールばかりですので、まぁ動きます罠

こころなしか電圧と電流値が安定している様な

そして温度もESP32で使った時よりも正確な気がします。

何が違うんでしょうねぇ・・・?

やっぱり配線・・・?(´・ω・`)

とは言っても、正確さとか私みたいなNewbieにはどうでも良くて

動けばいい感じでスルー

ESP32との違いは内部プルアップでしょうか。

XIAOにもINPUT_PULLUPはありますが、OUTPUT_PULLUPやOPENDRAIN_PULLUPみたいな定数は定義されておらず、外部抵抗が必要そうでした。

ただ、samd21のデータシートを見ると、オープンドレインのINPUT OUTPUT PULLUPは無いみたいですが、プッシュプルの出力が出来るみたい。

このデータシートの 23.6.3 I/O Pin Configuration の項

ただ、どうやるのかは謎( ノД`)

framework-arduino-samd-seeed\cores\arduino\wiring_digital.c を見ると、INPUT, INPUT_PULLUP, INPUT_PULLDOWN, OUTPUT しか受け付けてくれない様ですしおすし

レジスタに送る定数をビットで作ってPinModeで投げればいいのかしらん・・・?

この辺りにレジスタに送る定数とか書いてあるっぽいんですが、なんか確証が持てないので今はやめておきます・・・( ノД`)

framework-cmsis-atmel\CMSIS\Device\ATMEL\samd21\include\component\port.h

なんか色々と調べないと使いこなすのは難しそうですが、、

逆に言えば現状以上に何か出来る余地があるという事でしょう。

また、これだけ繋いでもまだピンが5本も残っているのは凄いですね。

I2CもSPIもカスケード出来ますし、UARTは未使用です。

小さいのに「使える」マイコンボードだと思います。

Seeeduino XIAOさん、とりあえず想定していた動作は一通りできましたので、今日のところはこの辺で。