Датчик облачности для обсерватории

43f535375a474560820ea23de727ac80.png

На мой взгляд одной из важнейших сопутствующих задач наземной наблюдательной астрономии является контроль астроклимата.
Астроклимат — это совокупность факторов атмосферы, влияющих на качество астрономических наблюдений, путем искажения излучения небесных объектов.
(внимание, под катом достаточно много изображений!)
В число этих факторов входит, например, показатель преломления воздуха, зависящий от его температуры. Изменение температуры воздуха на 1 градус цельсия настолько изменяет его показатель преломления, что уже сказывается на качестве изображения. В связи с этим телескопы стараются располагать выше температурных неодноростей — на горных пиках. Высоту самих башен так же выбирают такой, что бы телескоп был расположен над локальными неоднородностями.
Так же важным фактором является ветер, который может вызывать перемешивания мелких неоднородностей в воздухе, приводя тем самым к расфокусу изображения.
И наверное одним из наиболее важных факторов является количество ясных дней в году.
Затянутое облаками небо полностью блокирует работу телескопа и может является вестником
осадков, что опасно для оборудования.
Кроме природных факторов существуют и антропогенные. Это засветка от городов, выбросы в атмосферу, локальный нагрев воздуха.

Перед строительством обсерватории специальные исследовательские астроклиматические группы измеряют и анализируют все эти факторы на протяжении определенного периода времени, после чего выносят свой вердикт и разрешают установку телескопов в этом месте или же приступают к поиску другого места.

Но астроклимат вещь непостоянная и к сожалению порой может очень сильно изменяться, нередко благодаря человеку. Поэтому необходим постоянный мониторинг.
Я же предлагаю поговорить об одном из наиболее часто меняющемся факторе — облачности.
Особенно остро вопрос контроля облачности стоит в случае удаленно управляемой обсерватории, когда невозможно просто выйти на улицу и посмотреть что там на небе.
Некоторую помощь тут может оказать камера обзора неба — чувствительная широкоугольная камера, направленная в зенит.

89a187ce3dcd4a53a0815cc0bc50a317.png

Но хорошая камера с объективом — решение не самое бюджетное, накладывающее дополнительные требования к сопутствующему оборудованию и каналу связи. Кроме того, в случае автоматизированной обсерватории анализ кадров с целью выявления облаков так же не является самой тривиальной задачей.
В качестве единственного устройства контроля облаков, помимо камеры, в обсерваториях по всему миру с успехом применяют датчики, которые позволяют легко получить достаточно точную численную оценку ясности неба в ночное время. Кроме того, данные с датчиков очень просто накапливать и впоследствии проводить исторический анализ астроклимата.

Немного теории
Днем солнечное излучение согревает поверхность Земли и все, что на ней находится — здания, дороги, воду и т.д. Вся накопленная энергия впоследствии переизлучается в виде того же тепла (инфракрасного излучения).
Если бы у земли не было бы атмосферы, то вся накопленная энергия без каких-либо препятствий излучалась бы в космос. Но к счастью у нашей планеты есть атмосфера :)
В состав атмосферы входят разнообразные газы, аэрозоли, пылевые частицы и водяной пар. Испускаемое Землей инфракрасное излучение активно поглощается водяным паром, разогревая саму атмосферу (это позволяет поддерживать нашу планету достаточно теплой для существования жизни). Облака, как известно, состоят из водяного пара. Соответственно, чем больше этого пара в атмосфере (больше облаков) — тем выше температура. И наоборот, чем более ясное и чистое небо — тем температура ниже. Как и температура любого другого тела — температура атмосферы (неба) может быть измерена. Говоря о температуре имеют ввиду температуру воздушного столба (точнее конуса, угол раствора которого равен углу «зрения» конкретного датчика). Высота этого столба примерно 10–15 км, т.е. до тропосферы — атмосферного слоя, где «делается» погода.
Собственно под температурой неба всегда понимают то, насколько эта измеряемая конусообразная область теплее окружающего космического пространства (температура которого близка к абсолютному нулю) и насколько она холоднее кучевых облаков. Не стоит это путать с фактической температурой воздуха на какой-то определенной высоте.
(На высоте 10 км, как думаю, многие знают, фактическая температура может доходить и до -50 градусов цельсия).
Температуру окружающего воздуха в точке установки датчика выбирают в качестве опорной. Чем больше разность между окружающей температурой и измеренной температурой неба — тем небо более ясное. Обычно разность в 20 градусов говорит об очень чистой атмосфере, если же разность меньше пяти градусов — небо наглухо затянуто облаками.

