/**
Your goal is to write code for a simplified Battery Management System that reads the voltage of a single Lithium-ion battery cell
and enables a relay if the cell is safe to operate. The Arduino will read in the voltage a potentiometer that is acting as the
simulated cell and simulated load is a resistor and LED. There is a display to report out the voltage and state. The RGB LED
should also light up according to the state the Arduino is in. The button is used to transition between states.
Requirements
1) The Arduino shall read a button press for state transition. This button is used to transition from Ready to Active and from Fault to Init. The button should trigger an interrupt, do not poll the button.
2) The Ardunio is to read the voltage in mV across the potentiometer and display it on the LCD. The screen should show the updated voltage every 100ms.
3) The LCD screen shall display the state of the Arduino.
4) The relay is only closed in the “Active” state.
5) The Arduino will protect the cell by going into Fault state if there is an under or over voltage event. If the voltage crosses the over or under voltage limit for the specified duration then it is considered a fault.
a) Overvoltage Limit: voltage > 4.2v for at least 2 seconds
b) Undervoltage Limit: voltage < 1.9v for at least 3 seconds
6) Do not use any delays or blocking code.
7) The RGD LED must correspond to the state of the Arduino.
8) The relay must open within 50mS after a fault condition (over/under voltage for a given duration).
Disclaimers and Simulator Issues:
1) The button doesn't debounce correctly in this simulation so be aware of that.
2) The "%" operation can produce unexpected results sometimes. Avoid it if possible.
Submitting Project
Download the project and send the zip file to the recruiting and hiring manager.
Important:
Be sure to include as many comments as needed to communicate your design intent and strategy.
Even if you don’t finish the project, leaving comments informs the reviewer of what you were trying to accomplish.
**/
// Includes
#include <LiquidCrystal_I2C.h>
#include <SimpleTimer.h>
// LCD Object
LiquidCrystal_I2C lcd(0x27,20,4);
// Pin hardware
#define BTN_PIN 2
#define READY_LED_PIN 11
#define CHARGE_LED_PIN 12
#define DISCHARGE_LED_PIN 13
#define RELAY_PIN 4
#define closeRelay() digitalWrite(RELAY_PIN, HIGH)
#define openRelay() digitalWrite(RELAY_PIN, LOW)
#define RedLEDOn() digitalWrite(DISCHARGE_LED_PIN, HIGH)
#define RedLEDOff() digitalWrite(DISCHARGE_LED_PIN, LOW)
#define GreenLEDOn() digitalWrite(CHARGE_LED_PIN, HIGH)
#define GreenLEDOff() digitalWrite(CHARGE_LED_PIN, LOW)
#define BlueLEDOn() digitalWrite(READY_LED_PIN, HIGH)
#define BlueLEDOff() digitalWrite(READY_LED_PIN, LOW)
// used for state machine
enum State {
INIT,
READY,
ACTIVE,
FAULT
};
State currentState = INIT;
// cell voltages are stored in mV
uint16_t cellVoltage = 0;
const uint16_t cellVoltUpperLimit = 4200;
const uint16_t cellVoltLowerLimit = 1900;
boolean OverVoltage = false;
boolean UnderVoltage = false;
//Interrupt Parameter
volatile byte state = LOW;
//Timer
SimpleTimer VoltageTimer(100);
// helper function to write to the LCD. This function can be modified if needed.
void drawLCD(){
// create char array from cell voltage
char voltStr[5];
lcd.setCursor(6,0);
itoa(cellVoltage, voltStr, 10);
lcd.print(voltStr);
lcd.setCursor(7,1);
switch (currentState){
case INIT:
lcd.print("INIT ");
break;
case READY:
lcd.print("READY ");
break;
case ACTIVE:
lcd.print("ACTIVE");
break;
case FAULT:
lcd.print("FAULT ");
break;
}
}
void setup() {
// put your setup code here, to run once:
// Setup LCD
lcd.init();
lcd.clear();
lcd.backlight();
lcd.setCursor(0,0);
lcd.print("Volt: mV");
lcd.setCursor(0,1);
lcd.print("State: ");
//SetUp IRQ for button input
pinMode(BTN_PIN, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(BTN_PIN), IRQRxD, LOW);
//Begin communication for timer and Voltage input
Serial.begin(9600);
INIT_State(); //place system in initial state
}
void loop() {
// put your main code here, to run repeatedly:
// DO NOT put in blocking delays
int sensorValue;
//retain Timer states
static int UpperTimer = 0;
static int LowerTimer = 0;
drawLCD();
//Read Voltage input every 100ms and update display
if(VoltageTimer.isReady()){
// read the input on analog pin 0:
sensorValue = analogRead(A0);
// Convert the analog reading (which goes from 0 - 1023) to a voltage (0 - 5000mV):
cellVoltage = sensorValue * (5000.0 / 1023.0);
VoltageTimer.reset(); //reset timer so it can be checked again in another 100ms
if ((cellVoltage >= cellVoltUpperLimit) && !OverVoltage)
{
LowerTimer = 0; //In case it was set prior
UpperTimer++; //Count up to maximum time for overvoltage
if (UpperTimer >= 20){
FAULT_State();
OverVoltage = true;
UnderVoltage = false;
}
else{
//do nothing
}
}
else if ((cellVoltage <= cellVoltLowerLimit) && !UnderVoltage){
LowerTimer++; //Count up to maximum time for undervoltage
UpperTimer = 0; //reset in case it was set before
if (LowerTimer >= 30){
FAULT_State();
OverVoltage = false;
UnderVoltage = true;
}
else{
//do nothing
}
}
else{
//No Voltage limit problem
LowerTimer = 0; //In case it was set prior
UpperTimer = 0; //to prevent resetting timer
OverVoltage = false; //for future safety cases it should be determined if 1 case of no fault should reset back to zero or do debouncing
UnderVoltage = false;
}
}
}
void IRQRxD(){
if(currentState == INIT)
{
if(!OverVoltage && !UnderVoltage){
currentState = READY;
READY_State();
}
else{
currentState = FAULT;
FAULT_State();
}
}
else if(currentState == READY)
{
if(!OverVoltage && !UnderVoltage){
currentState = ACTIVE;
ACTIVE_State();
}
else{
currentState = FAULT;
FAULT_State();
}
}
else if(currentState == ACTIVE)
{
if(!OverVoltage && !UnderVoltage){
currentState = ACTIVE; //There's no definition if you're in Active and button is pushed so stay in ACTIVE
ACTIVE_State();
}
else{
currentState = FAULT;
FAULT_State();
}
}
else if(currentState == FAULT)
{
if(!OverVoltage && !UnderVoltage){
currentState = INIT;
INIT_State();
}
else{
currentState = FAULT; //Stay in Fault state if failed and cell is still failed
FAULT_State();
}
}
else
{
currentState = INIT;
INIT_State(); //Should only get here if system is in an undefined state, if so reinitialize
}
}
void INIT_State(){
openRelay();
RedLEDOff();
GreenLEDOff();
BlueLEDOff();
}
void READY_State(){
openRelay();
RedLEDOff();
GreenLEDOff();
BlueLEDOn();
}
void ACTIVE_State(){
closeRelay();
RedLEDOff();
GreenLEDOn();
BlueLEDOff();
}
void FAULT_State(){
openRelay();
RedLEDOn();
GreenLEDOff();
BlueLEDOff();
}