# 電池管理系統(Power Memage Unit)
***說明:用MCU做鋰電池狀態監控,同時控制其他DC電源系統的開關***
###### tags: `省電` `Arduino' 'Arduino Pro Mini` `I2C`
## 材料
1. MCU:[Arduino Mini Pro(8Mhz 3.3V)](https://www.sparkfun.com/products/11114)
2. Module:
* 電源監控:INA219
* DC迴路開關控制:n-MOSFet
## 架構
### Arduino Pro Mini 的功能
1. 控制某個mcu的電源迴路:使用MOSFet(p-chennel of n-chennel)
2. 提供I2C介面讓MCU來查詢/設定下列功能(以library形式打包):
* 設定I2C Address
* 設定下次喚醒間隔??秒
* 是否進入睡眠
* 是否監控電池電壓:電池電壓太低時延長喚醒週期
* 設定啟動腳位
* 設定INA219的Address
3. 控制流程(Ver 1.0)
1. 不同階段閃爍不同的LED燈 (P13,6.5mA)
* I2C Master 階段 (2短(200ms)/loop, 300mS間隔)
* I2C Slaver 階段 (1短(200ms)/loop)
* 讀取config 階段 (1長(1000ms))
* 睡眠 階段 (關閉)
2. 開機後先進入Master階段讀取config及INA219
3. 開啟MCU電源後,立刻進入slaver階段等待MCU下指令
4. MCU開機進行Setup
5. MCU再Loop結束前須決定是否要轉移給PMU控制電源/PMU控制+休眠/MCU持續運作
6. PMU收到關閉指令後,立即把I2C切換成Master
7. PMU睡醒後須先讀取INA219,按照設定決定要不要啟動MCU

### 控制迴路
1.持續供電: Arduino Pro Mini
2.控制迴路A:INA219
3.控制迴路B:LinkIt7697
4.控制迴路C:SIMCOM700E /
---
## Arduino Mini Pro 省電模式測試
* [參考來源](http://www.home-automation-community.com/arduino-low-power-how-to-run-atmega328p-for-a-year-on-coin-cell-battery/)
* [使用library:RoketScream](https://github.com/rocketscream/Low-Power)
* BOD的說明(TODO:待測試)
* [參考來源:BOD](http://ww1.microchip.com/downloads/en/devicedoc/doc7903.pdf)
* 目前測試下免兩個差異不大
> *(Timer 2 還不知道怎麼用 orz)*
> * powerDown(period_t period, adc_t adc, bod_t bod)
> * powerSave(period_t period, adc_t adc, bod_t bod, timer2_t timer2)
### 使用Raw腳供電(4-12V輸入)
* [Sparkfun 教學](https://learn.sparkfun.com/tutorials/using-the-arduino-pro-mini-33v)

### 睡眠測試:1.359mA, 未拆LED及線性穩壓器(LDO)
```cpp=1
// **** INCLUDES *****
#include "LowPower.h"
#define LED 13
uint16_t c = 0;
// 控制LED閃爍的funciton
void LEDFlash(uint8_t _ts)
{
for (uint8_t _i = 0; _i < _ts; _i++)
{
digitalWrite(LED, HIGH);
delay(250);
digitalWrite(LED, LOW);
delay(250);
}
}
void setup()
{
Serial.begin(115200);
pinMode(LED, OUTPUT);
LEDFlash(3);
}
void loop()
{
Serial.print(c);
Serial.print(F("-開始睡眠: "));
switch (c % 3)
{
case 0:
Serial.print(F("Power Down Mode"));
delay(500); // 預留一些時間讓uart 能把資料吐完
LowPower.powerDown(SLEEP_4S, ADC_OFF, BOD_OFF); // 進入睡眠模式
break;
case 1:
Serial.print(F("Power Save Mode"));
delay(500);
LowPower.powerSave(SLEEP_4S, ADC_OFF, BOD_OFF, TIMER2_OFF);
break;
default:
Serial.print(F("active Mode"));
delay(500);
delay(4000);
}
Serial.println(F("\t-起床"));
LEDFlash(3);
c++;
}
```
## 測試:Arduino Mini Pro
### Upload:
* 編譯的時候先按下RESET鈕,看到Uploading時再放開就可以上傳
### SoftReset:
```cpp=1
// 重新啟動
// Restarts program from beginning but does not reset the peripherals and registers
void vSoftwareReset(void)
{
asm __volatile__(" jmp 0");
}
```
### SRAM size
* [參考來源](https://playground.arduino.cc/Code/AvailableMemory/?fbclid=IwAR0qzHze5c3ZWY6_otxIAVZlpPmcJ-2MNviBiAMuyr91_M39ZtlslZf-_LI)
```cpp=1
// 剩餘的SRAM
// Arduino Mini Pro 共有2048bytes空間(2K)
int iGetFreeRam(void)
{
extern int _heap_start, * __brkval;
int v;
return (int)&v - (__brkval == 0 ? (int)&__malloc_heap_start : (int)__brkval);
}
```
### I2C communication
* [參考來源](https://www.arduino.cc/en/reference/wire)
*
## 測試:PMU端
### PMU端 WaterBox_PMU.h 檔
```cpp=1
#include <Arduino.h>
#ifndef _WATERBOX_PMU_
#define _WATERBOX_PMU_
#define _I2C_BUFFER_SIZE 30
#define COMMUNICATIONMODULE
enum POWER
{
OFF,
ON
};
enum STATE
{
MASTER,
SLEEP,
SLAVER,
};
enum UART
{
NONE,
EOL,
H_PMU,
H_I2C,
H_CMD
};
enum IC2Request {
Req_NONE, // 無Request
Req_ERROR, // 錯誤指令
Req_TIME, // 設定鬧鐘(秒), 回傳OK -> timer ? sec
Req_SLEEP, // 進入睡眠, 不回傳
Req_LOWPOWER, // 省電模式, 回傳OK->LowPower ON/OFF
Req_BAT_VOLATE, // 電池電壓, 回傳OK->volate
Req_BAT_CURRENT, // 電池電壓, 回傳OK->current
Req_AT // AT Cmd( 給外面的NBIOT用)
};
class WaterBox_PMU
{
public:
WaterBox_PMU(); // 建構函式
WaterBox_PMU(uint8_t _addr); // 建構函式(指定 Slaver時的I2C Address)
~WaterBox_PMU(); // 解構函式
void init(uint16_t _pin);
void setINA219(uint8_t _addr);
static void setDebuger(Stream& refSer); // 設定debug用的輸出
static void setWakeUpVolate(float _v); // 設定喚醒電壓
static void setSleepSec(uint32_t _sec);
static uint8_t Sleep(); // 開始睡眠:切換到Master,關閉供電後開始進入睡眠循環,循環結束時更新電池狀態,低於喚醒電源後再開始供電返回1 (可以用while(Sleep())一值睡
static void PowerSaveMode(POWER _state);
static void ControlPower(POWER _state);
static void getBetteryState(); // 取得電池狀態:切換到Master,呼叫INA219拿資料後更新變數
static void LED(uint16_t _times, uint16_t _interval);
static float Volate; // 電池上次的電壓
static float Current; // 電池上次的電流量
static enum STATE stete;
#ifdef COMMUNICATIONMODULE
static String ATCMD; // 給NBIOT的Command
static void ATClear();
#endif // COMMUNICATIONMODULE
private:
static uint8_t _Debug;
static uint16_t _ControlPin;
static Stream& refSerial;
static uint8_t _Addr;
uint8_t _INA219Addr = 0x40;
//uint8_t _SleepMode; // 未使用
static uint8_t _PowerSave; // 定義是不是要進入省電模式
static uint16_t _SleepSec; // 睡眠秒數
static uint16_t _i_for;
static float _WakeUpVoltage;
static void _SwitchToSlaver(uint8_t _addr);
static void _receiveEvent(int howMany);
static void _requestEvent();
static void _cmd(String _str);
static String _ComStr;
static String _recBuffer;
static IC2Request _REQUEST;
static void _Deguber(String _msg, UART _header, UART _uart = NONE);
};
#endif
```
### PMU端 WaterBox_PMU.cpp 檔
```cpp=1
#include "WaterBox_PMU.h"
#include <Wire.h>
#include <LowPower.h>
#include <Adafruit_INA219.h>
#define SLEEPTIME SLEEP_1S
uint8_t WaterBox_PMU::_Debug;
Stream& WaterBox_PMU::refSerial = Serial;
uint16_t WaterBox_PMU::_i_for;
uint8_t WaterBox_PMU::_PowerSave = false;
uint16_t WaterBox_PMU::_SleepSec;
IC2Request WaterBox_PMU::_REQUEST;
String WaterBox_PMU::_ComStr;
String WaterBox_PMU::_recBuffer;
float WaterBox_PMU::_WakeUpVoltage = 3.6;
float WaterBox_PMU::Volate;
float WaterBox_PMU::Current;
enum STATE WaterBox_PMU::stete = MASTER;
uint16_t WaterBox_PMU::_ControlPin;
uint8_t WaterBox_PMU::_Addr = 0x70;
#ifdef COMMUNICATIONMODULE
String WaterBox_PMU::ATCMD;
#endif // COMMUNICATIONMODULE
WaterBox_PMU::WaterBox_PMU() {
}
WaterBox_PMU::WaterBox_PMU(uint8_t _addr) {
_Addr = _addr;
}
WaterBox_PMU::~WaterBox_PMU() {
}
void WaterBox_PMU::init(uint16_t _pin) {
_ControlPin = _pin;
pinMode(_ControlPin, OUTPUT);
}
void WaterBox_PMU::setINA219(uint8_t _addr) {
_INA219Addr = _addr;
_Deguber(F("Setting INA219 I2C address: 0x"), H_PMU);
_Deguber(String(_INA219Addr, HEX), EOL);
}
void WaterBox_PMU::setDebuger(Stream& refSer) {
_Debug = true;
refSerial = refSer;
}
void WaterBox_PMU::setWakeUpVolate(float _v) {
_WakeUpVoltage = _v;
_Deguber(F("Setting WAKE UP Volate: "), H_PMU);
_Deguber(String(_WakeUpVoltage), NONE);
_Deguber(F(" V."), NONE, EOL);
}
void WaterBox_PMU::setSleepSec(uint32_t _sec) {
_SleepSec = _sec;
_Deguber(F("Setting Sleep Time: "), H_PMU);
_Deguber(String(_SleepSec), NONE);
_Deguber(F(" sec."), NONE, EOL);
}
// 開始睡眠:切換到Master,關閉供電後開始進入睡眠循環,循環結束時更新電池狀態,低於喚醒電源後再開始供電返回1 (可以用while(Sleep())一值睡
uint8_t WaterBox_PMU::Sleep() {
LED(1,700);
_Deguber(F("Prepare SLEEP Mode"), H_PMU, EOL);
_Deguber(F("Off the LED & Control POWER"), H_PMU, EOL);
digitalWrite(LED_BUILTIN, LOW);
ControlPower(OFF);
_Deguber(F("Enter SLEEP Mode"), H_PMU, EOL);
delay(100);
for (_i_for = 0; _i_for < _SleepSec; _i_for++)
{
if (_PowerSave) LowPower.powerDown(SLEEPTIME, ADC_OFF, BOD_OFF);
else delay(1000);
}
stete = MASTER;
_Deguber(F("Waker up, init I2C with Master and get bettery state..."), H_PMU, EOL);
ControlPower(ON);
Wire.begin();
getBetteryState();
ControlPower(OFF);
if (Volate < _WakeUpVoltage)
{
_Deguber(F("ERROR: Volate < Wake up voltage"), H_PMU);
_Deguber(String(_WakeUpVoltage), NONE, EOL);
return 1;
}
else
{
ControlPower(ON);
_Deguber(F(" I2C --> Slaver"), H_PMU, EOL);
_SwitchToSlaver(_Addr);
stete = SLAVER;
return 0;
}
}
void WaterBox_PMU::PowerSaveMode(POWER _state) {
if (_state == ON) _Deguber(F("Power Save Mode: ON"), H_PMU, EOL);
else if (_state == OFF) _Deguber(F("Power Save Mode: OFF"), H_PMU, EOL);
_PowerSave = _state;
}
void WaterBox_PMU::ControlPower(POWER _state) {
if (_state == ON) {
digitalWrite(_ControlPin, HIGH);
_Deguber(F("Control: ON"), H_PMU, EOL);
}
else if (_state == OFF) {
digitalWrite(_ControlPin, LOW);
_Deguber(F("Control: OFF"), H_PMU, EOL);
}
}
void WaterBox_PMU::getBetteryState() {
Adafruit_INA219 bettery(INA219_ADDRESS);
bettery.begin();
// To use a slightly lower 32V, 1A range (higher precision on amps):
//ina219.setCalibration_32V_1A();
// Or to use a lower 16V, 400mA range (higher precision on volts and amps):
bettery.setCalibration_16V_400mA();
Volate = bettery.getBusVoltage_V();
Current = bettery.getCurrent_mA();
_Deguber(F("Bettery Volate: "), H_PMU);
_Deguber(String(Volate), NONE);
_Deguber(F(", Current: "), NONE);
_Deguber(String(Current), NONE, EOL);
}
void WaterBox_PMU::LED(uint16_t _times, uint16_t _ratio) {
pinMode(LED_BUILTIN, OUTPUT);
for (_i_for = 0; _i_for < _times; _i_for++) {
digitalWrite(LED_BUILTIN, HIGH);
delay(_ratio);
digitalWrite(LED_BUILTIN, LOW);
delay(300);
}
}
void WaterBox_PMU::ATClear()
{
ATCMD = "";
}
void WaterBox_PMU::_Deguber(String _msg, UART _header, UART _uart = NONE) {
if (_Debug) {
switch (_header)
{
case H_PMU:
refSerial.print(F("[PMU]\t"));
break;
case H_I2C:
refSerial.print(F("[I2C]\t"));
break;
case H_CMD:
refSerial.print(F("[CMD]\t"));
break;
default:
break;
}
switch (_uart)
{
case NONE:
refSerial.print(_msg);
break;
case EOL:
refSerial.println(_msg);
break;
default:
break;
}
}
}
void WaterBox_PMU::_requestEvent()
{
switch (_REQUEST)
{
case Req_NONE:
_recBuffer = F("No String");
break;
case Req_TIME:
_recBuffer = F("OK,timer -> ");
_recBuffer += String(_SleepSec);
_recBuffer += F(" sec");
break;
case Req_SLEEP:
_recBuffer = F("Sleep");
break;
case Req_LOWPOWER:
_recBuffer = F("OK, LowPower -> ");
_recBuffer += String(_PowerSave);
break;
case Req_BAT_VOLATE:
_recBuffer = F("OK,");
_recBuffer += String(Volate);
break;
case Req_BAT_CURRENT:
_recBuffer = F("OK,");
_recBuffer += String(Current);
break;
case Req_AT:
_recBuffer = F("OK");
break;
default:
_recBuffer = F("Command ERROR");
break;
}
uint16_t _BufferSize = _recBuffer.length();
for (_i_for = 0; _i_for + _BufferSize < _I2C_BUFFER_SIZE; _i_for++)
{
_recBuffer += F(" ");
}
_Deguber(_recBuffer, H_I2C, EOL);
_Deguber("", NONE, EOL);
Wire.write(_recBuffer.c_str());
}
void WaterBox_PMU::_receiveEvent(int howMany)
{
_ComStr = "";
while (Wire.available())
{
char _c_ = Wire.read();
_ComStr += String(_c_);
}
LED(1,300);
_cmd(_ComStr);
}
void WaterBox_PMU::_cmd(String _str)
{
/*
TIME,sec // 設定鬧鐘(秒)
SLEEP // 進入睡眠
LOWPOWER,1 // 省電模式
VOLATE // 回傳 volate
CURRENT // 回傳 current
ATCMD,***** // 輸入AT Commend 資料
*/
_str.toUpperCase();
_ComStr = _str.substring(_str.indexOf(F(",")) + 1); // 重新利用_ComStr
if (_str.length() > 0) _REQUEST = Req_ERROR;
else _REQUEST = Req_NONE;
if (_str.indexOf(F("TIME")) > -1)
{
_Deguber(F("TIME -> "), H_CMD);
_Deguber(_ComStr, NONE, EOL);
_SleepSec= _ComStr.toInt();
setSleepSec(_SleepSec);
_REQUEST = Req_TIME;
}
if (_str.indexOf(F("SLEEP")) > -1)
{
_Deguber(F("Enter SLEEP"), H_CMD,EOL);
Sleep();
_REQUEST = Req_SLEEP;
}
if (_str.indexOf(F("LOWPOWER")) > -1)
{
_Deguber(F("LOWPOWER -> "), H_CMD);
_Deguber(_ComStr, NONE, EOL);
_PowerSave = _ComStr.toInt();
PowerSaveMode(POWER(_PowerSave));
_REQUEST = Req_LOWPOWER;
}
if (_str.indexOf(F("VOLATE")) > -1)
{
_Deguber(F("GET BATTERY VOLATE"), H_CMD, EOL);
_REQUEST = Req_BAT_VOLATE;
}
if (_str.indexOf(F("CURRENT")) > -1)
{
_Deguber(F("GET BATTERY CURRENT"), H_CMD, EOL);
_REQUEST = Req_BAT_CURRENT;
}
if (_str.indexOf(F("ATCMD")) > -1)
{
ATClear();
_Deguber(F("ATCMD >> "), H_CMD);
_Deguber(_ComStr, NONE, EOL);
ATCMD = _ComStr;
_REQUEST = Req_AT;
}
}
void WaterBox_PMU::_SwitchToSlaver(uint8_t _addr) {
Wire.begin(_addr);
Wire.onRequest(&WaterBox_PMU::_requestEvent); // I2C Master的指令:回傳
Wire.onReceive(&WaterBox_PMU::_receiveEvent); // I2C Master的指令:不回傳
}
```
### PMU端 *.ino 檔
```cpp=1
/*
Name: PowerManagUnit.ino
Created: 2020/2/18 下午 07:37:23
Author: Liu
*/
#include "WaterBox_PMU.h"
#define CONTROL_PIN 10
#include <SoftwareSerial.h>
#define UART_TX 8
#define UART_RX 9
WaterBox_PMU PMU;
static int _c = 0;
void showState()
{
Serial.print(F("PMU STATE >> "));
switch (PMU.stete)
{
case MASTER:
Serial.println(F("MASTER"));
break;
case SLEEP:
Serial.println(F("SLEEP"));
break;
case SLAVER:
Serial.println(F("SLAVER"));
break;
default:
break;
}
}
void setup()
{
Serial.begin(115200);
PMU.init(CONTROL_PIN);
PMU.setDebuger(Serial);
PMU.setSleepSec(5);
PMU.setWakeUpVolate(3.5);
PMU.PowerSaveMode(ON);
Serial.println(F("Setup Done"));
showState();
PMU.Sleep();
showState();
}
void loop()
{
//Serial.print(F("Loop "));
//Serial.println(_c);
if (PMU.ATCMD.length() > 0)
{
Serial.println(F("[LOOP]\t"));
Serial.print(F("[AT] \t"));
Serial.println(PMU.ATCMD);
PMU.ATClear();
PMU.LED(3,200);
}
_c++;
//if(_c%50==0) PMU.Sleep();
//else delay(1000);
}
```
## 測試:7697端(受控制端)
### 受控制端 WaterBox_PowerSaving.h
```cpp=1
#include <Arduino.h>
#ifndef _WATERBOX_POWERSAVING_
#define _WATERBOX_POWERSAVING_
#define RequestBufferSize (uint8_t)30
class WaterBox_PowerSaving
{
public:
WaterBox_PowerSaving(uint8_t _addr);
~WaterBox_PowerSaving();
enum POWER
{
NORMAL, // 對照OFF
LOWPOWER // 對照ON
};
void init();
void setDebuger(Stream& refSer); // 設定Deguber
void setSleepSec(uint32_t _sec); // 設定睡眠時間(秒)
void setSleepMode(POWER _state); // 設定睡眠模式
void sendData(String _data);
void getState();
void Sleep(); // 進入睡眠狀態
static void _Receive(uint8_t _addr, String _cmd); // I2C Master的指令:回傳
static void _Reques(uint8_t _addr); // I2C Master的指令:不回傳
static String ReqString; // I2C 回傳字串
static float Volate; // 系統電壓
static float Current; // 系統電池
private:
enum UART
{
NONE,
EOL,
H_PMU,
H_I2C,
H_CMD
};
struct COMMAND
{
String TIME = F("TIME,");
String AT = F("ATCMD,");
String SLEEP = F("SLEEP");
String VOLATE = F("VOLATE");
String CURRENT = F("CURRENT");
String LOWPOWER = F("LOWPOWER,");
};
static uint8_t _Debug;
static Stream& refSerial;
static uint8_t _Addr;
static String _CMDBuffer;
static void _sendCMD(String _cmd, uint8_t _req = false);
static void _Deguber(String _msg, UART _header, UART _uart = NONE);
COMMAND _cmd;
};
#endif
```
### 受控制端 WaterBox_PowerSaving.cpp
```cpp=1
#include "WaterBox_PowerSaving.h"
#include <Wire.h>
#include <Adafruit_INA219.h>
uint8_t WaterBox_PowerSaving::_Debug = false;
Stream& WaterBox_PowerSaving::refSerial = Serial;
uint8_t WaterBox_PowerSaving::_Addr = 0x70;
String WaterBox_PowerSaving::_CMDBuffer = "";
String WaterBox_PowerSaving::ReqString = "";
WaterBox_PowerSaving::WaterBox_PowerSaving(uint8_t _addr) {
_Addr = _addr;
}
WaterBox_PowerSaving::~WaterBox_PowerSaving() {
}
void WaterBox_PowerSaving::init() {
Wire.begin();
}
void WaterBox_PowerSaving::setDebuger(Stream& refSer) {
_Debug = true;
refSerial = refSer;
}
void WaterBox_PowerSaving::setSleepSec(uint32_t _sec) {
_CMDBuffer = _cmd.TIME + String(_sec);
_sendCMD(_CMDBuffer, true);
}
void WaterBox_PowerSaving::setSleepMode(POWER _state) {
_CMDBuffer = _cmd.LOWPOWER + String(_state);
_sendCMD(_CMDBuffer, true);
}
void WaterBox_PowerSaving::sendData(String _data) {
_CMDBuffer = _cmd.AT + _data;
_sendCMD(_CMDBuffer, true);
}
void WaterBox_PowerSaving::getState() {
_sendCMD(_cmd.VOLATE, true);
_sendCMD(_cmd.CURRENT, true);
}
void WaterBox_PowerSaving::Sleep()
{
_sendCMD(_cmd.SLEEP, false);
}
void WaterBox_PowerSaving::_Receive(uint8_t _addr, String _cmd) {
Wire.beginTransmission(_addr);
Wire.write(_cmd.c_str());
Wire.endTransmission();
}
void WaterBox_PowerSaving::_Reques(uint8_t _addr) {
Wire.requestFrom(_addr, RequestBufferSize);
ReqString = "";
while (Wire.available())
{
char c = Wire.read();
ReqString += String(c);
}
_Deguber(ReqString, H_I2C, EOL);
}
void WaterBox_PowerSaving::_sendCMD(String _cmd, uint8_t _req)
{
_Deguber(_cmd, H_CMD, EOL);
_Receive(_Addr, _cmd);
delay(10); // 必須留時間做為緩衝
if (_req) _Reques(_Addr);
}
void WaterBox_PowerSaving::_Deguber(String _msg, UART _header, UART _uart) {
if (_Debug) {
switch (_header)
{
case H_PMU:
refSerial.print(F("[PMU]\t"));
break;
case H_I2C:
refSerial.print(F("[I2C]\t"));
break;
case H_CMD:
refSerial.print(F("[CMD]\t"));
break;
default:
break;
}
switch (_uart)
{
case NONE:
refSerial.print(_msg);
break;
case EOL:
refSerial.println(_msg);
break;
default:
break;
}
}
}
```
### 受控制端 *.ino
```cpp=1
/*
Name: PowerControled.ino
Created: 2020/3/2 上午 11:49:52
Author: Liu
*/
#include "WaterBox_PowerSaving.h"
WaterBox_PowerSaving PSM(0x70);
void setup() {
Serial.begin(9600);
PSM.init();
PSM.setDebuger(Serial);
}
uint8_t _c = 0;
void loop()
{
switch (_c % 6)
{
case 0:
Serial.println("設定睡眠模式");
PSM.setSleepMode(PSM.NORMAL);
break;
case 1:
Serial.println("設定睡眠秒數:增加");
PSM.setSleepSec(30 + _c);
break;
case 2:
Serial.println("讀取狀態");
PSM.getState();
break;
case 3:
Serial.println("設定睡眠模式:低功耗");
PSM.setSleepMode(PSM.LOWPOWER);
break;
case 4:
Serial.println("AT 指令");
PSM.sendData("AT=123456\n\r");
break;
case 5:
Serial.println("開始睡眠");
PSM.Sleep();
delay(50000);
break;
}
_c++;
if (_c > 20) _c = 0;
delay(5000);
}
```
{"metaMigratedAt":"2023-06-15T03:34:40.421Z","metaMigratedFrom":"Content","title":"電池管理系統(Power Memage Unit)","breaks":true,"contributors":"[{\"id\":\"3bb96581-f11e-4e8b-8446-0937f0304d6f\",\"add\":37633,\"del\":18756}]"}