Контактные методы измерения температуры неба тут очевидно не подходят поэтому применяют инфракрасные термометры.
Существуют ручные термометры, подобные изображенному на фотографии ниже.

f1f0711c0eb7495f93266e2d09f00496.png

Это своего рода однопиксельный «тепловизор», угол зрения, которого прежде всего определяется встроенной линзой Френеля.

Можно провести простой эксперимент и направить устройство в небо: на чистый участок и на облако — результат будет заметен сразу.
587280bde01348f18f83c3153d8bb7b8.png
(image credit: Forrest M. Mims III., mynasadata.larc.nasa.gov)

Конструкция датчика облачности.
Разобравшись с температурой неба и измерением, предлагаю к рассмотрению простую конструкцию соответствующего датчика, способного работать в автоматическом автономном режиме.
Сердцем устройства является инфракрасный термометр фирмы Melexis — MLX90614, купить который довольно просто.

991a159156e847bc9d4a785e69a743c5.png

Термометр выполнен в удобном герметичном корпусе, напоминающем корпуса некоторых отечественных операционных усилителей.
С внешним миром устройство общается с помощью шины SMBus, совместимой с i2c, с некоторыми небольшими нюансами, о которых я расскажу далее.
Присутствует так же автономный аналоговый режим, когда на выходе устройства — ШИМ сигнал, со скважностью, зависящей от измеряемой температуры. Может быть, полезно при создании устройств наподобие термостата.
Устройство умеет измерять температуру с помощью двух сенсоров — классической термопары и инфракрасного датчика. Существуют так же версии, оснащенные сразу двумя ИК датчиками.

Некоторые характеристики устройства
5e4d4b3039bf44cba335dfa201460cda.png

Распиновка
1218e94c98464a698a88d7ff52de5339.png

Информационная линия SDA используется так же и для вывода ШИМ сигнала в соответствующем режиме.
По умолчанию устройство должно работать в SMBus режиме, но в моём случае почему-то оказался включен PWM. Это привело к тому, что после подключения я увидел на шине i2c полный хаос.
Для переключения устройства в SMBus режиме достаточно кратковременно замкнуть между собой линии SCL и SDA. К сожалению, при следующем включении устройство снова окажется в PWM режиме. Для переключения в режим SMBus «навсегда» нужно поменять параметры в EEPROM устройства.

Датчик облачности построен на основе микрокомпьютера Raspberry Pi B первого поколения.
Конечно, можно было бы обойтись простейшим avr микроконтроллером, но в моём случае датчик является частью более сложного прибора — универсальной allsky камеры, о которой я еще обязательно напишу. В своём проекте я использую дистрибутив Raspbian Jessie с ядром версии 4.4. Все нижеописанное справедливо для этой версии платы и для этой версии ОС.

В сети есть большое количество информации о подключении MLX90614 к микроконтроллерам и проблем тут обычно не возникает, а вот касательно Raspberry информации маловато и можно запросто встретить разнообразные грабли. Надеюсь, что эта статья поможет кому-то не наступить на них :)

Итак, подключается все очень просто.
890186ace5b142de8be44a22b8b88faa.png

Конденсатор C1 — керамический, его применение обязательно.
Резисторы R1 и R2 — 4K7, опциональные, т.к. в Raspberry Pi есть свои подтяжки на i2c шине.
Но eсли линия к mlx достаточно длинная — резисторы лучше поставить. В моём случае совсем рядом на шине висит еще одно устройство, в котором так же есть такие резисторы, поэтому для mlx я не ставил подтяжек. Я использую трехвольтовую версию mlx90614 поэтому в данном случае питание поступает от линии 3.3 вольта. В случае же пятивольтовой может потребоваться согласование уровней дабы не повредить Raspberry.

