Utility library for testing and calibrating the BQ34Z100-G1 fuel gauge IC.

Dependencies:   BQ34Z100G1

Utils library for our BQ34Z100G1 driver. See https://os.mbed.com/users/MultipleMonomials/code/BQ34Z100G1/wiki/Setup-and-Calibration-Guide

New releases of this code have moved to GitHub: https://github.com/USCRPL/BQ34Z100G1-Utils

BQ34Z100G1-Utils.cpp

Committer:
MultipleMonomials
Date:
2021-02-07
Revision:
0:fcd2c91c4626

File content as of revision 0:fcd2c91c4626:

/*
    USC RPL HAMSTER v2.3 BQ34Z100 Test Suite
    Contributors: Arpad Kovesdy
*/
#include "BQ34Z100G1-Utils.h"

#include <cinttypes>

namespace mbed
{
    FileHandle *mbed_override_console(int)
    {
        static BufferedSerial serial(USBTX, USBRX, 115200);
        return &serial;
    }
}

// helper function to print a bitfield prettily.
void printBitfield(uint16_t value, const char* name, const char** bitDescriptions)
{
	printf("\n");
	printf("%s: 0x%" PRIx16 " (0b", name, value);
	for (int i = 15; i >= 0; i--)
	{
		printf("%i", (value >> i) & 1);
	}
	printf(")\n");

	const size_t maxBit = sizeof(value) * 8 - 1;
	for (int i = maxBit; i>=0; i--)
	{
		// Description array is in reverse order numerically
		char const * description = bitDescriptions[maxBit - i];

		uint8_t bitValue = (value >> i) & 1;
		if(description != nullptr)
		{
			printf("- %s: %" PRIu8 "\n", description, bitValue);
		}
	}
}

void BQ34Utils::outputStatus()
{
    uint16_t status_code = soc.getStatus();

    // Descriptions for each bit of the status bytes
    const char* statusBitDescs[] = {
    		nullptr,
    		"Full Access Sealed (FAS)",
    		"Sealed (SS)",
    		"Calibration Enabled (CALEN)",
    		"Coulomb Counter Calibrating (CCA)",
    		"Board Calibration Active (BCA)",
    		"Valid Data Flash Checksum (CSV)",
    		nullptr,
    		nullptr,
    		nullptr,
    		"Full Sleep Mode (FULLSLEEP)",
    		"Sleep Mode (SLEEP)",
    		"Impedance Track using Constant Power (LDMD)",
    		"Ra Updates Disabled (RUP_DIS)",
    		"Voltage OK for Qmax Updates (VOK)",
    		"Qmax Updates Enabled (QEN)"
    };

    printBitfield(status_code, "Control Status", statusBitDescs);

	// Descriptions for each bit of the flags bytes
	const char* flagsBitDescs[] = {
			"Overtemperature in Charge (OTC)",
			"Overtemperature in Discharge (OTD)",
			"High Battery Voltage (BATHI)",
			"Low Battery Voltage (BATLOW)",
			"Charge Inhibited (CHG_INH)",
			"Charging Not Allowed (XCHG)",
			"Full Charge (FC)",
			"Charge Allowed (CHG)",
			"Open Circuit Voltage Measurement Performed (OCVTAKEN)",
			nullptr,
			nullptr,
			"Update Cycle Needed (CF)",
			nullptr,
			"SoC Threshold 1 Reached (SOC1)",
			"SoC Threshold Final Reached (SOCF)",
			"Discharge Detected (DSG)"
	};
	const char* flagsBBitDescs[] = {
			"State of Health Calc Active (SOH)",
			"LiFePO4 Relax Enabled (LIFE)",
			"Waiting for Depth of Discharge Measurement (FIRSTDOD)",
			nullptr,
			nullptr,
			"Depth of Discharge at End of Charge Updated (DODEOC)",
			"Remaining Capacity Changed (DTRC)",
			nullptr,
			nullptr,
			nullptr,
			nullptr,
			nullptr,
			nullptr,
			nullptr,
			nullptr,
			nullptr
	};

	std::pair<uint16_t, uint16_t> flags = soc.getFlags();
	printBitfield(flags.first, "Flags", flagsBitDescs);
	printBitfield(flags.second, "FlagsB", flagsBBitDescs);


    uint8_t updateStatus = soc.getUpdateStatus();
    printf("Update status: 0x%" PRIx8 "\n", updateStatus);
}

