Browse Source

various firmware changes. fix measurement. add uv led support. small values with decimal places. fix graph. set prescaler.

Thomas Buck 1 year ago
parent
commit
a4c9381396

+ 73
- 19
README.md View File

@@ -1,34 +1,88 @@
1 1
 # OpenChrono
2 2
 
3 3
 Chronograph for Airsoft use, released as Free Open Source hardware and software!
4
-Uses a 3D printed housing to hold an Arduino, an OLED, batteries and two photosensitive light barriers.
4
+
5
+Uses a 3D printed housing to hold an Arduino, an OLED display, batteries and two photosensitive IR light barriers.
6
+
7
+Fixed mounting on the front of the gun in the style of a silencer.
8
+
9
+Can optionally also include UV LEDs to illuminate tracer BBs.
5 10
 
6 11
 ## Hardware
7 12
 
8
-Take the STL files from the 'hardware' directory or modify the included OpenSCAD design and create your own custom STLs.
13
+Use the included OpenSCAD design file in `hardware/openchrono.scad` to render your own custom STLs that fit your use-case.
9 14
 
10 15
 ### Required Parts
11 16
 
12
-| Description        | Type          | Count  |
13
-| ------------------ | ------------- | ------ |
14
-| Arduino Nano       |               | 1x     |
15
-| LCD 128x64 I2C     | SSD1306 0.96" | 1x     |
16
-| Slide Switch       |               | 1x     |
17
-| IR Phototransistor | SFH 309 FA-5  | 2x     |
18
-| IR LED 3mm         |               | 2x     |
19
-| Resistor           | **TODO**      | 2x     |
20
-| Resistor           | **TODO**      | 1x     |
21
-| Screw              | M2 10mm       | 4x     |
22
-| Screw              | M2.5 10mm     | 2x     |
23
-| Screw              | M3 10mm       | 4x     |
24
-| Heatmelt Insert    | M3 8mm        | 4x     |
25
-| AAA Battery        |               | 3x     |
26
-| Bat. Terminal Neg. |               | 3x     |
27
-| Bat. Terminal Pos. |               | 3x     |
17
+Besides some common stuff like soldering wire and hotglue you need the following parts to build this project.
18
+
19
+| Description        | Type          | Count |
20
+| ------------------ | ------------- | ----- |
21
+| Arduino Nano       |               | 1x    |
22
+| LCD 128x64 I2C     | SSD1306 0.96" | 1x    |
23
+| Slide Switch       |               | 1x    |
24
+| IR Phototransistor | SFH 309 FA-5  | 2x    |
25
+| IR LED 3mm         |               | 2x    |
26
+| Resistor           | 1k Ohm        | 2x    |
27
+| Resistor           | 100 Ohm       | 1x    |
28
+| Screw              | M2 10mm       | 4x    |
29
+| Screw              | M2.5 10mm     | 2x    |
30
+| Screw              | M3 16mm       | 8x    |
31
+| Heatmelt Insert    | M3 <= 10mm    | 8x    |
32
+
33
+For the UV tracer option you also need the following parts.
34
+
35
+| Description | Type    | Count |
36
+| ----------- | ------- | ----- |
37
+| UV LED 3mm  |         | 2x    |
38
+| Resistor    | 100 Ohm | 1x    |
39
+
40
+You have different options for powering the project.
41
+My first version for testing uses a pre-made AA battery holder.
42
+
43
+| Description    | Type   | Count |
44
+| -------------- | ------ | ----- |
45
+| AA Battery     |        | 3x    |
46
+| AA Bat. Holder |        | 1x    |
47
+| Screw (sunk)   | M3 6mm | 2x    |
48
+
49
+The originally intended variant is a AAA battery holder printed into the model.
50
+I don't have the terminals for that yet so it is not finished.
51
+
52
+| Description        | Type | Count |
53
+| ------------------ | ---- | ----- |
54
+| AAA Battery        |      | 3x    |
55
+| Bat. Terminal Neg. |      | 3x    |
56
+| Bat. Terminal Pos. |      | 3x    |
57
+
58
+I'm also looking to design a LiPo version with charger included in the future.
28 59
 
29 60
 ## Software
30 61
 
