#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/i2c.h"
#include "ssd1306.h"
// I2C Configuration
#define I2C_PORT i2c0
#define I2C_SDA_PIN 4
#define I2C_SCL_PIN 5
// Button Configuration
#define BTN_NEXT 14 // Next page
#define BTN_PREV 15 // Previous page
#define BTN_ACTION 16 // Action button (Start/Stop/Set)
// DS1307 Configuration
#define DS1307_ADDR 0x68
#define DS1307_REG_SECONDS 0x00
#define DS1307_REG_MINUTES 0x01
#define DS1307_REG_HOURS 0x02
// Page enumeration
typedef enum {
PAGE_TIME = 0,
PAGE_ALARM,
PAGE_STOPWATCH,
PAGE_COUNT
} page_id_t;
// Stopwatch state
typedef enum {
STOPWATCH_STOPPED = 0,
STOPWATCH_RUNNING,
STOPWATCH_PAUSED
} stopwatch_state_t;
// Global variables
static ssd1306_t display;
static page_id_t current_page = PAGE_TIME;
static bool btn_next_pressed = false;
static bool btn_prev_pressed = false;
static bool btn_action_pressed = false;
// Time structures
typedef struct {
uint8_t seconds;
uint8_t minutes;
uint8_t hours;
} time_24h_t;
typedef struct {
uint8_t hours; // 1-12
uint8_t minutes;
uint8_t seconds;
bool is_pm;
} time_12h_t;
// Alarm structure
typedef struct {
uint8_t hours; // 0-23 format
uint8_t minutes;
bool enabled;
bool triggered;
} alarm_t;
static alarm_t alarm_data = {
.hours = 7,
.minutes = 30,
.enabled = false,
.triggered = false
};
// Stopwatch structure
typedef struct {
uint32_t elapsed_ms;
uint32_t start_time;
stopwatch_state_t state;
} stopwatch_t;
static stopwatch_t stopwatch = {
.elapsed_ms = 0,
.start_time = 0,
.state = STOPWATCH_STOPPED
};
// ------------ Helper Functions --------------
uint8_t bcd_to_dec(uint8_t val) {
return ((val >> 4) * 10) + (val & 0x0F);
}
void convert_to_12h(const time_24h_t *time_24h, time_12h_t *time_12h) {
time_12h->minutes = time_24h->minutes;
time_12h->seconds = time_24h->seconds;
if (time_24h->hours == 0) {
time_12h->hours = 12;
time_12h->is_pm = false;
} else if (time_24h->hours < 12) {
time_12h->hours = time_24h->hours;
time_12h->is_pm = false;
} else if (time_24h->hours == 12) {
time_12h->hours = 12;
time_12h->is_pm = true;
} else {
time_12h->hours = time_24h->hours - 12;
time_12h->is_pm = true;
}
}
// ------------ DS1307 Functions --------------
void ds1307_read_register(uint8_t reg, uint8_t *buf, uint8_t len) {
i2c_write_blocking(I2C_PORT, DS1307_ADDR, ®, 1, true);
i2c_read_blocking(I2C_PORT, DS1307_ADDR, buf, len, false);
}
void ds1307_get_time(time_24h_t *time) {
uint8_t data[3];
ds1307_read_register(DS1307_REG_SECONDS, data, 3);
time->seconds = bcd_to_dec(data[0] & 0x7F);
time->minutes = bcd_to_dec(data[1] & 0x7F);
time->hours = bcd_to_dec(data[2] & 0x3F);
}
// ------------ Button Interrupt Handler --------------
void gpio_callback(uint gpio, uint32_t events) {
if (gpio == BTN_NEXT && (events & GPIO_IRQ_EDGE_FALL)) {
btn_next_pressed = true;
}
if (gpio == BTN_PREV && (events & GPIO_IRQ_EDGE_FALL)) {
btn_prev_pressed = true;
}
if (gpio == BTN_ACTION && (events & GPIO_IRQ_EDGE_FALL)) {
btn_action_pressed = true;
}
}
void buttons_init() {
// Initialize buttons with pull-ups
gpio_init(BTN_NEXT);
gpio_set_dir(BTN_NEXT, GPIO_IN);
gpio_pull_up(BTN_NEXT);
gpio_init(BTN_PREV);
gpio_set_dir(BTN_PREV, GPIO_IN);
gpio_pull_up(BTN_PREV);
gpio_init(BTN_ACTION);
gpio_set_dir(BTN_ACTION, GPIO_IN);
gpio_pull_up(BTN_ACTION);
// Enable interrupts
gpio_set_irq_enabled_with_callback(BTN_NEXT, GPIO_IRQ_EDGE_FALL, true, &gpio_callback);
gpio_set_irq_enabled(BTN_PREV, GPIO_IRQ_EDGE_FALL, true);
gpio_set_irq_enabled(BTN_ACTION, GPIO_IRQ_EDGE_FALL, true);
}
// ------------ Alarm Functions --------------
void check_alarm(const time_24h_t *current_time) {
if (!alarm_data.enabled) return;
if (current_time->hours == alarm_data.hours &&
current_time->minutes == alarm_data.minutes &&
current_time->seconds == 0 &&
!alarm_data.triggered) {
alarm_data.triggered = true;
printf("*** ALARM TRIGGERED! ***\n");
// Blink display for alarm indication
for (int i = 0; i < 3; i++) {
ssd1306_invert(&display, 1);
ssd1306_show(&display);
sleep_ms(200);
ssd1306_invert(&display, 0);
ssd1306_show(&display);
sleep_ms(200);
}
}
// Reset trigger flag when time changes
if (current_time->minutes != alarm_data.minutes) {
alarm_data.triggered = false;
}
}
void alarm_toggle() {
alarm_data.enabled = !alarm_data.enabled;
printf("Alarm %s\n", alarm_data.enabled ? "ENABLED" : "DISABLED");
}
// ------------ Stopwatch Functions --------------
void stopwatch_start() {
if (stopwatch.state == STOPWATCH_STOPPED) {
stopwatch.start_time = to_ms_since_boot(get_absolute_time());
stopwatch.elapsed_ms = 0;
stopwatch.state = STOPWATCH_RUNNING;
} else if (stopwatch.state == STOPWATCH_PAUSED) {
// Resume from pause
stopwatch.start_time = to_ms_since_boot(get_absolute_time()) - stopwatch.elapsed_ms;
stopwatch.state = STOPWATCH_RUNNING;
}
}
void stopwatch_stop() {
if (stopwatch.state == STOPWATCH_RUNNING) {
uint32_t now = to_ms_since_boot(get_absolute_time());
stopwatch.elapsed_ms = now - stopwatch.start_time;
stopwatch.state = STOPWATCH_PAUSED;
}
}
void stopwatch_reset() {
stopwatch.elapsed_ms = 0;
stopwatch.start_time = 0;
stopwatch.state = STOPWATCH_STOPPED;
}
void stopwatch_update() {
if (stopwatch.state == STOPWATCH_RUNNING) {
uint32_t now = to_ms_since_boot(get_absolute_time());
stopwatch.elapsed_ms = now - stopwatch.start_time;
}
}
// ------------ Page Drawing Functions --------------
// PAGE 1: 12-Hour Time Display
void draw_page_time() {
time_24h_t time_24h;
ds1307_get_time(&time_24h);
time_12h_t time_12h;
convert_to_12h(&time_24h, &time_12h);
ssd1306_clear(&display);
// Title
ssd1306_draw_string(&display, 25, 2, 1, "12-HOUR TIME");
ssd1306_draw_line(&display, 0, 12, 127, 12);
// Large time display
char time_str[16];
snprintf(time_str, sizeof(time_str), "%02d:%02d:%02d",
time_12h.hours, time_12h.minutes, time_12h.seconds);
ssd1306_draw_string(&display, 10, 22, 2, time_str);
// AM/PM indicator
ssd1306_draw_string(&display, 50, 42, 2, time_12h.is_pm ? "PM" : "AM");
// Navigation
ssd1306_draw_string(&display, 2, 54, 1, "< PREV");
ssd1306_draw_string(&display, 90, 54, 1, "NEXT >");
ssd1306_show(&display);
// Check alarm
check_alarm(&time_24h);
}
// PAGE 2: Alarm
void draw_page_alarm() {
ssd1306_clear(&display);
// Title
ssd1306_draw_string(&display, 40, 2, 1, "ALARM");
ssd1306_draw_line(&display, 0, 12, 127, 12);
// Alarm time in 12h format
time_24h_t alarm_time_24h = {
.hours = alarm_data.hours,
.minutes = alarm_data.minutes,
.seconds = 0
};
time_12h_t alarm_time_12h;
convert_to_12h(&alarm_time_24h, &alarm_time_12h);
char alarm_str[16];
snprintf(alarm_str, sizeof(alarm_str), "%02d:%02d %s",
alarm_time_12h.hours, alarm_time_12h.minutes,
alarm_time_12h.is_pm ? "PM" : "AM");
ssd1306_draw_string(&display, 20, 22, 2, alarm_str);
// Status
ssd1306_draw_string(&display, 30, 40, 1, "Status:");
ssd1306_draw_string(&display, 38, 48, 1, alarm_data.enabled ? "ON" : "OFF");
// Instructions
ssd1306_draw_string(&display, 15, 56, 1, "ACTION:Toggle");
// Navigation
ssd1306_draw_string(&display, 2, 54, 1, "< PREV");
ssd1306_draw_string(&display, 90, 54, 1, "NEXT >");
ssd1306_show(&display);
}
// PAGE 3: Stopwatch
void draw_page_stopwatch() {
stopwatch_update();
ssd1306_clear(&display);
// Title
ssd1306_draw_string(&display, 30, 2, 1, "STOPWATCH");
ssd1306_draw_line(&display, 0, 12, 127, 12);
// Calculate time components
uint32_t total_seconds = stopwatch.elapsed_ms / 1000;
uint8_t minutes = (total_seconds / 60) % 60;
uint8_t seconds = total_seconds % 60;
uint8_t centiseconds = (stopwatch.elapsed_ms % 1000) / 10;
// Display stopwatch time
char sw_str[16];
snprintf(sw_str, sizeof(sw_str), "%02d:%02d.%02d",
minutes, seconds, centiseconds);
ssd1306_draw_string(&display, 10, 22, 2, sw_str);
// State indicator
const char* state_str = "";
switch (stopwatch.state) {
case STOPWATCH_STOPPED: state_str = "READY"; break;
case STOPWATCH_RUNNING: state_str = "RUNNING"; break;
case STOPWATCH_PAUSED: state_str = "PAUSED"; break;
}
ssd1306_draw_string(&display, 35, 42, 1, state_str);
// Instructions
if (stopwatch.state == STOPWATCH_STOPPED || stopwatch.state == STOPWATCH_PAUSED) {
ssd1306_draw_string(&display, 10, 52, 1, "ACTION:Start");
} else {
ssd1306_draw_string(&display, 10, 52, 1, "ACTION:Stop");
}
// Navigation
ssd1306_draw_string(&display, 2, 54, 1, "< PREV");
ssd1306_draw_string(&display, 90, 54, 1, "NEXT >");
ssd1306_show(&display);
}
// ------------ Page Router --------------
void draw_current_page() {
switch (current_page) {
case PAGE_TIME:
draw_page_time();
break;
case PAGE_ALARM:
draw_page_alarm();
break;
case PAGE_STOPWATCH:
draw_page_stopwatch();
break;
default:
break;
}
}
void next_page() {
current_page = (current_page + 1) % PAGE_COUNT;
}
void prev_page() {
if (current_page == 0) {
current_page = PAGE_COUNT - 1;
} else {
current_page--;
}
}
void handle_action_button() {
switch (current_page) {
case PAGE_TIME:
// No action on time page
break;
case PAGE_ALARM:
alarm_toggle();
break;
case PAGE_STOPWATCH:
if (stopwatch.state == STOPWATCH_STOPPED ||
stopwatch.state == STOPWATCH_PAUSED) {
stopwatch_start();
} else if (stopwatch.state == STOPWATCH_RUNNING) {
stopwatch_stop();
}
break;
}
}
// ------------ Main --------------
int main() {
stdio_init_all();
// Initialize I2C
i2c_init(I2C_PORT, 400000);
gpio_set_function(I2C_SDA_PIN, GPIO_FUNC_I2C);
gpio_set_function(I2C_SCL_PIN, GPIO_FUNC_I2C);
gpio_pull_up(I2C_SDA_PIN);
gpio_pull_up(I2C_SCL_PIN);
sleep_ms(250);
// Initialize OLED
display.external_vcc = false;
ssd1306_init(&display, 128, 64, 0x3C, I2C_PORT);
// Initialize buttons
buttons_init();
printf("\n=== 3-Page OLED System ===\n");
printf("Page 1: 12-Hour Time\n");
printf("Page 2: Alarm (07:30 AM default)\n");
printf("Page 3: Stopwatch\n\n");
printf("Controls:\n");
printf(" GPIO %d: Next Page\n", BTN_NEXT);
printf(" GPIO %d: Previous Page\n", BTN_PREV);
printf(" GPIO %d: Action (Toggle/Start/Stop)\n\n", BTN_ACTION);
// Display initial page
draw_current_page();
// Main loop
uint32_t last_update = 0;
while (1) {
// Handle button presses with debouncing
if (btn_next_pressed) {
btn_next_pressed = false;
next_page();
draw_current_page();
printf("Page: %d\n", current_page);
sleep_ms(200);
}
if (btn_prev_pressed) {
btn_prev_pressed = false;
prev_page();
draw_current_page();
printf("Page: %d\n", current_page);
sleep_ms(200);
}
if (btn_action_pressed) {
btn_action_pressed = false;
handle_action_button();
draw_current_page();
sleep_ms(200);
}
// Auto-refresh current page
uint32_t now = to_ms_since_boot(get_absolute_time());
uint32_t update_interval = (current_page == PAGE_STOPWATCH) ? 50 : 1000;
if (now - last_update >= update_interval) {
last_update = now;
draw_current_page();
}
sleep_ms(10);
}
return 0;
}