SMBus
Хотелось бы отдельно сказать про шины SMBus и I2C. Обе шины, в нашем случае (напряжение питания 3.3 вольта), электрически и сигнально совместимы, так что с MLX90614 можно работать как с обычным i2c устройством. Есть так же отличия в максимальных рабочих скоростях, но и этим в данном случае можно пренебречь.

Работа с устройством
Для Raspberry Pi существует два основных способа общаться с i2c устройствами — используя аппаратную i2c шину, посредством драйвера i2c_bcm2708 и библиотеки libi2c-dev или же используя популярную библиотеку bcm2835 которая программно эмулирует i2c протокол, с нужным интервалом дергая те же GPIO2 и GPIO3.
По умолчанию i2c адрес устройства — 0×5A
Забегая вперед скажу, что с bcm2835 проблем не было никаких и датчик MLX90614 заработал сразу, но этот способ мне не нравился, зачем программно эмулировать имеющееся оборудование на компьютере с весьма ограниченными ресурсами. Было принято решение работать через драйвер i2c_bcm2708.

Первым делом следует убедиться, что модуль i2c_bcm2708 загружен, выполнив команду lsmod,
если модуля нет в списке — необходимо его загрузить командой

sudo modprobe i2c_bcm2708

и затем добавить строчку i2c_bcm2708 в конец файла /etc/modules, это необходимо для того что бы модуль загружался при старте системы.

После загрузки модуля станут доступны два устройства — /dev/i2c-0 и /dev/i2c-1
Первый относится к нулевой шине i2c, второй соответственно к первой. В случае Raspberry Pi первых поколений — нулевая шина не распаяна на плате, первая же выведеная на гребенку GPIO, поэтому вся работа идет через /dev/i2c-1

Теперь если запустить команду

i2cdetect -y1

(поставляется в пакете libi2c-dev, y 1 — номер i2c шины) можно увидеть следующее (при условии, что у нас больше нет никаких i2c устройств).
1f50135809eb4218bc9ed9975ebc7c6a.png

Девайс с адресом 5a — наш MLX90614. Если же вы видите тут просто хаотичный массив из чисел — ваш mlx работает в ШИМ режиме, замкните на короткое время SCL и SDA — устройство должно переключиться в SMBus режим и вывод i2cdetect станет корректным.
Далее я покажу как можно поменять параметры в EEPROM и исправить эту ситуцию.

Работа с устройством очень проста. Пишем простейшую программу на С

Простейший пример
// Необходимые заголовочные файлы
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

///

int main()
{
        int fdev = open("/dev/i2c-1", O_RDWR);  // открываем i2c шину

        if (fdev < 0) {
                fprintf(stderr, "Failed to open I2C interface %s Error: %s\n", dev_path, strerror(errno));
                return -1;
        }

        unsigned char i2c_addr = 0x5A;

        // устанавливаем адрес слейвого девайса, в нашем случае 0x5A
        if (ioctl(fdev, I2C_SLAVE, i2c_addr) < 0) {
                fprintf(stderr, "Failed to select I2C slave device! Error: %s\n", strerror(errno));
                return -1;
        }

        // включаем проверку контрольных сумм, дабы не было проблем
        if (ioctl(fdev, I2C_PEC, 1) < 0) {
                fprintf(stderr, "Failed to enable SMBus packet error checking, error: %s\n", strerror(errno));
                return -1;
        }


        // пробуем что-нибудь спросить у устройства, отправив SMBus READ запрос

        i2c_data data;
        char command = 0x06; // команда 0x06 означает прочитать значение термопарного датчика.

        struct i2c_smbus_ioctl_data sdat = {
                .read_write = I2C_SMBUS_READ, 
                .command = command,
                .size = I2C_SMBUS_WORD_DATA,
                .data = &data
        };

        if (ioctl(fdev, I2C_SMBUS, &sdat) < 0) {
                fprintf(stderr, "Failed to perfom I2C_SMBUS transaction, error: %s\n", strerror(errno));
                return -1;
        }

        // выполняем вычисление значения температуры, как описано в даташите
        double temp = (double) data.word;
        temp = (temp * 0.02)-0.01;
        temp = temp - 273.15;

        // печатаем результат в цельсиях
        printf("Tamb = %04.2f\n", temp);

        return 0;
}

