2 Commits

Author SHA1 Message Date
  Thomas Buck 1e0c69663e add firmware workflow for gh actions 2 weeks ago
  Thomas Buck 125e2b4763 print bat percent 2 weeks ago
10 changed files with 168 additions and 33 deletions
  1. 78
    0
      .github/workflows/cmake.yml
  2. 1
    0
      README.md
  3. 1
    0
      docs/src/introduction.md
  4. 1
    0
      include/adc.h
  5. 3
    0
      include/lcd.h
  6. 28
    1
      src/adc.c
  7. 42
    30
      src/lcd.c
  8. 7
    0
      src/main.c
  9. 1
    0
      src/sequence.c
  10. 6
    2
      src/ui.c

+ 78
- 0
.github/workflows/cmake.yml View File

@@ -0,0 +1,78 @@
1
+# https://github.com/raspberrypi/pico-examples/blob/master/.github/workflows/cmake.yml
2
+
3
+name: Firmware
4
+
5
+# build for each push and pull request
6
+on: [push, pull_request]
7
+
8
+env:
9
+  BUILD_TYPE: Release
10
+
11
+jobs:
12
+  build:
13
+    runs-on: ubuntu-latest
14
+
15
+    permissions:
16
+      contents: write
17
+
18
+    steps:
19
+      - name: Install dependencies
20
+        run: sudo apt-get install -y cxxtest build-essential gcc-arm-none-eabi mtools zip
21
+
22
+      - name: Checkout repo
23
+        uses: actions/checkout@v4
24
+        with:
25
+          path: repo
26
+          fetch-depth: 0
27
+
28
+      - name: Checkout repo submodules
29
+        working-directory: ${{github.workspace}}/repo
30
+        run: git submodule update --init
31
+
32
+      - name: Checkout pico-sdk submodules
33
+        working-directory: ${{github.workspace}}/repo/pico-sdk
34
+        run: git submodule update --init
35
+
36
+      - name: Create Build Environment
37
+        working-directory: ${{github.workspace}}/repo
38
+        run:  cmake -E make_directory ${{github.workspace}}/repo/build
39
+
40
+      - name: Configure CMake
41
+        shell: bash
42
+        working-directory: ${{github.workspace}}/repo/build
43
+        run: cmake .. -DCMAKE_BUILD_TYPE=$BUILD_TYPE
44
+
45
+      - name: Get core count
46
+        id: core_count
47
+        run : cat /proc/cpuinfo  | grep processor | wc -l
48
+
49
+      - name: Build
50
+        working-directory: ${{github.workspace}}/repo/build
51
+        shell: bash
52
+        run: cmake --build . --config $BUILD_TYPE --parallel $(nproc)
53
+
54
+      - name: Upload a Build Artifact
55
+        uses: actions/upload-artifact@v4.0.0
56
+        with:
57
+          name: firmware.uf2
58
+          path: ${{github.workspace}}/repo/build/drumkit.uf2
59
+          if-no-files-found: error
60
+
61
+      - name: Upload a Build Artifact
62
+        uses: actions/upload-artifact@v4.0.0
63
+        with:
64
+          name: update.elf
65
+          path: ${{github.workspace}}/repo/build/drumkit.elf
66
+          if-no-files-found: error
67
+
68
+      - name: Archive release files
69
+        if: startsWith(github.ref, 'refs/tags/')
70
+        run: |
71
+          cd ${{github.workspace}}/repo/build
72
+          zip -r firmware drumkit.uf2 drumkit.elf
73
+
74
+      - name: Upload release files
75
+        if: startsWith(github.ref, 'refs/tags/')
76
+        uses: softprops/action-gh-release@v1
77
+        with:
78
+          files: ${{github.workspace}}/repo/build/firmware.zip

+ 1
- 0
README.md View File

