1
0
forked from IQ.Lvbs/IQ.Pilot

IQ.Pilot Prebuilt Release @ ab07000

This commit is contained in:
IQ.Lvbs history cleanup
2026-08-22 23:42:42 -05:00
commit 9f9c9a70cc
3729 changed files with 778697 additions and 0 deletions

39
panda/.gitignore vendored Normal file
View File

@@ -0,0 +1,39 @@
.venv
*.tmp
*.pyc
.*.swp
.*.swo
*.o
*.so
*.os
*.d
*.dump
a.out
*~
.#*
dist/
build/
pandacan.egg-info/
obj/
examples/output.csv
.DS_Store
.vscode*
nosetests.xml
.mypy_cache/
.sconsign.dblite
uv.lock
compile_commands.json
# CTU info files generated by Cppcheck
*.*.ctu-info
cppcheck-addon-ctu-file-list
# safety coverage-related files
*.gcda
*.gcno
tests/safety/coverage-out
tests/safety/coverage.info
*.profraw
*.profdata
mull.yml

19
panda/Dockerfile Normal file
View File

@@ -0,0 +1,19 @@
FROM ubuntu:24.04
ENV WORKDIR=/tmp/panda/
ENV PYTHONUNBUFFERED=1
ENV PATH="$WORKDIR/.venv/bin:$PATH"
WORKDIR $WORKDIR
# deps install
COPY pyproject.toml __init__.py setup.sh $WORKDIR
RUN mkdir -p $WORKDIR/python/ && touch $WORKDIR/__init__.py
RUN apt-get update && apt-get install -y --no-install-recommends sudo && DEBIAN_FRONTEND=noninteractive $WORKDIR/setup.sh
# second pass for the iqdbc moving tag
ARG CACHEBUST=1
RUN DEBIAN_FRONTEND=noninteractive $WORKDIR/setup.sh
RUN git config --global --add safe.directory $WORKDIR/panda
COPY . $WORKDIR

144
panda/Jenkinsfile vendored Normal file
View File

@@ -0,0 +1,144 @@
def docker_run(String step_label, int timeout_mins, String cmd) {
timeout(time: timeout_mins, unit: 'MINUTES') {
sh script: "docker run --rm --privileged \
--env PYTHONWARNINGS=error \
--volume /dev/bus/usb:/dev/bus/usb \
--volume /var/run/dbus:/var/run/dbus \
--net host \
${env.DOCKER_IMAGE_TAG} \
bash -c 'scons -j8 && ${cmd}'", \
label: step_label
}
}
def phone(String ip, String step_label, String cmd) {
withCredentials([file(credentialsId: 'id_rsa', variable: 'key_file')]) {
def ssh_cmd = """
ssh -tt -o StrictHostKeyChecking=no -i ${key_file} 'comma@${ip}' /usr/bin/bash <<'END'
set -e
source ~/.bash_profile
if [ -f /etc/profile ]; then
source /etc/profile
fi
export CI=1
export TEST_DIR=${env.TEST_DIR}
export SOURCE_DIR=${env.SOURCE_DIR}
export GIT_BRANCH=${env.GIT_BRANCH}
export GIT_COMMIT=${env.GIT_COMMIT}
export PYTHONPATH=${env.TEST_DIR}/../
export PYTHONWARNINGS=error
export LOGLEVEL=debug
ln -sf /data/openpilot/iqdbc_repo/iqdbc /data/iqdbc
# TODO: this is an agnos issue
export PYTEST_ADDOPTS="-p no:asyncio"
cd ${env.TEST_DIR} || true
${cmd}
exit 0
END"""
sh script: ssh_cmd, label: step_label
}
}
def phone_steps(String device_type, steps) {
lock(resource: "", label: device_type, inversePrecedence: true, variable: 'device_ip', quantity: 1) {
timeout(time: 20, unit: 'MINUTES') {
phone(device_ip, "git checkout", readFile("tests/setup_device_ci.sh"),)
steps.each { item ->
phone(device_ip, item[0], item[1])
}
}
}
}
pipeline {
agent any
environment {
CI = "1"
PYTHONWARNINGS= "error"
DOCKER_IMAGE_TAG = "panda:build-${env.GIT_COMMIT}"
TEST_DIR = "/data/panda"
SOURCE_DIR = "/data/panda_source/"
}
options {
timeout(time: 3, unit: 'HOURS')
disableConcurrentBuilds(abortPrevious: env.BRANCH_NAME != 'master')
}
stages {
stage ('Acquire resource locks') {
options {
lock(resource: "pandas")
}
stages {
stage('Build Docker Image') {
steps {
timeout(time: 20, unit: 'MINUTES') {
script {
dockerImage = docker.build("${env.DOCKER_IMAGE_TAG}", "--build-arg CACHEBUST=${env.GIT_COMMIT} .")
}
}
}
}
stage('jungle tests') {
steps {
script {
retry (3) {
docker_run("reset hardware", 3, "python3 ./tests/hitl/reset_jungles.py")
}
}
}
}
stage('parallel tests') {
parallel {
stage('test cuatro') {
agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } }
steps {
phone_steps("panda-cuatro", [
["build", "scons -j4"],
["flash", "cd scripts/ && ./reflash_internal_panda.py"],
["flash jungle", "cd board/jungle && ./flash.py --all"],
["test", "cd tests/hitl && pytest --durations=0 2*.py [5-9]*.py"],
])
}
}
stage('test tres') {
agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } }
steps {
phone_steps("panda-tres", [
["build", "scons -j4"],
["flash", "cd scripts/ && ./reflash_internal_panda.py"],
["flash jungle", "cd board/jungle && ./flash.py --all"],
["test", "cd tests/hitl && pytest --durations=0 2*.py [5-9]*.py"],
])
}
}
/*
stage('bootkick tests') {
steps {
script {
docker_run("test", 10, "pytest ./tests/som/test_bootkick.py")
}
}
}
*/
}
}
}
}
}
}

7
panda/LICENSE Normal file
View File

@@ -0,0 +1,7 @@
Copyright (c) 2016, Comma.ai, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

98
panda/LICENSE.md Normal file
View File

@@ -0,0 +1,98 @@
# IQ.Lvbs (Panda) License v0.1a
Copyright (c) 2026 IQ.Lvbs LLC, a part of Project Teal Lvbs Inc. All Rights Reserved.
DEFINITIONS
"Software" refers to IQ.Pilot, konn3kt, and all associated source code,
documentation, and assets owned by the Copyright Holder.
"Open Components" refers to portions of the Software explicitly marked as
open source.
"Proprietary Components" refers to all portions of the Software not made
available to the public in source form.
"Copyright Holder" refers to IQ.Lvbs LLC, a part of Project Teal Lvbs Inc.
GRANT OF LICENSE
Subject to the terms of this license, you are granted a limited,
non-exclusive, revocable license to:
1. View, study, and learn from the Open Components
2. Modify the Open Components for personal, internal, or open-source public use
3. Run the Software for personal, non-commercial purposes
RESTRICTIONS
You may NOT:
1. Claim ownership of any part of the Software, excluding your own
modifications that do not incorporate Proprietary Components.
2. Reverse engineer, decompile, disassemble, or in any way attempt to
circumvent the obfuscation of the Proprietary Components.
3. Use the Software or any derivative for commercial purposes without
explicit written permission from the Copyright Holder.
4. Remove or alter any copyright notices or this license.
5. Sublicense, sell, or transfer rights to the Software.
6. Use the Software and/or its source code to compete with or create a
substantially similar product.
7. Use the Software in closed source software not licensed by IQ.Lvbs LLC.
CONSEQUENCES OF VIOLATION
In the event any Restriction is violated, any product created using inspiration from, or source code from, IQ.Pilot or Konn3kt shall be subject to a licensing fee determined solely by the Copyright Holder. Additionally, the violating party hereby grants IQ.Lvbs LLC an exclusive, irrevocable, worldwide, royalty-free license to use any and all assets from the infringing product on IQ.Lvbs webpages, advertising materials, and in any other manner IQ.Lvbs sees fit.
OWNERSHIP
All rights, title, and interest in the Software remain exclusively with the
Copyright Holder. Any modifications, improvements, or derivative works you
create based on the Software are owned by the Copyright Holder. By
contributing modifications, you irrevocably assign all rights to the
Copyright Holder.
PROPRIETARY COMPONENTS
The Proprietary Components are provided in binary or obfuscated form only.
Reverse engineering, decompilation, or disassembly of Proprietary Components
is strictly prohibited. Violation of this provision entitles IQ.Lvbs LLC to
pursue all available legal remedies to protect its intellectual property and
trade secrets.
NO WARRANTY
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND. THE COPYRIGHT
HOLDER DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT
LIMITED TO MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND
NON-INFRINGEMENT.
LIMITATION OF LIABILITY
IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES, OR
OTHER LIABILITY ARISING FROM THE USE OF THE SOFTWARE. THE USER ACCEPTS FULL
RESPONSIBILITY FOR ANY AND ALL LIABILITIES WHEN USING IQ.LVBS SOFTWARE.
TERMINATION
This license terminates automatically if you violate any of its terms. Upon
termination, you must destroy all copies of the Software in your possession.
The Copyright Holder reserves the right to revoke this license at any time
for any reason.
GOVERNING LAW
This license shall be governed by the laws of the State of Illinois, United
States of America. Any disputes arising under this license shall be subject
to the exclusive jurisdiction of the courts located in Henry County, Illinois.
---
For commercial licensing inquiries, contact: support@iqlvbs.com

13
panda/__init__.py Normal file
View File

@@ -0,0 +1,13 @@
from .python.constants import McuType, BASEDIR, FW_PATH, USBPACKET_MAX_SIZE # noqa: F401
from .python.spi import PandaSpiException, PandaProtocolMismatch, STBootloaderSPIHandle # noqa: F401
from .python.serial import PandaSerial # noqa: F401
from .python.utils import logger # noqa: F401
from .python import (Panda, PandaDFU, # noqa: F401
pack_can_buffer, unpack_can_buffer, calculate_checksum,
DLC_TO_LEN, LEN_TO_DLC, CANPACKET_HEAD_SIZE)
# panda jungle
from .board.jungle import PandaJungle, PandaJungleDFU # noqa: F401
# panda body
from .board.body import PandaBody # noqa: F401

20
panda/board/README.md Normal file
View File

@@ -0,0 +1,20 @@
## Programming
```
./flash.py # flash application
./recover.py # flash bootstub
```
## Debugging
To print out the serial console from the STM32, run `tests/debug_console.py`
Troubleshooting
----
If your panda will not flash and green LED is on, use `recover.py`.
If panda is blinking fast with green LED, use `flash.py`.
Otherwise if LED is off and panda can't be seen with `lsusb` command, use [panda paw](https://comma.ai/shop/products/panda-paw) to go into DFU mode.
If your device has an internal panda and none of the above works, try running `../scripts/reflash_internal_panda.py`.

0
panda/board/__init__.py Normal file
View File

View File

@@ -0,0 +1,42 @@
# python helpers for the body panda
import struct
from panda import Panda
class PandaBody(Panda):
MOTOR_LEFT: int = 1
MOTOR_RIGHT: int = 2
# ****************** Motor Control *****************
@staticmethod
def _ensure_valid_motor(motor: int) -> None:
if motor not in (PandaBody.MOTOR_LEFT, PandaBody.MOTOR_RIGHT):
raise ValueError("motor must be MOTOR_LEFT or MOTOR_RIGHT")
def motor_set_speed(self, motor: int, speed: int) -> None:
self._ensure_valid_motor(motor)
assert -100 <= speed <= 100, "speed must be between -100 and 100"
speed_param = speed if speed >= 0 else (256 + speed)
self._handle.controlWrite(Panda.REQUEST_OUT, 0xe0, motor, speed_param, b'')
def motor_set_target_rpm(self, motor: int, rpm: float) -> None:
self._ensure_valid_motor(motor)
target_deci_rpm = int(round(rpm * 10.0))
assert -32768 <= target_deci_rpm <= 32767, "target rpm out of range (-3276.8 to 3276.7)"
target_param = target_deci_rpm if target_deci_rpm >= 0 else (target_deci_rpm + (1 << 16))
self._handle.controlWrite(Panda.REQUEST_OUT, 0xe4, motor, target_param, b'')
def motor_stop(self, motor: int) -> None:
self._ensure_valid_motor(motor)
self._handle.controlWrite(Panda.REQUEST_OUT, 0xe1, motor, 0, b'')
def motor_get_encoder_state(self, motor: int) -> tuple[int, float]:
self._ensure_valid_motor(motor)
dat = self._handle.controlRead(Panda.REQUEST_IN, 0xe2, motor, 0, 8)
position, rpm_milli = struct.unpack("<ii", dat)
return position, rpm_milli / 1000.0
def motor_reset_encoder(self, motor: int) -> None:
self._ensure_valid_motor(motor)
self._handle.controlWrite(Panda.REQUEST_OUT, 0xe3, motor, 0, b'')

49
panda/board/body/flash.py Normal file
View File

@@ -0,0 +1,49 @@
#!/usr/bin/env python3
import argparse
import os
import subprocess
from panda import Panda
BODY_DIR = os.path.dirname(os.path.realpath(__file__))
BOARD_DIR = os.path.abspath(os.path.join(BODY_DIR, ".."))
REPO_ROOT = os.path.abspath(os.path.join(BOARD_DIR, ".."))
DEFAULT_FIRMWARE = os.path.join(BOARD_DIR, "obj", "body_h7.bin.signed")
def build_body() -> None:
subprocess.check_call(
f"scons -C {REPO_ROOT} -j$(nproc) board/obj/body_h7.bin.signed",
shell=True,
)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("firmware", nargs="?", help="Optional path to firmware binary to flash")
parser.add_argument("--all", action="store_true", help="Flash all Panda devices")
parser.add_argument(
"--wait-usb",
action="store_true",
help="Wait for the panda to reconnect over USB after flashing (defaults to skipping reconnect).",
)
args = parser.parse_args()
firmware_path = os.path.abspath(args.firmware) if args.firmware is not None else DEFAULT_FIRMWARE
build_body()
if not os.path.isfile(firmware_path):
parser.error(f"firmware file not found: {firmware_path}")
if args.all:
serials = Panda.list()
print(f"found {len(serials)} panda(s) - {serials}")
else:
serials = [None]
for s in serials:
with Panda(serial=s) as p:
print("flashing", p.get_usb_serial())
p.flash(firmware_path, reconnect=args.wait_usb)
exit(1 if len(serials) == 0 else 0)

View File

@@ -0,0 +1,24 @@
# In-circuit debugging using openocd and gdb
## Hardware
Connect an ST-Link V2 programmer to the SWD pins on the board. The pins that need to be connected are:
- GND
- VTref
- SWDIO
- SWCLK
- NRST
Make sure you're using a genuine one for boards that do not have a 3.3V panda power rail. For example, the tres runs at 1.8V, which is not supported by the clones.
## Openocd
Install openocd. For Ubuntu 24.04, the one in the package manager works fine: `sudo apt install openocd`.
## GDB
You need `gdb-multiarch`.
Once openocd is running, you can connect from gdb as follows:
```
$ gdb-multiarch
(gdb) target ext :3333
```
To reset and break, use `monitor reset halt`.

4
panda/board/debug/debug_h7.sh Executable file
View File

@@ -0,0 +1,4 @@
#!/usr/bin/env bash
set -e
sudo openocd -f "interface/stlink.cfg" -c "transport select hla_swd" -f "target/stm32h7x.cfg" -c "init"

View File

@@ -0,0 +1,11 @@
#!/usr/bin/env sh
set -e
DFU_UTIL="dfu-util"
scons -u -j$(nproc)
PYTHONPATH=.. python3 -c "from python import Panda; Panda().reset(enter_bootstub=True); Panda().reset(enter_bootloader=True)" || true
sleep 1
$DFU_UTIL -d 0483:df11 -a 0 -s 0x08020000 -D obj/panda_h7.bin.signed
$DFU_UTIL -d 0483:df11 -a 0 -s 0x08000000:leave -D obj/bootstub.panda_h7.bin

3
panda/board/debug/gdb.sh Executable file
View File

@@ -0,0 +1,3 @@
#!/usr/bin/env bash
gdb-multiarch --eval-command="target extended-remote localhost:3333"

27
panda/board/flash.py Executable file
View File

@@ -0,0 +1,27 @@
#!/usr/bin/env python3
import os
import subprocess
import argparse
from panda import Panda
board_path = os.path.dirname(os.path.realpath(__file__))
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--all", action="store_true", help="Recover all Panda devices")
args = parser.parse_args()
subprocess.check_call(f"scons -C {board_path}/.. -j$(nproc) {board_path}", shell=True)
if args.all:
serials = Panda.list()
print(f"found {len(serials)} panda(s) - {serials}")
else:
serials = [None]
for s in serials:
with Panda(serial=s) as p:
print("flashing", p.get_usb_serial())
p.flash()
exit(1 if len(serials) == 0 else 0)

View File

