Files
watch-watch/main/ina226_monitor.c

186 lines
6.3 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Developed by TComLab
* Version: v0.1
* Date: 2025-12-15
*/
#include "ina226_monitor.h"
#include <math.h>
#include "driver/i2c.h"
#include "esp_check.h"
#include "esp_log.h"
#include "sdkconfig.h"
#ifndef CONFIG_WATCH_INA226_ENABLED
#define CONFIG_WATCH_INA226_ENABLED 0
#endif
#ifndef CONFIG_WATCH_INA226_I2C_PORT
#define CONFIG_WATCH_INA226_I2C_PORT 0
#endif
#ifndef CONFIG_WATCH_INA226_I2C_SDA
#define CONFIG_WATCH_INA226_I2C_SDA 6
#endif
#ifndef CONFIG_WATCH_INA226_I2C_SCL
#define CONFIG_WATCH_INA226_I2C_SCL 7
#endif
#ifndef CONFIG_WATCH_INA226_I2C_FREQ_HZ
#define CONFIG_WATCH_INA226_I2C_FREQ_HZ 400000
#endif
#ifndef CONFIG_WATCH_INA226_CURRENT_LSB_uA
#define CONFIG_WATCH_INA226_CURRENT_LSB_uA 50
#endif
#ifndef CONFIG_WATCH_INA226_SHUNT_MILLIOHM
#define CONFIG_WATCH_INA226_SHUNT_MILLIOHM 100
#endif
#ifndef CONFIG_WATCH_INA226_ADDR
#define CONFIG_WATCH_INA226_ADDR 0x40
#endif
#define INA226_REG_CONFIG 0x00
#define INA226_REG_BUS 0x02
#define INA226_REG_CURRENT 0x04
#define INA226_REG_CALIBRATION 0x05
#define INA226_CONFIG_AVG_16 (0x04 << 9)
#define INA226_CONFIG_VBUS_1100US (0x04 << 6)
#define INA226_CONFIG_VSH_1100US (0x04 << 3)
#define INA226_CONFIG_MODE_SHUNT_BUS_CONT 0x07
#if CONFIG_WATCH_INA226_ENABLED
static const char *TAG = "ina226";
static bool s_initialized;
static float s_current_lsb_a;
static uint8_t s_address = CONFIG_WATCH_INA226_ADDR;
static ina226_reading_t s_last_reading;
#endif
// Ініціалізує контролер INA226: налаштовує I2C, записує конфігурацію та
// калібрувальні значення, а також зберігає початковий стан вимірювань.
esp_err_t ina226_monitor_init(void)
{
#if !CONFIG_WATCH_INA226_ENABLED
return ESP_ERR_NOT_SUPPORTED;
#else
if (s_initialized) {
return ESP_OK;
}
i2c_config_t config = {
.mode = I2C_MODE_MASTER,
.sda_io_num = CONFIG_WATCH_INA226_I2C_SDA,
.scl_io_num = CONFIG_WATCH_INA226_I2C_SCL,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = CONFIG_WATCH_INA226_I2C_FREQ_HZ,
};
ESP_RETURN_ON_ERROR(i2c_param_config(CONFIG_WATCH_INA226_I2C_PORT, &config), TAG, "i2c config failed");
ESP_RETURN_ON_ERROR(i2c_driver_install(CONFIG_WATCH_INA226_I2C_PORT, config.mode, 0, 0, 0),
TAG, "i2c driver install failed");
double current_lsb_a = ((double)CONFIG_WATCH_INA226_CURRENT_LSB_uA) / 1000000.0;
double shunt_ohms = ((double)CONFIG_WATCH_INA226_SHUNT_MILLIOHM) / 1000.0;
double calibration = 0.00512 / (current_lsb_a * shunt_ohms);
if (calibration > 0xFFFF) calibration = 0xFFFF;
uint16_t calibration_value = (uint16_t)calibration;
s_current_lsb_a = (float)current_lsb_a;
const uint16_t config_value = INA226_CONFIG_AVG_16 |
INA226_CONFIG_VBUS_1100US |
INA226_CONFIG_VSH_1100US |
INA226_CONFIG_MODE_SHUNT_BUS_CONT;
uint8_t payload_cfg[3] = { INA226_REG_CONFIG, (uint8_t)(config_value >> 8), (uint8_t)(config_value & 0xFF) };
ESP_RETURN_ON_ERROR(i2c_master_write_to_device(CONFIG_WATCH_INA226_I2C_PORT,
s_address, payload_cfg, sizeof(payload_cfg),
pdMS_TO_TICKS(100)),
TAG, "config write failed");
uint8_t payload_cal[3] = { INA226_REG_CALIBRATION, (uint8_t)(calibration_value >> 8),
(uint8_t)(calibration_value & 0xFF) };
ESP_RETURN_ON_ERROR(i2c_master_write_to_device(CONFIG_WATCH_INA226_I2C_PORT,
s_address, payload_cal, sizeof(payload_cal),
pdMS_TO_TICKS(100)),
TAG, "calibration write failed");
s_last_reading = (ina226_reading_t){0};
s_initialized = true;
ESP_LOGI(TAG, "INA226 ініціалізовано (адреса 0x%02X)", s_address);
return ESP_OK;
#endif
}
// Дозволяє швидко перевірити, чи був модуль INA226 успішно ініціалізований.
bool ina226_monitor_ready(void)
{
#if CONFIG_WATCH_INA226_ENABLED
return s_initialized;
#else
return false;
#endif
}
// INA226 вимірює єдиний канал живлення, тому повертаємо 1.
size_t ina226_monitor_channel_count(void)
{
return 1;
}
#if CONFIG_WATCH_INA226_ENABLED
// Зчитує 16-бітний регістр INA226, використовуючи транзакцію write-then-read.
static esp_err_t ina226_read_register(uint8_t reg, uint16_t *out_value)
{
uint8_t value[2];
esp_err_t err = i2c_master_write_read_device(CONFIG_WATCH_INA226_I2C_PORT,
s_address, &reg, 1, value, sizeof(value),
pdMS_TO_TICKS(100));
if (err != ESP_OK) {
return err;
}
*out_value = ((uint16_t)value[0] << 8) | value[1];
return ESP_OK;
}
#endif
// Виконує одне вимірювання напруги/струму через INA226 та кешує результат.
esp_err_t ina226_monitor_sample(ina226_reading_t *out_reading)
{
#if !CONFIG_WATCH_INA226_ENABLED
return ESP_ERR_NOT_SUPPORTED;
#else
if (!s_initialized) {
return ESP_ERR_INVALID_STATE;
}
uint16_t bus_raw = 0;
uint16_t current_raw = 0;
ESP_RETURN_ON_ERROR(ina226_read_register(INA226_REG_BUS, &bus_raw), TAG, "bus read failed");
ESP_RETURN_ON_ERROR(ina226_read_register(INA226_REG_CURRENT, &current_raw), TAG, "current read failed");
float voltage_v = (float)bus_raw * 1.25f / 1000.0f;
float current_a = (int16_t)current_raw * s_current_lsb_a;
float power_w = voltage_v * current_a;
s_last_reading = (ina226_reading_t){
.voltage_v = voltage_v,
.current_a = current_a,
.power_w = power_w,
};
if (out_reading) {
*out_reading = s_last_reading;
}
return ESP_OK;
#endif
}
// Повертає останній виміряний набір даних без нового звернення до шини I2C.
const ina226_reading_t *ina226_monitor_get_last(void)
{
#if CONFIG_WATCH_INA226_ENABLED
return &s_last_reading;
#else
return NULL;
#endif
}