📜 ⬆️ ⬇️

Cheap chronograph for pneumatics do it yourself



In my first publication I want to tell you how I assembled a chronograph for a couple of evenings from cheap and accessible to all parts. As you may have guessed from the name, this device is used to measure the speed of a bullet in pneumatic (and not so) rifles and is useful for monitoring its technical condition.

1. Parts and accessories



This ends the parts you need to buy. Resistors can not be ordered, similar in value (but not less!) Can be pulled out of unnecessary consumer electronics. Thus, the total cost is less than 350 rubles, which is nothing compared to the price of a new factory chronograph (over 1000r for the simplest one, which in fact is even more primitive than our subject). In addition to the details we need:


The first 3 details are worthy of separate consideration, as they have their own characteristics, so we start with mini-reviews on them.
')

1.1. Digispark


It is a simple miniature Arduino-compatible board with ATtiny85 on board. How to connect to the Arduino IDE read on the official website of the project , there you can find drivers for it. There are two main types of this board: with microUSB and a more brutal one with a USB connector, divorced directly on the board.



My chronograph does not have its own power source, so I chose the first version of the board. The built-in battery / battery will greatly increase the price without adding almost nothing to usability. Power bank and cable to charge the phone lying almost everyone.

The characteristics are inherited from ATtiny85 by itself, its capabilities in our case are sufficient with a head. In fact, the MK in the chronograph does nothing but interrogate the two sensors and control the display. For those who first encounter Digispark, I put the most important features in the table:
Flash memory6KB (2KB busy loader)
Ram512 bytes
Eeprom512 bytes
Frequency16.5 MHz (default)
Number of I / O pins6
VIN Power5-12V
Pin 0PWM, SDA
Pin 1Pwm
Pin 2SCK, ADC1
Pin 3USB +, ADC3
Pin 4PWM, USB, ADC2
Pin 5PWM, ADC0

I use this tablet as a cheat sheet when developing various devices based on this board. As you probably noticed, the pin numbering for the analogRead () function is different, this should be taken into account. And one more peculiarity: on the third pin a 1.5 kΩ pull-up resistor hangs, since It is used in USB.

1.2. Display based on TM1637


The next important detail is the digital display on which information will be displayed. The display can be used any, my choice is due only to cheapness and ease of working with it. In principle, the display can be completely abandoned and the data can be output via cable to a PC, then the device will become even cheaper. For work, you will need a DigitalTube library . The subject to which I gave the link at the beginning of the post is a Grove display clone. Front view:



Behind:



The distance between the numbers is the same, so when the colon is off, the numerical values ​​are read normally. Together with the standard library, an example is supplied that works with Digispark without dancing with a tambourine:



All that the standard library can do is display the numbers 0-9 and the letters af, as well as change the brightness of the entire display. The value of the digit is set by the display function (int 0-3, int 0-15).

Express course on the use of the display
// 1.    #include <TM1637.h> // 2.   #define CLK 0 #define DIO 1 // 3.   TM1637 tm1637(CLK, DIO); // 4.  void setup() { tm1637.init(); tm1637.set(6); //  } // 5.  void loop() { //   x   int x = 1234; tm1637.display(0, x / 1000); tm1637.display(1, x / 100 % 10); tm1637.display(2, x / 10 % 10); tm1637.display(3, x % 10); delay(500); } 


If you try to display a character with a code beyond the [0, 15] bounds, the display shows nonsense, which is not static, so cheating for displaying special characters (degrees, minus) without a tambourine will not work:



This did not suit me, since in my chronograph I wanted to provide for the output not only of the speed, but also of the energy of the bullet (calculated on the basis of the mass previously prescribed in the sketch), these two values ​​should be output sequentially. To understand what the display is showing at a given point in time, you need to somehow separate these two values ​​visually, for example, using the “J” symbol. Of course, you can stupidly use the colon character as an indicator flag, but this is not true and not kosher) Therefore, I used to understand the library and based on the display function I made the function setSegments (byte addr, byte data), which lights in the figure with the number addr segments encoded in data:

 void setSegments(byte addr, byte data) { tm1637.start(); tm1637.writeByte(ADDR_FIXED); tm1637.stop(); tm1637.start(); tm1637.writeByte(addr|0xc0); tm1637.writeByte(data); tm1637.stop(); tm1637.start(); tm1637.writeByte(tm1637.Cmd_DispCtrl); tm1637.stop(); } 