@@ -0,0 +1,26 @@
Welcome to the jungle
======
Firmware for the Panda Jungle testing board.
Available for purchase at the [comma shop](https://comma.ai/shop/panda-jungle).
## udev rules
To make the jungle usable without root permissions, you might need to setup udev rules for it.
On ubuntu, this should do the trick:
``` bash
sudo tee /etc/udev/rules.d/12-panda_jungle.rules <<EOF
SUBSYSTEM=="usb", ATTRS{idVendor}=="3801", ATTRS{idProduct}=="ddcf", MODE="0666"
SUBSYSTEM=="usb", ATTRS{idVendor}=="3801", ATTRS{idProduct}=="ddef", MODE="0666"
EOF
sudo udevadm control --reload-rules && sudo udevadm trigger
```
## updating the firmware
Updating the firmware is easy! In the `board/jungle/` folder, run:
``` bash
./flash.py
```
If you somehow bricked your jungle, you'll need a [comma key](https://comma.ai/shop/products/comma-key) to put the microcontroller in DFU mode for the V1.
For V2, the onboard button serves this purpose. When powered on while holding the button to put it in DFU mode, running `./recover.sh` in `board/` should unbrick it.

View File

@@ -0,0 +1,159 @@
# python library to interface with panda
import os
import struct
from functools import wraps
from panda import Panda, PandaDFU
from panda.python.constants import McuType
BASEDIR = os.path.dirname(os.path.realpath(__file__))
FW_PATH = os.path.join(BASEDIR, "../obj/")
def ensure_jungle_health_packet_version(fn):
@wraps(fn)
def wrapper(self, *args, **kwargs):
if self.health_version != self.HEALTH_PACKET_VERSION:
raise RuntimeError(f"Jungle firmware ({self.health_version}) doesn't match the \
library's health packet version ({self.HEALTH_PACKET_VERSION}). \
Reflash jungle.")
return fn(self, *args, **kwargs)
return wrapper
class PandaJungleDFU(PandaDFU):
def recover(self):
fn = os.path.join(FW_PATH, self._mcu_type.config.bootstub_fn.replace("panda", "panda_jungle"))
with open(fn, "rb") as f:
code = f.read()
self.program_bootstub(code)
self.reset()
class PandaJungle(Panda):
USB_PIDS = (0xddef, 0xddcf)
HW_TYPE_UNKNOWN = b'\x00'
HW_TYPE_V2 = b'\x02'
H7_DEVICES = [HW_TYPE_V2, ]
SUPPORTED_DEVICES = H7_DEVICES
HEALTH_PACKET_VERSION = 1
HEALTH_STRUCT = struct.Struct("<IffffffHHHHHHHHHHHH")
HARNESS_ORIENTATION_NONE = 0
HARNESS_ORIENTATION_1 = 1
HARNESS_ORIENTATION_2 = 2
@classmethod
def spi_connect(cls, serial, ignore_version=False):
return None, None, None, None
def flash(self, fn=None, code=None, reconnect=True):
if not fn:
fn = os.path.join(FW_PATH, self._mcu_type.config.app_fn.replace("panda", "panda_jungle"))
super().flash(fn=fn, code=code, reconnect=reconnect)
def recover(self, timeout: int | None = 60, reset: bool = True) -> bool:
dfu_serial = self.get_dfu_serial()
if reset:
self.reset(enter_bootstub=True)
self.reset(enter_bootloader=True)
if not self.wait_for_dfu(dfu_serial, timeout=timeout):
return False
dfu = PandaJungleDFU(dfu_serial)
dfu.recover()
# reflash after recover
self.connect(True, True)
self.flash()
return True
def get_mcu_type(self) -> McuType:
hw_type = self.get_type()
if hw_type in PandaJungle.H7_DEVICES:
return McuType.H7
raise ValueError(f"unknown HW type: {hw_type}")
def up_to_date(self, fn=None) -> bool:
if fn is None:
fn = os.path.join(FW_PATH, self.get_mcu_type().config.app_fn.replace("panda", "panda_jungle"))
return super().up_to_date(fn=fn)
# ******************* health *******************
@ensure_jungle_health_packet_version
def health(self):
dat = self._handle.controlRead(PandaJungle.REQUEST_IN, 0xd2, 0, 0, self.HEALTH_STRUCT.size)
a = self.HEALTH_STRUCT.unpack(dat)
return {
"uptime": a[0],
"ch1_power": a[1],
"ch2_power": a[2],
"ch3_power": a[3],
"ch4_power": a[4],
"ch5_power": a[5],
"ch6_power": a[6],
"ch1_sbu1_voltage": a[7] / 1000.0,
"ch1_sbu2_voltage": a[8] / 1000.0,
"ch2_sbu1_voltage": a[9] / 1000.0,
"ch2_sbu2_voltage": a[10] / 1000.0,
"ch3_sbu1_voltage": a[11] / 1000.0,
"ch3_sbu2_voltage": a[12] / 1000.0,
"ch4_sbu1_voltage": a[13] / 1000.0,
"ch4_sbu2_voltage": a[14] / 1000.0,
"ch5_sbu1_voltage": a[15] / 1000.0,
"ch5_sbu2_voltage": a[16] / 1000.0,
"ch6_sbu1_voltage": a[17] / 1000.0,
"ch6_sbu2_voltage": a[18] / 1000.0,
}
# ******************* control *******************
# Returns tuple with health packet version and CAN packet/USB packet version
def get_packets_versions(self):
dat = self._handle.controlRead(PandaJungle.REQUEST_IN, 0xdd, 0, 0, 3)
if dat and len(dat) == 3:
a = struct.unpack("BBB", dat)
return (a[0], a[1], a[2])
return (-1, -1, -1)
# ******************* jungle stuff *******************
def set_panda_power(self, enabled):
self._handle.controlWrite(PandaJungle.REQUEST_OUT, 0xa0, int(enabled), 0, b'')
def set_panda_individual_power(self, port, enabled):
self._handle.controlWrite(PandaJungle.REQUEST_OUT, 0xa3, int(port), int(enabled), b'')
def set_harness_orientation(self, mode):
self._handle.controlWrite(PandaJungle.REQUEST_OUT, 0xa1, int(mode), 0, b'')
def set_ignition(self, enabled):
self._handle.controlWrite(PandaJungle.REQUEST_OUT, 0xa2, int(enabled), 0, b'')
def set_can_silent(self, silent):
self._handle.controlWrite(PandaJungle.REQUEST_OUT, 0xf5, int(silent), 0, b'')
def set_generated_can(self, enabled):
self._handle.controlWrite(PandaJungle.REQUEST_OUT, 0xa4, int(enabled), 0, b'')
# ******************* serial *******************
def debug_read(self):
ret = []
while 1:
lret = bytes(self._handle.controlRead(PandaJungle.REQUEST_IN, 0xe0, 0, 0, 0x40))
if len(lret) == 0:
break
ret.append(lret)
return b''.join(ret)
# ******************* header pins *******************
def set_header_pin(self, pin_num, enabled):
self._handle.controlWrite(Panda.REQUEST_OUT, 0xf7, int(pin_num), int(enabled), b'')

27
panda/board/jungle/flash.py Executable file
View File

@@ -0,0 +1,27 @@
#!/usr/bin/env python3
import os
import subprocess
import argparse
from panda import PandaJungle
board_path = os.path.dirname(os.path.realpath(__file__))
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--all", action="store_true", help="Recover all panda jungle devices")
args = parser.parse_args()
subprocess.check_call(f"scons -C {board_path}/.. -u -j$(nproc) .", shell=True)
if args.all:
serials = PandaJungle.list()
print(f"found {len(serials)} panda jungles(s) - {serials}")
else:
serials = [None]
for s in serials:
with PandaJungle(serial=s) as p:
print("flashing", p.get_usb_serial())
p.flash()
exit(1 if len(serials) == 0 else 0)

33
panda/board/jungle/recover.py Executable file
View File

@@ -0,0 +1,33 @@
#!/usr/bin/env python3
import os
import time
import subprocess
import argparse
from panda import PandaJungle, PandaJungleDFU
board_path = os.path.dirname(os.path.realpath(__file__))
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--all", action="store_true", help="Recover all panda jungle devices")
args = parser.parse_args()
subprocess.check_call(f"scons -C {board_path}/.. -u -j$(nproc) .", shell=True)
serials = PandaJungle.list() if args.all else [None]
for s in serials:
with PandaJungle(serial=s) as p:
print(f"putting {p.get_usb_serial()} in DFU mode")
p.reset(enter_bootstub=True)
p.reset(enter_bootloader=True)
# wait for reset panda jungles to come back up
time.sleep(1)
dfu_serials = PandaJungleDFU.list()
print(f"found {len(dfu_serials)} panda jungle(s) in DFU - {dfu_serials}")
for s in dfu_serials:
print("flashing", s)
PandaJungleDFU(s).recover()
exit(1 if len(dfu_serials) == 0 else 0)

View File

@@ -0,0 +1,29 @@
#!/usr/bin/env python3
import time
import re
from panda import PandaJungle
RED = '\033[91m'
GREEN = '\033[92m'
def colorize_errors(value):
if isinstance(value, str):
if re.search(r'(?i)No error', value):
return f'{GREEN}{value}\033[0m'
elif re.search(r'(?i)(?<!No error\s)(err|error)', value):
return f'{RED}{value}\033[0m'
return str(value)
if __name__ == "__main__":
jungle = PandaJungle()
while True:
print(chr(27) + "[2J") # clear screen
print("Connected to " + ("internal panda" if jungle.is_internal() else "External panda") + f" id: {jungle.get_serial()[0]}: {jungle.get_version()}")
for bus in range(3):
print(f"\nBus {bus}:")
health = jungle.can_health(bus)
for key, value in health.items():
print(f"{key}: {colorize_errors(value)} ", end=" ")
print()
time.sleep(1)

View File

@@ -0,0 +1,44 @@
#!/usr/bin/env python3
import os
import time
from collections import defaultdict
import binascii
from panda import PandaJungle
# fake
def sec_since_boot():
return time.time()
def can_printer():
p = PandaJungle()
print(f"Connected to: {p._serial}: {p.get_version()}")
time.sleep(1)
p.can_clear(0xFFFF)
start = sec_since_boot()
lp = sec_since_boot()
all_msgs = defaultdict(list)
canbus = os.getenv("CAN")
if canbus == "3":
p.set_obd(True)
canbus = "1"
while True:
can_recv = p.can_recv()
for addr, dat, bus in can_recv:
if canbus is None or str(bus) == canbus:
all_msgs[(addr, bus)].append((dat))
if sec_since_boot() - lp > 0.1:
dd = chr(27) + "[2J"
dd += "%5.2f\n" % (sec_since_boot() - start)
for (addr, bus), dat_log in sorted(all_msgs.items()):
dd += "%d: %s(%6d): %s\n" % (bus, "%04X(%4d)" % (addr, addr), len(dat_log), binascii.hexlify(dat_log[-1]).decode())
print(dd)
lp = sec_since_boot()
if __name__ == "__main__":
can_printer()

View File

@@ -0,0 +1,38 @@
#!/usr/bin/env python3
import os
import sys
import time
from panda import PandaJungle
setcolor = ["\033[1;32;40m", "\033[1;31;40m"]
unsetcolor = "\033[00m"
if __name__ == "__main__":
while True:
try:
claim = os.getenv("CLAIM") is not None
serials = PandaJungle.list()
if os.getenv("SERIAL"):
serials = [x for x in serials if x==os.getenv("SERIAL")]
panda_jungles = [PandaJungle(x, claim=claim) for x in serials]
if not len(panda_jungles):
sys.exit("no panda jungles found")
while True:
for i, panda_jungle in enumerate(panda_jungles):
while True:
ret = panda_jungle.debug_read()
if len(ret) > 0:
sys.stdout.write(setcolor[i] + ret.decode('ascii') + unsetcolor)
sys.stdout.flush()
else:
break
time.sleep(0.01)
except Exception as e:
print(e)
print("panda jungle disconnected!")
time.sleep(0.5)

View File

@@ -0,0 +1,68 @@
#!/usr/bin/env python3
import os
import time
import contextlib
import random
from termcolor import cprint
from panda import PandaJungle
# This script is intended to be used in conjunction with the echo.py test script from panda.
# It sends messages on bus 0, 1, 2 and checks for a reversed response to be sent back.
#################################################################
############################# UTILS #############################
#################################################################
def print_colored(text, color):
cprint(text + " "*40, color, end="\r")
def get_test_string():
return b"test"+os.urandom(4)
#################################################################
############################# TEST ##############################
#################################################################
def test_loopback():
for bus in range(3):
# Clear can
jungle.can_clear(bus)
# Send a random message
address = random.randint(1, 2000)
data = get_test_string()
jungle.can_send(address, data, bus)
time.sleep(0.1)
# Make sure it comes back reversed
incoming = jungle.can_recv()
found = False
for message in incoming:
incomingAddress, incomingData, incomingBus = message
if incomingAddress == address and incomingData == data[::-1] and incomingBus == bus:
found = True
break
if not found:
cprint("\nFAILED", "red")
raise AssertionError
#################################################################
############################# MAIN ##############################
#################################################################
jungle = None
counter = 0
if __name__ == "__main__":
# Connect to jungle silently
print_colored("Connecting to jungle", "blue")
with open(os.devnull, "w") as devnull:
with contextlib.redirect_stdout(devnull):
jungle = PandaJungle()
jungle.set_panda_power(True)
jungle.set_ignition(False)
# Run test
while True:
jungle.can_clear(0xFFFF)
test_loopback()
counter += 1
print_colored(str(counter) + " loopback cycles complete", "blue")

View File

@@ -0,0 +1,9 @@
#!/usr/bin/env python3
from panda import PandaJungle
if __name__ == "__main__":
for p in PandaJungle.list():
pp = PandaJungle(p)
print(f"{pp.get_serial()[0]}: {pp.get_version()}")

View File

@@ -0,0 +1,21 @@
#!/usr/bin/env python3
import time
from pprint import pprint
from panda import PandaJungle
if __name__ == "__main__":
i = 0
pi = 0
pj = PandaJungle()
while True:
st = time.monotonic()
while time.monotonic() - st < 1:
pj.health()
pj.can_health(0)
i += 1
pprint(pj.health())
print(f"Speed: {i - pi}Hz")
pi = i

View File

@@ -0,0 +1,141 @@
#!/usr/bin/env python3
import os
import time
import contextlib
import random
from termcolor import cprint
from iqdbc.car.structs import CarParams
from panda import Panda, PandaJungle
NUM_PANDAS_PER_TEST = 1
FOR_RELEASE_BUILDS = os.getenv("RELEASE") is not None # Release builds do not have ALLOUTPUT mode
BUS_SPEEDS = [125, 500, 1000]
#################################################################
############################# UTILS #############################
#################################################################
# To suppress the connection text
def silent_panda_connect(serial):
with open(os.devnull, "w") as devnull:
with contextlib.redirect_stdout(devnull):
panda = Panda(serial)
return panda
def print_colored(text, color):
cprint(text + " "*40, color, end="\r")
def connect_to_pandas():
print_colored("Connecting to pandas", "blue")
# Connect to pandas
pandas = []
for serial in panda_serials:
pandas.append(silent_panda_connect(serial))
print_colored("Connected", "blue")
def start_with_orientation(orientation):
print_colored("Restarting pandas with orientation " + str(orientation), "blue")
jungle.set_panda_power(False)
jungle.set_harness_orientation(orientation)
time.sleep(4)
jungle.set_panda_power(True)
time.sleep(2)
connect_to_pandas()
def can_loopback(sender):
receivers = list(filter((lambda p: (p != sender)), [jungle] + pandas))
for bus in range(4):
obd = False
if bus == 3:
obd = True
bus = 1
# Clear buses
for receiver in receivers:
receiver.set_obd(obd)
receiver.can_clear(bus) # TX
receiver.can_clear(0xFFFF) # RX
# Send a random string
addr = 0x18DB33F1 if FOR_RELEASE_BUILDS else random.randint(1, 2000)
string = b"test"+os.urandom(4)
sender.set_obd(obd)
time.sleep(0.2)
sender.can_send(addr, string, bus)
time.sleep(0.2)
# Check if all receivers have indeed received them in their receiving buffers
for receiver in receivers:
content = receiver.can_recv()
# Check amount of messages
if len(content) != 1:
raise Exception("Amount of received CAN messages (" + str(len(content)) + ") does not equal 1. Bus: " + str(bus) +" OBD: " + str(obd))
# Check content
if content[0][0] != addr or content[0][1] != string:
raise Exception("Received CAN message content or address does not match")
# Check bus
if content[0][2] != bus:
raise Exception("Received CAN message bus does not match")
#################################################################
############################# TEST ##############################
#################################################################
def test_loopback():
# disable safety modes
for panda in pandas:
panda.set_safety_mode(CarParams.SafetyModel.elm327 if FOR_RELEASE_BUILDS else CarParams.SafetyModel.allOutput)
# perform loopback with jungle as a sender
can_loopback(jungle)
# perform loopback with each possible panda as a sender
for panda in pandas:
can_loopback(panda)
# enable safety modes
for panda in pandas:
panda.set_safety_mode(CarParams.SafetyModel.silent)
#################################################################
############################# MAIN ##############################
#################################################################
jungle = None
pandas = [] # type: ignore
panda_serials = []
counter = 0
if __name__ == "__main__":
# Connect to jungle silently
print_colored("Connecting to jungle", "blue")
with open(os.devnull, "w") as devnull:
with contextlib.redirect_stdout(devnull):
jungle = PandaJungle()
jungle.set_panda_power(True)
jungle.set_ignition(False)
# Connect to <NUM_PANDAS_PER_TEST> new pandas before starting tests
print_colored("Waiting for " + str(NUM_PANDAS_PER_TEST) + " pandas to be connected", "yellow")
while True:
connected_serials = Panda.list()
if len(connected_serials) == NUM_PANDAS_PER_TEST:
panda_serials = connected_serials
break
start_with_orientation(PandaJungle.HARNESS_ORIENTATION_1)
# Set bus speeds
for device in pandas + [jungle]:
for bus in range(len(BUS_SPEEDS)):
device.set_can_speed_kbps(bus, BUS_SPEEDS[bus])
# Run test
while True:
test_loopback()
counter += 1
print_colored(str(counter) + " loopback cycles complete", "blue")

View File

@@ -0,0 +1,21 @@
#!/usr/bin/env python3
import os
import random
from iqdbc.car.structs import CarParams
from panda import PandaJungle
def get_test_string():
return b"test" + os.urandom(10)
if __name__ == "__main__":
p = PandaJungle()
p.set_safety_mode(CarParams.SafetyModel.allOutput)
print("Spamming all buses...")
while True:
at = random.randint(1, 2000)
st = get_test_string()[0:8]
bus = random.randint(0, 2)
p.can_send(at, st, bus)
# print("Sent message on bus: ", bus)

View File

@@ -0,0 +1,18 @@
#!/usr/bin/env python
import sys
from panda import PandaJungle
if __name__ == "__main__":
jungle = PandaJungle()
# If first argument specified, it sets the ignition (0 or 1)
if len(sys.argv) > 1:
if sys.argv[1] == '1':
jungle.set_ignition(True)
else:
jungle.set_ignition(False)
jungle.set_harness_orientation(1)
jungle.set_panda_power(True)

View File

Binary file not shown.

Binary file not shown.

BIN
panda/board/obj/body_h7/main.bin Executable file

Binary file not shown.

BIN
panda/board/obj/body_h7/main.elf Executable file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
panda/board/obj/panda/main.bin Executable file

Binary file not shown.

BIN
panda/board/obj/panda/main.elf Executable file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
panda/board/obj/panda_h7/main.bin Executable file

Binary file not shown.

BIN
panda/board/obj/panda_h7/main.elf Executable file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

1
panda/board/obj/version Normal file
View File

@@ -0,0 +1 @@
DEV-34a11299-DEBUG

27
panda/board/recover.py Executable file
View File

@@ -0,0 +1,27 @@
#!/usr/bin/env python3
import os
import time
import subprocess
from panda import Panda, PandaDFU
board_path = os.path.dirname(os.path.realpath(__file__))
if __name__ == "__main__":
subprocess.check_call(f"scons -C {board_path}/.. -j$(nproc) {board_path}", shell=True)
for s in Panda.list():
with Panda(serial=s) as p:
print(f"putting {p.get_usb_serial()} in DFU mode")
p.reset(enter_bootstub=True)
p.reset(enter_bootloader=True)
# wait for reset pandas to come back up
time.sleep(1)
dfu_serials = PandaDFU.list()
print(f"found {len(dfu_serials)} panda(s) in DFU - {dfu_serials}")
for s in dfu_serials:
print("flashing", s)
PandaDFU(s).recover()
exit(1 if len(dfu_serials) == 0 else 0)

View File

@@ -0,0 +1,583 @@
/**
******************************************************************************
* @file startup_stm32f413xx.s
* @author MCD Application Team
* @version V2.6.0
* @date 04-November-2016
* @brief STM32F413xx Devices vector table for GCC based toolchains.
* This module performs:
* - Set the initial SP
* - Set the initial PC == Reset_Handler,
* - Set the vector table entries with the exceptions ISR address
* - Branches to main in the C library (which eventually
* calls main()).
* After Reset the Cortex-M4 processor is in Thread mode,
* priority is Privileged, and the Stack is set to Main.
******************************************************************************
* @attention
*
* <h2><center>&copy; COPYRIGHT 2016 STMicroelectronics</center></h2>
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of STMicroelectronics nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER 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.
*
******************************************************************************
*/
.syntax unified
.cpu cortex-m4
.fpu softvfp
.thumb
.global g_pfnVectors
.global Default_Handler
/* start address for the initialization values of the .data section.
defined in linker script */
.word _sidata
/* start address for the .data section. defined in linker script */
.word _sdata
/* end address for the .data section. defined in linker script */
.word _edata
/* start address for the .bss section. defined in linker script */
.word _sbss
/* end address for the .bss section. defined in linker script */
.word _ebss
/* stack used for SystemInit_ExtMemCtl; always internal RAM used */
/**
* @brief This is the code that gets called when the processor first
* starts execution following a reset event. Only the absolutely
* necessary set is performed, after which the application
* supplied main() routine is called.
* @param None
* @retval : None
*/
.section .text.Reset_Handler
.weak Reset_Handler
.type Reset_Handler, %function
Reset_Handler:
ldr sp, =_estack /* set stack pointer */
bl __initialize_hardware_early
/* Copy the data segment initializers from flash to SRAM */
movs r1, #0
b LoopCopyDataInit
CopyDataInit:
ldr r3, =_sidata
ldr r3, [r3, r1]
str r3, [r0, r1]
adds r1, r1, #4
LoopCopyDataInit:
ldr r0, =_sdata
ldr r3, =_edata
adds r2, r0, r1
cmp r2, r3
bcc CopyDataInit
ldr r2, =_sbss
b LoopFillZerobss
/* Zero fill the bss segment. */
FillZerobss:
movs r3, #0
str r3, [r2], #4
LoopFillZerobss:
ldr r3, = _ebss
cmp r2, r3
bcc FillZerobss
/* Call the clock system intitialization function.*/
/* bl SystemInit */
/* Call static constructors */
/* bl __libc_init_array */
/* Call the application's entry point.*/
bl main
bx lr
.size Reset_Handler, .-Reset_Handler
/**
* @brief This is the code that gets called when the processor receives an
* unexpected interrupt. This simply enters an infinite loop, preserving
* the system state for examination by a debugger.
* @param None
* @retval None
*/
.section .text.Default_Handler,"ax",%progbits
Default_Handler:
Infinite_Loop:
b Infinite_Loop
.size Default_Handler, .-Default_Handler
/******************************************************************************
*
* The minimal vector table for a Cortex M3. Note that the proper constructs
* must be placed on this to ensure that it ends up at physical address
* 0x0000.0000.
*
*******************************************************************************/
.section .isr_vector,"a",%progbits
.type g_pfnVectors, %object
.size g_pfnVectors, .-g_pfnVectors
g_pfnVectors:
.word _estack
.word Reset_Handler
.word NMI_Handler
.word HardFault_Handler
.word MemManage_Handler
.word BusFault_Handler
.word UsageFault_Handler
.word 0
.word 0
.word 0
.word 0
.word SVC_Handler
.word DebugMon_Handler
.word 0
.word PendSV_Handler
.word SysTick_Handler
/* External Interrupts */
.word WWDG_IRQHandler /* Window WatchDog */
.word PVD_IRQHandler /* PVD through EXTI Line detection */
.word TAMP_STAMP_IRQHandler /* Tamper and TimeStamps through the EXTI line */
.word RTC_WKUP_IRQHandler /* RTC Wakeup through the EXTI line */
.word FLASH_IRQHandler /* FLASH */
.word RCC_IRQHandler /* RCC */
.word EXTI0_IRQHandler /* EXTI Line0 */
.word EXTI1_IRQHandler /* EXTI Line1 */
.word EXTI2_IRQHandler /* EXTI Line2 */
.word EXTI3_IRQHandler /* EXTI Line3 */
.word EXTI4_IRQHandler /* EXTI Line4 */
.word DMA1_Stream0_IRQHandler /* DMA1 Stream 0 */
.word DMA1_Stream1_IRQHandler /* DMA1 Stream 1 */
.word DMA1_Stream2_IRQHandler /* DMA1 Stream 2 */
.word DMA1_Stream3_IRQHandler /* DMA1 Stream 3 */
.word DMA1_Stream4_IRQHandler /* DMA1 Stream 4 */
.word DMA1_Stream5_IRQHandler /* DMA1 Stream 5 */
.word DMA1_Stream6_IRQHandler /* DMA1 Stream 6 */
.word ADC_IRQHandler /* ADC1, ADC2 and ADC3s */
.word CAN1_TX_IRQHandler /* CAN1 TX */
.word CAN1_RX0_IRQHandler /* CAN1 RX0 */
.word CAN1_RX1_IRQHandler /* CAN1 RX1 */
.word CAN1_SCE_IRQHandler /* CAN1 SCE */
.word EXTI9_5_IRQHandler /* External Line[9:5]s */
.word TIM1_BRK_TIM9_IRQHandler /* TIM1 Break and TIM9 */
.word TIM1_UP_TIM10_IRQHandler /* TIM1 Update and TIM10 */
.word TIM1_TRG_COM_TIM11_IRQHandler /* TIM1 Trigger and Commutation and TIM11 */
.word TIM1_CC_IRQHandler /* TIM1 Capture Compare */
.word TIM2_IRQHandler /* TIM2 */
.word TIM3_IRQHandler /* TIM3 */
.word TIM4_IRQHandler /* TIM4 */
.word I2C1_EV_IRQHandler /* I2C1 Event */
.word I2C1_ER_IRQHandler /* I2C1 Error */
.word I2C2_EV_IRQHandler /* I2C2 Event */
.word I2C2_ER_IRQHandler /* I2C2 Error */
.word SPI1_IRQHandler /* SPI1 */
.word SPI2_IRQHandler /* SPI2 */
.word USART1_IRQHandler /* USART1 */
.word USART2_IRQHandler /* USART2 */
.word USART3_IRQHandler /* USART3 */
.word EXTI15_10_IRQHandler /* External Line[15:10]s */
.word RTC_Alarm_IRQHandler /* RTC Alarm (A and B) through EXTI Line */
.word OTG_FS_WKUP_IRQHandler /* USB OTG FS Wakeup through EXTI line */
.word TIM8_BRK_TIM12_IRQHandler /* TIM8 Break and TIM12 */
.word TIM8_UP_TIM13_IRQHandler /* TIM8 Update and TIM13 */
.word TIM8_TRG_COM_TIM14_IRQHandler /* TIM8 Trigger and Commutation and TIM14 */
.word TIM8_CC_IRQHandler /* TIM8 Capture Compare */
.word DMA1_Stream7_IRQHandler /* DMA1 Stream7 */
.word FSMC_IRQHandler /* FSMC */
.word SDIO_IRQHandler /* SDIO */
.word TIM5_IRQHandler /* TIM5 */
.word SPI3_IRQHandler /* SPI3 */
.word UART4_IRQHandler /* UART4 */
.word UART5_IRQHandler /* UART5 */
.word TIM6_DAC_IRQHandler /* TIM6, DAC1 and DAC2 */
.word TIM7_IRQHandler /* TIM7 */
.word DMA2_Stream0_IRQHandler /* DMA2 Stream 0 */
.word DMA2_Stream1_IRQHandler /* DMA2 Stream 1 */
.word DMA2_Stream2_IRQHandler /* DMA2 Stream 2 */
.word DMA2_Stream3_IRQHandler /* DMA2 Stream 3 */
.word DMA2_Stream4_IRQHandler /* DMA2 Stream 4 */
.word DFSDM1_FLT0_IRQHandler /* DFSDM1 Filter0 */
.word DFSDM1_FLT1_IRQHandler /* DFSDM1 Filter1 */
.word CAN2_TX_IRQHandler /* CAN2 TX */
.word CAN2_RX0_IRQHandler /* CAN2 RX0 */
.word CAN2_RX1_IRQHandler /* CAN2 RX1 */
.word CAN2_SCE_IRQHandler /* CAN2 SCE */
.word OTG_FS_IRQHandler /* USB OTG FS */
.word DMA2_Stream5_IRQHandler /* DMA2 Stream 5 */
.word DMA2_Stream6_IRQHandler /* DMA2 Stream 6 */
.word DMA2_Stream7_IRQHandler /* DMA2 Stream 7 */
.word USART6_IRQHandler /* USART6 */
.word I2C3_EV_IRQHandler /* I2C3 event */
.word I2C3_ER_IRQHandler /* I2C3 error */
.word CAN3_TX_IRQHandler /* CAN3 TX */
.word CAN3_RX0_IRQHandler /* CAN3 RX0 */
.word CAN3_RX1_IRQHandler /* CAN3 RX1 */
.word CAN3_SCE_IRQHandler /* CAN3 SCE */
.word 0 /* Reserved */
.word 0 /* Reserved */
.word RNG_IRQHandler /* RNG */
.word FPU_IRQHandler /* FPU */
.word UART7_IRQHandler /* UART7 */
.word UART8_IRQHandler /* UART8 */
.word SPI4_IRQHandler /* SPI4 */
.word SPI5_IRQHandler /* SPI5 */
.word 0 /* Reserved */
.word SAI1_IRQHandler /* SAI1 */
.word UART9_IRQHandler /* UART9 */
.word UART10_IRQHandler /* UART10 */
.word 0 /* Reserved */
.word 0 /* Reserved */
.word QUADSPI_IRQHandler /* QuadSPI */
.word 0 /* Reserved */
.word 0 /* Reserved */
.word FMPI2C1_EV_IRQHandler /* FMPI2C1 Event */
.word FMPI2C1_ER_IRQHandler /* FMPI2C1 Error */
.word LPTIM1_IRQHandler /* LPTIM1 */
.word DFSDM2_FLT0_IRQHandler /* DFSDM2 Filter0 */
.word DFSDM2_FLT1_IRQHandler /* DFSDM2 Filter1 */
.word DFSDM2_FLT2_IRQHandler /* DFSDM2 Filter2 */
.word DFSDM2_FLT3_IRQHandler /* DFSDM2 Filter3 */
/*******************************************************************************
*
* Provide weak aliases for each Exception handler to the Default_Handler.
* As they are weak aliases, any function with the same name will override
* this definition.
*
*******************************************************************************/
.weak NMI_Handler
.thumb_set NMI_Handler,Default_Handler
.weak HardFault_Handler
.thumb_set HardFault_Handler,Default_Handler
.weak MemManage_Handler
.thumb_set MemManage_Handler,Default_Handler
.weak BusFault_Handler
.thumb_set BusFault_Handler,Default_Handler
.weak UsageFault_Handler
.thumb_set UsageFault_Handler,Default_Handler
.weak SVC_Handler
.thumb_set SVC_Handler,Default_Handler
.weak DebugMon_Handler
.thumb_set DebugMon_Handler,Default_Handler
.weak PendSV_Handler
.thumb_set PendSV_Handler,Default_Handler
.weak SysTick_Handler
.thumb_set SysTick_Handler,Default_Handler
.weak WWDG_IRQHandler
.thumb_set WWDG_IRQHandler,Default_Handler
.weak PVD_IRQHandler
.thumb_set PVD_IRQHandler,Default_Handler
.weak TAMP_STAMP_IRQHandler
.thumb_set TAMP_STAMP_IRQHandler,Default_Handler
.weak RTC_WKUP_IRQHandler
.thumb_set RTC_WKUP_IRQHandler,Default_Handler
.weak FLASH_IRQHandler
.thumb_set FLASH_IRQHandler,Default_Handler
.weak RCC_IRQHandler
.thumb_set RCC_IRQHandler,Default_Handler
.weak EXTI0_IRQHandler
.thumb_set EXTI0_IRQHandler,Default_Handler
.weak EXTI1_IRQHandler
.thumb_set EXTI1_IRQHandler,Default_Handler
.weak EXTI2_IRQHandler
.thumb_set EXTI2_IRQHandler,Default_Handler
.weak EXTI3_IRQHandler
.thumb_set EXTI3_IRQHandler,Default_Handler
.weak EXTI4_IRQHandler
.thumb_set EXTI4_IRQHandler,Default_Handler
.weak DMA1_Stream0_IRQHandler
.thumb_set DMA1_Stream0_IRQHandler,Default_Handler
.weak DMA1_Stream1_IRQHandler
.thumb_set DMA1_Stream1_IRQHandler,Default_Handler
.weak DMA1_Stream2_IRQHandler
.thumb_set DMA1_Stream2_IRQHandler,Default_Handler
.weak DMA1_Stream3_IRQHandler
.thumb_set DMA1_Stream3_IRQHandler,Default_Handler
.weak DMA1_Stream4_IRQHandler
.thumb_set DMA1_Stream4_IRQHandler,Default_Handler
.weak DMA1_Stream5_IRQHandler
.thumb_set DMA1_Stream5_IRQHandler,Default_Handler
.weak DMA1_Stream6_IRQHandler
.thumb_set DMA1_Stream6_IRQHandler,Default_Handler
.weak ADC_IRQHandler
.thumb_set ADC_IRQHandler,Default_Handler
.weak CAN1_TX_IRQHandler
.thumb_set CAN1_TX_IRQHandler,Default_Handler
.weak CAN1_RX0_IRQHandler
.thumb_set CAN1_RX0_IRQHandler,Default_Handler
.weak CAN1_RX1_IRQHandler
.thumb_set CAN1_RX1_IRQHandler,Default_Handler
.weak CAN1_SCE_IRQHandler
.thumb_set CAN1_SCE_IRQHandler,Default_Handler
.weak EXTI9_5_IRQHandler
.thumb_set EXTI9_5_IRQHandler,Default_Handler
.weak TIM1_BRK_TIM9_IRQHandler
.thumb_set TIM1_BRK_TIM9_IRQHandler,Default_Handler
.weak TIM1_UP_TIM10_IRQHandler
.thumb_set TIM1_UP_TIM10_IRQHandler,Default_Handler
.weak TIM1_TRG_COM_TIM11_IRQHandler
.thumb_set TIM1_TRG_COM_TIM11_IRQHandler,Default_Handler
.weak TIM1_CC_IRQHandler
.thumb_set TIM1_CC_IRQHandler,Default_Handler
.weak TIM2_IRQHandler
.thumb_set TIM2_IRQHandler,Default_Handler
.weak TIM3_IRQHandler
.thumb_set TIM3_IRQHandler,Default_Handler
.weak TIM4_IRQHandler
.thumb_set TIM4_IRQHandler,Default_Handler
.weak I2C1_EV_IRQHandler
.thumb_set I2C1_EV_IRQHandler,Default_Handler
.weak I2C1_ER_IRQHandler
.thumb_set I2C1_ER_IRQHandler,Default_Handler
.weak I2C2_EV_IRQHandler
.thumb_set I2C2_EV_IRQHandler,Default_Handler
.weak I2C2_ER_IRQHandler
.thumb_set I2C2_ER_IRQHandler,Default_Handler
.weak SPI1_IRQHandler
.thumb_set SPI1_IRQHandler,Default_Handler
.weak SPI2_IRQHandler
.thumb_set SPI2_IRQHandler,Default_Handler
.weak USART1_IRQHandler
.thumb_set USART1_IRQHandler,Default_Handler
.weak USART2_IRQHandler
.thumb_set USART2_IRQHandler,Default_Handler
.weak USART3_IRQHandler
.thumb_set USART3_IRQHandler,Default_Handler
.weak EXTI15_10_IRQHandler
.thumb_set EXTI15_10_IRQHandler,Default_Handler
.weak RTC_Alarm_IRQHandler
.thumb_set RTC_Alarm_IRQHandler,Default_Handler
.weak OTG_FS_WKUP_IRQHandler
.thumb_set OTG_FS_WKUP_IRQHandler,Default_Handler
.weak TIM8_BRK_TIM12_IRQHandler
.thumb_set TIM8_BRK_TIM12_IRQHandler,Default_Handler
.weak TIM8_UP_TIM13_IRQHandler
.thumb_set TIM8_UP_TIM13_IRQHandler,Default_Handler
.weak TIM8_TRG_COM_TIM14_IRQHandler
.thumb_set TIM8_TRG_COM_TIM14_IRQHandler,Default_Handler
.weak TIM8_CC_IRQHandler
.thumb_set TIM8_CC_IRQHandler,Default_Handler
.weak DMA1_Stream7_IRQHandler
.thumb_set DMA1_Stream7_IRQHandler,Default_Handler
.weak FSMC_IRQHandler
.thumb_set FSMC_IRQHandler,Default_Handler
.weak SDIO_IRQHandler
.thumb_set SDIO_IRQHandler,Default_Handler
.weak TIM5_IRQHandler
.thumb_set TIM5_IRQHandler,Default_Handler
.weak SPI3_IRQHandler
.thumb_set SPI3_IRQHandler,Default_Handler
.weak UART4_IRQHandler
.thumb_set UART4_IRQHandler,Default_Handler
.weak UART5_IRQHandler
.thumb_set UART5_IRQHandler,Default_Handler
.weak TIM6_DAC_IRQHandler
.thumb_set TIM6_DAC_IRQHandler,Default_Handler
.weak TIM7_IRQHandler
.thumb_set TIM7_IRQHandler,Default_Handler
.weak DMA2_Stream0_IRQHandler
.thumb_set DMA2_Stream0_IRQHandler,Default_Handler
.weak DMA2_Stream1_IRQHandler
.thumb_set DMA2_Stream1_IRQHandler,Default_Handler
.weak DMA2_Stream2_IRQHandler
.thumb_set DMA2_Stream2_IRQHandler,Default_Handler
.weak DMA2_Stream3_IRQHandler
.thumb_set DMA2_Stream3_IRQHandler,Default_Handler
.weak DMA2_Stream4_IRQHandler
.thumb_set DMA2_Stream4_IRQHandler,Default_Handler
.weak DFSDM1_FLT0_IRQHandler
.thumb_set DFSDM1_FLT0_IRQHandler,Default_Handler
.weak DFSDM1_FLT1_IRQHandler
.thumb_set DFSDM1_FLT1_IRQHandler,Default_Handler
.weak CAN2_TX_IRQHandler
.thumb_set CAN2_TX_IRQHandler,Default_Handler
.weak CAN2_RX0_IRQHandler
.thumb_set CAN2_RX0_IRQHandler,Default_Handler
.weak CAN2_RX1_IRQHandler
.thumb_set CAN2_RX1_IRQHandler,Default_Handler
.weak CAN2_SCE_IRQHandler
.thumb_set CAN2_SCE_IRQHandler,Default_Handler
.weak OTG_FS_IRQHandler
.thumb_set OTG_FS_IRQHandler,Default_Handler
.weak DMA2_Stream5_IRQHandler
.thumb_set DMA2_Stream5_IRQHandler,Default_Handler
.weak DMA2_Stream6_IRQHandler
.thumb_set DMA2_Stream6_IRQHandler,Default_Handler
.weak DMA2_Stream7_IRQHandler
.thumb_set DMA2_Stream7_IRQHandler,Default_Handler
.weak USART6_IRQHandler
.thumb_set USART6_IRQHandler,Default_Handler
.weak I2C3_EV_IRQHandler
.thumb_set I2C3_EV_IRQHandler,Default_Handler
.weak I2C3_ER_IRQHandler
.thumb_set I2C3_ER_IRQHandler,Default_Handler
.weak CAN3_TX_IRQHandler
.thumb_set CAN3_TX_IRQHandler,Default_Handler
.weak CAN3_RX0_IRQHandler
.thumb_set CAN3_RX0_IRQHandler,Default_Handler
.weak CAN3_RX1_IRQHandler
.thumb_set CAN3_RX1_IRQHandler,Default_Handler
.weak CAN3_SCE_IRQHandler
.thumb_set CAN3_SCE_IRQHandler,Default_Handler
.weak RNG_IRQHandler
.thumb_set RNG_IRQHandler,Default_Handler
.weak FPU_IRQHandler
.thumb_set FPU_IRQHandler,Default_Handler
.weak UART7_IRQHandler
.thumb_set UART7_IRQHandler,Default_Handler
.weak UART8_IRQHandler
.thumb_set UART8_IRQHandler,Default_Handler
.weak SPI4_IRQHandler
.thumb_set SPI4_IRQHandler,Default_Handler
.weak SPI5_IRQHandler
.thumb_set SPI5_IRQHandler,Default_Handler
.weak SAI1_IRQHandler
.thumb_set SAI1_IRQHandler,Default_Handler
.weak UART9_IRQHandler
.thumb_set UART9_IRQHandler,Default_Handler
.weak UART10_IRQHandler
.thumb_set UART10_IRQHandler,Default_Handler
.weak QUADSPI_IRQHandler
.thumb_set QUADSPI_IRQHandler,Default_Handler
.weak FMPI2C1_EV_IRQHandler
.thumb_set FMPI2C1_EV_IRQHandler,Default_Handler
.weak FMPI2C1_ER_IRQHandler
.thumb_set FMPI2C1_ER_IRQHandler,Default_Handler
.weak LPTIM1_IRQHandler
.thumb_set LPTIM1_IRQHandler,Default_Handler
.weak DFSDM2_FLT0_IRQHandler
.thumb_set DFSDM2_FLT0_IRQHandler,Default_Handler
.weak DFSDM2_FLT1_IRQHandler
.thumb_set DFSDM2_FLT1_IRQHandler,Default_Handler
.weak DFSDM2_FLT2_IRQHandler
.thumb_set DFSDM2_FLT2_IRQHandler,Default_Handler
.weak DFSDM2_FLT3_IRQHandler
.thumb_set DFSDM2_FLT3_IRQHandler,Default_Handler
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

View File

@@ -0,0 +1,166 @@
/*
*****************************************************************************
**
** File : stm32f4_flash.ld
**
** Abstract : Linker script for STM32F407VG Device with
** 1024KByte FLASH, 192KByte RAM
**
** Set heap size, stack size and stack location according
** to application requirements.
**
** Set memory bank area and size if external memory is used.
**
** Target : STMicroelectronics STM32
**
** Environment : Atollic TrueSTUDIO(R)
**
** Distribution: The file is distributed "as is," without any warranty
** of any kind.
**
** (c)Copyright Atollic AB.
** You may use this file as-is or modify it according to the needs of your
** project. Distribution of this file (unmodified or modified) is not
** permitted. Atollic AB permit registered Atollic TrueSTUDIO(R) users the
** rights to distribute the assembled, compiled & linked contents of this
** file as part of an application binary file, provided that it is built
** using the Atollic TrueSTUDIO(R) toolchain.
**
*****************************************************************************
*/
/* Entry Point */
ENTRY(Reset_Handler)
/* Highest address of the user mode stack */
enter_bootloader_mode = 0x2001FFFC;
_estack = 0x2001FFFC; /* end of 128K RAM on AHB bus*/
_app_start = 0x08004000; /* Reserve Sector 0(16K) for bootloader */
/* Generate a link error if heap and stack don't fit into RAM */
_Min_Heap_Size = 0; /* required amount of heap */
_Min_Stack_Size = 0x400; /* required amount of stack */
/* Specify the memory areas */
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 256K
RAM2 (xrw) : ORIGIN = 0x20040000, LENGTH = 64K
MEMORY_B1 (rx) : ORIGIN = 0x60000000, LENGTH = 0K
}
/* Define output sections */
SECTIONS
{
/* The startup code goes first into FLASH */
.isr_vector :
{
. = ALIGN(4);
KEEP(*(.isr_vector)) /* Startup code */
. = ALIGN(4);
} >FLASH
/* The program code and other data goes into FLASH */
.text :
{
. = ALIGN(4);
*(.text) /* .text sections (code) */
*(.text*) /* .text* sections (code) */
*(.rodata) /* .rodata sections (constants, strings, etc.) */
*(.rodata*) /* .rodata* sections (constants, strings, etc.) */
*(.glue_7) /* glue arm to thumb code */
*(.glue_7t) /* glue thumb to arm code */
*(.eh_frame)
KEEP (*(.init))
KEEP (*(.fini))
. = ALIGN(4);
_etext = .; /* define a global symbols at end of code */
_exit = .;
} >FLASH
.ARM.extab : { *(.ARM.extab* .gnu.linkonce.armextab.*) } >FLASH
.ARM : {
__exidx_start = .;
*(.ARM.exidx*)
__exidx_end = .;
} >FLASH
.preinit_array :
{
PROVIDE_HIDDEN (__preinit_array_start = .);
KEEP (*(.preinit_array*))
PROVIDE_HIDDEN (__preinit_array_end = .);
} >FLASH
.init_array :
{
PROVIDE_HIDDEN (__init_array_start = .);
KEEP (*(SORT(.init_array.*)))
KEEP (*(.init_array*))
PROVIDE_HIDDEN (__init_array_end = .);
} >FLASH
.fini_array :
{
PROVIDE_HIDDEN (__fini_array_start = .);
KEEP (*(.fini_array*))
KEEP (*(SORT(.fini_array.*)))
PROVIDE_HIDDEN (__fini_array_end = .);
} >FLASH
/* used by the startup to initialize data */
_sidata = .;
/* Initialized data sections goes into RAM, load LMA copy after code */
.data : AT ( _sidata )
{
. = ALIGN(4);
_sdata = .; /* create a global symbol at data start */
*(.data) /* .data sections */
*(.data*) /* .data* sections */
. = ALIGN(4);
_edata = .; /* define a global symbol at data end */
} >RAM
/* Uninitialized data section */
. = ALIGN(4);
.bss :
{
/* This is used by the startup in order to initialize the .bss secion */
_sbss = .; /* define a global symbol at bss start */
__bss_start__ = _sbss;
*(.bss)
*(.bss*)
*(COMMON)
. = ALIGN(4);
_ebss = .; /* define a global symbol at bss end */
__bss_end__ = _ebss;
} >RAM
/* User_heap_stack section, used to check that there is enough RAM left */
._user_heap_stack :
{
. = ALIGN(4);
PROVIDE ( end = . );
PROVIDE ( _end = . );
. = . + _Min_Heap_Size;
. = . + _Min_Stack_Size;
. = ALIGN(4);
} >RAM
/* MEMORY_bank1 section, code must be located here explicitly */
/* Example: extern int foo(void) __attribute__ ((section (".mb1text"))); */
.memory_b1_text :
{
*(.mb1text) /* .mb1text sections (code) */
*(.mb1text*) /* .mb1text* sections (code) */
*(.mb1rodata) /* read-only data (constants) */
*(.mb1rodata*)
} >MEMORY_B1
.ARM.attributes 0 : { *(.ARM.attributes) }
}

