#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 size of the keypad matrix
#define NUM_ROWS 4
#define NUM_COLS 4
// Define the GPIO pins used for rows and columns
uint row_pins[NUM_ROWS] = {5, 6, 7, 8};
uint col_pins[NUM_COLS] = {1, 2, 3, 4};
// Keypad character map
char keypad_keys[NUM_ROWS][NUM_COLS] = {
{'1', '2', '3', 'A'},
{'4', '5', '6', 'B'},
{'7', '8', '9', 'C'},
{'*', '0', '#', 'D'}
};
// LCD command flags
const int CMD_CLEAR_DISPLAY = 0x01;
const int CMD_RETURN_HOME = 0x02;
const int CMD_ENTRY_MODE_SET = 0x04;
const int CMD_DISPLAY_CONTROL = 0x08;
const int CMD_CURSOR_SHIFT = 0x10;
const int CMD_FUNCTION_SET = 0x20;
const int CMD_SET_CGRAM_ADDR = 0x40;
const int CMD_SET_DDRAM_ADDR = 0x80;
// Entry mode flags
const int ENTRY_SHIFT_INCREMENT = 0x01;
const int ENTRY_LEFT = 0x02;
// Display control flags
const int BLINK_ON = 0x01;
const int CURSOR_ON = 0x02;
const int DISPLAY_ON = 0x04;
// Cursor shift flags
const int MOVE_RIGHT = 0x04;
const int DISPLAY_MOVE = 0x08;
// Function set flags
const int DOT_SIZE_5x10 = 0x04;
const int USE_2_LINES = 0x08;
const int BIT_MODE_8 = 0x10;
// Backlight control flag
const int BACKLIGHT_ON = 0x08;
// LCD enable bit
const int ENABLE_BIT = 0x04;
// Default I2C address for the LCD
static int lcd_address = 0x27;
// Define character and command modes
#define MODE_CHARACTER 1
#define MODE_COMMAND 0
#define MAX_LCD_LINES 2
#define MAX_LCD_CHARS 16
// Define buzzer and potentiometer pins
#define BUZZER_PIN 14
#define POTENTIOMETER_PIN 26
// Function to send a single byte via I2C
void i2c_write_byte(uint8_t byte)
{
#ifdef i2c_default
i2c_write_blocking(i2c_default, lcd_address, &byte, 1, false);
#endif
}
// Toggle the enable pin of the LCD to latch data
void lcd_toggle_enable(uint8_t byte)
{
#define ENABLE_DELAY_US 600
sleep_us(ENABLE_DELAY_US);
i2c_write_byte(byte | ENABLE_BIT);
sleep_us(ENABLE_DELAY_US);
i2c_write_byte(byte & ~ENABLE_BIT);
sleep_us(ENABLE_DELAY_US);
}
// Send a byte to the LCD in either character or command mode
void lcd_send_byte(uint8_t byte, int mode)
{
uint8_t high_nibble = mode | (byte & 0xF0) | BACKLIGHT_ON;
uint8_t low_nibble = mode | ((byte << 4) & 0xF0) | BACKLIGHT_ON;
i2c_write_byte(high_nibble);
lcd_toggle_enable(high_nibble);
i2c_write_byte(low_nibble);
lcd_toggle_enable(low_nibble);
}
// Clear the LCD display
void lcd_clear()
{
lcd_send_byte(CMD_CLEAR_DISPLAY, MODE_COMMAND);
}
// Set the cursor position on the LCD
void lcd_set_cursor(int row, int col)
{
int address = (row == 0) ? 0x80 + col : 0xC0 + col;
lcd_send_byte(address, MODE_COMMAND);
}
// Send a character to the LCD
void lcd_send_char(char character)
{
lcd_send_byte(character, MODE_CHARACTER);
}
// Send a string to the LCD
void lcd_send_string(const char *string)
{
while (*string)
{
lcd_send_char(*string++);
}
}
// Initialize the LCD
void lcd_init()
{
lcd_send_byte(0x03, MODE_COMMAND);
lcd_send_byte(0x03, MODE_COMMAND);
lcd_send_byte(0x03, MODE_COMMAND);
lcd_send_byte(0x02, MODE_COMMAND);
lcd_send_byte(CMD_ENTRY_MODE_SET | ENTRY_LEFT, MODE_COMMAND);
lcd_send_byte(CMD_FUNCTION_SET | USE_2_LINES, MODE_COMMAND);
lcd_send_byte(CMD_DISPLAY_CONTROL | DISPLAY_ON, MODE_COMMAND);
lcd_clear();
}
// Initialize the keypad GPIO pins
void init_keypad()
{
for (int r = 0; r < NUM_ROWS; r++)
{
gpio_init(row_pins[r]);
gpio_set_dir(row_pins[r], GPIO_OUT);
gpio_put(row_pins[r], 1);
}
for (int c = 0; c < NUM_COLS; c++)
{
gpio_init(col_pins[c]);
gpio_set_dir(col_pins[c], GPIO_IN);
gpio_pull_up(col_pins[c]);
}
}
// Scan the keypad and return the pressed key
char scan_keypad()
{
for (int r = 0; r < NUM_ROWS; r++)
{
gpio_put(row_pins[r], 0);
for (int c = 0; c < NUM_COLS; c++)
{
if (gpio_get(col_pins[c]) == 0)
{
while (gpio_get(col_pins[c]) == 0);
gpio_put(row_pins[r], 1);
return keypad_keys[r][c];
}
}
gpio_put(row_pins[r], 1);
}
return '\0';
}
// Handle invalid frequency input
void handle_invalid_frequency()
{
lcd_clear();
lcd_set_cursor(0, 0);
lcd_send_string("Out of range");
sleep_ms(2000);
lcd_clear();
lcd_set_cursor(0, 0);
lcd_send_string("Freq: ");
}
// Clear the input buffer and reset the cursor
void clear_input(char *input, int *index)
{
*index = 0;
memset(input, 0, sizeof(char) * 6);
lcd_clear();
lcd_set_cursor(0, 0);
lcd_send_string("Freq: ");
}
// Setup the buzzer with PWM
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);
}
// Set the buzzer frequency
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);
}
// Adjust the buzzer volume based on potentiometer input
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 / 16); // Scale ADC result to duty cycle (0-4095)
}
// Stop the buzzer
void stop_buzzer()
{
uint slice_num = pwm_gpio_to_slice_num(BUZZER_PIN);
pwm_set_enabled(slice_num, false);
}
// Display the volume percentage on the LCD
void display_volume(uint16_t volume)
{
int volume_percentage = volume * 100 / 4095; // Convert ADC value to percentage
char volume_str[16];
snprintf(volume_str, sizeof(volume_str), "Vol: %d%%", volume_percentage);
lcd_set_cursor(1, 0);
lcd_send_string(volume_str);
}
int main()
{
stdio_init_all();
i2c_init(i2c_default, 100 * 1000);
gpio_set_function(16, GPIO_FUNC_I2C);
gpio_set_function(17, GPIO_FUNC_I2C);
gpio_pull_up(16);
gpio_pull_up(17);
bi_decl(bi_2pins_with_func(16, 17, GPIO_FUNC_I2C));
lcd_init();
init_keypad();
setup_buzzer();
// Initialize ADC for potentiometer
adc_init();
adc_gpio_init(POTENTIOMETER_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_send_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_send_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;
}