Created
September 19, 2019 19:37
-
-
Save kingoflolz/e68667319ce7effb66ba652568d221d0 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <cmath> | |
#include <cmath> | |
/** | |
Battery Tester 3001 | |
*/ | |
/* Includes ------------------------------------------------------------------*/ | |
#include <cmsis_os.h> | |
#include "Utils.hpp" | |
#include "Adc.hpp" | |
#include "AnalogIn.hpp" | |
#include "Dac.hpp" | |
#include "DigitalOut.hpp" | |
#include "FreeRTOSConfig.h" | |
//#include "IndependentWatchdog.hpp" | |
#include "Rtc.hpp" | |
#include "Uart.hpp" | |
#include "CheckLimits.hpp" | |
#include <string> | |
//#include "Timer.hpp" | |
//#include "Debounce.hpp" | |
#include "MinMaxAvg.hpp" | |
#include "PwmOut.hpp" | |
/* MCU Pinouts ---------------------------------------------------------------* | |
| A0 B0 | PWR_SUPPLY_SENSE | |
BOARD_TEMP | A1 B1 | | |
MUX_TEMP_OUT | A2 B2 | CS | |
CURRENT_ADC | A3 B3 | DEBUG_SWO | |
DSCHG_CONTROL_DAC | A4 B4 | SYNC_ERROR | |
CHG_CONTROL_DAC | A5 B5 | | |
BATTERY_P_SENSE | A6 B6 | MUX3 | |
BATTERY_N_SENSE | A7 B7 | MUX2 | |
MCO | A8 B8 | MUX1 | |
| A9 B9 | SD1_CD | |
CAN_GP_FLT | A10 B10 | ENABLE2 | |
CAN_GP_RX | A11 B11 | ENABLE | |
CAN_GP_TX | A12 B12 | | |
DEBUG_SWDIO | A13 B13 | SCLK | |
DEBUG_SWCLK | A14 B14 | MISO | |
| A15 B15 | MOSI | |
//////////////////// | |
| C0 D0 | | |
| C1 D1 | | |
| C2 D2 | SD1_CMD | |
| C3 D3 | | |
CHARGE_P_SENSE | C4 D4 | SD_READY1 | |
CHARGE_N_SENSE | C5 D5 | CS_ADC | |
| C6 D6 | | |
| C7 D7 | | |
SD1_DAT0 | C8 D8 | | |
SD1_DAT1 | C9 D9 | | |
SD1_DAT2 | C10 D10 | | |
SD1_DAT3 | C11 D11 | | |
SD1_CLK | C12 D12 | SCL | |
| C13 D13 | SDA | |
| C14 D14 | | |
| C14 D14 | | |
//////////////////// | |
UART8_RX | PE0 | | |
UART8_TX | PE1 | | |
TRACE_CLK | PE2 | | |
TRACE_D0 | PE3 | | |
TRACE_D1 | PE4 | | |
TRACE_D2 | PE5 | | |
TRACE_D3 | PE6 | | |
GPIO1 | PE7 | | |
GPIO2 | PE8 | | |
GPIO_5V_PG | PE9 | | |
| PE10 | | |
FRAM_CS | PE11 | | |
FRAM_SCK | PE12 | | |
FRAM_MISO | PE13 | | |
FRAM_MOSI | PE14 | | |
| PE15 | | |
* NOTES: | |
* Battery Current = (CURRENT_ADC - 2.5) / 0.02 | |
* Battery Voltage = (BATTERY_P_SENSE - BATTERY_N_SENSE) | |
* Charger Voltage = (CHARGER_P_SENSE - CHARGER_N_SENSE) | |
* The Isolation Relay for Discharging is ENABLE and for Charging is ENABLE2 | |
*----------------------------------------------------------------------------*/ | |
#include <DigitalOut.hpp> | |
#include <task.h> | |
#include "Uart.hpp" | |
#include <cmsis_os.h> | |
#include "Rtc.hpp" | |
#include <IndependentWatchdog.hpp> | |
#include <ControllerUtils.hpp> | |
#pragma clang diagnostic push | |
#pragma clang diagnostic ignored "-Wmissing-noreturn" | |
using namespace per::embedded::peripheral; | |
namespace per::embedded { | |
using namespace peripheral; | |
static void HandleWatchdogTimeout(uint32_t kickedThreads, bool startup); | |
static void CommandThread(void const *argument); | |
static void ControlThread(void const *argument); | |
osThreadId CommandThreadHandle; | |
osThreadId ControlThreadHandle; | |
Rtc rtc(true); | |
IndependentWatchdog<2> watchdog(20, 2000, cb::Callback2<void, uint32_t, bool>(HandleWatchdogTimeout)); | |
constexpr uint8_t RTC_WATCHDOG_REGISTER = 0; | |
constexpr uint8_t RTC_WATCHDOG_STARTUP_REGISTER = 1; | |
static void HandleWatchdogTimeout(uint32_t kickedThreads, bool startup) { | |
rtc.WriteRegister(RTC_WATCHDOG_REGISTER, ~kickedThreads); | |
rtc.WriteRegister(RTC_WATCHDOG_STARTUP_REGISTER, startup); | |
Breakpoint(); | |
} | |
/** | |
* \brief The entrypoint. You need to change it only if you are adding new threads or for debug purposes. | |
*/ | |
int PlaygroundMain() { | |
CommandThreadHandle = CreateThread("Command", CommandThread, osPriorityNormal, 1024); | |
ControlThreadHandle = CreateThread("Control", ControlThread, osPriorityHigh, 1024); | |
watchdog.RegisterThreads(std::array<osThreadId, 2>{ | |
CommandThreadHandle, | |
ControlThreadHandle | |
}); | |
osKernelStart(); | |
while (true) | |
Breakpoint(); | |
} | |
enum State { | |
Idle = 0, | |
Discharging, | |
Charging | |
}; | |
enum Error { | |
None, | |
Overcharge, | |
Overdischarge, | |
Overcurrent, | |
Overtemp, | |
CurrentRange, | |
InvalidCommand, | |
Timeout | |
}; | |
State state = State::Idle; | |
Error error = Error::None; | |
float chargePower = 0; | |
float chargeMaxVoltage = 4.2; | |
float chargeCurrentLimit = 0; | |
float chargeIntegrator = 0; | |
float dischargePower = 0; | |
float dischargeMinVoltage = 2.7; | |
float dischargeCurrentLimit = 1; | |
float voltageHO, voltageLO; | |
PIDController currentController = PIDController(0, 0, 0, -0.1f, 0.9f, 0); | |
float selectedCurrent; | |
float battVoltage; | |
float cellTempreture; | |
bool processCommand(char *command) { | |
std::string commandString(&command[2]); | |
std::string::size_type bytesProcessed = 0; | |
switch (command[0]) { | |
case 'p': | |
{ | |
//change pid values | |
float pValue = std::stof (commandString,&bytesProcessed); | |
commandString = commandString.substr(bytesProcessed); | |
float iValue = std::stof (commandString,&bytesProcessed); | |
commandString = commandString.substr(bytesProcessed); | |
float dValue = std::stof (commandString,&bytesProcessed); | |
commandString = commandString.substr(bytesProcessed); | |
currentController.UpdateConstants(pValue, iValue, dValue, 0); | |
return commandString == std::string("\n"); | |
} | |
case 'd': | |
{ | |
//change discharge current and hard limit | |
dischargePower = std::stof (commandString,&bytesProcessed); | |
commandString = commandString.substr(bytesProcessed); | |
dischargeCurrentLimit = std::stof (commandString,&bytesProcessed); | |
commandString = commandString.substr(bytesProcessed); | |
dischargeMinVoltage = std::stof (commandString,&bytesProcessed); | |
commandString = commandString.substr(bytesProcessed); | |
return commandString == std::string("\n"); | |
} | |
case 'c': | |
{ | |
//change charge current and hard limit | |
chargePower = std::stof (commandString,&bytesProcessed); | |
commandString = commandString.substr(bytesProcessed); | |
chargeCurrentLimit = std::stof (commandString,&bytesProcessed); | |
commandString = commandString.substr(bytesProcessed); | |
chargeMaxVoltage = std::stof (commandString,&bytesProcessed); | |
commandString = commandString.substr(bytesProcessed); | |
return commandString == std::string("\n"); | |
} | |
case 's': | |
{ | |
//change state | |
state = static_cast<State>(std::stoi(commandString, &bytesProcessed)); | |
commandString = commandString.substr(bytesProcessed); | |
error = None; | |
return true; | |
} | |
case 'n': | |
{ | |
// nop | |
return true; | |
} | |
default: | |
// unknown command | |
return false; | |
} | |
} | |
Timer tim9(9, Timer::Clock::Internal, 1, 2160); | |
PwmOut chargeCtrl(tim9, PE5); | |
PwmOut dischargeCtrl(tim9, PE6); | |
void ControlThread(void const *) { | |
TickType_t lastTime = xTaskGetTickCount(); | |
const float ANALOG_REF = 3.3; | |
AnalogIn ext5v(PF3, nullptr, ADC_SAMPLETIME_480CYCLES); | |
AnalogIn currentH(PA3, nullptr, ADC_SAMPLETIME_480CYCLES); | |
AnalogIn currentL(PC0, nullptr, ADC_SAMPLETIME_480CYCLES); | |
//AnalogIn voltageCharger(PA0); | |
watchdog.Startup(); | |
AnalogIn voltageL(PB1, nullptr, ADC_SAMPLETIME_480CYCLES); | |
AnalogIn voltageH(PF4, nullptr, ADC_SAMPLETIME_480CYCLES); | |
AnalogIn cellTemp(PC2, nullptr, ADC_SAMPLETIME_480CYCLES); | |
DigitalOut AIR(PF13); | |
LowPassFilter currentLPF; | |
LowPassFilter voltageLPF; | |
currentLPF.UpdateCoefficients(0.001f, .2f); | |
voltageLPF.UpdateCoefficients(0.001f, .2f); | |
while (true) { | |
float ext5vVoltage = 2 * ext5v.Read() * ANALOG_REF; | |
const float currLraw = 2 * currentL.Read() * ANALOG_REF; | |
const float currHraw = 2 * currentH.Read() * ANALOG_REF; | |
voltageHO = voltageH.Read(); | |
voltageLO = voltageL.Read(); | |
battVoltage = voltageLPF.Update(2 * (voltageH.Read()) * ANALOG_REF); | |
cellTempreture = 2 * cellTemp.Read() * ANALOG_REF / 0.01f - 273.15f; | |
if (currLraw > ext5vVoltage - 0.15f || currLraw < 0.15f){ | |
state = State::Idle; | |
error = CurrentRange; | |
} | |
if (currHraw > ext5vVoltage - 0.15f || currHraw < 0.15f){ | |
state = State::Idle; | |
error = CurrentRange; | |
} | |
float currL = (currLraw - ext5vVoltage/2) / 0.040f - 0.38f; // hardcoded calibration constants | |
float currH = (currHraw - ext5vVoltage/2) / 0.0052f - 57.1f; | |
selectedCurrent = currentLPF.Update((std::abs(currL) <= 45.0) ? currL : currH); | |
float measuredPower = selectedCurrent * battVoltage; | |
float multiplier = 0; | |
if (state == State::Charging) { | |
multiplier = -1; | |
} else if (state == State::Discharging) { | |
multiplier = 1; | |
} | |
float requestPower = 0; | |
if (state == State::Charging) { | |
requestPower = chargePower; | |
} else if (state == State::Discharging) { | |
requestPower = dischargePower; | |
} | |
float output = 0.1f + currentController.Command(0.001f, requestPower, std::fabs(measuredPower * multiplier)); | |
if (state == State::Charging) { | |
if (std::fabs(selectedCurrent) > chargeCurrentLimit) { | |
state = State::Idle; | |
error = Overcurrent; | |
} | |
if (battVoltage > chargeMaxVoltage) { | |
state = State::Idle; | |
error = Overcharge; | |
} | |
dischargeCtrl.SetDutyCycle(0); | |
chargeCtrl.SetDutyCycle(output); | |
} else if (state == State::Discharging) { | |
if (std::fabs(selectedCurrent) > dischargeCurrentLimit) { | |
state = State::Idle; | |
error = Overcurrent; | |
} | |
if (battVoltage < dischargeMinVoltage) { | |
state = State::Idle; | |
error = Overdischarge; | |
} | |
dischargeCtrl.SetDutyCycle(output); | |
chargeCtrl.SetDutyCycle(0); | |
} | |
AIR.Set(state != State::Idle); | |
if (state == State::Idle) { | |
currentController.Reset(); | |
dischargeCtrl.SetDutyCycle(0); | |
chargeCtrl.SetDutyCycle(0); | |
} | |
watchdog.Kick(); | |
vTaskDelayUntil(&lastTime, 1); | |
} | |
} | |
void CommandThread(void const *) { | |
TickType_t lastTime = xTaskGetTickCount(); | |
char commandBuffer[150] = {0}; | |
uint commandBufferIdx = 0; | |
Uart interface(PD9, PD8, 1000, 1000, 912600); | |
interface.PrintF(100, "reset\n"); | |
watchdog.Startup(); | |
uint commandTimeout = 0; | |
while (true) { | |
bool can_read = true; | |
while (can_read && commandBufferIdx < 149) { | |
can_read = interface.TryReceive(reinterpret_cast<uint8_t *>(&commandBuffer[commandBufferIdx]), 1) > 0; | |
if (can_read && commandBuffer[commandBufferIdx] == '\n') { | |
commandBuffer[commandBufferIdx+1] = '\0'; | |
if (processCommand(commandBuffer)) { | |
commandTimeout = 0; | |
interface.PrintF(100, "ok\n"); | |
interface.PrintF(100, "i %f\n", selectedCurrent); | |
interface.PrintF(100, "v %f\n", battVoltage); | |
interface.PrintF(100, "t %f\n", cellTempreture); | |
interface.PrintF(100, "h %f\n", voltageHO); | |
interface.PrintF(100, "l %f\n", voltageLO); | |
interface.PrintF(100, "s %d\n", state); | |
interface.PrintF(100, "e %d\n", error); | |
interface.PrintF(100, "d %d\n", dischargeCtrl.GetCompareValue()); | |
interface.PrintF(100, "c %d\n", chargeCtrl.GetCompareValue()); | |
} else { | |
interface.PrintF(100, "fail\n"); | |
// invalid command | |
error = InvalidCommand; | |
state = State::Idle; | |
} | |
commandBufferIdx = 0; | |
} else { | |
commandBufferIdx += can_read; | |
} | |
} | |
if (commandBufferIdx == 148) { | |
// overflow | |
state = State::Idle; | |
commandBufferIdx = 0; | |
} | |
commandTimeout++; | |
if (commandTimeout > 100) { | |
// timeout | |
error = Timeout; | |
state = State::Idle; | |
} | |
watchdog.Kick(); | |
vTaskDelayUntil(&lastTime, 5); | |
} | |
} | |
} | |
int main() { | |
return per::embedded::PlaygroundMain(); | |
} | |
#pragma clang diagnostic pop |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment