/*
* ATtiny85
┌──────┐
RST ─┤1 8├─ VCC (5V)
ADC0 ─┤2 7├─ PB2 (ADC1 - Voltage FB)
PB4 ─┤3 6├─ PB1 (OC0B - PWM A Low)
GND ─┤4 5├─ PB0 (OC0A - PWM A High)
└──────┘
PB3 = H-Bridge B High
PB4 = H-Bridge B Low
Vout+ ──[47kΩ]──┬──[10kΩ]── GND
│
PB2 (ADC1)
Vout- ──[Diodo]──[10kΩ]──┬── VCC
│
PB5 (ADC0)
* ══════════════════════════════════════════════════════════════
* INVERSOR SENOIDAL PWM - ATtiny85 @ 8MHz
* ══════════════════════════════════════════════════════════════
* Compatible con arquitectura AVR25
* Compilar con: Arduino IDE + ATtiny Core
* ══════════════════════════════════════════════════════════════
*/
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <util/delay.h>
// ============ CONFIGURACIÓN ============
#define F_CPU 8000000UL
// Pines H-Bridge
#define PIN_A_HI 0 // PB0 - OC0A (PWM Hardware)
#define PIN_A_LO 1 // PB1 - OC0B (PWM Hardware)
#define PIN_B_HI 3 // PB3
#define PIN_B_LO 4 // PB4
// Pines sensores
#define ADC_VOLTAGE 1 // PB2 - ADC1 - Feedback tensión
#define ADC_PROTECT 0 // PB5 - ADC0 - Protección
// Parámetros de onda
#define SINE_SAMPLES 64
#define TARGET_HZ 60
#define DEAD_TIME_US 1.5
// Límites ADC (8 bits - left adjusted)
#define V_TARGET 150
#define V_MIN 30
#define V_MAX 230
#define V_PROTECT 240
#define V_HYST 12
// ============ VARIABLES GLOBALES ============
volatile uint8_t sineIndex = 0;
volatile uint8_t amplitude = 180;
volatile uint8_t statusByte = 0;
// Bits de estado
#define BIT_SHUTDOWN 0
#define BIT_OVERVOLT 1
#define BIT_UPDATE 2
// ============ TABLA SENOIDAL ============
const uint8_t sineTable[SINE_SAMPLES] PROGMEM = {
128,140,152,164,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,164,152,140,
128,115,103, 91, 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, 91,103,115
};
// ════════════════════════════════════════════════════════════════
// FUNCIONES AUXILIARES OPTIMIZADAS
// ════════════════════════════════════════════════════════════════
// Lectura segura de PROGMEM
inline uint8_t getSine(uint8_t idx) {
return pgm_read_byte(&sineTable[idx & 0x3F]);
}
// Tiempo muerto preciso (1.5 µs @ 8MHz = 12 ciclos)
inline void deadTime(void) {
__asm__ __volatile__ (
"nop\n\t" "nop\n\t" "nop\n\t" "nop\n\t"
"nop\n\t" "nop\n\t" "nop\n\t" "nop\n\t"
"nop\n\t" "nop\n\t" "nop\n\t" "nop\n\t"
::
);
}
// Escalado optimizado (value * scale / 256)
inline uint8_t scaleValue(uint8_t value, uint8_t scale) {
return ((uint16_t)value * scale) >> 8;
}
// ════════════════════════════════════════════════════════════════
// CONTROL DE H-BRIDGE
// ════════════════════════════════════════════════════════════════
void setPhaseA(uint8_t sineVal) {
uint8_t scaled = scaleValue(sineVal, amplitude);
if (scaled >= 128) {
// Semiciclo positivo
uint8_t duty = (scaled - 128) << 1;
OCR0B = 0; // Apagar Low-Side
deadTime();
OCR0A = duty; // PWM High-Side
} else {
// Semiciclo negativo
uint8_t duty = (128 - scaled) << 1;
OCR0A = 0; // Apagar High-Side
deadTime();
OCR0B = duty; // PWM Low-Side
}
}
void setPhaseB(uint8_t sineVal) {
uint8_t scaled = scaleValue(sineVal, amplitude);
uint8_t portTemp = PORTB;
// Limpiar bits de Fase B
portTemp &= ~((1 << PIN_B_HI) | (1 << PIN_B_LO));
if (scaled >= 128) {
// Semiciclo positivo
uint8_t duty = (scaled - 128) << 1;
if (duty > 127) {
portTemp |= (1 << PIN_B_HI);
}
} else {
// Semiciclo negativo
uint8_t duty = (128 - scaled) << 1;
if (duty > 127) {
portTemp |= (1 << PIN_B_LO);
}
}
deadTime();
PORTB = portTemp;
}
// ════════════════════════════════════════════════════════════════
// ADC - LECTURA DE SENSORES
// ════════════════════════════════════════════════════════════════
uint8_t readADC(uint8_t channel) {
// Seleccionar canal (0 o 1) y alineación izquierda
ADMUX = (1 << ADLAR) | (channel & 0x01);
// Iniciar conversión
ADCSRA |= (1 << ADSC);
// Esperar finalización
while (ADCSRA & (1 << ADSC));
// Retornar byte alto (8 bits)
return ADCH;
}
// ════════════════════════════════════════════════════════════════
// CONTROL DE RETROALIMENTACIÓN
// ════════════════════════════════════════════════════════════════
void processFeedback(void) {
static uint8_t counter = 0;
// Ejecutar cada 16 ciclos completos (960ms @ 60Hz)
if (++counter < 16) return;
counter = 0;
// Leer tensión de salida
uint8_t voltage = readADC(ADC_VOLTAGE);
// Control proporcional simple
if (voltage < (V_TARGET - V_HYST)) {
// Tensión baja - aumentar amplitud
if (amplitude < 250) amplitude += 3;
}
else if (voltage > (V_TARGET + V_HYST)) {
// Tensión alta - reducir amplitud
if (amplitude > 100) amplitude -= 3;
}
// Protección de sobretensión
if (voltage > V_MAX) {
statusByte |= (1 << BIT_OVERVOLT);
amplitude = amplitude >> 1; // Reducir 50%
} else if (voltage < V_MAX - 20) {
statusByte &= ~(1 << BIT_OVERVOLT);
}
// Leer sensor de protección
uint8_t protect = readADC(ADC_PROTECT);
// Activar/desactivar shutdown
if (protect > V_PROTECT) {
statusByte |= (1 << BIT_SHUTDOWN);
} else if (protect < (V_PROTECT - 40)) {
statusByte &= ~(1 << BIT_SHUTDOWN);
}
}
// ════════════════════════════════════════════════════════════════
// ISR - TIMER1 - ACTUALIZACIÓN DE ONDA @ 3.84 kHz
// ════════════════════════════════════════════════════════════════
ISR(TIMER1_COMPA_vect) {
// Verificar modo shutdown
if (statusByte & (1 << BIT_SHUTDOWN)) {
OCR0A = 0;
OCR0B = 0;
PORTB &= ~((1 << PIN_B_HI) | (1 << PIN_B_LO));
return;
}
// Leer valores de tabla senoidal
uint8_t valA = getSine(sineIndex);
uint8_t valB = getSine(sineIndex + 32); // +180°
// Actualizar fases
setPhaseA(valA);
setPhaseB(valB);
// Incrementar índice
sineIndex++;
if (sineIndex >= SINE_SAMPLES) {
sineIndex = 0;
statusByte |= (1 << BIT_UPDATE); // Marcar actualización
}
}
// ════════════════════════════════════════════════════════════════
// CONFIGURACIÓN INICIAL
// ════════════════════════════════════════════════════════════════
void setup() {
cli(); // Deshabilitar interrupciones
// ──── GPIO ────
DDRB = (1 << PIN_A_HI) | (1 << PIN_A_LO) |
(1 << PIN_B_HI) | (1 << PIN_B_LO);
PORTB = 0;
// ──── TIMER0: Fast PWM @ 31.25 kHz ────
// WGM: Fast PWM (mode 3)
// COM: Clear on Compare Match (non-inverted)
// Clock: No prescaler (8 MHz)
TCCR0A = (1 << WGM01) | (1 << WGM00) | // Fast PWM
(1 << COM0A1) | (1 << COM0B1); // Non-inverted
TCCR0B = (1 << CS00); // Prescaler = 1
OCR0A = 0;
OCR0B = 0;
// ──── TIMER1: CTC @ 3.84 kHz ────
// Frecuencia: 8MHz / 64 / 33 = 3787.88 Hz
// Resultado: 59.81 Hz (error 0.32%)
GTCCR = 0;
TCCR1 = (1 << CTC1) | // Clear Timer on Compare
(1 << CS12) | (1 << CS11) | (1 << CS10); // Prescaler = 64
OCR1C = 32; // TOP
OCR1A = 32; // Compare
TIMSK = (1 << OCIE1A); // Enable interrupt
// ──── ADC: 125 kHz, 8-bit ────
ADMUX = (1 << ADLAR) | ADC_VOLTAGE; // Left adjust, canal 1
ADCSRA = (1 << ADEN) | // Enable
(1 << ADPS2) | (1 << ADPS1); // Prescaler = 64
// Inicialización completa
statusByte = 0;
amplitude = 180;
sineIndex = 0;
sei(); // Habilitar interrupciones
}
// ════════════════════════════════════════════════════════════════
// BUCLE PRINCIPAL
// ════════════════════════════════════════════════════════════════
void loop() {
// Procesar feedback cuando se complete un ciclo
if (statusByte & (1 << BIT_UPDATE)) {
statusByte &= ~(1 << BIT_UPDATE);
processFeedback();
}
// Modo de bajo consumo (opcional)
// set_sleep_mode(SLEEP_MODE_IDLE);
// sleep_mode();
}