View File

@@ -0,0 +1,767 @@
/**
******************************************************************************
* @file startup_stm32h735xx.s
* @author MCD Application Team
* @brief STM32H735xx Devices vector table for GCC based toolchain.
* This module performs:
* - Set the initial SP
* - Set the initial PC == Reset_Handler,
* - Set the vector table entries with the exceptions ISR address
* - Branches to main in the C library (which eventually
* calls main()).
* After Reset the Cortex-M processor is in Thread mode,
* priority is Privileged, and the Stack is set to Main.
******************************************************************************
* @attention
*
* <h2><center>&copy; Copyright (c) 2019 STMicroelectronics.
* All rights reserved.</center></h2>
*
* This software component is licensed by ST under BSD 3-Clause license,
* the "License"; You may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
* opensource.org/licenses/BSD-3-Clause
*
******************************************************************************
*/
.syntax unified
.cpu cortex-m7
.fpu softvfp
.thumb
.global g_pfnVectors
.global Default_Handler
/* start address for the initialization values of the .data section.
defined in linker script */
.word _sidata
/* start address for the .data section. defined in linker script */
.word _sdata
/* end address for the .data section. defined in linker script */
.word _edata
/* start address for the .bss section. defined in linker script */
.word _sbss
/* end address for the .bss section. defined in linker script */
.word _ebss
/* stack used for SystemInit_ExtMemCtl; always internal RAM used */
/**
* @brief This is the code that gets called when the processor first
* starts execution following a reset event. Only the absolutely
* necessary set is performed, after which the application
* supplied main() routine is called.
* @param None
* @retval : None
*/
.section .text.Reset_Handler
.weak Reset_Handler
.type Reset_Handler, %function
Reset_Handler:
ldr sp, =_estack /* set stack pointer */
bl __initialize_hardware_early
/* Copy the data segment initializers from flash to SRAM */
ldr r0, =_sdata
ldr r1, =_edata
ldr r2, =_sidata
movs r3, #0
b LoopCopyDataInit
CopyDataInit:
ldr r4, [r2, r3]
str r4, [r0, r3]
adds r3, r3, #4
LoopCopyDataInit:
adds r4, r0, r3
cmp r4, r1
bcc CopyDataInit
/* Zero fill the bss segment. */
ldr r2, =_sbss
ldr r4, =_ebss
movs r3, #0
b LoopFillZerobss
FillZerobss:
str r3, [r2]
adds r2, r2, #4
LoopFillZerobss:
cmp r2, r4
bcc FillZerobss
/* Call the clock system intitialization function.*/
/* bl SystemInit */
/* Call static constructors */
/* bl __libc_init_array */
/* Call the application's entry point.*/
bl main
bx lr
.size Reset_Handler, .-Reset_Handler
/**
* @brief This is the code that gets called when the processor receives an
* unexpected interrupt. This simply enters an infinite loop, preserving
* the system state for examination by a debugger.
* @param None
* @retval None
*/
.section .text.Default_Handler,"ax",%progbits
Default_Handler:
Infinite_Loop:
b Infinite_Loop
.size Default_Handler, .-Default_Handler
/******************************************************************************
*
* The minimal vector table for a Cortex M. Note that the proper constructs
* must be placed on this to ensure that it ends up at physical address
* 0x0000.0000.
*
*******************************************************************************/
.section .isr_vector,"a",%progbits
.type g_pfnVectors, %object
.size g_pfnVectors, .-g_pfnVectors
g_pfnVectors:
.word _estack
.word Reset_Handler
.word NMI_Handler
.word HardFault_Handler
.word MemManage_Handler
.word BusFault_Handler
.word UsageFault_Handler
.word 0
.word 0
.word 0
.word 0
.word SVC_Handler
.word DebugMon_Handler
.word 0
.word PendSV_Handler
.word SysTick_Handler
/* External Interrupts */
.word WWDG_IRQHandler /* Window WatchDog */
.word PVD_AVD_IRQHandler /* PVD/AVD through EXTI Line detection */
.word TAMP_STAMP_IRQHandler /* Tamper and TimeStamps through the EXTI line */
.word RTC_WKUP_IRQHandler /* RTC Wakeup through the EXTI line */
.word FLASH_IRQHandler /* FLASH */
.word RCC_IRQHandler /* RCC */
.word EXTI0_IRQHandler /* EXTI Line0 */
.word EXTI1_IRQHandler /* EXTI Line1 */
.word EXTI2_IRQHandler /* EXTI Line2 */
.word EXTI3_IRQHandler /* EXTI Line3 */
.word EXTI4_IRQHandler /* EXTI Line4 */
.word DMA1_Stream0_IRQHandler /* DMA1 Stream 0 */
.word DMA1_Stream1_IRQHandler /* DMA1 Stream 1 */
.word DMA1_Stream2_IRQHandler /* DMA1 Stream 2 */
.word DMA1_Stream3_IRQHandler /* DMA1 Stream 3 */
.word DMA1_Stream4_IRQHandler /* DMA1 Stream 4 */
.word DMA1_Stream5_IRQHandler /* DMA1 Stream 5 */
.word DMA1_Stream6_IRQHandler /* DMA1 Stream 6 */
.word ADC_IRQHandler /* ADC1, ADC2 and ADC3s */
.word FDCAN1_IT0_IRQHandler /* FDCAN1 interrupt line 0 */
.word FDCAN2_IT0_IRQHandler /* FDCAN2 interrupt line 0 */
.word FDCAN1_IT1_IRQHandler /* FDCAN1 interrupt line 1 */
.word FDCAN2_IT1_IRQHandler /* FDCAN2 interrupt line 1 */
.word EXTI9_5_IRQHandler /* External Line[9:5]s */
.word TIM1_BRK_IRQHandler /* TIM1 Break interrupt */
.word TIM1_UP_IRQHandler /* TIM1 Update interrupt */
.word TIM1_TRG_COM_IRQHandler /* TIM1 Trigger and Commutation interrupt */
.word TIM1_CC_IRQHandler /* TIM1 Capture Compare */
.word TIM2_IRQHandler /* TIM2 */
.word TIM3_IRQHandler /* TIM3 */
.word TIM4_IRQHandler /* TIM4 */
.word I2C1_EV_IRQHandler /* I2C1 Event */
.word I2C1_ER_IRQHandler /* I2C1 Error */
.word I2C2_EV_IRQHandler /* I2C2 Event */
.word I2C2_ER_IRQHandler /* I2C2 Error */
.word SPI1_IRQHandler /* SPI1 */
.word SPI2_IRQHandler /* SPI2 */
.word USART1_IRQHandler /* USART1 */
.word USART2_IRQHandler /* USART2 */
.word USART3_IRQHandler /* USART3 */
.word EXTI15_10_IRQHandler /* External Line[15:10]s */
.word RTC_Alarm_IRQHandler /* RTC Alarm (A and B) through EXTI Line */
.word 0 /* Reserved */
.word TIM8_BRK_TIM12_IRQHandler /* TIM8 Break and TIM12 */
.word TIM8_UP_TIM13_IRQHandler /* TIM8 Update and TIM13 */
.word TIM8_TRG_COM_TIM14_IRQHandler /* TIM8 Trigger and Commutation and TIM14 */
.word TIM8_CC_IRQHandler /* TIM8 Capture Compare */
.word DMA1_Stream7_IRQHandler /* DMA1 Stream7 */
.word FMC_IRQHandler /* FMC */
.word SDMMC1_IRQHandler /* SDMMC1 */
.word TIM5_IRQHandler /* TIM5 */
.word SPI3_IRQHandler /* SPI3 */
.word UART4_IRQHandler /* UART4 */
.word UART5_IRQHandler /* UART5 */
.word TIM6_DAC_IRQHandler /* TIM6 and DAC1&2 underrun errors */
.word TIM7_IRQHandler /* TIM7 */
.word DMA2_Stream0_IRQHandler /* DMA2 Stream 0 */
.word DMA2_Stream1_IRQHandler /* DMA2 Stream 1 */
.word DMA2_Stream2_IRQHandler /* DMA2 Stream 2 */
.word DMA2_Stream3_IRQHandler /* DMA2 Stream 3 */
.word DMA2_Stream4_IRQHandler /* DMA2 Stream 4 */
.word ETH_IRQHandler /* Ethernet */
.word ETH_WKUP_IRQHandler /* Ethernet Wakeup through EXTI line */
.word FDCAN_CAL_IRQHandler /* FDCAN calibration unit interrupt*/
.word 0 /* Reserved */
.word 0 /* Reserved */
.word 0 /* Reserved */
.word 0 /* Reserved */
.word DMA2_Stream5_IRQHandler /* DMA2 Stream 5 */
.word DMA2_Stream6_IRQHandler /* DMA2 Stream 6 */
.word DMA2_Stream7_IRQHandler /* DMA2 Stream 7 */
.word USART6_IRQHandler /* USART6 */
.word I2C3_EV_IRQHandler /* I2C3 event */
.word I2C3_ER_IRQHandler /* I2C3 error */
.word OTG_HS_EP1_OUT_IRQHandler /* USB OTG HS End Point 1 Out */
.word OTG_HS_EP1_IN_IRQHandler /* USB OTG HS End Point 1 In */
.word OTG_HS_WKUP_IRQHandler /* USB OTG HS Wakeup through EXTI */
.word OTG_HS_IRQHandler /* USB OTG HS */
.word DCMI_PSSI_IRQHandler /* DCMI, PSSI */
.word CRYP_IRQHandler /* CRYP */
.word HASH_RNG_IRQHandler /* Hash and Rng */
.word FPU_IRQHandler /* FPU */
.word UART7_IRQHandler /* UART7 */
.word UART8_IRQHandler /* UART8 */
.word SPI4_IRQHandler /* SPI4 */
.word SPI5_IRQHandler /* SPI5 */
.word SPI6_IRQHandler /* SPI6 */
.word SAI1_IRQHandler /* SAI1 */
.word LTDC_IRQHandler /* LTDC */
.word LTDC_ER_IRQHandler /* LTDC error */
.word DMA2D_IRQHandler /* DMA2D */
.word 0 /* Reserved */
.word OCTOSPI1_IRQHandler /* OCTOSPI1 */
.word LPTIM1_IRQHandler /* LPTIM1 */
.word CEC_IRQHandler /* HDMI_CEC */
.word I2C4_EV_IRQHandler /* I2C4 Event */
.word I2C4_ER_IRQHandler /* I2C4 Error */
.word SPDIF_RX_IRQHandler /* SPDIF_RX */
.word 0 /* Reserved */
.word 0 /* Reserved */
.word 0 /* Reserved */
.word 0 /* Reserved */
.word DMAMUX1_OVR_IRQHandler /* DMAMUX1 Overrun interrupt */
.word 0 /* Reserved */
.word 0 /* Reserved */
.word 0 /* Reserved */
.word 0 /* Reserved */
.word 0 /* Reserved */
.word 0 /* Reserved */
.word 0 /* Reserved */
.word DFSDM1_FLT0_IRQHandler /* DFSDM Filter0 Interrupt */
.word DFSDM1_FLT1_IRQHandler /* DFSDM Filter1 Interrupt */
.word DFSDM1_FLT2_IRQHandler /* DFSDM Filter2 Interrupt */
.word DFSDM1_FLT3_IRQHandler /* DFSDM Filter3 Interrupt */
.word 0 /* Reserved */
.word SWPMI1_IRQHandler /* Serial Wire Interface 1 global interrupt */
.word TIM15_IRQHandler /* TIM15 global Interrupt */
.word TIM16_IRQHandler /* TIM16 global Interrupt */
.word TIM17_IRQHandler /* TIM17 global Interrupt */
.word MDIOS_WKUP_IRQHandler /* MDIOS Wakeup Interrupt */
.word MDIOS_IRQHandler /* MDIOS global Interrupt */
.word 0 /* Reserved */
.word MDMA_IRQHandler /* MDMA global Interrupt */
.word 0 /* Reserved */
.word SDMMC2_IRQHandler /* SDMMC2 global Interrupt */
.word HSEM1_IRQHandler /* HSEM1 global Interrupt */
.word 0 /* Reserved */
.word ADC3_IRQHandler /* ADC3 global Interrupt */
.word DMAMUX2_OVR_IRQHandler /* DMAMUX Overrun interrupt */
.word BDMA_Channel0_IRQHandler /* BDMA Channel 0 global Interrupt */
.word BDMA_Channel1_IRQHandler /* BDMA Channel 1 global Interrupt */
.word BDMA_Channel2_IRQHandler /* BDMA Channel 2 global Interrupt */
.word BDMA_Channel3_IRQHandler /* BDMA Channel 3 global Interrupt */
.word BDMA_Channel4_IRQHandler /* BDMA Channel 4 global Interrupt */
.word BDMA_Channel5_IRQHandler /* BDMA Channel 5 global Interrupt */
.word BDMA_Channel6_IRQHandler /* BDMA Channel 6 global Interrupt */
.word BDMA_Channel7_IRQHandler /* BDMA Channel 7 global Interrupt */
.word COMP1_IRQHandler /* COMP1 global Interrupt */
.word LPTIM2_IRQHandler /* LP TIM2 global interrupt */
.word LPTIM3_IRQHandler /* LP TIM3 global interrupt */
.word LPTIM4_IRQHandler /* LP TIM4 global interrupt */
.word LPTIM5_IRQHandler /* LP TIM5 global interrupt */
.word LPUART1_IRQHandler /* LP UART1 interrupt */
.word 0 /* Reserved */
.word CRS_IRQHandler /* Clock Recovery Global Interrupt */
.word ECC_IRQHandler /* ECC diagnostic Global Interrupt */
.word SAI4_IRQHandler /* SAI4 global interrupt */
.word DTS_IRQHandler /* Digital Temperature Sensor interrupt */
.word 0 /* Reserved */
.word WAKEUP_PIN_IRQHandler /* Interrupt for all 6 wake-up pins */
.word OCTOSPI2_IRQHandler /* OCTOSPI2 Interrupt */
.word OTFDEC1_IRQHandler /* OTFDEC1 Interrupt */
.word OTFDEC2_IRQHandler /* OTFDEC2 Interrupt */
.word FMAC_IRQHandler /* FMAC Interrupt */
.word CORDIC_IRQHandler /* CORDIC Interrupt */
.word UART9_IRQHandler /* UART9 Interrupt */
.word USART10_IRQHandler /* UART10 Interrupt */
.word I2C5_EV_IRQHandler /* I2C5 Event Interrupt */
.word I2C5_ER_IRQHandler /* I2C5 Error Interrupt */
.word FDCAN3_IT0_IRQHandler /* FDCAN3 interrupt line 0 */
.word FDCAN3_IT1_IRQHandler /* FDCAN3 interrupt line 1 */
.word TIM23_IRQHandler /* TIM23 global interrupt */
.word TIM24_IRQHandler /* TIM24 global interrupt */
/*******************************************************************************
*
* Provide weak aliases for each Exception handler to the Default_Handler.
* As they are weak aliases, any function with the same name will override
* this definition.
*
*******************************************************************************/
.weak NMI_Handler
.thumb_set NMI_Handler,Default_Handler
.weak HardFault_Handler
.thumb_set HardFault_Handler,Default_Handler
.weak MemManage_Handler
.thumb_set MemManage_Handler,Default_Handler
.weak BusFault_Handler
.thumb_set BusFault_Handler,Default_Handler
.weak UsageFault_Handler
.thumb_set UsageFault_Handler,Default_Handler
.weak SVC_Handler
.thumb_set SVC_Handler,Default_Handler
.weak DebugMon_Handler
.thumb_set DebugMon_Handler,Default_Handler
.weak PendSV_Handler
.thumb_set PendSV_Handler,Default_Handler
.weak SysTick_Handler
.thumb_set SysTick_Handler,Default_Handler
.weak WWDG_IRQHandler
.thumb_set WWDG_IRQHandler,Default_Handler
.weak PVD_AVD_IRQHandler
.thumb_set PVD_AVD_IRQHandler,Default_Handler
.weak TAMP_STAMP_IRQHandler
.thumb_set TAMP_STAMP_IRQHandler,Default_Handler
.weak RTC_WKUP_IRQHandler
.thumb_set RTC_WKUP_IRQHandler,Default_Handler
.weak FLASH_IRQHandler
.thumb_set FLASH_IRQHandler,Default_Handler
.weak RCC_IRQHandler
.thumb_set RCC_IRQHandler,Default_Handler
.weak EXTI0_IRQHandler
.thumb_set EXTI0_IRQHandler,Default_Handler
.weak EXTI1_IRQHandler
.thumb_set EXTI1_IRQHandler,Default_Handler
.weak EXTI2_IRQHandler
.thumb_set EXTI2_IRQHandler,Default_Handler
.weak EXTI3_IRQHandler
.thumb_set EXTI3_IRQHandler,Default_Handler
.weak EXTI4_IRQHandler
.thumb_set EXTI4_IRQHandler,Default_Handler
.weak DMA1_Stream0_IRQHandler
.thumb_set DMA1_Stream0_IRQHandler,Default_Handler
.weak DMA1_Stream1_IRQHandler
.thumb_set DMA1_Stream1_IRQHandler,Default_Handler
.weak DMA1_Stream2_IRQHandler
.thumb_set DMA1_Stream2_IRQHandler,Default_Handler
.weak DMA1_Stream3_IRQHandler
.thumb_set DMA1_Stream3_IRQHandler,Default_Handler
.weak DMA1_Stream4_IRQHandler
.thumb_set DMA1_Stream4_IRQHandler,Default_Handler
.weak DMA1_Stream5_IRQHandler
.thumb_set DMA1_Stream5_IRQHandler,Default_Handler
.weak DMA1_Stream6_IRQHandler
.thumb_set DMA1_Stream6_IRQHandler,Default_Handler
.weak ADC_IRQHandler
.thumb_set ADC_IRQHandler,Default_Handler
.weak FDCAN1_IT0_IRQHandler
.thumb_set FDCAN1_IT0_IRQHandler,Default_Handler
.weak FDCAN2_IT0_IRQHandler
.thumb_set FDCAN2_IT0_IRQHandler,Default_Handler
.weak FDCAN1_IT1_IRQHandler
.thumb_set FDCAN1_IT1_IRQHandler,Default_Handler
.weak FDCAN2_IT1_IRQHandler
.thumb_set FDCAN2_IT1_IRQHandler,Default_Handler
.weak EXTI9_5_IRQHandler
.thumb_set EXTI9_5_IRQHandler,Default_Handler
.weak TIM1_BRK_IRQHandler
.thumb_set TIM1_BRK_IRQHandler,Default_Handler
.weak TIM1_UP_IRQHandler
.thumb_set TIM1_UP_IRQHandler,Default_Handler
.weak TIM1_TRG_COM_IRQHandler
.thumb_set TIM1_TRG_COM_IRQHandler,Default_Handler
.weak TIM1_CC_IRQHandler
.thumb_set TIM1_CC_IRQHandler,Default_Handler
.weak TIM2_IRQHandler
.thumb_set TIM2_IRQHandler,Default_Handler
.weak TIM3_IRQHandler
.thumb_set TIM3_IRQHandler,Default_Handler
.weak TIM4_IRQHandler
.thumb_set TIM4_IRQHandler,Default_Handler
.weak I2C1_EV_IRQHandler
.thumb_set I2C1_EV_IRQHandler,Default_Handler
.weak I2C1_ER_IRQHandler
.thumb_set I2C1_ER_IRQHandler,Default_Handler
.weak I2C2_EV_IRQHandler
.thumb_set I2C2_EV_IRQHandler,Default_Handler
.weak I2C2_ER_IRQHandler
.thumb_set I2C2_ER_IRQHandler,Default_Handler
.weak SPI1_IRQHandler
.thumb_set SPI1_IRQHandler,Default_Handler
.weak SPI2_IRQHandler
.thumb_set SPI2_IRQHandler,Default_Handler
.weak USART1_IRQHandler
.thumb_set USART1_IRQHandler,Default_Handler
.weak USART2_IRQHandler
.thumb_set USART2_IRQHandler,Default_Handler
.weak USART3_IRQHandler
.thumb_set USART3_IRQHandler,Default_Handler
.weak EXTI15_10_IRQHandler
.thumb_set EXTI15_10_IRQHandler,Default_Handler
.weak RTC_Alarm_IRQHandler
.thumb_set RTC_Alarm_IRQHandler,Default_Handler
.weak TIM8_BRK_TIM12_IRQHandler
.thumb_set TIM8_BRK_TIM12_IRQHandler,Default_Handler
.weak TIM8_UP_TIM13_IRQHandler
.thumb_set TIM8_UP_TIM13_IRQHandler,Default_Handler
.weak TIM8_TRG_COM_TIM14_IRQHandler
.thumb_set TIM8_TRG_COM_TIM14_IRQHandler,Default_Handler
.weak TIM8_CC_IRQHandler
.thumb_set TIM8_CC_IRQHandler,Default_Handler
.weak DMA1_Stream7_IRQHandler
.thumb_set DMA1_Stream7_IRQHandler,Default_Handler
.weak FMC_IRQHandler
.thumb_set FMC_IRQHandler,Default_Handler
.weak SDMMC1_IRQHandler
.thumb_set SDMMC1_IRQHandler,Default_Handler
.weak TIM5_IRQHandler
.thumb_set TIM5_IRQHandler,Default_Handler
.weak SPI3_IRQHandler
.thumb_set SPI3_IRQHandler,Default_Handler
.weak UART4_IRQHandler
.thumb_set UART4_IRQHandler,Default_Handler
.weak UART5_IRQHandler
.thumb_set UART5_IRQHandler,Default_Handler
.weak TIM6_DAC_IRQHandler
.thumb_set TIM6_DAC_IRQHandler,Default_Handler
.weak TIM7_IRQHandler
.thumb_set TIM7_IRQHandler,Default_Handler
.weak DMA2_Stream0_IRQHandler
.thumb_set DMA2_Stream0_IRQHandler,Default_Handler
.weak DMA2_Stream1_IRQHandler
.thumb_set DMA2_Stream1_IRQHandler,Default_Handler
.weak DMA2_Stream2_IRQHandler
.thumb_set DMA2_Stream2_IRQHandler,Default_Handler
.weak DMA2_Stream3_IRQHandler
.thumb_set DMA2_Stream3_IRQHandler,Default_Handler
.weak DMA2_Stream4_IRQHandler
.thumb_set DMA2_Stream4_IRQHandler,Default_Handler
.weak ETH_IRQHandler
.thumb_set ETH_IRQHandler,Default_Handler
.weak ETH_WKUP_IRQHandler
.thumb_set ETH_WKUP_IRQHandler,Default_Handler
.weak FDCAN_CAL_IRQHandler
.thumb_set FDCAN_CAL_IRQHandler,Default_Handler
.weak DMA2_Stream5_IRQHandler
.thumb_set DMA2_Stream5_IRQHandler,Default_Handler
.weak DMA2_Stream6_IRQHandler
.thumb_set DMA2_Stream6_IRQHandler,Default_Handler
.weak DMA2_Stream7_IRQHandler
.thumb_set DMA2_Stream7_IRQHandler,Default_Handler
.weak USART6_IRQHandler
.thumb_set USART6_IRQHandler,Default_Handler
.weak I2C3_EV_IRQHandler
.thumb_set I2C3_EV_IRQHandler,Default_Handler
.weak I2C3_ER_IRQHandler
.thumb_set I2C3_ER_IRQHandler,Default_Handler
.weak OTG_HS_EP1_OUT_IRQHandler
.thumb_set OTG_HS_EP1_OUT_IRQHandler,Default_Handler
.weak OTG_HS_EP1_IN_IRQHandler
.thumb_set OTG_HS_EP1_IN_IRQHandler,Default_Handler
.weak OTG_HS_WKUP_IRQHandler
.thumb_set OTG_HS_WKUP_IRQHandler,Default_Handler
.weak OTG_HS_IRQHandler
.thumb_set OTG_HS_IRQHandler,Default_Handler
.weak DCMI_PSSI_IRQHandler
.thumb_set DCMI_PSSI_IRQHandler,Default_Handler
.weak CRYP_IRQHandler
.thumb_set CRYP_IRQHandler,Default_Handler
.weak HASH_RNG_IRQHandler
.thumb_set HASH_RNG_IRQHandler,Default_Handler
.weak FPU_IRQHandler
.thumb_set FPU_IRQHandler,Default_Handler
.weak UART7_IRQHandler
.thumb_set UART7_IRQHandler,Default_Handler
.weak UART8_IRQHandler
.thumb_set UART8_IRQHandler,Default_Handler
.weak SPI4_IRQHandler
.thumb_set SPI4_IRQHandler,Default_Handler
.weak SPI5_IRQHandler
.thumb_set SPI5_IRQHandler,Default_Handler
.weak SPI6_IRQHandler
.thumb_set SPI6_IRQHandler,Default_Handler
.weak SAI1_IRQHandler
.thumb_set SAI1_IRQHandler,Default_Handler
.weak LTDC_IRQHandler
.thumb_set LTDC_IRQHandler,Default_Handler
.weak LTDC_ER_IRQHandler
.thumb_set LTDC_ER_IRQHandler,Default_Handler
.weak DMA2D_IRQHandler
.thumb_set DMA2D_IRQHandler,Default_Handler
.weak OCTOSPI1_IRQHandler
.thumb_set OCTOSPI1_IRQHandler,Default_Handler
.weak LPTIM1_IRQHandler
.thumb_set LPTIM1_IRQHandler,Default_Handler
.weak CEC_IRQHandler
.thumb_set CEC_IRQHandler,Default_Handler
.weak I2C4_EV_IRQHandler
.thumb_set I2C4_EV_IRQHandler,Default_Handler
.weak I2C4_ER_IRQHandler
.thumb_set I2C4_ER_IRQHandler,Default_Handler
.weak SPDIF_RX_IRQHandler
.thumb_set SPDIF_RX_IRQHandler,Default_Handler
.weak DMAMUX1_OVR_IRQHandler
.thumb_set DMAMUX1_OVR_IRQHandler,Default_Handler
.weak DFSDM1_FLT0_IRQHandler
.thumb_set DFSDM1_FLT0_IRQHandler,Default_Handler
.weak DFSDM1_FLT1_IRQHandler
.thumb_set DFSDM1_FLT1_IRQHandler,Default_Handler
.weak DFSDM1_FLT2_IRQHandler
.thumb_set DFSDM1_FLT2_IRQHandler,Default_Handler
.weak DFSDM1_FLT3_IRQHandler
.thumb_set DFSDM1_FLT3_IRQHandler,Default_Handler
.weak SWPMI1_IRQHandler
.thumb_set SWPMI1_IRQHandler,Default_Handler
.weak TIM15_IRQHandler
.thumb_set TIM15_IRQHandler,Default_Handler
.weak TIM16_IRQHandler
.thumb_set TIM16_IRQHandler,Default_Handler
.weak TIM17_IRQHandler
.thumb_set TIM17_IRQHandler,Default_Handler
.weak MDIOS_WKUP_IRQHandler
.thumb_set MDIOS_WKUP_IRQHandler,Default_Handler
.weak MDIOS_IRQHandler
.thumb_set MDIOS_IRQHandler,Default_Handler
.weak MDMA_IRQHandler
.thumb_set MDMA_IRQHandler,Default_Handler
.weak SDMMC2_IRQHandler
.thumb_set SDMMC2_IRQHandler,Default_Handler
.weak HSEM1_IRQHandler
.thumb_set HSEM1_IRQHandler,Default_Handler
.weak ADC3_IRQHandler
.thumb_set ADC3_IRQHandler,Default_Handler
.weak DMAMUX2_OVR_IRQHandler
.thumb_set DMAMUX2_OVR_IRQHandler,Default_Handler
.weak BDMA_Channel0_IRQHandler
.thumb_set BDMA_Channel0_IRQHandler,Default_Handler
.weak BDMA_Channel1_IRQHandler
.thumb_set BDMA_Channel1_IRQHandler,Default_Handler
.weak BDMA_Channel2_IRQHandler
.thumb_set BDMA_Channel2_IRQHandler,Default_Handler
.weak BDMA_Channel3_IRQHandler
.thumb_set BDMA_Channel3_IRQHandler,Default_Handler
.weak BDMA_Channel4_IRQHandler
.thumb_set BDMA_Channel4_IRQHandler,Default_Handler
.weak BDMA_Channel5_IRQHandler
.thumb_set BDMA_Channel5_IRQHandler,Default_Handler
.weak BDMA_Channel6_IRQHandler
.thumb_set BDMA_Channel6_IRQHandler,Default_Handler
.weak BDMA_Channel7_IRQHandler
.thumb_set BDMA_Channel7_IRQHandler,Default_Handler
.weak COMP1_IRQHandler
.thumb_set COMP1_IRQHandler,Default_Handler
.weak LPTIM2_IRQHandler
.thumb_set LPTIM2_IRQHandler,Default_Handler
.weak LPTIM3_IRQHandler
.thumb_set LPTIM3_IRQHandler,Default_Handler
.weak LPTIM4_IRQHandler
.thumb_set LPTIM4_IRQHandler,Default_Handler
.weak LPTIM5_IRQHandler
.thumb_set LPTIM5_IRQHandler,Default_Handler
.weak LPUART1_IRQHandler
.thumb_set LPUART1_IRQHandler,Default_Handler
.weak CRS_IRQHandler
.thumb_set CRS_IRQHandler,Default_Handler
.weak ECC_IRQHandler
.thumb_set ECC_IRQHandler,Default_Handler
.weak SAI4_IRQHandler
.thumb_set SAI4_IRQHandler,Default_Handler
.weak DTS_IRQHandler
.thumb_set DTS_IRQHandler,Default_Handler
.weak WAKEUP_PIN_IRQHandler
.thumb_set WAKEUP_PIN_IRQHandler,Default_Handler
.weak OCTOSPI2_IRQHandler
.thumb_set OCTOSPI2_IRQHandler,Default_Handler
.weak OTFDEC1_IRQHandler
.thumb_set OTFDEC1_IRQHandler,Default_Handler
.weak OTFDEC2_IRQHandler
.thumb_set OTFDEC2_IRQHandler,Default_Handler
.weak FMAC_IRQHandler
.thumb_set FMAC_IRQHandler,Default_Handler
.weak CORDIC_IRQHandler
.thumb_set CORDIC_IRQHandler,Default_Handler
.weak UART9_IRQHandler
.thumb_set UART9_IRQHandler,Default_Handler
.weak USART10_IRQHandler
.thumb_set USART10_IRQHandler,Default_Handler
.weak I2C5_EV_IRQHandler
.thumb_set I2C5_EV_IRQHandler,Default_Handler
.weak I2C5_ER_IRQHandler
.thumb_set I2C5_ER_IRQHandler,Default_Handler
.weak FDCAN3_IT0_IRQHandler
.thumb_set FDCAN3_IT0_IRQHandler,Default_Handler
.weak FDCAN3_IT1_IRQHandler
.thumb_set FDCAN3_IT1_IRQHandler,Default_Handler
.weak TIM23_IRQHandler
.thumb_set TIM23_IRQHandler,Default_Handler
.weak TIM24_IRQHandler
.thumb_set TIM24_IRQHandler,Default_Handler
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

