/*
* 🧩 Inversor SPWM para ATtiny85 @ 16 MHz — FRECUENCIA FIJA DEFINIDA EN CONSTANTE
* ✔️ Frecuencia fundamental definida en #define TARGET_FREQ (ej: 60 Hz)
* ✔️ OCR1C se calcula automáticamente en setup() para máxima precisión
* ✔️ Dead-time de 2us en flancos de subida
* ✔️ Control de amplitud inverso por A3 (PB3)
* ✔️ Protección de sobre corriente en A1 (PB2) con reinicio automático
* ✔️ Salidas: PB0 (OC0A) y PB1 (OC0B)
* ✔️ Filtro de amplitud para estabilidad
* ✔️ Código robusto, a prueba de fallos
*/
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
// ==============================
// 🔧 PARÁMETROS CONFIGURABLES (¡SOLO CAMBIA AQUÍ!)
// ==============================
#define TARGET_FREQ_HZ 60.0 // ← Cambia aquí la frecuencia deseada (50, 60, 400, etc)
#define SAMPLES 64
#define DEAD_TIME_US 2
#define DEAD_TIME_CYCLES (DEAD_TIME_US * 16) // 2us * 16MHz = 32 ciclos
#define CURRENT_THRESHOLD 800
#define CURRENT_HYSTERESIS 50
#define RECOVERY_TIME_MS 2000
#define AMPLITUDE_FILTER_SIZE 4
// ==============================
// 📊 TABLA DE SENO PRECISA (128 muestras, centrada en 128)
// ==============================
const uint8_t sineTable[SAMPLES] PROGMEM = {
128, 140, 152, 165, 176, 188, 198, 208, 218, 226, 234, 240, 245, 250, 253, 254, 255, 254, 253, 250,
245, 240, 234, 226, 218, 208, 198, 188, 176, 165, 152, 140, 128, 115, 103, 90, 79, 67, 57, 47,
37, 29, 21, 15, 10, 5, 2, 1, 0, 1, 2, 5, 10, 15, 21, 29, 37, 47, 57, 67,
79, 90, 103, 115
};
// ==============================
// 🧠 VARIABLES GLOBALES
// ==============================
volatile uint8_t sampleIndex = 0;
volatile uint8_t currentAmplitude = 255;
volatile uint16_t amplitudeBuffer[AMPLITUDE_FILTER_SIZE];
volatile uint8_t ampBufferIndex = 0;
volatile bool overCurrent = false;
volatile uint32_t overCurrentStart = 0;
// ==============================
// ⚙️ SETUP — ¡CÁLCULO AUTOMÁTICO DE OCR1C!
// ==============================
void setup() {
// Salidas PWM
pinMode(0, OUTPUT); // OC0A (PB0)
pinMode(1, OUTPUT); // OC0B (PB1)
// Entradas
pinMode(A1, INPUT); // Corriente (PB2)
pinMode(A3, INPUT); // Amplitud (PB3)
// Inicializar buffer de amplitud
for (uint8_t i = 0; i < AMPLITUDE_FILTER_SIZE; i++) {
amplitudeBuffer[i] = 1023;
}
// Configurar Timer0: Fast PWM 8-bit, no inversor
TCCR0A = (1 << COM0A1) | (1 << COM0B1) | (1 << WGM01) | (1 << WGM00);
TCCR0B = (1 << CS00); // Sin prescaler → 62.5 kHz
// ✅ CÁLCULO AUTOMÁTICO DE OCR1C PARA TIMER1
// Queremos: Fs = TARGET_FREQ * SAMPLES
// Timer1 en modo CTC, con prescaler 8
// OCR1C = (F_CPU / (prescaler * Fs)) - 1
// Fs = TARGET_FREQ * SAMPLES
const float Fs = TARGET_FREQ_HZ * SAMPLES; // Ej: 60 * 128 = 7680 Hz
const uint16_t prescaler = 8;
uint16_t ocr1c_val = (uint16_t)((F_CPU / (prescaler * Fs)) - 1.0);
// Verificar límites (OCR1C es de 8 bits → 0 a 255)
if (ocr1c_val > 255) ocr1c_val = 255;
if (ocr1c_val < 1) ocr1c_val = 1;
// Configurar Timer1
TCCR1 = (1 << CTC1) | (1 << CS11); // Prescaler 8
OCR1C = (uint8_t)ocr1c_val;
OCR1A = OCR1C; // Usamos OCR1A para la interrupción
TIMSK |= (1 << OCIE1A); // Habilitar interrupción
sei(); // Habilitar interrupciones globales
// 📌 Opcional: imprimir valor calculado (si tuvieras UART, para debug)
// Serial.print("TARGET_FREQ: "); Serial.print(TARGET_FREQ_HZ);
// Serial.print(" Hz → OCR1C = "); Serial.println(ocr1c_val);
}
// ==============================
// 🔄 ISR: ACTUALIZACIÓN DE MUESTRA
// ==============================
ISR(TIM1_COMPA_vect) {
static uint8_t lastSine = 128;
static uint8_t lastInverted = 127;
// Manejo de sobre corriente
if (overCurrent) {
if ((millis() - overCurrentStart) > RECOVERY_TIME_MS) {
overCurrent = false;
} else {
OCR0A = 0;
OCR0B = 0;
return;
}
}
// --- ACTUALIZAR AMPLITUD ---
uint16_t rawAmp = analogRead(A3);
amplitudeBuffer[ampBufferIndex] = rawAmp;
ampBufferIndex = (ampBufferIndex + 1) % AMPLITUDE_FILTER_SIZE;
uint32_t sumAmp = 0;
for (uint8_t i = 0; i < AMPLITUDE_FILTER_SIZE; i++) {
sumAmp += amplitudeBuffer[i];
}
uint16_t avgAmp = sumAmp / AMPLITUDE_FILTER_SIZE;
currentAmplitude = map(avgAmp, 0, 1023, 255, 0);
currentAmplitude = constrain(currentAmplitude, 0, 255);
// --- VERIFICAR CORRIENTE ---
uint16_t currentSense = analogRead(A1);
if (currentSense > (CURRENT_THRESHOLD + (overCurrent ? -CURRENT_HYSTERESIS : 0))) {
if (!overCurrent) {
overCurrent = true;
overCurrentStart = millis();
}
OCR0A = 0;
OCR0B = 0;
return;
}
// --- GENERAR PWM ---
uint8_t rawSine = pgm_read_byte(&sineTable[sampleIndex]);
int16_t centered = (int16_t)rawSine - 128;
centered = (centered * currentAmplitude) >> 8; // Escala por amplitud
uint8_t newSine = constrain(centered + 128, 0, 255);
uint8_t newInverted = 255 - newSine;
// Aplicar dead-time solo en flancos de subida
bool risingA = (newSine > 10) && (lastSine <= 10);
bool risingB = (newInverted > 10) && (lastInverted <= 10);
if (risingA || risingB) {
OCR0A = 0;
OCR0B = 0;
__builtin_avr_delay_cycles(DEAD_TIME_CYCLES);
}
OCR0A = newSine;
OCR0B = newInverted;
lastSine = newSine;
lastInverted = newInverted;
sampleIndex = (sampleIndex + 1) % SAMPLES;
}
// ==============================
// 🌀 LOOP PRINCIPAL
// ==============================
void loop() {
// Todo se maneja por interrupciones. Vacío.
}