호타리 2025. 6. 24. 16:57

2025.06.24

 

BMP180

 

i2c라이브러리를 이용해서 기압 측정

bmp180_i2c.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>
#include <math.h>
#include <string.h>
#include <errno.h>

// BMP180 I2C 주소 및 레지스터
#define BMP180_ADDRESS           0x77
#define BMP180_REG_CAL_AC1       0xAA
#define BMP180_REG_CONTROL       0xF4
#define BMP180_REG_RESULT        0xF6
#define BMP180_CMD_READ_TEMP     0x2E
#define BMP180_CMD_READ_PRESSURE 0x34

// 보정 계수 전역 저장
static short ac1, ac2, ac3, b1, b2, mb, mc, md;
static unsigned short ac4, ac5, ac6;

static int i2c_fd;

// 함수 프로토타입
static void  read_calibration_data(void);
static unsigned int read_ut(void);
static unsigned int read_up(int oss);

// 보정 데이터 읽기
static void read_calibration_data(void) {
    unsigned char buf[22];
    if (read(i2c_fd, buf, 22) != 22) {
        perror("Failed to read calibration data");
        exit(1);
    }
    ac1 = (buf[0] << 8) | buf[1];
    ac2 = (buf[2] << 8) | buf[3];
    ac3 = (buf[4] << 8) | buf[5];
    ac4 = (buf[6] << 8) | buf[7];
    ac5 = (buf[8] << 8) | buf[9];
    ac6 = (buf[10] << 8) | buf[11];
    b1  = (buf[12] << 8) | buf[13];
    b2  = (buf[14] << 8) | buf[15];
    mb  = (buf[16] << 8) | buf[17];
    mc  = (buf[18] << 8) | buf[19];
    md  = (buf[20] << 8) | buf[21];
}

// 원시 온도 읽기
static unsigned int read_ut(void) {
    unsigned char cmd[2] = { BMP180_REG_CONTROL, BMP180_CMD_READ_TEMP };
    if (write(i2c_fd, cmd, 2) != 2) {
        perror("Failed to write temp cmd");
        exit(1);
    }
    usleep(5000);

    unsigned char reg = BMP180_REG_RESULT;
    if (write(i2c_fd, &reg, 1) != 1) {
        perror("Failed to set temp read ptr");
        exit(1);
    }

    unsigned char buf[2];
    if (read(i2c_fd, buf, 2) != 2) {
        perror("Failed to read uncomp temp");
        exit(1);
    }
    return (buf[0] << 8) | buf[1];
}

// 원시 압력 읽기
static unsigned int read_up(int oss) {
    unsigned char cmd[2] = { BMP180_REG_CONTROL,
        (unsigned char)(BMP180_CMD_READ_PRESSURE + (oss << 6)) };
    if (write(i2c_fd, cmd, 2) != 2) {
        perror("Failed to write press cmd");
        exit(1);
    }
    // OSS에 따른 대기
    switch (oss) {
        case 0: usleep(5000);  break;
        case 1: usleep(8000);  break;
        case 2: usleep(14000); break;
        case 3: usleep(26000); break;
    }

    unsigned char reg = BMP180_REG_RESULT;
    if (write(i2c_fd, &reg, 1) != 1) {
        perror("Failed to set press read ptr");
        exit(1);
    }

    unsigned char buf[3];
    if (read(i2c_fd, buf, 3) != 3) {
        perror("Failed to read uncomp press");
        exit(1);
    }
    return (((buf[0] << 16) | (buf[1] << 8) | buf[2]) >> (8 - oss));
}