void BQ34Utils::sensorReset()
{
    printf("Resetting BQ34Z100 Sensor.\r\n");
    soc.reset();

    uint16_t deviceType = soc.readDeviceType();
    if(deviceType == 0x100)
    {
        printf("BQ34Z100 detected\r\n");
    }
    else
    {
        printf("Error communicating with BQ34Z100.  Expected DEVICE_TYPE = 0x100, got 0x%" PRIx16 "\n", deviceType);
        return;
    }

    printf("Chip reads as FW_VERSION 0x%" PRIx16 ", HW version 0x%" PRIx16 "\r\n", soc.readFWVersion(), soc.readHWVersion());
}


void BQ34Utils::displayData()
{
    ThisThread::sleep_for(10ms); //Let the device catch up
    printf("SOC: %d%%\r\n", soc.getSOC());
    printf("Voltage: %d mV\r\n", soc.getVoltage());
    printf("Current: %d mA\r\n", soc.getCurrent());
    printf("Remaining: %d mAh\r\n", soc.getRemaining());
    printf("Temperature: %.1f C\r\n", soc.getTemperature());
    printf("Max Error: %d%%\r\n", soc.getError());
    printf("Serial No: %d\r\n", soc.getSerial());
    printf("CHEM ID: %" PRIx16 "\r\n", soc.getChemID());
}

void BQ34Utils::testICConnection()
{
    printf("Testing Electrical Connection\r\n");
    int status = soc.getStatus();
    printf("Status: %d\r\n\r\n", status);
}

void BQ34Utils::startCal()
{
    printf("Starting calibration mode\r\n");
    soc.enableCal();
    soc.enterCal();
}

void BQ34Utils::stopCal()
{
    printf("Stopping calibration mode\r\n");
    soc.exitCal();
}

void BQ34Utils::startIt()
{
    printf("Enabling Impedance Tracking\r\n");
    soc.ITEnable();
}

//Outputs an integer of the length provided starting from the given index in flashBytes
//Provide pointer to first element (array pointer to flashbytes)
void BQ34Utils::outputFlashInt(uint8_t* flash, int index, int len)
{
    if (index > 31) index = index % 32;
    unsigned int result = 0;
    for (int i = 0; i<len; i++)
    {
        //result = result | ((uint32_t)flash[index+i] << 8*len);
        result |= ((uint32_t)flash[index+i] << 8*(len-i-1));
    }
    printf("%d", result);
}