View File

@@ -0,0 +1,221 @@
/*
******************************************************************************
**
** File : LinkerScript.ld
**
** Author : Auto-generated by System Workbench for STM32
**
** Abstract : Linker script for STM32H735ZGTx series
** 1024Kbytes FLASH and 560Kbytes RAM
**
** Set heap size, stack size and stack location according
** to application requirements.
**
** Set memory bank area and size if external memory is used.
**
** Target : STMicroelectronics STM32
**
** Distribution: The file is distributed “as is,” without any warranty
** of any kind.
**
*****************************************************************************
** @attention
**
** <h2><center>&copy; COPYRIGHT(c) 2019 STMicroelectronics</center></h2>
**
** Redistribution and use in source and binary forms, with or without modification,
** are permitted provided that the following conditions are met:
** 1. Redistributions of source code must retain the above copyright notice,
** this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright notice,
** this list of conditions and the following disclaimer in the documentation
** and/or other materials provided with the distribution.
** 3. Neither the name of STMicroelectronics nor the names of its contributors
** may be used to endorse or promote products derived from this software
** without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
** DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER 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.
**
*****************************************************************************
*/
/* Entry Point */
ENTRY(Reset_Handler)
/* Highest address of the user mode stack */
enter_bootloader_mode = 0x38001FFC;
_estack = 0x20020000; /* end of RAM */
_app_start = 0x08020000; /* Reserve Sector 0(128K) for bootloader */
/* Generate a link error if heap and stack don't fit into RAM */
_Min_Heap_Size = 0x200; /* required amount of heap */
_Min_Stack_Size = 0x400; /* required amount of stack */
/* Specify the memory areas */
MEMORY
{
/* RAM */
BACKUP_SRAM (xrw) : ORIGIN = 0x38800000, LENGTH = 4K /* Backup SRAM(4kb) */
SRAM4 (xrw) : ORIGIN = 0x38000000, LENGTH = 16K /* SRAM4(16kb) best for BDMA and SDMMC1*/
SRAM12 (xrw) : ORIGIN = 0x30000000, LENGTH = 32K /* SRAM1(16kb) + SRAM2(16kb), not for BDMA or SDMMC1 */
AXISRAM (xrw) : ORIGIN = 0x24000000, LENGTH = 320K /* AXI SRAM */
DTCMRAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K /* DTCM */
/* Code */
SYSTEM (rx) : ORIGIN = 0x1FF00000, LENGTH = 128K /* System memory */
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K
ITCMRAM (xrw) : ORIGIN = 0x00000000, LENGTH = 64K
}
/* Define output sections */
SECTIONS
{
/* The startup code goes first into FLASH */
.isr_vector :
{
. = ALIGN(4);
KEEP(*(.isr_vector)) /* Startup code */
. = ALIGN(4);
} >FLASH
/* The program code and other data goes into FLASH */
.text :
{
. = ALIGN(4);
*(.text) /* .text sections (code) */
*(.text*) /* .text* sections (code) */
*(.glue_7) /* glue arm to thumb code */
*(.glue_7t) /* glue thumb to arm code */
*(.eh_frame)
KEEP (*(.init))
KEEP (*(.fini))
. = ALIGN(4);
_etext = .; /* define a global symbols at end of code */
} >FLASH
/* Constant data goes into FLASH */
.rodata :
{
. = ALIGN(4);
*(.rodata) /* .rodata sections (constants, strings, etc.) */
*(.rodata*) /* .rodata* sections (constants, strings, etc.) */
. = ALIGN(4);
} >FLASH
.ARM.extab : { *(.ARM.extab* .gnu.linkonce.armextab.*) } >FLASH
.ARM : {
__exidx_start = .;
*(.ARM.exidx*)
__exidx_end = .;
} >FLASH
.preinit_array :
{
PROVIDE_HIDDEN (__preinit_array_start = .);
KEEP (*(.preinit_array*))
PROVIDE_HIDDEN (__preinit_array_end = .);
} >FLASH
.init_array :
{
PROVIDE_HIDDEN (__init_array_start = .);
KEEP (*(SORT(.init_array.*)))
KEEP (*(.init_array*))
PROVIDE_HIDDEN (__init_array_end = .);
} >FLASH
.fini_array :
{
PROVIDE_HIDDEN (__fini_array_start = .);
KEEP (*(SORT(.fini_array.*)))
KEEP (*(.fini_array*))
PROVIDE_HIDDEN (__fini_array_end = .);
} >FLASH
/* used by the startup to initialize data */
_sidata = LOADADDR(.data);
/* Initialized data sections goes into RAM, load LMA copy after code */
.data :
{
. = ALIGN(4);
_sdata = .; /* create a global symbol at data start */
*(.data) /* .data sections */
*(.data*) /* .data* sections */
. = ALIGN(4);
_edata = .; /* define a global symbol at data end */
} >DTCMRAM AT> FLASH
/* Uninitialized data section */
. = ALIGN(4);
.bss :
{
/* This is used by the startup in order to initialize the .bss secion */
_sbss = .; /* define a global symbol at bss start */
__bss_start__ = _sbss;
*(.bss)
*(.bss*)
*(COMMON)
. = ALIGN(4);
_ebss = .; /* define a global symbol at bss end */
__bss_end__ = _ebss;
} >DTCMRAM
/* User_heap_stack section, used to check that there is enough RAM left */
._user_heap_stack :
{
. = ALIGN(8);
PROVIDE ( end = . );
PROVIDE ( _end = . );
. = . + _Min_Heap_Size;
. = . + _Min_Stack_Size;
. = ALIGN(8);
} >DTCMRAM
.itcmram (NOLOAD) :
{
. = ALIGN(4);
*(.itcmram*)
} >ITCMRAM
.axisram (NOLOAD) :
{
. = ALIGN(4);
*(.axisram*)
} >AXISRAM
.sram12 (NOLOAD) :
{
. = ALIGN(4);
*(.sram12*)
} >SRAM12
.sram4 (NOLOAD) :
{
. = ALIGN(4);
*(.sram4*)
} >SRAM4
.backup_sram (NOLOAD) :
{
. = ALIGN(4);
*(.backup_sram*)
} >BACKUP_SRAM
.ARM.attributes 0 : { *(.ARM.attributes) }
}

15
panda/certs/debug Normal file
View File

@@ -0,0 +1,15 @@
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQC948lnRo4x44Rd7Y8bQAML4aKDC4XRx958fHV8K6+FbCaP1Z42
U2kX0yygak0LjoDutpgObmGHZA+Iz3HeUD6VGjr/teN24vPk+A95cRsjt8rgmGQ9
6HNjaNgjR+gl1F9XxFimMzir82Xpl1ekTueJNXa7ia5HVH1nFdiksOKHGQIDAQAB
AoGAQuPw2I6EHJLW1/eNB75e1FqhUqRGeYV8nEGDaUBCTi+wzc4kM2LijF/5QnDv
vvht9qkfm0XK2VSoHDtnEzcVM/l1ksb68n4R/1nUooAWY6cQI7dCSk/A6yS1EJFg
BXsgGbT/65khw9pzBW2zVtMVcVNWFayqfCO1I9WcDdA1x1kCQQDfrhoZTZNoDEUE
JKM4fiUdWr1h3Aw8KLJFFexSWeGDwo+qqnujYcKWkHa9qaH1RG5x8Kir9s9Oi4Js
mzKwov8fAkEA2VPJPWxJ4vVQpXle6wC1nyoL7s739yxMWFcabvkzDDhlIVBNdVJd
gZKsFWV7QnVNdDMjn9D27FwKu3i2D+kKxwJBANp1SMojqO765MEKI1t+YDNONx6H
cm+i85Fjuv4nCIjOEdCGVuCYDxtMFpxgO2y3HAMuHx5sm8XDnWsDHLvFRdMCQD7V
XqWHnYUk8AAnqy2+ssQl3/VXmZG5GQmhhV74Za3u0C5ljT+SZL6FrYMyKAT67T3f
WzllrT6BDglNyTWoZxkCQQCt0XSoGM3603GGYNt6AUlGSgtXSo/2Px7odGUtQoKA
FH9q6FVMYpQJ38spZxIGufZJmLP8LLg6YIWJj1F+akxr
-----END RSA PRIVATE KEY-----

1
panda/certs/debug.pub Normal file
View File

@@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC948lnRo4x44Rd7Y8bQAML4aKDC4XRx958fHV8K6+FbCaP1Z42U2kX0yygak0LjoDutpgObmGHZA+Iz3HeUD6VGjr/teN24vPk+A95cRsjt8rgmGQ96HNjaNgjR+gl1F9XxFimMzir82Xpl1ekTueJNXa7ia5HVH1nFdiksOKHGQ== batman@y840

1
panda/certs/release.pub Normal file
View File

@@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDGN9GU2nOc0kKq6vdZI5qUMzHt234ngqofrgCFFxL0D2Whex0zACp9gar0HZp+bvtpoSgU/Ev8wexNKr+A9QTradljiuxi5ctrOra9k+wxqNj63Wrcu4+wU5UnJEVf/buV4jCOFffMT8z3PO4imt8LzHuEIC/m/ASKVYyvuvBRQQ== batman@y840

38
panda/crypto/sign.py Executable file
View File

@@ -0,0 +1,38 @@
#!/usr/bin/env python3
import os
import sys
import struct
import hashlib
from Crypto.PublicKey import RSA
import binascii
# increment this to make new hardware not run old versions
VERSION = 2
if __name__ == "__main__":
with open(sys.argv[3]) as k:
rsa = RSA.importKey(k.read())
with open(sys.argv[1], "rb") as f:
dat = f.read()
print("signing", len(dat), "bytes")
with open(sys.argv[2], "wb") as f:
if os.getenv("SETLEN") is not None:
# add the version at the end
dat += b"VERS" + struct.pack("I", VERSION)
# add the length at the beginning
x = struct.pack("I", len(dat)) + dat[4:]
# mock signature of dat[4:]
dd = hashlib.sha1(dat[4:]).digest()
else:
x = dat
dd = hashlib.sha1(dat).digest()
print("hash:", str(binascii.hexlify(dd), "utf-8"))
dd = b"\x00\x01" + b"\xff" * 0x69 + b"\x00" + dd
rsa_out = pow(int.from_bytes(dd, byteorder='big', signed=False), rsa.d, rsa.n)
sig = (hex(rsa_out)[2:].rjust(0x100, '0'))
x += binascii.unhexlify(sig)
f.write(x)

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

7
panda/drivers/spi/.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
spidev.c
*.ko
*.cmd
*.mod
*.symvers
*.order
*.mod.c

View File

@@ -0,0 +1,14 @@
obj-m += spidev_panda.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
# GCC9 bug, apply kernel patch instead?
# https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=0b999ae3614d09d97a1575936bcee884f912b10e
ccflags-y := -Wno-missing-attributes
default:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean

27
panda/drivers/spi/load.sh Executable file
View File

@@ -0,0 +1,27 @@
#!/usr/bin/env bash
set -e
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)"
cd $DIR
make -j8
sudo su -c "echo spi0.0 > /sys/bus/spi/drivers/spidev/unbind" || true
sudo dmesg -C
#sudo rmmod -f spidev_panda
sudo rmmod spidev_panda || true
sudo insmod spidev_panda.ko
sudo su -c "echo 'file $DIR/spidev_panda.c +p' > /sys/kernel/debug/dynamic_debug/control"
sudo su -c "echo 'file $DIR/spi_panda.h +p' > /sys/kernel/debug/dynamic_debug/control"
sudo lsmod
echo "loaded"
ls -la /dev/spi*
sudo chmod 666 /dev/spi*
ipython -c "from panda import Panda; print(Panda.list())"
KERN=1 ipython -c "from panda import Panda; print(Panda.list())"
dmesg

33
panda/drivers/spi/patch Normal file
View File

