From ad8934a30fe34562094656e5de71fc118b627420 Mon Sep 17 00:00:00 2001 From: flowerg9 Date: Mon, 13 Apr 2026 19:47:57 +0200 Subject: Version 1 --- CMakeLists.txt | 24 +++ font.h | 110 +++++++++++ main.c | 577 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ssd1306.c | 306 ++++++++++++++++++++++++++++++ ssd1306.h | 274 +++++++++++++++++++++++++++ 5 files changed, 1291 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 font.h create mode 100644 main.c create mode 100644 ssd1306.c create mode 100644 ssd1306.h 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 + * , , , + * , , + * + */ +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 +#include +#include +#include +#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 +#include +#include +#include +#include +#include + +#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; ibuffer-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; ifont[4]) + return; + + uint32_t parts_per_line=(font[0]>>3)+((font[0]&7)>0); + for(uint8_t w=0; w>=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>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; ibuffer-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 +#include + +/** +* @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 -- cgit