This shows you the differences between two versions of the page.
|
pm:prj2021:apredescu:pistol-de-masurare-temperatura [2021/06/01 20:45] ovidiu.stanica |
pm:prj2021:apredescu:pistol-de-masurare-temperatura [2021/06/03 17:09] (current) ovidiu.stanica |
||
|---|---|---|---|
| Line 7: | Line 7: | ||
| Pistolul de măsurat temperatura nu intră în contact cu suprafața a cărei temperatură este măsurată.\\ | Pistolul de măsurat temperatura nu intră în contact cu suprafața a cărei temperatură este măsurată.\\ | ||
| - | Pentru eșantionarea temperaturii se folosește un senzor de temperatură cu raze infraroșii MXL90614.\\ | + | Pentru eșantionarea temperaturii se folosește un senzor de temperatură cu raze infraroșii MLX90614.\\ |
| - | Pentru măsurarea cât mai potrivită, vor fi introduse 3 moduri de măsurare, specifice unor medii de temperatură. | + | Pentru măsurarea cât mai potrivită, vor fi introduse 3 moduri de măsurare, specifice unor medii de temperatură.\\ |
| + | Se poate folosi exact ca un pistol de măsurat temperatura găsit în supermarket-uri. | ||
| ===== Descriere generală ===== | ===== Descriere generală ===== | ||
| Line 16: | Line 17: | ||
| Pistolul are 2 butoane prin care e controlată măsurarea temperaturii. Un buton, numit declanșator, activează măsurarea temperaturii cât timp e apăsat, în continuu. Temperatura este afișată pe LCD, pe rândul de jos, în partea stângă. În partea dreaptă e afișată temperatura trecută prin filtrul de procesare. | Pistolul are 2 butoane prin care e controlată măsurarea temperaturii. Un buton, numit declanșator, activează măsurarea temperaturii cât timp e apăsat, în continuu. Temperatura este afișată pe LCD, pe rândul de jos, în partea stângă. În partea dreaptă e afișată temperatura trecută prin filtrul de procesare. | ||
| - | Pistolul oferă 3 filtre de procesare:\\ | + | Pistolul oferă 2 filtre de procesare:\\ |
| * Minim (MIN) păstrează doar minimul temperaturilor măsurate;\\ | * Minim (MIN) păstrează doar minimul temperaturilor măsurate;\\ | ||
| * Maxim (MAX) păstrează doar minimul temperaturilor măsurate;\\ | * Maxim (MAX) păstrează doar minimul temperaturilor măsurate;\\ | ||
| - | * Average (AVG) face media temperaturilor măsurate;\\ | ||
| * None (---) afișează ultima temperatură măsurată; | * None (---) afișează ultima temperatură măsurată; | ||
| - | Filtrul de procesare e resetat când se începe o nouă măsuratoare (adică când se apasă declanșatorul) | + | Filtrul de procesare e resetat când se începe o nouă măsuratoare (adică când se apasă declanșatorul). |
| - | Carcasa pistolului va fi făcută la o imprimanta 3D. (sau cel puțin voi încerca să folosesc un soft de design, Fusion 360) | + | |
| + | ==Schema bloc== | ||
| + | {{:pm:prj2021:apredescu:schema_bloc_stanica_ovidiu-stefan.jpeg?400|}} | ||
| ===== Hardware Design ===== | ===== Hardware Design ===== | ||
| Voi avea nevoie de următoarele componente:\\ | Voi avea nevoie de următoarele componente:\\ | ||
| - | * Arduino Uno\\ | + | * Arduino Nano\\ |
| - | * LCD 1602 + Adaptor I2C\\ | + | * LCD 1602\\ |
| + | * Adaptor I2C\\ | ||
| * Buton schimbat mod măsurare\\ | * Buton schimbat mod măsurare\\ | ||
| * Buton declanșat măsurarea\\ | * Buton declanșat măsurarea\\ | ||
| Line 37: | Line 40: | ||
| * Întrerupător on/off | * Întrerupător on/off | ||
| - | {{:pm:prj2021:apredescu:schema_bloc_stanica_ovidiu-stefan.jpeg?400|}} | + | Display-ul și senzorul le-am pus pe aceeași "placă", conectate la Arduino printr-un cablu de Ethernet. Am folosit cablu de ethernet pentru că are ecranare cât de cât bună, și I2C este gândit să fie folosit pe același circuit board, deci nu suportă interferențe prea mari. |
| - | + | ||
| - | Display-ul si senzorul le-am pus pe aceeasi "placa", conectate la Arduino printr-un cablu de Ethernet. Am folosit cablu de ethernet pentru ca are ecranare cat de cat buna, si I2C este gandit sa fie folosit pe acelasi circuit board, deci nu suporta interferente prea mari. | + | |
| - | Butoanele sunt conectare direct langa Arduino. | + | Butoanele sunt conectare direct lângă Arduino. |
| - | {{:pm:prj2021:apredescu:schema_electrica_stanica_ovidiu.jpg?400|}} | + | ==Schema electrică== |
| + | {{:pm:prj2021:apredescu:schema_electrica_stanica_ovidiu.jpg?600|}} | ||
| ===== Software Design ===== | ===== Software Design ===== | ||
| - | Am folosit un timer (timer2) pe Atmega ca sa fac citirea senzorului de 2500 ori pe secunda. | + | Am folosit un timer (timer2) pe Atmega ca să fac citirea senzorului de 2500 ori pe secundă. |
| - | + | ||
| - | Pentru ca senzorul de temperatura este interfatat pe I2C, si comunicatia pe I2C are nevoie ca interrupt-urile sa fie pornite, nu pot face citirea senzorului direct in interrupt-ul de timer. | + | |
| - | In loc, interrupt-ul seteaza un flag global. Bucla principala verifica constant acest flag, si daca este setat, il elimina si face o citire a senzorului. | + | |
| - | + | ||
| - | Afisarea la display se face de 10 ori pe secunda. In mod ideal, afisare s-ar putea face mai des, dar refresh rate-ul display-ului nu permite (dureaza ~70ms ca un pixel sa-si schimbe starea) | + | |
| - | + | ||
| - | Filtrele sunt evaluate odata cu refresh-ul senzorului. Daca se cere schimbarea filtrului, inainte de citire, se reseteaza datele de acumulare si e schimbata functia de filtru. | + | |
| - | + | ||
| - | <code> | + | |
| - | #include <Wire.h> | + | |
| - | #include <LiquidCrystal_I2C.h> | + | |
| - | #include <Adafruit_MLX90614.h> | + | |
| - | + | ||
| - | byte stage1[] = { | + | |
| - | 0x00, | + | |
| - | 0x00, | + | |
| - | 0x00, | + | |
| - | 0x10, | + | |
| - | 0x10, | + | |
| - | 0x10, | + | |
| - | 0x18, | + | |
| - | 0x1C | + | |
| - | }; | + | |
| - | + | ||
| - | byte stage2[] = { | + | |
| - | 0x1C, | + | |
| - | 0x18, | + | |
| - | 0x10, | + | |
| - | 0x10, | + | |
| - | 0x10, | + | |
| - | 0x00, | + | |
| - | 0x00, | + | |
| - | 0x00 | + | |
| - | }; | + | |
| - | byte stage3[] = { | + | |
| - | 0x1F, | + | |
| - | 0x03, | + | |
| - | 0x01, | + | |
| - | 0x00, | + | |
| - | 0x00, | + | |
| - | 0x00, | + | |
| - | 0x00, | + | |
| - | 0x00 | + | |
| - | }; | + | |
| - | + | ||
| - | byte stage4[] = { | + | |
| - | 0x00, | + | |
| - | 0x00, | + | |
| - | 0x00, | + | |
| - | 0x01, | + | |
| - | 0x01, | + | |
| - | 0x01, | + | |
| - | 0x03, | + | |
| - | 0x07 | + | |
| - | }; | + | |
| - | + | ||
| - | char line1[19] = { 'C', 'U', 'R', 'R', 'E', 'N', 'T', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 0 }; | + | |
| - | char line2[19] = { ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 0 }; | + | |
| - | + | ||
| - | Adafruit_MLX90614 mlx = Adafruit_MLX90614(); | + | |
| - | LiquidCrystal_I2C lcd(0x3F, 16, 2); | + | |
| - | + | ||
| - | bool changeFilterLastState = false; | + | |
| - | bool measureLastState = false; | + | |
| - | + | ||
| - | #define MIN_TEMP (-50) | + | |
| - | #define MAX_TEMP (+320) | + | |
| - | + | ||
| - | typedef int filter_type; | + | |
| - | #define FILTER_MIN (1) | + | |
| - | #define FILTER_MAX (2) | + | |
| - | #define FILTER_CUR (0) | + | |
| - | + | ||
| - | filter_type currentFilter = FILTER_CUR; | + | |
| - | double accumulator = 0; | + | |
| - | double immediate = 0; | + | |
| - | + | ||
| - | bool measuring = false; | + | |
| - | int measuringStage = 0; | + | |
| - | + | ||
| - | volatile bool readPending = false; | + | |
| - | long lastRead = 0; | + | |
| - | + | ||
| - | filter_type getNextFilter() { | + | |
| - | if (currentFilter == FILTER_CUR) return FILTER_MIN; | + | |
| - | if (currentFilter == FILTER_MIN) return FILTER_MAX; | + | |
| - | if (currentFilter == FILTER_MAX) return FILTER_CUR; | + | |
| - | return FILTER_CUR; | + | |
| - | } | + | |
| - | + | ||
| - | void resetFilter() { | + | |
| - | if (currentFilter == FILTER_CUR) accumulator = 0; | + | |
| - | if (currentFilter == FILTER_MIN) accumulator = MAX_TEMP; | + | |
| - | if (currentFilter == FILTER_MAX) accumulator = MIN_TEMP; | + | |
| - | } | + | |
| - | + | ||
| - | void feedFilter(double imm) { | + | |
| - | immediate = imm; | + | |
| - | if (currentFilter == FILTER_CUR) { | + | |
| - | accumulator = imm; | + | |
| - | } | + | |
| - | if (currentFilter == FILTER_MIN) { | + | |
| - | accumulator = accumulator > imm ? imm : accumulator; | + | |
| - | } | + | |
| - | if (currentFilter == FILTER_MAX) { | + | |
| - | accumulator = accumulator < imm ? imm : accumulator; | + | |
| - | } | + | |
| - | } | + | |
| - | + | ||
| - | double readSensor() { | + | |
| - | return constrain(mlx.readObjectTempC(), MIN_TEMP, MAX_TEMP); | + | |
| - | } | + | |
| - | + | ||
| - | ISR(TIMER2_COMPA_vect){ | + | |
| - | readPending = true; | + | |
| - | } | + | |
| - | + | ||
| - | void readSensorAndUpdate() { | + | |
| - | bool measureThisState = /*!(PIND & PD5);*/ !digitalRead(5); | + | |
| - | if (measureThisState) { | + | |
| - | measuring = true; | + | |
| - | if (!measureLastState) { | + | |
| - | resetFilter(); | + | |
| - | } | + | |
| - | feedFilter(readSensor()); | + | |
| - | } else { | + | |
| - | measuring = false; | + | |
| - | bool changeFilterThisState = /*!(PINB & PB1)*/ !digitalRead(9); | + | |
| - | if (changeFilterThisState && !changeFilterLastState) { | + | |
| - | currentFilter = getNextFilter(); | + | |
| - | resetFilter(); | + | |
| - | feedFilter(readSensor()); | + | |
| - | } | + | |
| - | changeFilterLastState = changeFilterThisState; | + | |
| - | } | + | |
| - | measureLastState = measureThisState; | + | |
| - | } | + | |
| - | + | ||
| - | /** | + | |
| - | * Format: -56.22 | + | |
| - | * 74.78 | + | |
| - | * 313.01 | + | |
| - | * 6 chars length plus 2 spaces after, always fill 8! | + | |
| - | */ | + | |
| - | void formatTemp(double temp, char * loc) { | + | |
| - | int cursor = 0; | + | |
| - | if (temp < 0) { | + | |
| - | loc[cursor++] = '-'; | + | |
| - | temp = -temp; | + | |
| - | } | + | |
| - | + | ||
| - | int temp100 = ((int)(temp * 100)) % 100; | + | |
| - | int temp1 = (int)(temp); | + | |
| - | + | ||
| - | if (temp1 / 100) { | + | |
| - | loc[cursor++] = '0' + (temp1 / 100); | + | |
| - | temp1 = temp1 % 100; | + | |
| - | } | + | |
| - | + | ||
| - | if (temp1 / 10) { | + | |
| - | loc[cursor++] = '0' + (temp1 / 10); | + | |
| - | temp1 = temp1 % 10; | + | |
| - | } | + | |
| - | + | ||
| - | loc[cursor++] = '0' + temp1; | + | |
| - | loc[cursor++] = '.'; | + | |
| - | loc[cursor++] = '0' + (temp100 / 10); | + | |
| - | loc[cursor++] = '0' + (temp100 % 10); | + | |
| - | + | ||
| - | while (cursor != 8) { | + | |
| - | loc[cursor++] = ' '; | + | |
| - | } | + | |
| - | } | + | |
| - | + | ||
| - | void writeDisplay() { | + | |
| - | formatTemp(immediate, line2); | + | |
| - | if (currentFilter != FILTER_CUR) { | + | |
| - | formatTemp(accumulator, line2 + 10); | + | |
| - | if (currentFilter == FILTER_MAX) { | + | |
| - | line1[10] = 'M'; line1[11] = 'A'; line1[12] = 'X'; | + | |
| - | } | + | |
| - | if (currentFilter == FILTER_MIN) { | + | |
| - | line1[10] = 'M'; line1[11] = 'I'; line1[12] = 'N'; | + | |
| - | } | + | |
| - | } else { | + | |
| - | line1[10] = line1[11] = line1[12] = ' '; | + | |
| - | line2[8] = line2[9] = line2[10] = line2[11] = line2[12] = line2[13] = line2[14] = line2[15] = ' '; | + | |
| - | } | + | |
| - | if (measuring) { | + | |
| - | if (measuringStage == 0) line1[8] = 1; | + | |
| - | if (measuringStage == 1) line1[8] = 2; | + | |
| - | if (measuringStage == 2) line1[8] = 3; | + | |
| - | if (measuringStage == 3) line1[8] = 4; | + | |
| - | if (measuringStage == 3) measuringStage = 0; | + | |
| - | else measuringStage = measuringStage + 1; | + | |
| - | } else { | + | |
| - | line1[8] = ' '; | + | |
| - | } | + | |
| - | line1[16] = line2[16] = 0; | + | |
| - | // write lines | + | |
| - | lcd.setCursor(0, 0); | + | |
| - | lcd.print(line1); | + | |
| - | lcd.setCursor(0, 1); | + | |
| - | lcd.print(line2); | + | |
| - | } | + | |
| - | + | ||
| - | void setup() { | + | |
| - | DDRD |= (PD5 | PD6 | PD2); | + | |
| - | DDRB |= (PB1); | + | |
| - | pinMode(6, INPUT_PULLUP); | + | |
| - | pinMode(5, INPUT_PULLUP); | + | |
| - | Serial.begin(9600); | + | |
| - | mlx.begin(); | + | |
| - | Wire.setClock(100000); | + | |
| - | lcd.init(); | + | |
| - | lcd.init(); | + | |
| - | lcd.backlight(); | + | |
| - | lcd.createChar(1, stage1); | + | |
| - | lcd.createChar(2, stage2); | + | |
| - | lcd.createChar(3, stage3); | + | |
| - | lcd.createChar(4, stage4); | + | |
| - | cli(); | + | Pentru că senzorul de temperatură este interfațat pe I2C, și comunicația pe I2C are nevoie ca interrupt-urile să fie pornite, nu pot face citirea senzorului direct în interrupt-ul de timer. |
| - | TCCR2A = 0; | + | În loc, interrupt-ul setează un flag global. Bucla principală verifică constant acest flag, și dacă este setat, îl elimină și face o citire a senzorului. |
| - | TCCR2B = 0; | + | |
| - | TCNT2 = 0; | + | |
| - | OCR2A = 249; | + | |
| - | TCCR2B |= (1 << WGM21); | + | |
| - | TCCR2B |= (1 << CS22) | (0 << CS21) | (0 << CS20); | + | |
| - | TIMSK2 |= (1 << OCIE2A); | + | |
| - | sei(); | + | |
| - | } | + | |
| + | Afișarea la display se face de 10 ori pe secundă. În mod ideal, afișarea s-ar putea face mai des, dar refresh rate-ul display-ului nu permite (durează ~70ms ca un pixel să-și schimbe starea). | ||
| + | Filtrele sunt evaluate odată cu refresh-ul senzorului. Dacă se cere schimbarea filtrului, înainte de citire, se resetează datele de acumulare si e schimbată funcția de filtru. | ||
| - | void loop() { | + | ===== Rezultatul Obținut ===== |
| - | long current = millis(); | + | Am obținut un dispozitiv care poate măsura temperatura în timp real și care poate afișa maximul și minimul temperaturii dintr-un loc.\\ |
| - | if (current - lastRead >= 100) { | + | {{:pm:prj2021:apredescu:imagine1_stanica_ovidiu.jpeg?600|}} |
| - | writeDisplay(); | + | {{:pm:prj2021:apredescu:imagine_4_stanica_ovidiu.jpeg?600|}} |
| - | lastRead = current; | + | {{:pm:prj2021:apredescu:imagine_2_stanica_ovidiu.jpeg?600|}} |
| - | } | + | {{:pm:prj2021:apredescu:imagine_3_stanica_ovidiu.jpeg?600|}} |
| - | if (readPending) { | + | |
| - | readPending = false; | + | |
| - | readSensorAndUpdate(); | + | |
| - | } | + | |
| - | } | + | |
| - | </code> | + | |
| - | ===== Rezultatul Obtinut ===== | + | ===== Concluzii ===== |
| + | În urma realizării acestui proiect, am observat cât de ușor și de distractiv este să creezi proiecte folositoare folosindu-mă de arduino. | ||
| - | ===== Downloads ===== | + | ===== Download ===== |
| - | [[https://ocw.cs.pub.ro/courses/pm/prj2021/apredescu/pistol-de-masurare-temperatura?do=export_pdf|Link pdf]] | + | <note tip> |
| + | Arhiva conține o poză cu proiectul, schema bloc, schema electrică și codul sursă al proiectului: | ||
| + | {{:pm:prj2021:apredescu:resurse_stanica_ovidiu.zip|Arhivă Resurse}} | ||
| + | </note> | ||
| + | ===== Jurnal ===== | ||
| + | 2021/04/25 - Creare pagină wiki\\ | ||
| + | 2021/05/16 - Creare schemă bloc și actualizare pagină wiki\\ | ||
| + | 2021/06/01 - Finalizare parte hardware și software a proiectului + creare schemă electrică\\ | ||
| + | 2021/06/02 - Finalizare pagină wiki | ||
| ===== Bibliografie/Resurse ===== | ===== Bibliografie/Resurse ===== | ||
| - | [[https://ocw.cs.pub.ro/courses/pm/prj2021/apredescu/pistol-de-masurare-temperatura|Link proiect]] | + | [[https://ocw.cs.pub.ro/courses/pm/prj2021/apredescu/pistol-de-masurare-temperatura?do=export_pdf|Link pdf]]\\ |
| + | [[https://ocw.cs.pub.ro/courses/pm/prj2021/apredescu/pistol-de-masurare-temperatura|Link proiect]]\\ | ||
| + | https://www.youtube.com/watch?v=xAeO1ZQtwKc - Link cu video demo\\ | ||
| + | https://app.diagrams.net/ - Program folosit pentru schema bloc\\ | ||
| + | https://www.kicad.org/download/ - Program folosit pentru schema electrică | ||