├── spinner ├── shell.conf ├── Kconfig ├── prj.conf ├── debug.conf ├── main.c ├── sample.yaml └── CMakeLists.txt ├── docs ├── _static │ ├── images │ │ ├── logo.png │ │ └── logo.svg │ └── css │ │ └── light.css ├── theory │ ├── images │ │ ├── foc-abcs-anim.gif │ │ ├── svpwm-limit.odg │ │ ├── currsample-mid.odg │ │ ├── svpwm-pwm-timing.odg │ │ ├── currsample-svpwm-anim.gif │ │ ├── svpwm-abc-signs.py │ │ ├── svpwm-hexagon.py │ │ ├── svpwm-modulation.py │ │ ├── foc-abcs-anim.py │ │ ├── currsample-svpwm-anim.py │ │ └── svpwm-limit.svg │ ├── currsmp.rst │ ├── foc.rst │ └── svpwm.rst ├── components │ ├── currsmp │ │ ├── impl │ │ │ └── stm32.rst │ │ ├── images │ │ │ ├── intro-inverter-schematic.odg │ │ │ ├── intro-inverter-schematic.png │ │ │ ├── intro-inverter-schematic-blocks.odg │ │ │ └── intro-inverter-schematic-blocks.png │ │ └── index.rst │ ├── feedback │ │ ├── images │ │ │ └── halls.odg │ │ ├── impl │ │ │ └── stm32-halls.rst │ │ └── index.rst │ ├── svpwm │ │ ├── images │ │ │ └── ps-schematic.odg │ │ ├── impl │ │ │ ├── images │ │ │ │ ├── stm32-cc-output.png │ │ │ │ ├── stm32-timer-brk.png │ │ │ │ ├── stm32-timer-repcnt.png │ │ │ │ ├── stm32-timer-brkconf.png │ │ │ │ ├── stm32-timer-diagram.png │ │ │ │ └── stm32-timer-centeraligned.odg │ │ │ └── stm32.rst │ │ └── index.rst │ └── cloop │ │ ├── images │ │ └── cloop-only-schematic.odg │ │ └── index.rst ├── zbibliography.rst ├── requirements.txt ├── _doxygen │ └── groups.dox ├── bibliography.bib ├── doxygen.md ├── CMakeLists.txt ├── index.rst └── conf.py ├── boards ├── shields │ ├── ihm07m1 │ │ ├── Kconfig.shield │ │ ├── boards │ │ │ ├── nucleo_f302r8.overlay │ │ │ └── nucleo_g431rb.overlay │ │ └── ihm07m1.overlay │ └── ihm16m1 │ │ ├── Kconfig.shield │ │ ├── boards │ │ ├── nucleo_f302r8.overlay │ │ └── nucleo_g431rb.overlay │ │ └── ihm16m1.overlay └── extensions │ ├── nucleo_f302r8 │ └── nucleo_f302r8.overlay │ └── nucleo_g431rb │ └── nucleo_g431rb.overlay ├── .gitignore ├── Kconfig ├── lib ├── utils │ ├── Kconfig │ ├── CMakeLists.txt │ ├── stm32_tim.c │ └── stm32_adc.c ├── CMakeLists.txt ├── svm │ ├── CMakeLists.txt │ ├── Kconfig │ └── svm.c ├── Kconfig └── control │ ├── CMakeLists.txt │ ├── Kconfig │ ├── cloop_shell.c │ └── cloop.c ├── tests └── lib │ └── svm │ ├── prj.conf │ ├── testcase.yaml │ ├── CMakeLists.txt │ └── src │ └── main.c ├── CMakeLists.txt ├── drivers ├── svpwm │ ├── CMakeLists.txt │ ├── Kconfig │ ├── Kconfig.stm32 │ └── svpwm_stm32.c ├── feedback │ ├── CMakeLists.txt │ ├── Kconfig.stm32 │ ├── Kconfig │ └── halls_stm32.c ├── Kconfig ├── currsmp │ ├── CMakeLists.txt │ ├── Kconfig.stm32 │ ├── Kconfig │ └── currsmp_shunt_stm32.c └── CMakeLists.txt ├── zephyr └── module.yml ├── dts └── bindings │ ├── motor │ ├── st,stspin830.yaml │ ├── st,l6230.yaml │ └── motor-driver.yaml │ ├── svpwm │ └── st,stm32-svpwm.yaml │ ├── feedback │ └── st,stm32-halls.yaml │ └── currsmp │ └── st,stm32-currsmp-shunt.yaml ├── west.yml ├── .checkpatch.conf ├── include ├── spinner │ ├── control │ │ └── cloop.h │ ├── utils │ │ ├── stm32_tim.h │ │ └── stm32_adc.h │ ├── svm │ │ └── svm.h │ └── drivers │ │ ├── feedback.h │ │ ├── svpwm.h │ │ └── currsmp.h └── dts-bindings │ └── adc │ ├── stm32f3xx.h │ └── stm32g4xx.h ├── .editorconfig ├── .github └── workflows │ ├── docs.yml │ └── build.yml ├── .clang-format ├── README.md └── LICENSE /spinner/shell.conf: -------------------------------------------------------------------------------- 1 | CONFIG_SHELL=y 2 | -------------------------------------------------------------------------------- /docs/_static/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teslabs/spinner/HEAD/docs/_static/images/logo.png -------------------------------------------------------------------------------- /boards/shields/ihm07m1/Kconfig.shield: -------------------------------------------------------------------------------- 1 | config SHIELD_IHM07M1 2 | def_bool $(shields_list_contains,ihm07m1) 3 | -------------------------------------------------------------------------------- /boards/shields/ihm16m1/Kconfig.shield: -------------------------------------------------------------------------------- 1 | config SHIELD_IHM16M1 2 | def_bool $(shields_list_contains,ihm16m1) 3 | -------------------------------------------------------------------------------- /docs/theory/images/foc-abcs-anim.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teslabs/spinner/HEAD/docs/theory/images/foc-abcs-anim.gif -------------------------------------------------------------------------------- /docs/theory/images/svpwm-limit.odg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teslabs/spinner/HEAD/docs/theory/images/svpwm-limit.odg -------------------------------------------------------------------------------- /docs/components/currsmp/impl/stm32.rst: -------------------------------------------------------------------------------- 1 | STM32 2 | ===== 3 | 4 | .. note:: 5 | 6 | This page will be available soon. 7 | -------------------------------------------------------------------------------- /docs/theory/images/currsample-mid.odg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teslabs/spinner/HEAD/docs/theory/images/currsample-mid.odg -------------------------------------------------------------------------------- /docs/components/feedback/images/halls.odg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teslabs/spinner/HEAD/docs/components/feedback/images/halls.odg -------------------------------------------------------------------------------- /docs/theory/images/svpwm-pwm-timing.odg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teslabs/spinner/HEAD/docs/theory/images/svpwm-pwm-timing.odg -------------------------------------------------------------------------------- /docs/zbibliography.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Bibliography 3 | ============ 4 | 5 | .. bibliography:: bibliography.bib 6 | :all: 7 | -------------------------------------------------------------------------------- /docs/components/feedback/impl/stm32-halls.rst: -------------------------------------------------------------------------------- 1 | STM32 Halls 2 | =========== 3 | 4 | .. note:: 5 | 6 | This page will be available soon. 7 | -------------------------------------------------------------------------------- /docs/components/svpwm/images/ps-schematic.odg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teslabs/spinner/HEAD/docs/components/svpwm/images/ps-schematic.odg -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | Sphinx 2 | sphinx_rtd_theme 3 | breathe 4 | sphinxcontrib-mermaid 5 | sphinxcontrib-bibtex 6 | matplotlib 7 | numpy 8 | -------------------------------------------------------------------------------- /docs/theory/images/currsample-svpwm-anim.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teslabs/spinner/HEAD/docs/theory/images/currsample-svpwm-anim.gif -------------------------------------------------------------------------------- /spinner/Kconfig: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Teslabs Engineering S.L. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | source "Kconfig.zephyr" 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # zephyr 2 | /build* 3 | /twister-out* 4 | 5 | # python 6 | .venv 7 | 8 | # editors 9 | *.swp 10 | .vscode 11 | .~lock* 12 | -------------------------------------------------------------------------------- /docs/components/cloop/images/cloop-only-schematic.odg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teslabs/spinner/HEAD/docs/components/cloop/images/cloop-only-schematic.odg -------------------------------------------------------------------------------- /docs/components/svpwm/impl/images/stm32-cc-output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teslabs/spinner/HEAD/docs/components/svpwm/impl/images/stm32-cc-output.png -------------------------------------------------------------------------------- /docs/components/svpwm/impl/images/stm32-timer-brk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teslabs/spinner/HEAD/docs/components/svpwm/impl/images/stm32-timer-brk.png -------------------------------------------------------------------------------- /spinner/prj.conf: -------------------------------------------------------------------------------- 1 | # drivers 2 | CONFIG_SPINNER_CURRSMP=y 3 | CONFIG_SPINNER_FEEDBACK=y 4 | CONFIG_SPINNER_SVPWM=y 5 | 6 | # lib 7 | CONFIG_SPINNER_CLOOP=y 8 | -------------------------------------------------------------------------------- /Kconfig: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Teslabs Engineering S.L. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | rsource "drivers/Kconfig" 5 | rsource "lib/Kconfig" 6 | -------------------------------------------------------------------------------- /docs/components/svpwm/impl/images/stm32-timer-repcnt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teslabs/spinner/HEAD/docs/components/svpwm/impl/images/stm32-timer-repcnt.png -------------------------------------------------------------------------------- /lib/utils/Kconfig: -------------------------------------------------------------------------------- 1 | config SPINNER_UTILS_STM32 2 | bool "STM32 utilities" 3 | select USE_STM32_LL_RCC 4 | help 5 | Enable common utilities for STM32 SoCs. 6 | -------------------------------------------------------------------------------- /tests/lib/svm/prj.conf: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Teslabs Engineering S.L. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | CONFIG_ZTEST=y 5 | CONFIG_SPINNER_SVM=y 6 | -------------------------------------------------------------------------------- /docs/components/svpwm/impl/images/stm32-timer-brkconf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teslabs/spinner/HEAD/docs/components/svpwm/impl/images/stm32-timer-brkconf.png -------------------------------------------------------------------------------- /docs/components/svpwm/impl/images/stm32-timer-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teslabs/spinner/HEAD/docs/components/svpwm/impl/images/stm32-timer-diagram.png -------------------------------------------------------------------------------- /docs/components/currsmp/images/intro-inverter-schematic.odg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teslabs/spinner/HEAD/docs/components/currsmp/images/intro-inverter-schematic.odg -------------------------------------------------------------------------------- /docs/components/currsmp/images/intro-inverter-schematic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teslabs/spinner/HEAD/docs/components/currsmp/images/intro-inverter-schematic.png -------------------------------------------------------------------------------- /docs/components/svpwm/impl/images/stm32-timer-centeraligned.odg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teslabs/spinner/HEAD/docs/components/svpwm/impl/images/stm32-timer-centeraligned.odg -------------------------------------------------------------------------------- /docs/components/currsmp/images/intro-inverter-schematic-blocks.odg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teslabs/spinner/HEAD/docs/components/currsmp/images/intro-inverter-schematic-blocks.odg -------------------------------------------------------------------------------- /docs/components/currsmp/images/intro-inverter-schematic-blocks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teslabs/spinner/HEAD/docs/components/currsmp/images/intro-inverter-schematic-blocks.png -------------------------------------------------------------------------------- /lib/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Teslabs Engineering S.L. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | add_subdirectory(control) 5 | add_subdirectory(svm) 6 | add_subdirectory(utils) 7 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Teslabs Engineering S.L. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | add_subdirectory(drivers) 5 | add_subdirectory(lib) 6 | 7 | zephyr_include_directories(include) 8 | -------------------------------------------------------------------------------- /lib/svm/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Teslabs Engineering S.L. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | if(CONFIG_SPINNER_SVM) 5 | zephyr_library() 6 | zephyr_library_sources(svm.c) 7 | endif() 8 | 9 | -------------------------------------------------------------------------------- /drivers/svpwm/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Teslabs Engineering S.L. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | zephyr_library() 5 | zephyr_library_sources_ifdef(CONFIG_SPINNER_SVPWM_STM32 svpwm_stm32.c) 6 | 7 | -------------------------------------------------------------------------------- /spinner/debug.conf: -------------------------------------------------------------------------------- 1 | # compiler 2 | CONFIG_DEBUG_OPTIMIZATIONS=y 3 | 4 | # console 5 | CONFIG_CONSOLE=y 6 | 7 | # UART console 8 | CONFIG_SERIAL=y 9 | CONFIG_UART_CONSOLE=y 10 | 11 | # logging 12 | CONFIG_LOG=y 13 | -------------------------------------------------------------------------------- /tests/lib/svm/testcase.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Teslabs Engineering S.L. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | tests: 5 | lib.svm: 6 | tags: lib svm 7 | integration_platforms: 8 | - native_sim 9 | -------------------------------------------------------------------------------- /zephyr/module.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Teslabs Engineering S.L. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | build: 5 | kconfig: Kconfig 6 | cmake: . 7 | settings: 8 | board_root: . 9 | dts_root: . 10 | -------------------------------------------------------------------------------- /drivers/feedback/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Teslabs Engineering S.L. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | zephyr_library() 5 | zephyr_library_sources_ifdef(CONFIG_SPINNER_FEEDBACK_HALLS_STM32 halls_stm32.c) 6 | 7 | -------------------------------------------------------------------------------- /drivers/Kconfig: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Teslabs Engineering S.L. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | menu "Drivers" 5 | 6 | rsource "currsmp/Kconfig" 7 | rsource "feedback/Kconfig" 8 | rsource "svpwm/Kconfig" 9 | 10 | endmenu -------------------------------------------------------------------------------- /drivers/currsmp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Teslabs Engineering S.L. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | zephyr_library() 5 | zephyr_library_sources_ifdef(CONFIG_SPINNER_CURRSMP_SHUNT_STM32 currsmp_shunt_stm32.c) 6 | 7 | -------------------------------------------------------------------------------- /lib/Kconfig: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Teslabs Engineering S.L. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | menu "Libraries" 5 | 6 | rsource "control/Kconfig" 7 | rsource "svm/Kconfig" 8 | rsource "utils/Kconfig" 9 | 10 | endmenu 11 | -------------------------------------------------------------------------------- /lib/utils/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Teslabs Engineering S.L. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | if (CONFIG_SPINNER_UTILS_STM32) 5 | zephyr_library() 6 | zephyr_library_sources(stm32_tim.c stm32_adc.c) 7 | endif() 8 | -------------------------------------------------------------------------------- /lib/svm/Kconfig: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Teslabs Engineering S.L. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | config SPINNER_SVM 5 | bool "Space Vector Modulator" 6 | select CMSIS_DSP 7 | select CMSIS_DSP_FASTMATH 8 | help 9 | Space Vector Modulator. 10 | 11 | -------------------------------------------------------------------------------- /spinner/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Teslabs Engineering S.L. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | #include 7 | 8 | int main(void) 9 | { 10 | cloop_start(); 11 | cloop_set_ref(0.0f, 0.25f); 12 | 13 | return 0; 14 | } 15 | -------------------------------------------------------------------------------- /drivers/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Teslabs Engineering S.L. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | add_subdirectory_ifdef(CONFIG_SPINNER_CURRSMP currsmp) 5 | add_subdirectory_ifdef(CONFIG_SPINNER_FEEDBACK feedback) 6 | add_subdirectory_ifdef(CONFIG_SPINNER_SVPWM svpwm) 7 | -------------------------------------------------------------------------------- /tests/lib/svm/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Teslabs Engineering S.L. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | cmake_minimum_required(VERSION 3.20.0) 5 | find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) 6 | 7 | project(lib_svm) 8 | target_sources(app PRIVATE src/main.c) 9 | -------------------------------------------------------------------------------- /docs/components/currsmp/index.rst: -------------------------------------------------------------------------------- 1 | Current Sampling 2 | ================ 3 | 4 | Introduction 5 | ------------ 6 | 7 | API 8 | --- 9 | 10 | .. doxygengroup:: spinner_drivers_currsmp 11 | 12 | Implementations 13 | --------------- 14 | 15 | .. toctree:: 16 | :glob: 17 | 18 | impl/* 19 | -------------------------------------------------------------------------------- /docs/_doxygen/groups.dox: -------------------------------------------------------------------------------- 1 | /** 2 | * @defgroup spinner_drivers Driver APIs 3 | * @{ 4 | * @} 5 | */ 6 | 7 | /** 8 | * @defgroup spinner_lib_control Control APIs 9 | * @{ 10 | * @} 11 | */ 12 | 13 | /** 14 | * @defgroup spinner_lib_utils Utility APIs 15 | * @{ 16 | * @} 17 | */ 18 | 19 | 20 | -------------------------------------------------------------------------------- /lib/control/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Teslabs Engineering S.L. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | if(CONFIG_SPINNER_CLOOP) 5 | zephyr_library() 6 | zephyr_library_sources(cloop.c) 7 | zephyr_library_sources_ifdef(CONFIG_SPINNER_CLOOP_SHELL cloop_shell.c) 8 | endif() 9 | 10 | -------------------------------------------------------------------------------- /drivers/feedback/Kconfig.stm32: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Teslabs Engineering S.L. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | config SPINNER_FEEDBACK_HALLS_STM32 5 | bool "STM32 halls feedback driver" 6 | depends on SOC_FAMILY_STM32 7 | default y 8 | depends on DT_HAS_ST_STM32_HALLS_ENABLED 9 | select USE_STM32_LL_TIM 10 | help 11 | Enable halls driver for STM32 SoCs 12 | -------------------------------------------------------------------------------- /drivers/feedback/Kconfig: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Teslabs Engineering S.L. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | menuconfig SPINNER_FEEDBACK 5 | bool "Feedback Drivers" 6 | help 7 | Enable options for feedback drivers. 8 | 9 | if SPINNER_FEEDBACK 10 | 11 | module = SPINNER_FEEDBACK 12 | module-str = SPINNER_FEEDBACK 13 | source "subsys/logging/Kconfig.template.log_config" 14 | 15 | rsource "Kconfig.stm32" 16 | 17 | endif # SPINNER_FEEDBACK 18 | -------------------------------------------------------------------------------- /drivers/currsmp/Kconfig.stm32: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Teslabs Engineering S.L. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | config SPINNER_CURRSMP_SHUNT_STM32 5 | bool "STM32 shunt current sampling driver" 6 | depends on SOC_FAMILY_STM32 7 | default y 8 | depends on DT_HAS_ST_STM32_CURRSMP_SHUNT_ENABLED 9 | select SPINNER_UTILS_STM32 10 | select USE_STM32_LL_ADC 11 | select ZERO_LATENCY_IRQS 12 | help 13 | Enable shunt current sampling driver for STM32 SoCs 14 | -------------------------------------------------------------------------------- /dts/bindings/motor/st,stspin830.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, Teslabs Engineering S.L. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | description: | 5 | STM32 STSPIN830 Compact and versatile three-phase and three-sense motor 6 | driver. 7 | 8 | compatible: "st,stspin830" 9 | 10 | include: motor-driver.yaml 11 | 12 | properties: 13 | t-rise-ns: 14 | # STSPIN830 rise time: tINd(ON) + tRISE (Table 5) 15 | default: 670 16 | 17 | enable-gpios: 18 | required: true 19 | -------------------------------------------------------------------------------- /west.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Teslabs Engineering S.L. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | manifest: 5 | self: 6 | path: spinner 7 | 8 | remotes: 9 | - name: zephyr 10 | url-base: https://github.com/zephyrproject-rtos 11 | 12 | projects: 13 | - name: zephyr 14 | remote: zephyr 15 | repo-path: zephyr 16 | revision: main 17 | import: 18 | name-allowlist: 19 | - cmsis 20 | - cmsis-dsp 21 | - hal_stm32 22 | 23 | -------------------------------------------------------------------------------- /dts/bindings/motor/st,l6230.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, Teslabs Engineering S.L. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | description: STM32 L6230 DMOS driver for three-phase brushless DC motor 5 | 6 | compatible: "st,l6230" 7 | 8 | include: motor-driver.yaml 9 | 10 | properties: 11 | t-dead-ns: 12 | # L6230 dead time (Table 6) 13 | default: 1000 14 | 15 | t-rise-ns: 16 | # L6230 rise time: tD(ON) + tRISE (Fig. 4) 17 | default: 1050 18 | 19 | enable-gpios: 20 | required: true 21 | -------------------------------------------------------------------------------- /.checkpatch.conf: -------------------------------------------------------------------------------- 1 | --emacs 2 | --summary-file 3 | --show-types 4 | --max-line-length=100 5 | --min-conf-desc-length=1 6 | --typedefsfile=../zephyr/scripts/checkpatch/typedefsfile 7 | 8 | --ignore BRACES 9 | --ignore PRINTK_WITHOUT_KERN_LEVEL 10 | --ignore SPLIT_STRING 11 | --ignore VOLATILE 12 | --ignore CONFIG_EXPERIMENTAL 13 | --ignore AVOID_EXTERNS 14 | --ignore NETWORKING_BLOCK_COMMENT_STYLE 15 | --ignore DATE_TIME 16 | --ignore MINMAX 17 | --ignore CONST_STRUCT 18 | --ignore FILE_PATH_CHANGES 19 | --ignore NEW_TYPEDEFS 20 | --exclude ext 21 | -------------------------------------------------------------------------------- /spinner/sample.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024 Teslabs Engineering S.L. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | sample: 5 | description: Spinner 6 | name: spinner 7 | 8 | common: 9 | build_only: true 10 | 11 | tests: 12 | spinner.ihm07m1: 13 | integration_platforms: 14 | - nucleo_f302r8 15 | - nucleo_g431rb 16 | extra_args: 17 | SHIELD=ihm07m1 18 | 19 | spinner.ihm16m1: 20 | integration_platforms: 21 | - nucleo_f302r8 22 | - nucleo_g431rb 23 | extra_args: 24 | SHIELD=ihm16m1 25 | -------------------------------------------------------------------------------- /drivers/svpwm/Kconfig: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Teslabs Engineering S.L. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | menuconfig SPINNER_SVPWM 5 | bool "SV-PWM Drivers" 6 | help 7 | Enable options for SV-PWM drivers. 8 | 9 | if SPINNER_SVPWM 10 | 11 | module = SPINNER_SVPWM 12 | module-str = SPINNER_SVPWM 13 | source "subsys/logging/Kconfig.template.log_config" 14 | 15 | config SPINNER_SVPWM_INIT_PRIORITY 16 | int "SV-PWM init priority" 17 | default 90 18 | help 19 | SV-PWM initialization priority. 20 | 21 | rsource "Kconfig.stm32" 22 | 23 | endif # SPINNER_SVPWM 24 | -------------------------------------------------------------------------------- /boards/shields/ihm07m1/boards/nucleo_f302r8.overlay: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Teslabs Engineering S.L. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | &svpwm { 8 | pinctrl-0 = <&tim1_ch1_pa8 &tim1_ch2_pa9 &tim1_ch3_pa10 &tim1_bkin2_pa11>; 9 | pinctrl-names = "default"; 10 | }; 11 | 12 | &currsmp { 13 | pinctrl-0 = <&adc1_in1_pa0 &adc1_in7_pc1 &adc1_in6_pc0>; 14 | pinctrl-names = "default"; 15 | 16 | adc-channels = <1 7 6>; 17 | }; 18 | 19 | &feedback { 20 | pinctrl-0 = <&tim2_ch1_pa15 &tim2_ch2_pb3 &tim2_ch3_pb10>; 21 | pinctrl-names = "default"; 22 | }; 23 | -------------------------------------------------------------------------------- /boards/shields/ihm16m1/boards/nucleo_f302r8.overlay: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Teslabs Engineering S.L. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | &svpwm { 8 | pinctrl-0 = <&tim1_ch1_pa8 &tim1_ch2_pa9 &tim1_ch3_pa10 &tim1_bkin2_pa11>; 9 | pinctrl-names = "default"; 10 | }; 11 | 12 | &currsmp { 13 | pinctrl-0 = <&adc1_in1_pa0 &adc1_in7_pc1 &adc1_in6_pc0>; 14 | pinctrl-names = "default"; 15 | 16 | adc-channels = <1 7 6>; 17 | }; 18 | 19 | &feedback { 20 | pinctrl-0 = <&tim2_ch1_pa15 &tim2_ch2_pb3 &tim2_ch3_pb10>; 21 | pinctrl-names = "default"; 22 | }; 23 | -------------------------------------------------------------------------------- /drivers/svpwm/Kconfig.stm32: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Teslabs Engineering S.L. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | config SPINNER_SVPWM_STM32 5 | bool "STM32 SV-PWM driver" 6 | depends on SOC_FAMILY_STM32 7 | default y 8 | depends on DT_HAS_ST_STM32_SVPWM_ENABLED 9 | select USE_STM32_LL_TIM 10 | select SPINNER_SVM 11 | select SPINNER_UTILS_STM32 12 | help 13 | Enable SV-PWM driver for STM32 SoCs 14 | 15 | config SPINNER_SVPWM_STM32_PWM_FREQ 16 | int "PWM frequency" 17 | default 30000 18 | depends on SPINNER_SVPWM_STM32 19 | help 20 | PWM frequency (Hz) 21 | 22 | -------------------------------------------------------------------------------- /boards/shields/ihm07m1/boards/nucleo_g431rb.overlay: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Teslabs Engineering S.L. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | &svpwm { 8 | pinctrl-0 = <&tim1_ch1_pa8 &tim1_ch2_pa9 &tim1_ch3_pa10 &tim1_bkin2_pa11>; 9 | pinctrl-names = "default"; 10 | }; 11 | 12 | &currsmp { 13 | pinctrl-0 = <&adc1_in2_pa1 &adc1_in12_pb1 &adc1_in15_pb0>; 14 | pinctrl-names = "default"; 15 | 16 | adc-channels = <2 12 15>; 17 | }; 18 | 19 | &feedback { 20 | pinctrl-0 = <&tim2_ch1_pa15 &tim2_ch2_pb3 &tim2_ch3_pb10>; 21 | pinctrl-names = "default"; 22 | }; 23 | -------------------------------------------------------------------------------- /boards/shields/ihm16m1/boards/nucleo_g431rb.overlay: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Teslabs Engineering S.L. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | &svpwm { 8 | pinctrl-0 = <&tim1_ch1_pa8 &tim1_ch2_pa9 &tim1_ch3_pa10 &tim1_bkin2_pa11>; 9 | pinctrl-names = "default"; 10 | }; 11 | 12 | &currsmp { 13 | pinctrl-0 = <&adc1_in2_pa1 &adc1_in12_pb1 &adc1_in15_pb0>; 14 | pinctrl-names = "default"; 15 | 16 | adc-channels = <2 12 15>; 17 | }; 18 | 19 | &feedback { 20 | pinctrl-0 = <&tim2_ch1_pa15 &tim2_ch2_pb3 &tim2_ch3_pb10>; 21 | pinctrl-names = "default"; 22 | }; 23 | -------------------------------------------------------------------------------- /drivers/currsmp/Kconfig: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Teslabs Engineering S.L. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | menuconfig SPINNER_CURRSMP 5 | bool "Current Sample Drivers" 6 | help 7 | Enable options for current sample drivers. 8 | 9 | if SPINNER_CURRSMP 10 | 11 | module = SPINNER_CURRSMP 12 | module-str = SPINNER_CURRSMP 13 | source "subsys/logging/Kconfig.template.log_config" 14 | 15 | config SPINNER_CURRSMP_INIT_PRIORITY 16 | int "Current sampling init priority" 17 | default 80 18 | help 19 | Current sampling initialization priority. 20 | 21 | rsource "Kconfig.stm32" 22 | 23 | endif # SPINNER_CURRSMP 24 | -------------------------------------------------------------------------------- /boards/shields/ihm07m1/ihm07m1.overlay: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Teslabs Engineering S.L. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | &svpwm { 8 | driver { 9 | compatible = "st,l6230"; 10 | enable-gpios = <&st_morpho_header ST_MORPHO_L_1 GPIO_ACTIVE_HIGH>, 11 | <&st_morpho_header ST_MORPHO_L_2 GPIO_ACTIVE_HIGH>, 12 | <&st_morpho_header ST_MORPHO_L_3 GPIO_ACTIVE_HIGH>; 13 | }; 14 | }; 15 | 16 | &feedback { 17 | h1-gpios = <&st_morpho_header ST_MORPHO_L_17 GPIO_ACTIVE_HIGH>; 18 | h2-gpios = <&st_morpho_header ST_MORPHO_R_31 GPIO_ACTIVE_HIGH>; 19 | h3-gpios = <&st_morpho_header ST_MORPHO_R_25 GPIO_ACTIVE_HIGH>; 20 | }; 21 | -------------------------------------------------------------------------------- /boards/extensions/nucleo_f302r8/nucleo_f302r8.overlay: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Teslabs Engineering S.L. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #include 8 | 9 | &timers1 { 10 | svpwm: svpwm { 11 | compatible = "st,stm32-svpwm"; 12 | currsmp = <&currsmp>; 13 | }; 14 | }; 15 | 16 | &adc1 { 17 | currsmp: currsmp { 18 | compatible = "st,stm32-currsmp-shunt"; 19 | adc-resolution = <12>; 20 | adc-tsample = <2>; 21 | adc-trigger = ; 22 | }; 23 | }; 24 | 25 | &timers2 { 26 | feedback: feedback { 27 | compatible = "st,stm32-halls"; 28 | phase-shift = <60>; 29 | }; 30 | }; 31 | -------------------------------------------------------------------------------- /boards/shields/ihm16m1/ihm16m1.overlay: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Teslabs Engineering S.L. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | &svpwm { 8 | driver { 9 | compatible = "st,stspin830"; 10 | enable-gpios = <&st_morpho_header ST_MORPHO_R_11 GPIO_ACTIVE_HIGH>, 11 | <&st_morpho_header ST_MORPHO_R_13 GPIO_ACTIVE_HIGH>, 12 | <&st_morpho_header ST_MORPHO_R_15 GPIO_ACTIVE_HIGH>; 13 | }; 14 | }; 15 | 16 | &feedback { 17 | h1-gpios = <&st_morpho_header ST_MORPHO_L_17 GPIO_ACTIVE_HIGH>; 18 | h2-gpios = <&st_morpho_header ST_MORPHO_R_31 GPIO_ACTIVE_HIGH>; 19 | h3-gpios = <&st_morpho_header ST_MORPHO_R_25 GPIO_ACTIVE_HIGH>; 20 | }; 21 | -------------------------------------------------------------------------------- /spinner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------- 2 | # Spinner 3 | # 4 | # Copyright (c) 2021 Teslabs Engineering S.L. 5 | # SPDX-License-Identifier: Apache-2.0 6 | 7 | cmake_minimum_required(VERSION 3.13.1) 8 | find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) 9 | 10 | project(spinner LANGUAGES C VERSION 1.0.0) 11 | 12 | #------------------------------------------------------------------------------- 13 | # Options 14 | 15 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 16 | 17 | #------------------------------------------------------------------------------- 18 | # Application 19 | 20 | target_sources(app PRIVATE main.c) 21 | -------------------------------------------------------------------------------- /boards/extensions/nucleo_g431rb/nucleo_g431rb.overlay: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Teslabs Engineering S.L. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #include 8 | 9 | &timers1 { 10 | svpwm: svpwm { 11 | compatible = "st,stm32-svpwm"; 12 | currsmp = <&currsmp>; 13 | }; 14 | }; 15 | 16 | &adc1 { 17 | currsmp: currsmp { 18 | compatible = "st,stm32-currsmp-shunt"; 19 | 20 | adc-resolution = <12>; 21 | adc-tsample = <3>; 22 | adc-trigger = ; 23 | }; 24 | }; 25 | 26 | &timers2 { 27 | feedback: feedback { 28 | compatible = "st,stm32-halls"; 29 | phase-shift = <60>; 30 | }; 31 | }; 32 | -------------------------------------------------------------------------------- /docs/bibliography.bib: -------------------------------------------------------------------------------- 1 | @manual{rm0365, 2 | title = {RM0365 STM32F302xB/C/D/E and STM32F302x6/8 advanced ARM®-based 32-bit MCUs}, 3 | author = {STMicroelectronics}, 4 | edition = {8th}, 5 | year = {2020}, 6 | url = {https://www.st.com/resource/en/reference_manual/dm00094349-stm32f302xb-c-d-e-and-stm32f302x6-8-advanced-arm-based-32-bit-mcus-stmicroelectronics.pdf}, 7 | note = {[Online; accessed 4-May-2021]} 8 | } 9 | 10 | @manual{an4013, 11 | title = {AN4013 STM32 cross-series timer overview}, 12 | author = {STMicroelectronics}, 13 | edition = {8th}, 14 | year = {2019}, 15 | url = {https://www.st.com/resource/en/application_note/dm00042534-stm32-crossseries-timer-overview-stmicroelectronics.pdf}, 16 | note = {[Online; accessed 4-May-2021]} 17 | } 18 | -------------------------------------------------------------------------------- /docs/doxygen.md: -------------------------------------------------------------------------------- 1 | # Spinner API Documentation 2 | 3 | Welcome to the Spinner's API documentation! Spinner is a motor control firmware 4 | based on the Field Oriented Control (FOC) principles. The firmware is built on 5 | top of the [Zephyr RTOS](https://zephyrproject.org), a modern multi-platform 6 | RTOS. Spinner is still a proof of concept, so do not expect production grade 7 | stability or features. 8 | 9 | Some of the offered features are: 10 | 11 | - FOC based current control loop 12 | - Driver APIs for: 13 | 14 | - Current sampling 15 | - SV-PWM 16 | - Feedbacks (e.g. Halls) 17 | 18 | These pages contain documentation for all the APIs used in the Spinner 19 | firmware. For more details on the Spinner architecture, refer to the contextual 20 | documentation pages. 21 | -------------------------------------------------------------------------------- /include/spinner/control/cloop.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * 4 | * Current loop API. 5 | * 6 | * Copyright (c) 2021 Teslabs Engineering S.L. 7 | * SPDX-License-Identifier: Apache-2.0 8 | */ 9 | 10 | #ifndef _SPINNER_LIB_CONTROL_CLOOP_H_ 11 | #define _SPINNER_LIB_CONTROL_CLOOP_H_ 12 | 13 | /** 14 | * @defgroup spinner_lib_control_cloop Current Loop API 15 | * @ingroup spinner_lib_control 16 | * @{ 17 | */ 18 | 19 | /** 20 | * @brief Start current loop. 21 | */ 22 | void cloop_start(void); 23 | 24 | /** 25 | * @brief Stop current loop. 26 | */ 27 | void cloop_stop(void); 28 | 29 | /** 30 | * @brief Set current loop working point. 31 | * 32 | * @param[in] i_d i_d current value. 33 | * @param[in] i_q i_q current value. 34 | */ 35 | void cloop_set_ref(float i_d, float i_q); 36 | 37 | /** @} */ 38 | 39 | #endif /* _SPINNER_LIB_CONTROL_CLOOP_H_ */ 40 | -------------------------------------------------------------------------------- /docs/components/cloop/index.rst: -------------------------------------------------------------------------------- 1 | Current Loop 2 | ============ 3 | 4 | The current control loop is the component responsible of controlling the 5 | inverter input by providing the control signals in the :math:`\alpha,\beta` 6 | space. These are used by the SV-PWM driver to generate the PWM signals as well 7 | as adjust the current sampling time. The current loop makes use of the estimated phase 8 | currents, :math:`\hat{i}_a,~\hat{i}_b`, and the estimated electrical angle, 9 | :math:`\hat{\theta}` to apply the principles of Field Oriented Control (FOC). 10 | :numref:`cloop-schematic` shows a block diagram that illustrates how all the 11 | components are connected. 12 | 13 | .. _cloop-schematic: 14 | .. figure:: images/cloop-only-schematic.svg 15 | 16 | Current loop (highlighted blocks) 17 | 18 | API 19 | --- 20 | 21 | .. doxygengroup:: spinner_lib_control_cloop 22 | -------------------------------------------------------------------------------- /include/spinner/utils/stm32_tim.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * 4 | * STM32 Timer Utilities. 5 | * 6 | * Copyright (c) 2021 Teslabs Engineering S.L. 7 | * SPDX-License-Identifier: Apache-2.0 8 | */ 9 | 10 | #ifndef _SPINNER_LIB_UTILS_STM32_TIM_H_ 11 | #define _SPINNER_LIB_UTILS_STM32_TIM_H_ 12 | 13 | #include 14 | 15 | /** 16 | * @defgroup spinner_utils_stm32_tim STM32 Timer Utilities 17 | * @ingroup spinner_lib_utils 18 | * @{ 19 | */ 20 | 21 | /** 22 | * Obtain timer clock speed. 23 | * 24 | * @param[in] pclken Timer clock control subsystem. 25 | * @param[out] tim_clk Where computed timer clock will be stored. 26 | * 27 | * @return 0 on success, error code otherwise. 28 | */ 29 | int stm32_tim_clk_get(const struct stm32_pclken *pclken, uint32_t *tim_clk); 30 | 31 | /** @} */ 32 | 33 | #endif /* _SPINNER_LIB_UTILS_STM32_TIM_H_ */ 34 | -------------------------------------------------------------------------------- /dts/bindings/svpwm/st,stm32-svpwm.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021, Teslabs Engineering S.L. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | description: | 5 | STM32 SV-PWM device. 6 | 7 | The SV-PWM device is expected to be a children of any STM32 advanced control 8 | timer. Example usage: 9 | 10 | &timers1 { 11 | svpwm: svpwm { 12 | compatible = "st,stm32-svpwm"; 13 | pinctrl-0 = <&tim1_ch1_pa8 &tim1_ch2_pa9 &tim1_ch3_pa10 &tim1_ocp_pa11>; 14 | pinctrl-names = "default"; 15 | ... 16 | }; 17 | }; 18 | 19 | compatible: "st,stm32-svpwm" 20 | 21 | include: [base.yaml, pinctrl-device.yaml] 22 | 23 | properties: 24 | pinctrl-0: 25 | required: true 26 | 27 | pinctrl-names: 28 | required: true 29 | 30 | currsmp: 31 | type: phandle 32 | required: true 33 | description: | 34 | Current sampling device. 35 | -------------------------------------------------------------------------------- /dts/bindings/motor/motor-driver.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, Teslabs Engineering S.L. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | description: Bindings for motor driver ICs/circuits 5 | 6 | include: base.yaml 7 | 8 | properties: 9 | enable-comp-outputs: 10 | type: boolean 11 | description: | 12 | Enable complementary outputs, used to control the low side channels of 13 | each inverter leg. 14 | 15 | t-dead-ns: 16 | type: int 17 | required: false 18 | description: | 19 | Dead time in nanoseconds. If using an integrated controller, i.e. without 20 | complementary PWM signals, it still needs to be provided to configure 21 | accurate current measurements. 22 | 23 | t-rise-ns: 24 | type: int 25 | required: false 26 | description: | 27 | Rise time in nanoseconds. 28 | 29 | enable-gpios: 30 | type: phandle-array 31 | description: | 32 | Channel enable GPIOS (a, b, c). 33 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig: https://editorconfig.org/ 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # All (Defaults) 7 | [*] 8 | charset = utf-8 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | max_line_length = 100 13 | 14 | # Assembly 15 | [*.S] 16 | indent_style = tab 17 | indent_size = 8 18 | 19 | # C 20 | [*.{c,h}] 21 | indent_style = tab 22 | indent_size = 8 23 | 24 | # Python 25 | [*.py] 26 | indent_style = space 27 | indent_size = 4 28 | 29 | # YAML 30 | [*.{yml,yaml}] 31 | indent_style = space 32 | indent_size = 2 33 | 34 | # Shell Script 35 | [*.sh] 36 | indent_style = space 37 | indent_size = 4 38 | 39 | # CMake 40 | [{CMakeLists.txt,*.cmake}] 41 | indent_style = space 42 | indent_size = 2 43 | 44 | # Makefile 45 | [Makefile] 46 | indent_style = tab 47 | 48 | # Kconfig 49 | [Kconfig] 50 | indent_style = tab 51 | 52 | # devicetree 53 | [*.{dts,dtsi,overlay}] 54 | indent_style = tab 55 | indent_size = 8 56 | -------------------------------------------------------------------------------- /docs/theory/images/svpwm-abc-signs.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | 4 | 5 | fig, ax = plt.subplots() 6 | 7 | ax.set_xlabel("State space vector angle (deg)") 8 | ax.set_ylabel(r"$a, b, c$") 9 | 10 | alphad = np.arange(0, 360, 1) 11 | alpha = np.deg2rad(alphad) 12 | 13 | # amplitude assumed sqrt(3) / 2 (maximum) 14 | a = np.sqrt(3) / 2 * np.cos(alpha) - 1 / 2 * np.sin(alpha) 15 | b = np.sin(alpha) 16 | c = -(a + b) 17 | 18 | ax.plot(alphad, a, color="r", label="a") 19 | ax.plot(alphad, b, color="g", label="b") 20 | ax.plot(alphad, c, color="b", label="c") 21 | 22 | ax.set_xticks([0, 60, 120, 180, 240, 300, 360]) 23 | ax.set_xlim([0, 360]) 24 | 25 | ax.set_yticks([-1, 0, 1]) 26 | ax.set_ylim([-1, 1]) 27 | 28 | ax.axvline(0, ls="--") 29 | ax.axvline(60, ls="--") 30 | ax.axvline(120, ls="--") 31 | ax.axvline(180, ls="--") 32 | ax.axvline(240, ls="--") 33 | ax.axvline(300, ls="--") 34 | ax.axvline(360, ls="--") 35 | 36 | ax.legend(loc="upper right") 37 | 38 | plt.show() 39 | -------------------------------------------------------------------------------- /docs/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13.1) 2 | project(docs LANGUAGES NONE) 3 | 4 | find_package(Doxygen REQUIRED) 5 | find_program(SPHINXBUILD NAMES sphinx-build) 6 | if(NOT SPHINXBUILD) 7 | message(FATAL_ERROR "The 'sphinx-build' command was not found") 8 | endif() 9 | 10 | set(SPINNER_BASE ${CMAKE_SOURCE_DIR}/..) 11 | set(DOXYGEN_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/html/doxygen) 12 | set(SPHINXOPTS "" CACHE STRING "Sphinx options") 13 | 14 | configure_file(Doxyfile.in ${CMAKE_BINARY_DIR}/Doxyfile) 15 | 16 | add_custom_target( 17 | doxygen 18 | COMMAND 19 | ${CMAKE_COMMAND} -E make_directory ${DOXYGEN_OUTPUT_DIRECTORY} 20 | COMMAND 21 | ${DOXYGEN_EXECUTABLE} ${CMAKE_BINARY_DIR}/Doxyfile 22 | ) 23 | 24 | add_custom_target( 25 | html 26 | COMMAND 27 | ${CMAKE_COMMAND} -E env 28 | SPINNER_BASE=${SPINNER_BASE} 29 | DOXYGEN_OUTPUT_DIRECTORY=${DOXYGEN_OUTPUT_DIRECTORY} 30 | ${SPHINXBUILD} -b html ${SPHINXOPTS} ${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR}/html 31 | ) 32 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021, Teslabs Engineering S.L. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | name: Documentation 5 | 6 | on: [push, pull_request] 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v3 14 | 15 | - name: Install dependencies 16 | run: | 17 | sudo apt install -y cmake ninja-build python3 python3-pip doxygen graphviz 18 | pip3 install -r docs/requirements.txt 19 | 20 | - name: Build 21 | run: | 22 | cmake -Sdocs -Bbuild_docs 23 | cmake --build build_docs -t doxygen 24 | cmake --build build_docs -t html 25 | touch build_docs/html/.nojekyll 26 | 27 | - name: Deploy 28 | uses: JamesIves/github-pages-deploy-action@v4 29 | if: github.ref == 'refs/heads/main' 30 | with: 31 | branch: gh-pages 32 | folder: build_docs/html 33 | single-commit: true 34 | 35 | -------------------------------------------------------------------------------- /lib/utils/stm32_tim.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Teslabs Engineering S.L. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | #include 7 | 8 | #include 9 | 10 | int stm32_tim_clk_get(const struct stm32_pclken *pclken, uint32_t *tim_clk) 11 | { 12 | int ret; 13 | const struct device *clk; 14 | uint32_t bus_clk, apb_psc; 15 | 16 | clk = DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE); 17 | 18 | ret = clock_control_get_rate(clk, (clock_control_subsys_t *)pclken, 19 | &bus_clk); 20 | if (ret < 0) { 21 | return ret; 22 | } 23 | 24 | if (pclken->bus == STM32_CLOCK_BUS_APB1) { 25 | apb_psc = STM32_APB1_PRESCALER; 26 | } else { 27 | apb_psc = STM32_APB2_PRESCALER; 28 | } 29 | 30 | /* 31 | * If the APB prescaler equals 1, the timer clock frequencies 32 | * are set to the same frequency as that of the APB domain. 33 | * Otherwise, they are set to twice (×2) the frequency of the 34 | * APB domain. 35 | */ 36 | if (apb_psc == 1U) { 37 | *tim_clk = bus_clk; 38 | } else { 39 | *tim_clk = bus_clk * 2U; 40 | } 41 | 42 | return 0; 43 | } 44 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021, Teslabs Engineering S.L. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | name: Build 5 | 6 | on: [push, pull_request] 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-22.04 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v4 14 | with: 15 | path: spinner 16 | 17 | - name: Set up Python 18 | uses: actions/setup-python@v4 19 | with: 20 | python-version: 3.11 21 | 22 | - name: Setup Zephyr project 23 | uses: zephyrproject-rtos/action-zephyr-setup@v1 24 | with: 25 | app-path: spinner 26 | toolchains: arm-zephyr-eabi 27 | 28 | - name: Lint code 29 | run: | 30 | git diff --name-only ${{ github.base_ref}} ${{ github.sha }} -- '*.[ch]' | xargs zephyr/scripts/checkpatch.pl 31 | 32 | - name: Build 33 | working-directory: spinner 34 | run: | 35 | west twister -T spinner -v --inline-logs --integration 36 | 37 | - name: Test 38 | working-directory: spinner 39 | run: | 40 | west twister -T tests -v --inline-logs --integration 41 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | ===================== 2 | Spinner Documentation 3 | ===================== 4 | 5 | Welcome to the Spinner's documentation! Spinner is a motor control firmware 6 | based on the Field Oriented Control (FOC) principles. The firmware is built 7 | on top of the `Zephyr RTOS`_, a modern multi-platform RTOS. Spinner is still 8 | a proof of concept, so do not expect production grade stability or features. 9 | 10 | Some of the offered features are: 11 | 12 | - FOC based current control loop 13 | - Driver APIs for: 14 | 15 | - Current sampling 16 | - SV-PWM 17 | - Feedbacks (e.g. Halls) 18 | 19 | These documentation pages contain architecture details of the Spinner 20 | firmware as well as some driver implementation details. 21 | 22 | .. _Zephyr RTOS: https://zephyrproject.org 23 | 24 | .. toctree:: 25 | :caption: Theory 26 | :glob: 27 | :hidden: 28 | 29 | theory/* 30 | 31 | .. toctree:: 32 | :caption: Components 33 | :glob: 34 | :hidden: 35 | 36 | components/**/index 37 | 38 | .. toctree:: 39 | :caption: Reference 40 | :hidden: 41 | 42 | API 43 | zbibliography 44 | -------------------------------------------------------------------------------- /lib/control/Kconfig: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Teslabs Engineering S.L. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | menuconfig SPINNER_CLOOP 5 | bool "Current Loop" 6 | select SPINNER_SVM 7 | select CMSIS_DSP 8 | select CMSIS_DSP_CONTROLLER 9 | select CMSIS_DSP_TABLES_ARM_SIN_COS_F32 10 | depends on SPINNER_CURRSMP && SPINNER_FEEDBACK && SPINNER_SVPWM 11 | 12 | if SPINNER_CLOOP 13 | 14 | config SPINNER_CLOOP_SHELL 15 | bool "Control loop shell" 16 | default y 17 | depends on SHELL 18 | help 19 | Utility shell to test current loop. 20 | 21 | config SPINNER_CLOOP_T_KP 22 | int "Torque PID proportional constant" 23 | default 1500 24 | help 25 | Torque PID controller proportional (Kp) constant. Value is in thousands. 26 | 27 | config SPINNER_CLOOP_T_KI 28 | int "Torque PID integral constant" 29 | default 0 30 | help 31 | Torque PID controller Integral (Ki) constant. Value is in thousands. 32 | 33 | config SPINNER_CLOOP_F_KP 34 | int "Flux PID proportional constant" 35 | default 1500 36 | help 37 | Flux PID controller proportional (Kp) constant. Value is in thousands. 38 | 39 | config SPINNER_CLOOP_F_KI 40 | int "Flux PID integral constant" 41 | default 0 42 | help 43 | Flux PID controller integral (Ki) constant. Value is in thousands. 44 | 45 | endif # SPINNER_CLOOP 46 | 47 | -------------------------------------------------------------------------------- /include/spinner/svm/svm.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * 4 | * Space Vector Modulation (SVM). 5 | * 6 | * Copyright (c) 2021 Teslabs Engineering S.L. 7 | * SPDX-License-Identifier: Apache-2.0 8 | */ 9 | 10 | #ifndef _SPINNER_LIB_SVM_SVM_H_ 11 | #define _SPINNER_LIB_SVM_SVM_H_ 12 | 13 | #include 14 | 15 | /** 16 | * @defgroup spinner_control_svm Space Vector Modulation (SVM) API 17 | * @{ 18 | */ 19 | 20 | /** @brief SVM duty cycles. */ 21 | typedef struct { 22 | /** A channel duty cycle. */ 23 | float a; 24 | /** B channel duty cycle. */ 25 | float b; 26 | /** C channel duty cycle. */ 27 | float c; 28 | } svm_duties_t; 29 | 30 | /** @brief SVM state. */ 31 | typedef struct svm { 32 | /** SVM sector. */ 33 | uint8_t sector; 34 | /** Duty cycles. */ 35 | svm_duties_t duties; 36 | /** Minimum allowed duty cycle. */ 37 | float d_min; 38 | /** Maximum allowed duty cycle. */ 39 | float d_max; 40 | } svm_t; 41 | 42 | /** 43 | * @brief Initialize SVM. 44 | * 45 | * @param[in] svm SVM instance. 46 | */ 47 | void svm_init(svm_t *svm); 48 | 49 | /** 50 | * @brief Set v_alpha and v_beta. 51 | * 52 | * @param[in] svm SVM instance. 53 | * @param[in] va v_alpha value. 54 | * @param[in] vb v_beta value. 55 | */ 56 | void svm_set(svm_t *svm, float va, float vb); 57 | 58 | /** @} */ 59 | 60 | #endif /* _SPINNER_LIB_SVM_SVM_H_ */ 61 | -------------------------------------------------------------------------------- /include/spinner/drivers/feedback.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * 4 | * Feedback API. 5 | * 6 | * Copyright (c) 2021 Teslabs Engineering S.L. 7 | * SPDX-License-Identifier: Apache-2.0 8 | */ 9 | 10 | #ifndef _SPINNER_DRIVERS_FEEDBACK_H_ 11 | #define _SPINNER_DRIVERS_FEEDBACK_H_ 12 | 13 | #include 14 | #include 15 | 16 | /** 17 | * @defgroup spinner_drivers_feedback Feedback API 18 | * @ingroup spinner_drivers 19 | * @{ 20 | */ 21 | 22 | /** @cond INTERNAL_HIDDEN */ 23 | 24 | struct feedback_driver_api { 25 | float (*get_eangle)(const struct device *dev); 26 | float (*get_speed)(const struct device *dev); 27 | }; 28 | 29 | /** @endcond */ 30 | 31 | /** 32 | * @brief Get electrical angle. 33 | * 34 | * @param dev Feedback instance. 35 | * @return Electrical angle. 36 | */ 37 | static inline float feedback_get_eangle(const struct device *dev) 38 | { 39 | const struct feedback_driver_api *api = dev->api; 40 | 41 | return api->get_eangle(dev); 42 | } 43 | 44 | /** 45 | * @brief Get speed. 46 | * 47 | * @param dev Feedback instance. 48 | * @return Speed. 49 | */ 50 | static inline float feedback_get_speed(const struct device *dev) 51 | { 52 | const struct feedback_driver_api *api = dev->api; 53 | 54 | return api->get_speed(dev); 55 | } 56 | 57 | /** @} */ 58 | 59 | #endif /* _SPINNER_DRIVERS_FEEDBACK_H_ */ 60 | -------------------------------------------------------------------------------- /include/dts-bindings/adc/stm32f3xx.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * 4 | * DT definitions for STM32 ADC (F3XX series) 5 | * 6 | * Copyright (c) 2021 Teslabs Engineering S.L. 7 | * SPDX-License-Identifier: Apache-2.0 8 | */ 9 | 10 | #ifndef _DTS_BINDINGS_INVERTER_STM32F3XX_H_ 11 | #define _DTS_BINDINGS_INVERTER_STM32F3XX_H_ 12 | 13 | /* Ref. RM0365, Rev. 8, Table 88. */ 14 | 15 | #define _STM32_ADC_JEXT_POS 2U 16 | 17 | #define STM32_ADC_INJ_TRIG_TIM1_TRGO (0U << _STM32_ADC_JEXT_POS) 18 | #define STM32_ADC_INJ_TRIG_TIM1_CC4 (1U << _STM32_ADC_JEXT_POS) 19 | #define STM32_ADC_INJ_TRIG_TIM2_TRGO (2U << _STM32_ADC_JEXT_POS) 20 | #define STM32_ADC_INJ_TRIG_TIM2_CC1 (3U << _STM32_ADC_JEXT_POS) 21 | #define STM32_ADC_INJ_TRIG_TIM3_CC4 (4U << _STM32_ADC_JEXT_POS) 22 | #define STM32_ADC_INJ_TRIG_TIM4_TRGO (5U << _STM32_ADC_JEXT_POS) 23 | #define STM32_ADC_INJ_TRIG_EXTI15 (6U << _STM32_ADC_JEXT_POS) 24 | #define STM32_ADC_INJ_TRIG_TIM1_TRGO2 (8U << _STM32_ADC_JEXT_POS) 25 | #define STM32_ADC_INJ_TRIG_TIM3_CC3 (11U << _STM32_ADC_JEXT_POS) 26 | #define STM32_ADC_INJ_TRIG_TIM3_TRGO (12U << _STM32_ADC_JEXT_POS) 27 | #define STM32_ADC_INJ_TRIG_TIM3_CC1 (13U << _STM32_ADC_JEXT_POS) 28 | #define STM32_ADC_INJ_TRIG_TIM6_TRGO (14U << _STM32_ADC_JEXT_POS) 29 | #define STM32_ADC_INJ_TRIG_TIM15_TRGO (15U << _STM32_ADC_JEXT_POS) 30 | 31 | #endif /* _DTS_BINDINGS_INVERTER_STM32F3XX_H_ */ 32 | -------------------------------------------------------------------------------- /lib/control/cloop_shell.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Teslabs Engineering S.L. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | #include 7 | 8 | #include 9 | 10 | #include 11 | 12 | static int cmd_cloop_start(const struct shell *shell, size_t argc, char **argv) 13 | { 14 | ARG_UNUSED(shell); 15 | ARG_UNUSED(argc); 16 | ARG_UNUSED(argv); 17 | 18 | cloop_start(); 19 | 20 | return 0; 21 | } 22 | 23 | static int cmd_cloop_stop(const struct shell *shell, size_t argc, char **argv) 24 | { 25 | ARG_UNUSED(shell); 26 | ARG_UNUSED(argc); 27 | ARG_UNUSED(argv); 28 | 29 | cloop_stop(); 30 | 31 | return 0; 32 | } 33 | 34 | static int cmd_cloop_set(const struct shell *shell, size_t argc, char **argv) 35 | { 36 | if (argc != 2) { 37 | shell_help(shell); 38 | return -EINVAL; 39 | } 40 | 41 | /* NOTE: i_d = 0, assuming PMSM */ 42 | cloop_set_ref(0.0f, strtof(argv[1], NULL)); 43 | 44 | return 0; 45 | } 46 | 47 | SHELL_STATIC_SUBCMD_SET_CREATE( 48 | sub_cloop, 49 | SHELL_CMD(start, NULL, "Start current regulation loop", 50 | cmd_cloop_start), 51 | SHELL_CMD(stop, NULL, "Stop current regulation loop", cmd_cloop_stop), 52 | SHELL_CMD(set, NULL, "Set current regulation loop target", 53 | cmd_cloop_set), 54 | SHELL_SUBCMD_SET_END); 55 | 56 | SHELL_CMD_REGISTER(cloop, &sub_cloop, "Current Loop Control", NULL); 57 | -------------------------------------------------------------------------------- /docs/components/svpwm/index.rst: -------------------------------------------------------------------------------- 1 | SV-PWM 2 | ====== 3 | 4 | Introduction 5 | ------------ 6 | 7 | The SV-PWM driver is the responsible to control the power stage, that is, the 8 | circuit that powers the motor. The power stage is formed by three half-bridge 9 | circuits, one for each motor phase. Each half-bridge is composed by two 10 | *switches*: :math:`\mathrm{q_i}` and :math:`\mathrm{\bar{q_i}}`, where 11 | :math:`\mathrm{i} \in \mathrm{(a, b, c)}` (:numref:`ps-schematic`). These 12 | switches are implemented using transistors (e.g. MOSFET, GaN...). 13 | 14 | .. _ps-schematic: 15 | .. figure:: images/ps-schematic.svg 16 | 17 | Schematic of the power stage *switches* 18 | 19 | As the notation suggests, the switches are by complementary PWM signals, 20 | sometimes with the insertion of dead-time. The modulation scheme implemented by 21 | the driver is known as *Space Vector PWM*, hence its name. The theoretical 22 | details of this modulation can be found at the :doc:`/theory/svpwm` page. When 23 | using shunt resistors, current sampling needs to be synchronized with the PWM 24 | generation. Because of this reason, the SV-PWM driver also takes care of current 25 | sampling synchronization. The theoretical details can be found at the 26 | :doc:`/theory/currsmp` page. 27 | 28 | API 29 | --- 30 | 31 | .. doxygengroup:: spinner_drivers_svpwm 32 | 33 | 34 | Implementations 35 | --------------- 36 | 37 | .. toctree:: 38 | :glob: 39 | 40 | impl/* 41 | -------------------------------------------------------------------------------- /docs/theory/images/svpwm-hexagon.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | import matplotlib.lines as lines 4 | 5 | 6 | def v_s(q_c, q_b, q_a): 7 | return q_a + q_b * np.exp(1j * 2 * np.pi / 3) + q_c * np.exp(1j * 4 * np.pi / 3) 8 | 9 | 10 | def plot_vector(ax, v, label): 11 | r, i = np.real(v), np.imag(v) 12 | ax.quiver(0, 0, r, i, color="k", angles="xy", scale_units="xy", scale=1) 13 | ha = "left" if r > 0 else "right" 14 | va = "bottom" if i > 0 else "top" 15 | ax.annotate(label, (r, i), ha=ha, va=va) 16 | 17 | 18 | fig, ax = plt.subplots() 19 | ax.set_aspect("equal") 20 | ax.set_axis_off() 21 | ax.set_xlim([-1, 1]) 22 | ax.set_ylim([-1, 1]) 23 | 24 | # space vectors 25 | plot_vector(ax, v_s(0, 0, 0), r"$\vec{v_0} (000)$") 26 | plot_vector(ax, v_s(0, 0, 1), r"$\vec{v_1} (001)$") 27 | plot_vector(ax, v_s(0, 1, 0), r"$\vec{v_2} (010)$") 28 | plot_vector(ax, v_s(0, 1, 1), r"$\vec{v_3} (011)$") 29 | plot_vector(ax, v_s(1, 0, 0), r"$\vec{v_4} (100)$") 30 | plot_vector(ax, v_s(1, 0, 1), r"$\vec{v_5} (101)$") 31 | plot_vector(ax, v_s(1, 1, 0), r"$\vec{v_6} (110)$") 32 | plot_vector(ax, v_s(1, 1, 1), r"$\vec{v_7} (111)$") 33 | 34 | # hexagon 35 | angles = np.linspace(0, 2 * np.pi, 7) 36 | for i in range(len(angles) - 1): 37 | ax.add_line( 38 | lines.Line2D( 39 | [np.cos(angles[i]), np.cos(angles[i + 1])], 40 | [np.sin(angles[i]), np.sin(angles[i + 1])], 41 | color="k", 42 | ) 43 | ) 44 | 45 | plt.show() 46 | -------------------------------------------------------------------------------- /dts/bindings/feedback/st,stm32-halls.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021, Teslabs Engineering S.L. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | description: | 5 | Halls sensor driver for STM32 microcontrollers. 6 | 7 | The halls device is expected to be a children of any STM32 timer supporting 8 | the HALLS interface. Example usage: 9 | 10 | &timers2 { 11 | status = "okay"; 12 | 13 | feedback: feedback { 14 | compatible = "st,stm32-halls"; 15 | 16 | pinctrl-0 = <&tim2_ch1_pa15 &tim2_ch2_pb3 &tim2_ch3_pb10>; 17 | pinctrl-names = "default"; 18 | 19 | h1-gpios = <&gpioa 15 0>; 20 | h2-gpios = <&gpiob 3 0>; 21 | h3-gpios = <&gpiob 10 0>; 22 | phase-shift = <60>; 23 | }; 24 | }; 25 | 26 | compatible: "st,stm32-halls" 27 | 28 | include: [base.yaml, pinctrl-device.yaml] 29 | 30 | properties: 31 | pinctrl-0: 32 | required: true 33 | 34 | pinctrl-names: 35 | required: true 36 | 37 | h1-gpios: 38 | type: phandle-array 39 | required: true 40 | description: | 41 | H1 GPIO 42 | 43 | h2-gpios: 44 | type: phandle-array 45 | required: true 46 | description: | 47 | H2 GPIO 48 | 49 | h3-gpios: 50 | type: phandle-array 51 | required: true 52 | description: | 53 | H3 GPIO 54 | 55 | phase-shift: 56 | type: int 57 | default: 0 58 | description: | 59 | Phase shift between the low to high transition of signal H1 and the 60 | maximum of the Bemf induced on phase A. 61 | -------------------------------------------------------------------------------- /docs/components/feedback/index.rst: -------------------------------------------------------------------------------- 1 | Feedback 2 | ======== 3 | 4 | Introduction 5 | ------------ 6 | 7 | Feedback drivers are responsible of providing information of the rotor position 8 | or speed. One of the core principles of FOC is the knowledge of the rotor 9 | position, therefore, it is one of the core devices of the system. To be precise, 10 | FOC depends on the knowledge of the electrical angle of the motor, which is 11 | directly related to the rotor position via the number of pair poles. 12 | 13 | There is a wide variety of feedback sensors. Their choice depends on the end 14 | application or required control. For example, Halls may be a good choice for 15 | speed control as detailed above. However, for accurate position control digital 16 | encoders may be a better candidate. 17 | 18 | Halls 19 | ***** 20 | 21 | Hall sensors are a common type of feedback based on the `Hall effect`_. The 22 | sensor is actually composed by three individual hall sensors equally distributed 23 | in the distance of one electrical revolution. The combination of the three 24 | output signals using the XOR function results in a square wave that provides an 25 | electrical angle resolution of 60 degress (:numref:`feedback-halls`). Because of 26 | their low resolution Hall sensors are frequently used for speed control. An 27 | important characteristic of Hall sensors is that their position feedback is 28 | absolute. 29 | 30 | .. _feedback-halls: 31 | .. figure:: images/halls.svg 32 | 33 | Hall sensors signals versus the motor electrical angle. 34 | 35 | .. _Hall effect: https://en.wikipedia.org/wiki/Hall_effect_sensor 36 | 37 | API 38 | --- 39 | 40 | .. doxygengroup:: spinner_drivers_feedback 41 | 42 | Implementations 43 | --------------- 44 | 45 | .. toctree:: 46 | :glob: 47 | 48 | impl/* 49 | -------------------------------------------------------------------------------- /dts/bindings/currsmp/st,stm32-currsmp-shunt.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021, Teslabs Engineering S.L. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | description: | 5 | STM32 shunt current sampling driver. 6 | 7 | The shunt current sampling device is expected to be a children of any STM32 8 | ADC supporting injected conversions. Example usage: 9 | 10 | &adc1 { 11 | currsmp: currsmp { 12 | compatible = "st,stm32-currsmp-shunt"; 13 | pinctrl-0 = <&adc1_in1_pa0 &adc1_in7_pc1 &adc1_in6_pc0>; 14 | pinctrl-names = "default"; 15 | 16 | adc-resolution = <12>; 17 | adc-tsample-us = <2>; 18 | adc-channels = <1 7 6>; 19 | adc-trigger = ; 20 | }; 21 | }; 22 | 23 | compatible: "st,stm32-currsmp-shunt" 24 | 25 | include: [base.yaml, pinctrl-device.yaml] 26 | 27 | properties: 28 | pinctrl-0: 29 | required: true 30 | 31 | pinctrl-names: 32 | required: true 33 | 34 | adc-resolution: 35 | type: int 36 | required: true 37 | description: | 38 | ADC resolution in bits. Available resolutions can differ depending on 39 | the selected SoC family. 40 | 41 | adc-tsample: 42 | type: int 43 | required: true 44 | description: | 45 | ADC sampling time in cycles. Decimal sampling times must be rounded 46 | up, e.g. 19.5 needs to be provided as 20. Available sample times can 47 | differ depending on the SoC family. 48 | 49 | adc-channels: 50 | type: array 51 | required: true 52 | description: | 53 | ADC channels (a, b, c). 54 | 55 | adc-trigger: 56 | type: int 57 | required: true 58 | description: | 59 | External trigger for the injected ADC conversions. The external trigger 60 | must be an output of the timer used for SV-PWM. 61 | 62 | Definitions available at dts-bindings/adc/stm32fxxx.h files. 63 | -------------------------------------------------------------------------------- /include/spinner/drivers/svpwm.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * 4 | * SV-PWM API. 5 | * 6 | * Copyright (c) 2021 Teslabs Engineering S.L. 7 | * SPDX-License-Identifier: Apache-2.0 8 | */ 9 | 10 | #ifndef _SPINNER_DRIVERS_SVPWM_H_ 11 | #define _SPINNER_DRIVERS_SVPWM_H_ 12 | 13 | #include 14 | #include 15 | 16 | /** 17 | * @defgroup spinner_drivers_svpwm SV-PWM API 18 | * @ingroup spinner_drivers 19 | * @{ 20 | */ 21 | 22 | /** @cond INTERNAL_HIDDEN */ 23 | 24 | struct svpwm_driver_api { 25 | void (*start)(const struct device *dev); 26 | void (*stop)(const struct device *dev); 27 | void (*set_phase_voltages)(const struct device *dev, float v_alpha, 28 | float v_beta); 29 | }; 30 | 31 | /** @endcond */ 32 | 33 | /** 34 | * @brief Start the SV-PWM controller. 35 | * 36 | * @note Current sampling device must be started prior to SV-PWM, since SV-PWM 37 | * is the responsible to trigger current sampling measurements. 38 | * 39 | * @param[in] dev SV-PWM device. 40 | */ 41 | static inline void svpwm_start(const struct device *dev) 42 | { 43 | const struct svpwm_driver_api *api = dev->api; 44 | 45 | api->start(dev); 46 | } 47 | 48 | /** 49 | * @brief Stop the SV-PWM controller. 50 | * 51 | * @param[in] dev SV-PWM device. 52 | */ 53 | static inline void svpwm_stop(const struct device *dev) 54 | { 55 | const struct svpwm_driver_api *api = dev->api; 56 | 57 | api->stop(dev); 58 | } 59 | 60 | /** 61 | * @brief Set phase voltages. 62 | * 63 | * @param[in] dev SV-PWM device. 64 | * @param[in] v_alpha Alpha voltage. 65 | * @param[in] v_beta Beta voltage. 66 | */ 67 | static inline void svpwm_set_phase_voltages(const struct device *dev, 68 | float v_alpha, float v_beta) 69 | { 70 | const struct svpwm_driver_api *api = dev->api; 71 | 72 | api->set_phase_voltages(dev, v_alpha, v_beta); 73 | } 74 | 75 | /** @} */ 76 | 77 | #endif /* _SPINNER_DRIVERS_SVPWM_H_ */ 78 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from pathlib import Path 3 | import os 4 | import re 5 | 6 | SPINNER_BASE = os.environ["SPINNER_BASE"] 7 | DOXYGEN_OUTPUT_DIRECTORY = os.environ["DOXYGEN_OUTPUT_DIRECTORY"] 8 | 9 | # Project ---------------------------------------------------------------------- 10 | 11 | project = "Spinner" 12 | author = "Teslabs Engineering S.L." 13 | year = 2021 14 | copyright = f"{year}, {author}" 15 | 16 | cmake = Path(SPINNER_BASE) / "spinner" / "CMakeLists.txt" 17 | with open(cmake) as f: 18 | version = re.search(r"project\(.*VERSION\s+([0-9\.]+).*\)", f.read()).group(1) 19 | 20 | # Options ---------------------------------------------------------------------- 21 | 22 | extensions = [ 23 | "sphinx.ext.intersphinx", 24 | "breathe", 25 | "sphinx.ext.mathjax", 26 | "sphinxcontrib.bibtex", 27 | "matplotlib.sphinxext.plot_directive", 28 | ] 29 | 30 | numfig = True 31 | 32 | # HTML output ------------------------------------------------------------------ 33 | 34 | html_theme = "sphinx_rtd_theme" 35 | html_theme_options = { 36 | "logo_only": True, 37 | "collapse_navigation": False, 38 | } 39 | html_static_path = ["_static"] 40 | html_logo = "_static/images/logo.svg" 41 | 42 | # Options for intersphinx ------------------------------------------------------ 43 | 44 | intersphinx_mapping = {"zephyr": ("https://docs.zephyrproject.org/latest", None)} 45 | 46 | # Options for breathe ---------------------------------------------------------- 47 | 48 | breathe_projects = {"app": str(Path(DOXYGEN_OUTPUT_DIRECTORY) / "xml")} 49 | breathe_default_project = "app" 50 | breathe_domain_by_extension = {"h": "c", "c": "c"} 51 | breathe_default_members = ("members", ) 52 | 53 | # Options for sphinxcontrib.bibtex --------------------------------------------- 54 | 55 | bibtex_bibfiles = ["bibliography.bib"] 56 | 57 | 58 | def setup(app): 59 | app.add_css_file("css/custom.css") 60 | app.add_css_file("css/light.css") 61 | -------------------------------------------------------------------------------- /include/spinner/utils/stm32_adc.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * 4 | * STM32 ADC Utilities. 5 | * 6 | * Copyright (c) 2021 Teslabs Engineering S.L. 7 | * SPDX-License-Identifier: Apache-2.0 8 | */ 9 | 10 | #ifndef _SPINNER_LIB_UTILS_STM32_ADC_H_ 11 | #define _SPINNER_LIB_UTILS_STM32_ADC_H_ 12 | 13 | #include 14 | 15 | #include 16 | 17 | /** 18 | * @defgroup spinner_utils_stm32_adc STM32 ADC Utilities 19 | * @ingroup spinner_lib_utils 20 | * @{ 21 | */ 22 | 23 | /** 24 | * @brief Obtain the RES fields in the ADC CFGR register given the sampling 25 | * resolution in bits. 26 | * 27 | * @param[in] res_bits Sampling resolution in bits. 28 | * @param[out] res Obtained RES register value. 29 | * @return int 0 on success, -ENOTSUP if fiven bit resolution is not supported. 30 | */ 31 | int stm32_adc_res_get(uint8_t res_bits, uint32_t *res); 32 | 33 | /** 34 | * @brief Obtain the value of the SMP fields in the ADC SMPR register given 35 | * sampling time in cycles. 36 | * 37 | * @note For decimal sampling times, @p smp_time must be rounded up, e.g. 19.5 38 | * cycles should be provided as 20. 39 | * 40 | * @param[in] smp_time ADC sampling time in cycles 41 | * @param[out] smp Obtained SMP register value. 42 | * @return int 0 on success, -ENOTSUP if given sampling time is not supported. 43 | */ 44 | int stm32_adc_smp_get(uint32_t smp_time, uint32_t *smp); 45 | 46 | /** 47 | * Obtain ADC clock rate. 48 | * 49 | * @param[in] adc ADC instance. 50 | * @param[in] pclken ADC clock control subsystem. 51 | * @param[out] clk Where ADC clock (in Hz) will be stored. 52 | * 53 | * @return 0 on success, error code otherwise. 54 | */ 55 | int stm32_adc_clk_get(ADC_TypeDef *adc, const struct stm32_pclken *pclken, 56 | uint32_t *clk); 57 | 58 | /** 59 | * Obtain ADC clock SAR time. 60 | * 61 | * @param[in] res_bits Sampling resolution in bits. 62 | * @param[out] t_sar Where computed SAR time (in cycles) time will be stored. 63 | * 64 | * @return 0 on success, error code otherwise. 65 | */ 66 | int stm32_adc_t_sar_get(uint8_t res_bits, float *t_sar); 67 | 68 | /** @} */ 69 | 70 | #endif /* _SPINNER_LIB_UTILS_STM32_ADC_H_ */ 71 | -------------------------------------------------------------------------------- /docs/theory/images/svpwm-modulation.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | 4 | 5 | def calc(sector, angle): 6 | alpha = np.deg2rad(angle) 7 | 8 | # amplitude assumed sqrt(3) / 2 (maximum) 9 | a = np.sqrt(3) / 2 * np.cos(alpha) - 1 / 2 * np.sin(alpha) 10 | b = np.sin(alpha) 11 | c = -(a + b) 12 | 13 | if sector == 1: 14 | x = a 15 | y = b 16 | z = 1 - (x + y) 17 | 18 | da = x + y + 0.5 * z 19 | db = y + 0.5 * z 20 | dc = 0.5 * z 21 | elif sector == 2: 22 | x = -c 23 | y = -a 24 | z = 1 - (x + y) 25 | 26 | da = x + 0.5 * z 27 | db = x + y + 0.5 * z 28 | dc = 0.5 * z 29 | elif sector == 3: 30 | x = b 31 | y = c 32 | z = 1 - (x + y) 33 | 34 | da = 0.5 * z 35 | db = x + y + 0.5 * z 36 | dc = y + 0.5 * z 37 | elif sector == 4: 38 | x = -a 39 | y = -b 40 | z = 1 - (x + y) 41 | 42 | da = 0.5 * z 43 | db = x + 0.5 * z 44 | dc = x + y + 0.5 * z 45 | elif sector == 5: 46 | x = c 47 | y = a 48 | z = 1 - (x + y) 49 | 50 | da = y + 0.5 * z 51 | db = 0.5 * z 52 | dc = x + y + 0.5 * z 53 | elif sector == 6: 54 | x = -b 55 | y = -c 56 | z = 1 - (x + y) 57 | 58 | da = x + y + 0.5 * z 59 | db = 0.5 * z 60 | dc = x + 0.5 * z 61 | 62 | return da, db, dc 63 | 64 | 65 | fig, ax = plt.subplots() 66 | 67 | ax.set_xlabel("State space vector angle (deg)") 68 | ax.set_ylabel("Duty cycle") 69 | 70 | alpha = np.array([]) 71 | da = np.array([]) 72 | db = np.array([]) 73 | dc = np.array([]) 74 | 75 | for sector in range(1, 7): 76 | alpha_ = np.arange(60 * (sector - 1), 60 * sector, 1) 77 | da_, db_, dc_ = calc(sector, alpha_) 78 | 79 | alpha = np.append(alpha, alpha_) 80 | da = np.append(da, da_) 81 | db = np.append(db, db_) 82 | dc = np.append(dc, dc_) 83 | 84 | ax.plot(alpha, da, color="r", label=r"$d_a$") 85 | ax.plot(alpha, db, color="g", label=r"$d_b$") 86 | ax.plot(alpha, dc, color="b", label=r"$d_c$") 87 | 88 | ax.legend(loc="upper right") 89 | 90 | plt.show() 91 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | # 3 | # Note: The list of ForEachMacros can be obtained using: 4 | # 5 | # git grep -h '^#define [^[:space:]]*FOR_EACH[^[:space:]]*(' include/ \ 6 | # | sed "s,^#define \([^[:space:]]*FOR_EACH[^[:space:]]*\)(.*$, - '\1'," \ 7 | # | sort | uniq 8 | # 9 | # References: 10 | # - https://clang.llvm.org/docs/ClangFormatStyleOptions.html 11 | 12 | --- 13 | BasedOnStyle: LLVM 14 | AlignConsecutiveMacros: Consecutive 15 | AttributeMacros: 16 | - __aligned 17 | - __deprecated 18 | - __packed 19 | - __printf_like 20 | - __syscall 21 | - __subsystem 22 | BreakBeforeBraces: Linux 23 | ConstructorInitializerIndentWidth: 8 24 | ContinuationIndentWidth: 8 25 | ForEachMacros: 26 | - 'FOR_EACH' 27 | - 'FOR_EACH_FIXED_ARG' 28 | - 'FOR_EACH_IDX' 29 | - 'FOR_EACH_IDX_FIXED_ARG' 30 | - 'FOR_EACH_NONEMPTY_TERM' 31 | - 'RB_FOR_EACH' 32 | - 'RB_FOR_EACH_CONTAINER' 33 | - 'SYS_DLIST_FOR_EACH_CONTAINER' 34 | - 'SYS_DLIST_FOR_EACH_CONTAINER_SAFE' 35 | - 'SYS_DLIST_FOR_EACH_NODE' 36 | - 'SYS_DLIST_FOR_EACH_NODE_SAFE' 37 | - 'SYS_SFLIST_FOR_EACH_CONTAINER' 38 | - 'SYS_SFLIST_FOR_EACH_CONTAINER_SAFE' 39 | - 'SYS_SFLIST_FOR_EACH_NODE' 40 | - 'SYS_SFLIST_FOR_EACH_NODE_SAFE' 41 | - 'SYS_SLIST_FOR_EACH_CONTAINER' 42 | - 'SYS_SLIST_FOR_EACH_CONTAINER_SAFE' 43 | - 'SYS_SLIST_FOR_EACH_NODE' 44 | - 'SYS_SLIST_FOR_EACH_NODE_SAFE' 45 | - '_WAIT_Q_FOR_EACH' 46 | - 'Z_FOR_EACH' 47 | - 'Z_FOR_EACH_ENGINE' 48 | - 'Z_FOR_EACH_EXEC' 49 | - 'Z_FOR_EACH_FIXED_ARG' 50 | - 'Z_FOR_EACH_FIXED_ARG_EXEC' 51 | - 'Z_FOR_EACH_IDX' 52 | - 'Z_FOR_EACH_IDX_EXEC' 53 | - 'Z_FOR_EACH_IDX_FIXED_ARG' 54 | - 'Z_FOR_EACH_IDX_FIXED_ARG_EXEC' 55 | - 'Z_GENLIST_FOR_EACH_CONTAINER' 56 | - 'Z_GENLIST_FOR_EACH_CONTAINER_SAFE' 57 | - 'Z_GENLIST_FOR_EACH_NODE' 58 | - 'Z_GENLIST_FOR_EACH_NODE_SAFE' 59 | IncludeBlocks: Regroup 60 | IncludeCategories: 61 | - Regex: '^".*\.h"$' 62 | Priority: 0 63 | - Regex: '^<(assert|complex|ctype|errno|fenv|float|inttypes|limits|locale|math|setjmp|signal|stdarg|stdbool|stddef|stdint|stdio|stdlib|string|tgmath|time|wchar|wctype)\.h>$' 64 | Priority: 1 65 | - Regex: '^\$' 66 | Priority: 3 67 | - Regex: '^\$' 68 | Priority: 3 69 | - Regex: '^\$' 70 | Priority: 4 71 | - Regex: '.*' 72 | Priority: 2 73 | IndentCaseLabels: false 74 | IndentWidth: 8 75 | SpaceBeforeParens: ControlStatementsExceptControlMacros 76 | UseTab: Always 77 | WhitespaceSensitiveMacros: 78 | - STRINGIFY 79 | - Z_STRINGIFY 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Vertigo 3 |

