#include <Encoder.h>
#include <U8g2lib.h>
#include <avr/io.h>
// =======================================
// Pin-Definitionen
// =======================================
#define PIN_PWM_OUT 11 // Hardware-PWM-Ausgang (OC1A, Timer1)
// Frequenz-Encoder (Pins 2 & 3)
#define ENC_FREQ_A 2
#define ENC_FREQ_B 3
// Frequenz-Encoder-Button
#define PIN_FREQ_BUTTON 23 // Taster nach GND, INPUT_PULLUP
// Pulsweiten-Encoder (Pins 18 & 19)
#define ENC_PULSE_A 18
#define ENC_PULSE_B 19
// Pulsweiten-Encoder-Button
#define PIN_PULSE_BUTTON 24 // Taster nach GND, INPUT_PULLUP
// Separater Taster (Output an/aus)
#define PIN_TASTER 22 // Taster nach GND, INPUT_PULLUP
// =======================================
// Display (SSD1306, I2C)
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0);
// =======================================
// Encoder-Objekte
// =======================================
Encoder freqEncoder(ENC_FREQ_A, ENC_FREQ_B);
Encoder pulseEncoder(ENC_PULSE_A, ENC_PULSE_B);
// =======================================
// Einstellbare Parameter
// =======================================
// Frequenz: 30..1000 Hz, start=30
// Pulsweite: 0..400 us, start=0
volatile int frequency = 30;
volatile int pulseWidth = 0;
// Letzte bekannte Encoder-Positionen
long lastFreqPos = 0;
long lastPulsePos = 0;
// Accumulatoren (4 Flanken => 1 Rastung)
long freqAcc = 0;
long pulseAcc = 0;
// Output an/aus (start=false)
bool outputEnabled = false;
// Kurze Meldung bei Umschalten
bool showOutputMsg = false;
unsigned long msgUntil = 0;
// Taster-Abfrage
bool oldButtonState = true; // globaler Taster (Pin22)
// =======================================
// Timer1: Fast-PWM, ICR1=TOP, OCR1A=Duty
// =======================================
void updateTimer1(int freq, int pw_us);
// =======================================
// Splash-Screen (2,5 s)
// =======================================
void showSplashScreen() {
unsigned long start = millis();
while (millis() - start < 2500) {
u8g2.firstPage();
do {
u8g2.setFont(u8g2_font_ncenB08_tr);
const char* splash = "NG Interrupter";
uint8_t w = u8g2.getStrWidth(splash);
uint8_t x = (128 - w) / 2;
u8g2.drawStr(x, 32, splash);
} while(u8g2.nextPage());
}
}
void setup() {
pinMode(PIN_PWM_OUT, OUTPUT);
digitalWrite(PIN_PWM_OUT, LOW);
// Taster
pinMode(PIN_TASTER, INPUT_PULLUP); // Output on/off
pinMode(PIN_FREQ_BUTTON, INPUT_PULLUP); // Freq-Encoder push
pinMode(PIN_PULSE_BUTTON, INPUT_PULLUP); // Pulse-Encoder push
// Serial.begin(9600); <-- entfernt
u8g2.begin();
// Splash-Screen (2,5s)
showSplashScreen();
// Encoder-Startwerte
lastFreqPos = freqEncoder.read();
lastPulsePos = pulseEncoder.read();
// Timer1: Fast PWM, Mode=14
TCCR1A = 0;
TCCR1B = 0;
// WGM13:1, WGM12:1, WGM11:1 => Mode=14
TCCR1A |= (1 << WGM11);
TCCR1B |= (1 << WGM12) | (1 << WGM13);
// COM1A1=1 => non-inverting, wir starten AUS
TCCR1A &= ~(1 << COM1A1);
// Anfangswerte
updateTimer1(frequency, pulseWidth);
}
void loop() {
// --------------------------------------------
// 1) Globaler Taster (Output an/aus)
// --------------------------------------------
bool currentButton = digitalRead(PIN_TASTER);
if (oldButtonState == true && currentButton == false) {
// Toggle
outputEnabled = !outputEnabled;
showOutputMsg = true;
msgUntil = millis() + 500;
if (outputEnabled) {
// PWM an
TCCR1A |= (1 << COM1A1);
updateTimer1(frequency, pulseWidth);
// Serial.println("OUTPUT: ON"); <-- entfernt
} else {
// PWM aus
TCCR1A &= ~(1 << COM1A1);
digitalWrite(PIN_PWM_OUT, LOW);
// Serial.println("OUTPUT: OFF"); <-- entfernt
}
}
oldButtonState = currentButton;
// --------------------------------------------
// 2) Frequenz-Encoder
// --------------------------------------------
{
long newFreqPos = freqEncoder.read();
// Invertierte Richtung => rawDeltaF = (lastFreqPos - newFreqPos)
long rawDeltaF = (lastFreqPos - newFreqPos);
if (rawDeltaF != 0) {
lastFreqPos = newFreqPos;
freqAcc += rawDeltaF;
// => pro 4 Flanken => 1 Rastung
while (freqAcc >= 4) {
freqAcc -= 4;
// Drückt man den Frequenz-Encoder?
bool freqPressed = (digitalRead(PIN_FREQ_BUTTON) == LOW);
// Schrittgröße: wenn gedrückt => 10, sonst => 1
int step = freqPressed ? 10 : 1;
frequency += step;
}
while (freqAcc <= -4) {
freqAcc += 4;
bool freqPressed = (digitalRead(PIN_FREQ_BUTTON) == LOW);
int step = freqPressed ? 10 : 1;
frequency -= step;
}
if (frequency < 30) frequency = 30;
if (frequency > 1000) frequency = 1000;
if (outputEnabled) {
updateTimer1(frequency, pulseWidth);
}
// Serial.print("Frequency => "); <-- entfernt
// Serial.println(frequency); <-- entfernt
}
}
// --------------------------------------------
// 3) Pulsweiten-Encoder
// --------------------------------------------
{
long newPulsePos = pulseEncoder.read();
// Invertierte Richtung => rawDeltaP = (lastPulsePos - newPulsePos)
long rawDeltaP = (lastPulsePos - newPulsePos);
if (rawDeltaP != 0) {
lastPulsePos = newPulsePos;
pulseAcc += rawDeltaP;
// => pro 4 Flanken => 1 Rastung
while (pulseAcc >= 4) {
pulseAcc -= 4;
bool pulsePressed = (digitalRead(PIN_PULSE_BUTTON) == LOW);
// Schrittgröße: wenn gedrückt => +5, sonst => +1
int step = pulsePressed ? 5 : 1;
pulseWidth += step;
}
while (pulseAcc <= -4) {
pulseAcc += 4;
bool pulsePressed = (digitalRead(PIN_PULSE_BUTTON) == LOW);
int step = pulsePressed ? 5 : 1;
pulseWidth -= step;
}
if (pulseWidth < 0) pulseWidth = 0;
if (pulseWidth > 400) pulseWidth = 400;
if (outputEnabled) {
updateTimer1(frequency, pulseWidth);
}
// Serial.print("Pulse => "); <-- entfernt
// Serial.println(pulseWidth); <-- entfernt
}
}
// --------------------------------------------
// 4) Display
// --------------------------------------------
u8g2.firstPage();
do {
u8g2.setFont(u8g2_font_ncenB08_tr);
// Zeile 1: Frequenz
char buf[24];
sprintf(buf, "Freq: %d Hz", frequency);
uint8_t w = u8g2.getStrWidth(buf);
uint8_t x = (128 - w) / 2;
u8g2.drawStr(x, 12, buf);
// Zeile 2: Pulsweite
sprintf(buf, "Pulse: %d us", pulseWidth);
w = u8g2.getStrWidth(buf);
x = (128 - w) / 2;
u8g2.drawStr(x, 28, buf);
// Zeile 3: OUTPUT: ON/OFF Meldung
if (showOutputMsg) {
const char* outText = outputEnabled ? "OUTPUT: ON" : "OUTPUT: OFF";
w = u8g2.getStrWidth(outText);
x = (128 - w) / 2;
u8g2.drawStr(x, 44, outText);
}
} while(u8g2.nextPage());
// Meldung ausblenden nach 0,5 s
if (showOutputMsg && (millis() > msgUntil)) {
showOutputMsg = false;
}
// Kein Encoder-Delay => loop() läuft schnell
delay(5);
}
// =======================================
// Timer1-Update
// =======================================
void updateTimer1(int freq, int pw_us) {
if (!outputEnabled) {
digitalWrite(PIN_PWM_OUT, LOW);
return;
}
if (freq < 30) freq = 30;
if (freq > 1000) freq = 1000;
if (pw_us < 0) pw_us = 0;
if (pw_us > 400) pw_us = 400;
unsigned long period_us = 1000000UL / freq;
byte prescalers[5] = {1, 8, 64, 256, 1024};
byte chosenPrescaler = 1;
unsigned long top = 0;
for (int i = 0; i < 5; i++) {
unsigned long ps = prescalers[i];
unsigned long t = period_us * (F_CPU / ps / 1000000UL);
if (t <= 65535UL) {
chosenPrescaler = ps;
top = t;
break;
}
}
if (top == 0) {
chosenPrescaler = 1024;
top = 65535UL;
}
ICR1 = (uint16_t) top;
unsigned long pw_ticks = pw_us * (F_CPU / chosenPrescaler / 1000000UL);
if (pw_ticks > top) {
pw_ticks = top;
}
OCR1A = (uint16_t) pw_ticks;
// Prescaler bits
TCCR1B &= ~((1 << CS12) | (1 << CS11) | (1 << CS10));
switch(chosenPrescaler) {
case 1: TCCR1B |= (1 << CS10); break;
case 8: TCCR1B |= (1 << CS11); break;
case 64: TCCR1B |= (1 << CS11) | (1 << CS10); break;
case 256: TCCR1B |= (1 << CS12); break;
case 1024: TCCR1B |= (1 << CS12) | (1 << CS10); break;
}
}