void BQ34Utils::writeSettings()
{
	if(soc.getVoltage() <= FLASH_UPDATE_OK_VOLT * CELLCOUNT)
	{
		printf("WARNING: Measured voltage is below FLASH_UPDATE_OK_VOLT, flash memory writes may not go through.  However this is expected if voltage has not been calibrated yet.");
	}

    soc.unseal();
    printf("Starting overwrite of sensor settings\r\n");
    uint8_t* flashStore = soc.getFlashBytes(); //Get address of array for later

    //Page 48
    soc.changePage(48, 0); //calls ChangePage from BQ34Z100 editing page 48 from datasheet
    soc.readFlash();

    //declares old subclass properties as per BQ34Z100 function commands
    printf("Old design capacity:");
    outputFlashInt(flashStore, 11, 2);
    printf("\r\n");
    printf("Old design energy:");
    outputFlashInt(flashStore, 13, 2);
    printf("\r\n");

    //replaces the old subclass properties with new ones as per BQ34Z100 function commands
    soc.changePage48();
    printf("New design capacity:");
    outputFlashInt(flashStore, 11, 2);
    printf("\r\n");
    printf("New design energy:");
    outputFlashInt(flashStore, 13, 2);
    printf("\r\n");

    //Page 64
    soc.changePage(64, 0); //calls ChangePage from BQ34Z100 editing page 48 from datasheet
    soc.readFlash();

    //declares old subclass properties as per BQ34Z100 function commands
    printf("Old Pack Configuration:");
    outputFlashInt(flashStore, 0, 2);
    printf("\r\n");
    printf("Old LED Config:");
    outputFlashInt(flashStore, 4, 1);
    printf("\r\n");
    printf("Old Cell Count:");
    outputFlashInt(flashStore, 7, 1);
    printf("\r\n");

    //replaces the old subclass properties with new ones as per BQ34Z100 function commands
    soc.changePage64();
    printf("New Pack Configuration:");
    outputFlashInt(flashStore, 0, 2);
    printf("\r\n");
    printf("New LED Config:");
    outputFlashInt(flashStore, 4, 1);
    printf("\r\n");
    printf("New Cell Count:");
    outputFlashInt(flashStore, 7, 1);
    printf("\r\n");

    //Page 80

    soc.changePage(80, 0); //calls ChangePage from BQ34Z100 editing page 48 from datasheet
    soc.readFlash();
    //declares old subclass properties as per BQ34Z100 function commands
    printf("Old Load Select:");
    outputFlashInt(flashStore, 0, 1);
    printf("\r\n");
    printf("Old Load Mode:");
    outputFlashInt(flashStore, 1, 1);
    printf("\r\n");

    soc.changePage(80, 53);
    soc.readFlash();
    printf("Old Cell Terminate Voltage:");
    outputFlashInt(flashStore, 53, 2);
    printf("\r\n");

    //replaces the old subclass properties with new ones as per BQ34Z100 function commands
    soc.changePage80();
    soc.changePage(80, 0);
    soc.readFlash();
    printf("New Load Select:");
    outputFlashInt(flashStore, 0, 1);
    printf("\r\n");
    printf("New Load Mode:");
    outputFlashInt(flashStore, 1, 1);
    printf("\r\n");
    printf("Res Current:");
    outputFlashInt(flashStore, 10, 2);
    printf("\r\n");
    soc.changePage(80, 53);
    soc.readFlash();
    printf("New Cell Terminate Voltage:");
    outputFlashInt(flashStore, 53, 2);
    printf("\r\n");

    //Page 82
    soc.changePage(82, 0); //calls ChangePage from BQ34Z100 editing page 48 from datasheet
    soc.readFlash();

    //declares old subclass properties as per BQ34Z100 function commands
    printf("Old QMax0:");
    outputFlashInt(flashStore, 0, 2);
    printf("\r\n");

    //replaces the old subclass properties with new ones as per BQ34Z100 function commands
    soc.changePage82();
    printf("New QMax0:");
    outputFlashInt(flashStore, 0, 2);
    printf("\r\n");

    //Print the updatestatus
    //0x02 = Qmax and Ra data are learned, but Impedance Track is not enabled.
    //This should be the standard setting for a Golden Image File
    //0x04 = Impedance Track is enabled but Qmax and Ra data are not yet learned.
    //0x05 = Impedance Track is enabled and only Qmax has been updated during a learning cycle.
    //0x06 = Impedance Track is enabled. Qmax and Ra data are learned after a successful learning
    //cycle. This should be the operation setting for end equipment.
    printf("UPDATE STATUS:");
    outputFlashInt(flashStore, 4, 1);
    printf("\r\n");
}

void BQ34Utils::calibrateVoltage ()
{

    printf("Enter voltage across the pack: ");
    float batVoltage=-1;
    scanf("%f", &batVoltage);
    printf("Hand measured voltage: %f\r\n\n", batVoltage);
    //printf("Enter ACTUAL battery voltage in volts\r\n");
    //pc.scanf("%f", &batVoltage);
    uint16_t batVoltage_mv = (uint16_t)(batVoltage*1000.0f);
    printf("\r\nCurrent Monitor Bus Voltage Battery Voltage (V): %f\r\n", batVoltage);

    printf("New Flash Voltage: %d\r\n", soc.calibrateVoltage(batVoltage_mv));
}

