26 KiB
Air Quality Monitor
Air Quality Monitor is a lightweight web interface that displays real-time data from two environmental sensors: HTU21D and BMP180. The system uses NGINX to serve an HTML page that reads live data from JSON files. All the system is runing on a beagle bone black.
Project Description
This project was designed to visualize environmental parameters in a simple, fast, and efficient way using an embedded graphical interface. The data is obtained from the following sensors:
- HTU21D: Temperature and relative humidity.
- BMP180: Atmospheric pressure and temperature.
The data is stored in two JSON files, updated by the embedded system and displayed through an HTML/JavaScript-based frontend.
Project Structure
UI directory
- BMP180.json # Pressure and temperature data from BMP180
- HTU21D.json # Temperature and humidity data from HTU21D
- index.html # Main web interface
- index.js # JavaScript logic to fetch and display JSON data
- style.css # Custom CSS styles
The
.json
files are automatically updated by C programs that communicate with the sensors via I2C witch the librarys integrated on the repository.
Conection of the sensors with Beagle bone.
Both sensors that we are using are conected to the I2C port 2 of the beagle bone in parallel(P9-19 and P9-20):
🚀 Installation & Deployment with NGINX
Requirements
- Linux server with
nginx
installed. - Root access or permission to modify NGINX configuration.
Steps
- Install NGINX (if not already installed):
sudo apt update
sudo apt install nginx
- Copy the project files to NGINX’s public directory (e.g., /var/www/html):
- Redirect the configuration file to our repository folder:
sudo vi /etc/nginx/sites-available/default
- Replace the line:
root /var/www/html;
with:
root /home/debian/path/to/your/repository;
- Apply chages with:
sudo systemctl restart nginx
User interface
This is a simple web page that displays real-time sensor data from two devices:
- BMP180: Shows temperature and pressure.
- HTU21D: Shows humidity and temperature.
It’s styled with a separate CSS file (style.css) and fetches live data using JavaScript (index.js), which likely reads values from two JSON files (BMP180.json and HTU21D.json).
HTML
Complete code
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Sensor Dashboard</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="background"></div>
<div class="container">
<h1>Sensor Dashboard</h1>
<div class="sensor-card">
<img src="https://cdn-icons-png.flaticon.com/512/1126/1126741.png" alt="Temperature & Pressure Icon">
<h2>BMP180</h2>
<div class="data">
<span id="temperature">Temperature: -- °C</span>
<span id="pressure">Pressure: -- hPa</span>
</div>
</div>
<div class="sensor-card">
<img src="https://cdn-icons-png.flaticon.com/512/728/728093.png" alt="Humidity Icon">
<h2>HTU21D</h2>
<div class="data">
<span id="humidity">Humidity: -- %</span>
<span id="tempHTU">Temperature: -- °C</span>
</div>
</div>
</div>
<script src="index.js"></script>
</body>
</html>
Explanation
<!DOCTYPE html>
<html lang="en">
- Declares this document as HTML5.
- lang="en" sets the document language to English, which is useful for accessibility and SEO.
HEAD
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Sensor Dashboard</title>
<link rel="stylesheet" href="style.css" />
</head>
- charset="UTF-8" ensures proper text encoding.
- viewport makes the layout responsive on mobile devices.
- title is what appears in the browser tab.
- link imports the external CSS file (style.css) for styling.
BODY
<body>
<div class="background"></div>
<div class="container">
...
</div>
<script src="index.js"></script>
</body>
- The background div might be used for a visual effect like a background image or color gradient (defined in your CSS).
- container holds the main content (dashboard).
- index.js is loaded at the end to ensure the DOM is ready before scripts run.
Dashboard Title
<h1>Sensor Dashboard</h1>
- This is the main heading of your web page.
Sensor Card: BMP180
<div class="sensor-card">
<img src="https://cdn-icons-png.flaticon.com/512/1126/1126741.png" alt="Temperature & Pressure Icon">
<h2>BMP180</h2>
<div class="data">
<span id="temperature">Temperature: -- °C</span>
<span id="pressure">Pressure: -- hPa</span>
</div>
</div>
- This block represents the BMP180 sensor.
- The icon (from flaticon.com) visually represents pressure/temperature.
- The id attributes (temperature, pressure) are hooks used by JavaScript to insert real values from BMP180.json.
Sensor Card: HTU21D
<div class="sensor-card">
<img src="https://cdn-icons-png.flaticon.com/512/728/728093.png" alt="Humidity Icon">
<h2>HTU21D</h2>
<div class="data">
<span id="humidity">Humidity: -- %</span>
<span id="tempHTU">Temperature: -- °C</span>
</div>
</div>
- This block represents the HTU21D sensor.
- The image is a humidity icon.
- The id attributes (humidity, tempHTU) are also updated by JavaScript using HTU21D.json.
JavaScript Integration
<script src="index.js"></script>
- This line loads your script, which is responsible for:
- Fetching the JSON files.
- Parsing their content.
- Replacing the placeholder values (-- °C, -- hPa, etc.) with live sensor data.
Index.js
The script fetches data from two local JSON files:
- BMP180.json — contains temperature and pressure.
- HTU21D.json — contains temperature and humidity.
Then it updates the HTML every 3 seconds so the page always shows live sensor values.
Full code
function updateBMP180() {
fetch('BMP180.json')
.then(res => res.json())
.then(data => {
document.getElementById('temperature').textContent = `Temperature: ${data.temperature.toFixed(2)} °C`;
document.getElementById('pressure').textContent = `Pressure: ${data.pressure.toFixed(2)} hPa`;
})
.catch(err => console.error("Error BMP180:", err));
}
function updateHTU21D() {
fetch('HTU21D.json')
.then(res => res.json())
.then(data => {
document.getElementById('humidity').textContent = `Humidity: ${data.humidity.toFixed(1)} %`;
document.getElementById('tempHTU').textContent = `Temperature: ${data.temperature.toFixed(1)} °C`;
})
.catch(err => console.error("Error HTU21D:", err));
}
setInterval(() => {
updateBMP180();
updateHTU21D();
}, 3000);
// Carga inicial
updateBMP180();
updateHTU21D();
Code explanation
Function: updateBMP180
function updateBMP180() {
fetch('BMP180.json')
.then(res => res.json())
.then(data => {
document.getElementById('temperature').textContent = `Temperature: ${data.temperature.toFixed(2)} °C`;
document.getElementById('pressure').textContent = `Pressure: ${data.pressure.toFixed(2)} hPa`;
})
.catch(err => console.error("Error BMP180:", err));
}
- This defines a new function named updateBMP180. You’ll call this function when you want to update the BMP180 sensor data on the page.
- fetch('BMP180.json'): Loads the JSON file from the same directory.
- .then(res =>json()): Parses the response as JSON
- .then(data +> {}): Accesses the data inside the file.
- document.getElementById(...) updates the corresponding elements in your HTML.
Function: updateHTU21D
function updateHTU21D() {
fetch('HTU21D.json')
.then(res => res.json())
.then(data => {
document.getElementById('humidity').textContent = `Humidity: ${data.humidity.toFixed(1)} %`;
document.getElementById('tempHTU').textContent = `Temperature: ${data.temperature.toFixed(1)} °C`;
})
.catch(err => console.error("Error HTU21D:", err));
}
Works the same way as updateBMP180(), but:
- Fetches from HTU21D.json.
- Displays humidity and temperature.
- Uses .toFixed(1) for 1 decimal place (common for humidity values).
Auto-update every 3 seconds
setInterval(() => {
updateBMP180();
updateHTU21D();
}, 3000);
- Calls both update functions every 3,000 milliseconds (3 seconds).
- Keeps the UI in sync with new sensor readings, assuming the .json files are being updated continuously.
Initial load
updateBMP180();
updateHTU21D();
- Ensures the data is shown immediately on page load, before the 3-second interval kicks in.
CSS
Full code
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: "Segoe UI", sans-serif;
}
body, html {
height: 100%;
background-color: #0d1117;
color: #ffffff;
position: relative;
overflow: hidden;
}
.background {
background-image: url('https://wallpapers.com/images/hd/blue-circuit-board-traces-zn0xezd4t8axj9r6.webp');
background-size: cover;
background-position: center;
opacity: 0.1;
filter: blur(3px);
position: absolute;
top: 0; left: 0;
width: 100%;
height: 100%;
z-index: -1;
}
.container {
max-width: 800px;
margin: 40px auto;
background-color: #161b22;
padding: 30px;
border-radius: 20px;
box-shadow: 0 0 30px rgba(0, 255, 255, 0.2);
animation: fadeIn 1s ease-in;
}
h1 {
text-align: center;
margin-bottom: 30px;
font-size: 2.5rem;
color: #58a6ff;
}
.sensor-card {
background-color: #1f2937;
padding: 20px;
border-radius: 15px;
margin-bottom: 20px;
box-shadow: 0 0 20px rgba(100, 255, 255, 0.1);
animation: slideIn 1s ease;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
}
.sensor-card img {
width: 64px;
height: 64px;
margin-bottom: 10px;
}
.sensor-card h2 {
margin-bottom: 10px;
color: #90cdf4;
}
.data span {
display: block;
margin: 5px 0;
font-size: 1.2rem;
color: #ffffff;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-20px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes slideIn {
from { opacity: 0; transform: scale(0.9); }
to { opacity: 1; transform: scale(1); }
}
Explaining
Global Styling
- *: Resets default browser spacing (margin, padding) and sets a consistent font and box model across the entire page.
- body, html:
- Sets full height layout.
- Applies a dark background (#0d1117) with white text.
- Hides overflow and enables positioning for internal layers.
⸻
Background Layer
- .background:
- Adds a faint, blurred circuit board image as the background.
- Uses opacity: 0.1 and filter: blur(3px) to give it a soft tech feel.
- Positioned absolutely behind everything (z-index: -1).
⸻
Main Container
- .container:
- A centered, card-like section with:
- A dark background (#161b22)
- Rounded corners and soft glowing shadow.
- Padding and a fade-in animation on load.
⸻
Title
- h1:
- Large, centered header in light blue (#58a6ff).
- Styled to stand out at the top of the dashboard.
⸻
Sensor Cards
- .sensor-card:
- Styled boxes for each sensor.
- Darker background (#1f2937) with a soft shadow.
- Rounded corners, padding, and a slide-in animation when they load.
- Contents are centered vertically and horizontally.
- .sensor-card img:
- Sensor icons sized to 64×64 pixels with margin for spacing.
- .sensor-card h2:
- Sub-headers for each sensor card in a soft blue (#90cdf4).
⸻
Data Text
- .data span:
- Each line of sensor data (e.g., temperature, humidity).
- Displayed as blocks with spacing and larger font for visibility.
⸻
Animations
- @keyframes fadeIn:
- Smooth slide-down + fade-in for the container.
- @keyframes slideIn:
- Subtle zoom-in effect for each sensor card.
Sensor library
HTU21D library
The library contains 3 files, HTU21D.c, htu21d.h and main.c
htu21d.h
#ifndef HTU21D_H
#define HTU21D_H
// HTU21D i2c address
#define HTU21D_ADDR 0x40
//commands for readings
#define HTU21D_TEMP 0xE3
#define HTU21D_HUM 0xE5
#define HTU21D_RESET 0xFE
//funtion declarations
//Temp:
int getTemp(int fd, double *temp);
//HUM
int getHum(int fd, double *hum);
//RESET
int getReset(int fd);
#endif
Explanation
- HTU21D_ADDR: The I2C address of the sensor (0x40).
- HTU21D_TEMP: Command to read temperature (0xE3).
- HTU21D_HUM: Command to read humidity (0xE5).
- HTU21D_RESET: Command to reset the sensor (0xFE).
- Declares three functions:
- getTemp() – for reading temperature,
- getHum() – for reading humidity,
- getReset() – for resetting the sensor.
HTU21D.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//Aditional librarys
#include <errno.h>
#include <fcntl.h>
#include "htu21d.h"
#define I2C_PATH "/dev/i2c-%d"
#define I2C_PORT 2
int main(){
char filePath[20];
snprintf(filePath, sizeof(filePath), I2C_PATH, I2C_PORT );
int fd = open(filePath, O_RDWR);
if(fd<0){
fprintf(stderr, "Error: Unable to access HTU21D sensor: %s",strerror(errno));
exit(-1);
}
//measurements
double temperature=0;
double humidity=0;
if((getTemp(fd, &temperature)<0)||(getHum(fd, &humidity)<0)){
fprintf(stderr,"Error -404: Measurments not read");
exit(-1);
}
//printf("HTU21D Module \n");
//printf("%5.2fC \n", temperature);
//printf("%5.2fC \n", humidity);
printf("{");
printf("\"temperature\": %5.2f,", temperature);
printf("\"humidity\": %5.2f", humidity);
printf("}");
return 0;
}
Explanation
Librarys
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>
#include <i2c/smbus.h>
#include <stdio.h>
#include "htu21d.h"
These headers provide:
- unistd.h: basic system calls (like read, write, etc.)
- sys/ioctl.h: lets you control devices (e.g., set I2C slave address)
- linux/i2c-dev.h: allows interaction with the I2C bus
- i2c/smbus.h: provides high-level SMBus/I2C functions like i2c_smbus_read_i2c_block_data
- stdio.h: for printing error messages
- "htu21d.h": includes your own header file (constants and function declarations)
getTemp() Function
int getTemp(int fd, double *temp) {
getReset(fd); // Resets the sensor before reading
char buffer[3]; // Buffer to hold 3 bytes of data from sensor
__s32 res = i2c_smbus_read_i2c_block_data(fd, HTU21D_TEMP, 3, buffer);
- Calls getReset(fd) to ensure the sensor is in a clean state before reading.
- Uses i2c_smbus_read_i2c_block_data to read 3 bytes from the sensor using the temperature command 0xE3.
- These 3 bytes contain raw temperature data + CRC (which you ignore here).
if (res < 0) {
perror("ERROR-1: Failed to read Temperature");
return -1;
}
- If the read fails (e.g., I2C communication issue), it prints an error and returns -1.
*temp = -46.85 + 175.72 * (buffer[0] * 256 + buffer[1]) / 65536.0;
return 0;
}
- The first two bytes in buffer are combined to make a 16-bit raw temperature.
- The formula from the HTU21D datasheet is applied to convert raw data into actual temperature in Celsius.
- The result is stored in the variable pointed to by *temp.
- Returns 0 to indicate success.
getHum() Function
This function is nearly identical to getTemp(), but it reads humidity instead.
int getHum(int fd, double *hum) {
getReset(fd);
char buffer[3];
__s32 res = i2c_smbus_read_i2c_block_data(fd, HTU21D_HUM, 3, buffer);
- Uses the command HTU21D_HUM (value 0xE5) to read humidity data.
if (res < 0) {
perror("ERROR -3: Failed to read Humidity");
return -1;
}
*hum = -6 + 125 * (buffer[0] * 256 + buffer[1]) / 65536.0;
return 0;
}
- Applies the formula from the datasheet to convert the raw bytes to % humidity.
getReset() Function
int getReset(int fd) {
if (0 > ioctl(fd, I2C_SLAVE, HTU21D_ADDR)) {
perror("ERROR -2: Failed in reset");
return -2;
}
i2c_smbus_write_byte(fd, HTU21D_RESET);
return 0;
}
- ioctl() tells the I2C driver which slave device you want to talk to (address 0x40).
- Then i2c_smbus_write_byte() sends the reset command 0xFE to the HTU21D.
- Resets the sensor, which is sometimes necessary to avoid bad reads or stuck states.
BMP180 library
The library contains 3 files, bmp180.c, bmp180.h and main.c
bmp180.c
#include "bmp180.h"
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>
#include <stdio.h>
#define BMP180_ADDR 0x77
// Lectura de 16 bits de un registro (dos bytes)
static int read16(int fd, uint8_t reg, int16_t *value) {
uint8_t buf[2];
if (write(fd, ®, 1) != 1) return -1;
if (read(fd, buf, 2) != 2) return -1;
*value = (buf[0] << 8) | buf[1];
return 0;
}
// Escritura de 8 bits a un registro
static int write8(int fd, uint8_t reg, uint8_t value) {
uint8_t buf[2] = {reg, value};
if (write(fd, buf, 2) != 2) return -1;
return 0;
}
// Leer calibración desde el sensor
int bmp180_init(int fd, bmp180_calib_data_t *calib) {
if (read16(fd, 0xAA, &calib->AC1) < 0) return -1;
if (read16(fd, 0xAC, &calib->AC2) < 0) return -1;
if (read16(fd, 0xAE, &calib->AC3) < 0) return -1;
if (read16(fd, 0xB0, (int16_t*)&calib->AC4) < 0) return -1;
if (read16(fd, 0xB2, (int16_t*)&calib->AC5) < 0) return -1;
if (read16(fd, 0xB4, (int16_t*)&calib->AC6) < 0) return -1;
if (read16(fd, 0xB6, &calib->B1) < 0) return -1;
if (read16(fd, 0xB8, &calib->B2) < 0) return -1;
if (read16(fd, 0xBA, &calib->MB) < 0) return -1;
if (read16(fd, 0xBC, &calib->MC) < 0) return -1;
if (read16(fd, 0xBE, &calib->MD) < 0) return -1;
return 0;
}
// Leer temperatura sin procesar (raw temp)
static int bmp180_read_raw_temperature(int fd, int32_t *raw_temp) {
if (write8(fd, 0xF4, 0x2E) < 0) return -1; // start temp measurement
usleep(4500); // esperar 4.5 ms
int16_t value;
if (read16(fd, 0xF6, &value) < 0) return -1;
*raw_temp = value;
return 0;
}
// Leer presión sin procesar (raw pressure)
static int bmp180_read_raw_pressure(int fd, int32_t *raw_press, int oss) {
if (write8(fd, 0xF4, 0x34 + (oss << 6)) < 0) return -1; // start pressure measurement
usleep(25000); // esperar 25 ms para oss=0 (simple oversampling)
uint8_t buf[3];
uint8_t reg = 0xF6;
if (write(fd, ®, 1) != 1) return -1;
if (read(fd, buf, 3) != 3) return -1;
*raw_press = ((buf[0] << 16) | (buf[1] << 8) | buf[2]) >> (8 - oss);
return 0;
}
int bmp180_read_temperature(int fd, bmp180_calib_data_t *calib, double *temperature) {
int32_t UT;
if (bmp180_read_raw_temperature(fd, &UT) < 0) return -1;
int32_t X1 = ((UT - calib->AC6) * calib->AC5) >> 15;
int32_t X2 = (calib->MC << 11) / (X1 + calib->MD);
int32_t B5 = X1 + X2;
*temperature = ((B5 + 8) >> 4) / 10.0;
return 0;
}
int bmp180_read_pressure(int fd, bmp180_calib_data_t *calib, double *pressure) {
int oss = 0; // oversampling setting 0..3
int32_t UP;
if (bmp180_read_raw_pressure(fd, &UP, oss) < 0) return -1;
// Recalcular B5 para temperatura, necesario para presión
int32_t UT;
if (bmp180_read_raw_temperature(fd, &UT) < 0) return -1;
int32_t X1 = ((UT - calib->AC6) * calib->AC5) >> 15;
int32_t X2 = (calib->MC << 11) / (X1 + calib->MD);
int32_t B5 = X1 + X2;
int32_t B6 = B5 - 4000;
X1 = (calib->B2 * ((B6 * B6) >> 12)) >> 11;
X2 = (calib->AC2 * B6) >> 11;
int32_t X3 = X1 + X2;
int32_t B3 = (((calib->AC1 * 4 + X3) << oss) + 2) >> 2;
X1 = (calib->AC3 * B6) >> 13;
X2 = (calib->B1 * ((B6 * B6) >> 12)) >> 16;
X3 = ((X1 + X2) + 2) >> 2;
uint32_t B4 = (calib->AC4 * (uint32_t)(X3 + 32768)) >> 15;
uint32_t B7 = ((uint32_t)UP - B3) * (50000 >> oss);
int32_t p;
if (B7 < 0x80000000) {
p = (B7 << 1) / B4;
} else {
p = (B7 / B4) << 1;
}
X1 = (p >> 8) * (p >> 8);
X1 = (X1 * 3038) >> 16;
X2 = (-7357 * p) >> 16;
p = p + ((X1 + X2 + 3791) >> 4);
*pressure = p / 100.0; // Pa a hPa (mbar)
return 0;
}
explanation
Headers and defines
#include "bmp180.h"
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>
#include <stdio.h>
#define BMP180_ADDR 0x77
- Includes standard and I2C Linux headers.
- Defines the I2C address of the BMP180 sensor.
read16() – Read 16-bit (2-byte) value from a register
static int read16(int fd, uint8_t reg, int16_t *value)
- Sends the register address via write().
- Reads 2 bytes and stores the result as a big-endian 16-bit signed integer.
- Used to read calibration values and sensor data.
write8() – Write 8-bit value to a register
static int write8(int fd, uint8_t reg, uint8_t value)
- Sends 2 bytes: register address and value.
- Used to trigger temperature or pressure measurements by writing command codes to control register 0xF4
bmp180_init() – Read all calibration data from sensor
int bmp180_init(int fd, bmp180_calib_data_t *calib)
- Reads 11 calibration values from registers 0xAA to 0xBF.
- These are factory-set and unique to each sensor.
- Needed to apply temperature and pressure compensation formulas correctly.
bmp180_read_raw_temperature() – Trigger and read unprocessed temperature
static int bmp180_read_raw_temperature(int fd, int32_t *raw_temp)
- Writes 0x2E to control register 0xF4 to start temperature conversion.
- Waits 4.5 ms (conversion time).
- Reads result from 0xF6 and 0xF7.
bmp180_read_raw_pressure() – Trigger and read unprocessed pressure
static int bmp180_read_raw_pressure(int fd, int32_t *raw_press, int oss)
- Writes 0x34 + (oss << 6) to register 0xF4 to start pressure conversion.
- Waits 25 ms for conversion (OSS=0).
- Reads 3 bytes from 0xF6, 0xF7, 0xF8 and shifts them to get a 19-bit value.
- oss (oversampling setting) affects resolution and delay.
bmp180_read_temperature() – Convert raw temperature to °C
int bmp180_read_temperature(int fd, bmp180_calib_data_t *calib, double *temperature)
- Uses raw temperature and calibration data.
- Follows Bosch’s datasheet compensation formula: X1 = ((UT - AC6) * AC5) >> 15 X2 = (MC << 11) / (X1 + MD) B5 = X1 + X2 T = (B5 + 8) >> 4 → gives temperature in 0.1°C
- Final result is divided by 10.0 to get °C.
bmp180_read_pressure() – Convert raw pressure to hPa
int bmp180_read_pressure(int fd, bmp180_calib_data_t *calib, double *pressure)
- Uses raw pressure and temperature (needed for B5).
- Applies long compensation formula from datasheet:
- Many intermediate steps using calibration values.
- Computes B3, B4, B6, B7, X1, X2, X3, and finally p.
- Final pressure p is in Pa; divide by 100.0 to get hPa (mbar).
bmp180.h
#ifndef BMP180_H
#define BMP180_H
#include <stdint.h>
typedef struct {
int16_t AC1, AC2, AC3;
uint16_t AC4, AC5, AC6;
int16_t B1, B2;
int16_t MB, MC, MD;
} bmp180_calib_data_t;
int bmp180_init(int fd, bmp180_calib_data_t *calib);
int bmp180_read_temperature(int fd, bmp180_calib_data_t *calib, double *temperature);
int bmp180_read_pressure(int fd, bmp180_calib_data_t *calib, double *pressure);
#endif
Explanation
Header
#ifndef BMP180_H
#define BMP180_H
#include <stdint.h>
- Header guard to prevent multiple inclusion.
- Includes standard integer types (uint8_t, int16_t, etc.).
Structure: bmp180_calib_data_t
typedef struct {
int16_t AC1, AC2, AC3;
uint16_t AC4, AC5, AC6;
int16_t B1, B2;
int16_t MB, MC, MD;
} bmp180_calib_data_t;
- Holds all calibration constants (11 total).
- Types match datasheet: some are signed, others unsigned.
Function declarations
int bmp180_init(int fd, bmp180_calib_data_t *calib);
int bmp180_read_temperature(int fd, bmp180_calib_data_t *calib, double *temperature);
int bmp180_read_pressure(int fd, bmp180_calib_data_t *calib, double *pressure);
main.c
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/i2c-dev.h>
#include <sys/ioctl.h>
#include "bmp180.h"
#define I2C_BUS "/dev/i2c-2"
#define BMP180_ADDR 0x77
int main() {
int fd = open(I2C_BUS, O_RDWR);
if (fd < 0) {
perror("Error abriendo el bus I2C");
return 1;
}
if (ioctl(fd, I2C_SLAVE, BMP180_ADDR) < 0) {
perror("Error configurando la dirección I2C");
close(fd);
return 1;
}
bmp180_calib_data_t calib;
if (bmp180_init(fd, &calib) < 0) {
fprintf(stderr, "Error leyendo datos de calibración BMP180\n");
close(fd);
return 1;
}
double temperature, pressure;
if (bmp180_read_temperature(fd, &calib, &temperature) < 0) {
fprintf(stderr, "Error leyendo temperatura BMP180\n");
close(fd);
return 1;
}
if (bmp180_read_pressure(fd, &calib, &pressure) < 0) {
fprintf(stderr, "Error leyendo presión BMP180\n");
close(fd);
return 1;
}
printf("{\"temperature\": %.2f, \"pressure\": %.2f}\n", temperature, pressure);
close(fd);
return 0;
}
Explanation
Open I2C Bus
int fd = open("/dev/i2c-2", O_RDWR);
- Opens I2C device for read/write.
Set I2C Slave Address
ioctl(fd, I2C_SLAVE, BMP180_ADDR);
- Tells the kernel which I2C device address (0x77) to communicate with.
Initialize BMP180
bmp180_init(fd, &calib);
- Reads calibration constants from the sensor.
Read Temperature and Pressure
bmp180_read_temperature(fd, &calib, &temperature);
bmp180_read_pressure(fd, &calib, &pressure);
- Uses previously stored calibration values to read and convert real temperature and pressure values.
Output in JSON Format
printf("{\"temperature\": %.2f, \"pressure\": %.2f}\n", temperature, pressure);
- Very useful becouse we are integrating with a frontend (e.g. web UI or logger).
Cleanup
close(fd);
- Closes the I2C device after finishing communication.
Results
Photo to prove the functionality of the UI:
Screenshot of UI: