Proiectul vizează crearea unui dispozitiv de monitorizare pentru bicicletă. Acest device va furniza informații despre viteza mea de deplasare, viteza medie, distanța parcursă, temperatura si presiunea curenta alaturi de punctul cardinal spre care ma indrept, dar si datele giroscopice si accelerometrice. În plus, la alegerea utilizatorului, se va seta un target de distanță pentru a motiva performanța și a îmbunătăți rezultatele fizice.
Am ales acest proiect deoarece mersul pe bicicleta este una dintre pasiunile mele preferate si am vrut sa construiesc ceva ce imi poate fi util atat mie cat si altor oameni cu acelasi hobby, dar si sa lucrez la ceva la care chiar sa imi faca placere.
Din experienta mea, consider ca acest dispozitiv poate face experiența ciclismului mai plăcută, oferind o modalitate simplă și eficientă de a monitoriza datele relevante. Cu ajutorul său, utilizatorii pot seta obiective precise de distanță, se pot adapta la condițiile meteorologice și pot analiza tehnica de pedalare pentru a maximiza eficiența și siguranța. Totodata, acest device ofera o vedere clara asupra efortului parcurs dupa un antrenament pe bicicleta, mentinandu-te motivat.
Dispozitivul meu imi va afisa initial pe un display LCD 1602 IIC/I2Cviteza curenta, viteza medie si distanta parcursa. Viteza este calculata folosind un senzor de camp magnetic (Hall). Voi avea in plus 4 butoane cu urmatoarele functionalitati:
In acest fel utilizatorul va putea avea o evidenta clara a efortului depus.
Ecran LCD 1602 IIC/I2C → afisare 2 linii 16 coloane
Senzor magnetic Hall (KY-024)
Detecteaza prezenta unui camp magnetic. Daca se pune un magnet pe roata si acest senozr pe furca bicicletei, se poate determina viteza curenta cu care merge biciclistul. Senzorul are trei componente principale pe placa sa de circuit. Are unitatea de senzor din partea frontală a modulului care măsoară fizic zona și trimite un semnal analog către a doua unitate, amplificatorul. Amplificatorul amplifică semnalul, în funcție de valoarea de rezistență a potențiometrului, și trimite semnalul la ieșirea analogică a modulului. A treia componentă este un comparator care comută ieșirea digitală și LED-ul dacă semnalul scade sub o anumită valoare. Se poate controla sensibilitatea ajustând potențiometrul.
Modul 10DOF MPU9250 și BMP280 → giroscop, accelerometru, presiune, temperatura, magnetometru
Buton 1 → seteaza dimensiunea rotii
Button 2 → incrementeaza distanta target (are si contor pentru debouncing implementat in cod)
Button 3 → trigger sa afisez presiunea, temperatura si punctul cardinal
Button 4 → trigger sa afisez datele giroscopice si accelerometrice
LED → se aprinde la atingerea distantei target timp de 2s
Rezistenta are rol de a limita curentul ce trece prin LED. Fără o rezistență care să limiteze acest curent, LED-ul poate primi mai mult curent decât capacitatea sa nominală, ceea ce poate duce la arderea și distrugerea acestuia rapid.
In realizarea proiectului am folosit urmatoarele laboratoare studiate:
#include <LiquidCrystal_I2C.h> // pt interactiunea cu ecrane LCD prin protocol I2C; controleaza afisajul fara a necesita multe conexiuni cabluri #include <Wire.h> // faciliteaza schimbul de date dintre microcontroller si dispozitive I2C, adica senzorii mei #include <Adafruit_Sensor.h> // interfata pentru senzorii Adafruit, unifică codul de accesare a //senzorilor și reduce complexitatea necesară pentru a citi date #include "Adafruit_BMP280.h" // specifica senzor BMP 280, ofera functii directe de interactionare cu senzorul #include "I2C.h" // functionalitati suplimentare pentru comunicarea I2C fata de Wire.h
#define LED 7 #define B1 12 #define B2 3 #define B3 8 #define B4 9 #define reed 6 #define BMP280_I2C_ADDRESS 0x76 // adresa I2C pentru senzorul BMP280 #define MPU9250_IMU_ADDRESS 0x68 // adresa IMU #define MPU9250_MAG_ADDRESS 0x0C // adresa magnetometru /* Setările de scalare completă pentru giroscop, exprimate în grade pe secundă (DPS). (0x00, 0x08, 0x10, 0x18) reprezintă setările de configurare pentru diferitele sensibilități maxime ale giroscopului. */ #define GYRO_FULL_SCALE_250_DPS 0x00 #define GYRO_FULL_SCALE_500_DPS 0x08 #define GYRO_FULL_SCALE_1000_DPS 0x10 #define GYRO_FULL_SCALE_2000_DPS 0x18 /* Setările de scalare completă pentru accelerometru, exprimate în multiple ale accelerației gravitaționale G */ #define ACC_FULL_SCALE_2G 0x00 #define ACC_FULL_SCALE_4G 0x08 #define ACC_FULL_SCALE_8G 0x10 #define ACC_FULL_SCALE_16G 0x18 #define TEMPERATURE_OFFSET 21 // din documentatie #define G 9.80665 // constanta gravitațională #define NUM_READINGS 72 // Numărul de citiri pentru medierea (unghiului dat de magnetometru)
Explicatiile fiecarei variabile sunt date in comentarii deoarece am vrut sa fie usor de vizualizat si explicatia si codul descris.
float headingReadings[NUM_READINGS]; // Array pentru stocarea citirilor unghiurilor pt determinarea punctului cardinal int headingIndex = 0; // Indexul curent pentru înregistrarea noilor citiri float totalHeading = 0; // Suma totală a citirilor din array float averageHeading = 0; // Media citirilor Adafruit_BMP280 bmp; // creeare instanta a senzorului BMP280 pentru masurarea presiunii si temperaturii LiquidCrystal_I2C lcd(0x27, 16, 2); // initializare display LCD cu 16 col si 2 linii float radius = 13; // raza roata de bicicleta int reedVal; // valoare senzor citita long timer_one_rot = 0; // cronometru care masoara timpul intre 2 rotatii complete detectate de senzorul Reed float speed = 0.0; // viteza curenta float circumference; int tire_index = 0; // index pentru elementele din vectorul tire_dimensions float lastSize = radius; // ultima val a razei rotii utilizata int max_reed_nr = 212; // timp min intre 2 rotatii pt debouncing pt a preveni citiri false int reed_nr; // este contorul actual care se decrementeaza la fiecare apel al întreruperii până când poate fi considerată o nouă rotație validă // pentru detectarea apasarii butoanelor retin starile lor anterioare int old_button1_state = HIGH; int old_button2_state = HIGH; int old_button3_state = HIGH; int old_button4_state = HIGH; int readings = 0; // numara citirile de viteza pentru calculul vitezei medii int max_readings = 100; // nr max de masuratori ale vitezei pt calculul vitezei medii float averageSpeed = 0; float lastAverageSpeed = 0; float totalDistanceTraveled = 0; float tire_dimensions[3] = {13, 13.75, 14.5}; // cele 3 size-uri posibile pentru roti float temp; // temperatura inregistratat de senzorul BMP 280 float pressure; // presiunea inregistratat de senzorul BMP 280 // variabile care imi vor indica ce functii de afisare pe LCD voi apela bool display = false; bool displayDirection = false; float target_distance = 0; float t = 0; // contorizeaza timpul cat sta aprins LED-ul cand ating distanta de target unsigned long lastDebounceTime = 0; // Ultima dată când starea butonului a schimbat B2 const unsigned long debounceDelay = 300; // Debounce time în milisecunde
Structurile urmatoare stochează măsurători pe trei axe (x, y, z) pentru giroscop, accelerometru și magnetometru.
struct gyroscope_raw { int16_t x, y, z; } gyroscope; struct accelerometer_raw { int16_t x, y, z; } accelerometer; struct magnetometer_raw { int16_t x, y, z; struct { int8_t x, y, z; } adjustment; // asta este pentru calibrare } magnetometer; struct temperature_raw { int16_t value; } temperature; struct { struct { float x, y, z; } accelerometer, gyroscope, magnetometer; float temperature; } normalized; // valorile normalizate de la toti senzorii
Aceste functii verifica daca datele sunt gata pentru a fi citite. Se evita întârzierile în procesare și asigura că datele sunt actualizate și gata de utilizare când sunt necesare.
void setMagnetometerAdjustmentValues() // functie pentru calibrare { uint8_t buff[3]; // pentru valorile de ajustare I2CwriteByte(MPU9250_MAG_ADDRESS, 0x0A, 0x1F); //Modul 0x1F setează magnetometrul pentru a oferi valori pe 16 biți și permite accesul la ROM-ul de fuziune, care este necesar pentru a citi valorile de ajustare delay(3000); I2Cread(MPU9250_MAG_ADDRESS, 0x10, 3, buff); // citeste ajustarile magnetometer.adjustment.x = buff[0]; // Adjustare pt axa X magnetometer.adjustment.y = buff[1]; // Adjustare pt axa Y magnetometer.adjustment.z = buff[2]; // Adjustare pt axa Z I2CwriteByte(MPU9250_MAG_ADDRESS, 0x0A, 0x10); // Power down } bool isMagnetometerReady() // asigura ca datele sunt citite doar dupa ce sunt actualizate { uint8_t isReady; // flag de intrerupere I2Cread(MPU9250_MAG_ADDRESS, 0x02, 1, &isReady); return isReady & 0x01; } void readRawMagnetometer() // Citeste valorile brute de la magnetometru pe axele x, y, z și le stochează în structura magnetometer { uint8_t buff[7]; // Read output registers: // [0x03-0x04] masuratoare axa X // [0x05-0x06] masuratoare axa Y // [0x07-0x08] masuratoare axa Z I2Cread(MPU9250_MAG_ADDRESS, 0x03, 7, buff); // Magnetometer, creaza valori de 16 bits din date de 8 biti magnetometer.x = (buff[1] << 8 | buff[0]); magnetometer.y = (buff[3] << 8 | buff[2]); magnetometer.z = (buff[5] << 8 | buff[4]); } bool isImuReady() // verifică dacă datele de la IMU (Inertial Measurement Unit, care include giroscopul și accelerometrul) sunt gata de citit { uint8_t isReady; // flag de intrerupere I2Cread(MPU9250_IMU_ADDRESS, 58, 1, &isReady); return isReady & 0x01; } void readRawImu() // Citeste datele brute de la accelerometru și giroscop, plus temperatura, din IMU și le stochează în structurile respective { uint8_t buff[14]; // Read output registers: // [59-64] Accelerometer // [65-66] Temperature // [67-72] Gyroscope I2Cread(MPU9250_IMU_ADDRESS, 59, 14, buff); // Accelerometer, create 16 bits values from 8 bits data accelerometer.x = (buff[0] << 8 | buff[1]); accelerometer.y = (buff[2] << 8 | buff[3]); accelerometer.z = (buff[4] << 8 | buff[5]); // Temperature, create 16 bits values from 8 bits data temperature.value = (buff[6] << 8 | buff[7]); // Gyroscope, create 16 bits values from 8 bits data gyroscope.x = (buff[8] << 8 | buff[9]); gyroscope.y = (buff[10] << 8 | buff[11]); gyroscope.z = (buff[12] << 8 | buff[13]); }
Functiile de normalizare si calibrare
Normalizarea are rolul de a transforma datele brute citite de senzori in unitati standard. Calibrarea, cum ar fi in cazul setarea valorilor de ajustare pentru magnetometru permit compensarea variațiilor de fabricație și influențelor ambientale, crescând acuratețea și fiabilitatea măsurătorilor.
void normalize(gyroscope_raw gyroscope) // pt a transforma in grade pe secunda folosind factorul de scalare specificat în documentația senzorului { // (MPU datasheet pag 8) normalized.gyroscope.x = gyroscope.x / 32.8; normalized.gyroscope.y = gyroscope.y / 32.8; normalized.gyroscope.z = gyroscope.z / 32.8; } void normalize(accelerometer_raw accelerometer) // pt a transforma la m/s^2 { // factor de scalare (MPU datasheet pag 9) normalized.accelerometer.x = accelerometer.x * G / 16384; normalized.accelerometer.y = accelerometer.y * G / 16384; normalized.accelerometer.z = accelerometer.z * G / 16384; } void normalize(temperature_raw temperature) { //Scale Factor (MPU datasheet pag 11) && formula (MPU pag 33) normalized.temperature = ((temperature.value - TEMPERATURE_OFFSET) / 333.87) + TEMPERATURE_OFFSET; } void normalize(magnetometer_raw magnetometer) // calibreaza valorile brute si le ajusteaza { // factor scalar de senzitivitate (MPU datasheet pag 10) // 0.6 µT/LSB (14-bit) // 0.15µT/LSB (16-bit) // Avalori de ajustare (MPU register page 53) normalized.magnetometer.x = magnetometer.x * 0.15 * (((magnetometer.adjustment.x - 128) / 256) + 1); normalized.magnetometer.y = magnetometer.y * 0.15 * (((magnetometer.adjustment.y - 128) / 256) + 1); normalized.magnetometer.z = magnetometer.z * 0.15 * (((magnetometer.adjustment.z - 128) / 256) + 1); }
Aceasta functie asigura ca toate dispozitivele sunt configurate corespunzator inainte de inceperea buclei pricipale loop().
void setup() { Wire.begin(); Serial.begin(9600); I2CwriteByte(MPU9250_IMU_ADDRESS, 27, GYRO_FULL_SCALE_1000_DPS); // scalarea completă a giroscopului la 1000 de grade pe secundă I2CwriteByte(MPU9250_IMU_ADDRESS, 28, ACC_FULL_SCALE_2G); // scalarea completă a accelerometrului la 2G I2CwriteByte(MPU9250_IMU_ADDRESS, 55, 0x02); I2CwriteByte(MPU9250_IMU_ADDRESS, 56, 0x01); setMagnetometerAdjustmentValues(); // configurează și citește valorile de calibrare ale magnetometrului I2CwriteByte(MPU9250_MAG_ADDRESS, 0x0A, 0x12); // configurare LCD lcd.init(); lcd.backlight(); lcd.begin(16, 2); // Configurează pinii la care sunt conectate butoanele și LED-ul ca intrări cu rezistență de pull-up internă, respectiv ca ieșire pinMode(B1, INPUT_PULLUP); pinMode(B2, INPUT_PULLUP); pinMode(B3, INPUT_PULLUP); pinMode(B4, INPUT_PULLUP); pinMode(LED, OUTPUT); pinMode(reed, INPUT); // seteaza pinul pt senzorul reed ca intrare reed_nr = max_reed_nr; // reed_nr urmeaza a fi decrementat circumference = 2 * 3.14 * radius; cli(); // Dezactivează întreruperile globale în timpul configurării timerului pentru a evita problemele de sincronizare // Resetează registrele de control și contorul timerului 1 la 0 pentru configurare TCCR1A = 0; TCCR1B = 0; TCNT1 = 0; OCR1A = 1999; // = (1/1000) / ((1/(16*10^6))*8) - 1 valoarea de comparare a timerului 1 pt a genera la fiecare 1ms o intrerupere // frecventa ceasului = 16MHz TCCR1B |= (1 << WGM12); // mod CTC TCCR1B |= (1 << CS11); // prescaler = 8 TIMSK1 |= (1 << OCIE1A); // da enable intreruperea de comparare sei(); // activeaza intreruperile if (!bmp.begin(BMP280_I2C_ADDRESS)) { // Verifică dacă senzorul BMP280 este conectat și poate fi inițiat la adresa I2C specificată Serial.println("Could not find a valid BMP280 sensor, check wiring!"); while (1); // Blochează execuția programului dacă senzorul nu este detectat, evitând rularea ulterioară a codului } for (int i = 0; i < NUM_READINGS; i++) { headingReadings[i] = 0; } }
Functia se executa automat la fiecare 1ms, de fiecare data cand timerul 1 atinge valoarea 1999 si se reseteaza automat la 0 (mod CTC). Comentariile din cod descriu amanuntit logica de implementare, dar voi oferi si o explicatie per ansamblu.
Logica explicata:
Consider ca dificultatea acestui cod ar putea consta in diferentierea lui reed_nr de time_one_rot. timer_one_rot este un cronometru care măsoară intervalul de timp, în milisecunde, între rotații. Fiecare dată când senzorul Reed detectează trecerea unui magnet (indicând o rotație completă), timer_one_rot este resetat la zero, iar timpul până la următoarea detecție este folosit pentru a calcula viteza. In timp ce, variabila reed_nr este folosita ca un contor pentru debouncing. Evita citirile multiple sau false cauzate de zgomotul mecanic sau oscilațiile contactului senzorului reed, care ar putea înregistra mai multe impulsuri pentru o singură trecere a magnetului. O nouă detecție a rotației este acceptată numai după ce a trecut suficient timp de la ultima detecție validă.
ISR(TIMER1_COMPA_vect) { reedVal = digitalRead(reed); // Citeste starea pinului conectat la senzorul Reed care detecteaza rotatiile rotii bicicletei // se citeste starea fiecarui buton int button1 = digitalRead(B1); int button2 = digitalRead(B2); int button3 = digitalRead(B3); int button4 = digitalRead(B4); if ((button1 == LOW) && (old_button1_state == HIGH)) { // apasarea butonului 1 lastSize = tire_dimensions[tire_index]; // salveaza dimensiunea curenta a rotii tire_index = (tire_index + 1) % 3; // Rotește prin dimensiunile disponibile radius = tire_dimensions[tire_index]; circumference = 2 * M_PI * radius; // noua circumferinta target_distance = 0; // se seteaza distanta de target la 0 t = 0; // timerul pentru led resetat } else { digitalWrite(LED, LOW); } old_button1_state = button1; // actualizarea starii butonului if ((button2 == LOW) && (old_button2_state == HIGH)) { if (millis() - lastDebounceTime > debounceDelay) { // Actualizează distanța doar dacă au trecut debounceDelay milisecunde de la ultima schimbare target_distance++; lastDebounceTime = millis(); // Resetare timp la ultima schimbare } } old_button2_state = button2; if ((button3 == LOW) && (old_button3_state == HIGH)) { display = true; // flag verificat in loop care imi indica sa afisez pe lcd temperatura si presiunea } old_button3_state = button3; if ((button4 == LOW) && (old_button4_state == HIGH)) { displayDirection = true; // flag verificat in loop care imi indica sa afisez pe lcd datele giroscopice si accelerometrice } old_button4_state = button4; if (target_distance < totalDistanceTraveled) { // daca am depasit targetul setat aprind ledul if(t < 2000) { // pt a aprinde timp de 2s ledul cand ating distanta target digitalWrite(LED, HIGH); } t++; if(t > 2000) { // led stins dupa 2s digitalWrite(LED, LOW); } } else { digitalWrite(LED, LOW); t = 0; } if (reedVal != 1) { // s-a detectat o rotatie a rotii if (reed_nr == 0) { // a trecut suficient timp de la ultima activare si poate fi considerata noua citire (contorul de debouncing) /* Calculează viteza curentă. Formula convertește circumferința roții din inch în metri (înmulțind cu 0.0254), apoi calculează distanța pe oră împărțind la timpul între două rotații (în secunde) și înmulțind cu 3600 pentru a obține km/h */ speed = (3600 * (float(circumference) * 0.0254)) / float(timer_one_rot); totalDistanceTraveled += (circumference * 0.0000254); readings++; // incrementare nr de citiri facute averageSpeed = (averageSpeed + speed) / 2; // actualizare a vitezei medii cu ultima viteza curenta if (readings == max_readings) { readings = 1; averageSpeed = speed; //la 100 de citiri resetez v medie la ultima curenta masurata } timer_one_rot = 0; // resetare cronometru ce masoara timpul dintre 2 rotatii reed_nr = max_reed_nr; // resetarea contorului de debouncing la valoarea maximă pentru urmatoarea masurare // resetarea contorului de debouncing la valoarea maximă pentru a preveni citiri false imediat după o citire validă. } else { if (reed_nr > 0) { // perioada de debouncing reed_nr -= 1; } } } else { if (reed_nr > 0) { reed_nr -= 1; } } if (timer_one_rot > 2000) { // considera ca bicicleta e oprita aici speed = 0; } else { timer_one_rot += 1; // daca nu e oprita masor in continuare timpul intre rotatii } }
Funcția afiseaza pe un ecran LCD informații esențiale pentru cicliști, cum ar fi dimensiunea roții, viteza curentă, viteza medie, distanța totală parcursă și distanța țintă. Începe prin a verifica și afișa schimbările dimensiunii roții pentru a asigura acuratețea măsurătorilor, apoi continuă cu actualizarea constantă a vitezei și distanței, oferind ciclistului date necesare pentru monitorizarea performanței.
void displayNormal() { /* Verifică dacă raza roții curente (radius) este diferită de ultima raza stocată (lastSize). Dacă acestea sunt diferite, înseamnă că mărimea roții a fost schimbată și trebuie actualizat afișajul. */ if (radius != lastSize) { lcd.clear(); lcd.setCursor(0, 0); lcd.print("Wheel Size "); lcd.print(radius * 2); delay(700); lcd.clear(); lastSize = radius; averageSpeed = 0; // resetare a vitezei medii deoarece calculele anterioare nu mai sunt relevante } lcd.setCursor(0, 0); lcd.print(speed); if (speed < 10) { lcd.setCursor(4, 0); lcd.print(" "); } lcd.setCursor(6, 0); lcd.print("km/h"); lcd.setCursor(0, 1); lcd.print(averageSpeed); lcd.setCursor(6, 1); lcd.print("km/h"); lcd.setCursor(12, 0); lcd.print(totalDistanceTraveled); lcd.setCursor(12, 1); lcd.print(target_distance); }
Imi afiseaza pe ecran doar la apasarea butonului corespunzator (adica activarea flagului display) temperatura ambientala, presiunea (furnizate de senzorul BMP 280), dar si punctul cardinal spre care ma indrept (pe baza datelor magnetometrice obtinute de la MPU9250 si din functia determinCardinalDirection(averageHeading) pe care o voi detalia in sectiunea urmatoare).
void displayInfoScreen() { lcd.clear(); temp = bmp.readTemperature(); // Citește temperatura actuală de la senzorul BMP280 pressure = bmp.readPressure(); // Citește presiunea atmosferică de la același senzor BMP280 lcd.setCursor(0, 0); lcd.print("Temp: "); lcd.print(temp); lcd.print(" C"); String direction = determinCardinalDirection(averageHeading); lcd.print(" "); lcd.print(direction); lcd.setCursor(0, 1); lcd.print("Press: "); lcd.print(pressure); lcd.print(" Pa"); delay(2000); display = false; // resetez iar flagul cu false care poate fi activat doar prin apasarea butonului corespunzator lcd.clear(); }
Pe baza unghiului furnizat, funcția clasifică direcția în una dintre categoriile standard: Nord (N), Est (E), Sud (S) si Vest (W).
String determinCardinalDirection(float heading) { if (heading >= 315 || heading < 45) { return "N"; // Nord } else if (heading >= 45 && heading < 135) { return "E"; // Est } else if (heading >= 135 && heading < 225) { return "S"; // Sud } else if (heading >= 225 && heading < 315) { return "W"; // Vest } return "Unknown"; // În caz de eroare sau date invalide }
Functia este folosita pentru a actualiza si calcula punctul cardinal folosind media unui esantion de NUM_READINGS citiri. Se calculeaza un nou heading folosind funcția atan2 pentru a obține unghiul dintre axa y și x al magnetometrului, exprimat în grade. Dacă acest unghi este negativ, i se adaugă 360 de grade pentru a obține o valoare pozitivă. Apoi, se actualizeaza in vectorul de citiri (rotatie circulara) scotand cea mai veche valoare, adaugand noul unghi si se face media stabilizand astfel directia obtinuta si reducand din fluctuatiile senzorului.
void updateHeadingReadings() { float newHeading = (atan2(normalized.magnetometer.y, normalized.magnetometer.x)) * 180 / M_PI; if (newHeading < 0.0) { newHeading += 360.0; } // Incrementare index circular headingIndex = (headingIndex + 1) % NUM_READINGS; // Scoate cea mai veche valoare din suma totală totalHeading -= headingReadings[headingIndex]; // Adaugă noua valoare la poziția cea mai veche, acum actualizată headingReadings[headingIndex] = newHeading; totalHeading += newHeading; // Calculează media averageHeading = totalHeading / NUM_READINGS; }
Funcția este destinată să afișeze pe un ecran LCD valorile normalizate pentru giroscop (GYR) și accelerometru (ACC).
void display_Mag_Gyr() { lcd.clear(); lcd.setCursor(0, 0); lcd.print("GYR "); lcd.print(normalized.gyroscope.x, 1); lcd.print(" "); lcd.print(normalized.gyroscope.y, 1); lcd.print(" "); lcd.print(normalized.gyroscope.z, 1); lcd.setCursor(0, 1); lcd.print("ACC "); lcd.print(normalized.accelerometer.x, 1); lcd.print(" "); lcd.print(normalized.accelerometer.y, 1); lcd.print(" "); lcd.print(normalized.accelerometer.z, 1); delay(2000); displayDirection = false; lcd.clear(); delay(2000); }
Functia e conceputa pentru a gestiona activitatile ciclice ale dispozitivului. Citeste, proceseaza si normalizeaza perpetuu datele brute ale senzorilor (giroscop, magnetometru si accelerometru) si afiseaza informatii pe LCD in functie de flagul care este true (cele activate de apasarea butoanelor si afisarea default cu viteza, viteza medie, distanta si distanta target). Funcția introduce și o întârziere pentru a echilibra ritmul de executare. Functia updateHeadingReadings() este apelata aici pentru a colecta mereu datele senzorului pentru a obtine media stabila.
void loop() { if (isImuReady()) { // verifică dacă unitatea inerțială de măsurare (IMU), care include un giroscop și un accelerometru, este pregătită pentru a furniza date readRawImu(); // citeste datele brute // Normalizarea datelor brute normalize(gyroscope); normalize(accelerometer); normalize(temperature); } if (isMagnetometerReady()) { // Verifică dacă magnetometrul este gata să furnizeze date readRawMagnetometer(); // citire date brute normalize(magnetometer); // normalizarea datelor brute ale magnetometrului } updateHeadingReadings(); if ( displayDirection == true){ // Verifică dacă trebuie afișată direcția bazată pe datele magnetometrului și giroscopului display_Mag_Gyr(); } else if (display) { displayInfoScreen(); // verifica daca trebuie sa afiseze temp , presiunea si punctul cardinal } else { displayNormal(); // afișează defaultpe LCD viteza curentă, viteza medie și distanța totală parcursă si target } delay(500); // reduce viteza de executare pentru a stabiliza afisajul 500 }
Fara a apasa pe niciun buton, pe ecran imi sunt afisate viteza , viteza medie, distanta parcursa si distanta setata de target.
Butonul 1 imi seteaza dimensiunea rotii, am ledul aprins deoarece distanta target se reseteaza la 0 si eu parcursesem deja o distanta > 0. Dimensiunea rotii poate fi de 26, 27.5 sau 29 inch.
Butonul 2 imi va incrementa distanta target la fiecare apasare.
Butonul 3 imi va afisa temperatura, presiunea si punctul cardinal spre care ma indrept.
Butonul 4 imi va afisa datele giroscopice si magnetometrice.
La partea de hardware, ce a fost mai provocator a fost realizarea lipiturilor cu letconul pentru ca a necesitat multa rabdare, partea de conectat si facut cablajul mi-a placut, a fost ca un construit de Lego. Partea fizica cea mai dificila a fost realizarea carcasei, dar totodata am si vrut sa arate ca un produs final finisat si sunt destul de mandra de cum arata.
In ceea ce priveste partea de cod, m-am documentat din mai multe surse pe care le voi referi mai jos despre conceptele folosite (exemplu: interactiunea cu MPU9250, cum functioneaza si cum e construit senzorul). Ceea ce mi-a placut cel mai mult e ca chiar am observat o legatura logica si clara intre partea fizica si cea de software. Am avut o dificultate si m-am straduit mult sa rezolv partea ce priveste afisarea punctului cardinal (desi magnetometrul este calibrat si valorile ajustate, arata niste valori destul de dispersate), planuiesc sa lucrez in continuare la topicul acesta.
In final, produsul final arata bine, are functionalitate practica reala si mi-a placut sa lucrez la creearea lui.