//Input current in A
void BQ34Utils::calibrateCurrent()
{
    // first reset back to theoretical value
	soc.setSenseResistor();

    // reset sensor so it picks up new settings
	soc.reset();
	ThisThread::sleep_for(200ms);

    // Now calibrate more exactly using hand-measured value
	printf("Enter current through the sense resistor in A: ");
	float current=-1;
	scanf("%f", &current);
	printf("Hand measured current%f:\r\n\n", current);

	printf("Calibrating current shunt\r\n\r\n");
	int16_t current_int = (int16_t)(current*1000.0f);
	soc.calibrateShunt(current_int);
}


void BQ34Utils::readVoltageCurrent()
{
    Timer chargeTimer;
    chargeTimer.start();
	
    printf("Time (s),\tVoltage (V),\tCurrent (mA)\r\n");

	while (true) {
		int voltage = soc.getVoltage();
		int current = soc.getCurrent();
		printf("%f,\t%d,\t%d\r\n", std::chrono::duration_cast<std::chrono::duration<float>>(chargeTimer.elapsed_time()).count(), voltage, current);
		ThisThread::sleep_for(100ms);
	}
}

void BQ34Utils::resetVoltageCalibration()
{
    soc.setVoltageDivider(RESETVOLTAGE);
    printf("\r\n\nVoltage divider calibration reset.\r\n");
}

void BQ34Utils::testFloatConversion()
{
	// test data from https://e2e.ti.com/support/power-management/f/196/p/551252/2020286?tisearch=e2e-quicksearch&keymatch=xemics#2020286
	float valueFloat = .8335f;
	uint32_t valueXemics = 0x80555E9E;

	// try converting float to xemics
	uint32_t convertedValue = BQ34Z100::floatToXemics(valueFloat);
	printf("Converted value: 0x%" PRIx32 "\n", convertedValue);

	// try converting xemics to float
	float convertedFloat = BQ34Z100::xemicsToFloat(valueXemics);
	printf("Converted float: %f\n", convertedFloat);

	printf("Expected default CC Gain: 0x%" PRIx32 "\n", BQ34Z100::floatToXemics(0.4768));
	printf("Expected default CC Delta: 0x%" PRIx32 "\n", BQ34Z100::floatToXemics(567744.56));

}


int main()
{
    //declare the test harness
    BQ34Utils harness;

    while(1){
        int test=-1;
        printf("\r\n\nBattery State of Charge Sensor Test Suite:\r\n");

        //Menu for each test item
        printf("Select a test: \n\r");
        printf("1.  Reset Sensor (Restart)\r\n");
        printf("2.  Write Settings to Flash\r\n");
        printf("3.  Calibrate Voltage\r\n");
        printf("4.  Calibrate Current\r\n");
        printf("5.  Enable Calibration Mode\r\n");
        printf("6.  Disable Calibration Mode\r\n");
        printf("7.  Enable Impedance Tracking\r\n");
        printf("8.  Output Status\r\n");
        printf("9.  Display Data\r\n");
        printf("10.  Test Connection to Sensor\r\n");
        printf("11.  Reset Voltage Divider Calibration\r\n");
	    printf("12.  Test Float Conversion\r\n");
	    printf("13.  Read Voltage and Current Forever\r\n");

        scanf("%d", &test);
        printf("Running test %d:\r\n\n", test);

        //SWITCH. ADD A CASE FOR EACH TEST.
        switch(test) {
            case 1:         harness.sensorReset();                           break;
            case 2:         harness.writeSettings();                         break;
            case 3:         harness.calibrateVoltage();                      break;
            case 4:         harness.calibrateCurrent();                      break;
            case 5:         harness.startCal();                              break;
            case 6:         harness.stopCal();                               break;
            case 7:         harness.startIt();                               break;
            case 8:         harness.outputStatus();                          break;
            case 9:         harness.displayData();                           break;
            case 10:        harness.testICConnection();                      break;
            case 11:        harness.resetVoltageCalibration();               break;
	        case 12:        harness.testFloatConversion();                   break;
	        case 13:        harness.readVoltageCurrent();                    break;
            default:        printf("Invalid test number.\r\n");              break;
        }

        printf("done.\r\n");
    }
    return 0;
}