summaryrefslogtreecommitdiff
path: root/main.c
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 /main.c
Version 1main
Diffstat (limited to 'main.c')
-rw-r--r--main.c577
1 files changed, 577 insertions, 0 deletions
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;
+}