Cronógrafo barato com Arduino para Airsoft e Airgun!
Airsoft é um esporte que eu adoro e as vezes eu dou uns belos tiros e as vezes moro também. Ele é ótimo para fazer exercício físico e precisão no disparo é importante. Para isso podermos calibrar e fazer upgrades nas armas utilizando um cronógrafo que mede a velocidade e a energia do projetil, e você encontra ele disponível no mercado. Mas porque não fazer um extremamente preciso com Arduino, economizando grana, aprendendo programação e eletrônica, fazendo funções personalizadas para o equipamento e ainda se divertir? É isso que você vai aprender hoje nesse tutorial completo! Boa diversão!
Custo do projeto
O preço estimado do projeto comprando peças diretamente da china é de aproximadamente 160,00 Reais.
A seguir temos a lista de materiais necessários:
- 1 – Arduino NANO;
- 1 – Conversor DC-DC Step-Up MT3608;
- 1 – Módulo Encoder Rotativo KY-040;
- 1 – Knob para encoder;
- 1 – Display OLED I2C 0.96Inch 128×64;
- 1 – Conector tipo Jack;
- 1 – Chave gangorra 2 terminais pequena 13.2 x 8.2 mm;
- 1 – Bateria 18650 com pelo menos 500mAh;
- 1 – Placa BMS TP4056 com proteção de carga e descarga;
- 4 – Pés de silicone;
- 4 – Parafusos rosca soberba 2,2mm espessura x 8mm comprimento (para fixar a tampa);
- Pelo menos 1 metro de fio elétrico 0,25mm²;
- Peças impressas em 3D ou feitas de papelão ou madeira;
- 1 – PCI ilhada 70×30 cm;
- 1 – Capacitor poliéster de 1nF x 100V;
- 1 – CI LM339N;
- 1 – CI LM358P;
- 11 – Fototrasístor TIL78 de 5mm;
- 2 – LED emissor IR de 5mm;
Case e impressão 3D:
Clique aqui para baixar o desenho .STL da versão V1 do Cronógrado barato com Arduino
O código a ser carregado:
Abaixo segue a programação utilizada. Para carregar o código no Arduino você terá que adicionar algumas bibliotecas na IDE do Arduino e essas bibliotecas estão disponíveis aqui em baixo para download. O resto das bibliotecas utilizadas são nativas da IDE do Arduino e não precisam ser instaladas a parte.
Biblioteca Adafruit_SSD1306;
Biblioteca RotaryEncoder;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 |
/********************************************* Autor: Marlon Nardi Walendorff Projeto: Crónógrado para Airgun, Airsoft e carabinas de pressão. Detalhes do projeto: https://marlonnardi.com/2023/04/17/cronografo-barato-com-arduino-para-airsoft-e-airgun/ /**********************************************/ //==================== Inclusão de Bibliotecas =================// #include <Adafruit_SSD1306.h> #include <RotaryEncoder.h> //==================== Mapeamento de Hardware ==================// #define pin_Encoder_CLK 2 #define pin_Encoder_DT 3 #define pin_Encoder_SW 4 #define distance_m 0.1 //Distância entre os sensores em metros 0.1465m ou 14.65cm //==================== Instânciando Objetos ====================// Adafruit_SSD1306 Display(128, 64, &Wire, -1, 400000, 400000); RotaryEncoder EncoderOne(pin_Encoder_CLK, pin_Encoder_DT); //==================== Variáveis Globais ==================// //Valores de contagem e comparação const uint16_t t1_load = 0; const uint16_t t1_comp = 31250; byte cliques_encoder = 0; byte aux2 = 0; volatile uint16_t initial_value = 0; volatile uint16_t final_value = 0; volatile uint8_t aux = 0; volatile uint16_t counts_of_timer1 = 0; float time_ms = 0;// Tempo em milissegundos do percurso da munição float time_s = 0;//Tempo em segundos do percurso da munição float velocity_m_s = 0; //Velocidade em metros por segundo float velocity_f_p_s = 0; //Velocidade em pés por segundo ou Feet per seconds float energy_joule = 0;// Ec = (m.v²)/2 energia do progétil float weight_bullet_gram = 0.20;// Peso incial padrão de 0.20 gramas para medir energias armas int16_t valorEncoder = 0; //--------------------- Iniciar com valor para bbs 0.20g void setup() { //Serial.begin(9600); //Configura pino como entrada pinMode(8, INPUT); //Configura pino como entrada PULL-UP pinMode(pin_Encoder_SW, INPUT_PULLUP); //================= Interrupção Externa ========================// /* Vincula duas interrupções externas no pino 2 e 3 nas funções ISR0 e ISR1 para garantir que o encoder sempre seja lido com prioridade. */ attachInterrupt(digitalPinToInterrupt(2), ISR0, CHANGE); attachInterrupt(digitalPinToInterrupt(3), ISR1, CHANGE); EncoderOne.setPosition(20); //Inicializa o OLED 128X64 0.96 INCH com endereço I2C 0x3C Display.begin(SSD1306_SWITCHCAPVCC, 0x3C); Display.setTextColor(WHITE); //Define a cor do texto //Limpa o display, necessário para apagar a imagem inicial da adafruit Display.clearDisplay(); //Atualiza o display Display.display(); Display.setTextSize(1); //Define o tamanho da fonte do texto //Posição Largura/Altura Display.setCursor(0, 0); Display.print("Acesse o projeto em:"); Display.setTextSize(1); //Define o tamanho da fonte do texto //Posição Largura/Altura Display.setCursor(0, 25); Display.print("marlonnardi.com"); Display.display(); delay(2000); Display.clearDisplay(); Display.display(); //Não precisamos utilizar o registrador TCCR1A para o que vamos fazer mas precisamos resetar todos seus bits para 0 já que a IDE do Arduino muda esse valores por padrão para as função analogWrite //com esse registrador podemos ligar e desligar um pino sem ter que chamar nada no código, somente a primeira configuração, logo ele não interfere no tempo do void loop(). Os pinos são os OC1A e OC1B. //Isso pode ser util para gerar PWM e a função nativa do Arduino analogWrite() se aproveita disso. //Como o Timer1 é de 16 bits ele utiliza dois registradores o TCNT1L e TCNT1H, mas não nos preocupamos e ler esse valores pois a biblioteca AVR inclusa na IDE do Arduino faz isso por nós, basta escrever o valor para o registrador TCNT1. //Reseta o registrador de configuração do Timer1 para seu valor padrão TCCR1A = 0; //Seleciona borda de disparo da interrupção InputCapture, deixar padrão 0 - falling edge (Negative) // TCCR1B //Seta o modo de operação para CTC //TCCR1B &= ~(1<<WGM13); //TCCR1B |= (1<<WGM12); //Seta o prescaler para 8 TCCR1B &= ~(1 << CS12); TCCR1B |= (1 << CS11); TCCR1B &= ~(1 << CS10); //Reseta o valor do Timer1 TCNT1 = t1_load; //Reseta o valor de ICR1 que amazena o dado na qual aconteceu o pulso ICR1 = 0; //Seta o valor de comparação //OCR1A = t1_comp; //Seta a interrupção por Input Capture no Timer1 TIMSK1 = (1 << ICIE1); sei(); //Habilita as interrupções globais }//endSetup -------------------------------------- void loop() { // Serial.println(final_value); calculaDados(); seleciona_Tela(); }//end_void_loop ---------------------- void seleciona_Tela() { if (!digitalRead(pin_Encoder_SW) ) //Se o botão está solto { aux2 = 1; } if (digitalRead(pin_Encoder_SW) && aux2 == 1) //Se o botão está solto { //Limpa o display, necessário para apagar a imagem inicial da adafruit Display.clearDisplay(); //Atualiza o display Display.display(); cliques_encoder++; aux2 = 0; } if (cliques_encoder == 0) { screenOne(); } if (cliques_encoder == 1) { screenTwo(); } if (cliques_encoder >= 2) { cliques_encoder = 0; } }//----------------------- end_selecionaTela //================== ISRs Interrupções Externas =======================// /* Caso qualquer pino do encoder envie sinal, o metodo .tick() sempre será chamado, atualizando o valor do encoder via sua biblioteca. */ void ISR0()// Função ligada a uma interrupção ISR logo não pode retornar valor e deve ser mais rápida possível { if (cliques_encoder == 1) EncoderOne.tick();// Começa a ler o valor do encoder }//-------------------------endISR0 void ISR1()// Função ligada a uma interrupção ISR logo não pode retornar valor e deve ser mais rápida possível { if (cliques_encoder == 1) EncoderOne.tick();// Começa a ler o valor do encoder }//------------------------endISR1 void screenOne() { Display.clearDisplay(); Display.setTextSize(1); //Define o tamanho da fonte do texto //Posição Largura/Altura Display.setCursor(0, 3); Display.print(energy_joule); //Posição Largura/Altura Display.setCursor(31, 3); Display.print("j"); Display.setTextSize(1); //Define o tamanho da fonte do texto //Posição Largura/Altura Display.setCursor(45, 3); Display.print(weight_bullet_gram); //Posição Largura/Altura Display.setCursor(70, 3); Display.print("g"); Display.setTextSize(1); //Define o tamanho da fonte do texto //Posição Largura/Altura Display.setCursor(90, 3); Display.print(velocity_m_s, 0); //Posição Largura/Altura Display.setCursor(110, 3); Display.print("m/s"); if(velocity_f_p_s <= 9) { Display.setTextSize(4); //Define o tamanho da fonte do texto //Posição Largura/Altura Display.setCursor(55, 19); Display.print(velocity_f_p_s, 0); } if(velocity_f_p_s >=10 && velocity_f_p_s <= 99) { Display.setTextSize(4); //Define o tamanho da fonte do texto //Posição Largura/Altura Display.setCursor(43, 19); Display.print(velocity_f_p_s, 0); } if(velocity_f_p_s >=100 && velocity_f_p_s <= 999) { Display.setTextSize(4); //Define o tamanho da fonte do texto //Posição Largura/Altura Display.setCursor(29, 19); Display.print(velocity_f_p_s, 0); } if(velocity_f_p_s >=1000 && velocity_f_p_s <= 9999) { Display.setTextSize(4); //Define o tamanho da fonte do texto //Posição Largura/Altura Display.setCursor(15, 19); Display.print(velocity_f_p_s, 0); Display.setTextSize(2); //Define o tamanho da fonte do texto } Display.setTextSize(2); //Define o tamanho da fonte do texto //Posição Largura/Altura Display.setCursor(50, 50); Display.print("FPS"); Display.display(); }//end_screenOne ---------------------- void screenTwo() { Display.clearDisplay(); Display.setTextSize(1); //Define o tamanho da fonte do texto //Posição Largura/Altura Display.setCursor(3, 0); Display.print("Massa BBs em gramas"); Display.setTextSize(3); //Define o tamanho da fonte do texto //Posição Largura/Altura Display.setCursor(10, 22); Display.print(weight_bullet_gram, 2); Display.setTextSize(3); //Define o tamanho da fonte do texto //Posição Largura/Altura Display.setCursor(90, 22); Display.print("g"); valorEncoder = EncoderOne.getPosition();//Captura o valor do encoder if (EncoderOne.getPosition() < 0) { EncoderOne.setPosition(0); valorEncoder = 0; } if (EncoderOne.getPosition() > 999) { EncoderOne.setPosition(999); valorEncoder = 999; } weight_bullet_gram = valorEncoder/100.00; Display.display(); }//end_screenTwo ---------------------- void calculaDados() { if (aux >= 2) if (final_value < initial_value ) { counts_of_timer1 = 65535 - ((65535 - initial_value) + final_value); } if (final_value >= initial_value) { counts_of_timer1 = final_value - initial_value; } // (Periodo clock * Prescaler * counts_of_timer1) / 1E6 time_ms = (62.5 * 8.0 * (float)counts_of_timer1) / 1E6; time_s = time_ms / 1000.0; if((distance_m / time_s) <= 3047.00) { velocity_m_s = distance_m / time_s; velocity_f_p_s = velocity_m_s * 3.280839; } if((distance_m / time_s) > 3047.00) { velocity_m_s = 0; velocity_f_p_s = 0; } energy_joule = ((weight_bullet_gram / 1000.0) * (velocity_m_s * velocity_m_s)) / 2; aux = 0; }//end_calculaDados----------------------------- ISR(TIMER1_CAPT_vect) { if (aux == 0) { initial_value = ICR1; } if (aux == 1) { final_value = ICR1; } aux++; }//endISR -------------------------------------- |
O circuito eletrônico:
Para ver a imagem do circuito em alta resolução clique aqui
ATENÇÃO: Antes de montar o circuito e alimentá-lo ajuste a tensão de saída do conversor DC-DC MT3608 para 5V. Se você não fizer isso antes, a tensão de saída poderá estar maior e isso danificaria todo o seu circuito.