#include <Arduino.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <RotaryEncoder.h> // <-- LIBRERÍA CAMBIADA: Para encoder, compatible con ATmega
// --- Configuración de Pines ---
// MC145170
const int DATA_PIN = 4;
const int CLK_PIN = 5;
const int ENABLE_PIN = 6;
// Pantalla OLED
// NOTA: En el ATmega32U4, los pines I2C son fijos.
// SDA -> Pin 2
// SCL -> Pin 3
// No es necesario definirlos aquí, la librería "Wire" ya los conoce.
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1 // El pin de reset no se usa en este caso
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// Encoder Rotatorio
#define ENCODER_PIN1 7 // Pin A (CLK en algunos encoders)
#define ENCODER_PIN2 8 // Pin B (DT en algunos encoders)
#define ENCODER_SW 9 // Pin del pulsador (Switch)
// Crear el objeto para el encoder (reemplaza a ESP32Encoder)
RotaryEncoder encoder(ENCODER_PIN1, ENCODER_PIN2, RotaryEncoder::LatchMode::FOUR3);
// --- Parámetros del PLL ---
const long OSCILLATOR_FREQ = 8000000; // 8 MHz
const int REFERENCE_DIVIDER = 80; // Para obtener 100 kHz de referencia (8,000,000 / 100,000)
const long FREQUENCY_STEP = 100000; // 100 kHz
const long MIN_FREQUENCY = 88000000; // 88.0 MHz
const long MAX_FREQUENCY = 108000000; // 108.0 MHz
long currentFrequency = MIN_FREQUENCY;
long lastFrequency = 0;
// --- Prototipos de Funciones ---
void sendDataToPLL(uint16_t n_value, uint16_t r_value);
void updateDisplay();
void setup() {
Serial.begin(115200);
// Para placas con USB nativo como el Pro Micro/Leonardo, es bueno esperar a que el puerto serie se conecte.
while (!Serial && millis() < 5000) {
; // Espera 5 segundos o hasta que se abra el monitor serie
}
// Inicializar pines del PLL
pinMode(DATA_PIN, OUTPUT);
pinMode(CLK_PIN, OUTPUT);
pinMode(ENABLE_PIN, OUTPUT);
digitalWrite(ENABLE_PIN, LOW); // Activar PLL
// Configurar pin del pulsador del encoder
pinMode(ENCODER_SW, INPUT_PULLUP);
// Inicializar Pantalla OLED
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // La dirección I2C suele ser 0x3C
Serial.println(F("Fallo al iniciar SSD1306"));
for (;;); // Bucle infinito si falla
}
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.println("Sintetizador PLL");
display.display();
delay(2000);
// La nueva librería del encoder no requiere una función de setup separada.
// Enviar la configuración inicial al PLL
uint16_t n_value = currentFrequency / FREQUENCY_STEP;
sendDataToPLL(n_value, REFERENCE_DIVIDER);
updateDisplay();
lastFrequency = currentFrequency;
}
void loop() {
// --- NUEVA LÓGICA PARA LEER EL ENCODER ---
encoder.tick(); // Esencial llamar a esta función en cada ciclo del loop
// Obtener la dirección del último giro
RotaryEncoder::Direction direction = encoder.getDirection();
if (direction == RotaryEncoder::Direction::CLOCKWISE) {
currentFrequency += FREQUENCY_STEP;
} else if (direction == RotaryEncoder::Direction::COUNTERCLOCKWISE) {
currentFrequency -= FREQUENCY_STEP;
}
// Si direction es "NONE", no se hace nada.
// Limitar la frecuencia al rango FM
if (currentFrequency > MAX_FREQUENCY) {
currentFrequency = MAX_FREQUENCY;
}
if (currentFrequency < MIN_FREQUENCY) {
currentFrequency = MIN_FREQUENCY;
}
// Actualizar el PLL y la pantalla solo si la frecuencia ha cambiado
if (currentFrequency != lastFrequency) {
uint16_t n_value = currentFrequency / FREQUENCY_STEP;
sendDataToPLL(n_value, REFERENCE_DIVIDER);
updateDisplay();
lastFrequency = currentFrequency;
Serial.print("Frecuencia: ");
Serial.print(currentFrequency / 1000000.0, 1);
Serial.println(" MHz");
}
// Se puede agregar lógica para el pulsador del encoder aquí si se desea
// if (digitalRead(ENCODER_SW) == LOW) {
// // Hacer algo al presionar el botón
// }
delay(1); // Pequeña pausa para estabilizar
}
// =====================================================================
// == Las siguientes funciones NO necesitan ningún cambio. ==
// == shiftOut() es una función estándar de Arduino y la lógica del ==
// == PLL y la pantalla es idéntica. ==
// =====================================================================
// Función para enviar los datos a los registros del MC145170
void sendDataToPLL(uint16_t n_value, uint16_t r_value) {
digitalWrite(ENABLE_PIN, HIGH); // Deshabilitar la escritura mientras preparamos
// --- Cargar el registro R (15 bits) ---
// Los dos últimos bits son de control (00 para el registro R)
uint16_t r_data = (r_value << 2);
digitalWrite(ENABLE_PIN, LOW); // Habilitar escritura
// Enviar los 16 bits para el registro R
shiftOut(DATA_PIN, CLK_PIN, MSBFIRST, (r_data >> 8));
shiftOut(DATA_PIN, CLK_PIN, MSBFIRST, r_data & 0xFF);
digitalWrite(ENABLE_PIN, HIGH); // Pulsar ENABLE para latch de los datos
delayMicroseconds(1);
digitalWrite(ENABLE_PIN, LOW);
// Pequeña pausa entre escrituras de registros
delayMicroseconds(100);
// --- Cargar el registro N (16 bits) ---
// Los dos últimos bits son de control (01 para el registro N)
uint32_t n_data = (uint32_t(n_value) << 2) | 0b01;
digitalWrite(ENABLE_PIN, LOW);
shiftOut(DATA_PIN, CLK_PIN, MSBFIRST, (n_data >> 8));
shiftOut(DATA_PIN, CLK_PIN, MSBFIRST, n_data & 0xFF);
digitalWrite(ENABLE_PIN, HIGH);
delayMicroseconds(1);
digitalWrite(ENABLE_PIN, LOW);
}
// Actualizar la información en la pantalla OLED
void updateDisplay() {
display.clearDisplay();
display.setCursor(0, 0);
display.setTextSize(2);
// Formatear la frecuencia para mostrarla en MHz con un decimal
float freqMHz = currentFrequency / 1000000.0;
char freqStr[10];
dtostrf(freqMHz, 5, 1, freqStr); // 5 caracteres de ancho, 1 decimal
display.print(freqStr);
display.setTextSize(1);
display.print(" MHz");
display.display();
}