#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <math.h>
#include <stdint.h>
#include "pico/stdlib.h"
#include "hardware/gpio.h"
#include "hardware/irq.h"
#include "hardware/i2c.h"
#include "hardware/adc.h"
#include "hardware/pwm.h"
#include "hardware/clocks.h"
// LCD I2C (PCF8574)
#define I2C_PORT i2c0
#define SDA_PIN 16
#define SCL_PIN 17
#define LCD_ADDR 0x27
#define LCD_RS (1 << 0)
#define LCD_EN (1 << 2)
#define LCD_BL (1 << 3)
static uint8_t backlight = LCD_BL;
void i2c_write_u8(uint8_t data) { i2c_write_blocking(I2C_PORT, LCD_ADDR, &data, 1, false); }
void expander_write(uint8_t data) { i2c_write_u8(data | backlight); }
void pulse_enable(uint8_t data) {
expander_write(data | LCD_EN);
sleep_us(2);
expander_write(data & ~LCD_EN);
sleep_us(50);
}
void write4bits(uint8_t data) { expander_write(data); pulse_enable(data); }
void lcd_send(uint8_t value, bool isData) {
uint8_t mode = isData ? LCD_RS : 0;
uint8_t high = (value & 0xF0);
uint8_t low = ((value << 4) & 0xF0);
write4bits(high | mode);
write4bits(low | mode);
}
void lcd_cmd(uint8_t cmd) { lcd_send(cmd, false); if (cmd == 0x01 || cmd == 0x02) sleep_ms(2); }
void lcd_data(uint8_t d) { lcd_send(d, true); }
void lcd_clear(void) { lcd_cmd(0x01); }
void lcd_set_cursor(int row, int col) { lcd_cmd(0x80 | ((row ? 0x40 : 0x00) + col)); }
void lcd_print(const char *s) { while (*s) lcd_data((uint8_t)*s++); }
void lcd_init(void) {
sleep_ms(50);
write4bits(0x30); sleep_ms(5);
write4bits(0x30); sleep_us(150);
write4bits(0x30); sleep_us(150);
write4bits(0x20); sleep_us(150);
lcd_cmd(0x28);
lcd_cmd(0x0C);
lcd_cmd(0x06);
lcd_clear();
}
// PINS (mi SETUP)
#define BUZZER_PIN 15
#define POT_ADC_PIN 26
#define POT_ADC_CH 0
const uint COL_PINS[4] = {7, 6, 5, 4};
const uint ROW_PINS[4] = {11, 10, 9, 8};
const char keyMap[4][4] = {
{'1', '2', '3', 'A'},
{'4', '5', '6', 'B'},
{'7', '8', '9', 'C'},
{'*', '0', '#', 'D'}
};
// Keypad IRQ flags
volatile bool key_event = false;
// PWM hardware
static uint buzzer_slice;
static uint buzzer_chan;
volatile bool tone_active = false;
volatile int current_freq = 0;
static int last_vol = -1; // 0..90
// Helpers
static inline void columns_idle_low(void) {
for (int i = 0; i < 4; i++) gpio_put(COL_PINS[i], 0);
}
static int read_volume_percent_0_90(void) {
adc_select_input(POT_ADC_CH);
uint16_t raw = adc_read(); // 0..4095
int vol = (raw * 100) / 4095; // 0..100
vol = 100 - vol; // invertir pot: derecha sube
if (vol > 90) vol = 90;
if (vol < 0) vol = 0;
return vol;
}
// Duty lineal: 66% -> 66%
static inline float volume_to_duty_linear(int vol_0_90) {
float duty = (float)vol_0_90 / 100.0f; // 0..0.90
if (duty < 0.0f) duty = 0.0f;
if (duty > 0.90f) duty = 0.90f;
return duty;
}
// Forzar PIN a 0 fijo (sin PWM)
void buzzer_force_low(void) {
pwm_set_enabled(buzzer_slice, false);
gpio_set_function(BUZZER_PIN, GPIO_FUNC_SIO);
gpio_set_dir(BUZZER_PIN, GPIO_OUT);
gpio_put(BUZZER_PIN, 0);
}
// Volver a modo PWM (sin arrancar tono)
void buzzer_restore_pwm_mode(void) {
gpio_set_function(BUZZER_PIN, GPIO_FUNC_PWM);
}
void pwm_set_frequency_and_volume(int freq_hz, int vol_0_90) {
if (freq_hz < 20) freq_hz = 20;
if (freq_hz > 20000) freq_hz = 20000;
// Si vol=0: apaga señal REAL (pin a 0 fijo)
if (vol_0_90 <= 0) {
buzzer_force_low();
return;
}
// Si venimos de 0, hay que devolver el pin a PWM
buzzer_restore_pwm_mode();
uint32_t sys = clock_get_hz(clk_sys);
// Elegir div y top para que top <= 65535
float div = 1.0f;
float top_f = (float)sys / ((float)freq_hz * div) - 1.0f;
if (top_f > 65535.0f) {
div = (float)sys / ((float)freq_hz * 65536.0f);
if (div < 1.0f) div = 1.0f;
top_f = (float)sys / ((float)freq_hz * div) - 1.0f;
}
uint32_t top = (uint32_t)(top_f);
if (top > 65535) top = 65535;
if (top < 2) top = 2;
pwm_set_clkdiv(buzzer_slice, div);
pwm_set_wrap(buzzer_slice, (uint16_t)top);
float duty = volume_to_duty_linear(vol_0_90);
uint16_t level = (uint16_t)((float)top * duty);
pwm_set_chan_level(buzzer_slice, buzzer_chan, level);
pwm_set_enabled(buzzer_slice, true);
}
void tone_start(int freq_hz) {
if (last_vol < 0) last_vol = read_volume_percent_0_90();
current_freq = freq_hz;
tone_active = true;
pwm_set_frequency_and_volume(current_freq, last_vol);
}
void tone_stop(void) {
tone_active = false;
current_freq = 0;
buzzer_force_low();
// (quedará apagado fijo)
}
// Keypad ISR
void row_irq_cb(uint gpio, uint32_t events) {
if (events & GPIO_IRQ_EDGE_FALL) {
key_event = true;
for (int i = 0; i < 4; i++) {
gpio_set_irq_enabled(ROW_PINS[i], GPIO_IRQ_EDGE_FALL, false);
}
}
}
// Keypad init
void keypad_init_irq(void) {
for (int c = 0; c < 4; c++) {
gpio_init(COL_PINS[c]);
gpio_set_dir(COL_PINS[c], GPIO_OUT);
gpio_put(COL_PINS[c], 0);
}
for (int r = 0; r < 4; r++) {
gpio_init(ROW_PINS[r]);
gpio_set_dir(ROW_PINS[r], GPIO_IN);
gpio_pull_up(ROW_PINS[r]);
if (r == 0) {
gpio_set_irq_enabled_with_callback(ROW_PINS[r], GPIO_IRQ_EDGE_FALL, true, &row_irq_cb);
} else {
gpio_set_irq_enabled(ROW_PINS[r], GPIO_IRQ_EDGE_FALL, true);
}
}
}
// Keypad scan
static char keypad_read_key_event(void) {
sleep_ms(25);
int found_r = -1, found_c = -1;
for (int c = 0; c < 4; c++) {
for (int i = 0; i < 4; i++) gpio_put(COL_PINS[i], 1);
gpio_put(COL_PINS[c], 0);
sleep_us(80);
for (int r = 0; r < 4; r++) {
if (gpio_get(ROW_PINS[r]) == 0) { found_r = r; found_c = c; break; }
}
if (found_r != -1) break;
}
columns_idle_low();
if (found_r != -1) {
while (gpio_get(ROW_PINS[found_r]) == 0) sleep_ms(1);
}
for (int i = 0; i < 4; i++) gpio_set_irq_enabled(ROW_PINS[i], GPIO_IRQ_EDGE_FALL, true);
key_event = false;
if (found_r == -1) return 0;
return keyMap[found_r][found_c];
}
// MAIN
int main() {
// stdio_init_all(); // <-- (3) QUITADO para que no rompa en Wokwi
// --- PWM init (slice/chan) ---
gpio_set_function(BUZZER_PIN, GPIO_FUNC_PWM);
buzzer_slice = pwm_gpio_to_slice_num(BUZZER_PIN);
buzzer_chan = pwm_gpio_to_channel(BUZZER_PIN);
pwm_set_enabled(buzzer_slice, false);
pwm_set_chan_level(buzzer_slice, buzzer_chan, 0);
// --- I2C LCD ---
i2c_init(I2C_PORT, 100000);
gpio_set_function(SDA_PIN, GPIO_FUNC_I2C);
gpio_set_function(SCL_PIN, GPIO_FUNC_I2C);
gpio_pull_up(SDA_PIN);
gpio_pull_up(SCL_PIN);
lcd_init();
// --- ADC pot ---
adc_init();
adc_gpio_init(POT_ADC_PIN);
adc_select_input(POT_ADC_CH);
// --- Keypad ---
keypad_init_irq();
// --- LCD inicial ---
lcd_set_cursor(0, 0); lcd_print("Freq: ");
lcd_set_cursor(1, 0); lcd_print("Vol: ");
lcd_set_cursor(0, 6); lcd_print("0 Hz");
last_vol = read_volume_percent_0_90();
char buf[17];
lcd_set_cursor(1, 5);
snprintf(buf, sizeof(buf), "%2d%%", last_vol);
lcd_print(buf);
lcd_print(" ");
int input_val = 0;
absolute_time_t last_adc = get_absolute_time();
while (1) {
// ===== Keypad =====
if (!key_event) {
for (int r = 0; r < 4; r++) {
if (gpio_get(ROW_PINS[r]) == 0) {
key_event = true;
break;
}
}
}
if (key_event) {
char k = keypad_read_key_event();
if (k) {
if (k >= '0' && k <= '9' && !tone_active) {
input_val = input_val * 10 + (k - '0');
if (input_val > 20000) input_val = 20000;
snprintf(buf, sizeof(buf), "%-5d Hz", input_val);
lcd_set_cursor(0, 6);
lcd_print(buf);
}
else if (k == '#' && !tone_active) {
if (input_val > 0) {
int freq = input_val;
if (freq < 20) freq = 20;
if (freq > 20000) freq = 20000;
tone_start(freq);
snprintf(buf, sizeof(buf), "%-5d Hz", freq);
lcd_set_cursor(0, 6);
lcd_print(buf);
}
input_val = 0;
}
else if (k == '*') {
tone_stop();
input_val = 0;
lcd_set_cursor(0, 6);
lcd_print("0 Hz");
}
}
}
// Pot cada ~50ms
if (absolute_time_diff_us(last_adc, get_absolute_time()) > 50000) {
last_adc = get_absolute_time();
int vol = read_volume_percent_0_90();
if (vol != last_vol) {
last_vol = vol;
lcd_set_cursor(1, 5);
snprintf(buf, sizeof(buf), "%2d%%", last_vol);
lcd_print(buf);
lcd_print(" ");
// Si hay tono activo: actualiza volume (y si vol=0 apaga señal real)
if (tone_active && current_freq > 0) {
pwm_set_frequency_and_volume(current_freq, last_vol);
}
}
}
sleep_ms(1);
}
return 0;
}