int main(void) {
    const char *i2c_dev = "/dev/i2c-1";

    // I2C 버스 오픈
    if ((i2c_fd = open(i2c_dev, O_RDWR)) < 0) {
        perror("Open i2c bus");
        return 1;
    }
    if (ioctl(i2c_fd, I2C_SLAVE, BMP180_ADDRESS) < 0) {
        perror("ioctl I2C_SLAVE");
        return 1;
    }

    // 보정 데이터 읽기 위치 설정
    unsigned char start = BMP180_REG_CAL_AC1;
    if (write(i2c_fd, &start, 1) != 1) {
        perror("Set cal data ptr");
        return 1;
    }
    read_calibration_data();

    printf("BMP180 실시간 측정 시작 (Ctrl+C 또는 Ctrl+Z 로 종료)\n\n");

    while (1) {
        // 원시값 읽기
        unsigned int ut = read_ut();
        unsigned int up = read_up(3);  // 최고 해상도 OSS=3

        // 온도 계산
        long x1 = ((long)ut - ac6) * ac5 >> 15;
        long x2 = ((long)mc << 11) / (x1 + md);
        long b5 = x1 + x2;
        double temperature = ((b5 + 8) >> 4) / 10.0;  // °C

        // 압력 계산
        long b6 = b5 - 4000;
        x1 = (b2 * (b6 * b6 >> 12)) >> 11;
        x2 = (ac2 * b6) >> 11;
        long x3 = x1 + x2;
        long b3 = ((((long)ac1 * 4 + x3) << 3) + 2) >> 2;
        x1 = (ac3 * b6) >> 13;
        x2 = (b1 * (b6 * b6 >> 12)) >> 16;
        x3 = ((x1 + x2) + 2) >> 2;
        unsigned long b4 = (ac4 * (unsigned long)(x3 + 32768)) >> 15;
        unsigned long b7 = ((unsigned long)up - b3) * (50000 >> 3);
        long p;
        if (b7 < 0x80000000)
            p = (b7 * 2) / b4;
        else
            p = (b7 / b4) * 2;
        x1 = (p >> 8) * (p >> 8);
        x1 = (x1 * 3038) >> 16;
        x2 = (-7357 * p) >> 16;
        p = p + ((x1 + x2 + 3791) >> 4);

        double pressure_hpa = p / 100.0;

        printf("온도: %.2f °C    압력: %.2f hPa\n",
               temperature, pressure_hpa);

        sleep(1);
    }

    close(i2c_fd);
    return 0;
}

 

디바이스 드라이버 개발

bmp180.c

// SPDX-License-Identifier: GPL-2.0
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/i2c.h>
#include <linux/delay.h>
#include <linux/mutex.h>
#include <linux/sysfs.h>
#include <linux/of.h>
#include <linux/of_device.h>

#define BMP180_REG_CALIB   0xAA
#define BMP180_REG_CTRL    0xF4
#define BMP180_REG_DATA    0xF6
#define BMP180_CMD_TEMP    0x2E
#define BMP180_CMD_PRESS   0x34

struct bmp180_data {
    struct i2c_client *client;
    struct mutex      lock;
    /* Calibration coefficients */
    s16  ac1, ac2, ac3, b1, b2, mb, mc, md;
    u16  ac4, ac5, ac6;
    int  oss;
};

static int bmp180_read_calib(struct bmp180_data *d)
{
    u8 buf[22];
    int ret;

    ret = i2c_smbus_read_i2c_block_data(d->client,
                                        BMP180_REG_CALIB,
                                        sizeof(buf), buf);
    if (ret < 0)
        return ret;

    d->ac1 = (buf[0] << 8) | buf[1];
    d->ac2 = (buf[2] << 8) | buf[3];
    d->ac3 = (buf[4] << 8) | buf[5];
    d->ac4 = (buf[6] << 8) | buf[7];
    d->ac5 = (buf[8] << 8) | buf[9];
    d->ac6 = (buf[10] << 8) | buf[11];
    d->b1  = (buf[12] << 8) | buf[13];
    d->b2  = (buf[14] << 8) | buf[15];
    d->mb  = (buf[16] << 8) | buf[17];
    d->mc  = (buf[18] << 8) | buf[19];
    d->md  = (buf[20] << 8) | buf[21];
    return 0;
}

static int bmp180_read_raw_temp(struct bmp180_data *d, int *ut)
{
    int ret;
    u8 msb, lsb;

    /* Send temperature measurement command */
    ret = i2c_smbus_write_byte_data(d->client,
                                    BMP180_REG_CTRL,
                                    BMP180_CMD_TEMP);
    if (ret < 0)
        return ret;
    msleep(5);

    /* Read MSB and LSB directly from DATA registers */
    msb = i2c_smbus_read_byte_data(d->client, BMP180_REG_DATA);
    lsb = i2c_smbus_read_byte_data(d->client, BMP180_REG_DATA + 1);
    if ((int)msb < 0 || (int)lsb < 0)
        return -EIO;

    *ut = (msb << 8) | lsb;
    return 0;
}

