summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorflowerg9 <mail@rattus.ing>2026-04-13 19:47:57 +0200
committerflowerg9 <mail@rattus.ing>2026-04-13 19:47:57 +0200
commitad8934a30fe34562094656e5de71fc118b627420 (patch)
treedcc5f1c512cebb5452e69bb335fca454f264cf4e
Version 1main
-rw-r--r--CMakeLists.txt24
-rw-r--r--font.h110
-rw-r--r--main.c577
-rw-r--r--ssd1306.c306
-rw-r--r--ssd1306.h274
5 files changed, 1291 insertions, 0 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..15af56c
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,24 @@
+cmake_minimum_required(VERSION 3.13)
+
+include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake)
+
+project(syringe_pump C CXX ASM)
+set(CMAKE_C_STANDARD 11)
+set(CMAKE_CXX_STANDARD 17)
+
+pico_sdk_init()
+
+add_executable(syringe_pump
+ main.c
+ ssd1306.c
+ # Add other source files here
+)
+
+target_link_libraries(syringe_pump
+ pico_stdlib # Standard library
+ hardware_i2c
+ hardware_flash
+ # Add other libraries as needed
+)
+
+pico_add_extra_outputs(syringe_pump)
diff --git a/font.h b/font.h
new file mode 100644
index 0000000..de7b16a
--- /dev/null
+++ b/font.h
@@ -0,0 +1,110 @@
+#ifndef _inc_font
+#define _inc_font
+
+/*
+ * Format
+ * <height>, <width>, <additional spacing per char>,
+ * <first ascii char>, <last ascii char>,
+ * <data>
+ */
+const uint8_t font_8x5[] =
+{
+ 8, 5, 1, 32, 126,
+ 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x5F, 0x00, 0x00,
+ 0x00, 0x07, 0x00, 0x07, 0x00,
+ 0x14, 0x7F, 0x14, 0x7F, 0x14,
+ 0x24, 0x2A, 0x7F, 0x2A, 0x12,
+ 0x23, 0x13, 0x08, 0x64, 0x62,
+ 0x36, 0x49, 0x56, 0x20, 0x50,
+ 0x00, 0x08, 0x07, 0x03, 0x00,
+ 0x00, 0x1C, 0x22, 0x41, 0x00,
+ 0x00, 0x41, 0x22, 0x1C, 0x00,
+ 0x2A, 0x1C, 0x7F, 0x1C, 0x2A,
+ 0x08, 0x08, 0x3E, 0x08, 0x08,
+ 0x00, 0x80, 0x70, 0x30, 0x00,
+ 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x00, 0x00, 0x60, 0x60, 0x00,
+ 0x20, 0x10, 0x08, 0x04, 0x02,
+ 0x3E, 0x51, 0x49, 0x45, 0x3E,
+ 0x00, 0x42, 0x7F, 0x40, 0x00,
+ 0x72, 0x49, 0x49, 0x49, 0x46,
+ 0x21, 0x41, 0x49, 0x4D, 0x33,
+ 0x18, 0x14, 0x12, 0x7F, 0x10,
+ 0x27, 0x45, 0x45, 0x45, 0x39,
+ 0x3C, 0x4A, 0x49, 0x49, 0x31,
+ 0x41, 0x21, 0x11, 0x09, 0x07,
+ 0x36, 0x49, 0x49, 0x49, 0x36,
+ 0x46, 0x49, 0x49, 0x29, 0x1E,
+ 0x00, 0x00, 0x14, 0x00, 0x00,
+ 0x00, 0x40, 0x34, 0x00, 0x00,
+ 0x00, 0x08, 0x14, 0x22, 0x41,
+ 0x14, 0x14, 0x14, 0x14, 0x14,
+ 0x00, 0x41, 0x22, 0x14, 0x08,
+ 0x02, 0x01, 0x59, 0x09, 0x06,
+ 0x3E, 0x41, 0x5D, 0x59, 0x4E,
+ 0x7C, 0x12, 0x11, 0x12, 0x7C,
+ 0x7F, 0x49, 0x49, 0x49, 0x36,
+ 0x3E, 0x41, 0x41, 0x41, 0x22,
+ 0x7F, 0x41, 0x41, 0x41, 0x3E,
+ 0x7F, 0x49, 0x49, 0x49, 0x41,
+ 0x7F, 0x09, 0x09, 0x09, 0x01,
+ 0x3E, 0x41, 0x41, 0x51, 0x73,
+ 0x7F, 0x08, 0x08, 0x08, 0x7F,
+ 0x00, 0x41, 0x7F, 0x41, 0x00,
+ 0x20, 0x40, 0x41, 0x3F, 0x01,
+ 0x7F, 0x08, 0x14, 0x22, 0x41,
+ 0x7F, 0x40, 0x40, 0x40, 0x40,
+ 0x7F, 0x02, 0x1C, 0x02, 0x7F,
+ 0x7F, 0x04, 0x08, 0x10, 0x7F,
+ 0x3E, 0x41, 0x41, 0x41, 0x3E,
+ 0x7F, 0x09, 0x09, 0x09, 0x06,
+ 0x3E, 0x41, 0x51, 0x21, 0x5E,
+ 0x7F, 0x09, 0x19, 0x29, 0x46,
+ 0x26, 0x49, 0x49, 0x49, 0x32,
+ 0x03, 0x01, 0x7F, 0x01, 0x03,
+ 0x3F, 0x40, 0x40, 0x40, 0x3F,
+ 0x1F, 0x20, 0x40, 0x20, 0x1F,
+ 0x3F, 0x40, 0x38, 0x40, 0x3F,
+ 0x63, 0x14, 0x08, 0x14, 0x63,
+ 0x03, 0x04, 0x78, 0x04, 0x03,
+ 0x61, 0x59, 0x49, 0x4D, 0x43,
+ 0x00, 0x7F, 0x41, 0x41, 0x41,
+ 0x02, 0x04, 0x08, 0x10, 0x20,
+ 0x00, 0x41, 0x41, 0x41, 0x7F,
+ 0x04, 0x02, 0x01, 0x02, 0x04,
+ 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x00, 0x03, 0x07, 0x08, 0x00,
+ 0x20, 0x54, 0x54, 0x78, 0x40,
+ 0x7F, 0x28, 0x44, 0x44, 0x38,
+ 0x38, 0x44, 0x44, 0x44, 0x28,
+ 0x38, 0x44, 0x44, 0x28, 0x7F,
+ 0x38, 0x54, 0x54, 0x54, 0x18,
+ 0x00, 0x08, 0x7E, 0x09, 0x02,
+ 0x18, 0xA4, 0xA4, 0x9C, 0x78,
+ 0x7F, 0x08, 0x04, 0x04, 0x78,
+ 0x00, 0x44, 0x7D, 0x40, 0x00,
+ 0x20, 0x40, 0x40, 0x3D, 0x00,
+ 0x7F, 0x10, 0x28, 0x44, 0x00,
+ 0x00, 0x41, 0x7F, 0x40, 0x00,
+ 0x7C, 0x04, 0x78, 0x04, 0x78,
+ 0x7C, 0x08, 0x04, 0x04, 0x78,
+ 0x38, 0x44, 0x44, 0x44, 0x38,
+ 0xFC, 0x18, 0x24, 0x24, 0x18,
+ 0x18, 0x24, 0x24, 0x18, 0xFC,
+ 0x7C, 0x08, 0x04, 0x04, 0x08,
+ 0x48, 0x54, 0x54, 0x54, 0x24,
+ 0x04, 0x04, 0x3F, 0x44, 0x24,
+ 0x3C, 0x40, 0x40, 0x20, 0x7C,
+ 0x1C, 0x20, 0x40, 0x20, 0x1C,
+ 0x3C, 0x40, 0x30, 0x40, 0x3C,
+ 0x44, 0x28, 0x10, 0x28, 0x44,
+ 0x4C, 0x90, 0x90, 0x90, 0x7C,
+ 0x44, 0x64, 0x54, 0x4C, 0x44,
+ 0x00, 0x08, 0x36, 0x41, 0x00,
+ 0x00, 0x00, 0x77, 0x00, 0x00,
+ 0x00, 0x41, 0x36, 0x08, 0x00,
+ 0x02, 0x01, 0x02, 0x04, 0x02,
+};
+
+#endif
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..0c0e353
--- /dev/null
+++ b/main.c
@@ -0,0 +1,577 @@
+#include <stdio.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include "pico/stdlib.h"
+#include "hardware/i2c.h"
+#include "hardware/flash.h"
+#include "ssd1306.h"
+
+#define FLASH_TARGET_OFFSET (PICO_FLASH_SIZE_BYTES - 4096)
+#define DIAMETER_STORAGE_ADDR (XIP_BASE + FLASH_TARGET_OFFSET)
+
+const uint ENABLE_PIN = 10;
+const uint STEP_PIN = 16;
+const uint DIR_PIN = 17;
+const uint MS1 = 13;
+const uint MS2 = 14;
+const uint MS3 = 15;
+
+const uint LEFT = 18;
+const uint DOWN = 19;
+const uint RIGHT = 20;
+const uint UP = 21;
+
+const uint SDA_PIN = 4;
+const uint SCL_PIN = 5;
+const uint I2C_ADDR = 0x3C;
+ssd1306_t disp;
+
+float DIAMETER = 20.0;
+
+void save_diameter_to_flash(float diameter) {
+ uint32_t stored_value = *((uint32_t*)&diameter);
+ uint32_t page_buffer[FLASH_PAGE_SIZE / sizeof(uint32_t)];
+ page_buffer[0] = stored_value;
+
+ flash_range_erase(FLASH_TARGET_OFFSET, FLASH_SECTOR_SIZE);
+ flash_range_program(FLASH_TARGET_OFFSET, (const uint8_t*)page_buffer, FLASH_PAGE_SIZE);
+}
+
+float load_diameter_from_flash() {
+ uint32_t* flash_ptr = (uint32_t*)(DIAMETER_STORAGE_ADDR);
+ uint32_t stored_value = *flash_ptr;
+
+ if (stored_value == 0xFFFFFFFF) {
+ return 20.0;
+ }
+
+ return *((float*)&stored_value);
+}
+
+const float LEAD = 8.0;
+const float STEPS_PER_REV = 200 * 8;
+const float STEPS_PER_MM = STEPS_PER_REV / LEAD;
+
+const uint max_visible = 3;
+int selection = 0;
+int total_items = 5;
+int display_top = 0;
+bool update = true;
+
+//functions
+void init();
+void update_menu();
+float infuse(float current_val);
+float set_diameter(float current_val);
+float manual_move(float current_val);
+float snake_game(float current_val);
+
+typedef struct {
+ char *label;
+ int id;
+ float (*action)(float);
+} MenuItem;
+
+MenuItem menu[] = {
+ {"Infuse", 0, infuse},
+ {"Stop", 1, NULL},
+ {"Set Diameter", 2, set_diameter},
+ {"Move", 3, manual_move},
+ {"Snake", 4, snake_game},
+};
+
+int main() {
+
+ init();
+
+ while(true) {
+
+ if (update) {
+ update_menu();
+ update = false;
+ }
+ if (gpio_get(UP) && selection > 0)
+ {
+ selection--;
+ if (selection < display_top) { //scrolling up
+ display_top = selection;
+ }
+ update = true;
+ sleep_ms(200);
+ }
+ if (gpio_get(DOWN) && selection < total_items - 1)
+ {
+ selection++;
+ if (selection >= display_top + max_visible) //scrolling down
+ {
+ display_top = selection - max_visible + 1;
+ }
+ update = true;
+ sleep_ms(200);
+ }
+ if (gpio_get(RIGHT))
+ {
+ switch (selection) {
+ case 0:
+ menu[selection].action(DIAMETER);
+ break;
+
+ case 1:
+ gpio_put(ENABLE_PIN, 1); //disable stepper
+ break;
+
+ case 2:
+ DIAMETER = menu[selection].action(DIAMETER);
+ break;
+
+ case 3:
+ menu[selection].action(DIAMETER);
+ break;
+
+ case 4:
+ menu[selection].action(DIAMETER);
+ break;
+
+ default:
+ break;
+ }
+ update = true;
+ sleep_ms(200);
+ }
+
+ sleep_ms(10);
+
+ }
+
+ return 0;
+}
+
+void init() {
+ stdio_init_all();
+
+ //init pins
+ gpio_init(ENABLE_PIN);
+ gpio_init(STEP_PIN);
+ gpio_init(DIR_PIN);
+ gpio_init(MS1);
+ gpio_init(MS2);
+ gpio_init(MS3);
+ gpio_init(LEFT);
+ gpio_init(DOWN);
+ gpio_init(RIGHT);
+ gpio_init(UP);
+
+ //stepper driver stuff
+ gpio_set_dir(ENABLE_PIN, GPIO_OUT);
+ gpio_set_dir(STEP_PIN, GPIO_OUT);
+ gpio_set_dir(DIR_PIN, GPIO_OUT);
+ gpio_set_pulls(ENABLE_PIN, false, true); // pin, pullup, pulldown
+ gpio_set_pulls(STEP_PIN, false, true);
+ gpio_set_pulls(DIR_PIN, false, true);
+
+ gpio_put(ENABLE_PIN, 1); //disable stepper driver
+
+ gpio_set_dir(MS1, GPIO_OUT);
+ gpio_set_dir(MS2, GPIO_OUT);
+ gpio_set_dir(MS3, GPIO_OUT);
+ gpio_set_pulls(MS1, false, true);
+ gpio_set_pulls(MS2, false, true);
+ gpio_set_pulls(MS3, false, true);
+
+ //set microsteps ... 1/8
+ gpio_put(MS1, 1);
+ gpio_put(MS2, 1);
+ gpio_put(MS3, 0);
+
+ //switches
+ gpio_set_dir(LEFT, GPIO_IN);
+ gpio_set_dir(DOWN, GPIO_IN);
+ gpio_set_dir(RIGHT, GPIO_IN);
+ gpio_set_dir(UP, GPIO_IN);
+ gpio_set_pulls(LEFT, false, true);
+ gpio_set_pulls(DOWN, false, true);
+ gpio_set_pulls(RIGHT, false, true);
+ gpio_set_pulls(UP, false, true);
+
+ //init i2c
+ i2c_init(i2c0, 400000);
+ gpio_set_function(SDA_PIN, GPIO_FUNC_I2C);
+ gpio_set_function(SCL_PIN, GPIO_FUNC_I2C);
+ gpio_set_pulls(SDA_PIN, true, false);
+ gpio_set_pulls(SCL_PIN, true, false);
+
+ //init display
+ disp.external_vcc=false;
+ ssd1306_init(&disp, 128, 64, I2C_ADDR, i2c0);
+ ssd1306_clear(&disp);
+
+ DIAMETER = load_diameter_from_flash();
+}
+
+float set_diameter(float current_val) {
+ float temp_val = current_val;
+ bool editing = true;
+ sleep_ms(200);
+
+ while (editing) {
+ ssd1306_clear(&disp);
+ ssd1306_draw_string(&disp, 0, 0, 1, "Syringe Diameter:");
+
+ char buffer[10];
+ sprintf(buffer, "%.1f mm", temp_val);
+ ssd1306_draw_string(&disp, 0, 20, 2, buffer);
+
+ ssd1306_show(&disp);
+
+ if (gpio_get(UP))
+ {
+ temp_val += 0.1f;
+ sleep_ms(60);
+ }
+ if (gpio_get(DOWN) && temp_val > 0.0f)
+ {
+ temp_val -= 0.1f;
+ sleep_ms(200);
+ }
+ if (gpio_get(RIGHT))
+ {
+ save_diameter_to_flash(temp_val);
+ editing = false;
+ sleep_ms(200);
+ }
+ }
+ return temp_val;
+}
+
+void update_menu (){
+ ssd1306_clear(&disp);
+
+ for (int i = 0; i < max_visible; i++) {
+ int item_index = display_top + i;
+
+ if (item_index >= total_items) break;
+
+ int y_pos = i * 16;
+
+ if (item_index == selection) {
+ ssd1306_draw_string(&disp, 0, y_pos, 1, "> ");
+ ssd1306_draw_string(&disp, 15, y_pos, 1, menu[item_index].label);
+ } else {
+ ssd1306_draw_string(&disp, 15, y_pos, 1, menu[item_index].label);
+ }
+ }
+ ssd1306_show(&disp);
+}
+
+float infuse(float current_val) {
+ float radius = current_val / 2.0f;
+ float area = 3.14159265359f * radius * radius;
+ float ml_per_mm = area / 1000.0;
+ float steps_per_ml = STEPS_PER_MM / ml_per_mm;
+ float flowrate = 0.1;
+ float target_volume = 0.1;
+
+
+ float flowrate_tempval = 0.1;
+ float target_volume_tempval = 0.1;
+ bool editing = true;
+ bool editing_flowrate = true;
+ bool editing_volume = true;
+
+ sleep_ms(300);
+ while(editing) {
+ while(editing_flowrate) {
+ ssd1306_clear(&disp);
+ ssd1306_draw_string(&disp, 0, 0, 1, "Infuse");
+ ssd1306_draw_string(&disp, 0, 20, 1, "Flowrate:");
+
+ ssd1306_draw_string(&disp, 0, 30, 1, "> ");
+ char buffer_flowrate[32];
+ sprintf(buffer_flowrate, "%.1f ml/h", flowrate_tempval);
+ ssd1306_draw_string(&disp, 15, 30, 1, buffer_flowrate);
+
+
+ ssd1306_draw_string(&disp, 0, 40, 1, "Target volume");
+
+ char buffer_volume[32];
+ sprintf(buffer_volume, "%.1f ml", target_volume_tempval);
+ ssd1306_draw_string(&disp, 0, 50, 1, buffer_volume);
+ ssd1306_show(&disp); //update display
+
+ if (gpio_get(UP))
+ {
+ flowrate_tempval += 0.1f;
+ sleep_ms(60);
+ }
+ if (gpio_get(DOWN) && flowrate_tempval > 0.1f)
+ {
+ flowrate_tempval -= 0.1f;
+ sleep_ms(200);
+ }
+ if (gpio_get(RIGHT))
+ {
+ flowrate = flowrate_tempval;
+ editing_flowrate = false;
+ editing_volume = true;
+ sleep_ms(200);
+ }
+
+ }
+
+ while(editing_volume) {
+ ssd1306_clear(&disp);
+ ssd1306_draw_string(&disp, 0, 0, 1, "Infuse");
+ ssd1306_draw_string(&disp, 0, 20, 1, "Flowrate:");
+
+ char buffer_flowrate[10];
+ sprintf(buffer_flowrate, "%.1f ml/h", flowrate_tempval);
+ ssd1306_draw_string(&disp, 0, 30, 1, buffer_flowrate);
+
+
+ ssd1306_draw_string(&disp, 0, 40, 1, "Target volume");
+
+ ssd1306_draw_string(&disp, 0, 50, 1, "> ");
+ char buffer_volume[10];
+ sprintf(buffer_volume, "%.1f ml", target_volume_tempval);
+ ssd1306_draw_string(&disp, 15, 50, 1, buffer_volume);
+ ssd1306_show(&disp); //update display
+
+ if (gpio_get(UP))
+ {
+ target_volume_tempval += 0.1f;
+ sleep_ms(60);
+ }
+ if (gpio_get(DOWN) && target_volume_tempval > 0.1f)
+ {
+ target_volume_tempval -= 0.1f;
+ sleep_ms(200);
+ }
+ if (gpio_get(LEFT))
+ {
+ editing_flowrate = true;
+ editing_volume = false;
+ sleep_ms(200);
+ }
+ if (gpio_get(RIGHT))
+ {
+ target_volume = target_volume_tempval;
+ editing_flowrate = false;
+ editing_volume = false;
+ editing = false;
+ sleep_ms(200);
+ }
+ }
+ }
+
+ uint32_t total_steps = (uint32_t)(target_volume * steps_per_ml);
+ float ml_per_sec = flowrate / 3600.0;
+ float steps_per_sec = ml_per_sec * steps_per_ml;
+ uint32_t step_interval_us = (uint32_t)(1000000.0 / steps_per_sec); // interval of the step in microseconds
+
+ ssd1306_clear(&disp);
+ ssd1306_draw_string(&disp, 0, 0, 2, "Pumping...");
+ ssd1306_draw_string(&disp, 0, 25, 1, "Press LEFT to STOP");
+ ssd1306_show(&disp);
+
+ uint32_t current_steps = 0;
+ uint32_t last_step_time = time_us_32();
+ gpio_put(DIR_PIN, 1); //forward
+ gpio_put(ENABLE_PIN, 0); //enable stepper
+
+ while (current_steps < total_steps) {
+ uint32_t current_time = time_us_32();
+ if (current_time - last_step_time >= step_interval_us) {
+ gpio_put(STEP_PIN, 1);
+ sleep_us(2);
+ gpio_put(STEP_PIN, 0);
+
+ current_steps++;
+ last_step_time = current_time;
+ }
+
+ if (gpio_get(LEFT)) {
+ gpio_put(ENABLE_PIN, 1); //disable stepper
+ break;
+ }
+ }
+
+ gpio_put(ENABLE_PIN, 1); //disable stepper
+}
+
+float manual_move(float current_val) {
+ bool active = true;
+ gpio_put(ENABLE_PIN, 0); //enable stepper
+ sleep_ms(300);
+
+ ssd1306_clear(&disp);
+ ssd1306_draw_string(&disp, 0, 0, 1, "Manual Move");
+ ssd1306_show(&disp);
+
+ while (active) {
+ if (gpio_get(UP))
+ {
+ gpio_put(DIR_PIN, 0);
+ gpio_put(STEP_PIN, 1);
+ sleep_ms(1);
+ gpio_put(STEP_PIN, 0);
+ sleep_ms(1);
+ }
+ if (gpio_get(DOWN))
+ {
+ gpio_put(DIR_PIN, 1);
+ gpio_put(STEP_PIN, 1);
+ sleep_ms(1);
+ gpio_put(STEP_PIN, 0);
+ sleep_ms(1);
+ }
+ if (gpio_get(LEFT) || gpio_get(RIGHT))
+ {
+ gpio_put(ENABLE_PIN, 1); //disable stepper
+ active = false;
+ }
+ }
+
+ gpio_put(ENABLE_PIN, 1); //disable stepper
+ return 0;
+}
+
+#define SNAKE_W 64
+#define SNAKE_H 32
+#define CELL_SIZE 2
+
+typedef struct {
+ uint8_t x;
+ uint8_t y;
+} Point;
+
+Point snake[32];
+int snake_len = 3;
+Point food;
+uint8_t dir_x = 1;
+uint8_t dir_y = 0;
+uint8_t next_dir_x = 1;
+uint8_t next_dir_y = 0;
+int score = 0;
+
+void spawn_food() {
+ food.x = (rand() % (SNAKE_W - 2)) + 1;
+ food.y = (rand() % (SNAKE_H - 2)) + 1;
+ for (int i = 0; i < snake_len; i++) {
+ if (snake[i].x == food.x && snake[i].y == food.y) {
+ spawn_food();
+ return;
+ }
+ }
+}
+
+void draw_snake() {
+ for (int i = 0; i < snake_len; i++) {
+ ssd1306_draw_pixel(&disp, snake[i].x * CELL_SIZE, snake[i].y * CELL_SIZE);
+ ssd1306_draw_pixel(&disp, snake[i].x * CELL_SIZE + 1, snake[i].y * CELL_SIZE);
+ ssd1306_draw_pixel(&disp, snake[i].x * CELL_SIZE, snake[i].y * CELL_SIZE + 1);
+ ssd1306_draw_pixel(&disp, snake[i].x * CELL_SIZE + 1, snake[i].y * CELL_SIZE + 1);
+ }
+}
+
+void draw_food() {
+ ssd1306_draw_pixel(&disp, food.x * CELL_SIZE, food.y * CELL_SIZE);
+ ssd1306_draw_pixel(&disp, food.x * CELL_SIZE + 1, food.y * CELL_SIZE);
+ ssd1306_draw_pixel(&disp, food.x * CELL_SIZE, food.y * CELL_SIZE + 1);
+ ssd1306_draw_pixel(&disp, food.x * CELL_SIZE + 1, food.y * CELL_SIZE + 1);
+}
+
+float snake_game(float current_val) {
+ srand(1000);
+ snake_len = 3;
+ snake[0].x = SNAKE_W / 2;
+ snake[0].y = SNAKE_H / 2;
+ snake[1].x = snake[0].x - 1;
+ snake[1].y = snake[0].y;
+ snake[2].x = snake[0].x - 2;
+ snake[2].y = snake[0].y;
+ dir_x = 1;
+ dir_y = 0;
+ next_dir_x = 1;
+ next_dir_y = 0;
+ score = 0;
+ spawn_food();
+
+ bool playing = true;
+ int frame = 0;
+
+ sleep_ms(300);
+
+ while (playing) {
+ if (gpio_get(UP) && dir_y != 1) {
+ next_dir_x = 0; next_dir_y = -1;
+ }
+ if (gpio_get(DOWN) && dir_y != -1) {
+ next_dir_x = 0; next_dir_y = 1;
+ }
+ if (gpio_get(LEFT) && dir_x != 1) {
+ next_dir_x = -1; next_dir_y = 0;
+ }
+ if (gpio_get(RIGHT) && dir_x != -1) {
+ next_dir_x = 1; next_dir_y = 0;
+ }
+
+ if (frame % 5 == 0) {
+ dir_x = next_dir_x;
+ dir_y = next_dir_y;
+
+ Point head = {snake[0].x + dir_x, snake[0].y + dir_y};
+
+ if (head.x == 0 || head.x >= SNAKE_W - 1 || head.y == 0 || head.y >= SNAKE_H - 1) {
+ ssd1306_clear(&disp);
+ ssd1306_draw_string(&disp, 20, 20, 1, "Game Over!");
+ char buf[10];
+ sprintf(buf, "Score: %d", score);
+ ssd1306_draw_string(&disp, 20, 35, 1, buf);
+ ssd1306_show(&disp);
+ sleep_ms(2000);
+ playing = false;
+ continue;
+ }
+
+ for (int i = 1; i < snake_len; i++) {
+ if (head.x == snake[i].x && head.y == snake[i].y) {
+ ssd1306_clear(&disp);
+ ssd1306_draw_string(&disp, 20, 20, 1, "Game Over!");
+ char buf[10];
+ sprintf(buf, "Score: %d", score);
+ ssd1306_draw_string(&disp, 20, 35, 1, buf);
+ ssd1306_show(&disp);
+ sleep_ms(2000);
+ playing = false;
+ continue;
+ }
+ }
+
+ for (int i = snake_len; i > 0; i--) {
+ snake[i] = snake[i-1];
+ }
+ snake[0] = head;
+
+ if (head.x == food.x && head.y == food.y) {
+ snake_len++;
+ score++;
+ spawn_food();
+ }
+
+ ssd1306_clear(&disp);
+ ssd1306_draw_line(&disp, 0, 0, SNAKE_W * CELL_SIZE - 1, 0);
+ ssd1306_draw_line(&disp, 0, 0, 0, SNAKE_H * CELL_SIZE - 1);
+ ssd1306_draw_line(&disp, SNAKE_W * CELL_SIZE - 1, 0, SNAKE_W * CELL_SIZE - 1, SNAKE_H * CELL_SIZE - 1);
+ ssd1306_draw_line(&disp, 0, SNAKE_H * CELL_SIZE - 1, SNAKE_W * CELL_SIZE - 1, SNAKE_H * CELL_SIZE - 1);
+ draw_snake();
+ draw_food();
+ ssd1306_show(&disp);
+ }
+
+ frame++;
+ sleep_ms(25);
+ }
+
+ return 0;
+}
diff --git a/ssd1306.c b/ssd1306.c
new file mode 100644
index 0000000..bcac3c0
--- /dev/null
+++ b/ssd1306.c
@@ -0,0 +1,306 @@
+/*
+
+MIT License
+
+Copyright (c) 2021 David Schramm
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+#include <pico/stdlib.h>
+#include <hardware/i2c.h>
+#include <pico/binary_info.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "ssd1306.h"
+#include "font.h"
+
+inline static void swap(int32_t *a, int32_t *b) {
+ int32_t *t=a;
+ *a=*b;
+ *b=*t;
+}
+
+inline static void fancy_write(i2c_inst_t *i2c, uint8_t addr, const uint8_t *src, size_t len, char *name) {
+ switch(i2c_write_blocking(i2c, addr, src, len, false)) {
+ case PICO_ERROR_GENERIC:
+ printf("[%s] addr not acknowledged!\n", name);
+ break;
+ case PICO_ERROR_TIMEOUT:
+ printf("[%s] timeout!\n", name);
+ break;
+ default:
+ //printf("[%s] wrote successfully %lu bytes!\n", name, len);
+ break;
+ }
+}
+
+inline static void ssd1306_write(ssd1306_t *p, uint8_t val) {
+ uint8_t d[2]= {0x00, val};
+ fancy_write(p->i2c_i, p->address, d, 2, "ssd1306_write");
+}
+
+bool ssd1306_init(ssd1306_t *p, uint16_t width, uint16_t height, uint8_t address, i2c_inst_t *i2c_instance) {
+ p->width=width;
+ p->height=height;
+ p->pages=height/8;
+ p->address=address;
+
+ p->i2c_i=i2c_instance;
+
+
+ p->bufsize=(p->pages)*(p->width);
+ if((p->buffer=malloc(p->bufsize+1))==NULL) {
+ p->bufsize=0;
+ return false;
+ }
+
+ ++(p->buffer);
+
+ // from https://github.com/makerportal/rpi-pico-ssd1306
+ uint8_t cmds[]= {
+ SET_DISP,
+ // timing and driving scheme
+ SET_DISP_CLK_DIV,
+ 0x80,
+ SET_MUX_RATIO,
+ height - 1,
+ SET_DISP_OFFSET,
+ 0x00,
+ // resolution and layout
+ SET_DISP_START_LINE,
+ // charge pump
+ SET_CHARGE_PUMP,
+ p->external_vcc?0x10:0x14,
+ SET_SEG_REMAP | 0x01, // column addr 127 mapped to SEG0
+ SET_COM_OUT_DIR | 0x08, // scan from COM[N] to COM0
+ SET_COM_PIN_CFG,
+ width>2*height?0x02:0x12,
+ // display
+ SET_CONTRAST,
+ 0xff,
+ SET_PRECHARGE,
+ p->external_vcc?0x22:0xF1,
+ SET_VCOM_DESEL,
+ 0x30, // or 0x40?
+ SET_ENTIRE_ON, // output follows RAM contents
+ SET_NORM_INV, // not inverted
+ SET_DISP | 0x01,
+ // address setting
+ SET_MEM_ADDR,
+ 0x00, // horizontal
+ };
+
+ for(size_t i=0; i<sizeof(cmds); ++i)
+ ssd1306_write(p, cmds[i]);
+
+ return true;
+}
+
+inline void ssd1306_deinit(ssd1306_t *p) {
+ free(p->buffer-1);
+}
+
+inline void ssd1306_poweroff(ssd1306_t *p) {
+ ssd1306_write(p, SET_DISP|0x00);
+}
+
+inline void ssd1306_poweron(ssd1306_t *p) {
+ ssd1306_write(p, SET_DISP|0x01);
+}
+
+inline void ssd1306_contrast(ssd1306_t *p, uint8_t val) {
+ ssd1306_write(p, SET_CONTRAST);
+ ssd1306_write(p, val);
+}
+
+inline void ssd1306_invert(ssd1306_t *p, uint8_t inv) {
+ ssd1306_write(p, SET_NORM_INV | (inv & 1));
+}
+
+inline void ssd1306_clear(ssd1306_t *p) {
+ memset(p->buffer, 0, p->bufsize);
+}
+
+void ssd1306_clear_pixel(ssd1306_t *p, uint32_t x, uint32_t y) {
+ if(x>=p->width || y>=p->height) return;
+
+ p->buffer[x+p->width*(y>>3)]&=~(0x1<<(y&0x07));
+}
+
+void ssd1306_draw_pixel(ssd1306_t *p, uint32_t x, uint32_t y) {
+ if(x>=p->width || y>=p->height) return;
+
+ p->buffer[x+p->width*(y>>3)]|=0x1<<(y&0x07); // y>>3==y/8 && y&0x7==y%8
+}
+
+void ssd1306_draw_line(ssd1306_t *p, int32_t x1, int32_t y1, int32_t x2, int32_t y2) {
+ if(x1>x2) {
+ swap(&x1, &x2);
+ swap(&y1, &y2);
+ }
+
+ if(x1==x2) {
+ if(y1>y2)
+ swap(&y1, &y2);
+ for(int32_t i=y1; i<=y2; ++i)
+ ssd1306_draw_pixel(p, x1, i);
+ return;
+ }
+
+ float m=(float) (y2-y1) / (float) (x2-x1);
+
+ for(int32_t i=x1; i<=x2; ++i) {
+ float y=m*(float) (i-x1)+(float) y1;
+ ssd1306_draw_pixel(p, i, (uint32_t) y);
+ }
+}
+
+void ssd1306_clear_square(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t width, uint32_t height) {
+ for(uint32_t i=0; i<width; ++i)
+ for(uint32_t j=0; j<height; ++j)
+ ssd1306_clear_pixel(p, x+i, y+j);
+}
+
+void ssd1306_draw_square(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t width, uint32_t height) {
+ for(uint32_t i=0; i<width; ++i)
+ for(uint32_t j=0; j<height; ++j)
+ ssd1306_draw_pixel(p, x+i, y+j);
+}
+
+void ssd1306_draw_empty_square(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t width, uint32_t height) {
+ ssd1306_draw_line(p, x, y, x+width, y);
+ ssd1306_draw_line(p, x, y+height, x+width, y+height);
+ ssd1306_draw_line(p, x, y, x, y+height);
+ ssd1306_draw_line(p, x+width, y, x+width, y+height);
+}
+
+void ssd1306_draw_char_with_font(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t scale, const uint8_t *font, char c) {
+ if(c<font[3]||c>font[4])
+ return;
+
+ uint32_t parts_per_line=(font[0]>>3)+((font[0]&7)>0);
+ for(uint8_t w=0; w<font[1]; ++w) { // width
+ uint32_t pp=(c-font[3])*font[1]*parts_per_line+w*parts_per_line+5;
+ for(uint32_t lp=0; lp<parts_per_line; ++lp) {
+ uint8_t line=font[pp];
+
+ for(int8_t j=0; j<8; ++j, line>>=1) {
+ if(line & 1)
+ ssd1306_draw_square(p, x+w*scale, y+((lp<<3)+j)*scale, scale, scale);
+ }
+
+ ++pp;
+ }
+ }
+}
+
+void ssd1306_draw_string_with_font(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t scale, const uint8_t *font, const char *s) {
+ for(int32_t x_n=x; *s; x_n+=(font[1]+font[2])*scale) {
+ ssd1306_draw_char_with_font(p, x_n, y, scale, font, *(s++));
+ }
+}
+
+void ssd1306_draw_char(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t scale, char c) {
+ ssd1306_draw_char_with_font(p, x, y, scale, font_8x5, c);
+}
+
+void ssd1306_draw_string(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t scale, const char *s) {
+ ssd1306_draw_string_with_font(p, x, y, scale, font_8x5, s);
+}
+
+static inline uint32_t ssd1306_bmp_get_val(const uint8_t *data, const size_t offset, uint8_t size) {
+ switch(size) {
+ case 1:
+ return data[offset];
+ case 2:
+ return data[offset]|(data[offset+1]<<8);
+ case 4:
+ return data[offset]|(data[offset+1]<<8)|(data[offset+2]<<16)|(data[offset+3]<<24);
+ default:
+ __builtin_unreachable();
+ }
+ __builtin_unreachable();
+}
+
+void ssd1306_bmp_show_image_with_offset(ssd1306_t *p, const uint8_t *data, const long size, uint32_t x_offset, uint32_t y_offset) {
+ if(size<54) // data smaller than header
+ return;
+
+ const uint32_t bfOffBits=ssd1306_bmp_get_val(data, 10, 4);
+ const uint32_t biSize=ssd1306_bmp_get_val(data, 14, 4);
+ const uint32_t biWidth=ssd1306_bmp_get_val(data, 18, 4);
+ const int32_t biHeight=(int32_t) ssd1306_bmp_get_val(data, 22, 4);
+ const uint16_t biBitCount=(uint16_t) ssd1306_bmp_get_val(data, 28, 2);
+ const uint32_t biCompression=ssd1306_bmp_get_val(data, 30, 4);
+
+ if(biBitCount!=1) // image not monochrome
+ return;
+
+ if(biCompression!=0) // image compressed
+ return;
+
+ const int table_start=14+biSize;
+ uint8_t color_val=0;
+
+ for(uint8_t i=0; i<2; ++i) {
+ if(!((data[table_start+i*4]<<16)|(data[table_start+i*4+1]<<8)|data[table_start+i*4+2])) {
+ color_val=i;
+ break;
+ }
+ }
+
+ uint32_t bytes_per_line=(biWidth/8)+(biWidth&7?1:0);
+ if(bytes_per_line&3)
+ bytes_per_line=(bytes_per_line^(bytes_per_line&3))+4;
+
+ const uint8_t *img_data=data+bfOffBits;
+
+ int32_t step=biHeight>0?-1:1;
+ int32_t border=biHeight>0?-1:-biHeight;
+
+ for(uint32_t y=biHeight>0?biHeight-1:0; y!=(uint32_t)border; y+=step) {
+ for(uint32_t x=0; x<biWidth; ++x) {
+ if(((img_data[x>>3]>>(7-(x&7)))&1)==color_val)
+ ssd1306_draw_pixel(p, x_offset+x, y_offset+y);
+ }
+ img_data+=bytes_per_line;
+ }
+}
+
+inline void ssd1306_bmp_show_image(ssd1306_t *p, const uint8_t *data, const long size) {
+ ssd1306_bmp_show_image_with_offset(p, data, size, 0, 0);
+}
+
+void ssd1306_show(ssd1306_t *p) {
+ uint8_t payload[]= {SET_COL_ADDR, 0, p->width-1, SET_PAGE_ADDR, 0, p->pages-1};
+ if(p->width==64) {
+ payload[1]+=32;
+ payload[2]+=32;
+ }
+
+ for(size_t i=0; i<sizeof(payload); ++i)
+ ssd1306_write(p, payload[i]);
+
+ *(p->buffer-1)=0x40;
+
+ fancy_write(p->i2c_i, p->address, p->buffer-1, p->bufsize+1, "ssd1306_show");
+}
diff --git a/ssd1306.h b/ssd1306.h
new file mode 100644
index 0000000..49d1bb2
--- /dev/null
+++ b/ssd1306.h
@@ -0,0 +1,274 @@
+/*
+MIT License
+
+Copyright (c) 2021 David Schramm
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+/**
+* @file ssd1306.h
+*
+* simple driver for ssd1306 displays
+*/
+
+#ifndef _inc_ssd1306
+#define _inc_ssd1306
+#include <pico/stdlib.h>
+#include <hardware/i2c.h>
+
+/**
+* @brief defines commands used in ssd1306
+*/
+typedef enum {
+ SET_CONTRAST = 0x81,
+ SET_ENTIRE_ON = 0xA4,
+ SET_NORM_INV = 0xA6,
+ SET_DISP = 0xAE,
+ SET_MEM_ADDR = 0x20,
+ SET_COL_ADDR = 0x21,
+ SET_PAGE_ADDR = 0x22,
+ SET_DISP_START_LINE = 0x40,
+ SET_SEG_REMAP = 0xA0,
+ SET_MUX_RATIO = 0xA8,
+ SET_COM_OUT_DIR = 0xC0,
+ SET_DISP_OFFSET = 0xD3,
+ SET_COM_PIN_CFG = 0xDA,
+ SET_DISP_CLK_DIV = 0xD5,
+ SET_PRECHARGE = 0xD9,
+ SET_VCOM_DESEL = 0xDB,
+ SET_CHARGE_PUMP = 0x8D
+} ssd1306_command_t;
+
+/**
+* @brief holds the configuration
+*/
+typedef struct {
+ uint8_t width; /**< width of display */
+ uint8_t height; /**< height of display */
+ uint8_t pages; /**< stores pages of display (calculated on initialization*/
+ uint8_t address; /**< i2c address of display*/
+ i2c_inst_t *i2c_i; /**< i2c connection instance */
+ bool external_vcc; /**< whether display uses external vcc */
+ uint8_t *buffer; /**< display buffer */
+ size_t bufsize; /**< buffer size */
+} ssd1306_t;
+
+/**
+* @brief initialize display
+*
+* @param[in] p : pointer to instance of ssd1306_t
+* @param[in] width : width of display
+* @param[in] height : heigth of display
+* @param[in] address : i2c address of display
+* @param[in] i2c_instance : instance of i2c connection
+*
+* @return bool.
+* @retval true for Success
+* @retval false if initialization failed
+*/
+bool ssd1306_init(ssd1306_t *p, uint16_t width, uint16_t height, uint8_t address, i2c_inst_t *i2c_instance);
+
+/**
+* @brief deinitialize display
+*
+* @param[in] p : instance of display
+*
+*/
+void ssd1306_deinit(ssd1306_t *p);
+
+/**
+* @brief turn off display
+*
+* @param[in] p : instance of display
+*
+*/
+void ssd1306_poweroff(ssd1306_t *p);
+
+/**
+ @brief turn on display
+
+ @param[in] p : instance of display
+
+*/
+void ssd1306_poweron(ssd1306_t *p);
+
+/**
+ @brief set contrast of display
+
+ @param[in] p : instance of display
+ @param[in] val : contrast
+
+*/
+void ssd1306_contrast(ssd1306_t *p, uint8_t val);
+
+/**
+ @brief set invert display
+
+ @param[in] p : instance of display
+ @param[in] inv : inv==0: disable inverting, inv!=0: invert
+
+*/
+void ssd1306_invert(ssd1306_t *p, uint8_t inv);
+
+/**
+ @brief display buffer, should be called on change
+
+ @param[in] p : instance of display
+
+*/
+void ssd1306_show(ssd1306_t *p);
+
+/**
+ @brief clear display buffer
+
+ @param[in] p : instance of display
+
+*/
+void ssd1306_clear(ssd1306_t *p);
+
+/**
+ @brief clear pixel on buffer
+
+ @param[in] p : instance of display
+ @param[in] x : x position
+ @param[in] y : y position
+*/
+void ssd1306_clear_pixel(ssd1306_t *p, uint32_t x, uint32_t y);
+
+/**
+ @brief draw pixel on buffer
+
+ @param[in] p : instance of display
+ @param[in] x : x position
+ @param[in] y : y position
+*/
+void ssd1306_draw_pixel(ssd1306_t *p, uint32_t x, uint32_t y);
+
+/**
+ @brief draw line on buffer
+
+ @param[in] p : instance of display
+ @param[in] x1 : x position of starting point
+ @param[in] y1 : y position of starting point
+ @param[in] x2 : x position of end point
+ @param[in] y2 : y position of end point
+*/
+void ssd1306_draw_line(ssd1306_t *p, int32_t x1, int32_t y1, int32_t x2, int32_t y2);
+
+/**
+ @brief clear square at given position with given size
+
+ @param[in] p : instance of display
+ @param[in] x : x position of starting point
+ @param[in] y : y position of starting point
+ @param[in] width : width of square
+ @param[in] height : height of square
+*/
+void ssd1306_clear_square(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t width, uint32_t height);
+
+/**
+ @brief draw filled square at given position with given size
+
+ @param[in] p : instance of display
+ @param[in] x : x position of starting point
+ @param[in] y : y position of starting point
+ @param[in] width : width of square
+ @param[in] height : height of square
+*/
+void ssd1306_draw_square(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t width, uint32_t height);
+
+/**
+ @brief draw empty square at given position with given size
+
+ @param[in] p : instance of display
+ @param[in] x : x position of starting point
+ @param[in] y : y position of starting point
+ @param[in] width : width of square
+ @param[in] height : height of square
+*/
+void ssd1306_draw_empty_square(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t width, uint32_t height);
+
+/**
+ @brief draw monochrome bitmap with offset
+
+ @param[in] p : instance of display
+ @param[in] data : image data (whole file)
+ @param[in] size : size of image data in bytes
+ @param[in] x_offset : offset of horizontal coordinate
+ @param[in] y_offset : offset of vertical coordinate
+*/
+void ssd1306_bmp_show_image_with_offset(ssd1306_t *p, const uint8_t *data, const long size, uint32_t x_offset, uint32_t y_offset);
+
+/**
+ @brief draw monochrome bitmap
+
+ @param[in] p : instance of display
+ @param[in] data : image data (whole file)
+ @param[in] size : size of image data in bytes
+*/
+void ssd1306_bmp_show_image(ssd1306_t *p, const uint8_t *data, const long size);
+
+/**
+ @brief draw char with given font
+
+ @param[in] p : instance of display
+ @param[in] x : x starting position of char
+ @param[in] y : y starting position of char
+ @param[in] scale : scale font to n times of original size (default should be 1)
+ @param[in] font : pointer to font
+ @param[in] c : character to draw
+*/
+void ssd1306_draw_char_with_font(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t scale, const uint8_t *font, char c);
+
+/**
+ @brief draw char with builtin font
+
+ @param[in] p : instance of display
+ @param[in] x : x starting position of char
+ @param[in] y : y starting position of char
+ @param[in] scale : scale font to n times of original size (default should be 1)
+ @param[in] c : character to draw
+*/
+void ssd1306_draw_char(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t scale, char c);
+
+/**
+ @brief draw string with given font
+
+ @param[in] p : instance of display
+ @param[in] x : x starting position of text
+ @param[in] y : y starting position of text
+ @param[in] scale : scale font to n times of original size (default should be 1)
+ @param[in] font : pointer to font
+ @param[in] s : text to draw
+*/
+void ssd1306_draw_string_with_font(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t scale, const uint8_t *font, const char *s );
+
+/**
+ @brief draw string with builtin font
+
+ @param[in] p : instance of display
+ @param[in] x : x starting position of text
+ @param[in] y : y starting position of text
+ @param[in] scale : scale font to n times of original size (default should be 1)
+ @param[in] s : text to draw
+*/
+void ssd1306_draw_string(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t scale, const char *s);
+
+#endif