Компилируем:
gcc test.c -l -o test

Запускаем:
sudo ./test

… и получаем ошибку »Failed to perfom I2C_SMBUS transaction, error: bad message»

Это ответ от mlx90614, устройство не понимает наш запрос.
В попытках разобраться в чем дело я решил взять логический анализатор и посмотреть как происходит обмен с устройством.

В даташите приведен пример нормального обмена по шине SMBus, чтение, как и в нашем случае.
e507dc92fe8342c1a102c365ec7e4e73.png

Логический анализатор же показал следующую картину
37a78f2a27b045be91201e5be43a05a3.png

Видно что после выполнения команды write и отправки данных добавляется ненужный стоп бит (красная точка), перед тем как отправить запрос на чтение. Это как бы разбивает нашу одиночную посылку на два отдельных неполноценных пакета. Разумеется устройство не понимает такой запрос.

Попробовав повторить все то же самое с программной библиотекой bcm2835 я увидел, что все отрабатывает корректно.
0dd888a6bb0d4a68af2efbad4e83524b.png

Стало быть я как-то не так использовал api аппаратного драйвера.
В итоге, после определенного ковыряния в коде ядра и копания на форуме Rasperry Pi выяснилось, что для того что бы все заработало в драйвере должен быть активирован так называемый комбинированный режим записи-чтения. В этом режиме драйвер не разбивает один пакет с двумя командами чтения-записи на два независимых.
Что бы его активировать необходимо от рута выполнить команду:

echo -n 1 > /sys/module/i2c_bcm2708/parameters/combined

Запись нуля в combined соответственно выключает этот режим.

Теперь, после включения режима, если еще раз запустить наш предидущий пример — мы должны получить ответ.

sudo ./test
Tamb = 19.4

Все работает!
Теперь можно писать полноценную утилу для работы с устройством.

В даташите хорошо описаны все адреса EEPROM и RAM для чтения-записи значений и параметров.

7e28867a592842f8b5408ca03d7112e3.png

Как не трудно догадаться — регистр PWCTRL позволяет включать и выключать тот самый режим ШИМ.
Описание битов регистра из даташита.
3fda1c384ab44476b81833e237d0d424.png

Соответственно что бы выключить режим ШИМ надо необходимо 1-ый бит регистра PWCTRL установить в 0.

Чтение значений температур происходит из оперативной памяти устройствами
824bc94d4a84423c89aeb24d899addfc.png

Как видим отсюда можно прочесть термопарный, первый и второй (если имеется) канал ИК датчика, в виде сырых данных и в виде температуры.

Составим заголовочный файл с необходимыми адресами, mlx_addrs.h

mlx_addrs.h
// RAM
#define MLX90614_RAWIR1 0x04
#define MLX90614_RAWIR2 0x05
#define MLX90614_TA 0x06
#define MLX90614_TOBJ1 0x07
#define MLX90614_TOBJ2 0x08

// EEPROM
#define MLX90614_TOMAX 0x20
#define MLX90614_TOMIN 0x21
#define MLX90614_PWMCTRL 0x22
#define MLX90614_TARANGE 0x23
#define MLX90614_EMISS 0x24
#define MLX90614_CONFIG 0x25
#define MLX90614_ADDR 0x2E
#define MLX90614_ID1 0x1C
#define MLX90614_ID2 0x1D
#define MLX90614_ID3 0x1E
#define MLX90614_ID4 0x1F

И полный исходный код приложеня для работы с устройством MLX90614.

mlx90614.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "mlx_addrs.h"

// buffer for data reading or writing
typedef union i2c_smbus_data i2c_data;


static int DEBUG_MODE = 0;

extern const char* __progname;
///