static int bmp180_read_raw_press(struct bmp180_data *d, int *up)
{
    int ret;
    u8 msb, lsb, xlsb;

    ret = i2c_smbus_write_byte_data(d->client,
                                    BMP180_REG_CTRL,
                                    BMP180_CMD_PRESS + (d->oss << 6));
    if (ret < 0)
        return ret;
    msleep(5 + (3 << d->oss));

    msb  = i2c_smbus_read_byte_data(d->client, BMP180_REG_DATA);
    lsb  = i2c_smbus_read_byte_data(d->client, BMP180_REG_DATA + 1);
    xlsb = i2c_smbus_read_byte_data(d->client, BMP180_REG_DATA + 2);
    if ((int)msb < 0 || (int)lsb < 0 || (int)xlsb < 0)
        return -EIO;

    *up = ((msb << 16) | (lsb << 8) | xlsb) >> (8 - d->oss);
    return 0;
}

static long bmp180_calc_temp(struct bmp180_data *d, int ut, int *t)
{
    long x1 = ((long)ut - d->ac6) * d->ac5 >> 15;
    long x2 = ((long)d->mc << 11) / (x1 + d->md);
    long b5 = x1 + x2;

    *t = (b5 + 8) >> 4;  /* 0.1°C unit */
    return b5;
}

static int bmp180_calc_press(struct bmp180_data *d, int up,
                             long b5, int *p)
{
    long b6 = b5 - 4000;
    long x1 = (d->b2 * (b6 * b6 >> 12)) >> 11;
    long x2 = (d->ac2 * b6) >> 11;
    long x3 = x1 + x2;
    long b3 = ((((long)d->ac1 * 4 + x3) << d->oss) + 2) >> 2;
    x1 = (d->ac3 * b6) >> 13;
    x2 = (d->b1 * (b6 * b6 >> 12)) >> 16;
    x3 = ((x1 + x2) + 2) >> 2;
    long b4 = (d->ac4 * (unsigned long)(x3 + 32768)) >> 15;
    long b7 = ((unsigned long)up - b3) * (50000 >> d->oss);
    long p_raw = (b7 < 0x80000000 ?
                  (b7 << 1) / b4 :
                  (b7 / b4) << 1);
    x1 = (p_raw >> 8) * (p_raw >> 8);
    x1 = (x1 * 3038) >> 16;
    x2 = (-7357 * p_raw) >> 16;
    *p = p_raw + ((x1 + x2 + 3791) >> 4);

    return 0;
}

/* sysfs show functions */
static ssize_t show_temp(struct device *dev,
                         struct device_attribute *attr,
                         char *buf)
{
    struct bmp180_data *d = dev_get_drvdata(dev);
    int ut, t, ret;
    long b5;

    mutex_lock(&d->lock);
    ret = bmp180_read_raw_temp(d, &ut);
    if (ret < 0)
        goto out;
    b5 = bmp180_calc_temp(d, ut, &t);
out:
    mutex_unlock(&d->lock);

    if (ret < 0)
        return ret;
    return sprintf(buf, "%d\n", t);
}
static DEVICE_ATTR(temp, 0444, show_temp, NULL);

static ssize_t show_press(struct device *dev,
                          struct device_attribute *attr,
                          char *buf)
{
    struct bmp180_data *d = dev_get_drvdata(dev);
    int ut, up, p, ret;
    long b5;

    mutex_lock(&d->lock);
    ret = bmp180_read_raw_temp(d, &ut);
    if (ret < 0)
        goto out;
    b5 = bmp180_calc_temp(d, ut, &p);
    ret = bmp180_read_raw_press(d, &up);
    if (ret < 0)
        goto out;
    ret = bmp180_calc_press(d, up, b5, &p);
out:
    mutex_unlock(&d->lock);

    if (ret < 0)
        return ret;
    return sprintf(buf, "%d\n", p);
}
static DEVICE_ATTR(pressure, 0444, show_press, NULL);

static struct attribute *bmp180_attrs[] = {
    &dev_attr_temp.attr,
    &dev_attr_pressure.attr,
    NULL,
};
static const struct attribute_group bmp180_group = {
    .attrs = bmp180_attrs,
};

