/* * Copyright (c) 2019, Erich Styger, Sarah Gander * All rights reserved. * * Driver for GPIO pins * * SPDX-License-Identifier: BSD-3-Clause */ #include "McuLib.h" #if McuLib_CONFIG_CPU_IS_STM #include "libIIC.h" #endif #include "McuLC709203Fconfig.h" #include "McuLC709203F.h" #include "McuGenericI2C.h" #include "McuUtility.h" #include "McuWait.h" #define LC709203F_I2C_SLAVE_ADDR 0x0B #define LC709203F_REG_SET_BEFORE_RSOC 0x04 /* Before RSCOC */ #define LC709203F_REG_SET_THERMB 0x06 /* Thermistor B */ #define LC709203F_REG_INIT_RSOC 0x07 /* Initial RSOC */ #define LC709203F_REG_CELL_TEMP 0x08 /* Cell temperature */ #define LC709203F_REG_VOLTAGE 0x09 /* Cell voltage */ #define LC709203F_REG_CURRENT_DIRECTION 0x0A /* Current direction */ #define LC709203F_REG_ADJ_APPLI 0x0B /* APA, Adjustment Pack Application, sets Parasitic impedance */ #define LC709203F_REG_APT 0x0C /* APT, Adjustment Pack Thermistor, adjust temperature measurement delay timing */ #define LC709203F_REG_RSOC 0x0D /* RSOC, value based 0-100 scale */ #define LC709203F_REG_ITE 0x0F /* ITE, Indicator to Empty */ #define LC709203F_REG_IC_VER 0x11 /* IC version */ #define LC709203F_REG_CHG_PARAM 0x12 /* change of parameter */ #define LC709203F_REG_RSOC_ALM 0x13 /* Alarm low RSOC */ #define LC709203F_REG_LOW_CELL_VOL_ALM 0x14 /* Alarm Low Cell Voltage */ #define lC709203F_REG_PW_MODE 0x15 /* IC Power Mode */ #define LC709203F_REG_EN_NTC 0x16 /* Status Bit, enable Thermistor mode */ #define LC709203F_REG_NUM_PARAMS 0x1A /* Number of the parameter, display batter profile code */ #define MCULC_TEMP_ZERO_CELSIUS 0x0AAC /* From the data sheet command table: 0x0AAC is 0 degree Celsius */ static uint8_t i2cReadCmdData(uint8_t i2cAddr, uint8_t cmd, uint8_t *data, size_t length) { return McuGenericI2C_ReadAddress(i2cAddr, &cmd, sizeof(cmd), data, length); } static uint8_t i2cWriteCmdData(uint8_t i2cAddr, uint8_t cmd, uint8_t *data, size_t length) { return McuGenericI2C_WriteAddress(i2cAddr, &cmd, sizeof(cmd), data, length); } static uint8_t calcCrc8Atm(uint8_t a, uint8_t b) { /* the sensor uses a CRC-8-ATM, * Polynomial: x8 + x2 + x + 1 * Corresponds to: 100000111 */ #define POLY_8 0x8380 uint8_t i = 0; uint16_t val = 0; val = (uint16_t)(a^b); val <<= 8; for(i=0; i<8; i++) { if (val&0x8000 ){ val ^= POLY_8; } val <<= 1; } return (uint8_t)(val >> 8); } static uint8_t calcCRC_WriteAccess16(uint8_t i2cSlaveAddr, uint8_t cmd, uint8_t low, uint8_t high) { uint8_t crc; crc = calcCrc8Atm(0x00, i2cSlaveAddr<<1); /* I2C slave address */ crc = calcCrc8Atm(crc, cmd); /* command */ crc = calcCrc8Atm(crc, low); /* data byte */ return calcCrc8Atm(crc, high); /* data byte */ } static uint8_t calcCRC_ReadAccess16(uint8_t i2cSlaveAddr, uint8_t cmd, uint8_t low, uint8_t high) { uint8_t crc; crc = calcCrc8Atm(0x00, i2cSlaveAddr<<1); /* I2C slave address */ crc = calcCrc8Atm(crc, cmd); /* command */ crc = calcCrc8Atm(crc, (i2cSlaveAddr<<1)|1); /* I2C address with R bit set */ crc = calcCrc8Atm(crc, low); /* data byte */ return calcCrc8Atm(crc, high); /* data byte */ } static uint8_t CheckCrc(uint8_t i2cSlaveAddr, uint8_t cmd, uint8_t low, uint8_t high, uint8_t crc) { uint8_t val; val = calcCRC_ReadAccess16(i2cSlaveAddr, cmd, low, high); if (val != crc) { return ERR_FAILED; } return ERR_OK; } static uint8_t CheckI2CErr(uint8_t res) { if (res==ERR_OK) { return ERR_OK; /* ok */ } #if MCULC_CONFIG_BLOCK_ON_I2C_ERROR for(;;) { /* I2C Error? wait for the watchdog to kick in... */ McuWait_WaitOSms(50); } #endif return ERR_FAILED; } static uint8_t WriteCmdWordChecked(uint8_t i2cSlaveAddr, uint8_t cmd, uint8_t low, uint8_t high) { uint8_t data[3]; data[0] = low; data[1] = high; data[2] = calcCRC_WriteAccess16(i2cSlaveAddr, cmd, data[0], data[1]); return CheckI2CErr(i2cWriteCmdData(i2cSlaveAddr, cmd, data, sizeof(data))); } void McuLC_Wakeup(void) { /* Generates wake up signal on SDA, according to the data sheet. * This has to happen before any other I2C communication on the bus: * Pull down SDA for 0.6us, then high again, * wait for 400 us * */ #if McuLib_CONFIG_CPU_IS_STM /* set SDA pin of I2C to GPIO Output mode. */ HAL_I2C_MspDeInit(&hi2c3); GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Pin = I2C_SDA_Pin; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); HAL_GPIO_WritePin(I2C_SDA_GPIO_Port, I2C_SDA_Pin, GPIO_PIN_RESET); McuWait_Waitus(1); /* SDA min 0.6us low */ HAL_GPIO_WritePin(I2C_SDA_GPIO_Port, I2C_SDA_Pin, GPIO_PIN_SET); McuWait_Waitus(400); /* wait 400us */ /* set SDA pin of I2C to GPIO Ouput mode. */ HAL_I2C_MspInit(&hi2c3); #elif McuLib_CONFIG_SDK_VERSION_USED==McuLib_CONFIG_SDK_PROCESSOR_EXPERT PORT_PDD_SetPinMuxControl(PORTB_BASE_PTR, 3, PORT_PDD_MUX_CONTROL_ALT1); /* MUX SDA/PTB3 as GPIO */ PORT_PDD_SetPinOpenDrain(PORTB_BASE_PTR, 3, PORT_PDD_OPEN_DRAIN_ENABLE); GPIO_PDD_SetPortOutputDirectionMask(PTB_BASE_PTR, (1<<3)); /* SDA/PTB3 as output */ GPIO_PDD_ClearPortDataOutputMask(PTB_BASE_PTR, 1<<3); /* SDA/PB3 low */ McuWait_Waitus(1); /* SDA min 0.6us low */ GPIO_PDD_SetPortDataOutputMask(PTB_BASE_PTR, 1<<3); /* SDA/PB3 high */ McuWait_Waitus(400); /* wait 400us */ /* mux back to normal I2C mode with interrupts enabled */ PORTB_PCR3 = (uint32_t)((PORTB_PCR3 & (uint32_t)~(uint32_t)( PORT_PCR_ISF_MASK | PORT_PCR_MUX(0x05) )) | (uint32_t)( PORT_PCR_MUX(0x02) )); PORT_PDD_SetPinOpenDrain(PORTB_BASE_PTR, 0x03u, PORT_PDD_OPEN_DRAIN_ENABLE); /* Set SDA pin as open drain */ #else /* NYI for NXP SDK yet */ #endif } static uint8_t ReadCmdWordChecked(uint8_t i2cSlaveAddr, uint8_t cmd, uint16_t *val) { uint8_t data[3]; uint8_t res; res = CheckI2CErr(i2cReadCmdData(i2cSlaveAddr, cmd, data, sizeof(data))); if (res!=ERR_OK) { return res; } res = CheckI2CErr(CheckCrc(i2cSlaveAddr, cmd, data[0], data[1], data[2])); if (res!=ERR_OK) { return res; } *val = (data[1]<<8) | data[0]; return ERR_OK; } /* returns cell voltage in mV */ uint8_t McuLC_GetVoltage(uint16_t *pVoltage) { return ReadCmdWordChecked(LC709203F_I2C_SLAVE_ADDR, LC709203F_REG_VOLTAGE, pVoltage); } /* returns cell temperature (10 = 1 degree C) */ uint8_t McuLC_GetTemperature(int16_t *pTemperature) { /* cell temperature is in 0.1C units, from 0x09E4 (-20C) up to 0x0D04 (60C) */ uint8_t res; res = ReadCmdWordChecked(LC709203F_I2C_SLAVE_ADDR, LC709203F_REG_CELL_TEMP, (uint16_t*)pTemperature); if (res!=ERR_OK) { return res; } *pTemperature -= MCULC_TEMP_ZERO_CELSIUS; /* From the data sheet command table: 0x0AAC is 0 degree Celsius */ return ERR_OK; } uint8_t McuLC_SetTemperature(int16_t temperature) { /* cell temperature is in 0.1C units, from 0x09E4 (-20C) up to 0x0D04 (60C) */ temperature = ((uint16_t)temperature)+MCULC_TEMP_ZERO_CELSIUS; /* From the data sheet command table: 0x0AAC is 0 degree Celsius */ return WriteCmdWordChecked(LC709203F_I2C_SLAVE_ADDR, LC709203F_REG_CELL_TEMP, ((uint16_t)temperature)&0xff, (((uint16_t)temperature)>>8)&0xff); } /* returns battery Relative State of Charge in percent */ uint8_t McuLC_GetRSOC(uint16_t *pRsoc) { return ReadCmdWordChecked(LC709203F_I2C_SLAVE_ADDR, LC709203F_REG_RSOC, pRsoc); } /* Indicator to empty, returns battery charge in thousandth */ uint8_t McuLC_GetITE(uint16_t *pIte) { return ReadCmdWordChecked(LC709203F_I2C_SLAVE_ADDR, LC709203F_REG_ITE, pIte); } /* Indicator to empty, returns battery charge in thousandth */ uint8_t McuLC_GetICversion(uint16_t *pVersion) { return ReadCmdWordChecked(LC709203F_I2C_SLAVE_ADDR, LC709203F_REG_IC_VER, pVersion); } #if MCULC709203F_CONFIG_PARSE_COMMAND_ENABLED static const unsigned char *McuLC_CurrentDirectionToString(McuLC_CurrentDirection dir) { switch(dir) { case McuLC_CURRENT_DIR_AUTO: return (const unsigned char*)"auto"; case McuLC_CURRENT_DIR_CHARGING: return (const unsigned char*)"charging"; case McuLC_CURRENT_DIR_DISCHARING: return (const unsigned char*)"discharging"; case McuLC_CURRENT_DIR_ERROR: default: return (const unsigned char*)"ERROR"; } } #endif uint8_t McuLC_GetCurrentDirection(McuLC_CurrentDirection *pDir) { uint16_t val; uint8_t res; res = ReadCmdWordChecked(LC709203F_I2C_SLAVE_ADDR, LC709203F_REG_CURRENT_DIRECTION, &val); if (res!=ERR_OK) { return res; } switch(val) { case 0: *pDir = McuLC_CURRENT_DIR_AUTO; break; case 1: return McuLC_CURRENT_DIR_CHARGING; break; case 0xffff: return McuLC_CURRENT_DIR_DISCHARING; break; default: return McuLC_CURRENT_DIR_ERROR; break; } return ERR_OK; } uint8_t McuLC_SetCurrentDirection(McuLC_CurrentDirection direction) { uint8_t low, high; switch(direction) { case McuLC_CURRENT_DIR_AUTO: low = 0x00; high = 0x00; break; case McuLC_CURRENT_DIR_CHARGING: low = 0x01; high = 0x00; break; case McuLC_CURRENT_DIR_DISCHARING: low = 0xff; high = 0xff; break; default: CheckI2CErr(ERR_FAILED); return ERR_FAILED; } return WriteCmdWordChecked(LC709203F_I2C_SLAVE_ADDR, LC709203F_REG_CURRENT_DIRECTION, low, high); } #if MCULC709203F_CONFIG_PARSE_COMMAND_ENABLED static uint8_t PrintStatus(McuShell_ConstStdIOType *io) { uint8_t buf[32], res; McuShell_SendStatusStr((unsigned char*)"LC", (const unsigned char*)"\r\n", io->stdOut); { uint16_t version; res = McuLC_GetICversion(&version); /* battery charge in thousandth */ if (res==ERR_OK) { McuUtility_strcpy(buf, sizeof(buf), (unsigned char*)"0x"); McuUtility_strcatNum16Hex(buf, sizeof(buf), version); McuUtility_strcat(buf, sizeof(buf), (const unsigned char*)"\r\n"); } else { McuUtility_strcpy(buf, sizeof(buf), (unsigned char*)"ERROR\r\n"); } McuShell_SendStatusStr((unsigned char*)" IC version", buf, io->stdOut); } { uint16_t rsoc; res = McuLC_GetRSOC(&rsoc); /* battery charge in percent */ if (res==ERR_OK) { McuUtility_Num16uToStr(buf, sizeof(buf), rsoc); McuUtility_strcat(buf, sizeof(buf), (const unsigned char*)"\% (0..100)\r\n"); } else { McuUtility_strcpy(buf, sizeof(buf), (unsigned char*)"ERROR\r\n"); } McuShell_SendStatusStr((unsigned char*)" RSOC", buf, io->stdOut); } { uint16_t ite; res = McuLC_GetITE(&ite); /* battery charge in thousandth */ if (res==ERR_OK) { McuUtility_Num16uToStr(buf, sizeof(buf), ite); McuUtility_strcat(buf, sizeof(buf), (const unsigned char*)" (0..1000)\r\n"); } else { McuUtility_strcpy(buf, sizeof(buf), (unsigned char*)"ERROR\r\n"); } McuShell_SendStatusStr((unsigned char*)" ITE", buf, io->stdOut); } { int16_t temperature; bool isI2Cmode; res = McuLC_GetTemperature(&temperature); /* cell temperature in 1/10-degree C */ if (res==ERR_OK) { McuUtility_Num16sToStr(buf, sizeof(buf), temperature/10); McuUtility_chcat(buf, sizeof(buf), '.'); if (temperature<0) { /* make it positive */ temperature = -temperature; } McuUtility_strcatNum16s(buf, sizeof(buf), temperature%10); McuUtility_strcat(buf, sizeof(buf), (const unsigned char*)" C"); } else { McuUtility_strcpy(buf, sizeof(buf), (unsigned char*)"ERROR"); } if (McuLC_GetTemperatureMeasurementMode(&isI2Cmode)==ERR_OK) { if (isI2Cmode) { McuUtility_strcat(buf, sizeof(buf), (const unsigned char*)" (I2C mode)"); } else { McuUtility_strcat(buf, sizeof(buf), (const unsigned char*)" (Thermistor mode)"); } } else { McuUtility_strcat(buf, sizeof(buf), (const unsigned char*)" (ERROR!)"); } McuUtility_strcat(buf, sizeof(buf), (const unsigned char*)"\r\n"); McuShell_SendStatusStr((unsigned char*)" Temperature", buf, io->stdOut); } { uint16_t mVolt; res = McuLC_GetVoltage(&mVolt); /* cell voltage in milli-volts */ if (res==ERR_OK) { McuUtility_Num16uToStr(buf, sizeof(buf), mVolt); McuUtility_strcat(buf, sizeof(buf), (const unsigned char*)" mV\r\n"); } else { McuUtility_strcpy(buf, sizeof(buf), (unsigned char*)"ERROR\r\n"); } McuShell_SendStatusStr((unsigned char*)" Voltage", buf, io->stdOut); } { McuLC_CurrentDirection direction; res = McuLC_GetCurrentDirection(&direction); if (res==ERR_OK) { McuUtility_strcpy(buf, sizeof(buf), McuLC_CurrentDirectionToString(direction)); McuUtility_strcat(buf, sizeof(buf), (const unsigned char*)"\r\n"); } else { McuUtility_strcpy(buf, sizeof(buf), (unsigned char*)"ERROR\r\n"); } McuShell_SendStatusStr((unsigned char*)" Current dir", buf, io->stdOut); } return ERR_OK; } #endif /* MCULC709203F_CONFIG_PARSE_COMMAND_ENABLED */ #if MCULC709203F_CONFIG_PARSE_COMMAND_ENABLED uint8_t McuLC_ParseCommand(const unsigned char *cmd, bool *handled, const McuShell_StdIOType *io) { uint8_t res = ERR_OK; const uint8_t *p; if (McuUtility_strcmp((char*)cmd, McuShell_CMD_HELP)==0 || McuUtility_strcmp((char*)cmd, "LC help")==0) { McuShell_SendHelpStr((unsigned char*)"LC", (const unsigned char*)"Group of LC709203F commands\r\n", io->stdOut); McuShell_SendHelpStr((unsigned char*)" help|status", (const unsigned char*)"Print help or status information\r\n", io->stdOut); McuShell_SendHelpStr((unsigned char*)" settemp ", (const unsigned char*)"In I2C temperature mode, set the temperature in deci-Celsius\r\n", io->stdOut); *handled = TRUE; return ERR_OK; } else if ((McuUtility_strcmp((char*)cmd, McuShell_CMD_STATUS)==0) || (McuUtility_strcmp((char*)cmd, "LC status")==0)) { *handled = TRUE; return PrintStatus(io); } else if (McuUtility_strncmp((char*)cmd, (char*)"LC settemp ", sizeof("LC settemp ")-1)==0) { int16_t val; *handled = TRUE; p = cmd+sizeof("LC settemp ")-1; if (McuUtility_ScanDecimal16sNumber(&p, &val)!=ERR_OK) { return ERR_FAILED; } return McuLC_SetTemperature(val); } return res; } #endif /* MCULC709203F_CONFIG_PARSE_COMMAND_ENABLED */ uint8_t McuLC_GetTemperatureMeasurementMode(bool *isI2Cmode) { uint16_t val=0; uint8_t res; res = ReadCmdWordChecked(LC709203F_I2C_SLAVE_ADDR, LC709203F_REG_EN_NTC, &val); if (res!=ERR_OK) { return res; } *isI2Cmode = val==0; /* 0: i2c, 1: thermistor mode */ return ERR_OK; } uint8_t McuLC_SetTemperatureMeasurementMode(bool i2cMode) { return WriteCmdWordChecked(LC709203F_I2C_SLAVE_ADDR, LC709203F_REG_EN_NTC, i2cMode?0x00:0x01, 0x0); } uint8_t McuLC_SetPowerMode(bool sleepMode) { return WriteCmdWordChecked(LC709203F_I2C_SLAVE_ADDR, lC709203F_REG_PW_MODE, sleepMode?0x02:0x01, 0x00); } uint8_t McuLC_Init(void) { /* initializes LC709203F for Renata ICP543759PMT battery */ uint8_t res; /* operational mode (1: operational, 2: sleep), 0x0001 = operational mode */ res = WriteCmdWordChecked(LC709203F_I2C_SLAVE_ADDR, lC709203F_REG_PW_MODE, 0x01, 0x00); if (res!=ERR_OK) { return ERR_FAILED; } /* APA, set parasitic impedance (table 7 in data sheet): 0x000C, value for 280mA */ res = WriteCmdWordChecked(LC709203F_I2C_SLAVE_ADDR, LC709203F_REG_ADJ_APPLI, 0x0C, 0x00); if (res!=ERR_OK) { return ERR_FAILED; } /* Battery Profile (table 8 in data sheet): 0x0001 = Type 1 */ res = WriteCmdWordChecked(LC709203F_I2C_SLAVE_ADDR, LC709203F_REG_CHG_PARAM, 0x01, 0x00); if (res!=ERR_OK) { return ERR_FAILED; } /* write 0xAA55 to initialize RSOC */ res = WriteCmdWordChecked(LC709203F_I2C_SLAVE_ADDR, LC709203F_REG_INIT_RSOC, 0x55, 0xAA); if (res!=ERR_OK) { return ERR_FAILED; } /* Set battery measurement mode: 0x0001: thermistor mode. 0x0000: I2C mode */ #if MCULC709203F_CONFIG_USE_THERMISTOR res = WriteCmdWordChecked(LC709203F_I2C_SLAVE_ADDR, LC709203F_REG_EN_NTC, 0x01, 0x00); if (res!=ERR_OK) { return ERR_FAILED; } #else res = WriteCmdWordChecked(LC709203F_I2C_SLAVE_ADDR, LC709203F_REG_EN_NTC, 0x00, 0x00); if (res!=ERR_OK) { return ERR_FAILED; } #endif /* set the B-constant of the thermistor to be measured */ #if MCULC709203F_CONFIG_USE_THERMISTOR res = WriteCmdWordChecked(LC709203F_I2C_SLAVE_ADDR, LC709203F_REG_SET_THERMB, 0x6B, 0x0D); /* Thermistor B-Constant: 0x0D6B = B=3435 */ if (res!=ERR_OK) { return ERR_FAILED; } #endif /* Low voltage alarm setting: 0x0C1C = 3100mV = 3.1V, low byte first */ res = WriteCmdWordChecked(LC709203F_I2C_SLAVE_ADDR, LC709203F_REG_LOW_CELL_VOL_ALM, 0x1C, 0x0C); if (res!=ERR_OK) { return ERR_FAILED; } /* 0x0000 = alarm disable, low byte first */ res = WriteCmdWordChecked(LC709203F_I2C_SLAVE_ADDR, LC709203F_REG_RSOC_ALM, 0x00, 0x00); if (res!=ERR_OK) { return ERR_FAILED; } return ERR_OK; } void McuLC_Deinit(void) { /* nothing to do */ }