├── .clang-format ├── .gitattributes ├── .github └── workflows │ ├── docs.yml │ └── zephyr.yml ├── .gitignore ├── .vscode └── c_cpp_properties.json ├── CMakeLists.txt ├── Kconfig ├── LICENSE ├── README.md ├── app ├── CMakeLists.txt ├── Kconfig ├── boards │ ├── bms_15s80_sc.conf │ ├── bms_16s100_sc.conf │ ├── bms_16s100_sc_esp32c3.conf │ ├── bms_5s50_sc.conf │ ├── bms_8s50_ic.conf │ ├── bms_8s50_ic_f072.conf │ ├── bms_c1.conf │ ├── native_sim.conf │ └── native_sim.overlay ├── oled.conf ├── prj.conf ├── src │ ├── CMakeLists.txt │ ├── bms_common.c │ ├── bms_soc.c │ ├── button.c │ ├── button.h │ ├── data_objects.c │ ├── data_objects.h │ ├── leds.c │ ├── leds.h │ ├── main.c │ ├── oled.c │ └── oled_font.h ├── sysbuild.conf └── sysbuild │ ├── app.conf │ └── mcuboot │ ├── boards │ ├── bms_c1.conf │ └── bms_c1.overlay │ └── prj.conf ├── boards ├── libresolar │ ├── bms_15s80_sc │ │ ├── Kconfig.bms_15s80_sc │ │ ├── Kconfig.defconfig │ │ ├── bms_15s80_sc.dts │ │ ├── bms_15s80_sc.yaml │ │ ├── bms_15s80_sc_defconfig │ │ ├── board.cmake │ │ ├── board.yml │ │ └── support │ │ │ └── openocd.cfg │ ├── bms_16s100_sc │ │ ├── Kconfig.bms_16s100_sc │ │ ├── Kconfig.defconfig │ │ ├── bms_16s100_sc.dts │ │ ├── bms_16s100_sc.yaml │ │ ├── bms_16s100_sc_defconfig │ │ ├── board.cmake │ │ └── board.yml │ ├── bms_5s50_sc │ │ ├── Kconfig.bms_5s50_sc │ │ ├── Kconfig.defconfig │ │ ├── bms_5s50_sc.dts │ │ ├── bms_5s50_sc.yaml │ │ ├── bms_5s50_sc_defconfig │ │ ├── board.cmake │ │ ├── board.yml │ │ └── support │ │ │ └── openocd.cfg │ ├── bms_8s50_ic │ │ ├── Kconfig.bms_8s50_ic │ │ ├── Kconfig.defconfig │ │ ├── bms_8s50_ic.dts │ │ ├── bms_8s50_ic.yaml │ │ ├── bms_8s50_ic_0_1_0.conf │ │ ├── bms_8s50_ic_0_1_0.overlay │ │ ├── bms_8s50_ic_0_2_0.conf │ │ ├── bms_8s50_ic_0_2_0.overlay │ │ ├── bms_8s50_ic_defconfig │ │ ├── board.cmake │ │ ├── board.yml │ │ └── support │ │ │ └── openocd.cfg │ └── bms_c1 │ │ ├── Kconfig.bms_c1 │ │ ├── Kconfig.defconfig │ │ ├── Kconfig.sysbuild │ │ ├── bms_c1.dts │ │ ├── bms_c1.yaml │ │ ├── bms_c1_0_3_0.conf │ │ ├── bms_c1_0_3_0.overlay │ │ ├── bms_c1_0_4_0.conf │ │ ├── bms_c1_0_4_0.overlay │ │ ├── bms_c1_defconfig │ │ ├── board.cmake │ │ ├── board.yml │ │ └── support │ │ └── openocd.cfg └── shields │ └── uext_oled │ ├── Kconfig.shield │ └── uext_oled.overlay ├── docs ├── Doxyfile ├── Makefile ├── conf.py ├── index.rst ├── requirements.txt ├── src │ ├── api │ │ ├── bms.rst │ │ ├── bms_ic.rst │ │ ├── data_objects.rst │ │ └── misc.rst │ ├── dev │ │ ├── building_flashing.rst │ │ ├── customization.rst │ │ ├── simulator.rst │ │ ├── unit_tests.rst │ │ └── workspace_setup.rst │ ├── features.rst │ └── supported_hardware.rst └── static │ └── images │ ├── cc-by-sa-centered.png │ ├── favicon.ico │ └── logo.png ├── drivers ├── CMakeLists.txt ├── Kconfig └── bms_ic │ ├── CMakeLists.txt │ ├── Kconfig │ ├── bq769x0 │ ├── CMakeLists.txt │ ├── bq769x0.c │ └── bq769x0_registers.h │ ├── bq769x2 │ ├── CMakeLists.txt │ ├── bq769x2.c │ ├── bq769x2_emul.c │ ├── bq769x2_emul.h │ ├── bq769x2_interface.c │ ├── bq769x2_interface.h │ ├── bq769x2_priv.h │ └── bq769x2_registers.h │ └── isl94202 │ ├── CMakeLists.txt │ ├── isl94202.c │ ├── isl94202_emul.c │ ├── isl94202_emul.h │ ├── isl94202_interface.c │ ├── isl94202_interface.h │ ├── isl94202_priv.h │ └── isl94202_registers.h ├── dts └── bindings │ ├── bms.yaml │ └── bms_ic │ ├── bms-ic-common.yaml │ ├── renesas,isl94202.yaml │ ├── ti,bq769x0.yaml │ ├── ti,bq769x2-common.yaml │ ├── ti,bq769x2-i2c.yaml │ └── ti,bq769x2-spi.yaml ├── include ├── bms │ ├── bms.h │ └── bms_common.h ├── drivers │ └── bms_ic.h ├── dt-bindings │ └── bms_ic │ │ └── bq769x2.h └── helper.h ├── lib ├── CMakeLists.txt └── helper.c ├── scripts └── check-style.sh ├── tests ├── bms │ ├── CMakeLists.txt │ ├── boards │ │ └── native_sim.overlay │ ├── prj.conf │ ├── src │ │ └── state_machine.c │ └── testcase.yaml ├── bms_ic │ ├── bq769x2 │ │ ├── CMakeLists.txt │ │ ├── boards │ │ │ └── native_sim.overlay │ │ ├── prj.conf │ │ ├── src │ │ │ ├── functions.c │ │ │ └── interface.c │ │ └── testcase.yaml │ ├── common │ │ ├── CMakeLists.txt │ │ ├── bms_setup.c │ │ └── bms_setup.h │ └── isl94202 │ │ ├── CMakeLists.txt │ │ ├── boards │ │ └── native_sim.overlay │ │ ├── prj.conf │ │ ├── src │ │ └── main.c │ │ └── testcase.yaml └── helper │ ├── CMakeLists.txt │ ├── prj.conf │ ├── src │ ├── interpolation.c │ └── is_empty.c │ └── testcase.yaml ├── west.yml └── zephyr └── module.yml /.clang-format: -------------------------------------------------------------------------------- 1 | # Format definition for Libre Solar firmware 2 | # 3 | # Use Clang-Format plugin in VS Code and add "editor.formatOnSave": true to settings 4 | 5 | # Generic format 6 | BasedOnStyle: LLVM 7 | UseTab: Never 8 | IndentWidth: 4 9 | TabWidth: 4 10 | ColumnLimit: 100 11 | AccessModifierOffset: -4 12 | 13 | # Indentation and alignment 14 | AllowShortEnumsOnASingleLine: false 15 | AllowShortFunctionsOnASingleLine: false 16 | AllowShortIfStatementsOnASingleLine: false 17 | AlignConsecutiveAssignments: false 18 | AlignConsecutiveMacros: true 19 | AlignEscapedNewlines: DontAlign 20 | BreakBeforeBinaryOperators: NonAssignment 21 | IndentCaseLabels: true 22 | 23 | # Braces 24 | BreakBeforeBraces: Custom 25 | BraceWrapping: 26 | AfterClass: true 27 | AfterControlStatement: MultiLine 28 | AfterEnum: true 29 | AfterStruct: true 30 | AfterFunction: true 31 | BeforeElse: true 32 | SplitEmptyFunction: false 33 | Cpp11BracedListStyle: false 34 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Documentation 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v2 13 | with: 14 | fetch-depth: 0 # necessary to get tags 15 | 16 | - name: Install dependencies 17 | run: | 18 | sudo apt install -y git make python3 python3-pip doxygen graphviz 19 | pip3 install -r docs/requirements.txt 20 | 21 | - name: Build documentation 22 | run: | 23 | cd docs 24 | make html 25 | 26 | - name: Deploy to gh-pages 27 | uses: peaceiris/actions-gh-pages@v3 28 | with: 29 | github_token: ${{ secrets.GITHUB_TOKEN }} 30 | publish_dir: docs/build/html/ 31 | enable_jekyll: false 32 | allow_empty_commit: false 33 | force_orphan: true 34 | publish_branch: gh-pages 35 | -------------------------------------------------------------------------------- /.github/workflows/zephyr.yml: -------------------------------------------------------------------------------- 1 | name: Code check and build tests 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | # Avoid running twice if pushing to a PR branch in the base repo 8 | if: > 9 | github.event_name != 'pull_request' || 10 | github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name 11 | runs-on: ubuntu-latest 12 | # Fixed container version to avoid picolibc compile error for v0.26.15 (latest) 13 | # https://github.com/zephyrproject-rtos/docker-image/issues/205 14 | container: zephyrprojectrtos/ci:v0.26.14 15 | env: 16 | CMAKE_PREFIX_PATH: /opt/toolchains 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v3 20 | with: 21 | path: bms-firmware 22 | fetch-depth: 0 23 | 24 | - name: Initialize 25 | working-directory: bms-firmware 26 | run: | 27 | west init -l . 28 | west update --narrow 29 | west blobs fetch hal_espressif 30 | 31 | - name: Coding style check 32 | working-directory: bms-firmware 33 | run: | 34 | apt-get update 35 | apt-get install -y clang-format-15 colordiff 36 | git status 37 | bash scripts/check-style.sh 38 | 39 | - name: Build firmware 40 | working-directory: bms-firmware 41 | run: | 42 | cd app 43 | west build -p -b native_sim 44 | west build -p -b bms_5s50_sc 45 | west build -p -b bms_8s50_ic@0.2 -- -DEXTRA_CONF_FILE=oled.conf -DSHIELD=uext_oled 46 | west build -p -b bms_15s80_sc 47 | west build -p -b bms_16s100_sc 48 | west build -p -b bms_c1@0.4 49 | rm -rf build 50 | west build -b bms_c1@0.4 --sysbuild -p 51 | 52 | - name: Run unit-tests 53 | working-directory: bms-firmware 54 | run: | 55 | ../zephyr/scripts/twister -T ./tests --integration --inline-logs -n 56 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # backup files 2 | *.bak 3 | *_bak 4 | 5 | # folders generated by west/twister 6 | build 7 | twister-out* 8 | 9 | # native_sim EEPROM file 10 | eeprom.bin 11 | 12 | # editors 13 | .vscode/* 14 | !.vscode/c_cpp_properties.json 15 | *.swp 16 | *~ 17 | 18 | # others 19 | .clang_complete 20 | .gcc-flags.json 21 | .pio 22 | bin 23 | html 24 | -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Zephyr", 5 | "includePath": [ 6 | "${workspaceFolder}/app/src", 7 | "${workspaceFolder}/app/build/zephyr/include/generated", 8 | "${workspaceFolder}/../modules/hal/cmsis/CMSIS/Core/Include", 9 | "${workspaceFolder}/../modules/hal/stm32/stm32cube/stm32g0xx/soc", 10 | "${workspaceFolder}/../modules/thingset/src", 11 | "${workspaceFolder}/../zephyr/include", 12 | "${workspaceFolder}/../zephyr/soc/arm/st_stm32/common", 13 | "${workspaceFolder}/../zephyr/soc/arm/st_stm32/stm32g0", 14 | "" 15 | ], 16 | "browse": { 17 | "path": [ 18 | "${workspaceFolder}/app/src", 19 | "${workspaceFolder}/../modules/thingset/src", 20 | ], 21 | "limitSymbolsToIncludedHeaders": true 22 | }, 23 | "defines": [ 24 | "FIRMWARE_VERSION_ID=\"v21.0-21-ga535fa8\"" 25 | ], 26 | "cStandard": "c99", 27 | "cppStandard": "c++14", 28 | "forcedInclude": [ 29 | "${workspaceFolder}/app/build/zephyr/include/generated/autoconf.h" 30 | ] 31 | } 32 | ], 33 | "version": 4 34 | } 35 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Nordic Semiconductor ASA 2 | # SPDX-License-Identifier: Apache-2.0 3 | # 4 | # This CMake file is picked by the Zephyr build system because it is defined 5 | # as the module CMake entry point (see zephyr/module.yml). 6 | 7 | zephyr_include_directories(include) 8 | 9 | add_subdirectory(drivers) 10 | add_subdirectory(lib) 11 | -------------------------------------------------------------------------------- /Kconfig: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Nordic Semiconductor ASA 2 | # SPDX-License-Identifier: Apache-2.0 3 | # 4 | # This Kconfig file is picked by the Zephyr build system because it is defined 5 | # as the module Kconfig entry point (see zephyr/module.yml). You can browse 6 | # module options by going to Zephyr -> Modules in Kconfig. 7 | 8 | rsource "drivers/Kconfig" 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Libre Solar BMS Firmware 2 | 3 | ![build badge](https://github.com/LibreSolar/bms-firmware/actions/workflows/zephyr.yml/badge.svg) 4 | 5 | This repository contains the firmware for Libre Solar Battery Management Systems based on [Zephyr RTOS](https://www.zephyrproject.org/) . 6 | 7 | ## Development and release model 8 | 9 | The `main` branch is used for ongoing development of the firmware. 10 | 11 | Releases are created from `main` after significant updates have been introduced to the firmware. Each release has to pass tests with multiple boards. 12 | 13 | A release is tagged with a version number consisting of the release year and a release count for that year (starting at zero). For back-porting of bug-fixes, a branch named after the release followed by `-branch` is created, e.g. `v21.0-branch`. 14 | 15 | ## Documentation 16 | 17 | The firmware documentation including build instructions and API reference can be found under [libre.solar/bms-firmware](https://libre.solar/bms-firmware/). 18 | 19 | In order to build the documentation locally you need to install Doxygen, Sphinx and Breathe and run `make html` in the `docs` folder. 20 | 21 | ## License 22 | 23 | This firmware is released under the [Apache-2.0 License](LICENSE). 24 | -------------------------------------------------------------------------------- /app/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | 3 | cmake_minimum_required(VERSION 3.13.1) 4 | find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) 5 | 6 | project(libre_solar_bms) 7 | 8 | add_subdirectory_ifndef(CONFIG_MCUBOOT src) 9 | 10 | # determine git tag and commit hash for automatic firmware versioning 11 | find_package(Git) 12 | if(GIT_FOUND) 13 | execute_process( 14 | COMMAND ${GIT_EXECUTABLE} describe --long --dirty --tags 15 | OUTPUT_VARIABLE FIRMWARE_VERSION_ID 16 | OUTPUT_STRIP_TRAILING_WHITESPACE) 17 | else() 18 | set(FIRMWARE_VERSION_ID "unknown") 19 | endif() 20 | add_definitions(-DFIRMWARE_VERSION_ID="${FIRMWARE_VERSION_ID}") 21 | -------------------------------------------------------------------------------- /app/Kconfig: -------------------------------------------------------------------------------- 1 | # Copyright (c) The Libre Solar Project Contributors 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | mainmenu "Libre Solar Battery Management System Firmware" 5 | 6 | menu "Battery default settings" 7 | 8 | config BAT_CAPACITY_AH 9 | int "Battery nominal capacity in Ah" 10 | range 10 1000 11 | default 50 12 | help 13 | Nominal battery capacity or sum of parallel cells capacity 14 | 15 | choice 16 | prompt "Default cell type" 17 | default CELL_TYPE_LFP 18 | help 19 | Select the type of the single cells inside the battery 20 | to determine voltage set points. Total voltage set points 21 | are multiplied with the selected number of cells. 22 | 23 | The default values based on this configuration can be overriden via ThingSet. 24 | 25 | config CELL_TYPE_LFP 26 | bool "LiFePO4, 3.3V nominal" 27 | 28 | config CELL_TYPE_NMC 29 | bool "NMC/Graphite, 3.7V nominal, 4.2V max" 30 | 31 | config CELL_TYPE_LTO 32 | bool "NMC/Titanate, 2.4 V nominal" 33 | 34 | endchoice 35 | 36 | # values must match enum CellType in bms.h 37 | config CELL_TYPE 38 | int 39 | default 0 if CELL_TYPE_LFP 40 | default 1 if CELL_TYPE_NMC 41 | default 2 if CELL_TYPE_LTO 42 | 43 | endmenu 44 | 45 | # include main Zephyr menu entries from Zephyr root directory 46 | source "Kconfig.zephyr" 47 | -------------------------------------------------------------------------------- /app/boards/bms_15s80_sc.conf: -------------------------------------------------------------------------------- 1 | # Copyright (c) The Libre Solar Project Contributors 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | CONFIG_EEPROM=y 5 | CONFIG_EEPROM_AT24=y 6 | -------------------------------------------------------------------------------- /app/boards/bms_16s100_sc.conf: -------------------------------------------------------------------------------- 1 | # Copyright (c) The Libre Solar Project Contributors 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # STM32G0B1 does not have hardware random number generator. Test RNG should 5 | # be sufficient for CAN node address assignment purposes. 6 | CONFIG_TEST_RANDOM_GENERATOR=y 7 | 8 | CONFIG_CAN=y 9 | CONFIG_ISOTP=y 10 | CONFIG_ISOTP_FAST=y 11 | 12 | CONFIG_FLASH=y 13 | CONFIG_NVS=y 14 | CONFIG_NVS_LOG_LEVEL_WRN=y 15 | -------------------------------------------------------------------------------- /app/boards/bms_16s100_sc_esp32c3.conf: -------------------------------------------------------------------------------- 1 | 2 | # Flash 3 | CONFIG_MPU_ALLOW_FLASH_WRITE=y 4 | CONFIG_FLASH=y 5 | CONFIG_NVS=y 6 | CONFIG_NVS_LOG_LEVEL_WRN=y 7 | 8 | # Bluetooth 9 | CONFIG_BT=y 10 | CONFIG_BT_PERIPHERAL=y 11 | CONFIG_BT_DEVICE_NAME="LibreSolar BMS-C1" 12 | CONFIG_BT_DEVICE_NAME_DYNAMIC=y 13 | CONFIG_BT_GATT_CLIENT=y 14 | 15 | # Increase buffer sizes to allow long ATT values 16 | CONFIG_BT_BUF_ACL_RX_SIZE=251 17 | CONFIG_BT_BUF_ACL_TX_SIZE=251 18 | CONFIG_BT_L2CAP_TX_MTU=251 19 | 20 | CONFIG_THINGSET_BLE=y 21 | 22 | CONFIG_SHELL=y 23 | # Avoid conflicts with ThingSet ? command 24 | CONFIG_SHELL_WILDCARD=n 25 | # Support `select thingset` to run ThingSet requests directly 26 | CONFIG_SHELL_CMDS_SELECT=y 27 | 28 | CONFIG_THINGSET_SHELL=y 29 | CONFIG_THINGSET_SHELL_REPORTING=n 30 | CONFIG_THINGSET_SHELL_CMD_BUF_SIZE=256 31 | -------------------------------------------------------------------------------- /app/boards/bms_5s50_sc.conf: -------------------------------------------------------------------------------- 1 | # Copyright (c) The Libre Solar Project Contributors 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | CONFIG_EEPROM=y 5 | CONFIG_EEPROM_AT24=y 6 | 7 | # MCU does not have sufficient RAM 8 | CONFIG_THINGSET_CAN=n 9 | -------------------------------------------------------------------------------- /app/boards/bms_8s50_ic.conf: -------------------------------------------------------------------------------- 1 | # Copyright (c) The Libre Solar Project Contributors 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | CONFIG_EEPROM=y 5 | CONFIG_EEPROM_AT24=y 6 | 7 | CONFIG_CAN=y 8 | CONFIG_ISOTP=y 9 | CONFIG_ISOTP_FAST=y 10 | CONFIG_ENTROPY_GENERATOR=y 11 | 12 | CONFIG_THINGSET_CAN=y 13 | -------------------------------------------------------------------------------- /app/boards/bms_8s50_ic_f072.conf: -------------------------------------------------------------------------------- 1 | # Copyright (c) The Libre Solar Project Contributors 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | CONFIG_EEPROM=y 5 | CONFIG_EEPROM_AT24=y 6 | -------------------------------------------------------------------------------- /app/boards/bms_c1.conf: -------------------------------------------------------------------------------- 1 | # Copyright (c) The Libre Solar Project Contributors 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # CAN 5 | CONFIG_CAN=y 6 | CONFIG_ISOTP=y 7 | CONFIG_ISOTP_FAST=y 8 | CONFIG_ENTROPY_GENERATOR=y 9 | 10 | CONFIG_THINGSET_CAN=y 11 | 12 | # Flash 13 | CONFIG_MPU_ALLOW_FLASH_WRITE=y 14 | CONFIG_FLASH=y 15 | CONFIG_NVS=y 16 | CONFIG_NVS_LOG_LEVEL_WRN=y 17 | 18 | # Bluetooth 19 | CONFIG_BT=y 20 | CONFIG_BT_PERIPHERAL=y 21 | CONFIG_BT_DEVICE_NAME="LibreSolar BMS-C1" 22 | CONFIG_BT_DEVICE_NAME_DYNAMIC=y 23 | CONFIG_BT_GATT_CLIENT=y 24 | 25 | # Increase buffer sizes to allow long ATT values 26 | CONFIG_BT_BUF_ACL_RX_SIZE=251 27 | CONFIG_BT_BUF_ACL_TX_SIZE=251 28 | CONFIG_BT_L2CAP_TX_MTU=251 29 | 30 | CONFIG_THINGSET_BLE=y 31 | 32 | CONFIG_SHELL=y 33 | # Avoid conflicts with ThingSet ? command 34 | CONFIG_SHELL_WILDCARD=n 35 | # Support `select thingset` to run ThingSet requests directly 36 | CONFIG_SHELL_CMDS_SELECT=y 37 | 38 | CONFIG_THINGSET_SHELL=y 39 | CONFIG_THINGSET_SHELL_REPORTING=n 40 | CONFIG_THINGSET_SHELL_CMD_BUF_SIZE=256 41 | -------------------------------------------------------------------------------- /app/boards/native_sim.conf: -------------------------------------------------------------------------------- 1 | # Copyright (c) The Libre Solar Project Contributors 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # Enable I2C to emulate BMS 5 | CONFIG_I2C=y 6 | 7 | # Expose shell directly on the terminal 8 | CONFIG_NATIVE_UART_0_ON_STDINOUT=y 9 | 10 | # Use uart1 for ThingSet 11 | CONFIG_UART_NATIVE_POSIX_PORT_1_ENABLE=y 12 | 13 | CONFIG_LOG_MODE_MINIMAL=n 14 | CONFIG_LOG_BACKEND_NATIVE_POSIX=y 15 | 16 | CONFIG_GPIO=y 17 | 18 | CONFIG_CAN=y 19 | CONFIG_CAN_FD_MODE=y 20 | CONFIG_ISOTP=y 21 | CONFIG_ISOTP_FAST=y 22 | CONFIG_THINGSET_CAN=y 23 | 24 | CONFIG_SHELL=y 25 | # Avoid conflicts with ThingSet ? command 26 | CONFIG_SHELL_WILDCARD=n 27 | # Support `select thingset` to run ThingSet requests directly 28 | CONFIG_SHELL_CMDS_SELECT=y 29 | 30 | CONFIG_THINGSET_SHELL=y 31 | CONFIG_THINGSET_SHELL_REPORTING=n 32 | CONFIG_THINGSET_SHELL_CMD_BUF_SIZE=256 33 | 34 | CONFIG_EMUL=y 35 | CONFIG_EEPROM=y 36 | 37 | CONFIG_EVENTS=y 38 | 39 | CONFIG_THINGSET_STORAGE=y 40 | 41 | # Bluetooth 42 | CONFIG_BT=y 43 | CONFIG_BT_PERIPHERAL=y 44 | CONFIG_BT_DEVICE_NAME="LibreSolar BMS-C1" 45 | CONFIG_BT_DEVICE_NAME_DYNAMIC=y 46 | CONFIG_BT_GATT_CLIENT=y 47 | 48 | # Increase buffer sizes to allow long ATT values 49 | CONFIG_BT_BUF_ACL_RX_SIZE=251 50 | CONFIG_BT_BUF_ACL_TX_SIZE=251 51 | CONFIG_BT_L2CAP_TX_MTU=251 52 | 53 | CONFIG_THINGSET_BLE=y 54 | 55 | # required to generate node ID in ThingSet 56 | CONFIG_ENTROPY_GENERATOR=y 57 | CONFIG_ENTROPY_DEVICE_RANDOM_GENERATOR=y 58 | 59 | CONFIG_BMS_IC=y 60 | CONFIG_BMS_IC_MAX_THERMISTORS=2 61 | -------------------------------------------------------------------------------- /app/boards/native_sim.overlay: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) The Libre Solar Project Contributors 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #include 8 | 9 | / { 10 | pcb { 11 | compatible = "bms"; 12 | 13 | type = "BMS Simulator"; 14 | version-str = "v0.1"; 15 | version-num = <1>; 16 | }; 17 | 18 | chosen { 19 | zephyr,canbus = &can0; 20 | thingset,eeprom = &eeprom0; 21 | thingset,serial = &uart1; 22 | }; 23 | 24 | leds { 25 | compatible = "gpio-leds"; 26 | led1: led_0 { 27 | gpios = <&gpio0 0 GPIO_ACTIVE_HIGH>; 28 | }; 29 | led2: led_1 { 30 | gpios = <&gpio0 1 GPIO_ACTIVE_HIGH>; 31 | }; 32 | }; 33 | 34 | gpio_keys { 35 | compatible = "gpio-keys"; 36 | power_button: button { 37 | gpios = <&gpio0 3 GPIO_ACTIVE_HIGH>; 38 | }; 39 | }; 40 | 41 | aliases { 42 | led-red = &led1; 43 | led-green = &led2; 44 | sw-pwr = &power_button; 45 | bms-ic = &bq769x2; 46 | }; 47 | }; 48 | 49 | /* 50 | * Set up a virtual CAN interface with following commands: 51 | * 52 | * sudo ip link add dev vcan0 type vcan 53 | * sudo ip link set up vcan0 54 | */ 55 | &can0 { 56 | status = "okay"; 57 | host-interface = "vcan0"; 58 | }; 59 | 60 | &i2c0 { 61 | status = "okay"; 62 | 63 | bq769x2: bq76952@8 { 64 | compatible = "ti,bq769x2-i2c"; 65 | reg = <0x08>; 66 | alert-gpios = <&gpio0 2 GPIO_ACTIVE_HIGH>; 67 | used-cell-channels = <0xFFFF>; 68 | /* all NTCs configured with 18k pull-up */ 69 | ts1-pin-config = <0x07>; 70 | dchg-pin-config = <0x0F>; 71 | cell-temp-pins = ; 72 | fet-temp-pin = ; 73 | board-max-current = <50>; 74 | shunt-resistor-uohm = <500>; 75 | status = "okay"; 76 | }; 77 | }; 78 | -------------------------------------------------------------------------------- /app/oled.conf: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | 3 | CONFIG_DISPLAY=y 4 | CONFIG_CHARACTER_FRAMEBUFFER=y 5 | CONFIG_CHARACTER_FRAMEBUFFER_USE_DEFAULT_FONTS=n 6 | CONFIG_HEAP_MEM_POOL_SIZE=4096 7 | -------------------------------------------------------------------------------- /app/prj.conf: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | 3 | CONFIG_PICOLIBC_IO_FLOAT=y 4 | 5 | CONFIG_THREAD_NAME=y 6 | 7 | CONFIG_REBOOT=y 8 | 9 | CONFIG_EVENTS=y 10 | CONFIG_HWINFO=y 11 | 12 | CONFIG_LOG=y 13 | CONFIG_LOG_MODE_MINIMAL=y 14 | 15 | CONFIG_I2C=y 16 | CONFIG_BMS_IC=y 17 | 18 | # Enable ThingSet library and comms interfaces 19 | CONFIG_THINGSET=y 20 | CONFIG_THINGSET_SDK=y 21 | CONFIG_THINGSET_SERIAL=y 22 | CONFIG_THINGSET_STORAGE=y 23 | CONFIG_THINGSET_STORAGE_DATA_VERSION=2 24 | CONFIG_THINGSET_NODE_NAME="Libre Solar BMS" 25 | 26 | # Uncomment to disable live data publication at startup 27 | #CONFIG_THINGSET_REPORTING_LIVE_ENABLE_PRESET=n 28 | 29 | # Change default battery nominal capacity (in Ah) 30 | #CONFIG_BAT_CAPACITY_AH=? 31 | 32 | # Select cell type for initial configuration: LFP (default), NMC or LTO 33 | #CONFIG_CELL_TYPE_???=y 34 | -------------------------------------------------------------------------------- /app/src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | 3 | zephyr_include_directories(.) 4 | 5 | target_sources(app PRIVATE 6 | bms_common.c 7 | bms_soc.c 8 | button.c 9 | data_objects.c 10 | leds.c 11 | main.c 12 | ) 13 | 14 | zephyr_sources_ifdef(CONFIG_SHIELD_UEXT_OLED oled.c) 15 | -------------------------------------------------------------------------------- /app/src/bms_common.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) The Libre Solar Project Contributors 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #include 8 | #include 9 | 10 | #include "helper.h" 11 | 12 | #include 13 | 14 | LOG_MODULE_REGISTER(bms, CONFIG_LOG_DEFAULT_LEVEL); 15 | 16 | static const float ocv_lfp[NUM_OCV_POINTS] = { 17 | 3.392F, 3.314F, 3.309F, 3.308F, 3.304F, 3.296F, 3.283F, 3.275F, 3.271F, 3.268F, 3.265F, 18 | 3.264F, 3.262F, 3.252F, 3.240F, 3.226F, 3.213F, 3.190F, 3.177F, 3.132F, 2.833F, 19 | }; 20 | 21 | static const float ocv_nmc[NUM_OCV_POINTS] = { 22 | 4.198F, 4.135F, 4.089F, 4.056F, 4.026F, 3.993F, 3.962F, 3.924F, 3.883F, 3.858F, 3.838F, 23 | 3.819F, 3.803F, 3.787F, 3.764F, 3.745F, 3.726F, 3.702F, 3.684F, 3.588F, 2.800F, 24 | }; 25 | 26 | // Source: DOI:10.3390/batteries5010031 (https://www.mdpi.com/2313-0105/5/1/31) 27 | static const float ocv_lto[NUM_OCV_POINTS] = { 28 | 2.800F, 2.513F, 2.458F, 2.415F, 2.376F, 2.340F, 2.309F, 2.282F, 2.259F, 2.240F, 2.224F, 29 | 2.210F, 2.198F, 2.187F, 2.177F, 2.166F, 2.154F, 2.141F, 2.125F, 2.105F, 2.000F, 30 | }; 31 | 32 | static const float soc_default[NUM_OCV_POINTS] = { 33 | 100.0F, 95.0F, 90.0F, 85.0F, 80.0F, 75.0F, 70.0F, 65.0F, 60.0F, 55.0F, 50.0F, 34 | 45.0F, 40.0F, 35.0F, 30.0F, 25.0F, 20.0F, 15.0F, 10.0F, 5.0F, 0.0F, 35 | }; 36 | 37 | // actually used OCV vs. SOC curve 38 | float ocv_points[NUM_OCV_POINTS] = { 0 }; 39 | float soc_points[NUM_OCV_POINTS] = { 0 }; 40 | 41 | void bms_init_config(struct bms_context *bms, enum bms_cell_type type, float nominal_capacity_Ah) 42 | { 43 | bms->nominal_capacity_Ah = nominal_capacity_Ah; 44 | 45 | bms->chg_enable = true; 46 | bms->dis_enable = true; 47 | 48 | bms->ic_conf.auto_balancing = true; 49 | bms->ic_conf.bal_idle_delay = 1800; // default: 30 minutes 50 | bms->ic_conf.bal_idle_current = 0.1F; // A 51 | bms->ic_conf.bal_cell_voltage_diff = 0.01F; // 10 mV 52 | 53 | #ifdef CONFIG_BMS_IC_CURRENT_MONITORING 54 | /* 1C should be safe for all batteries */ 55 | bms->ic_conf.dis_oc_limit = bms->nominal_capacity_Ah; 56 | bms->ic_conf.chg_oc_limit = bms->nominal_capacity_Ah; 57 | 58 | bms->ic_conf.dis_oc_delay_ms = 320; 59 | bms->ic_conf.chg_oc_delay_ms = 320; 60 | 61 | bms->ic_conf.dis_sc_limit = bms->ic_conf.dis_oc_limit * 2; 62 | bms->ic_conf.dis_sc_delay_us = 200; 63 | #endif /* CONFIG_BMS_IC_CURRENT_MONITORING */ 64 | 65 | bms->ic_conf.dis_ut_limit = -20; 66 | bms->ic_conf.dis_ot_limit = 45; 67 | bms->ic_conf.chg_ut_limit = 0; 68 | bms->ic_conf.chg_ot_limit = 45; 69 | bms->ic_conf.temp_limit_hyst = 5; 70 | 71 | bms->ic_conf.cell_ov_delay_ms = 2000; 72 | bms->ic_conf.cell_uv_delay_ms = 2000; 73 | 74 | bms->ocv_points = ocv_points; 75 | bms->soc_points = soc_points; 76 | 77 | memcpy(soc_points, soc_default, sizeof(soc_points)); 78 | 79 | switch (type) { 80 | case CELL_TYPE_LFP: 81 | bms->ic_conf.cell_ov_limit = 3.80F; 82 | bms->ic_conf.cell_chg_voltage_limit = 3.55F; 83 | bms->ic_conf.cell_ov_reset = 3.40F; 84 | bms->ic_conf.bal_cell_voltage_min = 3.30F; 85 | bms->ic_conf.cell_uv_reset = 3.10F; 86 | bms->ic_conf.cell_dis_voltage_limit = 2.80F; 87 | /* 88 | * most cells survive even 2.0V, but we should keep some margin for further 89 | * self-discharge 90 | */ 91 | bms->ic_conf.cell_uv_limit = 2.50F; 92 | memcpy(ocv_points, ocv_lfp, sizeof(ocv_points)); 93 | break; 94 | case CELL_TYPE_NMC: 95 | bms->ic_conf.cell_ov_limit = 4.25F; 96 | bms->ic_conf.cell_chg_voltage_limit = 4.20F; 97 | bms->ic_conf.cell_ov_reset = 4.05F; 98 | bms->ic_conf.bal_cell_voltage_min = 3.80F; 99 | bms->ic_conf.cell_uv_reset = 3.50F; 100 | bms->ic_conf.cell_dis_voltage_limit = 3.20F; 101 | bms->ic_conf.cell_uv_limit = 3.00F; 102 | memcpy(ocv_points, ocv_nmc, sizeof(ocv_points)); 103 | break; 104 | case CELL_TYPE_LTO: 105 | bms->ic_conf.cell_ov_limit = 2.85F; 106 | bms->ic_conf.cell_chg_voltage_limit = 2.80F; 107 | bms->ic_conf.cell_ov_reset = 2.70F; 108 | bms->ic_conf.bal_cell_voltage_min = 2.50F; 109 | bms->ic_conf.cell_uv_reset = 2.10F; 110 | bms->ic_conf.cell_dis_voltage_limit = 2.00F; 111 | bms->ic_conf.cell_uv_limit = 1.90F; 112 | memcpy(ocv_points, ocv_lto, sizeof(ocv_points)); 113 | break; 114 | } 115 | 116 | /* trigger alert for all possible errors by default */ 117 | bms->ic_conf.alert_mask = BMS_ERR_ALL; 118 | } 119 | 120 | __weak void bms_state_machine(struct bms_context *bms) 121 | { 122 | switch (bms->state) { 123 | case BMS_STATE_OFF: 124 | if (bms_dis_allowed(bms)) { 125 | bms_ic_set_switches(bms->ic_dev, BMS_SWITCH_DIS, true); 126 | bms->state = BMS_STATE_DIS; 127 | LOG_INF("OFF -> DIS (error flags: 0x%08x)", bms->ic_data.error_flags); 128 | } 129 | else if (bms_chg_allowed(bms)) { 130 | bms_ic_set_switches(bms->ic_dev, BMS_SWITCH_CHG, true); 131 | bms->state = BMS_STATE_CHG; 132 | LOG_INF("OFF -> CHG (error flags: 0x%08x)", bms->ic_data.error_flags); 133 | } 134 | break; 135 | case BMS_STATE_CHG: 136 | if (!bms_chg_allowed(bms)) { 137 | bms_ic_set_switches(bms->ic_dev, BMS_SWITCH_CHG, false); 138 | /* DIS switch may be on on because of ideal diode control */ 139 | bms_ic_set_switches(bms->ic_dev, BMS_SWITCH_DIS, false); 140 | bms->state = BMS_STATE_OFF; 141 | LOG_INF("CHG -> OFF (error flags: 0x%08x)", bms->ic_data.error_flags); 142 | } 143 | else if (bms_dis_allowed(bms)) { 144 | bms_ic_set_switches(bms->ic_dev, BMS_SWITCH_DIS, true); 145 | bms->state = BMS_STATE_NORMAL; 146 | LOG_INF("CHG -> NORMAL (error flags: 0x%08x)", bms->ic_data.error_flags); 147 | } 148 | #ifndef CONFIG_BMS_IC_BQ769X2 /* bq769x2 has built-in ideal diode control */ 149 | else { 150 | /* ideal diode control for discharge MOSFET (with hysteresis) */ 151 | if (bms->ic_data.current > 0.5F) { 152 | bms_ic_set_switches(bms->ic_dev, BMS_SWITCH_DIS, true); 153 | } 154 | else if (bms->ic_data.current < 0.1F) { 155 | bms_ic_set_switches(bms->ic_dev, BMS_SWITCH_DIS, false); 156 | } 157 | } 158 | #endif 159 | break; 160 | case BMS_STATE_DIS: 161 | if (!bms_dis_allowed(bms)) { 162 | bms_ic_set_switches(bms->ic_dev, BMS_SWITCH_DIS, false); 163 | /* CHG_FET may be on because of ideal diode control */ 164 | bms_ic_set_switches(bms->ic_dev, BMS_SWITCH_CHG, false); 165 | bms->state = BMS_STATE_OFF; 166 | LOG_INF("DIS -> OFF (error flags: 0x%08x)", bms->ic_data.error_flags); 167 | } 168 | else if (bms_chg_allowed(bms)) { 169 | bms_ic_set_switches(bms->ic_dev, BMS_SWITCH_CHG, true); 170 | bms->state = BMS_STATE_NORMAL; 171 | LOG_INF("DIS -> NORMAL (error flags: 0x%08x)", bms->ic_data.error_flags); 172 | } 173 | #ifndef CONFIG_BMS_IC_BQ769X2 /* bq769x2 has built-in ideal diode control */ 174 | else { 175 | /* ideal diode control for charge MOSFET (with hysteresis) */ 176 | if (bms->ic_data.current < -0.5F) { 177 | bms_ic_set_switches(bms->ic_dev, BMS_SWITCH_CHG, true); 178 | } 179 | else if (bms->ic_data.current > -0.1F) { 180 | bms_ic_set_switches(bms->ic_dev, BMS_SWITCH_CHG, false); 181 | } 182 | } 183 | #endif 184 | break; 185 | case BMS_STATE_NORMAL: 186 | if (!bms_dis_allowed(bms)) { 187 | bms_ic_set_switches(bms->ic_dev, BMS_SWITCH_DIS, false); 188 | bms->state = BMS_STATE_CHG; 189 | LOG_INF("NORMAL -> CHG (error flags: 0x%08x)", bms->ic_data.error_flags); 190 | } 191 | else if (!bms_chg_allowed(bms)) { 192 | bms_ic_set_switches(bms->ic_dev, BMS_SWITCH_CHG, false); 193 | bms->state = BMS_STATE_DIS; 194 | LOG_INF("NORMAL -> DIS (error flags: 0x%08x)", bms->ic_data.error_flags); 195 | } 196 | break; 197 | case BMS_STATE_SHUTDOWN: 198 | /* do nothing and wait until shutdown is completed */ 199 | break; 200 | } 201 | } 202 | 203 | void bms_shutdown(struct bms_context *bms) 204 | { 205 | bms_ic_set_switches(bms->ic_dev, BMS_SWITCH_DIS, false); 206 | bms_ic_set_switches(bms->ic_dev, BMS_SWITCH_CHG, false); 207 | bms->state = BMS_STATE_SHUTDOWN; 208 | } 209 | 210 | bool bms_chg_error(uint32_t error_flags) 211 | { 212 | return error_flags 213 | & (BMS_ERR_CELL_OVERVOLTAGE | BMS_ERR_CHG_OVERCURRENT | BMS_ERR_OPEN_WIRE 214 | | BMS_ERR_CHG_UNDERTEMP | BMS_ERR_CHG_OVERTEMP | BMS_ERR_INT_OVERTEMP 215 | | BMS_ERR_CELL_FAILURE | BMS_ERR_CHG_OFF); 216 | } 217 | 218 | bool bms_dis_error(uint32_t error_flags) 219 | { 220 | return error_flags 221 | & (BMS_ERR_CELL_UNDERVOLTAGE | BMS_ERR_SHORT_CIRCUIT | BMS_ERR_DIS_OVERCURRENT 222 | | BMS_ERR_OPEN_WIRE | BMS_ERR_DIS_UNDERTEMP | BMS_ERR_DIS_OVERTEMP 223 | | BMS_ERR_INT_OVERTEMP | BMS_ERR_CELL_FAILURE | BMS_ERR_DIS_OFF); 224 | } 225 | 226 | bool bms_chg_allowed(struct bms_context *bms) 227 | { 228 | return !bms_chg_error(bms->ic_data.error_flags & ~BMS_ERR_CHG_OFF) && !bms->full 229 | && bms->chg_enable; 230 | } 231 | 232 | bool bms_dis_allowed(struct bms_context *bms) 233 | { 234 | return !bms_dis_error(bms->ic_data.error_flags & ~BMS_ERR_DIS_OFF) && !bms->empty 235 | && bms->dis_enable; 236 | } 237 | -------------------------------------------------------------------------------- /app/src/bms_soc.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) The Libre Solar Project Contributors 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #include 8 | 9 | #include "helper.h" 10 | 11 | void bms_soc_reset(struct bms_context *bms, int percent) 12 | { 13 | if (percent <= 100 && percent >= 0) { 14 | bms->soc = percent; 15 | } 16 | else if (bms->ocv_points != NULL && bms->soc_points != NULL 17 | && !is_empty((uint8_t *)bms->ocv_points, NUM_OCV_POINTS) 18 | && !is_empty((uint8_t *)bms->soc_points, NUM_OCV_POINTS)) 19 | { 20 | // Executes if both OCV and SOC points are valid pointers and arrays contain non-zero data. 21 | bms->soc = interpolate(bms->ocv_points, bms->soc_points, NUM_OCV_POINTS, 22 | bms->ic_data.cell_voltage_avg); 23 | } 24 | else { 25 | // no OCV curve specified, use simplified estimation instead 26 | float ocv_simple[2] = { bms->ic_conf.cell_chg_voltage_limit, 27 | bms->ic_conf.cell_dis_voltage_limit }; 28 | float soc_simple[2] = { 100.0F, 0.0F }; 29 | bms->soc = interpolate(ocv_simple, soc_simple, 2, bms->ic_data.cell_voltage_avg); 30 | } 31 | } 32 | 33 | void bms_soc_update(struct bms_context *bms) 34 | { 35 | static float coulomb_counter_mAs = 0; 36 | static int64_t last_update = 0; 37 | int64_t now = k_uptime_get(); 38 | 39 | coulomb_counter_mAs += bms->ic_data.current * (now - last_update); 40 | float soc_delta = coulomb_counter_mAs / (bms->nominal_capacity_Ah * 3.6e4F); 41 | 42 | if (soc_delta > 0.1F || soc_delta < -0.1F) { 43 | // only update SoC after significant changes to maintain higher resolution 44 | float soc_tmp = bms->soc + soc_delta; 45 | bms->soc = CLAMP(soc_tmp, 0.0F, 100.0F); 46 | coulomb_counter_mAs = 0; 47 | } 48 | 49 | last_update = now; 50 | } 51 | -------------------------------------------------------------------------------- /app/src/button.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) The Libre Solar Project Contributors 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #include "button.h" 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include "helper.h" 14 | 15 | LOG_MODULE_REGISTER(button, CONFIG_LOG_DEFAULT_LEVEL); 16 | 17 | #define BTN_GPIO DT_ALIAS(sw_pwr) 18 | #define BTN_CTLR DT_GPIO_CTLR(BTN_GPIO, gpios) 19 | #define BTN_PIN DT_GPIO_PIN(BTN_GPIO, gpios) 20 | #define BTN_FLAGS DT_GPIO_FLAGS(BTN_GPIO, gpios) 21 | 22 | static const struct device *btn_dev = DEVICE_DT_GET(BTN_CTLR); 23 | static struct gpio_callback btn_cb_data; 24 | static int64_t time_pressed; 25 | 26 | static void button_pressed_cb(const struct device *dev, struct gpio_callback *cb, uint32_t pins) 27 | { 28 | time_pressed = k_uptime_get(); 29 | } 30 | 31 | void button_init() 32 | { 33 | if (!device_is_ready(btn_dev)) { 34 | LOG_ERR("Button device not found"); 35 | return; 36 | } 37 | 38 | gpio_pin_configure(btn_dev, BTN_PIN, BTN_FLAGS | GPIO_INPUT); 39 | gpio_pin_interrupt_configure(btn_dev, BTN_PIN, GPIO_INT_EDGE_TO_ACTIVE); 40 | gpio_init_callback(&btn_cb_data, button_pressed_cb, BIT(BTN_PIN)); 41 | gpio_add_callback(btn_dev, &btn_cb_data); 42 | } 43 | 44 | bool button_pressed_for_3s() 45 | { 46 | return gpio_pin_get(btn_dev, BTN_PIN) == 1 && (k_uptime_get() - time_pressed) > 3000; 47 | } 48 | -------------------------------------------------------------------------------- /app/src/button.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) The Libre Solar Project Contributors 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #ifndef BUTTON_H_ 8 | #define BUTTON_H_ 9 | 10 | #include 11 | 12 | #ifdef __cplusplus 13 | extern "C" { 14 | #endif 15 | 16 | /** 17 | * @file 18 | * @brief Functions to handle on/off switch 19 | */ 20 | 21 | /** 22 | * Initialize button and configure interrupts 23 | */ 24 | void button_init(); 25 | 26 | /** 27 | * Check if button was pressed for 3 seconds 28 | * 29 | * \return true if pressed for at least 3 seconds 30 | */ 31 | bool button_pressed_for_3s(); 32 | 33 | #ifdef __cplusplus 34 | } 35 | #endif 36 | 37 | #endif /* BUTTON_H_ */ -------------------------------------------------------------------------------- /app/src/data_objects.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) The Libre Solar Project Contributors 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #ifndef DATA_OBJECTS_H_ 8 | #define DATA_OBJECTS_H_ 9 | 10 | /** 11 | * @file 12 | * @brief Handling of ThingSet data nodes 13 | */ 14 | 15 | #include 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | /* 22 | * Categories / first layer node IDs 23 | */ 24 | 25 | /* Device information (e.g. manufacturer, etc.) */ 26 | #define APP_ID_DEVICE 0x04 27 | #define APP_ID_DEVICE_MANUFACTURER 0x40 28 | #define APP_ID_DEVICE_TYPE 0x41 29 | #define APP_ID_DEVICE_HW_VER 0x42 30 | #define APP_ID_DEVICE_FW_VER 0x43 31 | #define APP_ID_DEVICE_SHUTDOWN 0x4A 32 | #define APP_ID_DEVICE_RESET 0x4B 33 | #define APP_ID_DEVICE_PRINT_REG 0x4C 34 | #define APP_ID_DEVICE_PRINT_REG_ADDR 0x4D 35 | #define APP_ID_DEVICE_PRINT_REGS 0x4E 36 | 37 | /* Configurable data (settings) */ 38 | #define APP_ID_CONF 0x05 39 | #define APP_ID_CONF_NOMINAL_CAPACITY 0x50 40 | #define APP_ID_CONF_SHORT_CIRCUIT_CURRENT 0x51 41 | #define APP_ID_CONF_SHORT_CIRCUIT_DELAY 0x52 42 | #define APP_ID_CONF_DIS_OVERCURRENT 0x53 43 | #define APP_ID_CONF_DIS_OVERCURRENT_DELAY 0x54 44 | #define APP_ID_CONF_CHG_OVERCURRENT 0x55 45 | #define APP_ID_CONF_CHG_OVERCURRENT_DELAY 0x56 46 | #define APP_ID_CONF_DIS_MAX_TEMP 0x58 47 | #define APP_ID_CONF_DIS_MIN_TEMP 0x59 48 | #define APP_ID_CONF_CHG_MAX_TEMP 0x5A 49 | #define APP_ID_CONF_CHG_MIN_TEMP 0x5B 50 | #define APP_ID_CONF_TEMP_HYST 0x5C 51 | #define APP_ID_CONF_CELL_OVERVOLTAGE 0x60 52 | #define APP_ID_CONF_CELL_OVERVOLTAGE_RESET 0x61 53 | #define APP_ID_CONF_CELL_OVERVOLTAGE_DELAY 0x62 54 | #define APP_ID_CONF_CELL_UNDERVOLTAGE 0x63 55 | #define APP_ID_CONF_CELL_UNDERVOLTAGE_RESET 0x64 56 | #define APP_ID_CONF_CELL_UNDERVOLTAGE_DELAY 0x65 57 | #define APP_ID_CONF_BAL_TARGET_DIFF 0x68 58 | #define APP_ID_CONF_BAL_MIN_VOLTAGE 0x69 59 | #define APP_ID_CONF_BAL_IDLE_DELAY 0x6A 60 | #define APP_ID_CONF_BAL_IDLE_CURRENT 0x6B 61 | #define APP_ID_CONF_PRESET_NMC 0xA0 62 | #define APP_ID_CONF_PRESET_NMC_CAPACITY 0xA1 63 | #define APP_ID_CONF_PRESET_LFP 0xA2 64 | #define APP_ID_CONF_PRESET_LFP_CAPACITY 0xA3 65 | #define APP_ID_CONF_PRESET_LTO 0xA4 66 | #define APP_ID_CONF_PRESET_LTO_CAPACITY 0xA5 67 | #define APP_ID_CONF_OCV_POINTS 0xB0 68 | #define APP_ID_CONF_SOC_POINTS 0xB1 69 | 70 | /* Measurement data */ 71 | #define APP_ID_MEAS 0x07 72 | #define APP_ID_MEAS_PACK_VOLTAGE 0x71 73 | #define APP_ID_MEAS_STACK_VOLTAGE 0x72 74 | #define APP_ID_MEAS_PACK_CURRENT 0x73 75 | #define APP_ID_MEAS_CELL_TEMPS 0x74 76 | #define APP_ID_MEAS_IC_TEMP 0x75 77 | #define APP_ID_MEAS_MCU_TEMP 0x76 78 | #define APP_ID_MEAS_MOSFET_TEMP 0x77 79 | #define APP_ID_MEAS_SOC 0x7C 80 | #define APP_ID_MEAS_ERROR_FLAGS 0x7E 81 | #define APP_ID_MEAS_BMS_STATE 0x7F 82 | #define APP_ID_MEAS_CELL_VOLTAGES 0x80 83 | #define APP_ID_MEAS_CELL_AVG_VOLTAGE 0x81 84 | #define APP_ID_MEAS_CELL_MIN_VOLTAGE 0x82 85 | #define APP_ID_MEAS_CELL_MAX_VOLTAGE 0x83 86 | #define APP_ID_MEAS_BALANCING_STATUS 0x84 87 | 88 | /* Input data (e.g. set-points) */ 89 | #define APP_ID_INPUT 0x09 90 | #define APP_ID_INPUT_CHG_ENABLE 0x90 91 | #define APP_ID_INPUT_DIS_ENABLE 0x91 92 | 93 | /** 94 | * Callback function to be called when conf values were changed 95 | */ 96 | void data_objects_update_conf(enum thingset_callback_reason reason); 97 | 98 | /** 99 | * Callback function to apply preset parameters for NMC type via ThingSet 100 | */ 101 | int32_t bat_preset_nmc(); 102 | 103 | /** 104 | * Callback function to apply preset parameters for LFP type via ThingSet 105 | */ 106 | int32_t bat_preset_lfp(); 107 | 108 | /** 109 | * Callback function to apply preset parameters for LTO type via ThingSet 110 | */ 111 | int32_t bat_preset_lto(); 112 | 113 | /** 114 | * Callback to read and print common BMS registers via ThingSet 115 | */ 116 | void print_registers(); 117 | 118 | /** 119 | * Callback to reset device (obviously...) 120 | */ 121 | void reset_device(); 122 | 123 | /** 124 | * Callback to invoke bms_shutdown via ThingSet 125 | */ 126 | void shutdown(); 127 | 128 | #endif /* DATA_OBJECTS_H_ */ 129 | -------------------------------------------------------------------------------- /app/src/leds.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) The Libre Solar Project Contributors 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #include "leds.h" 8 | 9 | #include 10 | 11 | #define SLEEP_TIME K_MSEC(100) 12 | 13 | extern struct bms_context bms; 14 | 15 | #include 16 | #include 17 | 18 | #define LED_DIS_NODE DT_ALIAS(led_red) 19 | #define LED_CHG_NODE DT_ALIAS(led_green) 20 | 21 | static const struct gpio_dt_spec led_dis = GPIO_DT_SPEC_GET(LED_DIS_NODE, gpios); 22 | static const struct gpio_dt_spec led_chg = GPIO_DT_SPEC_GET(LED_CHG_NODE, gpios); 23 | 24 | void leds_chg_set(bool on) 25 | { 26 | if (led_chg.port) { 27 | gpio_pin_set_dt(&led_chg, on); 28 | } 29 | } 30 | 31 | void leds_dis_set(bool on) 32 | { 33 | if (led_dis.port) { 34 | gpio_pin_set_dt(&led_dis, on); 35 | } 36 | } 37 | 38 | void leds_update_thread() 39 | { 40 | if (!device_is_ready(led_chg.port) || !device_is_ready(led_dis.port)) { 41 | return; 42 | } 43 | 44 | gpio_pin_configure_dt(&led_dis, GPIO_OUTPUT); 45 | 46 | gpio_pin_configure_dt(&led_chg, GPIO_OUTPUT); 47 | 48 | while (1) { 49 | leds_update(); 50 | k_sleep(SLEEP_TIME); 51 | } 52 | } 53 | 54 | void leds_update() 55 | { 56 | static uint32_t count = 0; 57 | 58 | // Charging LED control 59 | if (bms.state == BMS_STATE_NORMAL || bms.state == BMS_STATE_CHG) { 60 | if (bms.ic_data.current > bms.ic_conf.bal_idle_current && ((count / 2) % 10) == 0) { 61 | // not in idle: ____ ____ ____ 62 | leds_chg_set(0); 63 | } 64 | else { 65 | // idle: ______________ 66 | leds_chg_set(1); 67 | } 68 | } 69 | else if (bms.state == BMS_STATE_SHUTDOWN) { 70 | // completely off 71 | leds_chg_set(0); 72 | } 73 | else { 74 | if (bms_chg_error(bms.ic_data.error_flags)) { 75 | // quick flash 76 | leds_chg_set(count % 2); 77 | } 78 | else if (((count / 2) % 10) == 0) { 79 | // off without error: _ _ _ 80 | leds_chg_set(1); 81 | } 82 | else { 83 | leds_chg_set(0); 84 | } 85 | } 86 | 87 | // Discharging LED control 88 | if (bms.state == BMS_STATE_NORMAL || bms.state == BMS_STATE_DIS) { 89 | if (bms.ic_data.current < -bms.ic_conf.bal_idle_current && ((count / 2) % 10) == 0) { 90 | // not in idle: ____ ____ ____ 91 | leds_dis_set(0); 92 | } 93 | else { 94 | // idle: ______________ 95 | leds_dis_set(1); 96 | } 97 | } 98 | else if (bms.state == BMS_STATE_SHUTDOWN) { 99 | // completely off 100 | leds_dis_set(0); 101 | } 102 | else { 103 | if (bms_dis_error(bms.ic_data.error_flags)) { 104 | // quick flash 105 | leds_dis_set(count % 2); 106 | } 107 | else if (((count / 2) % 10) == 0) { 108 | // off without error: _ _ _ 109 | leds_dis_set(1); 110 | } 111 | else { 112 | leds_dis_set(0); 113 | } 114 | } 115 | 116 | count++; 117 | } 118 | 119 | K_THREAD_DEFINE(leds, 256, leds_update_thread, NULL, NULL, NULL, 4, 0, 0); 120 | -------------------------------------------------------------------------------- /app/src/leds.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) The Libre Solar Project Contributors 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #ifndef LEDS_H_ 8 | #define LEDS_H_ 9 | 10 | #include 11 | 12 | /** 13 | * LED control depending on BMS state, should be called every 100 ms 14 | * 15 | * Red LED 16 | * - Discharging finished (empty) (off) 17 | * - Discharging allowed (current < idle) _______________ 18 | * - Discharging active (current >= idle) ____ ____ ____ 19 | * - Discharging error (UV/OT/UT/OC/SC) _ _ _ _ _ _ _ _ 20 | * 21 | * Green LED 22 | * - Charging finished (full) (off) 23 | * - Charging allowed (current < idle) _______________ 24 | * - Charging active (current >= idle) ____ ____ ____ 25 | * - Charging error (OV/OT/UT/OC) _ _ _ _ _ _ _ _ 26 | * 27 | */ 28 | void leds_update(); 29 | 30 | /** 31 | * Thread that calls leds_update() in regular interval 32 | */ 33 | void leds_update_thread(); 34 | 35 | /** 36 | * Set green charging LED on or off 37 | */ 38 | void leds_chg_set(bool on); 39 | 40 | /** 41 | * Set red discharging LED on or off 42 | */ 43 | void leds_dis_set(bool on); 44 | 45 | #endif // LEDS_H_ 46 | -------------------------------------------------------------------------------- /app/src/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) The Libre Solar Project Contributors 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | #include "button.h" 13 | #include "data_objects.h" 14 | #include "helper.h" 15 | #include "leds.h" 16 | #include "thingset.h" 17 | #include 18 | 19 | LOG_MODULE_REGISTER(bms_main, CONFIG_LOG_DEFAULT_LEVEL); 20 | 21 | struct bms_context bms = { 22 | .ic_dev = DEVICE_DT_GET(DT_ALIAS(bms_ic)), 23 | }; 24 | 25 | int main(void) 26 | { 27 | int err; 28 | 29 | LOG_INF("Hardware: Libre Solar %s (%s)", DT_PROP(DT_PATH(pcb), type), 30 | DT_PROP(DT_PATH(pcb), version_str)); 31 | LOG_INF("Firmware: %s", FIRMWARE_VERSION_ID); 32 | 33 | if (!device_is_ready(bms.ic_dev)) { 34 | LOG_ERR("BMS IC not ready"); 35 | return -ENODEV; 36 | } 37 | 38 | bms_ic_assign_data(bms.ic_dev, &bms.ic_data); 39 | 40 | err = bms_ic_set_mode(bms.ic_dev, BMS_IC_MODE_ACTIVE); 41 | if (err != 0) { 42 | LOG_ERR("Failed to activate BMS IC: %d", err); 43 | } 44 | 45 | err = bms_ic_configure(bms.ic_dev, &bms.ic_conf, BMS_IC_CONF_ALL); 46 | if (err < 0) { 47 | LOG_ERR("Failed to configure BMS IC: %d", err); 48 | } 49 | 50 | err = bms_ic_read_data(bms.ic_dev, BMS_IC_DATA_CELL_VOLTAGES); 51 | if (err != 0) { 52 | LOG_ERR("Failed to read data from BMS IC: %d", err); 53 | } 54 | 55 | bms_soc_reset(&bms, -1); 56 | 57 | button_init(); 58 | 59 | int64_t t_start = k_uptime_get(); 60 | while (true) { 61 | err = bms_ic_read_data(bms.ic_dev, BMS_IC_DATA_ALL); 62 | if (err != 0) { 63 | LOG_ERR("Failed to read data from BMS IC: %d", err); 64 | } 65 | 66 | bms_soc_update(&bms); 67 | 68 | bms_state_machine(&bms); 69 | 70 | if (button_pressed_for_3s()) { 71 | LOG_WRN("Button pressed for 3s: shutdown..."); 72 | bms_shutdown(&bms); 73 | /* 74 | * Wait another 10s for the user to release the button again before actually turning off 75 | * the BMS IC (otherwise it will immediately restart). 76 | */ 77 | k_sleep(K_MSEC(10000)); 78 | bms_ic_set_mode(bms.ic_dev, BMS_IC_MODE_OFF); 79 | } 80 | 81 | t_start += CONFIG_BMS_IC_POLLING_INTERVAL_MS; 82 | k_sleep(K_TIMEOUT_ABS_MS(t_start)); 83 | } 84 | 85 | return 0; 86 | } 87 | 88 | static int init_config(void) 89 | { 90 | bms_init_config(&bms, (enum bms_cell_type)CONFIG_CELL_TYPE, CONFIG_BAT_CAPACITY_AH); 91 | 92 | return 0; 93 | } 94 | 95 | /* 96 | * The default configuration must be initialized before the ThingSet storage backend reads data 97 | * from EEPROM or flash (with THINGSET_INIT_PRIORITY_STORAGE = 40). 98 | */ 99 | SYS_INIT(init_config, APPLICATION, 0); 100 | -------------------------------------------------------------------------------- /app/src/oled.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) The Libre Solar Project Contributors 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #include "oled_font.h" 8 | 9 | #include 10 | 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | LOG_MODULE_REGISTER(oled, CONFIG_LOG_DEFAULT_LEVEL); 19 | 20 | extern struct bms_context bms; 21 | 22 | const struct device *oled_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_display)); 23 | 24 | static bool blink_on = false; 25 | 26 | void oled_overview_screen() 27 | { 28 | static char buf[30]; 29 | unsigned int len; 30 | 31 | cfb_framebuffer_clear(oled_dev, false); 32 | 33 | cfb_print(oled_dev, "Libre Solar", 0, 0); 34 | cfb_print(oled_dev, DT_PROP(DT_PATH(pcb), type), 0, 12); 35 | 36 | len = snprintf(buf, sizeof(buf), "%.2fV", (double)bms.ic_data.total_voltage); 37 | cfb_print(oled_dev, buf, 0, 28); 38 | 39 | len = snprintf(buf, sizeof(buf), "%.1fA", (double)bms.ic_data.current); 40 | cfb_print(oled_dev, buf, 64, 28); 41 | 42 | len = snprintf(buf, sizeof(buf), "T:%.1f", (double)bms.ic_data.cell_temp_avg); 43 | cfb_print(oled_dev, buf, 0, 40); 44 | 45 | len = snprintf(buf, sizeof(buf), "SOC:%.0f", (double)bms.soc); 46 | cfb_print(oled_dev, buf, 64, 40); 47 | 48 | len = snprintf(buf, sizeof(buf), "Err:0x%X", bms.ic_data.error_flags); 49 | cfb_print(oled_dev, buf, 0, 52); 50 | 51 | cfb_framebuffer_finalize(oled_dev); 52 | } 53 | 54 | void oled_cell_voltages_screen(int offset) 55 | { 56 | static char buf[30]; 57 | unsigned int len; 58 | 59 | cfb_framebuffer_clear(oled_dev, false); 60 | 61 | cfb_print(oled_dev, "Cell Voltages", 0, 0); 62 | 63 | for (int i = offset; i < CONFIG_BMS_IC_MAX_CELLS; i++) { 64 | if (blink_on || !(bms.ic_data.balancing_status & (1 << i))) { 65 | len = 66 | snprintf(buf, sizeof(buf), "%d:%.2f", i + 1, (double)bms.ic_data.cell_voltages[i]); 67 | cfb_print(oled_dev, buf, (i % 2 == 0) ? 0 : 64, 16 + (i / 2) * 12); 68 | } 69 | } 70 | 71 | cfb_framebuffer_finalize(oled_dev); 72 | } 73 | 74 | void oled_thread() 75 | { 76 | uint16_t rows; 77 | uint8_t ppt; 78 | 79 | if (!device_is_ready(oled_dev)) { 80 | LOG_ERR("OLED device %s not ready", oled_dev->name); 81 | return; 82 | } 83 | 84 | if (display_set_pixel_format(oled_dev, PIXEL_FORMAT_MONO10) != 0) { 85 | LOG_ERR("Failed to set required pixel format"); 86 | return; 87 | } 88 | 89 | LOG_DBG("Initialized OLED %s", oled_dev->name); 90 | 91 | if (cfb_framebuffer_init(oled_dev)) { 92 | LOG_ERR("Framebuffer initialization failed!"); 93 | return; 94 | } 95 | 96 | cfb_framebuffer_clear(oled_dev, true); 97 | cfb_framebuffer_invert(oled_dev); 98 | 99 | display_blanking_off(oled_dev); 100 | 101 | rows = cfb_get_display_parameter(oled_dev, CFB_DISPLAY_ROWS); 102 | ppt = cfb_get_display_parameter(oled_dev, CFB_DISPLAY_PPT); 103 | 104 | /* set first and only font */ 105 | cfb_framebuffer_set_font(oled_dev, 0); 106 | 107 | LOG_DBG("x_res %d, y_res %d, ppt %d, rows %d, cols %d", 108 | cfb_get_display_parameter(oled_dev, CFB_DISPLAY_WIDTH), 109 | cfb_get_display_parameter(oled_dev, CFB_DISPLAY_HEIGH), ppt, rows, 110 | cfb_get_display_parameter(oled_dev, CFB_DISPLAY_COLS)); 111 | 112 | while (true) { 113 | blink_on = !blink_on; 114 | 115 | oled_overview_screen(); 116 | k_sleep(K_SECONDS(3)); 117 | 118 | oled_cell_voltages_screen(0); 119 | k_sleep(K_SECONDS(3)); 120 | 121 | if (CONFIG_BMS_IC_MAX_CELLS > 8) { 122 | oled_cell_voltages_screen(8); 123 | k_sleep(K_SECONDS(3)); 124 | } 125 | } 126 | } 127 | 128 | K_THREAD_DEFINE(oled_thread_id, 1024, oled_thread, NULL, NULL, NULL, 0, 0, 1000); 129 | -------------------------------------------------------------------------------- /app/sysbuild.conf: -------------------------------------------------------------------------------- 1 | # Sysbuild configuration file 2 | 3 | # Use MCUboot as bootloader 4 | SB_CONFIG_BOOTLOADER_MCUBOOT=y 5 | -------------------------------------------------------------------------------- /app/sysbuild/app.conf: -------------------------------------------------------------------------------- 1 | # Extra configuration for app when using sysbild 2 | 3 | # DFU over ThingSet (e.g. CAN) 4 | CONFIG_THINGSET_DFU=y 5 | 6 | # Flash driver to store firmware image 7 | CONFIG_FLASH=y 8 | CONFIG_FLASH_MAP=y 9 | CONFIG_FLASH_PAGE_LAYOUT=y 10 | CONFIG_STREAM_FLASH=y 11 | CONFIG_IMG_MANAGER=y 12 | CONFIG_IMG_ERASE_PROGRESSIVELY=y 13 | -------------------------------------------------------------------------------- /app/sysbuild/mcuboot/boards/bms_c1.conf: -------------------------------------------------------------------------------- 1 | # Copyright (c) The Libre Solar Project Contributors 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | CONFIG_BOOT_MAX_IMG_SECTORS=512 5 | CONFIG_BOOT_UPGRADE_ONLY=y 6 | CONFIG_BOOT_VALIDATE_SLOT0=n 7 | CONFIG_BOOT_SIGNATURE_TYPE_NONE=y 8 | CONFIG_BOOT_BANNER=n 9 | 10 | CONFIG_SERIAL=n 11 | CONFIG_UART_INTERRUPT_DRIVEN=n 12 | 13 | CONFIG_CONSOLE=n 14 | CONFIG_UART_CONSOLE=n 15 | 16 | CONFIG_MCUBOOT_LOG_LEVEL_OFF=y 17 | CONFIG_DEBUG=n 18 | 19 | CONFIG_XIP=n 20 | -------------------------------------------------------------------------------- /app/sysbuild/mcuboot/boards/bms_c1.overlay: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) The Libre Solar Project Contributors 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | / { 8 | chosen { 9 | /* 10 | * Workaround to speed up MCUboot 2nd stage bootloader. 11 | * See: https://github.com/LibreSolar/bms-firmware/pull/78 12 | */ 13 | zephyr,console = &uart0; 14 | }; 15 | }; 16 | -------------------------------------------------------------------------------- /app/sysbuild/mcuboot/prj.conf: -------------------------------------------------------------------------------- 1 | # this file is a copy of mcuboot/boot/zephyr/prj.conf 2 | 3 | CONFIG_PM=n 4 | 5 | CONFIG_MAIN_STACK_SIZE=10240 6 | CONFIG_MBEDTLS_CFG_FILE="mcuboot-mbedtls-cfg.h" 7 | 8 | CONFIG_BOOT_SWAP_SAVE_ENCTLV=n 9 | CONFIG_BOOT_ENCRYPT_IMAGE=n 10 | 11 | CONFIG_BOOT_UPGRADE_ONLY=n 12 | CONFIG_BOOT_BOOTSTRAP=n 13 | 14 | ### mbedTLS has its own heap 15 | # CONFIG_HEAP_MEM_POOL_SIZE is not set 16 | 17 | ### We never want Zephyr's copy of tinycrypt. If tinycrypt is needed, 18 | ### MCUboot has its own copy in tree. 19 | # CONFIG_TINYCRYPT is not set 20 | # CONFIG_TINYCRYPT_ECC_DSA is not set 21 | # CONFIG_TINYCRYPT_SHA256 is not set 22 | 23 | CONFIG_FLASH=y 24 | 25 | ### Various Zephyr boards enable features that we don't want. 26 | # CONFIG_BT is not set 27 | # CONFIG_BT_CTLR is not set 28 | # CONFIG_I2C is not set 29 | 30 | CONFIG_LOG=y 31 | CONFIG_LOG_MODE_MINIMAL=y # former CONFIG_MODE_MINIMAL 32 | ### Ensure Zephyr logging changes don't use more resources 33 | CONFIG_LOG_DEFAULT_LEVEL=0 34 | ### Use info log level by default 35 | CONFIG_MCUBOOT_LOG_LEVEL_INF=y 36 | ### Decrease footprint by ~4 KB in comparison to CBPRINTF_COMPLETE=y 37 | CONFIG_CBPRINTF_NANO=y 38 | ### Use the minimal C library to reduce flash usage 39 | CONFIG_MINIMAL_LIBC=y 40 | -------------------------------------------------------------------------------- /boards/libresolar/bms_15s80_sc/Kconfig.bms_15s80_sc: -------------------------------------------------------------------------------- 1 | # Copyright (c) The Libre Solar Project Contributors 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | config BOARD_BMS_15S80_SC 5 | select SOC_STM32F072XB 6 | -------------------------------------------------------------------------------- /boards/libresolar/bms_15s80_sc/Kconfig.defconfig: -------------------------------------------------------------------------------- 1 | # Copyright (c) The Libre Solar Project Contributors 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | if BMS_IC 5 | 6 | config BMS_IC_MAX_THERMISTORS 7 | default 3 8 | 9 | endif # BMS_IC 10 | -------------------------------------------------------------------------------- /boards/libresolar/bms_15s80_sc/bms_15s80_sc.dts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) The Libre Solar Project Contributors 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | /dts-v1/; 8 | #include 9 | #include 10 | 11 | / { 12 | model = "Libre Solar BMS 5S50 SC"; 13 | compatible = "st,stm32f072"; 14 | 15 | pcb { 16 | compatible = "bms"; 17 | 18 | type = "BMS 15S80 SC"; 19 | version-str = "v0.1"; 20 | version-num = <1>; 21 | 22 | /* 23 | * Additional information: 24 | * - GAIN_PACK_VOLTAGE (105.6/5.6) 25 | * - PIN_V_BAT PA_4 26 | * - PIN_V_EXT PA_5 27 | */ 28 | }; 29 | 30 | chosen { 31 | zephyr,console = &usart3; 32 | zephyr,shell-uart = &usart3; 33 | zephyr,sram = &sram0; 34 | zephyr,flash = &flash0; 35 | thingset,can = &can1; 36 | thingset,eeprom = &eeprom0; 37 | thingset,serial = &uext_uart; 38 | }; 39 | 40 | leds { 41 | compatible = "gpio-leds"; 42 | led1: led_0 { 43 | gpios = <&gpioa 9 GPIO_ACTIVE_HIGH>; 44 | }; 45 | led2: led_1 { 46 | gpios = <&gpioa 10 GPIO_ACTIVE_HIGH>; 47 | }; 48 | }; 49 | 50 | gpio_keys { 51 | compatible = "gpio-keys"; 52 | power_button: button { 53 | gpios = <&gpioa 8 GPIO_ACTIVE_HIGH>; 54 | }; 55 | }; 56 | 57 | transceiver0: can-phy0 { 58 | compatible = "nxp,tja1040", "can-transceiver-gpio"; 59 | max-bitrate = <1000000>; 60 | standby-gpios = <&gpioa 15 GPIO_ACTIVE_HIGH>; 61 | #phy-cells = <0>; 62 | }; 63 | 64 | aliases { 65 | led-red = &led1; 66 | led-green = &led2; 67 | sw-pwr = &power_button; 68 | bms-ic = &bq769x0; 69 | }; 70 | 71 | soc { 72 | usart3: serial@40004800 { 73 | compatible = "st,stm32-usart", "st,stm32-uart"; 74 | reg = <0x40004800 0x400>; 75 | clocks = <&rcc STM32_CLOCK_BUS_APB1 0x00040000>; 76 | interrupts = <29 0>; 77 | status = "disabled"; 78 | }; 79 | usart4: serial@40004c00 { 80 | compatible = "st,stm32-usart", "st,stm32-uart"; 81 | reg = <0x40004c00 0x400>; 82 | clocks = <&rcc STM32_CLOCK_BUS_APB1 0x00080000>; 83 | interrupts = <29 0>; 84 | status = "disabled"; 85 | }; 86 | }; 87 | }; 88 | 89 | &clk_hse { 90 | clock-frequency = ; 91 | status = "okay"; 92 | }; 93 | 94 | &pll { 95 | clocks = <&clk_hse>; 96 | prediv = <1>; 97 | mul = <6>; 98 | status = "okay"; 99 | }; 100 | 101 | &rcc { 102 | clocks = <&pll>; 103 | clock-frequency = ; 104 | ahb-prescaler = <1>; 105 | apb1-prescaler = <1>; 106 | }; 107 | 108 | uext_uart: &usart2 { 109 | pinctrl-0 = <&usart2_tx_pa2 &usart2_rx_pa3>; 110 | pinctrl-names = "default"; 111 | current-speed = <115200>; 112 | status = "okay"; 113 | }; 114 | 115 | &usart2_rx_pa3 { 116 | bias-pull-up; // avoid junk characters if pin is left floating 117 | }; 118 | 119 | &usart3 { 120 | pinctrl-0 = <&usart3_tx_pb10 &usart3_rx_pb11>; 121 | pinctrl-names = "default"; 122 | current-speed = <115200>; 123 | status = "okay"; 124 | }; 125 | 126 | &usart3_rx_pb11 { 127 | bias-pull-up; // avoid junk characters if pin is left floating 128 | }; 129 | 130 | uext_i2c: &i2c1 { 131 | pinctrl-0 = <&i2c1_scl_pb6 &i2c1_sda_pb7>; 132 | pinctrl-names = "default"; 133 | status = "okay"; 134 | }; 135 | 136 | &i2c2 { 137 | pinctrl-0 = <&i2c2_scl_pb13 &i2c2_sda_pb14>; 138 | pinctrl-names = "default"; 139 | clock-frequency = ; 140 | status = "okay"; 141 | 142 | bq769x0: bq769x0@8 { 143 | compatible = "ti,bq769x0"; 144 | reg = <0x08>; 145 | alert-gpios = <&gpiob 12 GPIO_ACTIVE_HIGH>; 146 | bus-pchg-gpios = <&gpiob 2 GPIO_ACTIVE_HIGH>; 147 | board-max-current = <80>; 148 | shunt-resistor-uohm = <1500>; 149 | used-cell-channels = <0x7FFF>; 150 | }; 151 | 152 | eeprom0: eeprom@51 { 153 | // M24256-BW 154 | compatible = "atmel,at24"; 155 | reg = <0x51>; // 1 0 1 0 A2 A1 A0 = 1010001 156 | size = <32768>; 157 | pagesize = <64>; 158 | address-width = <16>; 159 | timeout = <7>; 160 | }; 161 | }; 162 | 163 | &spi1 { 164 | pinctrl-0 = <&spi1_sck_pb3 &spi1_miso_pb4 &spi1_mosi_pb5>; 165 | pinctrl-names = "default"; 166 | cs-gpios = <&gpioa 1 GPIO_ACTIVE_LOW>; 167 | status = "okay"; 168 | }; 169 | 170 | &iwdg { 171 | status = "okay"; 172 | }; 173 | 174 | &can1 { 175 | pinctrl-0 = <&can_rx_pb8 &can_tx_pb9>; 176 | pinctrl-names = "default"; 177 | bitrate = <500000>; 178 | phys = <&transceiver0>; 179 | status = "okay"; 180 | }; 181 | -------------------------------------------------------------------------------- /boards/libresolar/bms_15s80_sc/bms_15s80_sc.yaml: -------------------------------------------------------------------------------- 1 | identifier: bms_15s80_sc 2 | name: BMS-15S80-SC 3 | type: mcu 4 | arch: arm 5 | toolchain: 6 | - zephyr 7 | - gnuarmemb 8 | - xtools 9 | ram: 16 10 | flash: 128 11 | supported: 12 | - gpio 13 | - i2c 14 | - spi 15 | - watchdog 16 | - adc 17 | -------------------------------------------------------------------------------- /boards/libresolar/bms_15s80_sc/bms_15s80_sc_defconfig: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | 3 | CONFIG_CLOCK_CONTROL=y 4 | 5 | CONFIG_PINCTRL=y 6 | 7 | CONFIG_GPIO=y 8 | 9 | CONFIG_SERIAL=y 10 | CONFIG_UART_INTERRUPT_DRIVEN=y 11 | 12 | CONFIG_CONSOLE=y 13 | CONFIG_UART_CONSOLE=y 14 | -------------------------------------------------------------------------------- /boards/libresolar/bms_15s80_sc/board.cmake: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | 3 | board_runner_args(jlink "--device=STM32F072CB" "--speed=4000" "--reset-after-load") 4 | 5 | include(${ZEPHYR_BASE}/boards/common/openocd.board.cmake) 6 | include(${ZEPHYR_BASE}/boards/common/jlink.board.cmake) 7 | -------------------------------------------------------------------------------- /boards/libresolar/bms_15s80_sc/board.yml: -------------------------------------------------------------------------------- 1 | board: 2 | name: bms_15s80_sc 3 | vendor: libresolar 4 | socs: 5 | - name: stm32f072xb 6 | -------------------------------------------------------------------------------- /boards/libresolar/bms_15s80_sc/support/openocd.cfg: -------------------------------------------------------------------------------- 1 | source [find board/st_nucleo_f0.cfg] 2 | 3 | $_TARGETNAME configure -event gdb-attach { 4 | echo "Debugger attaching: halting execution" 5 | reset halt 6 | gdb_breakpoint_override hard 7 | } 8 | 9 | $_TARGETNAME configure -event gdb-detach { 10 | echo "Debugger detaching: resuming execution" 11 | resume 12 | } 13 | -------------------------------------------------------------------------------- /boards/libresolar/bms_16s100_sc/Kconfig.bms_16s100_sc: -------------------------------------------------------------------------------- 1 | # Copyright (c) The Libre Solar Project Contributors 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | config BOARD_BMS_16S100_SC 5 | select SOC_STM32G0B1XX 6 | -------------------------------------------------------------------------------- /boards/libresolar/bms_16s100_sc/Kconfig.defconfig: -------------------------------------------------------------------------------- 1 | # Copyright (c) The Libre Solar Project Contributors 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | if BMS_IC 5 | 6 | config BMS_IC_MAX_THERMISTORS 7 | default 2 8 | 9 | endif # BMS_IC 10 | -------------------------------------------------------------------------------- /boards/libresolar/bms_16s100_sc/bms_16s100_sc.dts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) The Libre Solar Project Contributors 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | /dts-v1/; 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | / { 14 | model = "Libre Solar BMS 16S100 IC"; 15 | compatible = "st,stm32g0b1"; 16 | 17 | pcb { 18 | compatible = "bms"; 19 | 20 | type = "BMS 16S100 SC"; 21 | version-str = "v0.2"; 22 | version-num = <2>; 23 | }; 24 | 25 | chosen { 26 | zephyr,console = &usart1; 27 | zephyr,shell-uart = &usart1; 28 | zephyr,sram = &sram0; 29 | zephyr,flash = &flash0; 30 | zephyr,code-partition = &slot0_partition; 31 | thingset,can = &fdcan1; 32 | thingset,serial = &uext_uart; 33 | }; 34 | 35 | leds { 36 | compatible = "gpio-leds"; 37 | led1: led_0 { 38 | //gpios = <&gpioa 5 GPIO_ACTIVE_HIGH>; // Nucleo board 39 | gpios = <&gpioc 13 GPIO_ACTIVE_HIGH>; // actual BMS board 40 | }; 41 | led2: led_1 { 42 | gpios = <&gpioc 14 GPIO_ACTIVE_HIGH>; 43 | }; 44 | }; 45 | 46 | gpio_keys { 47 | compatible = "gpio-keys"; 48 | power_button: button { 49 | gpios = <&gpioa 1 GPIO_ACTIVE_HIGH>; 50 | }; 51 | }; 52 | 53 | transceiver0: can-phy0 { 54 | compatible = "nxp,tja1042", "can-transceiver-gpio"; 55 | max-bitrate = <1000000>; 56 | standby-gpios = <&gpiod 2 GPIO_ACTIVE_HIGH>; 57 | #phy-cells = <0>; 58 | }; 59 | 60 | aliases { 61 | led-red = &led1; 62 | led-green = &led2; 63 | sw-pwr = &power_button; 64 | bms-ic = &bq76952; 65 | }; 66 | }; 67 | 68 | &clk_hse { 69 | clock-frequency = ; 70 | status = "okay"; 71 | }; 72 | 73 | &pll { 74 | div-m = <1>; 75 | mul-n = <16>; 76 | div-p = <2>; 77 | div-q = <2>; 78 | div-r = <2>; 79 | clocks = <&clk_hse>; 80 | status = "okay"; 81 | }; 82 | 83 | &rcc { 84 | clocks = <&pll>; 85 | clock-frequency = ; 86 | ahb-prescaler = <1>; 87 | apb1-prescaler = <1>; 88 | }; 89 | 90 | zephyr_udc0: &usb { 91 | pinctrl-0 = <&usb_dm_pa11 &usb_dp_pa12>; 92 | pinctrl-names = "default"; 93 | status = "okay"; 94 | }; 95 | 96 | &usart1 { 97 | pinctrl-0 = <&usart1_tx_pa9 &usart1_rx_pa10>; 98 | pinctrl-names = "default"; 99 | current-speed = <115200>; 100 | status = "okay"; 101 | }; 102 | 103 | &usart1_rx_pa10 { 104 | bias-pull-up; // avoid junk characters if pin is left floating 105 | }; 106 | 107 | uext_uart: &usart2 { 108 | pinctrl-0 = <&usart2_tx_pa2 &usart2_rx_pa3>; 109 | pinctrl-names = "default"; 110 | current-speed = <115200>; 111 | status = "okay"; 112 | }; 113 | 114 | &usart2_rx_pa3 { 115 | bias-pull-up; // avoid junk characters if pin is left floating 116 | }; 117 | 118 | &iwdg { 119 | status = "okay"; 120 | }; 121 | 122 | uext_i2c: &i2c1 { 123 | pinctrl-0 = <&i2c1_scl_pb6 &i2c1_sda_pb7>; 124 | pinctrl-names = "default"; 125 | clock-frequency = ; 126 | status = "okay"; 127 | }; 128 | 129 | &i2c2 { 130 | pinctrl-0 = <&i2c2_scl_pb13 &i2c2_sda_pb14>; 131 | pinctrl-names = "default"; 132 | clock-frequency = ; 133 | status = "okay"; 134 | 135 | bq76952: bq769x2@8 { 136 | compatible = "ti,bq769x2-i2c"; 137 | reg = <0x08>; 138 | alert-gpios = <&gpiob 8 GPIO_ACTIVE_HIGH>; 139 | used-cell-channels = <0xFFFF>; 140 | /* all NTCs configured with 18k pull-up */ 141 | ts1-pin-config = <0x07>; 142 | ts3-pin-config = <0x07>; 143 | dchg-pin-config = <0x07>; 144 | cell-temp-pins = ; 145 | fet-temp-pin = ; 146 | board-max-current = <100>; 147 | shunt-resistor-uohm = <300>; 148 | status = "okay"; 149 | }; 150 | }; 151 | 152 | &spi1 { 153 | pinctrl-0 = <&spi1_nss_pb0 &spi1_sck_pb3 154 | &spi1_miso_pb4 &spi1_mosi_pb5>; 155 | pinctrl-names = "default"; 156 | status = "okay"; 157 | }; 158 | 159 | &spi2 { 160 | pinctrl-0 = <&spi2_nss_pb12 &spi2_sck_pb13 161 | &spi2_miso_pb14 &spi2_mosi_pb15>; 162 | pinctrl-names = "default"; 163 | status = "okay"; 164 | }; 165 | 166 | &flash0 { 167 | partitions { 168 | compatible = "fixed-partitions"; 169 | #address-cells = <1>; 170 | #size-cells = <1>; 171 | 172 | boot_partition: partition@0 { 173 | label = "mcuboot"; 174 | reg = <0x00000000 0x0000C000>; 175 | read-only; 176 | }; 177 | slot0_partition: partition@C000 { 178 | label = "image-0"; 179 | reg = <0x0000C000 0x00032000>; 180 | }; 181 | slot1_partition: partition@3E000 { 182 | label = "image-1"; 183 | reg = <0x0003E000 0x00032000>; 184 | }; 185 | /* final 64KiB reserved for app storage partition */ 186 | storage_partition: partition@70000 { 187 | label = "storage"; 188 | reg = <0x00070000 0x00010000>; 189 | }; 190 | }; 191 | }; 192 | 193 | &lptim1 { 194 | status = "okay"; 195 | }; 196 | 197 | &fdcan1 { 198 | clocks = <&rcc STM32_CLOCK_BUS_APB1 0x00001000>, 199 | <&rcc STM32_SRC_PLL_Q FDCAN_SEL(1)>; 200 | pinctrl-0 = <&fdcan1_rx_pd0 &fdcan1_tx_pd1>; 201 | pinctrl-names = "default"; 202 | bitrate = <500000>; 203 | bitrate-data = <500000>; 204 | sample-point = <875>; 205 | sample-point-data = <875>; 206 | phys = <&transceiver0>; 207 | status = "okay"; 208 | }; 209 | -------------------------------------------------------------------------------- /boards/libresolar/bms_16s100_sc/bms_16s100_sc.yaml: -------------------------------------------------------------------------------- 1 | identifier: bms_16s100_sc 2 | name: BMS-16S100-SC 3 | type: mcu 4 | arch: arm 5 | toolchain: 6 | - zephyr 7 | - gnuarmemb 8 | - xtools 9 | ram: 144 10 | flash: 512 11 | supported: 12 | - uart 13 | - gpio 14 | - i2c 15 | - spi 16 | - nvs 17 | - dma 18 | - usb_device 19 | - lptim 20 | -------------------------------------------------------------------------------- /boards/libresolar/bms_16s100_sc/bms_16s100_sc_defconfig: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | 3 | CONFIG_ARM_MPU=y 4 | 5 | CONFIG_CLOCK_CONTROL=y 6 | 7 | CONFIG_PINCTRL=y 8 | 9 | CONFIG_GPIO=y 10 | 11 | CONFIG_SERIAL=y 12 | CONFIG_UART_INTERRUPT_DRIVEN=y 13 | 14 | CONFIG_CONSOLE=y 15 | CONFIG_UART_CONSOLE=y 16 | -------------------------------------------------------------------------------- /boards/libresolar/bms_16s100_sc/board.cmake: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | 3 | # Run before flashing: 4 | # pyocd pack --update 5 | # pyocd pack --install stm32g0 6 | 7 | # keil.stm32g0xx_dfp.1.3.0.pack introduced stm32g0b series, but the target does 8 | # not work with pyocd currently. 9 | board_runner_args(pyocd "--target=stm32g071rbtx") 10 | #board_runner_args(pyocd "--target=stm32g0b1cetx") 11 | 12 | board_runner_args(pyocd "--flash-opt=-O reset_type=hw") 13 | board_runner_args(pyocd "--flash-opt=-O connect_mode=under-reset") 14 | board_runner_args(jlink "--device=STM32G0B1CE" "--speed=4000" "--reset-after-load") 15 | board_runner_args(stm32cubeprogrammer "--port=swd" "--reset=hw") 16 | 17 | include(${ZEPHYR_BASE}/boards/common/stm32cubeprogrammer.board.cmake) 18 | include(${ZEPHYR_BASE}/boards/common/openocd.board.cmake) 19 | include(${ZEPHYR_BASE}/boards/common/pyocd.board.cmake) 20 | include(${ZEPHYR_BASE}/boards/common/jlink.board.cmake) 21 | -------------------------------------------------------------------------------- /boards/libresolar/bms_16s100_sc/board.yml: -------------------------------------------------------------------------------- 1 | board: 2 | name: bms_16s100_sc 3 | vendor: libresolar 4 | socs: 5 | - name: stm32g0b1xx 6 | -------------------------------------------------------------------------------- /boards/libresolar/bms_5s50_sc/Kconfig.bms_5s50_sc: -------------------------------------------------------------------------------- 1 | # Copyright (c) The Libre Solar Project Contributors 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | config BOARD_BMS_5S50_SC 5 | select SOC_STM32F072XB 6 | -------------------------------------------------------------------------------- /boards/libresolar/bms_5s50_sc/Kconfig.defconfig: -------------------------------------------------------------------------------- 1 | # Copyright (c) The Libre Solar Project Contributors 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | if BMS_IC 5 | 6 | config BMS_IC_MAX_CELLS 7 | default 5 8 | 9 | config BMS_IC_MAX_THERMISTORS 10 | default 1 11 | 12 | endif # BMS_IC 13 | -------------------------------------------------------------------------------- /boards/libresolar/bms_5s50_sc/bms_5s50_sc.dts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) The Libre Solar Project Contributors 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | /dts-v1/; 8 | #include 9 | #include 10 | 11 | / { 12 | model = "Libre Solar BMS 5S50 SC"; 13 | compatible = "st,stm32f072"; 14 | 15 | pcb { 16 | compatible = "bms"; 17 | 18 | type = "BMS 5S50 SC"; 19 | version-str = "v0.1"; 20 | version-num = <1>; 21 | 22 | /* 23 | * Additional information: 24 | * - GAIN_PACK_VOLTAGE (110/10) 25 | * - PIN_V_REF PA_0 26 | * - PIN_V_BAT PA_4 27 | * - PIN_V_EXT PA_5 28 | * - PIN_TEMP_1 PA_6 29 | * - PIN_TEMP_2 PA_7 30 | */ 31 | }; 32 | 33 | chosen { 34 | zephyr,console = &usart1; 35 | zephyr,shell-uart = &usart1; 36 | zephyr,sram = &sram0; 37 | zephyr,flash = &flash0; 38 | thingset,can = &can1; 39 | thingset,eeprom = &eeprom0; 40 | thingset,serial = &uext_uart; 41 | }; 42 | 43 | leds { 44 | compatible = "gpio-leds"; 45 | led1: led_0 { 46 | gpios = <&gpiob 14 GPIO_ACTIVE_HIGH>; 47 | }; 48 | led2: led_1 { 49 | gpios = <&gpiob 15 GPIO_ACTIVE_HIGH>; 50 | }; 51 | }; 52 | 53 | gpio_keys { 54 | compatible = "gpio-keys"; 55 | power_button: button { 56 | gpios = <&gpioa 8 GPIO_ACTIVE_HIGH>; 57 | }; 58 | }; 59 | 60 | transceiver0: can-phy0 { 61 | compatible = "nxp,tja1040", "can-transceiver-gpio"; 62 | max-bitrate = <1000000>; 63 | standby-gpios = <&gpioa 15 GPIO_ACTIVE_HIGH>; 64 | #phy-cells = <0>; 65 | }; 66 | 67 | aliases { 68 | led-red = &led1; 69 | led-green = &led2; 70 | sw-pwr = &power_button; 71 | bms-ic = &bq76920; 72 | }; 73 | }; 74 | 75 | &clk_hse { 76 | clock-frequency = ; 77 | status = "okay"; 78 | }; 79 | 80 | &pll { 81 | clocks = <&clk_hse>; 82 | prediv = <1>; 83 | mul = <6>; 84 | status = "okay"; 85 | }; 86 | 87 | &rcc { 88 | clocks = <&pll>; 89 | clock-frequency = ; 90 | ahb-prescaler = <1>; 91 | apb1-prescaler = <1>; 92 | }; 93 | 94 | &usart1 { 95 | pinctrl-0 = <&usart1_tx_pa9 &usart1_rx_pa10>; 96 | pinctrl-names = "default"; 97 | current-speed = <115200>; 98 | status = "okay"; 99 | }; 100 | 101 | &usart1_rx_pa10 { 102 | bias-pull-up; // avoid junk characters if pin is left floating 103 | }; 104 | 105 | uext_uart: &usart2 { 106 | pinctrl-0 = <&usart2_tx_pa2 &usart2_rx_pa3>; 107 | pinctrl-names = "default"; 108 | current-speed = <115200>; 109 | status = "okay"; 110 | }; 111 | 112 | &usart2_rx_pa3 { 113 | bias-pull-up; // avoid junk characters if pin is left floating 114 | }; 115 | 116 | uext_i2c: &i2c1 { 117 | pinctrl-0 = <&i2c1_scl_pb6 &i2c1_sda_pb7>; 118 | pinctrl-names = "default"; 119 | status = "okay"; 120 | }; 121 | 122 | &i2c2 { 123 | pinctrl-0 = <&i2c2_scl_pb10 &i2c2_sda_pb11>; 124 | pinctrl-names = "default"; 125 | clock-frequency = ; 126 | status = "okay"; 127 | 128 | bq76920: bq769x0@8 { 129 | compatible = "ti,bq769x0"; 130 | reg = <0x08>; 131 | alert-gpios = <&gpiob 12 GPIO_ACTIVE_HIGH>; 132 | bus-pchg-gpios = <&gpiob 2 GPIO_ACTIVE_HIGH>; 133 | board-max-current = <50>; 134 | shunt-resistor-uohm = <1000>; 135 | used-cell-channels = <0x001F>; 136 | }; 137 | 138 | eeprom0: eeprom@50 { 139 | // Microchip 24AA01 140 | compatible = "atmel,at24"; 141 | reg = <0x50>; 142 | size = <1024>; 143 | pagesize = <8>; 144 | address-width = <8>; 145 | /* 146 | * timeout of 5 ms as suggested in datasheet seems too optimistic 147 | * (several write errors occured during testing) 148 | */ 149 | timeout = <7>; 150 | }; 151 | }; 152 | 153 | &spi1 { 154 | pinctrl-0 = <&spi1_sck_pb3 &spi1_miso_pb4 &spi1_mosi_pb5>; 155 | pinctrl-names = "default"; 156 | cs-gpios = <&gpioa 1 GPIO_ACTIVE_LOW>; 157 | status = "okay"; 158 | }; 159 | 160 | &iwdg { 161 | status = "okay"; 162 | }; 163 | 164 | &can1 { 165 | pinctrl-0 = <&can_rx_pb8 &can_tx_pb9>; 166 | pinctrl-names = "default"; 167 | bitrate = <500000>; 168 | phys = <&transceiver0>; 169 | status = "okay"; 170 | }; 171 | -------------------------------------------------------------------------------- /boards/libresolar/bms_5s50_sc/bms_5s50_sc.yaml: -------------------------------------------------------------------------------- 1 | identifier: bms_5s50_sc 2 | name: BMS-5S50-SC 3 | type: mcu 4 | arch: arm 5 | toolchain: 6 | - zephyr 7 | - gnuarmemb 8 | - xtools 9 | ram: 16 10 | flash: 128 11 | supported: 12 | - gpio 13 | - i2c 14 | - spi 15 | - watchdog 16 | - adc 17 | -------------------------------------------------------------------------------- /boards/libresolar/bms_5s50_sc/bms_5s50_sc_defconfig: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | 3 | CONFIG_CLOCK_CONTROL=y 4 | 5 | CONFIG_PINCTRL=y 6 | 7 | CONFIG_GPIO=y 8 | 9 | CONFIG_SERIAL=y 10 | CONFIG_UART_INTERRUPT_DRIVEN=y 11 | 12 | CONFIG_CONSOLE=y 13 | CONFIG_UART_CONSOLE=y 14 | -------------------------------------------------------------------------------- /boards/libresolar/bms_5s50_sc/board.cmake: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | 3 | board_runner_args(jlink "--device=STM32F072CB" "--speed=4000" "--reset-after-load") 4 | 5 | include(${ZEPHYR_BASE}/boards/common/openocd.board.cmake) 6 | include(${ZEPHYR_BASE}/boards/common/jlink.board.cmake) 7 | -------------------------------------------------------------------------------- /boards/libresolar/bms_5s50_sc/board.yml: -------------------------------------------------------------------------------- 1 | board: 2 | name: bms_5s50_sc 3 | vendor: libresolar 4 | socs: 5 | - name: stm32f072xb 6 | -------------------------------------------------------------------------------- /boards/libresolar/bms_5s50_sc/support/openocd.cfg: -------------------------------------------------------------------------------- 1 | source [find board/st_nucleo_f0.cfg] 2 | 3 | $_TARGETNAME configure -event gdb-attach { 4 | echo "Debugger attaching: halting execution" 5 | reset halt 6 | gdb_breakpoint_override hard 7 | } 8 | 9 | $_TARGETNAME configure -event gdb-detach { 10 | echo "Debugger detaching: resuming execution" 11 | resume 12 | } 13 | -------------------------------------------------------------------------------- /boards/libresolar/bms_8s50_ic/Kconfig.bms_8s50_ic: -------------------------------------------------------------------------------- 1 | # Copyright (c) The Libre Solar Project Contributors 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | config BOARD_BMS_8S50_IC 5 | select SOC_STM32L452XX 6 | -------------------------------------------------------------------------------- /boards/libresolar/bms_8s50_ic/Kconfig.defconfig: -------------------------------------------------------------------------------- 1 | # Copyright (c) The Libre Solar Project Contributors 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | if BMS_IC 5 | 6 | config BMS_IC_MAX_THERMISTORS 7 | default 1 8 | 9 | endif # BMS_IC 10 | -------------------------------------------------------------------------------- /boards/libresolar/bms_8s50_ic/bms_8s50_ic.dts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) The Libre Solar Project Contributors 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | /dts-v1/; 8 | 9 | #include 10 | #include 11 | 12 | / { 13 | model = "Libre Solar BMS 8S50 IC"; 14 | compatible = "st,stm32l452"; 15 | 16 | pcb { 17 | compatible = "bms"; 18 | 19 | type = "BMS 8S50 IC"; 20 | 21 | /* 22 | * Additional information: 23 | * - Has MOSFET temperature sensor 24 | * - PIN_V_EXT PA_5 25 | * - PIN_TEMP_1 PA_6 26 | * - PIN_TEMP_2 PA_7 27 | */ 28 | }; 29 | 30 | chosen { 31 | zephyr,console = &usart1; 32 | zephyr,shell-uart = &usart1; 33 | zephyr,sram = &sram0; 34 | zephyr,flash = &flash0; 35 | thingset,can = &can1; 36 | thingset,eeprom = &eeprom0; 37 | thingset,serial = &uext_uart; 38 | }; 39 | 40 | leds { 41 | compatible = "gpio-leds"; 42 | led1: led_0 { 43 | gpios = <&gpiob 14 GPIO_ACTIVE_HIGH>; 44 | }; 45 | led2: led_1 { 46 | gpios = <&gpiob 15 GPIO_ACTIVE_HIGH>; 47 | }; 48 | }; 49 | 50 | gpio_keys { 51 | compatible = "gpio-keys"; 52 | power_button: button { 53 | gpios = <&gpioa 8 GPIO_ACTIVE_LOW>; 54 | }; 55 | }; 56 | 57 | transceiver0: can-phy0 { 58 | compatible = "ti,tcan334", "can-transceiver-gpio"; 59 | max-bitrate = <1000000>; 60 | standby-gpios = <&gpioc 13 GPIO_ACTIVE_HIGH>; 61 | #phy-cells = <0>; 62 | }; 63 | 64 | aliases { 65 | led-red = &led1; 66 | led-green = &led2; 67 | sw-pwr = &power_button; 68 | bms-ic = &isl94202; 69 | }; 70 | }; 71 | 72 | &clk_hsi { 73 | status = "okay"; 74 | }; 75 | 76 | &pll { 77 | div-m = <1>; 78 | mul-n = <20>; 79 | div-p = <7>; 80 | div-q = <2>; 81 | div-r = <4>; 82 | clocks = <&clk_hsi>; 83 | status = "okay"; 84 | }; 85 | 86 | &rcc { 87 | clocks = <&pll>; 88 | clock-frequency = ; 89 | ahb-prescaler = <1>; 90 | apb1-prescaler = <1>; 91 | apb2-prescaler = <1>; 92 | }; 93 | 94 | &usart1 { 95 | pinctrl-0 = <&usart1_tx_pa9 &usart1_rx_pa10>; 96 | pinctrl-names = "default"; 97 | current-speed = <115200>; 98 | status = "okay"; 99 | }; 100 | 101 | &usart1_rx_pa10 { 102 | bias-pull-up; // avoid junk characters if pin is left floating 103 | }; 104 | 105 | uext_uart: &usart2 { 106 | pinctrl-0 = <&usart2_tx_pa2 &usart2_rx_pa3>; 107 | pinctrl-names = "default"; 108 | current-speed = <115200>; 109 | status = "okay"; 110 | }; 111 | 112 | &usart2_rx_pa3 { 113 | bias-pull-up; // avoid junk characters if pin is left floating 114 | }; 115 | 116 | uext_i2c: &i2c1 { 117 | pinctrl-0 = <&i2c1_scl_pb6 &i2c1_sda_pb7>; 118 | pinctrl-names = "default"; 119 | status = "okay"; 120 | }; 121 | 122 | &i2c2 { 123 | pinctrl-0 = <&i2c2_scl_pb10 &i2c2_sda_pb11>; 124 | pinctrl-names = "default"; 125 | clock-frequency = ; 126 | status = "okay"; 127 | 128 | isl94202: isl94202@28 { 129 | compatible = "renesas,isl94202"; 130 | reg = <0x28>; /* 0x50 >> 1 */ 131 | pull-up-gpios = <&gpiob 2 GPIO_ACTIVE_HIGH>; 132 | board-max-current = <50>; 133 | used-cell-channels = <0xFF>; 134 | status = "okay"; 135 | }; 136 | 137 | eeprom0: eeprom@50 { 138 | // Microchip 24AA32A 139 | compatible = "atmel,at24"; 140 | reg = <0x50>; 141 | size = <32768>; 142 | pagesize = <32>; 143 | address-width = <16>; 144 | /* 145 | * timeout of 5 ms as suggested in datasheet seems too optimistic 146 | * (several write errors occured during testing) 147 | */ 148 | timeout = <7>; 149 | }; 150 | }; 151 | 152 | &spi1 { 153 | pinctrl-0 = <&spi1_sck_pb3 &spi1_miso_pb4 &spi1_mosi_pb5>; 154 | pinctrl-names = "default"; 155 | cs-gpios = <&gpioa 15 GPIO_ACTIVE_LOW>; 156 | status = "okay"; 157 | }; 158 | 159 | &can1 { 160 | pinctrl-0 = <&can1_rx_pb8 &can1_tx_pb9>; 161 | pinctrl-names = "default"; 162 | bitrate = <500000>; 163 | phys = <&transceiver0>; 164 | status = "okay"; 165 | }; 166 | 167 | &rng { 168 | status = "okay"; 169 | }; 170 | 171 | &rtc { 172 | status = "okay"; 173 | }; 174 | 175 | &flash0 { 176 | partitions { 177 | compatible = "fixed-partitions"; 178 | #address-cells = <1>; 179 | #size-cells = <1>; 180 | 181 | /* 182 | * Reserve the final 16 KiB for file system partition 183 | */ 184 | storage_partition: partition@3c000 { 185 | label = "storage"; 186 | reg = <0x0003c000 0x00004000>; 187 | }; 188 | }; 189 | }; 190 | -------------------------------------------------------------------------------- /boards/libresolar/bms_8s50_ic/bms_8s50_ic.yaml: -------------------------------------------------------------------------------- 1 | identifier: bms_8s50_ic 2 | name: BMS-8S50-IC 3 | type: mcu 4 | arch: arm 5 | toolchain: 6 | - zephyr 7 | - gnuarmemb 8 | - xtools 9 | ram: 64 10 | flash: 256 11 | supported: 12 | - nvs 13 | - can 14 | - rtc 15 | - counter 16 | - spi 17 | -------------------------------------------------------------------------------- /boards/libresolar/bms_8s50_ic/bms_8s50_ic_0_1_0.conf: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | 3 | # empty (using defaults from _defconfig file) 4 | -------------------------------------------------------------------------------- /boards/libresolar/bms_8s50_ic/bms_8s50_ic_0_1_0.overlay: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) The Libre Solar Project Contributors 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | / { 8 | pcb { 9 | version-str = "v0.1"; 10 | version-num = <1>; 11 | }; 12 | }; 13 | 14 | &isl94202 { 15 | shunt-resistor-uohm = <1000>; 16 | }; 17 | -------------------------------------------------------------------------------- /boards/libresolar/bms_8s50_ic/bms_8s50_ic_0_2_0.conf: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | 3 | # empty (using defaults from _defconfig file) 4 | -------------------------------------------------------------------------------- /boards/libresolar/bms_8s50_ic/bms_8s50_ic_0_2_0.overlay: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) The Libre Solar Project Contributors 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | / { 8 | pcb { 9 | version-str = "v0.2"; 10 | version-num = <2>; 11 | }; 12 | }; 13 | 14 | &isl94202 { 15 | shunt-resistor-uohm = <500>; 16 | }; 17 | -------------------------------------------------------------------------------- /boards/libresolar/bms_8s50_ic/bms_8s50_ic_defconfig: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | 3 | CONFIG_ARM_MPU=y 4 | 5 | CONFIG_CLOCK_CONTROL=y 6 | 7 | CONFIG_PINCTRL=y 8 | 9 | CONFIG_GPIO=y 10 | 11 | CONFIG_SERIAL=y 12 | CONFIG_UART_INTERRUPT_DRIVEN=y 13 | 14 | CONFIG_CONSOLE=y 15 | CONFIG_UART_CONSOLE=y 16 | -------------------------------------------------------------------------------- /boards/libresolar/bms_8s50_ic/board.cmake: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | 3 | board_runner_args(jlink "--device=STM32L452CE" "--speed=4000" "--reset-after-load") 4 | 5 | include(${ZEPHYR_BASE}/boards/common/openocd.board.cmake) 6 | include(${ZEPHYR_BASE}/boards/common/jlink.board.cmake) 7 | -------------------------------------------------------------------------------- /boards/libresolar/bms_8s50_ic/board.yml: -------------------------------------------------------------------------------- 1 | board: 2 | name: bms_8s50_ic 3 | vendor: libresolar 4 | socs: 5 | - name: stm32l452xx 6 | revision: 7 | format: major.minor.patch 8 | default: "0.2.0" 9 | revisions: 10 | - name: "0.2.0" 11 | - name: "0.1.0" 12 | -------------------------------------------------------------------------------- /boards/libresolar/bms_8s50_ic/support/openocd.cfg: -------------------------------------------------------------------------------- 1 | source [find interface/stlink.cfg] 2 | 3 | transport select hla_swd 4 | 5 | source [find target/stm32l4x.cfg] 6 | 7 | reset_config srst_only 8 | -------------------------------------------------------------------------------- /boards/libresolar/bms_c1/Kconfig.bms_c1: -------------------------------------------------------------------------------- 1 | # Copyright (c) The Libre Solar Project Contributors 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | config BOARD_BMS_C1 5 | select SOC_ESP32C3_MINI_N4 6 | -------------------------------------------------------------------------------- /boards/libresolar/bms_c1/Kconfig.defconfig: -------------------------------------------------------------------------------- 1 | # Copyright (c) The Libre Solar Project Contributors 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | config ENTROPY_ESP32_RNG 5 | default y if ENTROPY_GENERATOR 6 | 7 | config HEAP_MEM_POOL_SIZE 8 | default 98304 if WIFI 9 | default 40960 if BT 10 | default 4096 11 | 12 | if BMS_IC 13 | 14 | config BMS_IC_MAX_THERMISTORS 15 | default 2 16 | 17 | endif # BMS_IC 18 | -------------------------------------------------------------------------------- /boards/libresolar/bms_c1/Kconfig.sysbuild: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 Espressif Systems (Shanghai) Co., Ltd. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | choice BOOTLOADER 5 | default BOOTLOADER_MCUBOOT 6 | endchoice 7 | 8 | choice BOOT_SIGNATURE_TYPE 9 | default BOOT_SIGNATURE_TYPE_NONE 10 | endchoice 11 | -------------------------------------------------------------------------------- /boards/libresolar/bms_c1/bms_c1.dts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) The Libre Solar Project Contributors 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | /dts-v1/; 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | / { 18 | model = "Libre Solar BMS C1"; 19 | compatible = "espressif,esp32c3"; 20 | 21 | pcb { 22 | compatible = "bms"; 23 | 24 | type = "BMS C1"; 25 | }; 26 | 27 | chosen { 28 | zephyr,sram = &sram0; 29 | zephyr,console = &usb_serial; 30 | zephyr,shell-uart = &usb_serial; 31 | zephyr,flash = &flash0; 32 | zephyr,code-partition = &slot0_partition; 33 | zephyr,bt-hci = &esp32_bt_hci; 34 | thingset,can = &twai; 35 | thingset,serial = &uart0; 36 | }; 37 | 38 | leds { 39 | compatible = "gpio-leds"; 40 | led1: led_0 { 41 | gpios = <&gpio0 1 GPIO_ACTIVE_HIGH>; 42 | }; 43 | led2: led_1 { 44 | gpios = <&gpio0 0 GPIO_ACTIVE_HIGH>; 45 | }; 46 | }; 47 | 48 | gpio_keys { 49 | compatible = "gpio-keys"; 50 | power_button: button { 51 | gpios = <&gpio0 3 GPIO_ACTIVE_HIGH>; 52 | }; 53 | }; 54 | 55 | transceiver0: can-phy0 { 56 | compatible = "ti,sn65hvd230", "can-transceiver-gpio"; 57 | max-bitrate = <1000000>; 58 | standby-gpios = <&gpio0 10 GPIO_ACTIVE_HIGH>; 59 | #phy-cells = <0>; 60 | }; 61 | 62 | aliases { 63 | led-red = &led1; 64 | led-green = &led2; 65 | sw-pwr = &power_button; 66 | bms-ic = &bq76952; 67 | }; 68 | }; 69 | 70 | &uart0 { 71 | status = "okay"; 72 | current-speed = <115200>; 73 | pinctrl-0 = <&uart0_default>; 74 | pinctrl-names = "default"; 75 | }; 76 | 77 | &usb_serial { 78 | status = "okay"; 79 | }; 80 | 81 | &i2c0 { 82 | status = "okay"; 83 | clock-frequency = ; 84 | pinctrl-0 = <&i2c0_default>; 85 | pinctrl-names = "default"; 86 | 87 | bq76952: bq769x2@8 { 88 | compatible = "ti,bq769x2-i2c"; 89 | reg = <0x08>; 90 | alert-gpios = <&gpio0 2 GPIO_ACTIVE_HIGH>; 91 | used-cell-channels = <0xFFFF>; 92 | /* all NTCs configured with 18k pull-up */ 93 | ts1-pin-config = <0x07>; 94 | ts3-pin-config = <0x07>; 95 | dchg-pin-config = <0x0F>; 96 | cell-temp-pins = ; 97 | fet-temp-pin = ; 98 | board-max-current = <100>; 99 | shunt-resistor-uohm = <300>; 100 | status = "okay"; 101 | }; 102 | }; 103 | 104 | &trng0 { 105 | status = "okay"; 106 | }; 107 | 108 | &gpio0 { 109 | status = "okay"; 110 | }; 111 | 112 | &wdt0 { 113 | status = "okay"; 114 | }; 115 | 116 | &timer0 { 117 | status = "okay"; 118 | }; 119 | 120 | &timer1 { 121 | status = "okay"; 122 | }; 123 | 124 | &twai { 125 | status = "okay"; 126 | pinctrl-0 = <&twai_default>; 127 | pinctrl-names = "default"; 128 | bitrate = <500000>; 129 | phys = <&transceiver0>; 130 | }; 131 | 132 | &flash0 { 133 | status = "okay"; 134 | partitions { 135 | compatible = "fixed-partitions"; 136 | #address-cells = <1>; 137 | #size-cells = <1>; 138 | 139 | /* Reserve 60kB for the bootloader */ 140 | boot_partition: partition@0 { 141 | label = "mcuboot"; 142 | reg = <0x00000000 0x0000F000>; 143 | read-only; 144 | }; 145 | 146 | /* Reserve 1024kB for the application in slot 0 */ 147 | slot0_partition: partition@10000 { 148 | label = "image-0"; 149 | reg = <0x00010000 0x00100000>; 150 | }; 151 | 152 | /* Reserve 1024kB for the application in slot 1 */ 153 | slot1_partition: partition@110000 { 154 | label = "image-1"; 155 | reg = <0x00110000 0x00100000>; 156 | }; 157 | 158 | /* Reserve 256kB for the scratch partition */ 159 | scratch_partition: partition@210000 { 160 | label = "image-scratch"; 161 | reg = <0x00210000 0x00040000>; 162 | }; 163 | 164 | storage_partition: partition@250000 { 165 | label = "storage"; 166 | reg = <0x00250000 0x00006000>; 167 | }; 168 | }; 169 | }; 170 | 171 | &pinctrl { 172 | uart0_default: uart0_default { 173 | group1 { 174 | pinmux = ; 175 | }; 176 | group2 { 177 | pinmux = ; 178 | bias-pull-up; 179 | }; 180 | }; 181 | 182 | twai_default: twai_default { 183 | group1 { 184 | pinmux = , 185 | ; 186 | }; 187 | }; 188 | 189 | /* see version-specific overlays for I2C */ 190 | }; 191 | 192 | &esp32_bt_hci { 193 | status = "okay"; 194 | }; 195 | -------------------------------------------------------------------------------- /boards/libresolar/bms_c1/bms_c1.yaml: -------------------------------------------------------------------------------- 1 | identifier: bms_c1 2 | name: BMS-C1 3 | type: mcu 4 | arch: riscv 5 | toolchain: 6 | - zephyr 7 | -------------------------------------------------------------------------------- /boards/libresolar/bms_c1/bms_c1_0_3_0.conf: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | 3 | # empty (using defaults from _defconfig file) 4 | -------------------------------------------------------------------------------- /boards/libresolar/bms_c1/bms_c1_0_3_0.overlay: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) The Libre Solar Project Contributors 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | / { 8 | pcb { 9 | version-str = "v0.3"; 10 | version-num = <3>; 11 | }; 12 | }; 13 | 14 | &pinctrl { 15 | i2c0_default: i2c0_default { 16 | group1 { 17 | pinmux = , 18 | ; 19 | bias-pull-up; 20 | drive-open-drain; 21 | output-high; 22 | }; 23 | }; 24 | }; 25 | -------------------------------------------------------------------------------- /boards/libresolar/bms_c1/bms_c1_0_4_0.conf: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | 3 | # empty (using defaults from _defconfig file) 4 | -------------------------------------------------------------------------------- /boards/libresolar/bms_c1/bms_c1_0_4_0.overlay: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) The Libre Solar Project Contributors 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | / { 8 | pcb { 9 | version-str = "v0.4"; 10 | version-num = <4>; 11 | }; 12 | }; 13 | 14 | &gpio0 { 15 | deactivate-rs485 { 16 | gpio-hog; 17 | gpios = <6 GPIO_ACTIVE_HIGH>, <7 GPIO_ACTIVE_LOW>; 18 | output-low; 19 | }; 20 | }; 21 | 22 | &pinctrl { 23 | i2c0_default: i2c0_default { 24 | group1 { 25 | pinmux = , 26 | ; 27 | bias-pull-up; 28 | drive-open-drain; 29 | output-high; 30 | }; 31 | }; 32 | }; 33 | -------------------------------------------------------------------------------- /boards/libresolar/bms_c1/bms_c1_defconfig: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | 3 | CONFIG_CLOCK_CONTROL=y 4 | CONFIG_SYS_CLOCK_TICKS_PER_SEC=1000 5 | 6 | CONFIG_MAIN_STACK_SIZE=2048 7 | 8 | CONFIG_GPIO=y 9 | 10 | CONFIG_CONSOLE=y 11 | CONFIG_SERIAL=y 12 | CONFIG_UART_CONSOLE=y 13 | CONFIG_UART_INTERRUPT_DRIVEN=y 14 | -------------------------------------------------------------------------------- /boards/libresolar/bms_c1/board.cmake: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | 3 | if(NOT "${OPENOCD}" MATCHES "^${ESPRESSIF_TOOLCHAIN_PATH}/.*") 4 | set(OPENOCD OPENOCD-NOTFOUND) 5 | endif() 6 | find_program(OPENOCD openocd PATHS ${ESPRESSIF_TOOLCHAIN_PATH}/openocd-esp32/bin NO_DEFAULT_PATH) 7 | 8 | include(${ZEPHYR_BASE}/boards/common/esp32.board.cmake) 9 | include(${ZEPHYR_BASE}/boards/common/openocd.board.cmake) 10 | -------------------------------------------------------------------------------- /boards/libresolar/bms_c1/board.yml: -------------------------------------------------------------------------------- 1 | board: 2 | name: bms_c1 3 | vendor: libresolar 4 | socs: 5 | - name: esp32c3 6 | revision: 7 | format: major.minor.patch 8 | default: "0.4.0" 9 | revisions: 10 | - name: "0.4.0" 11 | - name: "0.3.0" 12 | -------------------------------------------------------------------------------- /boards/libresolar/bms_c1/support/openocd.cfg: -------------------------------------------------------------------------------- 1 | set ESP_RTOS none 2 | 3 | source [find interface/esp_usb_jtag.cfg] 4 | 5 | source [find target/esp32c3.cfg] 6 | adapter_khz 5000 7 | -------------------------------------------------------------------------------- /boards/shields/uext_oled/Kconfig.shield: -------------------------------------------------------------------------------- 1 | # Copyright (c) The Libre Solar Project Contributors 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | config SHIELD_UEXT_OLED 5 | def_bool $(shields_list_contains,uext_oled) 6 | -------------------------------------------------------------------------------- /boards/shields/uext_oled/uext_oled.overlay: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 PHYTEC Messtechnik GmbH 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | / { 8 | chosen { 9 | zephyr,display = &ssd1306_128x64; 10 | }; 11 | }; 12 | 13 | &uext_i2c { 14 | status = "okay"; 15 | 16 | ssd1306_128x64: ssd1306@3c { 17 | compatible = "solomon,ssd1306fb"; 18 | reg = <0x3c>; 19 | width = <128>; 20 | height = <64>; 21 | segment-offset = <0>; 22 | page-offset = <0>; 23 | display-offset = <0>; 24 | multiplex-ratio = <63>; 25 | segment-remap; 26 | com-invdir; 27 | prechargep = <0x22>; 28 | }; 29 | }; 30 | -------------------------------------------------------------------------------- /docs/Doxyfile: -------------------------------------------------------------------------------- 1 | # Doxygen configuration 2 | # 3 | # Only contains most important options, as HTML rendering is done by Sphinx 4 | 5 | INPUT = ../ 6 | EXCLUDE_PATTERNS = */build* 7 | OUTPUT_DIRECTORY = build/doxygen 8 | RECURSIVE = YES 9 | GENERATE_HTML = NO 10 | GENERATE_LATEX = NO 11 | GENERATE_XML = YES 12 | GENERATE_TREEVIEW = YES 13 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | # 3 | # Makefile to build documentation using Doxygen, Sphinx and Breathe 4 | 5 | BUILDDIR = build 6 | SPHINXBUILD = sphinx-build 7 | SPHINXOPTS = -j auto 8 | 9 | SPHINX_INSTALLED := $(shell command -v $(SPHINXBUILD) 2> /dev/null) 10 | 11 | ifndef SPHINX_INSTALLED 12 | $(error '$(SPHINXBUILD)' executable was not found. Please install from https://www.sphinx-doc.org) 13 | endif 14 | 15 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(SPHINXOPTS) . 16 | 17 | .PHONY: clean html 18 | 19 | html: 20 | mkdir -p build/doxygen 21 | doxygen 22 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 23 | 24 | clean: 25 | rm -rf $(BUILDDIR)/* 26 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from pathlib import Path 3 | import os 4 | 5 | extensions = [ 6 | "breathe", 7 | ] 8 | 9 | # Project 10 | 11 | project = "Battery Management System" 12 | author = "The Libre Solar Project" 13 | year = datetime.now().year 14 | copyright = f"{year} {author}" 15 | 16 | version = os.popen("git describe --tags --abbrev=0").read() 17 | 18 | # HTML output 19 | 20 | html_theme = "sphinx_rtd_theme" 21 | html_theme_options = { 22 | "collapse_navigation": False, 23 | "style_nav_header_background": "#005e83", 24 | } 25 | html_static_path = ["static"] 26 | html_logo = "static/images/logo.png" 27 | html_context = { 28 | "display_github": True, 29 | "github_user": "LibreSolar", 30 | "github_repo": "bms-firmware", 31 | "github_version": "main/docs/", 32 | } 33 | html_favicon = "static/images/favicon.ico" 34 | 35 | # Breathe 36 | 37 | breathe_projects = {"app": "build/doxygen/xml"} 38 | breathe_default_project = "app" 39 | breathe_domain_by_extension = {"h": "c", "c": "c"} 40 | breathe_default_members = ("members", ) 41 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | ====================================== 2 | Libre Solar BMS Firmware Documentation 3 | ====================================== 4 | 5 | This documentation describes the firmware based on `Zephyr RTOS`_ for different Libre Solar Battery 6 | Management Systems. 7 | 8 | The firmware is under ongoing development. Most recent version can be found on 9 | `GitHub `_. 10 | 11 | This documentation is licensed under the Creative Commons Attribution-ShareAlike 4.0 International 12 | (CC BY-SA 4.0) License. 13 | 14 | .. image:: static/images/cc-by-sa-centered.png 15 | 16 | The full license text is available at ``_. 17 | 18 | .. _Zephyr RTOS: https://zephyrproject.org 19 | 20 | .. toctree:: 21 | :caption: Overview 22 | :hidden: 23 | 24 | src/features 25 | src/supported_hardware 26 | 27 | .. toctree:: 28 | :caption: Development 29 | :hidden: 30 | 31 | src/dev/workspace_setup 32 | src/dev/building_flashing 33 | src/dev/customization 34 | src/dev/simulator 35 | src/dev/unit_tests 36 | 37 | .. toctree:: 38 | :caption: API Reference 39 | :hidden: 40 | 41 | src/api/bms 42 | src/api/bms_ic 43 | src/api/data_objects 44 | src/api/misc 45 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | breathe 2 | sphinx~=5.3 3 | sphinx-notfound-page 4 | sphinx-rtd-theme 5 | sphinx-tabs 6 | -------------------------------------------------------------------------------- /docs/src/api/bms.rst: -------------------------------------------------------------------------------- 1 | BMS application 2 | =============== 3 | 4 | .. doxygenfile:: include/bms/bms_common.h 5 | :project: app 6 | 7 | .. doxygenfile:: include/bms/bms.h 8 | :project: app 9 | -------------------------------------------------------------------------------- /docs/src/api/bms_ic.rst: -------------------------------------------------------------------------------- 1 | BMS front-end IC driver 2 | ======================= 3 | 4 | .. doxygenfile:: include/drivers/bms_ic.h 5 | :project: app 6 | -------------------------------------------------------------------------------- /docs/src/api/data_objects.rst: -------------------------------------------------------------------------------- 1 | Data Objects 2 | ============ 3 | 4 | .. doxygenfile:: data_objects.h 5 | :project: app 6 | -------------------------------------------------------------------------------- /docs/src/api/misc.rst: -------------------------------------------------------------------------------- 1 | Misc 2 | ==== 3 | 4 | Button 5 | ------ 6 | 7 | .. doxygenfile:: button.h 8 | :project: app 9 | 10 | Helper 11 | ------ 12 | 13 | .. doxygenfile:: include/helper.h 14 | :project: app 15 | -------------------------------------------------------------------------------- /docs/src/dev/building_flashing.rst: -------------------------------------------------------------------------------- 1 | Building and Flashing 2 | ===================== 3 | 4 | As the ``main`` branch may contain unstable code, make sure to select the desired release branch 5 | (see GitHub for a list of releases and branches): 6 | 7 | .. code-block:: bash 8 | 9 | git switch -branch 10 | west update 11 | 12 | Boards with ESP32 MCU 13 | """"""""""""""""""""" 14 | 15 | +-----------------------------+----------------------+----------------+ 16 | | Board with ESP32 MCU | board-name | revision(s) | 17 | +=============================+======================+================+ 18 | | Libre Solar BMS C1 | bms_c1 | 0.4, 0.3 | 19 | +-----------------------------+----------------------+----------------+ 20 | 21 | The ESP32 requires additional steps to get required binary blobs: 22 | 23 | .. code-block:: bash 24 | 25 | west blobs fetch hal_espressif 26 | 27 | Afterwards you can build and flash the firmware using the board name and revision from the table 28 | above: 29 | 30 | .. code-block:: bash 31 | 32 | west build -b @ 33 | west flash 34 | 35 | We recommend ``picocom`` as a serial console under Linux. Add ``--lower-dtr`` in order 36 | to reset the board before connecting, so that the entire log output during boot can be seen. 37 | 38 | .. code-block:: bash 39 | 40 | picocom /dev/ttyACM0 --lower-dtr 41 | 42 | Press Ctrl+a followed by q to exit. 43 | 44 | Boards with STM32 MCU 45 | """"""""""""""""""""" 46 | 47 | +-----------------------------+----------------------+----------------+ 48 | | Board with STM32 MCU | board-name | revision(s) | 49 | +=============================+======================+================+ 50 | | Libre Solar BMS 5S50 SC | bms_5s50_sc | 0.1 | 51 | +-----------------------------+----------------------+----------------+ 52 | | Libre Solar BMS 15S80 SC | bms_15s80_sc | 0.1 | 53 | +-----------------------------+----------------------+----------------+ 54 | | Libre Solar BMS 16S100 SC | bms_16s100_sc | 0.2 | 55 | +-----------------------------+----------------------+----------------+ 56 | | Libre Solar BMS 8S50 IC | bms_8s50_ic | 0.2, 0.1 | 57 | +-----------------------------+----------------------+----------------+ 58 | 59 | Supported hardware is further described in :doc:`../supported_hardware`. 60 | 61 | Build and flash the firmware using the board name and revision from the table above: 62 | 63 | .. code-block:: bash 64 | 65 | west build -b @ 66 | west flash 67 | 68 | The revision can be omitted if only a single board revision is available or if 69 | the default (most recent) version should be used. See also 70 | `here `_ 71 | for more details regarding board revision handling in Zephyr. 72 | 73 | For flashing with a specific debug probe (runner), e.g. J-Link, use the following command: 74 | 75 | .. code-block:: bash 76 | 77 | west flash -r jlink 78 | 79 | We recommend ``picocom`` as a serial console under Linux: 80 | 81 | .. code-block:: bash 82 | 83 | picocom -b 115200 /dev/ttyACM0 84 | 85 | Press Ctrl+a followed by q to exit. 86 | 87 | Device Firmware Upgrade (DFU) over CAN 88 | """""""""""""""""""""""""""""""""""""" 89 | 90 | For boards with large enough flash it is possible to enable support for upgrading the firmware over 91 | CAN (using ThingSet as the transport protocol). This has been tested with the BMS C1. 92 | 93 | First of all, the firmware has to be built with MCUboot support (using ``--sysbuild`` parameter): 94 | 95 | .. code-block:: bash 96 | 97 | rm -rf build 98 | west build -b @ --sysbuild 99 | west flash 100 | 101 | With that firmware flashed to the board, the CAN communication needs to be set up on the Linux host 102 | computer (replace ``can0`` with your interface name): 103 | 104 | .. code-block:: bash 105 | 106 | sudo ip link set can0 up type can bitrate 500000 restart-ms 500 107 | 108 | Test the CAN communication with ``candump can0``. You should see some published messages on the bus. 109 | 110 | For sending a new firmware image, use the Python script provided by the ThingSet SDK: 111 | 112 | .. code-block:: bash 113 | 114 | ../thingset-zephyr-sdk/scripts/thingset-dfu-can.py -c can0 -t 0x01 build/app/zephyr/zephyr.signed.bin 115 | 116 | The node address on the CAN bus is printed on the console during boot-up. If no other device is on 117 | the bus, it ends up with ``0x01``. It can also be determined from the ``candump`` output. The source 118 | address of the published messages (which becomes the target address for the firmware upgrade) is the 119 | least-significant byte of the CAN ID (first byte from the right). 120 | 121 | See also the `ThingSet specification `_ for further details regarding the CAN 122 | protocol. 123 | 124 | If everything worked correctly, you should see a progress bar on the console showing the firmware 125 | upgrade: 126 | 127 | .. code-block:: bash 128 | 129 | Initializing DFU 130 | Flashing |################################| 322/322 KiB = 100% 131 | Finishing DFU 132 | -------------------------------------------------------------------------------- /docs/src/dev/customization.rst: -------------------------------------------------------------------------------- 1 | Customization 2 | ============= 3 | 4 | This firmware is developed to allow easy addition of new boards and customization of the firmware. 5 | 6 | Hardware-specific changes 7 | ------------------------- 8 | 9 | In Zephyr, all hardware-specific configuration is described in the 10 | `Devicetree `_. 11 | 12 | The file ``boards/arm/board_name/board_name.dts`` contains the default devicetree specification 13 | (DTS) for a board. It is based on the DTS of the used MCU, which is included from the main Zephyr 14 | repository. 15 | 16 | In order to overwrite the default devicetree specification, so-called overlays can be used. An 17 | overlay file can be specified via the west command line. If it is stored as ``board_name.overlay`` 18 | in the ``app`` subfolder, it will be recognized automatically when building the firmware for that 19 | board. 20 | 21 | Application firmware configuration 22 | ---------------------------------- 23 | 24 | For configuration of the application-specific features, Zephyr uses the 25 | `Kconfig system `_. 26 | 27 | The configuration can be changed using ``west build -t menuconfig`` command or manually by changing 28 | the prj.conf file (see ``Kconfig`` file for possible options). 29 | 30 | Similar to DTS overlays, Kconfig can also be customized per board. Create a folder ``app/boards`` 31 | and a file ``board_name.conf`` in that folder. The configuration from this file will be merged with 32 | the ``prj.conf`` automatically. 33 | 34 | Change battery capacity, cell type and number of cells in series 35 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 36 | 37 | By default, the BMS is configured for LiFePO4 cells (``CONFIG_CELL_TYPE_LFP``). 38 | Possible other pre-defined options are ``CONFIG_BAT_TYPE_NMC``, ``CONFIG_BAT_TYPE_NMC_HV`` and 39 | ``CONFIG_BAT_TYPE_LTO``. 40 | 41 | The number of cells only has to be specified via ``CONFIG_BMS_IC_ISL94202_NUM_CELLS`` for boards 42 | with the ISL94202 chip. For all other chips it is detected automatically. 43 | 44 | To compile the firmware with default settings e.g. for a 24V LiFePO4 battery with a nominal capacity 45 | of 100Ah, add the following to ``prj.conf`` or the board-specific ``.conf`` file: 46 | 47 | .. code-block:: bash 48 | 49 | CONFIG_BAT_CAPACITY_AH=100 50 | CONFIG_CELL_TYPE_LFP=y 51 | 52 | Configure serial interface 53 | """""""""""""""""""""""""" 54 | 55 | The BMS exposes its data using the `ThingSet protocol `_. 56 | 57 | By default, live data is published on a serial interface in a 1 second interval. 58 | 59 | The used serial interface can be configured via Devicetree, e.g. with the following snippet in 60 | ``board_name.overlay``: 61 | 62 | .. code-block:: bash 63 | 64 | / { 65 | chosen { 66 | thingset,serial = &usart2; 67 | }; 68 | }; 69 | 70 | To disable regular data publication at startup, add the following to ``prj.conf`` or the 71 | board-specific ``.conf`` file: 72 | 73 | .. code-block:: bash 74 | 75 | CONFIG_THINGSET_REPORTING_LIVE_ENABLE_PRESET=n 76 | 77 | The default period for data publication can be changed with the following Kconfig option: 78 | 79 | .. code-block:: bash 80 | 81 | CONFIG_THINGSET_REPORTING_LIVE_PERIOD_PRESET=10 82 | 83 | Shields for UEXT connector 84 | -------------------------- 85 | 86 | There are some shields like an OLED display which can be connected to the UEXT connector. 87 | 88 | See the full list of shields in the ``boards/shields`` folder. 89 | 90 | The below example compiles the firmware with the OLED shield enabled. 91 | 92 | .. code-block:: bash 93 | 94 | west build -b bms_8s50_ic@0.2 app -- -DEXTRA_CONF_FILE=oled.conf -DSHIELD=uext_oled 95 | -------------------------------------------------------------------------------- /docs/src/dev/simulator.rst: -------------------------------------------------------------------------------- 1 | Simulator 2 | ========= 3 | 4 | Zephyr allows to run an application on a Linux or Mac hosts using the ``native_sim`` board. 5 | 6 | The BMS firmware currently does not mock an entire BMS IC. However, the simulated application 7 | allows to test the ThingSet communication and other basic features. 8 | 9 | Execute the following command to run the simulated application: 10 | 11 | .. code-block:: bash 12 | 13 | west build -b native_sim -t run 14 | 15 | In order to use a Linux host's Bluetooth adapter (here: ``hci0``) for Zephyr, install BlueZ utils 16 | and run the following commands: 17 | 18 | .. code-block:: bash 19 | 20 | west build -b native_sim 21 | sudo btmgmt power off 22 | sudo build/zephyr/zephyr.exe --bt-dev=hci0 23 | -------------------------------------------------------------------------------- /docs/src/dev/unit_tests.rst: -------------------------------------------------------------------------------- 1 | Unit Tests 2 | ========== 3 | 4 | Writing the tests is still work in progress. New functions should be implemented in test-driven 5 | development fashion. Tests for old functions will be added step by step. 6 | 7 | The tests use Zephyr's Ztest framework and the twister tool. Build and run the tests with the 8 | following command: 9 | 10 | .. code-block:: bash 11 | 12 | ../zephyr/scripts/twister -T ./tests --integration --inline-logs -n 13 | -------------------------------------------------------------------------------- /docs/src/dev/workspace_setup.rst: -------------------------------------------------------------------------------- 1 | 2 | Workspace Setup 3 | =============== 4 | 5 | This guide assumes you have already installed the Zephyr SDK and the ``west`` tool according to the 6 | `Zephyr documentation `_. 7 | 8 | Below commands initialize a new workspace and pull all required source files: 9 | 10 | .. code-block:: bash 11 | 12 | # create a new west workspace and pull the BMS firmware 13 | west init -m https://github.com/LibreSolar/bms-firmware west-workspace 14 | cd west-workspace/bms-firmware 15 | 16 | # pull Zephyr upstream repository and modules (may take a while) 17 | west update 18 | 19 | Afterwards, most important folders in your workspace will look like this: 20 | 21 | .. code-block:: bash 22 | 23 | west-workspace/ # contains .west/config 24 | │ 25 | ├── bms-firmware/ # application firmware repository 26 | │ ├── app/ # application source files 27 | │ ├── boards/ # board specifications 28 | │ ├── tests/ # unit test source files 29 | │ └── west.yml # main manifest file 30 | │ 31 | ├── modules/ # modules imported by Zephyr and BMS firmware 32 | | 33 | ├── tools/ # tools used by Zephyr 34 | │ 35 | └── zephyr/ # upstream Zephyr repository 36 | 37 | If you already have a west workspace set up (e.g. for other Libre Solar firmware), you can also 38 | re-use it to avoid having many copies of upstream Zephyr and modules: 39 | 40 | .. code-block:: bash 41 | 42 | # go to your workspace directory 43 | cd your-zephyr-workspace 44 | 45 | # pull the BMS firmware 46 | git clone https://github.com/LibreSolar/bms-firmware 47 | cd bms-firmware 48 | 49 | # re-configure and update the workspace 50 | # (to be done each time you switch between applications in same workspace) 51 | west config manifest.path bms-firmware 52 | west update 53 | -------------------------------------------------------------------------------- /docs/src/features.rst: -------------------------------------------------------------------------------- 1 | Features 2 | ======== 3 | 4 | Amongst all features inherited from underlying Zephyr, the BMS has the following features: 5 | 6 | - Monitoring and configuration using the `ThingSet`_ protocol (mapping to MQTT, CoAP and HTTP 7 | possible) 8 | 9 | - Serial interface 10 | - CAN bus 11 | - Bluetooth (not available on all boards) 12 | 13 | - Extensions via `UEXT connector`_ possible (depending on hardware) 14 | 15 | - I2C 16 | - SPI 17 | - UART 18 | 19 | - SOC estimation based on coulomb counting 20 | 21 | - Configuration options 22 | 23 | - Pack layout 24 | 25 | - Cell chemistry (e.g. LiFePO4, NMC, NCA, LTO) 26 | - Nominal capacity 27 | - Number of cells 28 | - Thermistor type 29 | - Shunt resistor 30 | - Custom open circuit voltage (OCV) look-up table 31 | 32 | - Protection 33 | 34 | - Discharge short circuit limit (A) 35 | - Discharge short circuit delay (us) 36 | - Discharge over-current limit (A) 37 | - Discharge over-current delay (ms) 38 | - Charge over-current limit (A) 39 | - Charge over-current delay (ms) 40 | - Cell target charge voltage (V) 41 | - Cell discharge voltage limit (V) 42 | - Cell over-voltage limit (V) 43 | - Cell over-voltage error reset threshold (V) 44 | - Cell over-voltage delay (ms) 45 | - Cell under-voltage limit (V) 46 | - Cell under-voltage error reset threshold (V) 47 | - Cell under-voltage delay (ms) 48 | - Discharge over-temperature (DOT) limit (°C) 49 | - Discharge under-temperature (DUT) limit (°C) 50 | - Charge over-temperature (COT) limit (°C) 51 | - Charge under-temperature (CUT) limit (°C) 52 | - Temperature limit hysteresis (°C) 53 | 54 | - Balancing 55 | 56 | - Enable automatic balancing. 57 | - Balancing cell voltage target difference (V) 58 | - Minimum cell voltage to start balancing (V) 59 | - Current threshold to be considered idle (A) 60 | - Minimum idle duration before balancing (s) 61 | 62 | 63 | .. _ThingSet: https://thingset.io 64 | .. _UEXT connector: https://en.wikipedia.org/wiki/UEXT 65 | -------------------------------------------------------------------------------- /docs/src/supported_hardware.rst: -------------------------------------------------------------------------------- 1 | Supported Hardware 2 | ================== 3 | 4 | Boards 5 | ------ 6 | 7 | The software is easily configurable to support different BMS boards with STM32 and ESP32 MCUs: 8 | 9 | +--------------------------------+----------------------+------------+----------------+ 10 | | Board | MCU | IC | Revisions | 11 | +================================+======================+============+================+ 12 | | Libre Solar `BMS-5S50-SC`_ | STM32F072 | bq76920 | 0.1 | 13 | +--------------------------------+----------------------+------------+----------------+ 14 | | Libre Solar `BMS-15S80-SC`_ | STM32F072 | bq76930/40 | 0.1 | 15 | +--------------------------------+----------------------+------------+----------------+ 16 | | Libre Solar `BMS-16S100-SC`_ | STM32G0B1 | BQ76952 | 0.2 | 17 | +--------------------------------+----------------------+------------+----------------+ 18 | | Libre Solar `BMS-C1`_ | ESP32-C3 | BQ76952 | 0.4, 0.3 | 19 | +--------------------------------+----------------------+------------+----------------+ 20 | | Libre Solar `BMS-8S50-IC`_ * | STM32L452 | ISL94202 | 0.2, 0.1 | 21 | +--------------------------------+----------------------+------------+----------------+ 22 | 23 | (*) Revision 0.1 of this board is also available with STM32F072 MCU. 24 | 25 | .. _BMS-5S50-SC: https://github.com/LibreSolar/bms-5s50-sc 26 | .. _BMS-15S80-SC: https://github.com/LibreSolar/bms-15s80-sc 27 | .. _BMS-16S100-SC: https://github.com/LibreSolar/bms-16s100-sc 28 | .. _BMS-C1: https://github.com/LibreSolar/bms-c1 29 | .. _BMS-8S50-IC: https://github.com/LibreSolar/bms-8s50-ic 30 | 31 | BMS ICs 32 | ------- 33 | 34 | The firmware allows to use different BMS monitoring ICs, which usually provide single-cell voltage monitoring, balancing, current monitoring and protection features. 35 | 36 | The chips currently supported by the firmware are listed below. 37 | 38 | +--------------------+------------------+---------+-----------------+ 39 | | Manufacturer | Chip / Datasheet | # Cells | Status | 40 | +====================+==================+=========+=================+ 41 | | Texas Instruments | `bq76920`_ | 3s-5s | full support | 42 | +--------------------+------------------+---------+-----------------+ 43 | | Texas Instruments | `bq76930`_ | 6s-10s | full support | 44 | +--------------------+------------------+---------+-----------------+ 45 | | Texas Instruments | `bq76940`_ | 9s-15s | full support | 46 | +--------------------+------------------+---------+-----------------+ 47 | | Texas Instruments | `BQ76952`_ | 3s-16s | full support | 48 | +--------------------+------------------+---------+-----------------+ 49 | | Renesas / Intersil | `ISL94202`_ | 3s-8s | full support | 50 | +--------------------+------------------+---------+-----------------+ 51 | 52 | .. _bq76920: https://www.ti.com/lit/ds/symlink/bq76920.pdf 53 | .. _bq76930: https://www.ti.com/lit/ds/symlink/bq76930.pdf 54 | .. _bq76940: https://www.ti.com/lit/ds/symlink/bq76940.pdf 55 | .. _BQ76952: https://www.ti.com/lit/ds/symlink/bq76952.pdf 56 | .. _ISL94202: https://www.renesas.com/us/en/document/dst/isl94202-datasheet 57 | -------------------------------------------------------------------------------- /docs/static/images/cc-by-sa-centered.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LibreSolar/bms-firmware/77c6206b92399028af6a5ca893afecc87236ee7f/docs/static/images/cc-by-sa-centered.png -------------------------------------------------------------------------------- /docs/static/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LibreSolar/bms-firmware/77c6206b92399028af6a5ca893afecc87236ee7f/docs/static/images/favicon.ico -------------------------------------------------------------------------------- /docs/static/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LibreSolar/bms-firmware/77c6206b92399028af6a5ca893afecc87236ee7f/docs/static/images/logo.png -------------------------------------------------------------------------------- /drivers/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) The Libre Solar Project Contributors 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | add_subdirectory_ifdef(CONFIG_BMS_IC bms_ic) 5 | -------------------------------------------------------------------------------- /drivers/Kconfig: -------------------------------------------------------------------------------- 1 | 2 | rsource "bms_ic/Kconfig" 3 | -------------------------------------------------------------------------------- /drivers/bms_ic/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) The Libre Solar Project Contributors 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | add_subdirectory_ifdef(CONFIG_BMS_IC_BQ769X0 bq769x0) 5 | add_subdirectory_ifdef(CONFIG_BMS_IC_BQ769X2 bq769x2) 6 | add_subdirectory_ifdef(CONFIG_BMS_IC_ISL94202 isl94202) 7 | -------------------------------------------------------------------------------- /drivers/bms_ic/Kconfig: -------------------------------------------------------------------------------- 1 | # Copyright (c) The Libre Solar Project Contributors 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | menuconfig BMS_IC 5 | bool "BMS IC drivers" 6 | help 7 | Enable drivers for Battery Management System (BMS) front-end ICs. 8 | 9 | if BMS_IC 10 | 11 | config BMS_IC_HAS_SWITCHES 12 | bool 13 | help 14 | Select to indicate that the IC supports controlling MOSFETs. 15 | 16 | config BMS_IC_HAS_CURRENT_MONITORING 17 | bool 18 | help 19 | Select to indicate that the IC supports current monitoring. 20 | 21 | module = BMS_IC 22 | module-str = bms_ic 23 | source "subsys/logging/Kconfig.template.log_config" 24 | 25 | config BMS_IC_BQ769X0 26 | bool "Texas Instruments bq769x0 series" 27 | depends on DT_HAS_TI_BQ769X0_ENABLED 28 | select BMS_IC_HAS_CURRENT_MONITORING 29 | select BMS_IC_HAS_SWITCHES 30 | select CRC 31 | default y 32 | help 33 | Driver for TI bq769x0. 34 | 35 | config BMS_IC_BQ769X2 36 | bool "Texas Instruments bq769x2 series" 37 | depends on DT_HAS_TI_BQ769X2_I2C_ENABLED || DT_HAS_TI_BQ769X2_SPI_ENABLED 38 | select BMS_IC_HAS_CURRENT_MONITORING 39 | select BMS_IC_HAS_SWITCHES 40 | select CRC 41 | default y 42 | help 43 | Driver for TI bq76942, bq76952 and bq769142. 44 | 45 | config BMS_IC_ISL94202 46 | bool "Intersil/Renesas ISL94202" 47 | depends on DT_HAS_RENESAS_ISL94202_ENABLED 48 | select BMS_IC_HAS_CURRENT_MONITORING 49 | select BMS_IC_HAS_SWITCHES 50 | default y 51 | help 52 | Driver for Intersil/Renesas ISL94202. 53 | 54 | config BMS_IC_ISL94202_NUM_CELLS 55 | int "Exact number of cells in series" 56 | depends on BMS_IC_ISL94202 57 | range 3 8 58 | default 8 59 | help 60 | The exact number of cells used by the application is needed for configuration of the chip. 61 | 62 | Typical choices: 63 | - 3 for 12V NMC Li-Ion battery 64 | - 4 for 12V LiFePO4 battery 65 | - 5 for 12V Titanate battery 66 | - 8 for 24V LiFePO4 battery 67 | 68 | config BMS_IC_CURRENT_MONITORING 69 | bool "Use BMS IC current monitoring" 70 | depends on BMS_IC_HAS_CURRENT_MONITORING 71 | default y 72 | help 73 | Use the current monitoring features if available in the IC. Can be disabled to reduce 74 | code size. 75 | 76 | config BMS_IC_SWITCHES 77 | bool "Control switches via BMS IC" 78 | depends on BMS_IC_HAS_SWITCHES 79 | default y 80 | help 81 | If available in the BMS IC, this config allows to manually control the MOSFETs for 82 | charging, discharging or pre-charging. 83 | 84 | config BMS_IC_MAX_CELLS 85 | int "Max. number of cells used" 86 | range 1 31 87 | default 16 if BMS_IC_BQ769X2 88 | default 15 if BMS_IC_BQ769X0 89 | default 8 if BMS_IC_ISL94202 90 | 91 | config BMS_IC_MAX_THERMISTORS 92 | int "Max. number of thermistors used" 93 | range 1 255 94 | default 3 95 | 96 | config BMS_IC_INIT_PRIORITY 97 | int "BMS IC driver initialization priority" 98 | range 0 99 99 | default 70 100 | help 101 | System initialization priority for BMS IC driver. 102 | 103 | config BMS_IC_POLLING_INTERVAL_MS 104 | int "BMS IC polling interval" 105 | range 100 10000 106 | default 500 107 | 108 | endif 109 | -------------------------------------------------------------------------------- /drivers/bms_ic/bq769x0/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) The Libre Solar Project Contributors 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | zephyr_library() 5 | 6 | zephyr_library_sources(bq769x0.c) 7 | -------------------------------------------------------------------------------- /drivers/bms_ic/bq769x0/bq769x0_registers.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) The Libre Solar Project Contributors 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #ifndef BQ769X0_REGISTERS_H_ 8 | #define BQ769X0_REGISTERS_H_ 9 | 10 | #include 11 | 12 | // register map 13 | #define BQ769X0_SYS_STAT 0x00 14 | #define BQ769X0_CELLBAL1 0x01 15 | #define BQ769X0_CELLBAL2 0x02 16 | #define BQ769X0_CELLBAL3 0x03 17 | #define BQ769X0_SYS_CTRL1 0x04 18 | #define BQ769X0_SYS_CTRL2 0x05 19 | #define BQ769X0_PROTECT1 0x06 20 | #define BQ769X0_PROTECT2 0x07 21 | #define BQ769X0_PROTECT3 0x08 22 | #define BQ769X0_OV_TRIP 0x09 23 | #define BQ769X0_UV_TRIP 0x0A 24 | #define BQ769X0_CC_CFG 0x0B 25 | 26 | #define BQ769X0_VC1_HI_BYTE 0x0C 27 | #define BQ769X0_VC1_LO_BYTE 0x0D 28 | #define BQ769X0_VC2_HI_BYTE 0x0E 29 | #define BQ769X0_VC2_LO_BYTE 0x0F 30 | #define BQ769X0_VC3_HI_BYTE 0x10 31 | #define BQ769X0_VC3_LO_BYTE 0x11 32 | #define BQ769X0_VC4_HI_BYTE 0x12 33 | #define BQ769X0_VC4_LO_BYTE 0x13 34 | #define BQ769X0_VC5_HI_BYTE 0x14 35 | #define BQ769X0_VC5_LO_BYTE 0x15 36 | #define BQ769X0_VC6_HI_BYTE 0x16 37 | #define BQ769X0_VC6_LO_BYTE 0x17 38 | #define BQ769X0_VC7_HI_BYTE 0x18 39 | #define BQ769X0_VC7_LO_BYTE 0x19 40 | #define BQ769X0_VC8_HI_BYTE 0x1A 41 | #define BQ769X0_VC8_LO_BYTE 0x1B 42 | #define BQ769X0_VC9_HI_BYTE 0x1C 43 | #define BQ769X0_VC9_LO_BYTE 0x1D 44 | #define BQ769X0_VC10_HI_BYTE 0x1E 45 | #define BQ769X0_VC10_LO_BYTE 0x1F 46 | #define BQ769X0_VC11_HI_BYTE 0x20 47 | #define BQ769X0_VC11_LO_BYTE 0x21 48 | #define BQ769X0_VC12_HI_BYTE 0x22 49 | #define BQ769X0_VC12_LO_BYTE 0x23 50 | #define BQ769X0_VC13_HI_BYTE 0x24 51 | #define BQ769X0_VC13_LO_BYTE 0x25 52 | #define BQ769X0_VC14_HI_BYTE 0x26 53 | #define BQ769X0_VC14_LO_BYTE 0x27 54 | #define BQ769X0_VC15_HI_BYTE 0x28 55 | #define BQ769X0_VC15_LO_BYTE 0x29 56 | 57 | #define BQ769X0_BAT_HI_BYTE 0x2A 58 | #define BQ769X0_BAT_LO_BYTE 0x2B 59 | 60 | #define BQ769X0_TS1_HI_BYTE 0x2C 61 | #define BQ769X0_TS1_LO_BYTE 0x2D 62 | #define BQ769X0_TS2_HI_BYTE 0x2E 63 | #define BQ769X0_TS2_LO_BYTE 0x2F 64 | #define BQ769X0_TS3_HI_BYTE 0x30 65 | #define BQ769X0_TS3_LO_BYTE 0x31 66 | 67 | #define BQ769X0_CC_HI_BYTE 0x32 68 | #define BQ769X0_CC_LO_BYTE 0x33 69 | 70 | #define BQ769X0_ADCGAIN1 0x50 71 | #define BQ769X0_ADCOFFSET 0x51 72 | #define BQ769X0_ADCGAIN2 0x59 73 | 74 | // for bit clear operations of the SYS_STAT register 75 | #define BQ769X0_SYS_STAT_CC_READY (0x80) 76 | #define BQ769X0_SYS_STAT_DEVICE_XREADY (0x20) 77 | #define BQ769X0_SYS_STAT_OVRD_ALERT (0x10) 78 | #define BQ769X0_SYS_STAT_UV (0x08) 79 | #define BQ769X0_SYS_STAT_OV (0x04) 80 | #define BQ769X0_SYS_STAT_SCD (0x02) 81 | #define BQ769X0_SYS_STAT_OCD (0x01) 82 | #define BQ769X0_SYS_STAT_ERROR_MASK (0x3F) 83 | 84 | #define LOW_BYTE(data) (uint8_t)(0xff & data) 85 | #define HIGH_BYTE(data) (uint8_t)(0xff & (data >> 8)) 86 | 87 | // maps for settings in protection registers 88 | static const uint16_t bq769x0_scd_delays[4] = { 70, 100, 200, 400 }; // us 89 | static const uint16_t bq769x0_scd_thresholds[8] = { 44, 67, 89, 111, 133, 155, 178, 200 }; // mV 90 | 91 | static const uint16_t bq769x0_ocd_delays[8] = { 8, 20, 40, 80, 160, 320, 640, 1280 }; // ms 92 | static const uint16_t bq769x0_ocd_thresholds[16] = { 17, 22, 28, 33, 39, 44, 50, 56, 93 | 61, 67, 72, 78, 83, 89, 94, 100 }; // mV 94 | 95 | static const uint16_t bq769x0_uv_delays[4] = { 1, 4, 8, 16 }; // s 96 | static const uint16_t bq769x0_ov_delays[4] = { 1, 2, 4, 8 }; // s 97 | 98 | union bq769x0_sys_stat { 99 | struct 100 | { 101 | uint8_t OCD : 1; 102 | uint8_t SCD : 1; 103 | uint8_t OV : 1; 104 | uint8_t UV : 1; 105 | uint8_t OVRD_ALERT : 1; 106 | uint8_t DEVICE_XREADY : 1; 107 | uint8_t RSVD : 1; 108 | uint8_t CC_READY : 1; 109 | }; 110 | uint8_t byte; 111 | }; 112 | 113 | union bq769x0_sys_ctrl1 { 114 | struct 115 | { 116 | uint8_t SHUT_B : 1; 117 | uint8_t SHUT_A : 1; 118 | uint8_t RSVD1 : 1; 119 | uint8_t TEMP_SEL : 1; 120 | uint8_t ADC_EN : 1; 121 | uint8_t RSVD2 : 2; 122 | uint8_t LOAD_PRESENT : 1; 123 | }; 124 | uint8_t byte; 125 | }; 126 | 127 | union bq769x0_sys_ctrl2 { 128 | struct 129 | { 130 | uint8_t CHG_ON : 1; 131 | uint8_t DSG_ON : 1; 132 | uint8_t WAKE_T : 2; 133 | uint8_t WAKE_EN : 1; 134 | uint8_t CC_ONESHOT : 1; 135 | uint8_t CC_EN : 1; 136 | uint8_t DELAY_DIS : 1; 137 | }; 138 | uint8_t byte; 139 | }; 140 | 141 | union bq769x0_protect1 { 142 | struct 143 | { 144 | uint8_t SCD_THRESH : 3; 145 | uint8_t SCD_DELAY : 2; 146 | uint8_t RSVD : 2; 147 | uint8_t RSNS : 1; 148 | }; 149 | uint8_t byte; 150 | }; 151 | 152 | union bq769x0_protect2 { 153 | struct 154 | { 155 | uint8_t OCD_THRESH : 4; 156 | uint8_t OCD_DELAY : 3; 157 | uint8_t RSVD : 1; 158 | }; 159 | uint8_t byte; 160 | }; 161 | 162 | union bq769x0_protect3 { 163 | struct 164 | { 165 | uint8_t RSVD : 4; 166 | uint8_t OV_DELAY : 2; 167 | uint8_t UV_DELAY : 2; 168 | }; 169 | uint8_t byte; 170 | }; 171 | 172 | union bq769x0_cellbal { 173 | struct 174 | { 175 | uint8_t RSVD : 3; 176 | uint8_t CB5 : 1; 177 | uint8_t CB4 : 1; 178 | uint8_t CB3 : 1; 179 | uint8_t CB2 : 1; 180 | uint8_t CB1 : 1; 181 | }; 182 | uint8_t byte; 183 | }; 184 | 185 | union bq769x0_vcell { 186 | struct 187 | { 188 | uint8_t BQ769X0_VC_HI; 189 | uint8_t BQ769X0_VC_LO; 190 | }; 191 | uint16_t word; 192 | }; 193 | 194 | #endif /* BQ769X0_REGISTERS_H_ */ 195 | -------------------------------------------------------------------------------- /drivers/bms_ic/bq769x2/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) The Libre Solar Project Contributors 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | zephyr_library() 5 | 6 | zephyr_library_sources(bq769x2.c bq769x2_interface.c) 7 | 8 | zephyr_library_sources_ifdef(CONFIG_EMUL bq769x2_emul.c) 9 | zephyr_include_directories_ifdef(CONFIG_EMUL .) 10 | -------------------------------------------------------------------------------- /drivers/bms_ic/bq769x2/bq769x2_emul.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) The Libre Solar Project Contributors 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #define DT_DRV_COMPAT ti_bq769x2_i2c 8 | 9 | #include "bq769x2_registers.h" 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | LOG_MODULE_REGISTER(bq769x2_emul, CONFIG_BMS_IC_LOG_LEVEL); 18 | 19 | /* 20 | * Memory layout of bq769x2 for direct commands, data and subcommands 21 | * 22 | * Subcommands and data memory don't overlap, so they are stored in the same array. 23 | */ 24 | #define BQ_DIRECT_MEM_SIZE (0x80) 25 | #define BQ_DATA_MEM_SIZE (0x9400) 26 | 27 | struct bq769x0_emul_data 28 | { 29 | /* Memory of bq769x2 for direct commands */ 30 | uint8_t direct_mem[BQ_DIRECT_MEM_SIZE]; 31 | /* Memory of bq769x2 for subcommands / data */ 32 | uint8_t data_mem[BQ_DATA_MEM_SIZE]; 33 | uint32_t cur_reg; 34 | }; 35 | 36 | struct bq769x0_emul_cfg 37 | { 38 | uint16_t addr; 39 | }; 40 | 41 | uint8_t bq769x2_emul_get_direct_mem(const struct emul *em, uint8_t addr) 42 | { 43 | struct bq769x0_emul_data *em_data = em->data; 44 | 45 | return em_data->direct_mem[addr]; 46 | } 47 | 48 | void bq769x2_emul_set_direct_mem(const struct emul *em, uint8_t addr, uint8_t byte) 49 | { 50 | struct bq769x0_emul_data *em_data = em->data; 51 | 52 | em_data->direct_mem[addr] = byte; 53 | } 54 | 55 | uint8_t bq769x2_emul_get_data_mem(const struct emul *em, uint16_t addr) 56 | { 57 | struct bq769x0_emul_data *em_data = em->data; 58 | 59 | return em_data->data_mem[addr]; 60 | } 61 | 62 | void bq769x2_emul_set_data_mem(const struct emul *em, uint16_t addr, uint8_t byte) 63 | { 64 | struct bq769x0_emul_data *em_data = em->data; 65 | 66 | em_data->data_mem[addr] = byte; 67 | } 68 | 69 | /* 70 | * This function emulates the actual behavior of the chip for some subcmds, if required for the unit 71 | * tests. 72 | * 73 | * Currently only command-only subcmds are supported (without data). 74 | */ 75 | static void bq769x0_emul_process_subcmd(const struct emul *em, const uint16_t data_addr) 76 | { 77 | struct bq769x0_emul_data *em_data = em->data; 78 | 79 | switch (data_addr) { 80 | case BQ769X2_SUBCMD_SET_CFGUPDATE: 81 | k_usleep(2000); 82 | em_data->direct_mem[BQ769X2_CMD_BATTERY_STATUS] = 0x01; 83 | break; 84 | case BQ769X2_SUBCMD_EXIT_CFGUPDATE: 85 | k_usleep(1000); 86 | em_data->direct_mem[BQ769X2_CMD_BATTERY_STATUS] = 0x00; 87 | break; 88 | }; 89 | } 90 | 91 | static int bq769x0_emul_write_bytes(const struct emul *em, const uint8_t reg_addr, 92 | const uint8_t *data, const size_t num_bytes) 93 | { 94 | struct bq769x0_emul_data *em_data = em->data; 95 | 96 | if (reg_addr >= sizeof(em_data->direct_mem)) { 97 | return -EINVAL; 98 | } 99 | 100 | memcpy(&em_data->direct_mem[reg_addr], data, num_bytes); 101 | 102 | if (reg_addr == BQ769X2_SUBCMD_DATA_CHECKSUM || reg_addr == BQ769X2_SUBCMD_DATA_LENGTH) { 103 | /* writing to BQ769X2_SUBCMD_DATA_LENGTH starts execution of a subcommand */ 104 | 105 | uint16_t data_addr = (em_data->direct_mem[BQ769X2_CMD_SUBCMD_UPPER] << 8) 106 | + em_data->direct_mem[BQ769X2_CMD_SUBCMD_LOWER]; 107 | uint8_t subcmd_bytes = 108 | em_data->direct_mem[BQ769X2_SUBCMD_DATA_LENGTH] - BQ769X2_SUBCMD_OVERHEAD_BYTES; 109 | 110 | if (data_addr >= sizeof(em_data->data_mem)) { 111 | return -EINVAL; 112 | } 113 | 114 | memcpy(&em_data->data_mem[data_addr], &em_data->direct_mem[BQ769X2_SUBCMD_DATA_START], 115 | subcmd_bytes); 116 | } 117 | else if (reg_addr == BQ769X2_CMD_SUBCMD_LOWER && num_bytes == 2) { 118 | /* writing to SUBCMD register initiates a subcmd / data read */ 119 | 120 | uint16_t data_addr = (em_data->direct_mem[BQ769X2_CMD_SUBCMD_UPPER] << 8) 121 | + em_data->direct_mem[BQ769X2_CMD_SUBCMD_LOWER]; 122 | 123 | if (data_addr >= sizeof(em_data->data_mem)) { 124 | return -EINVAL; 125 | } 126 | 127 | bq769x0_emul_process_subcmd(em, data_addr); 128 | 129 | memcpy(&em_data->direct_mem[BQ769X2_SUBCMD_DATA_START], &em_data->data_mem[data_addr], 4); 130 | 131 | // always assume maximum data type length of 4 bytes 132 | uint8_t checksum = em_data->direct_mem[BQ769X2_CMD_SUBCMD_UPPER] 133 | + em_data->direct_mem[BQ769X2_CMD_SUBCMD_LOWER]; 134 | for (int i = 0; i < 4; i++) { 135 | checksum += em_data->direct_mem[BQ769X2_SUBCMD_DATA_START + i]; 136 | } 137 | checksum = ~checksum; 138 | 139 | em_data->direct_mem[BQ769X2_SUBCMD_DATA_LENGTH] = 4 + BQ769X2_SUBCMD_OVERHEAD_BYTES; 140 | em_data->direct_mem[BQ769X2_SUBCMD_DATA_CHECKSUM] = checksum; 141 | } 142 | 143 | return 0; 144 | } 145 | 146 | static int bq769x0_emul_read_bytes(const struct emul *em, const uint8_t reg_addr, uint8_t *data, 147 | const size_t num_bytes) 148 | { 149 | struct bq769x0_emul_data *em_data = em->data; 150 | 151 | if (reg_addr >= sizeof(em_data->direct_mem)) { 152 | return -EINVAL; 153 | } 154 | 155 | memcpy(data, em_data->direct_mem + reg_addr, num_bytes); 156 | 157 | return 0; 158 | } 159 | 160 | static int bq769x0_emul_transfer(const struct emul *em, struct i2c_msg *msgs, int num_msgs, 161 | int addr) 162 | { 163 | if (num_msgs < 1) { 164 | LOG_ERR("Invalid number of messages: %d", num_msgs); 165 | return -EIO; 166 | } 167 | if (msgs[0].len < 1) { 168 | LOG_ERR("Unexpected msg0 length %d", msgs[0].len); 169 | return -EIO; 170 | } 171 | 172 | /* read operations are write-read, so the first message must always be a write */ 173 | if (msgs[0].flags & I2C_MSG_READ) { 174 | LOG_ERR("Unexpected read operation"); 175 | return -EIO; 176 | } 177 | 178 | uint8_t reg_addr = msgs[0].buf[0]; 179 | 180 | if (msgs[0].flags & I2C_MSG_STOP) { 181 | /* simple write operation */ 182 | bq769x0_emul_write_bytes(em, reg_addr, msgs[0].buf + 1, msgs[0].len - 1); 183 | } 184 | else if (num_msgs > 1) { 185 | /* write-read operation with reg_addr in the first msg */ 186 | bq769x0_emul_read_bytes(em, reg_addr, msgs[1].buf, msgs[1].len); 187 | } 188 | else { 189 | LOG_ERR("Unexpected I2C msg. flags: 0x%x, num_msgs: %d", msgs[0].flags, num_msgs); 190 | return -EIO; 191 | } 192 | 193 | return 0; 194 | } 195 | 196 | static struct i2c_emul_api bus_api = { 197 | .transfer = bq769x0_emul_transfer, 198 | }; 199 | 200 | static int bq769x2_emul_init(const struct emul *target, const struct device *parent) 201 | { 202 | return 0; 203 | } 204 | 205 | #define BQ769X2_EMUL(n) \ 206 | static struct bq769x0_emul_data bq769x0_emul_data_##n; \ 207 | static const struct bq769x0_emul_cfg bq769x0_emul_cfg_##n = { \ 208 | .addr = DT_INST_REG_ADDR(n), \ 209 | }; \ 210 | EMUL_DT_INST_DEFINE(n, bq769x2_emul_init, &bq769x0_emul_data_##n, &bq769x0_emul_cfg_##n, \ 211 | &bus_api, NULL) 212 | 213 | DT_INST_FOREACH_STATUS_OKAY(BQ769X2_EMUL) 214 | -------------------------------------------------------------------------------- /drivers/bms_ic/bq769x2/bq769x2_emul.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) The Libre Solar Project Contributors 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #ifndef DRIVERS_BMS_IC_BMS_IC_BQ769X2_EMUL_H_ 8 | #define DRIVERS_BMS_IC_BMS_IC_BQ769X2_EMUL_H_ 9 | 10 | #ifdef __cplusplus 11 | extern "C" { 12 | #endif 13 | 14 | #include 15 | 16 | uint8_t bq769x2_emul_get_direct_mem(const struct emul *em, uint8_t addr); 17 | 18 | void bq769x2_emul_set_direct_mem(const struct emul *em, uint8_t addr, uint8_t byte); 19 | 20 | uint8_t bq769x2_emul_get_data_mem(const struct emul *em, uint16_t addr); 21 | 22 | void bq769x2_emul_set_data_mem(const struct emul *em, uint16_t addr, uint8_t byte); 23 | 24 | #ifdef __cplusplus 25 | } 26 | #endif 27 | 28 | #endif // DRIVERS_BMS_IC_BMS_IC_BQ769X2_EMUL_H_ 29 | -------------------------------------------------------------------------------- /drivers/bms_ic/bq769x2/bq769x2_priv.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) The Libre Solar Project Contributors 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #ifndef DRIVERS_BMS_IC_BMS_IC_BQ769X2_PRIV_H_ 8 | #define DRIVERS_BMS_IC_BMS_IC_BQ769X2_PRIV_H_ 9 | 10 | /** 11 | * @file 12 | * @brief Private functions and definitions for bq769x2 IC driver 13 | */ 14 | 15 | #include 16 | 17 | #include 18 | #include 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | /** 25 | * Writes multiple bytes to bq769x2 IC registers 26 | * 27 | * @param reg_addr The address to write to 28 | * @param data The pointer to the data buffer 29 | * @param num_bytes Number of bytes to write 30 | * 31 | * @returns 0 if successful, negative errno otherwise 32 | */ 33 | typedef int (*bq769x2_write_bytes_t)(const struct device *dev, const uint8_t reg_addr, 34 | const uint8_t *data, const size_t num_bytes); 35 | 36 | /** 37 | * Reads multiple bytes from bq769x2 IC registers 38 | * 39 | * @param reg_addr The address to read the bytes from 40 | * @param data The pointer to where the data should be stored 41 | * @param num_bytes Number of bytes to read 42 | * 43 | * @returns 0 if successful, negative errno otherwise 44 | */ 45 | typedef int (*bq769x2_read_bytes_t)(const struct device *dev, const uint8_t reg_addr, uint8_t *data, 46 | const size_t num_bytes); 47 | 48 | /* read-only driver configuration */ 49 | struct bms_ic_bq769x2_config 50 | { 51 | struct i2c_dt_spec i2c; 52 | struct gpio_dt_spec alert_gpio; 53 | uint32_t shunt_resistor_uohm; 54 | uint32_t board_max_current; 55 | uint16_t used_cell_channels; 56 | uint8_t pin_config[9]; 57 | uint8_t cell_temp_pins[CONFIG_BMS_IC_MAX_THERMISTORS]; 58 | uint8_t num_cell_temps; 59 | uint8_t fet_temp_pin; 60 | bool crc_enabled; 61 | bool auto_pdsg; 62 | uint8_t reg0_config; 63 | uint8_t reg12_config; 64 | uint8_t max_balanced_cells; 65 | bq769x2_write_bytes_t write_bytes; 66 | bq769x2_read_bytes_t read_bytes; 67 | }; 68 | 69 | /* driver run-time data */ 70 | struct bms_ic_bq769x2_data 71 | { 72 | struct bms_ic_data *ic_data; 73 | bool config_update_mode_enabled; 74 | bool auto_balancing; 75 | }; 76 | 77 | #endif /* DRIVERS_BMS_IC_BMS_IC_BQ769X2_PRIV_H_ */ 78 | -------------------------------------------------------------------------------- /drivers/bms_ic/isl94202/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) The Libre Solar Project Contributors 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | zephyr_library() 5 | 6 | zephyr_library_sources(isl94202.c isl94202_interface.c) 7 | 8 | zephyr_library_sources_ifdef(CONFIG_EMUL isl94202_emul.c) 9 | zephyr_include_directories_ifdef(CONFIG_EMUL .) 10 | -------------------------------------------------------------------------------- /drivers/bms_ic/isl94202/isl94202_emul.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) The Libre Solar Project Contributors 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #define DT_DRV_COMPAT renesas_isl94202 8 | 9 | #include "isl94202_registers.h" 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | LOG_MODULE_REGISTER(isl94202_emul, CONFIG_BMS_IC_LOG_LEVEL); 18 | 19 | struct isl94202_emul_data 20 | { 21 | /* Memory of ILS94202 (registers 0x00 to 0xAB) */ 22 | uint8_t mem[0xAB + 1]; 23 | uint32_t cur_reg; 24 | }; 25 | 26 | struct isl94202_emul_cfg 27 | { 28 | uint16_t addr; 29 | }; 30 | 31 | uint8_t isl94202_emul_get_byte(const struct emul *em, uint8_t addr) 32 | { 33 | struct isl94202_emul_data *em_data = em->data; 34 | 35 | return em_data->mem[addr]; 36 | } 37 | 38 | void isl94202_emul_set_byte(const struct emul *em, uint8_t addr, uint8_t byte) 39 | { 40 | struct isl94202_emul_data *em_data = em->data; 41 | 42 | em_data->mem[addr] = byte; 43 | } 44 | 45 | uint16_t isl94202_emul_get_word(const struct emul *em, uint8_t addr) 46 | { 47 | struct isl94202_emul_data *em_data = em->data; 48 | 49 | return em_data->mem[addr] + (em_data->mem[addr + 1] << 8); 50 | } 51 | 52 | void isl94202_emul_set_word(const struct emul *em, uint8_t addr, uint16_t word) 53 | { 54 | struct isl94202_emul_data *em_data = em->data; 55 | 56 | em_data->mem[addr] = word & 0x00FF; 57 | em_data->mem[addr + 1] = word >> 8; 58 | } 59 | 60 | /* fill RAM and flash with suitable values */ 61 | void isl94202_emul_set_mem_defaults(const struct emul *em) 62 | { 63 | struct isl94202_emul_data *em_data = em->data; 64 | 65 | // Cell voltages 66 | // voltage = hexvalue * 1.8 * 8 / (4095 * 3) 67 | // hexvalue = voltage / (1.8 * 8) * 4095 * 3 68 | *((uint16_t *)&em_data->mem[0x90]) = 3.0 / (1.8 * 8) * 4095 * 3; // Cell 1 69 | *((uint16_t *)&em_data->mem[0x92]) = 3.1 / (1.8 * 8) * 4095 * 3; // Cell 2 70 | *((uint16_t *)&em_data->mem[0x94]) = 3.2 / (1.8 * 8) * 4095 * 3; // Cell 3 71 | *((uint16_t *)&em_data->mem[0x96]) = 3.3 / (1.8 * 8) * 4095 * 3; // Cell 4 72 | *((uint16_t *)&em_data->mem[0x98]) = 3.4 / (1.8 * 8) * 4095 * 3; // Cell 5 73 | *((uint16_t *)&em_data->mem[0x9A]) = 3.5 / (1.8 * 8) * 4095 * 3; // Cell 6 74 | *((uint16_t *)&em_data->mem[0x9C]) = 3.6 / (1.8 * 8) * 4095 * 3; // Cell 7 75 | *((uint16_t *)&em_data->mem[0x9E]) = 3.7 / (1.8 * 8) * 4095 * 3; // Cell 8 76 | 77 | // Pack voltage 78 | *((uint16_t *)&em_data->mem[0xA6]) = 3.3 * 8 / (1.8 * 32) * 4095; 79 | } 80 | 81 | static int isl94202_emul_write_bytes(const struct emul *em, const uint8_t reg_addr, 82 | const uint8_t *data, const size_t num_bytes) 83 | { 84 | struct isl94202_emul_data *em_data = em->data; 85 | 86 | if ((reg_addr > 0x58 && reg_addr < 0x7F) || reg_addr + num_bytes > 0xAB || num_bytes > 4) { 87 | return -EINVAL; 88 | } 89 | 90 | memcpy(em_data->mem + reg_addr, data, num_bytes); 91 | 92 | return 0; 93 | } 94 | 95 | static int isl94202_emul_read_bytes(const struct emul *em, const uint8_t reg_addr, uint8_t *data, 96 | const size_t num_bytes) 97 | { 98 | struct isl94202_emul_data *em_data = em->data; 99 | 100 | if ((reg_addr > 0x58 && reg_addr < 0x7F) || reg_addr + num_bytes > 0xAB || num_bytes > 4) { 101 | return -EINVAL; 102 | } 103 | 104 | memcpy(data, em_data->mem + reg_addr, num_bytes); 105 | 106 | return 0; 107 | } 108 | 109 | static int isl94202_emul_transfer(const struct emul *em, struct i2c_msg *msgs, int num_msgs, 110 | int addr) 111 | { 112 | if (num_msgs < 1) { 113 | LOG_ERR("Invalid number of messages: %d", num_msgs); 114 | return -EIO; 115 | } 116 | if (msgs[0].len < 1) { 117 | LOG_ERR("Unexpected msg0 length %d", msgs[0].len); 118 | return -EIO; 119 | } 120 | 121 | /* read operations are write-read, so the first message must always be a write */ 122 | if (msgs[0].flags & I2C_MSG_READ) { 123 | LOG_ERR("Unexpected read operation"); 124 | return -EIO; 125 | } 126 | 127 | uint8_t reg_addr = msgs[0].buf[0]; 128 | 129 | if (msgs[0].flags & I2C_MSG_STOP) { 130 | /* simple write operation */ 131 | isl94202_emul_write_bytes(em, reg_addr, msgs[0].buf + 1, msgs[0].len - 1); 132 | } 133 | else if (num_msgs > 1) { 134 | /* write-read operation with reg_addr in the first msg */ 135 | isl94202_emul_read_bytes(em, reg_addr, msgs[1].buf, msgs[1].len); 136 | } 137 | else { 138 | LOG_ERR("Unexpected I2C msg. flags: 0x%x, num_msgs: %d", msgs[0].flags, num_msgs); 139 | return -EIO; 140 | } 141 | 142 | return 0; 143 | } 144 | 145 | static struct i2c_emul_api bus_api = { 146 | .transfer = isl94202_emul_transfer, 147 | }; 148 | 149 | static int isl94202_emul_init(const struct emul *target, const struct device *parent) 150 | { 151 | return 0; 152 | } 153 | 154 | #define ISL94202_EMUL(n) \ 155 | static struct isl94202_emul_data isl94202_emul_data_##n; \ 156 | static const struct isl94202_emul_cfg isl94202_emul_cfg_##n = { \ 157 | .addr = DT_INST_REG_ADDR(n), \ 158 | }; \ 159 | EMUL_DT_INST_DEFINE(n, isl94202_emul_init, &isl94202_emul_data_##n, &isl94202_emul_cfg_##n, \ 160 | &bus_api, NULL) 161 | 162 | DT_INST_FOREACH_STATUS_OKAY(ISL94202_EMUL) 163 | -------------------------------------------------------------------------------- /drivers/bms_ic/isl94202/isl94202_emul.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) The Libre Solar Project Contributors 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #ifndef DRIVERS_BMS_IC_BMS_IC_ISL94202_EMUL_H_ 8 | #define DRIVERS_BMS_IC_BMS_IC_ISL94202_EMUL_H_ 9 | 10 | #ifdef __cplusplus 11 | extern "C" { 12 | #endif 13 | 14 | #include 15 | 16 | uint8_t isl94202_emul_get_byte(const struct emul *em, uint8_t addr); 17 | 18 | void isl94202_emul_set_byte(const struct emul *em, uint8_t addr, uint8_t byte); 19 | 20 | uint16_t isl94202_emul_get_word(const struct emul *em, uint8_t addr); 21 | 22 | void isl94202_emul_set_word(const struct emul *em, uint8_t addr, uint16_t word); 23 | 24 | void isl94202_emul_set_mem_defaults(const struct emul *em); 25 | 26 | #ifdef __cplusplus 27 | } 28 | #endif 29 | 30 | #endif // DRIVERS_BMS_IC_BMS_IC_ISL94202_EMUL_H_ 31 | -------------------------------------------------------------------------------- /drivers/bms_ic/isl94202/isl94202_interface.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) The Libre Solar Project Contributors 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #include "isl94202_interface.h" 8 | #include "isl94202_priv.h" 9 | #include "isl94202_registers.h" 10 | 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | LOG_MODULE_REGISTER(isl94202_if, CONFIG_LOG_DEFAULT_LEVEL); 18 | 19 | int isl94202_write_bytes(const struct device *dev, uint8_t reg_addr, uint8_t *data, 20 | uint32_t num_bytes) 21 | { 22 | const struct bms_ic_isl94202_config *dev_config = dev->config; 23 | 24 | uint8_t buf[5]; 25 | if ((reg_addr > 0x58 && reg_addr < 0x7F) || reg_addr + num_bytes > 0xAB || num_bytes > 4) 26 | return -1; 27 | 28 | buf[0] = reg_addr; // first byte contains register address 29 | memcpy(buf + 1, data, num_bytes); 30 | 31 | return i2c_write_dt(&dev_config->i2c, buf, num_bytes + 1); 32 | } 33 | 34 | int isl94202_read_bytes(const struct device *dev, uint8_t reg_addr, uint8_t *data, 35 | uint32_t num_bytes) 36 | { 37 | const struct bms_ic_isl94202_config *dev_config = dev->config; 38 | 39 | return i2c_write_read_dt(&dev_config->i2c, ®_addr, 1, data, num_bytes); 40 | } 41 | 42 | int isl94202_write_word(const struct device *dev, uint8_t reg_addr, uint16_t word) 43 | { 44 | uint8_t buf[2] = { word, word >> 8 }; 45 | 46 | return isl94202_write_bytes(dev, reg_addr, buf, 2); 47 | } 48 | 49 | int isl94202_write_delay(const struct device *dev, uint8_t reg_addr, uint8_t delay_unit, 50 | uint16_t delay_value, uint8_t extra_bits) 51 | { 52 | if (delay_unit > ISL94202_DELAY_MIN || extra_bits > 0xF) { 53 | return -1; 54 | } 55 | 56 | uint8_t unit_final = delay_unit; 57 | uint16_t value_final = delay_value; 58 | if (value_final > 1023) { 59 | if (unit_final < ISL94202_DELAY_S) { 60 | value_final = value_final / 1000; 61 | } 62 | else if (unit_final == ISL94202_DELAY_S) { 63 | value_final = value_final / 60; 64 | } 65 | else { 66 | return -1; 67 | } 68 | unit_final++; 69 | } 70 | 71 | /* delay value: bits 0-9, unit: bits A-B, extra bits: C-E */ 72 | uint16_t reg = value_final | (unit_final << 0xA) | (extra_bits << 0xC); 73 | 74 | return isl94202_write_word(dev, reg_addr, reg); 75 | } 76 | 77 | int isl94202_write_current_limit(const struct device *dev, uint8_t reg_addr, 78 | const uint16_t *voltage_thresholds_mv, int num_thresholds, 79 | float *current_limit, float shunt_res_mohm, uint8_t delay_unit, 80 | uint16_t delay_value) 81 | { 82 | uint8_t threshold_raw = 0; 83 | float actual_current_limit; 84 | int err; 85 | 86 | if (current_limit == NULL) { 87 | return -EINVAL; 88 | } 89 | 90 | /* initialize with lowest value */ 91 | actual_current_limit = voltage_thresholds_mv[0] / shunt_res_mohm; 92 | 93 | /* choose lower current limit if target setting not exactly possible */ 94 | for (int i = num_thresholds - 1; i >= 0; i--) { 95 | if ((uint16_t)(*current_limit * shunt_res_mohm) >= voltage_thresholds_mv[i]) { 96 | threshold_raw = (uint8_t)i; 97 | actual_current_limit = voltage_thresholds_mv[i] / shunt_res_mohm; 98 | break; 99 | } 100 | } 101 | 102 | err = isl94202_write_delay(dev, reg_addr, delay_unit, delay_value, threshold_raw); 103 | if (err == 0) { 104 | *current_limit = actual_current_limit; 105 | } 106 | 107 | return err; 108 | } 109 | 110 | int isl94202_write_voltage(const struct device *dev, uint8_t reg_addr, float voltage, 111 | uint8_t extra_bits) 112 | { 113 | uint16_t reg = extra_bits << 12; 114 | 115 | uint16_t voltage_raw = voltage * 4095 * 3 / 1.8F / 8; 116 | 117 | if (voltage_raw > 0x0FFF) { 118 | return -1; 119 | } 120 | 121 | reg |= (voltage_raw & 0x0FFF); 122 | 123 | return isl94202_write_word(dev, reg_addr, reg); 124 | } 125 | 126 | int isl94202_read_word(const struct device *dev, uint8_t reg_addr, uint16_t *value) 127 | { 128 | uint8_t buf[2] = { 0 }; 129 | if (isl94202_read_bytes(dev, reg_addr, buf, 2) == 0) { 130 | *value = buf[0] + (buf[1] << 8); 131 | return 0; 132 | } 133 | else { 134 | return -1; 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /drivers/bms_ic/isl94202/isl94202_interface.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) The Libre Solar Project Contributors 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #ifndef DRIVERS_BMS_IC_ISL94202_INTERFACE_H_ 8 | #define DRIVERS_BMS_IC_ISL94202_INTERFACE_H_ 9 | 10 | /** 11 | * @file 12 | * @brief Hardware interface for ISL94202 IC 13 | */ 14 | 15 | #ifdef __cplusplus 16 | extern "C" { 17 | #endif 18 | 19 | #include 20 | 21 | #include 22 | #include 23 | 24 | /** 25 | * Write multiple bytes to ISL94202 IC registers 26 | * 27 | * @param dev Pointer to the driver device structure instance 28 | * @param reg_addr The address to write to 29 | * @param data The pointer to the data buffer 30 | * @param num_bytes Number of bytes to write 31 | * 32 | * @returns 0 on success, otherwise negative error code. 33 | */ 34 | int isl94202_write_bytes(const struct device *dev, uint8_t reg_addr, uint8_t *data, 35 | uint32_t num_bytes); 36 | 37 | /** 38 | * Write a word (two bytes) to ISL94202 IC registers 39 | * 40 | * @param dev Pointer to the driver device structure instance 41 | * @param reg_addr The address to write to 42 | * @param word The word to be written 43 | * 44 | * @returns 0 on success, otherwise negative error code. 45 | */ 46 | int isl94202_write_word(const struct device *dev, uint8_t reg_addr, uint16_t word); 47 | 48 | /** 49 | * Read multiple bytes from ISL94202 IC registers 50 | * 51 | * @param dev Pointer to the driver device structure instance 52 | * @param reg_addr The address to read the bytes from 53 | * @param data The pointer to where the data should be stored 54 | * @param num_bytes Number of bytes to read 55 | * 56 | * @returns 0 on success, otherwise negative error code. 57 | */ 58 | int isl94202_read_bytes(const struct device *dev, uint8_t reg_addr, uint8_t *data, 59 | uint32_t num_bytes); 60 | 61 | /** 62 | * Read a word from ISL94202 IC registers 63 | * 64 | * @param dev Pointer to the driver device structure instance 65 | * @param reg_addr The address of the word 66 | * @param value Pointer to the variable to store the result 67 | * 68 | * @returns 0 on success, otherwise negative error code. 69 | */ 70 | int isl94202_read_word(const struct device *dev, uint8_t reg_addr, uint16_t *value); 71 | 72 | /** 73 | * Write a delay + extra bits to specified register 74 | * 75 | * @param dev Pointer to the driver device structure instance 76 | * @param reg_addr Register address 77 | * @param delay_unit Unit (us, ms, s or min) of the threshold value 78 | * @param delay_value Value of the delay in the given unit 79 | * @param extra_bits Four extra bits C-F 80 | * 81 | * @returns 0 on success, otherwise negative error code. 82 | */ 83 | int isl94202_write_delay(const struct device *dev, uint8_t reg_addr, uint8_t delay_unit, 84 | uint16_t delay_value, uint8_t extra_bits); 85 | 86 | /** 87 | * Write a current limit (threshold + delay) to specified register 88 | * 89 | * The actual current limit is written back to the current_limit parameter. 90 | * 91 | * @param dev Pointer to the driver device structure instance 92 | * @param reg_addr Register address 93 | * @param voltage_thresholds_mV Array of threshold values as defined in datasheet (mV) 94 | * @param num_thresholds Number of elements in array voltage_thresholds 95 | * @param current_limit Pointer to current limit threshold (A) 96 | * @param shunt_res_mOhm Resistance of the current measurement shunt (mOhm) 97 | * @param delay_unit Unit (us, ms, s or min) of the threshold value 98 | * @param delay_value Value of the delay in the given unit 99 | * 100 | * @returns 0 on success, otherwise negative error code. 101 | */ 102 | int isl94202_write_current_limit(const struct device *dev, uint8_t reg_addr, 103 | const uint16_t *voltage_thresholds_mV, int num_thresholds, 104 | float *current_limit, float shunt_res_mOhm, uint8_t delay_unit, 105 | uint16_t delay_value); 106 | 107 | /** 108 | * Write a voltage setting to specified register 109 | * 110 | * @param dev Pointer to the driver device structure instance 111 | * @param reg_addr Register address 112 | * @param voltage Voltage setting (V) 113 | * @param extra_bits Four extra bits left of voltage setting, set to 0 if not applicable 114 | * 115 | * @returns 0 on success, otherwise negative error code. 116 | */ 117 | int isl94202_write_voltage(const struct device *dev, uint8_t reg_addr, float voltage, 118 | uint8_t extra_bits); 119 | 120 | #ifdef __cplusplus 121 | } 122 | #endif 123 | 124 | #endif /* DRIVERS_BMS_IC_ISL94202_INTERFACE_H_ */ 125 | -------------------------------------------------------------------------------- /drivers/bms_ic/isl94202/isl94202_priv.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) The Libre Solar Project Contributors 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #ifndef DRIVERS_BMS_IC_BMS_IC_ISL94202_PRIV_H_ 8 | #define DRIVERS_BMS_IC_BMS_IC_ISL94202_PRIV_H_ 9 | 10 | /** 11 | * @file 12 | * @brief Private functions and definitions for isl94202 IC driver 13 | */ 14 | 15 | #include 16 | 17 | #include 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | /* read-only driver configuration */ 24 | struct bms_ic_isl94202_config 25 | { 26 | struct i2c_dt_spec i2c; 27 | struct gpio_dt_spec i2c_pullup; 28 | uint32_t shunt_resistor_uohm; 29 | uint32_t board_max_current; 30 | uint8_t used_cell_channels; 31 | }; 32 | 33 | /* driver run-time data */ 34 | struct bms_ic_isl94202_data 35 | { 36 | struct bms_ic_data *ic_data; 37 | const struct device *dev; 38 | struct k_work_delayable balancing_work; 39 | uint8_t fet_state; 40 | bool auto_balancing; 41 | }; 42 | 43 | #endif /* DRIVERS_BMS_IC_BMS_IC_ISL94202_PRIV_H_ */ 44 | -------------------------------------------------------------------------------- /dts/bindings/bms.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) The Libre Solar Project Contributors 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | description: Generic BMS device setup 5 | 6 | compatible: "bms" 7 | 8 | properties: 9 | type: 10 | type: string 11 | required: true 12 | description: Device type 13 | version-str: 14 | type: string 15 | required: true 16 | description: Hardware version number (human-readable) 17 | version-num: 18 | type: int 19 | required: true 20 | description: Hardware version number (usable in preprocessor defines) 21 | -------------------------------------------------------------------------------- /dts/bindings/bms_ic/bms-ic-common.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) The Libre Solar Project Contributors 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | description: Intersil/Renesas ISL94202 Standalone 3 to 8 Cell Li-Ion Battery Pack Monitor 5 | 6 | properties: 7 | shunt-resistor-uohm: 8 | type: int 9 | description: | 10 | Shunt resistor value in micro-Ohm. 11 | 12 | board-max-current: 13 | type: int 14 | description: | 15 | Maximum current allowed by the board design in Amperes. 16 | -------------------------------------------------------------------------------- /dts/bindings/bms_ic/renesas,isl94202.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) The Libre Solar Project Contributors 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | description: Intersil/Renesas ISL94202 Standalone 3 to 8 Cell Li-Ion Battery Pack Monitor 5 | 6 | compatible: "renesas,isl94202" 7 | 8 | include: [ bms-ic-common.yaml, i2c-device.yaml ] 9 | 10 | properties: 11 | pull-up-gpios: 12 | type: phandle-array 13 | required: false 14 | description: | 15 | GPIO to enable/disable the I2C pull-up resistors. 16 | 17 | used-cell-channels: 18 | type: int 19 | required: true 20 | description: | 21 | Bitset of cell channels used by the board. 22 | 23 | Example for 5s configuration: 0x1F 24 | -------------------------------------------------------------------------------- /dts/bindings/bms_ic/ti,bq769x0.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) The Libre Solar Project Contributors 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | description: Texas Instruments bq769x0 li-ion and li-phosphate battery monitor 5 | 6 | compatible: "ti,bq769x0" 7 | 8 | include: [ bms-ic-common.yaml, i2c-device.yaml ] 9 | 10 | properties: 11 | alert-gpios: 12 | type: phandle-array 13 | required: true 14 | description: Alert interrupt of bq769x0 15 | 16 | used-cell-channels: 17 | type: int 18 | required: true 19 | description: | 20 | Bitset of cell channels used by the board. See below examples for the maximum number of 21 | cells supported by the different chips: 22 | 23 | bq76920 (3-5s): 0b0000_0000_0001_1111 = 0x001F 24 | bq76930 (6-10s): 0b0000_0011_1111_1111 = 0x03FF 25 | bq76940 (9-15s): 0b0111_1111_1111_1111 = 0x7FFF 26 | 27 | bus-pchg-gpios: 28 | type: phandle-array 29 | required: false 30 | description: | 31 | GPIO to enable/disable the bus pre-charge MOSFET. 32 | 33 | thermistor-beta: 34 | type: int 35 | default: 3435 36 | description: | 37 | Thermistor beta value. 38 | 39 | Default is typical value for Semitec 103AT-5 thermistor. 40 | -------------------------------------------------------------------------------- /dts/bindings/bms_ic/ti,bq769x2-common.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) The Libre Solar Project Contributors 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | include: bms-ic-common.yaml 5 | 6 | properties: 7 | crc-enabled: 8 | type: boolean 9 | description: | 10 | Enable CRC for communication. This option depends on the exact part number used. 11 | 12 | alert-gpios: 13 | type: phandle-array 14 | required: true 15 | description: Alert interrupt of bq769x2 16 | 17 | used-cell-channels: 18 | type: int 19 | required: true 20 | description: | 21 | Bitset of cell channels used by the board. The supported channels for a particular chip 22 | correspond to the Settings:Configuration:Vcell Mode register. See below examples for the 23 | maximum number of cells supported by the different chips: 24 | 25 | bq76952 (3-16s): 0b1111_1111_1111_1111 = 0xFFFF 26 | bq769142 (3-14s): 0b1010_1111_1111_1111 = 0xAFFF 27 | bq76942 (3-10s): 0b0000_0011_1111_1111 = 0x03FF 28 | 29 | cfetoff-pin-config: 30 | type: int 31 | description: | 32 | Configuration of the CFETOFF pin. See Reference manual for details. 33 | 34 | dfetoff-pin-config: 35 | type: int 36 | description: | 37 | Configuration of the DFETOFF pin. See Reference manual for details. 38 | 39 | alert-pin-config: 40 | type: int 41 | description: | 42 | Configuration of the ALERT pin. See Reference manual for details. 43 | 44 | ts1-pin-config: 45 | type: int 46 | description: | 47 | Configuration of the TS1 pin. See Reference manual for details. 48 | 49 | ts2-pin-config: 50 | type: int 51 | description: | 52 | Configuration of the TS2 pin. See Reference manual for details. 53 | 54 | ts3-pin-config: 55 | type: int 56 | description: | 57 | Configuration of the TS3 pin. See Reference manual for details. 58 | 59 | hdq-pin-config: 60 | type: int 61 | description: | 62 | Configuration of the HDQ pin. See Reference manual for details. 63 | 64 | dchg-pin-config: 65 | type: int 66 | description: | 67 | Configuration of the DCHG pin. See Reference manual for details. 68 | 69 | ddsg-pin-config: 70 | type: int 71 | description: | 72 | Configuration of the DDSG pin. See Reference manual for details. 73 | 74 | cell-temp-pins: 75 | type: array 76 | required: true 77 | description: | 78 | Pins used for cell temperature sensing. The *-pin-config property of that pin has to be 79 | configured accordingly. 80 | 81 | Up to CONFIG_BMS_IC_MAX_THERMISTORS pins can be configured. 82 | 83 | See include/zephyr/dt-bindings/bms_ic/bq769x2.h for allowed values. 84 | 85 | fet-temp-pin: 86 | type: int 87 | enum: 88 | - 0 # CFETOFF 89 | - 1 # DFETOFF 90 | - 2 # ALERT 91 | - 3 # TS1 92 | - 4 # TS2 93 | - 5 # TS3 94 | - 6 # HDQ 95 | - 7 # DCHG 96 | - 8 # DDSG 97 | description: | 98 | Pin used for MOSFET temperature sensing. The *-pin-config property of that pin has to be 99 | configured accordingly. 100 | 101 | See include/zephyr/dt-bindings/bms_ic/bq769x2.h for allowed values. 102 | 103 | auto-pdsg: 104 | type: boolean 105 | description: | 106 | Enable automatic pre-discharge (bus precharge) before switching on DSG FETs. 107 | 108 | reg0-config: 109 | type: int 110 | default: 0 111 | description: | 112 | Configuration of the REG0 Config Register. REG0 is the pre-regulator for REG1/2 and must be 113 | enabled if REG1 or REG2 are used and REGIN is not supplied externally. 114 | Valid values: 0 and 1 115 | 116 | reg12-config: 117 | type: int 118 | default: 0 119 | description: | 120 | Configuration of the REG12 Config Register. REG2 is configured in the higher nibble and REG1 121 | in the lower nibble of this U1 register. 122 | Example: 0xDC to set both regulators to 3.3V and enable REG2 by default. 123 | 124 | max-balanced-cells: 125 | type: int 126 | default: 4 127 | description: | 128 | Maximum number of cells to be balanced at once. This board-specific value depends on the heat 129 | dissipation of the chip. 130 | -------------------------------------------------------------------------------- /dts/bindings/bms_ic/ti,bq769x2-i2c.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) The Libre Solar Project Contributors 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | description: | 5 | Texas Instruments BQ769x2 3s to 16s High Accuracy Battery Monitor and 6 | Protector for Li-Ion, Li-Polymer, and LiFePO4 Battery Packs (I2C) 7 | 8 | compatible: "ti,bq769x2-i2c" 9 | 10 | include: ["ti,bq769x2-common.yaml", "i2c-device.yaml"] 11 | -------------------------------------------------------------------------------- /dts/bindings/bms_ic/ti,bq769x2-spi.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) The Libre Solar Project Contributors 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | description: | 5 | Texas Instruments BQ769x2 3s to 16s High Accuracy Battery Monitor and 6 | Protector for Li-Ion, Li-Polymer, and LiFePO4 Battery Packs (SPI) 7 | 8 | compatible: "ti,bq769x2-spi" 9 | 10 | include: ["ti,bq769x2-common.yaml", "spi-device.yaml"] 11 | -------------------------------------------------------------------------------- /include/bms/bms.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) The Libre Solar Project Contributors 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #ifndef ZEPHYR_BMS_BMS_H_ 8 | #define ZEPHYR_BMS_BMS_H_ 9 | 10 | /** 11 | * @file 12 | * @brief Battery Management System (BMS) high-level API 13 | */ 14 | 15 | #ifdef __cplusplus 16 | extern "C" { 17 | #endif 18 | 19 | #include 20 | 21 | #include "bms_common.h" 22 | 23 | #include 24 | #include 25 | 26 | /* fixed number of OCV vs. SOC points */ 27 | #define NUM_OCV_POINTS 21 28 | 29 | /** 30 | * Possible BMS states 31 | */ 32 | enum bms_state 33 | { 34 | BMS_STATE_OFF, ///< Off state (charging and discharging disabled) 35 | BMS_STATE_CHG, ///< Charging state (discharging disabled) 36 | BMS_STATE_DIS, ///< Discharging state (charging disabled) 37 | BMS_STATE_NORMAL, ///< Normal operating mode (both charging and discharging enabled) 38 | BMS_STATE_SHUTDOWN, ///< BMS starting shutdown sequence 39 | }; 40 | 41 | /** 42 | * Battery cell types 43 | */ 44 | enum bms_cell_type 45 | { 46 | CELL_TYPE_LFP, ///< LiFePO4 Li-ion cells (3.3 V nominal) 47 | CELL_TYPE_NMC, ///< NMC/Graphite Li-ion cells (3.7 V nominal) 48 | CELL_TYPE_LTO, ///< NMC/Titanate (2.4 V nominal) 49 | }; 50 | 51 | /** 52 | * Battery Management System context information 53 | */ 54 | struct bms_context 55 | { 56 | /** Current state of the battery */ 57 | enum bms_state state; 58 | 59 | /** Manual enable/disable setting for charging */ 60 | bool chg_enable; 61 | /** Manual enable/disable setting for discharging */ 62 | bool dis_enable; 63 | 64 | /** CV charging to cell_chg_voltage_limit finished */ 65 | bool full; 66 | /** Battery is discharged below cell_dis_voltage_limit */ 67 | bool empty; 68 | 69 | /** Calculated State of Charge (%) */ 70 | float soc; 71 | 72 | /** Nominal capacity of battery pack (Ah) */ 73 | float nominal_capacity_Ah; 74 | 75 | /** 76 | * Pointer to an array containing the Open Circuit Voltage of the cell vs. SOC. The array 77 | * must be spaced the same as the soc_points. 78 | */ 79 | float *ocv_points; 80 | 81 | /** Pointer to an array containing the State of Charge points for the OCV. */ 82 | float *soc_points; 83 | 84 | /** BMS IC device pointer */ 85 | const struct device *ic_dev; 86 | 87 | /** BMS IC configuration applied during start-up. */ 88 | struct bms_ic_conf ic_conf; 89 | 90 | /** BMS IC data read from the device. */ 91 | struct bms_ic_data ic_data; 92 | }; 93 | 94 | /** 95 | * Initialization of struct bms_ic_conf with typical default values for the given cell type. 96 | * 97 | * @param bms Pointer to BMS object. 98 | * @param type One of enum CellType (defined as int so that it can be set via Kconfig). 99 | * @param capacity_Ah Nominal capacity of the battery pack. 100 | */ 101 | void bms_init_config(struct bms_context *bms, enum bms_cell_type type, float capacity_Ah); 102 | 103 | /** 104 | * Main BMS state machine 105 | * 106 | * @param bms Pointer to BMS object. 107 | */ 108 | void bms_state_machine(struct bms_context *bms); 109 | 110 | /** 111 | * Switch off MOSFETs and go into the shutdown state 112 | * 113 | * @param bms Pointer to BMS object. 114 | */ 115 | void bms_shutdown(struct bms_context *bms); 116 | 117 | /** 118 | * Update SOC based on most recent current measurement 119 | * 120 | * Function should be called each time after a new current measurement was obtained. 121 | * 122 | * @param bms Pointer to BMS object. 123 | */ 124 | void bms_soc_update(struct bms_context *bms); 125 | 126 | /** 127 | * Reset SOC to specified value or calculate based on average cell open circuit voltage 128 | * 129 | * @param bms Pointer to BMS object. 130 | * @param percent 0-100 %, -1 for calculation based on OCV 131 | */ 132 | void bms_soc_reset(struct bms_context *bms, int percent); 133 | 134 | /** 135 | * Charging error flags check 136 | * 137 | * @returns true if any charging error flag is set 138 | */ 139 | bool bms_chg_error(uint32_t error_flags); 140 | 141 | /** 142 | * Discharging error flags check 143 | * 144 | * @returns true if any discharging error flag is set 145 | */ 146 | bool bms_dis_error(uint32_t error_flags); 147 | 148 | /** 149 | * Check if charging is allowed 150 | * 151 | * @returns true if no charging error flags are set 152 | */ 153 | bool bms_chg_allowed(struct bms_context *bms); 154 | 155 | /** 156 | * Check if discharging is allowed 157 | * 158 | * @param bms Pointer to BMS object. 159 | * 160 | * @returns true if no discharging error flags are set 161 | */ 162 | bool bms_dis_allowed(struct bms_context *bms); 163 | 164 | #ifdef __cplusplus 165 | } 166 | #endif 167 | 168 | #endif /* ZEPHYR_BMS_BMS_H_ */ 169 | -------------------------------------------------------------------------------- /include/bms/bms_common.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) The Libre Solar Project Contributors 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #ifndef INCLUDE_ZEPHYR_BMS_BMS_COMMON_H_ 8 | #define INCLUDE_ZEPHYR_BMS_BMS_COMMON_H_ 9 | 10 | /** 11 | * @file 12 | * @brief Battery Management System (BMS) common defines and structs 13 | */ 14 | 15 | #ifdef __cplusplus 16 | extern "C" { 17 | #endif 18 | 19 | /* 20 | * BMS switches (MOSFETs or contactors) 21 | */ 22 | #define BMS_SWITCH_CHG BIT(0) 23 | #define BMS_SWITCH_DIS BIT(1) 24 | #define BMS_SWITCH_PDSG BIT(2) 25 | #define BMS_SWITCH_PCHG BIT(3) 26 | 27 | /* 28 | * BMS error flags 29 | */ 30 | #define BMS_ERR_CELL_UNDERVOLTAGE BIT(0) ///< Cell undervoltage flag 31 | #define BMS_ERR_CELL_OVERVOLTAGE BIT(1) ///< Cell overvoltage flag 32 | #define BMS_ERR_SHORT_CIRCUIT BIT(2) ///< Pack short circuit (discharge direction) 33 | #define BMS_ERR_DIS_OVERCURRENT BIT(3) ///< Pack overcurrent (discharge direction) 34 | #define BMS_ERR_CHG_OVERCURRENT BIT(4) ///< Pack overcurrent (charge direction) 35 | #define BMS_ERR_OPEN_WIRE BIT(5) ///< Cell open wire 36 | #define BMS_ERR_DIS_UNDERTEMP BIT(6) ///< Temperature below discharge minimum limit 37 | #define BMS_ERR_DIS_OVERTEMP BIT(7) ///< Temperature above discharge maximum limit 38 | #define BMS_ERR_CHG_UNDERTEMP BIT(8) ///< Temperature below charge maximum limit 39 | #define BMS_ERR_CHG_OVERTEMP BIT(9) ///< Temperature above charge maximum limit 40 | #define BMS_ERR_INT_OVERTEMP BIT(10) ///< Internal temperature above limit (e.g. BMS IC) 41 | #define BMS_ERR_CELL_FAILURE BIT(11) ///< Cell failure (too high voltage difference) 42 | #define BMS_ERR_DIS_OFF BIT(12) ///< Discharge FET is off even though it should be on 43 | #define BMS_ERR_CHG_OFF BIT(13) ///< Charge FET is off even though it should be on 44 | #define BMS_ERR_FET_OVERTEMP BIT(14) ///< MOSFET temperature above limit 45 | #define BMS_ERR_ALL GENMASK(14, 0) 46 | 47 | #ifdef __cplusplus 48 | } 49 | #endif 50 | 51 | #endif /* INCLUDE_ZEPHYR_BMS_BMS_COMMON_H_ */ 52 | -------------------------------------------------------------------------------- /include/dt-bindings/bms_ic/bq769x2.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) The Libre Solar Project Contributors 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #ifndef INCLUDE_ZEPHYR_DT_BINDINGS_BMS_IC_BQ769X2_H_ 8 | #define INCLUDE_ZEPHYR_DT_BINDINGS_BMS_IC_BQ769X2_H_ 9 | 10 | /* multi-function pins, which can be used for cell and FET temperature measurement */ 11 | #define BQ769X2_PIN_CFETOFF 0 12 | #define BQ769X2_PIN_DFETOFF 1 13 | #define BQ769X2_PIN_ALERT 2 14 | #define BQ769X2_PIN_TS1 3 15 | #define BQ769X2_PIN_TS2 4 16 | #define BQ769X2_PIN_TS3 5 17 | #define BQ769X2_PIN_HDQ 6 18 | #define BQ769X2_PIN_DCHG 7 19 | #define BQ769X2_PIN_DDSG 8 20 | 21 | #endif /* INCLUDE_ZEPHYR_DT_BINDINGS_BMS_IC_BQ769X2_H_ */ 22 | -------------------------------------------------------------------------------- /include/helper.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) The Libre Solar Project Contributors 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #ifndef HELPER_H 8 | #define HELPER_H 9 | 10 | /** 11 | * @file 12 | * @brief General helper functions 13 | */ 14 | 15 | #include 16 | #include 17 | 18 | #ifdef __INTELLISENSE__ 19 | /* 20 | * VS Code intellisense can't cope with all the Zephyr macro layers for logging, so provide it 21 | * with something more simple and make it silent. 22 | */ 23 | 24 | #define LOG_DBG(...) printf(__VA_ARGS__) 25 | 26 | #define LOG_INF(...) printf(__VA_ARGS__) 27 | 28 | #define LOG_WRN(...) printf(__VA_ARGS__) 29 | 30 | #define LOG_ERR(...) printf(__VA_ARGS__) 31 | 32 | #define LOG_MODULE_REGISTER(...) 33 | 34 | #else 35 | 36 | #include 37 | 38 | #endif /* VSCODE_INTELLISENSE_HACK */ 39 | 40 | #ifdef __cplusplus 41 | extern "C" { 42 | #endif 43 | 44 | /** 45 | * Interpolation in a look-up table. Values of a must be monotonically increasing/decreasing 46 | * 47 | * @returns interpolated value of array b at position value_a 48 | */ 49 | float interpolate(const float a[], const float b[], size_t size, float value_a); 50 | 51 | /** 52 | * Convert byte to bit-string 53 | * 54 | * Attention: Uses static buffer, not thread-safe 55 | * 56 | * @returns pointer to bit-string (8 characters + null-byte) 57 | */ 58 | const char *byte2bitstr(uint8_t b); 59 | 60 | /** 61 | * @brief Check if a buffer is entirely zeroed. 62 | * 63 | * @param buf A pointer to the buffer (uint8_t*) to be checked. 64 | * @param size The size of the buffer in bytes. 65 | * @returns true if the buffer is entirely zeroed, false if not. 66 | */ 67 | bool is_empty(uint8_t *buf, size_t size); 68 | 69 | #ifdef __cplusplus 70 | } 71 | #endif 72 | 73 | #endif /* HELPER_H */ 74 | -------------------------------------------------------------------------------- /lib/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) The Libre Solar Project Contributors 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | zephyr_library() 5 | 6 | zephyr_library_sources(helper.c) 7 | -------------------------------------------------------------------------------- /lib/helper.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) The Libre Solar Project Contributors 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #include "helper.h" 8 | 9 | float interpolate(const float a[], const float b[], size_t size, float value_a) 10 | { 11 | if (a[0] < a[size - 1]) { 12 | for (unsigned int i = 0; i < size; i++) { 13 | if (value_a <= a[i]) { 14 | if (i == 0) { 15 | return b[0]; // value_a smaller than first element 16 | } 17 | else { 18 | return b[i - 1] + (b[i] - b[i - 1]) * (value_a - a[i - 1]) / (a[i] - a[i - 1]); 19 | } 20 | } 21 | } 22 | return b[size - 1]; // value_a larger than last element 23 | } 24 | else { 25 | for (unsigned int i = 0; i < size; i++) { 26 | if (value_a >= a[i]) { 27 | if (i == 0) { 28 | return b[0]; // value_a smaller than first element 29 | } 30 | else { 31 | return b[i - 1] + (b[i] - b[i - 1]) * (value_a - a[i - 1]) / (a[i] - a[i - 1]); 32 | } 33 | } 34 | } 35 | return b[size - 1]; // value_a larger than last element 36 | } 37 | } 38 | 39 | const char *byte2bitstr(uint8_t b) 40 | { 41 | static char str[9]; 42 | 43 | str[0] = '\0'; 44 | for (int z = 128; z > 0; z >>= 1) { 45 | strcat(str, ((b & z) == z) ? "1" : "0"); 46 | } 47 | 48 | return str; 49 | } 50 | 51 | bool is_empty(uint8_t *buf, size_t size) 52 | { 53 | 54 | for (size_t i = 0; i < size; i++) { 55 | if (buf[i] != 0) { 56 | return false; 57 | } 58 | } 59 | 60 | return true; 61 | } 62 | -------------------------------------------------------------------------------- /scripts/check-style.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright (c) The Libre Solar Project Contributors 4 | # SPDX-License-Identifier: Apache-2.0 5 | 6 | DEFAULT_BRANCH="origin/main" 7 | 8 | if [ -x "$(command -v clang-format-diff)" ]; then 9 | CLANG_FORMAT_DIFF="clang-format-diff" 10 | elif [ -x "$(command -v clang-format-diff-15)" ]; then 11 | CLANG_FORMAT_DIFF="clang-format-diff-15" 12 | elif [ -x "$(command -v /usr/share/clang/clang-format-diff.py)" ]; then 13 | CLANG_FORMAT_DIFF="/usr/share/clang/clang-format-diff.py -v" 14 | fi 15 | 16 | echo "Style check for diff between branch $DEFAULT_BRANCH" 17 | 18 | STYLE_ERROR=0 19 | 20 | echo "Checking trailing whitespaces with git diff --check" 21 | git diff --check --color=always $DEFAULT_BRANCH 22 | if [[ $? -ne 0 ]]; then 23 | STYLE_ERROR=1 24 | else 25 | echo "No trailing whitespaces found." 26 | fi 27 | 28 | echo "Checking coding style with clang-format" 29 | 30 | # clang-format-diff returns 0 even for style differences, so we have to check the length of the 31 | # response 32 | CLANG_FORMAT_DIFF=`git diff $DEFAULT_BRANCH | $CLANG_FORMAT_DIFF -p1 | colordiff` 33 | if [[ "$(echo -n $CLANG_FORMAT_DIFF | wc -c)" -ne 0 ]]; then 34 | echo "${CLANG_FORMAT_DIFF}" 35 | STYLE_ERROR=1 36 | else 37 | echo "Coding style valid." 38 | fi 39 | 40 | exit $STYLE_ERROR 41 | -------------------------------------------------------------------------------- /tests/bms/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | 3 | cmake_minimum_required(VERSION 3.20.0) 4 | find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) 5 | 6 | project(bms_common_test) 7 | 8 | FILE(GLOB app_sources src/*.c) 9 | target_sources(app PRIVATE ${app_sources}) 10 | 11 | target_sources(app PRIVATE ../../app/src/bms_common.c) 12 | -------------------------------------------------------------------------------- /tests/bms/boards/native_sim.overlay: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) The Libre Solar Project Contributors 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #include 8 | 9 | / { 10 | pcb { 11 | compatible = "bms"; 12 | 13 | type = "Native Simulator BMS"; 14 | version-str = "v0.1"; 15 | version-num = <1>; 16 | }; 17 | 18 | chosen { 19 | zephyr,console = &uart0; 20 | zephyr,shell-uart = &uart0; 21 | zephyr,flash = &flash0; 22 | }; 23 | 24 | leds { 25 | compatible = "gpio-leds"; 26 | led1: led_0 { 27 | gpios = <&gpio0 14 GPIO_ACTIVE_HIGH>; 28 | }; 29 | led2: led_1 { 30 | gpios = <&gpio0 15 GPIO_ACTIVE_HIGH>; 31 | }; 32 | }; 33 | 34 | gpio_keys { 35 | compatible = "gpio-keys"; 36 | power_button: button { 37 | gpios = <&gpio0 8 GPIO_ACTIVE_LOW>; 38 | }; 39 | }; 40 | 41 | aliases { 42 | led-red = &led1; 43 | led-green = &led2; 44 | sw-pwr = &power_button; 45 | bms-ic = &bq769x2; 46 | }; 47 | }; 48 | 49 | &i2c0 { 50 | status = "okay"; 51 | 52 | bq769x2: bq76952@8 { 53 | compatible = "ti,bq769x2-i2c"; 54 | reg = <0x08>; 55 | alert-gpios = <&gpio0 2 GPIO_ACTIVE_HIGH>; 56 | used-cell-channels = <0xFFFF>; 57 | /* all NTCs configured with 18k pull-up */ 58 | ts1-pin-config = <0x07>; 59 | dchg-pin-config = <0x07>; 60 | cell-temp-pins = ; 61 | fet-temp-pin = ; 62 | board-max-current = <200>; 63 | shunt-resistor-uohm = <1500>; 64 | status = "okay"; 65 | }; 66 | }; 67 | -------------------------------------------------------------------------------- /tests/bms/prj.conf: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | 3 | CONFIG_ZTEST=y 4 | 5 | # Required for BMS emulation 6 | CONFIG_EMUL=y 7 | CONFIG_GPIO=y 8 | CONFIG_I2C=y 9 | 10 | CONFIG_BMS_IC=y 11 | CONFIG_BMS_IC_MAX_THERMISTORS=2 12 | 13 | # enable click-able absolute paths in assert messages 14 | CONFIG_BUILD_OUTPUT_STRIP_PATHS=n 15 | -------------------------------------------------------------------------------- /tests/bms/src/state_machine.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) The Libre Solar Project Contributors 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #include 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | struct bms_context bms = { 15 | .ic_dev = DEVICE_DT_GET(DT_ALIAS(bms_ic)), 16 | }; 17 | 18 | void init_conf() 19 | { 20 | bms.ic_conf.cell_ov_limit = 3.65; 21 | bms.ic_conf.cell_ov_delay_ms = 2000; 22 | 23 | bms.ic_conf.cell_uv_limit = 2.8; 24 | bms.ic_conf.cell_uv_delay_ms = 2000; 25 | 26 | bms.ic_conf.dis_ut_limit = -20; 27 | bms.ic_conf.dis_ot_limit = 45; 28 | bms.ic_conf.chg_ut_limit = 0; 29 | bms.ic_conf.chg_ot_limit = 45; 30 | bms.ic_conf.temp_limit_hyst = 2; 31 | 32 | bms.ic_conf.bal_cell_voltage_min = 3.2; 33 | bms.ic_conf.bal_idle_delay = 5 * 60; 34 | bms.ic_conf.bal_cell_voltage_diff = 0.01; 35 | 36 | for (int i = 0; i < CONFIG_BMS_IC_MAX_CELLS; i++) { 37 | bms.ic_data.cell_voltages[i] = 3.3; 38 | } 39 | bms.ic_data.cell_voltage_min = 3.3; 40 | bms.ic_data.cell_voltage_max = 3.3; 41 | bms.ic_data.cell_voltage_avg = 3.3; 42 | 43 | for (int i = 0; i < CONFIG_BMS_IC_MAX_THERMISTORS; i++) { 44 | bms.ic_data.cell_temps[i] = 25; 45 | } 46 | bms.ic_data.cell_temp_min = 25; 47 | bms.ic_data.cell_temp_max = 25; 48 | bms.ic_data.cell_temp_avg = 25; 49 | 50 | bms.state = BMS_STATE_OFF; 51 | bms.ic_data.error_flags = 0; 52 | 53 | bms.full = false; 54 | bms.empty = false; 55 | 56 | bms.chg_enable = true; 57 | bms.dis_enable = true; 58 | } 59 | 60 | ZTEST(state_machine, test_no_off2dis_if_dis_nok) 61 | { 62 | init_conf(); 63 | bms.empty = true; 64 | bms_state_machine(&bms); 65 | zassert_not_equal(BMS_STATE_DIS, bms.state); 66 | 67 | init_conf(); 68 | bms.ic_data.error_flags |= BMS_ERR_DIS_OVERTEMP; 69 | bms_state_machine(&bms); 70 | zassert_not_equal(BMS_STATE_DIS, bms.state); 71 | } 72 | 73 | ZTEST(state_machine, test_off2dis_if_dis_ok) 74 | { 75 | init_conf(); 76 | bms.full = true; 77 | bms_state_machine(&bms); 78 | zassert_equal(BMS_STATE_DIS, bms.state); 79 | } 80 | 81 | ZTEST(state_machine, test_no_off2chg_if_chg_ok) 82 | { 83 | init_conf(); 84 | bms_state_machine(&bms); 85 | zassert_not_equal(BMS_STATE_CHG, bms.state); 86 | } 87 | 88 | ZTEST(state_machine, test_off2chg_if_chg_ok_and_dis_nok) 89 | { 90 | init_conf(); 91 | bms.empty = true; 92 | bms_state_machine(&bms); 93 | zassert_equal(BMS_STATE_CHG, bms.state); 94 | 95 | init_conf(); 96 | bms.ic_data.error_flags |= BMS_ERR_DIS_OVERTEMP; 97 | bms_state_machine(&bms); 98 | zassert_equal(BMS_STATE_CHG, bms.state); 99 | } 100 | 101 | ZTEST(state_machine, test_chg2off_if_chg_nok) 102 | { 103 | init_conf(); 104 | bms.state = BMS_STATE_CHG; 105 | bms.full = true; 106 | bms_state_machine(&bms); 107 | zassert_equal(BMS_STATE_OFF, bms.state); 108 | 109 | init_conf(); 110 | bms.state = BMS_STATE_CHG; 111 | bms.ic_data.error_flags |= BMS_ERR_CHG_OVERTEMP; 112 | bms_state_machine(&bms); 113 | zassert_equal(BMS_STATE_OFF, bms.state); 114 | } 115 | 116 | ZTEST(state_machine, test_chg2normal_if_dis_ok) 117 | { 118 | init_conf(); 119 | bms.state = BMS_STATE_CHG; 120 | bms_state_machine(&bms); 121 | zassert_equal(BMS_STATE_NORMAL, bms.state); 122 | } 123 | 124 | ZTEST(state_machine, test_dis2off_if_dis_nok) 125 | { 126 | init_conf(); 127 | bms.state = BMS_STATE_DIS; 128 | bms.empty = true; 129 | bms_state_machine(&bms); 130 | zassert_equal(BMS_STATE_OFF, bms.state); 131 | 132 | init_conf(); 133 | bms.state = BMS_STATE_DIS; 134 | bms.ic_data.error_flags |= BMS_ERR_DIS_OVERTEMP; 135 | bms_state_machine(&bms); 136 | zassert_equal(BMS_STATE_OFF, bms.state); 137 | } 138 | 139 | ZTEST(state_machine, test_dis2normal_if_chg_ok) 140 | { 141 | init_conf(); 142 | bms.state = BMS_STATE_DIS; 143 | bms_state_machine(&bms); 144 | zassert_equal(BMS_STATE_NORMAL, bms.state); 145 | } 146 | 147 | ZTEST(state_machine, test_normal2dis_if_chg_nok) 148 | { 149 | init_conf(); 150 | bms.state = BMS_STATE_NORMAL; 151 | bms.full = true; 152 | bms_state_machine(&bms); 153 | zassert_equal(BMS_STATE_DIS, bms.state); 154 | 155 | init_conf(); 156 | bms.state = BMS_STATE_NORMAL; 157 | bms.ic_data.error_flags |= BMS_ERR_CHG_OVERTEMP; 158 | bms_state_machine(&bms); 159 | zassert_equal(BMS_STATE_DIS, bms.state); 160 | } 161 | 162 | ZTEST(state_machine, test_normal2chg_if_dis_nok) 163 | { 164 | init_conf(); 165 | bms.state = BMS_STATE_NORMAL; 166 | bms.empty = true; 167 | bms_state_machine(&bms); 168 | zassert_equal(BMS_STATE_CHG, bms.state); 169 | 170 | init_conf(); 171 | bms.state = BMS_STATE_NORMAL; 172 | bms.ic_data.error_flags |= BMS_ERR_DIS_OVERTEMP; 173 | bms_state_machine(&bms); 174 | zassert_equal(BMS_STATE_CHG, bms.state); 175 | } 176 | 177 | /* 178 | ZTEST(state_machine, test_no_normal2balancing_if_nok) 179 | { 180 | init_conf(); 181 | bms.state = BMS_STATE_NORMAL; 182 | bms.ic_data.cell_voltages[3] += 0.011; 183 | bms.ic_data.cell_voltage_max = bms.ic_data.cell_voltages[3]; 184 | bms.ic_data.cell_voltage_min = bms.ic_data.cell_voltages[2]; 185 | 186 | // idle time not long enough 187 | bms.ic_data.no_idle_timestamp = time(NULL) - 5*60 + 1; 188 | bms_state_machine(&bms); 189 | zassert_equal(BMS_STATE_NORMAL, bms.state); 190 | 191 | // SOC too low 192 | bms.ic_data.current = bms.ic_conf.bal_idle_current - 0.1; 193 | bms.ic_data.cell_voltages[3] = bms.ic_conf.bal_cell_voltage_min + 0.1; 194 | bms.ic_data.cell_voltage_max = bms.ic_data.cell_voltages[3]; 195 | bms_state_machine(&bms); 196 | zassert_equal(BMS_STATE_NORMAL, bms.state); 197 | } 198 | 199 | ZTEST(state_machine, test_normal2balancing_if_ok) 200 | { 201 | init_conf(); 202 | bms.state = BMS_STATE_NORMAL; 203 | bms.ic_data.no_idle_timestamp = time(NULL) - 5*60 - 1; 204 | bms.ic_data.cell_voltages[3] += 0.011; 205 | bms.ic_data.cell_voltage_max = bms.ic_data.cell_voltages[3]; 206 | bms.ic_data.cell_voltage_min = bms.ic_data.cell_voltages[2]; 207 | bms_state_machine(&bms); 208 | zassert_equal(BMS_STATE_BALANCING, bms.state); 209 | } 210 | 211 | ZTEST(state_machine, test_normal2balancing_if_ok) 212 | { 213 | init_conf(); 214 | bms.state = BMS_STATE_BALANCING; 215 | bms.ic_data.no_idle_timestamp = time(NULL); 216 | bms_state_machine(&bms); 217 | zassert_equal(BMS_STATE_NORMAL, bms.state); 218 | } 219 | 220 | ZTEST(state_machine, test_balancing2normal_if_done) 221 | { 222 | init_conf(); 223 | bms.state = BMS_STATE_BALANCING; 224 | bms.ic_data.no_idle_timestamp = time(NULL) - 5*60 - 1; 225 | bms.ic_data.cell_voltages[3] = 3.309; 226 | bms.ic_data.cell_voltage_max = bms.ic_data.cell_voltages[3]; 227 | bms.ic_data.cell_voltages[2] = 3.3; 228 | bms.ic_data.cell_voltage_min = bms.ic_data.cell_voltages[2]; 229 | bms_state_machine(&bms); 230 | zassert_equal(BMS_STATE_NORMAL, bms.state); 231 | } 232 | */ 233 | 234 | ZTEST_SUITE(state_machine, NULL, NULL, NULL, NULL, NULL); 235 | -------------------------------------------------------------------------------- /tests/bms/testcase.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | 3 | tests: 4 | bms.common: 5 | integration_platforms: 6 | - native_sim 7 | -------------------------------------------------------------------------------- /tests/bms_ic/bq769x2/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | 3 | cmake_minimum_required(VERSION 3.20.0) 4 | find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) 5 | 6 | project(bq769x2_test) 7 | 8 | FILE(GLOB app_sources src/*.c) 9 | target_sources(app PRIVATE ${app_sources}) 10 | 11 | add_subdirectory(../common app) 12 | -------------------------------------------------------------------------------- /tests/bms_ic/bq769x2/boards/native_sim.overlay: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) The Libre Solar Project Contributors 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #include 8 | 9 | / { 10 | pcb { 11 | compatible = "bms"; 12 | 13 | type = "Native Simulator BMS"; 14 | version-str = "v0.1"; 15 | version-num = <1>; 16 | }; 17 | 18 | chosen { 19 | zephyr,console = &uart0; 20 | zephyr,shell-uart = &uart0; 21 | zephyr,flash = &flash0; 22 | }; 23 | 24 | leds { 25 | compatible = "gpio-leds"; 26 | led1: led_0 { 27 | gpios = <&gpio0 14 GPIO_ACTIVE_HIGH>; 28 | }; 29 | led2: led_1 { 30 | gpios = <&gpio0 15 GPIO_ACTIVE_HIGH>; 31 | }; 32 | }; 33 | 34 | gpio_keys { 35 | compatible = "gpio-keys"; 36 | power_button: button { 37 | gpios = <&gpio0 8 GPIO_ACTIVE_LOW>; 38 | }; 39 | }; 40 | 41 | aliases { 42 | led-red = &led1; 43 | led-green = &led2; 44 | sw-pwr = &power_button; 45 | bms-ic = &bq769x2; 46 | }; 47 | }; 48 | 49 | &i2c0 { 50 | status = "okay"; 51 | 52 | bq769x2: bq76952@8 { 53 | compatible = "ti,bq769x2-i2c"; 54 | reg = <0x08>; 55 | alert-gpios = <&gpio0 2 GPIO_ACTIVE_HIGH>; 56 | used-cell-channels = <0xFFFF>; 57 | /* all NTCs configured with 18k pull-up */ 58 | ts1-pin-config = <0x07>; 59 | dchg-pin-config = <0x07>; 60 | cell-temp-pins = ; 61 | fet-temp-pin = ; 62 | board-max-current = <200>; 63 | shunt-resistor-uohm = <1500>; 64 | status = "okay"; 65 | }; 66 | }; 67 | -------------------------------------------------------------------------------- /tests/bms_ic/bq769x2/prj.conf: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | 3 | CONFIG_ZTEST=y 4 | 5 | # Required for BMS emulation 6 | CONFIG_EMUL=y 7 | CONFIG_GPIO=y 8 | CONFIG_I2C=y 9 | 10 | CONFIG_BMS_IC=y 11 | CONFIG_BMS_IC_MAX_THERMISTORS=2 12 | 13 | # enable click-able absolute paths in assert messages 14 | CONFIG_BUILD_OUTPUT_STRIP_PATHS=n 15 | -------------------------------------------------------------------------------- /tests/bms_ic/bq769x2/src/interface.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) The Libre Solar Project Contributors 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #include 8 | #include 9 | 10 | #include "bq769x2_emul.h" 11 | #include "bq769x2_interface.h" 12 | #include 13 | 14 | #include "bms_setup.h" 15 | 16 | #include 17 | #include 18 | 19 | static const struct emul *bms_ic_emul = EMUL_DT_GET(DT_ALIAS(bms_ic)); 20 | 21 | extern struct bms_context bms; 22 | 23 | ZTEST(bq769x2_interface, test_bq769x2_direct_read_u2) 24 | { 25 | uint16_t u2 = 0; 26 | int err; 27 | 28 | bq769x2_emul_set_direct_mem(bms_ic_emul, 0, 0x00); 29 | bq769x2_emul_set_direct_mem(bms_ic_emul, 1, 0x00); 30 | err = bq769x2_direct_read_u2(bms.ic_dev, 0, &u2); 31 | zassert_equal(0, err); 32 | zassert_equal(0, u2); 33 | 34 | bq769x2_emul_set_direct_mem(bms_ic_emul, 0, 0xFF); 35 | bq769x2_emul_set_direct_mem(bms_ic_emul, 1, 0xFF); 36 | err = bq769x2_direct_read_u2(bms.ic_dev, 0, &u2); 37 | zassert_equal(0, err); 38 | zassert_equal(UINT16_MAX, u2); 39 | } 40 | 41 | ZTEST(bq769x2_interface, test_bq769x2_direct_read_i2) 42 | { 43 | int16_t i2 = 0; 44 | int err; 45 | 46 | bq769x2_emul_set_direct_mem(bms_ic_emul, 0, 0x00); 47 | bq769x2_emul_set_direct_mem(bms_ic_emul, 1, 0x00); 48 | err = bq769x2_direct_read_i2(bms.ic_dev, 0, &i2); 49 | zassert_equal(0, err); 50 | zassert_equal(0, i2); 51 | 52 | bq769x2_emul_set_direct_mem(bms_ic_emul, 0, 0xFF); 53 | bq769x2_emul_set_direct_mem(bms_ic_emul, 1, 0xFF); 54 | err = bq769x2_direct_read_i2(bms.ic_dev, 0, &i2); 55 | zassert_equal(0, err); 56 | zassert_equal(-1, i2); 57 | 58 | bq769x2_emul_set_direct_mem(bms_ic_emul, 0, 0xFF); 59 | bq769x2_emul_set_direct_mem(bms_ic_emul, 1, 0x7F); 60 | err = bq769x2_direct_read_i2(bms.ic_dev, 0, &i2); 61 | zassert_equal(0, err); 62 | zassert_equal(INT16_MAX, i2); 63 | 64 | bq769x2_emul_set_direct_mem(bms_ic_emul, 0, 0x00); 65 | bq769x2_emul_set_direct_mem(bms_ic_emul, 1, 0x80); 66 | err = bq769x2_direct_read_i2(bms.ic_dev, 0, &i2); 67 | zassert_equal(0, err); 68 | zassert_equal(INT16_MIN, i2); 69 | } 70 | 71 | ZTEST(bq769x2_interface, test_bq769x2_subcmd_cmd_only) 72 | { 73 | // reset subcommand 74 | uint8_t subcmd_expected[2] = { 0x12, 0x00 }; // LOWER, UPPER 75 | 76 | // pre-set register 77 | bq769x2_emul_set_direct_mem(bms_ic_emul, 0x3E, 0xFF); 78 | bq769x2_emul_set_direct_mem(bms_ic_emul, 0x3F, 0xFF); 79 | 80 | // write subcmd register via API 81 | int err = bq769x2_subcmd_cmd_only(bms.ic_dev, 0x0012); 82 | zassert_equal(0, err); 83 | 84 | zassert_equal(subcmd_expected[0], bq769x2_emul_get_direct_mem(bms_ic_emul, 0x3E)); 85 | zassert_equal(subcmd_expected[1], bq769x2_emul_get_direct_mem(bms_ic_emul, 0x3F)); 86 | } 87 | 88 | ZTEST(bq769x2_interface, test_bq769x2_datamem_read_u1) 89 | { 90 | uint8_t value = 0; 91 | 92 | bq769x2_emul_set_data_mem(bms_ic_emul, 0x9180, 0xFF); 93 | bq769x2_emul_set_data_mem(bms_ic_emul, 0x9181, 0x00); 94 | bq769x2_emul_set_data_mem(bms_ic_emul, 0x9182, 0x00); 95 | bq769x2_emul_set_data_mem(bms_ic_emul, 0x9183, 0x00); 96 | 97 | uint8_t chk_expected = (uint8_t) ~(0x91 + 0x80 + 0xFF); 98 | 99 | int err = bq769x2_datamem_read_u1(bms.ic_dev, 0x9180, &value); 100 | zassert_equal(0, err); 101 | zassert_equal(UINT8_MAX, value); 102 | zassert_equal(chk_expected, bq769x2_emul_get_direct_mem(bms_ic_emul, 0x60)); 103 | } 104 | 105 | ZTEST(bq769x2_interface, test_bq769x2_datamem_read_u2) 106 | { 107 | uint16_t value = 0; 108 | 109 | bq769x2_emul_set_data_mem(bms_ic_emul, 0x9180, 0x00); // LSB 110 | bq769x2_emul_set_data_mem(bms_ic_emul, 0x9181, 0xFF); // MSB 111 | bq769x2_emul_set_data_mem(bms_ic_emul, 0x9182, 0x00); 112 | bq769x2_emul_set_data_mem(bms_ic_emul, 0x9183, 0x00); 113 | 114 | uint8_t chk_expected = (uint8_t) ~(0x91 + 0x80 + 0x00 + 0xFF); 115 | 116 | int err = bq769x2_datamem_read_u2(bms.ic_dev, 0x9180, &value); 117 | zassert_equal(0, err); 118 | zassert_equal(0xFF00, value); 119 | zassert_equal(chk_expected, bq769x2_emul_get_direct_mem(bms_ic_emul, 0x60)); 120 | } 121 | 122 | ZTEST(bq769x2_interface, test_bq769x2_datamem_read_i1) 123 | { 124 | int8_t value = 0; 125 | 126 | bq769x2_emul_set_data_mem(bms_ic_emul, 0x9180, 0x80); 127 | bq769x2_emul_set_data_mem(bms_ic_emul, 0x9181, 0x00); 128 | bq769x2_emul_set_data_mem(bms_ic_emul, 0x9182, 0x00); 129 | bq769x2_emul_set_data_mem(bms_ic_emul, 0x9183, 0x00); 130 | 131 | uint8_t chk_expected = (uint8_t) ~(0x91 + 0x80 + 0x80); 132 | 133 | int err = bq769x2_datamem_read_i1(bms.ic_dev, 0x9180, &value); 134 | zassert_equal(0, err); 135 | zassert_equal(INT8_MIN, value); 136 | zassert_equal(chk_expected, bq769x2_emul_get_direct_mem(bms_ic_emul, 0x60)); 137 | } 138 | 139 | ZTEST(bq769x2_interface, test_bq769x2_datamem_read_i2) 140 | { 141 | int16_t value = 0; 142 | 143 | bq769x2_emul_set_data_mem(bms_ic_emul, 0x9180, 0x00); // LSB 144 | bq769x2_emul_set_data_mem(bms_ic_emul, 0x9181, 0x80); // MSB 145 | bq769x2_emul_set_data_mem(bms_ic_emul, 0x9182, 0x00); 146 | bq769x2_emul_set_data_mem(bms_ic_emul, 0x9183, 0x00); 147 | 148 | uint8_t chk_expected = (uint8_t) ~(0x91 + 0x80 + 0x00 + 0x80); 149 | 150 | int err = bq769x2_datamem_read_i2(bms.ic_dev, 0x9180, &value); 151 | zassert_equal(0, err); 152 | zassert_equal(INT16_MIN, value); 153 | zassert_equal(chk_expected, bq769x2_emul_get_direct_mem(bms_ic_emul, 0x60)); 154 | } 155 | 156 | ZTEST(bq769x2_interface, test_bq769x2_datamem_read_f4) 157 | { 158 | float value = 0.0F; 159 | 160 | bq769x2_emul_set_data_mem(bms_ic_emul, 0x9180, 0xB6); // LSB 161 | bq769x2_emul_set_data_mem(bms_ic_emul, 0x9181, 0xF3); 162 | bq769x2_emul_set_data_mem(bms_ic_emul, 0x9182, 0x9D); 163 | bq769x2_emul_set_data_mem(bms_ic_emul, 0x9183, 0x3F); // MSB 164 | 165 | uint8_t chk_expected = (uint8_t) ~(0x91 + 0x80 + 0xB6 + 0xF3 + 0x9D + 0x3F); 166 | 167 | int err = bq769x2_datamem_read_f4(bms.ic_dev, 0x9180, &value); 168 | zassert_equal(0, err); 169 | zassert_equal(1.234F, value); 170 | zassert_equal(chk_expected, bq769x2_emul_get_direct_mem(bms_ic_emul, 0x60)); 171 | } 172 | 173 | ZTEST(bq769x2_interface, test_bq769x2_datamem_write_u1) 174 | { 175 | uint8_t data_expected[] = { 0xFF }; 176 | uint8_t chk_expected = (uint8_t) ~(0x91 + 0x80 + 0xFF); 177 | uint8_t len_expected = 4 + sizeof(data_expected); 178 | 179 | bq769x2_config_update_mode(bms.ic_dev, true); 180 | 181 | int err = bq769x2_datamem_write_u1(bms.ic_dev, 0x9180, UINT8_MAX); 182 | zassert_equal(0, err); 183 | 184 | zassert_equal(data_expected[0], bq769x2_emul_get_direct_mem(bms_ic_emul, 0x40)); 185 | zassert_equal(chk_expected, bq769x2_emul_get_direct_mem(bms_ic_emul, 0x60)); 186 | zassert_equal(len_expected, bq769x2_emul_get_direct_mem(bms_ic_emul, 0x61)); 187 | 188 | bq769x2_config_update_mode(bms.ic_dev, false); 189 | } 190 | 191 | ZTEST(bq769x2_interface, test_bq769x2_datamem_write_u2) 192 | { 193 | uint8_t data_expected[] = { 0x00, 0xFF }; 194 | uint8_t chk_expected = (uint8_t) ~(0x91 + 0x80 + 0xFF); 195 | uint8_t len_expected = 4 + sizeof(data_expected); 196 | 197 | bq769x2_config_update_mode(bms.ic_dev, true); 198 | 199 | int err = bq769x2_datamem_write_u2(bms.ic_dev, 0x9180, 0xFF00); 200 | zassert_equal(0, err); 201 | 202 | zassert_equal(data_expected[0], bq769x2_emul_get_direct_mem(bms_ic_emul, 0x40)); 203 | zassert_equal(data_expected[1], bq769x2_emul_get_direct_mem(bms_ic_emul, 0x41)); 204 | zassert_equal(chk_expected, bq769x2_emul_get_direct_mem(bms_ic_emul, 0x60)); 205 | zassert_equal(len_expected, bq769x2_emul_get_direct_mem(bms_ic_emul, 0x61)); 206 | 207 | bq769x2_config_update_mode(bms.ic_dev, false); 208 | } 209 | 210 | ZTEST(bq769x2_interface, test_bq769x2_datamem_write_i1) 211 | { 212 | uint8_t data_expected[] = { 0x80 }; 213 | uint8_t chk_expected = (uint8_t) ~(0x91 + 0x80 + 0x80); 214 | uint8_t len_expected = 4 + sizeof(data_expected); 215 | 216 | bq769x2_config_update_mode(bms.ic_dev, true); 217 | 218 | int err = bq769x2_datamem_write_i1(bms.ic_dev, 0x9180, INT8_MIN); 219 | zassert_equal(0, err); 220 | 221 | zassert_equal(data_expected[0], bq769x2_emul_get_direct_mem(bms_ic_emul, 0x40)); 222 | zassert_equal(chk_expected, bq769x2_emul_get_direct_mem(bms_ic_emul, 0x60)); 223 | zassert_equal(len_expected, bq769x2_emul_get_direct_mem(bms_ic_emul, 0x61)); 224 | 225 | bq769x2_config_update_mode(bms.ic_dev, false); 226 | } 227 | 228 | ZTEST(bq769x2_interface, test_bq769x2_datamem_write_i2) 229 | { 230 | uint8_t data_expected[] = { 0x00, 0x80 }; 231 | uint8_t chk_expected = (uint8_t) ~(0x91 + 0x80 + 0x00 + 0x80); 232 | uint8_t len_expected = 4 + sizeof(data_expected); 233 | 234 | bq769x2_config_update_mode(bms.ic_dev, true); 235 | 236 | int err = bq769x2_datamem_write_i2(bms.ic_dev, 0x9180, INT16_MIN); 237 | zassert_equal(0, err); 238 | 239 | zassert_equal(data_expected[0], bq769x2_emul_get_direct_mem(bms_ic_emul, 0x40)); 240 | zassert_equal(data_expected[1], bq769x2_emul_get_direct_mem(bms_ic_emul, 0x41)); 241 | zassert_equal(chk_expected, bq769x2_emul_get_direct_mem(bms_ic_emul, 0x60)); 242 | zassert_equal(len_expected, bq769x2_emul_get_direct_mem(bms_ic_emul, 0x61)); 243 | 244 | bq769x2_config_update_mode(bms.ic_dev, false); 245 | } 246 | 247 | ZTEST(bq769x2_interface, test_bq769x2_datamem_write_f4) 248 | { 249 | uint8_t data_expected[] = { 0xB6, 0xF3, 0x9D, 0x3F }; 250 | uint8_t chk_expected = (uint8_t) ~(0x91 + 0x80 + 0xB6 + 0xF3 + 0x9D + 0x3F); 251 | uint8_t len_expected = 4 + sizeof(data_expected); 252 | 253 | bq769x2_config_update_mode(bms.ic_dev, true); 254 | 255 | int err = bq769x2_datamem_write_f4(bms.ic_dev, 0x9180, 1.234F); 256 | zassert_equal(0, err); 257 | 258 | zassert_equal(data_expected[0], bq769x2_emul_get_direct_mem(bms_ic_emul, 0x40)); 259 | zassert_equal(data_expected[1], bq769x2_emul_get_direct_mem(bms_ic_emul, 0x41)); 260 | zassert_equal(data_expected[2], bq769x2_emul_get_direct_mem(bms_ic_emul, 0x42)); 261 | zassert_equal(data_expected[3], bq769x2_emul_get_direct_mem(bms_ic_emul, 0x43)); 262 | zassert_equal(chk_expected, bq769x2_emul_get_direct_mem(bms_ic_emul, 0x60)); 263 | zassert_equal(len_expected, bq769x2_emul_get_direct_mem(bms_ic_emul, 0x61)); 264 | 265 | bq769x2_config_update_mode(bms.ic_dev, false); 266 | } 267 | 268 | static void *bq769x2_setup(void) 269 | { 270 | common_setup_bms_defaults(&bms); 271 | 272 | return NULL; 273 | } 274 | 275 | ZTEST_SUITE(bq769x2_interface, NULL, bq769x2_setup, NULL, NULL, NULL); 276 | -------------------------------------------------------------------------------- /tests/bms_ic/bq769x2/testcase.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | 3 | tests: 4 | bms_ic.bq769x2: 5 | integration_platforms: 6 | - native_sim 7 | -------------------------------------------------------------------------------- /tests/bms_ic/common/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | 3 | target_sources(app PRIVATE bms_setup.c) 4 | 5 | zephyr_include_directories(.) 6 | -------------------------------------------------------------------------------- /tests/bms_ic/common/bms_setup.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) The Libre Solar Project Contributors 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | struct bms_context bms = { 13 | .ic_dev = DEVICE_DT_GET(DT_ALIAS(bms_ic)), 14 | }; 15 | 16 | float OCV[] = { // 100, 95, ..., 0 % 17 | 3.392, 3.314, 3.309, 3.308, 3.304, 3.296, 3.283, 3.275, 3.271, 3.268, 3.265, 18 | 3.264, 3.262, 3.252, 3.240, 3.226, 3.213, 3.190, 3.177, 3.132, 2.833 19 | }; 20 | 21 | void common_setup_bms_defaults() 22 | { 23 | bms_ic_assign_data(bms.ic_dev, &bms.ic_data); 24 | 25 | bms_ic_set_mode(bms.ic_dev, BMS_IC_MODE_ACTIVE); 26 | 27 | bms.ic_conf.dis_sc_limit = 35.0; 28 | bms.ic_conf.dis_sc_delay_us = 200; 29 | 30 | bms.ic_conf.dis_oc_limit = 25.0; 31 | bms.ic_conf.dis_oc_delay_ms = 320; 32 | 33 | bms.ic_conf.chg_oc_limit = 20.0; 34 | bms.ic_conf.chg_oc_delay_ms = 320; 35 | 36 | bms.ic_conf.cell_ov_limit = 3.65; 37 | bms.ic_conf.cell_ov_delay_ms = 2000; 38 | 39 | bms.ic_conf.cell_uv_limit = 2.8; 40 | bms.ic_conf.cell_uv_delay_ms = 2000; 41 | 42 | bms.ic_conf.dis_ut_limit = -20; 43 | bms.ic_conf.dis_ot_limit = 45; 44 | bms.ic_conf.chg_ut_limit = 0; 45 | bms.ic_conf.chg_ot_limit = 45; 46 | bms.ic_conf.temp_limit_hyst = 2; 47 | 48 | bms.ocv_points = OCV; 49 | 50 | bms.nominal_capacity_Ah = 45.0; 51 | bms.ic_data.connected_cells = 4; 52 | 53 | bms.ic_conf.bal_cell_voltage_min = 3.2; 54 | bms.ic_conf.bal_idle_delay = 10 * 60; 55 | bms.ic_conf.bal_cell_voltage_diff = 0.01; 56 | bms.ic_conf.bal_idle_current = 0.1; 57 | 58 | bms_ic_configure(bms.ic_dev, &bms.ic_conf, BMS_IC_CONF_ALL); 59 | 60 | bms_ic_read_data(bms.ic_dev, BMS_IC_DATA_CELL_VOLTAGES); 61 | 62 | bms_ic_set_switches(bms.ic_dev, BMS_SWITCH_DIS, true); 63 | bms_ic_set_switches(bms.ic_dev, BMS_SWITCH_CHG, true); 64 | } 65 | -------------------------------------------------------------------------------- /tests/bms_ic/common/bms_setup.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) The Libre Solar Project Contributors 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #ifndef TESTS_BMS_IC_COMMON_BMS_SETUP_H_ 8 | #define TESTS_BMS_IC_COMMON_BMS_SETUP_H_ 9 | 10 | void common_setup_bms_defaults(); 11 | 12 | #endif /* TESTS_BMS_IC_COMMON_BMS_SETUP_H_ */ 13 | -------------------------------------------------------------------------------- /tests/bms_ic/isl94202/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | 3 | cmake_minimum_required(VERSION 3.20.0) 4 | find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) 5 | 6 | project(isl94202_test) 7 | 8 | FILE(GLOB app_sources src/*.c) 9 | target_sources(app PRIVATE ${app_sources}) 10 | 11 | add_subdirectory(../common app) 12 | -------------------------------------------------------------------------------- /tests/bms_ic/isl94202/boards/native_sim.overlay: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) The Libre Solar Project Contributors 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | / { 8 | pcb { 9 | compatible = "bms"; 10 | 11 | type = "Native Simulator BMS"; 12 | version-str = "v0.1"; 13 | version-num = <1>; 14 | }; 15 | 16 | chosen { 17 | zephyr,console = &uart0; 18 | zephyr,shell-uart = &uart0; 19 | zephyr,flash = &flash0; 20 | }; 21 | 22 | leds { 23 | compatible = "gpio-leds"; 24 | led1: led_0 { 25 | gpios = <&gpio0 14 GPIO_ACTIVE_HIGH>; 26 | }; 27 | led2: led_1 { 28 | gpios = <&gpio0 15 GPIO_ACTIVE_HIGH>; 29 | }; 30 | }; 31 | 32 | gpio_keys { 33 | compatible = "gpio-keys"; 34 | power_button: button { 35 | gpios = <&gpio0 8 GPIO_ACTIVE_LOW>; 36 | }; 37 | }; 38 | 39 | aliases { 40 | led-red = &led1; 41 | led-green = &led2; 42 | sw-pwr = &power_button; 43 | bms-ic = &isl94202; 44 | }; 45 | }; 46 | 47 | &i2c0 { 48 | status = "okay"; 49 | 50 | isl94202: isl94202@28 { 51 | compatible = "renesas,isl94202"; 52 | reg = <0x28>; /* 0x50 >> 1 */ 53 | pull-up-gpios = <&gpio0 0 GPIO_ACTIVE_HIGH>; 54 | shunt-resistor-uohm = <2000>; 55 | board-max-current = <50>; 56 | used-cell-channels = <0xFF>; 57 | status = "okay"; 58 | }; 59 | }; 60 | -------------------------------------------------------------------------------- /tests/bms_ic/isl94202/prj.conf: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | 3 | CONFIG_ZTEST=y 4 | 5 | # Required for BMS emulation 6 | CONFIG_EMUL=y 7 | CONFIG_GPIO=y 8 | CONFIG_I2C=y 9 | 10 | CONFIG_BMS_IC=y 11 | CONFIG_BMS_IC_MAX_THERMISTORS=2 12 | 13 | # enable click-able absolute paths in assert messages 14 | CONFIG_BUILD_OUTPUT_STRIP_PATHS=n 15 | -------------------------------------------------------------------------------- /tests/bms_ic/isl94202/testcase.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | 3 | tests: 4 | bms_ic.isl94202: 5 | integration_platforms: 6 | - native_sim 7 | -------------------------------------------------------------------------------- /tests/helper/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | 3 | cmake_minimum_required(VERSION 3.20.0) 4 | find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) 5 | 6 | project(bms_helper_test) 7 | 8 | FILE(GLOB app_sources src/*.c) 9 | target_sources(app PRIVATE ${app_sources}) 10 | -------------------------------------------------------------------------------- /tests/helper/prj.conf: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | 3 | CONFIG_ZTEST=y 4 | -------------------------------------------------------------------------------- /tests/helper/src/interpolation.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) The Libre Solar Project Contributors 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #include "helper.h" 8 | 9 | #include 10 | 11 | #include 12 | 13 | ZTEST(interpolation, test_increasing) 14 | { 15 | float a[] = { 1, 2, 3, 4, 5 }; 16 | float b[] = { 2, 4, 6, 8, 10 }; 17 | 18 | float value_b = interpolate(a, b, sizeof(a) / sizeof(float), 1.75); 19 | zassert_equal(3.5F, value_b); 20 | 21 | value_b = interpolate(a, b, sizeof(a) / sizeof(float), -1); 22 | zassert_equal(2.0F, value_b); 23 | 24 | value_b = interpolate(a, b, sizeof(a) / sizeof(float), 6); 25 | zassert_equal(10.0F, value_b); 26 | } 27 | 28 | ZTEST(interpolation, test_decreasing) 29 | { 30 | float a[] = { 5, 4, 3, 2, 1 }; 31 | float b[] = { 2, 4, 6, 8, 10 }; 32 | 33 | float value_b = interpolate(a, b, sizeof(a) / sizeof(float), 1.75); 34 | zassert_equal(8.5F, value_b); 35 | 36 | value_b = interpolate(a, b, sizeof(a) / sizeof(float), -1); 37 | zassert_equal(10.0F, value_b); 38 | 39 | value_b = interpolate(a, b, sizeof(a) / sizeof(float), 6); 40 | zassert_equal(2.0F, value_b); 41 | } 42 | 43 | ZTEST_SUITE(interpolation, NULL, NULL, NULL, NULL, NULL); 44 | -------------------------------------------------------------------------------- /tests/helper/src/is_empty.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) The Libre Solar Project Contributors 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #include "helper.h" 8 | 9 | #include 10 | 11 | #include 12 | 13 | ZTEST(is_empty, test_empty) 14 | { 15 | float a[] = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }; 16 | zassert_true(is_empty((uint8_t *)a, sizeof(a))); 17 | 18 | #if __SIZEOF_POINTER__ == 4 19 | uint8_t b[] = { 0, 0, 0 }; 20 | #elif __SIZEOF_POINTER__ == 8 21 | uint8_t b[] = { 0, 0, 0, 0, 0, 0, 0 }; 22 | #endif 23 | zassert_true(is_empty((uint8_t *)b, sizeof(b))); 24 | } 25 | 26 | ZTEST(is_empty, test_filled_last_element) 27 | { 28 | float a[] = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0001 }; 29 | zassert_false(is_empty((uint8_t *)a, sizeof(a))); 30 | 31 | #if __SIZEOF_POINTER__ == 4 32 | uint8_t b[] = { 0, 0, 1 }; 33 | #elif __SIZEOF_POINTER__ == 8 34 | uint8_t b[] = { 0, 0, 0, 0, 0, 0, 1 }; 35 | #endif 36 | zassert_false(is_empty((uint8_t *)b, sizeof(b))); 37 | } 38 | 39 | ZTEST(is_empty, test_filled_first_element) 40 | { 41 | float a[] = { 0.0001, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }; 42 | zassert_false(is_empty((uint8_t *)a, sizeof(a))); 43 | 44 | #if __SIZEOF_POINTER__ == 4 45 | uint8_t b[] = { 1, 0, 0 }; 46 | #elif __SIZEOF_POINTER__ == 8 47 | uint8_t b[] = { 1, 0, 0, 0, 0, 0, 0 }; 48 | #endif 49 | zassert_false(is_empty((uint8_t *)b, sizeof(b))); 50 | } 51 | 52 | ZTEST(is_empty, test_unaligned_access) 53 | { 54 | 55 | uint8_t a[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; 56 | zassert_true(is_empty(((uint8_t *)a) + 1, sizeof(a) - 1)); 57 | 58 | uint8_t b[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }; 59 | zassert_false(is_empty(((uint8_t *)b) + 1, sizeof(b) - 1)); 60 | } 61 | 62 | ZTEST_SUITE(is_empty, NULL, NULL, NULL, NULL, NULL); 63 | -------------------------------------------------------------------------------- /tests/helper/testcase.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | 3 | tests: 4 | helper.test: 5 | integration_platforms: 6 | - native_sim 7 | -------------------------------------------------------------------------------- /west.yml: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | 3 | manifest: 4 | remotes: 5 | - name: zephyrproject 6 | url-base: https://github.com/zephyrproject-rtos 7 | - name: libresolar 8 | url-base: https://github.com/LibreSolar 9 | - name: thingset 10 | url-base: https://github.com/ThingSet 11 | - name: throwtheswitch 12 | url-base: https://github.com/ThrowTheSwitch 13 | projects: 14 | - name: zephyr 15 | remote: zephyrproject 16 | revision: v4.0-branch 17 | import: 18 | name-allowlist: 19 | - cmsis 20 | - edtt 21 | - hal_espressif 22 | - hal_stm32 23 | - mcuboot 24 | - tinycrypt 25 | - zcbor 26 | - name: thingset-zephyr-sdk 27 | remote: thingset 28 | revision: 48740769cea0d9666fc9132cd8923f9ce2c088f9 29 | path: thingset-zephyr-sdk 30 | import: 31 | name-allowlist: 32 | - thingset-node-c 33 | - zcbor 34 | -------------------------------------------------------------------------------- /zephyr/module.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Nordic Semiconductor ASA 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | build: 5 | # Path to the Kconfig file that will be sourced into Zephyr Kconfig tree under 6 | # Zephyr > Modules > example-application. Path is relative from root of this 7 | # repository. 8 | kconfig: Kconfig 9 | # Path to the folder that contains the CMakeLists.txt file to be included by 10 | # Zephyr build system. The `.` is the root of this repository. 11 | cmake: . 12 | settings: 13 | # Additional roots for boards and DTS files. Zephyr will use the 14 | # `/boards` for additional boards. The `.` is the root of this 15 | # repository. 16 | board_root: . 17 | # Zephyr will use the `/dts` for additional dts files and 18 | # `/dts/bindings` for additional dts binding files. The `.` is 19 | # the root of this repository. 20 | dts_root: . 21 | --------------------------------------------------------------------------------