int get_device(const int bus_num, const unsigned char i2c_addr)
{
        char dev_path[11] = { 0 };

        // construct path to i2c device
        snprintf(dev_path, 11, "/dev/i2c-%i", bus_num);

        if (DEBUG_MODE) {
                fprintf(stderr, "Opening i2c interface %s\n", dev_path);
        }

        int fdev = open(dev_path, O_RDWR);

        if (fdev < 0) {
                fprintf(stderr, "Failed to open I2C interface %s Error: %s\n", dev_path, strerror(errno));
                return -1;
        }

        if (DEBUG_MODE) {
                fprintf(stderr, "Setting up slave address 0x%02X\n", i2c_addr);
        }

        // set addr of the slave i2c device
        if (ioctl(fdev, I2C_SLAVE, i2c_addr) < 0) {
                fprintf(stderr, "Failed to select I2C slave device! Error: %s\n", strerror(errno));
                return -1;
        }





        // enable checksums
        if (ioctl(fdev, I2C_PEC, 1) < 0) {
                fprintf(stderr, "Failed to enable SMBus packet error checking, error: %s\n", strerror(errno));
                return -1;
        }

        return fdev;
}

int talk_to_device(const int fdev, const int read, const char command, i2c_data* data)
{
        // initialize i2c_smus structure for combined write/read request to device
        struct i2c_smbus_ioctl_data sdat = {
                .read_write = (read ? I2C_SMBUS_READ : I2C_SMBUS_WRITE), // set operation type: read or write
                .command = command,             // set command, i.e. register number
                .size = I2C_SMBUS_WORD_DATA,   // set data size, note: mlx supports only WORD
                .data = data    // pointer to data
        };

        if (DEBUG_MODE) {
                fprintf(stderr, "Perfoming %s request to device, command = 0x%02X\n"
                                                , (read ? "I2C_SMBUS_READ" : "I2C_SMBUS_WRITE"), command);
        }

        // perfom combined request to device
        if (ioctl(fdev, I2C_SMBUS, &sdat) < 0) {
                fprintf(stderr, "Failed to perfom I2C_SMBUS transaction, error: %s\n", strerror(errno));
                return -1;
        }

        if (DEBUG_MODE) {
                fprintf(stderr, "Ok, got answer from device\n");
        }

        return 0;
}

int check_args(const int bus_num, const unsigned char i2c_addr)
{
        if (bus_num > 1 || bus_num < 0) {
                fprintf(stderr, "Invalid bus number %i, please select 0 or 1\n", bus_num);
                return -1;
        }

        if (i2c_addr == 0) {
                fprintf(stderr, "Invalid i2c device address, please set proper address of the MLX\n");
                return -1;
        }

        return 0;
}

int read_data_from_sensor(const int fdev, const char command)
{
        i2c_data data;

        if (talk_to_device(fdev, 1, command, &data) < 0) {
                return  -1;
        }

        double temp = 0;

        switch (command) {
                case MLX90614_TA:
                case MLX90614_TOBJ1:
                case MLX90614_TOBJ2:
                        temp = (double) data.word;
                        temp = (temp * 0.02)-0.01;
                        temp = temp - 273.15;
                        printf("%s = %04.2f\n", (command == MLX90614_TA ? "Tamb" : "Tobj"), temp);

                        break;

                case MLX90614_EMISS:
                        printf("Emissivity correction coefficient = %i\n", data.word);
                        break;

                case MLX90614_PWMCTRL:
                        if (!(data.word & (1 << 1))) {
                                printf("PWM mode - disabled\n");
                        } else {
                                printf("PWM mode - enabled\n");
                                printf("In order to disable pwm mode - pull down SCL for >=1.2 ms and change EEPROM setting.\n");
                        }

                        break;
        }

        return 0;
}

