// Macropad: 3 Cherry MX keys + OLED 0.96" SSD1306 on an Arduino Nano.
// Short press -> serial "K1".."K3" for the PC helper. Hold any key 0.6 s -> next screen.
// PC -> "S:cpu_load,gpu_load,ram_load,cpu_temp,gpu_temp,ram_temp" ("-" = unknown).
// Serial 115200. "?" -> "MACROPAD 2" so the helper can find the right port.
// Library: U8g2, page mode (128-byte buffer instead of 1 KB, fits the Nano's 2 KB RAM).
#include <EEPROM.h>
#include <U8g2lib.h>
#include <Wire.h>
#include "macropad_core.h"
using namespace macropad;
// K1 is the left key: D4, D3, D2 sit left to right on the Nano, so the wires do not cross.
const uint8_t KEY_PINS[KEY_COUNT] = {4, 3, 2};
const char HELLO[] = "MACROPAD 2";
const uint8_t PAGE_COUNT = 3;
const int EEPROM_PAGE_ADDR = 0;
// The OLED header points toward the keys, so the picture is turned 180 degrees.
#define DISPLAY_ROTATION U8G2_R2
U8G2_SSD1306_128X64_NONAME_1_HW_I2C oled(DISPLAY_ROTATION, U8X8_PIN_NONE);
KeyTracker keys[KEY_COUNT];
char line[40];
uint8_t line_len = 0;
bool line_overflow = false;
int16_t stats[STAT_COUNT] = {UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN};
uint32_t stats_at = 0;
bool have_stats = false;
bool shown_online = false;
uint8_t page = 0;
const char *const LABELS[3] = {"CPU", "GPU", "RAM"};
const char DEGREE_C[] = "\xb0" "C";
bool online() {
return have_stats && millis() - stats_at < STATS_TIMEOUT_MS;
}
int16_t stat(uint8_t i) {
return online() ? stats[i] : UNKNOWN;
}
void draw_right(int16_t right, int16_t baseline, const char *text) {
oled.drawStr(right - oled.getStrWidth(text), baseline, text);
}
// Page 0: everything at once, a small table.
void draw_overview() {
char text[8];
oled.setFont(u8g2_font_6x10_tf);
if (online()) {
draw_right(78, 9, "LOAD");
draw_right(127, 9, "TEMP");
} else {
oled.drawStr(0, 9, "no PC");
}
oled.setFont(u8g2_font_7x14B_tf);
for (uint8_t row = 0; row < 3; row++) {
int16_t y = 26 + row * 18;
oled.drawStr(0, y, LABELS[row]);
format_value(text, stat(CPU_LOAD + row), "%");
draw_right(78, y, text);
format_value(text, stat(CPU_TEMP + row), DEGREE_C);
draw_right(127, y, text);
}
}
// Pages 1 and 2: one bar per device.
void draw_bars(uint8_t first, int16_t lo, int16_t hi, const char *suffix) {
const uint8_t BAR_X = 30, BAR_W = 62, BAR_H = 12;
char text[8];
oled.setFont(u8g2_font_7x14B_tf);
for (uint8_t row = 0; row < 3; row++) {
int16_t top = row * 22;
int16_t value = stat(first + row);
oled.drawStr(0, top + 15, LABELS[row]);
oled.drawFrame(BAR_X, top + 4, BAR_W, BAR_H);
oled.drawBox(BAR_X + 2, top + 6, bar_fill(value, lo, hi, BAR_W - 4), BAR_H - 4);
format_value(text, value, suffix);
draw_right(127, top + 15, text);
}
}
void redraw() {
oled.firstPage();
do {
if (page == 0) {
draw_overview();
} else if (page == 1) {
draw_bars(CPU_LOAD, 0, 100, "%");
} else {
draw_bars(CPU_TEMP, 20, 100, DEGREE_C);
}
} while (oled.nextPage());
shown_online = online();
}
void next_page() {
page = (page + 1) % PAGE_COUNT;
EEPROM.update(EEPROM_PAGE_ADDR, page);
redraw();
}
void handle_line() {
if (strcmp(line, "?") == 0) {
Serial.println(HELLO);
return;
}
if (parse_stats(line, stats)) {
stats_at = millis();
have_stats = true;
redraw();
}
}
void read_serial() {
while (Serial.available() > 0) {
char ch = Serial.read();
if (ch == '\r') {
continue;
}
if (ch == '\n') {
line[line_len] = '\0';
if (!line_overflow && line_len > 0) {
handle_line();
}
line_len = 0;
line_overflow = false;
continue;
}
if (line_len < sizeof(line) - 1) {
line[line_len++] = ch;
} else {
line_overflow = true;
}
}
}
void setup() {
for (uint8_t i = 0; i < KEY_COUNT; i++) {
pinMode(KEY_PINS[i], INPUT_PULLUP);
}
page = EEPROM.read(EEPROM_PAGE_ADDR);
if (page >= PAGE_COUNT) {
page = 0;
}
Serial.begin(115200);
oled.begin();
redraw();
}
void loop() {
uint32_t now = millis();
for (uint8_t i = 0; i < KEY_COUNT; i++) {
KeyEvent e = keys[i].update(digitalRead(KEY_PINS[i]) == LOW, now);
if (e == TAP) {
Serial.print('K');
Serial.println(i + 1);
}
if (e == HOLD) {
next_page();
}
}
read_serial();
if (online() != shown_online) {
redraw();
}
}
Loading
ssd1306
ssd1306
Nano: USB від ПК
5V і дані
K1, K2, K3: у макропаді це свічі Cherry MX
OLED перевернутий, як у корпусі