From 189117326b4d7690d8c110b664ab5fe71017270c Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 1 Jan 2023 23:14:32 +0300 Subject: [PATCH] Update UniTemp https://github.com/quen0n/unitemp-flipperzero/tree/dev --- applications/plugins/unitemp/README.md | 9 +- applications/plugins/unitemp/Sensors.c | 1 + applications/plugins/unitemp/Sensors.h | 1 + applications/plugins/unitemp/sensors/BMP180.c | 174 ++++++++++++++++++ applications/plugins/unitemp/sensors/BMP180.h | 62 +++++++ .../plugins/unitemp/sensors/Sensors.xlsx | Bin 12265 -> 12336 bytes 6 files changed, 245 insertions(+), 2 deletions(-) create mode 100644 applications/plugins/unitemp/sensors/BMP180.c create mode 100644 applications/plugins/unitemp/sensors/BMP180.h diff --git a/applications/plugins/unitemp/README.md b/applications/plugins/unitemp/README.md index 257e8a8ae..80a296e9b 100644 --- a/applications/plugins/unitemp/README.md +++ b/applications/plugins/unitemp/README.md @@ -1,9 +1,14 @@ ![Flipper usage](https://user-images.githubusercontent.com/10090793/206618263-c1e212e4-58dc-432e-87a8-5c19fd835b35.png) # Unitemp - Universal temperature sensor reader [![GitHub release](https://img.shields.io/github/release/quen0n/unitemp-flipperzero?include_prereleases=&sort=semver&color=blue)](https://github.com/quen0n/unitemp-flipperzero/releases/) -[![GitHub](https://img.shields.io/github/license/quen0n/unitemp-flipperzero)](https://github.com/quen0n/unitemp-flipperzero/blob/dev/LICENSE.md) +[![GitHub](https://img.shields.io/github/license/quen0n/unitemp-flipperzero)](https://github.com/quen0n/unitemp-flipperzero/blob/dev/LICENSE.md) +[![Build dev](https://github.com/quen0n/unitemp-flipperzero/actions/workflows/build_dev.yml/badge.svg?branch=dev)](https://github.com/quen0n/unitemp-flipperzero/actions/workflows/build_dev.yml) [Flipper Zero](https://flipperzero.one/) application for reading temperature, humidity and pressure sensors using Onewire, Singlewire, I2C protocols. ## List of supported sensors (supplemented) -![image](https://user-images.githubusercontent.com/10090793/209491886-f4c5ef6e-38b2-45b8-a8e7-4aeca9e155f2.png) +![image](https://user-images.githubusercontent.com/10090793/210119924-51119deb-f950-40ee-bc27-22b971243527.png) ## Installation Copy the contents of the repository to the `applications/plugins/unitemp` folder and build the project. Flash FZ along with resources. [More...](https://github.com/flipperdevices/flipperzero-firmware/blob/dev/documentation/fbt.md) +## Some community photos +![image](https://user-images.githubusercontent.com/10090793/210120132-7ddbc937-0a6b-4472-bd1c-7fbc3ecdf2ad.png) +![image](https://user-images.githubusercontent.com/10090793/210120135-12fc5810-77ff-49db-b799-e9479e1f57a7.png) +![image](https://user-images.githubusercontent.com/10090793/210120143-a2bae3ce-4190-421f-8c4f-c7c744903bd6.png) diff --git a/applications/plugins/unitemp/Sensors.c b/applications/plugins/unitemp/Sensors.c index 9740b9887..33f62b201 100644 --- a/applications/plugins/unitemp/Sensors.c +++ b/applications/plugins/unitemp/Sensors.c @@ -83,6 +83,7 @@ static const SensorType* sensorTypes[] = { &SHT30, &GXHT30, &LM75, + &BMP180, &BMP280, &BME280}; diff --git a/applications/plugins/unitemp/Sensors.h b/applications/plugins/unitemp/Sensors.h index 0643ffb1f..2193ce466 100644 --- a/applications/plugins/unitemp/Sensors.h +++ b/applications/plugins/unitemp/Sensors.h @@ -323,4 +323,5 @@ const GPIO* #include "./sensors/AM2320.h" #include "./sensors/DHT20.h" #include "./sensors/SHT30.h" +#include "./sensors/BMP180.h" #endif diff --git a/applications/plugins/unitemp/sensors/BMP180.c b/applications/plugins/unitemp/sensors/BMP180.c new file mode 100644 index 000000000..6fd5f2fa5 --- /dev/null +++ b/applications/plugins/unitemp/sensors/BMP180.c @@ -0,0 +1,174 @@ +/* + Unitemp - Universal temperature reader + Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#include "BMP180.h" +#include "../interfaces/I2CSensor.h" + +typedef struct { + int16_t AC1; + int16_t AC2; + int16_t AC3; + uint16_t AC4; + uint16_t AC5; + uint16_t AC6; + int16_t B1; + int16_t B2; + int16_t MB; + int16_t MC; + int16_t MD; +} BMP180_cal; + +typedef struct { + //Калибровочные значения + BMP180_cal bmp180_cal; +} BMP180_instance; + +const SensorType BMP180 = { + .typename = "BMP180", + .interface = &I2C, + .datatype = UT_TEMPERATURE | UT_PRESSURE, + .pollingInterval = 1000, + .allocator = unitemp_BMP180_I2C_alloc, + .mem_releaser = unitemp_BMP180_I2C_free, + .initializer = unitemp_BMP180_init, + .deinitializer = unitemp_BMP180_I2C_deinit, + .updater = unitemp_BMP180_I2C_update}; + +bool unitemp_BMP180_I2C_alloc(Sensor* sensor, char* args) { + UNUSED(args); + I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance; + + //Адреса на шине I2C (7 бит) + i2c_sensor->minI2CAdr = 0x77 << 1; + i2c_sensor->maxI2CAdr = 0x77 << 1; + + BMP180_instance* bmx280_instance = malloc(sizeof(BMP180_instance)); + i2c_sensor->sensorInstance = bmx280_instance; + return true; +} + +bool unitemp_BMP180_I2C_free(Sensor* sensor) { + UNUSED(sensor); + I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance; + free(i2c_sensor->sensorInstance); + return true; +} + +bool unitemp_BMP180_init(Sensor* sensor) { + I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance; + + //Перезагрузка + if(!unitemp_i2c_writeReg(i2c_sensor, 0xE0, 0xB6)) return false; + furi_delay_ms(100); + + //Проверка ID + uint8_t id = unitemp_i2c_readReg(i2c_sensor, 0xD0); + if(id != 0x55) { + FURI_LOG_E( + APP_NAME, "Sensor %s returned wrong ID 0x%02X, expected 0x55", sensor->name, id); + return false; + } + + BMP180_instance* bmp180_instance = i2c_sensor->sensorInstance; + + uint8_t buff[22] = {0}; + + //Чтение калибровочных значений + if(!unitemp_i2c_readRegArray(i2c_sensor, 0xAA, 22, buff)) return false; + bmp180_instance->bmp180_cal.AC1 = (buff[0] << 8) | buff[1]; + bmp180_instance->bmp180_cal.AC2 = (buff[2] << 8) | buff[3]; + bmp180_instance->bmp180_cal.AC3 = (buff[4] << 8) | buff[5]; + bmp180_instance->bmp180_cal.AC4 = (buff[6] << 8) | buff[7]; + bmp180_instance->bmp180_cal.AC5 = (buff[8] << 8) | buff[9]; + bmp180_instance->bmp180_cal.AC6 = (buff[10] << 8) | buff[11]; + bmp180_instance->bmp180_cal.B1 = (buff[12] << 8) | buff[13]; + bmp180_instance->bmp180_cal.B2 = (buff[14] << 8) | buff[15]; + bmp180_instance->bmp180_cal.MB = (buff[16] << 8) | buff[17]; + bmp180_instance->bmp180_cal.MC = (buff[18] << 8) | buff[19]; + bmp180_instance->bmp180_cal.MD = (buff[20] << 8) | buff[21]; + +#ifdef UNITEMP_DEBUG + FURI_LOG_D( + APP_NAME, + "Sensor BMP180 (0x%02X) calibration values: %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d", + i2c_sensor->currentI2CAdr, + bmp180_instance->bmp180_cal.AC1, + bmp180_instance->bmp180_cal.AC2, + bmp180_instance->bmp180_cal.AC3, + bmp180_instance->bmp180_cal.AC4, + bmp180_instance->bmp180_cal.AC5, + bmp180_instance->bmp180_cal.AC6, + bmp180_instance->bmp180_cal.B1, + bmp180_instance->bmp180_cal.B2, + bmp180_instance->bmp180_cal.MB, + bmp180_instance->bmp180_cal.MC, + bmp180_instance->bmp180_cal.MD); +#endif + return true; +} + +bool unitemp_BMP180_I2C_deinit(Sensor* sensor) { + //Нечего деинициализировать + UNUSED(sensor); + return true; +} + +UnitempStatus unitemp_BMP180_I2C_update(Sensor* sensor) { + I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance; + BMP180_instance* bmp180_instance = i2c_sensor->sensorInstance; + + //Чтение температуры + if(!unitemp_i2c_writeReg(i2c_sensor, 0xF4, 0x2E)) return UT_SENSORSTATUS_TIMEOUT; + furi_delay_ms(5); + uint8_t buff[3] = {0}; + if(!unitemp_i2c_readRegArray(i2c_sensor, 0xF6, 2, buff)) return UT_SENSORSTATUS_TIMEOUT; + int32_t UT = ((uint16_t)buff[0] << 8) + buff[1]; + int32_t X1 = (UT - bmp180_instance->bmp180_cal.AC6) * bmp180_instance->bmp180_cal.AC5 >> 15; + int32_t X2 = (bmp180_instance->bmp180_cal.MC << 11) / (X1 + bmp180_instance->bmp180_cal.MD); + int32_t B5 = X1 + X2; + sensor->temp = ((B5 + 8) / 16) * 0.1f; + + //Чтение давления + if(!unitemp_i2c_writeReg(i2c_sensor, 0xF4, 0x34 + (0b11 << 6))) return UT_SENSORSTATUS_TIMEOUT; + furi_delay_ms(26); + if(!unitemp_i2c_readRegArray(i2c_sensor, 0xF6, 3, buff)) return UT_SENSORSTATUS_TIMEOUT; + uint32_t UP = ((buff[0] << 16) + (buff[1] << 8) + buff[2]) >> (8 - 0b11); + + int32_t B6, X3, B3, P; + uint32_t B4, B7; + B6 = B5 - 4000; + X1 = (bmp180_instance->bmp180_cal.B2 * ((B6 * B6) >> 12)) >> 11; + X2 = (bmp180_instance->bmp180_cal.AC2 * B6) >> 11; + X3 = X1 + X2; + B3 = (((bmp180_instance->bmp180_cal.AC1 * 4 + X3) << 0b11) + 2) >> 2; + X1 = (bmp180_instance->bmp180_cal.AC3 * B6) >> 13; + X2 = (bmp180_instance->bmp180_cal.B1 * ((B6 * B6) >> 12)) >> 16; + X3 = ((X1 + X2) + 2) >> 2; + B4 = (bmp180_instance->bmp180_cal.AC4 * (unsigned long)(X3 + 32768)) >> 15; + B7 = ((unsigned long)UP - B3) * (50000 >> 0b11); + 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); + sensor->pressure = P; + + return UT_SENSORSTATUS_OK; +} diff --git a/applications/plugins/unitemp/sensors/BMP180.h b/applications/plugins/unitemp/sensors/BMP180.h new file mode 100644 index 000000000..4237fb57a --- /dev/null +++ b/applications/plugins/unitemp/sensors/BMP180.h @@ -0,0 +1,62 @@ +/* + Unitemp - Universal temperature reader + Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef UNITEMP_BMP180 +#define UNITEMP_BMP180 + +#include "../unitemp.h" +#include "../Sensors.h" +extern const SensorType BMP180; +/** + * @brief Выделение памяти и установка начальных значений датчика BMP180 + * + * @param sensor Указатель на создаваемый датчик + * @return Истина при успехе + */ +bool unitemp_BMP180_I2C_alloc(Sensor* sensor, char* args); + +/** + * @brief Инициализации датчика BMP180 + * + * @param sensor Указатель на датчик + * @return Истина если инициализация упспешная + */ +bool unitemp_BMP180_init(Sensor* sensor); + +/** + * @brief Деинициализация датчика + * + * @param sensor Указатель на датчик + */ +bool unitemp_BMP180_I2C_deinit(Sensor* sensor); + +/** + * @brief Обновление значений из датчика + * + * @param sensor Указатель на датчик + * @return Статус обновления + */ +UnitempStatus unitemp_BMP180_I2C_update(Sensor* sensor); + +/** + * @brief Высвободить память датчика + * + * @param sensor Указатель на датчик + */ +bool unitemp_BMP180_I2C_free(Sensor* sensor); + +#endif \ No newline at end of file diff --git a/applications/plugins/unitemp/sensors/Sensors.xlsx b/applications/plugins/unitemp/sensors/Sensors.xlsx index b139b1b00722c1c505ebb46541ba194caeddd262..9983aeb6ea1201003aa27a9ca54218e19e5f20c7 100644 GIT binary patch delta 3980 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 W4TletR0hJ~_*O|i$^*pTZ~p;vEoOuO delta 3935 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