int write_data_to_sensor(const int fdev, const char command, const unsigned short write_arg)
{
        i2c_data msg;

    // get current value of the register
        if (talk_to_device(fdev, 1, command, &msg) < 0) {
                return  -1;
        }

        unsigned short current_val = msg.word;

    if (DEBUG_MODE) {
        fprintf(stderr, "EEPROM cell = 0x%02X current value = 0x%04X\n", command, current_val);
    }

        msg.word = 0x0;

        if (DEBUG_MODE) {
                fprintf(stderr, "Erasing EEPROM cell = 0x%02X\n", command);
        }

    // provide some time for device
        usleep(1000);

        if (talk_to_device(fdev, 0, command, &msg) < 0) {
                fprintf(stderr, "Unable to erase EEPROM cell\n");
                return -1;
        }

        // delay between eeprom erasing and writing new value
    // without this delay writing to device may fail
        usleep(5000);

        if (command == MLX90614_ADDR) {
                msg.word = 0xFFFF;
                msg.word = msg.word << 8 | write_arg;  // MLX devices uses LSByte only for address, other bits are ignored
    } else if(command == MLX90614_PWMCTRL) {
                if (write_arg) {   // enable PWM bit
                        current_val |= (1 << 1);
                } else {     //disable PWM bit
                        current_val &= ~(1 << 1);
                }

                msg.word = current_val;
        } else {
                msg.word = write_arg;
        }

        if (DEBUG_MODE) {
                fprintf(stderr, "Trying to store value = 0x%04X to the EEPROM cell = 0x%02X\n", msg.word, command);
        }

        if (talk_to_device(fdev, 0, command, &msg) < 0) {
                fprintf(stderr, "Unable to write to EEPROM\n");
                return -1;
        }

        usleep(5000);

        if (command == MLX90614_ADDR) {
                printf("MLX device address succesfully changed to 0x%X\n", msg.word);
                printf("Please, power off and power on again the device to apply changes\n");
        } else if (command == MLX90614_EMISS) {
                printf("Warning! Emissivity correction coefficient was changed to %i\n", msg.word);
        } else if (command == MLX90614_PWMCTRL) {
                printf("PWM mode is now %s\n", (write_arg ? "enabled" : "disabled"));
        }

        return 0;
}

void show_usage()
{
        printf("Usage\n");
        printf("\t%s --bus [0-1] --i2c_addr [0x00-0x7F] command|command=values wflag\n", __progname);
        printf("\n");
        printf("\t\t-b, --bus\t\t- set i2c bus number (0 for Raspbery PI model A, 1 for Raspberry PI model B, default is 0)\n");
        printf("\t\t-c, --i2c_addr\t\t- set slave device address (default = 0x5A)\n");
        printf("\t\t-r, --new_addr=ADDR\t- set new i2c ADDR for the device\n");
        printf("\t\t-w, --write\t\t- perfom writing to the device (wflag)\n");
        printf("\t\t-i, --get_ir_temp\t- get temperature in C from the infrared sensor\n");
        printf("\t\t-a, --get_ambient_temp\t- get temperature in C from the PTAT element\n");
        printf("\t\t-e, --emissivity_coefficient\t- get value of the emissivity coefficient\n");
        printf("\t\t--emissivity_coefficient=VALUE\t- set new VALUE for emissivity coefficient, use with --write argument\n");
        printf("\t\t-p, --pwm_mode\t\t- check current state of the PWM\n");
        printf("\t\t--pwm_mode=1|0\t\t- disable (0) or enable (1) PWM mode, use with --write argument\n");
}

int main(int argc, char **argv)
{
        int bus_num = 0;
        unsigned char i2c_addr = MLX90614_I2CADDR;

        int op_read = 1;
        int write_arg_set = 0;

        unsigned char command = 0x00;
        unsigned short write_arg = 0x00;

        static struct option long_options[] = {
                { "help", no_argument, NULL, 'h' },
                { "bus", required_argument, NULL, 'b' },
                { "i2c_addr", required_argument, NULL, 'c' },
                { "new_addr", required_argument, NULL, 'r'},
                { "write", no_argument, NULL, 'w' },
                { "get_ir_temp", no_argument, NULL, 'i' },
                { "get_ambient_temp", no_argument, NULL, 'a' },
                { "emissivity_coefficient", optional_argument, NULL, 'e'},
                { "pwm_mode", optional_argument, NULL,'p'},
                { "debug", no_argument, NULL, 'd' }
        };

        int option_index = 0;
        int opt = getopt_long(argc, argv, "hbc:r:wiae:p:d", long_options, &option_index);

        while (opt != -1) {
                switch (opt) {
                        case 'h':
                                show_usage();
                                return 0;

                        case 'b':
                                bus_num = atoi(optarg);
                                break;

                        case 'c':
                                i2c_addr = strtol(optarg, NULL, 16);
                                break;

                        case 'r':
                                write_arg = strtol(optarg, NULL, 16);;
                                write_arg_set = 1;
                                command = MLX90614_ADDR;
                                break;

                        case 'w':
                                op_read = 0;
                                break;

                        case 'i':
                                command = MLX90614_TOBJ1;
                                break;

                        case 'a':
                                command = MLX90614_TA;
                                break;

                        case 'e':
                                command = MLX90614_EMISS;

                                if (optarg) {
                                        write_arg = atoi(optarg);
                                        printf("%i\n", write_arg);
                                        write_arg_set = 1;
                                }

                                break;

                        case 'p':
                                command = MLX90614_PWMCTRL;

                                if (optarg) {
                                        write_arg = atoi(optarg);
                                        write_arg_set = 1;
                                }

                                break;

                        case 'd':
                                DEBUG_MODE = 1;
                                break;

                        default:
                                show_usage();
                                abort();
                }

                opt = getopt_long(argc, argv, "bc:wiaep", long_options, &option_index);
        }

        if (check_args(bus_num, i2c_addr) < 0) {
                return -1;
        }

        if (!op_read && (command == MLX90614_TOBJ1 || command == MLX90614_TA)) {
                fprintf(stderr, "Read only data!\n");
                return -1;
        }

        if (!op_read && !write_arg_set) {
                fprintf(stderr, "Plese set parameter value for writing\n");
                return -1;
        }

        int fdev = get_device(bus_num, i2c_addr);

        if (fdev < 0) {
                return -1;
        }

        int res;

        if (op_read) {
                res = read_data_from_sensor(fdev, command);
        } else {
                res = write_data_to_sensor(fdev, command, write_arg);
        }

        close(fdev);

        return res;
}


