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
 # OpenChrono
1
 # OpenChrono
2
 
2
 
3
 Chronograph for Airsoft use, released as Free Open Source hardware and software!
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
 ## Hardware
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
 ### Required Parts
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
 ## Software
60
 ## Software
30
 
61
 
31
 This project uses the [U8g2 library by olikraus](https://github.com/olikraus/u8g2) to draw to the I2C OLED display.
62
 This project uses the [U8g2 library by olikraus](https://github.com/olikraus/u8g2) to draw to the I2C OLED display.
32
 
63
 
33
 You can compile and flash the software using either PlatformIO or the standard Arduino IDE.
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
         ticks = b - a;
21
         ticks = b - a;
22
     } else {
22
     } else {
23
         // the timer overflowed between measurements!
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
         tmp += 0x10000;
25
         tmp += 0x10000;
26
         ticks = (uint16_t)tmp;
26
         ticks = (uint16_t)tmp;
27
     }
27
     }
45
 }
45
 }
46
 
46
 
47
 void setup() {
47
 void setup() {
48
+    // we simply turn on the IR LEDs all the time
48
     pinMode(IR_LED_PIN, OUTPUT);
49
     pinMode(IR_LED_PIN, OUTPUT);
49
     digitalWrite(IR_LED_PIN, HIGH);
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
     lcd_init();
56
     lcd_init();
52
     delay(SCREEN_TIMEOUT); // show splash screen
57
     delay(SCREEN_TIMEOUT); // show splash screen
53
 
58
 
56
 }
61
 }
57
 
62
 
58
 void loop() {
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
     lcd_loop();
75
     lcd_loop();

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

15
 
15
 
16
 // --------------------------------------
16
 // --------------------------------------
17
 
17
 
18
-// hardware details
19
-
20
 #define SENSOR_DISTANCE 70.0 /* in mm */
18
 #define SENSOR_DISTANCE 70.0 /* in mm */
19
+
21
 #define BB_WEIGHT 0.25 /* in g */
20
 #define BB_WEIGHT 0.25 /* in g */
22
 #define BB_WEIGHTS { 0.20, 0.23, 0.25, 0.28, 0.475 } /* in g */
21
 #define BB_WEIGHTS { 0.20, 0.23, 0.25, 0.28, 0.475 } /* in g */
23
 
22
 
74
 // --------------------------------------
73
 // --------------------------------------
75
 
74
 
76
 #define IR_LED_PIN 4
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
 
166
 
167
         uint8_t left_off = (u8g2.getDisplayHeight() - (u8g2.getMaxCharHeight() * 4 + 3)) / 2;
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
         s += F(" m/s");
174
         s += F(" m/s");
171
         uint8_t l1 = u8g2.getStrWidth(s.c_str());
175
         uint8_t l1 = u8g2.getStrWidth(s.c_str());
172
         u8g2.drawStr(
176
         u8g2.drawStr(
175
             s.c_str()
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
         s += F(" FPS");
187
         s += F(" FPS");
180
         uint8_t l2 = u8g2.getStrWidth(s.c_str());
188
         uint8_t l2 = u8g2.getStrWidth(s.c_str());
181
         u8g2.drawStr(
189
         u8g2.drawStr(
251
         // max text
259
         // max text
252
         double max_metric = tick_to_metric(max);
260
         double max_metric = tick_to_metric(max);
253
         if (PREFERRED_UNITS == METRIC) {
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
         } else if (PREFERRED_UNITS == IMPERIAL) {
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
         } else {
274
         } else {
258
             s = String(metric_to_joules(max_metric, BB_WEIGHT), 2);
275
             s = String(metric_to_joules(max_metric, BB_WEIGHT), 2);
259
         }
276
         }
295
         // min text
312
         // min text
296
         double min_metric = tick_to_metric(min);
313
         double min_metric = tick_to_metric(min);
297
         if (PREFERRED_UNITS == METRIC) {
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
         } else if (PREFERRED_UNITS == IMPERIAL) {
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
         } else {
327
         } else {
302
             s = String(metric_to_joules(min_metric, BB_WEIGHT), 2);
328
             s = String(metric_to_joules(min_metric, BB_WEIGHT), 2);
303
         }
329
         }
316
         for (int i = 0; i < tick_count - 1; i++) {
342
         for (int i = 0; i < tick_count - 1; i++) {
317
             u8g2.drawLine(
343
             u8g2.drawLine(
318
                 graph_start + (i * segment_w),
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
                 graph_start + ((i + 1) * segment_w),
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
  * speed = SENSOR_DISTANCE / (65535 * 1000 / F_CPU)
34
  * speed = SENSOR_DISTANCE / (65535 * 1000 / F_CPU)
35
  * so we can measure from 17m/s (61km/h, approx. 0.03J @ 0.2g)
35
  * so we can measure from 17m/s (61km/h, approx. 0.03J @ 0.2g)
36
  * up to ridulous 1120000m/s (4032000km/h)
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
 #include <Arduino.h>
42
 #include <Arduino.h>
100
 
103
 
101
 double tick_to_metric(uint16_t ticks) {
104
 double tick_to_metric(uint16_t ticks) {
102
     // v = d / t
105
     // v = d / t
103
-    double period = 1000.0 / ((double)(F_CPU));
106
+    double period = 1000.0 / ((double)(F_CPU / TIMER_PRESCALER));
104
     double time = period * (double)ticks;
107
     double time = period * (double)ticks;
105
     double speed = (double)SENSOR_DISTANCE / time;
108
     double speed = (double)SENSOR_DISTANCE / time;
106
     return speed;
109
     return speed;

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

6
  * Copyright (c) 2022 Thomas Buck <thomas@xythobuz.de>
6
  * Copyright (c) 2022 Thomas Buck <thomas@xythobuz.de>
7
  *
7
  *
8
  * Two phototransistors connected to external interrupts 0 and 1.
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
 #include <Arduino.h>
13
 #include <Arduino.h>
25
     EIMSK = (1 << INT0) | (1 << INT1);
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
 ISR(INT0_vect) {
34
 ISR(INT0_vect) {
29
     time_a = timer_get();
35
     time_a = timer_get();
30
     trigger_a = 1;
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
 ISR(INT1_vect) {
43
 ISR(INT1_vect) {
34
     time_b = timer_get();
44
     time_b = timer_get();
35
     trigger_b = 1;
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
     TCCR1A = 0;
57
     TCCR1A = 0;
58
+
59
+    // prescaler
60
+#if TIMER_PRESCALER == 1
43
     TCCR1B = (1 << CS10);
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
 uint16_t timer_get() {
89
 uint16_t timer_get() {
47
     return TCNT1;
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
 void timer_init();
19
 void timer_init();
20
 uint16_t timer_get();
20
 uint16_t timer_get();
21
 
21
 
22
+void timer_start();
23
+
22
 #endif // __TIMING_H__
24
 #endif // __TIMING_H__

Loading…
Cancel
Save