From 1432a228467a3cc9083450b728c86e4fef98a5e8 Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Tue, 4 Jul 2023 16:53:35 +0100 Subject: [PATCH 01/17] First release of all-in-one code --- .github/workflows/main.yml | 11 +- .gitignore | 1 + STM32All-In-One/.gitignore | 5 + STM32All-In-One/README.md | 70 ++ .../boards/genericSTM32F030K6T6.json | 36 + STM32All-In-One/buildscript.py | 18 + STM32All-In-One/buildscript_versioning.py | 74 ++ .../include/EmbeddedFiles_Defines.h | 16 + STM32All-In-One/include/cell.h | 238 +++++ STM32All-In-One/include/defines.h | 65 ++ STM32All-In-One/include/packet_processor.h | 78 ++ STM32All-In-One/include/stm32_flash.h | 12 + STM32All-In-One/ldscript.ld | 200 ++++ STM32All-In-One/lib/Steinhart/Steinhart.cpp | 27 + STM32All-In-One/lib/Steinhart/Steinhart.h | 11 + STM32All-In-One/lib/crc16/crc16.cpp | 72 ++ STM32All-In-One/lib/crc16/crc16.h | 16 + STM32All-In-One/platformio.ini | 40 + STM32All-In-One/src/cell.cpp | 44 + STM32All-In-One/src/main.cpp | 990 ++++++++++++++++++ STM32All-In-One/src/packet_processor.cpp | 259 +++++ STM32All-In-One/src/stm32_flash.c | 109 ++ 22 files changed, 2390 insertions(+), 2 deletions(-) create mode 100644 STM32All-In-One/.gitignore create mode 100644 STM32All-In-One/README.md create mode 100644 STM32All-In-One/boards/genericSTM32F030K6T6.json create mode 100644 STM32All-In-One/buildscript.py create mode 100644 STM32All-In-One/buildscript_versioning.py create mode 100644 STM32All-In-One/include/EmbeddedFiles_Defines.h create mode 100644 STM32All-In-One/include/cell.h create mode 100644 STM32All-In-One/include/defines.h create mode 100644 STM32All-In-One/include/packet_processor.h create mode 100644 STM32All-In-One/include/stm32_flash.h create mode 100644 STM32All-In-One/ldscript.ld create mode 100644 STM32All-In-One/lib/Steinhart/Steinhart.cpp create mode 100644 STM32All-In-One/lib/Steinhart/Steinhart.h create mode 100644 STM32All-In-One/lib/crc16/crc16.cpp create mode 100644 STM32All-In-One/lib/crc16/crc16.h create mode 100644 STM32All-In-One/platformio.ini create mode 100644 STM32All-In-One/src/cell.cpp create mode 100644 STM32All-In-One/src/main.cpp create mode 100644 STM32All-In-One/src/packet_processor.cpp create mode 100644 STM32All-In-One/src/stm32_flash.c diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 470feec2..f9add1c0 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -50,8 +50,7 @@ jobs: pwd ls - - - name: Build code for modules + - name: Build code for ATTINY modules run: pio run --project-dir=/home/runner/work/diyBMSv4ESP32/diyBMSv4ESP32/ATTINYCellModule --project-conf=/home/runner/work/diyBMSv4ESP32/diyBMSv4ESP32/ATTINYCellModule/platformio.ini - name: Copy output @@ -59,6 +58,13 @@ jobs: cp ./ATTINYCellModule/.pio/build/V*/*.hex ~/OUTPUT/Modules/HEX/ cp ./ATTINYCellModule/.pio/build/V*/*.bin ~/OUTPUT/Modules/BIN/ + - name: Build code for STM32 modules + run: pio run --project-dir=/home/runner/work/diyBMSv4ESP32/diyBMSv4ESP32/STM32All-In-One --project-conf=/home/runner/work/diyBMSv4ESP32/diyBMSv4ESP32/STM32All-In-One/platformio.ini + + - name: Copy STM32 binary firmware output files + run: | + cp ./ATTINYCellModule/.pio/build/V*/*.bin ~/OUTPUT/Modules/BIN/ + - name: Get latest esptool run: | git clone https://github.com/espressif/esptool.git @@ -126,6 +132,7 @@ jobs: zip release.zip ./ControllerBoardTest/diybms_boardtest_espressif32_esp32-devkitc.bin zip release.zip ./Modules/manifest.json zip release.zip ./Modules/HEX/*.* + zip release.zip ./Modules/BIN/module_fw_V490*.* - name: Get current date id: date diff --git a/.gitignore b/.gitignore index bfbba094..5072885d 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,4 @@ dkms.conf *.log ESPController/compile_commands.json ATTINYCellModule/compile_commands.json +STM32All-In-One/compile_commands.json diff --git a/STM32All-In-One/.gitignore b/STM32All-In-One/.gitignore new file mode 100644 index 00000000..89cc49cb --- /dev/null +++ b/STM32All-In-One/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/STM32All-In-One/README.md b/STM32All-In-One/README.md new file mode 100644 index 00000000..a5bdbbe3 --- /dev/null +++ b/STM32All-In-One/README.md @@ -0,0 +1,70 @@ +# DIYBMS All-in-one 16S monitoring board + +This is the code for the (up to) 16S monitoring board. + +It uses an [STM32F030K6T6](https://www.st.com/en/microcontrollers-microprocessors/stm32f030k6.html) chip, with 32K flash and 4K RAM. + +**WARNING**: This code is considered experimental & prototype, use at your own risk - it may not work as intended or at all. + +## How to compile the code + +### Pre-compiled +The easiest way to use the code is with the pre-compiled version generated by [GITHUB Actions](https://github.com/stuartpittaway/diyBMSv4ESP32/actions). + +For this prototype version, look for successful compiles (green ticks) for the "all-in-one" branch. + +Inside the action, you will see "Artifacts" - download the ZIP files for the pre-compiled version of the code, look inside the ZIP file for a "BIN" folder and the files named "module_fw_V490_10K_genericSTM32F030K6T6.bin" or similar. + +### Manual compile +The code is written in C/C++ and you can use VSCode and Platform.IO to compile to code. + +This can also be used to upload the code into the microcontroller. + +Platform.IO should automatically configure the build environment and compile the code for you. + +Recommend installing GITHUB Desktop for a pain free experience. + +The compiled code will be named "module_fw_V490_10K_genericSTM32F030K6T6.bin" inside the ".pio\build\V490_10K" folder - this uses the 10,000 baud communication speed. A 5,000 baud (5K) version is also available. + +## Programming the STM32 chip + +Interfaces for both serial and ST-LINK are provided. If you don't have an ST-LINK programmer, use the serial method below. + +You will require [STM32 Cube Programmer](https://www.st.com/en/development-tools/stm32cubeprog.html) software. + + +### Serial Programming + +Use an off the shelf [USB Serial adapter](https://amzn.to/44umP3v) (Silicon Labs CP210x or similar). + +1. Disconnect the battery cells completely from the monitor PCB (2 pin and 17 pin terminal blocks on left of board). +2. Remove the "PWR" jump pin (if fitted) +3. Move the jump pin "PRG/RUN" to "PRG" (left 2 pins) +4. Connect the serial adapter to the connector marked "UART PROG". Pins are marked on the silkscreen. +``` +Serial Adapter --> Monitoring PCB + GND --> GND + TX --> RX + RX --> TX + +3.3V --> 3.3V +``` +**Under no circumstances connect a 5V supply. Ensure battery connector is unplugged.** + +5. Using STM Cube Programmer, select "UART" (top right of screen) and click "CONNECT" +6. Using the "Erasing & Programming" option (2nd icon down, on left of screen) +7. Select the firmware file in "file path" +8. Check "Verify programming" and then "Start Programming" +9. Wait for "File Download Complete" message, click OK. +10. Wait for "Download verified successfully" message, click OK. +11. Move "PRG/RUN" jumper to "RUN" +12. Disconnect USB programming cable BEFORE connecting battery connector! +13. Once disconnected, re-connect "PWR" jumper if required. + + +### ST-LINK + +Repeat above, but using ST-LINK connector on the PCB. There is no need to change the "PRG/RUN" jumper setting. + +Observe warnings about disconnecting the battery connector before programming, or connecting to your computer. + +Use the STM32 Cube software to program the firmware using ST-LINK protocol. \ No newline at end of file diff --git a/STM32All-In-One/boards/genericSTM32F030K6T6.json b/STM32All-In-One/boards/genericSTM32F030K6T6.json new file mode 100644 index 00000000..00b20e93 --- /dev/null +++ b/STM32All-In-One/boards/genericSTM32F030K6T6.json @@ -0,0 +1,36 @@ +{ + "build": { + "cpu": "cortex-m0", + "extra_flags": "-DSTM32F030x6", + "f_cpu": "48000000L", + "mcu": "stm32f030k6t6", + "product_line": "STM32F030x6", + "variant": "STM32F0xx/F030K6T" + }, + "debug": { + "jlink_device": "STM32F030K6", + "openocd_target": "stm32f0x", + "svd_path": "STM32F0x0.svd" + }, + "frameworks": [ + "arduino", + "cmsis", + "stm32cube", + "libopencm3" + ], + "name": "GENERIC STM32F030K6T6", + "upload": { + "maximum_ram_size": 4096, + "maximum_size": 31744, + "protocol": "stlink", + "protocols": [ + "dfu", + "jlink", + "stlink", + "blackmagic", + "serial" + ] + }, + "url": "", + "vendor": "" + } \ No newline at end of file diff --git a/STM32All-In-One/buildscript.py b/STM32All-In-One/buildscript.py new file mode 100644 index 00000000..df916a1c --- /dev/null +++ b/STM32All-In-One/buildscript.py @@ -0,0 +1,18 @@ +Import("env") + +platform = env.PioPlatform() + +my_flags = env.ParseFlags(env['BUILD_FLAGS']) +defines = {k: v for (k, v) in my_flags.get("CPPDEFINES")} +#print(my_flags) +#print(defines.get("DIYBMSMODULEVERSION")) +#print(str(env["BOARD_MCU"]).lower()) + + +if str(env["BOARD_MCU"]).lower()=="stm32f030k6t6": + efuse=0 + hfuse=0 + lfuse=0 + env.Replace(PROGNAME="module_fw_%s_%s" % (env["PIOENV"],env.get("BOARD"))) + + diff --git a/STM32All-In-One/buildscript_versioning.py b/STM32All-In-One/buildscript_versioning.py new file mode 100644 index 00000000..900c200a --- /dev/null +++ b/STM32All-In-One/buildscript_versioning.py @@ -0,0 +1,74 @@ +""" Script for DIYBMS """ +import datetime +import subprocess +import os +from os import path + +Import("env") + +git_sha=None + +AreWeInGitHubAction = True if "GITHUB_SHA" in env else False + +if (AreWeInGitHubAction): + git_sha=env["GITHUB_SHA"] +else: + if (path.exists('..'+os.path.sep+'.git')): + # Get the latest GIT version header/name + try: + git_sha = subprocess.check_output(['git','log','-1','--pretty=format:%H']).decode('utf-8') + except: + # Ignore any error, user may not have GIT installed + git_sha = None + +# print(env.Dump()) + +# These are used by GenerateBinaryFile.py +# If a user doesn't have GIT installed, use fake fffff numbers... +if (git_sha!=None): + env.Append(git_sha=git_sha) + env.Append(git_sha_short=git_sha[32:]) +else: + env.Append(git_sha="ffffffffffffffffffffffffffffffffffffffff") + env.Append(git_sha_short="ffffffff") + +include_dir = os.path.join(env.get('PROJECT_DIR'), 'include') + +if (os.path.exists(include_dir) == False): + raise Exception("Missing include folder") + + +with open(os.path.join(include_dir, 'EmbeddedFiles_Defines.h'), 'w') as f: + f.write("// This is an automatically generated file, any changes will be overwritten on compiliation!\n") + f.write("// DO NOT CHECK THIS INTO SOURCE CONTROL\n") + f.write("\n\n#ifndef EmbeddedFiles_Defines_H\n#define EmbeddedFiles_Defines_H\n\n") + + f.write("static const char GIT_VERSION[] = \"") + if (git_sha!=None): + f.write(git_sha) + else: + f.write("LocalCompile") + f.write("\";\n\n") + + f.write("static const uint16_t GIT_VERSION_B1 = 0x") + if (git_sha!=None): + f.write(git_sha[32:36]) + else: + #Default for local compile + f.write("FFFF") + f.write(";\n\n") + + f.write("static const uint16_t GIT_VERSION_B2 = 0x") + if (git_sha!=None): + f.write(git_sha[36:]) + else: + #Default for local compile + f.write("FFFF") + f.write(";\n\n") + + + f.write("static const char COMPILE_DATE_TIME[] = \"") + f.write(datetime.datetime.utcnow().isoformat()[:-3]+'Z') + f.write("\";\n\n") + + f.write("#endif") diff --git a/STM32All-In-One/include/EmbeddedFiles_Defines.h b/STM32All-In-One/include/EmbeddedFiles_Defines.h new file mode 100644 index 00000000..fdf4a828 --- /dev/null +++ b/STM32All-In-One/include/EmbeddedFiles_Defines.h @@ -0,0 +1,16 @@ +// This is an automatically generated file, any changes will be overwritten on compiliation! +// DO NOT CHECK THIS INTO SOURCE CONTROL + + +#ifndef EmbeddedFiles_Defines_H +#define EmbeddedFiles_Defines_H + +static const char GIT_VERSION[] = "a4f3024afd95548a3a8628db190a98564f7cd2d5"; + +static const uint16_t GIT_VERSION_B1 = 0x4f7c; + +static const uint16_t GIT_VERSION_B2 = 0xd2d5; + +static const char COMPILE_DATE_TIME[] = "2023-07-04T15:47:51.443Z"; + +#endif \ No newline at end of file diff --git a/STM32All-In-One/include/cell.h b/STM32All-In-One/include/cell.h new file mode 100644 index 00000000..835b2278 --- /dev/null +++ b/STM32All-In-One/include/cell.h @@ -0,0 +1,238 @@ +#include + +#ifndef DIYBMS_CELL_H // include guard +#define DIYBMS_CELL_H + +class Cell +{ +public: + // Returns TRUE if the cell voltage is greater than the required setting + bool BypassCheck() const + { + return (getCellVoltage() > BypassThresholdmV); + } + + /// @brief returns cell voltage with parasitic voltage removed + uint16_t getCellVoltage() const + { + return cellVoltage - getParasiteVoltage(); + } + + uint16_t CombineTemperatures() const + { + return (uint16_t)(TemperatureToByte(getInternalTemperature()) << 8) + TemperatureToByte(getExternalTemperature()); + } + + // Returns TRUE if the internal thermistor is hotter than the required setting (or over max safety limit) + bool BypassOverheatCheck() const + { + auto temp = getInternalTemperature(); + return (temp > BypassTemperatureSetPoint || temp > Cell::SafetyTemperatureCutoff || Cell::getOverTemperature()); + } + + /// @brief Set Cell voltage + /// @param v voltage in millivolts + void setCellVoltage(uint16_t v) + { + cellVoltage = v; + } + /// @brief Sets Parasite voltage (raw value) + /// @param v raw ADC value, which is internally divided by 128 + void setParasiteVoltage(uint16_t v) + { + parasiteVoltage = v / 128; + } + /// @brief Gets Parasite voltage + /// @return value in millivolts + uint16_t getParasiteVoltage() const + { + return parasiteVoltage; + } + /// @brief Set external temperature measurement + /// @param t Temperature in degrees C + void setExternalTemperature(int16_t t) + { + externalTemperature = t; + } + int16_t getExternalTemperature() const + { + return externalTemperature; + } + /// @brief Onboard temperature sensor (for balancing monitoring) + /// @param t temperature in C + void setInternalTemperature(int16_t t) + { + internalTemperature = t; + } + /// @brief Onboard temperature sensor (for balancing monitoring) + /// @return temperature in C + int16_t getInternalTemperature() const + { + return internalTemperature; + } + // This function reduces the scale of temperatures from int16_t type to a single byte (unsigned) + // We have an artifical floor at 40oC, anything below +40 is considered negative (below freezing) + // Gives range of -39 to +216 degrees C + uint8_t TemperatureToByte(int16_t TempInCelcius) const + { + if (TempInCelcius == -999) + { + // Not fitted... + return 0; + } + + TempInCelcius += 40; + // Set the limits and convert from signed to unsigned + if (TempInCelcius < 0) + { + // Limit -39 + TempInCelcius = 1; + } + if (TempInCelcius > 255) + { + TempInCelcius = 255; + } + return (uint8_t)TempInCelcius; + } + + bool IsBypassActive() const + { + return CellIsInBypass; + } + + void StartBypass() + { + // Record when the bypass started + bypassStartTime = millis(); + bypassStartVoltage = getCellVoltage(); + CellIsInBypass = true; + } + void StopBypass() + { + //"guess" how much energy we have burnt during the balance + // this IGNORES any over temperature situation we may had had! + + // bypassStartVoltage is in millivolts, so we get milli-amp current output + // For example 4000mV / 18R = 222.22mA + float CurrentmA = ((float)bypassStartVoltage / (float)LOAD_RESISTANCE); + + float seconds = (millis() - bypassStartTime) / 1000; + + float milliAmpHours = (CurrentmA * seconds) * (1.0 / 3600.0); + + MilliAmpHourBalanceCounter += milliAmpHours; + CellIsInBypass = false; + } + + auto getMilliAmpHourBalanceCounter() const { return MilliAmpHourBalanceCounter; } + void setMilliAmpHourBalanceCounter(float v) + { + MilliAmpHourBalanceCounter = v; + } + auto changesAllowed() const { return ChangesAllowed; } + void disableChanges() + { + ChangesAllowed = false; + } + + static auto getBypassThresholdmV() { return BypassThresholdmV; } + static void setBypassThresholdmV(uint16_t v) + { + BypassThresholdmV = v; + } + static auto getBypassTemperatureSetPoint() { return BypassTemperatureSetPoint; } + static auto getBypassTemperatureHysteresis() { return BypassTemperatureHysteresis; } + + static void setBypassTemperatureSetPoint(uint16_t v) + { + if (v > Cell::SafetyTemperatureCutoff) + { + v = Cell::SafetyTemperatureCutoff; + } + if (v > 20) + { + BypassTemperatureSetPoint = v; + + // Set back hysteresis is set point minus 5 degrees C + BypassTemperatureHysteresis = v - 5; + } + + // Revalidate fan temperature after bypass temperature change + setFanSwitchOnTemperature(getFanSwitchOnTemperature()); + } + + static uint8_t getFanSwitchOnTemperature() { return fanswitchontemperature; } + static void setFanSwitchOnTemperature(uint8_t v) + { + // Sanity check values + if (v > getBypassTemperatureHysteresis() || v < 15) + { + v = getBypassTemperatureHysteresis(); + } + + fanswitchontemperature = v; + } + static auto getRelayMinmV() { return relay_minimummv; } + static void setRelayMinmV(uint16_t v) + { + relay_minimummv = v; + } + static auto getRelayRange() { return relay_range; } + static void setRelayRange(uint16_t v) + { + relay_range = v; + } + + static auto getCalibration() { return Calibration; } + static void setCalibration(float v) + { + Calibration = v; + } + static auto getSafetyTemperatureCutoff() + { + return SafetyTemperatureCutoff; + } + /// @brief Indicator if we have exceeded temperature, and waiting for cooldown + /// @return + static bool getOverTemperature() + { + return OverTemperature; + } + static void setOverTemperature(bool v) + { + OverTemperature = v; + } + +private: + bool ChangesAllowed{true}; + uint16_t cellVoltage{0}; + uint16_t parasiteVoltage{0}; + int16_t externalTemperature{-999}; + int16_t internalTemperature{-999}; + bool CellIsInBypass{false}; + uint32_t bypassStartTime{0}; + uint16_t bypassStartVoltage{0}; + + float MilliAmpHourBalanceCounter{0}; + + // Defined in cell.cpp + /// @brief Define maximum allowed temperature as safety cut off + static const int16_t SafetyTemperatureCutoff; + + /// @brief Max temperature during balance + static uint8_t BypassTemperatureSetPoint; + /// @brief Balance over this voltage + static uint16_t BypassThresholdmV; + /// @brief Voltage calibration multiplier + static float Calibration; + /// @brief Temperature set back once limit is reached + static uint8_t BypassTemperatureHysteresis; + /// @brief Temperature has exceeded BypassTemperatureSetPoint, waiting for drop below BypassTemperatureHysteresis + static bool OverTemperature; + + static uint16_t relay_range; + static uint16_t relay_minimummv; + /// @brief Temperature at which FAN output is enabled + static uint8_t fanswitchontemperature; +}; +#endif diff --git a/STM32All-In-One/include/defines.h b/STM32All-In-One/include/defines.h new file mode 100644 index 00000000..cf3c459e --- /dev/null +++ b/STM32All-In-One/include/defines.h @@ -0,0 +1,65 @@ +/* +LICENSE +Attribution-NonCommercial-ShareAlike 2.0 UK: England & Wales (CC BY-NC-SA 2.0 UK) +https://creativecommons.org/licenses/by-nc-sa/2.0/uk/ + +* Non-Commercial — You may not use the material for commercial purposes. +* Attribution — You must give appropriate credit, provide a link to the license, and indicate if changes were made. + You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use. +* ShareAlike — If you remix, transform, or build upon the material, you must distribute your + contributions under the same license as the original. +* No additional restrictions — You may not apply legal terms or technological measures + that legally restrict others from doing anything the license permits. +*/ + +#include "EmbeddedFiles_Defines.h" +#include "cell.h" + +#ifndef DIYBMS_DEFINES_H // include guard +#define DIYBMS_DEFINES_H + +#if !defined(DIYBMSMODULEVERSION) +#error You need to specify the DIYBMSMODULEVERSION define +#endif + +#if defined(DIYBMSMODULEVERSION) && DIYBMSMODULEVERSION != 490 +#error Incorrect value for DIYBMSMODULEVERSION +#endif + +// Only the lowest 4 bits can be used! +enum COMMAND : uint8_t +{ + ResetBadPacketCounter = 0, + ReadVoltageAndStatus = 1, + Identify = 2, + ReadTemperature = 3, + ReadBadPacketCounter = 4, + ReadSettings = 5, + WriteSettings = 6, + ReadBalancePowerPWM = 7, + Timing = 8, + ReadBalanceCurrentCounter = 9, + ReadPacketReceivedCounter = 10, + ResetBalanceCurrentCounter = 11, + ReadAdditionalSettings = 12, + WriteAdditionalSettings = 13 +}; + +// Default values +struct CellModuleConfig +{ + uint16_t magicnumber; + uint16_t sizeofconfig; + uint8_t BypassTemperatureSetPoint; + uint16_t BypassThresholdmV; + float Calibration; + uint16_t relay_range; + uint16_t relay_minimummv; + uint8_t fanswitchontemperature; + uint16_t RunAwayCellMinimumVoltage; + uint16_t RunAwayCellDifferential; +} __attribute__((packed)); + +using CellData = std::array; + +#endif diff --git a/STM32All-In-One/include/packet_processor.h b/STM32All-In-One/include/packet_processor.h new file mode 100644 index 00000000..50d0b317 --- /dev/null +++ b/STM32All-In-One/include/packet_processor.h @@ -0,0 +1,78 @@ +#ifndef DIYBMS_PACKETPROCESSOR_H // include guard +#define DIYBMS_PACKETPROCESSOR_H + +#include + +#if (!defined(DIYBMSMODULEVERSION)) +#error You need to specify the DIYBMSMODULEVERSION define +#endif + +#include "Steinhart.h" +#include "defines.h" +#include "crc16.h" +#include "cell.h" + +#define maximum_cell_modules 16 + +// NOTE THIS MUST BE EVEN IN SIZE (BYTES) ESP8266 IS 32 BIT AND WILL ALIGN AS SUCH! +struct PacketStruct +{ + uint8_t start_address; + uint8_t end_address; + uint8_t command; + uint8_t hops; + uint16_t sequence; + uint16_t moduledata[maximum_cell_modules]; + uint16_t crc; +} __attribute__((packed)); + +typedef union +{ + float number; + uint8_t bytes[4]; + uint16_t word[2]; +} FLOATUNION_t; + +class PacketProcessor +{ +public: + PacketProcessor() = default; + ~PacketProcessor() = default; + + bool onPacketReceived(PacketStruct *receivebuffer, uint8_t number_of_active_cells, CellData &cells); + uint16_t IncrementWatchdogCounter() + { + watchdog_counter++; + return watchdog_counter; + } + uint16_t getPacketReceivedCounter() const { return PacketReceivedCounter; } + + auto getSettingsHaveChanged() const { return SettingsHaveChanged; } + void clearSettingsHaveChanged() { SettingsHaveChanged = false; } + + auto getRunAwayCellMinimumVoltage() const { return RunAwayCellMinimumVoltage; } + auto getRunAwayCellDifferential() const { return RunAwayCellDifferential; } + + void setRunAwayCellMinimumVoltage(uint16_t v) { RunAwayCellMinimumVoltage = v; } + void setRunAwayCellDifferential(uint16_t v) { RunAwayCellDifferential = v; } + + /// @brief TRUE if daughter board is installed (only checked on boot) + bool BalanceBoardInstalled = false; + +private: + bool processPacket(PacketStruct *buffer, uint8_t, Cell &cell); + + // Count of bad packets of data received, most likely with corrupt data or crc errors + uint16_t badpackets{0}; + // Count of number of WDT events which have triggered, could indicate standalone mode or problems with serial comms + uint16_t watchdog_counter{0}; + + uint16_t PacketReceivedCounter{0}; + + bool SettingsHaveChanged{false}; + + uint16_t RunAwayCellMinimumVoltage; + uint16_t RunAwayCellDifferential; +}; + +#endif diff --git a/STM32All-In-One/include/stm32_flash.h b/STM32All-In-One/include/stm32_flash.h new file mode 100644 index 00000000..a654b910 --- /dev/null +++ b/STM32All-In-One/include/stm32_flash.h @@ -0,0 +1,12 @@ + +#ifndef __STM32_FLASH_H +#define __STM32_FLASH_H + +/* Includes ------------------------------------------------------------------*/ +#include "stm32_def.h" + + +void flash_erase_and_write(uint8_t* buffer, uint16_t buffer_size); +void flash_copy_buffer(uint8_t* buffer, uint16_t numberofbytes); + +#endif /* __STM32_FLASH_H */ diff --git a/STM32All-In-One/ldscript.ld b/STM32All-In-One/ldscript.ld new file mode 100644 index 00000000..7492df89 --- /dev/null +++ b/STM32All-In-One/ldscript.ld @@ -0,0 +1,200 @@ +/* +****************************************************************************** +** +** File : LinkerScript.ld +** +** Author : Auto-generated by STM32CubeIDE +** +** Abstract : Linker script for STM32F030K6Tx Device from STM32F0 series +** 32Kbytes FLASH +** 4Kbytes RAM +** +** Set heap size, stack size and stack location according +** to application requirements. +** +** Set memory bank area and size if external memory is used. +** +** Target : STMicroelectronics STM32 +** +** Distribution: The file is distributed as is without any warranty +** of any kind. +** +***************************************************************************** +** @attention +** +**