Makefile
CC := gcc
PROGRAM = read_mlx90614
SRC := mlx90614.c
CFLAGS := -Wall -std=gnu99
TARGET_DIR := /opt/allsky/bin

all: $(PROGRAM)

$(PROGRAM): $(OBJECTS)

$(CC) $(CFLAGS) $(SRC) $(LDFLAG) -o $(PROGRAM)

install:
    mkdir -p $(TARGET_DIR)
    cp $(PROGRAM) $(TARGET_DIR)
    cp dht_to_db.sh $(TARGET_DIR)

clean:
    rm -fr $(PROGRAM) $(PROGRAM).o


Собираем и запускаем:
make

Чтение температуры ИК с ИК датчика, i2c шина 1, i2c адресс 0×5A:
./read_mlx90614 --bus 1 --i2c_addr 0×5a -i
Tobj = 21.3

Чтение температуры c термопарного датчика:
./read_mlx90614 --bus 1 --i2c_addr 0×5a -a
Tamb = 19.4

Чтение температуры c термопарного датчика:
./read_mlx90614 --bus 1 --i2c_addr 0×5a -a
Tamb = 19.4

Работа с режимом ШИМ.
Узнать текущий режим:
./read_mlx90614 --bus 1 --i2c_addr 0×5a -p
PWM mode — enabled

Выключить режим ШИМ:
./read_mlx90614 --bus 1 --i2c_addr 0×5a --pwm_mode=1 -w

Выключить режим ШИМ:
./read_mlx90614 --bus 1 --i2c_addr 0×5a --pwm_mode=0 -w

Так же есть дополнительный аргумент --debug, включающий режим отладки, это позволяет наглядно увидеть все взаимодействие с устройсвом.

./read_mlx90614 --bus 1 --i2c_addr 0×5a --pwm_mode=1 -w –debug

Opening i2c interface /dev/i2c-1
Setting up slave address 0×2A
Perfoming I2C_SMBUS_READ request to device, command = 0×22
Ok, got answer from device
EEPROM cell = 0×22 current value = 0×0201
Erasing EEPROM cell = 0×22
Trying to store value = 0×0203 to the EEPROM cell = 0×22
PWM mode is now enabled

Т.к. ИК окошко mlx90614 выполнено герметичным — нет необходимости в дополнительной гидроизоляции для уличного применения устройства.

Вот так датчик смонтирован у меня, на корпусе allsky камеры.
79316e7a787c4e659748d94a707d8689.png

Замер температуры неба производится каждые 5 минут, данные записываются в MySQL базу данных.
В последствии значение температуры неба так же накладывается на ночной снимок камеры.
9eea415826234cb9a76093545fd9cb0d.png

Температура неба -1.30 градуса цельсия, хорошее ясное летнее небо.

© Geektimes