31 62
 This project uses the [U8g2 library by olikraus](https://github.com/olikraus/u8g2) to draw to the I2C OLED display.
32 63
 
33 64
 You can compile and flash the software using either PlatformIO or the standard Arduino IDE.
34
-In the latter case, install the U8g2 library using the Arduino IDE Library Manager and then flash as usual.
65
+
66
+With the Arduino IDE [install the U8g2 library using the Library Manager](https://github.com/olikraus/u8g2/wiki/u8g2install) and then flash as usual.
67
+
68
+For PlatformIO run something like the following command.
69
+
70
+    pio run -t upload --upload-port /dev/ttyUSB0
71
+
72
+Replace `/dev/ttyUSB0` with the port you are using.
73
+
74
+### Configuration
75
+
76
+Take a look at `firmware/OpenChrono/config.h`.
77
+This file contains all the settings you can change as a user.
78
+
79
+The most important setting is `SENSOR_DISTANCE`, which is given from the 3D model of the case.
80
+It is echoed when rendering the OpenSCAD design.
81
+
82
+You can set `BB_WEIGHT` to the one you use most commonly, and `BB_WEIGHTS` to others interesting for you (`BB_WEIGHTS` should include `BB_WEIGHT`).
83
+These values are used to calculate the energy in Joules.
84
+
85
+Set `PREFERRED_UNITS` to what you would like to see in the 2D graph.
86
+
87
+The range of speeds that can be measured is determined by `TIMER_PRESCALER`.
88
+Take a look at the comment in `firmware/OpenChrono/ticks.cpp` for details.

+ 15
- 4
firmware/OpenChrono/OpenChrono.ino View File

@@ -21,7 +21,7 @@ static void calculate(uint16_t a, uint16_t b) {
21 21
         ticks = b - a;
22 22
     } else {
23 23
         // the timer overflowed between measurements!
24
-        uint32_t tmp = ((uint32_t)b) - ((uint32_t)a);
24
+        int32_t tmp = ((int32_t)b) - ((int32_t)a);
25 25
         tmp += 0x10000;
26 26
         ticks = (uint16_t)tmp;
27 27
     }
@@ -45,9 +45,14 @@ static void measure() {
45 45
 }
46 46
 
47 47
 void setup() {
48
+    // we simply turn on the IR LEDs all the time
48 49
     pinMode(IR_LED_PIN, OUTPUT);
49 50
     digitalWrite(IR_LED_PIN, HIGH);
50 51
 
52
+    // but the UV LEDs will only be pulsed on firing!
53
+    pinMode(UV_LED_PIN, OUTPUT);
54
+    digitalWrite(UV_LED_PIN, LOW);
55
+
51 56
     lcd_init();
52 57
     delay(SCREEN_TIMEOUT); // show splash screen
53 58
 
@@ -56,9 +61,15 @@ void setup() {
56 61
 }
57 62
 
58 63
 void loop() {
59
-    if ((time_a == 1) && (time_b == 1)) {
60
-        // we got an event on both inputs
61
-        measure();
64
+    if (trigger_b) {
65
+        if (trigger_a) {
66
+            // we got an event on both inputs
67
+            measure();
68
+        } else {
69
+            // we got a false second trigger!
70
+            // clear so next calculation will be correct
71
+            trigger_b = 0;
72
+        }
62 73
     }
63 74
 
64 75
     lcd_loop();

+ 7
- 2
firmware/OpenChrono/config.h View File

@@ -15,9 +15,8 @@
15 15
 
16 16
 // --------------------------------------
17 17
 
18
-// hardware details
19
-
20 18
 #define SENSOR_DISTANCE 70.0 /* in mm */
19
+
21 20
 #define BB_WEIGHT 0.25 /* in g */
22 21
 #define BB_WEIGHTS { 0.20, 0.23, 0.25, 0.28, 0.475 } /* in g */
23 22
 
@@ -74,6 +73,12 @@
74 73
 // --------------------------------------
75 74
 
76 75
 #define IR_LED_PIN 4
76
+#define UV_LED_PIN 5
77
+
78
+// --------------------------------------
79
+
80
+// allowed values: 1, 8, 64, 256, 1024
81
+#define TIMER_PRESCALER 64
77 82
 
78 83
 // --------------------------------------
79 84
 

+ 34
- 8
firmware/OpenChrono/lcd.cpp View File

@@ -166,7 +166,11 @@ void lcd_draw(uint8_t screen) {
166 166
 
167 167
         uint8_t left_off = (u8g2.getDisplayHeight() - (u8g2.getMaxCharHeight() * 4 + 3)) / 2;
168 168
 
169
-        s = String(metric, 0);
169
+        if (metric < 10.0) {
170
+            s = String(metric, 1);
171
+        } else {
172
+            s = String(metric, 0);
173
+        }
170 174
         s += F(" m/s");
171 175
         uint8_t l1 = u8g2.getStrWidth(s.c_str());
172 176
         u8g2.drawStr(
@@ -175,7 +179,11 @@ void lcd_draw(uint8_t screen) {
175 179
             s.c_str()
176 180
         );
177 181
 
178
-        s = String(imperial, 0);
182
+        if (imperial < 10.0) {
183
+            s = String(imperial, 1);
184
+        } else {
185
+            s = String(imperial, 0);
186
+        }
179 187
         s += F(" FPS");
180 188
         uint8_t l2 = u8g2.getStrWidth(s.c_str());
181 189
         u8g2.drawStr(
@@ -251,9 +259,18 @@ void lcd_draw(uint8_t screen) {
251 259
         // max text
252 260
         double max_metric = tick_to_metric(max);
253 261
         if (PREFERRED_UNITS == METRIC) {
254
-            s = String(max_metric, 0);
262
+            if (max_metric < 10.0) {
263
+                s = String(max_metric, 1);
264
+            } else {
265
+                s = String(max_metric, 0);
266
+            }
255 267
         } else if (PREFERRED_UNITS == IMPERIAL) {
256
-            s = String(metric_to_imperial(max_metric), 0);
268
+            double max_imperial = metric_to_imperial(max_metric);
269
+            if (max_imperial < 10.0) {
270
+                s = String(max_imperial, 1);
271
+            } else {
272
+                s = String(max_imperial, 0);
273
+            }
257 274
         } else {
258 275
             s = String(metric_to_joules(max_metric, BB_WEIGHT), 2);
259 276
         }
@@ -295,9 +312,18 @@ void lcd_draw(uint8_t screen) {
295 312
         // min text
296 313
         double min_metric = tick_to_metric(min);
297 314
         if (PREFERRED_UNITS == METRIC) {
298
-            s = String(min_metric, 0);
315
+            if (min_metric < 10.0) {
316
+                s = String(min_metric, 1);
317
+            } else {
318
+                s = String(min_metric, 0);
319
+            }
299 320
         } else if (PREFERRED_UNITS == IMPERIAL) {
300
-            s = String(metric_to_imperial(min_metric), 0);
321
+            double min_imperial = metric_to_imperial(min_metric);
322
+            if (min_imperial < 10.0) {
323
+                s = String(min_imperial, 1);
324
+            } else {
325
+                s = String(min_imperial, 0);
326
+            }
301 327
         } else {
302 328
             s = String(metric_to_joules(min_metric, BB_WEIGHT), 2);
303 329
         }
@@ -316,9 +342,9 @@ void lcd_draw(uint8_t screen) {
316 342
         for (int i = 0; i < tick_count - 1; i++) {
317 343
             u8g2.drawLine(
318 344
                 graph_start + (i * segment_w),
319
-                map(tick_history[i], min, max, 0, u8g2.getDisplayHeight() - 1),
345
+                map(tick_history[i], min, max, u8g2.getDisplayHeight() - 1, 0),
320 346
                 graph_start + ((i + 1) * segment_w),
321
-                map(tick_history[i + 1], min, max, 0, u8g2.getDisplayHeight() - 1)
347
+                map(tick_history[i + 1], min, max, u8g2.getDisplayHeight() - 1, 0)
322 348
             );
323 349
         }
324 350
 

+ 4
- 1
firmware/OpenChrono/ticks.cpp View File

@@ -34,6 +34,9 @@
34 34
  * speed = SENSOR_DISTANCE / (65535 * 1000 / F_CPU)
35 35
  * so we can measure from 17m/s (61km/h, approx. 0.03J @ 0.2g)
36 36
  * up to ridulous 1120000m/s (4032000km/h)
37
+ *
38
+ * with a prescaler of 8, we can measure from 2.14m/s to 140000m/s
39
+ * with a prescaler of 64, we can measure from 0.27m/s to 17500m/s
37 40
  */
38 41
 
39 42
 #include <Arduino.h>
@@ -100,7 +103,7 @@ uint16_t tick_min() {
100 103
 
101 104
 double tick_to_metric(uint16_t ticks) {
102 105
     // v = d / t
103
-    double period = 1000.0 / ((double)(F_CPU));
106
+    double period = 1000.0 / ((double)(F_CPU / TIMER_PRESCALER));
104 107
     double time = period * (double)ticks;
105 108
     double speed = (double)SENSOR_DISTANCE / time;
106 109
     return speed;

+ 82
- 2
firmware/OpenChrono/timing.cpp View File

@@ -6,6 +6,8 @@
6 6
  * Copyright (c) 2022 Thomas Buck <thomas@xythobuz.de>
7 7
  *
8 8
  * Two phototransistors connected to external interrupts 0 and 1.
9
+ * Timer1 (16bit) used to count time between triggers.
10
+ * Timer2 (8bit) used for timing UV LED pulse.
9 11
  */
10 12
 
11 13
 #include <Arduino.h>
@@ -25,24 +27,102 @@ void interrupt_init() {
25 27
     EIMSK = (1 << INT0) | (1 << INT1);
26 28
 }
27 29
 
30
+/*
31
+ * this is supposed to be the "input" sensor,
32
+ * the one that triggers first on firing.
33
+ */
28 34
 ISR(INT0_vect) {
29 35
     time_a = timer_get();
30 36
     trigger_a = 1;
31 37
 }
32 38
 
39
+/*
40
+ * this is supposed to be the "output" sensor,
41
+ * the one that triggers after the other sensor.
42
+ */
33 43
 ISR(INT1_vect) {
34 44
     time_b = timer_get();
35 45
     trigger_b = 1;
46
+
47
+    // we now need to turn on the UV led
48
+    // and make sure it will only be on shortly!
49
+    timer_start();
50
+    digitalWrite(UV_LED_PIN, HIGH);
36 51
 }
37 52
 
38 53
 // --------------------------------------
39 54
 
40
-void timer_init() {
41
-    // normal mode, prescaler 1
55
+static void timer1_init() {
56
+    // normal mode
42 57
     TCCR1A = 0;
58
+
59
+    // prescaler
60
+#if TIMER_PRESCALER == 1
43 61
     TCCR1B = (1 << CS10);
62
+#elif TIMER_PRESCALER == 8
63
+    TCCR1B = (1 << CS11);
64
+#elif TIMER_PRESCALER == 64
65
+    TCCR1B = (1 << CS11) | (1 << CS10);
66
+#elif TIMER_PRESCALER == 256
67
+    TCCR1B = (1 << CS12);
68
+#elif TIMER_PRESCALER == 1024
69
+    TCCR1B = (1 << CS12) | (1 << CS10);
70
+#else
71
+#error Invalid Prescaler for Timer1
72
+#endif
73
+}
74
+
75
+static void timer2_init() {
76
+    // normal mode, no clock source
77
+    TCCR2A = 0;
78
+    TCCR2B = 0;
79
+
80
+    // enable overflow interrupt
81
+    TIMSK2 = (1 << TOIE2);
82
+}
83
+
84
+void timer_init() {
85
+    timer1_init();
86
+    timer2_init();
44 87
 }
45 88
 
46 89
 uint16_t timer_get() {
47 90
     return TCNT1;
48 91
 }
92
+
93
+void timer_start() {
94
+    /*
95
+     * the distance between the second IR sensor
96
+     * and the UV LEDs is 7.5mm.
97
+     * Our bullet will travel with a speed of
98
+     * ~10m/s up to ~300m/s approximately.
99
+     * So it will move the 7.5mm in
100
+     * 750us to 25us respectively.
101
+     * So it makes sense to keep the UV LED
102
+     * on for 1ms.
103
+     *
104
+     * We reach exactly 1ms when counting to 250
105
+     * with a prescaler of 64 at 16MHz.
106
+     *
107
+     * If you __really__ want to increase the brightness
108
+     * of the tracer, reduce the pulse length here.
109
+     * Then you can also reduce the UV LED resistor for
110
+     * higher currents, according to the datasheet of
111
+     * your UV LED.
112
+     */
113
+    const static uint8_t pulse_length = 250;
114
+
115
+    // initial value we count up from
116
+    TCNT2 = 0xFF - pulse_length;
117
+
118
+    // prescaler 64
119
+    TCCR2B = (1 << CS22);
120
+}
121
+
122
+ISR(TIMER2_OVF_vect) {
123
+    // turn off UV LED
124
+    digitalWrite(UV_LED_PIN, LOW);
125
+
126
+    // and also stop timer
127
+    TCCR2B = 0;
128
+}

+ 2
- 0
firmware/OpenChrono/timing.h View File

@@ -19,4 +19,6 @@ void interrupt_init();
19 19
 void timer_init();
20 20
 uint16_t timer_get();
21 21
 
22
+void timer_start();
23
+
22 24
 #endif // __TIMING_H__

Loading…
Cancel
Save