
G12 (SDA) and G11 (SCL), and the interrupt pin is 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);
}
In Arduino IDE, select the board and port for CoreS3, then click "Verify" to compile the program. After compilation succeeds, click "Upload" to flash it to CoreS3. When the upload is complete, open the Serial Monitor to view the output.