4 |

5 | 6 | CI status 7 | 8 | 9 | Documentation 10 | 11 |

12 | 13 | # Introduction 14 | 15 | Spinner is a motor control firmware based on the Field Oriented Control (FOC) 16 | principles. The firmware is built on top of the [Zephyr RTOS](https://zephyrproject.org), 17 | a modern multi-platform RTOS. Spinner is still a proof of concept, so do not 18 | expect production grade stability or features. 19 | 20 | ## Getting Started 21 | 22 | Before getting started, make sure you have a proper Zephyr development 23 | environment. You can follow the official 24 | [Zephyr Getting Started Guide](https://docs.zephyrproject.org/latest/getting_started/index.html). 25 | 26 | ### Initialization 27 | 28 | The first step is to initialize the `spinner` workspace folder where the 29 | source and all Zephyr modules will be cloned. You can do that by running: 30 | 31 | ```shell 32 | # initialize spinner workspace 33 | west init -m https://github.com/teslabs/spinner --mr main spinner 34 | # update modules 35 | cd spinner 36 | west update 37 | ``` 38 | 39 | ### Build & Run 40 | 41 | The application can be built by running: 42 | 43 | ```shell 44 | west build -b $BOARD spinner [-- -DSHIELD=$SHIELD] 45 | ``` 46 | 47 | where `$BOARD` is the target board (see `boards`) and `$SHIELD` an optional 48 | shield (see `boards/shields`). Some other build configurations are also provided: 49 | 50 | - `debug.conf`: Enable debug-friendly build 51 | - `shell.conf`: Enable shell facilities 52 | 53 | They can be enabled by setting `OVERLAY_CONFIG`, e.g. 54 | 55 | ```shell 56 | west build -b $BOARD spinner -- -DOVERLAY_CONFIG=debug.conf 57 | ``` 58 | 59 | Once you have built the application you can flash it by running: 60 | 61 | ```shell 62 | west flash 63 | ``` 64 | 65 | ## Documentation 66 | 67 | The documentation is based on Sphinx. Doxygen is used to extract the API 68 | docstrings, but its HTML output can also be used if preferred. A simple CMake 69 | script is provided in order to facilitate the documentation build process. In 70 | order to configure CMake you need to run: 71 | 72 | ```shell 73 | cmake -Sdocs -Bbuild_docs 74 | ``` 75 | 76 | In order to build the Doxygen documentation you need to run: 77 | 78 | ```shell 79 | cmake --build build_docs -t doxygen 80 | ``` 81 | 82 | Note that Doxygen output is required by Sphinx, so every time you change your 83 | API docstrings, remember to run the `doxygen` target. In order to build the 84 | Sphinx HTML documentation you need to run: 85 | 86 | ```shell 87 | cmake --build build_docs -t html 88 | ``` 89 | -------------------------------------------------------------------------------- /docs/theory/images/foc-abcs-anim.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | 3 | import matplotlib.pyplot as plt 4 | import matplotlib.lines as lines 5 | import matplotlib.patches as mpatches 6 | from matplotlib.animation import FuncAnimation 7 | import numpy as np 8 | 9 | 10 | def circle(ax, radius): 11 | ax.add_artist(mpatches.Circle((0, 0), radius, fill=False, alpha=0.3)) 12 | 13 | 14 | def vector(color): 15 | return plt.quiver(0, 0, 0, 0, color=color, angles="xy", scale_units="xy", scale=1) 16 | 17 | 18 | # unit vectors 19 | a_un = np.exp(1j * 0) 20 | b_un = np.exp(1j * 2 * np.pi / 3) 21 | c_un = np.exp(1j * 4 * np.pi / 3) 22 | 23 | fig, ax = plt.subplots() 24 | ax.set_aspect("equal") 25 | ax.set_xlim([-1.75, 1.75]) 26 | ax.set_ylim([-1.75, 1.75]) 27 | ax.set_xticks([1, 1.5]) 28 | ax.set_yticks([1, 1.5]) 29 | 30 | # center axes (using left+bottom only) 31 | ax.spines["left"].set_position("center") 32 | ax.spines["bottom"].set_position("center") 33 | ax.spines["right"].set_color("none") 34 | ax.spines["top"].set_color("none") 35 | 36 | ax.xaxis.set_ticks_position("bottom") 37 | ax.yaxis.set_ticks_position("left") 38 | 39 | title = ax.text(0.95, 0.95, "", transform=ax.transAxes, ha="right") 40 | 41 | # enclosing circles 42 | circle(ax, 1) 43 | circle(ax, 3 / 2) 44 | 45 | # vectors (u, v, w, beta, alpha, space-vector) 46 | a_line = vector("red") 47 | a_line_label = ax.text(0, 0, r"$\vec{i_a}$", color="red") 48 | 49 | b_line = vector("green") 50 | b_line_label = ax.text(0, 0, r"$\vec{i_b}$", color="green") 51 | 52 | c_line = vector("blue") 53 | c_line_label = ax.text(0, 0, r"$\vec{i_c}$", color="blue") 54 | 55 | i_s_line = vector("black") 56 | i_s_line_label = ax.text(0, 0, r"$\vec{i_s}$", color="black") 57 | 58 | 59 | def update(angle): 60 | """Update plot with new angle.""" 61 | 62 | i_a = np.cos(angle) 63 | i_b = np.cos(angle - 2 * np.pi / 3) 64 | i_c = np.cos(angle - 4 * np.pi / 3) 65 | 66 | title.set_text("Rotor angle: {:.0f} deg".format(np.rad2deg(angle))) 67 | 68 | a = a_un * i_a 69 | a_line.set_UVC(np.real(a), np.imag(a)) 70 | a_line_label.set_position((np.real(a), np.imag(a))) 71 | 72 | b = b_un * i_b 73 | b_line.set_UVC(np.real(b), np.imag(b)) 74 | b_line_label.set_position((np.real(b), np.imag(b))) 75 | 76 | c = c_un * i_c 77 | c_line.set_UVC(np.real(c), np.imag(c)) 78 | c_line_label.set_position((1.1 * np.real(c), np.imag(c))) 79 | 80 | i_s = a + b + c 81 | i_s_line.set_UVC(np.real(i_s), np.imag(i_s)) 82 | i_s_line_label.set_position((np.real(i_s), np.imag(i_s))) 83 | 84 | return [ 85 | title, 86 | a_line, 87 | a_line_label, 88 | b_line, 89 | b_line_label, 90 | c_line, 91 | c_line_label, 92 | i_s_line, 93 | i_s_line_label, 94 | ] 95 | 96 | 97 | ani = FuncAnimation( 98 | fig, update, interval=50, frames=np.arange(0, 2 * np.pi, 2 * np.pi / 180), blit=True 99 | ) 100 | 101 | 102 | if __name__ == "__main__": 103 | parser = argparse.ArgumentParser() 104 | parser.add_argument("-o", "--output", help="Output file") 105 | args = parser.parse_args() 106 | 107 | if args.output: 108 | ani.save(args.output, fps=25, writer="imagemagick") 109 | else: 110 | plt.show() 111 | -------------------------------------------------------------------------------- /lib/control/cloop.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Teslabs Engineering S.L. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | struct cloop { 16 | const struct device *currsmp; 17 | const struct device *feedback; 18 | const struct device *svpwm; 19 | arm_pid_instance_f32 pid_i_q; 20 | arm_pid_instance_f32 pid_i_d; 21 | float i_q_ref; 22 | float i_d_ref; 23 | }; 24 | 25 | static struct cloop cloop; 26 | 27 | /** 28 | * @brief Current regulation callback. 29 | * 30 | * This function is called after current sampling is completed. 31 | * 32 | * @warning It is called from the highest priority IRQ. 33 | */ 34 | static void regulate(void *ctx) 35 | { 36 | struct currsmp_curr curr; 37 | float eangle, sin_eangle, cos_eangle; 38 | float i_alpha, i_beta; 39 | float i_q, i_d; 40 | float v_q, v_d; 41 | float v_alpha, v_beta; 42 | 43 | ARG_UNUSED(ctx); 44 | 45 | currsmp_get_currents(cloop.currsmp, &curr); 46 | eangle = feedback_get_eangle(cloop.feedback); 47 | arm_sin_cos_f32(eangle, &sin_eangle, &cos_eangle); 48 | 49 | /* i_a, i_b -> i_alpha, i_beta */ 50 | arm_clarke_f32(curr.i_a, curr.i_b, &i_alpha, &i_beta); 51 | /* i_alpha, i_beta -> i_q, i_d */ 52 | arm_park_f32(i_alpha, i_beta, &i_d, &i_q, sin_eangle, cos_eangle); 53 | 54 | /* PI (i_q, i_d -> v_q, v_d) */ 55 | v_q = arm_pid_f32(&cloop.pid_i_q, cloop.i_q_ref - i_q); 56 | v_d = arm_pid_f32(&cloop.pid_i_d, cloop.i_d_ref - i_d); 57 | 58 | /* v_q, v_d -> v_alpha, v_beta */ 59 | arm_inv_park_f32(v_d, v_q, &v_alpha, &v_beta, sin_eangle, cos_eangle); 60 | svpwm_set_phase_voltages(cloop.svpwm, v_alpha, v_beta); 61 | } 62 | 63 | static int cloop_init(void) 64 | { 65 | cloop.currsmp = DEVICE_DT_GET(DT_NODELABEL(currsmp)); 66 | cloop.svpwm = DEVICE_DT_GET(DT_NODELABEL(svpwm)); 67 | cloop.feedback = DEVICE_DT_GET(DT_NODELABEL(feedback)); 68 | 69 | cloop.i_q_ref = 0.0f; 70 | cloop.i_d_ref = 0.0f; 71 | 72 | cloop.pid_i_q.Kp = CONFIG_SPINNER_CLOOP_T_KP / 1000.0f; 73 | cloop.pid_i_q.Ki = CONFIG_SPINNER_CLOOP_T_KI / 1000.0f; 74 | cloop.pid_i_q.Kd = 0.0f; 75 | arm_pid_init_f32(&cloop.pid_i_q, 1); 76 | 77 | cloop.pid_i_d.Kp = CONFIG_SPINNER_CLOOP_F_KP / 1000.0f; 78 | cloop.pid_i_d.Ki = CONFIG_SPINNER_CLOOP_F_KI / 1000.0f; 79 | cloop.pid_i_d.Kd = 0.0f; 80 | arm_pid_init_f32(&cloop.pid_i_d, 1); 81 | 82 | currsmp_configure(cloop.currsmp, regulate, NULL); 83 | 84 | return 0; 85 | } 86 | 87 | SYS_INIT(cloop_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY); 88 | 89 | /******************************************************************************* 90 | * Public 91 | ******************************************************************************/ 92 | 93 | void cloop_start(void) 94 | { 95 | arm_pid_reset_f32(&cloop.pid_i_q); 96 | arm_pid_reset_f32(&cloop.pid_i_d); 97 | 98 | currsmp_start(cloop.currsmp); 99 | svpwm_start(cloop.svpwm); 100 | } 101 | 102 | void cloop_stop(void) 103 | { 104 | svpwm_stop(cloop.svpwm); 105 | currsmp_stop(cloop.currsmp); 106 | } 107 | 108 | void cloop_set_ref(float i_d, float i_q) 109 | { 110 | currsmp_pause(cloop.currsmp); 111 | cloop.i_d_ref = i_d; 112 | cloop.i_q_ref = i_q; 113 | currsmp_resume(cloop.currsmp); 114 | } 115 | -------------------------------------------------------------------------------- /lib/utils/stm32_adc.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Teslabs Engineering S.L. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | #include 7 | 8 | #include 9 | 10 | int stm32_adc_res_get(uint8_t res_bits, uint32_t *res) 11 | { 12 | switch (res_bits) { 13 | #if defined(CONFIG_SOC_SERIES_STM32F3X) || defined(CONFIG_SOC_SERIES_STM32G4X) 14 | case 6U: 15 | *res = LL_ADC_RESOLUTION_6B; 16 | break; 17 | case 8U: 18 | *res = LL_ADC_RESOLUTION_8B; 19 | break; 20 | case 10U: 21 | *res = LL_ADC_RESOLUTION_10B; 22 | break; 23 | case 12U: 24 | *res = LL_ADC_RESOLUTION_12B; 25 | break; 26 | #endif 27 | default: 28 | return -ENOTSUP; 29 | } 30 | 31 | return 0; 32 | } 33 | 34 | int stm32_adc_smp_get(uint32_t smp_time, uint32_t *smp) 35 | { 36 | switch (smp_time) { 37 | #if defined(CONFIG_SOC_SERIES_STM32F3X) 38 | case 2U: 39 | *smp = LL_ADC_SAMPLINGTIME_1CYCLE_5; 40 | break; 41 | case 3U: 42 | *smp = LL_ADC_SAMPLINGTIME_2CYCLES_5; 43 | break; 44 | case 5U: 45 | *smp = LL_ADC_SAMPLINGTIME_4CYCLES_5; 46 | break; 47 | case 8U: 48 | *smp = LL_ADC_SAMPLINGTIME_7CYCLES_5; 49 | break; 50 | case 20U: 51 | *smp = LL_ADC_SAMPLINGTIME_19CYCLES_5; 52 | break; 53 | case 62U: 54 | *smp = LL_ADC_SAMPLINGTIME_61CYCLES_5; 55 | break; 56 | case 181U: 57 | *smp = LL_ADC_SAMPLINGTIME_181CYCLES_5; 58 | break; 59 | case 602U: 60 | *smp = LL_ADC_SAMPLINGTIME_601CYCLES_5; 61 | break; 62 | #elif defined(CONFIG_SOC_SERIES_STM32G4X) 63 | case 3U: 64 | *smp = LL_ADC_SAMPLINGTIME_2CYCLES_5; 65 | break; 66 | case 7U: 67 | *smp = LL_ADC_SAMPLINGTIME_6CYCLES_5; 68 | break; 69 | case 13U: 70 | *smp = LL_ADC_SAMPLINGTIME_12CYCLES_5; 71 | break; 72 | case 25U: 73 | *smp = LL_ADC_SAMPLINGTIME_24CYCLES_5; 74 | break; 75 | case 48U: 76 | *smp = LL_ADC_SAMPLINGTIME_47CYCLES_5; 77 | break; 78 | case 93U: 79 | *smp = LL_ADC_SAMPLINGTIME_92CYCLES_5; 80 | break; 81 | case 248U: 82 | *smp = LL_ADC_SAMPLINGTIME_247CYCLES_5; 83 | break; 84 | case 641U: 85 | *smp = LL_ADC_SAMPLINGTIME_640CYCLES_5; 86 | break; 87 | #endif 88 | default: 89 | return -ENOTSUP; 90 | } 91 | 92 | return 0; 93 | } 94 | 95 | int stm32_adc_clk_get(ADC_TypeDef *adc, const struct stm32_pclken *pclken, 96 | uint32_t *clk) 97 | { 98 | const struct device *clk_dev; 99 | int ret; 100 | uint32_t div; 101 | 102 | /* obtain ADC clock rate */ 103 | clk_dev = DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE); 104 | ret = clock_control_get_rate(clk_dev, (clock_control_subsys_t *)pclken, 105 | clk); 106 | if (ret < 0) { 107 | return ret; 108 | } 109 | 110 | /* obtain divisor */ 111 | div = LL_ADC_GetCommonClock(__LL_ADC_COMMON_INSTANCE(adc)); 112 | switch (div) { 113 | case LL_ADC_CLOCK_SYNC_PCLK_DIV1: 114 | break; 115 | case LL_ADC_CLOCK_SYNC_PCLK_DIV2: 116 | *clk = *clk >> 1U; 117 | break; 118 | case LL_ADC_CLOCK_SYNC_PCLK_DIV4: 119 | *clk = *clk >> 2U; 120 | break; 121 | default: 122 | return -ENOTSUP; 123 | } 124 | 125 | return 0; 126 | } 127 | 128 | int stm32_adc_t_sar_get(uint8_t res_bits, float *t_sar) 129 | { 130 | /* obtain t_sar (in clock cycles) */ 131 | switch (res_bits) { 132 | #if defined(CONFIG_SOC_SERIES_STM32F3X) || defined(CONFIG_SOC_SERIES_STM32G4X) 133 | case 6U: 134 | *t_sar = 6.5f; 135 | break; 136 | case 8U: 137 | *t_sar = 8.5f; 138 | break; 139 | case 10U: 140 | *t_sar = 10.5f; 141 | break; 142 | case 12U: 143 | *t_sar = 12.5f; 144 | break; 145 | #endif 146 | default: 147 | return -ENOTSUP; 148 | } 149 | 150 | return 0; 151 | } 152 | -------------------------------------------------------------------------------- /lib/svm/svm.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Teslabs Engineering S.L. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | #include 7 | 8 | #include 9 | 10 | #include 11 | 12 | /******************************************************************************* 13 | * Private 14 | ******************************************************************************/ 15 | 16 | /** Value sqrt(3). */ 17 | #define SQRT_3 1.7320508075688773f 18 | 19 | /** 20 | * @brief Obtain sector based on a, b, c vector values. 21 | * 22 | * @param[in] a a component value. 23 | * @param[in] b b component value. 24 | * @param[in] c c component value. 25 | 26 | * @return Sector (1...6). 27 | */ 28 | static uint8_t get_sector(float a, float b, float c) 29 | { 30 | uint8_t sector = 0U; 31 | 32 | if (c < 0.0f) { 33 | if (a < 0.0f) { 34 | sector = 2U; 35 | } else { 36 | if (b < 0.0f) { 37 | sector = 6U; 38 | } else { 39 | sector = 1U; 40 | } 41 | } 42 | } else { 43 | if (a < 0.0f) { 44 | if (b <= 0.0f) { 45 | sector = 4U; 46 | } else { 47 | sector = 3U; 48 | } 49 | } else { 50 | sector = 5U; 51 | } 52 | } 53 | 54 | return sector; 55 | } 56 | 57 | /******************************************************************************* 58 | * Public 59 | ******************************************************************************/ 60 | 61 | void svm_init(svm_t *svm) 62 | { 63 | svm->sector = 0U; 64 | 65 | svm->duties.a = 0.0f; 66 | svm->duties.b = 0.0f; 67 | svm->duties.c = 0.0f; 68 | 69 | svm->d_min = 0.0f; 70 | svm->d_max = 1.0f; 71 | } 72 | 73 | void svm_set(svm_t *svm, float va, float vb) 74 | { 75 | float a, b, c, mod; 76 | float x, y, z; 77 | 78 | /* limit maximum amplitude to avoid distortions */ 79 | (void)arm_sqrt_f32(va * va + vb * vb, &mod); 80 | if (mod > SQRT_3 / 2.0f) { 81 | va = va / mod * (SQRT_3 / 2.0f); 82 | vb = vb / mod * (SQRT_3 / 2.0f); 83 | } 84 | 85 | a = va - 1.0f / SQRT_3 * vb; 86 | b = 2.0f / SQRT_3 * vb; 87 | c = -(a + b); 88 | 89 | svm->sector = get_sector(a, b, c); 90 | 91 | switch (svm->sector) { 92 | case 1U: 93 | x = a; 94 | y = b; 95 | z = 1.0f - (x + y); 96 | 97 | svm->duties.a = x + y + z * 0.5f; 98 | svm->duties.b = y + z * 0.5f; 99 | svm->duties.c = z * 0.5f; 100 | 101 | break; 102 | case 2U: 103 | x = -c; 104 | y = -a; 105 | z = 1.0f - (x + y); 106 | 107 | svm->duties.a = x + z * 0.5f; 108 | svm->duties.b = x + y + z * 0.5f; 109 | svm->duties.c = z * 0.5f; 110 | 111 | break; 112 | case 3U: 113 | x = b; 114 | y = c; 115 | z = 1.0f - (x + y); 116 | 117 | svm->duties.a = z * 0.5f; 118 | svm->duties.b = x + y + z * 0.5f; 119 | svm->duties.c = y + z * 0.5f; 120 | 121 | break; 122 | case 4U: 123 | x = -a; 124 | y = -b; 125 | z = 1.0f - (x + y); 126 | 127 | svm->duties.a = z * 0.5f; 128 | svm->duties.b = x + z * 0.5f; 129 | svm->duties.c = x + y + z * 0.5f; 130 | 131 | break; 132 | case 5U: 133 | x = c; 134 | y = a; 135 | z = 1.0f - (x + y); 136 | 137 | svm->duties.a = y + z * 0.5f; 138 | svm->duties.b = z * 0.5f; 139 | svm->duties.c = x + y + z * 0.5f; 140 | 141 | break; 142 | case 6U: 143 | x = -b; 144 | y = -c; 145 | z = 1.0f - (x + y); 146 | 147 | svm->duties.a = x + y + z * 0.5f; 148 | svm->duties.b = z * 0.5f; 149 | svm->duties.c = x + z * 0.5f; 150 | 151 | break; 152 | default: 153 | break; 154 | } 155 | 156 | svm->duties.a = CLAMP(svm->duties.a, svm->d_min, svm->d_max); 157 | svm->duties.b = CLAMP(svm->duties.b, svm->d_min, svm->d_max); 158 | svm->duties.c = CLAMP(svm->duties.c, svm->d_min, svm->d_max); 159 | } 160 | -------------------------------------------------------------------------------- /docs/_static/css/light.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2020, Juan Linietsky, Ariel Manzur and the Godot community 3 | * Copyright (c) 2021, Teslabs Engineering S.L. 4 | * SPDX-License-Identifier: CC-BY-3.0 5 | * 6 | * Light theme colors 7 | */ 8 | 9 | :root { 10 | --body-color: #404040; 11 | --content-wrap-background-color: #efefef; 12 | --content-background-color: #fcfcfc; 13 | --logo-opacity: 1.0; 14 | --navbar-background-color: #333f67; 15 | --navbar-background-color-hover: #29355c; 16 | --navbar-background-color-active: #212d51; 17 | --navbar-current-background-color: #212d51; 18 | --navbar-current-background-color-hover: #182343; 19 | --navbar-current-background-color-active: #131e3b; 20 | --navbar-level-1-color: #c3e3ff; 21 | --navbar-level-2-color: #b8d6f0; 22 | --navbar-level-3-color: #a3c4e1; 23 | --navbar-heading-color: #90caf9; 24 | --navbar-scrollbar-color: #2196f3; 25 | --navbar-scrollbar-hover-color: #1976d2; 26 | --navbar-scrollbar-active-color: #115494; 27 | --navbar-scrollbar-background: #131e2b; 28 | 29 | --link-color: #2980b9; 30 | --link-color-hover: #3091d1; 31 | --link-color-active: #105078; 32 | --link-color-visited: #9b59b6; 33 | --external-reference-icon: url("data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjEyIiB3aWR0aD0iMTIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMjk4MGI5Ij48cGF0aCBkPSJtNy41IDcuMXYzLjRoLTZ2LTZoMy40Ii8+PHBhdGggZD0ibTUuNzY1IDFoNS4yMzV2NS4zOWwtMS41NzMgMS41NDctMS4zMS0xLjMxLTIuNzI0IDIuNzIzLTIuNjktMi42ODggMi44MS0yLjgwOC0xLjMxMy0xLjMxeiIvPjwvZz48L3N2Zz4K"); 34 | --classref-badge-text-color: hsl(0, 0%, 45%); 35 | 36 | --hr-color: #e1e4e5; 37 | --table-row-odd-background-color: #f3f6f6; 38 | --code-background-color: #fff; 39 | --code-border-color: #e1e4e5; 40 | --code-literal-color: #d04c60; 41 | --input-background-color: #fcfcfc; 42 | --input-focus-border-color: #5f8cff; 43 | 44 | --search-input-background-color: #e6eef3; /* derived from --input-background-color */ 45 | --search-match-color: #2c6b96; /* derived from --link-color */ 46 | --search-match-background-color: #e3f2fd; /* derived from --link-color */ 47 | --search-active-color: #efefef; 48 | --search-credits-background-color: #333f67; /* derived from --navbar-background-color */ 49 | --search-credits-color: #b3b3b3; /* derived from --footer-color */ 50 | --search-credits-link-color: #4392c5; /* derived from --link-color */ 51 | 52 | --highlight-background-color: #f0f2f4; 53 | --highlight-background-emph-color: #dbe6c3; 54 | --highlight-default-color: #404040; 55 | --highlight-comment-color: #408090; 56 | --highlight-keyword-color: #007020; 57 | --highlight-keyword2-color: #902000; 58 | --highlight-number-color: #208050; 59 | --highlight-decorator-color: #4070a0; 60 | --highlight-type-color: #007020; 61 | --highlight-type2-color: #0e84b5; 62 | --highlight-function-color: #06287e; 63 | --highlight-operator-color: #666666; 64 | --highlight-string-color: #4070a0; 65 | 66 | --admonition-note-background-color: #e7f2fa; 67 | --admonition-note-color: #404040; 68 | --admonition-note-title-background-color: #6ab0de; 69 | --admonition-note-title-color: #fff; 70 | --admonition-attention-background-color: #ffedcc; 71 | --admonition-attention-color: #404040; 72 | --admonition-attention-title-background-color: #f0b37e; 73 | --admonition-attention-title-color: #fff; 74 | --admonition-danger-background-color: #fcf3f2; 75 | --admonition-danger-color: #404040; 76 | --admonition-danger-title-background-color: #e9a499; 77 | --admonition-danger-title-color: #fff; 78 | --admonition-tip-background-color: #dbfaf4; 79 | --admonition-tip-color: #404040; 80 | --admonition-tip-title-background-color: #1abc9c; 81 | --admonition-tip-title-color: #fff; 82 | 83 | --kbd-background-color: #fafbfc; 84 | --kbd-outline-color: #d1d5da; 85 | --kbd-shadow-color: #b0b7bf; 86 | --kbd-text-color: #444d56; 87 | 88 | --btn-neutral-background-color: #f3f6f6; 89 | --btn-neutral-hover-background-color: #e5ebeb; 90 | --footer-color: #808080; 91 | } 92 | -------------------------------------------------------------------------------- /include/spinner/drivers/currsmp.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * 4 | * Current Sampling API. 5 | * 6 | * Copyright (c) 2021 Teslabs Engineering S.L. 7 | * SPDX-License-Identifier: Apache-2.0 8 | */ 9 | 10 | #ifndef _SPINNER_DRIVERS_CURRSMP_H_ 11 | #define _SPINNER_DRIVERS_CURRSMP_H_ 12 | 13 | #include 14 | #include 15 | 16 | /** 17 | * @defgroup spinner_drivers_currsmp Current Sampling API 18 | * @ingroup spinner_drivers 19 | * @{ 20 | */ 21 | 22 | /** @brief Current sampling regulation callback. */ 23 | typedef void (*currsmp_regulation_cb_t)(void *ctx); 24 | 25 | /** @brief Current sampling currents. */ 26 | struct currsmp_curr { 27 | /** Phase a current. */ 28 | float i_a; 29 | /** Phase b current. */ 30 | float i_b; 31 | /** Phase c current. */ 32 | float i_c; 33 | }; 34 | 35 | /** @cond INTERNAL_HIDDEN */ 36 | 37 | struct currsmp_driver_api { 38 | void (*configure)(const struct device *dev, 39 | currsmp_regulation_cb_t regulation_cb, void *ctx); 40 | void (*get_currents)(const struct device *dev, 41 | struct currsmp_curr *curr); 42 | void (*set_sector)(const struct device *dev, uint8_t sector); 43 | uint32_t (*get_smp_time)(const struct device *dev); 44 | void (*start)(const struct device *dev); 45 | void (*stop)(const struct device *dev); 46 | void (*pause)(const struct device *dev); 47 | void (*resume)(const struct device *dev); 48 | }; 49 | 50 | /** @endcond */ 51 | 52 | /** 53 | * @brief Configure current sampling device. 54 | * 55 | * @note This function needs to be called before calling currsmp_start(). 56 | * 57 | * @param[in] dev Current sampling device. 58 | * @param[in] regulation_cb Callback called on each regulation cycle. 59 | * @param[in] ctx Callback context. 60 | */ 61 | static inline void currsmp_configure(const struct device *dev, 62 | currsmp_regulation_cb_t regulation_cb, 63 | void *ctx) 64 | { 65 | const struct currsmp_driver_api *api = dev->api; 66 | 67 | api->configure(dev, regulation_cb, ctx); 68 | } 69 | 70 | /** 71 | * @brief Get phase currents. 72 | * 73 | * @param[in] dev Current sampling device. 74 | * @param[out] curr Pointer where phase currents will be stored. 75 | */ 76 | static inline void currsmp_get_currents(const struct device *dev, 77 | struct currsmp_curr *curr) 78 | { 79 | const struct currsmp_driver_api *api = dev->api; 80 | 81 | api->get_currents(dev, curr); 82 | } 83 | 84 | /** 85 | * @brief Set SV-PWM sector. 86 | * 87 | * @param[in] dev Current sampling device. 88 | * @param[in] sector SV-PWM sector. 89 | */ 90 | static inline void currsmp_set_sector(const struct device *dev, uint8_t sector) 91 | { 92 | const struct currsmp_driver_api *api = dev->api; 93 | 94 | api->set_sector(dev, sector); 95 | } 96 | 97 | /** 98 | * @brief Obtain currents sampling time in nanoseconds. 99 | * 100 | * @param[in] dev Current sampling device. 101 | * 102 | * @return Sampling time in nanoseconds (zero indicates error). 103 | */ 104 | static inline uint32_t currsmp_get_smp_time(const struct device *dev) 105 | { 106 | const struct currsmp_driver_api *api = dev->api; 107 | 108 | return api->get_smp_time(dev); 109 | } 110 | 111 | /** 112 | * @brief Start sampling currents. 113 | * 114 | * @param[in] dev Current sampling device. 115 | * 116 | * @see currsmp_stop() 117 | */ 118 | static inline void currsmp_start(const struct device *dev) 119 | { 120 | const struct currsmp_driver_api *api = dev->api; 121 | 122 | api->start(dev); 123 | } 124 | 125 | /** 126 | * @brief Stop sampling currents. 127 | * 128 | * @param[in] dev Current sampling device. 129 | */ 130 | static inline void currsmp_stop(const struct device *dev) 131 | { 132 | const struct currsmp_driver_api *api = dev->api; 133 | 134 | api->stop(dev); 135 | } 136 | 137 | /** 138 | * @brief Pause current sampling. 139 | * 140 | * @note This function can be used to prevent current sampling 141 | * to call the regulation callback, thus allowing to adjust 142 | * shared context. 143 | * 144 | * @param dev Current sampling device. 145 | * 146 | * @see currsmp_resume() 147 | */ 148 | static inline void currsmp_pause(const struct device *dev) 149 | { 150 | const struct currsmp_driver_api *api = dev->api; 151 | 152 | api->pause(dev); 153 | } 154 | 155 | /** 156 | * @brief Resume current sampling. 157 | * 158 | * @param dev Current sampling device. 159 | */ 160 | static inline void currsmp_resume(const struct device *dev) 161 | { 162 | const struct currsmp_driver_api *api = dev->api; 163 | 164 | api->resume(dev); 165 | } 166 | 167 | /** @} */ 168 | 169 | #endif /* _SPINNER_DRIVERS_CURRSMP_H_ */ 170 | -------------------------------------------------------------------------------- /include/dts-bindings/adc/stm32g4xx.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * 4 | * DT definitions for STM32 ADC (G4XX series) 5 | * 6 | * Copyright (c) 2021 Teslabs Engineering S.L. 7 | * SPDX-License-Identifier: Apache-2.0 8 | */ 9 | 10 | #ifndef _DTS_BINDINGS_INVERTER_STM32G4XX_H_ 11 | #define _DTS_BINDINGS_INVERTER_STM32G4XX_H_ 12 | 13 | /* Ref. RM0440, Rev. 6, Tables 164, 166. */ 14 | 15 | #define _STM32_ADC_JEXT_POS 2U 16 | 17 | #define STM32_ADC12_INJ_TRIG_TIM1_TRGO (0U << _STM32_ADC_JEXT_POS) 18 | #define STM32_ADC12_INJ_TRIG_TIM1_CC4 (1U << _STM32_ADC_JEXT_POS) 19 | #define STM32_ADC12_INJ_TRIG_TIM2_TRGO (2U << _STM32_ADC_JEXT_POS) 20 | #define STM32_ADC12_INJ_TRIG_TIM2_CC1 (3U << _STM32_ADC_JEXT_POS) 21 | #define STM32_ADC12_INJ_TRIG_TIM3_CC4 (4U << _STM32_ADC_JEXT_POS) 22 | #define STM32_ADC12_INJ_TRIG_TIM4_TRGO (5U << _STM32_ADC_JEXT_POS) 23 | #define STM32_ADC12_INJ_TRIG_EXTI15 (6U << _STM32_ADC_JEXT_POS) 24 | #define STM32_ADC12_INJ_TRIG_TIM8_CC4 (7U << _STM32_ADC_JEXT_POS) 25 | #define STM32_ADC12_INJ_TRIG_TIM1_TRGO2 (8U << _STM32_ADC_JEXT_POS) 26 | #define STM32_ADC12_INJ_TRIG_TIM8_TRGO (9U << _STM32_ADC_JEXT_POS) 27 | #define STM32_ADC12_INJ_TRIG_TIM8_TRGO2 (10U << _STM32_ADC_JEXT_POS) 28 | #define STM32_ADC12_INJ_TRIG_TIM3_CC3 (11U << _STM32_ADC_JEXT_POS) 29 | #define STM32_ADC12_INJ_TRIG_TIM3_TRGO (12U << _STM32_ADC_JEXT_POS) 30 | #define STM32_ADC12_INJ_TRIG_TIM3_CC1 (13U << _STM32_ADC_JEXT_POS) 31 | #define STM32_ADC12_INJ_TRIG_TIM6_TRGO (14U << _STM32_ADC_JEXT_POS) 32 | #define STM32_ADC12_INJ_TRIG_TIM15_TRGO (15U << _STM32_ADC_JEXT_POS) 33 | #define STM32_ADC12_INJ_TRIG_TIM20_TRGO (16U << _STM32_ADC_JEXT_POS) 34 | #define STM32_ADC12_INJ_TRIG_TIM20_TRGO2 (17U << _STM32_ADC_JEXT_POS) 35 | #define STM32_ADC12_INJ_TRIG_TIM20_CC4 (18U << _STM32_ADC_JEXT_POS) 36 | #define STM32_ADC12_INJ_TRIG_HRTIM_ADC_TRG2 (19U << _STM32_ADC_JEXT_POS) 37 | #define STM32_ADC12_INJ_TRIG_HRTIM_ADC_TRG4 (20U << _STM32_ADC_JEXT_POS) 38 | #define STM32_ADC12_INJ_TRIG_HRTIM_ADC_TRG5 (21U << _STM32_ADC_JEXT_POS) 39 | #define STM32_ADC12_INJ_TRIG_HRTIM_ADC_TRG6 (22U << _STM32_ADC_JEXT_POS) 40 | #define STM32_ADC12_INJ_TRIG_HRTIM_ADC_TRG7 (23U << _STM32_ADC_JEXT_POS) 41 | #define STM32_ADC12_INJ_TRIG_HRTIM_ADC_TRG8 (24U << _STM32_ADC_JEXT_POS) 42 | #define STM32_ADC12_INJ_TRIG_HRTIM_ADC_TRG9 (25U << _STM32_ADC_JEXT_POS) 43 | #define STM32_ADC12_INJ_TRIG_HRTIM_ADC_TRG10 (26U << _STM32_ADC_JEXT_POS) 44 | #define STM32_ADC12_INJ_TRIG_TIM16_CC1 (27U << _STM32_ADC_JEXT_POS) 45 | #define STM32_ADC12_INJ_TRIG_LPTIMOUT (29U << _STM32_ADC_JEXT_POS) 46 | #define STM32_ADC12_INJ_TRIG_TIM7_TRGO (30U << _STM32_ADC_JEXT_POS) 47 | 48 | #define STM32_ADC345_INJ_TRIG_TIM1_TRGO (0U << _STM32_ADC_JEXT_POS) 49 | #define STM32_ADC345_INJ_TRIG_TIM1_CC4 (1U << _STM32_ADC_JEXT_POS) 50 | #define STM32_ADC345_INJ_TRIG_TIM2_TRGO (2U << _STM32_ADC_JEXT_POS) 51 | #define STM32_ADC345_INJ_TRIG_TIM8_CC2 (3U << _STM32_ADC_JEXT_POS) 52 | #define STM32_ADC345_INJ_TRIG_TIM4_CC3 (4U << _STM32_ADC_JEXT_POS) 53 | #define STM32_ADC345_INJ_TRIG_TIM4_TRGO (5U << _STM32_ADC_JEXT_POS) 54 | #define STM32_ADC345_INJ_TRIG_TIM4_CC4 (6U << _STM32_ADC_JEXT_POS) 55 | #define STM32_ADC345_INJ_TRIG_TIM8_CC4 (7U << _STM32_ADC_JEXT_POS) 56 | #define STM32_ADC345_INJ_TRIG_TIM1_TRGO2 (8U << _STM32_ADC_JEXT_POS) 57 | #define STM32_ADC345_INJ_TRIG_TIM8_TRGO (9U << _STM32_ADC_JEXT_POS) 58 | #define STM32_ADC345_INJ_TRIG_TIM8_TRGO2 (10U << _STM32_ADC_JEXT_POS) 59 | #define STM32_ADC345_INJ_TRIG_TIM1_CC3 (11U << _STM32_ADC_JEXT_POS) 60 | #define STM32_ADC345_INJ_TRIG_TIM3_TRGO (12U << _STM32_ADC_JEXT_POS) 61 | #define STM32_ADC345_INJ_TRIG_EXTI3 (13U << _STM32_ADC_JEXT_POS) 62 | #define STM32_ADC345_INJ_TRIG_TIM6_TRGO (14U << _STM32_ADC_JEXT_POS) 63 | #define STM32_ADC345_INJ_TRIG_TIM15_TRGO (15U << _STM32_ADC_JEXT_POS) 64 | #define STM32_ADC345_INJ_TRIG_TIM20_TRGO (16U << _STM32_ADC_JEXT_POS) 65 | #define STM32_ADC345_INJ_TRIG_TIM20_TRGO2 (17U << _STM32_ADC_JEXT_POS) 66 | #define STM32_ADC345_INJ_TRIG_TIM20_CC2 (18U << _STM32_ADC_JEXT_POS) 67 | #define STM32_ADC345_INJ_TRIG_HRTIM_ADC_TRG2 (19U << _STM32_ADC_JEXT_POS) 68 | #define STM32_ADC345_INJ_TRIG_HRTIM_ADC_TRG4 (20U << _STM32_ADC_JEXT_POS) 69 | #define STM32_ADC345_INJ_TRIG_HRTIM_ADC_TRG5 (21U << _STM32_ADC_JEXT_POS) 70 | #define STM32_ADC345_INJ_TRIG_HRTIM_ADC_TRG6 (22U << _STM32_ADC_JEXT_POS) 71 | #define STM32_ADC345_INJ_TRIG_HRTIM_ADC_TRG7 (23U << _STM32_ADC_JEXT_POS) 72 | #define STM32_ADC345_INJ_TRIG_HRTIM_ADC_TRG8 (24U << _STM32_ADC_JEXT_POS) 73 | #define STM32_ADC345_INJ_TRIG_HRTIM_ADC_TRG9 (25U << _STM32_ADC_JEXT_POS) 74 | #define STM32_ADC345_INJ_TRIG_HRTIM_ADC_TRG10 (26U << _STM32_ADC_JEXT_POS) 75 | #define STM32_ADC345_INJ_TRIG_HRTIM_ADC_TRG1 (27U << _STM32_ADC_JEXT_POS) 76 | #define STM32_ADC345_INJ_TRIG_HRTIM_ADC_TRG3 (28U << _STM32_ADC_JEXT_POS) 77 | #define STM32_ADC345_INJ_TRIG_LPTIMOUT (29U << _STM32_ADC_JEXT_POS) 78 | #define STM32_ADC345_INJ_TRIG_TIM7_TRGO (30U << _STM32_ADC_JEXT_POS) 79 | 80 | #endif /* _DTS_BINDINGS_INVERTER_STM32G4XX_H_ */ 81 | -------------------------------------------------------------------------------- /docs/theory/images/currsample-svpwm-anim.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | 3 | import numpy as np 4 | import matplotlib.pyplot as plt 5 | from matplotlib.animation import FuncAnimation 6 | 7 | 8 | parser = argparse.ArgumentParser() 9 | parser.add_argument("-o", "--output", help="Output file") 10 | parser.add_argument("-s", "--sector", type=int, default=1, help="Sector") 11 | args = parser.parse_args() 12 | 13 | 14 | def calc(sector, angle): 15 | alpha = np.deg2rad(angle) 16 | 17 | # amplitude assumed sqrt(3) / 2 (maximum) 18 | a = np.sqrt(3) / 2 * np.cos(alpha) - 1 / 2 * np.sin(alpha) 19 | b = np.sin(alpha) 20 | c = -(a + b) 21 | 22 | if sector == 1: 23 | x = a 24 | y = b 25 | z = 1 - (x + y) 26 | 27 | da = x + y + 0.5 * z 28 | db = y + 0.5 * z 29 | dc = 0.5 * z 30 | elif sector == 2: 31 | x = -c 32 | y = -a 33 | z = 1 - (x + y) 34 | 35 | da = x + 0.5 * z 36 | db = x + y + 0.5 * z 37 | dc = 0.5 * z 38 | elif sector == 3: 39 | x = b 40 | y = c 41 | z = 1 - (x + y) 42 | 43 | da = 0.5 * z 44 | db = x + y + 0.5 * z 45 | dc = y + 0.5 * z 46 | elif sector == 4: 47 | x = -a 48 | y = -b 49 | z = 1 - (x + y) 50 | 51 | da = 0.5 * z 52 | db = x + 0.5 * z 53 | dc = x + y + 0.5 * z 54 | elif sector == 5: 55 | x = c 56 | y = a 57 | z = 1 - (x + y) 58 | 59 | da = y + 0.5 * z 60 | db = 0.5 * z 61 | dc = x + y + 0.5 * z 62 | elif sector == 6: 63 | x = -b 64 | y = -c 65 | z = 1 - (x + y) 66 | 67 | da = x + y + 0.5 * z 68 | db = 0.5 * z 69 | dc = x + 0.5 * z 70 | 71 | return da, db, dc 72 | 73 | 74 | fig, axes = plt.subplots(nrows=7, sharex=True, figsize=(8, 6)) 75 | 76 | axes[0].set_ylim([0, 1]) 77 | 78 | for axis in axes[1:]: 79 | axis.set_xlim([0, 1]) 80 | axis.set_ylim([-0.1, 1.1]) 81 | axis.set_yticks([]) 82 | axis.set_xticks([0, 0.5, 1]) 83 | 84 | axes[1].set_ylabel(r"$q_a$") 85 | axes[2].set_ylabel(r"$\bar{q}_a$") 86 | axes[3].set_ylabel(r"$q_b$") 87 | axes[4].set_ylabel(r"$\bar{q}_b$") 88 | axes[5].set_ylabel(r"$q_c$") 89 | axes[6].set_ylabel(r"$\bar{q}_c$") 90 | 91 | axes[6].set_xlabel("Regulation periods") 92 | 93 | N = 200 94 | t = np.linspace(0, 1, N) 95 | 96 | # plot timer ramp 97 | trig = np.zeros(N) 98 | trig[: N // 2] = 0 + 2 * t[: N // 2] 99 | trig[N // 2 :] = 2 - 2 * t[N // 2 :] 100 | axes[0].plot(t, trig) 101 | 102 | (da_line,) = axes[0].plot((0, 0), (0, 0), "r-") 103 | (db_line,) = axes[0].plot((0, 0), (0, 0), "g-") 104 | (dc_line,) = axes[0].plot((0, 0), (0, 0), "b-") 105 | 106 | (qa_H_line,) = axes[1].step([], [], "r") 107 | (qa_L_line,) = axes[2].step([], [], "r-") 108 | (qb_H_line,) = axes[3].step([], [], "g") 109 | (qb_L_line,) = axes[4].step([], [], "g") 110 | (qc_H_line,) = axes[5].step([], [], "b") 111 | (qc_L_line,) = axes[6].step([], [], "b") 112 | 113 | qa_H_text = axes[1].text(0, 0, "") 114 | qa_L_text = axes[2].text(0, 0, "") 115 | qb_H_text = axes[3].text(0, 0, "") 116 | qb_L_text = axes[4].text(0, 0, "") 117 | qc_H_text = axes[5].text(0, 0, "") 118 | qc_L_text = axes[6].text(0, 0, "") 119 | 120 | 121 | def update(angle): 122 | """Update plot with new angle.""" 123 | da, db, dc = calc(args.sector, angle) 124 | 125 | # adjust trigger lines 126 | da_line.set_data((0, 1), (da, da)) 127 | db_line.set_data((0, 1), (db, db)) 128 | dc_line.set_data((0, 1), (dc, dc)) 129 | 130 | qa = np.zeros(N) 131 | qa[np.where(da > trig)] = 1 132 | 133 | qb = np.zeros(N) 134 | qb[np.where(db > trig)] = 1 135 | 136 | qc = np.zeros(N) 137 | qc[np.where(dc > trig)] = 1 138 | 139 | qa_H_line.set_data(t, qa) 140 | qa_L_line.set_data(t, 1 - qa) 141 | 142 | qb_H_line.set_data(t, qb) 143 | qb_L_line.set_data(t, 1 - qb) 144 | 145 | qc_H_line.set_data(t, qc) 146 | qc_L_line.set_data(t, 1 - qc) 147 | 148 | qa_H_text.set_text("{:.2f} %".format(100 * da)) 149 | qa_L_text.set_text("{:.2f} %".format(100 * (1 - da))) 150 | qb_H_text.set_text("{:.2f} %".format(100 * db)) 151 | qb_L_text.set_text("{:.2f} %".format(100 * (1 - db))) 152 | qc_H_text.set_text("{:.2f} %".format(100 * dc)) 153 | qc_L_text.set_text("{:.2f} %".format(100 * (1 - dc))) 154 | 155 | return [ 156 | da_line, 157 | db_line, 158 | dc_line, 159 | qa_H_line, 160 | qa_L_line, 161 | qb_H_line, 162 | qb_L_line, 163 | qc_H_line, 164 | qc_L_line, 165 | qa_H_text, 166 | qa_L_text, 167 | qb_H_text, 168 | qb_L_text, 169 | qc_H_text, 170 | qc_L_text, 171 | ] 172 | 173 | 174 | ani = FuncAnimation( 175 | fig, 176 | update, 177 | interval=50, 178 | frames=np.arange(60 * (args.sector - 1), 60 * args.sector, 0.5), 179 | blit=True, 180 | ) 181 | 182 | 183 | if args.output: 184 | ani.save(args.output, fps=25, writer="imagemagick") 185 | else: 186 | plt.show() 187 | -------------------------------------------------------------------------------- /tests/lib/svm/src/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Teslabs Engineering S.L. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | #include 7 | 8 | #include 9 | 10 | #include 11 | 12 | /** Value of sqrt(3). */ 13 | #define SQRT_3 1.7320508075688773f 14 | 15 | /** @brief Test that two floats are almost equal (works for small numbers). */ 16 | #define ALMOST_EQUAL(a, b) (fabs((a) - (b)) < 1.0e-5) 17 | 18 | /** 19 | * @brief Test that SV-PWM modulator works as expected. 20 | * 21 | * Space vector angular values from 0 to 360 degrees are tested in steps of 30 22 | * so that we can compare against exact results while testing all sectors. The 23 | * svm_set() input values are computed as follows: 24 | * 25 | * - A space vector angle is chosen [0..360), which determines the sector 26 | * - Space vector a, b, c components are computed 27 | * - a, b, c components are transformed to the alpha-beta space 28 | * 29 | * Note that in most cases a vector with a module 1 is used for simplicity 30 | * (svm_set() performs amplitude limitation, also tested). Angles that are at 31 | * the edge of two sectors (0, 60, 120, 180, 240, 300) are tested to be in 32 | * either of the adjacent sectors, since sector determination based on sign 33 | * threshold may carry floating point rounding errors. 34 | */ 35 | ZTEST(svm, test_api) 36 | { 37 | svm_t svm; 38 | 39 | /* initial state */ 40 | svm_init(&svm); 41 | zassert_equal(svm.duties.a, 0.0f, NULL); 42 | zassert_equal(svm.duties.b, 0.0f, NULL); 43 | zassert_equal(svm.duties.c, 0.0f, NULL); 44 | zassert_equal(svm.d_min, 0.0f, NULL); 45 | zassert_equal(svm.d_max, 1.0f, NULL); 46 | zassert_equal(svm.sector, 0U, NULL); 47 | 48 | /* 0 degrees (mod sqrt(3) / 2) */ 49 | svm_set(&svm, SQRT_3 / 2.0f, 0.0f); 50 | zassert_true((svm.sector == 1U) || (svm.sector == 6U), NULL); 51 | zassert_true(ALMOST_EQUAL(svm.duties.a, 0.5f + SQRT_3 / 4.0f), NULL); 52 | zassert_true(ALMOST_EQUAL(svm.duties.b, 0.5f - SQRT_3 / 4.0f), NULL); 53 | zassert_true(ALMOST_EQUAL(svm.duties.c, 0.5f - SQRT_3 / 4.0f), NULL); 54 | 55 | /* 0 degrees (amplitude is limited here, as all others that follow) */ 56 | svm_set(&svm, 1.0f, 0.0f); 57 | zassert_true((svm.sector == 1U) || (svm.sector == 6U), NULL); 58 | zassert_true(ALMOST_EQUAL(svm.duties.a, 0.5f + SQRT_3 / 4.0f), NULL); 59 | zassert_true(ALMOST_EQUAL(svm.duties.b, 0.5f - SQRT_3 / 4.0f), NULL); 60 | zassert_true(ALMOST_EQUAL(svm.duties.c, 0.5f - SQRT_3 / 4.0f), NULL); 61 | 62 | /* 30 degres */ 63 | svm_set(&svm, SQRT_3 / 2.0f, 0.5f); 64 | zassert_equal(svm.sector, 1U, NULL); 65 | zassert_true(ALMOST_EQUAL(svm.duties.a, 1.0f), NULL); 66 | zassert_true(ALMOST_EQUAL(svm.duties.b, 0.5f), NULL); 67 | zassert_true(ALMOST_EQUAL(svm.duties.c, 0.0f), NULL); 68 | 69 | /* 60 degrees */ 70 | svm_set(&svm, 0.5f, SQRT_3 / 2.0f); 71 | zassert_true((svm.sector == 1U) || (svm.sector == 2U), NULL); 72 | zassert_true(ALMOST_EQUAL(svm.duties.a, 0.5f + SQRT_3 / 4.0f), NULL); 73 | zassert_true(ALMOST_EQUAL(svm.duties.b, 0.5f + SQRT_3 / 4.0f), NULL); 74 | zassert_true(ALMOST_EQUAL(svm.duties.c, 0.5f - SQRT_3 / 4.0f), NULL); 75 | 76 | /* 90 degrees */ 77 | svm_set(&svm, 0.0f, 1.0f); 78 | zassert_equal(svm.sector, 2U, NULL); 79 | zassert_true(ALMOST_EQUAL(svm.duties.a, 0.5f), NULL); 80 | zassert_true(ALMOST_EQUAL(svm.duties.b, 1.0f), NULL); 81 | zassert_true(ALMOST_EQUAL(svm.duties.c, 0.0f), NULL); 82 | 83 | /* 120 degrees */ 84 | svm_set(&svm, -0.5f, 3.0f / (2.0f * SQRT_3)); 85 | zassert_true((svm.sector == 2U) || (svm.sector == 3U), NULL); 86 | zassert_true(ALMOST_EQUAL(svm.duties.a, 0.5f - SQRT_3 / 4.0f), NULL); 87 | zassert_true(ALMOST_EQUAL(svm.duties.b, 0.5f + SQRT_3 / 4.0f), NULL); 88 | zassert_true(ALMOST_EQUAL(svm.duties.c, 0.5f - SQRT_3 / 4.0f), NULL); 89 | 90 | /* 150 degrees */ 91 | svm_set(&svm, -SQRT_3 / 2.0f, 0.5f); 92 | zassert_equal(svm.sector, 3U, NULL); 93 | zassert_true(ALMOST_EQUAL(svm.duties.a, 0.0f), NULL); 94 | zassert_true(ALMOST_EQUAL(svm.duties.b, 1.0f), NULL); 95 | zassert_true(ALMOST_EQUAL(svm.duties.c, 0.5f), NULL); 96 | 97 | /* 180 degrees */ 98 | svm_set(&svm, -1.0f, 0.0f); 99 | zassert_true((svm.sector == 3U) || (svm.sector == 4U), NULL); 100 | zassert_true(ALMOST_EQUAL(svm.duties.a, 0.5f - SQRT_3 / 4.0f), NULL); 101 | zassert_true(ALMOST_EQUAL(svm.duties.b, 0.5f + SQRT_3 / 4.0f), NULL); 102 | zassert_true(ALMOST_EQUAL(svm.duties.c, 0.5f + SQRT_3 / 4.0f), NULL); 103 | 104 | /* 210 degrees */ 105 | svm_set(&svm, -SQRT_3 / 2.0f, -0.5f); 106 | zassert_equal(svm.sector, 4U, NULL); 107 | zassert_true(ALMOST_EQUAL(svm.duties.a, 0.0f), NULL); 108 | zassert_true(ALMOST_EQUAL(svm.duties.b, 0.5f), NULL); 109 | zassert_true(ALMOST_EQUAL(svm.duties.c, 1.0f), NULL); 110 | 111 | /* 240 degrees */ 112 | svm_set(&svm, -0.5f, -SQRT_3 / 2.0f); 113 | zassert_true((svm.sector == 4U) || (svm.sector == 5U), NULL); 114 | zassert_true(ALMOST_EQUAL(svm.duties.a, 0.5f - SQRT_3 / 4.0f), NULL); 115 | zassert_true(ALMOST_EQUAL(svm.duties.b, 0.5f - SQRT_3 / 4.0f), NULL); 116 | zassert_true(ALMOST_EQUAL(svm.duties.c, 0.5f + SQRT_3 / 4.0f), NULL); 117 | 118 | /* 270 degrees */ 119 | svm_set(&svm, 0.0f, -1.0f); 120 | zassert_equal(svm.sector, 5U, NULL); 121 | zassert_true(ALMOST_EQUAL(svm.duties.a, 0.5f), NULL); 122 | zassert_true(ALMOST_EQUAL(svm.duties.b, 0.0f), NULL); 123 | zassert_true(ALMOST_EQUAL(svm.duties.c, 1.0f), NULL); 124 | 125 | /* 300 degrees */ 126 | svm_set(&svm, 0.5f, -SQRT_3 / 2.0f); 127 | zassert_true((svm.sector == 5U) || (svm.sector == 6U), NULL); 128 | zassert_true(ALMOST_EQUAL(svm.duties.a, 0.5f + SQRT_3 / 4.0f), NULL); 129 | zassert_true(ALMOST_EQUAL(svm.duties.b, 0.5f - SQRT_3 / 4.0f), NULL); 130 | zassert_true(ALMOST_EQUAL(svm.duties.c, 0.5f + SQRT_3 / 4.0f), NULL); 131 | 132 | /* 330 degrees */ 133 | svm_set(&svm, SQRT_3 / 2.0f, -0.5f); 134 | zassert_equal(svm.sector, 6U, NULL); 135 | zassert_true(ALMOST_EQUAL(svm.duties.a, 1.0f), NULL); 136 | zassert_true(ALMOST_EQUAL(svm.duties.b, 0.0f), NULL); 137 | zassert_true(ALMOST_EQUAL(svm.duties.c, 0.5f), NULL); 138 | } 139 | 140 | ZTEST_SUITE(svm, NULL, NULL, NULL, NULL, NULL); 141 | -------------------------------------------------------------------------------- /docs/theory/currsmp.rst: -------------------------------------------------------------------------------- 1 | Current sampling 2 | ================ 3 | 4 | Knowledge of phase currents is at the core of Field Oriented Control as they are 5 | the controlled variables. Multiple methods can be used in order to measure motor 6 | phase currents, being one of the most populars the usage of shunt resistors. We 7 | will also see that current sampling is tightly related to the PWM control 8 | signals and hence the modulation scheme. 9 | 10 | Shunt resistors 11 | --------------- 12 | 13 | It can be demonstrated that current flows through the shunt resistor when the 14 | low transistor is turned on. Therefore, current measurements need to be 15 | synchronized with the PWM switching times. 16 | 17 | Only two phase currents are required to know the third one, as in a balanced 18 | system all currents sum zero. If we measure :math:`i_a` and :math:`i_b`, 19 | :math:`i_c` is also known. However, it is not always possible to sample the same 20 | currents as we are limited by the time the low side is active. The detailed 21 | analysis will be limited to the first sector case, for other sectors the same 22 | procedure can be followed. 23 | 24 | SV-PWM 25 | ------ 26 | 27 | When using the SV-PWM modulation technique we have that the duty cycles take a 28 | particular shape that will condition the sampling of the currents. If we look at 29 | the first sector we have that duty cycles look like the animation shown in 30 | :numref:`currsample-svpwm-anim` (dead-time ignored for simplicity). 31 | 32 | .. _currsample-svpwm-anim: 33 | .. figure:: images/currsample-svpwm-anim.gif 34 | 35 | Duty cycles for the first sector when using SV-PWM. 36 | 37 | Actually, other sectors are just a combination of what we have in 38 | :numref:`currsample-svpwm-anim`, only having direction changes in the linearly 39 | varying phase. 40 | 41 | .. plot:: theory/images/svpwm-modulation.py 42 | 43 | SV-PWM duty cycles shape. 44 | 45 | Summarizing, we will always have the following situation: 46 | 47 | 1. A phase with a **high duty cycle**, with its maximum at half of the period. 48 | 2. A phase that varies **linearly** either **increasing or decreasing** over a 49 | wide range of duty cycles. 50 | 3. A phase with a **low duty cycle**, with its minimum at half of the period. 51 | 52 | We will use this information in the next section when designing the sampling 53 | strategy. 54 | 55 | Sampling strategy 56 | ----------------- 57 | 58 | Because phase currents flow through the shunt resistor when the low-side is ON 59 | it is clear that we need to synchronize the measurements with the PWM signals. 60 | As we are on a balanced system, we can just measure two phase currents instead 61 | of all three. We also need to consider the modulation scheme (SV-PWM) in order 62 | to understand the limitations we have. As usual we will focus on analyzing the 63 | first SV-PWM sector and extrapolate the results to other sectors. 64 | :numref:`currsample-mid` provides a timing diagram for the first sector which 65 | will be useful for the analysis. 66 | 67 | .. _currsample-mid: 68 | .. figure:: images/currsample-mid.svg 69 | 70 | Duty cycles and current shapes for sector 1 71 | 72 | .. table:: 73 | 74 | =========================== ============================================ 75 | Variable Description 76 | =========================== ============================================ 77 | :math:`\mathrm{T_{RISE}}` Time taken by the current signal to rise and 78 | stabilize to its nominal value after a 79 | bottom transistor switch-on event. 80 | :math:`\mathrm{T_{NOISE}}` Time during which electric noise is present 81 | on a phase due to another phase bottom 82 | transistor switch-on event. 83 | :math:`\mathrm{T_{SAMPLE}}` Time taken to sample the currents. 84 | :math:`\mathrm{DT}` Dead-Time is a small time added to the PWM 85 | signals so that upper and bottom transistors 86 | do not change state at the same time thus 87 | avoiding shoot-throughs. 88 | =========================== ============================================ 89 | 90 | In order to derive a simple sampling strategy we will assume that sampling is 91 | always started at the middle of the PWM period. When sampling currents we always 92 | need to skip the measurement of the phase that can be potentially OFF in the 93 | active sector. In case of sector one, this happens for phase a, meaning we will 94 | need to sample phases b and c. :numref:`currsample-svpwm-anim` provides animated 95 | duty cycle waveforms that can help on understanding the given concepts. The same 96 | reasoning can be performed for the other sectors, leading to the results shown 97 | in :numref:`currsample-phases`. 98 | 99 | .. _currsample-phases: 100 | .. table:: Phases to be sampled on each SV-PWM sector. 101 | :align: center 102 | 103 | ====== ==================== 104 | Sector Phases to be sampled 105 | ====== ==================== 106 | 1 b, c 107 | 2 a, c 108 | 3 a, c 109 | 4 a, b 110 | 5 a, b 111 | 6 b, c 112 | ====== ==================== 113 | 114 | The only condition we must fulfill is that the time the low side is ON is big 115 | enough to allow sampling the currents. The lowest time the low side is ON is 116 | given by: 117 | 118 | .. math:: 119 | 120 | \mathrm{t_{min} = T_{PWM} \cdot (1 - d_{max}) - DT}. 121 | 122 | If we take SV-PWM equations we have that the maximum PWM duty cycle for the 123 | phases to be sampled is: 124 | 125 | .. math:: 126 | 127 | \mathrm{d_{max}} = \frac{1}{2} + \frac{\sqrt{3}}{4}. 128 | 129 | As we sample at the middle of the period, we need then: 130 | 131 | .. math:: 132 | 133 | \mathrm{T_s \leq \frac{t_{min}}{2}} 134 | 135 | which results in: 136 | 137 | .. math:: 138 | 139 | \mathrm{T_s \leq \frac{T_{PWM} \cdot \left( \frac{1}{2} - \frac{\sqrt{3}}{4} \right) - DT}{2}}. 140 | 141 | If this condition is not met, PWM frequency should be reduced. 142 | -------------------------------------------------------------------------------- /docs/theory/foc.rst: -------------------------------------------------------------------------------- 1 | Field Oriented Control 2 | ====================== 3 | 4 | Field Oriented Control (FOC) consists on controlling the stator currents 5 | represented by a vector in a 2-D time-invariant space :math:`dq`. The :math:`dq` 6 | space is an orthogonal space aligned with the rotor: flux is aligned with 7 | :math:`d` and torque is aligned with :math:`q`. A set of projections is used to 8 | transform from a three-phase speed and time dependent system to :math:`dq`. As 9 | the transformations are just projections the controlled magnitudes are 10 | instantaneous quantities, making the control structure valid for transient and 11 | steady state. 12 | 13 | It can be shown that in the :math:`dq` space we have: 14 | 15 | .. math:: 16 | 17 | T \propto \psi_R i_q 18 | 19 | that is, by maintaining the rotor flux constant :math:`\psi_R` we have that the 20 | generated torque :math:`T` is directly proportional to the :math:`i_q` stator 21 | current. We can then perform torque control by changing the :math:`i_q` current 22 | reference. Because the speed and time dependency is removed from the :math:`dq` 23 | space, the control strategy is also simplified as constant references are being 24 | controlled. 25 | 26 | Space Vector 27 | ------------ 28 | 29 | We have that for three-phase AC motors, voltages, currents and fluxes can be 30 | analyzed in terms of complex space vectors. First we define the :math:`abc` 31 | space, given by the following three unit vectors in the complex space: 32 | 33 | .. math:: 34 | 35 | \hat{a} &= e^{j0} \\ 36 | \hat{b} &= e^{j \frac{2 \pi}{3}} \\ 37 | \hat{c} &= e^{j \frac{4 \pi}{3}}. \\ 38 | 39 | Then, the space vector for currents (same applies to other magnitudes) is 40 | defined as: 41 | 42 | .. math:: 43 | \vec{i_s} = i_a \hat{a} + i_b \hat{b} + i_c \hat{c} 44 | = \vec{i_a} + \vec{i_b} + \vec{i_c}. 45 | 46 | The definition above may sound abstract, but with some more context it can be 47 | better understood. Let us start by looking at the currents shape. Given a 48 | three-phase balanced AC system, we have that phase currents are sinusoidal in 49 | steady state, i.e.: 50 | 51 | .. math:: 52 | 53 | i_a(t) &= I \cos(\omega t + \phi_0) \\ 54 | i_b(t) &= I \cos(\omega t - \frac{2 \pi}{3} + \phi_0) \\ 55 | i_c(t) &= I \cos(\omega t - \frac{4 \pi}{3} + \phi_0) \\ 56 | 57 | where :math:`I` is the current magnitude, :math:`\omega` is the rotation speed 58 | in rad/s and :math:`\phi_0` is an arbitrary initial phase. :math:`\omega t` is 59 | the instantaneous position, :math:`\theta`. Note that both rotation speed and 60 | instantaneous position are always in electrical terms. We can read from the 61 | equations that :math:`i_b` lags :math:`i_a` by :math:`\frac{2 \pi}{3}` rad, and 62 | :math:`i_c` lags :math:`i_b` by the same amount. We can also observe that the 63 | following equality holds as the system is balanced: 64 | 65 | .. math:: 66 | 67 | i_a(t) + i_b(t) + i_c(t) = 0. 68 | 69 | Using the above equations we can plot the space vector and its components as a 70 | function of the rotor position (:numref:`foc-abcs-anim`). The space vector can 71 | be seen as a CCW rotating vector with rotation speed :math:`\omega` in the 72 | complex space. 73 | 74 | .. _foc-abcs-anim: 75 | .. figure:: images/foc-abcs-anim.gif 76 | 77 | Animated visualization of the space vector. 78 | 79 | Clarke transform 80 | ---------------- 81 | 82 | Any non-orthogonal space indicates a redundancy in its axes. This is the case of 83 | the :math:`abc` space, which can be reduced to the complex space. The complex 84 | space is usually referred in the motor control literature as the :math:`\alpha 85 | \beta` space. In order to derive the transform from the :math:`abc` space to 86 | the :math:`\alpha \beta` space, we can take the projection of the space vector 87 | components into the :math:`\alpha \beta` axes, that is: 88 | 89 | .. math:: 90 | 91 | i_{\alpha} &= \Re(\vec{i_a} + \vec{i_b} + \vec{i_c}) 92 | = i_a + i_b \cos \left(\frac{2 \pi}{3}\right) + i_c \cos\left(\frac{4 \pi}{3}\right) 93 | = i_a - \frac{1}{2} (i_b + i_c), \\ 94 | i_{\beta} &= \Im(\vec{i_a} + \vec{i_b} + \vec{i_c}) 95 | = i_b + \sin\left(\frac{2 \pi}{3}\right) + i_c \sin\left(\frac{4 \pi}{3}\right) 96 | = \frac{\sqrt{3}}{2} (i_b - i_c). 97 | 98 | Using the equality :math:`i_a + i_b + i_c = 0`, we can further simplify the 99 | expressions: 100 | 101 | .. math:: 102 | 103 | i_{\alpha} &= i_a \\ 104 | i_{\beta} &= \left( \frac{1}{\sqrt{3}} i_a + \frac{2}{\sqrt{3}} i_b \right). 105 | 106 | This transform is known as the **Clarke transform**, which in matrix form is: 107 | 108 | .. math:: 109 | 110 | (a, b) \rightarrow (\alpha, \beta): \mathbf{C} = 111 | \begin{bmatrix} 112 | 1 && 0 \\ 113 | \frac{1}{\sqrt{3}} && \frac{2}{\sqrt{3}} 114 | \end{bmatrix}. 115 | 116 | It is important to note that :math:`\det{\mathbf{C}} \neq 1`, so the transform 117 | is not power-invariant. Its inverse is defined as: 118 | 119 | .. math:: 120 | 121 | (\alpha, \beta) \rightarrow (a, b): \mathbf{C^{-1}} = 122 | \begin{bmatrix} 123 | 1 && 0 \\ 124 | -\frac{1}{2} && \frac{\sqrt{3}}{2} 125 | \end{bmatrix}. 126 | 127 | Park transform 128 | -------------- 129 | 130 | After the application of the Clarke transformation, we still have quantities 131 | that are speed and time dependent. Assuming we have knowledge of the rotor 132 | position, :math:`\theta = \omega t`, we can de-rotate the :math:`\alpha\beta` 133 | space, therefore removing the speed and time dependency. The new frame will 134 | actually be a **rotating frame** and it is known as the :math:`dq` space, the 135 | space mentioned at the beginning. 136 | 137 | In order to derive the transformation we need to again project the 138 | :math:`\alpha\beta` components to the rotating frame. The transform is known as 139 | the **Park transform** and it is actually the well-known 2-D rotation matrix 140 | in its inverse form as we are de-rotating or moving clock-wise. 141 | 142 | .. math:: 143 | 144 | (\alpha, \beta) \rightarrow (d, q): \mathbf{P} = 145 | \begin{bmatrix} 146 | \cos(\theta) && \sin(\theta) \\ 147 | -\sin(\theta) && \cos(\theta) 148 | \end{bmatrix}. 149 | 150 | In its inverse form it is given by: 151 | 152 | .. math:: 153 | 154 | (\alpha, \beta) \rightarrow (d, q): \mathbf{P^{-1}} = 155 | \begin{bmatrix} 156 | \cos(\theta) && -\sin(\theta) \\ 157 | \sin(\theta) && \cos(\theta) 158 | \end{bmatrix}. 159 | -------------------------------------------------------------------------------- /docs/components/svpwm/impl/stm32.rst: -------------------------------------------------------------------------------- 1 | STM32 2 | ===== 3 | 4 | Introduction 5 | ------------ 6 | 7 | The timer peripheral is the core part of the SV-PWM driver. It generates the PWM 8 | signals that drive the inverter circuit and it also takes care of synchronizing 9 | ADC measurements made by the current sampling driver. 10 | 11 | The driver is designed to work using one of the *advanced control timers*, 12 | usually ``TIM1`` and ``TIM8``. They include specific functionalities that are 13 | crucial to have a performant and safe system :cite:`an4013`. 14 | :numref:`stm32-timer-diagram` shows the diagram of an advanced control timer. 15 | 16 | .. _stm32-timer-diagram: 17 | .. figure:: images/stm32-timer-diagram.png 18 | 19 | Advanced control timer diagram :cite:`rm0365`. 20 | 21 | PWM generation 22 | -------------- 23 | 24 | The timer peripheral clock, :math:`\mathrm{f_{TIM}}`, is as fundamental variable 25 | as it controls the timer counting rate. The timer clock is divided by the 26 | prescaler, which is controlled by the ``PSC`` register (16-bit). The counting 27 | rate, :math:`\mathrm{f_{CNT}}`, is defined as: 28 | 29 | .. math:: 30 | 31 | \mathrm{f_{CNT} = \frac{f_{TIM}}{PSC + 1}} 32 | 33 | STM32 timers have multiple PWM modes. The most interesting mode when doing motor 34 | control is the **center-aligned PWM mode** 35 | (:numref:`stm32-timer-centeraligned`). In this mode the counting direction 36 | (up/down) is automatically alternated by the timer. This mode provides an 37 | interesting feature when multiple PWM waveforms are generated such as in a 38 | 3-phase inverter. Contrary to edge-aligned modes, in this mode the rising and 39 | falling edges of the PWM signals are not synchronized with the counter 40 | roll-over. Therefore, switching time varies with the duty cycle value and 41 | switching noise is spread. This is a key feature for electric motor drives, 42 | since it allows to double the frequency of the current ripple for a given 43 | switching frequency. For instance, a 10 kHz PWM will generate inaudible 20 kHz 44 | current ripple. This feature also minimizes the switching losses due to the PWM 45 | frequency while guaranteeing a silent operation. 46 | 47 | .. _stm32-timer-centeraligned: 48 | .. figure:: images/stm32-timer-centeraligned.svg 49 | 50 | Timing diagram for a timer in center-aligned PWM mode. 51 | 52 | Using the above diagram, we can see that the PWM frequency 53 | (:math:`\mathrm{f_{PWM}}`) is given by: 54 | 55 | .. math:: 56 | 57 | \mathrm{f_{PWM} = \frac{f_{TIM}}{2 \cdot (ARR + 1) \cdot (PSC + 1)}} 58 | 59 | where ``ARR`` is the auto-reload register, a 16-bit value. In order to maximize 60 | PWM resolution ``PSC`` should be chosen so that ``ARR`` is maximized while 61 | fitting into its 16-bit register. 62 | 63 | .. topic:: ARR calculation example 64 | 65 | Given :math:`\mathrm{f_{TIM} = 72~MHz}` and :math:`\mathrm{f_{PWM} = 30~KHz}`, 66 | we start with :math:`\mathrm{PSC = 0}`, which leads to: 67 | 68 | .. math:: 69 | 70 | \mathrm{ARR = \frac{72~MHz}{2 \cdot 30~KHz \cdot (0 + 1)} - 1 = 1199}. 71 | 72 | As 1199 fits into a 16-bit register, we stick to :math:`\mathrm{PSC = 0}`. 73 | 74 | The PWM duty cycle is controlled via the ``CCRx`` registers (``x = 1, 2, ...``). 75 | ``CCRx`` value is compared against ``CNT`` so that the PWM signal is active when 76 | ``CNT < CCRx``. In case ``CCRx`` is set to zero, the PWM signal is always kept 77 | inactive. 78 | 79 | There is an important feature that has to be enabled for ``CCRx`` registers: 80 | pre-load. When pre-load is enabled, the register value is only updated when the 81 | timer update event occurs. This is particular useful for real-time control, as 82 | the new register values are applied synchronously. However in center aligned 83 | mode, we have two update events: overflow (at the end of up cycle) and underflow 84 | (at the end of down cycle). Update event happening on overflow should be avoided 85 | since it is the time when current sampling is likely going to occur, and so the 86 | regulation loop. Repetition counter feature comes to the rescue to solve this 87 | problem. In center-aligned mode, odd values of the repetition counter generate 88 | the update event either on overfow or underflow depending on when the repetition 89 | counter register ``RCR`` was written and the counter launched. If ``RCR`` was 90 | written before starting the counter, the update event will occur on underflow 91 | and on overflow if ``RCR`` was written after starting the counter. 92 | 93 | .. figure:: images/stm32-timer-repcnt.png 94 | 95 | Example of repetition counter update event generation :cite:`rm0365`. 96 | 97 | Up to now all the details on the signal generation have been given. The only 98 | missing part is now how to expose these signals to the outside via the ``OCx`` 99 | pins. This is controlled by the output stage of the capture/compare channel as 100 | seen on :numref:`stm32-cc-output`. In general it is necessary to control both 101 | high and low sides of each inverter leg. For this purpose complementary outputs 102 | can be enabled (``OCxN``). As described in the following section, it is also 103 | possible to insert dead-time when using complementary outputs. Some integrated 104 | drivers do not require complementary signals since they internally take care of 105 | their generation including dead-time insertion. 106 | 107 | .. _stm32-cc-output: 108 | .. figure :: images/stm32-cc-output.png 109 | 110 | Output stage of capture/compare channel :cite:`rm0365`. 111 | 112 | ADC synchronization 113 | ------------------- 114 | 115 | As detailed in the :doc:`/theory/currsmp` page, it is crucial to synchronize the 116 | current measurements with the PWM generation. For this purpose, the driver uses 117 | its fourth channel compare unit (``OC4``) to trigger the ADC. The value of the 118 | ``CCR4`` register controlling the signal duty cycle is updated every time phase 119 | voltages are set so that currents are always sampled at an optimal point. The 120 | ``OC4`` output is connected to the ``TRGO`` output signal. The ADC device 121 | managed by the current sampling driver is responsible to connect to this signal 122 | as a trigger source. 123 | 124 | Break function 125 | -------------- 126 | 127 | The break function is used to protect the power stage driven by the PWM signals. 128 | There are two break inputs which are usually connected to fault signals 129 | generated by the power stage circuit (e.g. over-current). When any of the input 130 | is activated a hardware protection mechanism is triggered so that the PWM 131 | outputs are disabled, leaving them in a pre-programmed state. The break 132 | circuitry works asynchronously, that is, it does not depend on any system clock. 133 | This feature makes sure that the circuitry does not suffer from any clock 134 | propagation delay or system clock failures. 135 | 136 | .. _stm32-timer-brk: 137 | .. figure:: images/stm32-timer-brk.png 138 | 139 | Break circuitry :cite:`rm0365`. 140 | 141 | As shown in :numref:`stm32-timer-brk` ``BRK`` is the result of either an 142 | external event (``BKIN``) or an internal event (``BRK_ACTH``) such as a clock 143 | failure event (refer to :cite:`rm0365` for more details). The first channel, 144 | ``BRK``, has priority over ``BRK2``. ``BRK`` can also be configured to either 145 | disable (inactive) or force PWM outputs to a predefined safe state. Furthermore, 146 | a dead-time can be programmed to avoid potential shoot-through when activating 147 | the break functionality. This provides a dual-level protection scheme, where for 148 | instance a low priority protection with all switches off can be overridden by a 149 | higher priority protection with low-side switches active. Let’s consider for 150 | instance that the fault occurs when the high-side PWM is ON, while the safe 151 | state is programmed to have high-side switched OFF and low-side switched ON. At 152 | the time the fault occurs the system will first disable the high-side PWM, and 153 | insert a dead time before switching ON the low side. 154 | 155 | .. figure:: images/stm32-timer-brkconf.png 156 | 157 | Typical break use case :cite:`rm0365`. 158 | -------------------------------------------------------------------------------- /drivers/feedback/halls_stm32.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Teslabs Engineering S.L. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | #define DT_DRV_COMPAT st_stm32_halls 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | 16 | #include 17 | #include 18 | 19 | LOG_MODULE_REGISTER(halls_stm32, CONFIG_SPINNER_FEEDBACK_LOG_LEVEL); 20 | 21 | /******************************************************************************* 22 | * Private 23 | ******************************************************************************/ 24 | 25 | struct halls_stm32_config { 26 | TIM_TypeDef *timer; 27 | struct stm32_pclken pclken; 28 | struct gpio_dt_spec h1; 29 | struct gpio_dt_spec h2; 30 | struct gpio_dt_spec h3; 31 | uint32_t irq; 32 | uint32_t phase_shift; 33 | const struct pinctrl_dev_config *pcfg; 34 | }; 35 | 36 | struct halls_stm32_data { 37 | uint16_t eangle; 38 | uint8_t last_state; 39 | uint32_t tfreq; 40 | int32_t raw_speed; 41 | }; 42 | 43 | static uint8_t halls_stm32_get_state(const struct device *dev) 44 | { 45 | const struct halls_stm32_config *config = dev->config; 46 | 47 | return (uint8_t)gpio_pin_get_raw(config->h3.port, config->h3.pin) 48 | << 2U | 49 | (uint8_t)gpio_pin_get_raw(config->h2.port, config->h2.pin) 50 | << 1U | 51 | (uint8_t)gpio_pin_get_raw(config->h1.port, config->h1.pin); 52 | } 53 | 54 | ISR_DIRECT_DECLARE(timer_irq) 55 | { 56 | const struct device *dev = DEVICE_DT_INST_GET(0); 57 | const struct halls_stm32_config *config = dev->config; 58 | struct halls_stm32_data *data = dev->data; 59 | 60 | uint8_t curr_state; 61 | uint16_t eangle = 0U; 62 | int8_t direction = 1; 63 | 64 | if (LL_TIM_IsActiveFlag_CC1(config->timer) == 0U) { 65 | return 0; 66 | } 67 | 68 | LL_TIM_ClearFlag_CC1(config->timer); 69 | 70 | curr_state = halls_stm32_get_state(dev); 71 | 72 | switch (curr_state) { 73 | case 5U: 74 | if (data->last_state == 4U) { 75 | eangle = 0; 76 | } else if (data->last_state == 1U) { 77 | eangle = 60; 78 | direction = -1; 79 | } 80 | 81 | break; 82 | case 1U: 83 | if (data->last_state == 5U) { 84 | eangle = 60; 85 | } else if (data->last_state == 3U) { 86 | eangle = 120; 87 | direction = -1; 88 | } 89 | break; 90 | case 3U: 91 | if (data->last_state == 1U) { 92 | eangle = 120; 93 | } else if (data->last_state == 2U) { 94 | eangle = 180; 95 | direction = -1; 96 | } 97 | break; 98 | case 2U: 99 | if (data->last_state == 3U) { 100 | eangle = 180; 101 | } else if (data->last_state == 6U) { 102 | eangle = 240; 103 | direction = -1; 104 | } 105 | break; 106 | case 6U: 107 | if (data->last_state == 2U) { 108 | eangle = 240; 109 | } else if (data->last_state == 4U) { 110 | eangle = 300; 111 | direction = -1; 112 | } 113 | break; 114 | case 4U: 115 | if (data->last_state == 6U) { 116 | eangle = 300; 117 | } else if (data->last_state == 5U) { 118 | eangle = 0; 119 | direction = -1; 120 | } 121 | break; 122 | default: 123 | __ASSERT(NULL, "Unexpected halls state: %d", curr_state); 124 | return 0; 125 | } 126 | 127 | eangle += config->phase_shift; 128 | 129 | data->eangle = eangle; 130 | data->last_state = curr_state; 131 | data->raw_speed = 132 | direction * (int32_t)LL_TIM_IC_GetCaptureCH1(config->timer); 133 | 134 | return 0; 135 | } 136 | 137 | /******************************************************************************* 138 | * API 139 | ******************************************************************************/ 140 | 141 | static float halls_stm32_get_eangle(const struct device *dev) 142 | { 143 | struct halls_stm32_data *data = dev->data; 144 | 145 | return (float)data->eangle; 146 | } 147 | 148 | static float halls_stm32_get_speed(const struct device *dev) 149 | { 150 | struct halls_stm32_data *data = dev->data; 151 | 152 | return (float)(data->tfreq / data->raw_speed / 6UL); 153 | } 154 | 155 | static const struct feedback_driver_api halls_stm32_driver_api = { 156 | .get_eangle = halls_stm32_get_eangle, 157 | .get_speed = halls_stm32_get_speed}; 158 | 159 | /******************************************************************************* 160 | * Init 161 | ******************************************************************************/ 162 | 163 | static int halls_stm32_init(const struct device *dev) 164 | { 165 | const struct halls_stm32_config *config = dev->config; 166 | struct halls_stm32_data *data = dev->data; 167 | 168 | int ret; 169 | const struct device *clk; 170 | LL_TIM_InitTypeDef init; 171 | LL_TIM_ENCODER_InitTypeDef enc_init; 172 | uint8_t curr_state; 173 | 174 | /* configure pinmux */ 175 | ret = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT); 176 | if (ret < 0) { 177 | LOG_ERR("pinctrl setup failed (%d)", ret); 178 | return ret; 179 | } 180 | 181 | /* enable timer clock */ 182 | clk = DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE); 183 | 184 | ret = clock_control_on(clk, (clock_control_subsys_t *)&config->pclken); 185 | if (ret < 0) { 186 | LOG_ERR("Could not turn on timer clock (%d)", ret); 187 | return ret; 188 | } 189 | 190 | /* initialize timer */ 191 | LL_TIM_StructInit(&init); 192 | if (LL_TIM_Init(config->timer, &init) != SUCCESS) { 193 | LOG_ERR("Could not initialize timer"); 194 | return -EIO; 195 | } 196 | 197 | /* configure encoder (halls) mode */ 198 | LL_TIM_SetClockSource(config->timer, LL_TIM_CLOCKSOURCE_INTERNAL); 199 | LL_TIM_IC_EnableXORCombination(config->timer); 200 | LL_TIM_SetTriggerInput(config->timer, LL_TIM_TS_TI1F_ED); 201 | 202 | LL_TIM_ENCODER_StructInit(&enc_init); 203 | enc_init.IC1ActiveInput = LL_TIM_ACTIVEINPUT_TRC; 204 | if (LL_TIM_ENCODER_Init(config->timer, &enc_init) != SUCCESS) { 205 | LOG_ERR("Could not initialize encoder mode"); 206 | return -EIO; 207 | } 208 | 209 | /* configure CC unit and timer update source */ 210 | LL_TIM_SetUpdateSource(config->timer, LL_TIM_UPDATESOURCE_COUNTER); 211 | LL_TIM_CC_EnableChannel(config->timer, LL_TIM_CHANNEL_CH1); 212 | LL_TIM_EnableIT_CC1(config->timer); 213 | 214 | /* store timer frequency (used for speed calculations) */ 215 | ret = stm32_tim_clk_get(&config->pclken, &data->tfreq); 216 | if (ret < 0) { 217 | return ret; 218 | } 219 | 220 | /* check H1/H2/H3 GPIO readiness */ 221 | if (!device_is_ready(config->h1.port) || 222 | !device_is_ready(config->h2.port) || 223 | !device_is_ready(config->h3.port)) { 224 | LOG_ERR("H1/H2/H3 GPIO device/s not ready"); 225 | return -ENODEV; 226 | } 227 | 228 | /* initialize electrical angle */ 229 | curr_state = halls_stm32_get_state(dev); 230 | switch (curr_state) { 231 | case 5U: 232 | data->eangle = 0U; 233 | break; 234 | case 1U: 235 | data->eangle = 60U; 236 | break; 237 | case 3U: 238 | data->eangle = 120U; 239 | break; 240 | case 2U: 241 | data->eangle = 180U; 242 | break; 243 | case 6U: 244 | data->eangle = 240U; 245 | break; 246 | case 4U: 247 | data->eangle = 300U; 248 | break; 249 | default: 250 | break; 251 | } 252 | 253 | data->eangle += config->phase_shift; 254 | data->last_state = curr_state; 255 | 256 | /* connect and enable timer IRQ */ 257 | IRQ_DIRECT_CONNECT(DT_IRQ_BY_NAME(DT_INST_PARENT(0), global, irq), 258 | DT_IRQ_BY_NAME(DT_INST_PARENT(0), global, priority), 259 | timer_irq, 0); 260 | irq_enable(config->irq); 261 | 262 | return 0; 263 | } 264 | 265 | PINCTRL_DT_INST_DEFINE(0); 266 | 267 | static const struct halls_stm32_config halls_stm32_config = { 268 | .timer = (TIM_TypeDef *)DT_REG_ADDR(DT_INST_PARENT(0)), 269 | .pclken = STM32_CLOCK_INFO(0, DT_INST_PARENT(0)), 270 | .h1 = GPIO_DT_SPEC_INST_GET(0, h1_gpios), 271 | .h2 = GPIO_DT_SPEC_INST_GET(0, h2_gpios), 272 | .h3 = GPIO_DT_SPEC_INST_GET(0, h3_gpios), 273 | .irq = DT_IRQ_BY_NAME(DT_INST_PARENT(0), global, irq), 274 | .phase_shift = DT_INST_PROP(0, phase_shift), 275 | .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(0), 276 | }; 277 | 278 | static struct halls_stm32_data halls_stm32_data; 279 | 280 | DEVICE_DT_INST_DEFINE(0, &halls_stm32_init, NULL, &halls_stm32_data, 281 | &halls_stm32_config, POST_KERNEL, 282 | CONFIG_KERNEL_INIT_PRIORITY_DEVICE, 283 | &halls_stm32_driver_api); 284 | -------------------------------------------------------------------------------- /docs/theory/svpwm.rst: -------------------------------------------------------------------------------- 1 | .. _theory-svpwm: 2 | 3 | SV-PWM 4 | ====== 5 | 6 | In a FOC based system we usually synthesize the voltage Space Vector 7 | :math:`\vec{v_s}` using an inverter. When using an inverter we can not generate 8 | all the voltage levels we want but only a discrete set. With a 2-level 3-phase 9 | inverter, the most common one, we can generate :math:`2^3 = 8` voltage levels, 10 | given by: 11 | 12 | .. math:: 13 | 14 | \vec{v_s} = V_d (q_a e^{j0} + q_b e^{j \frac{2\pi}{3}} + q_c e^{j \frac{4\pi}{3}}) 15 | 16 | where :math:`V_d` is the line voltage and :math:`q_a, q_b, q_c \in (0 = 17 | \text{OFF}, 1 = \text{ON})` correspond to the the *switch* state of each phase. 18 | 19 | .. _table-space-vectors: 20 | .. table:: Synthesizable space vectors with a 2-level 3-phase inverter. 21 | :align: center 22 | 23 | ================================================ =========== =========== =========== 24 | Space Vector :math:`q_c` :math:`q_b` :math:`q_a` 25 | ================================================ =========== =========== =========== 26 | :math:`\vec{v_0} = 0` 0 0 0 27 | :math:`\vec{v_1} = V_d` 0 0 1 28 | :math:`\vec{v_2} = V_d e^{j \frac{2 \pi}{3}}` 0 1 0 29 | :math:`\vec{v_3} = V_d e^{j \frac{\pi}{3}}` 0 1 1 30 | :math:`\vec{v_4} = V_d e^{-j \frac{2 \pi}{3}}` 1 0 0 31 | :math:`\vec{v_5} = V_d e^{-j \frac{\pi}{3}}` 1 0 1 32 | :math:`\vec{v_6} = -V_d` 1 1 0 33 | :math:`\vec{v_7} = 0` 1 1 1 34 | ================================================ =========== =========== =========== 35 | 36 | If we draw lines that go from one vector edge to the other we can observe that 37 | these lines form an hexagon as shown below. 38 | 39 | .. _svpwm-hexagon: 40 | .. plot:: theory/images/svpwm-hexagon.py 41 | 42 | SV-PWM synthesizable state vectors. 43 | 44 | In order to synthesize an arbitrary voltage, there is a rather simple technique: 45 | given the sector in which the vector to be synthesized falls, we can quickly 46 | alternate between the two adjacent vectors. Taking a voltage falling in the 47 | first sector, i.e. :math:`\theta \in \left( 0, \frac{\pi}{3} \right)`, we have 48 | that the average space-vector :math:`\vec{v^a_s}` is given by: 49 | 50 | .. math:: 51 | 52 | \left.\vec{v^a_s}\right|_{\text{sector} = 1} 53 | = \frac{1}{T} \left( x T \vec{v_1} + y T \vec{v_3} + z T \vec{0} \right) 54 | = x \vec{v_1} + y \vec{v_3} 55 | 56 | where :math:`T` is the averaging period and :math:`x + y + z = 1`. Note that 57 | :math:`z` is the fraction of time where the actual voltage is zero (this happens 58 | for :math:`\vec{v_0}` and :math:`\vec{v_7}`). Replacing with values from 59 | :numref:`table-space-vectors` we have: 60 | 61 | .. math:: 62 | \hat{V_s} e^{j \theta_s} = x V_d e^{j 0} + y V_d e^{j \frac{\pi}{3}} 63 | 64 | and by equaling both real and imaginary components, we obtain: 65 | 66 | .. math:: 67 | x &= \frac{\hat{V_s}}{V_d} \left( \cos(\theta_s) - \frac{1}{\sqrt{3}} \sin(\theta_s) \right) \\ 68 | y &= \frac{\hat{V_s}}{V_d} \frac{2}{\sqrt{3}} \sin(\theta_s). 69 | 70 | In terms of the :math:`\alpha` and :math:`\beta` components, we have: 71 | 72 | .. math:: 73 | x &= \frac{1}{V_d} \left( \hat{v_{\alpha}} - \frac{1}{\sqrt{3}} \hat{v_{\beta}} \right) \\ 74 | y &= \frac{1}{V_d} \frac{2}{\sqrt{3}} \hat{v_{\beta}}. 75 | 76 | If we repeat the previous calculation for each sector we get similar results. If 77 | we take: 78 | 79 | .. math:: 80 | :label: eq-abc 81 | 82 | a &= x \\ 83 | b &= y \\ 84 | c &= -(x + y) 85 | 86 | being :math:`x, y` the values obtained for the first sector, we can express the 87 | other values as a function of :math:`a, b, c`. 88 | 89 | .. _table-svpwm-XY: 90 | .. table:: SV-PWM X-Y 91 | :align: center 92 | 93 | ====== ========== ========== 94 | Sector :math:`x` :math:`y` 95 | ====== ========== ========== 96 | 1 a b 97 | 2 -c -a 98 | 3 b c 99 | 4 -a -b 100 | 5 c a 101 | 6 -b -c 102 | ====== ========== ========== 103 | 104 | Sector determination 105 | -------------------- 106 | 107 | As we have already seen, knowledge of the sector is essential to compute the 108 | :math:`x, y` values. If we are given the space vector in cartesian form, that 109 | is, :math:`v_{\alpha}` and :math:`v_{\beta}`, we can easily determine its angle 110 | by performing :math:`\arctan\left(\frac{v_{\beta}}{v_{\alpha}}\right)` and hence 111 | the sector. However, :math:`\arctan` is an expensive trigonometric computation. 112 | It turns out there is a faster way to determine the sector. 113 | 114 | If we take a look at the plot of Eq. :eq:`eq-abc` for all sectors, we have that 115 | each sector has a unique combination of signs: 116 | 117 | .. plot:: theory/images/svpwm-abc-signs.py 118 | 119 | Eq. :eq:`eq-abc` signs. 120 | 121 | .. table:: 122 | :align: center 123 | 124 | ====== ====================== ====================== ====================== 125 | Sector :math:`\text{sign}(a)` :math:`\text{sign}(b)` :math:`\text{sign}(c)` 126 | ====== ====================== ====================== ====================== 127 | 1 ``+`` ``+`` ``-`` 128 | 2 ``-`` ``+`` ``-`` 129 | 3 ``-`` ``+`` ``+`` 130 | 4 ``-`` ``-`` ``+`` 131 | 5 ``+`` ``-`` ``+`` 132 | 6 ``+`` ``-`` ``-`` 133 | ====== ====================== ====================== ====================== 134 | 135 | 136 | Amplitude limitation 137 | -------------------- 138 | 139 | By looking at the hexagon we can quickly observe that vectors falling in the 140 | middle of a sector will not have the same average amplitude as the ones that can 141 | be perfectly generated :math:`\vec{v_i}, i \in (0, ..., 7)`. The worst case 142 | happens for the space vectors falling just in the middle of the sector as shown 143 | on the figure below. 144 | 145 | .. figure:: images/svpwm-limit.svg 146 | 147 | Maximum synthesizable amplitude without distortion. 148 | 149 | Therefore, in order to avoid distortions the maximum average amplitude should be 150 | limited to: 151 | 152 | .. math:: 153 | 154 | \hat{V_s}_{MAX} = V_d \cos(30^{\circ}) = V_d \frac{\sqrt{3}}{2}. 155 | 156 | The previous value is actually the maximum line voltage we will be able to use 157 | when using SV-PWM. 158 | 159 | Duty cycles calculation 160 | ----------------------- 161 | 162 | Finally, we need to compute the PWM duty cycles using the values calculated in 163 | :numref:`table-svpwm-XY`. When using a center-aligned PWM we have that the 164 | actual PWM output is "ON" when the control variable is over the trigger signal 165 | (a saw-tooth) and "OFF" otherwise. 166 | 167 | There is still one thing left: the zero or null vector. Right at the beginning 168 | of this section we saw that in the formation of the space vector there is a 169 | fraction of time, :math:`z`, where a *zero* vector is active. There are actually 170 | a couple of zero vectors, :math:`\vec{v_0}` and :math:`\vec{v_7}`. Both vectors 171 | are valid in order to produce the space vector, however, it is common to use the 172 | null-vector that only requires a single switch state change with respect to the 173 | previous or future state. This choice is also known as the **reverse-alternating 174 | sequence**. For example, if the next state is :math:`\vec{v_1}` (001), then the 175 | null-vector choice would be :math:`\vec{v_0}`. Below the waveforms on the first 176 | sector are shown when using such sequence. 177 | 178 | .. _svpwm-pwm-timing: 179 | .. figure:: images/svpwm-pwm-timing.svg 180 | 181 | PWM waveforms in sector 1 (:math:`z = z_0 + z_7`). 182 | 183 | By looking at the timing diagram in :numref:`svpwm-pwm-timing`, we have that the 184 | duty cycles are for the first sector: 185 | 186 | .. math:: 187 | d_a &= x + y + \frac{z}{2} \\ 188 | d_b &= y + \frac{z}{2} \\ 189 | d_c &= \frac{z}{2} 190 | 191 | We can do a similar calculation for each sector, leading to the duty cycles 192 | listed in :numref:`table-svpwm-duties`. 193 | 194 | .. _table-svpwm-duties: 195 | .. table:: SV-PWM duty cycle equations 196 | :align: center 197 | 198 | ====== =========================== =========================== =========================== 199 | Sector :math:`d_a` :math:`d_b` :math:`d_c` 200 | ====== =========================== =========================== =========================== 201 | 1 :math:`x + y + \frac{z}{2}` :math:`y + \frac{z}{2}` :math:`\frac{z}{2}` 202 | 2 :math:`x + \frac{z}{2}` :math:`x + y + \frac{z}{2}` :math:`\frac{z}{2}` 203 | 3 :math:`\frac{z}{2}` :math:`x + y + \frac{z}{2}` :math:`y + \frac{z}{2}` 204 | 4 :math:`\frac{z}{2}` :math:`x + \frac{z}{2}` :math:`x + y + \frac{z}{2}` 205 | 5 :math:`y + \frac{z}{2}` :math:`\frac{z}{2}` :math:`x + y + \frac{z}{2}` 206 | 6 :math:`x + y + \frac{z}{2}` :math:`\frac{z}{2}` :math:`x + \frac{z}{2}` 207 | ====== =========================== =========================== =========================== 208 | 209 | Using equations from :numref:`table-svpwm-XY` and :numref:`table-svpwm-duties` 210 | we can plot the duty cycle waveforms: 211 | 212 | .. _svpwm-modulation: 213 | .. plot:: theory/images/svpwm-modulation.py 214 | 215 | SV-PWM duty cycle waveforms. 216 | -------------------------------------------------------------------------------- /drivers/svpwm/svpwm_stm32.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Teslabs Engineering S.L. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | #define DT_DRV_COMPAT st_stm32_svpwm 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | LOG_MODULE_REGISTER(svpwm_stm32, CONFIG_SPINNER_SVPWM_LOG_LEVEL); 21 | 22 | /******************************************************************************* 23 | * Private 24 | ******************************************************************************/ 25 | 26 | struct svpwm_stm32_config { 27 | TIM_TypeDef *timer; 28 | struct stm32_pclken pclken; 29 | bool enable_comp_outputs; 30 | uint32_t t_dead; 31 | uint32_t t_rise; 32 | const struct device *currsmp; 33 | const struct gpio_dt_spec *enable; 34 | size_t enable_len; 35 | const struct pinctrl_dev_config *pcfg; 36 | }; 37 | 38 | struct svpwm_stm32_data { 39 | uint32_t period; 40 | svm_t svm; 41 | }; 42 | 43 | /******************************************************************************* 44 | * API 45 | ******************************************************************************/ 46 | 47 | static void svpwm_stm32_start(const struct device *dev) 48 | { 49 | const struct svpwm_stm32_config *config = dev->config; 50 | struct svpwm_stm32_data *data = dev->data; 51 | 52 | svm_init(&data->svm); 53 | data->svm.sector = 5U; 54 | currsmp_set_sector(config->currsmp, data->svm.sector); 55 | 56 | /* activate enable pins if available */ 57 | for (size_t i = 0U; i < config->enable_len; i++) { 58 | gpio_pin_set(config->enable[i].port, config->enable[i].pin, 1); 59 | } 60 | 61 | /* configure timer OC for a, b, c */ 62 | LL_TIM_OC_SetCompareCH1(config->timer, data->period / 2U); 63 | LL_TIM_OC_SetCompareCH2(config->timer, data->period / 2U); 64 | LL_TIM_OC_SetCompareCH3(config->timer, data->period / 2U); 65 | 66 | LL_TIM_CC_EnableChannel(config->timer, LL_TIM_CHANNEL_CH1); 67 | LL_TIM_CC_EnableChannel(config->timer, LL_TIM_CHANNEL_CH2); 68 | LL_TIM_CC_EnableChannel(config->timer, LL_TIM_CHANNEL_CH3); 69 | if (config->enable_comp_outputs) { 70 | LL_TIM_CC_EnableChannel(config->timer, LL_TIM_CHANNEL_CH1N); 71 | LL_TIM_CC_EnableChannel(config->timer, LL_TIM_CHANNEL_CH2N); 72 | LL_TIM_CC_EnableChannel(config->timer, LL_TIM_CHANNEL_CH3N); 73 | } 74 | 75 | /* configure timer OC for ADC trigger */ 76 | LL_TIM_CC_EnableChannel(config->timer, LL_TIM_CHANNEL_CH4); 77 | 78 | /* start timer */ 79 | LL_TIM_EnableAllOutputs(config->timer); 80 | 81 | LL_TIM_EnableCounter(config->timer); 82 | } 83 | 84 | static void svpwm_stm32_stop(const struct device *dev) 85 | { 86 | const struct svpwm_stm32_config *config = dev->config; 87 | 88 | /* stop timer */ 89 | LL_TIM_DisableCounter(config->timer); 90 | 91 | LL_TIM_DisableAllOutputs(config->timer); 92 | 93 | LL_TIM_CC_DisableChannel(config->timer, LL_TIM_CHANNEL_CH1); 94 | LL_TIM_CC_DisableChannel(config->timer, LL_TIM_CHANNEL_CH2); 95 | LL_TIM_CC_DisableChannel(config->timer, LL_TIM_CHANNEL_CH3); 96 | if (config->enable_comp_outputs) { 97 | LL_TIM_CC_DisableChannel(config->timer, LL_TIM_CHANNEL_CH1N); 98 | LL_TIM_CC_DisableChannel(config->timer, LL_TIM_CHANNEL_CH2N); 99 | LL_TIM_CC_DisableChannel(config->timer, LL_TIM_CHANNEL_CH3N); 100 | } 101 | 102 | LL_TIM_CC_DisableChannel(config->timer, LL_TIM_CHANNEL_CH4); 103 | 104 | /* deactivate enable pins if available */ 105 | for (size_t i = 0U; i < config->enable_len; i++) { 106 | gpio_pin_set(config->enable[i].port, config->enable[i].pin, 0); 107 | } 108 | } 109 | 110 | static void svpwm_stm32_set_phase_voltages(const struct device *dev, 111 | float v_alpha, float v_beta) 112 | { 113 | const struct svpwm_stm32_config *config = dev->config; 114 | struct svpwm_stm32_data *data = dev->data; 115 | 116 | const svm_duties_t *duties = &data->svm.duties; 117 | 118 | /* space-vector modulation */ 119 | svm_set(&data->svm, v_alpha, v_beta); 120 | 121 | /* program duties */ 122 | LL_TIM_OC_SetCompareCH1(config->timer, 123 | (uint32_t)(data->period * duties->a)); 124 | LL_TIM_OC_SetCompareCH2(config->timer, 125 | (uint32_t)(data->period * duties->b)); 126 | LL_TIM_OC_SetCompareCH3(config->timer, 127 | (uint32_t)(data->period * duties->c)); 128 | 129 | /* inform current sampling device about current sector */ 130 | currsmp_set_sector(config->currsmp, data->svm.sector); 131 | } 132 | 133 | static const struct svpwm_driver_api svpwm_stm32_driver_api = { 134 | .start = svpwm_stm32_start, 135 | .stop = svpwm_stm32_stop, 136 | .set_phase_voltages = svpwm_stm32_set_phase_voltages, 137 | }; 138 | 139 | /******************************************************************************* 140 | * Initialization 141 | ******************************************************************************/ 142 | 143 | static int svpwm_stm32_init(const struct device *dev) 144 | { 145 | const struct svpwm_stm32_config *config = dev->config; 146 | struct svpwm_stm32_data *data = dev->data; 147 | 148 | int ret; 149 | uint32_t freq; 150 | uint16_t psc; 151 | const struct device *clk; 152 | LL_TIM_InitTypeDef tim_init; 153 | LL_TIM_OC_InitTypeDef tim_ocinit; 154 | LL_TIM_BDTR_InitTypeDef brk_dt_init; 155 | 156 | if (!device_is_ready(config->currsmp)) { 157 | LOG_ERR("Current sampling device not ready"); 158 | return -ENODEV; 159 | } 160 | 161 | /* configure pinmux */ 162 | ret = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT); 163 | if (ret < 0) { 164 | LOG_ERR("pinctrl setup failed (%d)", ret); 165 | return ret; 166 | } 167 | 168 | clk = DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE); 169 | 170 | /* enable timer clock */ 171 | ret = clock_control_on(clk, (clock_control_subsys_t *)&config->pclken); 172 | if (ret < 0) { 173 | LOG_ERR("Could not turn on timer clock (%d)", ret); 174 | return ret; 175 | } 176 | 177 | /* compute ARR */ 178 | ret = stm32_tim_clk_get(&config->pclken, &freq); 179 | if (ret < 0) { 180 | return ret; 181 | } 182 | 183 | psc = 0U; 184 | do { 185 | data->period = __LL_TIM_CALC_ARR( 186 | freq, psc, CONFIG_SPINNER_SVPWM_STM32_PWM_FREQ * 2U); 187 | psc++; 188 | } while (data->period > UINT16_MAX); 189 | 190 | /* initialize timer 191 | * NOTE: repetition counter set to 1, update will happen on underflow 192 | */ 193 | LL_TIM_StructInit(&tim_init); 194 | tim_init.CounterMode = LL_TIM_COUNTERMODE_CENTER_UP; 195 | tim_init.Autoreload = data->period; 196 | tim_init.RepetitionCounter = 1U; 197 | if (LL_TIM_Init(config->timer, &tim_init) != SUCCESS) { 198 | LOG_ERR("Could not initialize timer"); 199 | return -EIO; 200 | } 201 | 202 | /* initialize OC for a, b, c channels */ 203 | LL_TIM_OC_StructInit(&tim_ocinit); 204 | tim_ocinit.OCMode = LL_TIM_OCMODE_PWM1; 205 | tim_ocinit.CompareValue = data->period / 2U; 206 | 207 | if (LL_TIM_OC_Init(config->timer, LL_TIM_CHANNEL_CH1, &tim_ocinit) != 208 | SUCCESS) { 209 | LOG_ERR("Could not initialize timer OC for channel 1"); 210 | return -EIO; 211 | } 212 | 213 | if (LL_TIM_OC_Init(config->timer, LL_TIM_CHANNEL_CH2, &tim_ocinit) != 214 | SUCCESS) { 215 | LOG_ERR("Could not initialize timer OC for channel 2"); 216 | return -EIO; 217 | } 218 | 219 | if (LL_TIM_OC_Init(config->timer, LL_TIM_CHANNEL_CH3, &tim_ocinit) != 220 | SUCCESS) { 221 | LOG_ERR("Could not initialize timer OC for channel 3"); 222 | return -EIO; 223 | } 224 | 225 | /* initialize OC for ADC trigger channel */ 226 | tim_ocinit.OCMode = LL_TIM_OCMODE_PWM2; 227 | tim_ocinit.CompareValue = data->period - 1U; 228 | if (LL_TIM_OC_Init(config->timer, LL_TIM_CHANNEL_CH4, &tim_ocinit) != 229 | SUCCESS) { 230 | LOG_ERR("Could not initialize timer OC for channel 4"); 231 | return -EIO; 232 | } 233 | 234 | LL_TIM_SetTriggerOutput(config->timer, LL_TIM_TRGO_OC4REF); 235 | 236 | /* enable pre-load on all OC channels */ 237 | LL_TIM_OC_EnablePreload(config->timer, LL_TIM_CHANNEL_CH1); 238 | LL_TIM_OC_EnablePreload(config->timer, LL_TIM_CHANNEL_CH2); 239 | LL_TIM_OC_EnablePreload(config->timer, LL_TIM_CHANNEL_CH3); 240 | LL_TIM_OC_EnablePreload(config->timer, LL_TIM_CHANNEL_CH4); 241 | 242 | /* configure ADC sampling point (middle of the period) */ 243 | LL_TIM_OC_SetCompareCH4(config->timer, data->period - 1U); 244 | 245 | /* setup break and dead-time if available */ 246 | LL_TIM_BDTR_StructInit(&brk_dt_init); 247 | brk_dt_init.OSSRState = LL_TIM_OSSR_ENABLE; 248 | brk_dt_init.OSSIState = LL_TIM_OSSI_ENABLE; 249 | brk_dt_init.LockLevel = LL_TIM_LOCKLEVEL_1; 250 | /* TODO: add support for dead-time */ 251 | brk_dt_init.DeadTime = 0U; 252 | brk_dt_init.BreakState = LL_TIM_BREAK_ENABLE; 253 | brk_dt_init.BreakPolarity = LL_TIM_BREAK_POLARITY_HIGH; 254 | brk_dt_init.Break2State = LL_TIM_BREAK2_ENABLE; 255 | if (LL_TIM_BDTR_Init(config->timer, &brk_dt_init) != SUCCESS) { 256 | LOG_ERR("Could not initialize timer break"); 257 | return -EIO; 258 | } 259 | 260 | /* initialize enable GPIOs */ 261 | for (size_t i = 0U; i < config->enable_len; i++) { 262 | const struct gpio_dt_spec *enable_gpio = &config->enable[i]; 263 | 264 | if (!device_is_ready(enable_gpio->port)) { 265 | LOG_ERR("Enable GPIO not ready"); 266 | return -ENODEV; 267 | } 268 | 269 | ret = gpio_pin_configure_dt(enable_gpio, GPIO_OUTPUT_INACTIVE); 270 | if (ret < 0) { 271 | LOG_ERR("Could not configure enable GPIO"); 272 | return ret; 273 | } 274 | } 275 | 276 | return 0; 277 | } 278 | 279 | PINCTRL_DT_INST_DEFINE(0); 280 | 281 | static const struct gpio_dt_spec enable_pins[] = {DT_FOREACH_PROP_ELEM_SEP( 282 | DT_INST_CHILD(0, driver), enable_gpios, GPIO_DT_SPEC_GET_BY_IDX, (, ))}; 283 | 284 | static const struct svpwm_stm32_config svpwm_stm32_config = { 285 | .timer = (TIM_TypeDef *)DT_REG_ADDR(DT_INST_PARENT(0)), 286 | .pclken = STM32_CLOCK_INFO(0, DT_INST_PARENT(0)), 287 | .enable_comp_outputs = DT_PROP_OR(DT_INST_CHILD(0, driver), 288 | enable_comp_outputs, false), 289 | .t_dead = DT_PROP_OR(DT_INST_CHILD(0, driver), t_dead_ns, 0), 290 | .t_rise = DT_PROP_OR(DT_INST_CHILD(0, driver), t_rise_ns, 0), 291 | .currsmp = DEVICE_DT_GET(DT_INST_PHANDLE(0, currsmp)), 292 | .enable = enable_pins, 293 | .enable_len = ARRAY_SIZE(enable_pins), 294 | .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(0), 295 | }; 296 | 297 | static struct svpwm_stm32_data svpwm_stm32_data; 298 | 299 | DEVICE_DT_INST_DEFINE(0, &svpwm_stm32_init, NULL, &svpwm_stm32_data, 300 | &svpwm_stm32_config, POST_KERNEL, 301 | CONFIG_SPINNER_SVPWM_INIT_PRIORITY, 302 | &svpwm_stm32_driver_api); 303 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /docs/_static/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | image/svg+xml 43 | 44 | 45 | 46 | 47 | 51 | 55 | 60 | 65 | 70 | 75 | 77 | 82 | 87 | 92 | 97 | 114 | 115 | 118 | 119 | 122 | 123 | 126 | 127 | 130 | 131 | 134 | 135 | 138 | 139 | 142 | 143 | 146 | 147 | 150 | 151 | 154 | 155 | 158 | 159 | 162 | 163 | 166 | 167 | 170 | 171 | 174 | 175 | 213 | 214 | -------------------------------------------------------------------------------- /drivers/currsmp/currsmp_shunt_stm32.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Teslabs Engineering S.L. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | #define DT_DRV_COMPAT st_stm32_currsmp_shunt 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | 16 | #include 17 | #include 18 | 19 | LOG_MODULE_REGISTER(currsmp_shunt_stm32, CONFIG_SPINNER_CURRSMP_LOG_LEVEL); 20 | 21 | /******************************************************************************* 22 | * Private 23 | ******************************************************************************/ 24 | 25 | struct currsmp_shunt_stm32_config { 26 | ADC_TypeDef *adc; 27 | struct stm32_pclken pclken; 28 | uint32_t adc_irq; 29 | uint8_t adc_resolution; 30 | uint16_t adc_tsample; 31 | uint32_t adc_ch_a; 32 | uint32_t adc_ch_b; 33 | uint32_t adc_ch_c; 34 | uint32_t adc_trigger; 35 | const struct pinctrl_dev_config *pcfg; 36 | }; 37 | 38 | struct currsmp_shunt_stm32_data { 39 | currsmp_regulation_cb_t regulation_cb; 40 | void *regulation_ctx; 41 | uint16_t i_a_offset; 42 | uint16_t i_b_offset; 43 | uint16_t i_c_offset; 44 | uint8_t sector; 45 | uint32_t jsqr[3]; 46 | }; 47 | 48 | ISR_DIRECT_DECLARE(adc_irq) 49 | { 50 | const struct device *dev = DEVICE_DT_INST_GET(0); 51 | const struct currsmp_shunt_stm32_config *config = dev->config; 52 | struct currsmp_shunt_stm32_data *data = dev->data; 53 | 54 | if (LL_ADC_IsActiveFlag_JEOS(config->adc)) { 55 | LL_ADC_ClearFlag_JEOS(config->adc); 56 | data->regulation_cb(data->regulation_ctx); 57 | } 58 | 59 | return 0; 60 | } 61 | 62 | /** 63 | * Compute the ADC injected sequence register (JSQR) for the given 2 channels. 64 | * 65 | * @param[in] trigger ADC trigger. 66 | * @param[in] rank1_ch Rank 1 channel. 67 | * @param[in] rank2_ch Rank 2 channel. 68 | * 69 | * @return Computed JSQR register value. 70 | */ 71 | static uint32_t adc_calc_jsqr(uint32_t trigger, uint32_t rank1_ch, 72 | uint32_t rank2_ch) 73 | { 74 | uint32_t jsqr; 75 | 76 | uint8_t ch1 = __LL_ADC_CHANNEL_TO_DECIMAL_NB(rank1_ch); 77 | uint8_t ch2 = __LL_ADC_CHANNEL_TO_DECIMAL_NB(rank2_ch); 78 | 79 | #ifdef CONFIG_SOC_SERIES_STM32F3X 80 | /* F3X ADC uses channels 1..18, indexed from 0..17 */ 81 | ch1--; 82 | ch2--; 83 | #endif 84 | 85 | jsqr = ((ch1 & ADC_INJ_RANK_ID_JSQR_MASK) 86 | << ADC_INJ_RANK_1_JSQR_BITOFFSET_POS) | 87 | ((ch2 & ADC_INJ_RANK_ID_JSQR_MASK) 88 | << ADC_INJ_RANK_2_JSQR_BITOFFSET_POS) | 89 | LL_ADC_INJ_TRIG_EXT_RISING | trigger | 1U; 90 | 91 | return jsqr; 92 | } 93 | 94 | /** 95 | * @brief Configure ADC. 96 | * 97 | * @param[in] dev Current sampling device. 98 | * 99 | * @return 0 on success, negative errno otherwise. 100 | */ 101 | static int adc_configure(const struct device *dev) 102 | { 103 | const struct currsmp_shunt_stm32_config *config = dev->config; 104 | 105 | int ret; 106 | const struct device *clk; 107 | uint32_t smp; 108 | LL_ADC_CommonInitTypeDef adc_cinit; 109 | LL_ADC_InitTypeDef adc_init; 110 | LL_ADC_REG_InitTypeDef adc_rinit; 111 | LL_ADC_INJ_InitTypeDef adc_jinit; 112 | uint32_t adc_clk; 113 | 114 | /* enable ADC clock */ 115 | clk = DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE); 116 | ret = clock_control_on(clk, (clock_control_subsys_t *)&config->pclken); 117 | if (ret < 0) { 118 | LOG_ERR("Could not turn on ADC clock (%d)", ret); 119 | return ret; 120 | } 121 | 122 | /* configure common ADC instance */ 123 | LL_ADC_CommonStructInit(&adc_cinit); 124 | if (config->adc_resolution == 6U) { 125 | adc_cinit.CommonClock = LL_ADC_CLOCK_SYNC_PCLK_DIV2; 126 | } else { 127 | adc_cinit.CommonClock = LL_ADC_CLOCK_SYNC_PCLK_DIV4; 128 | } 129 | if (LL_ADC_CommonInit(__LL_ADC_COMMON_INSTANCE(config->adc), 130 | &adc_cinit) != SUCCESS) { 131 | LOG_ERR("Could not initialize common ADC"); 132 | return -EIO; 133 | } 134 | 135 | /* configure ADC */ 136 | LL_ADC_StructInit(&adc_init); 137 | 138 | ret = stm32_adc_res_get(config->adc_resolution, &adc_init.Resolution); 139 | if (ret < 0) { 140 | LOG_ERR("Unsupported ADC resolution"); 141 | return ret; 142 | } 143 | 144 | if (LL_ADC_Init(config->adc, &adc_init) != SUCCESS) { 145 | LOG_ERR("Could not initialize ADC"); 146 | return -EIO; 147 | } 148 | 149 | /* configure ADC (regular) */ 150 | LL_ADC_REG_StructInit(&adc_rinit); 151 | adc_rinit.Overrun = LL_ADC_REG_OVR_DATA_PRESERVED; 152 | if (LL_ADC_REG_Init(config->adc, &adc_rinit) != SUCCESS) { 153 | LOG_ERR("Could not initialize ADC regular group"); 154 | return -EIO; 155 | } 156 | 157 | /* configure ADC (injected) */ 158 | LL_ADC_INJ_StructInit(&adc_jinit); 159 | adc_jinit.TriggerSource = 160 | config->adc_trigger | LL_ADC_INJ_TRIG_EXT_RISING; 161 | if (LL_ADC_INJ_Init(config->adc, &adc_jinit) != SUCCESS) { 162 | LOG_ERR("Could not initialize ADC injected group"); 163 | return -EIO; 164 | } 165 | 166 | /* configure sampling time */ 167 | ret = stm32_adc_smp_get(config->adc_tsample, &smp); 168 | if (ret < 0) { 169 | LOG_ERR("Unsupported ADC sampling time"); 170 | return ret; 171 | } 172 | 173 | LL_ADC_SetChannelSamplingTime(config->adc, config->adc_ch_a, smp); 174 | LL_ADC_SetChannelSamplingTime(config->adc, config->adc_ch_b, smp); 175 | LL_ADC_SetChannelSamplingTime(config->adc, config->adc_ch_c, smp); 176 | 177 | /* enable internal ADC regulator */ 178 | #if defined(CONFIG_SOC_SERIES_STM32G4X) 179 | LL_ADC_DisableDeepPowerDown(config->adc); 180 | #endif 181 | LL_ADC_EnableInternalRegulator(config->adc); 182 | k_busy_wait(LL_ADC_DELAY_INTERNAL_REGUL_STAB_US); 183 | if (!LL_ADC_IsInternalRegulatorEnabled(config->adc)) { 184 | LOG_ERR("ADC internal regulator not enabled within expected " 185 | "time"); 186 | return -EIO; 187 | } 188 | 189 | /* calibrate ADC */ 190 | LL_ADC_StartCalibration(config->adc, LL_ADC_SINGLE_ENDED); 191 | while (LL_ADC_IsCalibrationOnGoing(config->adc)) 192 | ; 193 | 194 | /* wait to enable ADC after calibration */ 195 | ret = stm32_adc_clk_get(config->adc, &config->pclken, &adc_clk); 196 | if (ret < 0) { 197 | return ret; 198 | } 199 | 200 | k_busy_wait(MAX(1U, (uint32_t)((1.0e6f / (float)adc_clk) * 201 | LL_ADC_DELAY_CALIB_ENABLE_ADC_CYCLES))); 202 | 203 | /* enable ADC */ 204 | LL_ADC_Enable(config->adc); 205 | while (LL_ADC_IsActiveFlag_ADRDY(config->adc) != 1U) 206 | ; 207 | 208 | /* configure ADC IRQ */ 209 | LL_ADC_EnableIT_JEOS(config->adc); 210 | 211 | IRQ_DIRECT_CONNECT(DT_IRQ_BY_IDX(DT_INST_PARENT(0), 0, irq), 212 | DT_IRQ_BY_IDX(DT_INST_PARENT(0), 0, priority), 213 | adc_irq, IRQ_ZERO_LATENCY); 214 | irq_enable(config->adc_irq); 215 | 216 | return 0; 217 | } 218 | 219 | /** 220 | * @brief Perform regular ADC read. 221 | * 222 | * @param dev Current sampling device 223 | * @param channel ADC channel 224 | * 225 | * @return Sample value. 226 | */ 227 | static uint16_t adc_read(const struct device *dev, uint32_t channel) 228 | { 229 | const struct currsmp_shunt_stm32_config *config = dev->config; 230 | 231 | /* configure sequencer: only one channel */ 232 | LL_ADC_REG_SetSequencerLength(config->adc, LL_ADC_REG_SEQ_SCAN_DISABLE); 233 | LL_ADC_REG_SetSequencerRanks(config->adc, LL_ADC_REG_RANK_1, channel); 234 | 235 | /* perform regular conversion */ 236 | LL_ADC_REG_StartConversion(config->adc); 237 | while (LL_ADC_IsActiveFlag_EOS(config->adc) != 1U) 238 | ; 239 | 240 | LL_ADC_ClearFlag_EOS(config->adc); 241 | 242 | return (uint16_t)LL_ADC_REG_ReadConversionData32(config->adc); 243 | } 244 | 245 | /******************************************************************************* 246 | * API 247 | ******************************************************************************/ 248 | 249 | static void currsmp_shunt_stm32_configure(const struct device *dev, 250 | currsmp_regulation_cb_t regulation_cb, 251 | void *ctx) 252 | { 253 | struct currsmp_shunt_stm32_data *data = dev->data; 254 | 255 | data->regulation_cb = regulation_cb; 256 | data->regulation_ctx = ctx; 257 | } 258 | 259 | static void currsmp_shunt_stm32_get_currents(const struct device *dev, 260 | struct currsmp_curr *curr) 261 | { 262 | const struct currsmp_shunt_stm32_config *config = dev->config; 263 | struct currsmp_shunt_stm32_data *data = dev->data; 264 | 265 | uint16_t val_ch1; 266 | uint16_t val_ch2; 267 | int16_t i_a = 0, i_b = 0, i_c = 0; 268 | 269 | val_ch1 = (uint16_t)LL_ADC_INJ_ReadConversionData32(config->adc, 270 | LL_ADC_INJ_RANK_1); 271 | val_ch2 = (uint16_t)LL_ADC_INJ_ReadConversionData32(config->adc, 272 | LL_ADC_INJ_RANK_2); 273 | 274 | switch (data->sector) { 275 | case 1U: 276 | i_b = data->i_b_offset - val_ch1; 277 | i_c = data->i_c_offset - val_ch2; 278 | i_a = -(i_b + i_c); 279 | break; 280 | case 2U: 281 | i_a = data->i_a_offset - val_ch1; 282 | i_c = data->i_c_offset - val_ch2; 283 | i_b = -(i_a + i_c); 284 | break; 285 | case 3U: 286 | i_a = data->i_a_offset - val_ch1; 287 | i_c = data->i_c_offset - val_ch2; 288 | i_b = -(i_a + i_c); 289 | break; 290 | case 4U: 291 | i_a = data->i_a_offset - val_ch2; 292 | i_b = data->i_b_offset - val_ch1; 293 | i_c = -(i_a + i_b); 294 | break; 295 | case 5U: 296 | i_a = data->i_a_offset - val_ch2; 297 | i_b = data->i_b_offset - val_ch1; 298 | i_c = -(i_a + i_b); 299 | break; 300 | case 6U: 301 | i_b = data->i_b_offset - val_ch1; 302 | i_c = data->i_c_offset - val_ch2; 303 | i_a = -(i_b + i_c); 304 | break; 305 | default: 306 | __ASSERT(NULL, "Unexpected sector"); 307 | break; 308 | } 309 | 310 | curr->i_a = (float)i_a / (2U << (config->adc_resolution - 1U)); 311 | curr->i_b = (float)i_b / (2U << (config->adc_resolution - 1U)); 312 | curr->i_c = (float)i_c / (2U << (config->adc_resolution - 1U)); 313 | } 314 | 315 | static void currsmp_shunt_stm32_set_sector(const struct device *dev, 316 | uint8_t sector) 317 | { 318 | const struct currsmp_shunt_stm32_config *config = dev->config; 319 | struct currsmp_shunt_stm32_data *data = dev->data; 320 | 321 | data->sector = sector; 322 | config->adc->JSQR = data->jsqr[sector / 2U % 3U]; 323 | } 324 | 325 | static uint32_t currsmp_shunt_stm32_get_smp_time(const struct device *dev) 326 | { 327 | const struct currsmp_shunt_stm32_config *config = dev->config; 328 | 329 | int ret; 330 | uint32_t clk; 331 | float t_sar; 332 | 333 | ret = stm32_adc_clk_get(config->adc, &config->pclken, &clk); 334 | if (ret < 0) { 335 | LOG_ERR("Could not obtain ADC clock rate"); 336 | return 0U; 337 | } 338 | 339 | ret = stm32_adc_t_sar_get(config->adc_resolution, &t_sar); 340 | if (ret < 0) { 341 | LOG_ERR("Could not obtain ADC SAR time"); 342 | return 0U; 343 | } 344 | 345 | return (uint32_t)((1.0e9f / (float)clk) * 346 | (t_sar + 2.0f * (float)config->adc_tsample)); 347 | } 348 | 349 | static void currsmp_shunt_stm32_start(const struct device *dev) 350 | { 351 | const struct currsmp_shunt_stm32_config *config = dev->config; 352 | struct currsmp_shunt_stm32_data *data = dev->data; 353 | 354 | /* calibrate a, b, c offset */ 355 | LL_ADC_ClearFlag_EOS(config->adc); 356 | 357 | data->i_a_offset = adc_read(dev, config->adc_ch_a); 358 | data->i_b_offset = adc_read(dev, config->adc_ch_b); 359 | data->i_c_offset = adc_read(dev, config->adc_ch_c); 360 | 361 | /* start injected conversions (triggered by sv-pwm) */ 362 | LL_ADC_ClearFlag_JEOS(config->adc); 363 | LL_ADC_INJ_StartConversion(config->adc); 364 | } 365 | 366 | static void currsmp_shunt_stm32_stop(const struct device *dev) 367 | { 368 | const struct currsmp_shunt_stm32_config *config = dev->config; 369 | 370 | LL_ADC_INJ_StopConversion(config->adc); 371 | while (LL_ADC_INJ_IsStopConversionOngoing(config->adc) != 0U) 372 | ; 373 | 374 | while (LL_ADC_INJ_IsConversionOngoing(config->adc) != 0U) 375 | ; 376 | } 377 | 378 | static void currsmp_shunt_stm32_pause(const struct device *dev) 379 | { 380 | const struct currsmp_shunt_stm32_config *config = dev->config; 381 | 382 | LL_ADC_DisableIT_JEOS(config->adc); 383 | } 384 | 385 | static void currsmp_shunt_stm32_resume(const struct device *dev) 386 | { 387 | const struct currsmp_shunt_stm32_config *config = dev->config; 388 | 389 | LL_ADC_EnableIT_JEOS(config->adc); 390 | } 391 | 392 | static const struct currsmp_driver_api currsmp_shunt_stm32_driver_api = { 393 | .configure = currsmp_shunt_stm32_configure, 394 | .get_currents = currsmp_shunt_stm32_get_currents, 395 | .set_sector = currsmp_shunt_stm32_set_sector, 396 | .get_smp_time = currsmp_shunt_stm32_get_smp_time, 397 | .start = currsmp_shunt_stm32_start, 398 | .stop = currsmp_shunt_stm32_stop, 399 | .pause = currsmp_shunt_stm32_pause, 400 | .resume = currsmp_shunt_stm32_resume, 401 | }; 402 | 403 | /******************************************************************************* 404 | * Initialization 405 | ******************************************************************************/ 406 | 407 | static int currsmp_shunt_stm32_init(const struct device *dev) 408 | { 409 | const struct currsmp_shunt_stm32_config *config = dev->config; 410 | struct currsmp_shunt_stm32_data *data = dev->data; 411 | 412 | int ret; 413 | 414 | /* configure pinmux */ 415 | ret = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT); 416 | if (ret < 0) { 417 | LOG_ERR("pinctrl setup failed (%d)", ret); 418 | return ret; 419 | } 420 | 421 | /* configure ADC */ 422 | ret = adc_configure(dev); 423 | if (ret < 0) { 424 | return ret; 425 | } 426 | 427 | /* pre-compute ADC injected sequences */ 428 | data->jsqr[0] = adc_calc_jsqr(config->adc_trigger, config->adc_ch_b, 429 | config->adc_ch_c); 430 | data->jsqr[1] = adc_calc_jsqr(config->adc_trigger, config->adc_ch_a, 431 | config->adc_ch_c); 432 | data->jsqr[2] = adc_calc_jsqr(config->adc_trigger, config->adc_ch_b, 433 | config->adc_ch_a); 434 | 435 | return 0; 436 | } 437 | 438 | PINCTRL_DT_INST_DEFINE(0); 439 | 440 | static const struct currsmp_shunt_stm32_config currsmp_shunt_stm32_config = { 441 | .adc = (ADC_TypeDef *)DT_REG_ADDR(DT_INST_PARENT(0)), 442 | .pclken = STM32_CLOCK_INFO(0, DT_INST_PARENT(0)), 443 | .adc_irq = DT_IRQ_BY_IDX(DT_INST_PARENT(0), 0, irq), 444 | .adc_resolution = DT_INST_PROP(0, adc_resolution), 445 | .adc_tsample = DT_INST_PROP(0, adc_tsample), 446 | .adc_ch_a = __LL_ADC_DECIMAL_NB_TO_CHANNEL( 447 | DT_INST_PROP_BY_IDX(0, adc_channels, 0)), 448 | .adc_ch_b = __LL_ADC_DECIMAL_NB_TO_CHANNEL( 449 | DT_INST_PROP_BY_IDX(0, adc_channels, 1)), 450 | .adc_ch_c = __LL_ADC_DECIMAL_NB_TO_CHANNEL( 451 | DT_INST_PROP_BY_IDX(0, adc_channels, 2)), 452 | .adc_trigger = DT_INST_PROP(0, adc_trigger), 453 | .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(0), 454 | }; 455 | 456 | static struct currsmp_shunt_stm32_data currsmp_shunt_stm32_data; 457 | 458 | DEVICE_DT_INST_DEFINE(0, &currsmp_shunt_stm32_init, NULL, 459 | &currsmp_shunt_stm32_data, &currsmp_shunt_stm32_config, 460 | POST_KERNEL, CONFIG_SPINNER_CURRSMP_INIT_PRIORITY, 461 | &currsmp_shunt_stm32_driver_api); 462 | -------------------------------------------------------------------------------- /docs/theory/images/svpwm-limit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 30° 168 | 169 | 170 | 171 | 172 | 173 | Vd 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | VsMAX 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | --------------------------------------------------------------------------------