© COPYRIGHT(c) 2019 STMicroelectronics

+** +** Redistribution and use in source and binary forms, with or without modification, +** are permitted provided that the following conditions are met: +** 1. Redistributions of source code must retain the above copyright notice, +** this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright notice, +** this list of conditions and the following disclaimer in the documentation +** and/or other materials provided with the distribution. +** 3. Neither the name of STMicroelectronics nor the names of its contributors +** may be used to endorse or promote products derived from this software +** without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +** DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +** OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +** +***************************************************************************** +*/ + +/* Entry Point */ +ENTRY(Reset_Handler) + +/* Highest address of the user mode stack */ +_estack = 0x20001000; /* end of "RAM" Ram type memory */ + +_Min_Heap_Size = 0x200; /* required amount of heap */ +_Min_Stack_Size = 0x400; /* required amount of stack */ + +/* Memories definition */ +MEMORY +{ + FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 32K + RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 4K +} + +/* Sections */ +SECTIONS +{ + /* The startup code into "FLASH" Rom type memory */ + .isr_vector : + { + . = ALIGN(4); + KEEP(*(.isr_vector)) /* Startup code */ + . = ALIGN(4); + } >FLASH + + /* The program code and other data into "FLASH" Rom type memory */ + .text : + { + . = ALIGN(4); + *(.text) /* .text sections (code) */ + *(.text*) /* .text* sections (code) */ + *(.glue_7) /* glue arm to thumb code */ + *(.glue_7t) /* glue thumb to arm code */ + *(.eh_frame) + + KEEP (*(.init)) + KEEP (*(.fini)) + + . = ALIGN(4); + _etext = .; /* define a global symbols at end of code */ + } >FLASH + + /* Constant data into "FLASH" Rom type memory */ + .rodata : + { + . = ALIGN(4); + *(.rodata) /* .rodata sections (constants, strings, etc.) */ + *(.rodata*) /* .rodata* sections (constants, strings, etc.) */ + . = ALIGN(4); + } >FLASH + + .ARM.extab : { + . = ALIGN(4); + *(.ARM.extab* .gnu.linkonce.armextab.*) + . = ALIGN(4); + } >FLASH + + .ARM : { + . = ALIGN(4); + __exidx_start = .; + *(.ARM.exidx*) + __exidx_end = .; + . = ALIGN(4); + } >FLASH + + .preinit_array : + { + . = ALIGN(4); + PROVIDE_HIDDEN (__preinit_array_start = .); + KEEP (*(.preinit_array*)) + PROVIDE_HIDDEN (__preinit_array_end = .); + . = ALIGN(4); + } >FLASH + + .init_array : + { + . = ALIGN(4); + PROVIDE_HIDDEN (__init_array_start = .); + KEEP (*(SORT(.init_array.*))) + KEEP (*(.init_array*)) + PROVIDE_HIDDEN (__init_array_end = .); + . = ALIGN(4); + } >FLASH + + .fini_array : + { + . = ALIGN(4); + PROVIDE_HIDDEN (__fini_array_start = .); + KEEP (*(SORT(.fini_array.*))) + KEEP (*(.fini_array*)) + PROVIDE_HIDDEN (__fini_array_end = .); + . = ALIGN(4); + } >FLASH + + /* Used by the startup to initialize data */ + _sidata = LOADADDR(.data); + + /* Initialized data sections into "RAM" Ram type memory */ + .data : + { + . = ALIGN(4); + _sdata = .; /* create a global symbol at data start */ + *(.data) /* .data sections */ + *(.data*) /* .data* sections */ + + . = ALIGN(4); + _edata = .; /* define a global symbol at data end */ + + } >RAM AT> FLASH + + /* Uninitialized data section into "RAM" Ram type memory */ + . = ALIGN(4); + .bss : + { + /* This is used by the startup in order to initialize the .bss secion */ + _sbss = .; /* define a global symbol at bss start */ + __bss_start__ = _sbss; + *(.bss) + *(.bss*) + *(COMMON) + + . = ALIGN(4); + _ebss = .; /* define a global symbol at bss end */ + __bss_end__ = _ebss; + } >RAM + + /* User_heap_stack section, used to check that there is enough "RAM" Ram type memory left */ + ._user_heap_stack : + { + . = ALIGN(8); + PROVIDE ( end = . ); + PROVIDE ( _end = . ); + . = . + _Min_Heap_Size; + . = . + _Min_Stack_Size; + . = ALIGN(8); + } >RAM + + /* Remove information from the compiler libraries */ + /DISCARD/ : + { + libc.a ( * ) + libm.a ( * ) + libgcc.a ( * ) + } + + .ARM.attributes 0 : { *(.ARM.attributes) } +} diff --git a/STM32All-In-One/lib/Steinhart/Steinhart.cpp b/STM32All-In-One/lib/Steinhart/Steinhart.cpp new file mode 100644 index 00000000..654c0b18 --- /dev/null +++ b/STM32All-In-One/lib/Steinhart/Steinhart.cpp @@ -0,0 +1,27 @@ +#include "Steinhart.h" + + +int16_t Steinhart::ThermistorToCelcius(uint16_t BCOEFFICIENT, uint16_t RawADC, float ADCScaleMax) { + +//We can calculate the Steinhart-Hart Thermistor Equation based on the B Coefficient of the thermistor +// at 25 degrees C rating +#define NOMINAL_TEMPERATURE 25 + +//If we get zero its likely the ADC is connected to ground + if (RawADC>0){ + //https://arduinodiy.wordpress.com/2015/11/10/measuring-temperature-with-ntc-the-steinhart-hart-formula/ + + float steinhart = (ADCScaleMax/(float)RawADC - 1.0F); + + steinhart = log(steinhart); // ln(R/Ro) + steinhart /= BCOEFFICIENT; // 1/B * ln(R/Ro) + steinhart += 1.0F / (NOMINAL_TEMPERATURE + 273.15F); // + (1/To) + steinhart = 1.0F / steinhart; // Invert + steinhart -= 273.15F; // convert to oC + + return (int16_t)steinhart; + } + + return (int16_t)-999; +} + diff --git a/STM32All-In-One/lib/Steinhart/Steinhart.h b/STM32All-In-One/lib/Steinhart/Steinhart.h new file mode 100644 index 00000000..34302c94 --- /dev/null +++ b/STM32All-In-One/lib/Steinhart/Steinhart.h @@ -0,0 +1,11 @@ +#ifndef Steinhart_H // include guard +#define Steinhart_H + +#include + +class Steinhart { + public: + static int16_t ThermistorToCelcius(uint16_t BCOEFFICIENT, uint16_t RawADC, float ADCScaleMax); + +}; +#endif diff --git a/STM32All-In-One/lib/crc16/crc16.cpp b/STM32All-In-One/lib/crc16/crc16.cpp new file mode 100644 index 00000000..94846227 --- /dev/null +++ b/STM32All-In-One/lib/crc16/crc16.cpp @@ -0,0 +1,72 @@ +#include "crc16.h" + +// Calculate XMODEM 16 crc code on data array +uint16_t CRC16::CalculateArray(uint8_t data[], uint16_t length) +{ + //Calculates XMODEM CRC16 + uint8_t reflectIn=false; + uint8_t reflectOut=false; + uint16_t polynomial=0x1021; + uint16_t xorIn=0x0000; + uint16_t xorOut=0x0000; + uint16_t msbMask=0x8000; + uint16_t mask=0xffff; + + uint16_t crc = xorIn; + + int j; + uint8_t c; + uint16_t bit; + + if (length == 0) return crc; + + for (uint16_t i = 0; i < length; i++) + { + c = data[i]; + + if (reflectIn != 0) + c = (uint8_t) reflect(c, 8); + + j = 0x80; + + while (j > 0) + { + bit = (uint16_t)(crc & msbMask); + crc <<= 1; + + if ((c & j) != 0) + { + bit = (uint16_t)(bit ^ msbMask); + } + + if (bit != 0) + { + crc ^= polynomial; + } + + j >>= 1; + } + } + + if (reflectOut != 0) + crc = (uint16_t)((reflect(crc) ^ xorOut) & mask); + + return crc; +} + +uint8_t CRC16::reflect(uint8_t data, uint8_t bits) +{ + unsigned long reflection = 0x00000000; + + for (uint8_t bit = 0; bit < bits; bit++) + { + if ((data & 0x01) != 0) + { + reflection |= (unsigned long)(1 << ((bits - 1) - bit)); + } + + data = (uint8_t)(data >> 1); + } + + return reflection; +} diff --git a/STM32All-In-One/lib/crc16/crc16.h b/STM32All-In-One/lib/crc16/crc16.h new file mode 100644 index 00000000..ee29cdf0 --- /dev/null +++ b/STM32All-In-One/lib/crc16/crc16.h @@ -0,0 +1,16 @@ +#ifndef CHECKSUM16_H // include guard +#define CHECKSUM16_H + +#include + +/* +Calculates XMODEM CRC16 against an array of bytes +*/ + +class CRC16 { + public: + static uint16_t CalculateArray(uint8_t data[], uint16_t length); + private: + static uint8_t reflect(uint8_t data, uint8_t bits = 32); +}; +#endif diff --git a/STM32All-In-One/platformio.ini b/STM32All-In-One/platformio.ini new file mode 100644 index 00000000..dd74139b --- /dev/null +++ b/STM32All-In-One/platformio.ini @@ -0,0 +1,40 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[platformio] +default_envs = V490_10K, V490_5K + +[env] +;Version 16 results in a compile size larger than 32k +platform = ststm32@15.0.0 +board = genericSTM32F030K6T6 +framework = arduino +board_build.ldscript = ldscript.ld + +extra_scripts = + pre:buildscript_versioning.py + pre:buildscript.py + +lib_deps = + https://github.com/stuartpittaway/SerialEncoder + +monitor_port=COM3 +monitor_speed=115200 + +upload_port = COM6 +debug_tool = stlink +;upload_protocol = stlink +upload_protocol = serial + +[env:V490_10K] +build_flags=-DDIYBMSMODULEVERSION=490 -DINT_BCOEFFICIENT=3950 -DEXT_BCOEFFICIENT=3950 -DLOAD_RESISTANCE=18.0 -DDIYBMSBAUD=10000 -DDIYBMSREFMILLIVOLT=4096 -DADC_SAMPLINGTIME=ADC_SAMPLETIME_239CYCLES_5 + +[env:V490_5K] +build_flags=-DDIYBMSMODULEVERSION=490 -DINT_BCOEFFICIENT=3950 -DEXT_BCOEFFICIENT=3950 -DLOAD_RESISTANCE=18.0 -DDIYBMSBAUD=5000 -DDIYBMSREFMILLIVOLT=4096 -DADC_SAMPLINGTIME=ADC_SAMPLETIME_239CYCLES_5 diff --git a/STM32All-In-One/src/cell.cpp b/STM32All-In-One/src/cell.cpp new file mode 100644 index 00000000..7a0331be --- /dev/null +++ b/STM32All-In-One/src/cell.cpp @@ -0,0 +1,44 @@ +/* +____ ____ _ _ ____ __ __ ___ +( _ \(_ _)( \/ )( _ \( \/ )/ __) +)(_) )_)(_ \ / ) _ < ) ( \__ \ +(____/(____) (__) (____/(_/\/\_)(___/ + +DIYBMS V4 + +V490 UP TO 16S CELL MONITORING MODULES + +STM32F030K6T6 + +(c)2023 Stuart Pittaway + +COMPILE THIS CODE USING PLATFORM.IO + +LICENSE +Attribution-NonCommercial-ShareAlike 2.0 UK: England & Wales (CC BY-NC-SA 2.0 UK) +https://creativecommons.org/licenses/by-nc-sa/2.0/uk/ + +* Non-Commercial — You may not use the material for commercial purposes. +* Attribution — You must give appropriate credit, provide a link to the license, and indicate if changes were made. + You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use. +* ShareAlike — If you remix, transform, or build upon the material, you must distribute your + contributions under the same license as the original. +* No additional restrictions — You may not apply legal terms or technological measures + that legally restrict others from doing anything the license permits. +*/ + +#include "cell.h" + +// These variables are shared across all instances of this class +uint8_t Cell::BypassTemperatureSetPoint; +uint16_t Cell::BypassThresholdmV; +float Cell::Calibration; +uint8_t Cell::BypassTemperatureHysteresis; + +// Maximum temperature to allow +const int16_t Cell::SafetyTemperatureCutoff = 80; +bool Cell::OverTemperature = false; + +uint16_t Cell::relay_range; +uint16_t Cell::relay_minimummv; +uint8_t Cell::fanswitchontemperature; diff --git a/STM32All-In-One/src/main.cpp b/STM32All-In-One/src/main.cpp new file mode 100644 index 00000000..afa4b9b7 --- /dev/null +++ b/STM32All-In-One/src/main.cpp @@ -0,0 +1,990 @@ +/* +____ ____ _ _ ____ __ __ ___ +( _ \(_ _)( \/ )( _ \( \/ )/ __) +)(_) )_)(_ \ / ) _ < ) ( \__ \ +(____/(____) (__) (____/(_/\/\_)(___/ + +DIYBMS V4 + +V490 UP TO 16S CELL MONITORING MODULES + +STM32F030K6T6 + +(c)2023 Stuart Pittaway + +COMPILE THIS CODE USING PLATFORM.IO + +LICENSE +Attribution-NonCommercial-ShareAlike 2.0 UK: England & Wales (CC BY-NC-SA 2.0 UK) +https://creativecommons.org/licenses/by-nc-sa/2.0/uk/ + +* Non-Commercial — You may not use the material for commercial purposes. +* Attribution — You must give appropriate credit, provide a link to the license, and indicate if changes were made. + You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use. +* ShareAlike — If you remix, transform, or build upon the material, you must distribute your + contributions under the same license as the original. +* No additional restrictions — You may not apply legal terms or technological measures + that legally restrict others from doing anything the license permits. +*/ + +#define RX_BUFFER_SIZE 64 + +#include + +extern "C" +{ +#include "stm32_flash.h" +} + +#include "SPI.h" +#include +#include "packet_processor.h" + +// Our project code includes +#include "defines.h" +#include "cell.h" +#include +#include "packet_processor.h" + +#if !defined(DIYBMSBAUD) +#error Expected DIYBMSBAUD define +#endif + +#if !defined(HAL_UART_MODULE_ENABLED) +#error Expected HAL_UART_MODULE_ENABLED +#endif + +// Two unused jumpers which could be used to enable features/comms address etc. +const auto ADDRESS0_JP3 = PA11; + +const auto AFE_EN = PB0; +const auto RELAY = PB1; +const auto AFE_CS = PB5; +const auto ADC_CS_CNVST = PB6; +const auto SAMPLE_AFE = PB4; +const auto THERM_ENABLE = PA8; +const auto THERM_J11_T1 = PA_4; +const auto THERM_J12_T2 = PA_0; +const auto THERM_J13_T3 = PA_1; +const auto BALANCE_DETECT = PA12; +const auto FAN = PB3; + +// Prototype functions... +[[noreturn]] void ErrorFlashes(int number); +uint32_t takeRawMCP33151ADCReading(); +uint32_t MAX14921Command(uint8_t b1, uint8_t b2, uint8_t b3); +void DecimateRawADCParasiteVoltage(std::array &rawADC, CellData &cd, uint8_t numberCells); +void DecimateRawADCCellVoltage(std::array &rawADC, CellData &cd, uint8_t numberCells); +void TakeExternalTempMeasurements(CellData &cd); +uint16_t DecimateValue(uint64_t val); +void EnableThermistorPower(); +void DisableThermistorPower(); +int16_t ReadThermistor(uint32_t); +int16_t ReadTH(); +uint32_t MAX14921Command(uint16_t b1_2, uint8_t b3); +uint16_t CalculateCellBalanceRequirement(CellData &cd, uint8_t number_of_active_cells); + +uint32_t fan_timer = 0; + +uint32_t relay_timer = 0; + + + +/// @brief Global variable to delay balance function on first power up +uint8_t waitbeforebalance = 20; + +/// @brief Global variable to hold cell level data as its collected +CellData celldata{}; + +/// @brief Number of cells actually in use by the MAX chip (between 4 and 16) +uint8_t number_of_active_cells = 0; + +uint8_t SerialPacketReceiveBuffer[8 + sizeof(PacketStruct)]; + +/// @brief Serial encoder +SerialEncoder myPacketSerial; + +PacketProcessor PP; + +/// @brief Bitmap of which cells are balancing (maps into MAX chip register) +uint16_t cell_balancing = 0; + +const uint32_t LEVEL_SHIFTING_DELAY_MAX = 50; // μs +const uint32_t T_SETTLING_TIME_MAX = 10; // μs + +// Command byte 1 +#define AFE_CB1 bit(7) +#define AFE_CB2 bit(6) +#define AFE_CB3 bit(5) +#define AFE_CB4 bit(4) +#define AFE_CB5 bit(3) +#define AFE_CB6 bit(2) +#define AFE_CB7 bit(1) +#define AFE_CB8 bit(0) + +// Command byte 2 +#define AFE_CB9 bit(7) +#define AFE_CB10 bit(6) +#define AFE_CB11 bit(5) +#define AFE_CB12 bit(4) +#define AFE_CB13 bit(3) +#define AFE_CB14 bit(2) +#define AFE_CB15 bit(1) +#define AFE_CB16 bit(0) +// Command byte 3 +// Enable Cell Selection +#define AFE_ECS bit(7) +// Selects the cell for voltage readout during hold phase +#define AFE_SC0 bit(6) +#define AFE_SC1 bit(5) +#define AFE_SC2 bit(4) +#define AFE_SC3 bit(3) +// 0: Device in sample phase if SAMPL input is logic-high, 1: Device in hold phase +// Not used by this code - using SAMPLE pin instead +#define AFE_SMPLB bit(2) +// Diagnostic enable (normally zero) +#define AFE_DIAG bit(1) +// Low-power mode enabled (normally zero) +#define AFE_LOPW bit(0) + +#define BUFFER_CALIBRATION AFE_SC0 + +#define ANALOG_BUFFERED_T1 AFE_SC0 | AFE_SC2 | AFE_SC3 +#define ANALOG_BUFFERED_T2 AFE_SC1 | AFE_SC2 | AFE_SC3 +#define ANALOG_BUFFERED_T3 AFE_SC0 | AFE_SC1 | AFE_SC2 | AFE_SC3 + +#define ANALOG_CELL1 0 +#define ANALOG_CELL2 AFE_SC0 +#define ANALOG_CELL3 AFE_SC1 +#define ANALOG_CELL4 AFE_SC0 | AFE_SC1 +#define ANALOG_CELL5 AFE_SC2 +#define ANALOG_CELL6 AFE_SC0 | AFE_SC2 +#define ANALOG_CELL7 AFE_SC1 | AFE_SC2 +#define ANALOG_CELL8 AFE_SC0 | AFE_SC1 | AFE_SC2 +#define ANALOG_CELL9 AFE_SC3 +#define ANALOG_CELL10 AFE_SC0 | AFE_SC3 +#define ANALOG_CELL11 AFE_SC1 | AFE_SC3 +#define ANALOG_CELL12 AFE_SC0 | AFE_SC1 | AFE_SC3 +#define ANALOG_CELL13 AFE_SC2 | AFE_SC3 +#define ANALOG_CELL14 AFE_SC0 | AFE_SC2 | AFE_SC3 +#define ANALOG_CELL15 AFE_SC1 | AFE_SC2 | AFE_SC3 +#define ANALOG_CELL16 AFE_SC0 | AFE_SC1 | AFE_SC2 | AFE_SC3 + +constexpr std::array CellTable = + { + AFE_ECS | ANALOG_CELL1, + AFE_ECS | ANALOG_CELL2, + AFE_ECS | ANALOG_CELL3, + AFE_ECS | ANALOG_CELL4, + AFE_ECS | ANALOG_CELL5, + AFE_ECS | ANALOG_CELL6, + AFE_ECS | ANALOG_CELL7, + AFE_ECS | ANALOG_CELL8, + AFE_ECS | ANALOG_CELL9, + AFE_ECS | ANALOG_CELL10, + AFE_ECS | ANALOG_CELL11, + AFE_ECS | ANALOG_CELL12, + AFE_ECS | ANALOG_CELL13, + AFE_ECS | ANALOG_CELL14, + AFE_ECS | ANALOG_CELL15, + AFE_ECS | ANALOG_CELL16}; + +extern "C" void SystemClock_Config(void) +{ + RCC_OscInitTypeDef RCC_OscInitStruct = {}; + RCC_ClkInitTypeDef RCC_ClkInitStruct = {}; + RCC_PeriphCLKInitTypeDef PeriphClkInit = {}; + + /* Initializes the RCC Oscillators according to the specified parameters in the RCC_OscInitTypeDef structure. */ + RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; + RCC_OscInitStruct.HSEState = RCC_HSE_ON; + RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE; + if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) + { + Error_Handler(); + } + + /* Initializes the CPU, AHB and APB buses clocks */ + RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1; + RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSE; + RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; + RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1; + + if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK) + { + Error_Handler(); + } + PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_USART1; + PeriphClkInit.Usart1ClockSelection = RCC_USART1CLKSOURCE_PCLK1; + if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK) + { + Error_Handler(); + } + + /* Enables the Clock Security System */ + HAL_RCC_EnableCSS(); +} + +uint32_t SPITransfer24(uint8_t byte1, uint8_t byte2, uint8_t byte3) +{ + uint32_t data; + + data = SPI.transfer(byte1); + data <<= 8; + + data |= SPI.transfer(byte2); + data <<= 8; + + data |= SPI.transfer(byte3); + + return data; +} + +/// @brief Query MAX14921 chip (analog front end=AFE) and determine its ready status +/// @return number of connected battery cells (up to 16) +uint8_t queryAFE() +{ + uint8_t cellCount; + uint8_t DeviceIsNotReady = 1; + uint8_t countDown = 20; + + while (countDown > 0) + { + // MAX14921 + digitalWrite(SAMPLE_AFE, HIGH); // Sample cell voltages (all 16 cells at same time) + delay(60); // Wait 60ms for capacitors to fill and equalize against cell voltages + digitalWrite(SAMPLE_AFE, LOW); // Disable sampling - HOLD mode + delayMicroseconds(LEVEL_SHIFTING_DELAY_MAX); // Wait to settle + + // Tell AFE to switch AOUT channel to zero, all balancing off + auto reply = MAX14921Command(0, 0, CellTable.at(0)); + + // Bad reply - chip not enabled or connected? + if (reply == 0) + { + ErrorFlashes(7); + } + + DeviceIsNotReady = (reply & B00000010) >> 1; + + // Should be ZERO = MAX14921, 2= MAX14920 + // uint8_t productIdentifier = (reply & B11000000) >> 6; + // uint8_t dieVersion = (reply & B00110000) >> 4; + + // 1: UV_VA = VA is below UV_VAVTH (4.7V) + // VA (Analog Supply Voltage) below UV_VAVTH (4.7V) + if (((reply & B00001000) >> 3) != 0) + { + ErrorFlashes(4); + } + // 1: VP is below UV_VPVTH (6V). If LOPW = 1, VP UVLO circuit is disabled and this bit is always set to 1 + if (((reply & B00000100) >> 2) != 0) + { + ErrorFlashes(5); + } + + // thermal shutdown + if ((reply & B00000001) != 0) + { + ErrorFlashes(2); + } + + // Calculate how many cells are valid (based on reply from MAX chip) + // Start with 16 cells + cellCount = 16; + uint16_t cells = (reply & 0x00FFFF00) >> 8; + // Subtract 1 from cell count for each bit thats "1" (out of voltage range) + // bit ="1" if under voltage of UV_VCVTH or over 5V + while (cells) + { + cellCount -= cells & 1; + cells >>= 1; + } + + if (DeviceIsNotReady == 0) + { + // Everything is good - exit loop + break; + } + + // Loop again if needed until device is ready + delay(250); + countDown--; + } + + // Device not ready after 5 seconds + if (countDown == 0) + { + ErrorFlashes(6); + } + + return cellCount; +} + +/// @brief Loop through battery cells and take ADC measurement and store the raw (un-decimated) value in an array +/// @param cellCount number of cells to sample +/// @param rawADC array to hold output result +void ADCSampleCellVoltages(uint8_t cellCount, std::array &rawADC) +{ + // Scan active cell voltages (starting at highest) + for (int8_t cellid = cellCount; cellid >= 0; cellid--) + { + MAX14921Command(0, CellTable.at(cellid)); + // Delay required for AOUT to settle + delayMicroseconds(T_SETTLING_TIME_MAX); + // Read cell voltage + rawADC.at(cellid) = takeRawMCP33151ADCReading(); + } +} + +/// @brief Measure internal parasitic sampling voltages to offset from future voltage measurements +/// @param cellCount number of cells to sample +/// @param cd Cell data array +void ParasiticCapacitanceChargeInjectionErrorCalibration(uint8_t cellCount, CellData &cd) +{ + // MAX14921 + digitalWrite(SAMPLE_AFE, HIGH); // Sample mode + // Parasitic Capacitance Charge Injection Error Calibration + MAX14921Command(0, 0, 0); + delay(250); + digitalWrite(SAMPLE_AFE, LOW); // Hold mode + + std::array rawADC; + rawADC.fill(0); + + ADCSampleCellVoltages(cellCount, rawADC); + + DecimateRawADCParasiteVoltage(rawADC, cd, cellCount); + + digitalWrite(SAMPLE_AFE, HIGH); // Sample mode +} + +/// @brief Calibrate internal op-amp buffer +void BufferAmplifierOffsetCalibration() +{ + // MAX14921 + digitalWrite(SAMPLE_AFE, HIGH); // Sample mode + + // Parasitic Capacitance Charge Injection Error Calibration + MAX14921Command(0, BUFFER_CALIBRATION); + + // Takes 8ms to complete + delay(8); +} + +void NotificationLedOn() +{ + digitalWrite(PB7, HIGH); +} +void NotificationLedOff() +{ + digitalWrite(PB7, LOW); +} + +void configureModules() +{ + CellModuleConfig config; + + // Default values for cells + float Calibration = 1.0F; + uint8_t BypassTemperatureSetPoint = 55; + uint16_t BypassThresholdmV = 4250; + uint16_t relay_range = 5; + uint16_t relay_minimummv = 3400; + int16_t fanswitchontemperature = BypassTemperatureSetPoint - 8; + + uint16_t RunAwayCellMinimumVoltage = 3400; + uint16_t RunAwayCellDifferential = 75; + + flash_copy_buffer((uint8_t *)&config, sizeof(CellModuleConfig)); + // If we have a FLASH configuration, use that... + if (config.magicnumber == 0x1234 && config.sizeofconfig == sizeof(CellModuleConfig)) + { + Calibration = config.Calibration; + BypassTemperatureSetPoint = config.BypassTemperatureSetPoint; + BypassThresholdmV = config.BypassThresholdmV; + relay_range = config.relay_range; + relay_minimummv = config.relay_minimummv; + fanswitchontemperature = config.fanswitchontemperature; + RunAwayCellMinimumVoltage = config.RunAwayCellMinimumVoltage; + RunAwayCellDifferential = config.RunAwayCellDifferential; + } + + // These values are shared/static between cell instances + Cell::setCalibration(Calibration); + Cell::setBypassTemperatureSetPoint(BypassTemperatureSetPoint); + Cell::setBypassThresholdmV(BypassThresholdmV); + Cell::setRelayMinmV(relay_minimummv); + Cell::setRelayRange(relay_range); + Cell::setFanSwitchOnTemperature(fanswitchontemperature); + + PP.setRunAwayCellMinimumVoltage(RunAwayCellMinimumVoltage); + PP.setRunAwayCellDifferential(RunAwayCellDifferential); + + // All cells excluding ZERO are prevented from having config changes made in this all-in-one board setup + for (auto i = 1; i < celldata.size(); i++) + { + celldata.at(i).disableChanges(); + } +} + +void onPacketReceived() +{ + // diyBMSHAL::EnableSerial0TX(); + + // A data packet has just arrived, process it and forward the results to the + // next module + + if (PP.onPacketReceived((PacketStruct *)SerialPacketReceiveBuffer, number_of_active_cells, celldata)) + { + // Only light Notification if packet is good + NotificationLedOn(); + } + + // Send the packet (fixed length!) (even if it was invalid so controller can count crc errors) + myPacketSerial.sendBuffer(SerialPacketReceiveBuffer); + + Serial1.flush(); + + NotificationLedOff(); + + if (PP.getSettingsHaveChanged()) + { + // We need to update the settings stored in FLASH + // As this hardware pretends to be a 16S module - we copy all the settings from cell zero, everything else is ignored/wiped out + + // Save new values into flash - important to align this buffer "aligned(8)" for correct write into FLASH + CellModuleConfig config __attribute__((aligned(8))) = { + 0x1234, + sizeof(CellModuleConfig), + Cell::getBypassTemperatureSetPoint(), + Cell::getBypassThresholdmV(), + Cell::getCalibration(), + Cell::getRelayRange(), + Cell::getRelayMinmV(), + Cell::getFanSwitchOnTemperature(), + PP.getRunAwayCellMinimumVoltage(), + PP.getRunAwayCellDifferential()}; + + // This function uses the top 1K of FLASH memory to emulate EEPROM + flash_erase_and_write((uint8_t *)&config, sizeof(config)); + + configureModules(); + + PP.clearSettingsHaveChanged(); + } +} + +void setup() +{ + SystemClock_Config(); + + // LED + pinMode(PB7, OUTPUT); + NotificationLedOn(); + + pinMode(SAMPLE_AFE, OUTPUT); + pinMode(AFE_CS, OUTPUT); + pinMode(ADC_CS_CNVST, OUTPUT); + pinMode(THERM_ENABLE, OUTPUT); + pinMode(RELAY, OUTPUT); + pinMode(ADDRESS0_JP3, INPUT_FLOATING); + pinMode(AFE_EN, OUTPUT); + pinMode(THERM_J11_T1, INPUT_ANALOG); + pinMode(THERM_J12_T2, INPUT_ANALOG); + pinMode(THERM_J13_T3, INPUT_ANALOG); + pinMode(FAN, OUTPUT); + + // RELAY off + digitalWrite(RELAY, LOW); + // FAN off + digitalWrite(FAN, LOW); + digitalWrite(ADC_CS_CNVST, HIGH); + digitalWrite(AFE_CS, HIGH); + + // Track cell voltages (LOW=HOLD SAMPLE) + digitalWrite(SAMPLE_AFE, HIGH); + + DisableThermistorPower(); + + // STM32 use 12 bit ADC resolution + analogReadResolution(12); + // STM32 default voltage reference (3.3v) + analogReference(AR_DEFAULT); + + // Check if balance board is fitted? (pin pulled up to 3.3v by daughter board) + pinMode(BALANCE_DETECT, INPUT_PULLDOWN); + PP.BalanceBoardInstalled = digitalRead(BALANCE_DETECT) == HIGH; + // Disable pull down after checking + pinMode(BALANCE_DETECT, INPUT_FLOATING); + + // Set up data handler + Serial1.setTx(PA_9); + Serial1.setRx(PA_10); + Serial1.begin(DIYBMSBAUD, SERIAL_8N1); + myPacketSerial.begin(&Serial1, &onPacketReceived, sizeof(PacketStruct), SerialPacketReceiveBuffer, sizeof(SerialPacketReceiveBuffer)); + + SPI.begin(); + + // Restart MAX chip before we crack on... + digitalWrite(AFE_EN, LOW); + delay(100); + digitalWrite(AFE_EN, HIGH); + + // Warm up ADC + for (size_t i = 0; i < 128; i++) + { + takeRawMCP33151ADCReading(); + } + + number_of_active_cells = queryAFE(); + + // We need at least 4 cells with correct voltages for this to work + if (number_of_active_cells < 4) + { + ErrorFlashes(3); + } + + ParasiticCapacitanceChargeInjectionErrorCalibration(number_of_active_cells, celldata); + + BufferAmplifierOffsetCalibration(); + + configureModules(); + + NotificationLedOff(); +} + +uint32_t takeRawMCP33151ADCReading() +{ + /* ADC READING */ + // INPUT ACQUISITION PHASE (STANDBY), TOGGLE CONVERT START + auto MCP33151settings = SPISettings(50000000, MSBFIRST, SPI_MODE0); + + // Take a reading and throw away - reset ADC accumulator to zero + SPI.beginTransaction(MCP33151settings); + digitalWrite(ADC_CS_CNVST, LOW); + SPI.transfer(0xFF); + SPI.transfer(0xFF); + digitalWrite(ADC_CS_CNVST, HIGH); + SPI.endTransaction(); + + // Use MCP33151 Accumulator to over sample input voltage + // Pulse CNVST for each sample we want to take and average + // 256x over sample (last sample is taken by SPITransfer24 below) + for (size_t i = 0; i < 256 - 1; i++) + { + digitalWrite(ADC_CS_CNVST, LOW); + digitalWrite(ADC_CS_CNVST, HIGH); + // This is probably not required... + delayMicroseconds(1); + } + + // Take a reading + SPI.beginTransaction(MCP33151settings); + // ADC conversion stops when CNVST goes LOW + digitalWrite(ADC_CS_CNVST, LOW); + auto val = SPITransfer24(0xFF, 0xFF, 0xFF); + digitalWrite(ADC_CS_CNVST, HIGH); + SPI.endTransaction(); + + return val; +} + +/// @brief Decimates an ADC reading after oversampling +/// @param val Takes in a raw ADC value which has been oversampled giving ENOB of 16.5-17 bits +/// @return Corrected value +uint16_t DecimateValue(uint64_t val) +{ + // decimate result by right shifting 2 places (raw ADC is 14 bits) + val = val >> 2; + // 4096 voltage reference + val = val * ((uint64_t)DIYBMSREFMILLIVOLT); + // 4194304 = 2^22 effective resolution bits + val = val / 4194304UL; + + return (uint16_t)val; +} + +// Take the raw oversampled readings and decimate +void DecimateRawADCParasiteVoltage(std::array &rawADC, CellData &cells, uint8_t numberCells) +{ + for (int cellid = 0; cellid < numberCells; cellid++) + { + cells.at(cellid).setParasiteVoltage(DecimateValue(rawADC[cellid])); + } +} +// Take the raw oversampled readings and decimate +void DecimateRawADCCellVoltage(std::array &rawADC, CellData &cells, uint8_t numberCells) +{ + for (int cellid = 0; cellid < numberCells; cellid++) + { + cells.at(cellid).setCellVoltage(DecimateValue(rawADC[cellid])); + } +} + +/// @brief Send a 24bit command to MAX14921 chip over SPI +/// @param b1_2 byte1 and 2 as uint16 +/// @param b3 byte3 +/// @return 24 bit answer +uint32_t MAX14921Command(uint16_t b1_2, uint8_t b3) +{ + uint8_t b1 = (b1_2 & 0xFF00) >> 8; + uint8_t b2 = b1_2 & 0x00FF; + return MAX14921Command(b1, b2, b3); +} + +/// @brief Send a 24bit command to MAX14921 chip over SPI +/// @param b1 byte1 +/// @param b2 byte2 +/// @param b3 byte3 +/// @return 24 bit answer +uint32_t MAX14921Command(uint8_t b1, uint8_t b2, uint8_t b3) +{ + // Tell AFE to switch AOUT channel to this cell for the ADC to measure + const auto MAX14921SPISettings = SPISettings(40000000, MSBFIRST, SPI_MODE0); + SPI.beginTransaction(MAX14921SPISettings); + digitalWrite(AFE_CS, LOW); + auto reply = SPITransfer24(b1, b2, b3); + digitalWrite(AFE_CS, HIGH); + SPI.endTransaction(); + return reply; +} + +/// @brief Read external temperature sensors, connected to STM32 +/// @return Temperature in celcius. A value of -999 means not-connected (shorted to ground) +int16_t ReadThermistor(uint32_t pin) +{ + return Steinhart::ThermistorToCelcius(EXT_BCOEFFICIENT, (uint16_t)analogRead(pin), 4095); +} + +/// @brief Read external temperature sensors (connected to board J11/J12/J13 sockets) +/// @param cd Cell data array +void TakeExternalTempMeasurements(CellData &cd) +{ + // Set cell 1 and 2 to have these external temperature sensors, cell 0 uses TH3 onboard + cd.at(1).setExternalTemperature(ReadThermistor(THERM_J11_T1)); + cd.at(2).setExternalTemperature(ReadThermistor(THERM_J12_T2)); + cd.at(3).setExternalTemperature(ReadThermistor(THERM_J13_T3)); +} + +void EnableThermistorPower() +{ + digitalWrite(THERM_ENABLE, LOW); +} + +void DisableThermistorPower() +{ + digitalWrite(THERM_ENABLE, HIGH); +} +/// @brief Read onboard-TH1 sensor +/// @return Celcius temperature reading +int16_t ReadTH() +{ + auto value = DecimateValue(takeRawMCP33151ADCReading()); + // THx is connected to 3.3V max via 10K resistors - scale 3.3V to 4.096V reference + // 3.600 is used as temperature appears to be over read by 2 celcius + return Steinhart::ThermistorToCelcius(INT_BCOEFFICIENT, value, (3.600F / ((float)DIYBMSREFMILLIVOLT / 1000.0F)) * 4095.0F); +} + +/// @brief internal temperature sensors (on board) +/// @param cd Cell Data array +void TakeOnboardInternalTempMeasurements(CellData &cd) +{ + // internal temperature sensors (on board) + // Read temperature sensors + MAX14921Command(cell_balancing, ANALOG_BUFFERED_T1); + auto t1 = ReadTH(); + MAX14921Command(cell_balancing, ANALOG_BUFFERED_T2); + auto t2 = ReadTH(); + MAX14921Command(cell_balancing, ANALOG_BUFFERED_T3); + auto t3 = ReadTH(); + + // Cell 0 external temperature is the on-board sensor (marked TH6) + cd.at(0).setExternalTemperature(t3); + + // Spread the two internal temperature sensor values across all 16 cells (even though some may not be connected) + // Odd cells are on the left of the balance board, near TH1/T1, even on right side TH2/T2 + for (size_t i = 0; i < 16; i += 2) + { + cd.at(i).setInternalTemperature(t1); + cd.at(i + 1).setInternalTemperature(t2); + } +} + +[[noreturn]] void ErrorFlashes(int number) +{ + NotificationLedOff(); + delay(500); + while (true) + { + for (size_t i = 0; i < number; i++) + { + NotificationLedOn(); + delay(220); + NotificationLedOff(); + delay(220); + } + + delay(2000); + } +} + +/// @brief Calculate bit pattern for cell passive balancing (MOSFET switches) +/// @param cd +/// @param num_cells +/// @return bit pattern +uint16_t CalculateCellBalanceRequirement(CellData &cd, uint8_t num_cells) +{ + // if balance daughter board is not installed, always return zero - no balance + if (PP.BalanceBoardInstalled == false) + { + return 0; + } + + uint16_t reply = 0; + + for (auto i = 0; i < num_cells; i++) + { + // Get reference to cell object + Cell &cell = cd.at(i); + + if (cell.BypassCheck() == true) + { + // Our cell voltage is OVER the voltage setpoint limit, start draining cell using bypass resistor + if (!cell.IsBypassActive()) + { + // We have just entered the bypass code + cell.StartBypass(); + } + + // Enable balancing bit pattern - this enables the MOSFET and balance resistor + reply = reply | (uint16_t)(1U << (15 - i)); + } + else + { + if (cell.IsBypassActive()) + { + // We've just ended bypass.... + cell.StopBypass(); + } + } + } + return reply; +} + +/// @brief Calculate various functions against cell voltages +/// @param cd Cell data +/// @param num_cells number of active cells +/// @param lowestmV (reference) Lowest cell voltage (millivolt) +/// @param highestmV (reference) Highest cell voltage (millivolt) +/// @param range (reference) Range between highest and lowest cell voltages (millivolt) +/// @param highest_index Cell index with the highest voltage +/// @param total Total voltage of all cells (millivolt) +void CalculateCellVoltageMinMaxAvg(CellData &cd, + uint8_t num_cells, + uint16_t &lowestmV, + uint16_t &highestmV, + uint16_t &range, + uint8_t &highest_index, + uint32_t &total) +{ + lowestmV = 0xFFFF; + highestmV = 0; + // Index of cell with highest voltage + highest_index = -1; + total = 0; + + // Determine highest/lowest cell and average voltage + for (auto i = 0; i < num_cells; i++) + { + // Get reference to cell object + const Cell &cell = cd.at(i); + auto v = cell.getCellVoltage(); + total += v; + if (v < lowestmV) + { + lowestmV = v; + } + if (v > highestmV) + { + highestmV = v; + highest_index = i; + } + } + + // Determine voltage differential/range (highest - lowest) + range = highestmV - lowestmV; +} + +/// @brief Determine what (if any) cells need balancing, also controls relay output +uint16_t DoCellBalancing(const int16_t highestTemp) +{ + + uint16_t lowestmV; + uint16_t highestmV; + uint8_t highest_index; + uint16_t range; + uint32_t total; + + CalculateCellVoltageMinMaxAvg(celldata, number_of_active_cells, lowestmV, highestmV, range, highest_index, total); + + // Switch relay off + if (HAL_GetTick() > relay_timer) + { + relay_timer = 0; + digitalWrite(RELAY, LOW); + } + + // Enable external relay outputs, if both range and minimum voltage have been met + // Setup a timer, to enable this output for a minimum of 1 minute over-run after balance condition is no longer met + if (range > Cell::getRelayRange() && highestmV > Cell::getRelayMinmV()) + { + digitalWrite(HIGH, HIGH); + // This might run into problems if the HAL_GetTick wraps around - approx. every 50 days + relay_timer = HAL_GetTick() + 60000; + } + + // Daughter board not installed - stop here, no balancing possible (or fan control) + if (!PP.BalanceBoardInstalled) + { + return 0; + } + + // Now check the temperatures to ensure we remain safe + if (highestTemp > Cell::getBypassTemperatureSetPoint() || highestTemp > Cell::getSafetyTemperatureCutoff()) + { + // Temperature is over limit (or safety limit) + Cell::setOverTemperature(true); + // Disable all MOSFETs, allow to cool + return 0; + } + + if (Cell::getOverTemperature() == true) + { + // We went over temperature, so disable balancing until temperature drops below Hysteresis value + if (highestTemp > Cell::getBypassTemperatureHysteresis()) + { + // Still too hot - Disable all MOSFETs, allow to cool + return 0; + } + else + { + // Temperature dropped below limit, so return to normal, allow balancing + Cell::setOverTemperature(false); + } + } + + // Start with everything switched off + uint16_t cb = 0; + + // Calculate the average of all the cells + auto averagemv = (uint16_t)(total / number_of_active_cells); + + // Calculate the differential between highest cell voltage and the average + uint16_t highest_average_diff = highestmV - averagemv; + + // Should any cells require balancing - check if they have gone over the threshold + cb = CalculateCellBalanceRequirement(celldata, number_of_active_cells); + + // Now determine if we should balance the highest "run away" cell + // If the highest cell is above average cell voltage by X millivolts, then begin balancing until it no longer is. + // Also ensure the cell voltage is above a minimum - LIFEPO4 cells need to be over 3400mV for this function to be useful. + if (highestmV > PP.getRunAwayCellMinimumVoltage() && highest_average_diff > PP.getRunAwayCellDifferential()) + { + cb = cb | (uint16_t)(1U << (15 - highest_index)); + } + + return cb; +} + +void loop() +{ + + // Temperature sensor readings... + EnableThermistorPower(); + TakeExternalTempMeasurements(celldata); + DisableThermistorPower(); + + if (cell_balancing != 0) + { + // Switch off any bypass balancing before taking voltage readings + MAX14921Command(0, ANALOG_BUFFERED_T1); + // Allow voltages to bounce back + delay(20); + } + + digitalWrite(SAMPLE_AFE, HIGH); // Sample cell voltages (all 16 cells at same time) + delay(60); // Wait 60ms for capacitors to fill and equalize against cell voltages (1uF caps) + digitalWrite(SAMPLE_AFE, LOW); // Disable sampling - HOLD mode + delayMicroseconds(LEVEL_SHIFTING_DELAY_MAX); // Wait to settle + + // Array to hold raw ADC measurements (over sampled) + std::array rawADC; + rawADC.fill(0); + // Scan active cell voltages (starting at highest cell) + ADCSampleCellVoltages(number_of_active_cells, rawADC); + + // Once all cells are measured, we enable the sampling to resume... + // this links the sample capacitors with the cells to equalise the voltages in them + digitalWrite(SAMPLE_AFE, HIGH); + + // Read T1/T2/T3 + // This also re-enables balancing MOSFET if required.... + EnableThermistorPower(); + TakeOnboardInternalTempMeasurements(celldata); + DisableThermistorPower(); + + // Now process the ADC readings we have taken... + DecimateRawADCCellVoltage(rawADC, celldata, number_of_active_cells); + + auto highestTemp = max(celldata.at(0).getInternalTemperature(), celldata.at(1).getInternalTemperature()); + + // If fan timer has expired, switch off FAN + // This has the side effect of the fan switching off and on (not physically noticable) should the temperature still be too hot. + auto millis = HAL_GetTick(); + if (millis > fan_timer) + { + fan_timer = 0; + digitalWrite(FAN, LOW); + } + + // Enable fan if temperature is OVER Hysteresis value (target minus 5C), or FAN value, and set a 45 second over-run timer + if (highestTemp > Cell::getBypassTemperatureHysteresis() || highestTemp > Cell::getFanSwitchOnTemperature()) + { + digitalWrite(FAN, HIGH); + fan_timer = millis + 45000; + // Might have problems when HAL_GetTick rolls over - approx. 50days, fan could get stuck on + // in this case, the fan_timer would have overflowed (unsigned) so would be less than millis. + if (fan_timer < millis) + { + fan_timer = millis; + } + } + + // Don't immediately start balance on power up, wait a few cycles to settle + if (waitbeforebalance == 0) + { + cell_balancing = DoCellBalancing(highestTemp); + MAX14921Command(cell_balancing, ANALOG_BUFFERED_T1); + } + else + { + waitbeforebalance--; + } + + // Service the serial port/queue + for (size_t i = 0; i < 300; i++) + { + // Call update to receive, decode and process incoming packets. + myPacketSerial.checkInputStream(); + + // Allow data to be received in buffer (delay must be AFTER) checkInputStream + delay(1); + } + + // Every so often, we should call this to calibrate the op-amp as it changes in ambient temperature (takes 8ms to complete) + if (PP.getPacketReceivedCounter() % 4096 == 0) + { + BufferAmplifierOffsetCalibration(); + } +} diff --git a/STM32All-In-One/src/packet_processor.cpp b/STM32All-In-One/src/packet_processor.cpp new file mode 100644 index 00000000..1ed027bf --- /dev/null +++ b/STM32All-In-One/src/packet_processor.cpp @@ -0,0 +1,259 @@ +/* +____ ____ _ _ ____ __ __ ___ +( _ \(_ _)( \/ )( _ \( \/ )/ __) +)(_) )_)(_ \ / ) _ < ) ( \__ \ +(____/(____) (__) (____/(_/\/\_)(___/ + +DIYBMS V4 + +V490 UP TO 16S CELL MONITORING MODULES + +STM32F030K6T6 + +(c)2023 Stuart Pittaway + +COMPILE THIS CODE USING PLATFORM.IO + +LICENSE +Attribution-NonCommercial-ShareAlike 2.0 UK: England & Wales (CC BY-NC-SA 2.0 UK) +https://creativecommons.org/licenses/by-nc-sa/2.0/uk/ + +* Non-Commercial — You may not use the material for commercial purposes. +* Attribution — You must give appropriate credit, provide a link to the license, and indicate if changes were made. + You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use. +* ShareAlike — If you remix, transform, or build upon the material, you must distribute your + contributions under the same license as the original. +* No additional restrictions — You may not apply legal terms or technological measures + that legally restrict others from doing anything the license permits. +*/ + +#include "packet_processor.h" + +// Run when a new packet is received over serial +bool PacketProcessor::onPacketReceived(PacketStruct *receivebuffer, uint8_t number_of_active_cells, CellData &cells) +{ + // packet counter to see where packets get lost + PacketReceivedCounter++; + + // Calculate the CRC and compare to received + uint16_t validateCRC = CRC16::CalculateArray((unsigned char *)receivebuffer, sizeof(PacketStruct) - 2); + + if (validateCRC != receivebuffer->crc) + { + // The packet received was not correct, failed CRC check + badpackets++; + return false; + } + + // Loop through all the cells this module is "pretending" to be + for (uint8_t cellindex = 0; cellindex < number_of_active_cells; cellindex++) + { + auto mymoduleaddress = receivebuffer->hops; + + bool isPacketForMe = receivebuffer->start_address <= mymoduleaddress && receivebuffer->end_address >= mymoduleaddress; + + // Increment the hops no matter what (on valid CRC) + receivebuffer->hops++; + + // It's a good packet + if (isPacketForMe && processPacket(receivebuffer, mymoduleaddress, cells.at(cellindex))) + { + // Set flag to indicate we processed packet (other modules may also do this) + receivebuffer->command = receivebuffer->command | B10000000; + } + } // end for + + // Calculate new checksum over whole buffer (as hops have been increased) + receivebuffer->crc = CRC16::CalculateArray((unsigned char *)receivebuffer, sizeof(PacketStruct) - 2); + + // Return TRUE if one of the "modules" we are pretending to be processed the command + return ((receivebuffer->command & B10000000) > 0); +} + +// Process the request in the received packet +// command byte +// RRRR CCCC +// X = 1 bit indicate if packet processed +// R = 3 bits reserved not used +// C = 4 bits command (16 possible commands) +bool PacketProcessor::processPacket(PacketStruct *buffer, uint8_t mymoduleaddress, Cell &cell) +{ + uint8_t moduledata_index = mymoduleaddress % maximum_cell_modules; + switch (buffer->command & 0x0F) + { + case COMMAND::ResetBadPacketCounter: + badpackets = 0; + PacketReceivedCounter = 0; + return true; + + case COMMAND::ReadVoltageAndStatus: + { + // Read voltage of VCC + + // Maximum voltage 8191mV + buffer->moduledata[moduledata_index] = cell.getCellVoltage() & 0x1FFF; + + // 3 top bits + // X = In bypass + // Y = Bypass over temperature + // Z = Not used + + if (cell.BypassOverheatCheck()) + { + // Set bit + buffer->moduledata[moduledata_index] = buffer->moduledata[moduledata_index] | 0x4000; + } + + if (cell.IsBypassActive()) + { + // Set bit + buffer->moduledata[moduledata_index] = buffer->moduledata[moduledata_index] | 0x8000; + } + + return true; + } + + case COMMAND::Timing: + { + // Do nothing just accept and pass on the packet + return true; + } + + case COMMAND::Identify: + { + // identify module + // Ignored for 16S boards (meaningless) + return true; + } + + case COMMAND::ReadTemperature: + { + // Return the last known temperature values recorded - both internal and external. + // output bytes are cast to a uint16_t for transport to host + buffer->moduledata[moduledata_index] = cell.CombineTemperatures(); + return true; + } + + case COMMAND::ReadBalancePowerPWM: + { + // Doesn't really mean anything on the all-in-one board, on or off... + // If we are over temperature, then return zero + buffer->moduledata[moduledata_index] = (Cell::getOverTemperature() == false && cell.IsBypassActive()) ? 255U : 0; + return true; + } + + case COMMAND::ReadBadPacketCounter: + { + // Report number of bad packets + buffer->moduledata[moduledata_index] = badpackets; + return true; + } + + case COMMAND::ReadSettings: + { + // Report settings/configuration, fills whole moduledata buffer up + + FLOATUNION_t myFloat; + myFloat.number = BalanceBoardInstalled ? (float)LOAD_RESISTANCE : 0.0F; + buffer->moduledata[0] = myFloat.word[0]; + buffer->moduledata[1] = myFloat.word[1]; + + myFloat.number = Cell::getCalibration(); + buffer->moduledata[2] = myFloat.word[0]; + buffer->moduledata[3] = myFloat.word[1]; + + // Millivolts per ADC step (14 bit ADC in MAX chip) + myFloat.number = ((float)DIYBMSREFMILLIVOLT) / 16384.0F; + buffer->moduledata[4] = myFloat.word[0]; + buffer->moduledata[5] = myFloat.word[1]; + + buffer->moduledata[6] = Cell::getBypassTemperatureSetPoint(); + + // If changes are NOT allowed, set the high bit of moduledata[6], this allows the user interface to disable changes + buffer->moduledata[6] |= cell.changesAllowed() ? 0 : 0x8000; + + buffer->moduledata[7] = Cell::getBypassThresholdmV(); + buffer->moduledata[8] = INT_BCOEFFICIENT; + buffer->moduledata[9] = EXT_BCOEFFICIENT; + buffer->moduledata[10] = DIYBMSMODULEVERSION; + + // Version of firmware (taken automatically from GIT) + buffer->moduledata[14] = GIT_VERSION_B1; + buffer->moduledata[15] = GIT_VERSION_B2; + return true; + } + + case COMMAND::WriteSettings: + { + if (cell.changesAllowed()) + { + FLOATUNION_t myFloat; + + myFloat.word[0] = buffer->moduledata[0]; + myFloat.word[1] = buffer->moduledata[1]; + + myFloat.word[0] = buffer->moduledata[2]; + myFloat.word[1] = buffer->moduledata[3]; + if (myFloat.number < 0xFFFF) + { + Cell::setCalibration(myFloat.number); + } + if (buffer->moduledata[6] != 0xFF) + { + Cell::setBypassTemperatureSetPoint((uint8_t)buffer->moduledata[6]); + } + if (buffer->moduledata[7] != 0xFFFF) + { + Cell::setBypassThresholdmV(buffer->moduledata[7]); + } + SettingsHaveChanged = true; + } + + return true; + } + + case COMMAND::ReadBalanceCurrentCounter: + { + buffer->moduledata[moduledata_index] = (uint16_t)cell.getMilliAmpHourBalanceCounter(); + return true; + } + + case COMMAND::ReadPacketReceivedCounter: + { + buffer->moduledata[moduledata_index] = PacketReceivedCounter; + return true; + } + + case COMMAND::ResetBalanceCurrentCounter: + { + cell.setMilliAmpHourBalanceCounter(0); + return true; + } + + case COMMAND::ReadAdditionalSettings: + { + memset(buffer->moduledata, 0, sizeof(buffer->moduledata)); + buffer->moduledata[0] = Cell::getFanSwitchOnTemperature(); + buffer->moduledata[1] = Cell::getRelayMinmV(); + buffer->moduledata[2] = Cell::getRelayRange(); + buffer->moduledata[3] = cell.getParasiteVoltage(); + buffer->moduledata[4] = getRunAwayCellMinimumVoltage(); + buffer->moduledata[5] = getRunAwayCellDifferential(); + return true; + } + + case COMMAND::WriteAdditionalSettings: + { + Cell::setFanSwitchOnTemperature((uint8_t)buffer->moduledata[0]); + Cell::setRelayMinmV(buffer->moduledata[1]); + Cell::setRelayRange(buffer->moduledata[2]); + // 3=ParasiteVoltage + setRunAwayCellMinimumVoltage(buffer->moduledata[4]); + setRunAwayCellDifferential(buffer->moduledata[5]); + return true; + } + + default: + return false; + } +} diff --git a/STM32All-In-One/src/stm32_flash.c b/STM32All-In-One/src/stm32_flash.c new file mode 100644 index 00000000..e6c9ea4d --- /dev/null +++ b/STM32All-In-One/src/stm32_flash.c @@ -0,0 +1,109 @@ +/* +This is a clone of the stm32_eeprom.c library with modifications to remove the internal RAM buffer +and specifically targets 1K page sizes and the STM32F030K6T6 chip. +The top 1K of FLASH is used for this library - so ensure compiled code doesn't exceed 31744 bytes (adjust board config) + +Stuart Pittaway +*/ +/** + ****************************************************************************** + * @file stm32_flash.c + * @brief + ****************************************************************************** + * @attention + * + * Copyright (c) 2016-2021, STMicroelectronics + * All rights reserved. + * + * This software component is licensed by ST under BSD 3-Clause license, + * the "License"; You may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * opensource.org/licenses/BSD-3-Clause + * + ****************************************************************************** + */ +#include "stm32_flash.h" +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* Be able to change FLASH_PAGE_NUMBER to use if relevant */ +#if !defined(FLASH_PAGE_NUMBER) && defined(FLASH_PAGE_SIZE) +#define FLASH_PAGE_NUMBER ((uint32_t)(((LL_GetFlashSize() * 1024) / FLASH_PAGE_SIZE) - 1)) +#endif /* !FLASH_PAGE_NUMBER */ + +#define FLASH_END FLASH_BANK1_END +#define FLASH_BASE_ADDRESS ((uint32_t)((FLASH_END + 1) - FLASH_PAGE_SIZE)) +#define E2END (FLASH_PAGE_SIZE - 1) + +#define FLASH_FLAG_ALL_ERRORS (FLASH_FLAG_WRPERR | FLASH_FLAG_PGERR) + + /** + * @brief This function writes the buffer content into the flash + * @param none + * @retval none + */ + void flash_erase_and_write(uint8_t *buffer, uint16_t buffer_size) + { + FLASH_EraseInitTypeDef EraseInitStruct; + uint32_t offset = 0; + uint32_t address = FLASH_BASE_ADDRESS; + uint32_t address_end = FLASH_BASE_ADDRESS + E2END; + uint32_t pageError = 0; + uint64_t data = 0; + + /* ERASING page */ + EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES; + EraseInitStruct.PageAddress = FLASH_BASE_ADDRESS; + EraseInitStruct.NbPages = 1; + + if (HAL_FLASH_Unlock() == HAL_OK) + { + __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ALL_ERRORS); + if (HAL_FLASHEx_Erase(&EraseInitStruct, &pageError) == HAL_OK) + { + + while (address <= address_end) + { + data = *((uint64_t *)(buffer + offset)); + + if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, address, data) == HAL_OK) + { + address += 8; + offset += 8; + } + else + { + address = address_end + 1; + } + + if (offset > buffer_size) + { + break; + } + } + } + HAL_FLASH_Lock(); + } + } + + /** + * @brief This function copies the data from flash into the RAM buffer + * @param none + * @retval none + */ + void flash_copy_buffer(uint8_t *buffer, uint16_t numberofbytes) + { + if (numberofbytes > E2END + 1) + { + numberofbytes = E2END + 1; + } + memcpy(buffer, (uint8_t *)(FLASH_BASE_ADDRESS), numberofbytes); + } + +#ifdef __cplusplus +} +#endif From 6a5cbf6bf189c2e4b55cb35185f7c09b3947cf82 Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Tue, 4 Jul 2023 17:08:41 +0100 Subject: [PATCH 02/17] Update main.yml --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f9add1c0..b059f228 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -63,7 +63,7 @@ jobs: - name: Copy STM32 binary firmware output files run: | - cp ./ATTINYCellModule/.pio/build/V*/*.bin ~/OUTPUT/Modules/BIN/ + cp ./STM32All-In-One/.pio/build/V*/*.bin ~/OUTPUT/Modules/BIN/ - name: Get latest esptool run: | From 2e49e0e63128b90d7f38c7446c58c1b17c7c0de6 Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Tue, 4 Jul 2023 17:35:55 +0100 Subject: [PATCH 03/17] Update main.yml --- .github/workflows/main.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b059f228..de217c6e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -119,7 +119,7 @@ jobs: with: name: DIYBMS-Compiled - - name: Display structure of downloaded files + - name: Rename over-the-air firmware run: mv ./Controller/diybms_controller_firmware_espressif32_esp32-devkitc.bin ./Controller/esp32-controller-firmware-over-the-air.bin - name: Display structure of downloaded files @@ -138,7 +138,7 @@ jobs: id: date run: echo "dt=$(date +'%Y-%m-%d-%H-%M')" >> $GITHUB_ENV - - name: Display structure of downloaded files + - name: Rename ZIP run: mv release.zip release_${{ env.dt }}.zip - name: Publish Artifact @@ -149,6 +149,7 @@ jobs: ./Controller/*.* ./Modules/manifest.json ./Modules/HEX/*.* + ./Modules/BIN/module_fw_V490*.bin if-no-files-found: error - name: Create Release Files on MASTER branch only From 4a32a0377c3dc30d05062057754f52e3cd910bd1 Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Thu, 6 Jul 2023 15:06:12 +0100 Subject: [PATCH 04/17] Update README.md --- STM32All-In-One/README.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/STM32All-In-One/README.md b/STM32All-In-One/README.md index a5bdbbe3..da0fba61 100644 --- a/STM32All-In-One/README.md +++ b/STM32All-In-One/README.md @@ -67,4 +67,20 @@ Repeat above, but using ST-LINK connector on the PCB. There is no need to chang Observe warnings about disconnecting the battery connector before programming, or connecting to your computer. -Use the STM32 Cube software to program the firmware using ST-LINK protocol. \ No newline at end of file +Use the STM32 Cube software to program the firmware using ST-LINK protocol. + +## Error LED flash sequences + +On power up (after successful programming of the STM32 chip with code) the LED may flash a light sequence in the event of an error. + +* 0 = Successful power on +* 1 = Not used +* 2 = MAX14921 thermal shutdown +* 3 = Less than 4 cells detected (check wiring and soldering to MAX chip) +* 4 = MAX14921 VA pin (Analog Supply Voltage) below UV_VAVTH (4.7V) +* 5 = MAX14921 VP is below UV_VPVTH (6V) +* 6 = MAX14921 device reports not ready (after 5 seconds waiting) +* 7 = Unable to communicate with MAX14921 over SPI pins/interface + +Most of these reasons are caused by poor soldering to the MAX14921 chip. Check for solder bridges and loose pins (not soldered). + From eb403f9d15bf29bde41f799619700e6d5600a693 Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Mon, 7 Aug 2023 10:26:13 +0100 Subject: [PATCH 05/17] Add version for 4.5V reference --- STM32All-In-One/platformio.ini | 3 +++ STM32All-In-One/src/main.cpp | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/STM32All-In-One/platformio.ini b/STM32All-In-One/platformio.ini index dd74139b..128c7717 100644 --- a/STM32All-In-One/platformio.ini +++ b/STM32All-In-One/platformio.ini @@ -38,3 +38,6 @@ build_flags=-DDIYBMSMODULEVERSION=490 -DINT_BCOEFFICIENT=3950 -DEXT_BCOEFFICIENT [env:V490_5K] build_flags=-DDIYBMSMODULEVERSION=490 -DINT_BCOEFFICIENT=3950 -DEXT_BCOEFFICIENT=3950 -DLOAD_RESISTANCE=18.0 -DDIYBMSBAUD=5000 -DDIYBMSREFMILLIVOLT=4096 -DADC_SAMPLINGTIME=ADC_SAMPLETIME_239CYCLES_5 + +[env:V490_10K_VREF4500] +build_flags=-DDIYBMSMODULEVERSION=490 -DINT_BCOEFFICIENT=3950 -DEXT_BCOEFFICIENT=3950 -DLOAD_RESISTANCE=18.0 -DDIYBMSBAUD=10000 -DDIYBMSREFMILLIVOLT=4500 -DADC_SAMPLINGTIME=ADC_SAMPLETIME_239CYCLES_5 diff --git a/STM32All-In-One/src/main.cpp b/STM32All-In-One/src/main.cpp index afa4b9b7..6a542f13 100644 --- a/STM32All-In-One/src/main.cpp +++ b/STM32All-In-One/src/main.cpp @@ -597,7 +597,7 @@ uint16_t DecimateValue(uint64_t val) { // decimate result by right shifting 2 places (raw ADC is 14 bits) val = val >> 2; - // 4096 voltage reference + // 4096 or 4500 voltage reference val = val * ((uint64_t)DIYBMSREFMILLIVOLT); // 4194304 = 2^22 effective resolution bits val = val / 4194304UL; From 91756e49f2b4978023dd0f458b001c14b19b1b65 Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Mon, 7 Aug 2023 10:57:54 +0100 Subject: [PATCH 06/17] Update platformio.ini --- STM32All-In-One/platformio.ini | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/STM32All-In-One/platformio.ini b/STM32All-In-One/platformio.ini index 128c7717..e839851b 100644 --- a/STM32All-In-One/platformio.ini +++ b/STM32All-In-One/platformio.ini @@ -9,7 +9,7 @@ ; https://docs.platformio.org/page/projectconf.html [platformio] -default_envs = V490_10K, V490_5K +default_envs = V490_10K, V490_5K, V490_5K_VREF4500, V490_10K_VREF4500 [env] ;Version 16 results in a compile size larger than 32k @@ -39,5 +39,8 @@ build_flags=-DDIYBMSMODULEVERSION=490 -DINT_BCOEFFICIENT=3950 -DEXT_BCOEFFICIENT [env:V490_5K] build_flags=-DDIYBMSMODULEVERSION=490 -DINT_BCOEFFICIENT=3950 -DEXT_BCOEFFICIENT=3950 -DLOAD_RESISTANCE=18.0 -DDIYBMSBAUD=5000 -DDIYBMSREFMILLIVOLT=4096 -DADC_SAMPLINGTIME=ADC_SAMPLETIME_239CYCLES_5 +[env:V490_5K_VREF4500] +build_flags=-DDIYBMSMODULEVERSION=490 -DINT_BCOEFFICIENT=3950 -DEXT_BCOEFFICIENT=3950 -DLOAD_RESISTANCE=18.0 -DDIYBMSBAUD=5000 -DDIYBMSREFMILLIVOLT=4500 -DADC_SAMPLINGTIME=ADC_SAMPLETIME_239CYCLES_5 + [env:V490_10K_VREF4500] build_flags=-DDIYBMSMODULEVERSION=490 -DINT_BCOEFFICIENT=3950 -DEXT_BCOEFFICIENT=3950 -DLOAD_RESISTANCE=18.0 -DDIYBMSBAUD=10000 -DDIYBMSREFMILLIVOLT=4500 -DADC_SAMPLINGTIME=ADC_SAMPLETIME_239CYCLES_5 From a05eea0d64f4fa9ac05025652134aa1ca1a21783 Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Tue, 8 Aug 2023 12:25:26 +0100 Subject: [PATCH 07/17] Fix issue with 16 cell configuration (hang) --- STM32All-In-One/src/main.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/STM32All-In-One/src/main.cpp b/STM32All-In-One/src/main.cpp index 6a542f13..74afc722 100644 --- a/STM32All-In-One/src/main.cpp +++ b/STM32All-In-One/src/main.cpp @@ -85,11 +85,8 @@ uint32_t MAX14921Command(uint16_t b1_2, uint8_t b3); uint16_t CalculateCellBalanceRequirement(CellData &cd, uint8_t number_of_active_cells); uint32_t fan_timer = 0; - uint32_t relay_timer = 0; - - /// @brief Global variable to delay balance function on first power up uint8_t waitbeforebalance = 20; @@ -327,7 +324,7 @@ uint8_t queryAFE() void ADCSampleCellVoltages(uint8_t cellCount, std::array &rawADC) { // Scan active cell voltages (starting at highest) - for (int8_t cellid = cellCount; cellid >= 0; cellid--) + for (int8_t cellid = (cellCount-1); cellid >= 0; cellid--) { MAX14921Command(0, CellTable.at(cellid)); // Delay required for AOUT to settle @@ -546,7 +543,6 @@ void setup() } ParasiticCapacitanceChargeInjectionErrorCalibration(number_of_active_cells, celldata); - BufferAmplifierOffsetCalibration(); configureModules(); @@ -795,7 +791,7 @@ void CalculateCellVoltageMinMaxAvg(CellData &cd, total = 0; // Determine highest/lowest cell and average voltage - for (auto i = 0; i < num_cells; i++) + for (uint8_t i = 0; i < num_cells; i++) { // Get reference to cell object const Cell &cell = cd.at(i); @@ -817,6 +813,8 @@ void CalculateCellVoltageMinMaxAvg(CellData &cd, } /// @brief Determine what (if any) cells need balancing, also controls relay output +/// @param highestTemp Highest temperature of on-board sensors (balance temperature) +/// @return bit pattern for which MOSFETs need enabling uint16_t DoCellBalancing(const int16_t highestTemp) { From 0461bfefad288f397af79d4b791f027873c5cd17 Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Tue, 8 Aug 2023 13:39:09 +0100 Subject: [PATCH 08/17] Update main.yml --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index de217c6e..8d07fe39 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -115,7 +115,7 @@ jobs: needs: [job_build_modulecode] steps: - name: Download artifact DIYBMS-Compiled - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v3 with: name: DIYBMS-Compiled From f0f8bbac746b399b9483c6d9f5d3add9bc6c8f34 Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Tue, 8 Aug 2023 14:02:20 +0100 Subject: [PATCH 09/17] Fix issue for external relay control --- STM32All-In-One/src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/STM32All-In-One/src/main.cpp b/STM32All-In-One/src/main.cpp index 74afc722..dcd5b700 100644 --- a/STM32All-In-One/src/main.cpp +++ b/STM32All-In-One/src/main.cpp @@ -837,7 +837,7 @@ uint16_t DoCellBalancing(const int16_t highestTemp) // Setup a timer, to enable this output for a minimum of 1 minute over-run after balance condition is no longer met if (range > Cell::getRelayRange() && highestmV > Cell::getRelayMinmV()) { - digitalWrite(HIGH, HIGH); + digitalWrite(RELAY, HIGH); // This might run into problems if the HAL_GetTick wraps around - approx. every 50 days relay_timer = HAL_GetTick() + 60000; } From 1227bb92acffc434a8b9d918d760cf62f8ece840 Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Tue, 8 Aug 2023 15:31:39 +0100 Subject: [PATCH 10/17] Update buildscript.py --- STM32All-In-One/buildscript.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/STM32All-In-One/buildscript.py b/STM32All-In-One/buildscript.py index df916a1c..dfba793f 100644 --- a/STM32All-In-One/buildscript.py +++ b/STM32All-In-One/buildscript.py @@ -2,17 +2,14 @@ platform = env.PioPlatform() -my_flags = env.ParseFlags(env['BUILD_FLAGS']) -defines = {k: v for (k, v) in my_flags.get("CPPDEFINES")} +#my_flags = env.ParseFlags(env['BUILD_FLAGS']) +#defines = {k: v for (k, v) in my_flags.get("CPPDEFINES")} #print(my_flags) #print(defines.get("DIYBMSMODULEVERSION")) #print(str(env["BOARD_MCU"]).lower()) if str(env["BOARD_MCU"]).lower()=="stm32f030k6t6": - efuse=0 - hfuse=0 - lfuse=0 env.Replace(PROGNAME="module_fw_%s_%s" % (env["PIOENV"],env.get("BOARD"))) From 711d3a3b3d863ed165ceea0d453c75af1ba45acf Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Tue, 8 Aug 2023 16:20:12 +0100 Subject: [PATCH 11/17] Update main.cpp --- STM32All-In-One/src/main.cpp | 80 ++++++++++++++++++++---------------- 1 file changed, 44 insertions(+), 36 deletions(-) diff --git a/STM32All-In-One/src/main.cpp b/STM32All-In-One/src/main.cpp index dcd5b700..2c655806 100644 --- a/STM32All-In-One/src/main.cpp +++ b/STM32All-In-One/src/main.cpp @@ -31,6 +31,8 @@ Attribution-NonCommercial-ShareAlike 2.0 UK: England & Wales (CC BY-NC-SA 2.0 UK #include +extern "C"{ void SystemClock_Config(void); } + extern "C" { #include "stm32_flash.h" @@ -186,40 +188,43 @@ constexpr std::array CellTable = AFE_ECS | ANALOG_CELL15, AFE_ECS | ANALOG_CELL16}; -extern "C" void SystemClock_Config(void) +extern "C" { - RCC_OscInitTypeDef RCC_OscInitStruct = {}; - RCC_ClkInitTypeDef RCC_ClkInitStruct = {}; - RCC_PeriphCLKInitTypeDef PeriphClkInit = {}; - - /* Initializes the RCC Oscillators according to the specified parameters in the RCC_OscInitTypeDef structure. */ - RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; - RCC_OscInitStruct.HSEState = RCC_HSE_ON; - RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE; - if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) + void SystemClock_Config(void) { - Error_Handler(); - } + RCC_OscInitTypeDef RCC_OscInitStruct = {}; + RCC_ClkInitTypeDef RCC_ClkInitStruct = {}; + RCC_PeriphCLKInitTypeDef PeriphClkInit = {}; - /* Initializes the CPU, AHB and APB buses clocks */ - RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1; - RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSE; - RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; - RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1; + /* Initializes the RCC Oscillators according to the specified parameters in the RCC_OscInitTypeDef structure. */ + RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; + RCC_OscInitStruct.HSEState = RCC_HSE_ON; + RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE; + if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) + { + Error_Handler(); + } - if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK) - { - Error_Handler(); - } - PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_USART1; - PeriphClkInit.Usart1ClockSelection = RCC_USART1CLKSOURCE_PCLK1; - if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK) - { - Error_Handler(); - } + /* Initializes the CPU, AHB and APB buses clocks */ + RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1; + RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSE; + RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; + RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1; + + if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK) + { + Error_Handler(); + } + PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_USART1; + PeriphClkInit.Usart1ClockSelection = RCC_USART1CLKSOURCE_PCLK1; + if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK) + { + Error_Handler(); + } - /* Enables the Clock Security System */ - HAL_RCC_EnableCSS(); + /* Enables the Clock Security System */ + HAL_RCC_EnableCSS(); + } } uint32_t SPITransfer24(uint8_t byte1, uint8_t byte2, uint8_t byte3) @@ -324,7 +329,7 @@ uint8_t queryAFE() void ADCSampleCellVoltages(uint8_t cellCount, std::array &rawADC) { // Scan active cell voltages (starting at highest) - for (int8_t cellid = (cellCount-1); cellid >= 0; cellid--) + for (int8_t cellid = (cellCount - 1); cellid >= 0; cellid--) { MAX14921Command(0, CellTable.at(cellid)); // Delay required for AOUT to settle @@ -471,11 +476,8 @@ void onPacketReceived() PP.clearSettingsHaveChanged(); } } - -void setup() +void configurePins() { - SystemClock_Config(); - // LED pinMode(PB7, OUTPUT); NotificationLedOn(); @@ -502,12 +504,18 @@ void setup() // Track cell voltages (LOW=HOLD SAMPLE) digitalWrite(SAMPLE_AFE, HIGH); - DisableThermistorPower(); - // STM32 use 12 bit ADC resolution analogReadResolution(12); // STM32 default voltage reference (3.3v) analogReference(AR_DEFAULT); +} + +void setup() +{ + SystemClock_Config(); + + configurePins(); + DisableThermistorPower(); // Check if balance board is fitted? (pin pulled up to 3.3v by daughter board) pinMode(BALANCE_DETECT, INPUT_PULLDOWN); From c38a682a9491d08f47680bae21908cc0d0ffece2 Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Tue, 8 Aug 2023 16:20:16 +0100 Subject: [PATCH 12/17] Update platformio.ini --- STM32All-In-One/platformio.ini | 53 +++++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 8 deletions(-) diff --git a/STM32All-In-One/platformio.ini b/STM32All-In-One/platformio.ini index e839851b..5297815a 100644 --- a/STM32All-In-One/platformio.ini +++ b/STM32All-In-One/platformio.ini @@ -13,10 +13,13 @@ default_envs = V490_10K, V490_5K, V490_5K_VREF4500, V490_10K_VREF4500 [env] ;Version 16 results in a compile size larger than 32k -platform = ststm32@15.0.0 +platform = ststm32@15.2.0 +;platform = https://github.com/platformio/platform-ststm32.git#v16.1.0 board = genericSTM32F030K6T6 +board_build.core = stm32 framework = arduino board_build.ldscript = ldscript.ld +lib_archive = false extra_scripts = pre:buildscript_versioning.py @@ -28,19 +31,53 @@ lib_deps = monitor_port=COM3 monitor_speed=115200 -upload_port = COM6 +upload_port = COM4 debug_tool = stlink -;upload_protocol = stlink -upload_protocol = serial +upload_protocol = stlink +;upload_protocol = serial [env:V490_10K] -build_flags=-DDIYBMSMODULEVERSION=490 -DINT_BCOEFFICIENT=3950 -DEXT_BCOEFFICIENT=3950 -DLOAD_RESISTANCE=18.0 -DDIYBMSBAUD=10000 -DDIYBMSREFMILLIVOLT=4096 -DADC_SAMPLINGTIME=ADC_SAMPLETIME_239CYCLES_5 +build_flags= + -DDIYBMSMODULEVERSION=490 + -DINT_BCOEFFICIENT=3950 + -DEXT_BCOEFFICIENT=3950 + -DLOAD_RESISTANCE=18.0 + -DDIYBMSBAUD=10000 + -DDIYBMSREFMILLIVOLT=4096 + -DADC_SAMPLINGTIME=ADC_SAMPLETIME_239CYCLES_5 + -DSERIAL_RX_BUFFER_SIZE=64 + -DSERIAL_TX_BUFFER_SIZE=64 [env:V490_5K] -build_flags=-DDIYBMSMODULEVERSION=490 -DINT_BCOEFFICIENT=3950 -DEXT_BCOEFFICIENT=3950 -DLOAD_RESISTANCE=18.0 -DDIYBMSBAUD=5000 -DDIYBMSREFMILLIVOLT=4096 -DADC_SAMPLINGTIME=ADC_SAMPLETIME_239CYCLES_5 +build_flags= + -DDIYBMSMODULEVERSION=490 + -DINT_BCOEFFICIENT=3950 + -DEXT_BCOEFFICIENT=3950 + -DLOAD_RESISTANCE=18.0 + -DDIYBMSBAUD=5000 + -DDIYBMSREFMILLIVOLT=4096 + -DADC_SAMPLINGTIME=ADC_SAMPLETIME_239CYCLES_5 + -DSERIAL_RX_BUFFER_SIZE=64 + -DSERIAL_TX_BUFFER_SIZE=64 [env:V490_5K_VREF4500] -build_flags=-DDIYBMSMODULEVERSION=490 -DINT_BCOEFFICIENT=3950 -DEXT_BCOEFFICIENT=3950 -DLOAD_RESISTANCE=18.0 -DDIYBMSBAUD=5000 -DDIYBMSREFMILLIVOLT=4500 -DADC_SAMPLINGTIME=ADC_SAMPLETIME_239CYCLES_5 +build_flags=-DDIYBMSMODULEVERSION=490 + -DINT_BCOEFFICIENT=3950 + -DEXT_BCOEFFICIENT=3950 + -DLOAD_RESISTANCE=18.0 + -DDIYBMSBAUD=5000 + -DDIYBMSREFMILLIVOLT=4500 + -DADC_SAMPLINGTIME=ADC_SAMPLETIME_239CYCLES_5 + -DSERIAL_RX_BUFFER_SIZE=64 + -DSERIAL_TX_BUFFER_SIZE=64 [env:V490_10K_VREF4500] -build_flags=-DDIYBMSMODULEVERSION=490 -DINT_BCOEFFICIENT=3950 -DEXT_BCOEFFICIENT=3950 -DLOAD_RESISTANCE=18.0 -DDIYBMSBAUD=10000 -DDIYBMSREFMILLIVOLT=4500 -DADC_SAMPLINGTIME=ADC_SAMPLETIME_239CYCLES_5 +build_flags= + -DDIYBMSMODULEVERSION=490 + -DINT_BCOEFFICIENT=3950 + -DEXT_BCOEFFICIENT=3950 + -DLOAD_RESISTANCE=18.0 + -DDIYBMSBAUD=10000 + -DDIYBMSREFMILLIVOLT=4500 -DADC_SAMPLINGTIME=ADC_SAMPLETIME_239CYCLES_5 + -DSERIAL_RX_BUFFER_SIZE=64 + -DSERIAL_TX_BUFFER_SIZE=64 From 8a45d2818dfce2ce82cdc7e14c93a296c0080b74 Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Fri, 11 Aug 2023 10:23:41 +0100 Subject: [PATCH 13/17] Update platformio.ini --- STM32All-In-One/platformio.ini | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/STM32All-In-One/platformio.ini b/STM32All-In-One/platformio.ini index 5297815a..e280f97e 100644 --- a/STM32All-In-One/platformio.ini +++ b/STM32All-In-One/platformio.ini @@ -65,7 +65,7 @@ build_flags=-DDIYBMSMODULEVERSION=490 -DINT_BCOEFFICIENT=3950 -DEXT_BCOEFFICIENT=3950 -DLOAD_RESISTANCE=18.0 - -DDIYBMSBAUD=5000 + -DDIYBMSBAUD=5000 -DDIYBMSREFMILLIVOLT=4500 -DADC_SAMPLINGTIME=ADC_SAMPLETIME_239CYCLES_5 -DSERIAL_RX_BUFFER_SIZE=64 @@ -76,8 +76,9 @@ build_flags= -DDIYBMSMODULEVERSION=490 -DINT_BCOEFFICIENT=3950 -DEXT_BCOEFFICIENT=3950 - -DLOAD_RESISTANCE=18.0 - -DDIYBMSBAUD=10000 - -DDIYBMSREFMILLIVOLT=4500 -DADC_SAMPLINGTIME=ADC_SAMPLETIME_239CYCLES_5 + -DLOAD_RESISTANCE=18.0 + -DDIYBMSBAUD=10000 + -DDIYBMSREFMILLIVOLT=4500 + -DADC_SAMPLINGTIME=ADC_SAMPLETIME_239CYCLES_5 -DSERIAL_RX_BUFFER_SIZE=64 -DSERIAL_TX_BUFFER_SIZE=64 From 184136562c279ee3ccd8109ceeedf5b3020dd2b9 Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Thu, 17 Aug 2023 10:55:18 +0100 Subject: [PATCH 14/17] Revert to Arduino ESP 2.0.9 due to stability --- ESPController/platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ESPController/platformio.ini b/ESPController/platformio.ini index 4dc7e66f..a48b867b 100644 --- a/ESPController/platformio.ini +++ b/ESPController/platformio.ini @@ -56,7 +56,7 @@ upload_speed=921600 upload_port=COM3 board_build.arduino.upstream_packages = no platform_packages = - framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#2.0.11 + framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#2.0.9 board_build.partitions = diybms_partitions.csv From edba5ad98f38c7bf936b820bba4d05d8bd66056e Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Thu, 17 Aug 2023 10:55:41 +0100 Subject: [PATCH 15/17] Minor changes for clock on TFT screen --- ESPController/src/tft.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/ESPController/src/tft.cpp b/ESPController/src/tft.cpp index a81c52ce..66044c7a 100644 --- a/ESPController/src/tft.cpp +++ b/ESPController/src/tft.cpp @@ -98,7 +98,7 @@ void TFTDrawWifiDetails() tft.setTextDatum(TL_DATUM); int16_t y = tft.height() - fontHeight_2; - ; + tft.fillRect(0, y, tft.width(), tft.height() - y, TFT_DARKGREY); tft.setTextFont(2); tft.setTextColor(TFT_BLACK, TFT_DARKGREY); @@ -138,19 +138,22 @@ void DrawClock() // Draw the time in bottom right corner of screen int16_t y = tft.height() - fontHeight_2; - ; int16_t x = tft.width() - 38; + + std::string clock; + if (timeinfo.tm_hour < 10) { - x += tft.drawString("0", x, y); + clock.append("0"); } - x += tft.drawNumber(timeinfo.tm_hour, x, y); - x += tft.drawString(":", x, y); + clock.append(std::to_string(timeinfo.tm_hour)).append(":"); if (timeinfo.tm_min < 10) { - x += tft.drawString("0", x, y); + clock.append("0"); } - x += tft.drawNumber(timeinfo.tm_min, x, y); + clock.append(std::to_string(timeinfo.tm_min)); + + x += tft.drawString(clock.c_str(), x, y); } } From edf1f20792a543ce30f8c551c362baf57a268903 Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Thu, 17 Aug 2023 15:29:56 +0100 Subject: [PATCH 16/17] State of charge bar graph on TFT screen --- ESPController/include/tft.h | 3 +- ESPController/src/tft.cpp | 91 +++++++++++++++++++++++++++++++++++-- 2 files changed, 89 insertions(+), 5 deletions(-) diff --git a/ESPController/include/tft.h b/ESPController/include/tft.h index a0ca1c09..dc249191 100644 --- a/ESPController/include/tft.h +++ b/ESPController/include/tft.h @@ -34,7 +34,8 @@ enum ScreenTemplateToDisplay : uint8_t State = 5, AVRProgrammer=6, CurrentMonitor=7, - SystemInformation=8 + SoCBarGraph=8, + SystemInformation=9 }; diff --git a/ESPController/src/tft.cpp b/ESPController/src/tft.cpp index 66044c7a..6bd51c9c 100644 --- a/ESPController/src/tft.cpp +++ b/ESPController/src/tft.cpp @@ -298,7 +298,14 @@ void PageForward() _ScreenPageCounter++; } - if (_ScreenPageCounter > 2) + if (_ScreenPageCounter == 2 && mysettings.currentMonitoringEnabled == false) + { + // Don't show current if its not fitted/installed + // Skip to next page + _ScreenPageCounter++; + } + + if (_ScreenPageCounter > 3) { // Loop back to first page _ScreenPageCounter = 0; @@ -316,6 +323,13 @@ void PageBackward() //"Left" touch or delay counter has expired _ScreenPageCounter--; + if (_ScreenPageCounter == 2 && mysettings.currentMonitoringEnabled == false) + { + // Don't show current if its not fitted/installed + // Skip to next page + _ScreenPageCounter--; + } + if (_ScreenPageCounter == 1 && mysettings.currentMonitoringEnabled == false) { // Don't show current if its not fitted/installed @@ -326,7 +340,7 @@ void PageBackward() if (_ScreenPageCounter < 0) { // Loop back to last page - _ScreenPageCounter = 2; + _ScreenPageCounter = 3; } // Trigger a refresh of the screen @@ -392,6 +406,10 @@ ScreenTemplateToDisplay WhatScreenToDisplay() reply = ScreenTemplateToDisplay::CurrentMonitor; break; case 2: + // Show the current monitor SoC + reply = ScreenTemplateToDisplay::SoCBarGraph; + break; + case 3: // System Information reply = ScreenTemplateToDisplay::SystemInformation; break; @@ -574,6 +592,65 @@ void PrepareTFT_CurrentMonitor() TFTDrawWifiDetails(); } +void PrepareTFT_SocBarGraph() +{ + tft.fillScreen(TFT_BLACK); + + int16_t w = tft.width(); + // Take off the wifi banner height + int16_t h = tft.height() - fontHeight_2; + int16_t yhalfway = h / 2; + int16_t xhalfway = w / 2; + + tft.drawCentreString("State of Charge %", xhalfway, 10, 4); + + tft.drawRoundRect(xhalfway - 102, yhalfway - 26, 204, 52, 4, TFT_GREEN); + tft.drawRoundRect(xhalfway - 103, yhalfway - 27, 2 + 204, 2 + 52, 4, TFT_GREEN); + + tft.setTextColor(TFT_LIGHTGREY, TFT_BLACK); + + // The bar graph + + /* + int16_t w = tft.width(); + // Take off the wifi banner height + int16_t h = tft.height() - fontHeight_2; + int16_t yhalfway = h / 2; + int16_t xhalfway = w / 2; + */ + int16_t SoC = currentMonitor.stateofcharge; + + if (SoC > 100) + { + SoC = 100; + } + + tft.fillRectHGradient(xhalfway - 100, yhalfway - 22, 200, 44, TFT_RED, TFT_GREEN); + + if (SoC != 100) + { + //Clear between SoC and 100% + tft.fillRect((xhalfway - 100) + (2 * SoC), yhalfway - 22, 200-(2 * SoC), 44, TFT_BLACK); + } + + // Stripe lines + for (int16_t i = (xhalfway - 94); i < (xhalfway + 94); i += 6) + { + tft.fillRect(i, yhalfway - 22, 2, 44, TFT_BLACK); + } + + // Single bank, large font + tft.setTextColor(TFT_GREEN, TFT_BLACK); + tft.setTextDatum(TC_DATUM); + tft.setTextFont(7); + tft.drawFloat(currentMonitor.stateofcharge, 1, xhalfway, yhalfway + 55); +} + +void DrawTFT_SoCBarGraph() +{ + // Do nothing here - screen refreshes on page change +} + void DrawTFT_CurrentMonitor() { int16_t w = tft.width(); @@ -1148,11 +1225,14 @@ void updatetftdisplay_task(void *param) case ScreenTemplateToDisplay::VoltageFourBank: PrepareTFT_VoltageFourBank(); break; + case ScreenTemplateToDisplay::State: + PrepareTFT_ControlState(); + break; case ScreenTemplateToDisplay::CurrentMonitor: PrepareTFT_CurrentMonitor(); break; - case ScreenTemplateToDisplay::State: - PrepareTFT_ControlState(); + case ScreenTemplateToDisplay::SoCBarGraph: + PrepareTFT_SocBarGraph(); break; case ScreenTemplateToDisplay::SystemInformation: PrepareTFT_SystemInfo(); @@ -1190,6 +1270,9 @@ void updatetftdisplay_task(void *param) case ScreenTemplateToDisplay::CurrentMonitor: DrawTFT_CurrentMonitor(); break; + case ScreenTemplateToDisplay::SoCBarGraph: + DrawTFT_SoCBarGraph(); + break; case ScreenTemplateToDisplay::SystemInformation: DrawTFT_SystemInfo(); break; From 2b1dc07a2a45186b32c34ffd4d11b73e9e6e9a2e Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Fri, 29 Sep 2023 10:46:52 +0100 Subject: [PATCH 17/17] Update main.cpp --- STM32All-In-One/src/main.cpp | 51 ++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/STM32All-In-One/src/main.cpp b/STM32All-In-One/src/main.cpp index 2c655806..89e44182 100644 --- a/STM32All-In-One/src/main.cpp +++ b/STM32All-In-One/src/main.cpp @@ -31,7 +31,10 @@ Attribution-NonCommercial-ShareAlike 2.0 UK: England & Wales (CC BY-NC-SA 2.0 UK #include -extern "C"{ void SystemClock_Config(void); } +extern "C" +{ + void SystemClock_Config(void); +} extern "C" { @@ -665,10 +668,10 @@ int16_t ReadThermistor(uint32_t pin) /// @param cd Cell data array void TakeExternalTempMeasurements(CellData &cd) { - // Set cell 1 and 2 to have these external temperature sensors, cell 0 uses TH3 onboard - cd.at(1).setExternalTemperature(ReadThermistor(THERM_J11_T1)); - cd.at(2).setExternalTemperature(ReadThermistor(THERM_J12_T2)); - cd.at(3).setExternalTemperature(ReadThermistor(THERM_J13_T3)); + // Set cell 0/1/2 to map to external temperature sensors (socket on pcb) + cd.at(0).setExternalTemperature(ReadThermistor(THERM_J11_T1)); + cd.at(1).setExternalTemperature(ReadThermistor(THERM_J12_T2)); + cd.at(2).setExternalTemperature(ReadThermistor(THERM_J13_T3)); } void EnableThermistorPower() @@ -680,7 +683,7 @@ void DisableThermistorPower() { digitalWrite(THERM_ENABLE, HIGH); } -/// @brief Read onboard-TH1 sensor +/// @brief Read onboard thermistor sensor connected to the MCP33151 /// @return Celcius temperature reading int16_t ReadTH() { @@ -694,25 +697,29 @@ int16_t ReadTH() /// @param cd Cell Data array void TakeOnboardInternalTempMeasurements(CellData &cd) { - // internal temperature sensors (on board) - // Read temperature sensors - MAX14921Command(cell_balancing, ANALOG_BUFFERED_T1); - auto t1 = ReadTH(); - MAX14921Command(cell_balancing, ANALOG_BUFFERED_T2); - auto t2 = ReadTH(); + // internal temperature sensors on balance board + if (PP.BalanceBoardInstalled) + { + // Read temperature sensors + MAX14921Command(cell_balancing, ANALOG_BUFFERED_T1); + auto t1 = ReadTH(); + MAX14921Command(cell_balancing, ANALOG_BUFFERED_T2); + auto t2 = ReadTH(); + + // Spread the two internal temperature sensor values across all 16 cells (even though some may not be connected) + // Odd cells are on the left of the balance board, near TH1/T1, even on right side TH2/T2 + for (size_t i = 0; i < 16; i += 2) + { + cd.at(i).setInternalTemperature(t1); + cd.at(i + 1).setInternalTemperature(t2); + } + } + MAX14921Command(cell_balancing, ANALOG_BUFFERED_T3); auto t3 = ReadTH(); - // Cell 0 external temperature is the on-board sensor (marked TH6) - cd.at(0).setExternalTemperature(t3); - - // Spread the two internal temperature sensor values across all 16 cells (even though some may not be connected) - // Odd cells are on the left of the balance board, near TH1/T1, even on right side TH2/T2 - for (size_t i = 0; i < 16; i += 2) - { - cd.at(i).setInternalTemperature(t1); - cd.at(i + 1).setInternalTemperature(t2); - } + // Cell 0 internal temperature is the on-board (PCB) sensor (marked TH6) + cd.at(0).setInternalTemperature(t3); } [[noreturn]] void ErrorFlashes(int number)