// ============================================================================
// UJI INTERFACE - STM32 Blue Pill (STM32F103C8) + Wokwi
//
// Mengetes SELURUH interface / periferal yang terhubung ke pin IO:
//
// [1] GPIO OUTPUT : LED onboard PC13
// + LED0..LED5 = display biner 6-bit (LED0=LSB, LED5=MSB)
// + LED indikator Y/R/G (PA10, PA15, PB10)
// + Buzzer (PB11)
// [2] GPIO INPUT : 5x tombol (AKTIF LOW / INPUT_PULLUP)
// [3] ADC : Potensiometer PA1 (POT1) & PB0 (POT2)
// [4] I2C : OLED SSD1306 0.96" (PB6=SCL, PB7=SDA, alamat 0x3C)
// [5] UART : Serial2 / USART2 115200 (PA2=TX, PA3=RX) -> Serial Monitor
//
// Alur:
// setup() -> self-test otomatis seluruh interface (hasil di Serial Monitor)
// loop() -> mode interaktif: LED ikut tombol, nilai potensiometer & status
// tombol ditampilkan di OLED dan Serial Monitor.
//
// Catatan hardware asli: PA15/PB3/PB4 secara default dipakai JTAG. Supaya bisa
// jadi GPIO, JTAG dimatikan (SWD tetap aktif) di awal setup. Di Wokwi tidak
// diperlukan, tapi aman.
// ============================================================================
#include <Arduino.h>
#include <stdio.h>
#include <stdlib.h>
#if defined(STM32F1)
#include <stm32f1xx_hal.h> // utk membebaskan pin JTAG (PA15/PB3/PB4)
#endif
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// ---------------------------------------------------------------------------
// Definisi pin
// ---------------------------------------------------------------------------
#define LED_BUILTIN_PIN PC13 // LED onboard biru (AKTIF LOW)
#define BUZZER_PIN PB11 // Buzzer (Wokwi: PB11 | hardware asli: PB2)
// ---------------------------------------------------------------------------
// LED0..LED5 = DISPLAY BINER 6-bit (AKTIF HIGH)
// Indeks array = bit ke-n; index 0 (LED0) = LSB, index 5 (LED5) = MSB.
// ---------------------------------------------------------------------------
#define LED0_PIN PA9 // bit 0 (LSB)
#define LED1_PIN PA8 // bit 1
#define LED2_PIN PB15 // bit 2
#define LED3_PIN PB14 // bit 3
#define LED4_PIN PB13 // bit 4
#define LED5_PIN PB12 // bit 5 (MSB)
static const uint8_t binLedPins[] = { LED0_PIN, LED1_PIN, LED2_PIN,
LED3_PIN, LED4_PIN, LED5_PIN };
#define NUM_BIN_LEDS (sizeof(binLedPins) / sizeof(binLedPins[0])) // = 6
// LED indikator terpisah (bukan bagian display biner)
#define LED_YELLOW_PIN PA10
#define LED_RED_PIN PA15
#define LED_GREEN_PIN PB10
static const uint8_t indPins[] = { LED_YELLOW_PIN, LED_RED_PIN, LED_GREEN_PIN };
static const char indNames[][16] = { "PA10/LED_YELLOW", "PA15/LED_RED", "PB10/LED_GREEN" };
#define NUM_IND (sizeof(indPins) / sizeof(indPins[0])) // = 3
// Tombol (AKTIF LOW / INPUT_PULLUP)
static const uint8_t btnPins[] = { PA0, PB4, PB5, PB8, PB9 };
static const char btnNames[][16] = { "PA0/BTN_BUILTIN", "PB4/BTN0", "PB5/BTN1",
"PB8/BTN2", "PB9/BTN3" };
// LED pasangan tiap tombol (indeks sama dengan btnPins):
// BTN_BUILTIN->onboard, BTN0->LED_RED, BTN1->LED_YELLOW, BTN2->LED_GREEN,
// BTN3->LED3 (bit 3 display biner)
static const uint8_t btnLedMap[] = { LED_BUILTIN_PIN, LED_RED_PIN, LED_YELLOW_PIN,
LED_GREEN_PIN, LED3_PIN };
#define NUM_BTNS (sizeof(btnPins) / sizeof(btnPins[0])) // = 5
// Potensiometer (ADC1, resolusi 12-bit)
#define POT1_PIN PA1
#define POT2_PIN PB0
// OLED SSD1306 (I2C1: PB6=SCL, PB7=SDA)
#define OLED_ADDR 0x3C
Adafruit_SSD1306 oled(128, 64, &Wire, -1);
// ---------------------------------------------------------------------------
// Variabel global
// ---------------------------------------------------------------------------
static bool oledOk = false;
#define DEBOUNCE_MS 30
static bool btnStable[NUM_BTNS]; // state tombol setelah debounce
static bool btnLastRaw[NUM_BTNS];
static uint32_t btnDebounceAt[NUM_BTNS];
static uint32_t btnPressCount[NUM_BTNS];
static int lastPot1 = -1;
static int lastPot2 = -1;
static uint32_t lastOledMs = 0;
static uint32_t lastHeartbeatMs = 0;
// ===========================================================================
// Utilitas
// ===========================================================================
// Tulis LED dengan memperhitungkan polaritas (PC13 aktif LOW)
static void ledWrite(uint8_t pin, bool on) {
if (pin == LED_BUILTIN_PIN) on = !on;
digitalWrite(pin, on ? HIGH : LOW);
}
// Tampilkan nilai biner 6-bit pada LED0..LED5 (LED0 = LSB, LED5 = MSB)
static void binShow(uint8_t value) {
for (uint8_t i = 0; i < NUM_BIN_LEDS; i++) {
ledWrite(binLedPins[i], (bool)((value >> i) & 0x01U));
}
}
// Bunyikan buzzer (square wave software, tanpa butuh timer/PWM)
static void buzzerBeep(uint16_t freqHz, uint32_t durMs) {
if (freqHz == 0) { digitalWrite(BUZZER_PIN, LOW); return; }
const uint32_t halfUs = 500000UL / freqHz; // setengah periode (us)
const uint32_t start = millis();
while (millis() - start < durMs) {
digitalWrite(BUZZER_PIN, HIGH);
delayMicroseconds(halfUs);
digitalWrite(BUZZER_PIN, LOW);
delayMicroseconds(halfUs);
}
}
// Tampilkan satu baris teks pada OLED (font default 6x8 -> 8 baris)
static void oledLine(uint8_t row, const char *s) {
oled.setCursor(0, row * 8);
oled.print(s);
}
static void serialHeader(const char *s) {
Serial2.println();
Serial2.println("---------------------------------------------");
Serial2.println(s);
Serial2.println("---------------------------------------------");
}
// ===========================================================================
// SELF TEST - otomatis mengetes seluruh interface saat boot
// ===========================================================================
static void selfTest(void) {
// --- 1) UART: kalau teks ini muncul di Serial Monitor berarti UART OK ----
serialHeader("SELF TEST #1 : UART Serial2 (PA2/PA3)");
Serial2.println("UART : OK (baris ini terkirim lewat USART2)");
// --- 2) LED onboard PC13 (aktif LOW) ------------------------------------
serialHeader("SELF TEST #2 : LED onboard (PC13)");
Serial2.println("LED onboard kedip 3x ...");
for (uint8_t k = 0; k < 3; k++) {
ledWrite(LED_BUILTIN_PIN, true); delay(200);
ledWrite(LED_BUILTIN_PIN, false); delay(200);
}
Serial2.println("LED onboard : OK");
// --- 3) LED0..LED5 = display biner 6-bit (LED0 LSB, LED5 MSB) ------------
serialHeader("SELF TEST #3 : LED0..LED5 = display biner");
Serial2.println("Running LED bit0 -> bit5 (diulang 3x) ...");
for (uint8_t r = 0; r < 3; r++) {
for (uint8_t i = 0; i < NUM_BIN_LEDS; i++) {
binShow((uint8_t)(1U << i)); // nyalakan LED bit ke-i saja
delay(120);
}
}
binShow(0);
Serial2.println("Running LED : OK");
// --- 4) LED indikator Y/R/G (terpisah dari display biner) -----------------
serialHeader("SELF TEST #4 : LED indikator Y/R/G");
for (uint8_t i = 0; i < NUM_IND; i++) {
ledWrite(indPins[i], true);
Serial2.print(" ");
Serial2.print(indNames[i]);
Serial2.println(" -> nyala");
delay(250);
ledWrite(indPins[i], false);
}
Serial2.println("LED indikator : OK");
// --- 5) Buzzer (PB11) -----------------------------------------------------
serialHeader("SELF TEST #5 : Buzzer (PB11)");
Serial2.println("Nada naik: 523Hz -> 659Hz -> 784Hz ...");
buzzerBeep(523, 180); delay(60);
buzzerBeep(659, 180); delay(60);
buzzerBeep(784, 300);
digitalWrite(BUZZER_PIN, LOW);
Serial2.println("Buzzer : OK");
// --- 6) OLED SSD1306 (I2C1) ------------------------------------------------
serialHeader("SELF TEST #6 : OLED SSD1306 (I2C PB6/PB7)");
Serial2.print("OLED : ");
Serial2.println(oledOk ? "OK" : "GAGAL - cek PB6/PB7 & alamat 0x3C");
if (oledOk) {
oled.clearDisplay();
oled.setTextColor(SSD1306_WHITE);
oledLine(0, "== SELF TEST ==");
oledLine(1, "UART : OK");
oledLine(2, oledOk ? "OLED : OK" : "OLED : FAIL");
oledLine(3, "LED : OK");
oledLine(4, "BUZZER : OK");
oledLine(6, "TEKAN TOMBOL /");
oledLine(7, "PUTAR POT ...");
oled.display();
}
Serial2.println();
Serial2.println("SELF TEST SELESAI -> MODE INTERAKTIF");
Serial2.println("Tekan tombol (LED ikut nyala) & putar potensiometer,");
Serial2.println("amati hasilnya di OLED dan Serial Monitor ini.");
}
// ===========================================================================
// Mode interaktif
// ===========================================================================
// Baca + debounce semua tombol, deteksi tepi tekan
static void updateButtons(void) {
const uint32_t now = millis();
for (uint8_t i = 0; i < NUM_BTNS; i++) {
const bool raw = (digitalRead(btnPins[i]) == LOW); // LOW = ditekan
if (raw != btnLastRaw[i]) {
btnLastRaw[i] = raw;
btnDebounceAt[i] = now;
} else if (raw != btnStable[i] && (now - btnDebounceAt[i]) >= DEBOUNCE_MS) {
btnStable[i] = raw;
if (raw) {
btnPressCount[i]++;
Serial2.print(" [TOMBOL] ");
Serial2.print(btnNames[i]);
Serial2.print(" DITEKAN (tekanan ke-");
Serial2.print(btnPressCount[i]);
Serial2.println(")");
}
}
}
}
// LED menyala selama tombol pasangannya ditekan (verifikasi visual)
static void reflectButtons(void) {
for (uint8_t i = 0; i < NUM_BTNS; i++) {
ledWrite(btnLedMap[i], btnStable[i]);
}
}
// Persentase potensiometer (0..100)
static int potPercent(uint8_t pin) {
return (int)map(analogRead(pin), 0, 4095, 0, 100);
}
// Laporkan ke Serial bila nilai potensiometer berubah
static void checkPots(void) {
const int p1 = potPercent(POT1_PIN);
const int p2 = potPercent(POT2_PIN);
if (abs(p1 - lastPot1) >= 2 || abs(p2 - lastPot2) >= 2) {
lastPot1 = p1;
lastPot2 = p2;
Serial2.print(" [POT] POT1/PA1=");
Serial2.print(p1);
Serial2.print("% POT2/PB0=");
Serial2.print(p2);
Serial2.println("%");
}
}
// Kirim baris status ke Serial setiap ~3 detik.
// Berguna karena Serial Monitor Wokwi hanya menampilkan data yang terkirim
// saat monitor sudah terbuka/terlampir (output saat boot sering terlewat).
static void heartbeat(void) {
const uint32_t now = millis();
if (now - lastHeartbeatMs < 3000) return;
lastHeartbeatMs = now;
uint32_t totalPress = 0;
for (uint8_t i = 0; i < NUM_BTNS; i++) totalPress += btnPressCount[i];
Serial2.print(" [OK] sistem berjalan t=");
Serial2.print(now / 1000UL);
Serial2.print("s | total tekan tombol=");
Serial2.print(totalPress);
Serial2.println(" | putar POT utk lihat nilai");
}
// Dashboard OLED (di-refresh tiap ~100ms)
static void oledDashboard(void) {
if (!oledOk) return;
const uint32_t now = millis();
if (now - lastOledMs < 100) return;
lastOledMs = now;
char line[22];
char btn[NUM_BTNS + 1];
for (uint8_t i = 0; i < NUM_BTNS; i++) btn[i] = btnStable[i] ? '1' : '0';
btn[NUM_BTNS] = '\0';
oled.clearDisplay();
oled.setTextColor(SSD1306_WHITE);
oledLine(0, "IO TEST UART:OK");
snprintf(line, sizeof(line), "BTN: %s", btn);
oledLine(1, line);
snprintf(line, sizeof(line), "P1:%3d%% P2:%3d%%", lastPot1, lastPot2);
oledLine(2, line);
// Bar grafik potensiometer
oled.drawRect(0, 26, 127, 9, SSD1306_WHITE);
oled.fillRect(1, 27, map(lastPot1, 0, 100, 0, 125), 7, SSD1306_WHITE);
oled.drawRect(0, 40, 127, 9, SSD1306_WHITE);
oled.fillRect(1, 41, map(lastPot2, 0, 100, 0, 125), 7, SSD1306_WHITE);
oledLine(7, "LED nyala = tombol");
oled.display();
}
// ===========================================================================
// Arduino entry points
// ===========================================================================
void setup(void) {
#if defined(STM32F1)
// Bebaskan PA15/PB3/PB4 dari JTAG agar bisa jadi GPIO (SWD tetap aktif).
__HAL_RCC_AFIO_CLK_ENABLE();
__HAL_AFIO_REMAP_SWJ_NOJTAG();
#endif
Serial2.begin(115200); // USART2 di PA2/TX - PA3/RX
delay(300);
Serial2.println();
Serial2.println("============================================");
Serial2.println(" BLUE PILL IO INTERFACE TEST v1.0");
Serial2.println("============================================");
// --- pin mode: output ---
pinMode(LED_BUILTIN_PIN, OUTPUT);
ledWrite(LED_BUILTIN_PIN, false);
// LED0..LED5 (display biner 6-bit) + LED indikator Y/R/G
for (uint8_t i = 0; i < NUM_BIN_LEDS; i++) {
pinMode(binLedPins[i], OUTPUT);
ledWrite(binLedPins[i], false);
}
for (uint8_t i = 0; i < NUM_IND; i++) {
pinMode(indPins[i], OUTPUT);
ledWrite(indPins[i], false);
}
pinMode(BUZZER_PIN, OUTPUT);
digitalWrite(BUZZER_PIN, LOW);
// --- pin mode: input tombol (pull-up internal, aktif LOW) ---
for (uint8_t i = 0; i < NUM_BTNS; i++) {
pinMode(btnPins[i], INPUT_PULLUP);
btnStable[i] = btnLastRaw[i] = false;
btnDebounceAt[i] = 0;
btnPressCount[i] = 0;
}
// --- pin mode: analog (ADC 12-bit => 0..4095) ---
analogReadResolution(12);
pinMode(POT1_PIN, INPUT_ANALOG);
pinMode(POT2_PIN, INPUT_ANALOG);
// --- OLED / I2C1 (PB6=SCL, PB7=SDA) ---
Wire.begin();
oledOk = oled.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR);
if (oledOk) {
oled.clearDisplay();
oled.setTextColor(SSD1306_WHITE);
oledLine(2, "IO TEST v1.0");
oledLine(5, "Starting ...");
oled.display();
Serial2.println("OLED SSD1306 : terdeteksi @0x3C");
} else {
Serial2.println("PERINGATAN : OLED tidak terdeteksi!");
}
// --- jalankan self-test seluruh interface ---
selfTest();
}
void loop(void) {
updateButtons(); // baca & debounce tombol
reflectButtons(); // LED ikut menyala saat tombol ditekan
checkPots(); // lapor perubahan potensiometer ke Serial
heartbeat(); // kirim status berkala agar Serial Monitor selalu ada isinya
oledDashboard(); // refresh tampilan OLED
delay(5);
}