@@ -0,0 +1,33 @@
53c53,54
< #define SPIDEV_MAJOR 153 /* assigned */
---
> int SPIDEV_MAJOR = 0;
> //#define SPIDEV_MAJOR 153 /* assigned */
354a356,358
>
> #include "spi_panda.h"
>
413,414c417,419
< retval = __put_user((spi->mode & SPI_LSB_FIRST) ? 1 : 0,
< (__u8 __user *)arg);
---
> retval = panda_transfer(spidev, spi, arg);
> //retval = __put_user((spi->mode & SPI_LSB_FIRST) ? 1 : 0,
> // (__u8 __user *)arg);
697,698d701
< { .compatible = "rohm,dh2228fv" },
< { .compatible = "lineartechnology,ltc2488" },
831c834
< .name = "spidev",
---
> .name = "spidev_panda",
856c859
< status = register_chrdev(SPIDEV_MAJOR, "spi", &spidev_fops);
---
> status = register_chrdev(0, "spi", &spidev_fops);
860c863,865
< spidev_class = class_create(THIS_MODULE, "spidev");
---
> SPIDEV_MAJOR = status;
>
> spidev_class = class_create(THIS_MODULE, "spidev_panda");

12
panda/drivers/spi/pull-src.sh Executable file
View File

@@ -0,0 +1,12 @@
#!/usr/bin/env bash
set -e
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)"
cd $DIR
rm -f spidev.c
wget https://raw.githubusercontent.com/commaai/agnos-kernel-sdm845/master/drivers/spi/spidev.c
# diff spidev.c spidev_panda.c > patch
# git diff --no-index spidev.c spidev_panda.c
patch -o spidev_panda.c spidev.c -i patch

View File

View File

@@ -0,0 +1,25 @@
# How to use can_bit_transition.py to reverse engineer a single bit field
Let's say our goal is to find a brake pedal signal (caused by your foot pressing the brake pedal vs adaptive cruise control braking).
The following process will allow you to quickly find bits that are always 0 during a period of time (when you know you were not pressing the brake with your foot) and always 1 in a different period of time (when you know you were pressing the brake with your foot).
Open up a drive in cabana where you can find a place you used the brake pedal and another place where you did not use the brake pedal (and you can identify when you were on the brake pedal and when you were not). You may want to go out for a drive and put something in front of the camera after you put your foot on the brake and take it away before you take your foot off the brake so you can easily identify exactly when you had your foot on the brake based on the video in cabana. This is critical because this script needs the brake signal to always be high the entire time for one of the time frames. A 10 second time frame worked well for me.
I found a drive where I knew I was not pressing the brake between timestamp 50.0 thru 65.0 and I was pressing the brake between timestamp 69.0 thru 79.0. Determine what the timestamps are in cabana by plotting any message and putting your mouse over the plot at the location you want to discover the timestamp. The tool tip on mouse hover has the format: timestamp: value
Now download the log from cabana (Save Log button) and run the script passing in the timestamps
(replace csv file name with cabana log you downloaded and time ranges with your own)
```
./can_bit_transition.py ./honda_crv_ex_2017_can-1520354796875.csv 50.0-65.0 69.0-79.0
```
The script will output bits that were always low in the first time range and always high in the second time range (and vice versa)
```
id 17c 0 -> 1 at byte 4 bitmask 1
id 17c 0 -> 1 at byte 6 bitmask 32
id 221 1 -> 0 at byte 0 bitmask 4
id 1be 0 -> 1 at byte 0 bitmask 16
```
Now I go back to cabana and graph the above bits by searching for the message by id, double clicking on the appropriate bit, and then selecting "show plot". I already knew that message id 0x17c is both user brake and adaptive cruise control braking combined, so I plotted one of those signals along side 0x221 and 0x1be. By replaying a drive I could see that 0x221 was not a brake signal (when high at random times that did not correspond to braking). Next I looked at 0x1be and I found it was the brake pedal signal I was looking for (went high whenever I pressed the brake pedal and did not go high when adaptive cruise control was braking).

View File

@@ -0,0 +1,111 @@
#!/usr/bin/env python3
import csv
import sys
CSV_KEYS = {
"logger": {
"time": "Time",
"message_id": "MessageID",
"data": "Message",
"bus": "Bus"
},
"cabana": {
"time": "time",
"message_id": "addr",
"data": "data",
"bus": "bus"
}
}
class Message():
"""Details about a specific message ID."""
def __init__(self, message_id):
self.message_id = message_id
self.ones = [0] * 64 # bit set if 1 is always seen
self.zeros = [0] * 64 # bit set if 0 is always seen
def printBitDiff(self, other):
"""Prints bits that transition from always zero to always 1 and vice versa."""
for i in range(len(self.ones)):
zero_to_one = other.zeros[i] & self.ones[i]
if zero_to_one:
print('id %s 0 -> 1 at byte %d bitmask %d' % (self.message_id, i, zero_to_one))
one_to_zero = other.ones[i] & self.zeros[i]
if one_to_zero:
print('id %s 1 -> 0 at byte %d bitmask %d' % (self.message_id, i, one_to_zero))
class Info():
"""A collection of Messages."""
def __init__(self):
self.messages = {} # keyed by MessageID
def load(self, filename, start, end):
"""Given a CSV file, adds information about message IDs and their values."""
with open(filename, newline='') as inp:
reader = csv.DictReader(inp)
dtype = None
for row in reader:
if not len(row):
continue
if dtype is None:
dtype = "logger" if "Bus" in row else "cabana"
time = float(row[CSV_KEYS[dtype]["time"]])
bus = int(row[CSV_KEYS[dtype]["bus"]])
if time < start or bus > 127:
continue
elif time > end:
break
message_id = row[CSV_KEYS[dtype]["message_id"]]
if message_id.startswith('0x'):
message_id = message_id[2:] # remove leading '0x'
else:
message_id = hex(int(message_id))[2:] # old message IDs are in decimal
message_id = f'{bus}:{message_id}'
data = row[CSV_KEYS[dtype]["data"]]
if data.startswith('0x'):
data = data[2:] # remove leading '0x'
else:
data = data
new_message = False
if message_id not in self.messages:
self.messages[message_id] = Message(message_id)
new_message = True
message = self.messages[message_id]
bts = bytearray.fromhex(data)
for i in range(len(bts)):
ones = int(bts[i])
message.ones[i] = ones if new_message else message.ones[i] & ones
# Inverts the data and masks it to a byte to get the zeros as ones.
zeros = (~int(bts[i])) & 0xff
message.zeros[i] = zeros if new_message else message.zeros[i] & zeros
def PrintUnique(log_file, low_range, high_range):
# find messages with bits that are always low
start, end = list(map(float, low_range.split('-')))
low = Info()
low.load(log_file, start, end)
# find messages with bits that are always high
start, end = list(map(float, high_range.split('-')))
high = Info()
high.load(log_file, start, end)
# print messages that go from low to high
found = False
for message_id in sorted(high.messages, key=lambda x: int(x.split(':')[1], 16)):
if message_id in low.messages:
high.messages[message_id].printBitDiff(low.messages[message_id])
found = True
if not found:
print('No messages that transition from always low to always high found!')
if __name__ == "__main__":
if len(sys.argv) < 4:
print('Usage:\n%s log.csv <low-start>-<low-end> <high-start>-<high-end>' % sys.argv[0])
sys.exit(0)
PrintUnique(sys.argv[1], sys.argv[2], sys.argv[3])

43
panda/examples/can_logger.py Executable file
View File

@@ -0,0 +1,43 @@
#!/usr/bin/env python3
import csv
import time
from panda import Panda
def can_logger():
p = Panda()
try:
outputfile = open('output.csv', 'w')
csvwriter = csv.writer(outputfile)
# Write Header
csvwriter.writerow(['Bus', 'MessageID', 'Message', 'MessageLength', 'Time'])
print("Writing csv file output.csv. Press Ctrl-C to exit...\n")
bus0_msg_cnt = 0
bus1_msg_cnt = 0
bus2_msg_cnt = 0
start_time = time.time()
while True:
can_recv = p.can_recv()
for address, dat, src in can_recv:
csvwriter.writerow(
[str(src), str(hex(address)), f"0x{dat.hex()}", len(dat), str(time.time() - start_time)])
if src == 0:
bus0_msg_cnt += 1
elif src == 1:
bus1_msg_cnt += 1
elif src == 2:
bus2_msg_cnt += 1
print(f"Message Counts... Bus 0: {bus0_msg_cnt} Bus 1: {bus1_msg_cnt} Bus 2: {bus2_msg_cnt}", end='\r')
except KeyboardInterrupt:
print(f"\nNow exiting. Final message Counts... Bus 0: {bus0_msg_cnt} Bus 1: {bus1_msg_cnt} Bus 2: {bus2_msg_cnt}")
outputfile.close()
if __name__ == "__main__":
can_logger()

View File

@@ -0,0 +1,103 @@
# How to use can_unique.py to reverse engineer a single bit field
Let's say our goal is to find the CAN message indicating that the driver's door is either open or closed.
The following process is great for simple single-bit messages.
However for frequently changing values, such as RPM or speed, Cabana's graphical plots are probably better to use.
First record a few minutes of background CAN messages with all the doors closed and save it in background.csv:
```
./can_logger.py
mv output.csv background.csv
```
Then run can_logger.py for a few seconds while performing the action you're interested, such as opening and then closing the
front-left door and save it as door-fl-1.csv
Repeat the process and save it as door-f1-2.csv to have an easy way to confirm any suspicions.
Now we'll use can_unique.py to look for unique bits:
```
$ ./can_unique.py door-fl-1.csv background*
id 820 new one at byte 2 bitmask 2
id 520 new one at byte 3 bitmask 7
id 520 new zero at byte 3 bitmask 8
id 520 new one at byte 5 bitmask 6
id 520 new zero at byte 5 bitmask 9
id 559 new zero at byte 6 bitmask 4
id 804 new one at byte 5 bitmask 2
id 804 new zero at byte 5 bitmask 1
$ ./can_unique.py door-fl-2.csv background*
id 672 new one at byte 3 bitmask 3
id 820 new one at byte 2 bitmask 2
id 520 new one at byte 3 bitmask 7
id 520 new zero at byte 3 bitmask 8
id 520 new one at byte 5 bitmask 6
id 520 new zero at byte 5 bitmask 9
id 559 new zero at byte 6 bitmask 4
```
One of these bits hopefully indicates that the driver's door is open.
Let's go through each message ID to figure out which one is correct.
We expect any correct bits to have changed in both runs.
We can rule out 804 because it only occurred in the first run.
We can rule out 672 because it only occurred in the second run.
That leaves us with these message IDs: 820, 520, 559. Let's take a closer look at each one.
```
$ fgrep ,559, door-fl-1.csv |head
0,559,00ff0000000024f0
0,559,00ff000000004464
0,559,00ff0000000054a9
0,559,00ff0000000064e3
0,559,00ff00000000742e
0,559,00ff000000008451
0,559,00ff00000000949c
0,559,00ff00000000a4d6
0,559,00ff00000000b41b
0,559,00ff00000000c442
```
Message ID 559 looks like an incrementing value, so it's not what we're looking for.
```
$ fgrep ,520, door-fl-2.csv
0,520,26ff00f8a1890000
0,520,26ff00f8a2890000
0,520,26ff00f8a2890000
0,520,26ff00f8a1890000
0,520,26ff00f8a2890000
0,520,26ff00f8a1890000
0,520,26ff00f8a2890000
0,520,26ff00f8a1890000
0,520,26ff00f8a2890000
0,520,26ff00f8a1890000
0,520,26ff00f8a2890000
0,520,26ff00f8a1890000
```
Message ID 520 oscillates between two values. However I only opened and closed the door once, so this is probably not it.
```
$ fgrep ,820, door-fl-1.csv
0,820,44000100a500c802
0,820,44000100a500c803
0,820,44000300a500c803
0,820,44000300a500c802
0,820,44000300a500c802
0,820,44000300a500c802
0,820,44000100a500c802
0,820,44000100a500c802
0,820,44000100a500c802
```
Message ID 820 looks promising! It starts off at 44000100a500c802 when the door is closed.
When the door is open it goes to 44000300a500c802.
Then when the door is closed again, it goes back to 44000100a500c802.
Let's confirm by looking at the data from our other run:
```
$ fgrep ,820, door-fl-2.csv
0,820,44000100a500c802
0,820,44000300a500c802
0,820,44000100a500c802
```
Perfect! We now know that message id 820 at byte 2 bitmask 2 is set if the driver's door is open.
If we repeat the process with the front passenger's door,
then we'll find that message id 820 at byte 2 bitmask 4 is set if the front-right door is open.
This confirms our finding because it's common for similar signals to be near each other.

116
panda/examples/can_unique.py Executable file
View File

@@ -0,0 +1,116 @@
#!/usr/bin/env python3
# Given an interesting CSV file of CAN messages and a list of background CAN
# messages, print which bits in the interesting file have never appeared
# in the background files.
# Expects the CSV file to be in one of the following formats:
# can_logger.py
# Bus,MessageID,Message,MessageLength
# 0,0x292,0x040000001068,6
# The old can_logger.py format is also supported:
# Bus,MessageID,Message
# 0,344,c000c00000000000
# Cabana "Save Log" format
# time,addr,bus,data
# 240.47911496100002,53,0,0acc0ade0074bf9e
import csv
import sys
class Message():
"""Details about a specific message ID."""
def __init__(self, message_id):
self.message_id = message_id
self.data = {} # keyed by hex string encoded message data
self.ones = [0] * 64 # bit set if 1 is seen
self.zeros = [0] * 64 # bit set if 0 has been seen
def printBitDiff(self, other):
"""Prints bits that are set or cleared compared to other background."""
for i in range(len(self.ones)):
new_ones = ((~other.ones[i]) & 0xff) & self.ones[i]
if new_ones:
print('id %s new one at byte %d bitmask %d' % (
self.message_id, i, new_ones))
new_zeros = ((~other.zeros[i]) & 0xff) & self.zeros[i]
if new_zeros:
print('id %s new zero at byte %d bitmask %d' % (
self.message_id, i, new_zeros))
class Info():
"""A collection of Messages."""
def __init__(self):
self.messages = {} # keyed by MessageID
def load(self, filename):
"""Given a CSV file, adds information about message IDs and their values."""
with open(filename) as inp:
reader = csv.reader(inp)
header = next(reader, None)
if header[0] == 'time':
self.cabana(reader)
else:
self.logger(reader)
def cabana(self, reader):
for row in reader:
bus = row[2]
message_id = hex(int(row[1]))[2:]
message_id = f'{bus}:{message_id}'
data = row[3]
self.store(message_id, data)
def logger(self, reader):
for row in reader:
bus = row[0]
if row[1].startswith('0x'):
message_id = row[1][2:] # remove leading '0x'
else:
message_id = hex(int(row[1]))[2:] # old message IDs are in decimal
message_id = f'{bus}:{message_id}'
if row[1].startswith('0x'):
data = row[2][2:] # remove leading '0x'
else:
data = row[2]
self.store(message_id, data)
def store(self, message_id, data):
if message_id not in self.messages:
self.messages[message_id] = Message(message_id)
message = self.messages[message_id]
if data not in self.messages[message_id].data:
message.data[data] = True
bts = bytearray.fromhex(data)
for i in range(len(bts)):
message.ones[i] = message.ones[i] | int(bts[i])
# Inverts the data and masks it to a byte to get the zeros as ones.
message.zeros[i] = message.zeros[i] | ((~int(bts[i])) & 0xff)
def PrintUnique(interesting_file, background_files):
background = Info()
for background_file in background_files:
background.load(background_file)
interesting = Info()
interesting.load(interesting_file)
for message_id in sorted(interesting.messages):
if message_id not in background.messages:
print('New message_id: %s' % message_id)
else:
interesting.messages[message_id].printBitDiff(
background.messages[message_id])
if __name__ == "__main__":
if len(sys.argv) < 3:
print('Usage:\n%s interesting.csv background*.csv' % sys.argv[0])
sys.exit(0)
PrintUnique(sys.argv[1], sys.argv[2:])

View File

@@ -0,0 +1,121 @@
#!/usr/bin/env python3
import argparse
from tqdm import tqdm
from iqdbc.car.carlog import carlog
from iqdbc.car.uds import UdsClient, MessageTimeoutError, NegativeResponseError, InvalidSubAddressError, \
SESSION_TYPE, DATA_IDENTIFIER_TYPE
from iqdbc.car.structs import CarParams
from panda import Panda
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--rxoffset", default="")
parser.add_argument("--nonstandard", action="store_true")
parser.add_argument("--no-obd", action="store_true", help="Bus 1 will not be multiplexed to the OBD-II port")
parser.add_argument("--no-29bit", action="store_true", help="29 bit addresses will not be queried")
parser.add_argument("--debug", action="store_true")
parser.add_argument("--addr")
parser.add_argument("--sub_addr", "--subaddr", help="A hex sub-address or `scan` to scan the full sub-address range")
parser.add_argument("--bus")
parser.add_argument('-s', '--serial', help="Serial number of panda to use")
args = parser.parse_args()
if args.debug:
carlog.setLevel('DEBUG')
if args.addr:
addrs = [int(args.addr, base=16)]
else:
addrs = [0x700 + i for i in range(256)]
if not args.no_29bit:
addrs += [0x18da0000 + (i << 8) + 0xf1 for i in range(256)]
results = {}
sub_addrs: list[int | None] = [None]
if args.sub_addr:
if args.sub_addr == "scan":
sub_addrs = list(range(0xff + 1))
else:
sub_addrs = [int(args.sub_addr, base=16)]
if sub_addrs[0] > 0xff: # type: ignore
print(f"Invalid sub-address: 0x{sub_addrs[0]:X}, needs to be in range 0x0 to 0xff")
parser.print_help()
exit()
uds_data_ids = {}
for std_id in DATA_IDENTIFIER_TYPE:
uds_data_ids[std_id.value] = std_id.name
if args.nonstandard:
for uds_id in range(0xf100, 0xf180):
uds_data_ids[uds_id] = "IDENTIFICATION_OPTION_VEHICLE_MANUFACTURER_SPECIFIC_DATA_IDENTIFIER"
for uds_id in range(0xf1a0, 0xf1f0):
uds_data_ids[uds_id] = "IDENTIFICATION_OPTION_VEHICLE_MANUFACTURER_SPECIFIC"
for uds_id in range(0xf1f0, 0xf200):
uds_data_ids[uds_id] = "IDENTIFICATION_OPTION_SYSTEM_SUPPLIER_SPECIFIC"
panda_serials = Panda.list()
if args.serial is None and len(panda_serials) > 1:
print("\nMultiple pandas found, choose one:")
for serial in panda_serials:
with Panda(serial) as panda:
print(f" {serial}: internal={panda.is_internal()}")
print()
parser.print_help()
exit()
panda = Panda(serial=args.serial)
panda.set_safety_mode(CarParams.SafetyModel.elm327, 1 if args.no_obd else 0)
print("querying addresses ...")
with tqdm(addrs) as t:
for addr in t:
# skip functional broadcast addrs
if addr == 0x7df or addr == 0x18db33f1:
continue
if args.bus:
bus = int(args.bus)
else:
bus = 1
rx_addr = addr + int(args.rxoffset, base=16) if args.rxoffset else None
# Try all sub-addresses for addr. By default, this is None
for sub_addr in sub_addrs:
sub_addr_str = hex(sub_addr) if sub_addr is not None else None
t.set_description(f"{hex(addr)}, {sub_addr_str}")
uds_client = UdsClient(panda, addr, rx_addr, bus, sub_addr=sub_addr, timeout=0.2)
# Check for anything alive at this address, and switch to the highest
# available diagnostic session without security access
try:
uds_client.tester_present()
uds_client.diagnostic_session_control(SESSION_TYPE.DEFAULT)
uds_client.diagnostic_session_control(SESSION_TYPE.EXTENDED_DIAGNOSTIC)
except NegativeResponseError:
pass
except MessageTimeoutError:
continue
except InvalidSubAddressError as e:
print(f'*** Skipping address {hex(addr)}: {e}')
break
# Run queries against all standard UDS data identifiers, plus selected
# non-standardized identifier ranges if requested
resp = {}
for uds_data_id in sorted(uds_data_ids):
try:
data = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE(uds_data_id))
if data:
resp[uds_data_id] = data
except (NegativeResponseError, MessageTimeoutError, InvalidSubAddressError):
pass
if resp.keys():
results[(addr, sub_addr)] = resp
if len(results.items()):
for (addr, sub_addr), resp in results.items():
sub_addr_str = f", sub-address 0x{sub_addr:X}" if sub_addr is not None else ""
print(f"\n\n*** Results for address 0x{addr:X}{sub_addr_str} ***\n\n")
for rid, dat in resp.items():
print(f"0x{rid:02X} {uds_data_ids[rid]}: {dat}")
else:
print("no fw versions found!")

View File

@@ -0,0 +1,58 @@
#!/usr/bin/env python3
import time
import struct
from iqdbc.car.structs import CarParams
from panda import Panda
from hexdump import hexdump
from iqdbc.car.isotp import isotp_send, isotp_recv
# 0x7e0 = Toyota
# 0x18DB33F1 for Honda?
def get_current_data_for_pid(pid):
# 01 xx = Show current data
isotp_send(panda, b"\x01" + bytes([pid]), 0x7e0)
return isotp_recv(panda, 0x7e8)
def get_supported_pids():
ret = []
pid = 0
while 1:
supported = struct.unpack(">I", get_current_data_for_pid(pid)[2:])[0]
for i in range(1 + pid, 0x21 + pid):
if supported & 0x80000000:
ret.append(i)
supported <<= 1
pid += 0x20
if pid not in ret:
break
return ret
if __name__ == "__main__":
panda = Panda()
panda.set_safety_mode(CarParams.SafetyModel.elm327)
panda.can_clear(0)
# 09 02 = Get VIN
isotp_send(panda, b"\x09\x02", 0x7df)
ret = isotp_recv(panda, 0x7e8)
hexdump(ret)
print("VIN: %s" % "".join(map(chr, ret[:2])))
# 03 = get DTCS
isotp_send(panda, b"\x03", 0x7e0)
dtcs = isotp_recv(panda, 0x7e8)
print("DTCs:", "".join(map(chr, dtcs[:2])))
supported_pids = get_supported_pids()
print("Supported PIDs:", supported_pids)
while 1:
speed = struct.unpack(">B", get_current_data_for_pid(13)[2:])[0] # kph
rpm = struct.unpack(">H", get_current_data_for_pid(12)[2:])[0] / 4.0 # revs
throttle = struct.unpack(">B", get_current_data_for_pid(17)[2:])[0] / 255.0 * 100 # percent
temp = struct.unpack(">B", get_current_data_for_pid(5)[2:])[0] - 40 # degrees C
load = struct.unpack(">B", get_current_data_for_pid(4)[2:])[0] / 255.0 * 100 # percent
print("%d KPH, %d RPM, %.1f%% Throttle, %d deg C, %.1f%% load" % (speed, rpm, throttle, temp, load))
time.sleep(0.2)

51
panda/examples/tesla_tester.py Executable file
View File

@@ -0,0 +1,51 @@
#!/usr/bin/env python3
import binascii
from iqdbc.car.structs import CarParams
from panda import Panda
def tesla_tester():
p = Panda()
body_bus_speed = 125 # Tesla Body busses (B, BF) are 125kbps, rest are 500kbps
body_bus_num = 1 # My TDC to OBD adapter has PT on bus0 BDY on bus1 and CH on bus2
p.set_can_speed_kbps(body_bus_num, body_bus_speed)
# Now set the panda from its default of SAFETY_SILENT (read only) to SAFETY_ALLOUTPUT
# Careful, as this will let us send any CAN messages we want (which could be very bad!)
print("Setting Panda to output mode...")
p.set_safety_mode(CarParams.SafetyModel.allOutput)
# BDY 0x248 is the MCU_commands message, which includes folding mirrors, opening the trunk, frunk, setting the cars lock state and more.
# For our test, we will edit the 3rd byte, which is MCU_lockRequest. 0x01 will lock, 0x02 will unlock:
print("Unlocking Tesla...")
p.can_send(0x248, b"\x00\x00\x02\x00\x00\x00\x00\x00", body_bus_num)
#Or, we can set the first byte, MCU_frontHoodCommand + MCU_liftgateSwitch, to 0x01 to pop the frunk, or 0x04 to open/close the trunk (0x05 should open both)
print("Opening Frunk...")
p.can_send(0x248, b"\x01\x00\x00\x00\x00\x00\x00\x00", body_bus_num)
#Back to safety...
print("Disabling output on Panda...")
p.set_safety_mode(CarParams.SafetyModel.silent)
print("Reading VIN from 0x568. This is painfully slow and can take up to 3 minutes (1 minute per message; 3 messages needed for full VIN)...")
vin = {}
while True:
#Read the VIN
can_recv = p.can_recv()
for address, dat, src in can_recv:
if src == body_bus_num:
if address == 1384: # 0x568 is VIN
vin_index = int(binascii.hexlify(dat)[:2]) # first byte is the index, 00, 01, 02
vin_string = binascii.hexlify(dat)[2:] # rest of the string is the actual VIN data
vin[vin_index] = vin_string.decode("hex")
print("Got VIN index " + str(vin_index) + " data " + vin[vin_index])
#if we have all 3 parts of the VIN, print it and break out of our while loop
if 0 in vin and 1 in vin and 2 in vin:
print("VIN: " + vin[0] + vin[1] + vin[2][:3])
break
if __name__ == "__main__":
tesla_tester()

75
panda/pyproject.toml Normal file
View File

@@ -0,0 +1,75 @@
[project]
name = "pandacan"
version = "0.0.10"
description = "Code powering the comma.ai panda"
readme = "README.md"
requires-python = ">=3.11,<3.13" # macOS doesn't work with 3.13 due to pycapnp from iqdbc
license = {text = "MIT"}
authors = [{name = "comma.ai"}]
classifiers = [
"Natural Language :: English",
"Programming Language :: Python :: 3",
"Topic :: System :: Hardware",
]
dependencies = [
"libusb1",
"iqdbc @ git+https://git.konn3kt.com/teal/iqdbc.git@master#egg=iqdbc",
]
[project.optional-dependencies]
dev = [
"scons",
"pycryptodome >= 3.9.8",
"cffi",
"flaky",
"pytest",
"pytest-mock",
"pytest-timeout",
"pytest-randomly",
"ruff",
"mypy",
"setuptools",
"spidev; platform_system == 'Linux'",
]
[build-system]
requires = ["setuptools>=61", "wheel"]
build-backend = "setuptools.build_meta"
[tool.setuptools]
packages = ["panda"]
[tool.setuptools.package-dir]
panda = "."
[tool.mypy]
# third-party packages
ignore_missing_imports = true
# helpful warnings
warn_redundant_casts = true
warn_unreachable = true
warn_unused_ignores = true
# restrict dynamic typing
warn_return_any = true
# https://beta.ruff.rs/docs/configuration/#using-pyprojecttoml
[tool.ruff]
line-length = 160
target-version="py311"
[tool.ruff.lint]
select = ["E", "F", "W", "PIE", "C4", "ISC", "RUF100", "A"]
ignore = ["W292", "E741", "E402", "C408", "ISC003"]
flake8-implicit-str-concat.allow-multiline=false
[tool.ruff.lint.flake8-tidy-imports.banned-api]
"pytest.main".msg = "pytest.main requires special handling that is easy to mess up!"
[tool.pytest.ini_options]
addopts = "-Werror --strict-config --strict-markers --durations=10 --ignore-glob='*.sh' --ignore=tests/misra --ignore=tests/som --ignore=tests/hitl"
python_files = "test_*.py"
testpaths = [
"tests/"
]

