環境構築: Arduino IDE 入門チュートリアル を参考に IDE のインストールを完了し、使用する開発ボードに応じて対応するボードマネージャーと必要なドライバライブラリをインストールしてください。
使用するライブラリ:
使用するハードウェア製品:

G15 (SCL)、G13 (SDA) です。実際の接続と組み立ては下図の通りです:
#include <M5StamPLC.h>
M5Canvas canvas(&M5StamPLC.Display());
M5StamPLC_IO stamplc_io;
constexpr uint32_t MONITOR_UPDATE_INTERVAL_MS = 500;
void setup()
{
M5StamPLC.begin();
canvas.createSprite(M5StamPLC.Display().width(), M5StamPLC.Display().height());
canvas.setTextScroll(false);
canvas.fillScreen(TFT_BLACK);
canvas.setTextSize(1);
canvas.setFont(&fonts::efontCN_16);
canvas.println("Try to find M5StamPLC IO");
while (!stamplc_io.begin()) {
canvas.println("M5StamPLC_IO not found, retry in 1s...");
canvas.pushSprite(0, 0);
delay(1000);
}
canvas.printf("Found: 0x%02X FW: 0x%02X\n", stamplc_io.getCurrentAddress(), stamplc_io.getFirmwareVersion());
canvas.println("Start monitoring...");
canvas.pushSprite(0, 0);
}
void loop()
{
M5StamPLC.update();
static uint32_t last_update = 0;
bool need_update = stamplc_io.syncAddress();
uint32_t now = millis();
if (now - last_update >= MONITOR_UPDATE_INTERVAL_MS || need_update) {
last_update = now;
int16_t v1, v2;
int32_t i1, i2;
stamplc_io.readAllChannelsData(&v1, &i1, &v2, &i2);
uint8_t io_ctrl = stamplc_io.readRegister(M5StamPLC_IO::REG_IO_CONTROL);
uint8_t sys_status = stamplc_io.getSystemStatus();
canvas.fillScreen(TFT_BLACK);
canvas.setCursor(0, 0);
canvas.setTextColor(TFT_GREENYELLOW);
canvas.println("== Voltage/Current Monitor ==");
canvas.println();
canvas.setTextColor(sys_status & (1 << M5StamPLC_IO::SYS_CH1_INA226_ERROR) ? TFT_RED : TFT_GREEN);
canvas.printf("CH1: %d.%02dV %duA\n", v1 / 1000, abs(v1 % 1000) / 10, i1);
canvas.setTextColor(sys_status & (1 << M5StamPLC_IO::SYS_CH2_INA226_ERROR) ? TFT_RED : TFT_GREEN);
canvas.printf("CH2: %d.%02dV %duA\n", v2 / 1000, abs(v2 % 1000) / 10, i2);
canvas.setTextColor(TFT_YELLOW);
canvas.printf("Pull-up: CH1=%s CH2=%s\n", (io_ctrl & (1 << M5StamPLC_IO::BIT_CH1_PU_EN)) ? "ON" : "OFF",
(io_ctrl & (1 << M5StamPLC_IO::BIT_CH2_PU_EN)) ? "ON" : "OFF");
canvas.setTextColor(TFT_MAGENTA);
canvas.printf("Addr: 0x%02X DIP: 0x%02X\n", stamplc_io.getCurrentAddress(), stamplc_io.getExpectedAddress());
canvas.setTextColor(sys_status == 0 ? TFT_GREEN : TFT_RED);
canvas.printf("System: %s\n", sys_status == 0 ? "Normal" : "Error");
canvas.pushSprite(0, 0);
}
if (M5StamPLC.BtnA().wasClicked()) {
stamplc_io.toggleIOBit(M5StamPLC_IO::BIT_CH1_PU_EN);
last_update = now - MONITOR_UPDATE_INTERVAL_MS;
}
if (M5StamPLC.BtnB().wasClicked()) {
stamplc_io.toggleIOBit(M5StamPLC_IO::BIT_CH2_PU_EN);
last_update = now - MONITOR_UPDATE_INTERVAL_MS;
}
delay(10);
}電源投入後、StamPLC IO はアナログ信号の電圧値と電流値を自動的に検出し、検出結果を画面に表示します。A ボタンを押すと CH1 のプルアップ抵抗の状態を切り替え、B ボタンを押すと CH2 のプルアップ抵抗の状態を切り替えます。画面には現在のプルアップ抵抗の状態がリアルタイムで更新表示されます。
#include <M5StamPLC.h>
M5Canvas canvas(&M5StamPLC.Display());
M5StamPLC_IO stamplc_io;
constexpr uint16_t PWM_DUTY_MIN = 0;
constexpr uint16_t PWM_DUTY_MAX = 1000;
constexpr uint16_t PWM_DUTY_STEP = 100;
constexpr uint32_t DUTY_UPDATE_INTERVAL_MS = 300;
uint8_t pwm_freq = 50;
uint16_t mos1_duty = PWM_DUTY_MIN;
uint16_t mos2_duty = PWM_DUTY_MAX;
int16_t duty_step = PWM_DUTY_STEP;
uint32_t last_update_time = 0;
void updateDisplay()
{
canvas.fillScreen(TFT_BLACK);
canvas.setCursor(0, 0);
canvas.setTextColor(TFT_GREENYELLOW);
canvas.println("=== PWM Auto Sweep ===");
canvas.setTextColor(TFT_CYAN);
canvas.printf("Mode: PWM Freq: %dHz\n", pwm_freq);
canvas.printf("MOS1: %s%d%% MOS2: %s%d%% /%dms\n", duty_step > 0 ? "+" : "-", PWM_DUTY_STEP / 10,
duty_step > 0 ? "-" : "+", PWM_DUTY_STEP / 10, DUTY_UPDATE_INTERVAL_MS);
canvas.setTextColor(TFT_YELLOW);
canvas.printf("MOS1: %d.%d%% (%d/1000)\n", mos1_duty / 10, mos1_duty % 10, mos1_duty);
canvas.printf("MOS2: %d.%d%% (%d/1000)\n", mos2_duty / 10, mos2_duty % 10, mos2_duty);
canvas.pushSprite(0, 0);
}
void setup()
{
M5StamPLC.begin();
canvas.createSprite(M5StamPLC.Display().width(), M5StamPLC.Display().height());
canvas.setTextScroll(true);
canvas.fillScreen(TFT_BLACK);
canvas.setTextSize(1);
canvas.setFont(&fonts::efontCN_16);
canvas.println("Try to find M5StamPLC IO module...");
canvas.pushSprite(0, 0);
while (!stamplc_io.begin()) {
canvas.println("Not found, retry in 1s...");
canvas.pushSprite(0, 0);
delay(1000);
}
canvas.printf("Found: 0x%02X FW: 0x%02X\n", stamplc_io.getCurrentAddress(), stamplc_io.getFirmwareVersion());
canvas.pushSprite(0, 0);
pwm_freq = stamplc_io.getPWMFrequency();
if (pwm_freq == 0) {
pwm_freq = 50;
stamplc_io.setPWMFrequency(pwm_freq);
}
stamplc_io.setPWMMode(true);
stamplc_io.setChannelDuty(1, mos1_duty);
stamplc_io.setChannelDuty(2, mos2_duty);
updateDisplay();
}
void loop()
{
M5StamPLC.update();
bool needUpdate = stamplc_io.syncAddress();
uint32_t now = millis();
if (now - last_update_time >= DUTY_UPDATE_INTERVAL_MS) {
last_update_time = now;
int32_t next_duty = static_cast<int32_t>(mos1_duty) + duty_step;
if (next_duty >= PWM_DUTY_MAX) {
next_duty = PWM_DUTY_MAX;
duty_step = -PWM_DUTY_STEP;
} else if (next_duty <= PWM_DUTY_MIN) {
next_duty = PWM_DUTY_MIN;
duty_step = PWM_DUTY_STEP;
}
mos1_duty = static_cast<uint16_t>(next_duty);
mos2_duty = PWM_DUTY_MAX - mos1_duty;
stamplc_io.setChannelDuty(1, mos1_duty);
stamplc_io.setChannelDuty(2, mos2_duty);
needUpdate = true;
}
if (needUpdate) updateDisplay();
delay(10);
}電源投入後、MOS1 と MOS2 の PWM 出力は順番にデューティ比を変更します。順序は、MOS1 のデューティ比が増加 -> MOS2 のデューティ比が減少、となります。対応する出力状態は画面に表示され、MOS1 と MOS2 に接続された LED の明るさの変化も確認できます。
#include <M5StamPLC.h>
M5Canvas canvas(&M5StamPLC.Display());
M5StamPLC_IO stamplc_io;
constexpr uint32_t OUTPUT_UPDATE_INTERVAL_MS = 500;
constexpr uint8_t OUTPUT_COUNT = 3;
const uint8_t output_bits[OUTPUT_COUNT] = {
M5StamPLC_IO::BIT_RELAY_TRIG,
M5StamPLC_IO::BIT_EX_CTR_1,
M5StamPLC_IO::BIT_EX_CTR_2,
};
const char* output_names[OUTPUT_COUNT] = {"Relay", "MOS1", "MOS2"};
bool output_state[OUTPUT_COUNT] = {false, false, false};
uint8_t output_step = 0;
uint32_t last_update_time = 0;
uint8_t last_output_index = 0;
bool last_output_state = false;
bool last_output_valid = false;
void updateDisplay()
{
canvas.fillScreen(TFT_BLACK);
canvas.setCursor(0, 0);
canvas.setTextColor(TFT_GREENYELLOW);
canvas.println("=== Output Auto Sequence ===");
canvas.setTextColor(TFT_CYAN);
canvas.printf("Addr: 0x%02X\n", stamplc_io.getCurrentAddress());
canvas.println("Order: Relay -> MOS1 -> MOS2");
canvas.printf("Interval: %lums\n", static_cast<unsigned long>(OUTPUT_UPDATE_INTERVAL_MS));
for (uint8_t i = 0; i < OUTPUT_COUNT; i++) {
canvas.setTextColor(output_state[i] ? TFT_GREEN : TFT_RED);
canvas.printf("%s: %s\n", output_names[i], output_state[i] ? "ON" : "OFF");
}
if (last_output_valid) {
canvas.setTextColor(TFT_YELLOW);
canvas.printf("Last: %s -> %s\n", output_names[last_output_index], last_output_state ? "ON" : "OFF");
}
canvas.pushSprite(0, 0);
}
void setOutputState(uint8_t index, bool state)
{
output_state[index] = state;
stamplc_io.setRelayState(output_bits[index], state);
last_output_index = index;
last_output_state = state;
last_output_valid = true;
updateDisplay();
}
void setup()
{
/* Init M5StamPLC*/
M5StamPLC.begin();
canvas.createSprite(M5StamPLC.Display().width(), M5StamPLC.Display().height());
canvas.setTextScroll(false);
canvas.fillScreen(TFT_BLACK);
canvas.setTextSize(1);
canvas.setFont(&fonts::efontCN_16);
canvas.println("Try to find M5StamPLC IO");
canvas.pushSprite(0, 0);
/* Init M5StamPLC IO */
while (!stamplc_io.begin()) {
canvas.println("M5StamPLC_IO not found, retry in 1s...");
canvas.pushSprite(0, 0);
delay(1000);
}
for (uint8_t i = 0; i < OUTPUT_COUNT; i++) {
setOutputState(i, false);
}
}
void loop()
{
M5StamPLC.update();
if (stamplc_io.syncAddress()) {
updateDisplay();
}
uint32_t now = millis();
if (now - last_update_time >= OUTPUT_UPDATE_INTERVAL_MS) {
last_update_time = now;
uint8_t output_index = output_step % OUTPUT_COUNT;
bool state = output_step < OUTPUT_COUNT;
setOutputState(output_index, state);
output_step = (output_step + 1) % (OUTPUT_COUNT * 2);
}
delay(10);
}電源投入後、リレー出力は、リレー -> MOS1 -> MOS2 の順にオン/オフを繰り返します。対応する出力状態は画面に表示され、リレーのインジケータおよびリレーに接続された負荷状態の変化も確認できます。
リレー:
MOS1、MOS2:
