186 lines
6.3 KiB
C
186 lines
6.3 KiB
C
/*
|
||
* 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, ®, 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, ¤t_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
|
||
}
|