Segments are encoded very simply: the low-order data bit is responsible for the topmost segment, and so on. clockwise, the seventh bit is responsible for the central segment. For example, the character '1' is encoded as 0b00000110. The eighth, most significant bit is used only in the second digit and is responsible for the colon, in all other digits it is ignored. To make my life easier, I, like any lazy IT specialist, automated the process of getting character codes using excel:



Now you can easily do this:



Or so:



Let's say HELLO
 #include <TM1637.h> #define CLK 0 #define DIO 1 TM1637 tm1637(CLK, DIO); void setSegments(byte addr, byte data) { tm1637.start(); tm1637.writeByte(ADDR_FIXED); tm1637.stop(); tm1637.start(); tm1637.writeByte(addr|0xc0); tm1637.writeByte(data); tm1637.stop(); tm1637.start(); tm1637.writeByte(tm1637.Cmd_DispCtrl); tm1637.stop(); } void setup() { tm1637.init(); tm1637.set(6); } void loop() { //  Hello setSegments(0, 118); setSegments(1, 121); setSegments(2, 54); setSegments(3, 63); delay(500); } 


1.3. Sensors


Here I, unfortunately, cannot say anything special, because on the product page there is not a word about the characteristics or at least the markings on which one could dig up the datasheet. Typical noname. Only the wavelength of 940 nm is known.



At the cost of one LED, it was determined that the current above 40mA is lethal for them, and the supply voltage should be below 3.3V. The phototransistor is slightly transparent and reacts to light.

2. Parts preparation and assembly


The scheme is very simple and straightforward, of all the digispark pins we need only P0, P1 - to work with the display, and also P2 - to work with sensors:



As you can see, one resistor limits the current on the LEDs, the second - P2 to the ground. Phototransistors are connected in series, therefore the passage of a bullet in front of any optocoupler leads to a decrease in voltage on P2. By registering two consecutive voltage surges and measuring the time between them, we can determine the speed of the bullet (knowing the distance between the sensors, essno). Using one pin for measuring has one more plus - there is no required direction of movement of the bullet, you can shoot from both ends. We will collect from this handful of parts:



I followed the path of miniaturization and decided to make a sandwich with a piece of breadboard:



The whole sandwich filled with hot melt for durability:





It remains only to place the sensors in the tube and solder the wires:



The photo shows that I placed an additional electrolyte at 100 mKf parallel to the LEDs, so that when powered from a crib there is no pulsation of IR diodes.



Pin P2 as an input was chosen for a reason. Let me remind you that P3 and P4 are used in USB, so the use of P2 allows you to flash the device already assembled. Secondly, P2 is an analog input, so you can not use interrupts, but simply measure the difference in the cycle between the previous and current value on it, if the difference is above a certain threshold, then the bullet passes between one of the optocouplers. But there is one software trick, without which the above scheme does not take off, let's talk about it later.

3. Firmware


3.1. A few words about prescaler


Prescaler is a frequency divider, by default in arduino-like boards it is equal to 128. The maximum sampling frequency of the ADC depends on the value of this value, by default for 16 MHz controller it turns out 16/128 = 125 kHz. Each digitization takes 13 operations, so the maximum polling frequency of a pin is 9600 kHz (in theory, in practice, it is really no higher than 7 kHz). Those. The interval between measurements is approximately 120 ÎĽs, this is very, very much. A bullet flying at a speed of 300 m / s will fly 3.6 cm during this time - the controller simply does not have time to detect the fact that a bullet has passed through an optocoupler. For normal operation, you need an interval between measurements of at least 20 ÎĽs, the necessary divider value for this is 16. I went even further and use divider 8 in my device, this is done as follows:

 #ifndef cbi #define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit)) #endif #ifndef sbi #define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit)) #endif void setup() { sbi(ADCSRA,ADPS2); cbi(ADCSRA,ADPS1); cbi(ADCSRA,ADPS0); ... } 

Real analogRead interval measurements on different dividers:



