#include <stdio.h>
#include <string.h>
#include "pico/stdlib.h"
#include "hardware/i2c.h"
#include "hardware/pwm.h"
#include "hardware/adc.h"
#include "pico/binary_info.h"
// Define the number of rows and columns for the KeyPad
#define ROWS 4
#define COLS 4
// Pin numbers for rows and columns
uint rowPins[ROWS] = {6, 7, 8, 9};
uint colPins[COLS] = {10, 11, 12, 13};
// Key map for the keypad
char keys[ROWS][COLS] = {
{'1', '2', '3', 'A'},
{'4', '5', '6', 'B'},
{'7', '8', '9', 'C'},
{'*', '0', '#', 'D'}
};
const int LCD_CLEARDISPLAY = 0x01; // Limpia y 1rst pos (sup,iz)
const int LCD_RETURNHOME = 0x02; // 1rst pos (sup,iz)
const int LCD_ENTRYMODESET = 0x04; // Configura entrada del LCD que se encarga de las funciones de CursorShift
const int LCD_DISPLAYCONTROL = 0x08; /* On/Off pantalla, cursor y parpadeo del cursor*/
const int LCD_CURSORSHIFT = 0x10; // Mov. el cursor y desp. pantalla
const int LCD_FUNCTIONSET = 0x20; // Se encarga del tamaño de los puntos de los caracteres , tamaño de lineas , modo de bits
const int LCD_SETCGRAMADDR = 0x40; // establece CGRAM (Character Generator RAM)
const int LCD_SETDDRAMADDR = 0x80; // establece CGRAM (Character Generator RAM)
// flags for display entry mode
const int LCD_ENTRYSHIFTINCREMENT = 0x01; // Si se habilita mueve hacia la derecha el cursor , sino , se desplaza la pantalla
const int LCD_ENTRYLEFT = 0x02; // Si se habilita , escribe los caracteres de iz a der , sino , se escribe de der a iz
// flags for display and cursor control
const int LCD_BLINKON = 0x01; // Habilita parpadeo del cursor
const int LCD_CURSORON = 0x02; // Muestra el cursor en pantalla
const int LCD_DISPLAYON = 0x04; // Enciende la pantalla
// flags for display and cursor shift
const int LCD_MOVERIGHT = 0x04; // Desplaza el cursor a la derecha
const int LCD_DISPLAYMOVE = 0x08; // desplaza todo el contenido de la pantalla
// flags for function set
const int LCD_5x10DOTS = 0x04; // Tamaño de puntos de los caracteres 5x10
const int LCD_2LINE = 0x08; // Para usar 2 lineas de texto
const int LCD_8BITMODE = 0x10; // Usar el bus de datos de 8 bits
// flag for backlight control
const int LCD_BACKLIGHT = 0x08; // Controla la retroiluminacion
const int LCD_ENABLE_BIT = 0x04; // Se habillita para enviar datos o comandos al LCD
// Direccion I2C predeterminada para el microcontrolador.
static int addr = 0x27;
// Define que lo que le envian es Comando o caracter
#define LCD_CHARACTER 1
#define LCD_COMMAND 0
#define MAX_LINES 2
#define MAX_CHARS 16
// Definir el pin del buzzer
#define BUZZER_PIN 14
#define POT_PIN 26 // ADC pin for potentiometer
// Envia un solo byte a travez del I2C
void i2c_write_byte(uint8_t val)
{
#ifdef i2c_default
i2c_write_blocking(i2c_default, addr, &val, 1, false);
#endif
}
// Activa y desactiva el pin de habilitacion del LCD
void lcd_toggle_enable(uint8_t val)
{
#define DELAY_US 600
sleep_us(DELAY_US);
i2c_write_byte(val | LCD_ENABLE_BIT);
sleep_us(DELAY_US);
i2c_write_byte(val & ~LCD_ENABLE_BIT);
sleep_us(DELAY_US);
}
void lcd_send_byte(uint8_t val, int mode)
{
uint8_t high = mode | (val & 0xF0) | LCD_BACKLIGHT;
uint8_t low = mode | ((val << 4) & 0xF0) | LCD_BACKLIGHT;
i2c_write_byte(high);
lcd_toggle_enable(high);
i2c_write_byte(low);
lcd_toggle_enable(low);
}
void lcd_clear(void)
{
lcd_send_byte(LCD_CLEARDISPLAY, LCD_COMMAND);
}
void lcd_set_cursor(int line, int position)
{
int val = (line == 0) ? 0x80 + position : 0xC0 + position;
lcd_send_byte(val, LCD_COMMAND);
}
static void inline lcd_char(char val)
{
lcd_send_byte(val, LCD_CHARACTER);
}
void lcd_string(const char *s)
{
while (*s)
{
lcd_char(*s++);
}
}
void lcd_init()
{
lcd_send_byte(0x03, LCD_COMMAND);
lcd_send_byte(0x03, LCD_COMMAND);
lcd_send_byte(0x03, LCD_COMMAND);
lcd_send_byte(0x02, LCD_COMMAND);
lcd_send_byte(LCD_ENTRYMODESET | LCD_ENTRYLEFT, LCD_COMMAND);
lcd_send_byte(LCD_FUNCTIONSET | LCD_2LINE, LCD_COMMAND);
lcd_send_byte(LCD_DISPLAYCONTROL | LCD_DISPLAYON, LCD_COMMAND);
lcd_clear();
}
void init_keypad()
{
for (int r = 0; r < ROWS; r++)
{
gpio_init(rowPins[r]);
gpio_set_dir(rowPins[r], GPIO_OUT);
gpio_put(rowPins[r], 1);
}
for (int c = 0; c < COLS; c++)
{
gpio_init(colPins[c]);
gpio_set_dir(colPins[c], GPIO_IN);
gpio_pull_up(colPins[c]);
}
}
char scan_keypad()
{
for (int r = 0; r < ROWS; r++)
{
gpio_put(rowPins[r], 0);
for (int c = 0; c < COLS; c++)
{
if (gpio_get(colPins[c]) == 0)
{
while (gpio_get(colPins[c]) == 0)
;
gpio_put(rowPins[r], 1);
return keys[r][c];
}
}
gpio_put(rowPins[r], 1);
}
return '\0';
}
void handle_invalid_frequency()
{
lcd_clear();
lcd_set_cursor(0, 0);
lcd_string("Out of range");
sleep_ms(2000);
lcd_clear();
lcd_set_cursor(0, 0);
lcd_string("Freq: ");
}
void clear_input(char *input, int *idx)
{
*idx = 0;
memset(input, 0, sizeof(char) * 6);
lcd_clear();
lcd_set_cursor(0, 0);
lcd_string("Freq: ");
}
void setup_buzzer()
{
gpio_set_function(BUZZER_PIN, GPIO_FUNC_PWM);
uint slice_num = pwm_gpio_to_slice_num(BUZZER_PIN);
pwm_set_wrap(slice_num, 4095);
pwm_set_chan_level(slice_num, PWM_CHAN_A, 2048); // 50% duty cycle
pwm_set_enabled(slice_num, true);
}
void set_buzzer_frequency(int frequency)
{
uint slice_num = pwm_gpio_to_slice_num(BUZZER_PIN);
float divider = 125000000.0 / (4096 * frequency); // 125MHz / (wrap+1) / frequency
pwm_set_clkdiv(slice_num, divider);
}
void set_buzzer_volume()
{
uint16_t result = adc_read();
uint slice_num = pwm_gpio_to_slice_num(BUZZER_PIN);
pwm_set_chan_level(slice_num, PWM_CHAN_A, result / 4); // Scale ADC result to duty cycle (0-4095)
}
void stop_buzzer()
{
uint slice_num = pwm_gpio_to_slice_num(BUZZER_PIN);
pwm_set_enabled(slice_num, false);
}
void display_volume(uint16_t volume)
{
int volume_percentage = volume * 100 / 4095; // Convert ADC value to percentage
char volume_str[4];
snprintf(volume_str, sizeof(volume_str), "Vol: %d%%", volume_percentage);
lcd_set_cursor(1, 0);
lcd_string(volume_str);
}
int main()
{
stdio_init_all();
i2c_init(i2c_default, 100 * 1000);
gpio_set_function(4, GPIO_FUNC_I2C);
gpio_set_function(5, GPIO_FUNC_I2C);
gpio_pull_up(4);
gpio_pull_up(5);
bi_decl(bi_2pins_with_func(4, 5, GPIO_FUNC_I2C));
lcd_init();
init_keypad();
setup_buzzer();
// Initialize ADC for potentiometer
adc_init();
adc_gpio_init(POT_PIN);
adc_select_input(0); // Assuming potentiometer is connected to ADC0
gpio_init(20);
gpio_set_dir(20, GPIO_OUT);
char input[6] = {0};
int idx = 0;
int buzzer_active = 0;
lcd_set_cursor(0, 0);
lcd_string("Freq: ");
while (1)
{
char key = scan_keypad();
if (key != '\0')
{
if (key == '#')
{
input[idx] = '\0';
int frequency = atoi(input);
if (frequency < 20 || frequency > 20000)
{
handle_invalid_frequency();
}
else
{
printf("Frequency entered: %d Hz\n", frequency);
set_buzzer_frequency(frequency);
gpio_put(20, 1);
buzzer_active = 1;
}
clear_input(input, &idx);
}
else if (key == '*')
{
if (buzzer_active)
{
stop_buzzer();
gpio_put(20, 0);
buzzer_active = 0;
}
clear_input(input, &idx);
}
else if (idx < 5)
{
input[idx++] = key;
lcd_set_cursor(0, 6);
lcd_string(input);
}
sleep_ms(200); // Debounce delay
}
if (buzzer_active)
{
set_buzzer_volume(); // Adjust volume based on potentiometer
}
// Always display the volume
uint16_t volume = adc_read();
display_volume(volume);
sleep_ms(200); // Update volume display every 200 ms
}
return 0;
}