@@ -3,6 +3,7 @@
3 3
 ![PCB](https://github.com/xythobuz/lars/actions/workflows/kicad.yml/badge.svg)
4 4
 ![Docs](https://github.com/xythobuz/lars/actions/workflows/docs.yml/badge.svg)
5 5
 ![STLs](https://github.com/xythobuz/lars/actions/workflows/scad.yml/badge.svg)
6
+![Firmware](https://github.com/xythobuz/lars/actions/workflows/cmake.yml/badge.svg)
6 7
 
7 8
 This is a simple drum machine / loopstation.
8 9
 It's made for three hand-wound solenoids mounted to a tambourine.

+ 1
- 0
docs/src/introduction.md View File

@@ -3,6 +3,7 @@
3 3
 ![PCB](https://github.com/xythobuz/lars/actions/workflows/kicad.yml/badge.svg)
4 4
 ![Docs](https://github.com/xythobuz/lars/actions/workflows/docs.yml/badge.svg)
5 5
 ![STLs](https://github.com/xythobuz/lars/actions/workflows/scad.yml/badge.svg)
6
+![Firmware](https://github.com/xythobuz/lars/actions/workflows/cmake.yml/badge.svg)
6 7
 
7 8
 This is a simple drum machine / loopstation.
8 9
 It's made for three hand-wound solenoids mounted to a tambourine.

+ 1
- 0
include/adc.h View File

@@ -21,5 +21,6 @@
21 21
 
22 22
 void bat_init(void);
23 23
 float bat_get(void);
24
+float bat_to_percent(float voltage);
24 25
 
25 26
 #endif // __ADC_H__

+ 3
- 0
include/lcd.h View File

@@ -19,12 +19,15 @@
19 19
 #ifndef __LCD_H__
20 20
 #define __LCD_H__
21 21
 
22
+#include <stdint.h>
23
+
22 24
 #define LCD_WIDTH 128
23 25
 #define LCD_HEIGHT 64
24 26
 
25 27
 void lcd_init(void);
26 28
 void lcd_draw(const char *mode, const char *val, const char *bat);
27 29
 void lcd_draw_bye(void);
30
+void lcd_draw_bitmap(uint8_t *data, int width, int height, int x_off, int y_off);
28 31
 
29 32
 void lcd_debug_buttons(void);
30 33
 

+ 28
- 1
src/adc.c View File

@@ -17,11 +17,14 @@
17 17
  */
18 18
 
19 19
 #include <stdio.h>
20
+#include <math.h>
20 21
 #include "pico/stdlib.h"
21 22
 #include "hardware/adc.h"
22 23
 
23 24
 #include "adc.h"
24 25
 
26
+#define LIPO_USE_PERCENTAGE_CURVE
27
+
25 28
 #define ADC_NUM 2
26 29
 #define ADC_PIN (26 + ADC_NUM)
27 30
 
@@ -32,9 +35,14 @@
32 35
 #define BAT_R1 10000.0f
33 36
 #define BAT_R2 18000.0f
34 37
 
35
-#define FILTER_OLD 0.75f
38
+#define FILTER_OLD 0.95f
36 39
 #define FILTER_NEW (1.0f - FILTER_OLD)
37 40
 
41
+#ifndef LIPO_USE_PERCENTAGE_CURVE
42
+static const float full_battery = 4.1f;
43
+static const float empty_battery = 3.2f;
44
+#endif // ! LIPO_USE_PERCENTAGE_CURVE
45
+
38 46
 static float filtered = 0.0f;
39 47
 
40 48
 static float bat_read(void) {
@@ -56,3 +64,22 @@ float bat_get(void) {
56 64
     filtered = (filtered * FILTER_OLD) + (bat_read() * FILTER_NEW);
57 65
     return filtered;
58 66
 }
67
+
68
+float bat_to_percent(float voltage) {
69
+    float percentage = 0.0f;
70
+
71
+#ifdef LIPO_USE_PERCENTAGE_CURVE
72
+    /*
73
+     * Try to linearize the LiPo discharge curve.
74
+     * https://electronics.stackexchange.com/a/551667
75
+     *
76
+     * Seems to work relatively well, although
77
+     * "stopping" at 3.5V feels a bit high to me.
78
+     */
79
+    percentage = 123.0f - (123.0f / powf(1.0f + powf(voltage / 3.7f, 80.0f), 0.165f));
80
+#else // LIPO_USE_PERCENTAGE_CURVE
81
+    percentage = 100.0f * ((voltage - empty_battery) / (full_battery - empty_battery));
82
+#endif // LIPO_USE_PERCENTAGE_CURVE
83
+
84
+    return MIN(MAX(percentage, 0.0f), 100.0f);
85
+}

+ 42
- 30
src/lcd.c View File

@@ -16,16 +16,11 @@
16 16
  * See <http://www.gnu.org/licenses/>.
17 17
  */
18 18
 
19
-#include <stdio.h>
20
-#include <stdint.h>
21
-#include <string.h>
22
-#include "pico/stdlib.h"
23 19
 #include "hardware/i2c.h"
24 20
 
25 21
 #include "ssd1306.h"
26 22
 
27 23
 #include "buttons.h"
28
-#include "logo.h"
29 24
 #include "main.h"
30 25
 #include "lcd.h"
31 26
 
@@ -37,7 +32,7 @@ static const uint gpio_num_v2[2] = { 16, 17 };
37 32
 
38 33
 #define LCD_ADDR 0x3C
39 34
 
40
-static ssd1306_t disp;
35
+static ssd1306_t disp = {0};
41 36
 static bool buttons[NUM_BTNS] = {0};
42 37
 static bool changed = true;
43 38
 
@@ -48,72 +43,89 @@ static void lcd_debug_buttons_callback(enum buttons btn, bool v) {
48 43
 
49 44
 void lcd_debug_buttons(void) {
50 45
     buttons_callback(lcd_debug_buttons_callback);
46
+
51 47
     while (1) {
52 48
         buttons_run();
53 49
         handle_serial_input();
50
+
54 51
         if (changed) {
55 52
             changed = false;
53
+
56 54
             ssd1306_clear(&disp);
57 55
             ssd1306_draw_string(&disp, 0, 0, 3, "Buttons");
56
+
58 57
             for (uint i = 0; i < NUM_BTNS; i++) {
58
+                if ((hw_type == HW_PROTOTYPE) && (i >= BTN_D) && (i <= BTN_H)) {
59
+                    continue;
60
+                }
61
+
59 62
                 ssd1306_draw_char(&disp,
60
-                                    i * 12, LCD_HEIGHT - 20 - 16 - 1,
61
-                                    2, '0' + i);
63
+                                  i * 12, LCD_HEIGHT - 20 - 16 - 1,
64
+                                  2, '0' + i);
65
+
62 66
                 if (buttons[i]) {
63 67
                     ssd1306_draw_square(&disp,
64 68
                                         i * 12, LCD_HEIGHT - 20 - 1,
65 69
                                         10, 20);
66 70
                 } else {
67 71
                     ssd1306_draw_empty_square(&disp,
68
-                                                i * 12, LCD_HEIGHT - 20 - 1,
69
-                                                10, 20);
72
+                                              i * 12, LCD_HEIGHT - 20 - 1,
73
+                                              10, 20);
70 74
                 }
71 75
             }
76
+
72 77
             ssd1306_show(&disp);
73 78
         }
74 79
     }
75 80
 }
76 81
 
82
+void lcd_draw_bitmap(uint8_t *data, int width, int height, int x_off, int y_off) {
83
+    ssd1306_clear(&disp);
84
+
85
+    for (int y = 0; y < height; y++) {
86
+        for (int x = 0; x < width; x++) {
87
+            const uint pos = y * width + x;
88
+            const uint bit = 7 - (pos % 8);
89
+            if (data[pos / 8] & (1 << bit)) {
90
+                ssd1306_draw_pixel(&disp, x + x_off, y + y_off);
91
+            }
92
+        }
93
+    }
94
+
95
+    ssd1306_show(&disp);
96
+}
97
+
77 98
 void lcd_init(void) {
78 99
     if (hw_type == HW_PROTOTYPE) {
79
-        i2c_init(gpio_i2c_proto, 1000 * 1000);
100
+        i2c_init(gpio_i2c_proto, 2UL * 1000UL * 1000UL);
101
+
80 102
         for (uint i = 0; i < sizeof(gpio_num_proto) / sizeof(gpio_num_proto[0]); i++) {
81 103
             gpio_set_function(gpio_num_proto[i], GPIO_FUNC_I2C);
82 104
             gpio_pull_up(gpio_num_proto[i]);
83 105
         }
84 106
 
85
-        disp.external_vcc = false;
86
-        ssd1306_init(&disp, LCD_WIDTH, LCD_HEIGHT, LCD_ADDR, gpio_i2c_proto);
107
+        ssd1306_init(&disp,
108
+                     LCD_WIDTH, LCD_HEIGHT,
109
+                     LCD_ADDR, gpio_i2c_proto);
87 110
     } else if (hw_type == HW_V2) {
88
-        i2c_init(gpio_i2c_v2, 1000 * 1000);
111
+        i2c_init(gpio_i2c_v2, 2UL * 1000UL * 1000UL);
112
+
89 113
         for (uint i = 0; i < sizeof(gpio_num_v2) / sizeof(gpio_num_v2[0]); i++) {
90 114
             gpio_set_function(gpio_num_v2[i], GPIO_FUNC_I2C);
91 115
             gpio_pull_up(gpio_num_v2[i]);
92 116
         }
93 117
 
94
-        disp.external_vcc = false;
95
-        ssd1306_init(&disp, LCD_WIDTH, LCD_HEIGHT, LCD_ADDR, gpio_i2c_v2);
118
+        ssd1306_init(&disp,
119
+                     LCD_WIDTH, LCD_HEIGHT,
120
+                     LCD_ADDR, gpio_i2c_v2);
96 121
     }
97
-
98
-    // show logo
99
-    ssd1306_clear(&disp);
100
-    for (uint y = 0; y < LOGO_HEIGHT; y++) {
101
-        for (uint x = 0; x < LOGO_WIDTH; x++) {
102
-            const uint pos = y * LOGO_WIDTH + x;
103
-            const uint bit = 7 - (pos % 8);
104
-            if (logo_data[pos / 8] & (1 << bit)) {
105
-                ssd1306_draw_pixel(&disp, x, y);
106
-            }
107
-        }
108
-    }
109
-    ssd1306_show(&disp);
110 122
 }
111 123
 
112 124
 void lcd_draw(const char *mode, const char *val, const char *bat) {
113 125
     ssd1306_clear(&disp);
114 126
     ssd1306_draw_string(&disp, 0, 0, 2, mode);
115 127
     ssd1306_draw_string(&disp, 0, 20, 4, val);
116
-    ssd1306_draw_string(&disp, 0, LCD_HEIGHT - 1 - 10, 1, bat);
128
+    ssd1306_draw_string(&disp, 0, LCD_HEIGHT - 8, 1, bat);
117 129
     ssd1306_show(&disp);
118 130
 }
119 131
 

+ 7
- 0
src/main.c View File

@@ -26,6 +26,7 @@
26 26
 #include "encoder.h"
27 27
 #include "lcd.h"
28 28
 #include "led.h"
29
+#include "logo.h"
29 30
 #include "pulse.h"
30 31
 #include "sequence.h"
31 32
 #include "ui.h"
@@ -81,6 +82,11 @@ int main(void) {
81 82
     lcd_init();
82 83
     led_init();
83 84
 
85
+    // show logo
86
+    lcd_draw_bitmap(logo_data,
87
+                    LOGO_WIDTH, LOGO_HEIGHT,
88
+                    0, 0);
89
+
84 90
     // read out button state for debug options
85 91
     buttons_callback(debug_buttons_callback);
86 92
     for (uint i = 0; i < (DEBOUNCE_DELAY_MS + 5); i++) {
@@ -89,6 +95,7 @@ int main(void) {
89 95
         sleep_ms(1);
90 96
     }
91 97
 
98
+    // handle special button combos on boot
92 99
     if (debug_buttons[BTN_REC] && debug_buttons[BTN_CLICK]) {
93 100
         lcd_debug_buttons();
94 101
     } else if (debug_buttons[BTN_REC] && (!debug_buttons[BTN_CLICK])) {

+ 1
- 0
src/sequence.c View File

@@ -220,6 +220,7 @@ void sequence_run(void) {
220 220
                 pulse_trigger_out(ch, channel_times[ch]);
221 221
                 if (ui_get_machinemode() == MODE_LOOPSTATION) {
222 222
                     pulse_trigger_led(ch, channel_times[ch]);
223
+                    pulse_trigger_led(ch + 4, channel_times[ch]);
223 224
                 }
224 225
             }
225 226
         }

+ 6
- 2
src/ui.c View File

@@ -35,6 +35,7 @@ static enum ui_modes ui_mode = 0;
35 35
 static enum machine_modes machine_mode = 0;
36 36
 static uint32_t last_bat_fetch = 0;
37 37
 static float last_voltage = 0.0f;
38
+static float last_percentage = 0.0f;
38 39
 
39 40
 enum machine_modes ui_get_machinemode(void) {
40 41
     return machine_mode;
@@ -99,7 +100,7 @@ static void ui_redraw(void) {
99 100
         }
100 101
     }
101 102
 
102
-    snprintf(bat, sizeof(bat) - 1, "Bat: %.2fV", last_voltage);
103
+    snprintf(bat, sizeof(bat) - 1, "Bat: %.1f%% (%.2fV)", last_percentage, last_voltage);
103 104
     lcd_draw(mode, val, bat);
104 105
 }
105 106
 
@@ -286,8 +287,11 @@ void ui_run(void) {
286 287
         last_bat_fetch = now;
287 288
 
288 289
         float volt = bat_get();
289
-        if (fabsf(volt - last_voltage) >= 0.01f) {
290
+        float percentage = bat_to_percent(volt);
291
+        if ((fabsf(volt - last_voltage) >= 0.01f)
292
+                || (fabsf(percentage - last_percentage) >= 0.1f)) {
290 293
             last_voltage = volt;
294
+            last_percentage = percentage;
291 295
             ui_redraw();
292 296
         }
293 297
     }

Loading…
Cancel
Save