3.2. Final sketch


I will not describe the code in detail, it is already well documented. Instead, I will describe in general terms the algorithm of his work. So, all logic is reduced to the following stages:


This is a very simplified model, in the code itself I added a whistle, including calculating and showing the energy of the bullet based on the mass of the bullet entered in advance in the code.

Actually, all the code
 /* *      , © SinuX 23.03.2016 */ #include <TM1637.h> #define CLK 1 //   #define DIO 0 //   #define START_PIN 1 //    #define END_PIN 1 //    #define START_LEV 50 //    #define END_LEV 50 //    #define TIMEOUT 10000 //      #define BULLET_WEIGHT 0.00051 //     (  ) #define ENCODER_DIST 0.1 //      (10 = 0.1) #define SHOW_DELAY 3000 //    //   analogRead #ifndef cbi #define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit)) #endif #ifndef sbi #define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit)) #endif //   int prevVal, curVal; unsigned long startTime, endTime; TM1637 tm1637(CLK, DIO); /*   TM1637::display(),      *  :   -    ..    *   -   */ void setSegments(byte addr, byte data) { tm1637.start(); tm1637.writeByte(ADDR_FIXED); tm1637.stop(); tm1637.start(); tm1637.writeByte(addr|0xc0); tm1637.writeByte(data); tm1637.stop(); tm1637.start(); tm1637.writeByte(tm1637.Cmd_DispCtrl); tm1637.stop(); } //  void setup() { //  prescaler  8   analogRead cbi(ADCSRA,ADPS2); sbi(ADCSRA,ADPS1); sbi(ADCSRA,ADPS0); //   tm1637.init(); tm1637.set(6); //   setSegments(0, 118); setSegments(1, 121); setSegments(2, 54); setSegments(3, 63); delay(1000); } //   void loop() { //   showReady(); //   curVal = analogRead(START_PIN); do { prevVal = curVal; curVal = analogRead(START_PIN); } while (curVal - prevVal < START_LEV); startTime = micros(); //   curVal = analogRead(END_PIN); do { prevVal = curVal; curVal = analogRead(END_PIN); //     -       if (micros() - startTime >= TIMEOUT) { showError(); return; } } while (curVal - prevVal < END_LEV); endTime = micros(); //     showResult(); } //     void showReady() { setSegments(0, 73); setSegments(1, 73); setSegments(2, 73); setSegments(3, 73); delay(100); } //    ,   void showResult() { //     /     float bulletSpeed = ENCODER_DIST * 1000000 / (endTime - startTime); tm1637.display(0, (int)bulletSpeed / 100 % 10); tm1637.display(1, (int)bulletSpeed / 10 % 10); tm1637.display(2, (int)bulletSpeed % 10); setSegments(3, 84); delay(SHOW_DELAY); //         float bulletEnergy = BULLET_WEIGHT * bulletSpeed * bulletSpeed / 2; tm1637.point(1); //   ':' - ,  ) tm1637.display(0, (int)bulletEnergy / 10 % 10); tm1637.display(1, (int)bulletEnergy % 10); tm1637.display(2, (int)(bulletEnergy * 10) % 10); setSegments(3, 30); delay(SHOW_DELAY); tm1637.point(0); } //        void showError() { setSegments(0, 121); setSegments(1, 80); setSegments(2, 80); setSegments(3, 0); delay(SHOW_DELAY); } 


4. Examples of work


When properly connected, the device took off almost immediately, the only flaw found was that it reacts negatively to the LED and luminescent lighting (ripple frequency of about 40 kHz), from which spontaneous errors can occur. In total, the device has 3 modes of operation:

Greeting after switching on and going into shot standby mode (screen is filled with stripes):



In case of an error, “Err” is displayed, and again the transition to the standby mode:



Well, he measured the speed:



After the shot, the bullet speed is shown first (with the symbol 'n'), then the energy (the symbol 'J'), and the energy is calculated with an accuracy of one decimal place (you can see on the gif that a colon is lit when the joule is shown). I couldn’t find a prettier body yet, so I just filled it with thermosoil:



Perhaps, I have everything on it, I hope someone was useful.

Source: https://habr.com/ru/post/372315/


All Articles