static const struct of_device_id bmp180_of_match[] = {
    { .compatible = "bosch,bmp180" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, bmp180_of_match);

static const struct i2c_device_id bmp180_id[] = {
    { "bmp180", 0 },
    { }
};
MODULE_DEVICE_TABLE(i2c, bmp180_id);

static int bmp180_probe(struct i2c_client *client)
{
    struct bmp180_data *d;
    int ret;

    d = devm_kzalloc(&client->dev,
                     sizeof(*d), GFP_KERNEL);
    if (!d)
        return -ENOMEM;

    mutex_init(&d->lock);
    d->client = client;
    d->oss    = 0;
    i2c_set_clientdata(client, d);

    ret = bmp180_read_calib(d);
    if (ret < 0) {
        dev_err(&client->dev,
                "calibration read failed: %d\n", ret);
        return ret;
    }

    ret = sysfs_create_group(&client->dev.kobj,
                             &bmp180_group);
    if (ret)
        dev_err(&client->dev,
                "sysfs group create failed: %d\n", ret);

    dev_info(&client->dev, "BMP180 sensor probed\n");
    return ret;
}

static void bmp180_remove(struct i2c_client *client)
{
    sysfs_remove_group(&client->dev.kobj,
                       &bmp180_group);
}

static struct i2c_driver bmp180_driver = {
    .driver = {
        .name           = "bmp180",
        .of_match_table = of_match_ptr(bmp180_of_match),
    },
    .probe    = bmp180_probe,
    .remove   = bmp180_remove,
    .id_table = bmp180_id,
};
module_i2c_driver(bmp180_driver);

MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("BMP180 pressure/temperature sensor driver");
MODULE_LICENSE("GPL v2");

 

응용프로그램 개발

bmp180_read.c

// bmp180_read.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>

#define I2C_DEV_DIR   "/sys/bus/i2c/devices"
#define TEMP_FILE     "temp"
#define PRESS_FILE    "pressure"

// 디렉토리 안에 temp와 pressure 파일이 모두 있는 첫 번째 디렉토리 검색
static int find_bmp180_dir(char *out, size_t len) {
    DIR *d = opendir(I2C_DEV_DIR);
    struct dirent *ent;
    if (!d) return -1;

    while ((ent = readdir(d)) != NULL) {
        // 디렉토리 이름이 "버스-주소" 형태인지 간단히 체크
        if (strchr(ent->d_name, '-') == NULL) 
            continue;

        char path_temp[256], path_press[256];
        snprintf(path_temp,  sizeof(path_temp),
                 I2C_DEV_DIR "/%s/" TEMP_FILE, ent->d_name);
        snprintf(path_press, sizeof(path_press),
                 I2C_DEV_DIR "/%s/" PRESS_FILE, ent->d_name);

        if (access(path_temp, R_OK) == 0 && access(path_press, R_OK) == 0) {
            snprintf(out, len, I2C_DEV_DIR "/%s", ent->d_name);
            closedir(d);
            return 0;
        }
    }

    closedir(d);
    return -1;
}

// sysfs에서 정수 읽기
static int read_int(const char *path, int *out) {
    char buf[32];
    int fd = open(path, O_RDONLY);
    if (fd < 0) return -1;
    ssize_t n = read(fd, buf, sizeof(buf)-1);
    close(fd);
    if (n <= 0) return -1;
    buf[n] = '\0';
    *out = atoi(buf);
    return 0;
}

int main(void) {
    char devdir[128];
    if (find_bmp180_dir(devdir, sizeof(devdir)) < 0) {
        fprintf(stderr, "ERROR: bmp180 모듈 디렉토리를 찾을 수 없습니다.\n");
        return 1;
    }

    char temp_path[256], press_path[256];
    snprintf(temp_path,  sizeof(temp_path),
             "%s/" TEMP_FILE,  devdir);
    snprintf(press_path, sizeof(press_path),
             "%s/" PRESS_FILE, devdir);

    printf("BMP180 기압·온도 측정 (Ctrl+C로 종료)\n\n");
    while (1) {
        int t_raw, p_raw;
        if (read_int(temp_path,  &t_raw) < 0 ||
            read_int(press_path, &p_raw) < 0) {
            fprintf(stderr,
                "센서 값 읽기 실패: %s\n",
                strerror(errno));
            return 1;
        }

        // 모듈에서 temp는 0.1°C 단위로, pressure는 Pa 단위로 리턴됩니다.
        double temp_c = t_raw / 10.0;
        double press_hpa = p_raw / 100.0;

        printf("온도: %.1f °C    압력: %.2f hPa\n",
               temp_c, press_hpa);
        sleep(1);
    }
    return 0;
}

 

댓글수0