847
panda/python/__init__.py Normal file
View File

@@ -0,0 +1,847 @@
# python library to interface with panda
import os
import sys
import time
import usb1
import struct
import hashlib
import binascii
from functools import wraps, partial
from itertools import accumulate
from iqdbc.car.structs import CarParams
from .base import BaseHandle
from .constants import FW_PATH, McuType
from .dfu import PandaDFU
from .spi import PandaSpiHandle, PandaSpiException, PandaProtocolMismatch
from .usb import PandaUsbHandle
from .utils import logger
__version__ = '0.0.10'
CANPACKET_HEAD_SIZE = 0x6
DLC_TO_LEN = [0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 16, 20, 24, 32, 48, 64]
LEN_TO_DLC = {length: dlc for (dlc, length) in enumerate(DLC_TO_LEN)}
PANDA_CAN_CNT = 3
def calculate_checksum(data):
res = 0
for b in data:
res ^= b
return res
def pack_can_buffer(arr, chunk=False, fd=False):
snds = [bytearray(), ]
for address, dat, bus in arr:
extended = 1 if address >= 0x800 else 0
data_len_code = LEN_TO_DLC[len(dat)]
header = bytearray(CANPACKET_HEAD_SIZE)
word_4b = (address << 3) | (extended << 2)
header[0] = (data_len_code << 4) | (bus << 1) | int(fd)
header[1] = word_4b & 0xFF
header[2] = (word_4b >> 8) & 0xFF
header[3] = (word_4b >> 16) & 0xFF
header[4] = (word_4b >> 24) & 0xFF
header[5] = calculate_checksum(header[:5] + dat)
snds[-1].extend(header)
snds[-1].extend(dat)
if chunk and len(snds[-1]) > 256:
snds.append(bytearray())
return snds
def unpack_can_buffer(dat):
ret = []
while len(dat) >= CANPACKET_HEAD_SIZE:
data_len = DLC_TO_LEN[(dat[0]>>4)]
header = dat[:CANPACKET_HEAD_SIZE]
bus = (header[0] >> 1) & 0x7
address = (header[4] << 24 | header[3] << 16 | header[2] << 8 | header[1]) >> 3
if (header[1] >> 1) & 0x1:
# returned
bus += 128
if header[1] & 0x1:
# rejected
bus += 192
# we need more from the next transfer
if data_len > len(dat) - CANPACKET_HEAD_SIZE:
break
assert calculate_checksum(dat[:(CANPACKET_HEAD_SIZE+data_len)]) == 0, "CAN packet checksum incorrect"
data = dat[CANPACKET_HEAD_SIZE:(CANPACKET_HEAD_SIZE+data_len)]
dat = dat[(CANPACKET_HEAD_SIZE+data_len):]
ret.append((address, data, bus))
return (ret, dat)
def ensure_version(desc, lib_field, panda_field, fn):
@wraps(fn)
def wrapper(self, *args, **kwargs):
lib_version = getattr(self, lib_field)
panda_version = getattr(self, panda_field)
if lib_version != panda_version:
raise RuntimeError(f"{desc} packet version mismatch: panda's firmware v{panda_version}, library v{lib_version}. Reflash panda.")
return fn(self, *args, **kwargs)
return wrapper
ensure_can_packet_version = partial(ensure_version, "CAN", "CAN_PACKET_VERSION", "can_version")
ensure_can_health_packet_version = partial(ensure_version, "CAN health", "CAN_HEALTH_PACKET_VERSION", "can_health_version")
ensure_health_packet_version = partial(ensure_version, "health", "HEALTH_PACKET_VERSION", "health_version")
def _is_lite_hw() -> bool:
return os.environ.get('LITE') == '1' or os.path.exists('/tmp/lite_hw')
class Panda:
SERIAL_DEBUG = 0
SERIAL_SOM_DEBUG = 4
USB_VIDS = (0xbbaa, 0x3801) # 0x3801 is comma's registered VID
USB_PIDS = (0xddee, 0xddcc)
REQUEST_IN = usb1.ENDPOINT_IN | usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE
REQUEST_OUT = usb1.ENDPOINT_OUT | usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE
# from https://github.com/commaai/openpilot/blob/103b4df18cbc38f4129555ab8b15824d1a672bdf/cereal/log.capnp#L648
HW_TYPE_UNKNOWN = b'\x00'
HW_TYPE_WHITE = b'\x01'
HW_TYPE_GREY_PANDA = b'\x02'
HW_TYPE_BLACK = b'\x03'
HW_TYPE_PEDAL = b'\x04'
HW_TYPE_UNO = b'\x05'
HW_TYPE_DOS = b'\x06'
HW_TYPE_RED_PANDA = b'\x07'
HW_TYPE_RED_PANDA_V2 = b'\x08'
HW_TYPE_TRES = b'\x09'
HW_TYPE_CUATRO = b'\x0a'
HW_TYPE_BODY = b'\xb1'
CAN_PACKET_VERSION = 4
HEALTH_PACKET_VERSION = 17
CAN_HEALTH_PACKET_VERSION = 5
HEALTH_STRUCT = struct.Struct("<IIIIIIIIBBBBBHBBBHfBBHHHB")
CAN_HEALTH_STRUCT = struct.Struct("<BIBBBBBBBBIIIIIIIHHBBBIIII")
F4_DEVICES = [HW_TYPE_WHITE, HW_TYPE_BLACK, HW_TYPE_DOS]
H7_DEVICES = [HW_TYPE_RED_PANDA, HW_TYPE_TRES, HW_TYPE_CUATRO, HW_TYPE_BODY]
SUPPORTED_DEVICES = H7_DEVICES + F4_DEVICES + [HW_TYPE_GREY_PANDA, HW_TYPE_PEDAL, HW_TYPE_UNO, HW_TYPE_RED_PANDA_V2]
INTERNAL_DEVICES = (HW_TYPE_DOS, HW_TYPE_TRES, HW_TYPE_CUATRO)
MAX_FAN_RPMs = {
HW_TYPE_DOS: 6500,
HW_TYPE_TRES: 6600,
HW_TYPE_CUATRO: 12500,
}
HARNESS_STATUS_NC = 0
HARNESS_STATUS_NORMAL = 1
HARNESS_STATUS_FLIPPED = 2
def __init__(self, serial: str | None = None, claim: bool = True, disable_checks: bool = True, can_speed_kbps: int = 500, cli: bool = True):
self._disable_checks = disable_checks
self._handle: BaseHandle
self._handle_open = False
self.can_rx_overflow_buffer = b''
self._can_speed_kbps = can_speed_kbps
if cli and serial is None:
self._connect_serial = self._cli_select_panda()
else:
self._connect_serial = serial
# connect and set mcu type
self.connect(claim)
def _cli_select_panda(self):
dfu_pandas = PandaDFU.list()
if len(dfu_pandas) > 0:
print("INFO: some attached pandas are in DFU mode.")
pandas = self.list()
if len(pandas) == 0:
print("INFO: panda not available")
return None
if len(pandas) == 1:
print(f"INFO: connecting to panda {pandas[0]}")
return pandas[0]
while True:
print("Multiple pandas available:")
pandas.sort()
for idx, serial in enumerate(pandas):
print(f"{[idx]}: {serial}")
try:
choice = int(input("Choose serial [0]:") or "0")
return pandas[choice]
except (ValueError, IndexError):
print("Enter a valid index.")
def __enter__(self):
return self
def __exit__(self, *args):
self.close()
def close(self):
if self._handle_open:
self._handle.close()
self._handle_open = False
if self._context is not None:
self._context.close()
def connect(self, claim=True, wait=False):
self.close()
self._handle = None
while self._handle is None:
# try USB first, then SPI
self._context, self._handle, serial, self.bootstub, bcd = self.usb_connect(self._connect_serial, claim=claim, no_error=wait)
if self._handle is None:
self._context, self._handle, serial, self.bootstub, bcd = self.spi_connect(self._connect_serial)
if not wait:
break
if self._handle is None:
raise Exception("failed to connect to panda")
self._bcd_hw_type = None
ret = self._handle.controlRead(Panda.REQUEST_IN, 0xc1, 0, 0, 0x40)
missing_hw_type_endpoint = self.bootstub and ret.startswith(b'\xff\x00\xc1\x3e\xde\xad\xd0\x0d')
if missing_hw_type_endpoint and bcd is not None:
self._bcd_hw_type = bcd
self._assume_f4_mcu = (self._bcd_hw_type is None) and missing_hw_type_endpoint
self._serial = serial
self._connect_serial = serial
self._handle_open = True
self._mcu_type = self.get_mcu_type()
self.health_version, self.can_version, self.can_health_version = self.get_packets_versions()
logger.debug("connected")
hw_type = self.get_type()
if hw_type not in self.SUPPORTED_DEVICES:
print("WARNING: Using deprecated HW")
# disable openpilot's heartbeat checks
if self._disable_checks:
self.set_heartbeat_disabled()
self.set_power_save(0)
# reset comms
self.can_reset_communications()
# disable automatic CAN-FD switching
for bus in range(PANDA_CAN_CNT):
self.set_canfd_auto(bus, False)
# set CAN speed
for bus in range(PANDA_CAN_CNT):
self.set_can_speed_kbps(bus, self._can_speed_kbps)
@property
def spi(self) -> bool:
return isinstance(self._handle, PandaSpiHandle)
@classmethod
def spi_connect(cls, serial, ignore_version=False):
try:
handle = PandaSpiHandle()
dat = handle.get_protocol_version()
except PandaSpiException:
return None, None, None, False, None
spi_serial = binascii.hexlify(dat[:12]).decode()
pid = dat[13]
if pid not in (0xcc, 0xee):
raise PandaProtocolMismatch(f"invalid bootstub status ({pid=}). reflash panda")
bootstub = pid == 0xee
spi_version = dat[14]
# did we get the right panda?
if serial is not None and spi_serial != serial:
return None, None, None, False, None
# ensure our protocol version matches the panda
if (not ignore_version) and spi_version != handle.PROTOCOL_VERSION:
raise PandaProtocolMismatch(f"panda protocol mismatch: expected {handle.PROTOCOL_VERSION}, got {spi_version}. reflash panda")
# got a device and all good
return None, handle, spi_serial, bootstub, None
@classmethod
def usb_connect(cls, serial, claim=True, no_error=False):
handle, usb_serial, bootstub, bcd = None, None, None, None
context = usb1.USBContext()
context.open()
try:
for device in context.getDeviceList(skip_on_error=True):
if device.getVendorID() in cls.USB_VIDS and device.getProductID() in cls.USB_PIDS:
try:
this_serial = device.getSerialNumber()
except Exception:
# Allow to ignore errors on reconnect. USB hubs need some time to initialize after panda reset
if not no_error:
logger.exception("failed to get serial number of panda")
continue
if serial is None or this_serial == serial:
logger.debug("opening device %s %s", this_serial, hex(device.getProductID()))
usb_serial = this_serial
bootstub = (device.getProductID() & 0xF0) == 0xe0
handle = device.open()
if sys.platform not in ("win32", "cygwin", "msys", "darwin"):
handle.setAutoDetachKernelDriver(True)
if claim:
handle.claimInterface(0)
# handle.setInterfaceAltSetting(0, 0) # Issue in USB stack
this_bcd = device.getbcdDevice()
if this_bcd is not None and this_bcd != 0x2300:
bcd = bytearray([this_bcd >> 8, ])
break
except Exception:
logger.exception("USB connect error")
usb_handle = None
if handle is not None:
usb_handle = PandaUsbHandle(handle)
else:
context.close()
return context, usb_handle, usb_serial, bootstub, bcd
def is_connected_spi(self):
return isinstance(self._handle, PandaSpiHandle)
def is_connected_usb(self):
return isinstance(self._handle, PandaUsbHandle)
@classmethod
def list(cls, usb_only: bool = False):
ret = cls.usb_list()
if not usb_only:
ret += cls.spi_list()
return list(set(ret))
@classmethod
def usb_list(cls):
ret = []
try:
with usb1.USBContext() as context:
for device in context.getDeviceList(skip_on_error=True):
if device.getVendorID() in cls.USB_VIDS and device.getProductID() in cls.USB_PIDS:
try:
serial = device.getSerialNumber()
if len(serial) == 24:
ret.append(serial)
else:
logger.warning(f"found device with panda descriptors but invalid serial: {serial}", RuntimeWarning)
except Exception:
logger.exception("error connecting to panda")
except Exception:
logger.exception("exception while listing pandas")
return ret
@classmethod
def spi_list(cls):
_, _, serial, _, _ = cls.spi_connect(None, ignore_version=True)
if serial is not None:
return [serial, ]
return []
def reset(self, enter_bootstub=False, enter_bootloader=False, reconnect=True):
# no response is expected since it resets right away
timeout = 5000 if isinstance(self._handle, PandaSpiHandle) else 15000
try:
if enter_bootloader:
self._handle.controlWrite(Panda.REQUEST_IN, 0xd1, 0, 0, b'', timeout=timeout, expect_disconnect=True)
else:
if enter_bootstub:
self._handle.controlWrite(Panda.REQUEST_IN, 0xd1, 1, 0, b'', timeout=timeout, expect_disconnect=True)
else:
self._handle.controlWrite(Panda.REQUEST_IN, 0xd8, 0, 0, b'', timeout=timeout, expect_disconnect=True)
except Exception:
pass
self.close()
if not enter_bootloader and reconnect:
self.reconnect()
@property
def connected(self) -> bool:
return self._handle_open
def reconnect(self):
if self._handle_open:
self.close()
success = False
# wait up to 15 seconds
for _ in range(15*10):
try:
self.connect(claim=False, wait=True)
success = True
break
except Exception:
pass
time.sleep(0.1)
if not success:
raise Exception("reconnect failed")
@staticmethod
def flasher_present(handle: BaseHandle) -> bool:
fr = handle.controlRead(Panda.REQUEST_IN, 0xb0, 0, 0, 0xc)
return fr[4:8] == b"\xde\xad\xd0\x0d"
@staticmethod
def flash_static(handle, code, mcu_type):
assert mcu_type is not None, "must set valid mcu_type to flash"
# confirm flasher is present
assert Panda.flasher_present(handle)
# determine sectors to erase
apps_sectors_cumsum = accumulate(mcu_type.config.sector_sizes[1:])
last_sector = next((i + 1 for i, v in enumerate(apps_sectors_cumsum) if v > len(code)), -1)
assert last_sector >= 1, "Binary too small? No sector to erase."
assert last_sector < 7, "Binary too large! Risk of overwriting provisioning chunk."
# unlock flash
logger.info("flash: unlocking")
handle.controlWrite(Panda.REQUEST_IN, 0xb1, 0, 0, b'')
# erase sectors
logger.info(f"flash: erasing sectors 1 - {last_sector}")
for i in range(1, last_sector + 1):
handle.controlWrite(Panda.REQUEST_IN, 0xb2, i, 0, b'')
# flash over EP2
STEP = 0x200
logger.info("flash: flashing")
for i in range(0, len(code), STEP):
handle.bulkWrite(2, code[i:i + STEP])
# reset
logger.info("flash: resetting")
try:
handle.controlWrite(Panda.REQUEST_IN, 0xd8, 0, 0, b'', expect_disconnect=True)
except Exception:
pass
def flash(self, fn=None, code=None, reconnect=True):
if self.up_to_date(fn=fn):
logger.info("flash: already up to date")
return
hw_type = self.get_type()
if hw_type not in self.SUPPORTED_DEVICES:
if not (hw_type == Panda.HW_TYPE_UNKNOWN and _is_lite_hw()):
raise RuntimeError(f"HW type {hw_type.hex()} is deprecated and can no longer be flashed.")
if not fn:
fn = os.path.join(FW_PATH, self._mcu_type.config.app_fn)
assert os.path.isfile(fn)
logger.debug("flash: main version is %s", self.get_version())
if not self.bootstub:
self.reset(enter_bootstub=True)
assert(self.bootstub)
if code is None:
with open(fn, "rb") as f:
code = f.read()
# get version
logger.debug("flash: bootstub version is %s", self.get_version())
# do flash
Panda.flash_static(self._handle, code, mcu_type=self._mcu_type)
# reconnect
if reconnect:
self.reconnect()
def recover(self, timeout: int | None = 60, reset: bool = True) -> bool:
dfu_serial = self.get_dfu_serial()
if reset:
self.reset(enter_bootstub=True)
self.reset(enter_bootloader=True)
if not self.wait_for_dfu(dfu_serial, timeout=timeout):
return False
dfu = PandaDFU(dfu_serial)
dfu.recover()
# reflash after recover
self.connect(True, True)
self.flash()
return True
@staticmethod
def wait_for_dfu(dfu_serial: str | None, timeout: int | None = None) -> bool:
t_start = time.monotonic()
dfu_list = PandaDFU.list()
while (dfu_serial is None and len(dfu_list) == 0) or (dfu_serial is not None and dfu_serial not in dfu_list):
logger.debug("waiting for DFU...")
time.sleep(0.1)
if timeout is not None and (time.monotonic() - t_start) > timeout:
return False
dfu_list = PandaDFU.list()
return True
@classmethod
def wait_for_panda(cls, serial: str | None, timeout: int) -> bool:
t_start = time.monotonic()
serials = cls.list()
while (serial is None and len(serials) == 0) or (serial is not None and serial not in serials):
logger.debug("waiting for panda...")
time.sleep(0.1)
if timeout is not None and (time.monotonic() - t_start) > timeout:
return False
serials = cls.list()
return True
def up_to_date(self, fn=None) -> bool:
current = self.get_signature()
if fn is None:
fn = os.path.join(FW_PATH, self.get_mcu_type().config.app_fn)
expected = Panda.get_signature_from_firmware(fn)
return (current == expected)
def call_control_api(self, msg):
self._handle.controlWrite(Panda.REQUEST_OUT, msg, 0, 0, b'')
# ******************* health *******************
@ensure_health_packet_version
def health(self):
dat = self._handle.controlRead(Panda.REQUEST_IN, 0xd2, 0, 0, self.HEALTH_STRUCT.size)
a = self.HEALTH_STRUCT.unpack(dat)
return {
"uptime": a[0],
"voltage": a[1],
"current": a[2],
"safety_tx_blocked": a[3],
"safety_rx_invalid": a[4],
"tx_buffer_overflow": a[5],
"rx_buffer_overflow": a[6],
"faults": a[7],
"ignition_line": a[8],
"ignition_can": a[9],
"controls_allowed": a[10],
"car_harness_status": a[11],
"safety_mode": a[12],
"safety_param": a[13],
"fault_status": a[14],
"power_save_enabled": a[15],
"heartbeat_lost": a[16],
"alternative_experience": a[17],
"interrupt_load": a[18],
"fan_power": a[19],
"safety_rx_checks_invalid": a[20],
"spi_error_count": a[21],
"sbu1_voltage_mV": a[22],
"sbu2_voltage_mV": a[23],
"som_reset_triggered": a[24],
}
@ensure_can_health_packet_version
def can_health(self, can_number):
LEC_ERROR_CODE = {
0: "No error",
1: "Stuff error",
2: "Form error",
3: "AckError",
4: "Bit1Error",
5: "Bit0Error",
6: "CRCError",
7: "NoChange",
}
dat = self._handle.controlRead(Panda.REQUEST_IN, 0xc2, int(can_number), 0, self.CAN_HEALTH_STRUCT.size)
a = self.CAN_HEALTH_STRUCT.unpack(dat)
return {
"bus_off": a[0],
"bus_off_cnt": a[1],
"error_warning": a[2],
"error_passive": a[3],
"last_error": LEC_ERROR_CODE[a[4]],
"last_stored_error": LEC_ERROR_CODE[a[5]],
"last_data_error": LEC_ERROR_CODE[a[6]],
"last_data_stored_error": LEC_ERROR_CODE[a[7]],
"receive_error_cnt": a[8],
"transmit_error_cnt": a[9],
"total_error_cnt": a[10],
"total_tx_lost_cnt": a[11],
"total_rx_lost_cnt": a[12],
"total_tx_cnt": a[13],
"total_rx_cnt": a[14],
"total_fwd_cnt": a[15],
"total_tx_checksum_error_cnt": a[16],
"can_speed": a[17],
"can_data_speed": a[18],
"canfd_enabled": a[19],
"brs_enabled": a[20],
"canfd_non_iso": a[21],
"irq0_call_rate": a[22],
"irq1_call_rate": a[23],
"irq2_call_rate": a[24],
"can_core_reset_count": a[25],
}
# ******************* control *******************
def get_version(self):
return self._handle.controlRead(Panda.REQUEST_IN, 0xd6, 0, 0, 0x40).decode('utf8')
@staticmethod
def get_signature_from_firmware(fn) -> bytes:
with open(fn, 'rb') as f:
f.seek(-128, 2) # Seek from end of file
return f.read(128)
def get_signature(self) -> bytes:
part_1 = self._handle.controlRead(Panda.REQUEST_IN, 0xd3, 0, 0, 0x40)
part_2 = self._handle.controlRead(Panda.REQUEST_IN, 0xd4, 0, 0, 0x40)
return bytes(part_1 + part_2)
def get_type(self):
ret = self._handle.controlRead(Panda.REQUEST_IN, 0xc1, 0, 0, 0x40)
if self._bcd_hw_type is not None and (ret is None or len(ret) != 1):
ret = self._bcd_hw_type
return ret
# Returns tuple with health packet version and CAN packet/USB packet version
def get_packets_versions(self):
dat = self._handle.controlRead(Panda.REQUEST_IN, 0xdd, 0, 0, 3)
if dat and len(dat) == 3:
a = struct.unpack("BBB", dat)
return (a[0], a[1], a[2])
else:
return (0, 0, 0)
def get_mcu_type(self) -> McuType:
hw_type = self.get_type()
if hw_type in Panda.F4_DEVICES:
return McuType.F4
elif hw_type in Panda.H7_DEVICES:
return McuType.H7
else:
if self._assume_f4_mcu:
return McuType.F4
# C3 Lite (mr.one clone): panda has STM32F4 MCU (16-sector DFU layout = DOS board)
if hw_type == Panda.HW_TYPE_UNKNOWN and _is_lite_hw():
return McuType.F4
raise ValueError(f"unknown HW type: {hw_type}")
def is_internal(self):
hw_type = self.get_type()
if hw_type in Panda.INTERNAL_DEVICES:
return True
if hw_type == Panda.HW_TYPE_UNKNOWN and _is_lite_hw():
return True
return False
def get_serial(self):
"""
Returns the comma-issued dongle ID from our provisioning
"""
dat = self._handle.controlRead(Panda.REQUEST_IN, 0xd0, 0, 0, 0x20)
hashsig, calc_hash = dat[0x1c:], hashlib.sha1(dat[0:0x1c]).digest()[0:4]
assert(hashsig == calc_hash)
return [dat[0:0x10].decode("utf8"), dat[0x10:0x10 + 10].decode("utf8")]
def get_usb_serial(self):
"""
Returns the serial number reported from the USB descriptor;
matches the MCU UID
"""
return self._serial
def get_dfu_serial(self):
return PandaDFU.st_serial_to_dfu_serial(self._serial, self._mcu_type)
def get_uid(self):
"""
Returns the UID from the MCU
"""
dat = self._handle.controlRead(Panda.REQUEST_IN, 0xc3, 0, 0, 12)
return binascii.hexlify(dat).decode()
def get_secret(self):
return self._handle.controlRead(Panda.REQUEST_IN, 0xd0, 1, 0, 0x10)
def get_interrupt_call_rate(self, irqnum):
dat = self._handle.controlRead(Panda.REQUEST_IN, 0xc4, int(irqnum), 0, 4)
return struct.unpack("I", dat)[0]
# ******************* configuration *******************
def set_alternative_experience(self, alternative_experience, safety_param_iq=0):
self._handle.controlWrite(Panda.REQUEST_OUT, 0xdf, int(alternative_experience), int(safety_param_iq), b'')
def set_power_save(self, power_save_enabled=0):
self._handle.controlWrite(Panda.REQUEST_OUT, 0xe7, int(power_save_enabled), 0, b'')
def set_safety_mode(self, mode=CarParams.SafetyModel.silent, param=0):
self._handle.controlWrite(Panda.REQUEST_OUT, 0xdc, mode, param, b'')
def set_obd(self, obd):
self._handle.controlWrite(Panda.REQUEST_OUT, 0xdb, int(obd), 0, b'')
def set_can_loopback(self, enable):
# set can loopback mode for all buses
self._handle.controlWrite(Panda.REQUEST_OUT, 0xe5, int(enable), 0, b'')
def set_can_enable(self, bus_num, enable):
# sets the can transceiver enable pin
self._handle.controlWrite(Panda.REQUEST_OUT, 0xf4, int(bus_num), int(enable), b'')
def set_can_speed_kbps(self, bus, speed):
self._handle.controlWrite(Panda.REQUEST_OUT, 0xde, bus, int(speed * 10), b'')
def set_can_data_speed_kbps(self, bus, speed):
self._handle.controlWrite(Panda.REQUEST_OUT, 0xf9, bus, int(speed * 10), b'')
def set_canfd_non_iso(self, bus, non_iso):
self._handle.controlWrite(Panda.REQUEST_OUT, 0xfc, bus, int(non_iso), b'')
def set_canfd_auto(self, bus, auto):
self._handle.controlWrite(Panda.REQUEST_OUT, 0xe8, bus, int(auto), b'')
def set_uart_baud(self, uart, rate):
self._handle.controlWrite(Panda.REQUEST_OUT, 0xe4, uart, int(rate / 300), b'')
def set_uart_parity(self, uart, parity):
# parity, 0=off, 1=even, 2=odd
self._handle.controlWrite(Panda.REQUEST_OUT, 0xe2, uart, parity, b'')
def set_uart_callback(self, uart, install):
self._handle.controlWrite(Panda.REQUEST_OUT, 0xe3, uart, int(install), b'')
# ******************* can *******************
# The panda will NAK CAN writes when there is CAN congestion.
# libusb will try to send it again, with a max timeout.
# Timeout is in ms. If set to 0, the timeout is infinite.
CAN_SEND_TIMEOUT_MS = 10
def can_reset_communications(self):
self._handle.controlWrite(Panda.REQUEST_OUT, 0xc0, 0, 0, b'')
@ensure_can_packet_version
def can_send_many(self, arr, *, fd=False, timeout=CAN_SEND_TIMEOUT_MS):
snds = pack_can_buffer(arr, chunk=(not self.spi), fd=fd)
for tx in snds:
while len(tx) > 0:
bs = self._handle.bulkWrite(3, tx, timeout=timeout)
tx = tx[bs:]
def can_send(self, addr, dat, bus, *, fd=False, timeout=CAN_SEND_TIMEOUT_MS):
self.can_send_many([[addr, dat, bus]], fd=fd, timeout=timeout)
@ensure_can_packet_version
def can_recv(self):
dat = bytearray()
while True:
try:
dat = self._handle.bulkRead(1, 16384) # Max receive batch size + 2 extra reserve frames
break
except (usb1.USBErrorIO, usb1.USBErrorOverflow):
logger.error("CAN: BAD RECV, RETRYING")
time.sleep(0.1)
msgs, self.can_rx_overflow_buffer = unpack_can_buffer(self.can_rx_overflow_buffer + dat)
return msgs
def can_clear(self, bus):
"""Clears all messages from the specified internal CAN ringbuffer as
though it were drained.
Args:
bus (int): can bus number to clear a tx queue, or 0xFFFF to clear the
global can rx queue.
"""
self._handle.controlWrite(Panda.REQUEST_OUT, 0xf1, bus, 0, b'')
# ******************* serial *******************
def serial_read(self, port_number, maxlen=1024):
ret = b''
while 1:
r = bytes(self._handle.controlRead(Panda.REQUEST_IN, 0xe0, port_number, 0, 0x40))
if len(r) == 0 or len(ret) >= maxlen:
break
ret += r
return ret
def serial_write(self, port_number, ln):
ret = 0
if isinstance(ln, str):
ln = bytes(ln, 'utf-8')
for i in range(0, len(ln), 0x20):
ret += self._handle.bulkWrite(2, struct.pack("B", port_number) + ln[i:i + 0x20])
return ret
def send_heartbeat(self, engaged=True, engaged_aol=True):
self._handle.controlWrite(Panda.REQUEST_OUT, 0xf3, engaged, engaged_aol, b'')
# disable heartbeat checks for use outside of openpilot
# sending a heartbeat will reenable the checks
def set_heartbeat_disabled(self):
self._handle.controlWrite(Panda.REQUEST_OUT, 0xf8, 0, 0, b'')
# ****************** Timer *****************
def get_microsecond_timer(self):
dat = self._handle.controlRead(Panda.REQUEST_IN, 0xa8, 0, 0, 4)
return struct.unpack("I", dat)[0]
# ******************* IR *******************
def set_ir_power(self, percentage):
self._handle.controlWrite(Panda.REQUEST_OUT, 0xb0, int(percentage), 0, b'')
# ******************* Fan ******************
def set_fan_power(self, percentage):
self._handle.controlWrite(Panda.REQUEST_OUT, 0xb1, int(percentage), 0, b'')
def get_fan_rpm(self):
dat = self._handle.controlRead(Panda.REQUEST_IN, 0xb2, 0, 0, 2)
a = struct.unpack("H", dat)
return a[0]
# ****************** Siren *****************
def set_siren(self, enabled):
self._handle.controlWrite(Panda.REQUEST_OUT, 0xf6, int(enabled), 0, b'')
# ****************** Debug *****************
# arr: timer period
# ccrN: channel N pulse length
def set_clock_source_timer_params(self, arr, ccr1, ccr2, ccr3):
param1 = ((ccr1 & 0xFF) << 8) | (ccr2 & 0xFF)
param2 = ((ccr3 & 0xFF) << 8) | (arr & 0xFF)
self._handle.controlWrite(Panda.REQUEST_OUT, 0xe6, param1, param2, b'')
def force_relay_drive(self, intercept_relay_drive, ignition_relay_drive):
self._handle.controlWrite(Panda.REQUEST_OUT, 0xc5, (int(intercept_relay_drive) | int(ignition_relay_drive) << 1), 0, b'')
def read_som_gpio(self) -> bool:
r = self._handle.controlRead(Panda.REQUEST_IN, 0xc6, 0, 0, 1)
return r[0] == 1

