diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e557f2d2a5..5d7c73a773 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -39,6 +39,22 @@ jobs: cd src bazel build //software/embedded:thunderloop_main --copt=-O3 --platforms=//toolchains/cc:robot + - name: Motor Firmware build Test + run: | + cd src + bazel build @mdv6_firmware//:mdv6_firmware_main --platforms=//toolchains/cc:motor_board + + motor-firmware: + name: Motor Firmware + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/environment-setup + + - name: Build Motor Firmware + run: | + cd src + bazel build @mdv6_firmware//:mdv6_firmware_main --platforms=//toolchains/cc:motor_board software-tests: name: Software Tests @@ -90,7 +106,7 @@ jobs: //software/simulated_tests/... \ //software/ai/hl/... \ //software/ai/navigator/... - + - name: Upload simulated test proto logs # Ensure that simulated test logs get uploaded if: always() diff --git a/docs/setup-pi.md b/docs/setup-pi.md index 9c08ac0a95..2f97ca89ba 100644 --- a/docs/setup-pi.md +++ b/docs/setup-pi.md @@ -10,9 +10,11 @@ The Raspberry Pi 5 is compatible with many different (primarily Linux-based) ope ### Install OS Using Raspberry Pi Imager -To install the Raspberry Pi OS onto a microSD card, we use the Raspberry Pi Imager program. Install it [here](https://www.raspberrypi.com/software/). +To install the Raspberry Pi OS onto a microSD card, we use the Raspberry Pi Imager program. Install it [here](https://www.raspberrypi.com/software/). -*Note: if using the "Download for Linux" option, you will have to right click on the installed `.AppImage` file and select `Properties` → `Permissions` → `Allow Executing File as Program` (this may appear as a checkbox) +*Note 1: If on Linux, download the app image through `Download for Linux (x86_64)`. Using `sudo apt install rpi-imager` installs an older version of the program, which does not properly apply OS customization required in later steps. + +*Note 2: if using the "Download for Linux" option, you will have to right click on the installed `.AppImage` file and select `Properties` → `Permissions` → `Allow Executing File as Program` (this may appear as a checkbox) When you open the imager, you should see something like this: diff --git a/docs/software-architecture-and-design.md b/docs/software-architecture-and-design.md index 3167bbb0a9..1cdfb814c6 100644 --- a/docs/software-architecture-and-design.md +++ b/docs/software-architecture-and-design.md @@ -5,16 +5,6 @@ - [Table of Contents](#table-of-contents) -- [Architecture Overview](#architecture-overview) - - [League-Maintained Software](#league-maintained-software) - - [SSL Vision](#ssl-vision) - - [SSL Gamecontroller](#ssl-gamecontroller) -- [Protobuf](#protobuf) - - [Important Protobuf Messages](#important-protobuf-messages) - - [Primitives](#primitives) - - [Robot Status](#robot-status) -- [Conventions](#conventions) - - [Coordinates](#coordinates) - [Angles](#angles) - [Convention Diagram](#convention-diagram) - [Fullsystem](#fullsystem) @@ -71,74 +61,6 @@ -# Architecture Overview - -![High-Level Architecture Diagram](images/high_level_architecture.svg) - -At a high-level, our system is split into several independent processes that [communicate with each other](#inter-process-communication). Our architecture is designed in this manner to promote decoupling of different features, making our software easier to expand, maintain, and test. - -- [**Fullsystem**](#fullsystem) is the program that processes data and makes decisions for a [team](#team) of [robots](#robot). It manages [**Sensor Fusion**](#sensor-fusion), which is responsible for processing and filtering raw data, and the [**AI**](#ai) that makes gameplay decisions. - -- [**Thunderscope**](#thunderscope) is an application that provides a GUI for visualizing and interacting with our software. - -- The [**Simulator**](#simulator) provides a physics simulation of the world (robots, ball, and field), enabling testing of our gameplay when we don't have access to a real field. This process is optional and used only for development and testing purposes; in a real match, our system will receive data from [SSL-Vision](#ssl-vision). - -- [**Thunderloop**](/docs/robot-software-architecture.md#thunderloop) is the software that runs onboard our robots. It is responsible for coordinating communication between our [AI](#ai) computer and the motor and power boards in our robots. It is part our robot software architecture, which is documented [here](/docs/robot-software-architecture.md). - -## League-Maintained Software - -Our software is designed to interact with the following software developed and maintained by the RoboCup Small Size League: - -### SSL Vision -* This is the shared vision system used by the Small Size League. It is what connects to the cameras above the field, does the vision processing, and transmits the positional data of everything on the field to our [AI](#ai) computers. -* The GitHub repository can be found [here](https://github.com/RoboCup-SSL/ssl-vision) - -### SSL Gamecontroller -* Sometimes referred to as the "Referee", this is another shared piece of Small Size League software that is used to send gamecontroller and referee commands to the teams. A human and/or [computer auto-referee](#https://ssl.robocup.org/league-software/#auto-referees) controls this application during the games to send the appropriate commands to the robots. For example, some of these commands are what stage the gameplay is in, such as `HALT`, `STOP`, `READY`, or `PLAY`. -* The GitHub repository can be found [here](https://github.com/RoboCup-SSL/ssl-game-controller) - -# Protobuf -[Protobufs or protocol buffers](https://protobuf.dev/) are used to pass messages between components in our system. -After building using Bazel, the `.proto` files are generated into `.pb.h` and `.pb.cc` files, which are found in `bazel-out/k8-fastbuild/bin/proto`. -To include these files in our code, we simply include `proto/.pb.h`. - -## Important Protobuf Messages -These are [protobuf](https://developers.google.com/protocol-buffers/docs/cpptutorial) messages that we define and that are important for understanding how the [AI](#ai) works. - -### Primitives - -**Primitives** represent the low-level actions that a robot can execute. They are sent to the robots and can be "blindly" executed without knowledge of the [World](#world) and/or the high-level gameplay [strategy](#strategy). Primitives are understood directly by our robot software, which will translate the primitive into motor and power board inputs to make the robot move, dribble, kick, and/or chip as instructed. - -Each Primitive is a C++ class that generates an associated `TbotsProto::Primitive` protobuf message that can be sent to the robots. - -Primitives act as the abstraction between our [AI](#ai) and our robot software. It splits the responsibility such that the [AI](#ai) is responsible for sending a Primitive to a robot telling it what it wants it to do, and the robot is responsible for making sure it does what it's told. - -### Robot Status -The `TbotsProto::RobotStatus` protobuf message contains information about the status of a single robot. Examples of the information they include are: -* Robot battery voltage -* Whether or not the robot senses the ball in the breakbeam -* The capacitor charge on the robot -* The temperature of the dribbler motor - -Information about the robot status is communicated and stored as `RobotStatus` protobuf messages. [Thunderscope](#thunderscope) displays warnings from incoming `RobotStatus` messages so we can take appropriate action. For example, during a game we may get a "low battery warning" for a certain robot, so we know to substitute it and replace the battery before it dies on the field. - - -# Conventions - -Below documents various conventions we use and follow in our software. - -## Coordinates -We use a slightly custom coordinate convention to make it easier to write our code in a consistent and understandable way. This is particularly important for any code handling gameplay logic and positions on the field. - -The coordinate system is a simple 2D x-y plane. The x-dimension runs between the friendly and enemy goals, along the longer dimension of the field. The y-dimension runs perpendicular to the x-dimension, along the short dimension of the field. - -Because we have to be able to play on either side of a field during a game, this means the "friendly half of the field" will not always be in the positive or negative x part of the coordinate plane. This inconsistency is a problem when we want to specify points like "the friendly net", or "the enemy corner". We can't simple say the friendly net is `(-4.5, 0)` all the time, because this would not be the case if we were defending the other side of the field where the friendly net would be `(4.5, 0)`. - -In order to overcome this, our convention is that: -* The **friendly half** of the field is **always negative x**, and the **enemy half** of the field is **always positive x** -* `y` is positive to the "left" of someone looking at the enemy goal from the friendly goal -* The center of the field (inside the center-circle) is the origin / `(0, 0)` - This is easiest to understand in the [diagram](#convention-diagram) below. Based on what side we are defending, [Sensor Fusion](#sensor-fusion) will transform all the coordinates of incoming data so that it will match our convention. This means that from the perspective of the rest of the system, the friendly half of the field is always negative x and the enemy half is always positive x. Now when we want to tell a robot to move to the friendly goal, we can simply tell it so move to `(-4.5, 0)` and we know this will _always_ be the friendly side. All of our code is written with the assumption in mind. diff --git a/environment_setup/setup_software.sh b/environment_setup/setup_software.sh index 2a8c9a5660..fd94cce9a5 100755 --- a/environment_setup/setup_software.sh +++ b/environment_setup/setup_software.sh @@ -201,4 +201,8 @@ print_status_msg "Set up ansible-lint" /opt/tbotspython/bin/ansible-galaxy collection install ansible.posix print_status_msg "Finished setting up ansible-lint" +print_status_msg "Setting up STM32 cross-compiler" +install_stm32_cross_compiler $g_arch +print_status_msg "Done setting up STM32 cross-compiler" + print_status_msg "Done Software Setup, please reboot for changes to take place" diff --git a/environment_setup/util.sh b/environment_setup/util.sh index fb85ba8a2b..eb8bad99cb 100755 --- a/environment_setup/util.sh +++ b/environment_setup/util.sh @@ -121,6 +121,19 @@ install_python_dev_cross_compile_headers() { rm -rf /tmp/tbots_download_cache/python-3.12.0.tar.xz } +install_stm32_cross_compiler() { + arch="aarch64" + if is_x86 $1; then + arch="x86_64" + fi + download_link=https://developer.arm.com/-/media/Files/downloads/gnu/14.3.rel1/binrel/arm-gnu-toolchain-14.3.rel1-${arch}-arm-none-eabi.tar.xz + + wget -N $download_link -O /tmp/tbots_download_cache/arm-gnu-toolchain.tar.xz + tar -xf /tmp/tbots_download_cache/arm-gnu-toolchain.tar.xz -C /tmp/tbots_download_cache/ + sudo mv /tmp/tbots_download_cache/arm-gnu-toolchain-14.3.rel1-${arch}-arm-none-eabi /opt/tbotspython/arm-none-eabi-gcc + rm /tmp/tbots_download_cache/arm-gnu-toolchain.tar.xz +} + install_python_toolchain_headers() { sudo mkdir -p /opt/tbotspython/py_headers/include/ sudo ln -sfn "$(python3.12-config --includes | awk '{for(i=1;i<=NF;++i) if ($i ~ /^-I/) print substr($i, 3)}' | head -n1)" /opt/tbotspython/py_headers/include/ diff --git a/src/MODULE.bazel b/src/MODULE.bazel index d793364852..40d340f58f 100644 --- a/src/MODULE.bazel +++ b/src/MODULE.bazel @@ -193,6 +193,17 @@ http_archive( url = "https://github.com/saebyn/munkres-cpp/archive/61086fcf5b3a8ad4bda578cdc2d1dca57b548786.tar.gz", ) +# For flashing the motor driver board +http_archive( + name = "bazel_embedded", + sha256 = "91a78efbb0f94479fe9ca2912aced712be9e7028325bada9a73e7d325427b404", + strip_prefix = "bazel-embedded-cf5b240d510313b3383e0cdb550eb9f32be7244e", + url = "https://github.com/bazelembedded/bazel-embedded/archive/cf5b240d510313b3383e0cdb550eb9f32be7244e.zip", +) + +openocd = use_extension("//starlark/firmware:open_ocd.bzl", "openocd_extension") +use_repo(openocd, "com_openocd") + http_archive( name = "LTC4151", build_file = "@//extlibs:LTC4151.BUILD", @@ -201,6 +212,24 @@ http_archive( url = "https://github.com/kerrydwong/LTC4151/archive/729952f10bcdcf359877b6f728565c17a8f17423.tar.gz", ) +# TODO: Perhaps set this to pull latest +#http_archive( +# name = "mdv6_firmware", +# build_file = "@//extlibs:mdv6_firmware.bzl", +# sha256 = "8054aca4ae3543e39d3587c58aa5904917d9b268922564f6ca3acf4b59bd8fe8", +# strip_prefix = "MDv6_Firmware-81627d25b5f147c0b5b3f8ceb2c22c883625a3d1", +# url = "https://github.com/UBC-Thunderbots/MDv6_Firmware/archive/81627d25b5f147c0b5b3f8ceb2c22c883625a3d1.tar.gz", +#) + +git_repository = use_repo_rule("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") + +git_repository( + name = "mdv6_firmware", + branch = "master", + build_file = "@//extlibs:mdv6_firmware.bzl", + remote = "https://github.com/UBC-Thunderbots/MDv6_Firmware.git", +) + http_archive( name = "trinamic", build_file = "@//extlibs:trinamic.BUILD", @@ -224,6 +253,7 @@ register_toolchains( "//toolchains/cc:cc_toolchain_for_k8_aarch64_linux", "//toolchains/cc:cc_toolchain_for_k8", "//toolchains/cc:cc_toolchain_for_aarch64", + "//toolchains/cc:cc_toolchain_for_stm32", ) ############################################## @@ -256,6 +286,12 @@ new_local_repository( path = "/opt/tbotspython/aarch64-tbots-linux-gnu/", ) +new_local_repository( + name = "motor_board_gcc", + build_file = "@//extlibs:motor_board_gcc.BUILD", + path = "/opt/tbotspython/arm-none-eabi-gcc/", +) + new_local_repository( name = "py_cc_toolchain_host", build_file = "@//extlibs:py_cc_toolchain.BUILD", diff --git a/src/MODULE.bazel.lock b/src/MODULE.bazel.lock index 22fa90bdcf..89e9532ddf 100644 --- a/src/MODULE.bazel.lock +++ b/src/MODULE.bazel.lock @@ -261,6 +261,28 @@ }, "selectedYankedVersions": {}, "moduleExtensions": { + "//starlark/firmware:open_ocd.bzl%openocd_extension": { + "general": { + "bzlTransitiveDigest": "nHqUfbfGUyhDdTVk0sMZhhjz2Q4vRah/GiiBo7N+XrI=", + "usagesDigest": "fwt5j4wTh9/7Dm/s7mCIMf9myU5XrpzzXiXM8HfqgcA=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "com_openocd": { + "repoRuleId": "@@+_repo_rules+bazel_embedded//tools/openocd:openocd_repository.bzl%openocd_repository", + "attributes": {} + } + }, + "recordedRepoMappingEntries": [ + [ + "", + "bazel_embedded", + "+_repo_rules+bazel_embedded" + ] + ] + } + }, "@@apple_support+//crosstool:setup.bzl%apple_cc_configure_extension": { "general": { "bzlTransitiveDigest": "E970FlMbwpgJPdPUQzatKh6BMfeE0ZpWABvwshh7Tmg=", diff --git a/src/extlibs/mdv6_firmware.bzl b/src/extlibs/mdv6_firmware.bzl new file mode 100644 index 0000000000..09dcf87748 --- /dev/null +++ b/src/extlibs/mdv6_firmware.bzl @@ -0,0 +1,89 @@ +load("@bazel_embedded//tools/openocd:defs.bzl", "openocd_flash") + +cc_binary( + name = "mdv6_firmware_main", + # Linker file informs the toolchain how much RAM and flash you have as well as its locations on the chip + additional_linker_inputs = [ + "STM32CubeIDE/STM32F031C6TX_FLASH.ld", + ], + linkopts = ["-T $(location STM32CubeIDE/STM32F031C6TX_FLASH.ld)"], + target_compatible_with = [ + "@platforms//cpu:armv6-m", + "@platforms//os:none", + ], + deps = [ + ":mdv6_firmware", + ], +) + +openocd_flash( + name = "mdv6_firmware_flash", + device_configs = [ + # Part of the STM32F0 family + # https://www.st.com/resource/en/reference_manual/rm0091-stm32f0x1stm32f0x2stm32f0x8-advanced-armbased-32bit-mcus-stmicroelectronics.pdf + "target/stm32f0x.cfg", + ], + # WARNING! `flash_offset` attribute is broken in the Bazel rule. The .elf file (mdv6_firmware_main.stripped) + # contains the memory mappings already, so this attribute is necessary. + flash_offset = "", + # .stripped strips debug symbols to reduce the size of the binary. + # see: https://bazel.build/reference/be/c-cpp#cc_binary + image = ":mdv6_firmware_main.stripped", + interface_configs = [ + "interface/stlink.cfg", # The ST-Link V2 programmer is used to flash the firmware. + ], +) + +cc_library( + name = "mdv6_firmware", + srcs = glob([ + "Core/Src/**/*.c", + "Src/**/*.c", + "STM32CubeIDE/Application/Startup/*.s", + "STM32CubeIDE/Application/User/*.c", + "*.c", + "Drivers/STM32F0xx_HAL_Driver/Src/*.c", + ], exclude = [ + "Drivers/STM32F0xx_HAL_Driver/Src/*_template.c", + ]) + [ + "MCSDK_v6.4.1-Full/MotorControl/MCSDK/MCLib/Any/Src/bus_voltage_sensor.c", + "MCSDK_v6.4.1-Full/MotorControl/MCSDK/MCLib/Any/Src/circle_limitation.c", + "MCSDK_v6.4.1-Full/MotorControl/MCSDK/MCLib/Any/Src/digital_output.c", + "MCSDK_v6.4.1-Full/MotorControl/MCSDK/MCLib/Any/Src/feed_forward_ctrl.c", + "MCSDK_v6.4.1-Full/MotorControl/MCSDK/MCLib/Any/Src/ntc_temperature_sensor.c", + "MCSDK_v6.4.1-Full/MotorControl/MCSDK/MCLib/Any/Src/open_loop.c", + "MCSDK_v6.4.1-Full/MotorControl/MCSDK/MCLib/Any/Src/pid_regulator.c", + "MCSDK_v6.4.1-Full/MotorControl/MCSDK/MCLib/Any/Src/pqd_motor_power_measurement.c", + "MCSDK_v6.4.1-Full/MotorControl/MCSDK/MCLib/Any/Src/r_divider_bus_voltage_sensor.c", + "MCSDK_v6.4.1-Full/MotorControl/MCSDK/MCLib/Any/Src/ramp_ext_mngr.c", + "MCSDK_v6.4.1-Full/MotorControl/MCSDK/MCLib/Any/Src/revup_ctrl.c", + "MCSDK_v6.4.1-Full/MotorControl/MCSDK/MCLib/Any/Src/speed_pos_fdbk.c", + "MCSDK_v6.4.1-Full/MotorControl/MCSDK/MCLib/Any/Src/virtual_speed_sensor.c", + ], + hdrs = glob([ + "Core/Inc/**/*.h", + "*.h", + "Inc/**/*.h", + "Drivers/STM32F0xx_HAL_Driver/Inc/**/*.h", + "Drivers/CMSIS/Device/ST/STM32F0xx/Include/**/*.h", + "Drivers/CMSIS/Include/**/*.h", + "MCSDK_v6.4.1-Full/**/*.h", + ]), + includes = [ + "Core/Inc", + "Drivers/STM32F0xx_HAL_Driver/Inc", + "Drivers/CMSIS/Device/ST/STM32F0xx/Include", + "Drivers/CMSIS/Include", + "MCSDK_v6.4.1-Full/MotorControl/MCSDK/MCLib/Any/Inc", + "Inc", + ], + defines = [ + # Our MCU is the STM32F0251: https://www.st.com/resource/en/datasheet/stspin32f0251.pdf + "STM32F031x6", + # Use the HAL and Low-Layer APIs. + "USE_HAL_DRIVER", + "USE_FULL_LL_DRIVER", + "ARM_MATH_CM0", + ], + alwayslink = True, +) diff --git a/src/extlibs/motor_board_gcc.BUILD b/src/extlibs/motor_board_gcc.BUILD new file mode 100644 index 0000000000..3e87eea155 --- /dev/null +++ b/src/extlibs/motor_board_gcc.BUILD @@ -0,0 +1,9 @@ +package(default_visibility = ["//visibility:public"]) + +filegroup( + name = "includes", + srcs = glob([ + "arm-none-eabi/include", + "lib/gcc/arm-none-eabi/14.3.1/include", + ]), +) diff --git a/src/software/embedded/BUILD b/src/software/embedded/BUILD index d28b7ff4eb..e053b1cb30 100644 --- a/src/software/embedded/BUILD +++ b/src/software/embedded/BUILD @@ -73,6 +73,11 @@ sh_binary( srcs = ["setup_robot_software_deps.sh"], ) +sh_binary( + name = "setup_openocd_deps", + srcs = ["setup_openocd_deps.sh"], +) + filegroup( name = "hash_thunderloop_binary", srcs = ["hash_thunderloop_binary.sh"], diff --git a/src/software/embedded/ansible/BUILD b/src/software/embedded/ansible/BUILD index 1225195e86..f52f4b51ff 100644 --- a/src/software/embedded/ansible/BUILD +++ b/src/software/embedded/ansible/BUILD @@ -19,6 +19,7 @@ py_binary( ":playbooks", ":tasks", "//software/embedded:hash_thunderloop_binary.sh", + "//software/embedded:setup_openocd_deps", "//software/embedded:setup_robot_software_deps", "//software/embedded:thunderloop_main", "//software/embedded/linux_configs/pi:pi_files", diff --git a/src/software/embedded/ansible/playbooks/deploy_motor_firmware.yml b/src/software/embedded/ansible/playbooks/deploy_motor_firmware.yml new file mode 100644 index 0000000000..4f0980362e --- /dev/null +++ b/src/software/embedded/ansible/playbooks/deploy_motor_firmware.yml @@ -0,0 +1,106 @@ +--- +- name: Deploy firmware to the motor driver boards + hosts: THUNDERBOTS_HOSTS + + vars: + repo_root: "{{ workspace_dir | default(playbook_dir + '/../../../../') }}" + # Bazel output location (convenience symlink) + # bazel_raw_out_path: "bazel-bin/external/+_repo_rules2+mdv6_firmware/mdv6_firmware_main" + # bazel_out_path: "bazel-bin/external/+_repo_rules2+mdv6_firmware/mdv6_firmware_main.bin" + firmware_bin_name: "mdv6_firmware_main.bin" + firmware_archive_name: "mdv6_firmware.tar.gz" + script_path: "software/embedded/drivers/flash_motor_drivers.py" + cfg_path: "software/embedded/drivers/raspberrypi.cfg" + stm32_cfg_path: "software/embedded/drivers/stm32_rpi.cfg" + remote_dir: "~/motor_firmware" + arm_toolchain_path: "/opt/tbotspython/arm-none-eabi-gcc/bin" + + tasks: + - name: Build firmware locally + delegate_to: localhost + ansible.builtin.command: "bazel build @mdv6_firmware//:mdv6_firmware_main --platforms=//toolchains/cc:motor_board" + args: + chdir: "{{ repo_root }}" + changed_when: false + + - name: Get binary path + delegate_to: localhost + ansible.builtin.command: "bazel cquery @mdv6_firmware//:mdv6_firmware_main --output=files --platforms=//toolchains/cc:motor_board" + args: + chdir: "{{ repo_root }}" + register: cquery_result + changed_when: false + + - name: Set path facts + ansible.builtin.set_fact: + bazel_raw_out_path: "{{ cquery_result.stdout.strip() }}" + bazel_out_path: "{{ cquery_result.stdout.strip() }}.bin" + + - name: Debug resolved paths + ansible.builtin.debug: + msg: "ELF Path: {{ bazel_raw_out_path }} | BIN Path: {{ bazel_out_path }}" + + - name: Convert .elf binary to .bin binary + delegate_to: localhost + ansible.builtin.command: "{{ arm_toolchain_path }}/arm-none-eabi-objcopy -O binary {{ bazel_raw_out_path }} {{ bazel_out_path }}" + args: + chdir: "{{ repo_root }}" + + - name: Archive firmware binary locally + delegate_to: localhost + ansible.builtin.command: "tar -czf {{ firmware_archive_name }} {{ bazel_out_path | basename }}" # noqa: command-instead-of-module + args: + chdir: "{{ (repo_root, bazel_out_path | dirname) | path_join }}" + changed_when: false + + - name: Remove existing remote directory + ansible.builtin.file: + path: "{{ remote_dir }}" + state: absent + + - name: Create directory on remote + ansible.builtin.file: + path: "{{ remote_dir }}" + state: directory + mode: "0755" + + - name: Copy firmware archive to remote + ansible.builtin.copy: + src: "{{ (repo_root, bazel_out_path | dirname, firmware_archive_name) | path_join }}" + dest: "{{ remote_dir }}/{{ firmware_archive_name }}" + mode: "0644" + + - name: Copy flashing script to remote + ansible.builtin.copy: + src: "{{ (repo_root, script_path) | path_join }}" + dest: "{{ remote_dir }}/flash_motor_drivers.py" + mode: "0755" + + - name: Copy openocd config to remote + ansible.builtin.copy: + src: "{{ repo_root }}/{{ cfg_path }}" + dest: "{{ remote_dir }}/raspberrypi.cfg" + mode: "0644" + + - name: Copy STM32 OpenOCD config to remote + ansible.builtin.copy: + src: "{{ repo_root }}/{{ stm32_cfg_path }}" + dest: "{{ remote_dir }}/stm32_rpi.cfg" + mode: "0644" + + - name: Unarchive firmware on remote + ansible.builtin.unarchive: + src: "{{ remote_dir }}/{{ firmware_archive_name }}" + dest: "{{ remote_dir }}" + remote_src: true + + - name: Configure Pinmux for SWD + ansible.builtin.shell: "echo {{ ansible_sudo_pass }} | sudo -S pinctrl set 4 ip pu && echo {{ ansible_sudo_pass }} | sudo -S pinctrl set 17 ip pu && echo {{ + ansible_sudo_pass }} | sudo -S pinctrl set 18 ip pu" + ignore_errors: true + + - name: Run flashing script for Motor Drivers (A B C D) + ansible.builtin.shell: "echo {{ ansible_sudo_pass }} | sudo -S python3 flash_motor_drivers.py A B C D" + args: + chdir: "{{ remote_dir }}" + changed_when: true diff --git a/src/software/embedded/ansible/playbooks/setup_pi.yml b/src/software/embedded/ansible/playbooks/setup_pi.yml index a5a37c0aa4..060139876f 100644 --- a/src/software/embedded/ansible/playbooks/setup_pi.yml +++ b/src/software/embedded/ansible/playbooks/setup_pi.yml @@ -29,6 +29,12 @@ ansible.builtin.import_tasks: file: ../tasks/setup_robot_software_deps.yml + - name: Setup OpenOCD dependencies + tags: + - dependencies + ansible.builtin.import_tasks: + file: ../tasks/setup_openocd_deps.yml + - name: Setup systemd tags: - dependencies diff --git a/src/software/embedded/ansible/requirements.in b/src/software/embedded/ansible/requirements.in index f686024942..d392cb9e03 100644 --- a/src/software/embedded/ansible/requirements.in +++ b/src/software/embedded/ansible/requirements.in @@ -1 +1,2 @@ ansible==11.9.0 +gpiozero==2.0.1 diff --git a/src/software/embedded/ansible/requirements_lock.txt b/src/software/embedded/ansible/requirements_lock.txt index 77dfeb01c6..fcd4881d3e 100644 --- a/src/software/embedded/ansible/requirements_lock.txt +++ b/src/software/embedded/ansible/requirements_lock.txt @@ -98,6 +98,10 @@ cffi==2.0.0 \ --hash=sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453 \ --hash=sha256:fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf # via cryptography +colorzero==2.0 \ + --hash=sha256:0e60d743a6b8071498a56465f7719c96a5e92928f858bab1be2a0d606c9aa0f8 \ + --hash=sha256:e7d5a5c26cd0dc37b164ebefc609f388de24f8593b659191e12d85f8f9d5eb58 + # via gpiozero cryptography==46.0.1 \ --hash=sha256:0a17377fa52563d730248ba1f68185461fff36e8bc75d8787a7dd2e20a802b7a \ --hash=sha256:0ca4be2af48c24df689a150d9cd37404f689e2968e247b6b8ff09bff5bcd786f \ @@ -154,6 +158,10 @@ cryptography==46.0.1 \ --hash=sha256:f9b55038b5c6c47559aa33626d8ecd092f354e23de3c6975e4bb205df128a2a0 \ --hash=sha256:fd4b5e2ee4e60425711ec65c33add4e7a626adef79d66f62ba0acfd493af282d # via ansible-core +gpiozero==2.0.1 \ + --hash=sha256:8f621de357171d574c0b7ea0e358cb66e560818a47b0eeedf41ce1cdbd20c70b \ + --hash=sha256:d4ea1952689ec7e331f9d4ebc9adb15f1d01c2c9dcfabb72e752c9869ab7e97e + # via -r software/embedded/ansible/requirements.in jinja2==3.1.6 \ --hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \ --hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67 @@ -329,3 +337,9 @@ resolvelib==1.0.1 \ --hash=sha256:04ce76cbd63fded2078ce224785da6ecd42b9564b1390793f64ddecbe997b309 \ --hash=sha256:d2da45d1a8dfee81bdd591647783e340ef3bcb104b54c383f70d422ef5cc7dbf # via ansible-core + +# The following packages are considered to be unsafe in a requirements file: +setuptools==82.0.0 \ + --hash=sha256:22e0a2d69474c6ae4feb01951cb69d515ed23728cf96d05513d36e42b62b37cb \ + --hash=sha256:70b18734b607bd1da571d097d236cfcfacaf01de45717d59e6e04b96877532e0 + # via colorzero diff --git a/src/software/embedded/ansible/run_ansible.py b/src/software/embedded/ansible/run_ansible.py index bd62b9fb25..9f4fa0f6c6 100644 --- a/src/software/embedded/ansible/run_ansible.py +++ b/src/software/embedded/ansible/run_ansible.py @@ -29,9 +29,14 @@ def ansible_runner(playbook: str, options: dict = {}): # parse options vars = set(options.get("extra_vars", [])) + if "BUILD_WORKSPACE_DIRECTORY" in os.environ: + vars.add(f"workspace_dir={os.environ['BUILD_WORKSPACE_DIRECTORY']}") + + ssh_pass = options.get("ssh_pass", "") + vars.add(f"ansible_sudo_pass={ssh_pass}") + tags = set(options.get("tags", {})) skip_tags = set(options.get("skip_tags", {})) - ssh_pass = options.get("ssh_pass", "") hosts = set(options.get("hosts", [])) host_aliases = hosts.copy() @@ -105,7 +110,13 @@ def ansible_runner(playbook: str, options: dict = {}): passwords={"conn_pass": ssh_pass, "become_pass": ssh_pass}, ) - pbex.run() + try: + pbex.run() + except Exception as e: + import traceback + + traceback.print_exc() + raise e def main(): diff --git a/src/software/embedded/ansible/tasks/setup_openocd_deps.yml b/src/software/embedded/ansible/tasks/setup_openocd_deps.yml new file mode 100644 index 0000000000..1a992416d8 --- /dev/null +++ b/src/software/embedded/ansible/tasks/setup_openocd_deps.yml @@ -0,0 +1,17 @@ +--- +- name: Sync OpenOCD Script + become: true + become_method: ansible.builtin.sudo + ansible.posix.synchronize: + src: ../../setup_openocd_deps.sh + dest: ~/ + copy_links: true + +- name: Running the OpenOCD script, this will take a while + become_method: ansible.builtin.sudo + become: true + ansible.builtin.command: "/home/{{ ansible_user }}/setup_openocd_deps.sh" + register: result + changed_when: true + args: + chdir: ~/ diff --git a/src/software/embedded/drivers/BUILD b/src/software/embedded/drivers/BUILD new file mode 100644 index 0000000000..d9909598f6 --- /dev/null +++ b/src/software/embedded/drivers/BUILD @@ -0,0 +1,12 @@ +load("@ansible_deps//:requirements.bzl", "requirement") +load("@rules_python//python:defs.bzl", "py_binary") + +py_binary( + name = "flash_motor_drivers", + srcs = [ + "flash_motor_drivers.py", + ], + deps = [ + requirement("gpiozero"), + ], +) diff --git a/src/software/embedded/drivers/flash_motor_drivers.py b/src/software/embedded/drivers/flash_motor_drivers.py new file mode 100755 index 0000000000..f4d002589a --- /dev/null +++ b/src/software/embedded/drivers/flash_motor_drivers.py @@ -0,0 +1,109 @@ +import subprocess +import time +import sys +from gpiozero import LED +from rich import print + +# The reset pin for the motor drivers +MOTOR_DRIVER_RESET_PIN = 12 + +S0_PIN = 21 +S1_PIN = 16 + +DEMUX_DISABLE_PIN = 0 # Pull HIGH to disable SWD and SWCLK multiplexing + +# Put these in order of board 0, 1, 2, 3 +BOARD_NAMES = ["A", "B", "C", "D"] + + +class MotorDriverFlasher: + def __init__(self, board_letter, drivers): + switch_case_num = BOARD_NAMES.index(board_letter) + self.board_letter = board_letter + self.multiplex = [switch_case_num % 2, switch_case_num // 2] + + self.drivers = drivers + + def flash(self): + for i in range(2): + if self.multiplex[i] == 1: + self.drivers[i].on() + else: + self.drivers[i].off() + + print(f"Preparing to flash driver on board {self.board_letter}...") + + # Short delay to ensure lines settle + time.sleep(0.5) + + try: + # Run OpenOCD + result = subprocess.run( + [ + "sudo", + "openocd", + "-c", + f"set RESET_PIN {MOTOR_DRIVER_RESET_PIN}", + "-f", + "stm32_rpi.cfg", + # Force the Pi to hold the reset line down during connection + "-c", + "reset_config srst_only srst_nogate connect_assert_srst", + "-c", + "adapter srst delay 100", + "-c", + "init", + # Modern atomic command + "-c", + "program mdv6_firmware_main.bin verify reset exit 0x08000000", + ], + capture_output=True, + text=True, + check=True, + ) + print( + f"Flash output for driver:\n{result.stderr}" + ) # OpenOCD often prints to stderr + except subprocess.CalledProcessError as e: + print("Failed to flash driver!") + print("STDOUT:", e.stdout) + print("STDERR:", e.stderr) + # We raise to stop the process if one fails, or we could continue. + # Usually best to know immediately. + raise e + + # After flashing all, ensure all are set to High (Run) + print(f"Flash to board {self.board_letter} complete.") + # Let line settle rq + time.sleep(0.5) + + +if __name__ == "__main__": + # If no arguments given, + if not (2 <= len(sys.argv) <= 5): + print( + "Usage: python3 flash_motor_drivers.py (A to D valid)" + ) + sys.exit(1) + + # Check before attempting to flash if every argument is in BOARD_NAMES + for i in range(1, len(sys.argv)): + if sys.argv[i] not in BOARD_NAMES: + print( + f"Usage: python3 flash_motor_drivers.py (Valid Letters: {BOARD_NAMES})" + ) + sys.exit(1) + + # Initialize pins as LEDs since we only need high/low logic. + drivers = [LED(S0_PIN), LED(S1_PIN)] + + # Enable the multiplexers + DEMUX_DISABLE = LED(DEMUX_DISABLE_PIN) + DEMUX_DISABLE.off() + + for i in range(1, len(sys.argv)): + flasher = MotorDriverFlasher(sys.argv[i], drivers) + flasher.flash() + + # Once done flashing, disable multiplexing just in case + DEMUX_DISABLE.on() diff --git a/src/software/embedded/drivers/raspberrypi.cfg b/src/software/embedded/drivers/raspberrypi.cfg new file mode 100644 index 0000000000..98ef95cbff --- /dev/null +++ b/src/software/embedded/drivers/raspberrypi.cfg @@ -0,0 +1,18 @@ +# +# Config for using Raspberry Pi's expansion header for flashing using OpenOCD +# +# This is best used with a fast enough buffer but also +# is suitable for direct connection if the target voltage +# matches RPi's 3.3V and the cable is short enough. +# +# Do not forget the GND connection, pin 6 of the expansion header. +# + +adapter driver linuxgpiod + +transport select swd + +adapter gpio swclk 26 -chip 4 +adapter gpio swdio 20 -chip 4 + +reset_config srst_only srst_push_pull diff --git a/src/software/embedded/drivers/stm32_rpi.cfg b/src/software/embedded/drivers/stm32_rpi.cfg new file mode 100644 index 0000000000..c9d4e55b24 --- /dev/null +++ b/src/software/embedded/drivers/stm32_rpi.cfg @@ -0,0 +1,41 @@ +# Config for flashing STM32F0x using Raspberry Pi 5 (linuxgpiod) +# Overrides standard target events to avoid "adapter speed" errors. + +# Source the interface config. +# We assume this file is in the same directory or search path. +source [find raspberrypi.cfg] + +# Source the standard target config +source [find target/stm32f0x.cfg] + +if { [info exists RESET_PIN] } { + set _RESET_PIN $RESET_PIN +} else { + set _RESET_PIN 12 +} +adapter gpio srst $_RESET_PIN -chip 4 + +reset_config srst_only srst_nogate connect_assert_srst +adapter srst delay 200 + +# The linuxgpiod driver does not support "adapter speed" or "adapter_khz" commands. +# Standard STM32 target configs often try to change speed during reset events. +# We override these events to do nothing (or just avoid the speed change). + +# Hardcoded target name from standard stm32f0x.cfg +set _TARGETNAME stm32f0x.cpu + +$_TARGETNAME configure -event reset-start { + echo "Running overridden reset-start for linuxgpiod (skipping adapter speed change)" +} + +$_TARGETNAME configure -event reset-init { + echo "Running overridden reset-init for linuxgpiod" +} + +# Override reset configuration to use Software Reset (AIRCR) via SWD. +# The 'raspberrypi.cfg' sets 'srst_only' on GPIO 18, but our boards use +# different reset pins (e.g., GPIO 12) managed by the Python script. +# 'reset_config none' tells OpenOCD we don't have a controllable physical +# reset line, forcing it to use the Cortex-M software reset command instead. +reset_config none diff --git a/src/software/embedded/robot_diagnostics_cli/BUILD b/src/software/embedded/robot_diagnostics_cli/BUILD index ae24d0cbf7..e49b22b312 100644 --- a/src/software/embedded/robot_diagnostics_cli/BUILD +++ b/src/software/embedded/robot_diagnostics_cli/BUILD @@ -3,7 +3,7 @@ load("@robot_diagnostics_cli_deps//:requirements.bzl", "requirement") load("@rules_pkg//:pkg.bzl", "pkg_tar") load("@rules_python//python:defs.bzl", "py_binary") load("@rules_python//python:pip.bzl", "compile_pip_requirements") -load("//:starlark/defs.bzl", "pkg_executable") +load("//starlark:defs.bzl", "pkg_executable") package(default_visibility = ["//visibility:public"]) diff --git a/src/software/embedded/setup_openocd_deps.sh b/src/software/embedded/setup_openocd_deps.sh new file mode 100755 index 0000000000..e8bc580cd4 --- /dev/null +++ b/src/software/embedded/setup_openocd_deps.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +set -ex + +host_software_packages=( + git + autoconf + libtool + make + pkg-config + libusb-1.0-0 + libusb-1.0-0-dev +) + +# Install packages +sudo apt-get update +sudo apt-get install "${host_software_packages[@]}" -y + +# OpenOCD for MD fLash Installation +if ! command -v openocd &> /dev/null; then + cd ~ + # Using GitHub mirror because zylin Gerrit can be slow/unstable + git clone --depth 1 https://github.com/openocd-org/openocd.git + cd openocd + ./bootstrap + ./configure --enable-sysfsgpio --enable-linuxgpiod + make -j$(nproc) + sudo make install +fi diff --git a/src/software/embedded/setup_robot_software_deps.sh b/src/software/embedded/setup_robot_software_deps.sh index 5769d12f65..90d56cacd5 100755 --- a/src/software/embedded/setup_robot_software_deps.sh +++ b/src/software/embedded/setup_robot_software_deps.sh @@ -5,6 +5,10 @@ set -ex host_software_packages=( device-tree-compiler curl + python3 + python3-venv + python3-pip + libgpiod-dev ) # Install packages @@ -14,15 +18,10 @@ sudo apt-get install "${host_software_packages[@]}" -y # Install platformio udev rules curl -fsSL https://raw.githubusercontent.com/platformio/platformio-core/develop/platformio/assets/system/99-platformio-udev.rules | sudo tee /etc/udev/rules.d/99-platformio-udev.rules -# Install Python 3.12 -mkdir -p /tmp/tbots_download_cache -wget -N https://www.python.org/ftp/python/3.12.0/Python-3.12.0.tar.xz -O /tmp/tbots_download_cache/python-3.12.0.tar.xz -tar -xf /tmp/tbots_download_cache/python-3.12.0.tar.xz -C /tmp/tbots_download_cache/ -cd /tmp/tbots_download_cache/Python-3.12.0 -./configure --enable-optimizations -make -j$(nproc) -sudo make altinstall +# Why tf do we have a venv? Uh sure here we go +sudo mkdir -p /opt/tbotspython +sudo chown $(whoami):$(whoami) /opt/tbotspython -if ! sudo /usr/local/bin/python3.12 -m venv /opt/tbotspython ; then - echo "Error: Installing Python 3.12 failed" +if ! sudo /usr/local/bin/python3 -m venv /opt/tbotspython ; then + echo "Error: Installing Python 3 failed" fi diff --git a/src/starlark/BUILD b/src/starlark/BUILD new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/starlark/firmware/BUILD b/src/starlark/firmware/BUILD new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/starlark/firmware/open_ocd.bzl b/src/starlark/firmware/open_ocd.bzl new file mode 100644 index 0000000000..e8014de234 --- /dev/null +++ b/src/starlark/firmware/open_ocd.bzl @@ -0,0 +1,8 @@ +load("@bazel_embedded//tools/openocd:openocd_repository.bzl", "openocd_deps") + +def _openocd_extension_impl(_ctx): + openocd_deps() + +openocd_extension = module_extension( + implementation = _openocd_extension_impl, +) diff --git a/src/toolchains/cc/BUILD b/src/toolchains/cc/BUILD index fc805f8605..a560ba1276 100644 --- a/src/toolchains/cc/BUILD +++ b/src/toolchains/cc/BUILD @@ -2,6 +2,7 @@ load( ":cc_toolchain_config.bzl", "cc_toolchain_config_fullsystem", "cc_toolchain_config_k8_aarch64_linux", + "cc_toolchain_config_stm32", "make_builtin_include_directories", ) @@ -60,6 +61,14 @@ platform( ], ) +platform( + name = "motor_board", + constraint_values = [ + "@platforms//cpu:armv6-m", + "@platforms//os:none", + ], +) + # This represents a mapping of CPU -> Compiler To Use cc_toolchain_suite( name = "toolchain", @@ -115,6 +124,13 @@ filegroup( ], ) +filegroup( + name = "motor_board_gcc_all", + srcs = [ + "@motor_board_gcc//:includes", + ], +) + cc_toolchain_config_fullsystem( name = "linux_k8_gcc", builtin_include_directories = [ @@ -195,6 +211,39 @@ cc_toolchain( toolchain_identifier = "gcc-k8-aarch64-linux", ) +cc_toolchain_config_stm32( + name = "gcc-arm-none-abi", + builtin_include_directories = [ + "/opt/tbotspython/arm-none-eabi-gcc/arm-none-eabi/include/", + "/opt/tbotspython/arm-none-eabi-gcc/lib/gcc/arm-none-eabi/14.3.1/include/", + ], + tool_paths = { + "ar": "/opt/tbotspython/arm-none-eabi-gcc/bin/arm-none-eabi-ar", + "gcc": "/opt/tbotspython/arm-none-eabi-gcc/bin/arm-none-eabi-gcc", + "cpp": "/opt/tbotspython/arm-none-eabi-gcc/bin/arm-none-eabi-cpp", + "ld": "/opt/tbotspython/arm-none-eabi-gcc/bin/arm-none-eabi-ld", + "nm": "/opt/tbotspython/arm-none-eabi-gcc/bin/arm-none-eabi-nm", + "objdump": "/opt/tbotspython/arm-none-eabi-gcc/bin/arm-none-eabi-objdump", + "strip": "/opt/tbotspython/arm-none-eabi-gcc/bin/arm-none-eabi-strip", + }, + toolchain_identifier = "gcc-stm32", +) + +cc_toolchain( + name = "cc_toolchain_stm32", + all_files = ":motor_board_gcc_all", + ar_files = ":motor_board_gcc_all", + as_files = ":motor_board_gcc_all", + compiler_files = ":motor_board_gcc_all", + dwp_files = ":motor_board_gcc_all", + linker_files = ":motor_board_gcc_all", + objcopy_files = ":motor_board_gcc_all", + strip_files = ":motor_board_gcc_all", + supports_param_files = True, + toolchain_config = ":gcc-arm-none-abi", + toolchain_identifier = "stm32_gcc", +) + cc_toolchain_config_k8_aarch64_linux( name = "gcc_k8_aarch64_linux", builtin_include_directories = [ @@ -256,3 +305,13 @@ toolchain( toolchain = ":cc_toolchain_linux_aarch64_gcc", toolchain_type = "@bazel_tools//tools/cpp:toolchain_type", ) + +toolchain( + name = "cc_toolchain_for_stm32", + target_compatible_with = [ + "@platforms//cpu:armv6-m", + "@platforms//os:none", + ], + toolchain = ":cc_toolchain_stm32", + toolchain_type = "@bazel_tools//tools/cpp:toolchain_type", +) diff --git a/src/toolchains/cc/cc_toolchain_config.bzl b/src/toolchains/cc/cc_toolchain_config.bzl index 5b78d9207e..f98b54a646 100644 --- a/src/toolchains/cc/cc_toolchain_config.bzl +++ b/src/toolchains/cc/cc_toolchain_config.bzl @@ -84,7 +84,6 @@ ALL_COMPILE_ACTIONS = [ ACTION_NAMES.preprocess_assemble, ACTION_NAMES.lto_indexing, ACTION_NAMES.lto_backend, - ACTION_NAMES.strip, ACTION_NAMES.clif_match, ] @@ -614,6 +613,193 @@ cc_toolchain_config_fullsystem = rule( executable = True, ) +def _stm32_gcc_impl(ctx): + cortex_feature = feature( + name = "cortex_cpu", + enabled = True, + flag_sets = [ + flag_set( + actions = ALL_COMPILE_ACTIONS + ALL_LINK_ACTIONS, + flag_groups = [ + flag_group( + flags = [ + # Compile for the Cortex M0 + "-mcpu=cortex-m0", + # Use Thumb-2 instruction set + "-mthumb", + # No floating point unit + "-mfloat-abi=soft", + ], + ), + ], + ), + ], + ) + + space_optimization_feature = feature( + name = "space_optimization", + # Optimize for space, otherwise we'll likely overflow RAM and/or stack + enabled = True, + flag_sets = [ + flag_set( + actions = ALL_COMPILE_ACTIONS, + flag_groups = [ + flag_group( + flags = [ + # Optimize for size + "-Ofast", + # Places each function into its own section. If coupled with --gc-sections in the linker + # stage, this option strips out unused functions and reduces code size. + "-ffunction-sections", + # Places each data item into its own section. + "-fdata-sections", + ], + ), + ], + ), + flag_set( + actions = ALL_LINK_ACTIONS, + flag_groups = [ + flag_group( + flags = [ + # Garbage collects unused sections of code. Used with the compiler's -ffunction-sections, + # this option will completely strip out unused functions out of the created binary. + "-Wl,--gc-sections", + ], + ), + ], + ), + ], + ) + + no_syscalls_feature = feature( + name = "no_syscalls", + enabled = True, + flag_sets = [ + flag_set( + actions = ALL_LINK_ACTIONS, + flag_groups = [ + flag_group( + flags = [ + # Use Newlib Nano for smaller binary size, which is the default for STM32CubeIDE + "--specs=nano.specs", + # No OS, so stub out the system calls + "--specs=nosys.specs", + # Ensure the binary is static + "-static", + ], + ), + ], + ), + ], + ) + + map_file_feature = feature( + name = "map_file", + flag_sets = [ + flag_set( + actions = ALL_LINK_ACTIONS, + flag_groups = [ + flag_group( + flags = [ + # Generate a map file with the cross-reference table. This is very helpful to figure out + # what functions are taking the most space in RAM and flash and who calls them. + "-Wl,-Map=output.map,--cref", + ], + ), + ], + ), + ], + ) + + bare_metal_feature = feature( + name = "bare_metal", + enabled = True, + flag_sets = [ + flag_set( + actions = ALL_COMPILE_ACTIONS, + flag_groups = [ + flag_group( + flags = [ + # Tells the compiler that the target platform may not have an operating system, which + # allows it to make some performance and size optimizations. + "-ffreestanding", + ], + ), + ], + ), + ], + ) + + memory_usage_feature = feature( + name = "memory_usage", + enabled = True, + flag_sets = [ + flag_set( + actions = ALL_LINK_ACTIONS, + flag_groups = [ + flag_group( + flags = [ + # Print remaining FLASH and RAM available. + "-Wl,--print-memory-usage", + ], + ), + ], + ), + ], + ) + + features = [ + cortex_feature, + map_file_feature, + no_syscalls_feature, + space_optimization_feature, + bare_metal_feature, + memory_usage_feature, + ] + + return [ + cc_common.create_cc_toolchain_config_info( + ctx = ctx, + features = features, + action_configs = [], + artifact_name_patterns = [], + cxx_builtin_include_directories = ctx.attr.builtin_include_directories, + toolchain_identifier = ctx.attr.toolchain_identifier, + host_system_name = ctx.attr.host_system_name, + target_system_name = ctx.attr.target_system_name, + target_cpu = ctx.attr.target_cpu, + target_libc = "libc", + compiler = "gcc", + abi_version = "unknown", + abi_libc_version = "unknown", + tool_paths = [ + tool_path(name = name, path = path) + for name, path in ctx.attr.tool_paths.items() + ], + make_variables = [], + builtin_sysroot = None, + cc_target_os = None, + ), + ] + +cc_toolchain_config_stm32 = rule( + implementation = _stm32_gcc_impl, + attrs = { + "builtin_include_directories": attr.string_list(), + "extra_features": attr.string_list(), + "extra_no_canonical_prefixes_flags": attr.string_list(), + "host_compiler_warnings": attr.string_list(), + "host_system_name": attr.string(), + "host_unfiltered_compile_flags": attr.string_list(), + "target_cpu": attr.string(), + "target_system_name": attr.string(), + "tool_paths": attr.string_dict(), + "toolchain_identifier": attr.string(), + }, + provides = [CcToolchainConfigInfo], +) + def _k8_aarch64_linux_impl(ctx): host_system_name = "k8"