В автономных устройствах при работе от аккумуляторов прошивка должна иметь информацию о значениях таких параметров, как относительный заряд аккумулятора, время до полного разряда аккумулятора, других параметров. Наибольшую точность и надежность может обеспечить использование умных аккумуляторов.
Используемый нами умный аккумулятор включает в себя микросхему TI bq20z90, которая обеспечивает поддержку технологии SBS - Smart Battery System - система умного аккумулятора. SBS обеспечивает передачу информации о состоянии аккумулятора на другие устройства и изменение значений некоторых параметров. Обмен данными между умным аккумулятором и микроконтроллером устройства происходит по интерфейсу SMBUS v.1.1.
Изменяемые параметры доступны для редактирования разработчиком аккумулятора при переводе аккумулятора в режим "полного доступа" (Full Access) или "разблокирован" (Unsealed). В режиме "заблокирован" (Sealed) изменение параметров недоступно. После изменения значений параметров может потребоваться перекалибровка аккумулятора.
Для работы с двумя аккумуляторами их необходимо подключить к микроконтроллеру не напрямую, а через I2C-хаб (по причине совпадающих адресов). I2c-хаб позволяет выбрать один, второй, оба или ни одного аккумулятора для обмена данными. Нужно выбирать поочередно первый и второй аккумулятор.
Для задачи мониторинга состояния аккумулятора необходимо только вычитывание параметров. Из списка протоколов SMBUS необходимо реализовать Read Byte Protocol, Read Word Protocol.
В общем виде, программирование умного аккумулятора на микроконтроллере заключается в реализации протоколов SMBUS v.1.1 Read Byte Protocol, Read Word Protocol через использование функций драйвера для интерфейса TWI / I2C / SMBUS микроконтроллера: I2cSend1Byte, I2cReceive1Byte, I2cReceive2Bytes.
Функция I2cSend1Byte(u8Address, u8Data) отправляет 1 байт данных в устройство по указанному адресу.
Функции I2cReceive1Byte(u8Address, &u8Data) и I2cReceive2Bytes(u8Address, &u8Data0, &u8Data1) принимают 1 и 2 байта, соответственно, от устройства по указанному адресу.
int main(void) { ... InitI2cMaster(C_I2cBitRateInKhz, ClockSystem.u16CpuClockInKhz); ... // send command to get RelativeStateOfCharge - 1 byte I2cSend1Byte(u8AddressBattery, u8CommandToGetRelativeStateOfCharge); // read RelativeStateOfCharge - 1 byte I2cReceive1Byte(u8AddressBattery, &u8RelativeStateOfCharge); ... // send command to get AverageCurrent - 1 byte I2cSend1Byte(u8AddressBattery, u8CommandToGetAverageCurrent); // read AverageCurrent - 2 bytes I2cReceive2Bytes(u8AddressBattery, &u8AverageCurrentLsb, &u8AverageCurrentMsb); ... }
bool InitI2cMaster(U16 u16BitRateInKhz, U32 u32SystemClockInKhz) { // test for correct input frequency if ((u32SystemClockInKhz / 2 / u16BitRateInKhz - 5) < 0) { return false; } TWIC.CTRL = 0; // clear common register // enable TWI / I2C / SMBUS TWIC.MASTER.CTRLA = TWI_MASTER_ENABLE_bm; // set timeout for SMBUS compatibility TWIC.MASTER.CTRLB = TWI_MASTER_TIMEOUT_50US_gc; TWIC.MASTER.CTRLC = 0; // force initial bus state to idle TWIC.MASTER.STATUS = TWI_MASTER_BUSSTATE_IDLE_gc; // set baud rate TWIC.MASTER.BAUD = u32SystemClockInKhz / 2 / u16BitRateInKhz - 5; return true; }
bNextOpChangeDirection - необходимость смены направления передачи данных после этой команды.
bNotStandard - вместо бита повторного старта обмена данными используются биты стоп и старт передачи данных.
bool I2cSend1Byte(U8 u8Address, U8 u8Data, bool bNextOpChangeDirection, bool bNotStandard) { U16 u16TimeoutCounter; if (GetI2cBusState() == TWI_MASTER_BUSSTATE_BUSY_gc) { // aribtration lost if (IsI2cArbLost()) { TWIC.MASTER.STATUS = TWI_MASTER_ARBLOST_bm; } TWIC.MASTER.CTRLC = TWI_MASTER_CMD_STOP_gc; return false; } // send slave address to the I2C bus TWIC.MASTER.ADDR = u8Address & ~0x01; // check bus state after address sent u16TimeoutCounter = 0; while (u16TimeoutCounter++ <= C_MaxTimeout) { // aribtration lost if (IsI2cWif() && IsI2cArbLost()) { TWIC.MASTER.STATUS = TWI_MASTER_ARBLOST_bm; TWIC.MASTER.CTRLC = TWI_MASTER_CMD_STOP_gc; return false; } // slave not acknoledge if (IsI2cWif() && IsI2cRxAck()) { TWIC.MASTER.CTRLC = TWI_MASTER_CMD_STOP_gc; return false; } // slave acknoledge if (IsI2cWif() && !IsI2cRxAck()) { break; } } // no activity on I2C bus if (u16TimeoutCounter == C_MaxTimeout) { TWIC.MASTER.CTRLC = TWI_MASTER_CMD_STOP_gc; return false; } TWIC.MASTER.DATA = u8Data; u16TimeoutCounter = 0; while (u16TimeoutCounter++ <= C_MaxTimeout) { // aribtration lost if (IsI2cArbLost()) { TWIC.MASTER.CTRLC = TWI_MASTER_CMD_STOP_gc; return false; } // slave not acknowledge if (IsI2cWif() && IsI2cRxAck()) { TWIC.MASTER.CTRLC = TWI_MASTER_CMD_STOP_gc; return false; } // data is sent successfully if (IsI2cWif() && IsI2cClkHold()) { break; } } // no activity on I2C bus if (u16TimeoutCounter == C_MaxTimeout) { TWIC.MASTER.CTRLC = TWI_MASTER_CMD_STOP_gc; return false; } if (bNextOpChangeDirection) { if (bNotStandard) { // for used smart battery chip // no repeat start command, just stop and start again TWIC.MASTER.CTRLC = TWI_MASTER_CMD_STOP_gc; } else { // for standard SMbus 1.1 // repeated start TWIC.MASTER.CTRLC = TWI_MASTER_CMD_REPSTART_gc; } // delay between stop and start for (u16TimeoutCounter = 0; u16TimeoutCounter < C_MaxTimeout; u16TimeoutCounter++); } else { // stop data transmition TWIC.MASTER.CTRLC = TWI_MASTER_CMD_STOP_gc; } return true; }
bool I2cReceive1Byte(U8 u8Address, U8 *u8Data) { U16 u16TimeoutCounter; if (GetI2cBusState() == TWI_MASTER_BUSSTATE_BUSY_gc) { // aribtration lost if (IsI2cArbLost()) { TWIC.MASTER.STATUS = TWI_MASTER_ARBLOST_bm; } TWIC.MASTER.STATUS = TWI_MASTER_BUSSTATE_IDLE_gc; return false; } // send slave address to the I2C bus TWIC.MASTER.ADDR = u8Address | 0x01; // check bus state after address sent u16TimeoutCounter = 0; while (u16TimeoutCounter++ <= C_MaxTimeout) { // aribtration lost if (IsI2cWif() && IsI2cArbLost()) { TWIC.MASTER.STATUS = TWI_MASTER_ARBLOST_bm; TWIC.MASTER.CTRLC = TWI_MASTER_CMD_STOP_gc; return false; } // slave not acknowledge if (IsI2cRif() && IsI2cRxAck()) { TWIC.MASTER.CTRLC = TWI_MASTER_CMD_STOP_gc; return false; } // slave acknowledge if (IsI2cRif() && !IsI2cRxAck()) { break; } } // no activity on I2C bus if (u16TimeoutCounter == C_MaxTimeout) { TWIC.MASTER.CTRLC = TWI_MASTER_CMD_STOP_gc; return false; } u16TimeoutCounter = 0; while (u16TimeoutCounter++ <= C_MaxTimeout) { // data is ready to read if (IsI2cRif() && IsI2cClkHold()) { break; } } // no activity on I2C bus if (u16TimeoutCounter == C_MaxTimeout) { TWIC.MASTER.CTRLC = TWI_MASTER_CMD_STOP_gc; return false; } // get data byte *u8Data = TWIC.MASTER.DATA; // send nack to indicate no further read allowed // and send stop to finish transfer TWIC.MASTER.CTRLC = TWI_MASTER_ACKACT_bm | TWI_MASTER_CMD_STOP_gc; return true; }
bool I2cReceive2Bytes(U8 u8Address, U8 *u8Data0, U8 *u8Data1) { ... // get the first data byte *u8Data0 = TWIC.MASTER.DATA; // send acknowledge TWIC.MASTER.CTRLC = TWI_MASTER_CMD_RECVTRANS_gc; u16TimeoutCounter = 0; while (u16TimeoutCounter++ <= C_MaxTimeout) { // data is ready to read if (IsI2cRif() && IsI2cClkHold()) { break; } } // no activity on I2C bus if (u16TimeoutCounter == C_MaxTimeout) { TWIC.MASTER.CTRLC = TWI_MASTER_CMD_STOP_gc; return false; } // get the second data byte *u8Data1 = TWIC.MASTER.DATA; // send nack to indicate no further read allowed // and send stop to finish transfer TWIC.MASTER.CTRLC = TWI_MASTER_ACKACT_bm | TWI_MASTER_CMD_STOP_gc; return true; }
bool ProcessSbsCommandS1bR1b(U8 u8Address, U8 u8Data2Send, U8 *u8Data0) { U16 u16TimeoutCounter; bool bResult = false; // send command - 1 byte bResult = I2cSend1Byte(u8Address, u8Data2Send, true, true); if (!bResult) { return bResult; } // read data - 1 byte bResult = I2cReceive1Byte(u8Address, u8Data0); return bResult; }
bool ProcessSbsCommandS1bR2b(U8 u8Address, U8 u8Data2Send, U8 *u8Data0, U8 *u8Data1) { U16 u16TimeoutCounter; bool bResult = false; // send command - 1 byte bResult = I2cSend1Byte(u8Address, u8Data2Send, true, true); if (!bResult) { return bResult; } // read data - 2 byte bResult = I2cReceive2Bytes(u8Address, u8Data0, u8Data1); return bResult; }
#define C_Pca9543ApwAddress 0xE0 // A1, A0 is set to zero #define C_Pca9543ApwChannel1 1 #define C_Pca9543ApwChannel2 2 #define C_BatteryAddress 0x16 #define C_BatteryCmdGetTemperature 0x08 #define C_BatteryCmdGetVoltage 0x09 #define C_BatteryCmdGetAverageCurrent 0x0b #define C_BatteryCmdGetRelativeStateOfCharge 0x0d U8 GetPowerLevels(U8 *u8PowerLevelAcc1, U8 *u8PowerLevelAcc2) { // выбор 1-го канала bResult = I2cSend1Byte( C_Pca9543ApwAddress, C_Pca9543ApwChannel1, false, false); // получение данных от аккумулятора - относительный заряд bResult = ProcessSbsCommandS1bR1b(C_BatteryAddress, C_BatteryCmdGetRelativeStateOfCharge, u8PowerLevelAcc1); if (!bResult) { // батарея не подключена, проверить по состоянию переключателя питания *u8PowerLevelAcc1 = 0; } //============== other values ====================================== ... // получение температуры bResult = ProcessSbsCommandS1bR2b(C_BatteryAddress, C_BatteryCmdGetTemperature, &g_u8DataReceived0, &g_u8DataReceived1); g_u16BatteryTemperature = (g_u8DataReceived1 << 8) + g_u8DataReceived0; ... //============== other values end ================================== // выбор 2-го канала bResult = I2cSend1Byte( C_Pca9543ApwAddress, C_Pca9543ApwChannel2, false, false); // получение данных от аккумулятора - относительный заряд bResult = ProcessSbsCommandS1bR1b(C_BatteryAddress, C_BatteryCmdGetRelativeStateOfCharge, u8PowerLevelAcc2); if (!bResult) { // батарея не подключена, проверить по состоянию переключателя питания *u8PowerLevelAcc2 = 0; } } int main(void) { ... InitI2cMaster(C_I2cBitRateInKhz, ClockSystem.u32CpuClockInKhz); ... u8Result =GetPowerLevels(& g_au8PowerLevelBat[0], & g_au8PowerLevelBat[1]); ... }
Полученные результаты исследований используются при разработке портативных систем автономного электропитания для медицинских изделий "Комплекс универсальный для механической поддержки насосной функции левого и правого желудочков сердца "СТРИМ КАРДИО" и "Аппарат перфузионный для экстракорпоральной оксигенации"Ex-Stream".