Repository: neoxic/ESCape32 Branch: master Commit: 8f03a996af30 Files: 65 Total size: 248.7 KB Directory structure: gitextract_jbpzzf8n/ ├── .github/ │ └── workflows/ │ └── build.yml ├── CMakeLists.txt ├── LICENSE ├── README.md ├── boot/ │ ├── CMakeLists.txt │ ├── mcu/ │ │ ├── STM32F0/ │ │ │ ├── config.c │ │ │ ├── config.cmake │ │ │ ├── config.h │ │ │ └── config.ld │ │ ├── STM32G0/ │ │ │ ├── config.c │ │ │ ├── config.cmake │ │ │ ├── config.h │ │ │ └── config.ld │ │ ├── STM32G4/ │ │ │ ├── config.c │ │ │ ├── config.cmake │ │ │ ├── config.h │ │ │ └── config.ld │ │ └── common.ld │ └── src/ │ ├── common.h │ ├── io.c │ ├── main.c │ └── util.c ├── mcu/ │ ├── AT32F421/ │ │ ├── config.c │ │ ├── config.cmake │ │ ├── config.h │ │ ├── config.ld │ │ └── preset.c │ ├── GD32E230/ │ │ ├── config.c │ │ ├── config.cmake │ │ ├── config.h │ │ ├── config.ld │ │ └── preset.c │ ├── GD32F350/ │ │ ├── config.c │ │ ├── config.cmake │ │ ├── config.h │ │ ├── config.ld │ │ └── preset.c │ ├── STM32F051/ │ │ ├── config.c │ │ ├── config.cmake │ │ ├── config.h │ │ ├── config.ld │ │ └── preset.c │ ├── STM32G071/ │ │ ├── config.c │ │ ├── config.cmake │ │ ├── config.h │ │ ├── config.ld │ │ └── preset.c │ ├── STM32G431/ │ │ ├── config.c │ │ ├── config.cmake │ │ ├── config.h │ │ ├── config.ld │ │ └── preset.c │ ├── STM32L431/ │ │ ├── config.c │ │ ├── config.cmake │ │ ├── config.h │ │ ├── config.ld │ │ └── preset.c │ └── common.ld └── src/ ├── cli.c ├── common.h ├── defs.h ├── io.c ├── main.c ├── telem.c └── util.c ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/build.yml ================================================ name: Build ESCape32 on: workflow_dispatch jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - run: | sudo apt update sudo apt install -y cmake sudo apt install -y gcc-arm-none-eabi sudo apt install -y binutils-arm-none-eabi sudo apt install -y libnewlib-arm-none-eabi git clone https://github.com/libopencm3/libopencm3.git make -C libopencm3 TARGETS='stm32/f0 stm32/g0 stm32/g4 stm32/l4' cmake -B build -D LIBOPENCM3_DIR=libopencm3 cd build make - uses: actions/upload-artifact@v4 with: name: escape32-build path: build/**/*.bin retention-days: 7 ================================================ FILE: CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.15) set(CMAKE_SYSTEM_NAME Generic) set(CMAKE_C_COMPILER arm-none-eabi-gcc) set(CMAKE_C_COMPILER_WORKS 1) set(CMAKE_C_FLAGS "-Os -g") set(CMAKE_C_FLAGS_DEBUG "-Og") project(ESCape32 C) if(UNIX AND NOT CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) # Use 'CMAKE_INSTALL_PREFIX' as alternative system root set(CMAKE_PREFIX_PATH ${CMAKE_INSTALL_PREFIX}) include(GNUInstallDirs) include_directories(${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_INCLUDEDIR}) link_directories(${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}) endif() if(LIBOPENCM3_DIR) include_directories(${LIBOPENCM3_DIR}/include) link_directories(${LIBOPENCM3_DIR}/lib) endif() add_compile_options(-ffreestanding -ffunction-sections -fdata-sections -fsingle-precision-constant -Wall -Wextra -Wpedantic -Wundef -Wshadow -Wredundant-decls -Wstrict-prototypes -Wmissing-prototypes -Wno-variadic-macros -Wno-unused-result -Wno-unused-parameter -Wno-unused-label) add_link_options(-nostartfiles -Wl,--gc-sections) function(add_target name mcu) include(mcu/${mcu}/config.cmake) file(READ src/main.c main_c) string(REGEX MATCH "REVISION [0-9]+" rev1 ${main_c}) string(REGEX MATCH "REVPATCH [0-9]+" rev2 ${main_c}) string(REGEX REPLACE "[^0-9]+" "-rev" rev1 ${rev1}) if (rev2) string(REGEX REPLACE "[^0-9]+" "." rev2 ${rev2}) endif() set(elf ${name}${rev1}${rev2}.elf) set(bin ${name}${rev1}${rev2}.bin) set(hex ${name}${rev1}${rev2}.hex) file(GLOB srcs src/*.c mcu/${mcu}/*.c) add_executable(${elf} ${srcs}) target_include_directories(${elf} PRIVATE src mcu/${mcu}) target_compile_options(${elf} PRIVATE ${opts}) target_compile_definitions(${elf} PRIVATE ${mcu} ${name} ${defs} TARGET_NAME="${name}" ${ARGN}) target_link_options(${elf} PRIVATE -T${CMAKE_CURRENT_SOURCE_DIR}/mcu/${mcu}/config.ld -T${CMAKE_CURRENT_SOURCE_DIR}/mcu/common.ld ${opts}) target_link_libraries(${elf} c_nano gcc nosys ${libs}) add_custom_command( OUTPUT ${bin} ${hex} COMMAND arm-none-eabi-objcopy -O binary ${elf} ${bin} COMMAND arm-none-eabi-objcopy -O ihex ${elf} ${hex} DEPENDS ${elf} ) add_custom_target(${name} ALL DEPENDS ${hex}) add_custom_target(flash-${name} COMMAND st-flash --reset --connect-under-reset --format ihex write ${hex} DEPENDS ${hex}) endfunction() add_subdirectory(boot) add_target(AART1 AT32F421 DEAD_TIME=0 COMP_MAP=123 ARM=0 VOLUME=0 INPUT_MODE=1 ANALOG_CHAN=6 ANALOG_MIN=270 ANALOG_MAX=1440 DUTY_MIN=100 DUTY_SPUP=25 FULL_DUTY IO_RXTX) add_target(AIRBOT1 AT32F421 DEAD_TIME=66 COMP_MAP=321 SENS_MAP=0xA3A6 VOLT_MUL=738 CURR_MUL=30 LED_MAP=0xAFB3B4) add_target(AIRBOT2 STM32F051 DEAD_TIME=26 COMP_MAP=321 SENS_MAP=0xA3 VOLT_MUL=738 IO_PA2) add_target(EMAX1 STM32F051 DEAD_TIME=26 COMP_MAP=123 IO_PA2) add_target(ESCAPE1 STM32G071 DEAD_TIME=35 COMP_MAP=123 HALL_MAP=0xAFB35 SENS_MAP=0xA6A5A4 VOLT_MUL=1100 CURR_MUL=30 BEC_MAP=0xCEF LED_WS2812 LED_STAT IO_PA2) add_target(ESCAPE2 STM32G071 DEAD_TIME=35 COMP_MAP=123 SENS_MAP=0xA6 VOLT_MUL=1100 BEC_MAP=0xADE IO_PA2) add_target(ESCAPE3 AT32F421 DEAD_TIME=66 COMP_MAP=123 SENS_MAP=0xA6 VOLT_MUL=1100 BEC_MAP=0xADE) add_target(ESCAPE4 STM32G071 DEAD_TIME=35 COMP_MAP=123 HALL_MAP=0xB358 SENS_MAP=0xA6A5A4 VOLT_MUL=1100 CURR_MUL=30 BEC_MAP=0xCEF LED_MAP=0xF2AF LED1_INV LED_STAT IO_PA2) add_target(ESCAPE5 STM32G071 DEAD_TIME=35 COMP_MAP=123 SENS_MAP=0xA6 VOLT_MUL=1100 BEC_MAP=0xA45 LED_WS2812 LED_STAT IO_PA2) add_target(FLYCOLOR1 STM32F051 DEAD_TIME=26 COMP_MAP=123 SENS_MAP=0xA6 VOLT_MUL=1100 LED_MAP=0xB5B4B3 IO_PA2) add_target(FLYCOLOR2 STM32G071 DEAD_TIME=35 COMP_MAP=123 SENS_MAP=0xA6 VOLT_MUL=1100 LED_MAP=0xB8 LED_STAT) add_target(FLYCOLOR3 STM32G071 DEAD_TIME=50 COMP_MAP=123 SENS_MAP=0xA6A4 VOLT_MUL=2100 CURR_MUL=30) add_target(FLYINGRC1 AT32F421 DEAD_TIME=66 COMP_MAP=123) add_target(FREELYRC1 AT32F421 DEAD_TIME=66 COMP_MAP=123 SENS_MAP=0xA3 VOLT_MUL=1100 LED_WS2812 LED_STAT) add_target(GEPRC1 AT32F421 DEAD_TIME=66 COMP_MAP=123 SENS_MAP=0xA3 VOLT_MUL=1100) add_target(HAKRC1 STM32F051 DEAD_TIME=26 COMP_MAP=213 SENS_MAP=0xA3 VOLT_MUL=1100 LED_MAP=0xAFB5B3 LED_INV LED_OD) add_target(HAKRC2 AT32F421 DEAD_TIME=66 COMP_MAP=213 SENS_MAP=0xA3 VOLT_MUL=1100 LED_MAP=0xAFB5B3 LED_INV LED_OD) add_target(HAKRC3 STM32G071 DEAD_TIME=35 COMP_MAP=132 SENS_MAP=0xA1A5 VOLT_MUL=1100 CURR_MUL=20 LED_WS2812 LED_STAT) add_target(HGLRC1 STM32F051 DEAD_TIME=26 COMP_MAP=123 SENS_MAP=0xA6 VOLT_MUL=2100 IO_PA2) add_target(HHOBBIES1 GD32E230 DEAD_TIME=40 COMP_MAP=321 SENS_MAP=0xA6 VOLT_MUL=1100 LED_MAP=0xAF LED_INV LED_STAT) add_target(HOBBYWING1 STM32F051 DEAD_TIME=40 COMP_MAP=123 IO_PA2) add_target(HOBBYWING2 AT32F421 DEAD_TIME=96 COMP_MAP=123) add_target(HOLYBRO1 GD32F350 DEAD_TIME=57 COMP_MAP=321 SENS_MAP=0xA6A0 VOLT_MUL=1290 CURR_MUL=50 USE_COMP2) add_target(IFLIGHT1 STM32F051 DEAD_TIME=26 COMP_MAP=321 SENS_MAP=0xA3A6 VOLT_MUL=1100 CURR_MUL=60 LED_MAP=0xB8B5B3 LED_INV) add_target(IFLIGHT2 STM32G071 DEAD_TIME=35 COMP_MAP=213 SENS_MAP=0xA5A4 VOLT_MUL=1100 CURR_MUL=20 LED_WS2812 IO_PA6) add_target(IFLIGHT3 STM32G071 DEAD_TIME=35 COMP_MAP=132 SENS_MAP=0xA0 VOLT_MUL=1100 LED_WS2812) add_target(IFLIGHT4 STM32G071 DEAD_TIME=35 COMP_MAP=213 SENS_MAP=0xA5 VOLT_MUL=1100 LED_WS2812 IO_PA6) add_target(LUMENIER1 GD32F350 DEAD_TIME=57 COMP_MAP=321 SENS_MAP=0xA3 VOLT_MUL=1100) add_target(LYI1 AT32F421 DEAD_TIME=66 COMP_MAP=321 SENS_MAP=0xA3 VOLT_MUL=1100) add_target(NEUTRON1 AT32F421 DEAD_TIME=66 COMP_MAP=321) add_target(NEUTRON2 AT32F421 DEAD_TIME=66 COMP_MAP=321 INVERTED_HIGH) add_target(NEUTRON3 STM32L431 DEAD_TIME=44 COMP_MAP=123 SENS_MAP=0xA6A3 VOLT_MUL=2100 CURR_MUL=125 USE_COMP2 USE_OPAMP) add_target(ODDITYRC1 AT32F421 DEAD_TIME=66 COMP_MAP=123 SENS_MAP=0xA3BF VOLT_MUL=1100 CURR_MUL=40) add_target(PHOTONDRIVE1 STM32G431 DEAD_TIME=155 COMP_MAP=132 SENS_MAP=0xBFA6 VOLT_MUL=2236 CURR_MUL=63 TEMP_ADC2 TEMP_CHAN=14 TEMP_FUNC=NTC10K3455UP10K LED_WS2812 LED_STAT USE_PB1 USE_HSE=8 DUTY_SPUP=5 DUTY_RATE=20 PROT_TEMP=100) add_target(RHINO1 STM32F051 DEAD_TIME=26 COMP_MAP=321 SENS_MAP=0xA6 VOLT_MUL=1100) add_target(SEQURE1 STM32G071 DEAD_TIME=35 COMP_MAP=123 SENS_MAP=0xA6A4 VOLT_MUL=2100 CURR_MUL=30 BEC_MAP=0xCEF BEC_MIN=1 LED_WS2812 LED_STAT) add_target(SEQURE2 STM32G071 DEAD_TIME=50 COMP_MAP=123 SENS_MAP=0xA6A4 VOLT_MUL=2100 CURR_MUL=87 BEC_MAP=0xCEF BEC_MIN=1 LED_WS2812 LED_STAT ERPM_PIN=5) add_target(SKYSTARS1 GD32E230 DEAD_TIME=40 COMP_MAP=321 SENS_MAP=0xA3 VOLT_MUL=1100 LED_MAP=0xB5B3AF) add_target(TMOTOR1 STM32F051 DEAD_TIME=26 COMP_MAP=132 IO_PA2) add_target(TMOTOR2 STM32F051 DEAD_TIME=26 COMP_MAP=321) add_target(TMOTOR3 STM32G071 DEAD_TIME=35 COMP_MAP=231 IO_PA6) add_target(TMOTOR4 STM32G071 DEAD_TIME=35 COMP_MAP=123) ================================================ FILE: LICENSE ================================================ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ================================================ FILE: README.md ================================================ ESCape32 ======== Firmware for 32-bit BLDC motor electronic speed controllers that aims for simplicity. It is designed to deliver smooth and efficient motor drive, fast transitions from a complete stop to full throttle, robust direction reversals, and maximum hardware support. Features -------- + Servo PWM, Oneshot125, automatic throttle calibration + DSHOT 300/600/1200, bidirectional DSHOT, extended telemetry + Analog/serial/iBUS/SBUS/SBUS2/CRSF input mode + KISS/iBUS/S.Port/CRSF telemetry + DSHOT 3D mode, turtle mode, beacon, LED, programming + Sine startup mode, brushed mode, hybrid mode (sensored/sensorless) + Proportional brake, adjustable drag brake + Temperature/voltage/current/stall protection + Variable PWM frequency, active freewheeling + Customizable startup music/sounds Installation ------------ The list of compatible ESCs can be found [here](https://github.com/neoxic/ESCape32/wiki/Targets). The latest release can be downloaded [here](https://github.com/neoxic/ESCape32/releases). Visit the [ESCape32 Wiki](https://github.com/neoxic/ESCape32/wiki) for more information. Dependencies ------------ + cmake + arm-none-eabi-gcc + arm-none-eabi-binutils + arm-none-eabi-newlib + libopencm3 + stlink Building from source -------------------- Use `LIBOPENCM3_DIR` to specify a path to LibOpenCM3 if it is not in the system root: ``` git clone https://github.com/libopencm3/libopencm3.git make -C libopencm3 TARGETS='stm32/f0 stm32/g0 stm32/g4 stm32/l4' cmake -B build -D LIBOPENCM3_DIR=libopencm3 ``` Use `CMAKE_INSTALL_PREFIX` to specify an alternative system root: ``` cmake -B build -D CMAKE_INSTALL_PREFIX=~/local ``` To build all targets, run: ``` cmake -B build cd build make ``` To flash a particular target using an ST-LINK programmer, run: ``` make flash- ``` Building on GitHub ------------------ + Fork the repository. + Go to _Actions_. + Run the _Build ESCape32_ workflow. ================================================ FILE: boot/CMakeLists.txt ================================================ add_target(BOOT1_PA2 STM32F0 IO_PA2) add_target(BOOT1_PB4 STM32F0) add_target(BOOT2_PA2 STM32G0 IO_PA2) add_target(BOOT2_PA6 STM32G0 IO_PA6) add_target(BOOT2_PB4 STM32G0) add_target(BOOT3_PA2 STM32F0 AT32F4 IO_PA2) add_target(BOOT3_PA2_FE STM32F0 AT32F4 IO_PA2 IO_RXTX FAST_EXIT) add_target(BOOT4_PA2 STM32G4) ================================================ FILE: boot/mcu/STM32F0/config.c ================================================ /* ** Copyright (C) Arseny Vakhrushev ** ** This firmware is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This firmware is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this firmware. If not, see . */ #include "common.h" void init(void) { RCC_AHBENR = RCC_AHBENR_CRCEN | RCC_AHBENR_GPIOAEN | RCC_AHBENR_GPIOBEN; RCC_APB2ENR = RCC_APB2ENR_TIM1EN; FLASH_ACR = FLASH_ACR_LATENCY_1WS | FLASH_ACR_PRFTEN; RCC_CFGR = RCC_CFGR_PLLMUL_MUL12; RCC_CR |= RCC_CR_PLLON; while (!(RCC_CR & RCC_CR_PLLRDY)); RCC_CFGR |= RCC_CFGR_SW_PLL; GPIOA_MODER = 0xebffffff; GPIOB_MODER = 0xffffffff; #ifdef IO_PA2 RCC_APB1ENR |= RCC_APB1ENR_USART2EN; GPIOA_AFRL |= 0x100; // A2 (USART2_TX) #ifdef IO_RXTX GPIOA_AFRH |= 0x10000000; // A15 (USART2_RX) #endif GPIOA_PUPDR |= 0x10; // A2 (pull-up) GPIOA_MODER &= ~0x10; // A2 (USART2_TX) #else RCC_APB1ENR |= RCC_APB1ENR_TIM3EN; GPIOB_AFRL |= 0x10000; // B4 (TIM3_CH1) GPIOB_PUPDR |= 0x100; // B4 (pull-up) GPIOB_MODER &= ~0x100; // B4 (TIM3_CH1) #endif } ================================================ FILE: boot/mcu/STM32F0/config.cmake ================================================ set(opts -mcpu=cortex-m0 -mthumb) set(libs opencm3_stm32f0) ================================================ FILE: boot/mcu/STM32F0/config.h ================================================ /* ** Copyright (C) Arseny Vakhrushev ** ** This firmware is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This firmware is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this firmware. If not, see . */ #pragma once #define CLK 48000000 #define PAGE_SIZE 1024 ================================================ FILE: boot/mcu/STM32F0/config.ld ================================================ MEMORY { rom (rx) : ORIGIN = 0x8000000, LENGTH = 4K ram (rwx) : ORIGIN = 0x20000000, LENGTH = 4K } ================================================ FILE: boot/mcu/STM32G0/config.c ================================================ /* ** Copyright (C) Arseny Vakhrushev ** ** This firmware is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This firmware is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this firmware. If not, see . */ #include "common.h" void init(void) { RCC_IOPENR = 0x3; // GPIOAEN=1, GPIOBEN=1 RCC_AHBENR = RCC_AHBENR_FLASHEN | RCC_AHBENR_CRCEN; RCC_APBENR2 = RCC_APBENR2_TIM1EN; FLASH_ACR = FLASH_ACR_LATENCY_2WS | FLASH_ACR_PRFTEN | FLASH_ACR_ICEN | FLASH_ACR_DBG_SWEN; RCC_PLLCFGR = RCC_PLLCFGR_PLLSRC_HSI16 | 8 << RCC_PLLCFGR_PLLN_SHIFT | RCC_PLLCFGR_PLLREN | 1 << RCC_PLLCFGR_PLLR_SHIFT; RCC_CR |= RCC_CR_PLLON; while (!(RCC_CR & RCC_CR_PLLRDY)); RCC_CFGR = RCC_CFGR_SW_PLLRCLK; #ifdef IO_PA2 RCC_APBENR1 |= RCC_APBENR1_USART2EN; GPIOA_AFRL |= 0x100; // A2 (USART2_TX) #ifdef IO_RXTX GPIOA_AFRH |= 0x10000000; // A15 (USART2_RX) #endif GPIOA_PUPDR |= 0x10; // A2 (pull-up) GPIOA_MODER &= ~0x10; // A2 (USART2_TX) #else RCC_APBENR1 |= RCC_APBENR1_TIM3EN; #ifdef IO_PA6 GPIOA_AFRL |= 0x1000000; // A6 (TIM3_CH1) GPIOA_PUPDR |= 0x1000; // A6 (pull-up) GPIOA_MODER &= ~0x1000; // A6 (TIM3_CH1) #else GPIOB_AFRL |= 0x10000; // B4 (TIM3_CH1) GPIOB_PUPDR |= 0x100; // B4 (pull-up) GPIOB_MODER &= ~0x100; // B4 (TIM3_CH1) #endif #endif } ================================================ FILE: boot/mcu/STM32G0/config.cmake ================================================ set(opts -mcpu=cortex-m0plus -mthumb) set(libs opencm3_stm32g0) ================================================ FILE: boot/mcu/STM32G0/config.h ================================================ /* ** Copyright (C) Arseny Vakhrushev ** ** This firmware is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This firmware is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this firmware. If not, see . */ #pragma once #define CLK 64000000 #define PAGE_SIZE 2048 ================================================ FILE: boot/mcu/STM32G0/config.ld ================================================ MEMORY { rom (rx) : ORIGIN = 0x8000000, LENGTH = 4K ram (rwx) : ORIGIN = 0x20000000, LENGTH = 4K } ================================================ FILE: boot/mcu/STM32G4/config.c ================================================ /* ** Copyright (C) Arseny Vakhrushev ** ** This firmware is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This firmware is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this firmware. If not, see . */ #include "common.h" void init(void) { RCC_AHB1ENR = RCC_AHB1ENR_FLASHEN | RCC_AHB1ENR_CRCEN; RCC_AHB2ENR = RCC_AHB2ENR_GPIOAEN | RCC_AHB2ENR_GPIOBEN; RCC_APB1ENR1 = RCC_APB1ENR1_USART2EN; RCC_APB2ENR = RCC_APB2ENR_TIM1EN; FLASH_ACR = 4 | FLASH_ACR_PRFTEN | FLASH_ACR_ICEN | FLASH_ACR_DCEN | FLASH_ACR_DBG_SWEN; // LATENCY=4 RCC_PLLCFGR = RCC_PLLCFGR_PLLSRC_HSI16 | 10 << RCC_PLLCFGR_PLLN_SHIFT | RCC_PLLCFGR_PLLREN; RCC_CR |= RCC_CR_HSION | RCC_CR_PLLON; while (!(RCC_CR & (RCC_CR_HSIRDY | RCC_CR_PLLRDY))); RCC_CFGR = RCC_CFGR_SWx_PLL; GPIOA_AFRL = 0x00000700; // A2 (USART2_TX) #ifdef IO_RXTX GPIOA_AFRH = 0x70000000; // A15 (USART2_RX) #endif GPIOA_PUPDR = 0x24000010; // A2 (pull-up) GPIOB_PUPDR = 0x00000000; GPIOA_MODER = 0xebffffef; // A2 (USART2_TX) GPIOB_MODER = 0xffffffff; } ================================================ FILE: boot/mcu/STM32G4/config.cmake ================================================ set(opts -mcpu=cortex-m4 -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16) set(libs opencm3_stm32g4) ================================================ FILE: boot/mcu/STM32G4/config.h ================================================ /* ** Copyright (C) Arseny Vakhrushev ** ** This firmware is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This firmware is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this firmware. If not, see . */ #pragma once #define CLK 80000000 #define PAGE_SIZE 2048 #define IO_PA2 ================================================ FILE: boot/mcu/STM32G4/config.ld ================================================ MEMORY { rom (rx) : ORIGIN = 0x8000000, LENGTH = 4K ram (rwx) : ORIGIN = 0x20000000, LENGTH = 4K } ================================================ FILE: boot/mcu/common.ld ================================================ _rom = ORIGIN(rom); _rom_end = ORIGIN(rom) + LENGTH(rom); _ram_end = ORIGIN(ram) + LENGTH(ram); INCLUDE cortex-m-generic.ld ================================================ FILE: boot/src/common.h ================================================ /* ** Copyright (C) Arseny Vakhrushev ** ** This firmware is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This firmware is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this firmware. If not, see . */ #pragma once #include #include #include #include #ifdef AT32F4 #include #include #else #include #endif #include #include #include #include "config.h" #define CLK_CNT(rate) ((CLK + ((rate) >> 1)) / (rate)) #ifdef IO_PA2 #define IO_PIN 1 #elif defined IO_PA6 #define IO_PIN 2 #define TIM3_IDR (GPIOA_IDR & 0x40) // A6 #else #define IO_PIN 3 #define TIM3_IDR (GPIOB_IDR & 0x10) // B4 #endif extern char _rom[], _rom_end[], _ram_end[]; // Linker exports void init(void); void initio(void); int recvbuf(char *buf, int len); void sendbuf(const char *buf, int len); int recvval(void); void sendval(int val); int recvdata(char *buf); void senddata(const char *buf, int len); uint32_t crc32(const char *buf, int len); int write(char *dst, const char *src, int len) __attribute__((__long_call__)); void update(char *dst, const char *src, int len) __attribute__((__long_call__)); void setwrp(int type); ================================================ FILE: boot/src/io.c ================================================ /* ** Copyright (C) Arseny Vakhrushev ** ** This firmware is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This firmware is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this firmware. If not, see . */ #include "common.h" #ifdef AT32F4 #define USART2_TDR USART2_DR #define USART2_RDR USART2_DR #define USART2_ISR USART2_SR #define USART_ISR_RXNE USART_SR_RXNE #define USART_ISR_TXE USART_SR_TXE #define USART_ISR_TC USART_SR_TC #define USART_ISR_FE USART_SR_FE #define USART_ISR_NF USART_SR_NE #endif void initio(void) { #ifdef IO_PA2 #ifdef IO_RXTX GPIOA_PUPDR |= 0x80000000; // A15 (pull-down) GPIOA_MODER &= ~0x40000000; // A15 (USART2_RX) TIM1_ARR = CLK_CNT(20000) - 1; TIM1_CR1 = TIM_CR1_CEN | TIM_CR1_OPM; while (TIM1_CR1 & TIM_CR1_CEN) { // Wait for 50us high level on A15 if (!(GPIOA_IDR & 0x8000)) { // A15 low USART2_CR3 = USART_CR3_HDSEL; break; } } #else USART2_CR3 = USART_CR3_HDSEL; #endif USART2_BRR = CLK_CNT(38400); USART2_CR1 = USART_CR1_UE | USART_CR1_TE | USART_CR1_RE; #else TIM3_CCMR1 = TIM_CCMR1_OC1PE | TIM_CCMR1_OC1M_PWM2 | TIM_CCMR1_CC2S_IN_TI1 | TIM_CCMR1_IC2F_CK_INT_N_8; TIM3_SMCR = TIM_SMCR_SMS_RM | TIM_SMCR_TS_TI1F_ED; // Reset on any edge on TI1 TIM3_CCER = TIM_CCER_CC2E | TIM_CCER_CC2P; // IC2 on falling edge on TI1 TIM3_ARR = CLK_CNT(38400) - 1; // Bit time TIM3_CCR1 = CLK_CNT(76800); // Half-bit time TIM3_EGR = TIM_EGR_UG; TIM3_CR1 = TIM_CR1_CEN; #endif TIM1_PSC = CLK_CNT(10000) - 1; // 0.1ms resolution TIM1_ARR = 4999; // 500ms I/O timeout TIM1_CR1 = TIM_CR1_URS; TIM1_EGR = TIM_EGR_UG; TIM1_CR1 = TIM_CR1_CEN | TIM_CR1_URS; } #ifdef IO_PA2 int recvbuf(char *buf, int len) { TIM1_EGR = TIM_EGR_UG; TIM1_SR = ~TIM_SR_UIF; for (int i = 0; i < len; ++i) { while (!(USART2_ISR & USART_ISR_RXNE)) { if (TIM1_SR & TIM_SR_UIF) return 0; // Timeout } if (USART2_ISR & (USART_ISR_FE | USART_ISR_NF)) { // Data error #ifdef AT32F4 USART2_DR; // Clear flags #else USART2_ICR = USART_ICR_FECF | USART_ICR_NCF; #endif return 0; } buf[i] = USART2_RDR; // Clear RXNE TIM1_EGR = TIM_EGR_UG; } return 1; } void sendbuf(const char *buf, int len) { USART2_CR1 = USART_CR1_UE | USART_CR1_TE; for (int i = 0; i < len; ++i) { while (!(USART2_ISR & USART_ISR_TXE)); USART2_TDR = buf[i]; // Clear TXE+TC } while (!(USART2_ISR & USART_ISR_TC)); USART2_CR1 = USART_CR1_UE | USART_CR1_TE | USART_CR1_RE; } #else int recvbuf(char *buf, int len) { TIM1_EGR = TIM_EGR_UG; TIM1_SR = ~TIM_SR_UIF; int n = 10, b = 0; for (;;) { int sr = TIM3_SR; if (sr & TIM_SR_CC1IF) { // Half-bit time TIM3_SR = ~TIM_SR_CC1IF; if (n == 10) continue; int p = TIM3_IDR; // Signal level if (!n++) { // Start bit if (p) return 0; // Data error b = 0; continue; } if (n < 10) { // Data bit b >>= 1; if (p) b |= 0x80; continue; } if (!p) return 0; // Data error *buf++ = b; if (!--len) return 1; TIM1_EGR = TIM_EGR_UG; } if (sr & TIM_SR_CC2IF) { // Falling edge TIM3_SR = ~TIM_SR_CC2IF; if (n == 10) n = 0; } if (TIM1_SR & TIM_SR_UIF) return 0; // Timeout } } void sendbuf(const char *buf, int len) { TIM3_SMCR = 0; TIM3_CCR1 = 0; // Preload high level TIM3_EGR = TIM_EGR_UG; // Update registers and trigger UEV TIM3_CCER = TIM_CCER_CC1E; // Enable output int n = 0, b = 0; for (;;) { if (!(TIM3_SR & TIM_SR_UIF)) continue; TIM3_SR = ~TIM_SR_UIF; if (!len) break; int p = -1; if (!n++) b = *buf++; // Start bit else if (n < 10) { // Data bit if (b & 1) p = 0; b >>= 1; } else { // Stop bit n = 0; p = 0; --len; } TIM3_CCR1 = p; } TIM3_SMCR = TIM_SMCR_SMS_RM | TIM_SMCR_TS_TI1F_ED; // Reset on any edge on TI1 TIM3_CCER = TIM_CCER_CC2E | TIM_CCER_CC2P; // IC2 on falling edge on TI1 TIM3_CCR1 = CLK_CNT(76800); // Half-bit time TIM3_EGR = TIM_EGR_UG; } #endif int recvval(void) { char buf[2]; return recvbuf(buf, 2) && (buf[0] ^ buf[1]) == 0xff ? buf[0] : -1; } void sendval(int val) { char buf[2] = {val, ~val}; sendbuf(buf, 2); } int recvdata(char *buf) { int cnt = recvval(); if (cnt == -1) return -1; int len = (cnt + 1) << 2; uint32_t crc; return recvbuf(buf, len) && recvbuf((char *)&crc, 4) && crc32(buf, len) == crc ? len : -1; } void senddata(const char *buf, int len) { uint32_t crc = crc32(buf, len); sendval((len >> 2) - 1); sendbuf(buf, len); sendbuf((char *)&crc, 4); } ================================================ FILE: boot/src/main.c ================================================ /* ** Copyright (C) Arseny Vakhrushev ** ** This firmware is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This firmware is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this firmware. If not, see . */ #include "common.h" #define REVISION 4 #define CMD_PROBE 0 #define CMD_INFO 1 #define CMD_READ 2 #define CMD_WRITE 3 #define CMD_UPDATE 4 #define CMD_SETWRP 5 #define RES_OK 0 #define RES_ERROR 1 void main(void) { init(); initio(); if (RCC_CSR & (RCC_CSR_SFTRSTF | RCC_CSR_OBLRSTF)) { // Reboot RCC_CSR = RCC_CSR_RMVF; // Clear reset flags sendval(RES_OK); // ACK after reboot } #ifdef FAST_EXIT else goto done; #endif for (;;) { switch (recvval()) { case CMD_PROBE: // Probe bootloader sendval(RES_OK); break; case CMD_INFO: { // Get info int mcu = DBGMCU_IDCODE; char buf[32] = {REVISION, IO_PIN, mcu, mcu >> 8, mcu >> 16, mcu >> 24}; senddata(buf, sizeof buf); break; } case CMD_READ: { // Read block int num = recvval(); if (num == -1) goto done; int cnt = recvval(); if (cnt == -1) goto done; senddata(_rom_end + (num << 10), (cnt + 1) << 2); break; } case CMD_WRITE: { // Write block int num = recvval(); if (num == -1) goto done; char buf[1024]; int len = recvdata(buf); if (len == -1) goto done; sendval(write(_rom_end + (num << 10), buf, len) ? RES_OK : RES_ERROR); break; } case CMD_UPDATE: { // Update bootloader char *buf = _ram_end; // Use upper SRAM as buffer int pos = 0; for (int i = 0, n = (_rom_end - _rom) >> 10; i < n; ++i) { int len = recvdata(buf + pos); if (len == -1) goto done; sendval(RES_OK); pos += len; if (len < 1024) break; // Last block } update(_rom, buf, pos); sendval(RES_ERROR); break; } case CMD_SETWRP: // Set write protection switch (recvval()) { case 0x33: // Off setwrp(0); break; case 0x44: // Bootloader setwrp(1); break; case 0x55: // Full setwrp(2); break; } sendval(RES_ERROR); break; default: // Pass control to application done: if (*(uint16_t *)_rom_end != 0x32ea) break; __asm__("msr msp, %0" :: "g" (*(uint32_t *)(_rom_end + PAGE_SIZE))); // Initialize stack pointer (*(void (**)(void))(_rom_end + PAGE_SIZE + 4))(); // Jump to application for (;;); // Never return } } } ================================================ FILE: boot/src/util.c ================================================ /* ** Copyright (C) Arseny Vakhrushev ** ** This firmware is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This firmware is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this firmware. If not, see . */ #include "common.h" #ifndef FLASH_CR_STRT #define FLASH_CR_STRT FLASH_CR_START #endif uint32_t crc32(const char *buf, int len) { uint32_t *val = (uint32_t *)buf; len >>= 2; CRC_CR = CRC_CR_RESET | CRC_CR_REV_IN_WORD | CRC_CR_REV_OUT; while (len--) CRC_DR = *val++; return ~CRC_DR; } __attribute__((__section__(".ramtext"))) int write(char *dst, const char *src, int len) { FLASH_KEYR = FLASH_KEYR_KEY1; FLASH_KEYR = FLASH_KEYR_KEY2; FLASH_SR = -1; // Clear errors FLASH_CR = FLASH_CR_PER; uint32_t addr = ((uint32_t)dst + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1); uint32_t last = ((uint32_t)dst + len - 1) & ~(PAGE_SIZE - 1); while (addr <= last) { // Erase pages MMIO32(addr); // Invalid address triggers a hard fault #ifdef STM32F0 FLASH_AR = addr; FLASH_CR = FLASH_CR_PER | FLASH_CR_STRT; #else FLASH_CR = FLASH_CR_PER | FLASH_CR_STRT | ((addr - (uint32_t)_rom) / PAGE_SIZE) << FLASH_CR_PNB_SHIFT; #endif while (FLASH_SR & FLASH_SR_BSY); addr += PAGE_SIZE; } FLASH_CR = FLASH_CR_PG; #ifdef STM32F0 for (int pos = 0; pos < len; pos += 2) { // Write half-words *(uint16_t *)(dst + pos) = *(uint16_t *)(src + pos); while (FLASH_SR & FLASH_SR_BSY); } #else for (int pos = 0; pos < len; pos += 8) { // Write double words *(uint32_t *)(dst + pos) = *(uint32_t *)(src + pos); *(uint32_t *)(dst + pos + 4) = *(uint32_t *)(src + pos + 4); while (FLASH_SR & FLASH_SR_BSY); } #endif FLASH_CR = FLASH_CR_LOCK; #ifdef STM32F0 if (FLASH_SR & (FLASH_SR_PGERR | FLASH_SR_WRPRTERR)) return 0; #else if (FLASH_SR & (FLASH_SR_PROGERR | FLASH_SR_WRPERR)) return 0; #endif for (int pos = 0; pos < len; pos += 4) { // Check written data if (*(uint32_t *)(dst + pos) != *(uint32_t *)(src + pos)) return 0; } return 1; } __attribute__((__section__(".ramtext"))) void update(char *dst, const char *src, int len) { if (!write(dst, src, len)) return; SCB_AIRCR = SCB_AIRCR_VECTKEY | SCB_AIRCR_SYSRESETREQ; for (;;); // Never return } void setwrp(int type) { // 0 - off, 1 - bootloader, 2 - full FLASH_KEYR = FLASH_KEYR_KEY1; FLASH_KEYR = FLASH_KEYR_KEY2; FLASH_OPTKEYR = FLASH_OPTKEYR_KEY1; FLASH_OPTKEYR = FLASH_OPTKEYR_KEY2; FLASH_SR = -1; // Clear errors #ifdef STM32F0 char opts[6] = {FLASH_OPTION_BYTE_0, FLASH_OPTION_BYTE_1, FLASH_OPTION_BYTE_2, FLASH_OPTION_BYTE_3, 0xff, 0xff}; if (type == 1) opts[4] = ~((1 << ((_rom_end - _rom + 4095) >> 12)) - 1); else if (type == 2) opts[4] = opts[5] = 0; FLASH_CR = FLASH_CR_OPTWRE | FLASH_CR_OPTER; FLASH_CR = FLASH_CR_OPTWRE | FLASH_CR_OPTER | FLASH_CR_STRT; while (FLASH_SR & FLASH_SR_BSY); FLASH_CR = FLASH_CR_OPTWRE | FLASH_CR_OPTPG; for (int i = 0; i < 6; ++i) { FLASH_OPTION_BYTE(i) = opts[i]; while (FLASH_SR & FLASH_SR_BSY); } if (FLASH_SR & (FLASH_SR_PGERR | FLASH_SR_WRPRTERR)) return; #else FLASH_WRP1AR = type == 1 ? (((_rom_end - _rom + 2047) >> 11) - 1) << 16: type == 2 ? 0xff0000 : 0xff; FLASH_CR = FLASH_CR_OPTSTRT; while (FLASH_SR & FLASH_SR_BSY); if (FLASH_SR & (FLASH_SR_PROGERR | FLASH_SR_WRPERR)) return; #endif #ifdef AT32F4 SCB_AIRCR = SCB_AIRCR_VECTKEY | SCB_AIRCR_SYSRESETREQ; #else FLASH_CR = FLASH_CR_OBL_LAUNCH; #endif for (;;); // Never return } ================================================ FILE: mcu/AT32F421/config.c ================================================ /* ** Copyright (C) Arseny Vakhrushev ** ** This firmware is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This firmware is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this firmware. If not, see . */ #include #include #include "common.h" #if SENS_MAP == 0xA3 // A3 (volt) #define SENS_CHAN 0x3 #elif SENS_MAP == 0xA6 // A6 (volt) #define SENS_CHAN 0x6 #elif SENS_MAP == 0xA3A6 // A3 (volt), A6 (curr) #define SENS_CHAN 0x66 #elif SENS_MAP == 0xA3BF // A3 (volt), B15 (curr) #define SENS_CHAN 0x6e #elif SENS_MAP == 0xA6A3 // A6 (volt), A3 (curr) #define SENS_CHAN 0xc3 #elif SENS_MAP == 0xB2A6A3 // B2 (temp), A6 (volt), A3 (curr) #define SENS_CHAN 0x28c3 #endif #ifndef ANALOG_CHAN #define ANALOG_CHAN 0x2 // ADC_IN2 (A2) #endif #ifndef TEMP_CHAN #define TEMP_CHAN 0x10 // ADC_IN16 (temp) #define TEMP_FUNC(x) ((((x) - 1280) * 3800 >> 12) + 100) #endif #define ADC1_BASE ADC_BASE #define COMP_CSR MMIO32(SYSCFG_COMP_BASE + 0x1c) static char len, ain; static uint16_t buf[6]; #ifdef LED_WS2812 static uint16_t led[5]; #endif void init(void) { RCC_APB2RSTR = -1; RCC_APB1RSTR = -1; RCC_APB2RSTR = 0; RCC_APB1RSTR = 0; RCC_AHBENR = RCC_AHBENR_DMAEN | RCC_AHBENR_SRAMEN | RCC_AHBENR_GPIOAEN | RCC_AHBENR_GPIOBEN; RCC_APB2ENR = RCC_APB2ENR_SYSCFGCOMPEN | RCC_APB2ENR_ADCEN | RCC_APB2ENR_TIM1EN | RCC_APB2ENR_USART1EN; RCC_APB1ENR = RCC_APB1ENR_TIM3EN | RCC_APB1ENR_TIM6EN | RCC_APB1ENR_WWDGEN; SCB_VTOR = (uint32_t)_rom; // Set vector table address RCC_CFGR &= ~RCC_CFGR_SW_PLL; while (RCC_CFGR & RCC_CFGR_SWS_PLL); RCC_CR &= ~RCC_CR_PLLON; while (RCC_CR & RCC_CR_PLLRDY); FLASH_ACR = 0x13; // LATENCY=3WS, PFTEN RCC_CFGR = 0x2034c000; // ADCDIV=011 (PCLK/8), PLLMUL=011101 (x30) RCC_CR |= RCC_CR_PLLON; while (!(RCC_CR & RCC_CR_PLLRDY)); RCC_CFGR |= RCC_CFGR_SW_PLL; // Default GPIO state - analog input GPIOA_AFRL = 0x20000000; // A7 (TIM1_CH1N) GPIOA_AFRH = 0x00000222; // A8 (TIM1_CH1), A9 (TIM1_CH2), A10 (TIM1_CH3) GPIOB_AFRL = 0x00000022; // B0 (TIM1_CH2N), B1 (TIM1_CH3N) GPIOB_PUPDR = 0x00001000; // B6 (pull-up) GPIOA_MODER = 0xebeabfff; // A7 (TIM1_CH1N), A8 (TIM1_CH1), A9 (TIM1_CH2), A10 (TIM1_CH3) GPIOB_MODER = 0xffffeffa; // B0 (TIM1_CH2N), B1 (TIM1_CH3N), B6 (USART1_TX) #ifndef ANALOG RCC_APB2ENR |= RCC_APB2ENR_TIM15EN; GPIOA_PUPDR |= 0x10; // A2 (pull-up) GPIOA_MODER &= ~0x10; // A2 (TIM15_CH1) nvic_set_priority(NVIC_TIM15_IRQ, 0x40); #ifdef IO_AUX RCC_APB2ENR |= RCC_APB2ENR_TIM16EN; GPIOB_AFRH |= 0x2; // B8 (TIM16_CH1) GPIOB_PUPDR |= 0x10000; // B8 (pull-up) GPIOB_MODER &= ~0x10000; // B8 (TIM16_CH1) nvic_set_priority(NVIC_TIM16_IRQ, 0x40); #endif #endif nvic_set_priority(NVIC_USART1_IRQ, 0x80); nvic_set_priority(NVIC_USART2_IRQ, 0x40); nvic_set_priority(NVIC_DMA1_CHANNEL1_IRQ, 0x80); // ADC, WS2812 nvic_set_priority(NVIC_DMA1_CHANNEL2_3_DMA2_CHANNEL1_2_IRQ, 0x80); // USART1_TX nvic_set_priority(NVIC_DMA1_CHANNEL4_7_DMA2_CHANNEL3_5_IRQ, 0x40); // TIM15 or USART2_RX nvic_enable_irq(NVIC_TIM1_BRK_UP_TRG_COM_IRQ); nvic_enable_irq(NVIC_TIM1_CC_IRQ); nvic_enable_irq(NVIC_TIM3_IRQ); nvic_enable_irq(NVIC_TIM15_IRQ); nvic_enable_irq(NVIC_TIM16_IRQ); nvic_enable_irq(NVIC_USART1_IRQ); nvic_enable_irq(NVIC_USART2_IRQ); nvic_enable_irq(NVIC_DMA1_CHANNEL1_IRQ); nvic_enable_irq(NVIC_DMA1_CHANNEL2_3_DMA2_CHANNEL1_2_IRQ); nvic_enable_irq(NVIC_DMA1_CHANNEL4_7_DMA2_CHANNEL3_5_IRQ); TIM1_DIER = TIM_DIER_CC1IE; // ADC trigger TIM1_SMCR = TIM_SMCR_TS_ITR2; // TRGI=TIM3 TIM3_CR2 = TIM_CR2_MMS_COMPARE_OC3REF; // TRGO=OC3REF TIM3_CCMR1 = TIM_CCMR1_CC1S_IN_TI1; TIM3_CCMR2 = TIM_CCMR2_OC3PE | TIM_CCMR2_OC3M_PWM2; // Inverted PWM on OC3 TIM3_CCER = TIM_CCER_CC1E; // IC1 on rising edge on TI1 (COMP_OUT) ADC1_CR2 = ADC_CR2_ADON | ADC_CR2_TSVREFE; TIM6_ARR = CLK_MHZ * 3 - 1; TIM6_CR1 = TIM_CR1_CEN | TIM_CR1_OPM; while (TIM6_CR1 & TIM_CR1_CEN); // Wait for 3us (RM 18.4.2.1) ADC1_CR2 |= ADC_CR2_CAL; while (ADC1_CR2 & ADC_CR2_CAL); ADC1_CR1 = ADC_CR1_SCAN; ADC1_SMPR1 = -1; // Sampling time ~16us @ PCLK/8=15Mhz ADC1_SMPR2 = -1; ADC1_SQR3 = SENS_CHAN; len = SENS_CNT; if (IO_ANALOG) { ADC1_SQR3 |= ANALOG_CHAN << (len++ * 5); ain = 1; } ADC1_SQR3 |= (TEMP_CHAN | 0x220) << (len * 5); // ADC_IN17 (vref) len += 2; ADC1_SQR1 = (len - 1) << ADC_SQR1_L_LSB; #ifndef LED_WS2812 DMA1_CPAR(1) = (uint32_t)&ADC1_DR; DMA1_CMAR(1) = (uint32_t)buf; #endif } #ifdef LED_WS2812 void initled(void) { RCC_APB2ENR |= RCC_APB2ENR_TIM17EN; GPIOB_AFRL |= 0x20000000; // B7 (TIM17_CH1N) GPIOB_MODER &= ~0x4000; // B7 (TIM17_CH1N) } void ledctl(int x) { static int y = -1; if (DMA1_CCR(1) & DMA_CCR_EN) { // DMA channel is shared with ADC y = x; return; } if (x < 0 && (x = y) < 0) return; led[0] = x & 2 ? CLK_CNT(1250000) : CLK_CNT(2500000); // Green led[1] = x & 1 ? CLK_CNT(1250000) : CLK_CNT(2500000); // Red led[2] = x & 4 ? CLK_CNT(1250000) : CLK_CNT(2500000); // Blue led[3] = 0; led[4] = 0; DMA1_CPAR(1) = (uint32_t)&TIM17_CCR1; DMA1_CMAR(1) = (uint32_t)led; DMA1_CNDTR(1) = 5; DMA1_CCR(1) = DMA_CCR_EN | DMA_CCR_TCIE | DMA_CCR_DIR | DMA_CCR_MINC | DMA_CCR_PSIZE_16BIT | DMA_CCR_MSIZE_16BIT; TIM17_BDTR = TIM_BDTR_MOE; TIM17_CCMR1 = TIM_CCMR1_OC1PE | TIM_CCMR1_OC1M_PWM1; TIM17_CCER = TIM_CCER_CC1NE; TIM17_DIER = TIM_DIER_CC1DE; TIM17_CR2 = TIM_CR2_CCDS; // CC1 DMA request on UEV TIM17_ARR = CLK_CNT(800000) - 1; TIM17_RCR = 7; TIM17_EGR = TIM_EGR_UG; TIM17_CR1 = TIM_CR1_CEN; y = -1; } #endif void hsictl(int x) { int cr = RCC_CR; int tv = (cr & 0xfc) >> 2; // 6 bits RCC_CR = (cr & ~0xfc) | clamp(tv + x, 0, 0x3f) << 2; } void compctl(int x) { int cr = 0; switch (x & 3) { case COMP_IN1: cr = 0x418e1; // A1>A0 break; case COMP_IN2: cr = 0x418c1; // A1>A4 break; case COMP_IN3: cr = 0x418d1; // A1>A5 break; } if (x & 4) cr |= 0x8000; // Change polarity COMP_CSR = cr; } void io_serial(void) { RCC_APB2RSTR = RCC_APB2RSTR_TIM15RST; RCC_APB2RSTR = 0; nvic_clear_pending_irq(NVIC_TIM15_IRQ); RCC_APB1ENR |= RCC_APB1ENR_USART2EN; GPIOA_AFRL |= 0x100; // A2 (USART2_TX) #ifdef IO_RXTX GPIOA_AFRH |= 0x10000000; // A15 (USART2_RX) #endif } void io_analog(void) { RCC_APB2RSTR = RCC_APB2RSTR_TIM15RST; RCC_APB2RSTR = 0; nvic_clear_pending_irq(NVIC_TIM15_IRQ); GPIOA_PUPDR &= ~0x30; // A2 (no pull-up/pull-down) GPIOA_MODER |= 0x30; // A2 (analog) } void adctrig(void) { if (DMA1_CCR(1) & DMA_CCR_EN) return; #ifdef LED_WS2812 DMA1_CPAR(1) = (uint32_t)&ADC1_DR; DMA1_CMAR(1) = (uint32_t)buf; #endif DMA1_CNDTR(1) = len; DMA1_CCR(1) = DMA_CCR_EN | DMA_CCR_TCIE | DMA_CCR_MINC | DMA_CCR_PSIZE_16BIT | DMA_CCR_MSIZE_16BIT; ADC1_CR2 = ADC_CR2_ADON | ADC_CR2_TSVREFE | ADC_CR2_DMA | ADC_CR2_EXTTRIG | ADC_CR2_EXTSEL_SWSTART; } void tim1_cc_isr(void) { TIM1_SR = ~TIM_SR_CC1IF; if (!(ADC1_CR2 & ADC_CR2_DMA)) return; ADC1_CR2 = ADC_CR2_ADON | ADC_CR2_TSVREFE | ADC_CR2_DMA | ADC_CR2_EXTTRIG | ADC_CR2_EXTSEL_SWSTART | ADC_CR2_SWSTART; } void dma1_channel1_isr(void) { DMA1_IFCR = DMA_IFCR_CTCIF(1); #ifdef LED_WS2812 if (DMA1_CCR(1) & DMA_CCR_DIR) { DMA1_CCR(1) = 0; RCC_APB2RSTR = RCC_APB2RSTR_TIM17RST; // Errata 1.5.1 RCC_APB2RSTR = 0; return; } #endif DMA1_CCR(1) = 0; ADC1_CR2 = ADC_CR2_ADON | ADC_CR2_TSVREFE; #ifdef LED_WS2812 ledctl(-1); #endif int i = 0, u = 0, v = 0, c = 0, a = 0; #if SENS_CNT >= 2 c = buf[i++]; #endif #if SENS_CNT >= 1 v = buf[i++]; #endif #if SENS_CNT >= 3 u = buf[i++]; #endif if (ain) a = buf[i++]; int r = 4914000 / buf[i + 1]; adcdata(TEMP_FUNC(buf[i] * r >> 12), u * r >> 12, v * r >> 12, c * r >> 12, a * r >> 12); } ================================================ FILE: mcu/AT32F421/config.cmake ================================================ set(opts -mcpu=cortex-m4 -mthumb) set(libs opencm3_stm32f0) set(defs STM32F0 AT32F4) ================================================ FILE: mcu/AT32F421/config.h ================================================ /* ** Copyright (C) Arseny Vakhrushev ** ** This firmware is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This firmware is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this firmware. If not, see . */ #pragma once #define CLK 120000000 #define IO_PA2 #define IFTIM TIM3 #define IFTIM_XRES 0 #define IFTIM_ICFL 128 #define IFTIM_ICMR TIM3_CCMR1 #define IFTIM_ICM1 (TIM_CCMR1_CC1S_IN_TI1 | TIM_CCMR1_IC1F_DTF_DIV_16_N_8) #define IFTIM_ICM2 (TIM_CCMR1_CC1S_IN_TI1 | TIM_CCMR1_IC1F_DTF_DIV_8_N_8) #define IFTIM_ICM3 (TIM_CCMR1_CC1S_IN_TI1 | TIM_CCMR1_IC1F_DTF_DIV_4_N_8) #define IFTIM_ICIE TIM_DIER_CC1IE #define IFTIM_ICR TIM3_CCR1 #define IFTIM_OCR TIM3_CCR3 #define iftim_isr tim3_isr #define IOTIM TIM15 #define IOTIM_IDR (GPIOA_IDR & 0x4) // A2 #define IOTIM_DMA 5 #define iotim_isr tim15_isr #define iodma_isr dma1_channel4_7_dma2_channel3_5_isr #define IOTIM2 TIM16 #define iotim2_isr tim16_isr #define USART1_RX_DMA 3 #define USART1_TX_DMA 2 #define usart1_tx_dma_isr dma1_channel2_3_dma2_channel1_2_isr #define USART2_RX_DMA 5 #define USART2_TX_DMA 4 #define tim1_com_isr tim1_brk_up_trg_com_isr ================================================ FILE: mcu/AT32F421/config.ld ================================================ MEMORY { boot (rx) : ORIGIN = 0x8000000, LENGTH = 4K cfg (rx) : ORIGIN = ORIGIN(boot) + LENGTH(boot), LENGTH = 1K rom (rx) : ORIGIN = ORIGIN(cfg) + LENGTH(cfg), LENGTH = 64K - LENGTH(boot) - LENGTH(cfg) ram (rwx) : ORIGIN = 0x20000000, LENGTH = 8K } ================================================ FILE: mcu/AT32F421/preset.c ================================================ /* ** Copyright (C) Arseny Vakhrushev ** ** This firmware is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This firmware is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this firmware. If not, see . */ #include "common.h" const uint16_t sinedata[] = { 400, 407, 414, 421, 428, 435, 442, 449, 456, 463, 469, 476, 483, 490, 497, 504, 510, 517, 524, 530, 537, 543, 550, 556, 563, 569, 575, 582, 588, 594, 600, 606, 612, 618, 624, 629, 635, 641, 646, 652, 657, 662, 668, 673, 678, 683, 688, 693, 697, 702, 706, 711, 715, 719, 724, 728, 732, 735, 739, 743, 746, 750, 753, 756, 760, 763, 765, 768, 771, 773, 776, 778, 780, 783, 785, 786, 788, 790, 791, 793, 794, 795, 796, 797, 798, 798, 799, 799, 800, 800, 800, 800, 800, 799, 799, 798, 798, 797, 796, 795, 794, 793, 791, 790, 788, 786, 785, 783, 780, 778, 776, 773, 771, 768, 765, 763, 760, 756, 753, 750, 746, 743, 739, 735, 732, 728, 724, 719, 715, 711, 706, 702, 697, 693, 688, 683, 678, 673, 668, 662, 657, 652, 646, 641, 635, 629, 624, 618, 612, 606, 600, 594, 588, 582, 575, 569, 563, 556, 550, 543, 537, 530, 524, 517, 510, 504, 497, 490, 483, 476, 469, 463, 456, 449, 442, 435, 428, 421, 414, 407, 400, 393, 386, 379, 372, 365, 358, 351, 344, 337, 331, 324, 317, 310, 303, 296, 290, 283, 276, 270, 263, 257, 250, 244, 237, 231, 225, 218, 212, 206, 200, 194, 188, 182, 176, 171, 165, 159, 154, 148, 143, 138, 132, 127, 122, 117, 112, 107, 103, 98, 94, 89, 85, 81, 76, 72, 68, 65, 61, 57, 54, 50, 47, 44, 40, 37, 35, 32, 29, 27, 24, 22, 20, 17, 15, 14, 12, 10, 9, 7, 6, 5, 4, 3, 2, 2, 1, 1, 0, 0, 0, 0, 0, 1, 1, 2, 2, 3, 4, 5, 6, 7, 9, 10, 12, 14, 15, 17, 20, 22, 24, 27, 29, 32, 35, 37, 40, 44, 47, 50, 54, 57, 61, 65, 68, 72, 76, 81, 85, 89, 94, 98, 103, 107, 112, 117, 122, 127, 132, 138, 143, 148, 154, 159, 165, 171, 176, 182, 188, 194, 200, 206, 212, 218, 225, 231, 237, 244, 250, 257, 263, 270, 276, 283, 290, 296, 303, 310, 317, 324, 331, 337, 344, 351, 358, 365, 372, 379, 386, 393, }; ================================================ FILE: mcu/GD32E230/config.c ================================================ /* ** Copyright (C) Arseny Vakhrushev ** ** This firmware is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This firmware is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this firmware. If not, see . */ #include #include #include "common.h" #if SENS_MAP == 0xA3 // A3 (volt) #define SENS_CHAN 0x3 #elif SENS_MAP == 0xA6 // A6 (volt) #define SENS_CHAN 0x6 #elif SENS_MAP == 0xA6A3 // A6 (volt), A3 (curr) #define SENS_CHAN 0xc3 #endif #ifndef ANALOG_CHAN #define ANALOG_CHAN 0x2 // ADC_IN2 (A2) #endif #ifndef TEMP_CHAN #define TEMP_CHAN 0x10 // ADC_IN16 (temp) #define TEMP_FUNC(x) (((1450 - (x)) * 3800 >> 12) + 100) #endif #define ADC1_BASE ADC_BASE #define COMP_CSR MMIO32(SYSCFG_COMP_BASE + 0x1c) static char len, ain; static uint16_t buf[6]; void init(void) { RCC_APB2RSTR = -1; RCC_APB1RSTR = -1; RCC_APB2RSTR = 0; RCC_APB1RSTR = 0; RCC_AHBENR = RCC_AHBENR_DMAEN | RCC_AHBENR_SRAMEN | RCC_AHBENR_GPIOAEN | RCC_AHBENR_GPIOBEN; RCC_APB2ENR = RCC_APB2ENR_SYSCFGCOMPEN | RCC_APB2ENR_ADCEN | RCC_APB2ENR_TIM1EN | RCC_APB2ENR_USART1EN; RCC_APB1ENR = RCC_APB1ENR_TIM3EN | RCC_APB1ENR_TIM6EN | RCC_APB1ENR_WWDGEN; SCB_VTOR = (uint32_t)_rom; // Set vector table address RCC_CFGR &= ~RCC_CFGR_SW_PLL; while (RCC_CFGR & RCC_CFGR_SWS_PLL); RCC_CR &= ~RCC_CR_PLLON; while (RCC_CR & RCC_CR_PLLRDY); FLASH_ACR = 0x12; // LATENCY=2WS, PFTEN RCC_CFGR = 0x8040000; // PLLMUL=10001 (x18) RCC_CR |= RCC_CR_PLLON; while (!(RCC_CR & RCC_CR_PLLRDY)); RCC_CFGR |= RCC_CFGR_SW_PLL; // Default GPIO state - analog input GPIOA_AFRL = 0x20000000; // A7 (TIM1_CH1N) GPIOA_AFRH = 0x00000222; // A8 (TIM1_CH1), A9 (TIM1_CH2), A10 (TIM1_CH3) GPIOB_AFRL = 0x00000022; // B0 (TIM1_CH2N), B1 (TIM1_CH3N) GPIOB_PUPDR = 0x00001000; // B6 (pull-up) GPIOA_MODER = 0xebeabfff; // A7 (TIM1_CH1N), A8 (TIM1_CH1), A9 (TIM1_CH2), A10 (TIM1_CH3) GPIOB_MODER = 0xffffeffa; // B0 (TIM1_CH2N), B1 (TIM1_CH3N), B6 (USART1_TX) #ifndef ANALOG RCC_APB2ENR |= RCC_APB2ENR_TIM15EN; GPIOA_PUPDR |= 0x10; // A2 (pull-up) GPIOA_MODER &= ~0x10; // A2 (TIM15_CH1) nvic_set_priority(NVIC_TIM15_IRQ, 0x40); #endif nvic_set_priority(NVIC_USART1_IRQ, 0x80); nvic_set_priority(NVIC_USART2_IRQ, 0x40); nvic_set_priority(NVIC_DMA1_CHANNEL1_IRQ, 0x80); // ADC nvic_set_priority(NVIC_DMA1_CHANNEL2_3_DMA2_CHANNEL1_2_IRQ, 0x80); // USART1_TX nvic_set_priority(NVIC_DMA1_CHANNEL4_7_DMA2_CHANNEL3_5_IRQ, 0x40); // TIM15 or USART2_RX nvic_enable_irq(NVIC_TIM1_BRK_UP_TRG_COM_IRQ); nvic_enable_irq(NVIC_TIM1_CC_IRQ); nvic_enable_irq(NVIC_TIM3_IRQ); nvic_enable_irq(NVIC_TIM15_IRQ); nvic_enable_irq(NVIC_USART1_IRQ); nvic_enable_irq(NVIC_USART2_IRQ); nvic_enable_irq(NVIC_DMA1_CHANNEL1_IRQ); nvic_enable_irq(NVIC_DMA1_CHANNEL2_3_DMA2_CHANNEL1_2_IRQ); nvic_enable_irq(NVIC_DMA1_CHANNEL4_7_DMA2_CHANNEL3_5_IRQ); TIM1_DIER = TIM_DIER_CC1IE; // ADC trigger TIM1_SMCR = TIM_SMCR_TS_ITR2; // TRGI=TIM3 TIM3_CR2 = TIM_CR2_MMS_COMPARE_OC3REF; // TRGO=OC3REF TIM3_CCMR1 = TIM_CCMR1_CC1S_IN_TI1; TIM3_CCMR2 = TIM_CCMR2_OC3PE | TIM_CCMR2_OC3M_PWM2; // Inverted PWM on OC3 TIM3_CCER = TIM_CCER_CC1E; // IC1 on rising edge on TI1 (COMP_OUT) RCC_CR2 |= RCC_CR2_HSI14ON; // Enable IRC28M while (!(RCC_CR2 & RCC_CR2_HSI14RDY)); ADC1_CR2 = ADC_CR2_ADON | ADC_CR2_TSVREFE; TIM6_ARR = CLK_MHZ - 1; TIM6_CR1 = TIM_CR1_CEN | TIM_CR1_OPM; while (TIM6_CR1 & TIM_CR1_CEN); // Wait for 1us (RM 10.4.1) ADC1_CR2 |= ADC_CR2_CAL; while (ADC1_CR2 & ADC_CR2_CAL); ADC1_CR1 = ADC_CR1_SCAN; ADC1_SMPR1 = -1; // Sampling time ~17us @ IRC28M/2=14Mhz ADC1_SMPR2 = -1; ADC1_SQR3 = SENS_CHAN; len = SENS_CNT; if (IO_ANALOG) { ADC1_SQR3 |= ANALOG_CHAN << (len++ * 5); ain = 1; } ADC1_SQR3 |= (TEMP_CHAN | 0x220) << (len * 5); // ADC_IN17 (vref) len += 2; ADC1_SQR1 = (len - 1) << ADC_SQR1_L_LSB; DMA1_CPAR(1) = (uint32_t)&ADC1_DR; DMA1_CMAR(1) = (uint32_t)buf; } void compctl(int x) { int cr = 0; switch (x & 3) { case COMP_IN1: cr = 0x61; // A1>A0 break; case COMP_IN2: cr = 0x41; // A1>A4 break; case COMP_IN3: cr = 0x51; // A1>A5 break; } if (x & 4) cr |= 0x800; // Change polarity COMP_CSR = cr; } void io_serial(void) { RCC_APB2RSTR = RCC_APB2RSTR_TIM15RST; RCC_APB2RSTR = 0; nvic_clear_pending_irq(NVIC_TIM15_IRQ); RCC_APB1ENR |= RCC_APB1ENR_USART2EN; GPIOA_AFRL |= 0x100; // A2 (USART2_TX) #ifdef IO_RXTX GPIOA_AFRH |= 0x10000000; // A15 (USART2_RX) #endif } void io_analog(void) { RCC_APB2RSTR = RCC_APB2RSTR_TIM15RST; RCC_APB2RSTR = 0; nvic_clear_pending_irq(NVIC_TIM15_IRQ); GPIOA_PUPDR &= ~0x30; // A2 (no pull-up/pull-down) GPIOA_MODER |= 0x30; // A2 (analog) } void adctrig(void) { if (DMA1_CCR(1) & DMA_CCR_EN) return; DMA1_CNDTR(1) = len; DMA1_CCR(1) = DMA_CCR_EN | DMA_CCR_TCIE | DMA_CCR_MINC | DMA_CCR_PSIZE_16BIT | DMA_CCR_MSIZE_16BIT; ADC1_CR2 = ADC_CR2_ADON | ADC_CR2_TSVREFE | ADC_CR2_DMA | ADC_CR2_EXTTRIG | ADC_CR2_EXTSEL_SWSTART; } void tim1_brk_up_trg_com_isr(void) { int sr = TIM1_SR; if (sr & TIM_SR_UIF) { TIM1_SR = ~TIM_SR_UIF; if (TIM1_CCR4) COMP_CSR &= ~0x700; // COMP_OUT off } if (sr & TIM_SR_COMIF) tim1_com_isr(); } void tim1_cc_isr(void) { int sr = TIM1_SR; if (sr & TIM_SR_CC4IF) { TIM1_SR = ~TIM_SR_CC4IF; COMP_CSR |= 0x600; // COMP_OUT=TIM3_IC1 } if (!(sr & TIM_SR_CC1IF)) return; TIM1_SR = ~TIM_SR_CC1IF; if (!(ADC1_CR2 & ADC_CR2_DMA)) return; ADC1_CR2 = ADC_CR2_ADON | ADC_CR2_TSVREFE | ADC_CR2_DMA | ADC_CR2_EXTTRIG | ADC_CR2_EXTSEL_SWSTART | ADC_CR2_SWSTART; } void dma1_channel1_isr(void) { DMA1_IFCR = DMA_IFCR_CTCIF(1); DMA1_CCR(1) = 0; ADC1_CR2 = ADC_CR2_ADON | ADC_CR2_TSVREFE; int i = 0, u = 0, v = 0, c = 0, a = 0; #if SENS_CNT >= 2 c = buf[i++]; #endif #if SENS_CNT >= 1 v = buf[i++]; #endif #if SENS_CNT >= 3 u = buf[i++]; #endif if (ain) a = buf[i++]; int r = 4914000 / buf[i + 1]; adcdata(TEMP_FUNC(buf[i] * r >> 12), u * r >> 12, v * r >> 12, c * r >> 12, a * r >> 12); } ================================================ FILE: mcu/GD32E230/config.cmake ================================================ set(opts -mcpu=cortex-m23 -mthumb) set(libs opencm3_stm32f0) set(defs STM32F0) ================================================ FILE: mcu/GD32E230/config.h ================================================ /* ** Copyright (C) Arseny Vakhrushev ** ** This firmware is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This firmware is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this firmware. If not, see . */ #pragma once #define CLK 72000000 #define SW_BLANKING #define IO_PA2 #define IFTIM TIM3 #define IFTIM_XRES 0 #define IFTIM_ICFL 64 #define IFTIM_ICMR TIM3_CCMR1 #define IFTIM_ICM1 (TIM_CCMR1_CC1S_IN_TI1 | TIM_CCMR1_IC1F_DTF_DIV_8_N_8) #define IFTIM_ICM2 (TIM_CCMR1_CC1S_IN_TI1 | TIM_CCMR1_IC1F_DTF_DIV_4_N_8) #define IFTIM_ICM3 (TIM_CCMR1_CC1S_IN_TI1 | TIM_CCMR1_IC1F_DTF_DIV_2_N_8) #define IFTIM_ICIE TIM_DIER_CC1IE #define IFTIM_ICR TIM3_CCR1 #define IFTIM_OCR TIM3_CCR3 #define iftim_isr tim3_isr #define IOTIM TIM15 #define IOTIM_IDR (GPIOA_IDR & 0x4) // A2 #define IOTIM_DMA 5 #define iotim_isr tim15_isr #define iodma_isr dma1_channel4_7_dma2_channel3_5_isr #define USART1_RX_DMA 3 #define USART1_TX_DMA 2 #define usart1_tx_dma_isr dma1_channel2_3_dma2_channel1_2_isr #define USART2_RX_DMA 5 #define USART2_TX_DMA 4 void tim1_com_isr(void); ================================================ FILE: mcu/GD32E230/config.ld ================================================ MEMORY { boot (rx) : ORIGIN = 0x8000000, LENGTH = 4K cfg (rx) : ORIGIN = ORIGIN(boot) + LENGTH(boot), LENGTH = 1K rom (rx) : ORIGIN = ORIGIN(cfg) + LENGTH(cfg), LENGTH = 64K - LENGTH(boot) - LENGTH(cfg) ram (rwx) : ORIGIN = 0x20000000, LENGTH = 8K } ================================================ FILE: mcu/GD32E230/preset.c ================================================ /* ** Copyright (C) Arseny Vakhrushev ** ** This firmware is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This firmware is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this firmware. If not, see . */ #include "common.h" const uint16_t sinedata[] = { 240, 244, 248, 253, 257, 261, 265, 269, 273, 278, 282, 286, 290, 294, 298, 302, 306, 310, 314, 318, 322, 326, 330, 334, 338, 341, 345, 349, 353, 356, 360, 364, 367, 371, 374, 378, 381, 384, 388, 391, 394, 397, 401, 404, 407, 410, 413, 416, 418, 421, 424, 427, 429, 432, 434, 437, 439, 441, 444, 446, 448, 450, 452, 454, 456, 458, 459, 461, 463, 464, 466, 467, 468, 470, 471, 472, 473, 474, 475, 476, 476, 477, 478, 478, 479, 479, 479, 480, 480, 480, 480, 480, 480, 480, 479, 479, 479, 478, 478, 477, 476, 476, 475, 474, 473, 472, 471, 470, 468, 467, 466, 464, 463, 461, 459, 458, 456, 454, 452, 450, 448, 446, 444, 441, 439, 437, 434, 432, 429, 427, 424, 421, 418, 416, 413, 410, 407, 404, 401, 397, 394, 391, 388, 384, 381, 378, 374, 371, 367, 364, 360, 356, 353, 349, 345, 341, 338, 334, 330, 326, 322, 318, 314, 310, 306, 302, 298, 294, 290, 286, 282, 278, 273, 269, 265, 261, 257, 253, 248, 244, 240, 236, 232, 227, 223, 219, 215, 211, 207, 202, 198, 194, 190, 186, 182, 178, 174, 170, 166, 162, 158, 154, 150, 146, 142, 139, 135, 131, 127, 124, 120, 116, 113, 109, 106, 102, 99, 96, 92, 89, 86, 83, 79, 76, 73, 70, 67, 64, 62, 59, 56, 53, 51, 48, 46, 43, 41, 39, 36, 34, 32, 30, 28, 26, 24, 22, 21, 19, 17, 16, 14, 13, 12, 10, 9, 8, 7, 6, 5, 4, 4, 3, 2, 2, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 3, 4, 4, 5, 6, 7, 8, 9, 10, 12, 13, 14, 16, 17, 19, 21, 22, 24, 26, 28, 30, 32, 34, 36, 39, 41, 43, 46, 48, 51, 53, 56, 59, 62, 64, 67, 70, 73, 76, 79, 83, 86, 89, 92, 96, 99, 102, 106, 109, 113, 116, 120, 124, 127, 131, 135, 139, 142, 146, 150, 154, 158, 162, 166, 170, 174, 178, 182, 186, 190, 194, 198, 202, 207, 211, 215, 219, 223, 227, 232, 236, }; ================================================ FILE: mcu/GD32F350/config.c ================================================ /* ** Copyright (C) Arseny Vakhrushev ** ** This firmware is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This firmware is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this firmware. If not, see . */ #include #include #include "common.h" #if SENS_MAP == 0xA3 // A3 (volt) #define SENS_CHAN 0x3 #elif SENS_MAP == 0xA6 // A6 (volt) #define SENS_CHAN 0x6 #elif SENS_MAP == 0xA6A0 // A6 (volt), A0 (curr) #define SENS_CHAN 0xc0 #elif SENS_MAP == 0xA6A3 // A6 (volt), A3 (curr) #define SENS_CHAN 0xc3 #endif #ifndef ANALOG_CHAN #define ANALOG_CHAN 0x2 // ADC_IN2 (A2) #endif #ifndef TEMP_CHAN #define TEMP_CHAN 0x10 // ADC_IN16 (temp) #define TEMP_FUNC(x) (((1440 - (x)) * 2000 >> 11) + 100) #endif #define ADC1_BASE ADC_BASE #define COMP_CSR MMIO32(SYSCFG_COMP_BASE + 0x1c) #ifdef USE_COMP2 #define COMP_SHIFT 16 #else #define COMP_SHIFT 0 #endif static char len, ain; static uint16_t buf[6]; void init(void) { RCC_APB2RSTR = -1; RCC_APB1RSTR = -1; RCC_APB2RSTR = 0; RCC_APB1RSTR = 0; RCC_AHBENR = RCC_AHBENR_DMAEN | RCC_AHBENR_SRAMEN | RCC_AHBENR_GPIOAEN | RCC_AHBENR_GPIOBEN; RCC_APB2ENR = RCC_APB2ENR_SYSCFGCOMPEN | RCC_APB2ENR_ADCEN | RCC_APB2ENR_TIM1EN | RCC_APB2ENR_USART1EN; RCC_APB1ENR = RCC_APB1ENR_TIM2EN | RCC_APB1ENR_TIM6EN | RCC_APB1ENR_WWDGEN; SCB_VTOR = (uint32_t)_rom; // Set vector table address RCC_CFGR &= ~RCC_CFGR_SW_PLL; while (RCC_CFGR & RCC_CFGR_SWS_PLL); RCC_CR &= ~RCC_CR_PLLON; while (RCC_CR & RCC_CR_PLLRDY); RCC_CFGR = 0x8240000; // PLLMUL=11001 (x26) RCC_CR |= RCC_CR_PLLON; while (!(RCC_CR & RCC_CR_PLLRDY)); RCC_CFGR |= RCC_CFGR_SW_PLL; // Default GPIO state - analog input GPIOA_AFRL = 0x20000000; // A7 (TIM1_CH1N) GPIOA_AFRH = 0x00000222; // A8 (TIM1_CH1), A9 (TIM1_CH2), A10 (TIM1_CH3) GPIOB_AFRL = 0x00000022; // B0 (TIM1_CH2N), B1 (TIM1_CH3N) GPIOB_PUPDR = 0x00001000; // B6 (pull-up) GPIOA_MODER = 0xebeabfff; // A7 (TIM1_CH1N), A8 (TIM1_CH1), A9 (TIM1_CH2), A10 (TIM1_CH3) GPIOB_MODER = 0xffffeffa; // B0 (TIM1_CH2N), B1 (TIM1_CH3N), B6 (USART1_TX) #ifdef HALL_MAP RCC_APB1ENR |= RCC_APB1ENR_TIM3EN; GPIOB_AFRL |= 0x10000; // B4 (TIM3_CH1) GPIOB_MODER &= ~0x100; // B4 (TIM3_CH1) #endif #ifndef ANALOG #ifdef IO_PA2 RCC_APB2ENR |= RCC_APB2ENR_TIM15EN; GPIOA_PUPDR |= 0x10; // A2 (pull-up) GPIOA_MODER &= ~0x10; // A2 (TIM15_CH1) nvic_set_priority(NVIC_TIM15_IRQ, 0x40); #else RCC_APB1ENR |= RCC_APB1ENR_TIM3EN; GPIOB_AFRL |= 0x10000; // B4 (TIM3_CH1) GPIOB_PUPDR |= 0x100; // B4 (pull-up) GPIOB_MODER &= ~0x100; // B4 (TIM3_CH1) nvic_set_priority(NVIC_TIM3_IRQ, 0x40); #endif #endif nvic_set_priority(NVIC_USART1_IRQ, 0x80); nvic_set_priority(NVIC_USART2_IRQ, 0x40); nvic_set_priority(NVIC_DMA1_CHANNEL1_IRQ, 0x80); // ADC nvic_set_priority(NVIC_DMA1_CHANNEL2_3_DMA2_CHANNEL1_2_IRQ, 0x80); // USART1_TX nvic_set_priority(NVIC_DMA1_CHANNEL4_7_DMA2_CHANNEL3_5_IRQ, 0x40); // TIM3 or TIM15 or USART2_RX nvic_enable_irq(NVIC_TIM1_BRK_UP_TRG_COM_IRQ); nvic_enable_irq(NVIC_TIM1_CC_IRQ); nvic_enable_irq(NVIC_TIM2_IRQ); nvic_enable_irq(NVIC_TIM3_IRQ); nvic_enable_irq(NVIC_TIM15_IRQ); nvic_enable_irq(NVIC_USART1_IRQ); nvic_enable_irq(NVIC_USART2_IRQ); nvic_enable_irq(NVIC_DMA1_CHANNEL1_IRQ); nvic_enable_irq(NVIC_DMA1_CHANNEL2_3_DMA2_CHANNEL1_2_IRQ); nvic_enable_irq(NVIC_DMA1_CHANNEL4_7_DMA2_CHANNEL3_5_IRQ); TIM1_DIER = TIM_DIER_CC1IE; // ADC trigger TIM1_SMCR = TIM_SMCR_TS_ITR1; // TRGI=TIM2 TIM2_CR2 = TIM_CR2_MMS_COMPARE_OC1REF; // TRGO=OC1REF TIM2_CCMR1 = TIM_CCMR1_OC1PE | TIM_CCMR1_OC1M_PWM2; // Inverted PWM on OC1 TIM2_CCMR2 = TIM_CCMR2_CC4S_IN_TI4; TIM2_CCER = TIM_CCER_CC4E; // IC4 on rising edge on TI4 (COMP_OUT) RCC_CR2 |= RCC_CR2_HSI14ON; // Enable IRC28M while (!(RCC_CR2 & RCC_CR2_HSI14RDY)); ADC1_CR2 = ADC_CR2_ADON | ADC_CR2_TSVREFE; TIM6_ARR = CLK_MHZ - 1; TIM6_CR1 = TIM_CR1_CEN | TIM_CR1_OPM; while (TIM6_CR1 & TIM_CR1_CEN); // Wait for 1us (RM 11.4.1) ADC1_CR2 |= ADC_CR2_CAL; while (ADC1_CR2 & ADC_CR2_CAL); ADC1_CR1 = ADC_CR1_SCAN; ADC1_SMPR1 = -1; // Sampling time ~17us @ IRC28M/2=14Mhz ADC1_SMPR2 = -1; ADC1_SQR3 = SENS_CHAN; len = SENS_CNT; if (IO_ANALOG) { ADC1_SQR3 |= ANALOG_CHAN << (len++ * 5); ain = 1; } ADC1_SQR3 |= (TEMP_CHAN | 0x220) << (len * 5); // ADC_IN17 (vref) len += 2; ADC1_SQR1 = (len - 1) << ADC_SQR1_L_LSB; DMA1_CPAR(1) = (uint32_t)&ADC1_DR; DMA1_CMAR(1) = (uint32_t)buf; } void compctl(int x) { int cr = 0; switch (x & 3) { case COMP_IN1: cr = 0x61; // A1>A0 break; case COMP_IN2: cr = 0x41; // A1>A4 break; case COMP_IN3: cr = 0x51; // A1>A5 break; } if (x & 4) cr |= 0x800; // Change polarity COMP_CSR = cr << COMP_SHIFT; } void io_serial(void) { RCC_APB2RSTR = RCC_APB2RSTR_TIM15RST; RCC_APB2RSTR = 0; nvic_clear_pending_irq(NVIC_TIM15_IRQ); RCC_APB1ENR |= RCC_APB1ENR_USART2EN; GPIOA_AFRL |= 0x100; // A2 (USART2_TX) #ifdef IO_RXTX GPIOA_AFRH |= 0x10000000; // A15 (USART2_RX) #endif } void io_analog(void) { RCC_APB2RSTR = RCC_APB2RSTR_TIM15RST; RCC_APB2RSTR = 0; nvic_clear_pending_irq(NVIC_TIM15_IRQ); GPIOA_PUPDR &= ~0x30; // A2 (no pull-up/pull-down) GPIOA_MODER |= 0x30; // A2 (analog) } void adctrig(void) { if (DMA1_CCR(1) & DMA_CCR_EN) return; DMA1_CNDTR(1) = len; DMA1_CCR(1) = DMA_CCR_EN | DMA_CCR_TCIE | DMA_CCR_MINC | DMA_CCR_PSIZE_16BIT | DMA_CCR_MSIZE_16BIT; ADC1_CR2 = ADC_CR2_ADON | ADC_CR2_TSVREFE | ADC_CR2_DMA | ADC_CR2_EXTTRIG | ADC_CR2_EXTSEL_SWSTART; } void tim1_brk_up_trg_com_isr(void) { int sr = TIM1_SR; if (sr & TIM_SR_UIF) { TIM1_SR = ~TIM_SR_UIF; if (TIM1_CCR4) COMP_CSR &= ~(0x700 << COMP_SHIFT); // COMP_OUT off } if (sr & TIM_SR_COMIF) tim1_com_isr(); } void tim1_cc_isr(void) { int sr = TIM1_SR; if (sr & TIM_SR_CC4IF) { TIM1_SR = ~TIM_SR_CC4IF; COMP_CSR |= 0x400 << COMP_SHIFT; // COMP_OUT=TIM2_IC4 } if (!(sr & TIM_SR_CC1IF)) return; TIM1_SR = ~TIM_SR_CC1IF; if (!(ADC1_CR2 & ADC_CR2_DMA)) return; ADC1_CR2 = ADC_CR2_ADON | ADC_CR2_TSVREFE | ADC_CR2_DMA | ADC_CR2_EXTTRIG | ADC_CR2_EXTSEL_SWSTART | ADC_CR2_SWSTART; } void dma1_channel1_isr(void) { DMA1_IFCR = DMA_IFCR_CTCIF(1); DMA1_CCR(1) = 0; ADC1_CR2 = ADC_CR2_ADON | ADC_CR2_TSVREFE; int i = 0, u = 0, v = 0, c = 0, a = 0; #if SENS_CNT >= 2 c = buf[i++]; #endif #if SENS_CNT >= 1 v = buf[i++]; #endif #if SENS_CNT >= 3 u = buf[i++]; #endif if (ain) a = buf[i++]; int r = 4914000 / buf[i + 1]; adcdata(TEMP_FUNC(buf[i] * r >> 12), u * r >> 12, v * r >> 12, c * r >> 12, a * r >> 12); } ================================================ FILE: mcu/GD32F350/config.cmake ================================================ set(opts -mcpu=cortex-m4 -mthumb) set(libs opencm3_stm32f0) set(defs STM32F0) ================================================ FILE: mcu/GD32F350/config.h ================================================ /* ** Copyright (C) Arseny Vakhrushev ** ** This firmware is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This firmware is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this firmware. If not, see . */ #pragma once #define CLK 104000000 #define SW_BLANKING #define IFTIM TIM2 #define IFTIM_XRES 2 #define IFTIM_ICFL 128 #define IFTIM_ICMR TIM2_CCMR2 #define IFTIM_ICM1 (TIM_CCMR2_CC4S_IN_TI4 | TIM_CCMR2_IC4F_DTF_DIV_16_N_8) #define IFTIM_ICM2 (TIM_CCMR2_CC4S_IN_TI4 | TIM_CCMR2_IC4F_DTF_DIV_8_N_8) #define IFTIM_ICM3 (TIM_CCMR2_CC4S_IN_TI4 | TIM_CCMR2_IC4F_DTF_DIV_4_N_8) #define IFTIM_ICIE TIM_DIER_CC4IE #define IFTIM_ICR TIM2_CCR4 #define IFTIM_OCR TIM2_CCR1 #define iftim_isr tim2_isr #ifdef IO_PA2 #define IOTIM TIM15 #define IOTIM_IDR (GPIOA_IDR & 0x4) // A2 #define IOTIM_DMA 5 #define iotim_isr tim15_isr #else #define IOTIM TIM3 #define IOTIM_IDR (GPIOB_IDR & 0x10) // B4 #define IOTIM_DMA 4 #define iotim_isr tim3_isr #endif #define iodma_isr dma1_channel4_7_dma2_channel3_5_isr #define USART1_RX_DMA 3 #define USART1_TX_DMA 2 #define usart1_tx_dma_isr dma1_channel2_3_dma2_channel1_2_isr #define USART2_RX_DMA 5 #define USART2_TX_DMA 4 void tim1_com_isr(void); ================================================ FILE: mcu/GD32F350/config.ld ================================================ MEMORY { boot (rx) : ORIGIN = 0x8000000, LENGTH = 4K cfg (rx) : ORIGIN = ORIGIN(boot) + LENGTH(boot), LENGTH = 1K rom (rx) : ORIGIN = ORIGIN(cfg) + LENGTH(cfg), LENGTH = 64K - LENGTH(boot) - LENGTH(cfg) ram (rwx) : ORIGIN = 0x20000000, LENGTH = 8K } ================================================ FILE: mcu/GD32F350/preset.c ================================================ /* ** Copyright (C) Arseny Vakhrushev ** ** This firmware is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This firmware is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this firmware. If not, see . */ #include "common.h" const uint16_t sinedata[] = { 347, 353, 359, 365, 371, 377, 383, 389, 395, 401, 407, 413, 419, 424, 430, 436, 442, 448, 454, 459, 465, 471, 476, 482, 487, 493, 498, 504, 509, 514, 520, 525, 530, 535, 540, 545, 550, 555, 560, 565, 569, 574, 578, 583, 587, 592, 596, 600, 604, 608, 612, 616, 620, 623, 627, 630, 634, 637, 640, 644, 647, 650, 652, 655, 658, 661, 663, 665, 668, 670, 672, 674, 676, 678, 680, 681, 683, 684, 685, 687, 688, 689, 690, 690, 691, 692, 692, 693, 693, 693, 693, 693, 693, 693, 692, 692, 691, 690, 690, 689, 688, 687, 685, 684, 683, 681, 680, 678, 676, 674, 672, 670, 668, 665, 663, 661, 658, 655, 652, 650, 647, 644, 640, 637, 634, 630, 627, 623, 620, 616, 612, 608, 604, 600, 596, 592, 587, 583, 578, 574, 569, 565, 560, 555, 550, 545, 540, 535, 530, 525, 520, 514, 509, 504, 498, 493, 487, 482, 476, 471, 465, 459, 454, 448, 442, 436, 430, 424, 419, 413, 407, 401, 395, 389, 383, 377, 371, 365, 359, 353, 347, 340, 334, 328, 322, 316, 310, 304, 298, 292, 286, 280, 274, 269, 263, 257, 251, 245, 239, 234, 228, 222, 217, 211, 206, 200, 195, 189, 184, 179, 173, 168, 163, 158, 153, 148, 143, 138, 133, 128, 124, 119, 115, 110, 106, 101, 97, 93, 89, 85, 81, 77, 73, 70, 66, 63, 59, 56, 53, 49, 46, 43, 41, 38, 35, 32, 30, 28, 25, 23, 21, 19, 17, 15, 13, 12, 10, 9, 8, 6, 5, 4, 3, 3, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 3, 4, 5, 6, 8, 9, 10, 12, 13, 15, 17, 19, 21, 23, 25, 28, 30, 32, 35, 38, 41, 43, 46, 49, 53, 56, 59, 63, 66, 70, 73, 77, 81, 85, 89, 93, 97, 101, 106, 110, 115, 119, 124, 128, 133, 138, 143, 148, 153, 158, 163, 168, 173, 179, 184, 189, 195, 200, 206, 211, 217, 222, 228, 234, 239, 245, 251, 257, 263, 269, 274, 280, 286, 292, 298, 304, 310, 316, 322, 328, 334, 340, }; ================================================ FILE: mcu/STM32F051/config.c ================================================ /* ** Copyright (C) Arseny Vakhrushev ** ** This firmware is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This firmware is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this firmware. If not, see . */ #include #include "common.h" #if SENS_MAP == 0xA3 // A3 (volt) #define SENS_CHAN 0x8 #elif SENS_MAP == 0xA6 // A6 (volt) #define SENS_CHAN 0x40 #elif SENS_MAP == 0xA3A6 // A3 (volt), A6 (curr) #define SENS_CHAN 0x48 #define SENS_SWAP #elif SENS_MAP == 0xA6A3 // A6 (volt), A3 (curr) #define SENS_CHAN 0x48 #endif #ifndef ANALOG_CHAN #define ANALOG_CHAN 0x2 // ADC_IN2 (A2) #endif #ifdef TEMP_CHAN #define TEMP_SHIFT 12 #else #define TEMP_SHIFT 0 #define TEMP_CHAN 0x10000 // ADC_IN16 (temp) #define TEMP_FUNC(x) (((x) / 3300 - ST_TSENSE_CAL1_30C) * 320 / (ST_TSENSE_CAL2_110C - ST_TSENSE_CAL1_30C) + 120) #endif #define COMP_CSR MMIO32(SYSCFG_COMP_BASE + 0x1c) #ifdef USE_COMP2 #define COMP_SHIFT 16 #else #define COMP_SHIFT 0 #endif static char len, ain; static uint16_t buf[6]; void init(void) { RCC_APB2RSTR = -1; RCC_APB1RSTR = -1; RCC_APB2RSTR = 0; RCC_APB1RSTR = 0; RCC_AHBENR = RCC_AHBENR_DMAEN | RCC_AHBENR_SRAMEN | RCC_AHBENR_GPIOAEN | RCC_AHBENR_GPIOBEN; RCC_APB2ENR = RCC_APB2ENR_SYSCFGCOMPEN | RCC_APB2ENR_ADCEN | RCC_APB2ENR_TIM1EN | RCC_APB2ENR_USART1EN; RCC_APB1ENR = RCC_APB1ENR_TIM2EN | RCC_APB1ENR_TIM6EN | RCC_APB1ENR_WWDGEN; SYSCFG_CFGR1 = SYSCFG_CFGR1_MEM_MODE_SRAM; // Map SRAM at 0x00000000 memcpy(_vec, _rom, _ram - _vec); // Copy vector table to SRAM // Default GPIO state - analog input GPIOA_AFRL = 0x20000000; // A7 (TIM1_CH1N) GPIOA_AFRH = 0x00000222; // A8 (TIM1_CH1), A9 (TIM1_CH2), A10 (TIM1_CH3) GPIOB_AFRL = 0x00000022; // B0 (TIM1_CH2N), B1 (TIM1_CH3N) GPIOB_PUPDR = 0x00001000; // B6 (pull-up) GPIOA_MODER = 0xebeabfff; // A7 (TIM1_CH1N), A8 (TIM1_CH1), A9 (TIM1_CH2), A10 (TIM1_CH3) GPIOB_MODER = 0xffffeffa; // B0 (TIM1_CH2N), B1 (TIM1_CH3N), B6 (USART1_TX) #ifdef HALL_MAP RCC_APB1ENR |= RCC_APB1ENR_TIM3EN; GPIOB_AFRL |= 0x10000; // B4 (TIM3_CH1) GPIOB_MODER &= ~0x100; // B4 (TIM3_CH1) #endif #ifndef ANALOG #ifdef IO_PA2 RCC_APB2ENR |= RCC_APB2ENR_TIM15EN; GPIOA_PUPDR |= 0x10; // A2 (pull-up) GPIOA_MODER &= ~0x10; // A2 (TIM15_CH1) nvic_set_priority(NVIC_TIM15_IRQ, 0x40); #else RCC_APB1ENR |= RCC_APB1ENR_TIM3EN; GPIOB_AFRL |= 0x10000; // B4 (TIM3_CH1) GPIOB_PUPDR |= 0x100; // B4 (pull-up) GPIOB_MODER &= ~0x100; // B4 (TIM3_CH1) nvic_set_priority(NVIC_TIM3_IRQ, 0x40); #endif #endif nvic_set_priority(NVIC_USART1_IRQ, 0x80); nvic_set_priority(NVIC_USART2_IRQ, 0x40); nvic_set_priority(NVIC_DMA1_CHANNEL1_IRQ, 0x80); // ADC nvic_set_priority(NVIC_DMA1_CHANNEL2_3_DMA2_CHANNEL1_2_IRQ, 0x80); // USART1_TX nvic_set_priority(NVIC_DMA1_CHANNEL4_7_DMA2_CHANNEL3_5_IRQ, 0x40); // TIM3 or TIM15 or USART2_RX nvic_enable_irq(NVIC_TIM1_BRK_UP_TRG_COM_IRQ); nvic_enable_irq(NVIC_TIM1_CC_IRQ); nvic_enable_irq(NVIC_TIM2_IRQ); nvic_enable_irq(NVIC_TIM3_IRQ); nvic_enable_irq(NVIC_TIM15_IRQ); nvic_enable_irq(NVIC_USART1_IRQ); nvic_enable_irq(NVIC_USART2_IRQ); nvic_enable_irq(NVIC_DMA1_CHANNEL1_IRQ); nvic_enable_irq(NVIC_DMA1_CHANNEL2_3_DMA2_CHANNEL1_2_IRQ); nvic_enable_irq(NVIC_DMA1_CHANNEL4_7_DMA2_CHANNEL3_5_IRQ); TIM1_SMCR = TIM_SMCR_TS_ITR1; // TRGI=TIM2 TIM2_CR2 = TIM_CR2_MMS_COMPARE_OC1REF; // TRGO=OC1REF TIM2_CCMR1 = TIM_CCMR1_OC1PE | TIM_CCMR1_OC1M_PWM2; // Inverted PWM on OC1 TIM2_CCMR2 = TIM_CCMR2_CC4S_IN_TI4; TIM2_CCER = TIM_CCER_CC4E; // IC4 on rising edge on TI4 (COMP_OUT) ADC1_CR = ADC_CR_ADCAL; while (ADC1_CR & ADC_CR_ADCAL); while (ADC1_CR = ADC_CR_ADEN, !(ADC1_ISR & ADC_ISR_ADRDY)); // Keep powering on until ready (Errata 2.5.3) ADC1_CFGR1 = ADC_CFGR1_DMAEN | ADC_CFGR1_EXTEN_RISING_EDGE; ADC1_SMPR = ADC_SMPR_SMP_239DOT5; // Sampling time ~17us @ HSI14 ADC1_CCR = ADC_CCR_VREFEN | ADC_CCR_TSEN; ADC1_CHSELR = SENS_CHAN | TEMP_CHAN | 0x20000; // ADC_IN17 (vref) len = SENS_CNT + 2; if (IO_ANALOG) { ADC1_CHSELR |= 1 << ANALOG_CHAN; ++len; ain = 1; } DMA1_CPAR(1) = (uint32_t)&ADC1_DR; DMA1_CMAR(1) = (uint32_t)buf; } void compctl(int x) { int cr = 0; switch (x & 3) { case COMP_IN1: cr = 0x61; // A1>A0 break; case COMP_IN2: cr = 0x41; // A1>A4 break; case COMP_IN3: cr = 0x51; // A1>A5 break; } if (x & 4) cr |= 0x800; // Change polarity COMP_CSR = cr << COMP_SHIFT; } void io_serial(void) { RCC_APB2RSTR = RCC_APB2RSTR_TIM15RST; RCC_APB2RSTR = 0; nvic_clear_pending_irq(NVIC_TIM15_IRQ); RCC_APB1ENR |= RCC_APB1ENR_USART2EN; GPIOA_AFRL |= 0x100; // A2 (USART2_TX) #ifdef IO_RXTX GPIOA_AFRH |= 0x10000000; // A15 (USART2_RX) #endif } void io_analog(void) { RCC_APB2RSTR = RCC_APB2RSTR_TIM15RST; RCC_APB2RSTR = 0; nvic_clear_pending_irq(NVIC_TIM15_IRQ); GPIOA_PUPDR &= ~0x30; // A2 (no pull-up/pull-down) GPIOA_MODER |= 0x30; // A2 (analog) } void adctrig(void) { if (DMA1_CCR(1) & DMA_CCR_EN) return; DMA1_CNDTR(1) = len; DMA1_CCR(1) = DMA_CCR_EN | DMA_CCR_TCIE | DMA_CCR_MINC | DMA_CCR_PSIZE_16BIT | DMA_CCR_MSIZE_16BIT; ADC1_CR = ADC_CR_ADSTART; } void tim1_brk_up_trg_com_isr(void) { int sr = TIM1_SR; if (sr & TIM_SR_UIF) { TIM1_SR = ~TIM_SR_UIF; if (TIM1_CCR4) COMP_CSR &= ~(0x700 << COMP_SHIFT); // COMP_OUT off } if (sr & TIM_SR_COMIF) tim1_com_isr(); } void tim1_cc_isr(void) { TIM1_SR = ~TIM_SR_CC4IF; COMP_CSR |= 0x400 << COMP_SHIFT; // COMP_OUT=TIM2_IC4 } void dma1_channel1_isr(void) { DMA1_IFCR = DMA_IFCR_CTCIF(1); DMA1_CCR(1) = 0; int i = 0, u = 0, v = 0, c = 0, a = 0; #ifndef ANALOG_LAST if (ain) a = buf[i++]; #endif #ifdef SENS_SWAP v = buf[i++]; c = buf[i++]; #else #if SENS_CNT >= 2 c = buf[i++]; #endif #if SENS_CNT >= 1 v = buf[i++]; #endif #endif #if SENS_CNT >= 3 u = buf[i++]; #endif #ifdef ANALOG_LAST if (ain) a = buf[i++]; #endif int r = ST_VREFINT_CAL * 3300 / buf[i + 1]; adcdata(TEMP_FUNC(buf[i] * r >> TEMP_SHIFT), u * r >> 12, v * r >> 12, c * r >> 12, a * r >> 12); } ================================================ FILE: mcu/STM32F051/config.cmake ================================================ set(opts -mcpu=cortex-m0 -mthumb) set(libs opencm3_stm32f0) set(defs STM32F0) ================================================ FILE: mcu/STM32F051/config.h ================================================ /* ** Copyright (C) Arseny Vakhrushev ** ** This firmware is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This firmware is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this firmware. If not, see . */ #pragma once #define CLK 48000000 #define SW_BLANKING #define IFTIM TIM2 #define IFTIM_XRES 2 #define IFTIM_ICFL 64 #define IFTIM_ICMR TIM2_CCMR2 #define IFTIM_ICM1 (TIM_CCMR2_CC4S_IN_TI4 | TIM_CCMR2_IC4F_DTF_DIV_8_N_8) #define IFTIM_ICM2 (TIM_CCMR2_CC4S_IN_TI4 | TIM_CCMR2_IC4F_DTF_DIV_4_N_8) #define IFTIM_ICM3 (TIM_CCMR2_CC4S_IN_TI4 | TIM_CCMR2_IC4F_DTF_DIV_2_N_8) #define IFTIM_ICIE TIM_DIER_CC4IE #define IFTIM_ICR TIM2_CCR4 #define IFTIM_OCR TIM2_CCR1 #define iftim_isr tim2_isr #ifdef IO_PA2 #define IOTIM TIM15 #define IOTIM_IDR (GPIOA_IDR & 0x4) // A2 #define IOTIM_DMA 5 #define iotim_isr tim15_isr #else #define IOTIM TIM3 #define IOTIM_IDR (GPIOB_IDR & 0x10) // B4 #define IOTIM_DMA 4 #define iotim_isr tim3_isr #endif #define iodma_isr dma1_channel4_7_dma2_channel3_5_isr #define USART1_RX_DMA 3 #define USART1_TX_DMA 2 #define usart1_tx_dma_isr dma1_channel2_3_dma2_channel1_2_isr #define USART2_RX_DMA 5 #define USART2_TX_DMA 4 void tim1_com_isr(void); ================================================ FILE: mcu/STM32F051/config.ld ================================================ MEMORY { boot (rx) : ORIGIN = 0x8000000, LENGTH = 4K cfg (rx) : ORIGIN = ORIGIN(boot) + LENGTH(boot), LENGTH = 1K rom (rx) : ORIGIN = ORIGIN(cfg) + LENGTH(cfg), LENGTH = 32K - LENGTH(boot) - LENGTH(cfg) vec (rx) : ORIGIN = 0x20000000, LENGTH = 192 ram (rwx) : ORIGIN = ORIGIN(vec) + LENGTH(vec), LENGTH = 8K - LENGTH(vec) } _vec = ORIGIN(vec); ================================================ FILE: mcu/STM32F051/preset.c ================================================ /* ** Copyright (C) Arseny Vakhrushev ** ** This firmware is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This firmware is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this firmware. If not, see . */ #include "common.h" const uint16_t sinedata[] = { 160, 163, 166, 168, 171, 174, 177, 179, 182, 185, 188, 191, 193, 196, 199, 201, 204, 207, 209, 212, 215, 217, 220, 223, 225, 228, 230, 233, 235, 238, 240, 242, 245, 247, 249, 252, 254, 256, 259, 261, 263, 265, 267, 269, 271, 273, 275, 277, 279, 281, 283, 284, 286, 288, 289, 291, 293, 294, 296, 297, 299, 300, 301, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 315, 316, 317, 317, 318, 318, 318, 319, 319, 319, 320, 320, 320, 320, 320, 320, 320, 320, 320, 319, 319, 319, 318, 318, 318, 317, 317, 316, 315, 315, 314, 313, 312, 311, 310, 309, 308, 307, 306, 305, 304, 303, 301, 300, 299, 297, 296, 294, 293, 291, 289, 288, 286, 284, 283, 281, 279, 277, 275, 273, 271, 269, 267, 265, 263, 261, 259, 256, 254, 252, 249, 247, 245, 242, 240, 238, 235, 233, 230, 228, 225, 223, 220, 217, 215, 212, 209, 207, 204, 201, 199, 196, 193, 191, 188, 185, 182, 179, 177, 174, 171, 168, 166, 163, 160, 157, 154, 152, 149, 146, 143, 141, 138, 135, 132, 129, 127, 124, 121, 119, 116, 113, 111, 108, 105, 103, 100, 97, 95, 92, 90, 87, 85, 82, 80, 78, 75, 73, 71, 68, 66, 64, 61, 59, 57, 55, 53, 51, 49, 47, 45, 43, 41, 39, 37, 36, 34, 32, 31, 29, 27, 26, 24, 23, 21, 20, 19, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 5, 4, 3, 3, 2, 2, 2, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 4, 5, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 19, 20, 21, 23, 24, 26, 27, 29, 31, 32, 34, 36, 37, 39, 41, 43, 45, 47, 49, 51, 53, 55, 57, 59, 61, 64, 66, 68, 71, 73, 75, 78, 80, 82, 85, 87, 90, 92, 95, 97, 100, 103, 105, 108, 111, 113, 116, 119, 121, 124, 127, 129, 132, 135, 138, 141, 143, 146, 149, 152, 154, 157, }; ================================================ FILE: mcu/STM32G071/config.c ================================================ /* ** Copyright (C) Arseny Vakhrushev ** ** This firmware is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This firmware is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this firmware. If not, see . */ #include #include #include "common.h" #if SENS_MAP == 0xA0 // A0 (volt) #define SENS_CHAN 0x0 #elif SENS_MAP == 0xA5 // A5 (volt) #define SENS_CHAN 0x5 #elif SENS_MAP == 0xA6 // A6 (volt) #define SENS_CHAN 0x6 #elif SENS_MAP == 0xA1A5 // A1 (volt), A5 (curr) #define SENS_CHAN 0x15 #elif SENS_MAP == 0xA5A4 // A5 (volt), A4 (curr) #define SENS_CHAN 0x54 #elif SENS_MAP == 0xA6A4 // A6 (volt), A4 (curr) #define SENS_CHAN 0x64 #elif SENS_MAP == 0xA6A5A4 // A6 (temp), A5 (volt), A4 (curr) #define SENS_CHAN 0x654 #endif #ifndef ANALOG_CHAN #ifdef IO_PA6 #define ANALOG_CHAN 0x6 // ADC_IN6 (A6) #else #define ANALOG_CHAN 0x2 // ADC_IN2 (A2) #endif #endif #ifdef TEMP_CHAN #define TEMP_SHIFT 12 #else #define TEMP_SHIFT 0 #define TEMP_CHAN 0xc // ADC_IN12 (temp) #define TEMP_FUNC(x) (((x) / 3000 - ST_TSENSE_CAL1_30C) * 400 / (ST_TSENSE_CAL2_130C - ST_TSENSE_CAL1_30C) + 120) #endif #define COMP1_CSR MMIO32(COMP_BASE + 0x0) #define COMP2_CSR MMIO32(COMP_BASE + 0x4) static char len, ain; static uint16_t buf[6]; #ifdef LED_WS2812 static uint16_t led[5]; #endif void init(void) { RCC_AHBRSTR = -1; RCC_APBRSTR1 = -1; RCC_APBRSTR2 = -1; RCC_AHBRSTR = 0; RCC_APBRSTR1 = 0; RCC_APBRSTR2 = 0; RCC_IOPENR = 0x27; // GPIOAEN=1, GPIOBEN=1, GPIOCEN=1, GPIOFEN=1 RCC_AHBENR = RCC_AHBENR_DMAEN | RCC_AHBENR_FLASHEN; RCC_APBENR1 = RCC_APBENR1_TIM2EN | RCC_APBENR1_TIM6EN | RCC_APBENR1_WWDGEN; RCC_APBENR2 = RCC_APBENR2_SYSCFGEN | RCC_APBENR2_TIM1EN | RCC_APBENR2_USART1EN | RCC_APBENR2_ADCEN; SYSCFG_CFGR1 = SYSCFG_CFGR1_PA11_RMP | SYSCFG_CFGR1_PA12_RMP | SYSCFG_CFGR1_UCPD1_STROBE; // A11->A9, A12->A10, disable internal pull-downs on A8/B15 SCB_VTOR = (uint32_t)_rom; // Set vector table address #ifdef USE_HSE if (!cfg.throt_cal) { TIM6_PSC = CLK_MHZ - 1; // 1us resolution TIM6_ARR = 9999; TIM6_EGR = TIM_EGR_UG; TIM6_SR = ~TIM_SR_UIF; TIM6_CR1 = TIM_CR1_CEN | TIM_CR1_OPM; RCC_CR |= RCC_CR_HSEON; while (!(RCC_CR & RCC_CR_HSERDY)) { if (!(TIM6_CR1 & TIM_CR1_CEN)) { // Timeout 10ms RCC_CR &= ~RCC_CR_HSEON; goto skip; } } RCC_CFGR = RCC_CFGR_SW_HSE; while ((RCC_CFGR & RCC_CFGR_SWS_MASK << RCC_CFGR_SWS_SHIFT) != RCC_CFGR_SWS_HSE << RCC_CFGR_SWS_SHIFT); RCC_CR &= ~RCC_CR_PLLON; while (RCC_CR & RCC_CR_PLLRDY); RCC_PLLCFGR = RCC_PLLCFGR_PLLSRC_HSE | (128 / USE_HSE) << RCC_PLLCFGR_PLLN_SHIFT | RCC_PLLCFGR_PLLREN | 1 << RCC_PLLCFGR_PLLR_SHIFT; RCC_CR |= RCC_CR_PLLON; while (!(RCC_CR & RCC_CR_PLLRDY)); RCC_CFGR = RCC_CFGR_SW_PLLRCLK; } skip: #endif // Default GPIO state - analog input GPIOA_AFRL = 0x20000000; // A7 (TIM1_CH1N) GPIOA_AFRH = 0x00000222; // A8 (TIM1_CH1), A9 (TIM1_CH2), A10 (TIM1_CH3) GPIOB_AFRL = 0x00000002; // B0 (TIM1_CH2N) GPIOB_PUPDR = 0x00001000; // B6 (pull-up) GPIOA_MODER = 0xebeabfff; // A7 (TIM1_CH1N), A8 (TIM1_CH1), A9 (TIM1_CH2), A10 (TIM1_CH3) GPIOB_MODER = 0xffffeffe; // B0 (TIM1_CH2N), B6 (USART1_TX) #ifdef IO_PA6 // N version GPIOB_AFRH |= 0x20000000; // B15 (TIM1_CH3N) GPIOB_MODER &= ~0x40000000; // B15 (TIM1_CH3N) #else GPIOB_AFRL |= 0x20; // B1 (TIM1_CH3N) GPIOB_MODER &= ~0x4; // B1 (TIM1_CH3N) #endif #ifdef HALL_MAP RCC_APBENR1 |= RCC_APBENR1_TIM3EN; GPIOC_AFRL |= 0x1000000; // C6 (TIM3_CH1) GPIOC_MODER &= ~0x1000; // C6 (TIM3_CH1) #endif #ifndef ANALOG #ifdef IO_PA2 RCC_APBENR2 |= RCC_APBENR2_TIM15EN; GPIOA_AFRL |= 0x500; // A2 (TIM15_CH1) GPIOA_PUPDR |= 0x10; // A2 (pull-up) GPIOA_MODER &= ~0x10; // A2 (TIM15_CH1) nvic_set_priority(NVIC_TIM15_IRQ, 0x40); #else RCC_APBENR1 |= RCC_APBENR1_TIM3EN; #ifdef IO_PA6 GPIOA_AFRL |= 0x1000000; // A6 (TIM3_CH1) GPIOA_PUPDR |= 0x1000; // A6 (pull-up) GPIOA_MODER &= ~0x1000; // A6 (TIM3_CH1) #else GPIOB_AFRL |= 0x10000; // B4 (TIM3_CH1) GPIOB_PUPDR |= 0x100; // B4 (pull-up) GPIOB_MODER &= ~0x100; // B4 (TIM3_CH1) #endif nvic_set_priority(NVIC_TIM34_IRQ, 0x40); #endif #ifndef IO_AUX #elif IO_AUX == 0xB8 RCC_APBENR2 |= RCC_APBENR2_TIM16EN; GPIOB_AFRH |= 0x2; // B8 (TIM16_CH1) GPIOB_PUPDR |= 0x10000; // B8 (pull-up) GPIOB_MODER &= ~0x10000; // B8 (TIM16_CH1) nvic_set_priority(NVIC_TIM16_FDCAN_IT0_IRQ, 0x40); #elif IO_AUX == 0xB9 RCC_APBENR2 |= RCC_APBENR2_TIM17EN; GPIOB_AFRH |= 0x20; // B9 (TIM17_CH1) GPIOB_PUPDR |= 0x40000; // B9 (pull-up) GPIOB_MODER &= ~0x40000; // B9 (TIM17_CH1) nvic_set_priority(NVIC_TIM17_FDCAN_IT1_IRQ, 0x40); #else RCC_APBENR1 |= RCC_APBENR1_TIM3EN; GPIOC_AFRL |= 0x1000000; // C6 (TIM3_CH1) GPIOC_PUPDR |= 0x1000; // C6 (pull-up) GPIOC_MODER &= ~0x1000; // C6 (TIM3_CH1) nvic_set_priority(NVIC_TIM34_IRQ, 0x40); #endif #endif nvic_set_priority(NVIC_USART1_IRQ, 0x80); nvic_set_priority(NVIC_USART2_LPUART2_IRQ, 0x40); nvic_set_priority(NVIC_DMA1_CHANNEL1_IRQ, 0x40); // TIM3 or TIM15 or USART2_RX nvic_set_priority(NVIC_DMA1_CHANNEL2_3_IRQ, 0x80); // USART1_TX nvic_set_priority(NVIC_DMA1_CHANNEL4_7_DMAMUX_IRQ, 0x80); // ADC, WS2812 nvic_enable_irq(NVIC_TIM1_BRK_UP_TRG_COM_IRQ); nvic_enable_irq(NVIC_TIM2_IRQ); nvic_enable_irq(NVIC_TIM34_IRQ); nvic_enable_irq(NVIC_TIM15_IRQ); nvic_enable_irq(NVIC_TIM16_FDCAN_IT0_IRQ); nvic_enable_irq(NVIC_TIM17_FDCAN_IT1_IRQ); nvic_enable_irq(NVIC_USART1_IRQ); nvic_enable_irq(NVIC_USART2_LPUART2_IRQ); nvic_enable_irq(NVIC_DMA1_CHANNEL1_IRQ); nvic_enable_irq(NVIC_DMA1_CHANNEL2_3_IRQ); nvic_enable_irq(NVIC_DMA1_CHANNEL4_7_DMAMUX_IRQ); #ifdef IO_PA2 DMAMUX1_CxCR(1) = DMAMUX_CxCR_DMAREQ_ID_TIM15_CH1; #else DMAMUX1_CxCR(1) = DMAMUX_CxCR_DMAREQ_ID_TIM3_CH1; #endif DMAMUX1_CxCR(2) = DMAMUX_CxCR_DMAREQ_ID_USART1_RX; DMAMUX1_CxCR(3) = DMAMUX_CxCR_DMAREQ_ID_USART1_TX; DMAMUX1_CxCR(4) = DMAMUX_CxCR_DMAREQ_ID_ADC; TIM1_SMCR = TIM_SMCR_TS_ITR1; // TRGI=TIM2 TIM2_CR2 = TIM_CR2_MMS_COMPARE_OC3REF; // TRGO=OC3REF TIM2_CCMR1 = TIM_CCMR1_CC2S_IN_TI2; TIM2_CCMR2 = TIM_CCMR2_OC3PE | TIM_CCMR2_OC3M_PWM2; // Inverted PWM on OC3 TIM2_CCER = TIM_CCER_CC2E; // IC2 on rising edge on TI2 (COMP2_OUT) #if defined IO_PA2 || defined IO_PA6 TIM2_CCMR1 |= TIM_CCMR1_CC1S_IN_TI1; TIM2_CCER |= TIM_CCER_CC1E; // IC1 on rising edge on TI1 (COMP1_OUT) #endif ADC_CFGR2(ADC1) = ADC_CFGR2_LFTRIG | ADC_CFGR2_CKMODE_PCLK_DIV4 << ADC_CFGR2_CKMODE_SHIFT; ADC_CCR(ADC1) = ADC_CCR_VREFEN | ADC_CCR_TSEN; ADC_CR(ADC1) = ADC_CR_ADVREGEN; TIM6_PSC = 0; TIM6_ARR = CLK_KHZ / 50 - 1; TIM6_EGR = TIM_EGR_UG; TIM6_SR = ~TIM_SR_UIF; TIM6_CR1 = TIM_CR1_CEN | TIM_CR1_OPM; while (TIM6_CR1 & TIM_CR1_CEN); // Wait for 20us (RM 15.3.2) ADC_CR(ADC1) = ADC_CR_ADVREGEN | ADC_CR_ADCAL; while (ADC_CR(ADC1) & ADC_CR_ADCAL); ADC_CR(ADC1) = ADC_CR_ADEN | ADC_CR_ADVREGEN; while (!(ADC_ISR(ADC1) & ADC_ISR_ADRDY)); ADC_CFGR1(ADC1) = ADC_CFGR1_DMAEN | ADC_CFGR1_EXTEN_RISING_EDGE | ADC_CFGR1_CHSELRMOD; ADC_SMPR1(ADC1) = ADC_SMPR_SMPx_160DOT5CYC; // Sampling time ~10us @ PCLK/4=16Mhz ADC_CHSELR(ADC1) = SENS_CHAN; len = SENS_CNT; if (IO_ANALOG) { ADC_CHSELR(ADC1) |= ANALOG_CHAN << (len++ << 2); ain = 1; } ADC_CHSELR(ADC1) |= (TEMP_CHAN | 0xfd0) << (len << 2); // ADC_IN13 (vref) len += 2; while (!(ADC_ISR(ADC1) & ADC_ISR_CCRDY)); DMA1_CPAR(4) = (uint32_t)&ADC_DR(ADC1); DMA1_CMAR(4) = (uint32_t)buf; } #ifdef LED_WS2812 void initled(void) { RCC_APBENR2 |= RCC_APBENR2_TIM16EN; GPIOB_AFRH |= 0x2; // B8 (TIM16_CH1) GPIOB_MODER &= ~0x10000; // B8 (TIM16_CH1) TIM16_BDTR = TIM_BDTR_MOE; TIM16_CCMR1 = TIM_CCMR1_OC1PE | TIM_CCMR1_OC1M_PWM1; TIM16_CCER = TIM_CCER_CC1E; TIM16_CR2 = TIM_CR2_CCDS; // CC1 DMA request on UEV TIM16_ARR = CLK_CNT(800000) - 1; TIM16_RCR = 7; DMAMUX1_CxCR(6) = DMAMUX_CxCR_DMAREQ_ID_TIM16_CH1; DMA1_CPAR(6) = (uint32_t)&TIM16_CCR1; DMA1_CMAR(6) = (uint32_t)led; } void ledctl(int x) { if (DMA1_CCR(6) & DMA_CCR_EN) return; led[0] = x & 2 ? CLK_CNT(1250000) : CLK_CNT(2500000); // Green led[1] = x & 1 ? CLK_CNT(1250000) : CLK_CNT(2500000); // Red led[2] = x & 4 ? CLK_CNT(1250000) : CLK_CNT(2500000); // Blue led[3] = 0; led[4] = 0; DMA1_CNDTR(6) = 5; DMA1_CCR(6) = DMA_CCR_EN | DMA_CCR_TCIE | DMA_CCR_DIR | DMA_CCR_MINC | DMA_CCR_PSIZE_16BIT | DMA_CCR_MSIZE_16BIT; TIM16_DIER = TIM_DIER_CC1DE; TIM16_EGR = TIM_EGR_UG; TIM16_CR1 = TIM_CR1_CEN; } #endif void hsictl(int x) { int cr = RCC_ICSCR; int tv = (cr & 0x7f00) >> 8; // 7 bits RCC_ICSCR = (cr & ~0x7f00) | clamp(tv + x, 0, 0x7f) << 8; } void compctl(int x) { int id = 0; int cr = 0; switch (x & 3) { #ifdef IO_PA2 case COMP_IN1: id = 1; cr = 0x108281; // A0>A1 break; case COMP_IN2: id = 2; cr = 0x108271; // B7>A3 break; case COMP_IN3: id = 2; cr = 0x108071; // B7>B4 break; #else case COMP_IN1: id = 2; cr = 0x100281; // A3>A2 break; case COMP_IN2: #ifdef IO_PA6 // N version id = 1; cr = 0x100281; // A1>A0 #else id = 2; cr = 0x100261; // A3>B3 #endif break; case COMP_IN3: id = 2; cr = 0x100271; // A3>B7 break; #endif } if (x & 4) cr ^= 0x8000; // Change polarity switch (id) { case 0: COMP1_CSR = 0; COMP2_CSR = 0; TIM2_TISEL = 0; break; #if defined IO_PA2 || defined IO_PA6 case 1: COMP1_CSR = cr; TIM2_TISEL = 0x1; // TI1=COMP1_OUT break; #endif case 2: COMP2_CSR = cr; TIM2_TISEL = 0x100; // TI2=COMP2_OUT break; } } void io_serial(void) { RCC_APBRSTR2 = RCC_APBRSTR2_TIM15RST; RCC_APBRSTR2 = 0; nvic_clear_pending_irq(NVIC_TIM15_IRQ); RCC_APBENR1 |= RCC_APBENR1_USART2EN; GPIOA_AFRL = (GPIOA_AFRL & ~0xf00) | 0x100; // A2 (USART2_TX) #ifdef IO_RXTX GPIOA_AFRH |= 0x10000000; // A15 (USART2_RX) #endif DMAMUX1_CxCR(1) = DMAMUX_CxCR_DMAREQ_ID_USART2_RX; DMAMUX1_CxCR(5) = DMAMUX_CxCR_DMAREQ_ID_USART2_TX; } #ifdef IO_PA6 void io_analog(void) { RCC_APBRSTR1 = RCC_APBRSTR1_TIM3RST; RCC_APBRSTR1 = 0; nvic_clear_pending_irq(NVIC_TIM34_IRQ); GPIOA_PUPDR &= ~0x3000; // A6 (no pull-up/pull-down) GPIOA_MODER |= 0x3000; // A6 (analog) } #else void io_analog(void) { RCC_APBRSTR2 = RCC_APBRSTR2_TIM15RST; RCC_APBRSTR2 = 0; nvic_clear_pending_irq(NVIC_TIM15_IRQ); GPIOA_PUPDR &= ~0x30; // A2 (no pull-up/pull-down) GPIOA_MODER |= 0x30; // A2 (analog) } #endif void adctrig(void) { if (DMA1_CCR(4) & DMA_CCR_EN) return; DMA1_CNDTR(4) = len; DMA1_CCR(4) = DMA_CCR_EN | DMA_CCR_TCIE | DMA_CCR_MINC | DMA_CCR_PSIZE_16BIT | DMA_CCR_MSIZE_16BIT; ADC_CR(ADC1) = ADC_CR_ADSTART | ADC_CR_ADVREGEN; } void dma1_channel4_7_dmamux_isr(void) { #ifdef LED_WS2812 if (DMA1_ISR & DMA_ISR_TCIF(6)) { DMA1_IFCR = DMA_IFCR_CTCIF(6); DMA1_CCR(6) = 0; TIM16_DIER = 0; TIM16_CR1 = 0; return; } #endif DMA1_IFCR = DMA_IFCR_CTCIF(4); DMA1_CCR(4) = 0; int i = 0, u = 0, v = 0, c = 0, a = 0; #if SENS_CNT >= 2 c = buf[i++]; #endif #if SENS_CNT >= 1 v = buf[i++]; #endif #if SENS_CNT >= 3 u = buf[i++]; #endif if (ain) a = buf[i++]; int r = ST_VREFINT_CAL * 3000 / buf[i + 1]; adcdata(TEMP_FUNC(buf[i] * r >> TEMP_SHIFT), u * r >> 12, v * r >> 12, c * r >> 12, a * r >> 12); } ================================================ FILE: mcu/STM32G071/config.cmake ================================================ set(opts -mcpu=cortex-m0plus -mthumb) set(libs opencm3_stm32g0) set(defs STM32G0) ================================================ FILE: mcu/STM32G071/config.h ================================================ /* ** Copyright (C) Arseny Vakhrushev ** ** This firmware is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This firmware is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this firmware. If not, see . */ #pragma once #define CLK 64000000 #define IFTIM TIM2 #define IFTIM_XRES 2 #define IFTIM_ICFL 64 #define IFTIM_ICMR TIM2_CCMR1 #if defined IO_PA2 || defined IO_PA6 #define IFTIM_ICM1 (TIM_CCMR1_CC1S_IN_TI1 | TIM_CCMR1_IC1F_DTF_DIV_8_N_8 | TIM_CCMR1_CC2S_IN_TI2 | TIM_CCMR1_IC2F_DTF_DIV_8_N_8) #define IFTIM_ICM2 (TIM_CCMR1_CC1S_IN_TI1 | TIM_CCMR1_IC1F_DTF_DIV_4_N_8 | TIM_CCMR1_CC2S_IN_TI2 | TIM_CCMR1_IC2F_DTF_DIV_4_N_8) #define IFTIM_ICM3 (TIM_CCMR1_CC1S_IN_TI1 | TIM_CCMR1_IC1F_DTF_DIV_2_N_8 | TIM_CCMR1_CC2S_IN_TI2 | TIM_CCMR1_IC2F_DTF_DIV_2_N_8) #define IFTIM_ICIE (TIM_DIER_CC1IE | TIM_DIER_CC2IE) #define IFTIM_ICR (sr & TIM_SR_CC1IF ? TIM2_CCR1 : TIM2_CCR2) #else #define IFTIM_ICM1 (TIM_CCMR1_CC2S_IN_TI2 | TIM_CCMR1_IC2F_DTF_DIV_8_N_8) #define IFTIM_ICM2 (TIM_CCMR1_CC2S_IN_TI2 | TIM_CCMR1_IC2F_DTF_DIV_4_N_8) #define IFTIM_ICM3 (TIM_CCMR1_CC2S_IN_TI2 | TIM_CCMR1_IC2F_DTF_DIV_2_N_8) #define IFTIM_ICIE TIM_DIER_CC2IE #define IFTIM_ICR TIM2_CCR2 #endif #define IFTIM_OCR TIM2_CCR3 #define iftim_isr tim2_isr #ifdef IO_PA2 #define IOTIM TIM15 #define IOTIM_IDR (GPIOA_IDR & 0x4) // A2 #define iotim_isr tim15_isr #else #define IOTIM TIM3 #ifdef IO_PA6 #define IOTIM_IDR (GPIOA_IDR & 0x40) // A6 #else #define IOTIM_IDR (GPIOB_IDR & 0x10) // B4 #endif #define iotim_isr tim3_isr #endif #define IOTIM_DMA 1 #define iodma_isr dma1_channel1_isr #ifndef IO_AUX #elif IO_AUX == 0xB8 #define IOTIM2 TIM16 #define iotim2_isr tim16_fdcan_it0_isr #elif IO_AUX == 0xB9 #define IOTIM2 TIM17 #define iotim2_isr tim17_fdcan_it1_isr #else #define IOTIM2 TIM3 #define iotim2_isr tim34_isr #endif #define USART1_RX_DMA 2 #define USART1_TX_DMA 3 #define usart1_tx_dma_isr dma1_channel2_3_isr #define USART2_RX_DMA 1 #define USART2_TX_DMA 5 #define usart2_isr usart2_lpuart2_isr #define tim1_com_isr tim1_brk_up_trg_com_isr #define tim3_isr tim34_isr ================================================ FILE: mcu/STM32G071/config.ld ================================================ MEMORY { boot (rx) : ORIGIN = 0x8000000, LENGTH = 4K cfg (rx) : ORIGIN = ORIGIN(boot) + LENGTH(boot), LENGTH = 2K rom (rx) : ORIGIN = ORIGIN(cfg) + LENGTH(cfg), LENGTH = 64K - LENGTH(boot) - LENGTH(cfg) ram (rwx) : ORIGIN = 0x20000000, LENGTH = 8K } ================================================ FILE: mcu/STM32G071/preset.c ================================================ /* ** Copyright (C) Arseny Vakhrushev ** ** This firmware is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This firmware is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this firmware. If not, see . */ #include "common.h" const uint16_t sinedata[] = { 213, 217, 220, 224, 228, 232, 235, 239, 243, 246, 250, 254, 257, 261, 265, 268, 272, 275, 279, 282, 286, 289, 293, 296, 300, 303, 306, 310, 313, 316, 320, 323, 326, 329, 332, 335, 338, 341, 344, 347, 350, 353, 356, 358, 361, 364, 366, 369, 371, 374, 376, 379, 381, 383, 385, 387, 390, 392, 394, 396, 397, 399, 401, 403, 404, 406, 408, 409, 410, 412, 413, 414, 416, 417, 418, 419, 420, 421, 421, 422, 423, 423, 424, 424, 425, 425, 425, 426, 426, 426, 426, 426, 426, 426, 425, 425, 425, 424, 424, 423, 423, 422, 421, 421, 420, 419, 418, 417, 416, 414, 413, 412, 410, 409, 408, 406, 404, 403, 401, 399, 397, 396, 394, 392, 390, 387, 385, 383, 381, 379, 376, 374, 371, 369, 366, 364, 361, 358, 356, 353, 350, 347, 344, 341, 338, 335, 332, 329, 326, 323, 320, 316, 313, 310, 306, 303, 300, 296, 293, 289, 286, 282, 279, 275, 272, 268, 265, 261, 257, 254, 250, 246, 243, 239, 235, 232, 228, 224, 220, 217, 213, 209, 206, 202, 198, 194, 191, 187, 183, 180, 176, 172, 169, 165, 161, 158, 154, 151, 147, 144, 140, 137, 133, 130, 126, 123, 120, 116, 113, 110, 106, 103, 100, 97, 94, 91, 88, 85, 82, 79, 76, 73, 70, 68, 65, 62, 60, 57, 55, 52, 50, 47, 45, 43, 41, 39, 36, 34, 32, 30, 29, 27, 25, 23, 22, 20, 18, 17, 16, 14, 13, 12, 10, 9, 8, 7, 6, 5, 5, 4, 3, 3, 2, 2, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 3, 3, 4, 5, 5, 6, 7, 8, 9, 10, 12, 13, 14, 16, 17, 18, 20, 22, 23, 25, 27, 29, 30, 32, 34, 36, 39, 41, 43, 45, 47, 50, 52, 55, 57, 60, 62, 65, 68, 70, 73, 76, 79, 82, 85, 88, 91, 94, 97, 100, 103, 106, 110, 113, 116, 120, 123, 126, 130, 133, 137, 140, 144, 147, 151, 154, 158, 161, 165, 169, 172, 176, 180, 183, 187, 191, 194, 198, 202, 206, 209, }; ================================================ FILE: mcu/STM32G431/config.c ================================================ /* ** Copyright (C) Arseny Vakhrushev ** ** This firmware is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This firmware is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this firmware. If not, see . */ #include #include #include "common.h" #if SENS_MAP == 0xA6 // A6 (volt) #define SENS_CHAN 0x3 #elif SENS_MAP == 0xA6F1 // A6 (volt), F1 (curr) #define SENS_CHAN 0xca #elif SENS_MAP == 0xBFA6 // B15 (volt), A6 (curr) #define SENS_CHAN 0x3c3 #elif SENS_MAP == 0xB2A6A7 // B2 (temp), A6 (volt), A7 (curr) #define SENS_CHAN 0xc0c4 #endif #ifdef TEMP_CHAN #define TEMP_SHIFT 12 #else #define TEMP_SHIFT 0 #define TEMP_CHAN 0x10 // ADC1_IN16 (temp) #define TEMP_FUNC(x) (((x) / 3000 - ST_TSENSE_CAL1_30C) * 400 / (ST_TSENSE_CAL2_130C - ST_TSENSE_CAL1_30C) + 120) #endif #define COMP1_CSR MMIO32(COMP_BASE + 0x0) #define COMP2_CSR MMIO32(COMP_BASE + 0x4) #define TIM1_CCMR3 MMIO32(TIM1_BASE + 0x50) #define TIM2_TISEL MMIO32(TIM2_BASE + 0x5c) static char len1, len2, ain; static uint16_t buf[6]; #ifdef LED_WS2812 static uint16_t led[5]; #endif void init(void) { RCC_AHB1RSTR = -1; RCC_AHB2RSTR = -1; RCC_AHB3RSTR = -1; RCC_APB1RSTR1 = -1; RCC_APB1RSTR2 = -1; RCC_APB2RSTR = -1; RCC_AHB1RSTR = 0; RCC_AHB2RSTR = 0; RCC_AHB3RSTR = 0; RCC_APB1RSTR1 = 0; RCC_APB1RSTR2 = 0; RCC_APB2RSTR = 0; RCC_AHB1ENR = RCC_AHB1ENR_DMA1EN | RCC_AHB1ENR_DMA2EN | RCC_AHB1ENR_DMAMUX1EN | RCC_AHB1ENR_FLASHEN; RCC_AHB2ENR = RCC_AHB2ENR_GPIOAEN | RCC_AHB2ENR_GPIOBEN | RCC_AHB2ENR_GPIOCEN | RCC_AHB2ENR_GPIOFEN | RCC_AHB2ENR_ADC12EN; RCC_APB1ENR1 = RCC_APB1ENR1_TIM2EN | RCC_APB1ENR1_TIM6EN | RCC_APB1ENR1_WWDGEN | RCC_APB1ENR1_PWREN; RCC_APB2ENR = RCC_APB2ENR_SYSCFGEN | RCC_APB2ENR_TIM1EN | RCC_APB2ENR_USART1EN; PWR_CR3 = PWR_CR3_UCPD1_DBDIS; // Disable internal pull-downs on B4/B6 SCB_VTOR = (uint32_t)_rom; // Set vector table address RCC_CFGR = RCC_CFGR_SWx_HSI16; while ((RCC_CFGR & RCC_CFGR_SWS_MASK << RCC_CFGR_SWS_SHIFT) != RCC_CFGR_SWx_HSI16 << RCC_CFGR_SWS_SHIFT); RCC_CR &= ~RCC_CR_PLLON; while (RCC_CR & RCC_CR_PLLRDY); #ifdef USE_HSE if (!cfg.throt_cal) { TIM6_PSC = 15; // 1us resolution @ HSI16 TIM6_ARR = 9999; TIM6_EGR = TIM_EGR_UG; TIM6_SR = ~TIM_SR_UIF; TIM6_CR1 = TIM_CR1_CEN | TIM_CR1_OPM; RCC_CR |= RCC_CR_HSEON; while (!(RCC_CR & RCC_CR_HSERDY)) { if (!(TIM6_CR1 & TIM_CR1_CEN)) { // Timeout 10ms RCC_CR &= ~RCC_CR_HSEON; goto skip; } } RCC_PLLCFGR = RCC_PLLCFGR_PLLSRC_HSE | (336 / USE_HSE) << RCC_PLLCFGR_PLLN_SHIFT | RCC_PLLCFGR_PLLREN; } else skip: #endif RCC_PLLCFGR = RCC_PLLCFGR_PLLSRC_HSI16 | 21 << RCC_PLLCFGR_PLLN_SHIFT | RCC_PLLCFGR_PLLREN; RCC_CR |= RCC_CR_PLLON; while (!(RCC_CR & RCC_CR_PLLRDY)); RCC_CFGR = RCC_CFGR_SWx_PLL | RCC_CFGR_HPRE_DIV2 << RCC_CFGR_HPRE_SHIFT; PWR_CR5 = 0; // R1MODE=0 (boost mode) TIM6_PSC = 0; TIM6_ARR = CLK_MHZ / 2 - 1; TIM6_EGR = TIM_EGR_UG; TIM6_SR = ~TIM_SR_UIF; TIM6_CR1 = TIM_CR1_CEN | TIM_CR1_OPM; while (TIM6_CR1 & TIM_CR1_CEN); // Ensure 1us HCLK/2 transition period (RM 7.2.7) RCC_CFGR = RCC_CFGR_SWx_PLL; // Default GPIO state - analog input GPIOA_AFRH = 0x00000666; // A8 (TIM1_CH1), A9 (TIM1_CH2), A10 (TIM1_CH3) GPIOB_AFRL = 0x07000000; // B6 (USART1_TX) GPIOA_PUPDR = 0x24000000; GPIOB_PUPDR = 0x00001000; // B6 (pull-up) GPIOA_MODER = 0xebeaffff; // A8 (TIM1_CH1), A9 (TIM1_CH2), A10 (TIM1_CH3) GPIOB_MODER = 0xffffefff; // B6 (USART1_TX) #ifdef USE_XOR GPIOB_AFRH |= 0x46600000; // B13 (TIM1_CH1N), B14 (TIM1_CH2N), B15 (TIM1_CH3N) GPIOB_MODER &= ~0x54000000; // B13 (TIM1_CH1N), B14 (TIM1_CH2N), B15 (TIM1_CH3N) #else GPIOA_AFRL |= 0x60000000; // A7 (TIM1_CH1N) GPIOB_AFRL |= 0x6; // B0 (TIM1_CH2N) GPIOA_MODER &= ~0x4000; // A7 (TIM1_CH1N) GPIOB_MODER &= ~0x1; // B0 (TIM1_CH2N) #ifdef USE_PB1 GPIOB_AFRL |= 0x60; // B1 (TIM1_CH3N) GPIOB_MODER &= ~0x4; // B1 (TIM1_CH3N) #else GPIOF_AFRL |= 0x6; // F0 (TIM1_CH3N) GPIOF_MODER &= ~0x1; // F0 (TIM1_CH3N) #endif #endif #ifdef HALL_MAP RCC_APB1ENR1 |= RCC_APB1ENR1_TIM3EN; #ifdef USE_XOR GPIOB_AFRL |= 0x220002; // B0 (TIM3_CH3), B4 (TIM3_CH1), B5 (TIM3_CH2) GPIOB_PUPDR |= 0x501; // B0,B4,B5 (pull-up) GPIOB_MODER &= ~0x501; // B0 (TIM3_CH3), B4 (TIM3_CH1), B5 (TIM3_CH2) #else GPIOB_AFRL |= 0x20000; // B4 (TIM3_CH1) GPIOB_MODER &= ~0x100; // B4 (TIM3_CH1) #endif #endif #ifndef ANALOG RCC_APB2ENR |= RCC_APB2ENR_TIM15EN; GPIOA_AFRL |= 0x900; // A2 (TIM15_CH1) GPIOA_PUPDR |= 0x10; // A2 (pull-up) GPIOA_MODER &= ~0x10; // A2 (TIM15_CH1) nvic_set_priority(NVIC_TIM1_BRK_TIM15_IRQ, 0x40); #ifdef IO_AUX RCC_APB2ENR |= RCC_APB2ENR_TIM8EN; GPIOA_AFRH |= 0x20000000; // A15 (TIM8_CH1) GPIOA_PUPDR |= 0x40000000; // A15 (pull-up) GPIOA_MODER &= ~0x40000000; // A15 (TIM8_CH1) nvic_set_priority(NVIC_TIM8_CC_IRQ, 0x40); #endif #endif nvic_set_priority(NVIC_USART1_IRQ, 0x80); nvic_set_priority(NVIC_USART2_IRQ, 0x40); nvic_set_priority(NVIC_DMA1_CHANNEL1_IRQ, 0x40); // TIM15 or USART2_RX nvic_set_priority(NVIC_DMA1_CHANNEL3_IRQ, 0x80); // USART1_TX nvic_set_priority(NVIC_DMA1_CHANNEL4_IRQ, 0x80); // ADC1 nvic_set_priority(NVIC_DMA1_CHANNEL5_IRQ, 0x80); // ADC2 nvic_set_priority(NVIC_DMA2_CHANNEL1_IRQ, 0x80); // WS2812 nvic_enable_irq(NVIC_TIM1_BRK_TIM15_IRQ); nvic_enable_irq(NVIC_TIM1_TRG_TIM17_IRQ); nvic_enable_irq(NVIC_TIM2_IRQ); nvic_enable_irq(NVIC_TIM3_IRQ); nvic_enable_irq(NVIC_TIM8_CC_IRQ); nvic_enable_irq(NVIC_USART1_IRQ); nvic_enable_irq(NVIC_USART2_IRQ); nvic_enable_irq(NVIC_DMA1_CHANNEL1_IRQ); nvic_enable_irq(NVIC_DMA1_CHANNEL3_IRQ); nvic_enable_irq(NVIC_DMA1_CHANNEL4_IRQ); nvic_enable_irq(NVIC_DMA1_CHANNEL5_IRQ); nvic_enable_irq(NVIC_DMA2_CHANNEL1_IRQ); DMAMUX1_CxCR(1) = DMAMUX_CxCR_DMAREQ_ID_TIM15_CH1; DMAMUX1_CxCR(2) = DMAMUX_CxCR_DMAREQ_ID_UART1_RX; DMAMUX1_CxCR(3) = DMAMUX_CxCR_DMAREQ_ID_UART1_TX; DMAMUX1_CxCR(4) = DMAMUX_CxCR_DMAREQ_ID_ADC1; DMAMUX1_CxCR(5) = DMAMUX_CxCR_DMAREQ_ID_ADC2; TIM1_CCMR3 = 0x68; // OC5PE=1, OC5M=PWM1 TIM1_SMCR = TIM_SMCR_TS_ITR1; // TRGI=TIM2 TIM2_CR2 = TIM_CR2_MMS_COMPARE_OC3REF; // TRGO=OC3REF TIM2_CCMR1 = TIM_CCMR1_CC1S_IN_TI1; TIM2_CCMR2 = TIM_CCMR2_OC3PE | TIM_CCMR2_OC3M_PWM2; // Inverted PWM on OC3 TIM2_CCER = TIM_CCER_CC1E; // IC1 on rising edge on TI1 (COMPx_OUT) ADC_CCR(ADC1) = ADC_CCR_VREFEN | ADC_CCR_TSEN | ADC_CCR_CKMODE_DIV4; ADC_CR(ADC1) = 0; // DEEPPWD=0 ADC_CR(ADC2) = 0; // DEEPPWD=0 ADC_CR(ADC1) = ADC_CR_ADVREGEN; ADC_CR(ADC2) = ADC_CR_ADVREGEN; TIM6_ARR = CLK_KHZ / 50 - 1; TIM6_SR = ~TIM_SR_UIF; TIM6_CR1 = TIM_CR1_CEN | TIM_CR1_OPM; while (TIM6_CR1 & TIM_CR1_CEN); // Wait for 20us (RM 21.4.6) ADC_CR(ADC1) = ADC_CR_ADVREGEN | ADC_CR_ADCAL; ADC_CR(ADC2) = ADC_CR_ADVREGEN | ADC_CR_ADCAL; while (ADC_CR(ADC1) & ADC_CR_ADCAL); while (ADC_CR(ADC2) & ADC_CR_ADCAL); while (ADC_CR(ADC1) = ADC_CR_ADEN | ADC_CR_ADVREGEN, !(ADC_ISR(ADC1) & ADC_ISR_ADRDY)); // Keep powering on until ready (RM 21.4.9) while (ADC_CR(ADC2) = ADC_CR_ADEN | ADC_CR_ADVREGEN, !(ADC_ISR(ADC2) & ADC_ISR_ADRDY)); ADC_CFGR1(ADC1) = ADC_CFGR1_DMAEN | ADC12_CFGR1_EXTSEL_TIM1_TRGO | ADC_CFGR1_EXTEN_RISING_EDGE; ADC_CFGR1(ADC2) = ADC_CFGR1_DMAEN | ADC12_CFGR1_EXTSEL_TIM1_TRGO | ADC_CFGR1_EXTEN_RISING_EDGE; ADC_SMPR1(ADC1) = -1; // Sampling time ~15us @ HCLK/4=42Mhz ADC_SMPR1(ADC2) = -1; ADC_SMPR2(ADC1) = -1; ADC_SMPR2(ADC2) = -1; uint64_t seq1 = 0; uint64_t seq2 = SENS_CHAN; len1 = 0; len2 = SENS_CNT; if (IO_ANALOG) { #ifdef ANALOG_CHAN #ifdef ANALOG_ADC2 seq2 |= ANALOG_CHAN << (len2++ * 6); #else seq1 = ANALOG_CHAN; len1 = 1; #endif #else seq1 = 0x3; // ADC1_IN3 (A2) len1 = 1; #endif ain = 1; } #ifdef TEMP_ADC2 seq2 |= TEMP_CHAN << (len2++ * 6); #else seq1 |= TEMP_CHAN << (len1++ * 6); #endif seq1 |= 0x12 << (len1++ * 6); // ADC1_IN18 (vref) ADC_SQR1(ADC1) = seq1 << 6 | (len1 - 1); ADC_SQR1(ADC2) = seq2 << 6 | (len2 - 1); ADC_SQR2(ADC1) = seq1 >> 24; ADC_SQR2(ADC2) = seq2 >> 24; DMA1_CPAR(4) = (uint32_t)&ADC_DR(ADC1); DMA1_CMAR(4) = (uint32_t)(buf + len2); DMA1_CPAR(5) = (uint32_t)&ADC_DR(ADC2); DMA1_CMAR(5) = (uint32_t)buf; } #ifdef LED_WS2812 void initled(void) { RCC_APB2ENR |= RCC_APB2ENR_TIM16EN; GPIOB_AFRH |= 0x1; // B8 (TIM16_CH1) GPIOB_MODER &= ~0x10000; // B8 (TIM16_CH1) TIM16_BDTR = TIM_BDTR_MOE; TIM16_CCMR1 = TIM_CCMR1_OC1PE | TIM_CCMR1_OC1M_PWM1; TIM16_CCER = TIM_CCER_CC1E; TIM16_CR2 = TIM_CR2_CCDS; // CC1 DMA request on UEV TIM16_ARR = CLK_CNT(800000) - 1; TIM16_RCR = 7; DMAMUX1_CxCR(9) = DMAMUX_CxCR_DMAREQ_ID_TIM16_CH1; DMA2_CPAR(1) = (uint32_t)&TIM16_CCR1; DMA2_CMAR(1) = (uint32_t)led; } void ledctl(int x) { if (DMA2_CCR(1) & DMA_CCR_EN) return; led[0] = x & 2 ? CLK_CNT(1250000) : CLK_CNT(2500000); // Green led[1] = x & 1 ? CLK_CNT(1250000) : CLK_CNT(2500000); // Red led[2] = x & 4 ? CLK_CNT(1250000) : CLK_CNT(2500000); // Blue led[3] = 0; led[4] = 0; DMA2_CNDTR(1) = 5; DMA2_CCR(1) = DMA_CCR_EN | DMA_CCR_TCIE | DMA_CCR_DIR | DMA_CCR_MINC | DMA_CCR_PSIZE_16BIT | DMA_CCR_MSIZE_16BIT; TIM16_DIER = TIM_DIER_CC1DE; TIM16_EGR = TIM_EGR_UG; TIM16_CR1 = TIM_CR1_CEN; } #endif void hsictl(int x) { int cr = RCC_ICSCR; int tv = (cr & 0x7f000000) >> 24; // 7 bits RCC_ICSCR = (cr & ~0x7f000000) | clamp(tv + x, 0, 0x7f) << 24; } void compctl(int x) { int id = 0; int cr = 0; switch (x & 3) { case COMP_IN1: id = 1; cr = 0x80071; // A1>A0 break; case COMP_IN2: id = 1; cr = 0x80061; // A1>A4 break; case COMP_IN3: id = 2; cr = 0x80161; // A3>A5 break; } if (x & 4) cr |= 0x8000; // Change polarity switch (id) { case 0: COMP1_CSR = 0; COMP2_CSR = 0; TIM2_TISEL = 0; break; case 1: COMP1_CSR = cr; TIM2_TISEL = 0x1; // TI1=COMP1_OUT break; case 2: COMP2_CSR = cr; TIM2_TISEL = 0x2; // TI1=COMP2_OUT break; } } void io_serial(void) { RCC_APB2RSTR = RCC_APB2RSTR_TIM15RST; RCC_APB2RSTR = 0; nvic_clear_pending_irq(NVIC_TIM1_BRK_TIM15_IRQ); RCC_APB1ENR1 |= RCC_APB1ENR1_USART2EN; GPIOA_AFRL = (GPIOA_AFRL & ~0xf00) | 0x700; // A2 (USART2_TX) #ifdef IO_RXTX GPIOA_AFRH |= 0x70000000; // A15 (USART2_RX) #endif DMAMUX1_CxCR(1) = DMAMUX_CxCR_DMAREQ_ID_UART2_RX; DMAMUX1_CxCR(6) = DMAMUX_CxCR_DMAREQ_ID_UART2_TX; } void io_analog(void) { RCC_APB2RSTR = RCC_APB2RSTR_TIM15RST; RCC_APB2RSTR = 0; nvic_clear_pending_irq(NVIC_TIM1_BRK_TIM15_IRQ); GPIOA_PUPDR &= ~0x30; // A2 (no pull-up/pull-down) GPIOA_MODER |= 0x30; // A2 (analog) } void adctrig(void) { if ((DMA1_CCR(4) & DMA_CCR_EN) || (DMA1_CCR(5) & DMA_CCR_EN)) return; DMA1_CNDTR(4) = len1; DMA1_CCR(4) = DMA_CCR_EN | DMA_CCR_TCIE | DMA_CCR_MINC | DMA_CCR_PSIZE_16BIT | DMA_CCR_MSIZE_16BIT; ADC_CR(ADC1) = ADC_CR_ADSTART | ADC_CR_ADVREGEN; if (!len2) return; DMA1_CNDTR(5) = len2; DMA1_CCR(5) = DMA_CCR_EN | DMA_CCR_TCIE | DMA_CCR_MINC | DMA_CCR_PSIZE_16BIT | DMA_CCR_MSIZE_16BIT; ADC_CR(ADC2) = ADC_CR_ADSTART | ADC_CR_ADVREGEN; } static void adcdma(void) { int i = 0, u = 0, v = 0, c = 0, a = 0; #if SENS_CNT >= 2 c = buf[i++]; #endif #if SENS_CNT >= 1 v = buf[i++]; #endif #if SENS_CNT >= 3 u = buf[i++]; #endif #if !defined ANALOG_ADC2 && defined TEMP_ADC2 int t = buf[i++]; if (ain) a = buf[i++]; #else if (ain) a = buf[i++]; int t = buf[i++]; #endif int r = ST_VREFINT_CAL * 3000 / buf[i]; adcdata(TEMP_FUNC(t * r >> TEMP_SHIFT), u * r >> 12, v * r >> 12, c * r >> 12, a * r >> 12); } void dma1_channel4_isr(void) { DMA1_IFCR = DMA_IFCR_CTCIF(4); DMA1_CCR(4) = 0; if (DMA1_CCR(5) & DMA_CCR_EN) return; adcdma(); } void dma1_channel5_isr(void) { DMA1_IFCR = DMA_IFCR_CTCIF(5); DMA1_CCR(5) = 0; if (DMA1_CCR(4) & DMA_CCR_EN) return; adcdma(); } #ifdef LED_WS2812 void dma2_channel1_isr(void) { DMA2_IFCR = DMA_IFCR_CTCIF(1); DMA2_CCR(1) = 0; TIM16_DIER = 0; TIM16_CR1 = 0; } #endif ================================================ FILE: mcu/STM32G431/config.cmake ================================================ set(opts -mcpu=cortex-m4 -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16) set(libs opencm3_stm32g4) set(defs STM32G4) ================================================ FILE: mcu/STM32G431/config.h ================================================ /* ** Copyright (C) Arseny Vakhrushev ** ** This firmware is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This firmware is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this firmware. If not, see . */ #pragma once #define CLK 168000000 #define IO_PA2 #define IFTIM TIM2 #define IFTIM_XRES 2 #define IFTIM_ICFL 256 #define IFTIM_ICMR TIM2_CCMR1 #define IFTIM_ICM1 (TIM_CCMR1_CC1S_IN_TI1 | TIM_CCMR1_IC1F_DTF_DIV_32_N_8) #define IFTIM_ICM2 (TIM_CCMR1_CC1S_IN_TI1 | TIM_CCMR1_IC1F_DTF_DIV_16_N_8) #define IFTIM_ICM3 (TIM_CCMR1_CC1S_IN_TI1 | TIM_CCMR1_IC1F_DTF_DIV_8_N_8) #define IFTIM_ICIE TIM_DIER_CC1IE #define IFTIM_ICR TIM2_CCR1 #define IFTIM_OCR TIM2_CCR3 #define iftim_isr tim2_isr #define IOTIM TIM15 #define IOTIM_IDR (GPIOA_IDR & 0x4) // A2 #define IOTIM_DMA 1 #define iotim_isr tim1_brk_tim15_isr #define iodma_isr dma1_channel1_isr #define IOTIM2 TIM8 #define iotim2_isr tim8_cc_isr #define USART1_RX_DMA 2 #define USART1_TX_DMA 3 #define usart1_tx_dma_isr dma1_channel3_isr #define USART2_RX_DMA 1 #define USART2_TX_DMA 6 #define tim1_com_isr tim1_trg_tim17_isr #define TIM1_CCR5 MMIO32(TIM1_BASE + 0x48) #define TIM_CCER_CC5E 0x10000 ================================================ FILE: mcu/STM32G431/config.ld ================================================ MEMORY { boot (rx) : ORIGIN = 0x8000000, LENGTH = 4K cfg (rx) : ORIGIN = ORIGIN(boot) + LENGTH(boot), LENGTH = 2K rom (rx) : ORIGIN = ORIGIN(cfg) + LENGTH(cfg), LENGTH = 64K - LENGTH(boot) - LENGTH(cfg) ram (rwx) : ORIGIN = 0x20000000, LENGTH = 8K } ================================================ FILE: mcu/STM32G431/preset.c ================================================ /* ** Copyright (C) Arseny Vakhrushev ** ** This firmware is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This firmware is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this firmware. If not, see . */ #include "common.h" const uint16_t sinedata[] = { 560, 570, 580, 589, 599, 609, 619, 628, 638, 648, 657, 667, 676, 686, 695, 705, 714, 724, 733, 742, 752, 761, 770, 779, 788, 797, 805, 814, 823, 831, 840, 848, 857, 865, 873, 881, 889, 897, 905, 912, 920, 927, 935, 942, 949, 956, 963, 970, 976, 983, 989, 995, 1001, 1007, 1013, 1019, 1024, 1030, 1035, 1040, 1045, 1050, 1054, 1059, 1063, 1068, 1072, 1075, 1079, 1083, 1086, 1089, 1093, 1096, 1098, 1101, 1103, 1106, 1108, 1110, 1111, 1113, 1115, 1116, 1117, 1118, 1119, 1119, 1120, 1120, 1120, 1120, 1120, 1119, 1119, 1118, 1117, 1116, 1115, 1113, 1111, 1110, 1108, 1106, 1103, 1101, 1098, 1096, 1093, 1089, 1086, 1083, 1079, 1075, 1072, 1068, 1063, 1059, 1054, 1050, 1045, 1040, 1035, 1030, 1024, 1019, 1013, 1007, 1001, 995, 989, 983, 976, 970, 963, 956, 949, 942, 935, 927, 920, 912, 905, 897, 889, 881, 873, 865, 857, 848, 840, 831, 823, 814, 805, 797, 788, 779, 770, 761, 752, 742, 733, 724, 714, 705, 695, 686, 676, 667, 657, 648, 638, 628, 619, 609, 599, 589, 580, 570, 560, 550, 540, 531, 521, 511, 501, 492, 482, 472, 463, 453, 444, 434, 425, 415, 406, 396, 387, 378, 368, 359, 350, 341, 332, 323, 315, 306, 297, 289, 280, 272, 263, 255, 247, 239, 231, 223, 215, 208, 200, 193, 185, 178, 171, 164, 157, 150, 144, 137, 131, 125, 119, 113, 107, 101, 96, 90, 85, 80, 75, 70, 66, 61, 57, 52, 48, 45, 41, 37, 34, 31, 27, 24, 22, 19, 17, 14, 12, 10, 9, 7, 5, 4, 3, 2, 1, 1, 0, 0, 0, 0, 0, 1, 1, 2, 3, 4, 5, 7, 9, 10, 12, 14, 17, 19, 22, 24, 27, 31, 34, 37, 41, 45, 48, 52, 57, 61, 66, 70, 75, 80, 85, 90, 96, 101, 107, 113, 119, 125, 131, 137, 144, 150, 157, 164, 171, 178, 185, 193, 200, 208, 215, 223, 231, 239, 247, 255, 263, 272, 280, 289, 297, 306, 315, 323, 332, 341, 350, 359, 368, 378, 387, 396, 406, 415, 425, 434, 444, 453, 463, 472, 482, 492, 501, 511, 521, 531, 540, 550, }; ================================================ FILE: mcu/STM32L431/config.c ================================================ /* ** Copyright (C) Arseny Vakhrushev ** ** This firmware is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This firmware is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this firmware. If not, see . */ #include #include #include "common.h" #if SENS_MAP == 0xA6 // A6 (volt) #define SENS_CHAN 0xb #elif SENS_MAP == 0xA6A3 // A6 (volt), A3 (curr) #define SENS_CHAN 0x2c8 #endif #ifndef ANALOG_CHAN #define ANALOG_CHAN 0x7 // ADC_IN7 (A2) #endif #ifdef TEMP_CHAN #define TEMP_SHIFT 12 #else #define TEMP_SHIFT 0 #define TEMP_CHAN 0x11 // ADC_IN17 (temp) #define TEMP_FUNC(x) (((x) / 3000 - ST_TSENSE_CAL1_30C) * 400 / (ST_TSENSE_CAL2_110C - ST_TSENSE_CAL1_30C) + 120) #endif #ifdef USE_COMP2 #define COMP_CSR MMIO32(COMP_BASE + 0x4) #else #define COMP_CSR MMIO32(COMP_BASE + 0x0) #define TIM1_CCMR3 MMIO32(TIM1_BASE + 0x54) #endif #define TIM2_OR1 MMIO32(TIM2_BASE + 0x50) static char len, ain; static uint16_t buf[6]; void init(void) { RCC_AHB1RSTR = -1; RCC_AHB2RSTR = -1; RCC_AHB3RSTR = -1; RCC_APB1RSTR1 = -1; RCC_APB1RSTR2 = -1; RCC_APB2RSTR = -1; RCC_AHB1RSTR = 0; RCC_AHB2RSTR = 0; RCC_AHB3RSTR = 0; RCC_APB1RSTR1 = 0; RCC_APB1RSTR2 = 0; RCC_APB2RSTR = 0; RCC_AHB1ENR = RCC_AHB1ENR_DMA1EN | RCC_AHB1ENR_DMA2EN | RCC_AHB1ENR_FLASHEN; RCC_AHB2ENR = RCC_AHB2ENR_GPIOAEN | RCC_AHB2ENR_GPIOBEN | RCC_AHB2ENR_GPIOCEN | RCC_AHB2ENR_ADCEN; RCC_APB1ENR1 = RCC_APB1ENR1_TIM2EN | RCC_APB1ENR1_TIM6EN | 0x800; // WWDGEN=1 RCC_APB2ENR = RCC_APB2ENR_SYSCFGEN | RCC_APB2ENR_TIM1EN | RCC_APB2ENR_USART1EN; SCB_VTOR = (uint32_t)_rom; // Set vector table address // Default GPIO state - analog input GPIOA_AFRL = 0x10000000; // A7 (TIM1_CH1N) GPIOA_AFRH = 0x00000111; // A8 (TIM1_CH1), A9 (TIM1_CH2), A10 (TIM1_CH3) GPIOB_AFRL = 0x07000011; // B0 (TIM1_CH2N), B1 (TIM1_CH3N), B6 (USART1_TX) GPIOA_PUPDR = 0x24000000; GPIOB_PUPDR = 0x00001000; // B6 (pull-up) GPIOA_MODER = 0xebeabfff; // A7 (TIM1_CH1N), A8 (TIM1_CH1), A9 (TIM1_CH2), A10 (TIM1_CH3) GPIOB_MODER = 0xffffeffa; // B0 (TIM1_CH2N), B1 (TIM1_CH3N), B6 (USART1_TX) #ifndef ANALOG RCC_APB2ENR |= RCC_APB2ENR_TIM15EN; GPIOA_AFRL |= 0xe00; // A2 (TIM15_CH1) GPIOA_PUPDR |= 0x10; // A2 (pull-up) GPIOA_MODER &= ~0x10; // A2 (TIM15_CH1) nvic_set_priority(NVIC_TIM1_BRK_TIM15_IRQ, 0x40); #endif nvic_set_priority(NVIC_USART1_IRQ, 0x80); nvic_set_priority(NVIC_USART2_IRQ, 0x40); nvic_set_priority(NVIC_DMA1_CHANNEL1_IRQ, 0x80); // ADC nvic_set_priority(NVIC_DMA1_CHANNEL5_IRQ, 0x40); // TIM15 nvic_set_priority(NVIC_DMA1_CHANNEL6_IRQ, 0x40); // USART2_RX nvic_set_priority(NVIC_DMA2_CHANNEL6_IRQ, 0x80); // USART1_TX nvic_enable_irq(NVIC_TIM1_BRK_TIM15_IRQ); nvic_enable_irq(NVIC_TIM1_UP_TIM16_IRQ); nvic_enable_irq(NVIC_TIM1_TRG_COM_TIM17_IRQ); nvic_enable_irq(NVIC_TIM1_CC_IRQ); nvic_enable_irq(NVIC_TIM2_IRQ); nvic_enable_irq(NVIC_USART1_IRQ); nvic_enable_irq(NVIC_USART2_IRQ); nvic_enable_irq(NVIC_DMA1_CHANNEL1_IRQ); nvic_enable_irq(NVIC_DMA1_CHANNEL5_IRQ); nvic_enable_irq(NVIC_DMA1_CHANNEL6_IRQ); nvic_enable_irq(NVIC_DMA2_CHANNEL6_IRQ); DMA1_CSELR = 0x2270000; // C5S=TIM15, C6S=USART2_RX, C7S=USART2_TX DMA2_CSELR = 0x2200000; // C6S=USART1_TX, C7S=USART1_RX #ifndef USE_COMP2 TIM1_CCMR3 = 0x68; // OC5PE=1, OC5M=PWM1 TIM2_OR1 = 0x4; // TI4=COMP1_OUT #elif defined USE_OPAMP RCC_APB1ENR1 |= RCC_APB1ENR1_OPAMPEN; OPAMP1_CSR = 0x39; // OPAEN=1, OPAMODE=PGA, PGA_GAIN=16 #endif TIM1_SMCR = TIM_SMCR_TS_ITR1; // TRGI=TIM2 TIM2_CR2 = TIM_CR2_MMS_COMPARE_OC1REF; // TRGO=OC1REF TIM2_CCMR1 = TIM_CCMR1_OC1PE | TIM_CCMR1_OC1M_PWM2; // Inverted PWM on OC1 TIM2_CCMR2 = TIM_CCMR2_CC4S_IN_TI4; TIM2_CCER = TIM_CCER_CC4E; // IC4 on rising edge on TI4 (COMPx_OUT) ADC_CCR(ADC1) = ADC_CCR_VREFEN | ADC_CCR_TSEN | 0x20000; // CKMODE=HCLK/2 ADC_CR(ADC1) = 0; // DEEPPWD=0 ADC_CR(ADC1) = ADC_CR_ADVREGEN; TIM6_ARR = CLK_KHZ / 50 - 1; TIM6_CR1 = TIM_CR1_CEN | TIM_CR1_OPM; while (TIM6_CR1 & TIM_CR1_CEN); // Wait for 20us (RM 16.4.6) ADC_CR(ADC1) = ADC_CR_ADVREGEN | ADC_CR_ADCAL; while (ADC_CR(ADC1) & ADC_CR_ADCAL); while (ADC_CR(ADC1) = ADC_CR_ADEN | ADC_CR_ADVREGEN, !(ADC_ISR(ADC1) & ADC_ISR_ADRDY)); // Keep powering on until ready (RM 16.4.9) ADC_CFGR1(ADC1) = ADC_CFGR1_DMAEN | ADC_CFGR1_EXTSEL_VAL(9) | ADC_CFGR1_EXTEN_RISING_EDGE; // EXTSEL=TIM1_TRGO ADC_SMPR1(ADC1) = -1; // Sampling time ~16us @ HCLK/2=40Mhz ADC_SMPR2(ADC1) = -1; uint64_t seq = SENS_CHAN; len = SENS_CNT; if (IO_ANALOG) { seq |= ANALOG_CHAN << (len++ * 6); ain = 1; } seq |= TEMP_CHAN << (len * 6); // ADC_IN0 (vref) len += 2; ADC_SQR1(ADC1) = seq << 6 | (len - 1); ADC_SQR2(ADC1) = seq >> 24; DMA1_CPAR(1) = (uint32_t)&ADC_DR(ADC1); DMA1_CMAR(1) = (uint32_t)buf; } void hsictl(int x) { int cr = RCC_ICSCR; int tv = (cr & 0x7f000000) >> 24; // 7 bits RCC_ICSCR = (cr & ~0x7f000000) | ((tv + x) & 0x7f) << 24; } void compctl(int x) { int cr = 0; switch (x & 3) { #ifdef USE_COMP2 case COMP_IN1: cr = 0x4000071; // B4>A4 break; case COMP_IN2: cr = 0x6000071; // B4>A5 break; case COMP_IN3: cr = 0x71; // B4>B7 break; #else case COMP_IN1: cr = 0x2000071; // A1>A0 break; case COMP_IN2: cr = 0x4000071; // A1>A4 break; case COMP_IN3: cr = 0x6000071; // A1>A5 break; #endif } if (x & 4) cr |= 0x8000; // Change polarity COMP_CSR = cr; } void io_serial(void) { RCC_APB2RSTR = RCC_APB2RSTR_TIM15RST; RCC_APB2RSTR = 0; nvic_clear_pending_irq(NVIC_TIM1_BRK_TIM15_IRQ); RCC_APB1ENR1 |= RCC_APB1ENR1_USART2EN; GPIOA_AFRL = (GPIOA_AFRL & ~0xf00) | 0x700; // A2 (USART2_TX) #ifdef IO_RXTX GPIOA_AFRH |= 0x30000000; // A15 (USART2_RX) #endif } void io_analog(void) { RCC_APB2RSTR = RCC_APB2RSTR_TIM15RST; RCC_APB2RSTR = 0; nvic_clear_pending_irq(NVIC_TIM1_BRK_TIM15_IRQ); GPIOA_PUPDR &= ~0x30; // A2 (no pull-up/pull-down) GPIOA_MODER |= 0x30; // A2 (analog) } void adctrig(void) { if (DMA1_CCR(1) & DMA_CCR_EN) return; DMA1_CNDTR(1) = len; DMA1_CCR(1) = DMA_CCR_EN | DMA_CCR_TCIE | DMA_CCR_MINC | DMA_CCR_PSIZE_16BIT | DMA_CCR_MSIZE_16BIT; ADC_CR(ADC1) = ADC_CR_ADSTART | ADC_CR_ADVREGEN; } #ifdef USE_COMP2 void tim1_up_tim16_isr(void) { TIM1_SR = ~TIM_SR_UIF; if (TIM1_CCR4) TIM2_OR1 = 0; } void tim1_cc_isr(void) { TIM1_SR = ~TIM_SR_CC4IF; TIM2_OR1 = 0x8; // TI4=COMP2_OUT } #endif void dma1_channel6_isr(void) { dma1_channel5_isr(); } void dma1_channel1_isr(void) { DMA1_IFCR = DMA_IFCR_CTCIF(1); DMA1_CCR(1) = 0; int i = 0, u = 0, v = 0, c = 0, a = 0; #if SENS_CNT >= 2 c = buf[i++]; #endif #if SENS_CNT >= 1 v = buf[i++]; #endif #if SENS_CNT >= 3 u = buf[i++]; #endif if (ain) a = buf[i++]; int r = ST_VREFINT_CAL * 3000 / buf[i + 1]; adcdata(TEMP_FUNC(buf[i] * r >> TEMP_SHIFT), u * r >> 12, v * r >> 12, c * r >> 12, a * r >> 12); } ================================================ FILE: mcu/STM32L431/config.cmake ================================================ set(opts -mcpu=cortex-m4 -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16) set(libs opencm3_stm32l4) set(defs STM32L4) ================================================ FILE: mcu/STM32L431/config.h ================================================ /* ** Copyright (C) Arseny Vakhrushev ** ** This firmware is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This firmware is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this firmware. If not, see . */ #pragma once #define CLK 80000000 #define IO_PA2 #define IFTIM TIM2 #define IFTIM_XRES 2 #define IFTIM_ICFL 128 #define IFTIM_ICMR TIM2_CCMR2 #define IFTIM_ICM1 (TIM_CCMR2_CC4S_IN_TI4 | TIM_CCMR2_IC4F_DTF_DIV_16_N_8) #define IFTIM_ICM2 (TIM_CCMR2_CC4S_IN_TI4 | TIM_CCMR2_IC4F_DTF_DIV_8_N_8) #define IFTIM_ICM3 (TIM_CCMR2_CC4S_IN_TI4 | TIM_CCMR2_IC4F_DTF_DIV_4_N_8) #define IFTIM_ICIE TIM_DIER_CC4IE #define IFTIM_ICR TIM2_CCR4 #define IFTIM_OCR TIM2_CCR1 #define iftim_isr tim2_isr #define IOTIM TIM15 #define IOTIM_IDR (GPIOA_IDR & 0x4) // A2 #define IOTIM_DMA 5 #define iotim_isr tim1_brk_tim15_isr #define iodma_isr dma1_channel5_isr #define USART1_DMA_BASE DMA2_BASE #define USART1_RX_DMA 7 #define USART1_TX_DMA 6 #define usart1_tx_dma_isr dma2_channel6_isr #define USART2_RX_DMA 6 #define USART2_TX_DMA 7 #define tim1_com_isr tim1_trg_com_tim17_isr #ifdef USE_COMP2 #define SW_BLANKING #else #define TIM1_CCR5 MMIO32(TIM1_BASE + 0x58) #define TIM_CCER_CC5E 0x10000 #endif ================================================ FILE: mcu/STM32L431/config.ld ================================================ MEMORY { boot (rx) : ORIGIN = 0x8000000, LENGTH = 4K cfg (rx) : ORIGIN = ORIGIN(boot) + LENGTH(boot), LENGTH = 2K rom (rx) : ORIGIN = ORIGIN(cfg) + LENGTH(cfg), LENGTH = 64K - LENGTH(boot) - LENGTH(cfg) ram (rwx) : ORIGIN = 0x20000000, LENGTH = 8K } ================================================ FILE: mcu/STM32L431/preset.c ================================================ /* ** Copyright (C) Arseny Vakhrushev ** ** This firmware is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This firmware is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this firmware. If not, see . */ #include "common.h" const uint16_t sinedata[] = { 267, 271, 276, 280, 285, 290, 294, 299, 304, 308, 313, 317, 322, 326, 331, 335, 340, 344, 349, 353, 358, 362, 366, 371, 375, 379, 383, 387, 392, 396, 400, 404, 408, 412, 416, 419, 423, 427, 431, 434, 438, 441, 445, 448, 452, 455, 458, 461, 465, 468, 471, 474, 477, 479, 482, 485, 487, 490, 493, 495, 497, 500, 502, 504, 506, 508, 510, 512, 514, 515, 517, 518, 520, 521, 523, 524, 525, 526, 527, 528, 529, 530, 530, 531, 532, 532, 532, 533, 533, 533, 533, 533, 533, 533, 532, 532, 532, 531, 530, 530, 529, 528, 527, 526, 525, 524, 523, 521, 520, 518, 517, 515, 514, 512, 510, 508, 506, 504, 502, 500, 497, 495, 493, 490, 487, 485, 482, 479, 477, 474, 471, 468, 465, 461, 458, 455, 452, 448, 445, 441, 438, 434, 431, 427, 423, 419, 416, 412, 408, 404, 400, 396, 392, 387, 383, 379, 375, 371, 366, 362, 358, 353, 349, 344, 340, 335, 331, 326, 322, 317, 313, 308, 304, 299, 294, 290, 285, 280, 276, 271, 267, 262, 257, 253, 248, 243, 239, 234, 229, 225, 220, 216, 211, 207, 202, 198, 193, 189, 184, 180, 175, 171, 167, 162, 158, 154, 150, 146, 141, 137, 133, 129, 125, 121, 117, 114, 110, 106, 102, 99, 95, 92, 88, 85, 81, 78, 75, 72, 68, 65, 62, 59, 56, 54, 51, 48, 46, 43, 40, 38, 36, 33, 31, 29, 27, 25, 23, 21, 19, 18, 16, 15, 13, 12, 10, 9, 8, 7, 6, 5, 4, 3, 3, 2, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 3, 3, 4, 5, 6, 7, 8, 9, 10, 12, 13, 15, 16, 18, 19, 21, 23, 25, 27, 29, 31, 33, 36, 38, 40, 43, 46, 48, 51, 54, 56, 59, 62, 65, 68, 72, 75, 78, 81, 85, 88, 92, 95, 99, 102, 106, 110, 114, 117, 121, 125, 129, 133, 137, 141, 146, 150, 154, 158, 162, 167, 171, 175, 180, 184, 189, 193, 198, 202, 207, 211, 216, 220, 225, 229, 234, 239, 243, 248, 253, 257, 262, }; ================================================ FILE: mcu/common.ld ================================================ SECTIONS { .cfg : { _cfg_start = .; *(.cfg) . = ALIGN(8); _cfg_end = .; } >ram AT >cfg } _boot = ORIGIN(boot); _cfg = ORIGIN(cfg); _rom = ORIGIN(rom); _ram = ORIGIN(ram); _eod = LOADADDR(.data) + SIZEOF(.data); INCLUDE cortex-m-generic.ld ================================================ FILE: src/cli.c ================================================ /* ** Copyright (C) Arseny Vakhrushev ** ** This firmware is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This firmware is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this firmware. If not, see . */ #include "common.h" #define CFG_MAP(XX) \ XX( 0, val, arm) \ XX( 1, val, damp) \ XX( 2, val, revdir) \ XX( 3, val, brushed) \ XX( 4, val, timing) \ XX( 5, val, sine_range) \ XX( 6, val, sine_power) \ XX( 7, val, freq_min) \ XX( 8, val, freq_max) \ XX( 9, val, duty_min) \ XX(10, val, duty_max) \ XX(11, val, duty_spup) \ XX(12, val, duty_ramp) \ XX(13, val, duty_rate) \ XX(14, val, duty_drag) \ XX(15, val, duty_lock) \ XX(16, val, throt_mode) \ XX(17, val, throt_rev) \ XX(18, val, throt_brk) \ XX(19, val, throt_set) \ XX(20, val, throt_ztc) \ XX(21, val, throt_cal) \ XX(22, val, throt_min) \ XX(23, val, throt_mid) \ XX(24, val, throt_max) \ XX(25, val, analog_min) \ XX(26, val, analog_max) \ XX(27, val, input_mode) \ XX(28, val, input_ch1) \ XX(29, val, input_ch2) \ XX(30, val, telem_mode) \ XX(31, val, telem_phid) \ XX(32, val, telem_poles) \ XX(33, val, prot_stall) \ XX(34, val, prot_temp) \ XX(35, val, prot_sens) \ XX(36, val, prot_volt) \ XX(37, val, prot_cells) \ XX(38, val, prot_curr) \ XX(39, val, prot_park) \ XX(40, str, music) \ XX(41, val, volume) \ XX(42, val, beacon) \ XX(43, val, bec) \ XX(44, val, led) \ static int beep = -1; static int split(char *str, char **vec, int len, const char *sep) { int idx = 0; for (char *val; (val = strsep(&str, sep));) { if (!*val) continue; vec[idx++] = val; if (idx == len) break; } return idx; } static int getidx(const char *str, const char *const vec[]) { int idx = 0; const char *const *pos = vec; for (const char *val; (val = *pos++) && strcasecmp(val, str); ++idx); return idx; } static int getval(const char *str, int *val) { char *end; int res = strtol(str, &end, 10); if (*end) return 0; *val = res; return 1; } static void appendstr(char **pos, const char *str) { *pos = stpcpy(*pos, str); } static void appendval(char **pos, int val) { char buf[12]; appendstr(pos, itoa(val, buf, 10)); } static void appenddec(char **pos, int val) { char buf[12]; appendstr(pos, itoa(val / 100, buf, 10)); appendstr(pos, "."); for (int i = strlen(itoa(val % 100, buf, 10)); i < 2; ++i) appendstr(pos, "0"); appendstr(pos, buf); } #define appendpair(pos, type, key) \ appendstr(pos, #key); \ appendstr(pos, ": "); \ append##type(pos, cfg.key); \ appendstr(pos, "\n"); \ #define setstr(str, key) strlcpy(cfg.key, str, sizeof cfg.key) #define setval(str, key) \ if (!getval(str, &val)) goto error; \ cfg.key = val; \ #define setbeepstr(str) static int setbeepval(int val) { if (beep < 0 || val < 0) return val; beep = beepval = val; return val; } int execcmd(char *str) { static const char *const cmds[] = {"help", "info", "show", "get", "set", "save", "reset", "play", "throt", "beep", 0}; static const char *const keys[] = { #define XX(idx, type, key) #key, CFG_MAP(XX) #undef XX 0}; char *args[10]; int narg = split(str, args, 10, " \t\r\n"); if (!narg) return 0; int val; char *pos = str; switch (getidx(args[0], cmds)) { case 0: // 'help' appendstr(&pos, "Usage:\n" "info\n" "show\n" "get \n" "set \n" "save\n" "reset\n" "play []\n" "throt \n" "beep\n" ); break; case 1: // 'info' if (narg != 1) goto error; appendstr(&pos, "ESCape32 rev"); appendval(&pos, setbeepval(cfg.revision)); appendstr(&pos, "."); appendval(&pos, cfg.revpatch); appendstr(&pos, " ["); appendstr(&pos, cfg.name); appendstr(&pos, "]\nTemp: "); appendval(&pos, temp1); if (temp2) { appendstr(&pos, "C, ext "); appendval(&pos, temp2); } appendstr(&pos, "C\nVolt: "); appenddec(&pos, volt); appendstr(&pos, "V\nCurr: "); appenddec(&pos, curr); appendstr(&pos, "A\nCsum: "); appendval(&pos, csum); appendstr(&pos, "mAh\nERPM: "); appendval(&pos, erpm); appendstr(&pos, "\n"); break; case 2: // 'show' if (narg != 1) goto error; #define XX(idx, type, key) \ appendpair(&pos, type, key); CFG_MAP(XX) #undef XX break; case 3: // 'get ' if (narg != 2) goto error; switch (getidx(args[1], keys)) { #define XX(idx, type, key) \ case idx: \ appendpair(&pos, type, key); \ setbeep##type(cfg.key); \ break; CFG_MAP(XX) #undef XX default: goto error; } break; case 4: // 'set ' if (narg != 3) goto error; switch (getidx(args[1], keys)) { #define XX(idx, type, key) \ case idx: \ set##type(args[2], key); \ checkcfg(); \ appendpair(&pos, type, key); \ setbeep##type(cfg.key); \ break; CFG_MAP(XX) #undef XX default: goto error; } break; case 5: // 'save' if (narg != 1 || !setbeepval(savecfg())) goto error; break; case 6: // 'reset' if (narg != 1 || !setbeepval(resetcfg())) goto error; break; case 7: // 'play []' if (narg < 2 || narg > 3) goto error; val = cfg.volume; if (narg == 3 && (!getval(args[2], &val) || val < 1 || val > 100)) goto error; if (!playmusic(args[1], val)) goto error; break; case 8: // 'throt ' if (narg != 2 || !getval(args[1], &val) || val < -2000 || val > 2000) goto error; throt = val; analog = 0; break; case 9: // 'beep' if (narg != 1) goto error; if (beep < 0) beep = 0; beepval = beep; break; default: goto error; } appendstr(&pos, "OK\n"); return pos - str; error: appendstr(&pos, "ERROR\n"); return pos - str; } ================================================ FILE: src/common.h ================================================ /* ** Copyright (C) Arseny Vakhrushev ** ** This firmware is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This firmware is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this firmware. If not, see . */ #pragma once #include #include #include #include #include #include #include #ifdef AT32F4 #include #include #else #include #endif #include #include #include #include #include "config.h" #include "defs.h" #define CLK_CNT(rate) ((CLK + ((rate) >> 1)) / (rate)) #define CLK_KHZ (CLK / 1000) #define CLK_MHZ (CLK / 1000000) #ifdef ANALOG #define IO_ANALOG (cfg.throt_set < 100) #elif defined IO_PA2 || defined IO_PA6 || defined ANALOG_CHAN #define IO_ANALOG (cfg.input_mode == 1) #else #define IO_ANALOG 0 #endif #define GPIO(port, name) _GPIO(port, name) #define _GPIO(port, name) __GPIO(port, name) #define __GPIO(port, name) GPIO##port##_##name typedef struct { const uint16_t id; const char revision; const char revpatch; const char name[15]; const char _null; char arm; char damp; char revdir; char brushed; char timing; char sine_range; char sine_power; char freq_min; char freq_max; char duty_min; char duty_max; char duty_spup; char duty_ramp; char duty_rate; char duty_drag; char duty_lock; char throt_mode; char throt_rev; char throt_brk; char throt_set; char throt_ztc; char throt_cal; uint16_t throt_min; uint16_t throt_mid; uint16_t throt_max; uint16_t analog_min; uint16_t analog_max; char input_mode; char input_ch1; char input_ch2; char telem_mode; char telem_phid; char telem_poles; uint16_t prot_stall; char prot_temp; char prot_sens; char prot_volt; char prot_cells; uint16_t prot_curr; char prot_park; char music[256]; char volume; char beacon; char bec; char led; } Cfg; typedef struct { int Kp, Ki, Kd, Li, i, x; } PID; typedef void (*Func)(void); extern char _boot[], _cfg[], _cfg_start[], _cfg_end[], _rom[], _ram[], _eod[], _vec[]; // Linker exports extern const uint16_t sinedata[]; extern const Cfg cfgdata; extern Cfg cfg; extern int throt, brake, ertm, erpm, temp1, temp2, volt, curr, csum, dshotval, beepval; extern char analog, telreq, telmode, flipdir, beacon, dshotext, auxup; void init(void); void initio(void); void initgpio(void); void initled(void); void inittelem(void); int hallcode(void); void ledctl(int x); void hsictl(int x); void compctl(int x); void io_serial(void); void io_analog(void); void adctrig(void); void adcdata(int t, int u, int v, int c, int a); void delay(int ms, Func f); void kisstelem(void); void autotelem(void); int execcmd(char *str); char crc8(const char *buf, int len); char crc8dvbs2(const char *buf, int len); int scale(int x, int a1, int a2, int b1, int b2); int smooth(int *s, int x, int n); void initpid(PID *pid, int x); int calcpid(PID *pid, int x, int y); void checkcfg(void); int savecfg(void); int resetcfg(void); void resetcom(void); int playmusic(const char *str, int vol); void playsound(const char *buf, int vol); static inline int min(int a, int b) {return a < b ? a : b;} static inline int max(int a, int b) {return a > b ? a : b;} static inline int clamp(int x, int a, int b) {return min(max(x, a), b);} // Temperature sensors static inline int NTC10K3455LO2K(int x) { return (x < 2338 ? (x - 1650) * 46 + 74841 : (x - 2640) * 83 + 132044) >> 8; } static inline int NTC10K3455UP2K(int x) { return (x > 961 ? (x - 1650) * -46 + 74841 : (x - 660) * -83 + 132044) >> 8; } static inline int NTC10K3455LO10K(int x) { return (x < 2762 ? (x - 1650) * 36 + 25600 : (x - 3036) * 151 + 107130) >> 8; } static inline int NTC10K3455UP10K(int x) { return (x > 537 ? (x - 1650) * -36 + 25600 : (x - 264) * -151 + 107130) >> 8; } ================================================ FILE: src/defs.h ================================================ /* ** Copyright (C) Arseny Vakhrushev ** ** This firmware is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This firmware is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this firmware. If not, see . */ #pragma once #if DEAD_TIME < 128 #define TIM_DTG DEAD_TIME #elif DEAD_TIME < 256 #define TIM_DTG (((DEAD_TIME - 128) >> 1) | 0x80) #elif DEAD_TIME < 512 #define TIM_DTG (((DEAD_TIME - 256) >> 3) | 0xc0) #elif DEAD_TIME < 1024 #define TIM_DTG (((DEAD_TIME - 512) >> 4) | 0xe0) #endif #if COMP_MAP == 123 #define COMP_IN1 1 #define COMP_IN2 2 #define COMP_IN3 3 #elif COMP_MAP == 231 #define COMP_IN1 2 #define COMP_IN2 3 #define COMP_IN3 1 #elif COMP_MAP == 312 #define COMP_IN1 3 #define COMP_IN2 1 #define COMP_IN3 2 #elif COMP_MAP == 132 #define COMP_IN1 1 #define COMP_IN2 3 #define COMP_IN3 2 #elif COMP_MAP == 321 #define COMP_IN1 3 #define COMP_IN2 2 #define COMP_IN3 1 #elif COMP_MAP == 213 #define COMP_IN1 2 #define COMP_IN2 1 #define COMP_IN3 3 #endif #ifndef SENS_MAP #define SENS_MAP 0 #define SENS_CNT 0 #define SENS_CHAN 0 #elif SENS_MAP <= 0xff #define SENS_CNT 1 #elif SENS_MAP <= 0xffff #define SENS_CNT 2 #elif SENS_MAP <= 0xffffff #define SENS_CNT 3 #endif #ifndef LED_MAP #ifdef LED_WS2812 #define LED_CNT 3 #else #define LED_CNT 0 #endif #elif LED_MAP <= 0xff #define LED_CNT 1 #elif LED_MAP <= 0xffff #define LED_CNT 2 #elif LED_MAP <= 0xffffff #define LED_CNT 3 #elif LED_MAP <= 0xffffffff #define LED_CNT 4 #endif #ifndef TEMP_SENS #define TEMP_SENS NTC10K3455UP2K #endif #ifndef VOLT_MUL #define VOLT_MUL 0 // % #endif #ifndef CURR_MUL #define CURR_MUL 0 // mA/mV #endif #ifndef SERIAL_BR #define SERIAL_BR 460800 #endif #ifndef ERPM_PORT #define ERPM_PORT B #endif #ifndef PARK_PORT #define PARK_PORT B #endif #ifndef BEC_MIN #define BEC_MIN 0 #endif #ifndef BEC_MAX #define BEC_MAX (BEC_MIN + 3) #endif // Default settings #ifndef ARM #define ARM 1 #endif #ifndef DAMP #define DAMP 1 #endif #ifndef REVDIR #define REVDIR 0 #endif #ifndef BRUSHED #define BRUSHED 0 #endif #ifndef TIMING #define TIMING 16 #endif #ifndef SINE_RANGE #define SINE_RANGE 0 #endif #ifndef SINE_POWER #define SINE_POWER 8 #endif #ifndef FREQ_MIN #define FREQ_MIN 24 #endif #ifndef FREQ_MAX #define FREQ_MAX 48 #endif #ifndef DUTY_MIN #define DUTY_MIN 1 #endif #ifndef DUTY_MAX #define DUTY_MAX 100 #endif #ifndef DUTY_SPUP #define DUTY_SPUP 15 #endif #ifndef DUTY_RAMP #define DUTY_RAMP 0 #endif #ifndef DUTY_RATE #define DUTY_RATE 30 #endif #ifndef DUTY_DRAG #define DUTY_DRAG 0 #endif #ifndef DUTY_LOCK #define DUTY_LOCK 0 #endif #ifndef THROT_MODE #define THROT_MODE 0 #endif #ifndef THROT_ZTC #define THROT_ZTC 0 #endif #ifndef THROT_REV #define THROT_REV 0 #endif #ifndef THROT_BRK #define THROT_BRK 100 #endif #ifndef THROT_SET #define THROT_SET 0 #endif #ifndef THROT_CAL #ifdef USE_HSE #define THROT_CAL 0 #else #define THROT_CAL 1 #endif #endif #ifndef THROT_MIN #define THROT_MIN 1000 #endif #ifndef THROT_MID #define THROT_MID 1500 #endif #ifndef THROT_MAX #define THROT_MAX 2000 #endif #ifndef ANALOG_MIN #define ANALOG_MIN 100 #endif #ifndef ANALOG_MAX #define ANALOG_MAX 3200 #endif #ifndef INPUT_MODE #define INPUT_MODE 0 #endif #ifndef INPUT_CH1 #define INPUT_CH1 0 #endif #ifndef INPUT_CH2 #define INPUT_CH2 0 #endif #ifndef TELEM_MODE #define TELEM_MODE 0 #endif #ifndef TELEM_PHID #define TELEM_PHID 0 #endif #ifndef TELEM_POLES #define TELEM_POLES 14 #endif #ifndef PROT_STALL #define PROT_STALL 0 #endif #ifndef PROT_TEMP #define PROT_TEMP 0 #endif #ifndef PROT_SENS #define PROT_SENS 0 #endif #ifndef PROT_VOLT #define PROT_VOLT 0 #endif #ifndef PROT_CELLS #define PROT_CELLS 0 #endif #ifndef PROT_CURR #define PROT_CURR 0 #endif #ifndef PROT_PARK #define PROT_PARK 0 #endif #ifndef MUSIC #define MUSIC "dfa#" #endif #ifndef VOLUME #define VOLUME 25 #endif #ifndef BEACON #define BEACON 50 #endif #ifndef BEC #define BEC 0 #endif #ifndef LED #define LED 0 #endif ================================================ FILE: src/io.c ================================================ /* ** Copyright (C) Arseny Vakhrushev ** ** This firmware is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This firmware is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this firmware. If not, see . */ #include "common.h" #ifdef AT32F4 #define USART2_TDR USART2_DR #define USART2_RDR USART2_DR #define USART2_ISR USART2_SR #define USART_CR1_M0 USART_CR1_M #define USART_ISR_FE USART_SR_FE #define USART_ISR_NF USART_SR_NE #endif static void entryirq(void); static void calibirq(void); static void servoirq(void); static void dshotirq(void); static void dshotdma(void); static void cliirq(void); #ifdef IO_PA2 static void serialirq(void); static void serialdma(void); static void ibusdma(void); static void sbusdma(void); static void crsfirq(void); static char rxlen; #endif static Func ioirq, iodma; static char dshotinv, iobuf[1024]; static uint16_t dshotarr1, dshotarr2, dshotbuf1[32], dshotbuf2[23] = {-1, -1, 0, -1, 0, -1, -1, 0, -1, 0, -1, -1, 0, -1, 0, -1, 0, -1, -1, -1}; static void setthrot(int x) { if (x < 0) return; throt = cfg.throt_mode ? x < cfg.throt_mid - 50 ? scale(x, cfg.throt_min, cfg.throt_mid - 50, -2000, 0): x > cfg.throt_mid + 50 ? scale(x, cfg.throt_mid + 50, cfg.throt_max, 0, 2000): 0: x > cfg.throt_min + 50 ? scale(x, cfg.throt_min + 50, cfg.throt_max, 0, 2000): 0; } #if defined IO_PA2 || defined IO_AUX static void setbrake(int x) { if (x < 0) return; brake = scale(x, 1100, 1900, 0, cfg.duty_drag); auxup = 0; } #endif #ifdef IO_AUX void iotim2_isr(void) { #if IOTIM2 == TIM16 || IOTIM2 == TIM17 static uint16_t t1; uint16_t t2 = TIM_CCR1(IOTIM2); uint16_t er = TIM_CCER(IOTIM2); TIM_CCER(IOTIM2) = er ^ TIM_CCER_CC1P; if (!(er & TIM_CCER_CC1P)) { // Rising edge t1 = t2; return; } int x = t2 - t1; #else int x = TIM_CCR2(IOTIM2); #endif if (x < 800 || x > 2200) return; // Invalid signal setbrake(x); } #endif void initio(void) { ioirq = entryirq; TIM_BDTR(IOTIM) = TIM_BDTR_MOE; TIM_CCMR1(IOTIM) = TIM_CCMR1_CC1S_IN_TI1 | TIM_CCMR1_IC1F_CK_INT_N_8; TIM_SMCR(IOTIM) = TIM_SMCR_SMS_RM | TIM_SMCR_TS_TI1FP1; // Reset on rising edge on TI1 TIM_CCER(IOTIM) = TIM_CCER_CC1E; // IC1 on rising edge on TI1 TIM_DIER(IOTIM) = TIM_DIER_UIE | TIM_DIER_CC1IE; TIM_PSC(IOTIM) = CLK_MHZ - 1; // 1us resolution TIM_ARR(IOTIM) = -1; TIM_CR1(IOTIM) = TIM_CR1_URS; TIM_EGR(IOTIM) = TIM_EGR_UG; TIM_CR1(IOTIM) = TIM_CR1_CEN | TIM_CR1_ARPE | TIM_CR1_URS; #ifdef IO_AUX #if IOTIM2 == TIM16 || IOTIM2 == TIM17 TIM_CCMR1(IOTIM2) = TIM_CCMR1_CC1S_IN_TI1 | TIM_CCMR1_IC1F_DTF_DIV_8_N_8; TIM_CCER(IOTIM2) = TIM_CCER_CC1E; // IC1 on rising edge on TI1 TIM_DIER(IOTIM2) = TIM_DIER_CC1IE; #else TIM_CCMR1(IOTIM2) = TIM_CCMR1_CC1S_IN_TI1 | TIM_CCMR1_IC1F_DTF_DIV_8_N_8 | TIM_CCMR1_CC2S_IN_TI1 | TIM_CCMR1_IC2F_DTF_DIV_8_N_8; TIM_SMCR(IOTIM2) = TIM_SMCR_SMS_RM | TIM_SMCR_TS_TI1FP1; // Reset on rising edge on TI1 TIM_CCER(IOTIM2) = TIM_CCER_CC2E | TIM_CCER_CC2P; // IC2 on falling edge on TI1 TIM_DIER(IOTIM2) = TIM_DIER_CC2IE; #endif TIM_PSC(IOTIM2) = CLK_MHZ - 1; // 1us resolution TIM_ARR(IOTIM2) = -1; TIM_EGR(IOTIM2) = TIM_EGR_UG; TIM_CR1(IOTIM2) = TIM_CR1_CEN; #endif } static void entryirq(void) { static int n, c, d; if (TIM_SR(IOTIM) & TIM_SR_UIF) { // Timeout ~66ms TIM_SR(IOTIM) = ~TIM_SR_UIF; if (!IOTIM_IDR) { // Low level if (IO_ANALOG) goto analog; n = 0; return; } if (++c < 16) return; // Wait for ~1s before entering CLI ioirq = cliirq; #ifdef IO_PA2 io_serial(); #ifdef IO_RXTX GPIOA_PUPDR |= 0x80000000; // A15 (pull-down) GPIOA_MODER &= ~0x40000000; // A15 (USART2_RX) TIM15_ARR = CLK_CNT(20000) - 1; TIM15_EGR = TIM_EGR_UG; TIM15_SR = ~TIM_SR_UIF; TIM15_CR1 = TIM_CR1_CEN | TIM_CR1_OPM; while (TIM15_CR1 & TIM_CR1_CEN) { // Wait for 50us high level on A15 if (!(GPIOA_IDR & 0x8000)) { // A15 low USART2_CR3 = USART_CR3_HDSEL; break; } } #else USART2_CR3 = USART_CR3_HDSEL; #endif USART2_BRR = CLK_CNT(38400); USART2_CR1 = USART_CR1_UE | USART_CR1_TE | USART_CR1_RE | USART_CR1_RXNEIE; #else TIM3_CCER = 0; TIM3_CCMR1 = TIM_CCMR1_OC1PE | TIM_CCMR1_OC1M_PWM2 | TIM_CCMR1_CC2S_IN_TI1 | TIM_CCMR1_IC2F_CK_INT_N_8; TIM3_SMCR = TIM_SMCR_SMS_RM | TIM_SMCR_TS_TI1F_ED; // Reset on any edge on TI1 TIM3_CCER = TIM_CCER_CC2E | TIM_CCER_CC2P; // IC2 on falling edge on TI1 TIM3_SR = ~TIM_SR_CC2IF; TIM3_DIER = TIM_DIER_CC2IE; TIM3_PSC = 0; TIM3_ARR = CLK_CNT(38400) - 1; // Bit time TIM3_CCR1 = CLK_CNT(76800); // Half-bit time TIM3_EGR = TIM_EGR_UG; TIM3_CR1 = TIM_CR1_CEN; #endif return; } int t = TIM_CCR1(IOTIM); // Time between two rising edges if (IO_ANALOG) { analog: #ifndef ANALOG_CHAN io_analog(); analog = 1; #endif return; } if (!n++) return; // First capture is always invalid IWDG_KR = IWDG_KR_START; #ifdef IO_PA2 if (cfg.input_mode >= 2) { ioirq = serialirq; io_serial(); switch (cfg.input_mode) { case 2: // Serial iodma = serialdma; rxlen = 4; USART2_BRR = CLK_CNT(SERIAL_BR); break; case 3: // iBUS iodma = ibusdma; rxlen = 32; USART2_BRR = CLK_CNT(115200); break; case 4: // SBUS/SBUS2 iodma = sbusdma; rxlen = 25; USART2_BRR = CLK_CNT(100000); USART2_CR1 = USART_CR1_PCE | USART_CR1_M0; USART2_CR2 = USART_CR2_STOPBITS_2; #ifndef AT32F4 USART2_CR2 |= USART_CR2_RXINV | USART_CR2_TXINV; GPIOA_PUPDR = (GPIOA_PUPDR & ~0x30) | 0x20; // A2 (pull-down) #endif TIM15_PSC = CLK_MHZ / 8 - 1; // 125ns resolution TIM15_ARR = -1; TIM15_EGR = TIM_EGR_UG; TIM15_CR1 = TIM_CR1_CEN | TIM_CR1_ARPE; break; case 5: // CRSF ioirq = crsfirq; USART2_BRR = CLK_CNT(416666); break; } USART2_CR3 = USART_CR3_HDSEL; USART2_CR1 |= USART_CR1_UE | USART_CR1_TE | USART_CR1_RE | USART_CR1_IDLEIE; DMA1_CPAR(USART2_RX_DMA) = (uint32_t)&USART2_RDR; DMA1_CMAR(USART2_RX_DMA) = (uint32_t)iobuf; DMA1_CPAR(USART2_TX_DMA) = (uint32_t)&USART2_TDR; DMA1_CMAR(USART2_TX_DMA) = (uint32_t)iobuf; return; } #endif if (TIM_PSC(IOTIM)) { if (t > 2000) { // Servo/Oneshot125 ioirq = calibirq; TIM_DIER(IOTIM) = TIM_DIER_CC1IE; TIM_CR1(IOTIM) = TIM_CR1_CEN; calibirq(); return; } TIM_PSC(IOTIM) = TIM_PSC(IOTIM) == CLK_MHZ - 1 ? CLK_MHZ / 8 - 1 : 0; TIM_EGR(IOTIM) = TIM_EGR_UG; n = 0; return; } int m = 2; while (t >= CLK_CNT(800000)) t >>= 1, --m; if (d != m) { d = m; n = 1; return; } if (m < 0 || n < 4) return; ioirq = dshotirq; iodma = dshotdma; dshotarr1 = CLK_CNT(150000 << m) - 1; dshotarr2 = CLK_CNT(375000 << m) - 1; TIM_CCER(IOTIM) = 0; TIM_CCMR1(IOTIM) = TIM_CCMR1_CC1S_IN_TRC | TIM_CCMR1_IC1F_CK_INT_N_8; TIM_SMCR(IOTIM) = TIM_SMCR_SMS_RM | TIM_SMCR_TS_TI1F_ED; // Reset on any edge on TI1 TIM_DIER(IOTIM) = TIM_DIER_UIE; TIM_ARR(IOTIM) = dshotarr1; // Frame reset timeout TIM_EGR(IOTIM) = TIM_EGR_UG; DMA1_CPAR(IOTIM_DMA) = (uint32_t)&TIM_CCR1(IOTIM); DMA1_CMAR(IOTIM_DMA) = (uint32_t)dshotbuf1; } static void calibirq(void) { static int n, q, x, y; int p = TIM_CCR1(IOTIM); // Pulse period if (cfg.throt_cal && // 50/100/125/200/250/333/500Hz 11/22ms servo PWM within 8% margin ((p < 22880 && p > 21120) || (p < 20800 && p > 19200) || (p < 11440 && p > 10560) || (p < 10400 && p > 9600) || (p < 8320 && p > 7680) || (p < 5200 && p > 4800) || (p < 4160 && p > 3840) || (p < 3120 && p > 2880) || (p < 2080 && p > 1920))) { IWDG_KR = IWDG_KR_RESET; q += p - ((p + 500) / 1000) * 1000; // Cumulative error if (++n & 3) return; if (q > x) { // Slow down y = -q; q = 0; hsictl(-1); return; } if (q < y) { // Speed up x = -q; q = 0; hsictl(1); return; } } ioirq = servoirq; TIM_CCMR1(IOTIM) = TIM_CCMR1_CC1S_IN_TI1 | TIM_CCMR1_IC1F_DTF_DIV_8_N_8 | TIM_CCMR1_CC2S_IN_TI1 | TIM_CCMR1_IC2F_DTF_DIV_8_N_8; TIM_CCER(IOTIM) = TIM_CCER_CC2E | TIM_CCER_CC2P; // IC2 on falling edge on TI1 TIM_SR(IOTIM) = ~TIM_SR_CC2IF; TIM_DIER(IOTIM) = TIM_DIER_CC2IE; } static void servoirq(void) { int x = TIM_CCR2(IOTIM); if (x >= 28 && x <= 32) { // Telemetry request IWDG_KR = IWDG_KR_RESET; telreq = 1; return; } if (x < 800 || x > 2200) return; // Invalid signal IWDG_KR = IWDG_KR_RESET; setthrot(x); } static void dshotirq(void) { if (!(TIM_DIER(IOTIM) & TIM_DIER_UIE) || !(TIM_SR(IOTIM) & TIM_SR_UIF)) return; // Fall through exactly once TIM_SR(IOTIM) = ~TIM_SR_UIF; if (!TIM_CCER(IOTIM)) { // Detect DSHOT polarity TIM_CCER(IOTIM) = TIM_CCER_CC1E; // IC1 on any edge on TI1 dshotinv = IOTIM_IDR; // Inactive level } DMA1_CNDTR(IOTIM_DMA) = 32; DMA1_CCR(IOTIM_DMA) = DMA_CCR_EN | DMA_CCR_TCIE | DMA_CCR_CIRC | DMA_CCR_MINC | DMA_CCR_PSIZE_16BIT | DMA_CCR_MSIZE_16BIT; TIM_ARR(IOTIM) = -1; TIM_EGR(IOTIM) = TIM_EGR_UG; TIM_CR1(IOTIM) = TIM_CR1_CEN | TIM_CR1_ARPE; TIM_DIER(IOTIM) = TIM_DIER_CC1DE; } static void dshotreset(void) { #ifdef AT32F4 // Errata 1.5.1 RCC_APB2RSTR = RCC_APB2RSTR_TIM15RST; RCC_APB2RSTR = 0; TIM15_BDTR = TIM_BDTR_MOE; TIM15_CR1 = TIM_CR1_CEN | TIM_CR1_ARPE; #else TIM_CCER(IOTIM) = 0; TIM_DIER(IOTIM) = 0; TIM_CR2(IOTIM) = 0; #endif DMA1_CCR(IOTIM_DMA) = 0; DMA1_CMAR(IOTIM_DMA) = (uint32_t)dshotbuf1; DMA1_CNDTR(IOTIM_DMA) = 32; DMA1_CCR(IOTIM_DMA) = DMA_CCR_EN | DMA_CCR_TCIE | DMA_CCR_CIRC | DMA_CCR_MINC | DMA_CCR_PSIZE_16BIT | DMA_CCR_MSIZE_16BIT; TIM_ARR(IOTIM) = -1; TIM_EGR(IOTIM) = TIM_EGR_UG; TIM_CCMR1(IOTIM) = TIM_CCMR1_CC1S_IN_TRC | TIM_CCMR1_IC1F_CK_INT_N_8; TIM_SMCR(IOTIM) = TIM_SMCR_SMS_RM | TIM_SMCR_TS_TI1F_ED; // Reset on any edge on TI1 TIM_CCER(IOTIM) = TIM_CCER_CC1E; // IC1 on any edge on TI1 TIM_DIER(IOTIM) = TIM_DIER_CC1DE; } static void dshotresync(void) { if (dshotinv) dshotreset(); DMA1_CCR(IOTIM_DMA) = 0; TIM_CR1(IOTIM) = TIM_CR1_CEN | TIM_CR1_ARPE | TIM_CR1_URS; TIM_ARR(IOTIM) = dshotarr1; // Frame reset timeout TIM_EGR(IOTIM) = TIM_EGR_UG; TIM_SR(IOTIM) = ~TIM_SR_UIF; TIM_DIER(IOTIM) = TIM_DIER_UIE; } static int dshotcrc(int x, int inv) { int a = x; for (int b = x; b >>= 4; a ^= b); if (inv) a = ~a; return a & 0xf; } static void dshotdma(void) { static const char gcr[] = {0x19, 0x1b, 0x12, 0x13, 0x1d, 0x15, 0x16, 0x17, 0x1a, 0x09, 0x0a, 0x0b, 0x1e, 0x0d, 0x0e, 0x0f}; static int cmd, cnt, rep; if (DMA1_CCR(IOTIM_DMA) & DMA_CCR_DIR) { dshotreset(); if (!dshotval) { int a = ertm ? min(ertm, 65408) : 65408; int b = 0; while (a > 511) a >>= 1, ++b; dshotval = a | b << 9; } int a = dshotval << 4 | dshotcrc(dshotval, 1); int b = 0; for (int i = 0, j = 0; i < 16; i += 4, j += 5) b |= gcr[a >> i & 0xf] << j; for (int p = -1, i = 19; i >= 0; --i) { if (b >> i & 1) p = ~p; dshotbuf2[20 - i] = p; } if (!rep || !--rep) dshotval = 0; return; } if (dshotinv) { // Bidirectional DSHOT TIM_CCER(IOTIM) = 0; TIM_SMCR(IOTIM) = 0; TIM_CCMR1(IOTIM) = 0; // Disable OC before enabling PWM to force OC1REF update (RM: OC1M, note #2) TIM_CCMR1(IOTIM) = TIM_CCMR1_OC1PE | TIM_CCMR1_OC1M_PWM2; TIM_CR2(IOTIM) = TIM_CR2_CCDS; // CC1 DMA request on UEV using the same DMA channel DMA1_CCR(IOTIM_DMA) = 0; DMA1_CMAR(IOTIM_DMA) = (uint32_t)dshotbuf2; DMA1_CNDTR(IOTIM_DMA) = 23; DMA1_CCR(IOTIM_DMA) = DMA_CCR_EN | DMA_CCR_TCIE | DMA_CCR_DIR | DMA_CCR_MINC | DMA_CCR_PSIZE_16BIT | DMA_CCR_MSIZE_16BIT; TIM_CCR1(IOTIM) = 0; // Preload high level __disable_irq(); TIM_ARR(IOTIM) = max(CLK_CNT(33333) - TIM_CNT(IOTIM) - 1, 19); // 30us output delay TIM_EGR(IOTIM) = TIM_EGR_UG; // Update registers and trigger DMA to preload the first bit TIM_EGR(IOTIM); // Ensure UEV has happened TIM_ARR(IOTIM) = dshotarr2; // Preload bit time TIM_CCER(IOTIM) = TIM_CCER_CC1E; // Enable output __enable_irq(); } int x = 0; int y = dshotarr1 + 1; // Two bit time int z = y >> 2; // Half-bit time for (int i = 0; i < 32; i += 2) { if (i && dshotbuf1[i] >= y) { // Invalid pulse timing dshotresync(); return; } x <<= 1; if (dshotbuf1[i + 1] >= z) x |= 1; } if (dshotcrc(x, dshotinv)) { // Invalid checksum dshotresync(); return; } IWDG_KR = IWDG_KR_RESET; int tlm = x & 0x10; x >>= 5; if (!x || x > 47) { if (tlm) telreq = 1; // Telemetry request throt = x ? cfg.throt_mode ? (x > 1047 ? x - 1047 : 47 - x) << 1 : x - 47 : 0; cmd = 0; return; } if (!tlm || ertm) return; // Telemetry bit must be set, motor must be stopped if (cmd != x) { cmd = x; cnt = 0; } if (cnt < 10) ++cnt; switch (cmd) { case 1: // DSHOT_CMD_BEACON1 case 2: // DSHOT_CMD_BEACON2 case 3: // DSHOT_CMD_BEACON3 case 4: // DSHOT_CMD_BEACON4 case 5: // DSHOT_CMD_BEACON5 beacon = cmd; break; case 7: // DSHOT_CMD_SPIN_DIRECTION_1 if (cnt != 6) break; cfg.revdir = 0; break; case 8: // DSHOT_CMD_SPIN_DIRECTION_2 if (cnt != 6) break; cfg.revdir = 1; break; case 9: // DSHOT_CMD_3D_MODE_OFF if (cnt != 6) break; cfg.throt_mode = 0; break; case 10: // DSHOT_CMD_3D_MODE_ON if (cnt != 6) break; cfg.throt_mode = 1; break; case 12: // DSHOT_CMD_SAVE_SETTINGS if (cnt != 6) break; beepval = savecfg(); break; case 13: // DSHOT_CMD_EXTENDED_TELEMETRY_ENABLE if (cnt != 6) break; dshotext = 1; dshotval = 0xe00; rep = 10; break; case 14: // DSHOT_CMD_EXTENDED_TELEMETRY_DISABLE if (cnt != 6) break; dshotext = 0; dshotval = 0xeff; rep = 10; break; case 20: // DSHOT_CMD_SPIN_DIRECTION_NORMAL if (cnt != 6) break; flipdir = 0; break; case 21: // DSHOT_CMD_SPIN_DIRECTION_REVERSED if (cnt != 6) break; flipdir = 1; break; #if LED_CNT >= 1 case 22: // DSHOT_CMD_LED0_ON cfg.led |= 1; break; case 26: // DSHOT_CMD_LED0_OFF cfg.led &= ~1; break; #endif #if LED_CNT >= 2 case 23: // DSHOT_CMD_LED1_ON cfg.led |= 2; break; case 27: // DSHOT_CMD_LED1_OFF cfg.led &= ~2; break; #endif #if LED_CNT >= 3 case 24: // DSHOT_CMD_LED2_ON cfg.led |= 4; break; case 28: // DSHOT_CMD_LED2_OFF cfg.led &= ~4; break; #endif #if LED_CNT >= 4 case 25: // DSHOT_CMD_LED3_ON cfg.led |= 8; break; case 29: // DSHOT_CMD_LED3_OFF cfg.led &= ~8; break; #endif case 40: // Select motor timing if (cnt != 6) break; if ((x = (cfg.timing >> 1) + 1) > 15 || x < 8) x = 8; cfg.timing = x << 1; beepval = x - 7; break; case 41: // Select PWM frequency if (cnt != 6) break; if ((x = (cfg.freq_min >> 2) + 1) > 12 || x < 6) x = 6; cfg.freq_min = x << 2; cfg.freq_max = x << 3; beepval = x - 5; break; case 42: // Select maximum duty cycle ramp if (cnt != 6) break; if ((x = cfg.duty_ramp / 10 + 1) > 10) x = 0; cfg.duty_ramp = x * 10; beepval = x; break; case 43: // Select duty cycle slew rate if (cnt != 6) break; if ((x = cfg.duty_rate / 10 + 1) > 10) x = 1; cfg.duty_rate = x * 10; beepval = x; break; case 47: // Reset settings if (cnt != 6) break; beepval = resetcfg(); break; } } void iotim_isr(void) { ioirq(); } void iodma_isr(void) { #ifdef IO_PA2 DMA1_IFCR = DMA_IFCR_CTCIF(IOTIM_DMA) | DMA_IFCR_CTCIF(USART2_RX_DMA); #else DMA1_IFCR = DMA_IFCR_CTCIF(IOTIM_DMA); #endif iodma(); } #ifdef IO_PA2 void usart2_isr(void) { ioirq(); } static int getchan(const char *buf, int n) { if (n < 0) return -1; int i = (n * 11) >> 3; int a = (n * 3) & 7; int b = ((n + 1) * 3) & 7; int x = a < b ? buf[i] >> a | (buf[i + 1] & ((1 << b) - 1)) << (8 - a): buf[i] >> a | buf[i + 1] << (8 - a) | (buf[i + 2] & ((1 << b) - 1)) << (16 - a); return (x * 5 >> 3) + 880; } static void serialresync(void) { #ifdef AT32F4 USART2_SR, USART2_DR; // Clear flags #else USART2_ICR = USART_ICR_IDLECF; #endif USART2_CR1 |= USART_CR1_IDLEIE; USART2_CR3 = USART_CR3_HDSEL; DMA1_CCR(USART2_RX_DMA) = 0; } static void serialirq(void) { if (USART2_CR1 & USART_CR1_TCIE) { USART2_CR1 = USART_CR1_UE | USART_CR1_TE | USART_CR1_RE; return; } #ifdef AT32F4 USART2_SR, USART2_DR; // Clear flags #else USART2_RQR = USART_RQR_RXFRQ; // Clear RXNE USART2_ICR = USART_ICR_IDLECF | USART_ICR_ORECF; #endif USART2_CR1 &= ~USART_CR1_IDLEIE; USART2_CR3 = USART_CR3_HDSEL | USART_CR3_DMAT | USART_CR3_DMAR; DMA1_CNDTR(USART2_RX_DMA) = rxlen; DMA1_CCR(USART2_RX_DMA) = DMA_CCR_EN | DMA_CCR_TCIE | DMA_CCR_CIRC | DMA_CCR_MINC | DMA_CCR_PSIZE_8BIT | DMA_CCR_MSIZE_8BIT; } static int serialresp(int x) { iobuf[0] = x; iobuf[1] = x >> 8; iobuf[2] = crc8(iobuf, 2); return 3; } static int serialreq(char a, int x) { switch (a & 0xf) { case 0x1: // Throttle if (x & 0x8000) x -= 0x10000; // Propagate sign if (x < -2000 || x > 2000) break; throt = x; break; case 0x2: // Reversed motor direction flipdir = !!x; break; case 0x3: // Drag brake adjustment brake = min(x, 100) * cfg.duty_drag * 41 >> 12; auxup = 0; break; #if LED_CNT > 0 case 0x4: // LED cfg.led = x & ((1 << LED_CNT) - 1); break; #endif } switch (a >> 4) { case 0x8: // Combined telemetry iobuf[0] = temp1; iobuf[1] = temp2; iobuf[2] = volt; iobuf[3] = volt >> 8; iobuf[4] = curr; iobuf[5] = curr >> 8; iobuf[6] = csum; iobuf[7] = csum >> 8; iobuf[8] = x = min(ertm, 0xffff); iobuf[9] = x >> 8; iobuf[10] = crc8(iobuf, 10); return 11; case 0x9: return serialresp(min(ertm, 0xffff)); // Electrical revolution time (us) case 0xa: return serialresp(temp1); // ESC temperature (C) case 0xb: return serialresp(temp2); // Motor temperature (C) case 0xc: return serialresp(volt); // Voltage (V/100) case 0xd: return serialresp(curr); // Current (A/100) case 0xe: return serialresp(csum); // Consumption (mAh) } return 0; } static void serialdma(void) { if (crc8(iobuf, 4)) { // Invalid checksum serialresync(); return; } IWDG_KR = IWDG_KR_RESET; int len = serialreq(iobuf[0], iobuf[1] | iobuf[2] << 8); if (!len) return; #ifdef AT32F4 USART2_SR = ~USART_SR_TC; #else USART2_ICR = USART_ICR_TCCF; #endif USART2_CR1 = USART_CR1_UE | USART_CR1_TE | USART_CR1_TCIE; DMA1_CCR(USART2_TX_DMA) = 0; DMA1_CNDTR(USART2_TX_DMA) = len; DMA1_CCR(USART2_TX_DMA) = DMA_CCR_EN | DMA_CCR_DIR | DMA_CCR_MINC | DMA_CCR_PSIZE_8BIT | DMA_CCR_MSIZE_8BIT; } static void ibusdma(void) { if (iobuf[0] != 0x20 || iobuf[1] != 0x40) { // Invalid frame serialresync(); return; } int n1 = cfg.input_ch1; int n2 = cfg.input_ch2; int x1 = -1; int x2 = -1; int u = 0xff9f; for (int i = 1;; ++i) { int j = i << 1; char a = iobuf[j]; char b = iobuf[j + 1]; int v = a | b << 8; if (i == 15) { if (u == v) break; serialresync(); // Invalid checksum return; } u -= a + b; if (i == n1) x1 = v & 0xfff; else if (i == n2) x2 = v & 0xfff; } IWDG_KR = IWDG_KR_RESET; setthrot(x1); setbrake(x2); } static void sbusrx(void) { TIM15_SR = ~TIM_SR_UIF; TIM15_DIER = 0; TIM15_ARR = -1; TIM15_EGR = TIM_EGR_UG; USART2_CR1 = USART_CR1_PCE | USART_CR1_M0 | USART_CR1_UE | USART_CR1_TE | USART_CR1_RE; ioirq = serialirq; } static void sbustx(void) { static const char slot[] = { 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3, // 2..7 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3, // 10..15 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, // 18..23 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb, // 26..31 }; static int n; int a = 0, b = 0; TIM15_SR = ~TIM_SR_UIF; switch (n) { case 0: // SBS-01T (ESC temperature) a = temp1 + 100; b = a >> 8 | 0x80; break; case 1: // SBS-01T (motor temperature) a = temp2 + 100; b = a >> 8 | 0x80; break; case 2: // SBS-01C (current) b = curr; a = b >> 8 | 0x40; break; case 3: // SBS-01C (voltage) b = volt; a = b >> 8; break; case 4: // SBS-01C (consumption) b = csum; a = b >> 8; break; case 5: // SBS-01R (RPM) a = min(erpm / (cfg.telem_poles * 3), 0xffff); b = a >> 8; break; } iobuf[0] = slot[n + (cfg.telem_phid - 1) * 6]; iobuf[1] = a; iobuf[2] = b; DMA1_CCR(USART2_TX_DMA) = 0; DMA1_CNDTR(USART2_TX_DMA) = 3; DMA1_CCR(USART2_TX_DMA) = DMA_CCR_EN | DMA_CCR_DIR | DMA_CCR_MINC | DMA_CCR_PSIZE_8BIT | DMA_CCR_MSIZE_8BIT; if (++n < 6) return; ioirq = sbusrx; n = 0; } static void sbusdma(void) { if (iobuf[0] != 0x0f) { // Invalid frame serialresync(); return; } TIM15_EGR = TIM_EGR_UG; IWDG_KR = IWDG_KR_RESET; setthrot(getchan(iobuf + 1, cfg.input_ch1 - 1)); setbrake(getchan(iobuf + 1, cfg.input_ch2 - 1)); int a = iobuf[24]; if ((a & 0xf) != 0x4) return; // No telemetry if (telmode == 2 || telmode == 3 || a >> 4 != cfg.telem_phid - 1) { // Disable RX for 7280us (2000+660*8) ioirq = sbusrx; __disable_irq(); TIM15_ARR = 58239 - TIM15_CNT; TIM15_EGR = TIM_EGR_UG; __enable_irq(); } else { // Delay TX for 3980us (2000+660*3) ioirq = sbustx; __disable_irq(); TIM15_ARR = 31839 - TIM15_CNT; TIM15_EGR = TIM_EGR_UG; TIM15_EGR; // Ensure UEV has happened TIM15_ARR = 5279; // Preload slot interval __enable_irq(); } TIM15_SR = ~TIM_SR_UIF; TIM15_DIER = TIM_DIER_UIE; USART2_CR1 = USART_CR1_PCE | USART_CR1_M0 | USART_CR1_UE | USART_CR1_TE; } static void crsfirq(void) { #ifdef AT32F4 USART2_SR, USART2_DR; // Clear flags #else USART2_RQR = USART_RQR_RXFRQ; // Clear RXNE USART2_ICR = USART_ICR_IDLECF | USART_ICR_ORECF; #endif int len = 64 - DMA1_CNDTR(USART2_RX_DMA); USART2_CR3 = USART_CR3_HDSEL | USART_CR3_DMAT | USART_CR3_DMAR; USART2_CR1 = USART_CR1_UE | USART_CR1_TE | USART_CR1_RE | USART_CR1_IDLEIE; DMA1_CCR(USART2_RX_DMA) = 0; DMA1_CNDTR(USART2_RX_DMA) = 64; DMA1_CCR(USART2_RX_DMA) = DMA_CCR_EN | DMA_CCR_MINC | DMA_CCR_PSIZE_8BIT | DMA_CCR_MSIZE_8BIT; if (len != 26 || iobuf[1] != 0x18 || iobuf[2] != 0x16 || crc8dvbs2(iobuf + 2, 24)) return; // Invalid frame IWDG_KR = IWDG_KR_RESET; setthrot(getchan(iobuf + 3, cfg.input_ch1 - 1)); setbrake(getchan(iobuf + 3, cfg.input_ch2 - 1)); } static void cliirq(void) { static int i, j; int cr = USART2_CR1; if (cr & USART_CR1_TXEIE) { USART2_TDR = iobuf[i++]; // Clear TXE+TC if (i < j) return; USART2_CR1 = USART_CR1_UE | USART_CR1_TE | USART_CR1_TCIE; i = 0; j = 0; return; } if (cr & USART_CR1_TCIE) { USART2_CR1 = USART_CR1_UE | USART_CR1_TE | USART_CR1_RE | USART_CR1_RXNEIE; return; } if (USART2_ISR & (USART_ISR_FE | USART_ISR_NF) || i == sizeof iobuf - 1) WWDG_CR = WWDG_CR_WDGA; // Data error char b = USART2_RDR; // Clear RXNE if (b == '\b' || b == 0x7f) { // Backspace if (i) --i; return; } iobuf[i++] = b; if (i == 2 && iobuf[0] == 0x00 && iobuf[1] == 0xff) scb_reset_system(); // Reboot into bootloader if (b != '\n') return; iobuf[i] = '\0'; i = 0; if (!(j = execcmd(iobuf))) return; USART2_CR1 = USART_CR1_UE | USART_CR1_TE | USART_CR1_TXEIE; } #else static void cliirq(void) { static int i, j, n, b; switch (TIM3_DIER) { case TIM_DIER_UIE: // Output TIM3_SR = ~TIM_SR_UIF; if (i < j) { int p = -1; if (!n++) b = iobuf[i]; // Start bit else if (n < 10) { // Data bit if (b & 1) p = 0; b >>= 1; } else { // Stop bit if (++i == j) { // End of data i = 0; j = 0; } n = 0; p = 0; } TIM3_CCR1 = p; break; } TIM3_SMCR = TIM_SMCR_SMS_RM | TIM_SMCR_TS_TI1F_ED; // Reset on any edge on TI1 TIM3_CCER = TIM_CCER_CC2E | TIM_CCER_CC2P; // IC2 on falling edge on TI1 TIM3_SR = ~TIM_SR_CC2IF; TIM3_DIER = TIM_DIER_CC2IE; TIM3_CCR1 = CLK_CNT(76800); // Half-bit time TIM3_EGR = TIM_EGR_UG; break; case TIM_DIER_CC1IE: // Half-bit time TIM3_SR = ~TIM_SR_CC1IF; int p = IOTIM_IDR; // Signal level if (!n++) { // Start bit if (p) WWDG_CR = WWDG_CR_WDGA; // Data error b = 0; break; } if (n < 10) { // Data bit b >>= 1; if (p) b |= 0x80; break; } if (!p || i == sizeof iobuf - 1) WWDG_CR = WWDG_CR_WDGA; // Data error TIM3_SR = ~TIM_SR_CC2IF; TIM3_DIER = TIM_DIER_CC2IE; n = 0; if (b == '\b' || b == 0x7f) { // Backspace if (i) --i; break; } iobuf[i++] = b; if (i == 2 && iobuf[0] == 0x00 && iobuf[1] == 0xff) scb_reset_system(); // Reboot into bootloader if (b != '\n') break; iobuf[i] = '\0'; i = 0; if (!(j = execcmd(iobuf))) break; TIM3_SMCR = 0; TIM3_CCR1 = 0; // Preload high level TIM3_EGR = TIM_EGR_UG; // Update registers and trigger UEV TIM3_CCER = TIM_CCER_CC1E; // Enable output TIM3_DIER = TIM_DIER_UIE; break; case TIM_DIER_CC2IE: // Falling edge TIM3_SR = ~(TIM_SR_CC1IF | TIM_SR_CC2IF); TIM3_DIER = TIM_DIER_CC1IE; break; } } #endif ================================================ FILE: src/main.c ================================================ /* ** Copyright (C) Arseny Vakhrushev ** ** This firmware is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This firmware is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this firmware. If not, see . */ #include "common.h" #define REVISION 15 #define REVPATCH 0 const Cfg cfgdata = { .id = 0x32ea, .revision = REVISION, .revpatch = REVPATCH, .name = TARGET_NAME, .arm = ARM, // Wait for 250ms zero throttle on startup .damp = DAMP, // Complementary PWM (active freewheeling) .revdir = REVDIR, // Reversed motor direction .brushed = BRUSHED, // Brushed mode .timing = TIMING, // Motor timing (15/16 deg) [1..31] .sine_range = SINE_RANGE, // Sine startup range (%) [0 - off, 5..25] .sine_power = SINE_POWER, // Sine startup power (%) [1..15] .freq_min = FREQ_MIN, // Minimum PWM frequency (kHz) [16..48] .freq_max = FREQ_MAX, // Maximum PWM frequency (kHz) [16..96] .duty_min = DUTY_MIN, // Minimum duty cycle (%) [1..100] .duty_max = DUTY_MAX, // Maximum duty cycle (%) [1..100] .duty_spup = DUTY_SPUP, // Maximum duty cycle during spin-up (%) [1..25] .duty_ramp = DUTY_RAMP, // Maximum duty cycle ramp (kERPM) [0..100] .duty_rate = DUTY_RATE, // Duty cycle slew rate (0.1%/ms) [1..100] .duty_drag = DUTY_DRAG, // Drag brake power (%) [0..100] .duty_lock = DUTY_LOCK, // Active drag brake (0 - off, 1 - soft, 2 - hard) .throt_mode = THROT_MODE, // Throttle mode (0 - forward, 1 - forward/reverse, 2 - forward/brake/reverse, 3 - forward/brake) .throt_rev = THROT_REV, // Maximum reverse throttle (0 - 100%, 1 - 75%, 2 - 50%, 3 - 25%) .throt_brk = THROT_BRK, // Maximum brake power (%) [0..100] .throt_set = THROT_SET, // Preset throttle (%) [0..100] .throt_ztc = THROT_ZTC, // Zero-throttle coasting .throt_cal = THROT_CAL, // Automatic throttle calibration .throt_min = THROT_MIN, // Minimum throttle setpoint (us) .throt_mid = THROT_MID, // Middle throttle setpoint (us) .throt_max = THROT_MAX, // Maximum throttle setpoint (us) .analog_min = ANALOG_MIN, // Minimum analog throttle setpoint (mV) .analog_max = ANALOG_MAX, // Maximum analog throttle setpoint (mV) .input_mode = INPUT_MODE, // Input mode (0 - servo/Oneshot125/DSHOT, 1 - analog, 2 - serial, 3 - iBUS, 4 - SBUS/SBUS2, 5 - CRSF) .input_ch1 = INPUT_CH1, // Throttle channel [0 - off, 1..14 - iBUS, 1..16 - SBUS/SBUS2/CRSF] .input_ch2 = INPUT_CH2, // Auxiliary channel [0 - off, 1..14 - iBUS, 1..16 - SBUS/SBUS2/CRSF] .telem_mode = TELEM_MODE, // Telemetry mode (0 - KISS, 1 - KISS auto, 2 - iBUS, 3 - S.Port, 4 - CRSF) .telem_phid = TELEM_PHID, // Telemetry physical ID [0 - off, 1..2 - iBUS, 1..28 - S.Port, 1..4 - SBUS2] .telem_poles = TELEM_POLES, // Number of motor poles for RPM telemetry [2..100] .prot_stall = PROT_STALL, // Stall protection (ERPM) [0 - off, 1800..3200] .prot_temp = PROT_TEMP, // Temperature threshold (C) [0 - off, 60..140] .prot_sens = PROT_SENS, // Temperature sensor (0 - ESC, 1 - motor, 2 - both) .prot_volt = PROT_VOLT, // Low voltage cutoff per battery cell (V/10) [0 - off, 28..38] .prot_cells = PROT_CELLS, // Number of battery cells [0 - auto, 1..24] .prot_curr = PROT_CURR, // Maximum current (A) [0..500] .prot_park = PROT_PARK, // Parking speed [0..4] .music = MUSIC, // Startup music .volume = VOLUME, // Sound volume (%) [0..100] .beacon = BEACON, // Beacon volume (%) [0..100] .bec = BEC, // BEC voltage control (0 - 5.5V, 1 - 6.5V, 2 - 7.4V, 3 - 8.4V, 4 - 12V) .led = LED, // LED on/off bits [0..15] }; __attribute__((__section__(".cfg"))) Cfg cfg = cfgdata; int throt, brake, ertm, erpm, temp1, temp2, volt, curr, csum, dshotval, beepval = -1; char analog, telreq, telmode, flipdir, beacon, dshotext, auxup; static int oldstep, step, sine, sync, ival, cutback, led; static char prep, fast, lock, tick, ready, reverse; static uint32_t tickms, tickmsv; static volatile char tickmsf; #ifndef HALL_MAP static const int hall; #else static int hall; static int getcode(void) { int x = -1; for (int i = 0, j = 0; j < 4; ++j) { int y = hallcode(); if (x == y) continue; if (++i == 20) hard_fault_handler(); // Unstable signal x = y; j = 0; } return x; } #endif /* 6-step commutation sequence: # +|- COMP MASK BEMF 1 C|B 101 110 101 2 A|B 011 011 001 3 A|C 110 101 011 4 B|C 001 110 010 5 B|A 111 011 110 6 C|A 010 101 100 */ static void nextstep(void) { if (sine) { // Sine startup TIM_ARR(IFTIM) = IFTIM_OCR = sine; TIM_EGR(IFTIM) = TIM_EGR_UG; if (!prep && step) step = step * 60 - 59; // Switch over from 6-step if (reverse) { if (--step < 1) step = 360; } else { if (++step > 360) step = 1; } int a = step - 1; int b = a < 120 ? a + 240 : a - 120; int c = a < 240 ? a + 120 : a - 240; int p = min(cfg.sine_power << 3, 120 - cutback); // 50% cutback at 15C above prot_temp TIM1_CR1 = TIM_CR1_CEN | TIM_CR1_ARPE | TIM_CR1_UDIS; TIM1_ARR = CLK_KHZ / 24 - 1; TIM1_CCR1 = DEAD_TIME + (sinedata[a] * p >> 7); TIM1_CCR2 = DEAD_TIME + (sinedata[b] * p >> 7); TIM1_CCR3 = DEAD_TIME + (sinedata[c] * p >> 7); TIM1_CR1 = TIM_CR1_CEN | TIM_CR1_ARPE; #ifdef ERPM_PIN if (step == 1) GPIO(ERPM_PORT, BSRR) = 1 << (ERPM_PIN + 16); else if (step == 181) GPIO(ERPM_PORT, BSRR) = 1 << ERPM_PIN; #endif if (prep) return; TIM1_CCMR1 = TIM_CCMR1_OC1PE | TIM_CCMR1_OC1M_PWM1 | TIM_CCMR1_OC2PE | TIM_CCMR1_OC2M_PWM1; TIM1_CCMR2 = TIM_CCMR2_OC3PE | TIM_CCMR2_OC3M_PWM1; #ifdef PWM_ENABLE int er = TIM_CCER_CC1E | TIM_CCER_CC1NP | TIM_CCER_CC2E | TIM_CCER_CC2NP | TIM_CCER_CC3E | TIM_CCER_CC3NP; #else int er = TIM_CCER_CC1E | TIM_CCER_CC1NE | TIM_CCER_CC2E | TIM_CCER_CC2NE | TIM_CCER_CC3E | TIM_CCER_CC3NE; #endif #ifdef INVERTED_HIGH er |= TIM_CCER_CC1P | TIM_CCER_CC2P | TIM_CCER_CC3P; #endif TIM1_CCER = er; TIM1_EGR = TIM_EGR_UG | TIM_EGR_COMG; TIM_DIER(IFTIM) = 0; compctl(0); sync = 0; prep = 1; return; } #ifdef HALL_MAP static const char map[][2] = {{2, 4}, {4, 6}, {3, 5}, {6, 2}, {1, 3}, {5, 1}}; // Hall sensor code mapping if (hall > 4000) { int code = getcode(); if (code < 1 || code > 6) hard_fault_handler(); // Invalid Hall sensor code step = map[code - 1][!!reverse]; } else #endif if (reverse) { if (--step < 1) step = 6; } else { if (++step > 6) step = 1; } static const uint16_t seq[] = {0x175, 0xd9, 0x1ab, 0x72, 0x1de, 0xac}; // Commutation sequence static int pcc, val, cnt, buf[6]; int x = seq[step - 1]; int m = x >> 3; // Energized phase mask int p = x & m; // Positive phase int n = ~x & m; // Negative phase int cc = m >> 3 ^ reverse << 2; // Floating phase int m1 = TIM_CCMR1_OC1PE | TIM_CCMR1_OC2PE; #ifdef TIM1_CCR5 int m2 = TIM_CCMR2_OC3PE; int er = TIM_CCER_CC5E; #else int m2 = TIM_CCMR2_OC3PE | TIM_CCMR2_OC4PE | TIM_CCMR2_OC4M_PWM1; int er = TIM_CCER_CC4E; #endif if (cfg.throt_ztc && !throt) p = n = 0; // Zero-throttle coasting if (p & 1) { m1 |= TIM_CCMR1_OC1M_PWM1; #ifdef PWM_ENABLE er |= TIM_CCER_CC1E; #else er |= cfg.damp ? TIM_CCER_CC1E | TIM_CCER_CC1NE : TIM_CCER_CC1E; #endif } else if (n & 1) { #ifdef PWM_ENABLE m1 |= TIM_CCMR1_OC1M_FORCE_LOW; #else m1 |= TIM_CCMR1_OC1M_FORCE_HIGH; #endif er |= TIM_CCER_CC1NE; } else { #ifdef PWM_ENABLE m1 |= TIM_CCMR1_OC1M_FORCE_HIGH; #else m1 |= TIM_CCMR1_OC1M_FORCE_LOW; #endif er |= TIM_CCER_CC1NE; } if (p & 2) { m1 |= TIM_CCMR1_OC2M_PWM1; #ifdef PWM_ENABLE er |= TIM_CCER_CC2E; #else er |= cfg.damp ? TIM_CCER_CC2E | TIM_CCER_CC2NE : TIM_CCER_CC2E; #endif } else if (n & 2) { #ifdef PWM_ENABLE m1 |= TIM_CCMR1_OC2M_FORCE_LOW; #else m1 |= TIM_CCMR1_OC2M_FORCE_HIGH; #endif er |= TIM_CCER_CC2NE; } else { #ifdef PWM_ENABLE m1 |= TIM_CCMR1_OC2M_FORCE_HIGH; #else m1 |= TIM_CCMR1_OC2M_FORCE_LOW; #endif er |= TIM_CCER_CC2NE; } if (p & 4) { m2 |= TIM_CCMR2_OC3M_PWM1; #ifdef PWM_ENABLE er |= TIM_CCER_CC3E; #else er |= cfg.damp ? TIM_CCER_CC3E | TIM_CCER_CC3NE : TIM_CCER_CC3E; #endif } else if (n & 4) { #ifdef PWM_ENABLE m2 |= TIM_CCMR2_OC3M_FORCE_LOW; #else m2 |= TIM_CCMR2_OC3M_FORCE_HIGH; #endif er |= TIM_CCER_CC3NE; } else { #ifdef PWM_ENABLE m2 |= TIM_CCMR2_OC3M_FORCE_HIGH; #else m2 |= TIM_CCMR2_OC3M_FORCE_LOW; #endif er |= TIM_CCER_CC3NE; } #ifdef PWM_ENABLE er |= TIM_CCER_CC1NP | TIM_CCER_CC2NP | TIM_CCER_CC3NP; #endif #ifdef INVERTED_HIGH er |= TIM_CCER_CC1P | TIM_CCER_CC2P | TIM_CCER_CC3P; #endif TIM1_CCMR1 = m1; TIM1_CCMR2 = m2; TIM1_CCER = er; compctl(pcc); pcc = cc; if (ival > 1000 << IFTIM_XRES) { val = 1000 << IFTIM_XRES; cnt = 0; } else if (++cnt == 6) { if (abs(val - ival) > ival >> 1) { // Probably desync sync = 0; fast = 0; ival = 5000 << IFTIM_XRES; ertm = 100000000; } val = ival; cnt = 0; } if (ertm < 100) { // 600K+ ERPM #ifdef TIM1_CCR5 TIM1_CCR5 = 0; #else TIM1_CCR4 = 0; #endif IFTIM_ICMR = IFTIM_ICM3; TIM_CR1(IFTIM) = TIM_CR1_CEN | TIM_CR1_ARPE | TIM_CR1_URS; } else if (ertm < 200) { // 300K+ ERPM #ifdef TIM1_CCR5 TIM1_CCR5 = 0; #else TIM1_CCR4 = 0; #endif IFTIM_ICMR = IFTIM_ICM2; TIM_CR1(IFTIM) = TIM_CR1_CEN | TIM_CR1_ARPE | TIM_CR1_URS; } else if (ertm < 1000) { // 60K+ ERPM #ifdef TIM1_CCR5 TIM1_CCR5 = 0; #else TIM1_CCR4 = 0; #endif IFTIM_ICMR = IFTIM_ICM1; TIM_CR1(IFTIM) = TIM_CR1_CEN | TIM_CR1_ARPE | TIM_CR1_URS; } else if (ertm < 2000) { // 30K+ ERPM #ifdef TIM1_CCR5 TIM1_CCR5 = 0; #else TIM1_CCR4 = 0; #endif IFTIM_ICMR = IFTIM_ICM1; TIM_CR1(IFTIM) = TIM_CR1_CEN | TIM_CR1_ARPE | TIM_CR1_URS | TIM_CR1_CKD_CK_INT_MUL_2; } else { // Minimum PWM frequency #ifdef TIM1_CCR5 TIM1_CCR5 = IFTIM_ICFL << 2; #else TIM1_CCR4 = IFTIM_ICFL << 2; #endif IFTIM_ICMR = IFTIM_ICM1; TIM_CR1(IFTIM) = TIM_CR1_CEN | TIM_CR1_ARPE | TIM_CR1_URS | TIM_CR1_CKD_CK_INT_MUL_4; } TIM_SR(IFTIM) = 0; // Clear BEMF events before enabling interrupts TIM_DIER(IFTIM) = TIM_DIER_UIE | IFTIM_ICIE; buf[step - 1] = hall > 4000 ? hall << IFTIM_XRES : ival; if (sync < 6) return; ertm = (buf[0] + buf[1] + buf[2] + buf[3] + buf[4] + buf[5]) >> (IFTIM_XRES + 1); // Electrical revolution time (us) #ifdef ERPM_PIN if (step == 1) GPIO(ERPM_PORT, BSRR) = 1 << (ERPM_PIN + 16); else if (step == 4) GPIO(ERPM_PORT, BSRR) = 1 << ERPM_PIN; #endif } static void laststep(void) { resetcom(); if (sine && prep) { // Switch over to 6-step step = (step + 29) / 60 + 1; if (step > 6) step = 1; } sine = 0; prep = 0; if (lock) nextstep(); else { #ifdef PWM_ENABLE TIM1_CCMR1 = TIM_CCMR1_OC1PE | TIM_CCMR1_OC1M_PWM2 | TIM_CCMR1_OC2PE | TIM_CCMR1_OC2M_PWM2; TIM1_CCMR2 = TIM_CCMR2_OC3PE | TIM_CCMR2_OC3M_PWM2; #else TIM1_CCMR1 = TIM_CCMR1_OC1PE | TIM_CCMR1_OC1M_PWM1 | TIM_CCMR1_OC2PE | TIM_CCMR1_OC2M_PWM1; TIM1_CCMR2 = TIM_CCMR2_OC3PE | TIM_CCMR2_OC3M_PWM1; #endif } TIM1_EGR = TIM_EGR_UG | TIM_EGR_COMG; compctl(0); oldstep = step; step = 0; } void tim1_com_isr(void) { if (!(TIM1_DIER & TIM_DIER_COMIE)) return; #if !defined STM32G4 && !defined AT32F4 int m1 = TIM1_CCMR1; int m2 = TIM1_CCMR2; #ifdef PWM_ENABLE if ((m1 & TIM_CCMR1_OC1M_MASK) == TIM_CCMR1_OC1M_FORCE_HIGH) m1 &= ~TIM_CCMR1_OC1M_MASK; if ((m1 & TIM_CCMR1_OC2M_MASK) == TIM_CCMR1_OC2M_FORCE_HIGH) m1 &= ~TIM_CCMR1_OC2M_MASK; if ((m2 & TIM_CCMR2_OC3M_MASK) == TIM_CCMR2_OC3M_FORCE_HIGH) m2 &= ~TIM_CCMR2_OC3M_MASK; #else if ((m1 & TIM_CCMR1_OC1M_MASK) == TIM_CCMR1_OC1M_FORCE_LOW) m1 &= ~TIM_CCMR1_OC1M_MASK; if ((m1 & TIM_CCMR1_OC2M_MASK) == TIM_CCMR1_OC2M_FORCE_LOW) m1 &= ~TIM_CCMR1_OC2M_MASK; if ((m2 & TIM_CCMR2_OC3M_MASK) == TIM_CCMR2_OC3M_FORCE_LOW) m2 &= ~TIM_CCMR2_OC3M_MASK; #endif TIM1_CCMR1 = m1; TIM1_CCMR2 = m2; TIM1_EGR = TIM_EGR_COMG; #endif TIM1_SR = ~TIM_SR_COMIF; nextstep(); } void iftim_isr(void) { // BEMF zero-crossing int er = TIM_DIER(IFTIM); int sr = TIM_SR(IFTIM); if ((er & TIM_DIER_UIE) && (sr & TIM_SR_UIF)) { // Timeout TIM_SR(IFTIM) = ~TIM_SR_UIF; TIM_DIER(IFTIM) = 0; sync = 0; fast = 0; ival = 10000 << IFTIM_XRES; ertm = 100000000; return; } if (!(er & IFTIM_ICIE)) return; int t = IFTIM_ICR; // Time since last zero-crossing if (t < ival >> 1) return; int u = ival * 3; fast = (t < u >> 2 || t > u >> 1) && ertm < 2000; // Fast acceleration/deceleration ival = (t + u) >> 2; // Commutation interval IFTIM_OCR = max((ival - (ival * cfg.timing >> 5)) >> 1, 1); // Commutation delay TIM_EGR(IFTIM) = TIM_EGR_UG; TIM_DIER(IFTIM) = 0; if (sync < 6) ++sync; } #ifdef HALL_MAP void tim3_isr(void) { // Any change on Hall sensor inputs if (TIM3_SR & TIM_SR_UIF) { // Timeout TIM3_SR = ~TIM_SR_UIF; hall = 0x10000; if (sine || !step) return; sync = 0; fast = 0; ival = 10000 << IFTIM_XRES; ertm = 100000000; return; } hall = (TIM3_CCR1 + hall * 3) >> 2; if (hall < 5000 || sine || !step) return; ival = hall << IFTIM_XRES; TIM1_EGR = TIM_EGR_COMG; TIM_EGR(IFTIM) = TIM_EGR_UG; TIM_DIER(IFTIM) = 0; if (sync < 6) ++sync; } #endif void adcdata(int t, int u, int v, int c, int a) { static int z = 3300, i, q, st = -1, su = -1, sv = -1, sc = -1, sa = -1; if ((c -= z) >= 0) ready = 1; else { if (!ready) z += c >> 1; c = 0; } temp1 = max((t = smooth(&st, t, 10)) >> 2, 0); // C temp2 = hall || cfg.prot_sens ? max((u = smooth(&su, TEMP_SENS(u), 10)) >> 2, 0) : 0; // C volt = smooth(&sv, v * VOLT_MUL * 131 >> 17, 7); // V/100 curr = smooth(&sc, c * CURR_MUL * 205 >> 11, 4); // A/100 i += curr; // Current integral if (!(tickms & 0x3ff)) { csum = (q += i >> 10) * 91 >> 15; // mAh i = 0; } if (cfg.prot_temp) { // Temperature protection int x = -(cfg.prot_temp << 2); switch (cfg.prot_sens) { case 0: x += t; break; case 1: x += u; break; case 2: x += max(t, u); break; } cutback = clamp(x, 0, 60); } if (!analog) return; throt = scale(smooth(&sa, a, 5), cfg.analog_min, cfg.analog_max, cfg.throt_set * 20, 2000); } void sys_tick_handler(void) { SCB_ICSR = SCB_ICSR_PENDSVSET; // Continue with low priority SCB_SCR = 0; // Resume main loop if (++tick & 15) return; // 16kHz -> 1kHz if (++tickms == tickmsv) tickmsf = 0; } void delay(int ms, Func f) { __disable_irq(); tickmsv = tickms + ms; tickmsf = 1; __enable_irq(); while (tickmsf) f(); } void pend_sv_handler(void) { static int a = -1; if (telreq && !telmode) { // Telemetry request kisstelem(); telreq = 0; } if (tick & 15) return; // 16kHz -> 1kHz adctrig(); if (!(tickms & 31)) autotelem(); // Telemetry every 32ms int b = cfg.led ? cfg.led : led; if (a != b) ledctl(a = b); // Update LED } void hard_fault_handler(void) { ledctl(1); // Indicate error TIM1_EGR = TIM_EGR_BG; TIM6_PSC = CLK_KHZ / 10 - 1; // 0.1ms resolution TIM6_ARR = 9999; TIM6_EGR = TIM_EGR_UG; TIM6_SR = ~TIM_SR_UIF; TIM6_CR1 = TIM_CR1_CEN | TIM_CR1_OPM; while (TIM6_CR1 & TIM_CR1_CEN); // Wait for 1s WWDG_CR = WWDG_CR_WDGA; // Trigger watchdog reset for (;;); // Never return } static void delayf(void) { TIM6_EGR = TIM_EGR_UG; // Reset arming timeout } static void beep(void) { static const char *const beacons[] = {"EG", "FA", "GB", "AB#", "aDGE"}; static const char *const values[] = {"c6", "C2", "D2C2", "E2D2C2", "F#2E2D2C2", "G#A#G#A#G#2", "G#A#G#A#F#2G#2", "G#A#G#A#E2F#2G#2", "G#A#G#A#D2E2F#2G#2", "G#A#G#A#C2D2E2F#2G#2", 0}; if (beacon) { playmusic(beacons[beacon - 1], cfg.beacon); beacon = 0; } if (beepval < 0) return; int i[10], n = 0, x = beepval, vol = max(cfg.volume, 25); while (i[n++] = x % 10, x /= 10); while (n--) { if (x++) delay(500, delayf); playmusic(values[i[n]], vol); } beepval = -1; } #ifdef PARK_PIN static char park1; static uint16_t park2, park3; static int park(void) { if (!--park3) return 0; if (park1 == 3 || ++park2 < 800) return 1; int x = -1; for (int i = 0, j = 0; j < 4; ++j) { int y = GPIO(PARK_PORT, IDR) & (1 << PARK_PIN); if (x == y) continue; if (++i == 20) return 0; // Unstable signal x = y; j = 0; } switch (park1) { case 0: if (!x) return 1; park1 = 1; break; case 1: if (x) return 1; park1 = 2; park3 = -1; break; case 2: if (!x) return 1; park1 = 3; park3 = (0xffff - park3) >> 1; reverse = !reverse; break; } park2 = 0; return 1; } #endif void main(void) { memcpy(_cfg_start, _cfg, _cfg_end - _cfg_start); // Copy configuration to SRAM checkcfg(); const int brushed = cfg.brushed; throt = cfg.throt_set * 20; brake = cfg.duty_drag; lock = cfg.duty_lock; telmode = cfg.telem_mode; #if defined ANALOG || defined ANALOG_CHAN analog = IO_ANALOG; #endif init(); initgpio(); initled(); inittelem(); #ifndef ANALOG initio(); #endif TIM1_BDTR = TIM_DTG | TIM_BDTR_OSSR | TIM_BDTR_MOE; TIM1_ARR = CLK_KHZ / 24 - 1; TIM1_CR1 = TIM_CR1_CEN | TIM_CR1_ARPE; #ifdef STM32G0 TIM1_CR2 = TIM_CR2_CCPC | TIM_CR2_CCUS | TIM_CR2_MMS_COMPARE_PULSE << 16; // TRGO2=OC1 #else TIM1_CR2 = TIM_CR2_CCPC | TIM_CR2_CCUS | TIM_CR2_MMS_COMPARE_PULSE; // TRGO=OC1 #endif TIM_PSC(IFTIM) = (CLK_MHZ >> (IFTIM_XRES + 1)) - 1; // 125/250/500ns resolution TIM_ARR(IFTIM) = 0; TIM_CR1(IFTIM) = TIM_CR1_URS; TIM_EGR(IFTIM) = TIM_EGR_UG; TIM_CR1(IFTIM) = TIM_CR1_CEN | TIM_CR1_ARPE | TIM_CR1_URS; #ifdef HALL_MAP if (!brushed && getcode() != 7) { // Hybrid mode TIM3_CCMR1 = TIM_CCMR1_CC1S_IN_TRC | TIM_CCMR1_IC1F_DTF_DIV_8_N_8; TIM3_SMCR = TIM_SMCR_SMS_RM | TIM_SMCR_TS_TI1F_ED; // Reset on any edge on TI1 TIM3_CCER = TIM_CCER_CC1E; // IC1 on any edge on TI1 TIM3_DIER = TIM_DIER_UIE | TIM_DIER_CC1IE; TIM3_PSC = CLK_MHZ / 2 - 1; // 500ns resolution TIM3_ARR = -1; TIM3_CR1 = TIM_CR1_URS; #ifdef USE_XOR TIM3_CR2 = TIM_CR2_TI1S; #endif TIM3_EGR = TIM_EGR_UG; TIM3_CR1 = TIM_CR1_CEN | TIM_CR1_ARPE | TIM_CR1_URS; hall = 0x10000; } #endif nvic_set_priority(NVIC_PENDSV_IRQ, 0x80); STK_RVR = CLK_KHZ / 16 - 1; // 16kHz STK_CVR = 0; STK_CSR = STK_CSR_ENABLE | STK_CSR_TICKINT | STK_CSR_CLKSOURCE_AHB; #ifndef ANALOG int cells = cfg.prot_cells; #if SENS_CNT > 0 while (!ready) __WFI(); // Wait for sensors if (!cells) cells = (volt + 439) / 440; // Assume maximum 4.4V per battery cell #endif int csr = RCC_CSR; RCC_CSR = RCC_CSR_RMVF; // Clear reset flags if (!(csr & (RCC_CSR_IWDGRSTF | RCC_CSR_WWDGRSTF))) { // Power-on const char *str = cfg.music; if (str[0] == '~') playsound(_eod, clamp(atoi(str + 1), 0, 100)); else playmusic(str, cfg.volume); if (cfg.prot_volt) { // Report the number of battery cells beepval = cells; delay(250, delayf); beep(); } } if (cfg.arm || (csr & RCC_CSR_WWDGRSTF)) { // Arming required rearm: TIM6_PSC = CLK_KHZ / 10 - 1; // 0.1ms resolution TIM6_ARR = 2499; // 250ms TIM6_CR1 = TIM_CR1_URS; TIM6_EGR = TIM_EGR_UG; TIM6_CR1 = TIM_CR1_CEN | TIM_CR1_URS; TIM6_SR = ~TIM_SR_UIF; throt = 1; while (!(TIM6_SR & TIM_SR_UIF)) { // Wait for 250ms zero throttle __WFI(); beep(); if (!throt) continue; TIM6_EGR = TIM_EGR_UG; } throt = 0; TIM6_CR1 = 0; playmusic(hall ? "G_GC" : "GC", cfg.volume); } #endif laststep(); PID bpid = {.Kp = 50, .Ki = 0, .Kd = 1000}; // Stall protection PID cpid = {.Kp = 80, .Ki = 0, .Kd = 600}; // Overcurrent protection for (int curduty = 0, running = 0, braking = 2, cutoff = 0, boost = 0, choke = 0, n = 0;;) { int ccr, arr = CLK_KHZ / cfg.freq_min; int input = cutoff == 3000 ? 0 : throt; int range = cfg.sine_range * 20; int delta = range ? 10 : 0; int newduty = 0; if (!running) curduty = 0; if (!input) { // Neutral if (braking == 1) braking = 2; // Reverse after braking if (lock != cfg.duty_lock && !brushed) { // Switch brake mode lock = cfg.duty_lock; if (!running) laststep(); } if (sync < 6 || erpm < 800 || lock == 2) { // Drag brake #ifdef PARK_PIN if (cfg.prot_park && running && park()) { // Parking sine = (1000 << IFTIM_XRES) / cfg.prot_park; ertm = 100000000; erpm = 0; goto skipduty; } #endif curduty = lock ? min(brake, 100 - cutback) : brake * 20; // 60% cutback at 15C above prot_temp running = 0; goto setduty; } boost = 0; // Coasting goto calcduty; } #ifdef PARK_PIN park1 = 0; park2 = 0; park3 = -1; #endif if (input < 0) { // Reverse if ((cfg.throt_mode == 2 && braking != 2) || cfg.throt_mode == 3) { // Proportional brake curduty = scale(input, -2000, 0, cfg.throt_brk * 20, brake * 20); running = 0; braking = 1; goto setduty; } input = input * (cfg.throt_rev - 4) >> 2; reverse = !cfg.revdir ^ flipdir; running = 1; } else { // Forward reverse = cfg.revdir ^ flipdir; running = 1; braking = 0; } if (range + (sine ? delta : -delta) < input) newduty = scale(input, range + delta, 2000, cfg.duty_min * 20, cfg.duty_max * 20); else sine = scale(input, 0, range - delta, 1000 << IFTIM_XRES, cfg.prot_stall ? (333333 << IFTIM_XRES) / cfg.prot_stall : 145 << IFTIM_XRES); if (sine) { // Sine startup if (!newduty) { if (!ertm) goto skipduty; ertm = sine * (180 >> IFTIM_XRES); erpm = 60000000 / ertm; goto skipduty; } __disable_irq(); if (prep) { // Switch over to 6-step int a = step - 1; int b = a / 60; int c = b * 60; IFTIM_OCR = sine * (reverse ? (void)(++b == 6 && (b = 0)), a - c + 1 : c - a + 60); // Commutation delay TIM_ARR(IFTIM) = (1 << (IFTIM_XRES + 16)) - 1; TIM_EGR(IFTIM) = TIM_EGR_UG; step = b + 1; } sine = 0; prep = 0; sync = 0; fast = 0; ival = 10000 << IFTIM_XRES; nextstep(); __enable_irq(); initpid(&bpid, 10000 << IFTIM_XRES); curduty = 0; boost = 0; } calcduty: if (brushed && step != reverse + 1) step = 0; // Change brushed direction if ((newduty += boost - choke) < 0) newduty = 0; if (ertm) { // Variable PWM frequency arr = scale(ertm, 1000, 2000, CLK_KHZ / cfg.freq_max, arr); // 30..60 kERPM erpm = 60000000 / ertm; } int maxduty = min(scale(erpm, 0, cfg.duty_ramp * 1000, cfg.duty_spup * 20, 2000), 2000 - cutback * 25); // 75% cutback at 15C above prot_temp if (newduty > maxduty) newduty = maxduty; int a = fast ? 0 : cfg.duty_rate; int b = a >> 3; if (n < (a & 7)) ++b; if (++n == 8) n = 0; if (curduty > newduty ? sync < 6 || (curduty -= b) < newduty : (curduty += b) > newduty) curduty = newduty; // Duty cycle slew rate limiting setduty: #ifdef FULL_DUTY // Allow 100% duty cycle ccr = scale(curduty, 0, 2000, lock || (running && cfg.damp) ? DEAD_TIME : 0, arr--); #else ccr = scale(curduty, 0, 2000, lock || (running && cfg.damp) ? DEAD_TIME : 0, brushed ? arr - (CLK_MHZ * 3 >> 1) : arr); #endif TIM1_CR1 = TIM_CR1_CEN | TIM_CR1_ARPE | TIM_CR1_UDIS; TIM1_ARR = arr; TIM1_CCR1 = ccr; TIM1_CCR2 = ccr; TIM1_CCR3 = ccr; TIM1_CR1 = TIM_CR1_CEN | TIM_CR1_ARPE; skipduty: if (running && !step) { // Start motor if (brushed) { int m1 = TIM_CCMR1_OC1PE | TIM_CCMR1_OC2PE; int m2 = TIM_CCMR2_OC3PE; #ifdef PWM_ENABLE int er = TIM_CCER_CC1E | TIM_CCER_CC1NP | TIM_CCER_CC2E | TIM_CCER_CC2NP | TIM_CCER_CC3E | TIM_CCER_CC3NP; if (reverse) { m1 |= TIM_CCMR1_OC1M_FORCE_LOW | TIM_CCMR1_OC2M_PWM1; m2 |= TIM_CCMR2_OC3M_FORCE_LOW; } else { m1 |= TIM_CCMR1_OC1M_PWM1 | TIM_CCMR1_OC2M_FORCE_LOW; m2 |= TIM_CCMR2_OC3M_PWM1; } #else int er = 0; if (reverse) { m1 |= TIM_CCMR1_OC1M_FORCE_HIGH | TIM_CCMR1_OC2M_PWM1; m2 |= TIM_CCMR2_OC3M_FORCE_HIGH; er |= TIM_CCER_CC1NE | TIM_CCER_CC2E | TIM_CCER_CC3NE; if (cfg.damp) er |= TIM_CCER_CC2NE; } else { m1 |= TIM_CCMR1_OC1M_PWM1 | TIM_CCMR1_OC2M_FORCE_HIGH; m2 |= TIM_CCMR2_OC3M_PWM1; er |= TIM_CCER_CC1E | TIM_CCER_CC2NE | TIM_CCER_CC3E; if (cfg.damp) er |= TIM_CCER_CC1NE | TIM_CCER_CC3NE; } #endif #ifdef INVERTED_HIGH er |= TIM_CCER_CC1P | TIM_CCER_CC2P | TIM_CCER_CC3P; #endif TIM1_CCMR1 = m1; TIM1_CCMR2 = m2; TIM1_CCER = er; TIM1_EGR = TIM_EGR_UG | TIM_EGR_COMG; step = reverse + 1; ertm = 600; // 100K ERPM (freq_min/duty_spup/duty_ramp have no effect) goto tick; } __disable_irq(); step = oldstep; ival = 10000 << IFTIM_XRES; ertm = 100000000; nextstep(); TIM1_EGR = TIM_EGR_UG | TIM_EGR_COMG; #ifdef SW_BLANKING TIM1_DIER |= TIM_DIER_COMIE | TIM_DIER_UIE | TIM_DIER_CC4IE; #else TIM1_DIER |= TIM_DIER_COMIE; #endif TIM_ARR(IFTIM) = IFTIM_OCR = (1 << (IFTIM_XRES + 16)) - 1; TIM_EGR(IFTIM) = TIM_EGR_UG; __enable_irq(); initpid(&bpid, 10000 << IFTIM_XRES); boost = 0; } else if (!running && step) { // Stop motor __disable_irq(); #ifdef SW_BLANKING TIM1_DIER &= ~(TIM_DIER_COMIE | TIM_DIER_UIE | TIM_DIER_CC4IE); #else TIM1_DIER &= ~TIM_DIER_COMIE; #endif TIM_DIER(IFTIM) = 0; TIM_ARR(IFTIM) = 0; TIM_EGR(IFTIM) = TIM_EGR_UG; laststep(); sync = 0; fast = 0; ertm = 0; erpm = 0; __enable_irq(); } tick: SCB_SCR = SCB_SCR_SLEEPONEXIT; // Suspend main loop __WFI(); if (tick & 15) continue; // 16kHz -> 1kHz #ifndef ANALOG if (++auxup == 100) { // Restore brake after 100ms timeout brake = cfg.duty_drag; auxup = 0; } if (cutoff < 3000) cutoff = volt < cfg.prot_volt * cells * 10 ? cutoff + 1 : 0; else if (!running) goto rearm; // Low voltage cutoff after 3s #endif boost = cfg.prot_stall ? clamp(boost + (calcpid(&bpid, hall > 4000 ? hall : ival >> IFTIM_XRES, 20000000 / cfg.prot_stall - 800) >> 16), 0, 160) : 0; // Up to 8% choke = cfg.prot_curr ? clamp(choke + (calcpid(&cpid, curr, cfg.prot_curr * 100) >> 10), 0, 2000) : 0; beep(); #ifdef LED_STAT int x = 0; if (running) { if ((tickms << (throt < 0) & 0x1ff) < (sine ? 0x180 : 0x100)) x = LED_CNT >= 2 ? 2 : 1; } else if (curduty) { if ((tickms & (lock ? 0x2ff : 0x3ff)) < 0x40) x = LED_CNT >= 3 ? 4 : LED_CNT; } if (cutback || cutoff || choke) x |= 1; led = x; #endif } } ================================================ FILE: src/telem.c ================================================ /* ** Copyright (C) Arseny Vakhrushev ** ** This firmware is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This firmware is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this firmware. If not, see . */ #include "common.h" #ifndef USART1_DMA_BASE #define USART1_DMA_BASE DMA1_BASE #endif #ifdef AT32F4 #define USART1_TDR USART1_DR #define USART1_RDR USART1_DR #endif static int ibusfunc(int len); static int sportfunc(int len); static int (*iofunc)(int len); static char iobuf[16]; void inittelem(void) { USART1_BRR = CLK_CNT(115200); switch (telmode) { case 2: // iBUS iofunc = ibusfunc; #ifndef AT32F4 USART1_RTOR = 12; // TX delay ~100us USART1_CR2 = USART_CR2_RTOEN; #endif break; case 3: // S.Port iofunc = sportfunc; USART1_BRR = CLK_CNT(57600); #ifndef AT32F4 USART1_RTOR = 26; // TX delay ~450us USART1_CR2 = USART_CR2_RTOEN | USART_CR2_RXINV | USART_CR2_TXINV; GPIOB_PUPDR = (GPIOB_PUPDR & ~0x3000) | 0x2000; // B6 (pull-down) #endif break; case 4: // CRSF USART1_BRR = CLK_CNT(416666); break; } if (iofunc) { USART1_CR3 = USART_CR3_HDSEL | USART_CR3_DMAT | USART_CR3_DMAR; USART1_CR1 = USART_CR1_UE | USART_CR1_TE | USART_CR1_RE | USART_CR1_IDLEIE; } else { USART1_CR3 = USART_CR3_HDSEL | USART_CR3_DMAT; USART1_CR1 = USART_CR1_UE | USART_CR1_TE; } DMA_CPAR(USART1_DMA_BASE, USART1_RX_DMA) = (uint32_t)&USART1_RDR; DMA_CMAR(USART1_DMA_BASE, USART1_RX_DMA) = (uint32_t)iobuf; DMA_CPAR(USART1_DMA_BASE, USART1_TX_DMA) = (uint32_t)&USART1_TDR; DMA_CMAR(USART1_DMA_BASE, USART1_TX_DMA) = (uint32_t)iobuf; } void usart1_isr(void) { int cr = USART1_CR1; if (cr & USART_CR1_TCIE) goto reading; #ifdef AT32F4 USART1_SR, USART1_DR; // Clear flags #else USART1_RQR = USART_RQR_RXFRQ; // Clear RXNE USART1_ICR = USART_ICR_IDLECF | USART_ICR_ORECF | USART_ICR_RTOCF; #endif int len = iofunc(sizeof iobuf - DMA_CNDTR(USART1_DMA_BASE, USART1_RX_DMA)); if (len) { #ifdef AT32F4 USART1_SR = ~USART_SR_TC; #else USART1_ICR = USART_ICR_TCCF; #endif USART1_CR1 = USART_CR1_UE | USART_CR1_TE | USART_CR1_TCIE; DMA_CCR(USART1_DMA_BASE, USART1_TX_DMA) = 0; DMA_CNDTR(USART1_DMA_BASE, USART1_TX_DMA) = len; DMA_CCR(USART1_DMA_BASE, USART1_TX_DMA) = DMA_CCR_EN | DMA_CCR_DIR | DMA_CCR_MINC | DMA_CCR_PSIZE_8BIT | DMA_CCR_MSIZE_8BIT; return; } reading: #ifdef AT32F4 USART1_CR1 = USART_CR1_UE | USART_CR1_TE | USART_CR1_RE | USART_CR1_IDLEIE; #else USART1_CR1 = USART_CR1_UE | USART_CR1_TE | USART_CR1_RE | USART_CR1_RTOIE; #endif DMA_CCR(USART1_DMA_BASE, USART1_RX_DMA) = 0; DMA_CNDTR(USART1_DMA_BASE, USART1_RX_DMA) = sizeof iobuf; DMA_CCR(USART1_DMA_BASE, USART1_RX_DMA) = DMA_CCR_EN | DMA_CCR_MINC | DMA_CCR_PSIZE_8BIT | DMA_CCR_MSIZE_8BIT; } void usart1_tx_dma_isr(void) { DMA_IFCR(USART1_DMA_BASE) = DMA_IFCR_CTCIF(USART1_TX_DMA); DMA_CCR(USART1_DMA_BASE, USART1_TX_DMA) = 0; } static int ibusresp(char a, int x) { char b = x, c = x >> 8; int u = 0xfff9 - a - b - c; iobuf[0] = 6; iobuf[1] = a; iobuf[2] = b; iobuf[3] = c; iobuf[4] = u; iobuf[5] = u >> 8; return 6; } static int ibusfunc(int len) { static const uint16_t type[] = {0x201, 0x201, 0x203, 0x205, 0x206, 0x202}; if (len != 4 || iobuf[0] != 4) return 0; // Invalid frame char a = iobuf[1]; if (a + (iobuf[2] | iobuf[3] << 8) != 0xfffb) return 0; // Invalid checksum int n = (a & 0xf) - (cfg.telem_phid - 1) * 6 - 1; if (n < 0 || n > 5) return 0; switch (a >> 4) { case 0x8: return 4; // Probe case 0x9: return ibusresp(a, type[n]); // Type case 0xa: // Value switch (n) { case 0: return ibusresp(a, (temp1 + 40) * 10); case 1: return ibusresp(a, (temp2 + 40) * 10); case 2: return ibusresp(a, volt); case 3: return ibusresp(a, curr); case 4: return ibusresp(a, csum); case 5: return ibusresp(a, min(erpm / (cfg.telem_poles >> 1), 0xffff)); } } return 0; } static void sportbyte(int *crc, int *pos, char b) { *crc += b; *crc += *crc >> 8; *crc &= 0xff; if (b == 0x7d || b == 0x7e) { // Byte stuffing b &= ~0x20; iobuf[(*pos)++] = 0x7d; } iobuf[(*pos)++] = b; } static int sportresp(int t, int v) { int crc = 0, pos = 0; sportbyte(&crc, &pos, 0x10); sportbyte(&crc, &pos, t); sportbyte(&crc, &pos, t >> 8); sportbyte(&crc, &pos, v); sportbyte(&crc, &pos, v >> 8); sportbyte(&crc, &pos, v >> 16); sportbyte(&crc, &pos, v >> 24); sportbyte(&crc, &pos, 0xff - crc); return pos; } static int sportfunc(int len) { static const uint16_t type[] = {0xb70, 0x400, 0x210, 0x200, 0xb30, 0x500}; static int n; if (len != 2 || iobuf[0] != 0x7e) return 0; // Invalid frame if ((iobuf[1] & 0x1f) != cfg.telem_phid - 1) return 0; if (n == 6) n = 0; int t = type[n]; switch (n++) { case 0: return sportresp(t, temp1); case 1: return sportresp(t, temp2); case 2: return sportresp(t, volt); case 3: return sportresp(t, curr * 205 >> 11); case 4: return sportresp(t, csum); case 5: return sportresp(t, erpm / (cfg.telem_poles >> 1)); } return 0; } void kisstelem(void) { if (DMA_CCR(USART1_DMA_BASE, USART1_TX_DMA) & DMA_CCR_EN) return; int r = erpm * 41 >> 12; iobuf[0] = temp1; iobuf[1] = volt >> 8; iobuf[2] = volt; iobuf[3] = curr >> 8; iobuf[4] = curr; iobuf[5] = csum >> 8; iobuf[6] = csum; iobuf[7] = r >> 8; iobuf[8] = r; iobuf[9] = crc8(iobuf, 9); DMA_CNDTR(USART1_DMA_BASE, USART1_TX_DMA) = 10; DMA_CCR(USART1_DMA_BASE, USART1_TX_DMA) = DMA_CCR_EN | DMA_CCR_TCIE | DMA_CCR_DIR | DMA_CCR_MINC | DMA_CCR_PSIZE_8BIT | DMA_CCR_MSIZE_8BIT; } static void crsftelem(void) { static int n; int a, b, len = 0; if (DMA_CCR(USART1_DMA_BASE, USART1_TX_DMA) & DMA_CCR_EN) return; iobuf[0] = 0xc8; switch (n++) { case 0: // Temperature a = temp1 * 10; b = temp2 * 10; iobuf[1] = 0x07; iobuf[2] = 0x0d; iobuf[3] = 0; iobuf[4] = a >> 8; iobuf[5] = a; iobuf[6] = b >> 8; iobuf[7] = b; iobuf[8] = crc8dvbs2(iobuf + 2, 6); len = 9; break; case 1: // Battery a = volt * 205 >> 11; b = curr * 205 >> 11; iobuf[1] = 0x0a; iobuf[2] = 0x08; iobuf[3] = a >> 8; iobuf[4] = a; iobuf[5] = b >> 8; iobuf[6] = b; iobuf[7] = csum >> 16; iobuf[8] = csum >> 8; iobuf[9] = csum; iobuf[10] = 0; iobuf[11] = crc8dvbs2(iobuf + 2, 9); len = 12; break; case 2: // RPM a = erpm / (cfg.telem_poles >> 1); iobuf[1] = 0x06; iobuf[2] = 0x0c; iobuf[3] = 0; iobuf[4] = a >> 16; iobuf[5] = a >> 8; iobuf[6] = a; iobuf[7] = crc8dvbs2(iobuf + 2, 5); len = 8; n = 0; break; } DMA_CNDTR(USART1_DMA_BASE, USART1_TX_DMA) = len; DMA_CCR(USART1_DMA_BASE, USART1_TX_DMA) = DMA_CCR_EN | DMA_CCR_TCIE | DMA_CCR_DIR | DMA_CCR_MINC | DMA_CCR_PSIZE_8BIT | DMA_CCR_MSIZE_8BIT; } void autotelem(void) { static int n; int x = 0; switch (telmode) { case 1: // KISS kisstelem(); break; case 4: // CRSF crsftelem(); break; } if (!dshotext) return; switch (n++) { // Extended DSHOT telemetry case 0: x = 0x200 | (temp1 & 0xff); // ESC temperature (C) break; case 1: x = 0x800 | (temp2 & 0xff); // Motor temperature (C) break; case 2: x = 0x400 | ((volt * 41 >> 10) & 0xff); // Voltage (V/4) break; case 3: x = 0x600 | ((curr * 41 >> 12) & 0xff); // Current (A) n = 0; break; } __disable_irq(); if (!dshotval) dshotval = x; __enable_irq(); } ================================================ FILE: src/util.c ================================================ /* ** Copyright (C) Arseny Vakhrushev ** ** This firmware is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This firmware is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this firmware. If not, see . */ #include "common.h" #ifndef HALL_MAP #elif HALL_MAP == 0xAFB35 #define HALL1_PORT A #define HALL1_PIN1 15 #define HALL2_PORT B #define HALL2_PIN2 3 #define HALL2_PIN3 5 #elif HALL_MAP == 0xB357 #define HALL_PORT B #define HALL_PIN1 3 #define HALL_PIN2 5 #define HALL_PIN3 7 #elif HALL_MAP == 0xB358 #define HALL_PORT B #define HALL_PIN1 3 #define HALL_PIN2 5 #define HALL_PIN3 8 #elif HALL_MAP == 0xB450 #define HALL_PORT B #define HALL_PIN1 4 #define HALL_PIN2 5 #define HALL_PIN3 0 #endif #ifndef BEC_MAP #elif BEC_MAP == 0xA45 #define BEC_PORT A #define BEC_PIN1 4 #define BEC_PIN2 5 #elif BEC_MAP == 0xB35 #define BEC_PORT B #define BEC_PIN1 3 #define BEC_PIN2 5 #elif BEC_MAP == 0xCEF #define BEC_PORT C #define BEC_PIN1 14 #define BEC_PIN2 15 #endif #ifndef LED_MAP #elif LED_MAP == 0xAF #define LED1_PORT A #define LED1_PIN 15 #elif LED_MAP == 0xB8 #define LED1_PORT B #define LED1_PIN 8 #elif LED_MAP == 0xF2AF #define LED1_PORT F #define LED1_PIN 2 #define LED2_PORT A #define LED2_PIN 15 #elif LED_MAP == 0xAFB3B4 #define LED1_PORT A #define LED1_PIN 15 #define LED2_PORT B #define LED2_PIN 3 #define LED3_PORT B #define LED3_PIN 4 #elif LED_MAP == 0xAFB5B3 #define LED1_PORT A #define LED1_PIN 15 #define LED2_PORT B #define LED2_PIN 5 #define LED3_PORT B #define LED3_PIN 3 #elif LED_MAP == 0xB5B3AF #define LED1_PORT B #define LED1_PIN 5 #define LED2_PORT B #define LED2_PIN 3 #define LED3_PORT A #define LED3_PIN 15 #elif LED_MAP == 0xB5B4B3 #define LED1_PORT B #define LED1_PIN 5 #define LED2_PORT B #define LED2_PIN 4 #define LED3_PORT B #define LED3_PIN 3 #elif LED_MAP == 0xB8B5B3 #define LED1_PORT B #define LED1_PIN 8 #define LED2_PORT B #define LED2_PIN 5 #define LED3_PORT B #define LED3_PIN 3 #endif #ifdef LED_INV #define LED1_INV #define LED2_INV #define LED3_INV #endif #ifdef LED_OD #define LED1_OD #define LED2_OD #define LED3_OD #endif #ifndef FLASH_CR_STRT #define FLASH_CR_STRT FLASH_CR_START #endif static char busy; void initgpio(void) { #if defined HALL_MAP && !defined USE_XOR #ifdef HALL1_PORT #ifdef HALL2_PIN2 GPIO(HALL1_PORT, PUPDR) |= 1 << HALL1_PIN1 * 2; GPIO(HALL2_PORT, PUPDR) |= 1 << HALL2_PIN2 * 2 | 1 << HALL2_PIN3 * 2; GPIO(HALL1_PORT, MODER) &= ~(3 << HALL1_PIN1 * 2); GPIO(HALL2_PORT, MODER) &= ~(3 << HALL2_PIN2 * 2 | 3 << HALL2_PIN3 * 2); #else GPIO(HALL1_PORT, PUPDR) |= 1 << HALL1_PIN1 * 2 | 1 << HALL1_PIN2 * 2; GPIO(HALL2_PORT, PUPDR) |= 1 << HALL2_PIN3 * 2; GPIO(HALL1_PORT, MODER) &= ~(3 << HALL1_PIN1 * 2 | 3 << HALL1_PIN2 * 2); GPIO(HALL2_PORT, MODER) &= ~(3 << HALL2_PIN3 * 2); #endif #else GPIO(HALL_PORT, PUPDR) |= 1 << HALL_PIN1 * 2 | 1 << HALL_PIN2 * 2 | 1 << HALL_PIN3 * 2; GPIO(HALL_PORT, MODER) &= ~(3 << HALL_PIN1 * 2 | 3 << HALL_PIN2 * 2 | 3 << HALL_PIN3 * 2); #endif #endif #ifdef BEC_MAP int x = cfg.bec - BEC_MIN; #if BEC_MAP == 0xADE // SWD pins if (!(GPIOA_IDR & 0x6000)) { // External pull-down GPIOA_ODR |= (x & 3) << 13; GPIOA_OSPEEDR &= ~0x3c000000; // A13,A14 (low speed) GPIOA_PUPDR &= ~0x3c000000; // A13,A14 (no pull-up/pull-down) GPIOA_MODER ^= 0x3c000000; // A13,A14 (output) } #else int y = GPIO(BEC_PORT, ODR) | (x & 1) << BEC_PIN1; int z = GPIO(BEC_PORT, MODER) & ~(2 << BEC_PIN1 * 2); #ifdef BEC_PIN2 #if BEC_PIN2 >= 1 y |= (x & 2) << (BEC_PIN2 - 1); #else y |= (x & 2) >> (1 - BEC_PIN2); #endif z &= ~(2 << BEC_PIN2 * 2); #ifdef BEC_PIN3 #if BEC_PIN3 >= 2 y |= (x & 4) << (BEC_PIN3 - 2); #else y |= (x & 4) >> (2 - BEC_PIN3); #endif z &= ~(2 << BEC_PIN3 * 2); #endif #endif GPIO(BEC_PORT, ODR) = y; GPIO(BEC_PORT, MODER) = z; #endif #endif #ifdef ERPM_PIN GPIO(ERPM_PORT, MODER) = (GPIO(ERPM_PORT, MODER) & ~(3 << ERPM_PIN * 2)) | 1 << ERPM_PIN * 2; #endif #ifdef PARK_PIN GPIO(PARK_PORT, PUPDR) |= 1 << PARK_PIN * 2; GPIO(PARK_PORT, MODER) &= ~(3 << PARK_PIN * 2); #endif } __attribute__((__weak__)) void initled(void) { #ifdef LED1_PORT #ifdef LED1_INV GPIO(LED1_PORT, ODR) |= 1 << LED1_PIN; #endif #ifdef LED1_OD GPIO(LED1_PORT, OTYPER) |= 1 << LED1_PIN; #endif GPIO(LED1_PORT, MODER) &= ~(2 << LED1_PIN * 2); #endif #ifdef LED2_PORT #ifdef LED2_INV GPIO(LED2_PORT, ODR) |= 1 << LED2_PIN; #endif #ifdef LED2_OD GPIO(LED2_PORT, OTYPER) |= 1 << LED2_PIN; #endif GPIO(LED2_PORT, MODER) &= ~(2 << LED2_PIN * 2); #endif #ifdef LED3_PORT #ifdef LED3_INV GPIO(LED3_PORT, ODR) |= 1 << LED3_PIN; #endif #ifdef LED3_OD GPIO(LED3_PORT, OTYPER) |= 1 << LED3_PIN; #endif GPIO(LED3_PORT, MODER) &= ~(2 << LED3_PIN * 2); #endif #ifdef LED4_PORT #ifdef LED4_INV GPIO(LED4_PORT, ODR) |= 1 << LED4_PIN; #endif #ifdef LED4_OD GPIO(LED4_PORT, OTYPER) |= 1 << LED4_PIN; #endif GPIO(LED4_PORT, MODER) &= ~(2 << LED4_PIN * 2); #endif } #ifdef HALL_MAP int hallcode(void) { #ifdef HALL1_PORT int x1 = GPIO(HALL1_PORT, IDR); int x2 = GPIO(HALL2_PORT, IDR); #ifdef HALL2_PIN2 #if HALL2_PIN3 >= 2 #if HALL2_PIN2 >= 1 return (x1 & (1 << HALL1_PIN1)) >> HALL1_PIN1 | (x2 & (1 << HALL2_PIN2)) >> (HALL2_PIN2 - 1) | (x2 & (1 << HALL2_PIN3)) >> (HALL2_PIN3 - 2); #else return (x1 & (1 << HALL1_PIN1)) >> HALL1_PIN1 | (x2 & (1 << HALL2_PIN2)) << (1 - HALL2_PIN2) | (x2 & (1 << HALL2_PIN3)) >> (HALL2_PIN3 - 2); #endif #else #if HALL2_PIN2 >= 1 return (x1 & (1 << HALL1_PIN1)) >> HALL1_PIN1 | (x2 & (1 << HALL2_PIN2)) >> (HALL2_PIN2 - 1) | (x2 & (1 << HALL2_PIN3)) << (2 - HALL2_PIN3); #else return (x1 & (1 << HALL1_PIN1)) >> HALL1_PIN1 | (x2 & (1 << HALL2_PIN2)) << (1 - HALL2_PIN2) | (x2 & (1 << HALL2_PIN3)) << (2 - HALL2_PIN3); #endif #endif #else #if HALL2_PIN3 >= 2 #if HALL1_PIN2 >= 1 return (x1 & (1 << HALL1_PIN1)) >> HALL1_PIN1 | (x1 & (1 << HALL1_PIN2)) >> (HALL1_PIN2 - 1) | (x2 & (1 << HALL2_PIN3)) >> (HALL2_PIN3 - 2); #else return (x1 & (1 << HALL1_PIN1)) >> HALL1_PIN1 | (x1 & (1 << HALL1_PIN2)) << (1 - HALL1_PIN2) | (x2 & (1 << HALL2_PIN3)) >> (HALL2_PIN3 - 2); #endif #else #if HALL1_PIN2 >= 1 return (x1 & (1 << HALL1_PIN1)) >> HALL1_PIN1 | (x1 & (1 << HALL1_PIN2)) >> (HALL1_PIN2 - 1) | (x2 & (1 << HALL2_PIN3)) << (2 - HALL2_PIN3); #else return (x1 & (1 << HALL1_PIN1)) >> HALL1_PIN1 | (x1 & (1 << HALL1_PIN2)) << (1 - HALL1_PIN2) | (x2 & (1 << HALL2_PIN3)) << (2 - HALL2_PIN3); #endif #endif #endif #else int x = GPIO(HALL_PORT, IDR); #if HALL_PIN3 >= 2 #if HALL_PIN2 >= 1 return (x & (1 << HALL_PIN1)) >> HALL_PIN1 | (x & (1 << HALL_PIN2)) >> (HALL_PIN2 - 1) | (x & (1 << HALL_PIN3)) >> (HALL_PIN3 - 2); #else return (x & (1 << HALL_PIN1)) >> HALL_PIN1 | (x & (1 << HALL_PIN2)) << (1 - HALL_PIN2) | (x & (1 << HALL_PIN3)) >> (HALL_PIN3 - 2); #endif #else #if HALL_PIN2 >= 1 return (x & (1 << HALL_PIN1)) >> HALL_PIN1 | (x & (1 << HALL_PIN2)) >> (HALL_PIN2 - 1) | (x & (1 << HALL_PIN3)) << (2 - HALL_PIN3); #else return (x & (1 << HALL_PIN1)) >> HALL_PIN1 | (x & (1 << HALL_PIN2)) << (1 - HALL_PIN2) | (x & (1 << HALL_PIN3)) << (2 - HALL_PIN3); #endif #endif #endif } #endif __attribute__((__weak__)) void ledctl(int x) { #ifdef LED1_PORT #ifdef LED1_INV GPIO(LED1_PORT, BSRR) = x & 1 ? 1 << (LED1_PIN + 16) : 1 << LED1_PIN; #else GPIO(LED1_PORT, BSRR) = x & 1 ? 1 << LED1_PIN : 1 << (LED1_PIN + 16); #endif #endif #ifdef LED2_PORT #ifdef LED2_INV GPIO(LED2_PORT, BSRR) = x & 2 ? 1 << (LED2_PIN + 16) : 1 << LED2_PIN; #else GPIO(LED2_PORT, BSRR) = x & 2 ? 1 << LED2_PIN : 1 << (LED2_PIN + 16); #endif #endif #ifdef LED3_PORT #ifdef LED3_INV GPIO(LED3_PORT, BSRR) = x & 4 ? 1 << (LED3_PIN + 16) : 1 << LED3_PIN; #else GPIO(LED3_PORT, BSRR) = x & 4 ? 1 << LED3_PIN : 1 << (LED3_PIN + 16); #endif #endif #ifdef LED4_PORT #ifdef LED4_INV GPIO(LED4_PORT, BSRR) = x & 8 ? 1 << (LED4_PIN + 16) : 1 << LED4_PIN; #else GPIO(LED4_PORT, BSRR) = x & 8 ? 1 << LED4_PIN : 1 << (LED4_PIN + 16); #endif #endif } __attribute__((__weak__)) void hsictl(int x) { int cr = RCC_CR; int tv = (cr & 0xf8) >> 3; // 5 bits RCC_CR = (cr & ~0xf8) | clamp(tv + x, 0, 0x1f) << 3; } char crc8(const char *buf, int len) { static const char tbl[] = { 0x00, 0x07, 0x0e, 0x09, 0x1c, 0x1b, 0x12, 0x15, 0x38, 0x3f, 0x36, 0x31, 0x24, 0x23, 0x2a, 0x2d, 0x70, 0x77, 0x7e, 0x79, 0x6c, 0x6b, 0x62, 0x65, 0x48, 0x4f, 0x46, 0x41, 0x54, 0x53, 0x5a, 0x5d, 0xe0, 0xe7, 0xee, 0xe9, 0xfc, 0xfb, 0xf2, 0xf5, 0xd8, 0xdf, 0xd6, 0xd1, 0xc4, 0xc3, 0xca, 0xcd, 0x90, 0x97, 0x9e, 0x99, 0x8c, 0x8b, 0x82, 0x85, 0xa8, 0xaf, 0xa6, 0xa1, 0xb4, 0xb3, 0xba, 0xbd, 0xc7, 0xc0, 0xc9, 0xce, 0xdb, 0xdc, 0xd5, 0xd2, 0xff, 0xf8, 0xf1, 0xf6, 0xe3, 0xe4, 0xed, 0xea, 0xb7, 0xb0, 0xb9, 0xbe, 0xab, 0xac, 0xa5, 0xa2, 0x8f, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9d, 0x9a, 0x27, 0x20, 0x29, 0x2e, 0x3b, 0x3c, 0x35, 0x32, 0x1f, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0d, 0x0a, 0x57, 0x50, 0x59, 0x5e, 0x4b, 0x4c, 0x45, 0x42, 0x6f, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7d, 0x7a, 0x89, 0x8e, 0x87, 0x80, 0x95, 0x92, 0x9b, 0x9c, 0xb1, 0xb6, 0xbf, 0xb8, 0xad, 0xaa, 0xa3, 0xa4, 0xf9, 0xfe, 0xf7, 0xf0, 0xe5, 0xe2, 0xeb, 0xec, 0xc1, 0xc6, 0xcf, 0xc8, 0xdd, 0xda, 0xd3, 0xd4, 0x69, 0x6e, 0x67, 0x60, 0x75, 0x72, 0x7b, 0x7c, 0x51, 0x56, 0x5f, 0x58, 0x4d, 0x4a, 0x43, 0x44, 0x19, 0x1e, 0x17, 0x10, 0x05, 0x02, 0x0b, 0x0c, 0x21, 0x26, 0x2f, 0x28, 0x3d, 0x3a, 0x33, 0x34, 0x4e, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5c, 0x5b, 0x76, 0x71, 0x78, 0x7f, 0x6a, 0x6d, 0x64, 0x63, 0x3e, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2c, 0x2b, 0x06, 0x01, 0x08, 0x0f, 0x1a, 0x1d, 0x14, 0x13, 0xae, 0xa9, 0xa0, 0xa7, 0xb2, 0xb5, 0xbc, 0xbb, 0x96, 0x91, 0x98, 0x9f, 0x8a, 0x8d, 0x84, 0x83, 0xde, 0xd9, 0xd0, 0xd7, 0xc2, 0xc5, 0xcc, 0xcb, 0xe6, 0xe1, 0xe8, 0xef, 0xfa, 0xfd, 0xf4, 0xf3, }; char crc = 0; while (len--) crc = tbl[crc ^ *buf++]; return crc; } char crc8dvbs2(const char *buf, int len) { static const char tbl[] = { 0x00, 0xd5, 0x7f, 0xaa, 0xfe, 0x2b, 0x81, 0x54, 0x29, 0xfc, 0x56, 0x83, 0xd7, 0x02, 0xa8, 0x7d, 0x52, 0x87, 0x2d, 0xf8, 0xac, 0x79, 0xd3, 0x06, 0x7b, 0xae, 0x04, 0xd1, 0x85, 0x50, 0xfa, 0x2f, 0xa4, 0x71, 0xdb, 0x0e, 0x5a, 0x8f, 0x25, 0xf0, 0x8d, 0x58, 0xf2, 0x27, 0x73, 0xa6, 0x0c, 0xd9, 0xf6, 0x23, 0x89, 0x5c, 0x08, 0xdd, 0x77, 0xa2, 0xdf, 0x0a, 0xa0, 0x75, 0x21, 0xf4, 0x5e, 0x8b, 0x9d, 0x48, 0xe2, 0x37, 0x63, 0xb6, 0x1c, 0xc9, 0xb4, 0x61, 0xcb, 0x1e, 0x4a, 0x9f, 0x35, 0xe0, 0xcf, 0x1a, 0xb0, 0x65, 0x31, 0xe4, 0x4e, 0x9b, 0xe6, 0x33, 0x99, 0x4c, 0x18, 0xcd, 0x67, 0xb2, 0x39, 0xec, 0x46, 0x93, 0xc7, 0x12, 0xb8, 0x6d, 0x10, 0xc5, 0x6f, 0xba, 0xee, 0x3b, 0x91, 0x44, 0x6b, 0xbe, 0x14, 0xc1, 0x95, 0x40, 0xea, 0x3f, 0x42, 0x97, 0x3d, 0xe8, 0xbc, 0x69, 0xc3, 0x16, 0xef, 0x3a, 0x90, 0x45, 0x11, 0xc4, 0x6e, 0xbb, 0xc6, 0x13, 0xb9, 0x6c, 0x38, 0xed, 0x47, 0x92, 0xbd, 0x68, 0xc2, 0x17, 0x43, 0x96, 0x3c, 0xe9, 0x94, 0x41, 0xeb, 0x3e, 0x6a, 0xbf, 0x15, 0xc0, 0x4b, 0x9e, 0x34, 0xe1, 0xb5, 0x60, 0xca, 0x1f, 0x62, 0xb7, 0x1d, 0xc8, 0x9c, 0x49, 0xe3, 0x36, 0x19, 0xcc, 0x66, 0xb3, 0xe7, 0x32, 0x98, 0x4d, 0x30, 0xe5, 0x4f, 0x9a, 0xce, 0x1b, 0xb1, 0x64, 0x72, 0xa7, 0x0d, 0xd8, 0x8c, 0x59, 0xf3, 0x26, 0x5b, 0x8e, 0x24, 0xf1, 0xa5, 0x70, 0xda, 0x0f, 0x20, 0xf5, 0x5f, 0x8a, 0xde, 0x0b, 0xa1, 0x74, 0x09, 0xdc, 0x76, 0xa3, 0xf7, 0x22, 0x88, 0x5d, 0xd6, 0x03, 0xa9, 0x7c, 0x28, 0xfd, 0x57, 0x82, 0xff, 0x2a, 0x80, 0x55, 0x01, 0xd4, 0x7e, 0xab, 0x84, 0x51, 0xfb, 0x2e, 0x7a, 0xaf, 0x05, 0xd0, 0xad, 0x78, 0xd2, 0x07, 0x53, 0x86, 0x2c, 0xf9, }; char crc = 0; while (len--) crc = tbl[crc ^ *buf++]; return crc; } int scale(int x, int a1, int a2, int b1, int b2) { if (x <= a1) return b1; if (x >= a2) return b2; return b1 + (x - a1) * (b2 - b1) / (a2 - a1); } int smooth(int *s, int x, int n) { int q; if (x < 0) return 0; // Sanity check if ((q = *s) < 0) *s = q = x << n; // Initialize state return (*s = x + q - (q >> n)) >> n; } void initpid(PID *pid, int x) { pid->i = 0; pid->x = x; } int calcpid(PID *pid, int x, int y) { int p = x - y; // Proportional error int l = pid->Li; // Integral error limit int i = clamp(pid->i + p, -l, l); // Integral error int d = x - pid->x; // Derivative error pid->i = i; pid->x = x; return pid->Kp * p + pid->Ki * i + pid->Kd * d; } void checkcfg(void) { #ifndef ANALOG #ifndef ANALOG_CHAN if (IO_ANALOG) cfg.arm = 1; // Ensure low level on startup else #endif cfg.arm = !!cfg.arm; #else cfg.arm = 0; #endif #ifdef PWM_ENABLE cfg.damp = 1; #else cfg.damp = !!cfg.damp; #endif cfg.revdir = !!cfg.revdir; cfg.brushed = !!cfg.brushed; cfg.timing = clamp(cfg.timing, 1, 31); cfg.sine_range = cfg.sine_range && !cfg.brushed ? clamp(cfg.sine_range, 5, 25) : 0; cfg.sine_power = clamp(cfg.sine_power, 1, 15); cfg.freq_min = clamp(cfg.freq_min, 16, 48); cfg.freq_max = clamp(cfg.freq_max, cfg.freq_min, 96); cfg.duty_min = clamp(cfg.duty_min, 1, 100); cfg.duty_max = clamp(cfg.duty_max, cfg.duty_min, 100); cfg.duty_spup = clamp(cfg.duty_spup, 1, 100); cfg.duty_ramp = clamp(cfg.duty_ramp, 0, 100); cfg.duty_rate = clamp(cfg.duty_rate, 1, 100); cfg.duty_drag = clamp(cfg.duty_drag, 0, 100); cfg.duty_lock = clamp(cfg.duty_lock, 0, cfg.brushed ? 0 : 2); cfg.throt_mode = clamp(cfg.throt_mode, 0, IO_ANALOG ? 0 : cfg.duty_lock ? 1 : 3); cfg.throt_rev = clamp(cfg.throt_rev, 0, 3); cfg.throt_brk = clamp(cfg.throt_brk, cfg.duty_drag, 100); cfg.throt_set = clamp(cfg.throt_set, 0, cfg.arm ? 0 : 100); cfg.throt_ztc = !!cfg.throt_ztc; cfg.throt_cal = !!cfg.throt_cal; cfg.throt_min = clamp(cfg.throt_min, 900, 1900); cfg.throt_max = clamp(cfg.throt_max, cfg.throt_min + 200, 2100); cfg.throt_mid = clamp(cfg.throt_mid, cfg.throt_min + 100, cfg.throt_max - 100); cfg.analog_min = clamp(cfg.analog_min, 0, 3200); cfg.analog_max = clamp(cfg.analog_max, cfg.analog_min + 200, 3400); #ifdef IO_PA2 cfg.input_mode = clamp(cfg.input_mode, 0, 5); cfg.input_ch1 = clamp(cfg.input_ch1, 1, cfg.input_mode < 3 ? 0 : cfg.input_mode == 3 ? 14 : 16); cfg.input_ch2 = clamp(cfg.input_ch2, 0, cfg.input_mode < 3 ? 0 : cfg.input_mode == 3 ? 14 : 16); #else #if defined IO_PA6 || defined ANALOG_CHAN cfg.input_mode = clamp(cfg.input_mode, 0, 1); #else cfg.input_mode = 0; #endif cfg.input_ch1 = 0; cfg.input_ch2 = 0; #endif cfg.telem_mode = clamp(cfg.telem_mode, 0, 4); cfg.telem_phid = cfg.telem_mode == 2 ? clamp(cfg.telem_phid, 1, 2): cfg.telem_mode == 3 ? clamp(cfg.telem_phid, 1, 28): cfg.input_mode == 4 ? clamp(cfg.telem_phid, 0, 4) : 0; cfg.telem_poles = clamp(cfg.telem_poles & ~1, 2, 100); cfg.prot_stall = cfg.prot_stall && !cfg.brushed ? clamp(cfg.prot_stall, 1500, 3500) : 0; cfg.prot_temp = cfg.prot_temp ? clamp(cfg.prot_temp, 60, 140) : 0; #if SENS_CNT >= 3 cfg.prot_sens = clamp(cfg.prot_sens, 0, 2); #else cfg.prot_sens = 0; #endif #if SENS_CNT >= 1 && VOLT_MUL > 0 cfg.prot_volt = cfg.prot_volt ? clamp(cfg.prot_volt, 28, 38) : 0; cfg.prot_cells = clamp(cfg.prot_cells, 0, 24); #else cfg.prot_volt = 0; cfg.prot_cells = 0; #endif #if SENS_CNT >= 2 && CURR_MUL > 0 cfg.prot_curr = clamp(cfg.prot_curr, 0, 999); #else cfg.prot_curr = 0; #endif #ifdef PARK_PIN cfg.prot_park = clamp(cfg.prot_park, 0, 4); #else cfg.prot_park = 0; #endif cfg.volume = clamp(cfg.volume, 0, 100); cfg.beacon = clamp(cfg.beacon, 0, 100); #ifdef BEC_MAP cfg.bec = clamp(cfg.bec, BEC_MIN, BEC_MAX); #else cfg.bec = 0; #endif #if LED_CNT > 0 cfg.led &= (1 << LED_CNT) - 1; #else cfg.led = 0; #endif } int savecfg(void) { if (ertm || busy) return 0; __disable_irq(); FLASH_KEYR = FLASH_KEYR_KEY1; FLASH_KEYR = FLASH_KEYR_KEY2; FLASH_SR = -1; // Clear errors FLASH_CR = FLASH_CR_PER; #ifdef STM32F0 FLASH_AR = (uint32_t)_cfg; FLASH_CR = FLASH_CR_PER | FLASH_CR_STRT; // Erase page #else FLASH_CR = FLASH_CR_PER | FLASH_CR_STRT | ((uint32_t)(_cfg - _boot) >> 11) << FLASH_CR_PNB_SHIFT; // Erase page #endif while (FLASH_SR & FLASH_SR_BSY); FLASH_CR = FLASH_CR_PG; #ifdef STM32F0 #define T uint16_t #else #define T uint32_t #endif T *dst = (T *)_cfg; T *src = (T *)_cfg_start; T *end = (T *)_cfg_end; #undef T while (src < end) { // Write data *dst++ = *src++; #ifndef STM32F0 *dst++ = *src++; #endif while (FLASH_SR & FLASH_SR_BSY); } FLASH_CR = FLASH_CR_LOCK; __enable_irq(); #ifdef STM32F0 if (FLASH_SR & (FLASH_SR_PGERR | FLASH_SR_WRPRTERR)) return 0; #else if (FLASH_SR & (FLASH_SR_PROGERR | FLASH_SR_WRPERR)) return 0; #endif return !memcmp(_cfg, _cfg_start, _cfg_end - _cfg_start); } int resetcfg(void) { __disable_irq(); memcpy(&cfg, &cfgdata, sizeof cfgdata); __enable_irq(); checkcfg(); return savecfg(); } void resetcom(void) { #ifdef PWM_ENABLE TIM1_CCMR1 = TIM_CCMR1_OC1M_FORCE_HIGH | TIM_CCMR1_OC2M_FORCE_HIGH; TIM1_CCMR2 = TIM_CCMR2_OC3M_FORCE_HIGH; int er = TIM_CCER_CC1NE | TIM_CCER_CC1NP | TIM_CCER_CC2NE | TIM_CCER_CC2NP | TIM_CCER_CC3NE | TIM_CCER_CC3NP; #else TIM1_CCMR1 = TIM_CCMR1_OC1M_FORCE_LOW | TIM_CCMR1_OC2M_FORCE_LOW; TIM1_CCMR2 = TIM_CCMR2_OC3M_FORCE_LOW; int er = TIM_CCER_CC1NE | TIM_CCER_CC2NE | TIM_CCER_CC3NE; #endif #ifdef INVERTED_HIGH er |= TIM_CCER_CC1P | TIM_CCER_CC2P | TIM_CCER_CC3P; #endif TIM1_CCER = er; TIM1_EGR = TIM_EGR_UG | TIM_EGR_COMG; } static void delayf(void) { TIM6_EGR = TIM_EGR_UG; // Reset arming timeout if (!(TIM1_SR & TIM_SR_UIF)) return; TIM1_SR = ~TIM_SR_UIF; int a = TIM1_CCR1; int b = TIM1_CCR3; TIM1_CCR1 = b; TIM1_CCR3 = a; } int playmusic(const char *str, int vol) { static const uint16_t arr[] = {15287, 14429, 13619, 12856, 12133, 11452, 10810, 10203, 9630, 9090, 8579, 8097, 7643}; char *end; int tmp = strtol(str, &end, 10); // Tempo if (str == end) tmp = 125; // 120 BPM by default else { if (tmp < 10 || tmp > 999) return 0; // Sanity check tmp = 15000 / tmp; str = end; } if (!vol || ertm || busy) return 0; busy = 1; resetcom(); #ifdef PWM_ENABLE TIM1_CCMR1 = TIM_CCMR1_OC1PE | TIM_CCMR1_OC1M_PWM1 | TIM_CCMR1_OC2M_FORCE_LOW; TIM1_CCMR2 = TIM_CCMR2_OC3PE | TIM_CCMR2_OC3M_PWM1; int er = TIM_CCER_CC1E | TIM_CCER_CC1NP | TIM_CCER_CC2NE | TIM_CCER_CC2NP | TIM_CCER_CC3E | TIM_CCER_CC3NP; #else TIM1_CCMR1 = TIM_CCMR1_OC1PE | TIM_CCMR1_OC1M_PWM1 | TIM_CCMR1_OC2M_FORCE_HIGH; TIM1_CCMR2 = TIM_CCMR2_OC3PE | TIM_CCMR2_OC3M_PWM1; int er = TIM_CCER_CC1E | TIM_CCER_CC1NE | TIM_CCER_CC2NE | TIM_CCER_CC3E | TIM_CCER_CC3NE; #endif #ifdef INVERTED_HIGH er |= TIM_CCER_CC1P | TIM_CCER_CC2P | TIM_CCER_CC3P; #endif TIM1_CCER = er; TIM1_PSC = CLK_MHZ / 8 - 1; // 125ns resolution for (int a, b, c = 0; (a = *str++);) { if (a >= 'a' && a <= 'g') a -= 'c', b = 0; // Low note else if (a >= 'A' && a <= 'G') a -= 'C', b = 1; // High note else if (a == '_') { // Pause TIM1_CCR1 = 0; TIM1_CCR3 = 0; goto update; } else { if (a == '+' && !c++) continue; // Octave up if (a == '-' && c--) continue; // Octave down break; // Invalid specifier } a = (a + 7) % 7 << 1; if (a > 4) --a; if (*str == '#') ++a, ++str; TIM1_ARR = arr[a] >> (b + c); // Frequency TIM1_CCR1 = (DEAD_TIME + CLK_MHZ / 8 - 1) * 8 / CLK_MHZ + vol; // Volume TIM1_CCR3 = 0; update: TIM1_EGR = TIM_EGR_UG | TIM_EGR_COMG; a = strtol(str, &end, 10); // Duration if (str == end) a = 1; else { if (a < 1 || a > 99) break; // Sanity check str = end; } delay(tmp * a, delayf); } TIM1_PSC = 0; TIM1_ARR = CLK_KHZ / 24 - 1; resetcom(); busy = 0; return !str[-1]; } void playsound(const char *buf, int vol) { // AU file format, 8-bit linear PCM, mono const uint32_t *hdr = (const uint32_t *)buf; if (hdr[0] != 0x646e732e || hdr[3] != 0x2000000 || hdr[5] != 0x1000000 || !vol || ertm || busy) return; busy = 1; resetcom(); #ifdef PWM_ENABLE TIM1_CCMR1 = TIM_CCMR1_OC1PE | TIM_CCMR1_OC1M_PWM1 | TIM_CCMR1_OC2M_FORCE_LOW; TIM1_CCMR2 = TIM_CCMR2_OC3PE | TIM_CCMR2_OC3M_PWM1; int er = TIM_CCER_CC1E | TIM_CCER_CC1NP | TIM_CCER_CC2NE | TIM_CCER_CC2NP | TIM_CCER_CC3E | TIM_CCER_CC3NP; #else TIM1_CCMR1 = TIM_CCMR1_OC1PE | TIM_CCMR1_OC1M_PWM1 | TIM_CCMR1_OC2M_FORCE_HIGH; TIM1_CCMR2 = TIM_CCMR2_OC3PE | TIM_CCMR2_OC3M_PWM1; int er = TIM_CCER_CC1E | TIM_CCER_CC1NE | TIM_CCER_CC2NE | TIM_CCER_CC3E | TIM_CCER_CC3NE; #endif #ifdef INVERTED_HIGH er |= TIM_CCER_CC1P | TIM_CCER_CC2P | TIM_CCER_CC3P; #endif TIM1_CCER = er; TIM1_CCR1 = 0; TIM1_CCR3 = 0; TIM1_ARR = CLK_KHZ / 24 - 1; TIM1_EGR = TIM_EGR_UG | TIM_EGR_COMG; TIM6_PSC = 0; TIM6_ARR = CLK_CNT(__builtin_bswap32(hdr[4])) - 1; TIM6_EGR = TIM_EGR_UG; TIM6_CR1 = TIM_CR1_CEN; buf += __builtin_bswap32(hdr[1]); for (int len = __builtin_bswap32(hdr[2]);;) { if (!(TIM6_SR & TIM_SR_UIF)) continue; TIM6_SR = ~TIM_SR_UIF; if (len-- <= 0) break; int8_t x = *buf++; TIM1_CR1 = TIM_CR1_CEN | TIM_CR1_ARPE | TIM_CR1_UDIS; TIM1_CCR1 = DEAD_TIME + ((x + 128) * vol * CLK_MHZ >> 13); TIM1_CCR3 = DEAD_TIME + ((127 - x) * vol * CLK_MHZ >> 13); TIM1_CR1 = TIM_CR1_CEN | TIM_CR1_ARPE; } TIM6_CR1 = 0; resetcom(); busy = 0; }