
G12 (SDA) と G11 (SCL)、割り込みピンは G10 (INT) です。#include <Arduino.h>
#include <M5Faces.h>
#include <M5Unified.h>
#include <math.h>
namespace {
M5Faces_Calculator3 calculator;
String entry = "0";
String last_key;
double left_value = 0.0;
char pending_operator = 0;
bool has_left_value = false;
bool just_evaluated = false;
bool calc_error = false;
double memory_value = 0.0;
bool memory_valid = false;
String format_number(double value)
{
if (!isfinite(value)) return "ERROR";
String result(value, 6);
while (result.indexOf('.') >= 0 && result.endsWith("0")) result.remove(result.length() - 1);
if (result.endsWith(".")) result.remove(result.length() - 1);
if (result == "-0") result = "0";
return result;
}
bool apply_operation(double right_value)
{
switch (pending_operator) {
case '+':
left_value += right_value;
break;
case '-':
left_value -= right_value;
break;
case '*':
left_value *= right_value;
break;
case '/':
if (right_value == 0.0) return false;
left_value /= right_value;
break;
default:
left_value = right_value;
break;
}
return isfinite(left_value);
}
void clear_calculator()
{
entry = "0";
left_value = 0.0;
pending_operator = 0;
has_left_value = false;
just_evaluated = false;
calc_error = false;
memory_value = 0.0;
memory_valid = false;
}
void draw_key(int x, int y, int w, int h, const char* label, bool highlighted)
{
const uint16_t fill = highlighted ? TFT_GREEN : TFT_DARKGREY;
const uint16_t text = highlighted ? TFT_BLACK : TFT_WHITE;
M5.Display.fillRoundRect(x, y, w, h, 5, fill);
M5.Display.drawRoundRect(x, y, w, h, 5, TFT_WHITE);
M5.Display.setTextColor(text, fill);
M5.Display.drawString(label, x + w / 2, y + h / 2);
}
void redraw_calculator()
{
M5.Display.fillScreen(TFT_BLACK);
M5.Display.setTextDatum(middle_center);
M5.Display.setTextColor(TFT_GREEN, TFT_BLACK);
M5.Display.drawString("Faces Calculator3", M5.Display.width() / 2, 13);
String display = calc_error ? "ERROR" : entry;
if (!calc_error && has_left_value && pending_operator) {
display = format_number(left_value) + String(pending_operator) + entry;
}
// Keep the display panel edges aligned with the four keypad columns.
M5.Display.fillRoundRect(8, 23, 296, 40, 6, TFT_DARKGREY);
M5.Display.drawRoundRect(8, 23, 296, 40, 6, TFT_WHITE);
M5.Display.setTextColor(calc_error ? TFT_RED : TFT_WHITE, TFT_DARKGREY);
M5.Display.setTextDatum(middle_right);
M5.Display.drawString(display, 300, 43);
static const char* labels[] = {
"AC", "M", "%", "/",
"7", "8", "9", "*",
"4", "5", "6", "+",
"1", "2", "3", "-",
".", "0", "+/-", "=",
};
M5.Display.setTextDatum(middle_center);
for (int index = 0; index < 20; ++index) {
const int column = index % 4;
const int row = index / 4;
const int x = 8 + column * 76;
const int y = 67 + row * 34;
draw_key(x, y, 68, 30, labels[index], last_key == labels[index]);
}
}
void handle_key(char value)
{
if (calc_error) clear_calculator();
if (value >= '0' && value <= '9') {
if (just_evaluated || entry == "0") entry = "";
if (entry.length() < 16) entry += value;
if (entry.length() == 0) entry = "0";
just_evaluated = false;
last_key = String(value);
return;
}
if (value == '.') {
if (just_evaluated) entry = "0";
if (entry.indexOf('.') < 0 && entry.length() < 16) entry += ".";
just_evaluated = false;
last_key = ".";
return;
}
if (value == '\b') {
clear_calculator();
last_key = "AC";
return;
}
// Calculator3's physical AC key is reported as ASCII 'A'.
if (value == 'A') {
clear_calculator();
last_key = "AC";
return;
}
// The single M key acts as store on first press and recall afterwards.
if (value == 'M') {
if (memory_valid) {
entry = format_number(memory_value);
just_evaluated = true;
} else {
memory_value = entry.toDouble();
memory_valid = true;
}
last_key = "M";
return;
}
if (value == '%') {
entry = format_number(entry.toDouble() / 100.0);
last_key = "%";
just_evaluated = true;
return;
}
// Calculator3 reports the physical +/- key as ASCII backtick (0x60).
if (value == '`') {
if (entry.startsWith("-")) {
entry.remove(0, 1);
} else if (entry != "0") {
entry = "-" + entry;
}
last_key = "+/-";
just_evaluated = false;
return;
}
if (value == '+' || value == '-' || value == '*' || value == '/') {
const double current = entry.toDouble();
if (!has_left_value) {
left_value = current;
has_left_value = true;
} else if (pending_operator && !apply_operation(current)) {
calc_error = true;
last_key = String(value);
return;
}
pending_operator = value;
entry = "0";
just_evaluated = false;
last_key = String(value);
return;
}
if (value == '=' || value == '\n') {
if (has_left_value && pending_operator) {
if (!apply_operation(entry.toDouble())) {
calc_error = true;
} else {
entry = format_number(left_value);
}
has_left_value = false;
pending_operator = 0;
just_evaluated = true;
}
last_key = "=";
}
}
} // namespace
void setup()
{
Serial.begin(115200);
auto config = M5.config();
config.fallback_board = m5::board_t::board_M5StackCoreS3;
M5.begin(config);
if (!M5.In_I2C.isEnabled()) {
const int sda = M5.getPin(m5::pin_name_t::in_i2c_sda);
const int scl = M5.getPin(m5::pin_name_t::in_i2c_scl);
M5.In_I2C.begin(I2C_NUM_1, sda, scl);
}
M5.Display.setRotation(1);
M5.Display.setFont(&fonts::FreeMonoBold12pt7b);
M5.Display.setTextSize(1);
M5.Display.setTextWrap(false);
M5.Display.setTextScroll(false);
redraw_calculator();
if (calculator.begin(&M5.In_I2C) != M5FACES_OK) {
Serial.println("Calculator3 init failed");
M5.Display.setTextDatum(middle_center);
M5.Display.setTextColor(TFT_RED, TFT_BLACK);
M5.Display.drawString("init failed", M5.Display.width() / 2, 120);
return;
}
Serial.println("Calculator3 ready");
redraw_calculator();
}
void loop()
{
if (calculator.update()) {
const uint8_t raw = calculator.getKey();
const char value = calculator.getChar();
const char* name = M5Faces_Calculator3::calc3_code_parse(raw);
Serial.printf("Calculator3 raw=0x%02X key=%s", raw, name ? name : "UNKNOWN");
if (value >= ' ' && value <= '~') Serial.printf(" value='%c'", value);
Serial.println();
if (value != '\0') {
handle_key(value);
redraw_calculator();
}
}
delay(5);
}
Arduino IDE で CoreS3 に対応するボードとポートを選択し、「検証」をクリックしてプログラムをコンパイルします。コンパイルに成功したら「書き込み」をクリックして CoreS3 に書き込み、完了後にシリアルモニターで実行結果を確認できます。