Rancang Bangun Sistem Manajemen Daya dan Pemantauan Termal Inverter Berbasis STM32 pada Unit PLTS Pasca Bencana
/* USER CODE BEGIN Header */
/**
**************************
* @file : main.c
* @brief : Final Robust Version - Monitoring System (With Buzzer & Fixed Sensors)
**************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "ssd1306.h"
#include "ssd1306_fonts.h"
#include <stdio.h>
/* USER CODE END Includes */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
// I2C Addresses (Format HAL: 7-bit address digeser ke kiri 1 bit)
#define INA219_ADDRESS (0x40 << 1) // Hasil: 0x80
#define MLX90614_ADDRESS (0x5A << 1) // Hasil: 0xB4
#define MLX90614_TOBJ1 0x07 // Register Suhu Objek
// Ambang Batas Sensor
#define LDR_THRESHOLD_HIGH 2500
#define LDR_THRESHOLD_LOW 1500
#define SUHU_NORMAL 45.0f
#define SUHU_PERINGATAN 65.0f
#define LVD_THRESHOLD 10.0f // Batas 10.0V untuk Trip
/* USER CODE END PD */
/* Private variables ---------------------------------------------------------*/
ADC_HandleTypeDef hadc1;
I2C_HandleTypeDef hi2c1;
/* USER CODE BEGIN PV */
float suhu_inverter = 0.0f;
float v_baterai = 0.0f;
float arus_beban = 0.0f;
float daya_total = 0.0f;
uint16_t ldr_value = 0;
float faktor_V = 1.0f;
float faktor_I = 1.0f;
char status_sistem[16] = "INIT";
uint8_t status_siang = 1;
uint8_t i2c_error_flag = 0;
uint32_t tick_terakhir = 0;
const uint32_t interval_baca = 1000;
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_ADC1_Init(void);
static void MX_I2C1_Init(void);
/* USER CODE BEGIN PFP */
void INA219_Init(void);
void Reset_I2C(void);
uint16_t Baca_LDR(void);
void Baca_MLX90614(void);
void Baca_INA219(void);
void Update_OLED(void);
/* USER CODE END PFP */
/* USER CODE BEGIN 0 */
void Reset_I2C(void) {
HAL_I2C_DeInit(&hi2c1);
HAL_Delay(10);
MX_I2C1_Init();
HAL_Delay(10);
ssd1306_Init();
INA219_Init();
i2c_error_flag = 0;
}
uint16_t Baca_LDR(void) {
uint32_t total_adc = 0;
const uint8_t sample_count = 10; // Ambil 10 sampel untuk oversampling
for (uint8_t i = 0; i < sample_count; i++) {
HAL_ADC_Start(&hadc1);
if (HAL_ADC_PollForConversion(&hadc1, 10) == HAL_OK) {
total_adc += HAL_ADC_GetValue(&hadc1);
}
HAL_ADC_Stop(&hadc1);
HAL_Delay(2); // Beri jeda 2ms antar sampling agar kapasitor internal ADC stabil
}
return (uint16_t)(total_adc / sample_count); // Kembalikan nilai rata-rata
}
void Baca_MLX90614(void) {
uint8_t buffer[3];
uint16_t raw_data;
// Membaca 3 byte data (LSB, MSB, PEC) dari register TOBJ1
if (HAL_I2C_Mem_Read(&hi2c1, MLX90614_ADDRESS, MLX90614_TOBJ1, I2C_MEMADD_SIZE_8BIT, buffer, 3, 100) == HAL_OK) {
// Menggabungkan LSB dan MSB
raw_data = (buffer[1] << 8) | buffer[0];
// Konversi raw data ke derajat Celcius
suhu_inverter = (raw_data * 0.02f) - 273.15f;
} else {
i2c_error_flag = 1;
}
}
void INA219_Init(void) {
// Register 0x00 (Config): 0x399F -> Range 32V, Gain /8 (320mV), 12-bit ADC, Continuous
uint8_t config_buf[2] = {0x39, 0x9F};
HAL_I2C_Mem_Write(&hi2c1, INA219_ADDRESS, 0x00, I2C_MEMADD_SIZE_8BIT, config_buf, 2, 100);
HAL_Delay(10);
}
void Baca_INA219(void) {
uint8_t buf[2];
uint16_t raw_bus;
int16_t raw_shunt;
// 1. Baca Bus Voltage (Register 0x02)
if (HAL_I2C_Mem_Read(&hi2c1, INA219_ADDRESS, 0x02, I2C_MEMADD_SIZE_8BIT, buf, 2, 100) == HAL_OK) {
raw_bus = (buf[0] << 8) | buf[1];
// Shift kanan 3 bit karena 3 bit LSB adalah flag (CNVR & OVF)
// LSB = 4mV (0.004 V)
v_baterai = ((raw_bus >> 3) * 0.004f) * faktor_V;
} else {
i2c_error_flag = 1;
}
// 2. Baca Shunt Voltage (Register 0x01)
if (HAL_I2C_Mem_Read(&hi2c1, INA219_ADDRESS, 0x01, I2C_MEMADD_SIZE_8BIT, buf, 2, 100) == HAL_OK) {
raw_shunt = (int16_t)((buf[0] << 8) | buf[1]);
// LSB Shunt = 10uV = 0.00001 V.
// Asumsi R_Shunt = 0.1 Ohm (standar modul INA219)
arus_beban = ((float)raw_shunt * 0.0001f) * faktor_I;
} else {
i2c_error_flag = 1;
}
// Hindari pembacaan minus kecil saat tidak ada beban
if (arus_beban < 0.0f) arus_beban = 0.0f;
daya_total = v_baterai * arus_beban;
}
void Update_OLED(void) {
char buff_teks[25];
ssd1306_Fill(Black);
sprintf(buff_teks, "V:%.2fV", v_baterai); ssd1306_SetCursor(0, 0); ssd1306_WriteString(buff_teks, Font_7x10, White);
sprintf(buff_teks, "I:%.2fA", arus_beban); ssd1306_SetCursor(60, 0); ssd1306_WriteString(buff_teks, Font_7x10, White);
sprintf(buff_teks, "P:%.1fW", daya_total); ssd1306_SetCursor(0, 15); ssd1306_WriteString(buff_teks, Font_7x10, White);
sprintf(buff_teks, "T:%.1fC", suhu_inverter); ssd1306_SetCursor(60, 15); ssd1306_WriteString(buff_teks, Font_7x10, White);
sprintf(buff_teks, "%s", status_sistem); ssd1306_SetCursor(0, 30); ssd1306_WriteString(buff_teks, Font_7x10, White);
ssd1306_UpdateScreen();
}
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void) {
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* Configure the system clock */
SystemClock_Config();
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_ADC1_Init();
MX_I2C1_Init();
/* USER CODE BEGIN 2 */
ssd1306_Init();
INA219_Init();
// Tampilan awal diposisikan di tengah layar OLED
ssd1306_Fill(Black);
ssd1306_SetCursor(22, 27); // Koordinat diubah ke tengah (X=22, Y=27)
ssd1306_WriteString("SYSTEM READY", Font_7x10, White); // Teks diubah menjadi SYSTEM READY
ssd1306_UpdateScreen();
HAL_Delay(1000);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1) {
uint32_t tick_sekarang = HAL_GetTick();
if (tick_sekarang - tick_terakhir >= interval_baca) {
tick_terakhir = tick_sekarang;
if (i2c_error_flag == 1) Reset_I2C();
ldr_value = Baca_LDR();
Baca_MLX90614();
Baca_INA219();
if (i2c_error_flag == 0) {
// Logika Indikator Suhu (LED)
if (suhu_inverter < SUHU_NORMAL) {
HAL_GPIO_WritePin(GPIOA, LED_G_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOA, LED_Y_Pin | LED_R_Pin, GPIO_PIN_RESET);
} else if (suhu_inverter >= SUHU_NORMAL && suhu_inverter <= SUHU_PERINGATAN) {
HAL_GPIO_WritePin(GPIOA, LED_Y_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOA, LED_G_Pin | LED_R_Pin, GPIO_PIN_RESET);
} else {
HAL_GPIO_WritePin(GPIOA, LED_R_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOA, LED_G_Pin | LED_Y_Pin, GPIO_PIN_RESET);
}
// Proteksi (Low Voltage Disconnect & Overheat) + Buzzer
if ((v_baterai < LVD_THRESHOLD && v_baterai > 1.0f) || (suhu_inverter >= SUHU_PERINGATAN)) {
HAL_GPIO_WritePin(GPIOA, RELAY_RINGAN_Pin | RELAY_BERAT_Pin | BUZZER_Pin, GPIO_PIN_SET);
sprintf(status_sistem, (suhu_inverter >= SUHU_PERINGATAN) ? "TRIP: OVERHEAT" : "TRIP: LOW BATT");
} else {
HAL_GPIO_WritePin(GPIOA, BUZZER_Pin, GPIO_PIN_RESET);
// Logika Beban Berdasarkan LDR (Siang/Malam)
if (ldr_value > LDR_THRESHOLD_HIGH) {
HAL_GPIO_WritePin(GPIOA, RELAY_RINGAN_Pin | RELAY_BERAT_Pin, GPIO_PIN_RESET);
sprintf(status_sistem, "SIANG (FULL)");
} else {
HAL_GPIO_WritePin(GPIOA, RELAY_RINGAN_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOA, RELAY_BERAT_Pin, GPIO_PIN_SET);
sprintf(status_sistem, "MALAM (ECO)");
}
}
Update_OLED();
}
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
{
Error_Handler();
}
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_ADC;
PeriphClkInit.AdcClockSelection = RCC_ADCPCLK2_DIV2;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
{
Error_Handler();
}
}
/**
* @brief ADC1 Initialization Function
* @param None
* @retval None
*/
static void MX_ADC1_Init(void)
{
ADC_ChannelConfTypeDef sConfig = {0};
hadc1.Instance = ADC1;
hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
hadc1.Init.ContinuousConvMode = DISABLE;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.NbrOfConversion = 1;
if (HAL_ADC_Init(&hadc1) != HAL_OK)
{
Error_Handler();
}
/** Configure Regular Channel
*/
sConfig.Channel = ADC_CHANNEL_0;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
}
/**
* @brief I2C1 Initialization Function
* @param None
* @retval None
*/
static void MX_I2C1_Init(void)
{
hi2c1.Instance = I2C1;
hi2c1.Init.ClockSpeed = 100000;
hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
hi2c1.Init.OwnAddress1 = 0;
hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
hi2c1.Init.OwnAddress2 = 0;
hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
if (HAL_I2C_Init(&hi2c1) != HAL_OK)
{
Error_Handler();
}
}
/**
* @brief GPIO Initialization Function
* @param None
* @retval None
*/
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOA, LED_R_Pin|LED_Y_Pin|LED_G_Pin|BUZZER_Pin, GPIO_PIN_RESET);
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOA, RELAY_RINGAN_Pin|RELAY_BERAT_Pin, GPIO_PIN_SET);
/*Configure GPIO pins : LED_R_Pin LED_Y_Pin LED_G_Pin RELAY_RINGAN_Pin
RELAY_BERAT_Pin BUZZER_Pin */
GPIO_InitStruct.Pin = LED_R_Pin|LED_Y_Pin|LED_G_Pin|RELAY_RINGAN_Pin
|RELAY_BERAT_Pin|BUZZER_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
__disable_irq();
while (1)
{
}
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
1. Sensor MLX90614
Memantau titik panas (hotspot) pada inverter. Jika suhu naik, maka sistem akan memberikan peringatan melalui led merah, kuning, hijau
2. Sensor INA219
Memantau tegangan, arus, dan daya baterai secara realtime. Jika beban yang ditarik terlalu besar atau durasi penggunaan terlalu lama hingga menyebabkan tegangan baterai drop di bawah ambang batas kritis (misal < 11.V untuk baterai 12V), mikrokontroler akan mendeteksi kondisi Under-Voltage. Sistem langsung memerintahkan Relay terminal/pin out untuk trip
3. Sensor LDR
Sensor LDR (Light Dependent Resistor) memiliki fungsi spesifik sebagai sakelar otomatis berbasis intensitas cahaya (Photo-Switch) untuk menjalankan algoritma manajemen beban. Berikut adalah rincian fungsionalitas LDR jika diuraikan ke dalam konteks sistem Anda:
1. Pendeteksi Ketersediaan Suplai Energi: LDR bertugas memantau intensitas sinar matahari secara real-time. Data perubahan resistansi LDR dikonversi menjadi sinyal analog yang dibaca oleh pin ADC pada STM32 untuk menentukan apakah sistem sedang berada pada siklus produksi energi (siang) atau siklus penyimpanan murni (malam).
2. Pengatur Aktuasi Relai (Load Shedding): Berdasarkan status cahaya dari LDR, STM32 akan membagi jalur distribusi daya:
• Kondisi Siang (Intensitas Cahaya Tinggi): Karena panel surya aktif menyuplai daya secara maksimal, STM32 mengaktifkan pin aktuator untuk Relai Beban Berat (ON) dan Relai Beban Ringan (ON) secara bersamaan.
• Kondisi Malam (Intensitas Cahaya Rendah): Karena suplai daya hanya bergantung pada kapasitas baterai, STM32 akan memutus sirkuit Relai Beban Berat (OFF) dan membatasi agar hanya Relai Beban Ringan (ON) yang mendapatkan aliran listrik.
3. Pencegah Overload dan Over-discharge: Fungsi utama LDR pada sistem ini adalah membatasi penarikan arus listrik saat tidak ada energi yang dihasilkan oleh panel surya. Dengan memutus beban berat di malam hari, LDR memastikan baterai tidak mengalami pengurasan daya berlebih (overdischarge) dan mencegah beban lebih (overload) yang dapat merusak sistem kelistrikan.
(Jalur: I2C) Menampilkan parameter V, I, W baterai (dari INA219), kondisi panel surya pada siklus produksi energi (siang) atau siklus penyimpanan murni (malam).
Pada arsitektur pemantauan daya PLTS ini, Modul Traffic LED (Merah, Kuning, Hijau) bertindak sebagai sistem peringatan visual (visual warning system) yang merepresentasikan status termal inverter dari hasil pembacaan sensor suhu MLX90614. Fungsi spesifik dari masing-masing warna pada modul ini dirancang untuk memberikan informasi kondisi secara instan tanpa harus melihat angka pada layar:
• LED Hijau (Normal / Aman): Menyala ketika suhu inverter berada di bawah ambang batas (misalnya, di bawah 45°C). Ini mengindikasikan bahwa pelepasan panas (heat dissipation) pada inverter berjalan baik dan sistem beroperasi dalam kondisi termal yang optimal.
• LED Kuning (Waspada / Peringatan Dini): Menyala ketika suhu inverter mulai meningkat dan memasuki zona transisi (misalnya, antara 45°C hingga 65°C). Ini adalah sinyal peringatan dini bahwa beban atau suhu lingkungan sedang tinggi, sehingga teknisi atau operator bisa waspada sebelum terjadi masalah.
• LED Merah (Kritis / Overheat): Menyala saat suhu telah melewati batas maksimal toleransi operasional (misalnya, di atas 65°C). Indikator ini sangat krusial karena menandakan adanya risiko thermal runaway atau kerusakan komponen internal inverter akibat panas berlebih.
Modul Relay berfungsi sebagai aktuator pemutus dan penyambung arus listrik (sakelar elektromekanis) yang menerima perintah langsung dari mikrokontroler STM32. Secara spesifik, fungsi modul relay dalam sistem manajemen daya ini dapat dijabarkan sebagai berikut:
• Eksekutor Manajemen Beban (Load Shedding Actuator): Ini adalah fungsi utamanya. Berdasarkan hasil pembacaan sensor LDR (siang/malam), mikrokontroler akan mengirimkan sinyal ke modul relay. Relay inilah yang secara fisik membuka (memutus) atau menutup (menyambung) sirkuit aliran daya dari baterai/inverter menuju perangkat beban listrik. Relay menerjemahkan logika software STM32 menjadi tindakan hardware.
• Pemisah Beban Berat dan Ringan: Dalam sistem Anda, Anda akan menggunakan minimal dua kanal (channel) relay:
a) Relay 1 (Beban Ringan): Tetap dalam kondisi tersambung (NC - Normally Closed atau diaktifkan terus) baik siang maupun malam.
b) Relay 2 (Beban Berat): Dihidupkan di siang hari saat suplai matahari melimpah, dan diputus secara otomatis di malam hari untuk mencegah over-discharge pada baterai.
Komentar
Posting Komentar