Skip to content

Instantly share code, notes, and snippets.

@ryansturmer
Last active December 4, 2024 13:54
Show Gist options
  • Save ryansturmer/6bad7f45b11de132c71b7782385b0e63 to your computer and use it in GitHub Desktop.
Save ryansturmer/6bad7f45b11de132c71b7782385b0e63 to your computer and use it in GitHub Desktop.
Embedded C Code Review Example 2 - Reading a Battery Fuel Gauge
#include <stdint.h>
#include <stdbool.h>
#define COULOMB_COUNT_POLL_INTERVAL 5000 // 5 seconds
#define COULOMB_REMOVAL_INTERVAL 2000 // 2 seconds
#define FUEL_GAUGE_ADDRESS 0x55
#define FUEL_GAUGE_POWER_OK 0x01
#define SYSTEM_TICK_MS 1
// HAL Functions, assume these are defined elsewhere
bool I2C_WriteRegister(uint8_t address, uint8_t reg, uint16_t data);
bool I2C_ReadRegister(uint8_t address, uint8_t reg, uint16_t *data);
bool I2C_IsTransactionComplete();
uint32_t GetSystemTick(void);
// Fuel gauge registers
#define FUEL_GAUGE_REG_POWER 0x00
#define FUEL_GAUGE_REG_GAIN 0x01
#define FUEL_GAUGE_REG_COULOMB_COUNT 0x02
#define FUEL_GAUGE_REG_REMOVE_COULOMB 0x03
// State machine states
typedef enum {
STATE_INIT,
STATE_IDLE,
STATE_REQUEST_POWER_STATUS,
STATE_WAIT_FOR_POWER_STATUS,
STATE_WAIT_FOR_GAIN_SET,
STATE_REQUEST_COULOMB_COUNT,
STATE_WAIT_FOR_COULOMB_COUNT,
STATE_REQUEST_REMOVE_COULOMBS,
STATE_WAIT_FOR_REMOVE_COULOMBS
} BatteryState;
static BatteryState current_state = STATE_INIT;
static uint32_t last_poll_time = 0;
static uint32_t last_coulomb_removal_time = 0;
static bool coulomb_removal_requested = false;
static uint16_t requested_coulomb_removal = 0;
static bool power_ok = false;
static uint16_t coulomb_count = 0;
// Public API, should be declared in header, but here for review
void StateMachine_Run(void);
void RequestCoulombRemoval(uint16_t amount);
uint16_t GetCoulombCount(void);
void StateMachine_Run(void) {
uint32_t current_time = GetSystemTick();
static uint16_t response_data = 0; // Response buffer for I2C
switch (current_state) {
case STATE_INIT:
current_state = STATE_REQUEST_POWER_STATUS;
break;
case STATE_REQUEST_POWER_STATUS:
// Check to see if we've just powered on for the first time
// This should only happen if the battery goes completely dead
I2C_ReadRegister(FUEL_GAUGE_ADDRESS, FUEL_GAUGE_REG_POWER, &response_data);
current_state = STATE_WAIT_FOR_POWER_STATUS;
break;
case STATE_WAIT_FOR_POWER_STATUS:
if (I2C_TransactionComplete()) {
power_ok = (response_data & FUEL_GAUGE_POWER_OK) != 0;
if (!power_ok) {
// Fresh powerup. Need to set gain on fuel gauge.
I2C_WriteRegister(FUEL_GAUGE_ADDRESS, FUEL_GAUGE_REG_GAIN);
current_state = STATE_REQUEST_COULOMB_COUNT;
}
}
break;
case STATE_WAIT_FOR_GAIN_SET:
if (I2C_TransactionComplete()) {
current_state = STATE_IDLE;
}
case STATE_IDLE:
if (current_time - last_poll_time >= COULOMB_COUNT_POLL_INTERVAL) {
current_state = STATE_REQUEST_COULOMB_COUNT;
}
if (coulomb_removal_requested) {
current_state = STATE_REQUEST_REMOVE_COULOMBS;
}
break;
case STATE_REQUEST_COULOMB_COUNT:
I2C_ReadRegister(FUEL_GAUGE_ADDRESS, FUEL_GAUGE_REG_COULOMB_COUNT, &response_data);
current_state = STATE_WAIT_FOR_COULOMB_COUNT;
break;
case STATE_WAIT_FOR_COULOMB_COUNT:
while (!I2C_TransactionComplete());
coulomb_count = response_data;
last_poll_time = current_time;
current_state = STATE_IDLE;
break;
case STATE_REQUEST_REMOVE_COULOMBS:
I2C_WriteRegister(FUEL_GAUGE_ADDRESS, FUEL_GAUGE_REG_REMOVE_COULOMB, requested_coulomb_removal);
current_state = STATE_WAIT_FOR_REMOVE_COULOMBS;
break;
case STATE_WAIT_FOR_REMOVE_COULOMBS:
if (I2C_TransactionComplete()) {
coulomb_removal_requested = false;
last_coulomb_removal_time = current_time;
current_state = STATE_IDLE;
}
break;
default:
current_state = STATE_INIT;
break;
}
}
void RequestCoulombRemoval(uint16_t amount) {
requested_coulomb_removal = amount;
coulomb_removal_requested = true;
}
uint16_t GetCoulombCount(void) {
return coulomb_count;
}
int main(void) {
while (1) {
StateMachine_Run();
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment