6. týden: Senzory a vstupní zařízení Week 6: Sensors and Input Devices
Během šestého týdne bylo úkolem použít nějaký senzor pro měření fyzikální veličiny pomocí Arduina. During the sixth week, the task was to use a sensor to measure a physical quantity using an Arduino.
Měření teploty a hloubky Temperature and depth measurement
Doma jsem měl nevyužitý senzor teploty a senzor absolutního tlaku, tak jsem se rozhodl postavit přístroj na měření teploty kapaliny a hloubky ponoření sondy. Mozkem zařízení je Arduino Nano napájené z počítače přes USB. Naměřená data se zobrazují na OLED displeji a zároveň se ukládají na SD kartu pro pozdější zpracování a tvorbu grafů. Měření a zápis dat lze bezpečně ukončit stisknutím tlačítka. I had an unused temperature sensor and an absolute pressure sensor at home, so I decided to build a device for measuring liquid temperature and the immersion depth of the probe. The brain of the device is an Arduino Nano powered from a computer via USB. The measured data is displayed on an OLED display and simultaneously saved to an SD card for later processing and graph creation. Measurement and data recording can be safely terminated by pressing a button.
Zapojení Wiring
Seznam použitých součástek: List of used components:
- Univerzální nepájivé pole Universal breadboard
- Arduino Nano (klon) Arduino Nano (clone)
- Vodotěsný teploměr DS18B20 Waterproof thermometer DS18B20
- 4,7kΩ rezistor 4.7kΩ resistor
- MPRLS - tlakový senzor 172kPa I2C 3-5V - Adafruit 3965 MPRLS - pressure sensor 172kPa I2C 3-5V - Adafruit 3965
- I2C OLED displej 0,96" bílý 128 × 64 I2C OLED display 0.96" white 128 × 64
- MicroSD Card modul SPI MicroSD Card module SPI
- 32 GB MicroSD karta Lexar 32 GB MicroSD card Lexar
- MicroSD adaptér Kingston MicroSD adapter Kingston
- Čtečka paměťových karet SanDisk Memory card reader SanDisk
- Tlačítko TC-1212T (12 × 12 × 7.3 mm) TC-1212T Button (12 × 12 × 7.3 mm)
- LEGO trubička LEGO tube
Teplotní sonda využívá jednovodičovou sběrnici OneWire. Pro její správné fungování je nutné zapojit 4,7kΩ pull-up rezistor mezi napájecí a datový pin. Senzor tlaku MPRLS a OLED displej komunikují přes protokol I2C (dva vodiče - datový SDA a hodinový SCL). Modul čtečky SD karet je připojen přes rozhraní SPI (čtyři vodiče - data do karty MOSI, data z karty MISO, hodinový signál SCK a Chip Select CS). The temperature probe uses a OneWire single-wire bus. For its proper functioning, it is necessary to connect a 4.7kΩ pull-up resistor between the power and data pins. The MPRLS pressure sensor and the OLED display communicate via the I2C protocol (two wires - data SDA and clock SCL). The SD card reader module is connected via the SPI interface (four wires - data to card MOSI, data from card MISO, clock signal SCK, and Chip Select CS).
Zapojení jsem navrhl v programu KiCAD. Součástky jako Arduino Nano (Arduino_Nano_Every z knihovny MCU_Module), teplotní senzor DS18B20 (DS18B20), tlačítko (SW_Push) a rezistor (R) jsou již součástí základních knihoven programu. Symboly pro ostatní moduly lze stáhnout z internetu, případně je ve schématu nahradit standardními kolíkovými lištami (např. Conn_01x04_Socket pro OLED displej).
I designed the wiring in the KiCAD program. Components like the Arduino Nano (Arduino_Nano_Every from the MCU_Module library), the DS18B20 temperature sensor (DS18B20), the button (SW_Push), and the resistor (R) are already part of the program's basic libraries. Symbols for other modules can be downloaded from the internet, or replaced in the schematic with standard pin headers (e.g., Conn_01x04_Socket for the OLED display).
Teplotní sonda se standardně prodává s několikametrovým kabelem. Pro snadnější zapojení do nepájivého pole (breadboardu) jsem kabel zkrátil a na jeho konce připájel kolíkové piny. The temperature probe is normally sold with a several-meter-long cable. For easier connection to the breadboard, I shortened the cable and soldered header pins to its ends.
Poté jsem obvod zapojil podle připraveného schématu. Then I wired the circuit according to the prepared schematic.
Kód Code
Kód je napsán v prostředí Arduino IDE. Program v pravidelných 800ms intervalech vyčítá hodnoty teploty z teplotní sondy a absolutního tlaku z I2C senzoru. Z naměřeného tlaku a referenční hodnoty pak vypočítává hloubku ponoru podle vzorce: The code is written in the Arduino IDE environment. The program reads temperature values from the temperature probe and absolute pressure from the I2C sensor at regular 800ms intervals. From the measured pressure and the reference value, it then calculates the immersion depth according to the formula:
- h - hloubka ponoření [m] h - immersion depth [m]
- P - tlak naměřený pod hladinou [Pa] P - pressure measured below the surface [Pa]
- Pref - referenční atmosférický tlak na hladině [Pa] Pref - reference atmospheric pressure at the surface [Pa]
- ρ - hustota kapaliny [kg/m³] ρ - liquid density [kg/m³]
- g - tíhové zrychlení [m/s²] g - gravitational acceleration [m/s²]
Získaná data se průběžně zobrazují na OLED displeji a ukládají do CSV souboru na SD kartu. Stiskem tlačítka lze měření a zápis na kartu bezpečně ukončit. The acquired data is continuously displayed on the OLED display and saved to a CSV file on the SD card. By pressing the button, measurement and saving to the card can be safely terminated.
Použité knihovny: Used libraries:
- Wire - sběrnice I2C pro senzor tlaku a OLED displej Wire - I2C bus for the pressure sensor and OLED display
- Adafruit_MPRLS - senzor tlaku Adafruit_MPRLS - pressure sensor
- OneWire - sběrnice 1-Wire pro teplotní senzor OneWire - 1-Wire bus for the temperature sensor
- SPI - sběrnice SPI pro čtečku SD karet SPI - SPI bus for the SD card reader
- SD - modul čtečky SD karet SD - SD card reader module
- U8x8lib - OLED displej U8x8lib - OLED display
Kód v Arduinu: Code in Arduino:
#include <Wire.h>
#include <Adafruit_MPRLS.h>
#include <OneWire.h>
#include <SPI.h>
#include <SD.h>
#include <U8x8lib.h>
#define ONE_WIRE_BUS 2
#define BUTTON_PIN 3
#define CHIP_SELECT_SD 10
// inicializace periferii
OneWire oneWire(ONE_WIRE_BUS);
Adafruit_MPRLS mprls = Adafruit_MPRLS(-1, -1);
U8X8_SSD1306_128X64_NONAME_HW_I2C display(/* reset=*/ U8X8_PIN_NONE);
// merene hodnoty
float referencePressure = 0.0;
float currentTempC = 0.0;
float currentDepth_m = 0.0;
// rizeni behu
bool isRunning = true;
bool lastButtonState = HIGH;
unsigned long lastSensorReadTime = 0;
// sd karta a data
bool sdReady = false;
unsigned long sampleCount = 0;
void updateDisplay();
void setup() {
pinMode(BUTTON_PIN, INPUT_PULLUP);
// oled displej
display.begin();
display.setPowerSave(0);
display.setFont(u8x8_font_courB18_2x3_r);
display.clear();
// senzor tlaku
if (!mprls.begin()) {
display.print("err tlak");
delay(2000);
display.clear();
}
// sd karta
if (SD.begin(CHIP_SELECT_SD)) {
sdReady = true;
if (SD.exists("data.csv")) SD.remove("data.csv");
// hlavicka csv
File dataFile = SD.open("data.csv", FILE_WRITE);
if (dataFile) {
dataFile.println(F("vzorec,teplota,hloubka"));
dataFile.close();
}
} else {
display.print("err sd");
delay(2000);
display.clear();
}
// kalibrace tlaku
delay(500);
referencePressure = mprls.readPressure();
// start prevodu teploty
oneWire.reset();
oneWire.skip();
oneWire.write(0x44);
updateDisplay();
}
void loop() {
// cteni tlacitka
bool currentButtonState = digitalRead(BUTTON_PIN);
// detekce stisku pro zastaveni
if (currentButtonState == LOW && lastButtonState == HIGH) {
isRunning = false;
display.clear();
display.setCursor(0, 2);
display.print("STOP");
delay(50);
}
lastButtonState = currentButtonState;
// periodicke mereni
if (isRunning && (millis() - lastSensorReadTime >= 800)) {
lastSensorReadTime = millis();
sampleCount++;
// vycteni teploty
oneWire.reset();
oneWire.skip();
oneWire.write(0xBE);
int16_t raw = oneWire.read() | (oneWire.read() << 8);
currentTempC = (float)raw / 16.0;
// novy prevod
oneWire.reset();
oneWire.skip();
oneWire.write(0x44);
// vypocet hloubky
float currentPressure = mprls.readPressure();
currentDepth_m = ((currentPressure - referencePressure) * 100.0) / 9810.0;
if (currentDepth_m < 0) currentDepth_m = 0;
// ulozeni na sd
if (sdReady) {
File dataFile = SD.open("data.csv", FILE_WRITE);
if (dataFile) {
dataFile.print(sampleCount);
dataFile.print(',');
dataFile.print(currentTempC, 1);
dataFile.print(',');
dataFile.println(currentDepth_m, 3);
dataFile.close();
}
}
updateDisplay();
}
}
void updateDisplay() {
display.clear();
// radek 1: teplota
display.setCursor(0, 0);
display.print(currentTempC, 1);
display.print(" C");
// radek 2: hloubka
display.setCursor(0, 4);
int displayDepth_mm = currentDepth_m * 1000;
display.print(displayDepth_mm);
display.print(" mm");
}
Pro následné zpracování dat na počítači jsem vytvořil Python skript. Ten načte soubor DATA.csv, převede hodnoty hloubky z metrů na milimetry a na data aplikuje klouzavý průměr (s oknem o velikosti 5 vzorků), čímž se vyhladí šum měření. Nakonec pomocí knihovny Matplotlib vykreslí grafy pro teplotu a hloubku.
For subsequent data processing on a computer, I created a Python script. It reads the DATA.csv file, converts depth values from meters to millimeters, and applies a moving average (with a window size of 5 samples) to the data, which smooths out the measurement noise. Finally, using the Matplotlib library, it plots graphs for temperature and depth.
import matplotlib.pyplot as plt
import pandas as pd
window_size = 5
# nacteni dat z DATA.csv
try:
df = pd.read_csv('DATA.csv')
except FileNotFoundError:
print("DATA.csv nebyl nalezen.")
exit()
# prevod hloubky z metru na milimetry
df['hloubka_mm'] = df['hloubka'] * 1000
# vypocet klouzaveho prumeru pro teplotu i převedenou hloubku
df['temp_avg'] = df['teplota'].rolling(window=window_size, center=True).mean()
df['depth_avg_mm'] = df['hloubka_mm'].rolling(window=window_size, center=True).mean()
# vytvoreni platna pro grafy
fig, (ax_temp, ax_depth) = plt.subplots(1, 2, figsize=(14, 6))
# vykresleni teploty
ax_temp.plot(df['vzorec'], df['teplota'], 'b-', alpha=0.3, label='Původní data')
ax_temp.plot(df['vzorec'], df['temp_avg'], 'b-', linewidth=2, label=f'Průměr (n = {window_size})')
ax_temp.set_title('Teplota')
ax_temp.set_xlabel('Číslo vzorku')
ax_temp.set_ylabel('Teplota (°C)')
ax_temp.grid(True, linestyle='--', alpha=0.7)
ax_temp.legend()
# vykresleni hloubky
ax_depth.plot(df['vzorec'], df['hloubka_mm'], 'r-', alpha=0.3, label='Původní data')
ax_depth.plot(df['vzorec'], df['depth_avg_mm'], 'r-', linewidth=2, label=f'Průměr (n = {window_size})')
ax_depth.set_title('Hloubka')
ax_depth.set_xlabel('Číslo vzorku')
ax_depth.set_ylabel('Hloubka (mm)')
ax_depth.grid(True, linestyle='--', alpha=0.7)
ax_depth.legend()
# finalni uprava a zobrazeni
plt.tight_layout()
plt.show()
Měření hloubky a teploty vody ve sklenici Measuring the depth and temperature of water in a glass
Funkčnost přístroje jsem otestoval ve sklenici s teplou vodou. Měření hloubky je velmi přesné (s odchylkou zhruba do ±2 mm). Zjistil jsem však, že po vytažení z vody zůstává konec měřicí trubičky zaplněn několika milimetry vody. Pokud se trubička před dalším měřením nevyfoukne, dojde ke špatnému stanovení referenčního atmosférického tlaku při kalibraci a všechny následně měřené hodnoty jsou pak posunuté. I tested the device's functionality in a glass of warm water. The depth measurement is very accurate (with a deviation of roughly up to ±2 mm). However, I found that after pulling it out of the water, the end of the measuring tube remains filled with a few millimeters of water. If the tube is not blown out before the next measurement, the reference atmospheric pressure is incorrectly determined during calibration, and all subsequently measured values are then shifted.