#include <Wire.h>
#include <SPI.h>
// ===== Pin =====
#define CS_MATRIX PB0 // SPI #1: MAX7219 (chip select)
#define LATCH_595 PB1 // SPI #2: 74HC595 (latch)
const int TOMBOL[3] = {PB12, PB13, PB14}; // tombol kandidat A, B, C
// ===== Alamat I2C =====
#define LCD_ADDR 0x27 // I2C #1: LCD 1602
#define RTC_ADDR 0x68 // I2C #2: RTC DS1307
// ===== Pengaturan voting =====
// RTC diset 08:00:00 saat start. Voting ditutup pukul 08:02:00 (2 menit).
const int JAM_TUTUP = 8, MENIT_TUTUP = 1;
int suara[3] = {0, 0, 0};
bool terbuka = true;
bool sedangDitekan[3] = {false, false, false};
bool perluTampil = true;
int detikLama = -1;
int jam, menit, detik;
SPISettings pengaturanSPI(1000000, MSBFIRST, SPI_MODE0);
// ---------- LCD 1602 lewat PCF8574 (I2C) ----------
void tulisPCF(uint8_t d) {
Wire.beginTransmission(LCD_ADDR);
Wire.write(d | 0x08); // bit 3 = lampu latar
Wire.endTransmission();
}
void kirimNibble(uint8_t n, uint8_t rs) {
uint8_t d = (n << 4) | rs;
tulisPCF(d | 0x04); // E = 1
delayMicroseconds(2);
tulisPCF(d); // E = 0
delayMicroseconds(50);
}
void kirimByte(uint8_t b, uint8_t rs) {
kirimNibble(b >> 4, rs);
kirimNibble(b & 0x0F, rs);
}
void lcdInit() {
delay(50);
kirimNibble(0x03, 0); delay(5);
kirimNibble(0x03, 0); delayMicroseconds(150);
kirimNibble(0x03, 0);
kirimNibble(0x02, 0); // mode 4-bit
kirimByte(0x28, 0); // 2 baris
kirimByte(0x0C, 0); // display on
kirimByte(0x06, 0);
kirimByte(0x01, 0); delay(3); // hapus layar
}
void lcdBaris(uint8_t baris, const char* teks) {
kirimByte(0x80 | (baris ? 0x40 : 0x00), 0);
uint8_t i = 0;
for (; i < 16 && teks[i]; i++) kirimByte(teks[i], 1);
for (; i < 16; i++) kirimByte(' ', 1); // sisa baris diisi spasi
}
// ---------- RTC DS1307 (I2C) ----------
uint8_t keBCD(int v) { return ((v / 10) << 4) | (v % 10); }
int dariBCD(int v) { return (v >> 4) * 10 + (v & 0x0F); }
void aturWaktuAwal() { // set 08:00:00
Wire.beginTransmission(RTC_ADDR);
Wire.write(0);
Wire.write(keBCD(0)); // detik
Wire.write(keBCD(0)); // menit
Wire.write(keBCD(8)); // jam
Wire.write(keBCD(1)); // hari dalam seminggu (tidak dipakai)
Wire.write(keBCD(7)); // tanggal
Wire.write(keBCD(10)); // bulan
Wire.write(keBCD(26)); // tahun
Wire.endTransmission();
}
bool bacaWaktu() {
Wire.beginTransmission(RTC_ADDR);
Wire.write(0);
if (Wire.endTransmission() != 0) return false;
if (Wire.requestFrom((uint8_t)RTC_ADDR, (uint8_t)7) != 7) return false;
detik = dariBCD(Wire.read() & 0x7F);
menit = dariBCD(Wire.read());
jam = dariBCD(Wire.read() & 0x3F);
return true;
}
bool waktuHabis() {
return jam > JAM_TUTUP || (jam == JAM_TUTUP && menit >= MENIT_TUTUP);
}
// ---------- MAX7219 matrix (SPI) ----------
void maxKirim(uint8_t reg, uint8_t data) {
SPI.beginTransaction(pengaturanSPI);
digitalWrite(CS_MATRIX, LOW);
SPI.transfer(reg);
SPI.transfer(data);
digitalWrite(CS_MATRIX, HIGH);
SPI.endTransaction();
}
void maxInit() {
maxKirim(0x0F, 0x00); // test off
maxKirim(0x0C, 0x01); // nyalakan chip
maxKirim(0x09, 0x00); // tanpa decode
maxKirim(0x0B, 0x07); // pakai 8 baris
maxKirim(0x0A, 0x03); // kecerahan
}
// Kolom tiap batang: A = kiri, B = tengah, C = kanan
const uint8_t MASK[3] = {0b11000000, 0b00011000, 0b00000011};
void gambarGrafik() {
for (int baris = 0; baris < 8; baris++) {
uint8_t data = 0;
for (int k = 0; k < 3; k++) {
int tinggi = min(suara[k], 8); // 1 suara = 1 baris, maksimum 8
if (baris >= 8 - tinggi) data |= MASK[k];
}
maxKirim(baris + 1, data);
}
}
// ---------- 74HC595 + LED (SPI) ----------
void kirimLED(uint8_t data) {
SPI.beginTransaction(pengaturanSPI);
SPI.transfer(data);
SPI.endTransaction();
digitalWrite(LATCH_595, HIGH); // pulsa latch: data keluar ke LED
digitalWrite(LATCH_595, LOW);
}
// ---------- Logika voting ----------
int pemimpin() { // 0=A, 1=B, 2=C, -1 = seri atau belum ada suara
int maks = max(suara[0], max(suara[1], suara[2]));
if (maks == 0) return -1;
int jumlah = 0, idx = -1;
for (int k = 0; k < 3; k++)
if (suara[k] == maks) { jumlah++; idx = k; }
return (jumlah == 1) ? idx : -1;
}
void tampilkanLCD() {
char b[20];
snprintf(b, sizeof(b), "A:%02d B:%02d C:%02d", suara[0], suara[1], suara[2]);
lcdBaris(0, b);
if (terbuka) {
snprintf(b, sizeof(b), "%02d:%02d:%02d BUKA", jam, menit, detik);
} else {
int p = pemimpin();
if (p < 0) snprintf(b, sizeof(b), "TUTUP - SERI");
else snprintf(b, sizeof(b), "TUTUP Menang: %c", 'A' + p);
}
lcdBaris(1, b);
}
void tampilkanLED() {
uint8_t led = 0;
int p = pemimpin();
if (p >= 0) led |= (1 << p); // LED 1-3: kandidat yang memimpin
if (terbuka) led |= (1 << 3); // LED 4: voting sedang dibuka
kirimLED(led);
}
// ---------- Program utama ----------
void setup() {
Serial.begin(115200);
for (int k = 0; k < 3; k++) pinMode(TOMBOL[k], INPUT_PULLUP);
pinMode(CS_MATRIX, OUTPUT); digitalWrite(CS_MATRIX, HIGH);
pinMode(LATCH_595, OUTPUT); digitalWrite(LATCH_595, LOW);
Wire.begin(); // I2C: PB6 (SCL), PB7 (SDA)
SPI.begin(); // SPI: PA5 (SCK), PA7 (MOSI)
lcdInit();
maxInit();
aturWaktuAwal();
lcdBaris(0, "VOTING DIGITAL");
lcdBaris(1, bacaWaktu() ? "RTC OK" : "RTC ERROR");
delay(1500);
}
void loop() {
// 1. Baca jam dari RTC
bacaWaktu();
// 2. Tutup voting kalau waktu habis
if (terbuka && waktuHabis()) {
terbuka = false;
perluTampil = true;
Serial.println("Voting DITUTUP");
Serial.print("A="); Serial.print(suara[0]);
Serial.print(" B="); Serial.print(suara[1]);
Serial.print(" C="); Serial.println(suara[2]);
}
// 3. Baca tombol: satu kali tekan = satu suara (hanya saat voting dibuka)
for (int k = 0; k < 3; k++) {
bool ditekan = (digitalRead(TOMBOL[k]) == LOW);
if (ditekan && !sedangDitekan[k] && terbuka) {
suara[k]++;
perluTampil = true;
}
sedangDitekan[k] = ditekan;
}
// 4. Perbarui jam di LCD tiap detik saat voting dibuka
if (terbuka && detik != detikLama) {
detikLama = detik;
perluTampil = true;
}
// 5. Perbarui semua tampilan hanya kalau ada yang berubah
if (perluTampil) {
perluTampil = false;
tampilkanLCD(); // I2C: LCD
gambarGrafik(); // SPI: MAX7219
tampilkanLED(); // SPI: 74HC595
}
delay(20);
}Loading
stm32-bluepill
stm32-bluepill