61
panda/python/base.py Normal file
View File

@@ -0,0 +1,61 @@
from abc import ABC, abstractmethod
from .constants import McuType
TIMEOUT = int(15 * 1e3) # default timeout, in milliseconds
class BaseHandle(ABC):
"""
A handle to talk to a panda.
Borrows heavily from the libusb1 handle API.
"""
@abstractmethod
def close(self) -> None:
...
@abstractmethod
def controlWrite(self, request_type: int, request: int, value: int, index: int, data, timeout: int = TIMEOUT, expect_disconnect: bool = False):
...
@abstractmethod
def controlRead(self, request_type: int, request: int, value: int, index: int, length: int, timeout: int = TIMEOUT) -> bytes:
...
@abstractmethod
def bulkWrite(self, endpoint: int, data: bytes, timeout: int = TIMEOUT) -> int:
...
@abstractmethod
def bulkRead(self, endpoint: int, length: int, timeout: int = TIMEOUT) -> bytes:
...
class BaseSTBootloaderHandle(ABC):
"""
A handle to talk to a panda while it's in the STM32 bootloader.
"""
@abstractmethod
def get_mcu_type(self) -> McuType:
...
@abstractmethod
def close(self) -> None:
...
@abstractmethod
def clear_status(self) -> None:
...
@abstractmethod
def program(self, address: int, dat: bytes) -> None:
...
@abstractmethod
def erase_sector(self, sector: int) -> None:
...
@abstractmethod
def jump(self, address: int) -> None:
...

67
panda/python/constants.py Normal file
View File

@@ -0,0 +1,67 @@
import os
import enum
from typing import NamedTuple
BASEDIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../")
FW_PATH = os.path.join(BASEDIR, "board/obj/")
USBPACKET_MAX_SIZE = 0x40
class McuConfig(NamedTuple):
mcu: str
mcu_idcode: int
sector_sizes: list[int]
sector_count: int # total sector count, used for MCU identification in DFU mode
uid_address: int
block_size: int
serial_number_address: int
app_address: int
app_fn: str
bootstub_address: int
bootstub_fn: str
def sector_address(self, i):
# assume bootstub is in sector 0
return self.bootstub_address + sum(self.sector_sizes[:i])
F4Config = McuConfig(
"STM32F4",
0x463,
[0x4000 for _ in range(4)] + [0x10000] + [0x20000 for _ in range(11)],
16,
0x1FFF7A10,
0x800,
0x1FFF79C0,
0x8004000,
"panda.bin.signed",
0x8000000,
"bootstub.panda.bin",
)
H7Config = McuConfig(
"STM32H7",
0x483,
[0x20000 for _ in range(7)],
8,
0x1FF1E800,
0x400,
# there is an 8th sector, but we use that for the provisioning chunk, so don't program over that!
0x080FFFC0,
0x8020000,
"panda_h7.bin.signed",
0x8000000,
"bootstub.panda_h7.bin",
)
@enum.unique
class McuType(enum.Enum):
F4 = F4Config
H7 = H7Config
@property
def config(self):
return self.value
MCU_TYPE_BY_IDCODE = {m.config.mcu_idcode: m for m in McuType}

137
panda/python/dfu.py Normal file
View File

@@ -0,0 +1,137 @@
import os
import usb1
import struct
import binascii
from .base import BaseSTBootloaderHandle
from .spi import STBootloaderSPIHandle, PandaSpiException
from .usb import STBootloaderUSBHandle
from .constants import FW_PATH, McuType
class PandaDFU:
def __init__(self, dfu_serial: str | None):
# try USB, then SPI
handle: BaseSTBootloaderHandle | None
self._context, handle = PandaDFU.usb_connect(dfu_serial)
if handle is None:
self._context, handle = PandaDFU.spi_connect(dfu_serial)
if handle is None:
raise Exception(f"failed to open DFU device {dfu_serial}")
self._handle: BaseSTBootloaderHandle = handle
self._mcu_type: McuType = self._handle.get_mcu_type()
def __enter__(self):
return self
def __exit__(self, *args):
self.close()
def close(self):
if self._handle is not None:
self._handle.close()
self._handle = None
if self._context is not None:
self._context.close()
@staticmethod
def usb_connect(dfu_serial: str | None):
handle = None
context = usb1.USBContext()
context.open()
for device in context.getDeviceList(skip_on_error=True):
if device.getVendorID() == 0x0483 and device.getProductID() == 0xdf11:
try:
this_dfu_serial = device.open().getASCIIStringDescriptor(3)
except Exception:
continue
if this_dfu_serial == dfu_serial or dfu_serial is None:
handle = STBootloaderUSBHandle(device, device.open())
break
return context, handle
@staticmethod
def spi_connect(dfu_serial: str | None):
handle = None
this_dfu_serial = None
try:
handle = STBootloaderSPIHandle()
this_dfu_serial = PandaDFU.st_serial_to_dfu_serial(handle.get_uid(), handle.get_mcu_type())
except PandaSpiException:
handle = None
if dfu_serial is not None and dfu_serial != this_dfu_serial:
handle = None
return None, handle
@staticmethod
def usb_list() -> list[str]:
dfu_serials = []
try:
with usb1.USBContext() as context:
for device in context.getDeviceList(skip_on_error=True):
if device.getVendorID() == 0x0483 and device.getProductID() == 0xdf11:
try:
dfu_serials.append(device.open().getASCIIStringDescriptor(3))
except Exception:
pass
except Exception:
pass
return dfu_serials
@staticmethod
def spi_list() -> list[str]:
try:
_, h = PandaDFU.spi_connect(None)
if h is not None:
dfu_serial = PandaDFU.st_serial_to_dfu_serial(h.get_uid(), h.get_mcu_type())
return [dfu_serial, ]
except PandaSpiException:
pass
return []
@staticmethod
def st_serial_to_dfu_serial(st: str, mcu_type: McuType = McuType.H7):
if st is None or st == "none":
return None
try:
uid_base = struct.unpack("H" * 6, bytes.fromhex(st))
if mcu_type == McuType.H7:
return binascii.hexlify(struct.pack("!HHH", uid_base[1] + uid_base[5], uid_base[0] + uid_base[4], uid_base[3])).upper().decode("utf-8")
except struct.error:
return None
def get_mcu_type(self) -> McuType:
return self._mcu_type
def reset(self):
self._handle.jump(self._mcu_type.config.bootstub_address)
def program_bootstub(self, code_bootstub):
self._handle.clear_status()
# erase bootstub + app sectors
for i in (0, 1):
self._handle.erase_sector(i)
# write bootstub
self._handle.program(self._mcu_type.config.bootstub_address, code_bootstub)
def recover(self):
fn = os.path.join(FW_PATH, self._mcu_type.config.bootstub_fn)
with open(fn, "rb") as f:
code = f.read()
self.program_bootstub(code)
self.reset()
@staticmethod
def list() -> list[str]:
ret = PandaDFU.usb_list()
ret += PandaDFU.spi_list()
return list(set(ret))

35
panda/python/serial.py Normal file
View File

@@ -0,0 +1,35 @@
# mimic a python serial port
class PandaSerial:
def __init__(self, panda, port, baud):
self.panda = panda
self.port = port
self.panda.set_uart_parity(self.port, 0)
self._baudrate = baud
self.panda.set_uart_baud(self.port, baud)
self.buf = b""
def read(self, l=1):
tt = self.panda.serial_read(self.port)
if len(tt) > 0:
self.buf += tt
ret = self.buf[0:l]
self.buf = self.buf[l:]
return ret
def write(self, dat):
return self.panda.serial_write(self.port, dat)
def close(self):
pass
def flush(self):
pass
@property
def baudrate(self):
return self._baudrate
@baudrate.setter
def baudrate(self, value):
self.panda.set_uart_baud(self.port, value)
self._baudrate = value

123
panda/python/socketpanda.py Normal file
View File

@@ -0,0 +1,123 @@
import socket
import struct
import time
# /**
# * struct canfd_frame - CAN flexible data rate frame structure
# * @can_id: CAN ID of the frame and CAN_*_FLAG flags, see canid_t definition
# * @len: frame payload length in byte (0 .. CANFD_MAX_DLEN)
# * @flags: additional flags for CAN FD
# * @__res0: reserved / padding
# * @__res1: reserved / padding
# * @data: CAN FD frame payload (up to CANFD_MAX_DLEN byte)
# */
# struct canfd_frame {
# canid_t can_id; /* 32 bit CAN_ID + EFF/RTR/ERR flags */
# __u8 len; /* frame payload length in byte */
# __u8 flags; /* additional flags for CAN FD */
# __u8 __res0; /* reserved / padding */
# __u8 __res1; /* reserved / padding */
# __u8 data[CANFD_MAX_DLEN] __attribute__((aligned(8)));
# };
CAN_HEADER_FMT = "=IBB2x"
CAN_HEADER_LEN = struct.calcsize(CAN_HEADER_FMT)
CAN_MAX_DLEN = 8
CANFD_MAX_DLEN = 64
CAN_CONFIRM_FLAG = 0x800
CAN_EFF_FLAG = 0x80000000
CANFD_BRS = 0x01 # bit rate switch (second bitrate for payload data)
CANFD_FDF = 0x04 # mark CAN FD for dual use of struct canfd_frame
# socket.SO_RXQ_OVFL is missing
# https://github.com/torvalds/linux/blob/47ac09b91befbb6a235ab620c32af719f8208399/include/uapi/asm-generic/socket.h#L61
SO_RXQ_OVFL = 40
import typing
@typing.no_type_check # mypy struggles with macOS here...
def create_socketcan(interface:str, recv_buffer_size:int) -> socket.socket:
# settings mostly from https://github.com/linux-can/can-utils/blob/master/candump.c
socketcan = socket.socket(socket.AF_CAN, socket.SOCK_RAW, socket.CAN_RAW)
socketcan.setblocking(False)
socketcan.setsockopt(socket.SOL_CAN_RAW, socket.CAN_RAW_FD_FRAMES, 1)
socketcan.setsockopt(socket.SOL_CAN_RAW, socket.CAN_RAW_RECV_OWN_MSGS, 1)
socketcan.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, recv_buffer_size)
# TODO: why is it always 2x the requested size?
assert socketcan.getsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF) == recv_buffer_size * 2
# TODO: how to dectect and alert on buffer overflow?
socketcan.setsockopt(socket.SOL_SOCKET, SO_RXQ_OVFL, 1)
socketcan.bind((interface,))
return socketcan
# Panda class substitute for socketcan device (to support using the uds/iso-tp/xcp/ccp library)
class SocketPanda():
def __init__(self, interface:str="can0", recv_buffer_size:int=212992) -> None:
self.interface = interface
self.recv_buffer_size = recv_buffer_size
self.socket = create_socketcan(interface, recv_buffer_size)
def __del__(self):
self.socket.close()
def get_serial(self) -> tuple[int, int]:
return (0, 0)
def get_version(self) -> int:
return 0
def can_clear(self, bus:int) -> None:
self.socket.close()
self.socket = create_socketcan(self.interface, self.recv_buffer_size)
def set_safety_mode(self, mode:int, param=0) -> None:
pass
def can_send_many(self, arr, *, fd=False, timeout=0) -> None:
for msg in arr:
self.can_send(*msg, fd=fd, timeout=timeout)
def can_send(self, addr, dat, bus, *, fd=False, timeout=0) -> None:
# Even if the CANFD_FDF flag is not set, the data still must be 8 bytes for classic CAN frames.
data_len = CANFD_MAX_DLEN if fd else CAN_MAX_DLEN
msg_len = len(dat)
msg_dat = dat.ljust(data_len, b'\x00')
# Set extended ID flag
if addr > 0x7ff:
addr |= CAN_EFF_FLAG
# Set FD flags
flags = CANFD_BRS | CANFD_FDF if fd else 0
can_frame = struct.pack(CAN_HEADER_FMT, addr, msg_len, flags) + msg_dat
# Try to send until timeout. sendto might block if the TX buffer is full.
# TX buffer size can also be adjusted through `ip link set can0 txqueuelen <size>` if needed
start_t = time.monotonic()
while (time.monotonic() - start_t < (timeout / 1000)) or (timeout == 0):
try:
self.socket.sendto(can_frame, (self.interface,))
break
except (BlockingIOError, OSError):
continue
else:
raise TimeoutError
def can_recv(self) -> list[tuple[int, bytes, int]]:
msgs = list()
while True:
try:
dat, _, msg_flags, _ = self.socket.recvmsg(self.recv_buffer_size)
assert len(dat) >= CAN_HEADER_LEN, f"ERROR: received {len(dat)} bytes"
can_id, msg_len, _ = struct.unpack(CAN_HEADER_FMT, dat[:CAN_HEADER_LEN])
assert len(dat) >= CAN_HEADER_LEN + msg_len, f"ERROR: received {len(dat)} bytes, expected at least {CAN_HEADER_LEN + msg_len} bytes"
msg_dat = dat[CAN_HEADER_LEN:CAN_HEADER_LEN+msg_len]
bus = 128 if (msg_flags & CAN_CONFIRM_FLAG) else 0
msgs.append((can_id, msg_dat, bus))
except BlockingIOError:
break # buffered data exhausted
return msgs

417
panda/python/spi.py Normal file
View File

