├── .gitmodules ├── .gitignore ├── examples ├── 50-stlinkv3.rules └── all.py ├── MANIFEST.in ├── pyproject.toml ├── .github └── workflows │ └── build_wheels.yml ├── src ├── stbridge.h ├── bindings.cpp └── stbridge.cpp ├── README.md ├── setup.py └── LICENSE /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libs"] 2 | path = src/STLINK-V3-BRIDGE_libusb 3 | url = https://github.com/dragonlock2/STLINK-V3-BRIDGE_libusb.git 4 | [submodule "libusb"] 5 | path = src/libusb 6 | url = https://github.com/libusb/libusb.git 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ST stuff 2 | libSTLinkUSBDriver.* 3 | version.txt 4 | 5 | # Compile stuff 6 | build/ 7 | dist/ 8 | *.so 9 | stbridge.egg-info/ 10 | 11 | # Cscope stuff 12 | cscope.* 13 | 14 | # IDE stuff 15 | .DS_Store 16 | .vscode/ 17 | -------------------------------------------------------------------------------- /examples/50-stlinkv3.rules: -------------------------------------------------------------------------------- 1 | SUBSYSTEM=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="374e", TAG+="uaccess" 2 | SUBSYSTEM=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="374f", TAG+="uaccess" 3 | SUBSYSTEM=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="3753", TAG+="uaccess" 4 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include src/stbridge.h 2 | graft src/STLINK-V3-BRIDGE_libusb 3 | recursive-include src/libusb *.c *.h 4 | recursive-include src/libusb *.sh *.ac *.am *.in *.extra 5 | recursive-include src/libusb *.sln *.vcxproj *.props 6 | recursive-include src/libusb AUTHORS ChangeLog NEWS README 7 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "stbridge" 3 | version = "0.1.1" 4 | authors = [ 5 | { name = "Matthew Tran", email = "matthewlamtran@berkeley.edu" }, 6 | ] 7 | description = "Python wrapper for STLINK-V3-BRIDGE" 8 | readme = "README.md" 9 | requires-python = ">=3.9" 10 | classifiers = [ 11 | "Programming Language :: Python :: 3", 12 | "Operating System :: OS Independent", 13 | ] 14 | 15 | [project.urls] 16 | Homepage = "https://github.com/dragonlock2/stbridge" 17 | Issues = "https://github.com/dragonlock2/stbridge/issues" 18 | 19 | [build-system] 20 | requires = [ 21 | "wheel>=0.44", 22 | "setuptools>=72", 23 | "setuptools<72.2.0; implementation_name == 'pypy'", # https://github.com/pypa/distutils/issues/283 24 | "pybind11>=2.13" 25 | ] 26 | build-backend = "setuptools.build_meta" 27 | 28 | [tool.setuptools.packages.find] 29 | exclude = ["examples"] 30 | 31 | [tool.setuptools] 32 | include-package-data = false 33 | 34 | [tool.cibuildwheel.macos] 35 | before-all = "brew install autoconf automake libtool" 36 | -------------------------------------------------------------------------------- /.github/workflows/build_wheels.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: [push, pull_request, workflow_dispatch] 4 | 5 | jobs: 6 | build_sdist: 7 | name: Build sdist on ubuntu-latest 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v4 11 | with: 12 | submodules: recursive 13 | - run: pipx run build --sdist -o dist 14 | - uses: actions/upload-artifact@v4 15 | with: 16 | name: dist-source 17 | path: ./dist/*.tar.gz 18 | 19 | build_wheels: 20 | name: Build wheels on ${{ matrix.os }} 21 | runs-on: ${{ matrix.os }} 22 | needs: build_sdist 23 | strategy: 24 | matrix: 25 | # macos-13 is an intel runner, macos-14 is apple silicon 26 | os: [ubuntu-latest, windows-latest, macos-13, macos-14] 27 | steps: 28 | - uses: actions/checkout@v4 29 | with: 30 | submodules: recursive 31 | - uses: pypa/cibuildwheel@v2.21.3 32 | with: 33 | output-dir: dist 34 | - uses: actions/upload-artifact@v4 35 | with: 36 | name: dist-${{ matrix.os }} 37 | path: ./dist/*.whl 38 | 39 | merge: 40 | name: Merge Artifacts 41 | runs-on: ubuntu-latest 42 | needs: build_wheels 43 | steps: 44 | - uses: actions/upload-artifact/merge@v4 45 | with: 46 | name: dist 47 | delete-merged: true 48 | -------------------------------------------------------------------------------- /src/stbridge.h: -------------------------------------------------------------------------------- 1 | #ifndef STBRIDGE_H 2 | #define STBRIDGE_H 3 | 4 | #include 5 | #include 6 | 7 | /* 8 | * API designed to closely match JABI 9 | */ 10 | class USBInterface; 11 | 12 | enum class CANMode { 13 | NORMAL, 14 | LOOPBACK, 15 | LISTENONLY, 16 | }; 17 | 18 | struct CANMessage { 19 | int id; 20 | bool ext; 21 | bool rtr; 22 | std::vector data; 23 | 24 | CANMessage() : id(0), ext(false), rtr(false) {} 25 | CANMessage(int id, int req_len) : id(id), ext(id & ~0x7FF), rtr(true), data(req_len, 0) {} 26 | CANMessage(int id, std::vector data) : id(id), ext(id & ~0x7FF), rtr(false), data(data) {} 27 | }; 28 | 29 | std::ostream &operator<<(std::ostream &os, CANMessage const &m); 30 | 31 | enum class I2CFreq { 32 | STANDARD, // 100kHz 33 | FAST, // 400kHz 34 | FAST_PLUS , // 1MHz 35 | }; 36 | 37 | enum class GPIODir { 38 | INPUT, 39 | OUTPUT, 40 | OPEN_DRAIN, 41 | }; 42 | 43 | enum class GPIOPull { 44 | NONE, 45 | UP, 46 | DOWN, 47 | }; 48 | 49 | enum class ADCChannel { 50 | TARGET_VOLTAGE = 0, 51 | }; 52 | 53 | class Device { 54 | public: 55 | /* Metadata */ 56 | std::string serial(); 57 | 58 | /* CAN */ 59 | void can_set_filter(int id, int id_mask, bool rtr, bool rtr_mask); 60 | void can_set_rate(int bitrate); 61 | void can_set_mode(CANMode mode); 62 | void can_write(CANMessage msg); 63 | int can_read(CANMessage &msg); 64 | 65 | /* I2C */ 66 | void i2c_set_freq(I2CFreq preset); 67 | void i2c_write(int addr, std::vector data); 68 | std::vector i2c_read(int addr, size_t len); 69 | 70 | /* GPIO */ 71 | void gpio_set_mode(int idx, GPIODir dir=GPIODir::INPUT, GPIOPull pull=GPIOPull::NONE); 72 | void gpio_write(int idx, bool val); 73 | void gpio_write_all(int pin_vals); 74 | bool gpio_read(int idx); 75 | 76 | /* ADC */ 77 | float adc_read(ADCChannel chan=ADCChannel::TARGET_VOLTAGE); // V 78 | 79 | /* SPI */ 80 | void spi_set_freq(int freq); 81 | void spi_set_mode(int mode); 82 | void spi_set_bitorder(bool msb); 83 | void spi_set_nss(bool val); 84 | void spi_write(std::vector data); 85 | std::vector spi_read(size_t len); 86 | 87 | private: 88 | // using PImpl to workaround libusb.h and pyconfig.h defining ssize_t 89 | struct device_data; 90 | std::shared_ptr pdata; 91 | 92 | Device(std::shared_ptr pdata); 93 | friend class USBInterface; 94 | }; 95 | 96 | class USBInterface { 97 | public: 98 | static Device get_device(std::string sn); 99 | static std::vector list_devices(); 100 | }; 101 | 102 | #endif // STBRIDGE_H 103 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # stbridge 2 | 3 | Python wrapper for STLINK-V3-BRIDGE. Tested on macOS, Windows, and Linux. Uses `libusb` for straightforward cross-platform support. It's deployed on [PyPI](https://pypi.org/project/stbridge) so you can install it as follows. 4 | 5 | ``` 6 | pip install stbridge 7 | ``` 8 | 9 | Development on this project will only be for bug fixes and PRs due to being tied to proprietary hardware and ST's license. See [dragonlock2/JABI](https://github.com/dragonlock2/JABI) for a true cross-hardware solution. 10 | 11 | ## Supported features 12 | 13 | - SPI controller 14 | - I2C controller 15 | - GPIO 16 | - CAN 17 | 18 | ## Setup 19 | 20 | To install from source, you'll need a few dependencies. 21 | 22 | - macOS 23 | - `brew install autoconf automake libtool` 24 | - Linux 25 | - `apt install autotools-dev autoconf libtool` 26 | - Windows 27 | - Install [Visual Studio C++](https://visualstudio.microsoft.com/vs/features/cplusplus/) for its C++ compiler. 28 | - Windows (MSYS2/MinGW) (experimental) 29 | - Install [MSYS2](https://www.msys2.org/) to install the following packages. 30 | - `pacman -S mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-autotools` 31 | - If you're not on `x86_64`, your exact package names may be different. 32 | 33 | Then run the following. 34 | 35 | ``` 36 | git clone --recursive https://github.com/dragonlock2/stbridge.git 37 | pip install ./stbridge 38 | ``` 39 | 40 | ## Use 41 | 42 | Check out [`stbridge.cpp`](src/stbridge.cpp) and [`stbridge.h`](src/stbridge.h) as well as [examples](examples) for function syntax. There's a 5 second timeout for commands in case they're unsuccessful (e.g. using CAN without other nodes). 43 | 44 | ``` 45 | python3 examples/all.py 46 | ``` 47 | 48 | In case no devices are available, you may need to do the following. 49 | - On Linux, add [`50-stlinkv3.rules`](examples/50-stlinkv3.rules) to `/etc/udev/rules.d`. 50 | - On Windows, manually install the WinUSB driver on any `STLINK-V3` devices in Device Manager. 51 | 52 | ## Known Issues 53 | 54 | - Not reading CAN messages fast enough can crash USB comms on the next CAN operation, even init. Should be detected as an overrun but isn't. Also looks like more messages than can be stored in the STLINK's FIFO's can be read back. I have a feeling what's happening is messages are stored on a separate FIFO on the STLINK and once it overruns, it crashes. This appears to be an ST issue. 55 | - Received remote CAN frames are misidentified as data frames. Garbage data is returned in remote frames due to ST's driver. This appears to be an ST issue. 56 | - Setting CAN filters for specific extended IDs throws a parameter error. Letting everything through still works. 57 | - There's some (fixed size) libusb memory leaks on Windows. 58 | 59 | ## License 60 | 61 | This software is under ST's [Ultimate Liberty License](https://www.st.com/content/ccc/resource/legal/legal_agreement/license_agreement/group0/87/0c/3d/ad/0a/ba/44/26/DM00216740/files/DM00216740.pdf/jcr:content/translations/en.DM00216740.pdf). 62 | -------------------------------------------------------------------------------- /examples/all.py: -------------------------------------------------------------------------------- 1 | import stbridge as st 2 | import time 3 | 4 | if __name__ == '__main__': 5 | dev = st.USBInterface.list_devices()[0] 6 | print('Connected! Starting tests...') 7 | 8 | # Target Voltage Test 9 | print(f'Target Voltage: {round(dev.adc_read(), 5)}V') 10 | 11 | # SPI Test 12 | print('\nInitiating SPI at 187kHz, MODE3, LSB first... ', end='') 13 | dev.spi_set_freq(187000) 14 | dev.spi_set_mode(3) 15 | dev.spi_set_bitorder(False) 16 | 17 | print('Sending out 0x69... ', end='') 18 | dev.spi_set_nss(False) 19 | dev.spi_write([0x69]) 20 | dev.spi_set_nss(True) 21 | 22 | dev.spi_set_nss(False) 23 | print('Read in', hex(dev.spi_read(1)[0]).upper(), '\b!') 24 | dev.spi_set_nss(True) 25 | 26 | # I2C Test 27 | print('\nScanning for I2C devices at 1MHz...') 28 | dev.i2c_set_freq(st.I2CFreq.FAST_PLUS) 29 | 30 | for addr in range(128): 31 | try: 32 | if dev.i2c_read(addr, 1): 33 | print('Found!:', hex(addr)) 34 | except: 35 | pass 36 | 37 | # CAN Test 38 | print("\nInitializing CAN at 1Mbps...") 39 | try: 40 | dev.can_set_rate(125000) 41 | 42 | print("Sending some CAN messages...") 43 | for _ in range(5): 44 | dev.can_write(st.CANMessage(42, list(b'hola!'))) 45 | print("Sent!") 46 | time.sleep(0.1) 47 | 48 | print("Listening to CAN for 1 sec...") 49 | time.sleep(1.0) 50 | while (msg := dev.can_read()): 51 | print("\t", msg) 52 | except: 53 | print("Failed CAN! Make sure transceiver and another node connected!") 54 | 55 | # GPIO Test 56 | print("\nTesting GPIO...") 57 | for i in range(st.BRG_GPIO_MAX_NB): 58 | print('Flashing GPIO', i, '\b...') 59 | dev.gpio_set_mode(i, st.GPIODir.OUTPUT) 60 | dev.gpio_write(i, 0) 61 | for _ in range(6): 62 | dev.gpio_write(i, not dev.gpio_read(i)) 63 | time.sleep(0.1) 64 | 65 | print('Reading GPIO using internal pull-ups, so disconnect any loads') 66 | for i in range(st.BRG_GPIO_MAX_NB): 67 | dev.gpio_set_mode(i, st.GPIODir.INPUT, st.GPIOPull.UP if (i % 2 == 0) else st.GPIOPull.DOWN) 68 | for i in range(st.BRG_GPIO_MAX_NB): 69 | print("Reading GPIO", i, "\b...", "SUCCESS" if (dev.gpio_read(i) == (i % 2 == 0)) else "FAIL") 70 | for i in range(st.BRG_GPIO_MAX_NB): 71 | dev.gpio_set_mode(i, st.GPIODir.INPUT, st.GPIOPull.DOWN if (i % 2 == 0) else st.GPIOPull.UP) 72 | for i in range(st.BRG_GPIO_MAX_NB): 73 | print("Reading GPIO", i, "\b...", "SUCCESS" if (dev.gpio_read(i) != (i % 2 == 0)) else "FAIL") 74 | print('\nDone!') 75 | 76 | NUM_LIMIT = 2 ** st.BRG_GPIO_MAX_NB 77 | print(f'Toggling out the numbers 0-{NUM_LIMIT - 1} in binary where GPIO 0 is the LSb') 78 | for i in range(st.BRG_GPIO_MAX_NB): 79 | dev.gpio_set_mode(i, st.GPIODir.OUTPUT) 80 | 81 | dev.gpio_write_all(0) 82 | time.sleep(1) 83 | 84 | for i in range(NUM_LIMIT): 85 | dev.gpio_write_all(i) 86 | 87 | dev.gpio_write_all(0) 88 | print('Done!') 89 | -------------------------------------------------------------------------------- /src/bindings.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "stbridge.h" 5 | 6 | namespace py = pybind11; 7 | using namespace pybind11::literals; 8 | 9 | py::object can_read_simple(Device &d) { 10 | CANMessage msg; 11 | if (d.can_read(msg) == -1) { 12 | return py::none(); 13 | } 14 | return py::cast(msg); 15 | } 16 | 17 | PYBIND11_MODULE(stbridge, m) { 18 | py::enum_(m, "CANMode") 19 | .value("NORMAL", CANMode::NORMAL) 20 | .value("LOOPBACK", CANMode::LOOPBACK) 21 | .value("LISTENONLY", CANMode::LISTENONLY); 22 | 23 | py::class_(m, "CANMessage") 24 | .def(py::init<>()) 25 | .def(py::init()) 26 | .def(py::init>()) 27 | .def_readwrite("id", &CANMessage::id) 28 | .def_readwrite("ext", &CANMessage::ext) 29 | .def_readwrite("rtr", &CANMessage::rtr) 30 | .def_readwrite("data", &CANMessage::data) // Note can't set individual elements 31 | .def("__repr__", [](const CANMessage &m){ std::stringstream s; s << m; return s.str(); }); 32 | 33 | py::enum_(m, "I2CFreq") 34 | .value("STANDARD", I2CFreq::STANDARD) 35 | .value("FAST", I2CFreq::FAST) 36 | .value("FAST_PLUS", I2CFreq::FAST_PLUS); 37 | 38 | py::enum_(m, "GPIODir") 39 | .value("INPUT", GPIODir::INPUT) 40 | .value("OUTPUT", GPIODir::OUTPUT) 41 | .value("OPEN_DRAIN", GPIODir::OPEN_DRAIN); 42 | 43 | py::enum_(m, "GPIOPull") 44 | .value("NONE", GPIOPull::NONE) 45 | .value("UP", GPIOPull::UP) 46 | .value("DOWN", GPIOPull::DOWN); 47 | 48 | m.attr("BRG_GPIO_MAX_NB") = 4; // from bridge.h 49 | 50 | py::enum_(m, "ADCChannel") 51 | .value("TARGET_VOLTAGE", ADCChannel::TARGET_VOLTAGE); 52 | 53 | py::class_(m, "Device") 54 | .def("serial", &Device::serial) 55 | 56 | .def("can_set_filter", &Device::can_set_filter) 57 | .def("can_set_rate", &Device::can_set_rate) 58 | .def("can_set_mode", &Device::can_set_mode) 59 | .def("can_write", &Device::can_write) 60 | .def("can_read", &can_read_simple) 61 | 62 | .def("i2c_set_freq", &Device::i2c_set_freq) 63 | .def("i2c_write", &Device::i2c_write) 64 | .def("i2c_read", &Device::i2c_read) 65 | 66 | .def("gpio_set_mode", &Device::gpio_set_mode, "idx"_a, "dir"_a=GPIODir::INPUT, "pull"_a=GPIOPull::NONE) 67 | .def("gpio_write", &Device::gpio_write) 68 | .def("gpio_write_all", &Device::gpio_write_all) 69 | .def("gpio_read", &Device::gpio_read) 70 | 71 | .def("adc_read", &Device::adc_read, "chan"_a=ADCChannel::TARGET_VOLTAGE) 72 | 73 | .def("spi_set_freq", &Device::spi_set_freq) 74 | .def("spi_set_mode", &Device::spi_set_mode) 75 | .def("spi_set_bitorder", &Device::spi_set_bitorder) 76 | .def("spi_set_nss", &Device::spi_set_nss) 77 | .def("spi_write", &Device::spi_write) 78 | .def("spi_read", &Device::spi_read); 79 | 80 | py::class_(m, "USBInterface") 81 | .def("get_device", &USBInterface::get_device) 82 | .def("list_devices", &USBInterface::list_devices); 83 | } 84 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import multiprocessing 2 | import platform 3 | import subprocess 4 | import sys 5 | from pathlib import Path 6 | from setuptools import setup 7 | from pybind11.setup_helpers import Pybind11Extension, build_ext 8 | 9 | def msvc_platform(): 10 | bits = 64 if sys.maxsize > 2**32 else 32 11 | if platform.machine() in ["x86", "AMD64"]: 12 | plat = "Win32" if bits == 32 else "x64" 13 | elif platform.machine() in ["ARM", "ARM64"]: 14 | plat = "ARM" if bits == 32 else "ARM64" 15 | else: 16 | plat = "" 17 | return plat 18 | 19 | class build_jabi(build_ext): 20 | def run(self): 21 | # build libusb from source 22 | msvc = sys.platform == "win32" and "GCC" not in sys.version 23 | plat = msvc_platform() 24 | if msvc and not list(Path(f"src/libusb/build").glob(f"**/{plat}")): 25 | msbuild = subprocess.check_output([ 26 | "C:/Program Files (x86)/Microsoft Visual Studio/Installer/vswhere.exe", 27 | "-requires", "Microsoft.Component.MSBuild", "-find", "MSBuild/**/Bin/MSBuild.exe", 28 | "-latest", # TODO pick correct version 29 | ]).decode('utf-8').strip() 30 | if not msbuild: 31 | raise Exception("pls install msvc") 32 | cmds = [ 33 | [msbuild, "/p:configuration=release", f"/p:platform={plat}", "/target:libusb_static", "msvc/libusb.sln"] 34 | ] 35 | elif sys.platform != "win32" and not Path("src/libusb/libusb/.libs").exists(): 36 | cmds = [ 37 | ["./bootstrap.sh"], 38 | ["./autogen.sh", "--disable-udev"], 39 | ["./configure", "--enable-static", "--disable-shared", "--disable-udev", "--with-pic"], 40 | ["make", f"-j{multiprocessing.cpu_count()}"], 41 | ] 42 | else: 43 | # MinGW subprocess doesn't run correctly, can manually run above 44 | cmds = [] 45 | for c in cmds: 46 | subprocess.run(c, cwd="src/libusb").check_returncode() 47 | 48 | # link libusb 49 | if msvc: 50 | self.library_dirs.append(str(list(Path("src/libusb/build").glob(f"**/{plat}/**/libusb-1.0.lib"))[0].parent)) 51 | self.libraries.append("libusb-1.0") 52 | else: 53 | self.library_dirs.append("src/libusb/libusb/.libs") 54 | self.libraries.append("usb-1.0") 55 | 56 | # continue build 57 | build_ext.run(self) 58 | 59 | setup(ext_modules=[ 60 | Pybind11Extension( 61 | name = "stbridge", 62 | sources = [ 63 | "src/bindings.cpp", 64 | "src/stbridge.cpp", 65 | "src/STLINK-V3-BRIDGE_libusb/src/bridge/bridge.cpp", 66 | "src/STLINK-V3-BRIDGE_libusb/src/common/stlink_device.cpp", 67 | "src/STLINK-V3-BRIDGE_libusb/src/common/stlink_interface.cpp", 68 | "src/STLINK-V3-BRIDGE_libusb/src/error/ErrLog.cpp", 69 | ], 70 | include_dirs = [ 71 | "src/libusb/libusb", 72 | "src/STLINK-V3-BRIDGE_libusb/src/bridge", 73 | "src/STLINK-V3-BRIDGE_libusb/src/common", 74 | "src/STLINK-V3-BRIDGE_libusb/src/error", 75 | ], 76 | )], 77 | cmdclass={"build_ext": build_jabi}, 78 | ) 79 | 80 | # python -m build 81 | # python -m twine upload dist/**/* 82 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | SLA0044 Rev5/February 2018 2 | 3 | BY INSTALLING COPYING, DOWNLOADING, ACCESSING OR OTHERWISE USING THIS SOFTWARE OR ANY PART THEREOF (AND THE RELATED DOCUMENTATION) FROM STMICROELECTRONICS INTERNATIONAL N.V, SWISS BRANCH AND/OR ITS AFFILIATED COMPANIES (STMICROELECTRONICS), THE RECIPIENT, ON BEHALF OF HIMSELF OR HERSELF, OR ON BEHALF OF ANY ENTITY BY WHICH SUCH RECIPIENT IS EMPLOYED AND/OR ENGAGED AGREES TO BE BOUND BY THIS SOFTWARE LICENSE AGREEMENT. 4 | 5 | Under STMicroelectronics’ intellectual property rights, the redistribution, reproduction and use in source and binary forms of the software or any part thereof, with or without modification, are permitted provided that the following conditions are met: 6 | 1. Redistribution of source code (modified or not) must retain any copyright notice, this list of conditions and the disclaimer set forth below as items 10 and 11. 7 | 2. Redistributions in binary form, except as embedded into microcontroller or microprocessor device manufactured by or for STMicroelectronics or a software update for such device, must reproduce any copyright notice provided with the binary code, this list of conditions, and the disclaimer set forth below as items 10 and 11, in documentation and/or other materials provided with the distribution. 8 | 3. Neither the name of STMicroelectronics nor the names of other contributors to this software may be used to endorse or promote products derived from this software or part thereof without specific written permission. 9 | 4. This software or any part thereof, including modifications and/or derivative works of this software, must be used and execute solely and exclusively on or in combination with a microcontroller or microprocessor device manufactured by or for STMicroelectronics. 10 | 5. No use, reproduction or redistribution of this software partially or totally may be done in any manner that would subject this software to any Open Source Terms. “Open Source Terms” shall mean any open source license which requires as part of distribution of software that the source code of such software is distributed therewith or otherwise made available, or open source license that substantially complies with the Open Source definition specified at www.opensource.org and any other comparable open source license such as for example GNU General Public License (GPL), Eclipse Public License (EPL), Apache Software License, BSD license or MIT license. 11 | 6. STMicroelectronics has no obligation to provide any maintenance, support or updates for the software. 12 | 7. The software is and will remain the exclusive property of STMicroelectronics and its licensors. The recipient will not take any action that jeopardizes STMicroelectronics and its licensors' proprietary rights or acquire any rights in the software, except the limited rights specified hereunder. 13 | 8. The recipient shall comply with all applicable laws and regulations affecting the use of the software or any part thereof including any applicable export control law or regulation. 14 | 9. Redistribution and use of this software or any part thereof other than as permitted under this license is void and will automatically terminate your rights under this license. 15 | 10. THIS SOFTWARE IS PROVIDED BY STMICROELECTRONICS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS, IMPLIED OR STATUTORY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT OF THIRD PARTY INTELLECTUAL PROPERTY RIGHTS, WHICH ARE DISCLAIMED TO THE FULLEST EXTENT PERMITTED BY LAW. IN NO EVENT SHALL STMICROELECTRONICS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 16 | 11. EXCEPT AS EXPRESSLY PERMITTED HEREUNDER, NO LICENSE OR OTHER RIGHTS, WHETHER EXPRESS OR IMPLIED, ARE GRANTED UNDER ANY PATENT OR OTHER INTELLECTUAL PROPERTY RIGHTS OF STMICROELECTRONICS OR ANY THIRD PARTY. 17 | 18 | -------------------------------------------------------------------------------- /src/stbridge.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "bridge.h" 3 | #include "stbridge.h" 4 | 5 | static inline void check_error(Brg_StatusT stat) { 6 | if (stat != BRG_NO_ERR && 7 | stat != BRG_OLD_FIRMWARE_WARNING && 8 | stat != BRG_COM_FREQ_MODIFIED) { 9 | throw std::runtime_error("BRG_ERROR: " + std::to_string(stat)); 10 | } 11 | } 12 | 13 | struct Device::device_data { 14 | std::shared_ptr stlink; 15 | std::shared_ptr brg; 16 | std::string sn; 17 | Brg_CanInitT can_params; 18 | Brg_CanFilterConfT can_filter_params; 19 | Brg_I2cInitT i2c_params; 20 | Brg_GpioConfT gpio_conf[BRG_GPIO_MAX_NB]; 21 | Brg_SpiInitT spi_params; 22 | 23 | device_data(std::string sn, std::shared_ptr brg, std::shared_ptr stlink) : stlink(stlink), brg(brg), sn(sn) {} 24 | }; 25 | 26 | Device::Device(std::shared_ptr pdata) : pdata(pdata) { 27 | /* init CAN */ 28 | // http://www.bittiming.can-wiki.info/ 29 | uint32_t baud = 125000; // bps 30 | uint32_t baud_final; 31 | pdata->can_params.BitTimeConf.PropSegInTq = 1; 32 | pdata->can_params.BitTimeConf.PhaseSeg1InTq = 4; 33 | pdata->can_params.BitTimeConf.PhaseSeg2InTq = 2; 34 | pdata->can_params.BitTimeConf.SjwInTq = 1; 35 | check_error(pdata->brg->GetCANbaudratePrescal(&pdata->can_params.BitTimeConf, baud, &pdata->can_params.Prescaler, &baud_final)); 36 | if (baud != baud_final) { 37 | throw std::runtime_error("actual baud rate mismatch: " + std::to_string(baud_final) + "bps"); 38 | } 39 | 40 | // https://stackoverflow.com/questions/57094729/what-is-the-meaning-of-canbus-function-mode-initilazing-settings-for-stm32 41 | pdata->can_params.bIsAbomEn = false; 42 | pdata->can_params.bIsAwumEn = false; 43 | pdata->can_params.bIsNartEn = false; 44 | pdata->can_params.bIsRflmEn = false; 45 | pdata->can_params.bIsTxfpEn = false; 46 | pdata->can_params.Mode = CAN_MODE_NORMAL; 47 | 48 | // default let all messages through 49 | pdata->can_filter_params.FilterBankNb = 0; 50 | pdata->can_filter_params.bIsFilterEn = true; 51 | pdata->can_filter_params.FilterMode = CAN_FILTER_ID_MASK; 52 | pdata->can_filter_params.FilterScale = CAN_FILTER_32BIT; 53 | pdata->can_filter_params.Id[0].RTR = CAN_DATA_FRAME; 54 | pdata->can_filter_params.Id[0].IDE = CAN_ID_STANDARD; 55 | pdata->can_filter_params.Id[0].ID = 0x00000000; 56 | pdata->can_filter_params.Mask[0].RTR = CAN_DATA_FRAME; 57 | pdata->can_filter_params.Mask[0].IDE = CAN_ID_STANDARD; 58 | pdata->can_filter_params.Mask[0].ID = 0x00000000; 59 | pdata->can_filter_params.AssignedFifo = CAN_MSG_RX_FIFO0; 60 | 61 | /* I2C */ 62 | pdata->i2c_params.OwnAddr = 0x00; // for responder mode, not needed 63 | pdata->i2c_params.AddrMode = I2C_ADDR_7BIT; // defaulting to 7 bit 64 | pdata->i2c_params.AnFilterEn = I2C_FILTER_DISABLE; // disabling filters 65 | pdata->i2c_params.DigitalFilterEn = I2C_FILTER_DISABLE; 66 | pdata->i2c_params.Dnf = 0; 67 | check_error(pdata->brg->GetI2cTiming(I2C_STANDARD, 100, 0, 0, 0, 0, &pdata->i2c_params.TimingReg)); // 100kHz default 68 | check_error(pdata->brg->InitI2C(&pdata->i2c_params)); 69 | 70 | /* GPIO */ 71 | for (int i = 0; i < BRG_GPIO_MAX_NB; i++) { 72 | pdata->gpio_conf[i].Mode = GPIO_MODE_INPUT; 73 | pdata->gpio_conf[i].Speed = GPIO_SPEED_LOW; 74 | pdata->gpio_conf[i].Pull = GPIO_NO_PULL; 75 | pdata->gpio_conf[i].OutputType = GPIO_OUTPUT_PUSHPULL; 76 | } 77 | Brg_GpioInitT gpio_params; 78 | gpio_params.GpioMask = BRG_GPIO_ALL; 79 | gpio_params.ConfigNb = BRG_GPIO_MAX_NB; 80 | gpio_params.pGpioConf = pdata->gpio_conf; 81 | check_error(pdata->brg->InitGPIO(&gpio_params)); 82 | 83 | /* SPI */ 84 | pdata->spi_params.Direction = SPI_DIRECTION_2LINES_FULLDUPLEX; 85 | pdata->spi_params.Mode = SPI_MODE_MASTER; 86 | pdata->spi_params.DataSize = SPI_DATASIZE_8B; 87 | pdata->spi_params.Cpol = SPI_CPOL_LOW; 88 | pdata->spi_params.Cpha = SPI_CPHA_1EDGE; 89 | pdata->spi_params.FirstBit = SPI_FIRSTBIT_LSB; 90 | pdata->spi_params.FrameFormat = SPI_FRF_MOTOROLA; 91 | pdata->spi_params.Nss = SPI_NSS_SOFT; // software-controlled NSS 92 | pdata->spi_params.NssPulse = SPI_NSS_NO_PULSE; 93 | pdata->spi_params.Crc = SPI_CRC_DISABLE; 94 | pdata->spi_params.CrcPoly = 0; 95 | pdata->spi_params.SpiDelay = DEFAULT_NO_DELAY; 96 | uint32_t spi_freq = 750; // kHz 97 | uint32_t spi_freq_final; 98 | check_error(pdata->brg->GetSPIbaudratePrescal(spi_freq, &pdata->spi_params.Baudrate, &spi_freq_final)); 99 | if (spi_freq != spi_freq_final) { 100 | throw std::runtime_error("actual SPI freq mismatch: " + std::to_string(spi_freq_final) + "kHz"); 101 | } 102 | check_error(pdata->brg->InitSPI(&pdata->spi_params)); 103 | } 104 | 105 | std::ostream &operator<<(std::ostream &os, CANMessage const &m) { 106 | std::stringstream s; 107 | s << std::hex << std::showbase << "CANMessage("; 108 | s << "id=" << m.id << ",ext=" << m.ext << ",rtr=" << m.rtr; 109 | if (m.rtr) { 110 | s << ",data.size()=" << m.data.size(); 111 | } else { 112 | s << ",data={"; 113 | for (auto i : m.data) { s << static_cast(i) << ","; } 114 | s << "}"; 115 | } 116 | s << ")"; 117 | return os << s.str(); 118 | } 119 | 120 | std::string Device::serial() { 121 | return pdata->sn; 122 | } 123 | 124 | void Device::can_set_filter(int id, int id_mask, bool rtr, bool rtr_mask) { 125 | pdata->can_filter_params.Id[0].ID = id; 126 | pdata->can_filter_params.Mask[0].ID = id_mask; 127 | pdata->can_filter_params.Id[0].RTR = rtr ? CAN_REMOTE_FRAME : CAN_DATA_FRAME; 128 | pdata->can_filter_params.Mask[0].RTR = rtr_mask ? CAN_REMOTE_FRAME : CAN_DATA_FRAME; 129 | check_error(pdata->brg->InitCAN(&pdata->can_params, BRG_INIT_FULL)); // need a transceiver hooked up for this 130 | check_error(pdata->brg->InitFilterCAN(&pdata->can_filter_params)); 131 | check_error(pdata->brg->StartMsgReceptionCAN()); // never gonna call stop :P 132 | } 133 | 134 | void Device::can_set_rate(int bitrate) { 135 | uint32_t baud = bitrate; 136 | uint32_t baud_final; 137 | check_error(pdata->brg->GetCANbaudratePrescal(&pdata->can_params.BitTimeConf, baud, &pdata->can_params.Prescaler, &baud_final)); 138 | if (baud != baud_final) { 139 | throw std::runtime_error("actual baud rate mismatch: " + std::to_string(baud_final)); 140 | } 141 | check_error(pdata->brg->InitCAN(&pdata->can_params, BRG_INIT_FULL)); // need a transceiver hooked up for this 142 | check_error(pdata->brg->InitFilterCAN(&pdata->can_filter_params)); 143 | check_error(pdata->brg->StartMsgReceptionCAN()); // never gonna call stop :P 144 | } 145 | 146 | void Device::can_set_mode(CANMode mode) { 147 | switch (mode) { 148 | case CANMode::NORMAL: pdata->can_params.Mode = CAN_MODE_NORMAL; break; 149 | case CANMode::LOOPBACK: pdata->can_params.Mode = CAN_MODE_LOOPBACK; break; 150 | case CANMode::LISTENONLY: pdata->can_params.Mode = CAN_MODE_SILENT; break; 151 | default: throw std::runtime_error("invalid mode"); break; 152 | } 153 | check_error(pdata->brg->InitCAN(&pdata->can_params, BRG_INIT_FULL)); // need a transceiver hooked up for this 154 | check_error(pdata->brg->InitFilterCAN(&pdata->can_filter_params)); 155 | check_error(pdata->brg->StartMsgReceptionCAN()); // never gonna call stop :P 156 | } 157 | 158 | void Device::can_write(CANMessage msg) { 159 | if (msg.data.size() > 8) { 160 | throw std::runtime_error("message too long!"); 161 | } 162 | Brg_CanTxMsgT bmsg; 163 | bmsg.IDE = msg.ext ? CAN_ID_EXTENDED : CAN_ID_STANDARD; 164 | bmsg.ID = msg.id; 165 | bmsg.RTR = msg.rtr ? CAN_REMOTE_FRAME : CAN_DATA_FRAME; 166 | bmsg.DLC = msg.data.size(); 167 | check_error(pdata->brg->WriteMsgCAN(&bmsg, msg.data.data(), bmsg.DLC)); 168 | } 169 | 170 | int Device::can_read(CANMessage &msg) { 171 | uint16_t num_messages = 0; 172 | check_error(pdata->brg->GetRxMsgNbCAN(&num_messages)); 173 | if (num_messages > 0) { 174 | Brg_CanRxMsgT bmsg; 175 | uint8_t data[8] = {0}; 176 | uint16_t data_size; // unused 177 | check_error(pdata->brg->GetRxMsgCAN(&bmsg, 1, data, 8, &data_size)); 178 | msg.ext = bmsg.IDE == CAN_ID_EXTENDED; 179 | msg.id = bmsg.ID; 180 | msg.rtr = bmsg.RTR == CAN_REMOTE_FRAME; 181 | msg.data = std::vector(bmsg.DLC, 0); 182 | if (!msg.rtr) { 183 | memcpy(msg.data.data(), data, bmsg.DLC); 184 | } 185 | } 186 | return num_messages - 1; 187 | } 188 | 189 | void Device::i2c_set_freq(I2CFreq preset) { 190 | I2cModeT speed; 191 | int kHz; 192 | switch (preset) { 193 | case I2CFreq::STANDARD: speed = I2C_STANDARD; kHz = 100; break; 194 | case I2CFreq::FAST: speed = I2C_FAST; kHz = 400; break; 195 | case I2CFreq::FAST_PLUS: speed = I2C_FAST_PLUS; kHz = 1000; break; 196 | default: throw std::runtime_error("invalid i2c speed!"); break; 197 | } 198 | check_error(pdata->brg->GetI2cTiming(speed, kHz, 0, 0, 0, 0, &pdata->i2c_params.TimingReg)); 199 | check_error(pdata->brg->InitI2C(&pdata->i2c_params)); 200 | } 201 | 202 | void Device::i2c_write(int addr, std::vector data) { 203 | if (data.size() == 0) { 204 | throw std::runtime_error("must write at least 1 byte!"); 205 | } 206 | check_error(pdata->brg->WriteI2C(data.data(), addr, data.size(), NULL)); 207 | } 208 | 209 | std::vector Device::i2c_read(int addr, size_t len) { 210 | if (len == 0) { 211 | throw std::runtime_error("must read at least 1 byte!"); 212 | } 213 | std::vector data(len, 0); 214 | check_error(pdata->brg->ReadI2C(data.data(), addr, len, NULL)); 215 | return data; 216 | } 217 | 218 | void Device::gpio_set_mode(int idx, GPIODir dir, GPIOPull pull) { 219 | if (idx >= BRG_GPIO_MAX_NB) { 220 | throw std::runtime_error("invalid pin number!"); 221 | } 222 | switch (dir) { 223 | case GPIODir::INPUT: 224 | pdata->gpio_conf[idx].Mode = GPIO_MODE_INPUT; 225 | break; 226 | case GPIODir::OUTPUT: 227 | pdata->gpio_conf[idx].Mode = GPIO_MODE_OUTPUT; 228 | pdata->gpio_conf[idx].OutputType = GPIO_OUTPUT_PUSHPULL; 229 | break; 230 | case GPIODir::OPEN_DRAIN: 231 | pdata->gpio_conf[idx].Mode = GPIO_MODE_OUTPUT; 232 | pdata->gpio_conf[idx].OutputType = GPIO_OUTPUT_OPENDRAIN; 233 | break; 234 | default: 235 | throw std::runtime_error("invalid direction!"); 236 | break; 237 | } 238 | switch (pull) { 239 | case GPIOPull::NONE: pdata->gpio_conf[idx].Pull = GPIO_NO_PULL; break; 240 | case GPIOPull::UP: pdata->gpio_conf[idx].Pull = GPIO_PULL_UP; break; 241 | case GPIOPull::DOWN: pdata->gpio_conf[idx].Pull = GPIO_PULL_DOWN; break; 242 | default: throw std::runtime_error("invalid pull!"); break; 243 | } 244 | Brg_GpioInitT gpio_params; 245 | gpio_params.GpioMask = BRG_GPIO_ALL; 246 | gpio_params.ConfigNb = BRG_GPIO_MAX_NB; 247 | gpio_params.pGpioConf = pdata->gpio_conf; 248 | check_error(pdata->brg->InitGPIO(&gpio_params)); 249 | } 250 | 251 | void Device::gpio_write(int idx, bool val) { 252 | if (idx >= BRG_GPIO_MAX_NB) { 253 | throw std::runtime_error("invalid pin number!"); 254 | } 255 | Brg_GpioValT gpio_vals[BRG_GPIO_MAX_NB]; 256 | uint8_t gpio_err; 257 | gpio_vals[idx] = val ? GPIO_SET : GPIO_RESET; 258 | check_error(pdata->brg->SetResetGPIO(1 << idx, gpio_vals, &gpio_err)); 259 | if (gpio_err != 0) { 260 | throw std::runtime_error("GPIO error??"); 261 | } 262 | } 263 | 264 | void Device::gpio_write_all(int pin_vals) { 265 | if (pin_vals >= 1 << BRG_GPIO_MAX_NB) { 266 | throw std::runtime_error("pin_vals too large!"); 267 | } 268 | Brg_GpioValT gpio_vals[BRG_GPIO_MAX_NB]; 269 | uint8_t gpio_err; 270 | for (int i = 0; i < BRG_GPIO_MAX_NB; ++i) { 271 | gpio_vals[i] = pin_vals & (1 << i) ? GPIO_SET : GPIO_RESET; 272 | } 273 | check_error(pdata->brg->SetResetGPIO((1 << BRG_GPIO_MAX_NB) - 1, gpio_vals, &gpio_err)); 274 | if (gpio_err != 0) { 275 | throw std::runtime_error("GPIO error??"); 276 | } 277 | } 278 | 279 | bool Device::gpio_read(int idx) { 280 | if (idx >= BRG_GPIO_MAX_NB) { 281 | throw std::runtime_error("invalid pin number!"); 282 | } 283 | Brg_GpioValT gpio_vals[BRG_GPIO_MAX_NB]; 284 | uint8_t gpio_err; 285 | check_error(pdata->brg->ReadGPIO(1 << idx, gpio_vals, &gpio_err)); 286 | if (gpio_err != 0) { 287 | throw std::runtime_error("GPIO error??"); 288 | } 289 | return gpio_vals[idx] == GPIO_SET; 290 | } 291 | 292 | float Device::adc_read(ADCChannel chan) { 293 | if (chan != ADCChannel::TARGET_VOLTAGE) { 294 | throw std::runtime_error("invalid ADC channel!"); 295 | } 296 | float v; 297 | check_error(pdata->brg->GetTargetVoltage(&v)); 298 | return v; 299 | } 300 | 301 | void Device::spi_set_freq(int freq) { 302 | uint32_t spi_freq = freq / 1000; 303 | uint32_t spi_freq_final; 304 | check_error(pdata->brg->GetSPIbaudratePrescal(spi_freq, &pdata->spi_params.Baudrate, &spi_freq_final)); 305 | if (spi_freq != spi_freq_final) { 306 | throw std::runtime_error("actual SPI freq mismatch: " + std::to_string(spi_freq_final) + "kHz"); 307 | } 308 | check_error(pdata->brg->InitSPI(&pdata->spi_params)); 309 | } 310 | 311 | void Device::spi_set_mode(int mode) { 312 | switch (mode) { 313 | case 0: 314 | pdata->spi_params.Cpol = SPI_CPOL_LOW; 315 | pdata->spi_params.Cpha = SPI_CPHA_1EDGE; 316 | break; 317 | case 1: 318 | pdata->spi_params.Cpol = SPI_CPOL_LOW; 319 | pdata->spi_params.Cpha = SPI_CPHA_2EDGE; 320 | break; 321 | case 2: 322 | pdata->spi_params.Cpol = SPI_CPOL_HIGH; 323 | pdata->spi_params.Cpha = SPI_CPHA_1EDGE; 324 | break; 325 | case 3: 326 | pdata->spi_params.Cpol = SPI_CPOL_HIGH; 327 | pdata->spi_params.Cpha = SPI_CPHA_2EDGE; 328 | break; 329 | default: 330 | throw std::runtime_error("invalid SPI mode!"); 331 | break; 332 | } 333 | check_error(pdata->brg->InitSPI(&pdata->spi_params)); 334 | } 335 | 336 | void Device::spi_set_bitorder(bool msb) { 337 | pdata->spi_params.FirstBit = msb ? SPI_FIRSTBIT_MSB : SPI_FIRSTBIT_LSB; 338 | check_error(pdata->brg->InitSPI(&pdata->spi_params)); 339 | } 340 | 341 | void Device::spi_set_nss(bool val) { 342 | check_error(pdata->brg->SetSPIpinCS(val ? SPI_NSS_HIGH : SPI_NSS_LOW)); 343 | } 344 | 345 | void Device::spi_write(std::vector data) { 346 | check_error(pdata->brg->WriteSPI(data.data(), data.size(), NULL)); 347 | } 348 | 349 | std::vector Device::spi_read(size_t len) { 350 | std::vector data(len, 0); 351 | check_error(pdata->brg->ReadSPI(data.data(), len, NULL)); 352 | return data; 353 | } 354 | 355 | /* device acquisition */ 356 | Device USBInterface::get_device(std::string sn) { 357 | auto stlink = std::make_shared(STLINK_BRIDGE); 358 | if (stlink->LoadStlinkLibrary("") != STLINKIF_NO_ERR) { 359 | throw std::runtime_error("couldn't load stlink library??"); 360 | } 361 | 362 | auto brg = std::make_shared(*stlink); 363 | brg->SetOpenModeExclusive(false); 364 | check_error(Brg::ConvSTLinkIfToBrgStatus(stlink->EnumDevices(NULL, false))); 365 | check_error(brg->OpenStlink(sn.c_str(), true)); 366 | 367 | return Device(std::make_shared(sn, brg, stlink)); 368 | } 369 | 370 | std::vector USBInterface::list_devices() { 371 | auto stlink = std::make_shared(STLINK_BRIDGE); 372 | if (stlink->LoadStlinkLibrary("") != STLINKIF_NO_ERR) { 373 | throw std::runtime_error("couldn't load stlink library??"); 374 | } 375 | 376 | uint32_t num_devices = 0; 377 | if (stlink->EnumDevices(&num_devices, false) != STLINKIF_NO_ERR) { 378 | throw std::runtime_error("failed to enumerate devices??"); 379 | } 380 | 381 | std::vector devices; 382 | devices.reserve(num_devices); 383 | for (uint32_t i = 0; i < num_devices; i++) { 384 | STLink_DeviceInfo2T info; 385 | if (stlink->GetDeviceInfo2(i, &info, sizeof info) != STLINKIF_NO_ERR) { 386 | throw std::runtime_error("failed to get device info??"); 387 | } 388 | info.EnumUniqueId[SERIAL_NUM_STR_MAX_LEN - 1] = 0; // just in case 389 | std::string sn(info.EnumUniqueId); 390 | 391 | auto brg = std::make_shared(*stlink); 392 | brg->SetOpenModeExclusive(false); 393 | check_error(brg->OpenStlink(sn.c_str(), true)); 394 | devices.push_back(Device(std::make_shared(sn, brg, stlink))); 395 | } 396 | return devices; 397 | } 398 | --------------------------------------------------------------------------------