Программирование умных аккумуляторов (Smart Battery) на микроконтроллерах Atmel семейства Xmega

В автономных устройствах при работе от аккумуляторов прошивка должна иметь информацию о значениях таких параметров, как относительный заряд аккумулятора, время до полного разряда аккумулятора, других параметров. Наибольшую точность и надежность может обеспечить использование умных аккумуляторов.

Используемый нами умный аккумулятор включает в себя микросхему TI bq20z90, которая обеспечивает поддержку технологии SBS - Smart Battery System - система умного аккумулятора. SBS обеспечивает передачу информации о состоянии аккумулятора на другие устройства и изменение значений некоторых параметров. Обмен данными между умным аккумулятором и микроконтроллером устройства происходит по интерфейсу SMBUS v.1.1.

1. Параметры умного аккумулятора для мониторирования:
2. Параметры умного аккумулятора, значения которых могут быть изменены:
3. Основные принципы обмена данными по шине TWI / I2C / SMBUS
4. Протоколы обмена данными SMBUS v.1.1
5. Ссылки
6. Структура программы для работы с умным аккумулятором
7. Реализация программы для программирования умного аккумулятора на микроконтроллере Atmel семейства Xmega
7.1. Драйвер I2C для микроконтроллера Atmel семейства Xmega
7.1.1. Инициализация
7.1.2. Передача одного байта данных на ведомое устройство, указанное по его адресу
7.1.3. Чтение одного байта с ведомого устройства
7.1.4. Чтение двух байтов с ведомого устройства, отличается от обработчика чтения одного байта следующим
7.2. Реализация протоколов SMBUS для чтения данных
7.2.1. Read Byte Protocol - чтение одного байта
7.2.2. Read Word Protocol - чтение двух байтов
7.2.3. Получение информации о состоянии аккумуляторов
Параметры умного аккумулятора для мониторирования:
  • RelativeStateOfCharge - относительный заряд (от 0 до 100 %);
  • AverageCurrent - средний ток (усреднение за период 14.5 с);
  • Voltage - напряжение;
  • Temperature - температура аккумулятора;
  • AverageTimeToEmpty - время до полного разряда аккумулятора (вычисляемое по значению среднего тока);
  • BatteryStatus - флаги ошибкок и текущих режимов.
Параметры умного аккумулятора, значения которых могут быть изменены:
  • RemainingCapacityAlarm - значение оставшегося заряда для выдачи тревожного сообщения о разряде;
  • BatteryMode - изменение режима работы аккумулятора и формата выдачи информации (использовать для вычислений и выдачи информации мА или мВт, передавать в зарядное устройство ток и напряжение или нет, др.);
  • CycleCount - количество циклов заряда/разряда;
  • DesignCapacity - заявленная емкость аккумулятора;
  • DesignVoltage - заявленное напряжение аккумулятора;
  • SerialNumber - серийный номер;
  • и другие параметры.

Изменяемые параметры доступны для редактирования разработчиком аккумулятора при переводе аккумулятора в режим "полного доступа" (Full Access) или "разблокирован" (Unsealed). В режиме "заблокирован" (Sealed) изменение параметров недоступно. После изменения значений параметров может потребоваться перекалибровка аккумулятора.

Основные принципы обмена данными по шине TWI / I2C / SMBUS
  • TWI - название интерфейса микроконтроллера; 
  • I2C - двухпроводной интерфейс от Philips; 
  • SMBUS- двухпроводной интерфейс для связи с микросхемами управления питанием, основан на I2C.для передачи данных используются две линии - тактовых импульсов (SCL) и данных (SDA);
  • допустимая частота тактовых импульсов: для I2C от 0 до 400 кГц, для SMBUSот 10 кГц до 100 кГц;
  • каждое устройство на шине имеет уникальный адрес разрядностью 7 бит (за исключением зарезервированных адресов); в случае использования устройств с одинаковыми адресами необходимо использовать I2C-хаб;
  • данные с ведущего на ведомый передаются в виде пакетов следующей структуры: старт импульс (1 бит, служебный - начало обмена данными), адрес (7 бит), тип операции - чтение или запись (1 бит), флаг подтверждения - принят или нет (1 бит), данные (8 бит), флаг подтверждения (1 бит) {при необходимости продолжение обмена данными - данные (8 бит), флаг подтверждения (1 бит), ...}, стоп импульс (1 бит, служебный - конец обмена данными);
  • при изменении направления передачи данных (запись / чтение) надо установить повторный старт импульс (1 бит, служебный), адрес (7 бит), тип операции - чтение или запись (1 бит), далее аналогично предыдущему пункту;
  • при появлении ошибок надо остановить передачу (установить стоп импульс);
  • интерфейс I2C не регламентирует протокол обмена данными, интерфейс SMBUS v.1.1 имеет 8 протоколов обмена данными.
Протоколы обмена данными SMBUS v.1.1
  • Quick Command
  • Send Byte
  • Receive Byte
  • Write Byte/Word
  • Read Byte/Word
  • Process Call
  • Block Read
  • Block Write

Для работы с двумя аккумуляторами их необходимо подключить к микроконтроллеру не напрямую, а через 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);
    ...
}
Реализация программы для программирования умного аккумулятора на микроконтроллере Atmel семейства Xmega
Драйвер I2C для микроконтроллера Atmel семейства Xmega
Инициализация
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;
}
Реализация протоколов SMBUS для чтения данных
Read Byte Protocol - чтение одного байта
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;
}
Read Word Protocol - чтение двух байтов
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".

Продукты