@@ -0,0 +1,417 @@
import binascii
import os
import fcntl
import math
import time
import struct
import threading
from contextlib import contextmanager
from functools import reduce
from .base import BaseHandle, BaseSTBootloaderHandle, TIMEOUT
from .constants import McuType, MCU_TYPE_BY_IDCODE, USBPACKET_MAX_SIZE
from .utils import logger
try:
import spidev
except ImportError:
spidev = None
# Constants
SYNC = 0x5A
HACK = 0x79
DACK = 0x85
NACK = 0x1F
CHECKSUM_START = 0xAB
MIN_ACK_TIMEOUT_MS = 100
MAX_XFER_RETRY_COUNT = 5
SPI_BUF_SIZE = 4096 # from panda/board/drivers/spi.h
XFER_SIZE = SPI_BUF_SIZE - 0x40 # give some room for SPI protocol overhead
DEV_PATH = "/dev/spidev0.0"
def crc8(data):
crc = 0xFF # standard init value
poly = 0xD5 # standard crc8: x8+x7+x6+x4+x2+1
size = len(data)
for i in range(size - 1, -1, -1):
crc ^= data[i]
for _ in range(8):
if ((crc & 0x80) != 0):
crc = ((crc << 1) ^ poly) & 0xFF
else:
crc <<= 1
return crc
class PandaSpiException(Exception):
pass
class PandaProtocolMismatch(PandaSpiException):
pass
class PandaSpiUnavailable(PandaSpiException):
pass
class PandaSpiNackResponse(PandaSpiException):
pass
class PandaSpiMissingAck(PandaSpiException):
pass
class PandaSpiBadChecksum(PandaSpiException):
pass
class PandaSpiTransferFailed(PandaSpiException):
pass
SPI_LOCK = threading.Lock()
SPI_DEVICES = {}
class SpiDevice:
"""
Provides locked, thread-safe access to a panda's SPI interface.
"""
MAX_SPEED = 50000000 # max of the SDM845
def __init__(self, speed=MAX_SPEED):
assert speed <= self.MAX_SPEED
if not os.path.exists(DEV_PATH):
raise PandaSpiUnavailable(f"SPI device not found: {DEV_PATH}")
if spidev is None:
raise PandaSpiUnavailable("spidev is not installed")
with SPI_LOCK:
if speed not in SPI_DEVICES:
SPI_DEVICES[speed] = spidev.SpiDev()
SPI_DEVICES[speed].open(0, 0)
SPI_DEVICES[speed].max_speed_hz = speed
self._spidev = SPI_DEVICES[speed]
@contextmanager
def acquire(self):
try:
SPI_LOCK.acquire()
fcntl.flock(self._spidev, fcntl.LOCK_EX)
yield self._spidev
finally:
fcntl.flock(self._spidev, fcntl.LOCK_UN)
SPI_LOCK.release()
def close(self):
pass
class PandaSpiHandle(BaseHandle):
"""
A class that mimics a libusb1 handle for panda SPI communications.
"""
PROTOCOL_VERSION = 2
HEADER = struct.Struct("<BBHH")
def __init__(self) -> None:
self.dev = SpiDevice()
self.no_retry = "NO_RETRY" in os.environ
# helpers
def _calc_checksum(self, data: bytes) -> int:
cksum = CHECKSUM_START
for b in data:
cksum ^= b
return cksum
def _wait_for_ack(self, spi, ack_val: int, timeout: int, tx: int, length: int = 1) -> bytes:
timeout_s = max(MIN_ACK_TIMEOUT_MS, timeout) * 1e-3
start = time.monotonic()
while (timeout == 0) or ((time.monotonic() - start) < timeout_s):
dat = spi.xfer2([tx, ] * length)
if dat[0] == ack_val:
return bytes(dat)
elif dat[0] == NACK:
raise PandaSpiNackResponse
raise PandaSpiMissingAck
def _transfer_spidev(self, spi, endpoint: int, data, timeout: int, max_rx_len: int = 1000, expect_disconnect: bool = False) -> bytes:
max_rx_len = max(USBPACKET_MAX_SIZE, max_rx_len)
logger.debug("- send header")
packet = self.HEADER.pack(SYNC, endpoint, len(data), max_rx_len)
packet += bytes([self._calc_checksum(packet), ])
spi.xfer2(packet)
logger.debug("- waiting for header ACK")
self._wait_for_ack(spi, HACK, MIN_ACK_TIMEOUT_MS, 0x11)
logger.debug("- sending data")
packet = bytes([*data, self._calc_checksum(data)])
spi.xfer2(packet)
if expect_disconnect:
logger.debug("- expecting disconnect, returning")
return b""
else:
logger.debug("- waiting for data ACK")
preread_len = USBPACKET_MAX_SIZE + 1 # read enough for a controlRead
dat = self._wait_for_ack(spi, DACK, timeout, 0x13, length=3 + preread_len)
# get response length, then response
response_len = struct.unpack("<H", dat[1:3])[0]
if response_len > max_rx_len:
raise PandaSpiException(f"response length greater than max ({max_rx_len} {response_len})")
# read rest
remaining = (response_len + 1) - preread_len
if remaining > 0:
dat += bytes(spi.readbytes(remaining))
dat = dat[:3 + response_len + 1]
if self._calc_checksum(dat) != 0:
raise PandaSpiBadChecksum
return dat[3:-1]
def _transfer(self, endpoint: int, data, timeout: int, max_rx_len: int = 1000, expect_disconnect: bool = False) -> bytes:
logger.debug("starting transfer: endpoint=%d, max_rx_len=%d", endpoint, max_rx_len)
logger.debug("==============================================")
n = 0
start_time = time.monotonic()
exc = PandaSpiException()
while (timeout == 0) or (time.monotonic() - start_time) < timeout*1e-3:
n += 1
logger.debug("\ntry #%d", n)
with self.dev.acquire() as spi:
try:
return self._transfer_spidev(spi, endpoint, data, timeout, max_rx_len, expect_disconnect)
except PandaSpiException as e:
exc = e
logger.debug("SPI transfer failed, retrying", exc_info=True)
if self.no_retry:
break
# ensure slave is in a consistent state and ready for the next transfer
# (e.g. slave TX buffer isn't stuck full)
nack_cnt = 0
attempts = 5
while (nack_cnt <= 3) and (attempts > 0):
attempts -= 1
try:
self._wait_for_ack(spi, NACK, MIN_ACK_TIMEOUT_MS, 0x11, length=XFER_SIZE//2)
nack_cnt += 1
except PandaSpiException:
nack_cnt = 0
raise exc
def get_protocol_version(self) -> bytes:
vers_str = b"VERSION"
def _get_version(spi) -> bytes:
spi.writebytes(vers_str)
logger.debug("- waiting for echo")
start = time.monotonic()
while True:
version_bytes = spi.readbytes(len(vers_str) + 2)
if bytes(version_bytes).startswith(vers_str):
break
if (time.monotonic() - start) > 0.001:
raise PandaSpiMissingAck
rlen = struct.unpack("<H", bytes(version_bytes[-2:]))[0]
if rlen > 1000:
raise PandaSpiException("response length greater than max")
# get response
dat = spi.readbytes(rlen + 1)
resp = dat[:-1]
calculated_crc = crc8(bytes(version_bytes + resp))
if calculated_crc != dat[-1]:
raise PandaSpiBadChecksum
return bytes(resp)
exc = PandaSpiException()
with self.dev.acquire() as spi:
for _ in range(10):
try:
return _get_version(spi)
except PandaSpiException as e:
exc = e
logger.debug("SPI get protocol version failed, retrying", exc_info=True)
raise exc
# libusb1 functions
def close(self):
self.dev.close()
def controlWrite(self, request_type: int, request: int, value: int, index: int, data, timeout: int = TIMEOUT, expect_disconnect: bool = False):
return self._transfer(0, struct.pack("<BHHH", request, value, index, 0), timeout, expect_disconnect=expect_disconnect)
def controlRead(self, request_type: int, request: int, value: int, index: int, length: int, timeout: int = TIMEOUT):
return self._transfer(0, struct.pack("<BHHH", request, value, index, length), timeout, max_rx_len=length)
def bulkWrite(self, endpoint: int, data: bytes, timeout: int = TIMEOUT) -> int:
mv = memoryview(data)
for x in range(math.ceil(len(data) / XFER_SIZE)):
self._transfer(endpoint, mv[XFER_SIZE*x:XFER_SIZE*(x+1)], timeout)
return len(data)
def bulkRead(self, endpoint: int, length: int, timeout: int = TIMEOUT) -> bytes:
ret = b""
for _ in range(math.ceil(length / XFER_SIZE)):
d = self._transfer(endpoint, [], timeout, max_rx_len=XFER_SIZE)
ret += d
if len(d) < XFER_SIZE:
break
return ret
class STBootloaderSPIHandle(BaseSTBootloaderHandle):
"""
Implementation of the STM32 SPI bootloader protocol described in:
https://www.st.com/resource/en/application_note/an4286-spi-protocol-used-in-the-stm32-bootloader-stmicroelectronics.pdf
NOTE: the bootloader's state machine is fragile and immediately gets into a bad state when
sending any junk, e.g. when using the panda SPI protocol.
"""
SYNC = 0x5A
ACK = 0x79
NACK = 0x1F
def __init__(self):
self.dev = SpiDevice(speed=1000000)
# say hello
try:
with self.dev.acquire() as spi:
spi.xfer([self.SYNC, ])
try:
self._get_ack(spi, 0.1)
except (PandaSpiNackResponse, PandaSpiMissingAck):
# NACK ok here, will only ACK the first time
pass
self._mcu_type = MCU_TYPE_BY_IDCODE[self.get_chip_id()]
except PandaSpiException:
raise PandaSpiException("failed to connect to panda") from None
def _get_ack(self, spi, timeout=1.0):
data = 0x00
start_time = time.monotonic()
while data not in (self.ACK, self.NACK) and (time.monotonic() - start_time < timeout):
data = spi.xfer([0x00, ])[0]
time.sleep(0)
spi.xfer([self.ACK, ])
if data == self.NACK:
raise PandaSpiNackResponse
elif data != self.ACK:
raise PandaSpiMissingAck
def _cmd_no_retry(self, cmd: int, data: list[bytes] | None = None, read_bytes: int = 0, predata=None) -> bytes:
ret = b""
with self.dev.acquire() as spi:
# sync + command
spi.xfer([self.SYNC, ])
spi.xfer([cmd, cmd ^ 0xFF])
self._get_ack(spi, timeout=0.01)
# "predata" - for commands that send the first data without a checksum
if predata is not None:
spi.xfer(predata)
self._get_ack(spi)
# send data
if data is not None:
for d in data:
if predata is not None:
spi.xfer(d + self._checksum(predata + d))
else:
spi.xfer(d + self._checksum(d))
self._get_ack(spi, timeout=20)
# receive
if read_bytes > 0:
ret = spi.xfer([0x00, ]*(read_bytes + 1))[1:]
if data is None or len(data) == 0:
self._get_ack(spi)
return bytes(ret)
def _cmd(self, cmd: int, data: list[bytes] | None = None, read_bytes: int = 0, predata=None) -> bytes:
exc = PandaSpiException()
for n in range(MAX_XFER_RETRY_COUNT):
try:
return self._cmd_no_retry(cmd, data, read_bytes, predata)
except PandaSpiException as e:
exc = e
logger.debug("SPI transfer failed, %d retries left", MAX_XFER_RETRY_COUNT - n - 1, exc_info=True)
raise exc
def _checksum(self, data: bytes) -> bytes:
if len(data) == 1:
ret = data[0] ^ 0xFF
else:
ret = reduce(lambda a, b: a ^ b, data)
return bytes([ret, ])
# *** Bootloader commands ***
def read(self, address: int, length: int):
data = [struct.pack('>I', address), struct.pack('B', length - 1)]
return self._cmd(0x11, data=data, read_bytes=length)
def get_bootloader_id(self):
return self.read(0x1FF1E7FE, 1)
def get_chip_id(self) -> int:
r = self._cmd(0x02, read_bytes=3)
if r[0] != 1: # response length - 1
raise PandaSpiException("incorrect response length")
return ((r[1] << 8) + r[2])
def go_cmd(self, address: int) -> None:
self._cmd(0x21, data=[struct.pack('>I', address), ])
# *** helpers ***
def get_uid(self):
dat = self.read(McuType.H7.config.uid_address, 12)
return binascii.hexlify(dat).decode()
def erase_sector(self, sector: int):
p = struct.pack('>H', 0) # number of sectors to erase
d = struct.pack('>H', sector)
self._cmd(0x44, data=[d, ], predata=p)
# *** PandaDFU API ***
def get_mcu_type(self):
return self._mcu_type
def clear_status(self):
pass
def close(self):
self.dev.close()
def program(self, address, dat):
bs = 256 # max block size for writing to flash over SPI
dat += b"\xFF" * ((bs - len(dat)) % bs)
for i in range(len(dat) // bs):
block = dat[i * bs:(i + 1) * bs]
self._cmd(0x31, data=[
struct.pack('>I', address + i*bs),
bytes([len(block) - 1]) + block,
])
def jump(self, address):
self.go_cmd(self._mcu_type.config.bootstub_address)

98
panda/python/usb.py Normal file
View File

@@ -0,0 +1,98 @@
import struct
from .base import BaseHandle, BaseSTBootloaderHandle, TIMEOUT
from .constants import McuType
class PandaUsbHandle(BaseHandle):
def __init__(self, libusb_handle):
self._libusb_handle = libusb_handle
def close(self):
self._libusb_handle.close()
def controlWrite(self, request_type: int, request: int, value: int, index: int, data, timeout: int = TIMEOUT, expect_disconnect: bool = False):
return self._libusb_handle.controlWrite(request_type, request, value, index, data, timeout)
def controlRead(self, request_type: int, request: int, value: int, index: int, length: int, timeout: int = TIMEOUT):
return self._libusb_handle.controlRead(request_type, request, value, index, length, timeout)
def bulkWrite(self, endpoint: int, data: bytes, timeout: int = TIMEOUT) -> int:
return self._libusb_handle.bulkWrite(endpoint, data, timeout) # type: ignore
def bulkRead(self, endpoint: int, length: int, timeout: int = TIMEOUT) -> bytes:
return self._libusb_handle.bulkRead(endpoint, length, timeout) # type: ignore
class STBootloaderUSBHandle(BaseSTBootloaderHandle):
DFU_DNLOAD = 1
DFU_UPLOAD = 2
DFU_GETSTATUS = 3
DFU_CLRSTATUS = 4
DFU_ABORT = 6
def __init__(self, libusb_device, libusb_handle):
self._libusb_handle = libusb_handle
# example from F4: lsusb -v | grep Flash
# iInterface 4 @Internal Flash /0x08000000/04*016Kg,01*064Kg,011*128Kg
for i in range(20):
desc = libusb_handle.getStringDescriptor(i, 0)
if desc is not None and desc.startswith("@Internal Flash"):
sector_count = sum([int(s.split('*')[0]) for s in desc.split('/')[-1].split(',')])
break
mcu_by_sector_count = {m.config.sector_count: m for m in McuType}
assert sector_count in mcu_by_sector_count, f"Unkown MCU: {sector_count=}"
self._mcu_type = mcu_by_sector_count[sector_count]
def _status(self) -> None:
while 1:
dat = self._libusb_handle.controlRead(0x21, self.DFU_GETSTATUS, 0, 0, 6)
if dat[1] == 0:
break
def _erase_page_address(self, address: int) -> None:
self._libusb_handle.controlWrite(0x21, self.DFU_DNLOAD, 0, 0, b"\x41" + struct.pack("I", address))
self._status()
def get_mcu_type(self):
return self._mcu_type
def erase_sector(self, sector: int):
self._erase_page_address(self._mcu_type.config.sector_address(sector))
def clear_status(self):
# Clear status
stat = self._libusb_handle.controlRead(0x21, self.DFU_GETSTATUS, 0, 0, 6)
if stat[4] == 0xa:
self._libusb_handle.controlRead(0x21, self.DFU_CLRSTATUS, 0, 0, 0)
elif stat[4] == 0x9:
self._libusb_handle.controlWrite(0x21, self.DFU_ABORT, 0, 0, b"")
self._status()
stat = str(self._libusb_handle.controlRead(0x21, self.DFU_GETSTATUS, 0, 0, 6))
def close(self):
self._libusb_handle.close()
def program(self, address, dat):
# Set Address Pointer
self._libusb_handle.controlWrite(0x21, self.DFU_DNLOAD, 0, 0, b"\x21" + struct.pack("I", address))
self._status()
# Program
bs = min(len(dat), self._mcu_type.config.block_size)
dat += b"\xFF" * ((bs - len(dat)) % bs)
for i in range(len(dat) // bs):
ldat = dat[i * bs:(i + 1) * bs]
print("programming %d with length %d" % (i, len(ldat)))
self._libusb_handle.controlWrite(0x21, self.DFU_DNLOAD, 2 + i, 0, ldat)
self._status()
def jump(self, address):
self._libusb_handle.controlWrite(0x21, self.DFU_DNLOAD, 0, 0, b"\x21" + struct.pack("I", address))
self._status()
try:
self._libusb_handle.controlWrite(0x21, self.DFU_DNLOAD, 2, 0, b"")
_ = str(self._libusb_handle.controlRead(0x21, self.DFU_GETSTATUS, 0, 0, 6))
except Exception:
pass

11
panda/python/utils.py Normal file
View File

@@ -0,0 +1,11 @@
import os
import logging
# set up logging
LOGLEVEL = os.environ.get('LOGLEVEL', 'INFO').upper()
logger = logging.getLogger('panda')
logger.setLevel(LOGLEVEL)
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter('%(message)s'))
logger.addHandler(handler)

58
panda/scripts/benchmark.py Executable file
View File

@@ -0,0 +1,58 @@
#!/usr/bin/env python3
import io
import os
import time
import pstats
import cProfile
from contextlib import contextmanager
from panda import Panda, PandaDFU
from panda.tests.hitl.helpers import get_random_can_messages
PROFILE = "PROFILE" in os.environ
@contextmanager
def print_time(desc):
if PROFILE:
pr = cProfile.Profile()
pr.enable()
start = time.perf_counter()
yield
end = time.perf_counter()
print(f"{end - start:.3f}s - {desc}")
if PROFILE:
pr.disable()
s = io.StringIO()
ps = pstats.Stats(pr, stream=s).sort_stats("cumtime")
ps.print_stats()
print(s.getvalue())
if __name__ == "__main__":
with print_time("Panda()"):
p = Panda()
with print_time("PandaDFU.list()"):
PandaDFU.list()
fxn = [
'reset',
'reconnect',
'up_to_date',
'health',
#'flash',
]
for f in fxn:
with print_time(f"Panda.{f}()"):
getattr(p, f)()
p.set_can_loopback(True)
for n in range(6):
msgs = get_random_can_messages(int(10**n))
with print_time(f"Panda.can_send_many() - {len(msgs)} msgs"):
p.can_send_many(msgs)
with print_time("Panda.can_recv()"):
m = p.can_recv()

View File

@@ -0,0 +1,49 @@
#!/usr/bin/env python3
import os
import time
import threading
from typing import Any
from iqdbc.car.structs import CarParams
from panda import Panda
JUNGLE = "JUNGLE" in os.environ
if JUNGLE:
from panda import PandaJungle
# The TX buffers on pandas is 0x100 in length.
NUM_MESSAGES_PER_BUS = 10000
def flood_tx(panda):
print('Sending!')
msg = b"\xaa" * 4
packet = [[0xaa, msg, 0], [0xaa, msg, 1], [0xaa, msg, 2]] * NUM_MESSAGES_PER_BUS
panda.can_send_many(packet, timeout=10000)
print(f"Done sending {3*NUM_MESSAGES_PER_BUS} messages!")
if __name__ == "__main__":
serials = Panda.list()
if JUNGLE:
sender = Panda()
receiver = PandaJungle()
else:
if len(serials) != 2:
raise Exception("Connect two pandas to perform this test!")
sender = Panda(serials[0])
receiver = Panda(serials[1]) # type: ignore
receiver.set_safety_mode(CarParams.SafetyModel.allOutput)
sender.set_safety_mode(CarParams.SafetyModel.allOutput)
# Start transmisson
threading.Thread(target=flood_tx, args=(sender,)).start()
# Receive as much as we can in a few second time period
rx: list[Any] = []
old_len = 0
start_time = time.time()
while time.time() - start_time < 3 or len(rx) > old_len:
old_len = len(rx)
print(old_len)
rx.extend(receiver.can_recv())
print(f"Received {len(rx)} messages")

29
panda/scripts/can_health.py Executable file
View File

@@ -0,0 +1,29 @@
#!/usr/bin/env python3
import time
import re
from panda import Panda
RED = '\033[91m'
GREEN = '\033[92m'
def colorize_errors(value):
if isinstance(value, str):
if re.search(r'(?i)No error', value):
return f'{GREEN}{value}\033[0m'
elif re.search(r'(?i)(?<!No error\s)(err|error)', value):
return f'{RED}{value}\033[0m'
return str(value)
if __name__ == "__main__":
panda = Panda()
while True:
print(chr(27) + "[2J") # clear screen
print("Connected to " + ("internal panda" if panda.is_internal() else "External panda") + f" id: {panda.get_serial()[0]}: {panda.get_version()}")
for bus in range(3):
print(f"\nBus {bus}:")
health = panda.can_health(bus)
for key, value in health.items():
print(f"{key}: {colorize_errors(value)} ", end=" ")
print()
time.sleep(1)

46
panda/scripts/can_printer.py Executable file
View File

@@ -0,0 +1,46 @@
#!/usr/bin/env python3
import os
import time
from collections import defaultdict
import binascii
from iqdbc.car.structs import CarParams
from panda import Panda
# fake
def sec_since_boot():
return time.time()
def can_printer():
p = Panda()
print(f"Connected to id: {p.get_serial()[0]}: {p.get_version()}")
time.sleep(1)
p.can_clear(0xFFFF)
p.set_safety_mode(CarParams.SafetyModel.allOutput)
start = sec_since_boot()
lp = sec_since_boot()
all_msgs = defaultdict(list)
canbus = os.getenv("CAN")
if canbus == "3":
p.set_obd(True)
canbus = "1"
while True:
can_recv = p.can_recv()
for addr, dat, bus in can_recv:
if canbus is None or str(bus) == canbus:
all_msgs[(addr, bus)].append((dat))
if sec_since_boot() - lp > 0.1:
dd = chr(27) + "[2J"
dd += "%5.2f\n" % (sec_since_boot() - start)
for (addr, bus), dat_log in sorted(all_msgs.items()):
dd += "%d: %s(%6d): %s\n" % (bus, "%04X(%4d)" % (addr, addr), len(dat_log), binascii.hexlify(dat_log[-1]).decode())
print(dd)
lp = sec_since_boot()
if __name__ == "__main__":
can_printer()

89
panda/scripts/check_fw_size.py Executable file
View File

@@ -0,0 +1,89 @@
#!/usr/bin/env python3
import subprocess
from collections import defaultdict
def check_space(file, mcu):
MCUS = {
"H7": {
".flash": 1024*1024, # FLASH
".dtcmram": 128*1024, # DTCMRAM
".itcmram": 64*1024, # ITCMRAM
".axisram": 320*1024, # AXI SRAM
".sram12": 32*1024, # SRAM1(16kb) + SRAM2(16kb)
".sram4": 16*1024, # SRAM4
".backup_sram": 4*1024, # SRAM4
},
}
IGNORE_LIST = [
".ARM.attributes",
".comment",
".debug_line",
".debug_info",
".debug_abbrev",
".debug_aranges",
".debug_str",
".debug_ranges",
".debug_loc",
".debug_frame",
".debug_line_str",
".debug_rnglists",
".debug_loclists",
]
FLASH = [
".isr_vector",
".text",
".rodata",
".data"
]
RAM = [
".data",
".bss",
"._user_heap_stack" # _user_heap_stack considered free?
]
result = {}
calcs = defaultdict(int)
output = str(subprocess.check_output(f"arm-none-eabi-size -x --format=sysv {file}", shell=True), 'utf-8')
for row in output.split('\n'):
pop = False
line = row.split()
if len(line) == 3 and line[0].startswith('.'):
if line[0] in IGNORE_LIST:
continue
result[line[0]] = [line[1], line[2]]
if line[0] in FLASH:
calcs[".flash"] += int(line[1], 16)
pop = True
if line[0] in RAM:
calcs[".dtcmram"] += int(line[1], 16)
pop = True
if pop:
result.pop(line[0])
if len(result):
for line in result:
calcs[line] += int(result[line][0], 16)
print(f"=======SUMMARY FOR {mcu} FILE {file}=======")
for line in calcs:
if line in MCUS[mcu]:
used_percent = (100 - (MCUS[mcu][line] - calcs[line]) / MCUS[mcu][line] * 100)
print(f"SECTION: {line} size: {MCUS[mcu][line]} USED: {calcs[line]}({used_percent:.2f}%) FREE: {MCUS[mcu][line] - calcs[line]}")
else:
print(line, calcs[line])
print()
if __name__ == "__main__":
# panda
check_space("../board/obj/panda_h7/bootstub.elf", "H7")
check_space("../board/obj/panda_h7/main.elf", "H7")
# jungle v2
check_space("../board/obj/panda_jungle_h7/bootstub.elf", "H7")
check_space("../board/obj/panda_jungle_h7/main.elf", "H7")
# body
check_space("../board/obj/body_h7/bootstub.elf", "H7")
check_space("../board/obj/body_h7/main.elf", "H7")

62
panda/scripts/debug_console.py Executable file
View File

@@ -0,0 +1,62 @@
#!/usr/bin/env python3
import os
import sys
import time
import select
import codecs
from panda import Panda
setcolor = ["\033[1;32;40m", "\033[1;31;40m"]
unsetcolor = "\033[00m"
port_number = int(os.getenv("PORT", "0"))
claim = os.getenv("CLAIM") is not None
no_color = os.getenv("NO_COLOR") is not None
no_reconnect = os.getenv("NO_RECONNECT") is not None
if __name__ == "__main__":
while True:
try:
serials = Panda.list()
if os.getenv("SERIAL"):
serials = [x for x in serials if x == os.getenv("SERIAL")]
pandas = [Panda(x, claim=claim) for x in serials]
decoders = [codecs.getincrementaldecoder('utf-8')() for _ in pandas]
if not len(pandas):
print("no pandas found")
if no_reconnect:
sys.exit(0)
time.sleep(1)
continue
if os.getenv("BAUD") is not None:
for panda in pandas:
panda.set_uart_baud(port_number, int(os.getenv("BAUD"))) # type: ignore
while True:
for i, panda in enumerate(pandas):
while True:
ret = panda.serial_read(port_number)
if len(ret) > 0:
decoded = decoders[i].decode(ret)
if no_color:
sys.stdout.write(decoded)
else:
sys.stdout.write(setcolor[i] + decoded + unsetcolor)
sys.stdout.flush()
else:
break
if select.select([sys.stdin], [], [], 0) == ([sys.stdin], [], []):
ln = sys.stdin.readline()
if claim:
panda.serial_write(port_number, ln)
time.sleep(0.01)
except KeyboardInterrupt:
break
except Exception:
print("panda disconnected!")
time.sleep(0.5)

View File

@@ -0,0 +1,50 @@
#!/usr/bin/env python3
import matplotlib.pyplot as plt
HASHING_PRIME = 23
REGISTER_MAP_SIZE = 0x3FF
BYTES_PER_REG = 4
# From ST32F413 datasheet
REGISTER_ADDRESS_REGIONS = [
(0x40000000, 0x40007FFF),
(0x40010000, 0x400107FF),
(0x40011000, 0x400123FF),
(0x40012C00, 0x40014BFF),
(0x40015000, 0x400153FF),
(0x40015800, 0x40015BFF),
(0x40016000, 0x400167FF),
(0x40020000, 0x40021FFF),
(0x40023000, 0x400233FF),
(0x40023800, 0x40023FFF),
(0x40026000, 0x400267FF),
(0x50000000, 0x5003FFFF),
(0x50060000, 0x500603FF),
(0x50060800, 0x50060BFF),
(0x50060800, 0x50060BFF),
(0xE0000000, 0xE00FFFFF)
]
def _hash(reg_addr):
return (((reg_addr >> 16) ^ ((((reg_addr + 1) & 0xFFFF) * HASHING_PRIME) & 0xFFFF)) & REGISTER_MAP_SIZE)
# Calculate hash for each address
hashes = []
double_hashes = []
for (start_addr, stop_addr) in REGISTER_ADDRESS_REGIONS:
for addr in range(start_addr, stop_addr + 1, BYTES_PER_REG):
h = _hash(addr)
hashes.append(h)
double_hashes.append(_hash(h))
# Make histograms
plt.subplot(2, 1, 1)
plt.hist(hashes, bins=REGISTER_MAP_SIZE)
plt.title("Number of collisions per _hash")
plt.xlabel("Address")
plt.subplot(2, 1, 2)
plt.hist(double_hashes, bins=REGISTER_MAP_SIZE)
plt.title("Number of collisions per double _hash")
plt.xlabel("Address")
plt.show()

17
panda/scripts/echo.py Executable file
View File

@@ -0,0 +1,17 @@
#!/usr/bin/env python3
from iqdbc.car.structs import CarParams
from panda import Panda
# This script is intended to be used in conjunction with the echo_loopback_test.py test script from panda jungle.
# It sends a reversed response back for every message received containing b"test".
if __name__ == "__main__":
p = Panda()
p.set_safety_mode(CarParams.SafetyModel.allOutput)
p.set_power_save(False)
while True:
incoming = p.can_recv()
for message in incoming:
address, data, bus = message
if b'test' in data:
p.can_send(address, data[::-1], bus)

14
panda/scripts/fan/fan_test.py Executable file
View File

@@ -0,0 +1,14 @@
#!/usr/bin/env python
import time
from panda import Panda
if __name__ == "__main__":
p = Panda()
power = 0
while True:
p.set_fan_power(power)
time.sleep(5)
print("Power: ", power, "RPM:", str(p.get_fan_rpm()))
power += 10
power %= 110

7
panda/scripts/get_version.py Executable file
View File

@@ -0,0 +1,7 @@
#!/usr/bin/env python3
from panda import Panda
if __name__ == "__main__":
for p in Panda.list():
pp = Panda(p)
print(f"{pp.get_serial()[0]}: {pp.get_version()}")

14
panda/scripts/ir_test.py Executable file
View File

@@ -0,0 +1,14 @@
#!/usr/bin/env python3
import time
from panda import Panda
power = 0
if __name__ == "__main__":
p = Panda()
while True:
p.set_ir_power(power)
print("Power: ", str(power))
time.sleep(1)
power += 10
power %= 100

95
panda/scripts/loopback_test.py Executable file
View File

@@ -0,0 +1,95 @@
#!/usr/bin/env python3
import os
import time
import random
import argparse
from itertools import permutations
from iqdbc.car.structs import CarParams
from panda import Panda
def get_test_string():
return b"test" + os.urandom(10)
def run_test(sleep_duration):
pandas = Panda.list()
print(pandas)
if len(pandas) < 2:
raise Exception("Minimum two pandas are needed for test")
run_test_w_pandas(pandas, sleep_duration)
def run_test_w_pandas(pandas, sleep_duration):
h = [Panda(x) for x in pandas]
print("H", h)
for hh in h:
hh.set_safety_mode(CarParams.SafetyModel.allOutput)
# test both directions
for ho in permutations(list(range(len(h))), r=2):
print("***************** TESTING", ho)
panda0, panda1 = h[ho[0]], h[ho[1]]
# **** test health packet ****
print("health", ho[0], h[ho[0]].health())
# **** test can line loopback ****
for bus, obd in [(0, False), (1, False), (2, False), (1, True), (2, True)]:
print("\ntest can", bus)
# flush
cans_echo = panda0.can_recv()
cans_loop = panda1.can_recv()
panda0.set_obd(None)
panda1.set_obd(None)
if obd is True:
panda0.set_obd(bus)
panda1.set_obd(bus)
bus = 3
# send the characters
at = random.randint(1, 2000)
st = get_test_string()[0:8]
panda0.can_send(at, st, bus)
time.sleep(0.1)
# check for receive
cans_echo = panda0.can_recv()
cans_loop = panda1.can_recv()
print("Bus", bus, "echo", cans_echo, "loop", cans_loop)
assert len(cans_echo) == 1
assert len(cans_loop) == 1
assert cans_echo[0][0] == at
assert cans_loop[0][0] == at
assert cans_echo[0][1] == st
assert cans_loop[0][1] == st
assert cans_echo[0][2] == 0x80 | bus
if cans_loop[0][2] != bus:
print("EXPECTED %d GOT %d" % (bus, cans_loop[0][2]))
assert cans_loop[0][2] == bus
print("CAN pass", bus, ho)
time.sleep(sleep_duration)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("-n", type=int, help="Number of test iterations to run")
parser.add_argument("-sleep", type=int, help="Sleep time between tests", default=0)
args = parser.parse_args()
if args.n is None:
while True:
run_test(sleep_duration=args.sleep)
else:
for _ in range(args.n):
run_test(sleep_duration=args.sleep)

22
panda/scripts/make_release.sh Executable file
View File

@@ -0,0 +1,22 @@
#!/usr/bin/env bash
set -e
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)"
export CERT=/home/batman/xx/pandaextra/certs/release
if [ ! -f "$CERT" ]; then
echo "No release cert found, cannot build release."
echo "You probably aren't looking to do this anyway."
exit
fi
export RELEASE=1
export BUILDER=DEV
cd $DIR/../board
scons -u -c
rm obj/*
scons -u
cd obj
RELEASE_NAME=$(awk '{print $1}' version)
zip -j ../../release/panda-$RELEASE_NAME.zip version panda.bin.signed bootstub.panda.bin panda_h7.bin.signed bootstub.panda_h7.bin

View File

@@ -0,0 +1,71 @@
#!/usr/bin/env python3
import os
import usb1
import time
import struct
import itertools
import threading
from typing import Any
from iqdbc.car.structs import CarParams
from panda import Panda
JUNGLE = "JUNGLE" in os.environ
if JUNGLE:
from panda import PandaJungle
# Generate unique messages
NUM_MESSAGES_PER_BUS = 10000
messages = [bytes(struct.pack("Q", i)) for i in range(NUM_MESSAGES_PER_BUS)]
tx_messages = list(itertools.chain.from_iterable([[0xaa, msg, 0], [0xaa, msg, 1], [0xaa, msg, 2]] for msg in messages))
def flood_tx(panda):
print('Sending!')
transferred = 0
while True:
try:
print(f"Sending block {transferred}-{len(tx_messages)}: ", end="")
panda.can_send_many(tx_messages[transferred:], timeout=10)
print("OK")
break
except usb1.USBErrorTimeout as e:
transferred += (e.transferred // 16)
print("timeout, transferred: ", transferred)
print(f"Done sending {3*NUM_MESSAGES_PER_BUS} messages!")
if __name__ == "__main__":
serials = Panda.list()
receiver: Panda | PandaJungle
if JUNGLE:
sender = Panda()
receiver = PandaJungle()
else:
if len(serials) != 2:
raise Exception("Connect two pandas to perform this test!")
sender = Panda(serials[0])
receiver = Panda(serials[1])
receiver.set_safety_mode(CarParams.SafetyModel.allOutput)
sender.set_safety_mode(CarParams.SafetyModel.allOutput)
# Start transmisson
threading.Thread(target=flood_tx, args=(sender,)).start()
# Receive as much as we can, and stop when there hasn't been anything for a second
rx: list[Any] = []
old_len = 0
last_change = time.monotonic()
while time.monotonic() - last_change < 1:
if old_len < len(rx):
last_change = time.monotonic()
old_len = len(rx)
rx.extend(receiver.can_recv())
print(f"Received {len(rx)} messages")
# Check if we received everything
for bus in range(3):
received_msgs = {bytes(m[1]) for m in filter(lambda m, b=bus: m[2] == b, rx)} # type: ignore
dropped_msgs = set(messages).difference(received_msgs)
print(f"Bus {bus} dropped msgs: {len(list(dropped_msgs))} / {len(messages)}")

32
panda/scripts/read_flash_spi.py Executable file
View File

@@ -0,0 +1,32 @@
#!/usr/bin/env python3
from panda import Panda, PandaDFU
if __name__ == "__main__":
try:
from openpilot.system.hardware import HARDWARE
HARDWARE.recover_internal_panda()
Panda.wait_for_dfu(None, 5)
except Exception:
pass
p = PandaDFU(None)
cfg = p.get_mcu_type().config
def readmem(addr, length, fn):
print(f"reading {hex(addr)} {hex(length)} bytes to {fn}")
max_size = 255
with open(fn, "wb") as f:
to_read = length
while to_read > 0:
l = min(to_read, max_size)
dat = p._handle.read(addr, l)
assert len(dat) == l
f.write(dat)
to_read -= len(dat)
addr += len(dat)
addr = cfg.bootstub_address
for i, sector_size in enumerate(cfg.sector_sizes):
readmem(addr, sector_size, f"sector_{i}.bin")
addr += sector_size

Some files were not shown because too many files have changed in this diff Show More