mirror of
https://github.com/dragonpilot/dragonpilot.git
synced 2026-08-21 08:03:42 +08:00
openpilot v0.11.1 release
date: 2026-06-04T09:49:56 master commit: c0ab3550eca2e9daf197c46b7e4b24aa9637cf2e
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
.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
|
||||
|
||||
.claude/
|
||||
TASK.md
|
||||
@@ -0,0 +1,20 @@
|
||||
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/ $WORKDIR/board/body/ $WORKDIR/board/jungle/ && \
|
||||
touch $WORKDIR/__init__.py $WORKDIR/board/__init__.py $WORKDIR/board/body/__init__.py $WORKDIR/board/jungle/__init__.py
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends sudo && DEBIAN_FRONTEND=noninteractive $WORKDIR/setup.sh
|
||||
|
||||
# second pass for the opendbc moving tag
|
||||
ARG CACHEBUST=1
|
||||
RUN DEBIAN_FRONTEND=noninteractive $WORKDIR/setup.sh
|
||||
|
||||
RUN git config --global --add safe.directory $WORKDIR/panda
|
||||
COPY . $WORKDIR
|
||||
Vendored
+139
@@ -0,0 +1,139 @@
|
||||
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 && ${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 -o ConnectTimeout=30 -o ConnectionAttempts=3 -o ServerAliveInterval=10 -o ServerAliveCountMax=3 -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/opendbc_repo/opendbc /data/opendbc
|
||||
|
||||
# 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') {
|
||||
retry (3) {
|
||||
def date = sh(script: 'date', returnStdout: true).trim()
|
||||
phone(device_ip, "set time", "date -s '${date}'")
|
||||
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"],
|
||||
["flash", "cd scripts/ && ./reflash_internal_panda.py"],
|
||||
["flash jungle", "cd board/jungle && ./flash.py --all"],
|
||||
["test", "cd tests/hitl && pytest -o 'addopts=' --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"],
|
||||
["flash", "cd scripts/ && ./reflash_internal_panda.py"],
|
||||
["flash jungle", "cd board/jungle && ./flash.py --all"],
|
||||
["test", "cd tests/hitl && pytest -o 'addopts=' --durations=0 2*.py [5-9]*.py"],
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
@@ -0,0 +1,84 @@
|
||||
# Welcome to panda
|
||||
|
||||
panda speaks CAN and CAN FD, and it runs on the [STM32H725](https://www.st.com/resource/en/reference_manual/rm0468-stm32h723733-stm32h725735-and-stm32h730-value-line-advanced-armbased-32bit-mcus-stmicroelectronics.pdf).
|
||||
|
||||
## Directory structure
|
||||
|
||||
```
|
||||
.
|
||||
├── board # Code that runs on the STM32
|
||||
├── python # Python userspace library for interfacing with the panda
|
||||
├── tests # Tests for panda
|
||||
├── scripts # Miscellaneous used for panda development and debugging
|
||||
├── examples # Example scripts for using a panda in a car
|
||||
```
|
||||
|
||||
## Safety Model
|
||||
|
||||
panda is compiled with vehicle-specific safety logic provided by [opendbc](https://github.com/commaai/opendbc). See details about the car safety models, safety testing, and code rigor in that repository.
|
||||
|
||||
## Code Rigor
|
||||
|
||||
The panda firmware is written for its use in conjunction with [openpilot](https://github.com/commaai/openpilot). The panda firmware, through its safety model, provides and enforces the
|
||||
[openpilot safety](https://github.com/commaai/openpilot/blob/master/docs/SAFETY.md). Due to its critical function, it's important that the application code rigor within the `board` folder is held to high standards.
|
||||
|
||||
These are the [CI regression tests](https://github.com/commaai/panda/actions) we have in place:
|
||||
* A generic static code analysis is performed by [cppcheck](https://github.com/danmar/cppcheck/).
|
||||
* In addition, [cppcheck](https://github.com/danmar/cppcheck/) has a specific addon to check for [MISRA C:2012](https://misra.org.uk/) violations. See [current coverage](https://github.com/commaai/panda/blob/master/tests/misra/coverage_table).
|
||||
* Compiler options are strict: the flags `-Wall -Wextra -Wstrict-prototypes -Werror` are enforced.
|
||||
* The [safety logic](https://github.com/commaai/panda/tree/master/opendbc/safety) is tested and verified by [unit tests](https://github.com/commaai/panda/tree/master/opendbc/safety/tests) for each supported car variant.
|
||||
to ensure that the behavior remains unchanged.
|
||||
* A hardware-in-the-loop test verifies panda's functionalities on all active panda variants, including:
|
||||
* additional safety model checks
|
||||
* compiling and flashing the bootstub and app code
|
||||
* receiving, sending, and forwarding CAN messages on all buses
|
||||
* CAN loopback and latency tests through SPI
|
||||
|
||||
The above tests are themselves tested by:
|
||||
* a [mutation test](tests/misra/test_mutation.py) on the MISRA coverage
|
||||
* a [mutation test]([tests/misra/test_mutation.py](https://github.com/commaai/opendbc/blob/master/opendbc/safety/tests/mutation.sh)) on the vehicle-specific safety logic
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
git clone https://github.com/commaai/panda.git
|
||||
cd panda
|
||||
|
||||
# setup your environment
|
||||
./setup.sh
|
||||
|
||||
# build fw + run the tests
|
||||
./test.sh
|
||||
```
|
||||
|
||||
See [the Panda class](https://github.com/commaai/panda/blob/master/python/__init__.py) for how to interact with the panda.
|
||||
|
||||
For example, to receive CAN messages:
|
||||
``` python
|
||||
>>> from panda import Panda
|
||||
>>> panda = Panda()
|
||||
>>> panda.can_recv()
|
||||
```
|
||||
And to send one on bus 0:
|
||||
``` python
|
||||
>>> from opendbc.car.structs import CarParams
|
||||
>>> panda.set_safety_mode(CarParams.SafetyModel.allOutput)
|
||||
>>> panda.can_send(0x1aa, b'message', 0)
|
||||
```
|
||||
Note that you may have to setup [udev rules](https://github.com/commaai/panda/tree/master/drivers/linux) for Linux, such as
|
||||
``` bash
|
||||
sudo tee /etc/udev/rules.d/11-panda.rules <<EOF
|
||||
SUBSYSTEM=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="df11", MODE="0666"
|
||||
SUBSYSTEM=="usb", ATTRS{idVendor}=="3801", ATTRS{idProduct}=="ddcc", MODE="0666"
|
||||
SUBSYSTEM=="usb", ATTRS{idVendor}=="3801", ATTRS{idProduct}=="ddee", MODE="0666"
|
||||
SUBSYSTEM=="usb", ATTRS{idVendor}=="bbaa", ATTRS{idProduct}=="ddcc", MODE="0666"
|
||||
SUBSYSTEM=="usb", ATTRS{idVendor}=="bbaa", ATTRS{idProduct}=="ddee", MODE="0666"
|
||||
EOF
|
||||
sudo udevadm control --reload-rules && sudo udevadm trigger
|
||||
```
|
||||
|
||||
The panda jungle uses different udev rules. See [the repo](https://github.com/commaai/panda_jungle#udev-rules) for instructions.
|
||||
|
||||
## Licensing
|
||||
|
||||
panda software is released under the MIT license unless otherwise specified.
|
||||
@@ -0,0 +1,176 @@
|
||||
import os
|
||||
import hashlib
|
||||
import opendbc
|
||||
import subprocess
|
||||
|
||||
PREFIX = "arm-none-eabi-"
|
||||
BUILDER = "DEV"
|
||||
|
||||
common_flags = []
|
||||
|
||||
if os.getenv("RELEASE"):
|
||||
BUILD_TYPE = "RELEASE"
|
||||
cert_fn = os.getenv("CERT")
|
||||
assert cert_fn is not None, 'No certificate file specified. Please set CERT env variable'
|
||||
assert os.path.exists(cert_fn), 'Certificate file not found. Please specify absolute path'
|
||||
else:
|
||||
BUILD_TYPE = "DEBUG"
|
||||
cert_fn = File("./board/certs/debug").srcnode().relpath
|
||||
common_flags += ["-DALLOW_DEBUG"]
|
||||
|
||||
if os.getenv("DEBUG"):
|
||||
common_flags += ["-DDEBUG"]
|
||||
|
||||
def objcopy(source, target, env, for_signature):
|
||||
return '$OBJCOPY -O binary %s %s' % (source[0], target[0])
|
||||
|
||||
def get_version(builder, build_type):
|
||||
try:
|
||||
git = subprocess.check_output(["git", "rev-parse", "--short=8", "HEAD"], encoding='utf8').strip()
|
||||
except subprocess.CalledProcessError:
|
||||
git = "unknown"
|
||||
return f"{builder}-{git}-{build_type}"
|
||||
|
||||
def get_key_header(name):
|
||||
from Crypto.PublicKey import RSA
|
||||
|
||||
public_fn = File(f'./board/certs/{name}.pub').srcnode().get_path()
|
||||
with open(public_fn) as f:
|
||||
rsa = RSA.importKey(f.read())
|
||||
assert(rsa.size_in_bits() == 1024)
|
||||
|
||||
rr = pow(2**1024, 2, rsa.n)
|
||||
n0inv = 2**32 - pow(rsa.n, -1, 2**32)
|
||||
|
||||
r = [
|
||||
f"RSAPublicKey {name}_rsa_key = {{",
|
||||
f" .len = 0x20,",
|
||||
f" .n0inv = {n0inv}U,",
|
||||
f" .n = {to_c_uint32(rsa.n)},",
|
||||
f" .rr = {to_c_uint32(rr)},",
|
||||
f" .exponent = {rsa.e},",
|
||||
f"}};",
|
||||
]
|
||||
return r
|
||||
|
||||
def to_c_uint32(x):
|
||||
nums = []
|
||||
for _ in range(0x20):
|
||||
nums.append(x % (2**32))
|
||||
x //= (2**32)
|
||||
return "{" + 'U,'.join(map(str, nums)) + "U}"
|
||||
|
||||
|
||||
def build_project(project_name, project, main, extra_flags):
|
||||
project_dir = Dir(f'./board/obj/{project_name}/')
|
||||
|
||||
flags = project["FLAGS"] + extra_flags + common_flags + [
|
||||
"-Wall",
|
||||
"-Wextra",
|
||||
"-Wstrict-prototypes",
|
||||
"-Werror",
|
||||
"-mlittle-endian",
|
||||
"-mthumb",
|
||||
"-nostdlib",
|
||||
"-fno-builtin",
|
||||
"-std=gnu11",
|
||||
"-fmax-errors=1",
|
||||
f"-T{File(project['LINKER_SCRIPT']).srcnode().relpath}",
|
||||
"-fsingle-precision-constant",
|
||||
"-Os",
|
||||
"-g",
|
||||
]
|
||||
|
||||
env = Environment(
|
||||
ENV=os.environ,
|
||||
CC=PREFIX + 'gcc',
|
||||
AS=PREFIX + 'gcc',
|
||||
OBJCOPY=PREFIX + 'objcopy',
|
||||
OBJDUMP=PREFIX + 'objdump',
|
||||
OBJPREFIX=project_dir,
|
||||
CFLAGS=flags,
|
||||
ASFLAGS=flags,
|
||||
LINKFLAGS=flags,
|
||||
CPPPATH=[Dir("./"), "./board/stm32h7/inc", opendbc.INCLUDE_PATH],
|
||||
ASCOM="$AS $ASFLAGS -o $TARGET -c $SOURCES",
|
||||
BUILDERS={
|
||||
'Objcopy': Builder(generator=objcopy, suffix='.bin', src_suffix='.elf')
|
||||
},
|
||||
tools=["default", "compilation_db"],
|
||||
)
|
||||
|
||||
startup = env.Object(project["STARTUP_FILE"])
|
||||
|
||||
# Build bootstub
|
||||
bs_env = env.Clone()
|
||||
bs_env.Append(CFLAGS="-DBOOTSTUB", ASFLAGS="-DBOOTSTUB", LINKFLAGS="-DBOOTSTUB")
|
||||
bs_elf = bs_env.Program(f"{project_dir}/bootstub.elf", [
|
||||
startup,
|
||||
"./board/crypto/rsa.c",
|
||||
"./board/crypto/sha.c",
|
||||
"./board/bootstub.c",
|
||||
])
|
||||
bs_env.Objcopy(f"./board/obj/bootstub.{project_name}.bin", bs_elf)
|
||||
|
||||
# Build + sign main (aka app)
|
||||
main_elf = env.Program(f"{project_dir}/main.elf", [
|
||||
startup,
|
||||
main
|
||||
], LINKFLAGS=[f"-Wl,--section-start,.isr_vector={project['APP_START_ADDRESS']}"] + flags)
|
||||
main_bin = env.Objcopy(f"{project_dir}/main.bin", main_elf)
|
||||
sign_py = File(f"./board/crypto/sign.py").srcnode().relpath
|
||||
env.Command(f"./board/obj/{project_name}.bin.signed", main_bin, f"SETLEN=1 {sign_py} $SOURCE $TARGET {cert_fn}")
|
||||
|
||||
|
||||
|
||||
base_project_h7 = {
|
||||
"STARTUP_FILE": "./board/stm32h7/startup_stm32h7x5xx.s",
|
||||
"LINKER_SCRIPT": "./board/stm32h7/stm32h7x5_flash.ld",
|
||||
"APP_START_ADDRESS": "0x8020000",
|
||||
"FLAGS": [
|
||||
"-mcpu=cortex-m7",
|
||||
"-mhard-float",
|
||||
"-DSTM32H7",
|
||||
"-DSTM32H725xx",
|
||||
"-Iboard/stm32h7/inc",
|
||||
"-mfpu=fpv5-d16",
|
||||
],
|
||||
}
|
||||
|
||||
# Common autogenerated includes
|
||||
with open("board/obj/gitversion.h", "w") as f:
|
||||
version = get_version(BUILDER, BUILD_TYPE)
|
||||
f.write(f'extern const uint8_t gitversion[{len(version)+1}];\n')
|
||||
f.write(f'const uint8_t gitversion[{len(version)+1}] = "{version}";\n')
|
||||
|
||||
with open("board/obj/version", "w") as f:
|
||||
f.write(f'{get_version(BUILDER, BUILD_TYPE)}')
|
||||
|
||||
certs = [get_key_header(n) for n in ["debug", "release"]]
|
||||
with open("board/obj/cert.h", "w") as f:
|
||||
for cert in certs:
|
||||
f.write("\n".join(cert) + "\n")
|
||||
|
||||
# Packet version defines: SHA hash of the struct header files
|
||||
def version_hash(path):
|
||||
with open(path, "rb") as f:
|
||||
# Normalize line endings on Windows
|
||||
return int.from_bytes(hashlib.sha256(f.read().replace(b'\r', b'')).digest()[:4], 'little')
|
||||
hh, ch, jh = version_hash("board/health.h"), version_hash(os.path.join(opendbc.INCLUDE_PATH, "opendbc/safety/can.h")), version_hash("board/jungle/jungle_health.h")
|
||||
common_flags += [f"-DHEALTH_PACKET_VERSION=0x{hh:08X}U", f"-DCAN_PACKET_VERSION_HASH=0x{ch:08X}U",
|
||||
f"-DJUNGLE_HEALTH_PACKET_VERSION=0x{jh:08X}U"]
|
||||
|
||||
# panda fw
|
||||
build_project("panda_h7", base_project_h7, "./board/main.c", [])
|
||||
|
||||
# panda jungle fw
|
||||
flags = [
|
||||
"-DPANDA_JUNGLE",
|
||||
]
|
||||
build_project("panda_jungle_h7", base_project_h7, "./board/jungle/main.c", flags)
|
||||
|
||||
# body fw
|
||||
build_project("body_h7", base_project_h7, "./board/body/main.c", ["-DPANDA_BODY"])
|
||||
|
||||
# test files
|
||||
SConscript('tests/libpanda/SConscript')
|
||||
@@ -0,0 +1,13 @@
|
||||
import os
|
||||
|
||||
env = Environment(
|
||||
COMPILATIONDB_USE_ABSPATH=True,
|
||||
tools=["default", "compilation_db"],
|
||||
)
|
||||
|
||||
SetOption('num_jobs', max(1, int((os.cpu_count() or 1)-1)))
|
||||
|
||||
env.CompilationDatabase("compile_commands.json")
|
||||
|
||||
# panda fw & test files
|
||||
SConscript('SConscript')
|
||||
@@ -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
|
||||
@@ -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,0 +1,62 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
// ******************** Prototypes ********************
|
||||
typedef enum {
|
||||
BOOT_STANDBY,
|
||||
BOOT_BOOTKICK,
|
||||
BOOT_RESET,
|
||||
} BootState;
|
||||
|
||||
typedef void (*board_init)(void);
|
||||
typedef void (*board_init_bootloader)(void);
|
||||
typedef void (*board_enable_can_transceiver)(uint8_t transceiver, bool enabled);
|
||||
typedef void (*board_set_can_mode)(uint8_t mode);
|
||||
typedef uint32_t (*board_read_voltage_mV)(void);
|
||||
typedef uint32_t (*board_read_current_mA)(void);
|
||||
typedef void (*board_set_ir_power)(uint8_t percentage);
|
||||
typedef void (*board_set_fan_enabled)(bool enabled);
|
||||
typedef void (*board_set_siren)(bool enabled);
|
||||
typedef void (*board_set_bootkick)(BootState state);
|
||||
typedef bool (*board_read_som_gpio)(void);
|
||||
typedef void (*board_set_amp_enabled)(bool enabled);
|
||||
|
||||
struct board {
|
||||
harness_configuration *harness_config;
|
||||
GPIO_TypeDef * const led_GPIO[3];
|
||||
const uint8_t led_pin[3];
|
||||
const uint8_t led_pwm_channels[3]; // leave at 0 to disable PWM
|
||||
const bool has_spi;
|
||||
const bool has_fan;
|
||||
const uint16_t avdd_mV;
|
||||
const uint8_t fan_enable_cooldown_time;
|
||||
board_init init;
|
||||
board_init_bootloader init_bootloader;
|
||||
board_enable_can_transceiver enable_can_transceiver;
|
||||
board_set_can_mode set_can_mode;
|
||||
board_read_voltage_mV read_voltage_mV;
|
||||
board_read_current_mA read_current_mA;
|
||||
board_set_ir_power set_ir_power;
|
||||
board_set_fan_enabled set_fan_enabled;
|
||||
board_set_siren set_siren;
|
||||
board_set_bootkick set_bootkick;
|
||||
board_read_som_gpio read_som_gpio;
|
||||
board_set_amp_enabled set_amp_enabled;
|
||||
};
|
||||
|
||||
// ******************* Definitions ********************
|
||||
// These should match the enums in cereal/log.capnp and __init__.py
|
||||
#define HW_TYPE_UNKNOWN 0U
|
||||
#define HW_TYPE_RED_PANDA 7U
|
||||
#define HW_TYPE_TRES 9U
|
||||
#define HW_TYPE_CUATRO 10U
|
||||
|
||||
// CAN modes
|
||||
#define CAN_MODE_NORMAL 0U
|
||||
#define CAN_MODE_OBD_CAN2 1U
|
||||
|
||||
extern struct board board_tres;
|
||||
extern struct board board_cuatro;
|
||||
extern struct board board_red;
|
||||
@@ -0,0 +1,137 @@
|
||||
#pragma once
|
||||
|
||||
#include "board_declarations.h"
|
||||
|
||||
// ////////////////////////// //
|
||||
// Cuatro (STM32H7) + Harness //
|
||||
// ////////////////////////// //
|
||||
|
||||
static void cuatro_enable_can_transceiver(uint8_t transceiver, bool enabled) {
|
||||
switch (transceiver) {
|
||||
case 1U:
|
||||
set_gpio_output(GPIOB, 7, !enabled);
|
||||
break;
|
||||
case 2U:
|
||||
set_gpio_output(GPIOB, 10, !enabled);
|
||||
break;
|
||||
case 3U:
|
||||
set_gpio_output(GPIOD, 8, !enabled);
|
||||
break;
|
||||
case 4U:
|
||||
set_gpio_output(GPIOB, 11, !enabled);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t cuatro_read_voltage_mV(void) {
|
||||
return adc_get_mV(&(const adc_signal_t) ADC_CHANNEL_DEFAULT(ADC1, 8)) * 11U;
|
||||
}
|
||||
|
||||
static uint32_t cuatro_read_current_mA(void) {
|
||||
return adc_get_mV(&(const adc_signal_t) ADC_CHANNEL_DEFAULT(ADC1, 3)) * 2U;
|
||||
}
|
||||
|
||||
static void cuatro_set_fan_enabled(bool enabled) {
|
||||
set_gpio_output(GPIOD, 3, !enabled);
|
||||
}
|
||||
|
||||
static void cuatro_set_bootkick(BootState state) {
|
||||
set_gpio_output(GPIOA, 0, state != BOOT_BOOTKICK);
|
||||
// DC_IN rising edge wakes SOM from ship mode
|
||||
set_gpio_output(GPIOC, 11, state != BOOT_BOOTKICK);
|
||||
}
|
||||
|
||||
static void cuatro_set_amp_enabled(bool enabled) {
|
||||
set_gpio_output(GPIOB, 0, enabled);
|
||||
}
|
||||
|
||||
static void cuatro_init(void) {
|
||||
common_init_gpio();
|
||||
|
||||
// open drain
|
||||
set_gpio_output_type(GPIOD, 3, OUTPUT_TYPE_OPEN_DRAIN); // FAN_EN
|
||||
set_gpio_output_type(GPIOC, 11, OUTPUT_TYPE_OPEN_DRAIN); // DC_IN_EN_N
|
||||
|
||||
// Power readout
|
||||
set_gpio_mode(GPIOC, 5, MODE_ANALOG);
|
||||
set_gpio_mode(GPIOA, 6, MODE_ANALOG);
|
||||
|
||||
// CAN transceiver enables
|
||||
set_gpio_pullup(GPIOB, 7, PULL_NONE);
|
||||
set_gpio_mode(GPIOB, 7, MODE_OUTPUT);
|
||||
set_gpio_pullup(GPIOD, 8, PULL_NONE);
|
||||
set_gpio_mode(GPIOD, 8, MODE_OUTPUT);
|
||||
|
||||
// FDCAN3, different pins on this package than the rest of the reds
|
||||
set_gpio_pullup(GPIOD, 12, PULL_NONE);
|
||||
set_gpio_alternate(GPIOD, 12, GPIO_AF5_FDCAN3);
|
||||
set_gpio_pullup(GPIOD, 13, PULL_NONE);
|
||||
set_gpio_alternate(GPIOD, 13, GPIO_AF5_FDCAN3);
|
||||
|
||||
// C2: SOM GPIO used as input (fan control at boot)
|
||||
set_gpio_mode(GPIOC, 2, MODE_INPUT);
|
||||
set_gpio_pullup(GPIOC, 2, PULL_DOWN);
|
||||
|
||||
// SOM bootkick + reset lines
|
||||
cuatro_set_bootkick(BOOT_BOOTKICK);
|
||||
|
||||
// SOM debugging UART
|
||||
gpio_uart7_init();
|
||||
uart_init(&uart_ring_som_debug, 115200);
|
||||
|
||||
// fan setup
|
||||
set_gpio_alternate(GPIOC, 8, GPIO_AF2_TIM3);
|
||||
register_set_bits(&(GPIOC->OTYPER), GPIO_OTYPER_OT8); // open drain
|
||||
|
||||
// Clock source
|
||||
clock_source_init(true);
|
||||
|
||||
// Sound codec
|
||||
cuatro_set_amp_enabled(false);
|
||||
set_gpio_alternate(GPIOA, 2, GPIO_AF8_SAI4); // SAI4_SCK_B
|
||||
set_gpio_alternate(GPIOC, 0, GPIO_AF8_SAI4); // SAI4_FS_B
|
||||
set_gpio_alternate(GPIOD, 11, GPIO_AF10_SAI4); // SAI4_SD_A
|
||||
set_gpio_alternate(GPIOE, 3, GPIO_AF8_SAI4); // SAI4_SD_B
|
||||
set_gpio_alternate(GPIOE, 4, GPIO_AF3_DFSDM1); // DFSDM1_DATIN3
|
||||
set_gpio_alternate(GPIOE, 9, GPIO_AF3_DFSDM1); // DFSDM1_CKOUT
|
||||
set_gpio_alternate(GPIOE, 6, GPIO_AF10_SAI4); // SAI4_MCLK_B
|
||||
sound_init();
|
||||
}
|
||||
|
||||
static harness_configuration cuatro_harness_config = {
|
||||
.GPIO_SBU1 = GPIOC,
|
||||
.GPIO_SBU2 = GPIOA,
|
||||
.GPIO_relay_SBU1 = GPIOA,
|
||||
.GPIO_relay_SBU2 = GPIOA,
|
||||
.pin_SBU1 = 4,
|
||||
.pin_SBU2 = 1,
|
||||
.pin_relay_SBU1 = 9,
|
||||
.pin_relay_SBU2 = 3,
|
||||
.adc_signal_SBU1 = ADC_CHANNEL_DEFAULT(ADC1, 4),
|
||||
.adc_signal_SBU2 = ADC_CHANNEL_DEFAULT(ADC1, 17)
|
||||
};
|
||||
|
||||
board board_cuatro = {
|
||||
.harness_config = &cuatro_harness_config,
|
||||
.has_spi = true,
|
||||
.has_fan = true,
|
||||
.avdd_mV = 1800U,
|
||||
.fan_enable_cooldown_time = 3U,
|
||||
.init = cuatro_init,
|
||||
.init_bootloader = unused_init_bootloader,
|
||||
.enable_can_transceiver = cuatro_enable_can_transceiver,
|
||||
.led_GPIO = {GPIOC, GPIOC, GPIOC},
|
||||
.led_pin = {6, 7, 9},
|
||||
.led_pwm_channels = {1, 2, 4},
|
||||
.set_can_mode = tres_set_can_mode,
|
||||
.read_voltage_mV = cuatro_read_voltage_mV,
|
||||
.read_current_mA = cuatro_read_current_mA,
|
||||
.set_fan_enabled = cuatro_set_fan_enabled,
|
||||
.set_ir_power = unused_set_ir_power,
|
||||
.set_siren = fake_siren_set,
|
||||
.set_bootkick = cuatro_set_bootkick,
|
||||
.read_som_gpio = tres_read_som_gpio,
|
||||
.set_amp_enabled = cuatro_set_amp_enabled
|
||||
};
|
||||
@@ -0,0 +1,134 @@
|
||||
#pragma once
|
||||
|
||||
#include "board_declarations.h"
|
||||
|
||||
// ///////////////////////////// //
|
||||
// Red Panda (STM32H7) + Harness //
|
||||
// ///////////////////////////// //
|
||||
|
||||
static void red_enable_can_transceiver(uint8_t transceiver, bool enabled) {
|
||||
switch (transceiver) {
|
||||
case 1U:
|
||||
set_gpio_output(GPIOG, 11, !enabled);
|
||||
break;
|
||||
case 2U:
|
||||
set_gpio_output(GPIOB, 3, !enabled);
|
||||
break;
|
||||
case 3U:
|
||||
set_gpio_output(GPIOD, 7, !enabled);
|
||||
break;
|
||||
case 4U:
|
||||
set_gpio_output(GPIOB, 4, !enabled);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void red_set_can_mode(uint8_t mode) {
|
||||
red_enable_can_transceiver(2U, false);
|
||||
red_enable_can_transceiver(4U, false);
|
||||
switch (mode) {
|
||||
case CAN_MODE_NORMAL:
|
||||
case CAN_MODE_OBD_CAN2:
|
||||
if ((bool)(mode == CAN_MODE_NORMAL) != (bool)(harness.status == HARNESS_STATUS_FLIPPED)) {
|
||||
// B12,B13: disable normal mode
|
||||
set_gpio_pullup(GPIOB, 12, PULL_NONE);
|
||||
set_gpio_mode(GPIOB, 12, MODE_ANALOG);
|
||||
|
||||
set_gpio_pullup(GPIOB, 13, PULL_NONE);
|
||||
set_gpio_mode(GPIOB, 13, MODE_ANALOG);
|
||||
|
||||
// B5,B6: FDCAN2 mode
|
||||
set_gpio_pullup(GPIOB, 5, PULL_NONE);
|
||||
set_gpio_alternate(GPIOB, 5, GPIO_AF9_FDCAN2);
|
||||
|
||||
set_gpio_pullup(GPIOB, 6, PULL_NONE);
|
||||
set_gpio_alternate(GPIOB, 6, GPIO_AF9_FDCAN2);
|
||||
red_enable_can_transceiver(2U, true);
|
||||
} else {
|
||||
// B5,B6: disable normal mode
|
||||
set_gpio_pullup(GPIOB, 5, PULL_NONE);
|
||||
set_gpio_mode(GPIOB, 5, MODE_ANALOG);
|
||||
|
||||
set_gpio_pullup(GPIOB, 6, PULL_NONE);
|
||||
set_gpio_mode(GPIOB, 6, MODE_ANALOG);
|
||||
// B12,B13: FDCAN2 mode
|
||||
set_gpio_pullup(GPIOB, 12, PULL_NONE);
|
||||
set_gpio_alternate(GPIOB, 12, GPIO_AF9_FDCAN2);
|
||||
|
||||
set_gpio_pullup(GPIOB, 13, PULL_NONE);
|
||||
set_gpio_alternate(GPIOB, 13, GPIO_AF9_FDCAN2);
|
||||
red_enable_can_transceiver(4U, true);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t red_read_voltage_mV(void){
|
||||
return adc_get_mV(&(const adc_signal_t) ADC_CHANNEL_DEFAULT(ADC1, 2)) * 11U;
|
||||
}
|
||||
|
||||
static void red_init(void) {
|
||||
common_init_gpio();
|
||||
|
||||
// G11,B3,D7,B4: transceiver enable
|
||||
set_gpio_pullup(GPIOG, 11, PULL_NONE);
|
||||
set_gpio_mode(GPIOG, 11, MODE_OUTPUT);
|
||||
|
||||
set_gpio_pullup(GPIOB, 3, PULL_NONE);
|
||||
set_gpio_mode(GPIOB, 3, MODE_OUTPUT);
|
||||
|
||||
set_gpio_pullup(GPIOD, 7, PULL_NONE);
|
||||
set_gpio_mode(GPIOD, 7, MODE_OUTPUT);
|
||||
|
||||
set_gpio_pullup(GPIOB, 4, PULL_NONE);
|
||||
set_gpio_mode(GPIOB, 4, MODE_OUTPUT);
|
||||
|
||||
//B1: 5VOUT_S
|
||||
set_gpio_pullup(GPIOB, 1, PULL_NONE);
|
||||
set_gpio_mode(GPIOB, 1, MODE_ANALOG);
|
||||
|
||||
// B14: usb load switch, enabled by pull resistor on board, obsolete for red panda
|
||||
set_gpio_output_type(GPIOB, 14, OUTPUT_TYPE_OPEN_DRAIN);
|
||||
set_gpio_pullup(GPIOB, 14, PULL_UP);
|
||||
set_gpio_mode(GPIOB, 14, MODE_OUTPUT);
|
||||
set_gpio_output(GPIOB, 14, 1);
|
||||
}
|
||||
|
||||
static harness_configuration red_harness_config = {
|
||||
.GPIO_SBU1 = GPIOC,
|
||||
.GPIO_SBU2 = GPIOA,
|
||||
.GPIO_relay_SBU1 = GPIOC,
|
||||
.GPIO_relay_SBU2 = GPIOC,
|
||||
.pin_SBU1 = 4,
|
||||
.pin_SBU2 = 1,
|
||||
.pin_relay_SBU1 = 10,
|
||||
.pin_relay_SBU2 = 11,
|
||||
.adc_signal_SBU1 = ADC_CHANNEL_DEFAULT(ADC1, 4),
|
||||
.adc_signal_SBU2 = ADC_CHANNEL_DEFAULT(ADC1, 17)
|
||||
};
|
||||
|
||||
board board_red = {
|
||||
.set_bootkick = unused_set_bootkick,
|
||||
.harness_config = &red_harness_config,
|
||||
.has_spi = false,
|
||||
.has_fan = false,
|
||||
.avdd_mV = 3300U,
|
||||
.fan_enable_cooldown_time = 0U,
|
||||
.init = red_init,
|
||||
.init_bootloader = unused_init_bootloader,
|
||||
.enable_can_transceiver = red_enable_can_transceiver,
|
||||
.led_GPIO = {GPIOE, GPIOE, GPIOE},
|
||||
.led_pin = {4, 3, 2},
|
||||
.set_can_mode = red_set_can_mode,
|
||||
.read_voltage_mV = red_read_voltage_mV,
|
||||
.read_current_mA = unused_read_current,
|
||||
.set_fan_enabled = unused_set_fan_enabled,
|
||||
.set_ir_power = unused_set_ir_power,
|
||||
.set_siren = unused_set_siren,
|
||||
.read_som_gpio = unused_read_som_gpio,
|
||||
.set_amp_enabled = unused_set_amp_enabled
|
||||
};
|
||||
@@ -0,0 +1,175 @@
|
||||
#pragma once
|
||||
|
||||
#include "board_declarations.h"
|
||||
|
||||
// ///////////////////////////
|
||||
// Tres (STM32H7) + Harness //
|
||||
// ///////////////////////////
|
||||
|
||||
static bool tres_ir_enabled;
|
||||
static bool tres_fan_enabled;
|
||||
static void tres_update_fan_ir_power(void) {
|
||||
set_gpio_output(GPIOD, 3, tres_ir_enabled || tres_fan_enabled);
|
||||
}
|
||||
|
||||
static void tres_set_ir_power(uint8_t percentage){
|
||||
tres_ir_enabled = (percentage > 0U);
|
||||
tres_update_fan_ir_power();
|
||||
pwm_set(TIM3, 4, percentage);
|
||||
}
|
||||
|
||||
static void tres_set_bootkick(BootState state) {
|
||||
set_gpio_output(GPIOA, 0, state != BOOT_BOOTKICK);
|
||||
set_gpio_output(GPIOC, 12, state != BOOT_RESET);
|
||||
}
|
||||
|
||||
static void tres_set_fan_enabled(bool enabled) {
|
||||
// NOTE: fan controller reset doesn't work on a tres if IR is enabled
|
||||
tres_fan_enabled = enabled;
|
||||
tres_update_fan_ir_power();
|
||||
}
|
||||
|
||||
static void tres_enable_can_transceiver(uint8_t transceiver, bool enabled) {
|
||||
static bool can0_enabled = false;
|
||||
static bool can2_enabled = false;
|
||||
|
||||
switch (transceiver) {
|
||||
case 1U:
|
||||
can0_enabled = enabled;
|
||||
break;
|
||||
case 2U:
|
||||
set_gpio_output(GPIOB, 10, !enabled);
|
||||
break;
|
||||
case 3U:
|
||||
can2_enabled = enabled;
|
||||
break;
|
||||
case 4U:
|
||||
set_gpio_output(GPIOB, 11, !enabled);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// CAN0 and 2 are tied, so enable both if either is enabled
|
||||
set_gpio_output(GPIOG, 11, !(can0_enabled || can2_enabled));
|
||||
set_gpio_output(GPIOD, 7, !(can0_enabled || can2_enabled));
|
||||
}
|
||||
|
||||
static void tres_set_can_mode(uint8_t mode) {
|
||||
current_board->enable_can_transceiver(2U, false);
|
||||
current_board->enable_can_transceiver(4U, false);
|
||||
switch (mode) {
|
||||
case CAN_MODE_NORMAL:
|
||||
case CAN_MODE_OBD_CAN2:
|
||||
if ((bool)(mode == CAN_MODE_NORMAL) != (bool)(harness.status == HARNESS_STATUS_FLIPPED)) {
|
||||
// B12,B13: disable normal mode
|
||||
set_gpio_pullup(GPIOB, 12, PULL_NONE);
|
||||
set_gpio_mode(GPIOB, 12, MODE_ANALOG);
|
||||
|
||||
set_gpio_pullup(GPIOB, 13, PULL_NONE);
|
||||
set_gpio_mode(GPIOB, 13, MODE_ANALOG);
|
||||
|
||||
// B5,B6: FDCAN2 mode
|
||||
set_gpio_pullup(GPIOB, 5, PULL_NONE);
|
||||
set_gpio_alternate(GPIOB, 5, GPIO_AF9_FDCAN2);
|
||||
|
||||
set_gpio_pullup(GPIOB, 6, PULL_NONE);
|
||||
set_gpio_alternate(GPIOB, 6, GPIO_AF9_FDCAN2);
|
||||
current_board->enable_can_transceiver(2U, true);
|
||||
} else {
|
||||
// B5,B6: disable normal mode
|
||||
set_gpio_pullup(GPIOB, 5, PULL_NONE);
|
||||
set_gpio_mode(GPIOB, 5, MODE_ANALOG);
|
||||
|
||||
set_gpio_pullup(GPIOB, 6, PULL_NONE);
|
||||
set_gpio_mode(GPIOB, 6, MODE_ANALOG);
|
||||
// B12,B13: FDCAN2 mode
|
||||
set_gpio_pullup(GPIOB, 12, PULL_NONE);
|
||||
set_gpio_alternate(GPIOB, 12, GPIO_AF9_FDCAN2);
|
||||
|
||||
set_gpio_pullup(GPIOB, 13, PULL_NONE);
|
||||
set_gpio_alternate(GPIOB, 13, GPIO_AF9_FDCAN2);
|
||||
current_board->enable_can_transceiver(4U, true);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static bool tres_read_som_gpio (void) {
|
||||
return (get_gpio_input(GPIOC, 2) != 0);
|
||||
}
|
||||
|
||||
static void tres_init(void) {
|
||||
// Enable USB 3.3V LDO for USB block
|
||||
register_set_bits(&(PWR->CR3), PWR_CR3_USBREGEN);
|
||||
register_set_bits(&(PWR->CR3), PWR_CR3_USB33DEN);
|
||||
while ((PWR->CR3 & PWR_CR3_USB33RDY) == 0U);
|
||||
|
||||
common_init_gpio();
|
||||
|
||||
// C2: SOM GPIO used as input (fan control at boot)
|
||||
set_gpio_mode(GPIOC, 2, MODE_INPUT);
|
||||
set_gpio_pullup(GPIOC, 2, PULL_DOWN);
|
||||
|
||||
// SOM bootkick + reset lines
|
||||
// WARNING: make sure output state is set before configuring as output
|
||||
tres_set_bootkick(BOOT_BOOTKICK);
|
||||
set_gpio_mode(GPIOC, 12, MODE_OUTPUT);
|
||||
|
||||
// SOM debugging UART
|
||||
gpio_uart7_init();
|
||||
uart_init(&uart_ring_som_debug, 115200);
|
||||
|
||||
// fan setup
|
||||
set_gpio_alternate(GPIOC, 8, GPIO_AF2_TIM3);
|
||||
|
||||
// Initialize IR PWM and set to 0%
|
||||
set_gpio_alternate(GPIOC, 9, GPIO_AF2_TIM3);
|
||||
pwm_init(TIM3, 4);
|
||||
tres_set_ir_power(0U);
|
||||
|
||||
// Fake siren
|
||||
set_gpio_alternate(GPIOC, 10, GPIO_AF4_I2C5);
|
||||
set_gpio_alternate(GPIOC, 11, GPIO_AF4_I2C5);
|
||||
register_set_bits(&(GPIOC->OTYPER), GPIO_OTYPER_OT10 | GPIO_OTYPER_OT11); // open drain
|
||||
|
||||
// Clock source
|
||||
clock_source_init(false);
|
||||
}
|
||||
|
||||
static harness_configuration tres_harness_config = {
|
||||
.GPIO_SBU1 = GPIOC,
|
||||
.GPIO_SBU2 = GPIOA,
|
||||
.GPIO_relay_SBU1 = GPIOA,
|
||||
.GPIO_relay_SBU2 = GPIOA,
|
||||
.pin_SBU1 = 4,
|
||||
.pin_SBU2 = 1,
|
||||
.pin_relay_SBU1 = 8,
|
||||
.pin_relay_SBU2 = 3,
|
||||
.adc_signal_SBU1 = ADC_CHANNEL_DEFAULT(ADC1, 4),
|
||||
.adc_signal_SBU2 = ADC_CHANNEL_DEFAULT(ADC1, 17)
|
||||
};
|
||||
|
||||
board board_tres = {
|
||||
.harness_config = &tres_harness_config,
|
||||
.has_spi = true,
|
||||
.has_fan = true,
|
||||
.avdd_mV = 1800U,
|
||||
.fan_enable_cooldown_time = 3U,
|
||||
.init = tres_init,
|
||||
.init_bootloader = unused_init_bootloader,
|
||||
.enable_can_transceiver = tres_enable_can_transceiver,
|
||||
.led_GPIO = {GPIOE, GPIOE, GPIOE},
|
||||
.led_pin = {4, 3, 2},
|
||||
.set_can_mode = tres_set_can_mode,
|
||||
.read_voltage_mV = red_read_voltage_mV,
|
||||
.read_current_mA = unused_read_current,
|
||||
.set_fan_enabled = tres_set_fan_enabled,
|
||||
.set_ir_power = tres_set_ir_power,
|
||||
.set_siren = fake_i2c_siren_set,
|
||||
.set_bootkick = tres_set_bootkick,
|
||||
.read_som_gpio = tres_read_som_gpio,
|
||||
.set_amp_enabled = unused_set_amp_enabled
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
void unused_init_bootloader(void) {
|
||||
}
|
||||
|
||||
void unused_set_ir_power(uint8_t percentage) {
|
||||
UNUSED(percentage);
|
||||
}
|
||||
|
||||
void unused_set_fan_enabled(bool enabled) {
|
||||
UNUSED(enabled);
|
||||
}
|
||||
|
||||
void unused_set_siren(bool enabled) {
|
||||
UNUSED(enabled);
|
||||
}
|
||||
|
||||
uint32_t unused_read_current(void) {
|
||||
return 0U;
|
||||
}
|
||||
|
||||
void unused_set_bootkick(BootState state) {
|
||||
UNUSED(state);
|
||||
}
|
||||
|
||||
bool unused_read_som_gpio(void) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void unused_set_amp_enabled(bool enabled) {
|
||||
UNUSED(enabled);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
# python helpers for the body panda
|
||||
import struct
|
||||
|
||||
from panda import Panda
|
||||
|
||||
class PandaBody(Panda):
|
||||
|
||||
MOTOR_LEFT: int = 1
|
||||
MOTOR_RIGHT: int = 2
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._rpm_left: int = 0
|
||||
self._rpm_right: int = 0
|
||||
|
||||
@property
|
||||
def rpm_left(self) -> int:
|
||||
return self._rpm_left
|
||||
|
||||
@rpm_left.setter
|
||||
def rpm_left(self, value: int) -> None:
|
||||
self._rpm_left = int(value)
|
||||
self._handle.controlWrite(Panda.REQUEST_OUT, 0xb3, self._rpm_left, self._rpm_right, b'')
|
||||
|
||||
@property
|
||||
def rpm_right(self) -> int:
|
||||
return self._rpm_right
|
||||
|
||||
@rpm_right.setter
|
||||
def rpm_right(self, value: int) -> None:
|
||||
self._rpm_right = int(value)
|
||||
self._handle.controlWrite(Panda.REQUEST_OUT, 0xb3, self._rpm_left, self._rpm_right, b'')
|
||||
|
||||
# ****************** 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_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
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,394 @@
|
||||
/*
|
||||
* File: BLDC_controller.h
|
||||
*
|
||||
* Code generated for Simulink model 'BLDC_controller'.
|
||||
*
|
||||
* Model version : 1.1297
|
||||
* Simulink Coder version : 8.13 (R2017b) 24-Jul-2017
|
||||
* C/C++ source code generated on : Sun Mar 6 11:02:11 2022
|
||||
*
|
||||
* Target selection: ert.tlc
|
||||
* Embedded hardware selection: ARM Compatible->ARM Cortex
|
||||
* Emulation hardware selection:
|
||||
* Differs from embedded hardware (MATLAB Host)
|
||||
* Code generation objectives:
|
||||
* 1. Execution efficiency
|
||||
* 2. RAM efficiency
|
||||
* Validation result: Not run
|
||||
*/
|
||||
|
||||
#ifndef RTW_HEADER_BLDC_controller_h_
|
||||
#define RTW_HEADER_BLDC_controller_h_
|
||||
#include "rtwtypes.h"
|
||||
#ifndef BLDC_controller_COMMON_INCLUDES_
|
||||
# define BLDC_controller_COMMON_INCLUDES_
|
||||
#include "rtwtypes.h"
|
||||
#endif /* BLDC_controller_COMMON_INCLUDES_ */
|
||||
|
||||
/* Macros for accessing real-time model data structure */
|
||||
|
||||
/* Forward declaration for rtModel */
|
||||
typedef struct tag_RTM RT_MODEL;
|
||||
|
||||
/* Block signals and states (auto storage) for system '<S13>/Counter' */
|
||||
typedef struct {
|
||||
int16_T UnitDelay_DSTATE; /* '<S18>/UnitDelay' */
|
||||
} DW_Counter;
|
||||
|
||||
/* Block signals and states (auto storage) for system '<S50>/Low_Pass_Filter' */
|
||||
typedef struct {
|
||||
int32_T UnitDelay1_DSTATE[2]; /* '<S56>/UnitDelay1' */
|
||||
} DW_Low_Pass_Filter;
|
||||
|
||||
/* Block signals and states (auto storage) for system '<S25>/Counter' */
|
||||
typedef struct {
|
||||
uint16_T UnitDelay_DSTATE; /* '<S30>/UnitDelay' */
|
||||
} DW_Counter_b;
|
||||
|
||||
/* Block signals and states (auto storage) for system '<S21>/either_edge' */
|
||||
typedef struct {
|
||||
boolean_T UnitDelay_DSTATE; /* '<S26>/UnitDelay' */
|
||||
} DW_either_edge;
|
||||
|
||||
/* Block signals and states (auto storage) for system '<S20>/Debounce_Filter' */
|
||||
typedef struct {
|
||||
DW_either_edge either_edge_p; /* '<S21>/either_edge' */
|
||||
DW_Counter_b Counter_e; /* '<S24>/Counter' */
|
||||
DW_Counter_b Counter_n1; /* '<S25>/Counter' */
|
||||
boolean_T UnitDelay_DSTATE; /* '<S21>/UnitDelay' */
|
||||
} DW_Debounce_Filter;
|
||||
|
||||
/* Block signals and states (auto storage) for system '<S83>/I_backCalc_fixdt' */
|
||||
typedef struct {
|
||||
int32_T UnitDelay_DSTATE; /* '<S88>/UnitDelay' */
|
||||
int32_T UnitDelay_DSTATE_m; /* '<S90>/UnitDelay' */
|
||||
} DW_I_backCalc_fixdt;
|
||||
|
||||
/* Block signals and states (auto storage) for system '<S63>/PI_clamp_fixdt' */
|
||||
typedef struct {
|
||||
int32_T ResettableDelay_DSTATE; /* '<S77>/Resettable Delay' */
|
||||
uint8_T icLoad; /* '<S77>/Resettable Delay' */
|
||||
boolean_T UnitDelay1_DSTATE; /* '<S74>/UnitDelay1' */
|
||||
} DW_PI_clamp_fixdt;
|
||||
|
||||
/* Block signals and states (auto storage) for system '<S61>/PI_clamp_fixdt' */
|
||||
typedef struct {
|
||||
int32_T ResettableDelay_DSTATE; /* '<S67>/Resettable Delay' */
|
||||
uint8_T icLoad; /* '<S67>/Resettable Delay' */
|
||||
boolean_T UnitDelay1_DSTATE; /* '<S65>/UnitDelay1' */
|
||||
} DW_PI_clamp_fixdt_m;
|
||||
|
||||
/* Block signals and states (auto storage) for system '<S62>/PI_clamp_fixdt' */
|
||||
typedef struct {
|
||||
int16_T ResettableDelay_DSTATE; /* '<S72>/Resettable Delay' */
|
||||
uint8_T icLoad; /* '<S72>/Resettable Delay' */
|
||||
boolean_T UnitDelay1_DSTATE; /* '<S69>/UnitDelay1' */
|
||||
} DW_PI_clamp_fixdt_g;
|
||||
|
||||
/* Block signals and states (auto storage) for system '<Root>' */
|
||||
typedef struct {
|
||||
DW_PI_clamp_fixdt_g PI_clamp_fixdt_kh;/* '<S62>/PI_clamp_fixdt' */
|
||||
DW_PI_clamp_fixdt_m PI_clamp_fixdt_l4;/* '<S61>/PI_clamp_fixdt' */
|
||||
DW_PI_clamp_fixdt PI_clamp_fixdt_i; /* '<S63>/PI_clamp_fixdt' */
|
||||
DW_I_backCalc_fixdt I_backCalc_fixdt_j;/* '<S82>/I_backCalc_fixdt' */
|
||||
DW_I_backCalc_fixdt I_backCalc_fixdt1;/* '<S83>/I_backCalc_fixdt1' */
|
||||
DW_I_backCalc_fixdt I_backCalc_fixdt_i;/* '<S83>/I_backCalc_fixdt' */
|
||||
DW_either_edge either_edge_i; /* '<S20>/either_edge' */
|
||||
DW_Debounce_Filter Debounce_Filter_k;/* '<S20>/Debounce_Filter' */
|
||||
DW_Low_Pass_Filter Low_Pass_Filter_m;/* '<S50>/Low_Pass_Filter' */
|
||||
DW_Counter Counter_e; /* '<S13>/Counter' */
|
||||
int32_T Divide1; /* '<S81>/Divide1' */
|
||||
int32_T UnitDelay_DSTATE; /* '<S40>/UnitDelay' */
|
||||
int16_T Gain4_e[3]; /* '<S57>/Gain4' */
|
||||
int16_T DataTypeConversion[2]; /* '<S56>/Data Type Conversion' */
|
||||
int16_T z_counterRawPrev; /* '<S17>/z_counterRawPrev' */
|
||||
int16_T Merge; /* '<S59>/Merge' */
|
||||
int16_T Switch1; /* '<S78>/Switch1' */
|
||||
int16_T Vd_max1; /* '<S80>/Vd_max1' */
|
||||
int16_T Gain3; /* '<S80>/Gain3' */
|
||||
int16_T Vq_max_M1; /* '<S80>/Vq_max_M1' */
|
||||
int16_T Gain5; /* '<S80>/Gain5' */
|
||||
int16_T i_max; /* '<S80>/i_max' */
|
||||
int16_T Divide1_n; /* '<S80>/Divide1' */
|
||||
int16_T Gain1; /* '<S80>/Gain1' */
|
||||
int16_T Gain4; /* '<S80>/Gain4' */
|
||||
int16_T Switch2_i; /* '<S87>/Switch2' */
|
||||
int16_T Switch2_o; /* '<S93>/Switch2' */
|
||||
int16_T Switch2_a; /* '<S91>/Switch2' */
|
||||
int16_T Divide3; /* '<S42>/Divide3' */
|
||||
int16_T Merge1; /* '<S33>/Merge1' */
|
||||
int16_T Abs1; /* '<S5>/Abs1' */
|
||||
int16_T Abs5_h; /* '<S50>/Abs5' */
|
||||
int16_T Divide11; /* '<S17>/Divide11' */
|
||||
int16_T r_sin_M1; /* '<S52>/r_sin_M1' */
|
||||
int16_T r_cos_M1; /* '<S52>/r_cos_M1' */
|
||||
int16_T UnitDelay3_DSTATE; /* '<S13>/UnitDelay3' */
|
||||
int16_T UnitDelay4_DSTATE; /* '<S17>/UnitDelay4' */
|
||||
int16_T UnitDelay2_DSTATE; /* '<S17>/UnitDelay2' */
|
||||
int16_T UnitDelay3_DSTATE_o; /* '<S17>/UnitDelay3' */
|
||||
int16_T UnitDelay5_DSTATE; /* '<S17>/UnitDelay5' */
|
||||
int16_T UnitDelay4_DSTATE_e; /* '<S13>/UnitDelay4' */
|
||||
int16_T UnitDelay4_DSTATE_eu; /* '<S8>/UnitDelay4' */
|
||||
int8_T Switch2_e; /* '<S12>/Switch2' */
|
||||
int8_T UnitDelay2_DSTATE_b; /* '<S12>/UnitDelay2' */
|
||||
int8_T If1_ActiveSubsystem; /* '<S7>/If1' */
|
||||
int8_T If2_ActiveSubsystem; /* '<S7>/If2' */
|
||||
int8_T If1_ActiveSubsystem_j; /* '<S47>/If1' */
|
||||
int8_T SwitchCase_ActiveSubsystem; /* '<S59>/Switch Case' */
|
||||
int8_T If1_ActiveSubsystem_a; /* '<S59>/If1' */
|
||||
int8_T If1_ActiveSubsystem_o; /* '<S48>/If1' */
|
||||
int8_T SwitchCase_ActiveSubsystem_d; /* '<S80>/Switch Case' */
|
||||
int8_T If2_ActiveSubsystem_f; /* '<S33>/If2' */
|
||||
int8_T If2_ActiveSubsystem_a; /* '<S45>/If2' */
|
||||
uint8_T z_ctrlMod; /* '<S5>/F03_02_Control_Mode_Manager' */
|
||||
uint8_T UnitDelay3_DSTATE_fy; /* '<S10>/UnitDelay3' */
|
||||
uint8_T UnitDelay1_DSTATE; /* '<S10>/UnitDelay1' */
|
||||
uint8_T UnitDelay2_DSTATE_f; /* '<S10>/UnitDelay2' */
|
||||
uint8_T UnitDelay_DSTATE_e; /* '<S20>/UnitDelay' */
|
||||
uint8_T is_active_c1_BLDC_controller;/* '<S5>/F03_02_Control_Mode_Manager' */
|
||||
uint8_T is_c1_BLDC_controller; /* '<S5>/F03_02_Control_Mode_Manager' */
|
||||
uint8_T is_ACTIVE; /* '<S5>/F03_02_Control_Mode_Manager' */
|
||||
boolean_T Merge_p; /* '<S21>/Merge' */
|
||||
boolean_T dz_cntTrnsDet; /* '<S17>/dz_cntTrnsDet' */
|
||||
boolean_T UnitDelay2_DSTATE_c; /* '<S2>/UnitDelay2' */
|
||||
boolean_T UnitDelay5_DSTATE_m; /* '<S2>/UnitDelay5' */
|
||||
boolean_T UnitDelay6_DSTATE; /* '<S2>/UnitDelay6' */
|
||||
boolean_T UnitDelay_DSTATE_b; /* '<S39>/UnitDelay' */
|
||||
boolean_T UnitDelay1_DSTATE_n; /* '<S17>/UnitDelay1' */
|
||||
boolean_T n_commDeacv_Mode; /* '<S13>/n_commDeacv' */
|
||||
boolean_T dz_cntTrnsDet_Mode; /* '<S17>/dz_cntTrnsDet' */
|
||||
} DW;
|
||||
|
||||
/* Constant parameters (auto storage) */
|
||||
typedef struct {
|
||||
/* Computed Parameter: r_sin_M1_Table
|
||||
* Referenced by: '<S52>/r_sin_M1'
|
||||
*/
|
||||
int16_T r_sin_M1_Table[181];
|
||||
|
||||
/* Computed Parameter: r_cos_M1_Table
|
||||
* Referenced by: '<S52>/r_cos_M1'
|
||||
*/
|
||||
int16_T r_cos_M1_Table[181];
|
||||
|
||||
/* Computed Parameter: r_sin3PhaA_M1_Table
|
||||
* Referenced by: '<S96>/r_sin3PhaA_M1'
|
||||
*/
|
||||
int16_T r_sin3PhaA_M1_Table[181];
|
||||
|
||||
/* Computed Parameter: r_sin3PhaB_M1_Table
|
||||
* Referenced by: '<S96>/r_sin3PhaB_M1'
|
||||
*/
|
||||
int16_T r_sin3PhaB_M1_Table[181];
|
||||
|
||||
/* Computed Parameter: r_sin3PhaC_M1_Table
|
||||
* Referenced by: '<S96>/r_sin3PhaC_M1'
|
||||
*/
|
||||
int16_T r_sin3PhaC_M1_Table[181];
|
||||
|
||||
/* Computed Parameter: iq_maxSca_M1_Table
|
||||
* Referenced by: '<S80>/iq_maxSca_M1'
|
||||
*/
|
||||
uint16_T iq_maxSca_M1_Table[50];
|
||||
|
||||
/* Computed Parameter: z_commutMap_M1_table
|
||||
* Referenced by: '<S94>/z_commutMap_M1'
|
||||
*/
|
||||
int8_T z_commutMap_M1_table[18];
|
||||
|
||||
/* Computed Parameter: vec_hallToPos_Value
|
||||
* Referenced by: '<S11>/vec_hallToPos'
|
||||
*/
|
||||
int8_T vec_hallToPos_Value[8];
|
||||
} ConstP;
|
||||
|
||||
/* External inputs (root inport signals with auto storage) */
|
||||
typedef struct {
|
||||
boolean_T b_motEna; /* '<Root>/b_motEna' */
|
||||
uint8_T z_ctrlModReq; /* '<Root>/z_ctrlModReq' */
|
||||
int16_T r_inpTgt; /* '<Root>/r_inpTgt' */
|
||||
uint8_T b_hallA; /* '<Root>/b_hallA ' */
|
||||
uint8_T b_hallB; /* '<Root>/b_hallB' */
|
||||
uint8_T b_hallC; /* '<Root>/b_hallC' */
|
||||
int16_T i_phaAB; /* '<Root>/i_phaAB' */
|
||||
int16_T i_phaBC; /* '<Root>/i_phaBC' */
|
||||
int16_T i_DCLink; /* '<Root>/i_DCLink' */
|
||||
int16_T a_mechAngle; /* '<Root>/a_mechAngle' */
|
||||
} ExtU;
|
||||
|
||||
/* External outputs (root outports fed by signals with auto storage) */
|
||||
typedef struct {
|
||||
int16_T DC_phaA; /* '<Root>/DC_phaA' */
|
||||
int16_T DC_phaB; /* '<Root>/DC_phaB' */
|
||||
int16_T DC_phaC; /* '<Root>/DC_phaC' */
|
||||
uint8_T z_errCode; /* '<Root>/z_errCode' */
|
||||
int16_T n_mot; /* '<Root>/n_mot' */
|
||||
int16_T a_elecAngle; /* '<Root>/a_elecAngle' */
|
||||
int16_T iq; /* '<Root>/iq' */
|
||||
int16_T id; /* '<Root>/id' */
|
||||
} ExtY;
|
||||
|
||||
/* Parameters (auto storage) */
|
||||
struct P_ {
|
||||
int32_T dV_openRate; /* Variable: dV_openRate
|
||||
* Referenced by: '<S37>/dV_openRate'
|
||||
*/
|
||||
int16_T dz_cntTrnsDetHi; /* Variable: dz_cntTrnsDetHi
|
||||
* Referenced by: '<S17>/dz_cntTrnsDet'
|
||||
*/
|
||||
int16_T dz_cntTrnsDetLo; /* Variable: dz_cntTrnsDetLo
|
||||
* Referenced by: '<S17>/dz_cntTrnsDet'
|
||||
*/
|
||||
int16_T n_cruiseMotTgt; /* Variable: n_cruiseMotTgt
|
||||
* Referenced by: '<S61>/n_cruiseMotTgt'
|
||||
*/
|
||||
int16_T z_maxCntRst; /* Variable: z_maxCntRst
|
||||
* Referenced by:
|
||||
* '<S13>/Counter'
|
||||
* '<S13>/z_maxCntRst'
|
||||
* '<S13>/z_maxCntRst2'
|
||||
* '<S13>/UnitDelay3'
|
||||
* '<S17>/z_counter'
|
||||
*/
|
||||
uint16_T cf_speedCoef; /* Variable: cf_speedCoef
|
||||
* Referenced by: '<S17>/cf_speedCoef'
|
||||
*/
|
||||
uint16_T t_errDequal; /* Variable: t_errDequal
|
||||
* Referenced by: '<S20>/t_errDequal'
|
||||
*/
|
||||
uint16_T t_errQual; /* Variable: t_errQual
|
||||
* Referenced by: '<S20>/t_errQual'
|
||||
*/
|
||||
int16_T Vd_max; /* Variable: Vd_max
|
||||
* Referenced by:
|
||||
* '<S36>/Vd_max'
|
||||
* '<S80>/Vd_max1'
|
||||
*/
|
||||
int16_T Vq_max_M1[46]; /* Variable: Vq_max_M1
|
||||
* Referenced by: '<S80>/Vq_max_M1'
|
||||
*/
|
||||
int16_T Vq_max_XA[46]; /* Variable: Vq_max_XA
|
||||
* Referenced by: '<S80>/Vq_max_XA'
|
||||
*/
|
||||
int16_T a_phaAdvMax; /* Variable: a_phaAdvMax
|
||||
* Referenced by: '<S42>/a_phaAdvMax'
|
||||
*/
|
||||
int16_T i_max; /* Variable: i_max
|
||||
* Referenced by:
|
||||
* '<S36>/i_max'
|
||||
* '<S80>/i_max'
|
||||
*/
|
||||
int16_T id_fieldWeakMax; /* Variable: id_fieldWeakMax
|
||||
* Referenced by: '<S42>/id_fieldWeakMax'
|
||||
*/
|
||||
int16_T n_commAcvLo; /* Variable: n_commAcvLo
|
||||
* Referenced by: '<S13>/n_commDeacv'
|
||||
*/
|
||||
int16_T n_commDeacvHi; /* Variable: n_commDeacvHi
|
||||
* Referenced by: '<S13>/n_commDeacv'
|
||||
*/
|
||||
int16_T n_fieldWeakAuthHi; /* Variable: n_fieldWeakAuthHi
|
||||
* Referenced by: '<S42>/n_fieldWeakAuthHi'
|
||||
*/
|
||||
int16_T n_fieldWeakAuthLo; /* Variable: n_fieldWeakAuthLo
|
||||
* Referenced by: '<S42>/n_fieldWeakAuthLo'
|
||||
*/
|
||||
int16_T n_max; /* Variable: n_max
|
||||
* Referenced by:
|
||||
* '<S36>/n_max'
|
||||
* '<S80>/n_max1'
|
||||
*/
|
||||
int16_T n_stdStillDet; /* Variable: n_stdStillDet
|
||||
* Referenced by: '<S13>/n_stdStillDet'
|
||||
*/
|
||||
int16_T r_errInpTgtThres; /* Variable: r_errInpTgtThres
|
||||
* Referenced by: '<S20>/r_errInpTgtThres'
|
||||
*/
|
||||
int16_T r_fieldWeakHi; /* Variable: r_fieldWeakHi
|
||||
* Referenced by: '<S42>/r_fieldWeakHi'
|
||||
*/
|
||||
int16_T r_fieldWeakLo; /* Variable: r_fieldWeakLo
|
||||
* Referenced by: '<S42>/r_fieldWeakLo'
|
||||
*/
|
||||
uint16_T cf_KbLimProt; /* Variable: cf_KbLimProt
|
||||
* Referenced by:
|
||||
* '<S82>/cf_KbLimProt'
|
||||
* '<S83>/cf_KbLimProt'
|
||||
*/
|
||||
uint16_T cf_idKp; /* Variable: cf_idKp
|
||||
* Referenced by: '<S63>/cf_idKp1'
|
||||
*/
|
||||
uint16_T cf_iqKp; /* Variable: cf_iqKp
|
||||
* Referenced by: '<S62>/cf_iqKp'
|
||||
*/
|
||||
uint16_T cf_nKp; /* Variable: cf_nKp
|
||||
* Referenced by: '<S61>/cf_nKp'
|
||||
*/
|
||||
uint16_T cf_currFilt; /* Variable: cf_currFilt
|
||||
* Referenced by: '<S50>/cf_currFilt'
|
||||
*/
|
||||
uint16_T cf_idKi; /* Variable: cf_idKi
|
||||
* Referenced by: '<S63>/cf_idKi1'
|
||||
*/
|
||||
uint16_T cf_iqKi; /* Variable: cf_iqKi
|
||||
* Referenced by: '<S62>/cf_iqKi'
|
||||
*/
|
||||
uint16_T cf_iqKiLimProt; /* Variable: cf_iqKiLimProt
|
||||
* Referenced by:
|
||||
* '<S81>/cf_iqKiLimProt'
|
||||
* '<S83>/cf_iqKiLimProt'
|
||||
*/
|
||||
uint16_T cf_nKi; /* Variable: cf_nKi
|
||||
* Referenced by: '<S61>/cf_nKi'
|
||||
*/
|
||||
uint16_T cf_nKiLimProt; /* Variable: cf_nKiLimProt
|
||||
* Referenced by:
|
||||
* '<S82>/cf_nKiLimProt'
|
||||
* '<S83>/cf_nKiLimProt'
|
||||
*/
|
||||
uint8_T n_polePairs; /* Variable: n_polePairs
|
||||
* Referenced by: '<S15>/n_polePairs'
|
||||
*/
|
||||
uint8_T z_ctrlTypSel; /* Variable: z_ctrlTypSel
|
||||
* Referenced by: '<S1>/z_ctrlTypSel'
|
||||
*/
|
||||
uint8_T z_selPhaCurMeasABC; /* Variable: z_selPhaCurMeasABC
|
||||
* Referenced by: '<S49>/z_selPhaCurMeasABC'
|
||||
*/
|
||||
boolean_T b_angleMeasEna; /* Variable: b_angleMeasEna
|
||||
* Referenced by:
|
||||
* '<S3>/b_angleMeasEna'
|
||||
* '<S13>/b_angleMeasEna'
|
||||
*/
|
||||
boolean_T b_cruiseCtrlEna; /* Variable: b_cruiseCtrlEna
|
||||
* Referenced by: '<S1>/b_cruiseCtrlEna'
|
||||
*/
|
||||
boolean_T b_diagEna; /* Variable: b_diagEna
|
||||
* Referenced by: '<S4>/b_diagEna'
|
||||
*/
|
||||
boolean_T b_fieldWeakEna; /* Variable: b_fieldWeakEna
|
||||
* Referenced by:
|
||||
* '<S6>/b_fieldWeakEna'
|
||||
* '<S97>/b_fieldWeakEna'
|
||||
*/
|
||||
};
|
||||
|
||||
/* Parameters (auto storage) */
|
||||
typedef struct P_ P;
|
||||
|
||||
/* Real-time Model Data Structure */
|
||||
struct tag_RTM {
|
||||
P *defaultParam;
|
||||
ExtU *inputs;
|
||||
ExtY *outputs;
|
||||
DW *dwork;
|
||||
};
|
||||
|
||||
/* Constant parameters (auto storage) */
|
||||
extern const ConstP rtConstP;
|
||||
|
||||
/* Model entry point functions */
|
||||
extern void BLDC_controller_initialize(RT_MODEL *const rtM);
|
||||
extern void BLDC_controller_step(RT_MODEL *const rtM);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,386 @@
|
||||
/*
|
||||
* File: BLDC_controller_data.c
|
||||
*
|
||||
* Code generated for Simulink model 'BLDC_controller'.
|
||||
*
|
||||
* Model version : 1.1297
|
||||
* Simulink Coder version : 8.13 (R2017b) 24-Jul-2017
|
||||
* C/C++ source code generated on : Sun Mar 6 11:02:11 2022
|
||||
*
|
||||
* Target selection: ert.tlc
|
||||
* Embedded hardware selection: ARM Compatible->ARM Cortex
|
||||
* Emulation hardware selection:
|
||||
* Differs from embedded hardware (MATLAB Host)
|
||||
* Code generation objectives:
|
||||
* 1. Execution efficiency
|
||||
* 2. RAM efficiency
|
||||
* Validation result: Not run
|
||||
*/
|
||||
|
||||
#include "BLDC_controller.h"
|
||||
|
||||
/* Constant parameters (auto storage) */
|
||||
const ConstP rtConstP = {
|
||||
/* Computed Parameter: r_sin_M1_Table
|
||||
* Referenced by: '<S52>/r_sin_M1'
|
||||
*/
|
||||
{ 8192, 8682, 9162, 9630, 10087, 10531, 10963, 11381, 11786, 12176, 12551,
|
||||
12911, 13255, 13583, 13894, 14189, 14466, 14726, 14968, 15191, 15396, 15582,
|
||||
15749, 15897, 16026, 16135, 16225, 16294, 16344, 16374, 16384, 16374, 16344,
|
||||
16294, 16225, 16135, 16026, 15897, 15749, 15582, 15396, 15191, 14968, 14726,
|
||||
14466, 14189, 13894, 13583, 13255, 12911, 12551, 12176, 11786, 11381, 10963,
|
||||
10531, 10087, 9630, 9162, 8682, 8192, 7692, 7182, 6664, 6138, 5604, 5063,
|
||||
4516, 3964, 3406, 2845, 2280, 1713, 1143, 572, 0, -572, -1143, -1713, -2280,
|
||||
-2845, -3406, -3964, -4516, -5063, -5604, -6138, -6664, -7182, -7692, -8192,
|
||||
-8682, -9162, -9630, -10087, -10531, -10963, -11381, -11786, -12176, -12551,
|
||||
-12911, -13255, -13583, -13894, -14189, -14466, -14726, -14968, -15191,
|
||||
-15396, -15582, -15749, -15897, -16026, -16135, -16225, -16294, -16344,
|
||||
-16374, -16384, -16374, -16344, -16294, -16225, -16135, -16026, -15897,
|
||||
-15749, -15582, -15396, -15191, -14968, -14726, -14466, -14189, -13894,
|
||||
-13583, -13255, -12911, -12551, -12176, -11786, -11381, -10963, -10531,
|
||||
-10087, -9630, -9162, -8682, -8192, -7692, -7182, -6664, -6138, -5604, -5063,
|
||||
-4516, -3964, -3406, -2845, -2280, -1713, -1143, -572, 0, 572, 1143, 1713,
|
||||
2280, 2845, 3406, 3964, 4516, 5063, 5604, 6138, 6664, 7182, 7692, 8192 },
|
||||
|
||||
/* Computed Parameter: r_cos_M1_Table
|
||||
* Referenced by: '<S52>/r_cos_M1'
|
||||
*/
|
||||
{ 14189, 13894, 13583, 13255, 12911, 12551, 12176, 11786, 11381, 10963, 10531,
|
||||
10087, 9630, 9162, 8682, 8192, 7692, 7182, 6664, 6138, 5604, 5063, 4516,
|
||||
3964, 3406, 2845, 2280, 1713, 1143, 572, 0, -572, -1143, -1713, -2280, -2845,
|
||||
-3406, -3964, -4516, -5063, -5604, -6138, -6664, -7182, -7692, -8192, -8682,
|
||||
-9162, -9630, -10087, -10531, -10963, -11381, -11786, -12176, -12551, -12911,
|
||||
-13255, -13583, -13894, -14189, -14466, -14726, -14968, -15191, -15396,
|
||||
-15582, -15749, -15897, -16026, -16135, -16225, -16294, -16344, -16374,
|
||||
-16384, -16374, -16344, -16294, -16225, -16135, -16026, -15897, -15749,
|
||||
-15582, -15396, -15191, -14968, -14726, -14466, -14189, -13894, -13583,
|
||||
-13255, -12911, -12551, -12176, -11786, -11381, -10963, -10531, -10087,
|
||||
-9630, -9162, -8682, -8192, -7692, -7182, -6664, -6138, -5604, -5063, -4516,
|
||||
-3964, -3406, -2845, -2280, -1713, -1143, -572, 0, 572, 1143, 1713, 2280,
|
||||
2845, 3406, 3964, 4516, 5063, 5604, 6138, 6664, 7182, 7692, 8192, 8682, 9162,
|
||||
9630, 10087, 10531, 10963, 11381, 11786, 12176, 12551, 12911, 13255, 13583,
|
||||
13894, 14189, 14466, 14726, 14968, 15191, 15396, 15582, 15749, 15897, 16026,
|
||||
16135, 16225, 16294, 16344, 16374, 16384, 16374, 16344, 16294, 16225, 16135,
|
||||
16026, 15897, 15749, 15582, 15396, 15191, 14968, 14726, 14466, 14189 },
|
||||
|
||||
/* Computed Parameter: r_sin3PhaA_M1_Table
|
||||
* Referenced by: '<S96>/r_sin3PhaA_M1'
|
||||
*/
|
||||
{ -13091, -13634, -14126, -14565, -14953, -15289, -15577, -15816, -16009,
|
||||
-16159, -16269, -16340, -16377, -16383, -16362, -16317, -16253, -16172,
|
||||
-16079, -15977, -15870, -15762, -15656, -15555, -15461, -15377, -15306,
|
||||
-15248, -15206, -15180, -15172, -15180, -15206, -15248, -15306, -15377,
|
||||
-15461, -15555, -15656, -15762, -15870, -15977, -16079, -16172, -16253,
|
||||
-16317, -16362, -16383, -16377, -16340, -16269, -16159, -16009, -15816,
|
||||
-15577, -15289, -14953, -14565, -14126, -13634, -13091, -12496, -11849,
|
||||
-11154, -10411, -9623, -8791, -7921, -7014, -6075, -5107, -4115, -3104,
|
||||
-2077, -1041, 0, 1041, 2077, 3104, 4115, 5107, 6075, 7014, 7921, 8791, 9623,
|
||||
10411, 11154, 11849, 12496, 13091, 13634, 14126, 14565, 14953, 15289, 15577,
|
||||
15816, 16009, 16159, 16269, 16340, 16377, 16383, 16362, 16317, 16253, 16172,
|
||||
16079, 15977, 15870, 15762, 15656, 15555, 15461, 15377, 15306, 15248, 15206,
|
||||
15180, 15172, 15180, 15206, 15248, 15306, 15377, 15461, 15555, 15656, 15762,
|
||||
15870, 15977, 16079, 16172, 16253, 16317, 16362, 16383, 16377, 16340, 16269,
|
||||
16159, 16009, 15816, 15577, 15289, 14953, 14565, 14126, 13634, 13091, 12496,
|
||||
11849, 11154, 10411, 9623, 8791, 7921, 7014, 6075, 5107, 4115, 3104, 2077,
|
||||
1041, 0, -1041, -2077, -3104, -4115, -5107, -6075, -7014, -7921, -8791,
|
||||
-9623, -10411, -11154, -11849, -12496, -13091 },
|
||||
|
||||
/* Computed Parameter: r_sin3PhaB_M1_Table
|
||||
* Referenced by: '<S96>/r_sin3PhaB_M1'
|
||||
*/
|
||||
{ 15172, 15180, 15206, 15248, 15306, 15377, 15461, 15555, 15656, 15762, 15870,
|
||||
15977, 16079, 16172, 16253, 16317, 16362, 16383, 16377, 16340, 16269, 16159,
|
||||
16009, 15816, 15577, 15289, 14953, 14565, 14126, 13634, 13091, 12496, 11849,
|
||||
11154, 10411, 9623, 8791, 7921, 7014, 6075, 5107, 4115, 3104, 2077, 1041, 0,
|
||||
-1041, -2077, -3104, -4115, -5107, -6075, -7014, -7921, -8791, -9623, -10411,
|
||||
-11154, -11849, -12496, -13091, -13634, -14126, -14565, -14953, -15289,
|
||||
-15577, -15816, -16009, -16159, -16269, -16340, -16377, -16383, -16362,
|
||||
-16317, -16253, -16172, -16079, -15977, -15870, -15762, -15656, -15555,
|
||||
-15461, -15377, -15306, -15248, -15206, -15180, -15172, -15180, -15206,
|
||||
-15248, -15306, -15377, -15461, -15555, -15656, -15762, -15870, -15977,
|
||||
-16079, -16172, -16253, -16317, -16362, -16383, -16377, -16340, -16269,
|
||||
-16159, -16009, -15816, -15577, -15289, -14953, -14565, -14126, -13634,
|
||||
-13091, -12496, -11849, -11154, -10411, -9623, -8791, -7921, -7014, -6075,
|
||||
-5107, -4115, -3104, -2077, -1041, 0, 1041, 2077, 3104, 4115, 5107, 6075,
|
||||
7014, 7921, 8791, 9623, 10411, 11154, 11849, 12496, 13091, 13634, 14126,
|
||||
14565, 14953, 15289, 15577, 15816, 16009, 16159, 16269, 16340, 16377, 16383,
|
||||
16362, 16317, 16253, 16172, 16079, 15977, 15870, 15762, 15656, 15555, 15461,
|
||||
15377, 15306, 15248, 15206, 15180, 15172 },
|
||||
|
||||
/* Computed Parameter: r_sin3PhaC_M1_Table
|
||||
* Referenced by: '<S96>/r_sin3PhaC_M1'
|
||||
*/
|
||||
{ -13091, -12496, -11849, -11154, -10411, -9623, -8791, -7921, -7014, -6075,
|
||||
-5107, -4115, -3104, -2077, -1041, 0, 1041, 2077, 3104, 4115, 5107, 6075,
|
||||
7014, 7921, 8791, 9623, 10411, 11154, 11849, 12496, 13091, 13634, 14126,
|
||||
14565, 14953, 15289, 15577, 15816, 16009, 16159, 16269, 16340, 16377, 16383,
|
||||
16362, 16317, 16253, 16172, 16079, 15977, 15870, 15762, 15656, 15555, 15461,
|
||||
15377, 15306, 15248, 15206, 15180, 15172, 15180, 15206, 15248, 15306, 15377,
|
||||
15461, 15555, 15656, 15762, 15870, 15977, 16079, 16172, 16253, 16317, 16362,
|
||||
16383, 16377, 16340, 16269, 16159, 16009, 15816, 15577, 15289, 14953, 14565,
|
||||
14126, 13634, 13091, 12496, 11849, 11154, 10411, 9623, 8791, 7921, 7014,
|
||||
6075, 5107, 4115, 3104, 2077, 1041, 0, -1041, -2077, -3104, -4115, -5107,
|
||||
-6075, -7014, -7921, -8791, -9623, -10411, -11154, -11849, -12496, -13091,
|
||||
-13634, -14126, -14565, -14953, -15289, -15577, -15816, -16009, -16159,
|
||||
-16269, -16340, -16377, -16383, -16362, -16317, -16253, -16172, -16079,
|
||||
-15977, -15870, -15762, -15656, -15555, -15461, -15377, -15306, -15248,
|
||||
-15206, -15180, -15172, -15180, -15206, -15248, -15306, -15377, -15461,
|
||||
-15555, -15656, -15762, -15870, -15977, -16079, -16172, -16253, -16317,
|
||||
-16362, -16383, -16377, -16340, -16269, -16159, -16009, -15816, -15577,
|
||||
-15289, -14953, -14565, -14126, -13634, -13091 },
|
||||
|
||||
/* Computed Parameter: iq_maxSca_M1_Table
|
||||
* Referenced by: '<S80>/iq_maxSca_M1'
|
||||
*/
|
||||
{ 65535U, 65523U, 65484U, 65418U, 65326U, 65207U, 65062U, 64890U, 64691U,
|
||||
64465U, 64211U, 63930U, 63620U, 63281U, 62913U, 62516U, 62088U, 61630U,
|
||||
61140U, 60618U, 60062U, 59473U, 58848U, 58187U, 57489U, 56752U, 55974U,
|
||||
55155U, 54291U, 53381U, 52422U, 51413U, 50349U, 49227U, 48043U, 46792U,
|
||||
45470U, 44069U, 42581U, 40997U, 39307U, 37494U, 35541U, 33422U, 31105U,
|
||||
28540U, 25655U, 22323U, 18304U, 12974U },
|
||||
|
||||
/* Computed Parameter: z_commutMap_M1_table
|
||||
* Referenced by: '<S94>/z_commutMap_M1'
|
||||
*/
|
||||
{ -1, 1, 0, -1, 0, 1, 0, -1, 1, 1, -1, 0, 1, 0, -1, 0, 1, -1 },
|
||||
|
||||
/* Computed Parameter: vec_hallToPos_Value
|
||||
* Referenced by: '<S11>/vec_hallToPos'
|
||||
*/
|
||||
{ 0, 2, 0, 1, 4, 3, 5, 0 }
|
||||
};
|
||||
|
||||
P rtP_Left = {
|
||||
/* Variable: dV_openRate
|
||||
* Referenced by: '<S37>/dV_openRate'
|
||||
*/
|
||||
12288,
|
||||
|
||||
/* Variable: dz_cntTrnsDetHi
|
||||
* Referenced by: '<S17>/dz_cntTrnsDet'
|
||||
*/
|
||||
40,
|
||||
|
||||
/* Variable: dz_cntTrnsDetLo
|
||||
* Referenced by: '<S17>/dz_cntTrnsDet'
|
||||
*/
|
||||
20,
|
||||
|
||||
/* Variable: n_cruiseMotTgt
|
||||
* Referenced by: '<S61>/n_cruiseMotTgt'
|
||||
*/
|
||||
0,
|
||||
|
||||
/* Variable: z_maxCntRst
|
||||
* Referenced by:
|
||||
* '<S13>/Counter'
|
||||
* '<S13>/z_maxCntRst'
|
||||
* '<S13>/z_maxCntRst2'
|
||||
* '<S13>/UnitDelay3'
|
||||
* '<S17>/z_counter'
|
||||
*/
|
||||
2000,
|
||||
|
||||
/* Variable: cf_speedCoef
|
||||
* Referenced by: '<S17>/cf_speedCoef'
|
||||
*/
|
||||
5334U,
|
||||
|
||||
/* Variable: t_errDequal
|
||||
* Referenced by: '<S20>/t_errDequal'
|
||||
*/
|
||||
9600U,
|
||||
|
||||
/* Variable: t_errQual
|
||||
* Referenced by: '<S20>/t_errQual'
|
||||
*/
|
||||
1280U,
|
||||
|
||||
/* Variable: Vd_max
|
||||
* Referenced by:
|
||||
* '<S36>/Vd_max'
|
||||
* '<S80>/Vd_max1'
|
||||
*/
|
||||
14400,
|
||||
|
||||
/* Variable: Vq_max_M1
|
||||
* Referenced by: '<S80>/Vq_max_M1'
|
||||
*/
|
||||
{ 14400, 14396, 14386, 14368, 14343, 14311, 14271, 14225, 14171, 14109, 14040,
|
||||
13963, 13879, 13786, 13685, 13576, 13459, 13333, 13198, 13053, 12900, 12736,
|
||||
12562, 12377, 12181, 11973, 11753, 11520, 11273, 11011, 10733, 10438, 10124,
|
||||
9790, 9433, 9051, 8640, 8196, 7713, 7184, 6597, 5935, 5170, 4245, 3019, 0 },
|
||||
|
||||
/* Variable: Vq_max_XA
|
||||
* Referenced by: '<S80>/Vq_max_XA'
|
||||
*/
|
||||
{ 0, 320, 640, 960, 1280, 1600, 1920, 2240, 2560, 2880, 3200, 3520, 3840, 4160,
|
||||
4480, 4800, 5120, 5440, 5760, 6080, 6400, 6720, 7040, 7360, 7680, 8000, 8320,
|
||||
8640, 8960, 9280, 9600, 9920, 10240, 10560, 10880, 11200, 11520, 11840,
|
||||
12160, 12480, 12800, 13120, 13440, 13760, 14080, 14400 },
|
||||
|
||||
/* Variable: a_phaAdvMax
|
||||
* Referenced by: '<S42>/a_phaAdvMax'
|
||||
*/
|
||||
400,
|
||||
|
||||
/* Variable: i_max
|
||||
* Referenced by:
|
||||
* '<S36>/i_max'
|
||||
* '<S80>/i_max'
|
||||
*/
|
||||
30000,
|
||||
|
||||
/* Variable: id_fieldWeakMax
|
||||
* Referenced by: '<S42>/id_fieldWeakMax'
|
||||
*/
|
||||
4000,
|
||||
|
||||
/* Variable: n_commAcvLo
|
||||
* Referenced by: '<S13>/n_commDeacv'
|
||||
*/
|
||||
240,
|
||||
|
||||
/* Variable: n_commDeacvHi
|
||||
* Referenced by: '<S13>/n_commDeacv'
|
||||
*/
|
||||
480,
|
||||
|
||||
/* Variable: n_fieldWeakAuthHi
|
||||
* Referenced by: '<S42>/n_fieldWeakAuthHi'
|
||||
*/
|
||||
6400,
|
||||
|
||||
/* Variable: n_fieldWeakAuthLo
|
||||
* Referenced by: '<S42>/n_fieldWeakAuthLo'
|
||||
*/
|
||||
4800,
|
||||
|
||||
/* Variable: n_max
|
||||
* Referenced by:
|
||||
* '<S36>/n_max'
|
||||
* '<S80>/n_max1'
|
||||
*/
|
||||
32000,
|
||||
|
||||
/* Variable: n_stdStillDet
|
||||
* Referenced by: '<S13>/n_stdStillDet'
|
||||
*/
|
||||
48,
|
||||
|
||||
/* Variable: r_errInpTgtThres
|
||||
* Referenced by: '<S20>/r_errInpTgtThres'
|
||||
*/
|
||||
9600,
|
||||
|
||||
/* Variable: r_fieldWeakHi
|
||||
* Referenced by: '<S42>/r_fieldWeakHi'
|
||||
*/
|
||||
16000,
|
||||
|
||||
/* Variable: r_fieldWeakLo
|
||||
* Referenced by: '<S42>/r_fieldWeakLo'
|
||||
*/
|
||||
12000,
|
||||
|
||||
/* Variable: cf_KbLimProt
|
||||
* Referenced by:
|
||||
* '<S82>/cf_KbLimProt'
|
||||
* '<S83>/cf_KbLimProt'
|
||||
*/
|
||||
768U,
|
||||
|
||||
/* Variable: cf_idKp
|
||||
* Referenced by: '<S63>/cf_idKp1'
|
||||
*/
|
||||
1638U,
|
||||
|
||||
/* Variable: cf_iqKp
|
||||
* Referenced by: '<S62>/cf_iqKp'
|
||||
*/
|
||||
2458U,
|
||||
|
||||
/* Variable: cf_nKp
|
||||
* Referenced by: '<S61>/cf_nKp'
|
||||
*/
|
||||
9666U,
|
||||
|
||||
/* Variable: cf_currFilt
|
||||
* Referenced by: '<S50>/cf_currFilt'
|
||||
*/
|
||||
7864U,
|
||||
|
||||
/* Variable: cf_idKi
|
||||
* Referenced by: '<S63>/cf_idKi1'
|
||||
*/
|
||||
1474U,
|
||||
|
||||
/* Variable: cf_iqKi
|
||||
* Referenced by: '<S62>/cf_iqKi'
|
||||
*/
|
||||
2458U,
|
||||
|
||||
/* Variable: cf_iqKiLimProt
|
||||
* Referenced by:
|
||||
* '<S81>/cf_iqKiLimProt'
|
||||
* '<S83>/cf_iqKiLimProt'
|
||||
*/
|
||||
1474U,
|
||||
|
||||
/* Variable: cf_nKi
|
||||
* Referenced by: '<S61>/cf_nKi'
|
||||
*/
|
||||
502U,
|
||||
|
||||
/* Variable: cf_nKiLimProt
|
||||
* Referenced by:
|
||||
* '<S82>/cf_nKiLimProt'
|
||||
* '<S83>/cf_nKiLimProt'
|
||||
*/
|
||||
492U,
|
||||
|
||||
/* Variable: n_polePairs
|
||||
* Referenced by: '<S15>/n_polePairs'
|
||||
*/
|
||||
15U,
|
||||
|
||||
/* Variable: z_ctrlTypSel
|
||||
* Referenced by: '<S1>/z_ctrlTypSel'
|
||||
*/
|
||||
2U,
|
||||
|
||||
/* Variable: z_selPhaCurMeasABC
|
||||
* Referenced by: '<S49>/z_selPhaCurMeasABC'
|
||||
*/
|
||||
0U,
|
||||
|
||||
/* Variable: b_angleMeasEna
|
||||
* Referenced by:
|
||||
* '<S3>/b_angleMeasEna'
|
||||
* '<S13>/b_angleMeasEna'
|
||||
*/
|
||||
0,
|
||||
|
||||
/* Variable: b_cruiseCtrlEna
|
||||
* Referenced by: '<S1>/b_cruiseCtrlEna'
|
||||
*/
|
||||
0,
|
||||
|
||||
/* Variable: b_diagEna
|
||||
* Referenced by: '<S4>/b_diagEna'
|
||||
*/
|
||||
1,
|
||||
|
||||
/* Variable: b_fieldWeakEna
|
||||
* Referenced by:
|
||||
* '<S6>/b_fieldWeakEna'
|
||||
* '<S97>/b_fieldWeakEna'
|
||||
*/
|
||||
0
|
||||
}; /* Modifiable parameters */
|
||||
|
||||
/*
|
||||
* File trailer for generated code.
|
||||
*
|
||||
* [EOF]
|
||||
*/
|
||||
@@ -0,0 +1,311 @@
|
||||
#ifndef BLDC_H
|
||||
#define BLDC_H
|
||||
|
||||
#include "board/body/bldc/bldc_defs.h"
|
||||
#include "board/body/boards/board_declarations.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "board/stm32h7/lladc.h"
|
||||
|
||||
// Matlab includes and defines - from auto-code generation
|
||||
#include "BLDC_controller.h" /* Model's header file */
|
||||
#include "BLDC_controller.c"
|
||||
#include "BLDC_controller_data.c"
|
||||
#include "rtwtypes.h"
|
||||
|
||||
static RT_MODEL rtM_Left_Obj;
|
||||
static RT_MODEL rtM_Right_Obj;
|
||||
|
||||
RT_MODEL *const rtM_Left = &rtM_Left_Obj;
|
||||
RT_MODEL *const rtM_Right = &rtM_Right_Obj;
|
||||
|
||||
static DW rtDW_Left; /* Observable states */
|
||||
static ExtU rtU_Left; /* External inputs */
|
||||
static ExtY rtY_Left; /* External outputs */
|
||||
extern P rtP_Left; // This is defined in BLDC_controller_data.c
|
||||
|
||||
static DW rtDW_Right; /* Observable states */
|
||||
static ExtU rtU_Right; /* External inputs */
|
||||
static ExtY rtY_Right; /* External outputs */
|
||||
static P rtP_Right; /* Parameters */
|
||||
|
||||
//static int16_t curDC_max = (I_DC_MAX * A2BIT_CONV);
|
||||
static int16_t curL_phaA = 0, curL_phaC = 0, curL_DC = 0;
|
||||
static int16_t curR_phaA = 0, curR_phaC = 0, curR_DC = 0;
|
||||
|
||||
volatile uint16_t batt_voltage_raw = 0;
|
||||
volatile uint16_t batt_percentage = 0;
|
||||
|
||||
volatile int rpm_left = 0;
|
||||
volatile int rpm_right = 0;
|
||||
|
||||
volatile bool enable_motors = 0; // initially motors are disabled for safety
|
||||
static bool enableFin = 0;
|
||||
|
||||
static const uint16_t pwm_res = ( (uint32_t)CORE_FREQ * 1000000U / 2U ) / PWM_FREQ;
|
||||
|
||||
static uint16_t offsetcount = 0;
|
||||
static uint32_t offsetrlA = 0;
|
||||
static uint32_t offsetrlC = 0;
|
||||
static uint32_t offsetrrA = 0;
|
||||
static uint32_t offsetrrC = 0;
|
||||
static uint32_t offsetdcl = 0;
|
||||
static uint32_t offsetdcr = 0;
|
||||
|
||||
#define ADC_CHANNEL_BLDC(a, c) {.adc = (a), .channel = (c), .sample_time = SAMPLETIME_16_CYCLES, .oversampling = OVERSAMPLING_4}
|
||||
|
||||
const adc_signal_t adc_curL_phaA = ADC_CHANNEL_BLDC(ADC2, 10);
|
||||
const adc_signal_t adc_curL_phaC = ADC_CHANNEL_BLDC(ADC2, 11);
|
||||
const adc_signal_t adc_curL_DC = ADC_CHANNEL_BLDC(ADC2, 18);
|
||||
const adc_signal_t adc_curR_phaA = ADC_CHANNEL_BLDC(ADC1, 7);
|
||||
const adc_signal_t adc_curR_phaC = ADC_CHANNEL_BLDC(ADC1, 15);
|
||||
const adc_signal_t adc_curR_DC = ADC_CHANNEL_BLDC(ADC1, 5);
|
||||
const adc_signal_t adc_batVoltage = ADC_CHANNEL_BLDC(ADC1, 4);
|
||||
|
||||
void motor_set_enable(bool enable) {
|
||||
enable_motors = enable;
|
||||
}
|
||||
|
||||
float motor_encoder_get_speed_rpm(uint8_t motor) {
|
||||
float speed_rpm = 0.0f;
|
||||
if (motor == BODY_MOTOR_LEFT) {
|
||||
speed_rpm = (float)rtY_Left.n_mot;
|
||||
} else if (motor == BODY_MOTOR_RIGHT) {
|
||||
speed_rpm = (float)rtY_Right.n_mot;
|
||||
}
|
||||
|
||||
if (ABS(speed_rpm) < RPM_DEADBAND) {
|
||||
speed_rpm = 0.0f;
|
||||
}
|
||||
|
||||
return speed_rpm;
|
||||
}
|
||||
|
||||
void bldc_init(void) {
|
||||
adc_init(ADC1);
|
||||
adc_init(ADC2);
|
||||
|
||||
// Initialize Hall Sensors for Left Motor (PB6, PB7, PB8)
|
||||
set_gpio_mode(GPIOB, 6, MODE_INPUT); set_gpio_pullup(GPIOB, 6, PULL_UP);
|
||||
set_gpio_mode(GPIOB, 7, MODE_INPUT); set_gpio_pullup(GPIOB, 7, PULL_UP);
|
||||
set_gpio_mode(GPIOB, 8, MODE_INPUT); set_gpio_pullup(GPIOB, 8, PULL_UP);
|
||||
|
||||
// Initialize Hall Sensors for Right Motor (PA0, PA1, PA2)
|
||||
set_gpio_mode(GPIOA, 0, MODE_INPUT); set_gpio_pullup(GPIOA, 0, PULL_UP);
|
||||
set_gpio_mode(GPIOA, 1, MODE_INPUT); set_gpio_pullup(GPIOA, 1, PULL_UP);
|
||||
set_gpio_mode(GPIOA, 2, MODE_INPUT); set_gpio_pullup(GPIOA, 2, PULL_UP);
|
||||
|
||||
// Setup the model pointers for Left motor
|
||||
rtM_Left->defaultParam = &rtP_Left;
|
||||
rtM_Left->inputs = &rtU_Left;
|
||||
rtM_Left->outputs = &rtY_Left;
|
||||
rtM_Left->dwork = &rtDW_Left;
|
||||
BLDC_controller_initialize(rtM_Left);
|
||||
|
||||
/* Set BLDC controller parameters */
|
||||
rtP_Left.b_angleMeasEna = 0; // Motor angle input: 0 = estimated angle, 1 = measured angle
|
||||
rtP_Left.z_selPhaCurMeasABC = 2; // Left motor measured current phases {Green, Blue} = {iA, iB}
|
||||
rtP_Left.z_ctrlTypSel = CTRL_TYP_SEL;
|
||||
rtP_Left.b_diagEna = DIAG_ENA;
|
||||
rtP_Left.i_max = (int16_t)(I_MOT_MAX * A2BIT_CONV) << 4;
|
||||
rtP_Left.n_max = N_MOT_MAX << 4;
|
||||
rtP_Left.b_fieldWeakEna = FIELD_WEAK_ENA;
|
||||
rtP_Left.id_fieldWeakMax = (int16_t)(FIELD_WEAK_MAX * A2BIT_CONV) << 4;
|
||||
rtP_Left.a_phaAdvMax = PHASE_ADV_MAX << 4;
|
||||
rtP_Left.r_fieldWeakHi = FIELD_WEAK_HI << 4;
|
||||
rtP_Left.r_fieldWeakLo = FIELD_WEAK_LO << 4;
|
||||
rtP_Left.z_maxCntRst = 4000;
|
||||
rtP_Left.cf_speedCoef = CF_SPEED_COEF;
|
||||
rtP_Left.t_errQual = 1280U; // 80ms at 16kHz loop rate
|
||||
rtP_Left.t_errDequal = T_ERR_DEQUAL_CYCLES;
|
||||
|
||||
// Setup the model pointers for Right motor
|
||||
rtP_Right = rtP_Left; // copy parameters
|
||||
// Right motor measured current phases A & C (based on adc_curR_phaA/C definitions)
|
||||
rtP_Right.z_selPhaCurMeasABC = 2;
|
||||
rtM_Right->defaultParam = &rtP_Right;
|
||||
rtM_Right->inputs = &rtU_Right;
|
||||
rtM_Right->outputs = &rtY_Right;
|
||||
rtM_Right->dwork = &rtDW_Right;
|
||||
BLDC_controller_initialize(rtM_Right);
|
||||
|
||||
// Initialize GPIOs for Motor Control
|
||||
// Left Motor (TIM1): PE8(CH1N), PE9(CH1), PE10(CH2N), PE11(CH2), PE12(CH3N), PE13(CH3)
|
||||
set_gpio_alternate(GPIOE, 8, GPIO_AF1_TIM1);
|
||||
set_gpio_alternate(GPIOE, 9, GPIO_AF1_TIM1);
|
||||
set_gpio_alternate(GPIOE, 10, GPIO_AF1_TIM1);
|
||||
set_gpio_alternate(GPIOE, 11, GPIO_AF1_TIM1);
|
||||
set_gpio_alternate(GPIOE, 12, GPIO_AF1_TIM1);
|
||||
set_gpio_alternate(GPIOE, 13, GPIO_AF1_TIM1);
|
||||
|
||||
// Right Motor (TIM8): PC6(CH1), PC7(CH2), PC8(CH3), PA5(CH1N), PB14(CH2N), PB15(CH3N)
|
||||
set_gpio_alternate(GPIOC, 6, GPIO_AF3_TIM8);
|
||||
set_gpio_alternate(GPIOC, 7, GPIO_AF3_TIM8);
|
||||
set_gpio_alternate(GPIOC, 8, GPIO_AF3_TIM8);
|
||||
set_gpio_alternate(GPIOA, 5, GPIO_AF3_TIM8);
|
||||
set_gpio_alternate(GPIOB, 14, GPIO_AF3_TIM8);
|
||||
set_gpio_alternate(GPIOB, 15, GPIO_AF3_TIM8);
|
||||
|
||||
// --- LEFT MOTOR (TIM1) ---
|
||||
// TIM8 is an advanced control timer. We configure it for 3-phase center-aligned PWM.
|
||||
LEFT_TIM->PSC = 0;
|
||||
LEFT_TIM->ARR = pwm_res; // Set auto-reload register for PWM_FREQ
|
||||
LEFT_TIM->CR1 = TIM_CR1_CMS_0; // Center-aligned mode 1
|
||||
LEFT_TIM->RCR = 1; // Update event once per 2 PWM periods (16kHz)
|
||||
|
||||
// Configure channel 1, 2, 3 as PWM mode 1, with preload enabled
|
||||
LEFT_TIM->CCMR1 = TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1PE | \
|
||||
TIM_CCMR1_OC2M_2 | TIM_CCMR1_OC2M_1 | TIM_CCMR1_OC2PE;
|
||||
LEFT_TIM->CCMR2 = TIM_CCMR2_OC3M_2 | TIM_CCMR2_OC3M_1 | TIM_CCMR2_OC3PE;
|
||||
|
||||
// Enable complementary outputs for channels 1, 2, 3
|
||||
LEFT_TIM->CCER = TIM_CCER_CC1E | TIM_CCER_CC1NE | \
|
||||
TIM_CCER_CC2E | TIM_CCER_CC2NE | \
|
||||
TIM_CCER_CC3E | TIM_CCER_CC3NE;
|
||||
|
||||
// --- RIGHT MOTOR (TIM8) ---
|
||||
RIGHT_TIM->PSC = 0;
|
||||
RIGHT_TIM->ARR = pwm_res;
|
||||
RIGHT_TIM->CR1 = TIM_CR1_CMS_0;
|
||||
RIGHT_TIM->RCR = 1;
|
||||
|
||||
RIGHT_TIM->CCMR1 = TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1PE | \
|
||||
TIM_CCMR1_OC2M_2 | TIM_CCMR1_OC2M_1 | TIM_CCMR1_OC2PE;
|
||||
RIGHT_TIM->CCMR2 = TIM_CCMR2_OC3M_2 | TIM_CCMR2_OC3M_1 | TIM_CCMR2_OC3PE;
|
||||
|
||||
RIGHT_TIM->CCER = TIM_CCER_CC1E | TIM_CCER_CC1NE | \
|
||||
TIM_CCER_CC2E | TIM_CCER_CC2NE | \
|
||||
TIM_CCER_CC3E | TIM_CCER_CC3NE;
|
||||
|
||||
// Set dead time (20 cycles -> ~166ns with 120MHz clock) and enable motor outputs
|
||||
LEFT_TIM->BDTR = 20U | TIM_BDTR_MOE;
|
||||
RIGHT_TIM->BDTR = 20U | TIM_BDTR_MOE;
|
||||
|
||||
// Generate an update event to load the registers
|
||||
LEFT_TIM->EGR = TIM_EGR_UG;
|
||||
RIGHT_TIM->EGR = TIM_EGR_UG;
|
||||
|
||||
// Enable TIM8 update interrupt for bldc_step
|
||||
LEFT_TIM->DIER |= TIM_DIER_UIE;
|
||||
|
||||
// Start the timers
|
||||
LEFT_TIM->CR1 |= TIM_CR1_CEN;
|
||||
RIGHT_TIM->CR1 |= TIM_CR1_CEN;
|
||||
}
|
||||
|
||||
void bldc_step(void) {
|
||||
// Calibrate ADC offsets for the first few cycles
|
||||
if(offsetcount < 2000) { // calibrate ADC offsets
|
||||
offsetcount++;
|
||||
uint32_t rawL_A = adc_get_raw(&adc_curL_phaA);
|
||||
uint32_t rawL_C = adc_get_raw(&adc_curL_phaC);
|
||||
uint32_t rawR_A = adc_get_raw(&adc_curR_phaA);
|
||||
uint32_t rawR_C = adc_get_raw(&adc_curR_phaC);
|
||||
uint32_t rawL_DC = adc_get_raw(&adc_curL_DC);
|
||||
uint32_t rawR_DC = adc_get_raw(&adc_curR_DC);
|
||||
|
||||
if (offsetcount == 1) {
|
||||
offsetrlA = rawL_A; offsetrlC = rawL_C;
|
||||
offsetrrA = rawR_A; offsetrrC = rawR_C;
|
||||
offsetdcl = rawL_DC; offsetdcr = rawR_DC;
|
||||
} else {
|
||||
offsetrlA = (rawL_A + offsetrlA) / 2;
|
||||
offsetrlC = (rawL_C + offsetrlC) / 2;
|
||||
offsetrrA = (rawR_A + offsetrrA) / 2;
|
||||
offsetrrC = (rawR_C + offsetrrC) / 2;
|
||||
offsetdcl = (rawL_DC + offsetdcl) / 2;
|
||||
offsetdcr = (rawR_DC + offsetdcr) / 2;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Get Left motor currents
|
||||
curL_phaA = (int16_t)(((int32_t)offsetrlA - (int32_t)adc_get_raw(&adc_curL_phaA)) >> 5);
|
||||
curL_phaC = (int16_t)(((int32_t)offsetrlC - (int32_t)adc_get_raw(&adc_curL_phaC)) >> 5);
|
||||
curL_DC = (int16_t)(((int32_t)offsetdcl - (int32_t)adc_get_raw(&adc_curL_DC)) >> 4);
|
||||
|
||||
// Get Right motor currents
|
||||
curR_phaA = (int16_t)(((int32_t)offsetrrA - (int32_t)adc_get_raw(&adc_curR_phaA)) >> 5);
|
||||
curR_phaC = (int16_t)(((int32_t)offsetrrC - (int32_t)adc_get_raw(&adc_curR_phaC)) >> 5);
|
||||
curR_DC = (int16_t)(((int32_t)offsetdcr - (int32_t)adc_get_raw(&adc_curR_DC)) >> 4);
|
||||
|
||||
// Safety: Don't enable if offsets are bogus (e.g. ADC failed)
|
||||
if (offsetrrA == 0 || offsetrrC == 0 || !enable_motors) {
|
||||
enableFin = 0;
|
||||
} else {
|
||||
enableFin = 1;
|
||||
}
|
||||
|
||||
// Read Hall Sensors
|
||||
rtU_Left.b_hallA = !((GPIOB->IDR >> 6) & 1);
|
||||
rtU_Left.b_hallB = !((GPIOB->IDR >> 7) & 1);
|
||||
rtU_Left.b_hallC = !((GPIOB->IDR >> 8) & 1);
|
||||
|
||||
rtU_Right.b_hallA = !((GPIOA->IDR >> 0) & 1);
|
||||
rtU_Right.b_hallB = !((GPIOA->IDR >> 1) & 1);
|
||||
rtU_Right.b_hallC = !((GPIOA->IDR >> 2) & 1);
|
||||
|
||||
if (!enableFin) {
|
||||
LEFT_TIM->BDTR &= ~TIM_BDTR_MOE;
|
||||
RIGHT_TIM->BDTR &= ~TIM_BDTR_MOE;
|
||||
} else {
|
||||
LEFT_TIM->BDTR |= TIM_BDTR_MOE;
|
||||
RIGHT_TIM->BDTR |= TIM_BDTR_MOE;
|
||||
}
|
||||
|
||||
// read battery voltage
|
||||
batt_voltage_raw = adc_get_raw(&adc_batVoltage);
|
||||
|
||||
int16_t batVoltageCalib = batt_voltage_raw * BAT_CALIB_REAL_VOLTAGE / BAT_CALIB_ADC;
|
||||
batt_percentage = 100 - (((420 * BAT_CELLS) - batVoltageCalib) / BAT_CELLS / VOLTS_PER_PERCENT / 100);
|
||||
|
||||
// ========================= LEFT MOTOR ===========================
|
||||
rtU_Left.b_motEna = enableFin;
|
||||
rtU_Left.z_ctrlModReq = CTRL_MOD_REQ; // Speed Mode
|
||||
int deadband_rpm_left = rpm_left;
|
||||
if (ABS(deadband_rpm_left) < RPM_DEADBAND) {
|
||||
deadband_rpm_left = 0;
|
||||
}
|
||||
rtU_Left.r_inpTgt = (CLAMP((int)deadband_rpm_left, -MAX_RPM, MAX_RPM) * RPM_TO_UNIT);
|
||||
|
||||
rtU_Left.i_phaAB = curL_phaA;
|
||||
rtU_Left.i_phaBC = curL_phaC;
|
||||
rtU_Left.i_DCLink = curL_DC;
|
||||
|
||||
BLDC_controller_step(rtM_Left);
|
||||
|
||||
int ul = rtY_Left.DC_phaA;
|
||||
int vl = rtY_Left.DC_phaB;
|
||||
int wl = rtY_Left.DC_phaC;
|
||||
|
||||
LEFT_TIM->CCR1 = (uint16_t)CLAMP((ul + pwm_res / 2), PWM_MARGIN, pwm_res - PWM_MARGIN);
|
||||
LEFT_TIM->CCR2 = (uint16_t)CLAMP((vl + pwm_res / 2), PWM_MARGIN, pwm_res - PWM_MARGIN);
|
||||
LEFT_TIM->CCR3 = (uint16_t)CLAMP((wl + pwm_res / 2), PWM_MARGIN, pwm_res - PWM_MARGIN);
|
||||
|
||||
// ========================= RIGHT MOTOR ===========================
|
||||
rtU_Right.b_motEna = enableFin;
|
||||
rtU_Right.z_ctrlModReq = CTRL_MOD_REQ; // Speed Mode
|
||||
int deadband_rpm_right = rpm_right;
|
||||
if (ABS(deadband_rpm_right) < RPM_DEADBAND) {
|
||||
deadband_rpm_right = 0;
|
||||
}
|
||||
rtU_Right.r_inpTgt = -(CLAMP((int)deadband_rpm_right, -MAX_RPM, MAX_RPM) * RPM_TO_UNIT);
|
||||
|
||||
rtU_Right.i_phaAB = curR_phaA;
|
||||
rtU_Right.i_phaBC = curR_phaC;
|
||||
rtU_Right.i_DCLink = curR_DC;
|
||||
|
||||
BLDC_controller_step(rtM_Right);
|
||||
|
||||
int ur = rtY_Right.DC_phaA;
|
||||
int vr = rtY_Right.DC_phaB;
|
||||
int wr = rtY_Right.DC_phaC;
|
||||
|
||||
RIGHT_TIM->CCR1 = (uint16_t)CLAMP((ur + pwm_res / 2), PWM_MARGIN, pwm_res - PWM_MARGIN);
|
||||
RIGHT_TIM->CCR2 = (uint16_t)CLAMP((vr + pwm_res / 2), PWM_MARGIN, pwm_res - PWM_MARGIN);
|
||||
RIGHT_TIM->CCR3 = (uint16_t)CLAMP((wr + pwm_res / 2), PWM_MARGIN, pwm_res - PWM_MARGIN);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,56 @@
|
||||
#ifndef BLDC_DEFS_H
|
||||
#define BLDC_DEFS_H
|
||||
|
||||
#include "stm32h7xx.h" // For GPIO_TypeDef
|
||||
|
||||
#define COM_CTRL 0 // [-] Commutation Control Type
|
||||
#define SIN_CTRL 1 // [-] Sinusoidal Control Type
|
||||
#define FOC_CTRL 2 // [-] Field Oriented Control (FOC) Type
|
||||
|
||||
#define LEFT_TIM TIM8
|
||||
#define RIGHT_TIM TIM1
|
||||
|
||||
#define PWM_FREQ 32000
|
||||
#define PWM_MARGIN 100
|
||||
#define CF_SPEED_COEF (PWM_FREQ / 3)
|
||||
#define MAX_RPM 1000
|
||||
#define RPM_TO_UNIT 16
|
||||
#define RPM_DEADBAND 1
|
||||
|
||||
#define BODY_MOTOR_LEFT 1U
|
||||
#define BODY_MOTOR_RIGHT 2U
|
||||
|
||||
// Stall detection recovery time. Time to wait after a stall before re-enabling controls.
|
||||
#define STALL_DEQUAL_TIME_MS 3000
|
||||
#define T_ERR_DEQUAL_CYCLES (uint16_t)(STALL_DEQUAL_TIME_MS * (PWM_FREQ / 2) / 1000)
|
||||
|
||||
// Motor
|
||||
#define I_DC_MAX 6
|
||||
#define I_MOT_MAX 6
|
||||
#define A2BIT_CONV 310
|
||||
#define N_MOT_MAX 1000
|
||||
|
||||
// Control selections
|
||||
#define CTRL_TYP_SEL FOC_CTRL // [-] Control type selection: COM_CTRL, SIN_CTRL, FOC_CTRL (default)
|
||||
#define CTRL_MOD_REQ SPD_MODE // [-] Control mode request: OPEN_MODE, VLT_MODE (default), SPD_MODE, TRQ_MODE. Note: SPD_MODE and TRQ_MODE are only available for CTRL_FOC!
|
||||
#define DIAG_ENA 1 // [-] Motor Diagnostics enable flag: 0 = Disabled, 1 = Enabled (default)
|
||||
|
||||
// Field Weakening / Phase Advance
|
||||
#define FIELD_WEAK_ENA 1 // [-] Field Weakening / Phase Advance enable flag: 0 = Disabled (default), 1 = Enabled
|
||||
#define FIELD_WEAK_MAX 5 // [A] Maximum Field Weakening D axis current (only for FOC).
|
||||
#define PHASE_ADV_MAX 25 // [deg] Maximum Phase Advance angle (only for SIN).
|
||||
#define FIELD_WEAK_HI 1000 // Input target High threshold
|
||||
#define FIELD_WEAK_LO 750 // Input target Low threshold
|
||||
|
||||
// Battery configuration
|
||||
#define BAT_CELLS 3 // 3 sets in series
|
||||
#define BAT_CELL_FULL_MV 4200U // mV per cell at 100%
|
||||
#define BAT_CELL_EMPTY_MV 3386U // mV per cell at 0% (from V1: 4200 - 100 * 8.14)
|
||||
#define VOLTS_PER_PERCENT 0.00814 // Volts per percent, for conversion of volts to percentage
|
||||
#define BAT_CALIB_REAL_VOLTAGE 1260U // multimeter voltage
|
||||
#define BAT_CALIB_ADC 1275U // adc reading voltage
|
||||
|
||||
void bldc_init(void);
|
||||
void bldc_step(void);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* File: rtwtypes.h
|
||||
*
|
||||
* Code generated for Simulink model 'BLDC_controller'.
|
||||
*
|
||||
* Model version : 1.1297
|
||||
* Simulink Coder version : 8.13 (R2017b) 24-Jul-2017
|
||||
* C/C++ source code generated on : Sun Mar 6 11:02:11 2022
|
||||
*
|
||||
* Target selection: ert.tlc
|
||||
* Embedded hardware selection: ARM Compatible->ARM Cortex
|
||||
* Emulation hardware selection:
|
||||
* Differs from embedded hardware (MATLAB Host)
|
||||
* Code generation objectives:
|
||||
* 1. Execution efficiency
|
||||
* 2. RAM efficiency
|
||||
* Validation result: Not run
|
||||
*/
|
||||
|
||||
#ifndef RTWTYPES_H
|
||||
#define RTWTYPES_H
|
||||
|
||||
/* Logical type definitions */
|
||||
#if (!defined(__cplusplus))
|
||||
# ifndef false
|
||||
# define false (0U)
|
||||
# endif
|
||||
|
||||
# ifndef true
|
||||
# define true (1U)
|
||||
# endif
|
||||
#endif
|
||||
|
||||
/*=======================================================================*
|
||||
* Target hardware information
|
||||
* Device type: MATLAB Host
|
||||
* Number of bits: char: 8 short: 16 int: 32
|
||||
* long: 32 long long: 64
|
||||
* native word size: 64
|
||||
* Byte ordering: LittleEndian
|
||||
* Signed integer division rounds to: Zero
|
||||
* Shift right on a signed integer as arithmetic shift: on
|
||||
*=======================================================================*/
|
||||
|
||||
/*=======================================================================*
|
||||
* Fixed width word size data types: *
|
||||
* int8_T, int16_T, int32_T - signed 8, 16, or 32 bit integers *
|
||||
* uint8_T, uint16_T, uint32_T - unsigned 8, 16, or 32 bit integers *
|
||||
* real32_T, real64_T - 32 and 64 bit floating point numbers *
|
||||
*=======================================================================*/
|
||||
typedef signed char int8_T;
|
||||
typedef unsigned char uint8_T;
|
||||
typedef short int16_T;
|
||||
typedef unsigned short uint16_T;
|
||||
typedef int int32_T;
|
||||
typedef unsigned int uint32_T;
|
||||
typedef long long int64_T;
|
||||
typedef unsigned long long uint64_T;
|
||||
typedef float real32_T;
|
||||
typedef double real64_T;
|
||||
|
||||
/*===========================================================================*
|
||||
* Generic type definitions: boolean_T, char_T, byte_T, int_T, uint_T, *
|
||||
* real_T, time_T, ulong_T, ulonglong_T. *
|
||||
*===========================================================================*/
|
||||
typedef double real_T;
|
||||
typedef double time_T;
|
||||
typedef unsigned char boolean_T;
|
||||
typedef int int_T;
|
||||
typedef unsigned int uint_T;
|
||||
typedef unsigned long ulong_T;
|
||||
typedef unsigned long long ulonglong_T;
|
||||
typedef char char_T;
|
||||
typedef unsigned char uchar_T;
|
||||
typedef char_T byte_T;
|
||||
|
||||
/*=======================================================================*
|
||||
* Min and Max: *
|
||||
* int8_T, int16_T, int32_T - signed 8, 16, or 32 bit integers *
|
||||
* uint8_T, uint16_T, uint32_T - unsigned 8, 16, or 32 bit integers *
|
||||
*=======================================================================*/
|
||||
#define MAX_int8_T ((int8_T)(127))
|
||||
#define MIN_int8_T ((int8_T)(-128))
|
||||
#define MAX_uint8_T ((uint8_T)(255U))
|
||||
#define MAX_int16_T ((int16_T)(32767))
|
||||
#define MIN_int16_T ((int16_T)(-32768))
|
||||
#define MAX_uint16_T ((uint16_T)(65535U))
|
||||
#define MAX_int32_T ((int32_T)(2147483647))
|
||||
#define MIN_int32_T ((int32_T)(-2147483647-1))
|
||||
#define MAX_uint32_T ((uint32_T)(0xFFFFFFFFU))
|
||||
#define MAX_int64_T ((int64_T)(9223372036854775807LL))
|
||||
#define MIN_int64_T ((int64_T)(-9223372036854775807LL-1LL))
|
||||
#define MAX_uint64_T ((uint64_T)(0xFFFFFFFFFFFFFFFFULL))
|
||||
|
||||
/* Block D-Work pointer type */
|
||||
typedef void * pointer_T;
|
||||
|
||||
#endif /* RTWTYPES_H */
|
||||
|
||||
/*
|
||||
* File trailer for generated code.
|
||||
*
|
||||
* [EOF]
|
||||
*/
|
||||
@@ -0,0 +1,46 @@
|
||||
#include "board/body/boards/board_declarations.h"
|
||||
|
||||
void board_body_init(void) {
|
||||
// Initialize CAN pins
|
||||
set_gpio_pullup(CAN_RX_PORT, CAN_RX_PIN, PULL_NONE);
|
||||
set_gpio_alternate(CAN_RX_PORT, CAN_RX_PIN, GPIO_AF9_FDCAN1);
|
||||
set_gpio_pullup(CAN_TX_PORT, CAN_TX_PIN, PULL_NONE);
|
||||
set_gpio_alternate(CAN_TX_PORT, CAN_TX_PIN, GPIO_AF9_FDCAN1);
|
||||
|
||||
// Initialize button input (PC15)
|
||||
set_gpio_mode(IGNITION_SW_PORT, IGNITION_SW_PIN, MODE_INPUT);
|
||||
SYSCFG->EXTICR[3] &= ~(SYSCFG_EXTICR4_EXTI15);
|
||||
SYSCFG->EXTICR[3] |= SYSCFG_EXTICR4_EXTI15_PC;
|
||||
EXTI->PR1 = (1U << 15);
|
||||
EXTI->RTSR1 |= (1U << 15);
|
||||
EXTI->FTSR1 |= (1U << 15);
|
||||
EXTI->IMR1 |= (1U << 15);
|
||||
|
||||
// Initialize barrel jack detection input (PC13)
|
||||
set_gpio_pullup(CHARGING_DETECT_PORT, CHARGING_DETECT_PIN, PULL_UP);
|
||||
set_gpio_mode(CHARGING_DETECT_PORT, CHARGING_DETECT_PIN, MODE_INPUT);
|
||||
SYSCFG->EXTICR[3] &= ~(SYSCFG_EXTICR4_EXTI13);
|
||||
SYSCFG->EXTICR[3] |= SYSCFG_EXTICR4_EXTI13_PC;
|
||||
EXTI->PR1 = (1U << 13);
|
||||
EXTI->RTSR1 |= (1U << 13);
|
||||
EXTI->FTSR1 |= (1U << 13);
|
||||
EXTI->IMR1 |= (1U << 13);
|
||||
|
||||
// Initialize and turn on mici power
|
||||
set_gpio_mode(OBDC_POWER_ON_PORT, OBDC_POWER_ON_PIN, MODE_OUTPUT);
|
||||
set_gpio_output(OBDC_POWER_ON_PORT, OBDC_POWER_ON_PIN, 1);
|
||||
|
||||
// Initialize and turn off gpu power
|
||||
set_gpio_mode(GPU_POWER_ON_PORT, GPU_POWER_ON_PIN, MODE_OUTPUT);
|
||||
set_gpio_output(GPU_POWER_ON_PORT, GPU_POWER_ON_PIN, 0);
|
||||
|
||||
// Initialize and turn off ignition output
|
||||
set_gpio_mode(OBDC_IGNITION_ON_PORT, OBDC_IGNITION_ON_PIN, MODE_OUTPUT);
|
||||
set_gpio_output(OBDC_IGNITION_ON_PORT, OBDC_IGNITION_ON_PIN, 0);
|
||||
}
|
||||
|
||||
board board_body = {
|
||||
.led_GPIO = {GPIOA, GPIOA, GPIOA},
|
||||
.led_pin = {10, 10, 10},
|
||||
.init = board_body_init,
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "board/body/bldc/bldc_defs.h"
|
||||
|
||||
// ******************** Prototypes ********************
|
||||
typedef void (*board_init)(void);
|
||||
typedef void (*board_init_bootloader)(void);
|
||||
typedef void (*board_enable_can_transceiver)(uint8_t transceiver, bool enabled);
|
||||
|
||||
struct board {
|
||||
GPIO_TypeDef * const led_GPIO[3];
|
||||
const uint8_t led_pin[3];
|
||||
const uint8_t led_pwm_channels[3]; // leave at 0 to disable PWM
|
||||
board_init init;
|
||||
board_init_bootloader init_bootloader;
|
||||
const bool has_spi;
|
||||
};
|
||||
|
||||
// ******************* Definitions ********************
|
||||
#define HW_TYPE_BODY 0xB1U
|
||||
|
||||
// Pin definitions
|
||||
// CAN
|
||||
#define CAN_TX_PORT GPIOD
|
||||
#define CAN_TX_PIN 1
|
||||
#define CAN_RX_PORT GPIOD
|
||||
#define CAN_RX_PIN 0
|
||||
#define CAN_TRANSCEIVER_EN_PORT GPIOD
|
||||
#define CAN_TRANSCEIVER_EN_PIN 12
|
||||
|
||||
// Ignition and charging detection
|
||||
#define IGNITION_SW_PORT GPIOC
|
||||
#define IGNITION_SW_PIN 15
|
||||
#define CHARGING_DETECT_PORT GPIOC
|
||||
#define CHARGING_DETECT_PIN 13
|
||||
|
||||
// Dotstar LED
|
||||
#define DOTSTAR_CLK_PORT GPIOB
|
||||
#define DOTSTAR_CLK_PIN 3
|
||||
#define DOTSTAR_DATA_PORT GPIOB
|
||||
#define DOTSTAR_DATA_PIN 5
|
||||
|
||||
// Mici Power On
|
||||
#define OBDC_POWER_ON_PORT GPIOB
|
||||
#define OBDC_POWER_ON_PIN 12
|
||||
|
||||
// GPU Power On
|
||||
#define GPU_POWER_ON_PORT GPIOD
|
||||
#define GPU_POWER_ON_PIN 8
|
||||
|
||||
// Ignition On
|
||||
#define OBDC_IGNITION_ON_PORT GPIOB
|
||||
#define OBDC_IGNITION_ON_PIN 11
|
||||
@@ -0,0 +1,118 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "board/can.h"
|
||||
#include "board/health.h"
|
||||
#include "board/body/boards/board_declarations.h"
|
||||
#include "board/drivers/drivers.h"
|
||||
#include "opendbc/safety/declarations.h"
|
||||
#include "board/body/bldc/bldc.h"
|
||||
|
||||
#define BODY_CAN_ADDR_MOTOR_SPEED 0x201U
|
||||
#define BODY_CAN_ADDR_VAR_VALUES 0x202U
|
||||
#define BODY_CAN_ADDR_BODY_DATA 0x203U
|
||||
#define BODY_CAN_ADDR_V2_ID 0x222U
|
||||
#define BODY_CAN_MOTOR_SPEED_PERIOD_US 10000U
|
||||
#define BODY_CAN_CMD_TIMEOUT_US 100000U
|
||||
#define BODY_BUS_NUMBER 0U
|
||||
|
||||
static uint32_t last_can_cmd_timestamp_us = 0U;
|
||||
static uint16_t counter = 0U;
|
||||
|
||||
void body_can_send_motor_speeds(uint8_t bus, float left_speed_rpm, float right_speed_rpm) {
|
||||
CANPacket_t pkt = {0};
|
||||
pkt.bus = bus;
|
||||
pkt.addr = BODY_CAN_ADDR_MOTOR_SPEED;
|
||||
pkt.data_len_code = 8;
|
||||
int16_t left_speed_deci = left_speed_rpm;
|
||||
int16_t right_speed_deci = -(right_speed_rpm);
|
||||
pkt.data[0] = (uint8_t)((left_speed_deci >> 8) & 0xFFU);
|
||||
pkt.data[1] = (uint8_t)(left_speed_deci & 0xFFU);
|
||||
pkt.data[2] = (uint8_t)((right_speed_deci >> 8) & 0xFFU);
|
||||
pkt.data[3] = (uint8_t)(right_speed_deci & 0xFFU);
|
||||
pkt.data[4] = 0U;
|
||||
pkt.data[5] = 0U;
|
||||
pkt.data[6] = counter & 0xFFU;
|
||||
can_set_checksum(&pkt);
|
||||
can_send(&pkt, bus, true);
|
||||
counter++;
|
||||
}
|
||||
|
||||
void body_can_send_var_values(uint8_t bus, bool ignition, bool enable_motors, uint8_t fault, uint8_t left_z_errcode, uint8_t right_z_errcode) {
|
||||
CANPacket_t pkt = {0};
|
||||
pkt.bus = bus;
|
||||
pkt.addr = BODY_CAN_ADDR_VAR_VALUES;
|
||||
pkt.data_len_code = 3;
|
||||
pkt.data[0] = (ignition ? 1U : 0U) | ((enable_motors ? 1U : 0U) << 1U) | ((fault & 0x3FU) << 2U);
|
||||
pkt.data[1] = left_z_errcode;
|
||||
pkt.data[2] = right_z_errcode;
|
||||
can_set_checksum(&pkt);
|
||||
can_send(&pkt, bus, true);
|
||||
}
|
||||
|
||||
void body_can_send_body_data(uint8_t bus, uint8_t mcu_temp_raw, uint16_t batt_voltage_raw, uint8_t batt_percentage, bool charger_connected) {
|
||||
CANPacket_t pkt = {0};
|
||||
pkt.bus = bus;
|
||||
pkt.addr = BODY_CAN_ADDR_BODY_DATA;
|
||||
pkt.data_len_code = 4;
|
||||
pkt.data[0] = mcu_temp_raw;
|
||||
pkt.data[1] = (uint8_t)((batt_voltage_raw >> 8) & 0xFFU);
|
||||
pkt.data[2] = (uint8_t)(batt_voltage_raw & 0xFFU);
|
||||
pkt.data[3] = (charger_connected ? 1U : 0U) | ((batt_percentage & 0x7FU) << 1U);
|
||||
can_set_checksum(&pkt);
|
||||
can_send(&pkt, bus, true);
|
||||
}
|
||||
|
||||
void body_can_process_target(int16_t left_target_deci_rpm, int16_t right_target_deci_rpm) {
|
||||
rpm_left = (int)(((float)left_target_deci_rpm) * 0.1f);
|
||||
rpm_right = (int)(((float)right_target_deci_rpm) * 0.1f);
|
||||
last_can_cmd_timestamp_us = microsecond_timer_get();
|
||||
}
|
||||
|
||||
void body_can_rx(CANPacket_t *msg) {
|
||||
if ((msg->addr == 0x250U) && (GET_LEN(msg) >= 4U)) {
|
||||
int16_t left_target_deci_rpm = (int16_t)((msg->data[0] << 8U) | msg->data[1]);
|
||||
int16_t right_target_deci_rpm = (int16_t)((msg->data[2] << 8U) | msg->data[3]);
|
||||
body_can_process_target(left_target_deci_rpm, right_target_deci_rpm);
|
||||
}
|
||||
}
|
||||
|
||||
void body_can_init(void) {
|
||||
last_can_cmd_timestamp_us = 0U;
|
||||
can_silent = false;
|
||||
can_loopback = false;
|
||||
(void)set_safety_hooks(SAFETY_BODY, 0U);
|
||||
set_gpio_output(CAN_TRANSCEIVER_EN_PORT, CAN_TRANSCEIVER_EN_PIN, 0); // Enable CAN transceiver
|
||||
can_init_all();
|
||||
}
|
||||
|
||||
void body_can_periodic(uint32_t now, bool ignition, bool plug_charging) {
|
||||
if ((last_can_cmd_timestamp_us != 0U) &&
|
||||
((now - last_can_cmd_timestamp_us) >= BODY_CAN_CMD_TIMEOUT_US)) {
|
||||
rpm_left = 0;
|
||||
rpm_right = 0;
|
||||
last_can_cmd_timestamp_us = 0U;
|
||||
}
|
||||
|
||||
static uint32_t last_motor_speed_tx_us = 0;
|
||||
if ((now - last_motor_speed_tx_us) >= BODY_CAN_MOTOR_SPEED_PERIOD_US) {
|
||||
float left_speed_rpm = motor_encoder_get_speed_rpm(BODY_MOTOR_LEFT);
|
||||
float right_speed_rpm = motor_encoder_get_speed_rpm(BODY_MOTOR_RIGHT);
|
||||
body_can_send_motor_speeds(BODY_BUS_NUMBER, left_speed_rpm, right_speed_rpm);
|
||||
body_can_send_var_values(BODY_BUS_NUMBER, ignition, enable_motors, 0U, rtY_Left.z_errCode, rtY_Right.z_errCode);
|
||||
body_can_send_body_data(BODY_BUS_NUMBER, 0U, batt_voltage_raw, batt_percentage, plug_charging);
|
||||
|
||||
// Send message on 0x222 to identify as body v2
|
||||
CANPacket_t id_pkt = {0};
|
||||
id_pkt.bus = BODY_BUS_NUMBER;
|
||||
id_pkt.addr = BODY_CAN_ADDR_V2_ID;
|
||||
id_pkt.data_len_code = 1;
|
||||
id_pkt.data[0] = 1U;
|
||||
can_set_checksum(&id_pkt);
|
||||
can_send(&id_pkt, BODY_BUS_NUMBER, true);
|
||||
|
||||
last_motor_speed_tx_us = now;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "board/config.h"
|
||||
#include "board/body/boards/board_declarations.h"
|
||||
|
||||
#define DOTSTAR_LED_COUNT 10U
|
||||
#define DOTSTAR_GLOBAL_BRIGHTNESS_MAX 31U
|
||||
|
||||
typedef struct {
|
||||
uint8_t r;
|
||||
uint8_t g;
|
||||
uint8_t b;
|
||||
} dotstar_rgb_t;
|
||||
|
||||
typedef struct {
|
||||
bool initialized;
|
||||
uint8_t global_brightness;
|
||||
dotstar_rgb_t pixels[DOTSTAR_LED_COUNT];
|
||||
} dotstar_state_t;
|
||||
|
||||
static dotstar_state_t dotstar_state = {
|
||||
.initialized = false,
|
||||
.global_brightness = DOTSTAR_GLOBAL_BRIGHTNESS_MAX,
|
||||
};
|
||||
|
||||
static inline void dotstar_set_clk(bool high) {
|
||||
if (high) {
|
||||
DOTSTAR_CLK_PORT->BSRR = (uint32_t)(1U << DOTSTAR_CLK_PIN);
|
||||
} else {
|
||||
DOTSTAR_CLK_PORT->BSRR = (uint32_t)(1U << (DOTSTAR_CLK_PIN + 16U));
|
||||
}
|
||||
}
|
||||
|
||||
static inline void dotstar_set_data(bool high) {
|
||||
if (high) {
|
||||
DOTSTAR_DATA_PORT->BSRR = (uint32_t)(1U << DOTSTAR_DATA_PIN);
|
||||
} else {
|
||||
DOTSTAR_DATA_PORT->BSRR = (uint32_t)(1U << (DOTSTAR_DATA_PIN + 16U));
|
||||
}
|
||||
}
|
||||
|
||||
static inline void dotstar_write_byte(uint8_t value) {
|
||||
for (int8_t bit = 7; bit >= 0; bit--) {
|
||||
dotstar_set_data(((value >> bit) & 0x1U) != 0U);
|
||||
delay(15);
|
||||
dotstar_set_clk(true);
|
||||
delay(15);
|
||||
dotstar_set_clk(false);
|
||||
delay(15);
|
||||
}
|
||||
}
|
||||
|
||||
static inline uint16_t dotstar_latch_len(uint16_t led_count) {
|
||||
uint16_t len = (uint16_t)((led_count + 15U) / 16U);
|
||||
if (len < led_count) {
|
||||
len = led_count;
|
||||
}
|
||||
return (len < 4U) ? 4U : len;
|
||||
}
|
||||
|
||||
static inline void dotstar_send_start_frame(void) {
|
||||
for (uint8_t i = 0U; i < 4U; i++) {
|
||||
dotstar_write_byte(0x00U);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void dotstar_send_end_frame(uint16_t led_count) {
|
||||
uint16_t latch_len = dotstar_latch_len(led_count);
|
||||
for (uint16_t i = 0U; i < latch_len; i++) {
|
||||
dotstar_write_byte(0xFFU);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void dotstar_show(void) {
|
||||
if (!dotstar_state.initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
dotstar_send_start_frame();
|
||||
|
||||
uint8_t prefix = (uint8_t)(0xE0U | dotstar_state.global_brightness);
|
||||
for (uint16_t i = 0U; i < DOTSTAR_LED_COUNT; i++) {
|
||||
dotstar_rgb_t *pixel = &dotstar_state.pixels[i];
|
||||
dotstar_write_byte(prefix);
|
||||
dotstar_write_byte(pixel->b);
|
||||
dotstar_write_byte(pixel->g);
|
||||
dotstar_write_byte(pixel->r);
|
||||
}
|
||||
|
||||
dotstar_send_end_frame(DOTSTAR_LED_COUNT);
|
||||
}
|
||||
|
||||
static inline void dotstar_init(void) {
|
||||
set_gpio_pullup(DOTSTAR_CLK_PORT, DOTSTAR_CLK_PIN, PULL_NONE);
|
||||
set_gpio_output_type(DOTSTAR_CLK_PORT, DOTSTAR_CLK_PIN, OUTPUT_TYPE_PUSH_PULL);
|
||||
set_gpio_mode(DOTSTAR_CLK_PORT, DOTSTAR_CLK_PIN, MODE_OUTPUT);
|
||||
register_set(&(DOTSTAR_CLK_PORT->OSPEEDR), GPIO_OSPEEDR_OSPEED5, GPIO_OSPEEDR_OSPEED5_Msk);
|
||||
dotstar_set_clk(false);
|
||||
|
||||
set_gpio_pullup(DOTSTAR_DATA_PORT, DOTSTAR_DATA_PIN, PULL_NONE);
|
||||
set_gpio_output_type(DOTSTAR_DATA_PORT, DOTSTAR_DATA_PIN, OUTPUT_TYPE_PUSH_PULL);
|
||||
set_gpio_mode(DOTSTAR_DATA_PORT, DOTSTAR_DATA_PIN, MODE_OUTPUT);
|
||||
register_set(&(DOTSTAR_DATA_PORT->OSPEEDR), GPIO_OSPEEDR_OSPEED5, GPIO_OSPEEDR_OSPEED5_Msk);
|
||||
dotstar_set_data(false);
|
||||
|
||||
dotstar_state.initialized = true;
|
||||
dotstar_state.global_brightness = DOTSTAR_GLOBAL_BRIGHTNESS_MAX;
|
||||
|
||||
for (uint16_t i = 0U; i < DOTSTAR_LED_COUNT; i++) {
|
||||
dotstar_state.pixels[i].r = 0U;
|
||||
dotstar_state.pixels[i].g = 0U;
|
||||
dotstar_state.pixels[i].b = 0U;
|
||||
}
|
||||
|
||||
dotstar_show();
|
||||
}
|
||||
|
||||
static inline void dotstar_set_pixel(uint16_t index, uint8_t r, uint8_t g, uint8_t b) {
|
||||
if (!dotstar_state.initialized || (index >= DOTSTAR_LED_COUNT)) {
|
||||
return;
|
||||
}
|
||||
dotstar_state.pixels[index].r = r;
|
||||
dotstar_state.pixels[index].g = g;
|
||||
dotstar_state.pixels[index].b = b;
|
||||
}
|
||||
|
||||
static inline void dotstar_fill(uint8_t r, uint8_t g, uint8_t b) {
|
||||
if (!dotstar_state.initialized) {
|
||||
return;
|
||||
}
|
||||
for (uint16_t i = 0U; i < DOTSTAR_LED_COUNT; i++) {
|
||||
dotstar_state.pixels[i].r = r;
|
||||
dotstar_state.pixels[i].g = g;
|
||||
dotstar_state.pixels[i].b = b;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void dotstar_set_global_brightness(uint8_t brightness) {
|
||||
dotstar_state.global_brightness = (brightness > DOTSTAR_GLOBAL_BRIGHTNESS_MAX) ? DOTSTAR_GLOBAL_BRIGHTNESS_MAX : brightness;
|
||||
}
|
||||
|
||||
static inline void dotstar_hue_to_rgb(uint16_t hue, uint8_t *r, uint8_t *g, uint8_t *b) {
|
||||
hue %= 765U;
|
||||
if (hue < 255U) {
|
||||
*r = (uint8_t)(255U - hue);
|
||||
*g = (uint8_t)hue;
|
||||
*b = 0U;
|
||||
} else if (hue < 510U) {
|
||||
hue -= 255U;
|
||||
*r = 0U;
|
||||
*g = (uint8_t)(255U - hue);
|
||||
*b = (uint8_t)hue;
|
||||
} else {
|
||||
hue -= 510U;
|
||||
*r = (uint8_t)hue;
|
||||
*g = 0U;
|
||||
*b = (uint8_t)(255U - hue);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void dotstar_run_rainbow(uint32_t now_us) {
|
||||
uint32_t brightness_phase = (now_us / 40000U) % 62U;
|
||||
uint8_t brightness = (brightness_phase <= 31U) ? (uint8_t)(brightness_phase + 1U) : (uint8_t)(62U - brightness_phase);
|
||||
if (brightness == 0U) {
|
||||
brightness = 1U;
|
||||
}
|
||||
dotstar_set_global_brightness(brightness);
|
||||
|
||||
uint32_t base_hue = (now_us / 10000U) % 765U;
|
||||
for (uint16_t i = 0U; i < DOTSTAR_LED_COUNT; i++) {
|
||||
uint16_t hue = (uint16_t)((base_hue + (i * 70U)) % 765U);
|
||||
uint8_t r, g, b;
|
||||
dotstar_hue_to_rgb(hue, &r, &g, &b);
|
||||
dotstar_set_pixel(i, r, g, b);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void dotstar_apply_breathe(dotstar_rgb_t color, uint32_t now_us, uint32_t cycle_us) {
|
||||
if (!dotstar_state.initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (cycle_us == 0U) {
|
||||
dotstar_set_global_brightness(DOTSTAR_GLOBAL_BRIGHTNESS_MAX);
|
||||
dotstar_fill(color.r, color.g, color.b);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t phase = now_us % cycle_us;
|
||||
uint32_t half_cycle = cycle_us / 2U;
|
||||
if (half_cycle == 0U) {
|
||||
half_cycle = 1U;
|
||||
}
|
||||
|
||||
uint32_t amplitude = (phase <= half_cycle) ? phase : (cycle_us - phase);
|
||||
uint32_t scale = (amplitude * 255U) / half_cycle;
|
||||
if (scale > 255U) {
|
||||
scale = 255U;
|
||||
}
|
||||
|
||||
uint8_t r = (uint8_t)((color.r * scale) / 255U);
|
||||
uint8_t g = (uint8_t)((color.g * scale) / 255U);
|
||||
uint8_t b = (uint8_t)((color.b * scale) / 255U);
|
||||
|
||||
dotstar_set_global_brightness(DOTSTAR_GLOBAL_BRIGHTNESS_MAX);
|
||||
dotstar_fill(r, g, b);
|
||||
}
|
||||
@@ -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)
|
||||
@@ -0,0 +1,144 @@
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "board/config.h"
|
||||
#include "board/drivers/led.h"
|
||||
#include "board/drivers/pwm.h"
|
||||
#include "board/drivers/usb.h"
|
||||
#include "board/early_init.h"
|
||||
#include "board/obj/gitversion.h"
|
||||
#include "board/body/can.h"
|
||||
#include "opendbc/safety/safety.h"
|
||||
#include "board/drivers/can_common.h"
|
||||
#include "board/drivers/fdcan.h"
|
||||
#include "board/can_comms.h"
|
||||
#include "board/body/dotstar.h"
|
||||
#include "bldc/bldc.h"
|
||||
|
||||
extern int _app_start[0xc000];
|
||||
|
||||
#include "board/body/main_comms.h"
|
||||
|
||||
static volatile uint32_t tick_count = 0U;
|
||||
static volatile uint32_t ignition_press_timestamp_us = 0U;
|
||||
static volatile bool ignition = false;
|
||||
static volatile bool plug_charging = false;
|
||||
|
||||
void debug_ring_callback(uart_ring *ring) {
|
||||
char rcv;
|
||||
while (get_char(ring, &rcv)) {
|
||||
(void)injectc(ring, rcv);
|
||||
}
|
||||
}
|
||||
|
||||
void __attribute__ ((noinline)) enable_fpu(void) {
|
||||
SCB->CPACR |= ((3UL << (10U * 2U)) | (3UL << (11U * 2U)));
|
||||
}
|
||||
|
||||
void __initialize_hardware_early(void) {
|
||||
enable_fpu();
|
||||
early_initialization();
|
||||
}
|
||||
|
||||
void bldc_tim8_handler(void) {
|
||||
if ((LEFT_TIM->SR & TIM_SR_UIF) != 0) {
|
||||
LEFT_TIM->SR = ~TIM_SR_UIF;
|
||||
bldc_step();
|
||||
}
|
||||
}
|
||||
|
||||
void tick_handler(void) {
|
||||
if (TICK_TIMER->SR != 0) {
|
||||
if (can_health[0].transmit_error_cnt >= 128) {
|
||||
(void)llcan_init(CANIF_FROM_CAN_NUM(0));
|
||||
}
|
||||
static bool led_on = false;
|
||||
led_set(LED_RED, led_on);
|
||||
led_on = !led_on;
|
||||
tick_count++;
|
||||
}
|
||||
TICK_TIMER->SR = 0;
|
||||
}
|
||||
|
||||
static void exti15_10_handler(void) {
|
||||
if ((EXTI->PR1 & (1U << CHARGING_DETECT_PIN)) != 0U) {
|
||||
EXTI->PR1 = (1U << CHARGING_DETECT_PIN);
|
||||
plug_charging = (get_gpio_input(CHARGING_DETECT_PORT, CHARGING_DETECT_PIN) != 0);
|
||||
}
|
||||
|
||||
if ((EXTI->PR1 & (1U << IGNITION_SW_PIN)) != 0U) {
|
||||
EXTI->PR1 = (1U << IGNITION_SW_PIN);
|
||||
|
||||
static uint32_t last_press_event_us = 0U;
|
||||
const uint32_t debounce_us = 200000U; // 200 ms
|
||||
|
||||
uint32_t now = microsecond_timer_get();
|
||||
bool pressed = (get_gpio_input(IGNITION_SW_PORT, IGNITION_SW_PIN) == 0);
|
||||
|
||||
if (pressed) {
|
||||
if (get_ts_elapsed(now, last_press_event_us) > debounce_us) {
|
||||
last_press_event_us = now;
|
||||
ignition_press_timestamp_us = now;
|
||||
ignition = !ignition;
|
||||
set_gpio_output(OBDC_IGNITION_ON_PORT, OBDC_IGNITION_ON_PIN, ignition);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
disable_interrupts();
|
||||
init_interrupts(true);
|
||||
|
||||
clock_init();
|
||||
peripherals_init();
|
||||
|
||||
current_board = &board_body;
|
||||
hw_type = HW_TYPE_BODY;
|
||||
|
||||
current_board->init();
|
||||
|
||||
REGISTER_INTERRUPT(EXTI15_10_IRQn, exti15_10_handler, 10000U, FAULT_INTERRUPT_RATE_EXTI);
|
||||
NVIC_ClearPendingIRQ(EXTI15_10_IRQn);
|
||||
NVIC_EnableIRQ(EXTI15_10_IRQn);
|
||||
|
||||
REGISTER_INTERRUPT(TIM8_UP_TIM13_IRQn, bldc_tim8_handler, 100000U, FAULT_INTERRUPT_RATE_TICK);
|
||||
NVIC_ClearPendingIRQ(TIM8_UP_TIM13_IRQn);
|
||||
NVIC_EnableIRQ(TIM8_UP_TIM13_IRQn);
|
||||
|
||||
REGISTER_INTERRUPT(TICK_TIMER_IRQ, tick_handler, 10U, FAULT_INTERRUPT_RATE_TICK);
|
||||
|
||||
led_init();
|
||||
microsecond_timer_init();
|
||||
tick_timer_init();
|
||||
usb_init();
|
||||
body_can_init();
|
||||
dotstar_init();
|
||||
bldc_init();
|
||||
enable_interrupts();
|
||||
|
||||
plug_charging = (get_gpio_input(CHARGING_DETECT_PORT, CHARGING_DETECT_PIN) != 0);
|
||||
|
||||
while (true) {
|
||||
uint32_t now = microsecond_timer_get();
|
||||
if (plug_charging) {
|
||||
motor_set_enable(false);
|
||||
dotstar_apply_breathe((dotstar_rgb_t){255U, 40U, 0U}, now, 2000000U);
|
||||
} else if (ignition) {
|
||||
dotstar_run_rainbow(now);
|
||||
} else {
|
||||
dotstar_apply_breathe((dotstar_rgb_t){0U, 255U, 10U}, now, 1500000U);
|
||||
}
|
||||
|
||||
if (ignition) {
|
||||
motor_set_enable(true);
|
||||
body_can_periodic(now, ignition, plug_charging);
|
||||
} else {
|
||||
motor_set_enable(false);
|
||||
}
|
||||
|
||||
dotstar_show();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
void comms_endpoint2_write(const uint8_t *data, uint32_t len) {
|
||||
UNUSED(data);
|
||||
UNUSED(len);
|
||||
}
|
||||
|
||||
int comms_control_handler(ControlPacket_t *req, uint8_t *resp) {
|
||||
unsigned int resp_len = 0;
|
||||
|
||||
switch (req->request) {
|
||||
// **** 0xc1: get hardware type
|
||||
case 0xc1:
|
||||
resp[0] = hw_type;
|
||||
resp_len = 1;
|
||||
break;
|
||||
|
||||
// **** 0xd1: enter bootloader mode
|
||||
case 0xd1:
|
||||
switch (req->param1) {
|
||||
case 0:
|
||||
print("-> entering bootloader\n");
|
||||
enter_bootloader_mode = ENTER_BOOTLOADER_MAGIC;
|
||||
NVIC_SystemReset();
|
||||
break;
|
||||
case 1:
|
||||
print("-> entering softloader\n");
|
||||
enter_bootloader_mode = ENTER_SOFTLOADER_MAGIC;
|
||||
NVIC_SystemReset();
|
||||
break;
|
||||
default:
|
||||
print("Bootloader mode invalid\n");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
// **** 0xd3/0xd4: signature bytes
|
||||
case 0xd3:
|
||||
case 0xd4: {
|
||||
uint8_t offset = (req->request == 0xd3) ? 0U : 64U;
|
||||
resp_len = 64U;
|
||||
char *code = (char *)_app_start;
|
||||
int code_len = _app_start[0];
|
||||
(void)memcpy(resp, &code[code_len + offset], resp_len);
|
||||
break;
|
||||
}
|
||||
|
||||
// **** 0xd6: get version
|
||||
case 0xd6:
|
||||
(void)memcpy(resp, gitversion, sizeof(gitversion));
|
||||
resp_len = sizeof(gitversion) - 1U;
|
||||
break;
|
||||
|
||||
// **** 0xd8: reset ST
|
||||
case 0xd8:
|
||||
NVIC_SystemReset();
|
||||
break;
|
||||
|
||||
// **** 0xdd: get healthpacket and CANPacket version hashes
|
||||
case 0xdd: {
|
||||
uint32_t versions[2] = {HEALTH_PACKET_VERSION, CAN_PACKET_VERSION_HASH};
|
||||
(void)memcpy(resp, (uint8_t *)versions, sizeof(versions));
|
||||
resp_len = sizeof(versions);
|
||||
break;
|
||||
}
|
||||
|
||||
// **** 0xb3: set motor speeds
|
||||
case 0xb3:
|
||||
rpm_left = (int16_t)req->param1;
|
||||
rpm_right = (int16_t)req->param2;
|
||||
break;
|
||||
|
||||
// **** 0xb4: enable/disable motors
|
||||
case 0xb4:
|
||||
enable_motors = (req->param1 == 1U);
|
||||
if (enable_motors == 0) {
|
||||
rpm_left = 0;
|
||||
rpm_right = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// Ignore unhandled requests
|
||||
break;
|
||||
}
|
||||
return resp_len;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#include "board/body/boards/board_declarations.h"
|
||||
#include "board/body/boards/board_body.h"
|
||||
|
||||
extern board *current_board;
|
||||
extern uint8_t hw_type;
|
||||
|
||||
void detect_board_type(void) {
|
||||
// Board type set explicitly in main()
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
#define VERS_TAG 0x53524556
|
||||
#define MIN_VERSION 2
|
||||
|
||||
// ********************* Includes *********************
|
||||
#include "board/config.h"
|
||||
|
||||
#include "board/drivers/led.h"
|
||||
#include "board/drivers/pwm.h"
|
||||
#include "board/drivers/usb.h"
|
||||
|
||||
#include "board/early_init.h"
|
||||
#include "board/provision.h"
|
||||
|
||||
#include "board/crypto/rsa.h"
|
||||
#include "board/crypto/sha.h"
|
||||
|
||||
#include "board/obj/cert.h"
|
||||
#include "board/obj/gitversion.h"
|
||||
#include "board/flasher.h"
|
||||
|
||||
// cppcheck-suppress unusedFunction ; used in headers not included in cppcheck
|
||||
void __initialize_hardware_early(void) {
|
||||
early_initialization();
|
||||
}
|
||||
|
||||
void fail(void) {
|
||||
soft_flasher_start();
|
||||
}
|
||||
|
||||
// know where to sig check
|
||||
extern void *_app_start[];
|
||||
|
||||
int main(void) {
|
||||
// Init interrupt table
|
||||
init_interrupts(true);
|
||||
|
||||
disable_interrupts();
|
||||
clock_init();
|
||||
detect_board_type();
|
||||
|
||||
#ifdef PANDA_JUNGLE
|
||||
current_board->set_panda_power(true);
|
||||
#endif
|
||||
|
||||
if (enter_bootloader_mode == ENTER_SOFTLOADER_MAGIC) {
|
||||
enter_bootloader_mode = 0;
|
||||
soft_flasher_start();
|
||||
}
|
||||
|
||||
// validate length
|
||||
int len = (int)_app_start[0];
|
||||
if ((len < 8) || (len > (0x1000000 - 0x4000 - 4 - RSANUMBYTES))) goto fail;
|
||||
|
||||
// compute SHA hash
|
||||
uint8_t digest[SHA_DIGEST_SIZE];
|
||||
SHA_hash(&_app_start[1], len-4, digest);
|
||||
|
||||
// verify version, last bytes in the signed area
|
||||
uint32_t vers[2] = {0};
|
||||
memcpy(&vers, ((void*)&_app_start[0]) + len - sizeof(vers), sizeof(vers));
|
||||
if (vers[0] != VERS_TAG || vers[1] < MIN_VERSION) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
// verify RSA signature
|
||||
if (RSA_verify(&release_rsa_key, ((void*)&_app_start[0]) + len, RSANUMBYTES, digest, SHA_DIGEST_SIZE)) {
|
||||
goto good;
|
||||
}
|
||||
|
||||
// allow debug if built from source
|
||||
#ifdef ALLOW_DEBUG
|
||||
if (RSA_verify(&debug_rsa_key, ((void*)&_app_start[0]) + len, RSANUMBYTES, digest, SHA_DIGEST_SIZE)) {
|
||||
goto good;
|
||||
}
|
||||
#endif
|
||||
|
||||
// here is a failure
|
||||
fail:
|
||||
fail();
|
||||
return 0;
|
||||
good:
|
||||
// jump to flash
|
||||
((void(*)(void)) _app_start[1])();
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// ******************** Prototypes ********************
|
||||
void print(const char *a){ UNUSED(a); }
|
||||
void puth(unsigned int i){ UNUSED(i); }
|
||||
void puth2(unsigned int i){ UNUSED(i); }
|
||||
__attribute__((unused)) static void puth4(unsigned int i){ UNUSED(i); }
|
||||
void hexdump(const void *a, int l){ UNUSED(a); UNUSED(l); }
|
||||
typedef struct board board;
|
||||
typedef struct harness_configuration harness_configuration;
|
||||
void pwm_init(TIM_TypeDef *TIM, uint8_t channel);
|
||||
void pwm_set(TIM_TypeDef *TIM, uint8_t channel, uint8_t percentage);
|
||||
// No UART support in bootloader
|
||||
typedef struct uart_ring uart_ring;
|
||||
void uart_init(uart_ring *q, int baud) { UNUSED(q); UNUSED(baud); }
|
||||
|
||||
// ********************* Globals **********************
|
||||
uint8_t hw_type = 0;
|
||||
board *current_board;
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#define PANDA_CAN_CNT 3U
|
||||
|
||||
#include "opendbc/safety/can.h"
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
CAN transactions to and from the host come in the form of
|
||||
a certain number of CANPacket_t. The transaction is split
|
||||
into multiple transfers or chunks.
|
||||
|
||||
CAN packet byte layout (wire format used by comms_can_{read,write}):
|
||||
+--------+--------+--------+--------+--------+--------+--------+------------------------------+
|
||||
| byte 0 | byte 1 | byte 2 | byte 3 | byte 4 | byte 5 | byte 6 | ... byte 13 / byte 69 |
|
||||
+--------+--------+--------+--------+--------+--------+--------+------------------------------+
|
||||
| DLC | addr | addr | addr | flags | cksum | data0 | ... data7 / data63 |
|
||||
| bus | | | | | | | (classic CAN / CAN FD) |
|
||||
| fd | | | | | | | |
|
||||
+--------+--------+--------+--------+--------+--------+--------+------------------------------+
|
||||
Byte/bit fields:
|
||||
byte 0: DLC[7:4], bus[3:1], fd[0]
|
||||
bytes 1..4: (addr << 3) | (extended << 2) | (returned << 1) | rejected
|
||||
byte 5: checksum = XOR(header[0..4] + payload)
|
||||
bytes 6..13 (classic CAN, up to 8 bytes) / bytes 6..69 (CAN FD, up to 64 bytes): payload
|
||||
|
||||
USB/SPI transfer chunking used by this file:
|
||||
+--------------------------------------------+ ... +--------------------------------------------+
|
||||
| transport chunk 0 | | transport chunk N |
|
||||
+--------------------------------------------+ +--------------------------------------------+
|
||||
| concatenated CANPacket_t bytes | | continuation and/or next CANPacket_t bytes |
|
||||
| (no per-64-byte counter/header in protocol)| | |
|
||||
+--------------------------------------------+ +--------------------------------------------+
|
||||
|
||||
* comms_can_read outputs this buffer in chunks of a specified length.
|
||||
chunks are always the given length, except the last one.
|
||||
* comms_can_write reads in this buffer in chunks.
|
||||
* both functions maintain an overflow buffer for a partial CANPacket_t that
|
||||
spans multiple transfers/chunks.
|
||||
* the overflow buffers are reset by a dedicated control transfer handler,
|
||||
which is sent by the host on each start of a connection.
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
uint32_t ptr;
|
||||
uint32_t tail_size;
|
||||
uint8_t data[72];
|
||||
} asm_buffer;
|
||||
|
||||
static asm_buffer can_read_buffer = {.ptr = 0U, .tail_size = 0U};
|
||||
|
||||
int comms_can_read(uint8_t *data, uint32_t max_len) {
|
||||
uint32_t pos = 0U;
|
||||
|
||||
// Send tail of previous message if it is in buffer
|
||||
if (can_read_buffer.ptr > 0U) {
|
||||
uint32_t overflow_len = MIN(max_len - pos, can_read_buffer.ptr);
|
||||
(void)memcpy(&data[pos], can_read_buffer.data, overflow_len);
|
||||
pos += overflow_len;
|
||||
(void)memcpy(can_read_buffer.data, &can_read_buffer.data[overflow_len], can_read_buffer.ptr - overflow_len);
|
||||
can_read_buffer.ptr -= overflow_len;
|
||||
}
|
||||
|
||||
if (can_read_buffer.ptr == 0U) {
|
||||
// Fill rest of buffer with new data
|
||||
CANPacket_t can_packet;
|
||||
while ((pos < max_len) && can_pop(&can_rx_q, &can_packet)) {
|
||||
uint32_t pckt_len = CANPACKET_HEAD_SIZE + dlc_to_len[can_packet.data_len_code];
|
||||
if ((pos + pckt_len) <= max_len) {
|
||||
(void)memcpy(&data[pos], (uint8_t*)&can_packet, pckt_len);
|
||||
pos += pckt_len;
|
||||
} else {
|
||||
(void)memcpy(&data[pos], (uint8_t*)&can_packet, max_len - pos);
|
||||
can_read_buffer.ptr += pckt_len - (max_len - pos);
|
||||
// cppcheck-suppress objectIndex
|
||||
(void)memcpy(can_read_buffer.data, &((uint8_t*)&can_packet)[(max_len - pos)], can_read_buffer.ptr);
|
||||
pos = max_len;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
static asm_buffer can_write_buffer = {.ptr = 0U, .tail_size = 0U};
|
||||
|
||||
// send on CAN
|
||||
void comms_can_write(const uint8_t *data, uint32_t len) {
|
||||
uint32_t pos = 0U;
|
||||
|
||||
// Assembling can message with data from buffer
|
||||
if (can_write_buffer.ptr != 0U) {
|
||||
if (can_write_buffer.tail_size <= (len - pos)) {
|
||||
// we have enough data to complete the buffer
|
||||
CANPacket_t to_push = {0};
|
||||
(void)memcpy(&can_write_buffer.data[can_write_buffer.ptr], &data[pos], can_write_buffer.tail_size);
|
||||
can_write_buffer.ptr += can_write_buffer.tail_size;
|
||||
pos += can_write_buffer.tail_size;
|
||||
|
||||
// send out
|
||||
(void)memcpy((uint8_t*)&to_push, can_write_buffer.data, can_write_buffer.ptr);
|
||||
can_send(&to_push, to_push.bus, false);
|
||||
|
||||
// reset overflow buffer
|
||||
can_write_buffer.ptr = 0U;
|
||||
can_write_buffer.tail_size = 0U;
|
||||
} else {
|
||||
// maybe next time
|
||||
uint32_t data_size = len - pos;
|
||||
(void) memcpy(&can_write_buffer.data[can_write_buffer.ptr], &data[pos], data_size);
|
||||
can_write_buffer.tail_size -= data_size;
|
||||
can_write_buffer.ptr += data_size;
|
||||
pos += data_size;
|
||||
}
|
||||
}
|
||||
|
||||
// rest of the message
|
||||
while (pos < len) {
|
||||
uint32_t pckt_len = CANPACKET_HEAD_SIZE + dlc_to_len[(data[pos] >> 4U)];
|
||||
if ((pos + pckt_len) <= len) {
|
||||
CANPacket_t to_push = {0};
|
||||
(void)memcpy((uint8_t*)&to_push, &data[pos], pckt_len);
|
||||
can_send(&to_push, to_push.bus, false);
|
||||
pos += pckt_len;
|
||||
} else {
|
||||
(void)memcpy(can_write_buffer.data, &data[pos], len - pos);
|
||||
can_write_buffer.ptr = len - pos;
|
||||
can_write_buffer.tail_size = pckt_len - can_write_buffer.ptr;
|
||||
pos += can_write_buffer.ptr;
|
||||
}
|
||||
}
|
||||
|
||||
refresh_can_tx_slots_available();
|
||||
}
|
||||
|
||||
void comms_can_reset(void) {
|
||||
can_write_buffer.ptr = 0U;
|
||||
can_write_buffer.tail_size = 0U;
|
||||
can_read_buffer.ptr = 0U;
|
||||
can_read_buffer.tail_size = 0U;
|
||||
}
|
||||
|
||||
// TODO: make this more general!
|
||||
void refresh_can_tx_slots_available(void) {
|
||||
if (can_tx_check_min_slots_free(MAX_CAN_MSGS_PER_USB_BULK_TRANSFER)) {
|
||||
can_tx_comms_resume_usb();
|
||||
}
|
||||
if (can_tx_check_min_slots_free(MAX_CAN_MSGS_PER_SPI_BULK_TRANSFER)) {
|
||||
can_tx_comms_resume_spi();
|
||||
}
|
||||
}
|
||||
@@ -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-----
|
||||
@@ -0,0 +1 @@
|
||||
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC948lnRo4x44Rd7Y8bQAML4aKDC4XRx958fHV8K6+FbCaP1Z42U2kX0yygak0LjoDutpgObmGHZA+Iz3HeUD6VGjr/teN24vPk+A95cRsjt8rgmGQ96HNjaNgjR+gl1F9XxFimMzir82Xpl1ekTueJNXa7ia5HVH1nFdiksOKHGQ== batman@y840
|
||||
@@ -0,0 +1 @@
|
||||
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDGN9GU2nOc0kKq6vdZI5qUMzHt234ngqofrgCFFxL0D2Whex0zACp9gar0HZp+bvtpoSgU/Ev8wexNKr+A9QTradljiuxi5ctrOra9k+wxqNj63Wrcu4+wU5UnJEVf/buV4jCOFffMT8z3PO4imt8LzHuEIC/m/ASKVYyvuvBRQQ== batman@y840
|
||||
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
typedef struct {
|
||||
uint8_t request;
|
||||
uint16_t param1;
|
||||
uint16_t param2;
|
||||
uint16_t length;
|
||||
} __attribute__((packed)) ControlPacket_t;
|
||||
|
||||
int comms_control_handler(ControlPacket_t *req, uint8_t *resp);
|
||||
void comms_endpoint2_write(const uint8_t *data, uint32_t len);
|
||||
void comms_can_write(const uint8_t *data, uint32_t len);
|
||||
int comms_can_read(uint8_t *data, uint32_t max_len);
|
||||
void comms_can_reset(void);
|
||||
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
//#define DEBUG
|
||||
//#define DEBUG_UART
|
||||
//#define DEBUG_USB
|
||||
//#define DEBUG_SPI
|
||||
//#define DEBUG_FAULTS
|
||||
//#define DEBUG_COMMS
|
||||
//#define DEBUG_FAN
|
||||
|
||||
#define CAN_INIT_TIMEOUT_MS 500U
|
||||
#define USBPACKET_MAX_SIZE 0x40U
|
||||
#define MAX_CAN_MSGS_PER_USB_BULK_TRANSFER 51U
|
||||
#define MAX_CAN_MSGS_PER_SPI_BULK_TRANSFER 170U
|
||||
|
||||
// USB definitions
|
||||
#define USB_VID 0x3801U
|
||||
|
||||
#ifdef PANDA_JUNGLE
|
||||
#ifdef BOOTSTUB
|
||||
#define USB_PID 0xDDEFU
|
||||
#else
|
||||
#define USB_PID 0xDDCFU
|
||||
#endif
|
||||
#else
|
||||
#ifdef BOOTSTUB
|
||||
#define USB_PID 0xDDEEU
|
||||
#else
|
||||
#define USB_PID 0xDDCCU
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// platform includes
|
||||
#ifdef STM32H7
|
||||
#include "board/stm32h7/stm32h7_config.h"
|
||||
#else
|
||||
// TODO: uncomment this, cppcheck complains
|
||||
// building for tests
|
||||
//#include "fake_stm.h"
|
||||
#endif
|
||||
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
uint8_t crc_checksum(const uint8_t *dat, int len, const uint8_t poly) {
|
||||
uint8_t crc = 0xFFU;
|
||||
int i;
|
||||
int j;
|
||||
for (i = len - 1; i >= 0; i--) {
|
||||
crc ^= dat[i];
|
||||
for (j = 0; j < 8; j++) {
|
||||
if ((crc & 0x80U) != 0U) {
|
||||
crc = (uint8_t)((crc << 1) ^ poly);
|
||||
}
|
||||
else {
|
||||
crc <<= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2007 The Android Open Source Project
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * 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.
|
||||
* * Neither the name of Google Inc. 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 Google Inc. ``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 Google Inc. 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.
|
||||
*/
|
||||
|
||||
#ifndef SYSTEM_CORE_INCLUDE_MINCRYPT_HASH_INTERNAL_H_
|
||||
#define SYSTEM_CORE_INCLUDE_MINCRYPT_HASH_INTERNAL_H_
|
||||
|
||||
#include "stdint.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif // __cplusplus
|
||||
|
||||
struct HASH_CTX; // forward decl
|
||||
|
||||
typedef struct HASH_VTAB {
|
||||
void (* const init)(struct HASH_CTX*);
|
||||
void (* const update)(struct HASH_CTX*, const void*, int);
|
||||
const uint8_t* (* const final)(struct HASH_CTX*);
|
||||
const uint8_t* (* const hash)(const void*, int, uint8_t*);
|
||||
int size;
|
||||
} HASH_VTAB;
|
||||
|
||||
typedef struct HASH_CTX {
|
||||
const HASH_VTAB * f;
|
||||
uint64_t count;
|
||||
uint8_t buf[64];
|
||||
uint32_t state[8]; // upto SHA2
|
||||
} HASH_CTX;
|
||||
|
||||
#define HASH_init(ctx) (ctx)->f->init(ctx)
|
||||
#define HASH_update(ctx, data, len) (ctx)->f->update(ctx, data, len)
|
||||
#define HASH_final(ctx) (ctx)->f->final(ctx)
|
||||
#define HASH_hash(data, len, digest) (ctx)->f->hash(data, len, digest)
|
||||
#define HASH_size(ctx) (ctx)->f->size
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif // __cplusplus
|
||||
|
||||
#endif // SYSTEM_CORE_INCLUDE_MINCRYPT_HASH_INTERNAL_H_
|
||||
@@ -0,0 +1,294 @@
|
||||
/* rsa.c
|
||||
**
|
||||
** Copyright 2012, The Android Open Source Project
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * 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.
|
||||
** * Neither the name of Google Inc. 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 Google Inc. ``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 Google Inc. 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.
|
||||
*/
|
||||
|
||||
#include "rsa.h"
|
||||
#include "sha.h"
|
||||
|
||||
// a[] -= mod
|
||||
static void subM(const RSAPublicKey* key,
|
||||
uint32_t* a) {
|
||||
int64_t A = 0;
|
||||
int i;
|
||||
for (i = 0; i < key->len; ++i) {
|
||||
A += (uint64_t)a[i] - key->n[i];
|
||||
a[i] = (uint32_t)A;
|
||||
A >>= 32;
|
||||
}
|
||||
}
|
||||
|
||||
// return a[] >= mod
|
||||
static int geM(const RSAPublicKey* key,
|
||||
const uint32_t* a) {
|
||||
int i;
|
||||
for (i = key->len; i;) {
|
||||
--i;
|
||||
if (a[i] < key->n[i]) return 0;
|
||||
if (a[i] > key->n[i]) return 1;
|
||||
}
|
||||
return 1; // equal
|
||||
}
|
||||
|
||||
// montgomery c[] += a * b[] / R % mod
|
||||
static void montMulAdd(const RSAPublicKey* key,
|
||||
uint32_t* c,
|
||||
const uint32_t a,
|
||||
const uint32_t* b) {
|
||||
uint64_t A = (uint64_t)a * b[0] + c[0];
|
||||
uint32_t d0 = (uint32_t)A * key->n0inv;
|
||||
uint64_t B = (uint64_t)d0 * key->n[0] + (uint32_t)A;
|
||||
int i;
|
||||
|
||||
for (i = 1; i < key->len; ++i) {
|
||||
A = (A >> 32) + (uint64_t)a * b[i] + c[i];
|
||||
B = (B >> 32) + (uint64_t)d0 * key->n[i] + (uint32_t)A;
|
||||
c[i - 1] = (uint32_t)B;
|
||||
}
|
||||
|
||||
A = (A >> 32) + (B >> 32);
|
||||
|
||||
c[i - 1] = (uint32_t)A;
|
||||
|
||||
if (A >> 32) {
|
||||
subM(key, c);
|
||||
}
|
||||
}
|
||||
|
||||
// montgomery c[] = a[] * b[] / R % mod
|
||||
static void montMul(const RSAPublicKey* key,
|
||||
uint32_t* c,
|
||||
const uint32_t* a,
|
||||
const uint32_t* b) {
|
||||
int i;
|
||||
for (i = 0; i < key->len; ++i) {
|
||||
c[i] = 0;
|
||||
}
|
||||
for (i = 0; i < key->len; ++i) {
|
||||
montMulAdd(key, c, a[i], b);
|
||||
}
|
||||
}
|
||||
|
||||
// In-place public exponentiation.
|
||||
// Input and output big-endian byte array in inout.
|
||||
static void modpow(const RSAPublicKey* key,
|
||||
uint8_t* inout) {
|
||||
uint32_t a[RSANUMWORDS];
|
||||
uint32_t aR[RSANUMWORDS];
|
||||
uint32_t aaR[RSANUMWORDS];
|
||||
uint32_t* aaa = 0;
|
||||
int i;
|
||||
|
||||
// Convert from big endian byte array to little endian word array.
|
||||
for (i = 0; i < key->len; ++i) {
|
||||
uint32_t tmp =
|
||||
(inout[((key->len - 1 - i) * 4) + 0] << 24) |
|
||||
(inout[((key->len - 1 - i) * 4) + 1] << 16) |
|
||||
(inout[((key->len - 1 - i) * 4) + 2] << 8) |
|
||||
(inout[((key->len - 1 - i) * 4) + 3] << 0);
|
||||
a[i] = tmp;
|
||||
}
|
||||
|
||||
if (key->exponent == 65537) {
|
||||
aaa = aaR; // Re-use location.
|
||||
montMul(key, aR, a, key->rr); // aR = a * RR / R mod M
|
||||
for (i = 0; i < 16; i += 2) {
|
||||
montMul(key, aaR, aR, aR); // aaR = aR * aR / R mod M
|
||||
montMul(key, aR, aaR, aaR); // aR = aaR * aaR / R mod M
|
||||
}
|
||||
montMul(key, aaa, aR, a); // aaa = aR * a / R mod M
|
||||
} else if (key->exponent == 3) {
|
||||
aaa = aR; // Re-use location.
|
||||
montMul(key, aR, a, key->rr); /* aR = a * RR / R mod M */
|
||||
montMul(key, aaR, aR, aR); /* aaR = aR * aR / R mod M */
|
||||
montMul(key, aaa, aaR, a); /* aaa = aaR * a / R mod M */
|
||||
}
|
||||
|
||||
// Make sure aaa < mod; aaa is at most 1x mod too large.
|
||||
if (geM(key, aaa)) {
|
||||
subM(key, aaa);
|
||||
}
|
||||
|
||||
// Convert to bigendian byte array
|
||||
for (i = key->len - 1; i >= 0; --i) {
|
||||
uint32_t tmp = aaa[i];
|
||||
*inout++ = tmp >> 24;
|
||||
*inout++ = tmp >> 16;
|
||||
*inout++ = tmp >> 8;
|
||||
*inout++ = tmp >> 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Expected PKCS1.5 signature padding bytes, for a keytool RSA signature.
|
||||
// Has the 0-length optional parameter encoded in the ASN1 (as opposed to the
|
||||
// other flavor which omits the optional parameter entirely). This code does not
|
||||
// accept signatures without the optional parameter.
|
||||
|
||||
/*
|
||||
static const uint8_t sha_padding[RSANUMBYTES] = {
|
||||
0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0x00, 0x30, 0x21, 0x30,
|
||||
0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a,
|
||||
0x05, 0x00, 0x04, 0x14,
|
||||
|
||||
// 20 bytes of hash go here.
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||
};
|
||||
*/
|
||||
|
||||
static const uint8_t sha_padding_1024[RSANUMBYTES] = {
|
||||
0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0x00,
|
||||
|
||||
// 20 bytes of hash go here.
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||
};
|
||||
|
||||
// SHA-1 of PKCS1.5 signature sha_padding for 2048 bit, as above.
|
||||
// At the location of the bytes of the hash all 00 are hashed.
|
||||
/*static const uint8_t kExpectedPadShaRsa2048[SHA_DIGEST_SIZE] = {
|
||||
0xdc, 0xbd, 0xbe, 0x42, 0xd5, 0xf5, 0xa7, 0x2e,
|
||||
0x6e, 0xfc, 0xf5, 0x5d, 0xaf, 0x9d, 0xea, 0x68,
|
||||
0x7c, 0xfb, 0xf1, 0x67
|
||||
};*/
|
||||
|
||||
// Verify a 2048-bit RSA PKCS1.5 signature against an expected hash.
|
||||
// Both e=3 and e=65537 are supported. hash_len may be
|
||||
// SHA_DIGEST_SIZE (== 20) to indicate a SHA-1 hash, or
|
||||
// SHA256_DIGEST_SIZE (== 32) to indicate a SHA-256 hash. No other
|
||||
// values are supported.
|
||||
//
|
||||
// Returns 1 on successful verification, 0 on failure.
|
||||
int RSA_verify(const RSAPublicKey *key,
|
||||
const uint8_t *signature,
|
||||
const int len,
|
||||
const uint8_t *hash,
|
||||
const int hash_len) {
|
||||
uint8_t buf[RSANUMBYTES];
|
||||
int i;
|
||||
//const uint8_t* padding_hash;
|
||||
|
||||
if (key->len != RSANUMWORDS) {
|
||||
return 0; // Wrong key passed in.
|
||||
}
|
||||
|
||||
if (len != sizeof(buf)) {
|
||||
return 0; // Wrong input length.
|
||||
}
|
||||
|
||||
if (hash_len != SHA_DIGEST_SIZE) {
|
||||
return 0; // Unsupported hash.
|
||||
}
|
||||
|
||||
if (key->exponent != 3 && key->exponent != 65537) {
|
||||
return 0; // Unsupported exponent.
|
||||
}
|
||||
|
||||
for (i = 0; i < len; ++i) { // Copy input to local workspace.
|
||||
buf[i] = signature[i];
|
||||
}
|
||||
|
||||
modpow(key, buf); // In-place exponentiation.
|
||||
|
||||
#ifdef TEST_RSA
|
||||
printf("sig\n");
|
||||
for (i=0;i<len;i++) { if(i!=0 && i%0x10 == 0) printf("\n"); printf("%02X ", signature[i]); } printf("\n");
|
||||
printf("hash\n");
|
||||
for (i=0;i<hash_len;i++) { if(i!=0 && i%0x10 == 0) printf("\n"); printf("%02X ", hash[i]); } printf("\n");
|
||||
printf("out\n");
|
||||
for (i=0;i<RSANUMBYTES;i++) { if(i!=0 && i%0x10 == 0) printf("\n"); printf("%02X ", buf[i]); } printf("\n");
|
||||
printf("target\n");
|
||||
for (i=0;i<RSANUMBYTES;i++) { if(i!=0 && i%0x10 == 0) printf("\n"); printf("%02X ", sha_padding_1024[i]); } printf("\n");
|
||||
#endif
|
||||
|
||||
// Xor sha portion, so it all becomes 00 iff equal.
|
||||
for (i = len - hash_len; i < len; ++i) {
|
||||
buf[i] ^= *hash++;
|
||||
}
|
||||
|
||||
// Hash resulting buf, in-place.
|
||||
/*switch (hash_len) {
|
||||
case SHA_DIGEST_SIZE:
|
||||
padding_hash = kExpectedPadShaRsa2048;
|
||||
SHA_hash(buf, len, buf);
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// Compare against expected hash value.
|
||||
for (i = 0; i < hash_len; ++i) {
|
||||
if (buf[i] != padding_hash[i]) {
|
||||
return 0;
|
||||
}
|
||||
}*/
|
||||
|
||||
for (i = 0; i < RSANUMBYTES; ++i) {
|
||||
if (buf[i] != sha_padding_1024[i]) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 1; // All checked out OK.
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/* rsa.h
|
||||
**
|
||||
** Copyright 2008, The Android Open Source Project
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * 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.
|
||||
** * Neither the name of Google Inc. 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 Google Inc. ``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 Google Inc. 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.
|
||||
*/
|
||||
|
||||
#ifndef SYSTEM_CORE_INCLUDE_MINCRYPT_RSA_H_
|
||||
#define SYSTEM_CORE_INCLUDE_MINCRYPT_RSA_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define RSANUMBYTES 128 /* 1024 bit key length */
|
||||
#define RSANUMWORDS (RSANUMBYTES / sizeof(uint32_t))
|
||||
|
||||
typedef struct RSAPublicKey {
|
||||
int len; /* Length of n[] in number of uint32_t */
|
||||
uint32_t n0inv; /* -1 / n[0] mod 2^32 */
|
||||
uint32_t n[RSANUMWORDS]; /* modulus as little endian array */
|
||||
uint32_t rr[RSANUMWORDS]; /* R^2 as little endian array */
|
||||
int exponent; /* 3 or 65537 */
|
||||
} RSAPublicKey;
|
||||
|
||||
int RSA_verify(const RSAPublicKey *key,
|
||||
const uint8_t* signature,
|
||||
const int len,
|
||||
const uint8_t* hash,
|
||||
const int hash_len);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // SYSTEM_CORE_INCLUDE_MINCRYPT_RSA_H_
|
||||
@@ -0,0 +1,179 @@
|
||||
/* sha.c
|
||||
**
|
||||
** Copyright 2013, The Android Open Source Project
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * 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.
|
||||
** * Neither the name of Google Inc. 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 Google Inc. ``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 Google Inc. 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.
|
||||
*/
|
||||
|
||||
// Optimized for minimal code size.
|
||||
|
||||
void *memcpy(void *str1, const void *str2, unsigned int n);
|
||||
|
||||
#include "sha.h"
|
||||
|
||||
#define rol(bits, value) (((value) << (bits)) | ((value) >> (32 - (bits))))
|
||||
|
||||
static void SHA1_Transform(SHA_CTX* ctx) {
|
||||
uint32_t W[80];
|
||||
uint32_t A, B, C, D, E;
|
||||
uint8_t* p = ctx->buf;
|
||||
int t;
|
||||
|
||||
for(t = 0; t < 16; ++t) {
|
||||
uint32_t tmp = *p++ << 24;
|
||||
tmp |= *p++ << 16;
|
||||
tmp |= *p++ << 8;
|
||||
tmp |= *p++;
|
||||
W[t] = tmp;
|
||||
}
|
||||
|
||||
for(; t < 80; t++) {
|
||||
W[t] = rol(1,W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16]);
|
||||
}
|
||||
|
||||
A = ctx->state[0];
|
||||
B = ctx->state[1];
|
||||
C = ctx->state[2];
|
||||
D = ctx->state[3];
|
||||
E = ctx->state[4];
|
||||
|
||||
for(t = 0; t < 80; t++) {
|
||||
uint32_t tmp = rol(5,A) + E + W[t];
|
||||
|
||||
if (t < 20)
|
||||
tmp += (D^(B&(C^D))) + 0x5A827999;
|
||||
else if ( t < 40)
|
||||
tmp += (B^C^D) + 0x6ED9EBA1;
|
||||
else if ( t < 60)
|
||||
tmp += ((B&C)|(D&(B|C))) + 0x8F1BBCDC;
|
||||
else
|
||||
tmp += (B^C^D) + 0xCA62C1D6;
|
||||
|
||||
E = D;
|
||||
D = C;
|
||||
C = rol(30,B);
|
||||
B = A;
|
||||
A = tmp;
|
||||
}
|
||||
|
||||
ctx->state[0] += A;
|
||||
ctx->state[1] += B;
|
||||
ctx->state[2] += C;
|
||||
ctx->state[3] += D;
|
||||
ctx->state[4] += E;
|
||||
}
|
||||
|
||||
static const HASH_VTAB SHA_VTAB = {
|
||||
SHA_init,
|
||||
SHA_update,
|
||||
SHA_final,
|
||||
SHA_hash,
|
||||
SHA_DIGEST_SIZE
|
||||
};
|
||||
|
||||
void SHA_init(SHA_CTX* ctx) {
|
||||
ctx->f = &SHA_VTAB;
|
||||
ctx->state[0] = 0x67452301;
|
||||
ctx->state[1] = 0xEFCDAB89;
|
||||
ctx->state[2] = 0x98BADCFE;
|
||||
ctx->state[3] = 0x10325476;
|
||||
ctx->state[4] = 0xC3D2E1F0;
|
||||
ctx->count = 0;
|
||||
}
|
||||
|
||||
|
||||
void SHA_update(SHA_CTX* ctx, const void* data, int len) {
|
||||
int i = (int) (ctx->count & 63);
|
||||
const uint8_t* p = (const uint8_t*)data;
|
||||
|
||||
ctx->count += len;
|
||||
|
||||
while (len--) {
|
||||
ctx->buf[i++] = *p++;
|
||||
if (i == 64) {
|
||||
SHA1_Transform(ctx);
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const uint8_t* SHA_final(SHA_CTX* ctx) {
|
||||
uint8_t *p = ctx->buf;
|
||||
uint64_t cnt = ctx->count * 8;
|
||||
int i;
|
||||
|
||||
SHA_update(ctx, (uint8_t*)"\x80", 1);
|
||||
while ((ctx->count & 63) != 56) {
|
||||
SHA_update(ctx, (uint8_t*)"\0", 1);
|
||||
}
|
||||
|
||||
/* Hack - right shift operator with non const argument requires
|
||||
* libgcc.a which is missing in EON
|
||||
* thus expanding for loop from
|
||||
|
||||
for (i = 0; i < 8; ++i) {
|
||||
uint8_t tmp = (uint8_t) (cnt >> ((7 - i) * 8));
|
||||
SHA_update(ctx, &tmp, 1);
|
||||
}
|
||||
|
||||
to
|
||||
*/
|
||||
|
||||
uint8_t tmp = 0;
|
||||
tmp = (uint8_t) (cnt >> ((7 - 0) * 8));
|
||||
SHA_update(ctx, &tmp, 1);
|
||||
tmp = (uint8_t) (cnt >> ((7 - 1) * 8));
|
||||
SHA_update(ctx, &tmp, 1);
|
||||
tmp = (uint8_t) (cnt >> ((7 - 2) * 8));
|
||||
SHA_update(ctx, &tmp, 1);
|
||||
tmp = (uint8_t) (cnt >> ((7 - 3) * 8));
|
||||
SHA_update(ctx, &tmp, 1);
|
||||
tmp = (uint8_t) (cnt >> ((7 - 4) * 8));
|
||||
SHA_update(ctx, &tmp, 1);
|
||||
tmp = (uint8_t) (cnt >> ((7 - 5) * 8));
|
||||
SHA_update(ctx, &tmp, 1);
|
||||
tmp = (uint8_t) (cnt >> ((7 - 6) * 8));
|
||||
SHA_update(ctx, &tmp, 1);
|
||||
tmp = (uint8_t) (cnt >> ((7 - 7) * 8));
|
||||
SHA_update(ctx, &tmp, 1);
|
||||
|
||||
for (i = 0; i < 5; i++) {
|
||||
uint32_t tmp = ctx->state[i];
|
||||
*p++ = tmp >> 24;
|
||||
*p++ = tmp >> 16;
|
||||
*p++ = tmp >> 8;
|
||||
*p++ = tmp >> 0;
|
||||
}
|
||||
|
||||
return ctx->buf;
|
||||
}
|
||||
|
||||
/* Convenience function */
|
||||
const uint8_t* SHA_hash(const void* data, int len, uint8_t* digest) {
|
||||
SHA_CTX ctx;
|
||||
SHA_init(&ctx);
|
||||
SHA_update(&ctx, data, len);
|
||||
memcpy(digest, SHA_final(&ctx), SHA_DIGEST_SIZE);
|
||||
return digest;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2005 The Android Open Source Project
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * 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.
|
||||
* * Neither the name of Google Inc. 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 Google Inc. ``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 Google Inc. 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.
|
||||
*/
|
||||
#ifndef SYSTEM_CORE_INCLUDE_MINCRYPT_SHA1_H_
|
||||
#define SYSTEM_CORE_INCLUDE_MINCRYPT_SHA1_H_
|
||||
|
||||
#include "hash-internal.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif // __cplusplus
|
||||
|
||||
typedef HASH_CTX SHA_CTX;
|
||||
|
||||
void SHA_init(SHA_CTX* ctx);
|
||||
void SHA_update(SHA_CTX* ctx, const void* data, int len);
|
||||
const uint8_t* SHA_final(SHA_CTX* ctx);
|
||||
|
||||
// Convenience method. Returns digest address.
|
||||
// NOTE: *digest needs to hold SHA_DIGEST_SIZE bytes.
|
||||
const uint8_t* SHA_hash(const void* data, int len, uint8_t* digest);
|
||||
|
||||
#define SHA_DIGEST_SIZE 20
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif // __cplusplus
|
||||
|
||||
#endif // SYSTEM_CORE_INCLUDE_MINCRYPT_SHA1_H_
|
||||
Executable
+38
@@ -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)
|
||||
@@ -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`.
|
||||
Executable
+4
@@ -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"
|
||||
Executable
+11
@@ -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
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
gdb-multiarch --eval-command="target extended-remote localhost:3333"
|
||||
@@ -0,0 +1,68 @@
|
||||
#include "board/drivers/drivers.h"
|
||||
|
||||
bool bootkick_reset_triggered = false;
|
||||
|
||||
void bootkick_tick(bool ignition, bool recent_heartbeat) {
|
||||
static uint16_t bootkick_last_serial_ptr = 0;
|
||||
static uint8_t waiting_to_boot_countdown = 0;
|
||||
static uint8_t boot_reset_countdown = 0;
|
||||
static uint8_t bootkick_harness_status_prev = HARNESS_STATUS_NC;
|
||||
static bool bootkick_ign_prev = false;
|
||||
static BootState boot_state = BOOT_BOOTKICK;
|
||||
BootState boot_state_prev = boot_state;
|
||||
const bool harness_inserted = (harness.status != bootkick_harness_status_prev) && (harness.status != HARNESS_STATUS_NC);
|
||||
|
||||
if ((ignition && !bootkick_ign_prev) || harness_inserted) {
|
||||
// bootkick on rising edge of ignition or harness insertion
|
||||
boot_state = BOOT_BOOTKICK;
|
||||
} else if (recent_heartbeat) {
|
||||
// disable bootkick once openpilot is up
|
||||
boot_state = BOOT_STANDBY;
|
||||
} else {
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
Ensure SOM boots in case it goes into QDL mode. Reset behavior:
|
||||
* shouldn't trigger on the first boot after power-on
|
||||
* only try reset once per bootkick, i.e. don't keep trying until booted
|
||||
* only try once per panda boot, since openpilot will reset panda on startup
|
||||
* once BOOT_RESET is triggered, it stays until countdown is finished
|
||||
*/
|
||||
if (!bootkick_reset_triggered && (boot_state == BOOT_BOOTKICK) && (boot_state_prev == BOOT_STANDBY)) {
|
||||
waiting_to_boot_countdown = 20U;
|
||||
}
|
||||
if (waiting_to_boot_countdown > 0U) {
|
||||
bool serial_activity = uart_ring_som_debug.w_ptr_tx != bootkick_last_serial_ptr;
|
||||
if (serial_activity || current_board->read_som_gpio() || (boot_state != BOOT_BOOTKICK)) {
|
||||
waiting_to_boot_countdown = 0U;
|
||||
} else {
|
||||
// try a reset
|
||||
if (waiting_to_boot_countdown == 1U) {
|
||||
boot_reset_countdown = 5U;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handle reset state
|
||||
if (boot_reset_countdown > 0U) {
|
||||
boot_state = BOOT_RESET;
|
||||
bootkick_reset_triggered = true;
|
||||
} else {
|
||||
if (boot_state == BOOT_RESET) {
|
||||
boot_state = BOOT_BOOTKICK;
|
||||
}
|
||||
}
|
||||
|
||||
// update state
|
||||
bootkick_ign_prev = ignition;
|
||||
bootkick_harness_status_prev = harness.status;
|
||||
bootkick_last_serial_ptr = uart_ring_som_debug.w_ptr_tx;
|
||||
if (waiting_to_boot_countdown > 0U) {
|
||||
waiting_to_boot_countdown--;
|
||||
}
|
||||
if (boot_reset_countdown > 0U) {
|
||||
boot_reset_countdown--;
|
||||
}
|
||||
current_board->set_bootkick(boot_state);
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
#include "board/drivers/drivers.h"
|
||||
#include "opendbc/safety/ignition.h"
|
||||
|
||||
uint32_t safety_tx_blocked = 0;
|
||||
uint32_t safety_rx_invalid = 0;
|
||||
uint32_t tx_buffer_overflow = 0;
|
||||
uint32_t rx_buffer_overflow = 0;
|
||||
|
||||
can_health_t can_health[PANDA_CAN_CNT] = {{0}, {0}, {0}};
|
||||
|
||||
bool can_silent = true;
|
||||
bool can_loopback = false;
|
||||
|
||||
// ********************* instantiate queues *********************
|
||||
#define can_buffer(x, size) \
|
||||
static CANPacket_t elems_##x[size]; \
|
||||
extern can_ring can_##x; \
|
||||
can_ring can_##x = { .w_ptr = 0, .r_ptr = 0, .fifo_size = (size), .elems = (CANPacket_t *)&(elems_##x) };
|
||||
|
||||
#define CAN_RX_BUFFER_SIZE 4096U
|
||||
#define CAN_TX_BUFFER_SIZE 416U
|
||||
|
||||
#ifdef STM32H7
|
||||
// ITCM RAM and DTCM RAM are the fastest for Cortex-M7 core access
|
||||
__attribute__((section(".axisram"))) can_buffer(rx_q, CAN_RX_BUFFER_SIZE)
|
||||
__attribute__((section(".itcmram"))) can_buffer(tx1_q, CAN_TX_BUFFER_SIZE)
|
||||
__attribute__((section(".itcmram"))) can_buffer(tx2_q, CAN_TX_BUFFER_SIZE)
|
||||
#else // kept for PC
|
||||
can_buffer(rx_q, CAN_RX_BUFFER_SIZE)
|
||||
can_buffer(tx1_q, CAN_TX_BUFFER_SIZE)
|
||||
can_buffer(tx2_q, CAN_TX_BUFFER_SIZE)
|
||||
#endif
|
||||
can_buffer(tx3_q, CAN_TX_BUFFER_SIZE)
|
||||
|
||||
// FIXME:
|
||||
// cppcheck-suppress misra-c2012-9.3
|
||||
can_ring *can_queues[PANDA_CAN_CNT] = {&can_tx1_q, &can_tx2_q, &can_tx3_q};
|
||||
|
||||
// ********************* interrupt safe queue *********************
|
||||
bool can_pop(can_ring *q, CANPacket_t *elem) {
|
||||
bool ret = 0;
|
||||
|
||||
ENTER_CRITICAL();
|
||||
if (q->w_ptr != q->r_ptr) {
|
||||
*elem = q->elems[q->r_ptr];
|
||||
if ((q->r_ptr + 1U) == q->fifo_size) {
|
||||
q->r_ptr = 0;
|
||||
} else {
|
||||
q->r_ptr += 1U;
|
||||
}
|
||||
ret = 1;
|
||||
}
|
||||
EXIT_CRITICAL();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool can_push(can_ring *q, const CANPacket_t *elem) {
|
||||
bool ret = false;
|
||||
uint32_t next_w_ptr;
|
||||
|
||||
ENTER_CRITICAL();
|
||||
if ((q->w_ptr + 1U) == q->fifo_size) {
|
||||
next_w_ptr = 0;
|
||||
} else {
|
||||
next_w_ptr = q->w_ptr + 1U;
|
||||
}
|
||||
if (next_w_ptr != q->r_ptr) {
|
||||
q->elems[q->w_ptr] = *elem;
|
||||
q->w_ptr = next_w_ptr;
|
||||
ret = true;
|
||||
}
|
||||
EXIT_CRITICAL();
|
||||
if (!ret) {
|
||||
#ifdef DEBUG
|
||||
print("can_push to ");
|
||||
if (q == &can_rx_q) {
|
||||
print("can_rx_q");
|
||||
} else if (q == &can_tx1_q) {
|
||||
print("can_tx1_q");
|
||||
} else if (q == &can_tx2_q) {
|
||||
print("can_tx2_q");
|
||||
} else if (q == &can_tx3_q) {
|
||||
print("can_tx3_q");
|
||||
} else {
|
||||
print("unknown");
|
||||
}
|
||||
print(" failed!\n");
|
||||
#endif
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t can_slots_empty(const can_ring *q) {
|
||||
uint32_t ret = 0;
|
||||
|
||||
ENTER_CRITICAL();
|
||||
if (q->w_ptr >= q->r_ptr) {
|
||||
ret = q->fifo_size - 1U - q->w_ptr + q->r_ptr;
|
||||
} else {
|
||||
ret = q->r_ptr - q->w_ptr - 1U;
|
||||
}
|
||||
EXIT_CRITICAL();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void can_clear(can_ring *q) {
|
||||
ENTER_CRITICAL();
|
||||
q->w_ptr = 0;
|
||||
q->r_ptr = 0;
|
||||
EXIT_CRITICAL();
|
||||
// handle TX buffer full with zero ECUs awake on the bus
|
||||
refresh_can_tx_slots_available();
|
||||
}
|
||||
|
||||
// assign CAN numbering
|
||||
// bus num: CAN Bus numbers in panda, sent to/from USB
|
||||
// Min: 0; Max: 127; Bit 7 marks message as receipt (bus 129 is receipt for but 1)
|
||||
// cans: Look up MCU can interface from bus number
|
||||
// can number: numeric lookup for MCU CAN interfaces (0 = CAN1, 1 = CAN2, etc);
|
||||
// bus_lookup: Translates from 'can number' to 'bus number'.
|
||||
// can_num_lookup: Translates from 'bus number' to 'can number'.
|
||||
// forwarding bus: If >= 0, forward all messages from this bus to the specified bus.
|
||||
|
||||
// Helpers
|
||||
// Panda: Bus 0=CAN1 Bus 1=CAN2 Bus 2=CAN3
|
||||
bus_config_t bus_config[PANDA_CAN_CNT] = {
|
||||
{ .bus_lookup = 0U, .can_num_lookup = 0U, .forwarding_bus = -1, .can_speed = 5000U, .can_data_speed = 20000U, .canfd_auto = false, .canfd_enabled = false, .brs_enabled = false, .canfd_non_iso = false },
|
||||
{ .bus_lookup = 1U, .can_num_lookup = 1U, .forwarding_bus = -1, .can_speed = 5000U, .can_data_speed = 20000U, .canfd_auto = false, .canfd_enabled = false, .brs_enabled = false, .canfd_non_iso = false },
|
||||
{ .bus_lookup = 2U, .can_num_lookup = 2U, .forwarding_bus = -1, .can_speed = 5000U, .can_data_speed = 20000U, .canfd_auto = false, .canfd_enabled = false, .brs_enabled = false, .canfd_non_iso = false },
|
||||
};
|
||||
|
||||
void can_init_all(void) {
|
||||
for (uint8_t i=0U; i < PANDA_CAN_CNT; i++) {
|
||||
bus_config[i].canfd_enabled = false;
|
||||
can_clear(can_queues[i]);
|
||||
(void)can_init(i);
|
||||
}
|
||||
}
|
||||
|
||||
void can_set_orientation(bool flipped) {
|
||||
bus_config[0].bus_lookup = flipped ? 2U : 0U;
|
||||
bus_config[0].can_num_lookup = flipped ? 2U : 0U;
|
||||
bus_config[2].bus_lookup = flipped ? 0U : 2U;
|
||||
bus_config[2].can_num_lookup = flipped ? 0U : 2U;
|
||||
}
|
||||
|
||||
#ifdef PANDA_JUNGLE
|
||||
void can_set_forwarding(uint8_t from, uint8_t to) {
|
||||
bus_config[from].forwarding_bus = to;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool can_tx_check_min_slots_free(uint32_t min) {
|
||||
return
|
||||
(can_slots_empty(&can_tx1_q) >= min) &&
|
||||
(can_slots_empty(&can_tx2_q) >= min) &&
|
||||
(can_slots_empty(&can_tx3_q) >= min);
|
||||
}
|
||||
|
||||
uint8_t calculate_checksum(const uint8_t *dat, uint32_t len) {
|
||||
uint8_t checksum = 0U;
|
||||
for (uint32_t i = 0U; i < len; i++) {
|
||||
checksum ^= dat[i];
|
||||
}
|
||||
return checksum;
|
||||
}
|
||||
|
||||
void can_set_checksum(CANPacket_t *packet) {
|
||||
packet->checksum = 0U;
|
||||
packet->checksum = calculate_checksum((uint8_t *) packet, CANPACKET_HEAD_SIZE + GET_LEN(packet));
|
||||
}
|
||||
|
||||
bool can_check_checksum(CANPacket_t *packet) {
|
||||
return (calculate_checksum((uint8_t *) packet, CANPACKET_HEAD_SIZE + GET_LEN(packet)) == 0U);
|
||||
}
|
||||
|
||||
void can_send(CANPacket_t *to_push, uint8_t bus_number, bool skip_tx_hook) {
|
||||
if (skip_tx_hook || safety_tx_hook(to_push) != 0) {
|
||||
if (bus_number < PANDA_CAN_CNT) {
|
||||
// add CAN packet to send queue
|
||||
tx_buffer_overflow += can_push(can_queues[bus_number], to_push) ? 0U : 1U;
|
||||
process_can(CAN_NUM_FROM_BUS_NUM(bus_number));
|
||||
}
|
||||
} else {
|
||||
safety_tx_blocked += 1U;
|
||||
to_push->returned = 0U;
|
||||
to_push->rejected = 1U;
|
||||
|
||||
// data changed
|
||||
can_set_checksum(to_push);
|
||||
rx_buffer_overflow += can_push(&can_rx_q, to_push) ? 0U : 1U;
|
||||
}
|
||||
}
|
||||
|
||||
bool is_speed_valid(uint32_t speed, const uint32_t *all_speeds, uint8_t len) {
|
||||
bool ret = false;
|
||||
for (uint8_t i = 0U; i < len; i++) {
|
||||
if (all_speeds[i] == speed) {
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
#include "board/drivers/drivers.h"
|
||||
|
||||
#define CLOCK_SOURCE_PERIOD_MS 50U
|
||||
#define CLOCK_SOURCE_PULSE_LEN_MS 2U
|
||||
|
||||
void clock_source_set_timer_params(uint16_t param1, uint16_t param2) {
|
||||
// Pulse length of each channel
|
||||
register_set(&(TIM1->CCR1), (((param1 & 0xFF00U) >> 8U)*10U), 0xFFFFU);
|
||||
register_set(&(TIM1->CCR2), ((param1 & 0x00FFU)*10U), 0xFFFFU);
|
||||
register_set(&(TIM8->CCR3), (((param2 & 0xFF00U) >> 8U)*10U), 0xFFFFU);
|
||||
// Timer period
|
||||
register_set(&(TIM1->ARR), (((param2 & 0x00FFU)*10U) - 1U), 0xFFFFU);
|
||||
register_set(&(TIM1->CCR4), ((TIM1->ARR + 1U) / 2U), 0xFFFFU);
|
||||
}
|
||||
|
||||
void clock_source_init(bool enable_channel1) {
|
||||
// Setup timer
|
||||
register_set(&(TIM1->PSC), ((APB2_TIMER_FREQ*100U)-1U), 0xFFFFU); // Tick on 0.1 ms
|
||||
register_set(&(TIM1->ARR), ((CLOCK_SOURCE_PERIOD_MS*10U) - 1U), 0xFFFFU); // Period
|
||||
register_set(&(TIM1->CCMR1), 0U, 0xFFFFU); // No output on compare
|
||||
register_set(&(TIM1->CCER), TIM_CCER_CC1E | TIM_CCER_CC2NE, 0xFFFFU); // Enable compares
|
||||
register_set(&(TIM1->CCR1), (CLOCK_SOURCE_PULSE_LEN_MS*10U), 0xFFFFU); // Compare 1 value
|
||||
register_set(&(TIM1->CCR2), (CLOCK_SOURCE_PULSE_LEN_MS*10U), 0xFFFFU); // Compare 2 value
|
||||
register_set(&(TIM1->CCR4), (CLOCK_SOURCE_PERIOD_MS*5U), 0xFFFFU); // For slave timer
|
||||
register_set_bits(&(TIM1->DIER), TIM_DIER_UIE | TIM_DIER_CC1IE); // Enable interrupts
|
||||
|
||||
|
||||
// No interrupts
|
||||
NVIC_DisableIRQ(TIM1_UP_TIM10_IRQn);
|
||||
NVIC_DisableIRQ(TIM1_CC_IRQn);
|
||||
|
||||
// Set GPIO as timer channels
|
||||
if (enable_channel1) {
|
||||
set_gpio_alternate(GPIOA, 8, GPIO_AF1_TIM1);
|
||||
}
|
||||
set_gpio_alternate(GPIOB, 14, GPIO_AF1_TIM1);
|
||||
|
||||
// Set PWM mode
|
||||
register_set(&(TIM1->CCMR1), (0b110UL << TIM_CCMR1_OC1M_Pos) | (0b110UL << TIM_CCMR1_OC2M_Pos), 0xFFFFU);
|
||||
register_set(&(TIM1->CCMR2), (0b110UL << TIM_CCMR2_OC3M_Pos) | (0b111UL << TIM_CCMR2_OC4M_Pos), 0xFFFFU);
|
||||
|
||||
// Enable output
|
||||
register_set(&(TIM1->BDTR), TIM_BDTR_MOE, 0xFFFFU);
|
||||
|
||||
// Sync with slave
|
||||
register_set(&(TIM1->SMCR), TIM_SMCR_MSM , 0xFFFFU);
|
||||
register_set(&(TIM1->CR2), (0b0111U << TIM_CR2_MMS_Pos), 0xFFFFU);
|
||||
register_set(&(TIM8->SMCR), (0b0100U << TIM_SMCR_SMS_Pos) | (0b000U << TIM_SMCR_TS_Pos), 0xFFFFU);
|
||||
|
||||
// Setup slave timer (TIM8)
|
||||
register_set(&(TIM8->PSC), TIM1->PSC, 0xFFFFU);
|
||||
register_set(&(TIM8->ARR), TIM1->ARR, 0xFFFFU);
|
||||
register_set(&(TIM8->CCMR2), (0b110UL << TIM_CCMR2_OC3M_Pos), 0xFFFFU);
|
||||
register_set(&(TIM8->CCR3), (CLOCK_SOURCE_PULSE_LEN_MS * 10U), 0xFFFFU);
|
||||
register_set(&(TIM8->CCER), TIM_CCER_CC3NE, 0xFFFFU);
|
||||
|
||||
// MOE for TIM8 as well
|
||||
register_set(&(TIM8->BDTR), TIM_BDTR_MOE, 0xFFFFU);
|
||||
|
||||
// Set GPIO
|
||||
set_gpio_alternate(GPIOB, 15, GPIO_AF3_TIM8);
|
||||
|
||||
// Enable timers
|
||||
register_set(&(TIM1->CR1), TIM_CR1_CEN, 0x3FU);
|
||||
register_set(&(TIM8->CR1), TIM_CR1_CEN, 0x3FU);
|
||||
}
|
||||
@@ -0,0 +1,286 @@
|
||||
#pragma once
|
||||
|
||||
#include "board/can.h"
|
||||
#include "board/health.h"
|
||||
#include "board/crc.h"
|
||||
#ifdef STM32H7
|
||||
#include "board/stm32h7/lladc_declarations.h"
|
||||
#endif
|
||||
|
||||
// ******************** bootkick ********************
|
||||
|
||||
extern bool bootkick_reset_triggered;
|
||||
|
||||
void bootkick_tick(bool ignition, bool recent_heartbeat);
|
||||
|
||||
// ******************** can_common ********************
|
||||
|
||||
typedef struct {
|
||||
volatile uint32_t w_ptr;
|
||||
volatile uint32_t r_ptr;
|
||||
uint32_t fifo_size;
|
||||
CANPacket_t *elems;
|
||||
} can_ring;
|
||||
|
||||
typedef struct {
|
||||
uint8_t bus_lookup;
|
||||
uint8_t can_num_lookup;
|
||||
int8_t forwarding_bus;
|
||||
uint32_t can_speed;
|
||||
uint32_t can_data_speed;
|
||||
bool canfd_auto;
|
||||
bool canfd_enabled;
|
||||
bool brs_enabled;
|
||||
bool canfd_non_iso;
|
||||
} bus_config_t;
|
||||
|
||||
extern uint32_t safety_tx_blocked;
|
||||
extern uint32_t safety_rx_invalid;
|
||||
extern uint32_t tx_buffer_overflow;
|
||||
extern uint32_t rx_buffer_overflow;
|
||||
|
||||
extern can_health_t can_health[PANDA_CAN_CNT];
|
||||
|
||||
// Ignition detected from CAN messages
|
||||
extern bool ignition_can;
|
||||
extern uint32_t ignition_can_cnt;
|
||||
|
||||
extern bool can_silent;
|
||||
extern bool can_loopback;
|
||||
|
||||
// ******************* functions prototypes *********************
|
||||
bool can_init(uint8_t can_number);
|
||||
void process_can(uint8_t can_number);
|
||||
|
||||
// ********************* instantiate queues *********************
|
||||
extern can_ring *can_queues[PANDA_CAN_CNT];
|
||||
|
||||
// helpers
|
||||
#define WORD_TO_BYTE_ARRAY(dst8, src32) 0[dst8] = ((src32) & 0xFFU); 1[dst8] = (((src32) >> 8U) & 0xFFU); 2[dst8] = (((src32) >> 16U) & 0xFFU); 3[dst8] = (((src32) >> 24U) & 0xFFU)
|
||||
#define BYTE_ARRAY_TO_WORD(dst32, src8) ((dst32) = 0[src8] | (1[src8] << 8U) | (2[src8] << 16U) | (3[src8] << 24U))
|
||||
|
||||
// ********************* interrupt safe queue *********************
|
||||
bool can_pop(can_ring *q, CANPacket_t *elem);
|
||||
bool can_push(can_ring *q, const CANPacket_t *elem);
|
||||
uint32_t can_slots_empty(const can_ring *q);
|
||||
extern bus_config_t bus_config[PANDA_CAN_CNT];
|
||||
|
||||
#define CANIF_FROM_CAN_NUM(num) (cans[num])
|
||||
#define BUS_NUM_FROM_CAN_NUM(num) (bus_config[num].bus_lookup)
|
||||
#define CAN_NUM_FROM_BUS_NUM(num) (bus_config[num].can_num_lookup)
|
||||
|
||||
void can_init_all(void);
|
||||
void can_set_orientation(bool flipped);
|
||||
#ifdef PANDA_JUNGLE
|
||||
void can_set_forwarding(uint8_t from, uint8_t to);
|
||||
#endif
|
||||
bool can_tx_check_min_slots_free(uint32_t min);
|
||||
uint8_t calculate_checksum(const uint8_t *dat, uint32_t len);
|
||||
void can_set_checksum(CANPacket_t *packet);
|
||||
bool can_check_checksum(CANPacket_t *packet);
|
||||
void can_send(CANPacket_t *to_push, uint8_t bus_number, bool skip_tx_hook);
|
||||
bool is_speed_valid(uint32_t speed, const uint32_t *all_speeds, uint8_t len);
|
||||
|
||||
// ******************** clock_source ********************
|
||||
|
||||
void clock_source_set_timer_params(uint16_t param1, uint16_t param2);
|
||||
void clock_source_init(bool enable_channel1);
|
||||
|
||||
// ******************** fan ********************
|
||||
|
||||
struct fan_state_t {
|
||||
uint16_t tach_counter;
|
||||
uint16_t rpm;
|
||||
uint8_t power;
|
||||
float error_integral;
|
||||
uint8_t cooldown_counter;
|
||||
};
|
||||
extern struct fan_state_t fan_state;
|
||||
|
||||
void fan_set_power(uint8_t percentage);
|
||||
void llfan_init(void);
|
||||
void fan_init(void);
|
||||
// Call this at FAN_TICK_FREQ
|
||||
void fan_tick(void);
|
||||
|
||||
// ******************** fdcan ********************
|
||||
#ifdef STM32H7
|
||||
|
||||
typedef struct {
|
||||
volatile uint32_t header[2];
|
||||
volatile uint32_t data_word[CANPACKET_DATA_SIZE_MAX/4U];
|
||||
} canfd_fifo;
|
||||
|
||||
extern FDCAN_GlobalTypeDef *cans[PANDA_CAN_CNT];
|
||||
|
||||
#define CAN_ACK_ERROR 3U
|
||||
|
||||
void can_clear_send(FDCAN_GlobalTypeDef *FDCANx, uint8_t can_number);
|
||||
void update_can_health_pkt(uint8_t can_number, uint32_t ir_reg);
|
||||
|
||||
void process_can(uint8_t can_number);
|
||||
void can_rx(uint8_t can_number);
|
||||
bool can_init(uint8_t can_number);
|
||||
|
||||
// ******************** harness ********************
|
||||
|
||||
#define HARNESS_STATUS_NC 0U
|
||||
#define HARNESS_STATUS_NORMAL 1U
|
||||
#define HARNESS_STATUS_FLIPPED 2U
|
||||
|
||||
struct harness_t {
|
||||
uint8_t status;
|
||||
uint16_t sbu1_voltage_mV;
|
||||
uint16_t sbu2_voltage_mV;
|
||||
bool relay_driven;
|
||||
bool sbu_adc_lock;
|
||||
};
|
||||
extern struct harness_t harness;
|
||||
|
||||
struct harness_configuration {
|
||||
GPIO_TypeDef * const GPIO_SBU1;
|
||||
GPIO_TypeDef * const GPIO_SBU2;
|
||||
GPIO_TypeDef * const GPIO_relay_SBU1;
|
||||
GPIO_TypeDef * const GPIO_relay_SBU2;
|
||||
const uint8_t pin_SBU1;
|
||||
const uint8_t pin_SBU2;
|
||||
const uint8_t pin_relay_SBU1;
|
||||
const uint8_t pin_relay_SBU2;
|
||||
const adc_signal_t adc_signal_SBU1;
|
||||
const adc_signal_t adc_signal_SBU2;
|
||||
};
|
||||
|
||||
// The ignition relay is only used for testing purposes
|
||||
void set_intercept_relay(bool intercept, bool ignition_relay);
|
||||
bool harness_check_ignition(void);
|
||||
void harness_tick(void);
|
||||
void harness_init(void);
|
||||
|
||||
// ******************** interrupts ********************
|
||||
|
||||
typedef struct interrupt {
|
||||
IRQn_Type irq_type;
|
||||
void (*handler)(void);
|
||||
uint32_t call_counter;
|
||||
uint32_t call_rate;
|
||||
uint32_t max_call_rate; // Call rate is defined as the amount of calls each second
|
||||
uint32_t call_rate_fault;
|
||||
} interrupt;
|
||||
|
||||
void interrupt_timer_init(void);
|
||||
uint32_t microsecond_timer_get(void);
|
||||
void unused_interrupt_handler(void);
|
||||
|
||||
extern interrupt interrupts[NUM_INTERRUPTS];
|
||||
|
||||
#define REGISTER_INTERRUPT(irq_num, func_ptr, call_rate_max, rate_fault) \
|
||||
interrupts[irq_num].irq_type = (irq_num); \
|
||||
interrupts[irq_num].handler = (func_ptr); \
|
||||
interrupts[irq_num].call_counter = 0U; \
|
||||
interrupts[irq_num].call_rate = 0U; \
|
||||
interrupts[irq_num].max_call_rate = (call_rate_max); \
|
||||
interrupts[irq_num].call_rate_fault = (rate_fault);
|
||||
|
||||
extern float interrupt_load;
|
||||
|
||||
void handle_interrupt(IRQn_Type irq_type);
|
||||
// Every second
|
||||
void interrupt_timer_handler(void);
|
||||
void init_interrupts(bool check_rate_limit);
|
||||
|
||||
#endif // STM32H7
|
||||
|
||||
// ******************** registers ********************
|
||||
|
||||
// 10 bit hash with 23 as a prime
|
||||
#define REGISTER_MAP_SIZE 0x3FFU
|
||||
#define HASHING_PRIME 23U
|
||||
|
||||
// Do not put bits in the check mask that get changed by the hardware
|
||||
void register_set(volatile uint32_t *addr, uint32_t val, uint32_t mask);
|
||||
// Set individual bits. Also add them to the check_mask.
|
||||
// Do not use this to change bits that get reset by the hardware
|
||||
void register_set_bits(volatile uint32_t *addr, uint32_t val);
|
||||
// Clear individual bits. Also add them to the check_mask.
|
||||
// Do not use this to clear bits that get set by the hardware
|
||||
void register_clear_bits(volatile uint32_t *addr, uint32_t val);
|
||||
// To be called periodically
|
||||
void check_registers(void);
|
||||
void init_registers(void);
|
||||
|
||||
// ******************** simple_watchdog ********************
|
||||
|
||||
typedef struct simple_watchdog_state_t {
|
||||
uint32_t fault;
|
||||
uint32_t last_ts;
|
||||
uint32_t threshold;
|
||||
} simple_watchdog_state_t;
|
||||
|
||||
void simple_watchdog_kick(void);
|
||||
void simple_watchdog_init(uint32_t fault, uint32_t threshold);
|
||||
|
||||
// ******************** spi ********************
|
||||
|
||||
// got max rate from hitting a non-existent endpoint
|
||||
// in a tight loop, plus some buffer
|
||||
#define SPI_IRQ_RATE 16000U
|
||||
|
||||
#define SPI_BUF_SIZE 4096U
|
||||
extern uint8_t spi_buf_rx[SPI_BUF_SIZE];
|
||||
extern uint8_t spi_buf_tx[SPI_BUF_SIZE];
|
||||
|
||||
extern uint16_t spi_error_count;
|
||||
|
||||
void can_tx_comms_resume_spi(void);
|
||||
void spi_init(void);
|
||||
void spi_rx_done(void);
|
||||
void spi_tx_done(bool reset);
|
||||
|
||||
// ******************** uart ********************
|
||||
#ifdef STM32H7
|
||||
|
||||
// ***************************** Definitions *****************************
|
||||
#define FIFO_SIZE_INT 0x400U
|
||||
|
||||
typedef struct uart_ring {
|
||||
volatile uint16_t w_ptr_tx;
|
||||
volatile uint16_t r_ptr_tx;
|
||||
uint8_t *elems_tx;
|
||||
uint32_t tx_fifo_size;
|
||||
volatile uint16_t w_ptr_rx;
|
||||
volatile uint16_t r_ptr_rx;
|
||||
uint8_t *elems_rx;
|
||||
uint32_t rx_fifo_size;
|
||||
USART_TypeDef *uart;
|
||||
void (*callback)(struct uart_ring*);
|
||||
bool overwrite;
|
||||
} uart_ring;
|
||||
|
||||
// ***************************** Function prototypes *****************************
|
||||
void debug_ring_callback(uart_ring *ring);
|
||||
void uart_tx_ring(uart_ring *q);
|
||||
uart_ring *get_ring_by_number(int a);
|
||||
// ************************* Low-level buffer functions *************************
|
||||
bool get_char(uart_ring *q, char *elem);
|
||||
bool injectc(uart_ring *q, char elem);
|
||||
bool put_char(uart_ring *q, char elem);
|
||||
void clear_uart_buff(uart_ring *q);
|
||||
// ************************ High-level debug functions **********************
|
||||
void putch(const char a);
|
||||
void print(const char *a);
|
||||
void puthx(uint32_t i, uint8_t len);
|
||||
void puth(unsigned int i);
|
||||
#if defined(DEBUG_SPI) || defined(BOOTSTUB) || defined(DEBUG)
|
||||
static void puth4(unsigned int i);
|
||||
#endif
|
||||
#if defined(DEBUG_SPI) || defined(DEBUG_USB) || defined(DEBUG_COMMS)
|
||||
static void hexdump(const void *a, int l);
|
||||
#endif
|
||||
|
||||
#endif // STM32H7
|
||||
|
||||
// ******************** usb ********************
|
||||
|
||||
void usb_init(void);
|
||||
void refresh_can_tx_slots_available(void);
|
||||
void can_tx_comms_resume_usb(void);
|
||||
@@ -0,0 +1,115 @@
|
||||
#include "board/stm32h7/lli2c.h"
|
||||
|
||||
#define CODEC_I2C_ADDR 0x10
|
||||
|
||||
void siren_tim7_init(void) {
|
||||
// Init trigger timer (around 2.5kHz)
|
||||
register_set(&TIM7->PSC, 0U, 0xFFFFU);
|
||||
register_set(&TIM7->ARR, 133U, 0xFFFFU);
|
||||
register_set(&TIM7->CR2, (0b10U << TIM_CR2_MMS_Pos), TIM_CR2_MMS_Msk);
|
||||
register_set(&TIM7->CR1, TIM_CR1_ARPE | TIM_CR1_URS, 0x088EU);
|
||||
TIM7->SR = 0U;
|
||||
TIM7->CR1 |= TIM_CR1_CEN;
|
||||
}
|
||||
|
||||
void siren_dac_init(void) {
|
||||
DAC1->DHR8R1 = (1U << 7);
|
||||
DAC1->DHR8R2 = (1U << 7);
|
||||
register_set(&DAC1->MCR, 0U, 0xFFFFFFFFU);
|
||||
register_set(&DAC1->CR, DAC_CR_TEN1 | (6U << DAC_CR_TSEL1_Pos) | DAC_CR_DMAEN1, 0xFFFFFFFFU);
|
||||
register_set_bits(&DAC1->CR, DAC_CR_EN1 | DAC_CR_EN2);
|
||||
}
|
||||
|
||||
void siren_dma_init(void) {
|
||||
// 1Vpp sine wave with 1V offset
|
||||
static const uint8_t fake_siren_lut[360] = { 134U, 135U, 137U, 138U, 139U, 140U, 141U, 143U, 144U, 145U, 146U, 148U, 149U, 150U, 151U, 152U, 154U, 155U, 156U, 157U, 158U, 159U, 160U, 162U, 163U, 164U, 165U, 166U, 167U, 168U, 169U, 170U, 171U, 172U, 174U, 175U, 176U, 177U, 177U, 178U, 179U, 180U, 181U, 182U, 183U, 184U, 185U, 186U, 186U, 187U, 188U, 189U, 190U, 190U, 191U, 192U, 193U, 193U, 194U, 195U, 195U, 196U, 196U, 197U, 197U, 198U, 199U, 199U, 199U, 200U, 200U, 201U, 201U, 202U, 202U, 202U, 203U, 203U, 203U, 203U, 204U, 204U, 204U, 204U, 204U, 204U, 204U, 205U, 205U, 205U, 205U, 205U, 205U, 205U, 204U, 204U, 204U, 204U, 204U, 204U, 204U, 203U, 203U, 203U, 203U, 202U, 202U, 202U, 201U, 201U, 200U, 200U, 199U, 199U, 199U, 198U, 197U, 197U, 196U, 196U, 195U, 195U, 194U, 193U, 193U, 192U, 191U, 190U, 190U, 189U, 188U, 187U, 186U, 186U, 185U, 184U, 183U, 182U, 181U, 180U, 179U, 178U, 177U, 177U, 176U, 175U, 174U, 172U, 171U, 170U, 169U, 168U, 167U, 166U, 165U, 164U, 163U, 162U, 160U, 159U, 158U, 157U, 156U, 155U, 154U, 152U, 151U, 150U, 149U, 148U, 146U, 145U, 144U, 143U, 141U, 140U, 139U, 138U, 137U, 135U, 134U, 133U, 132U, 130U, 129U, 128U, 127U, 125U, 124U, 123U, 122U, 121U, 119U, 118U, 117U, 116U, 115U, 113U, 112U, 111U, 110U, 109U, 108U, 106U, 105U, 104U, 103U, 102U, 101U, 100U, 99U, 98U, 97U, 96U, 95U, 94U, 93U, 92U, 91U, 90U, 89U, 88U, 87U, 86U, 85U, 84U, 83U, 82U, 82U, 81U, 80U, 79U, 78U, 78U, 77U, 76U, 76U, 75U, 74U, 74U, 73U, 72U, 72U, 71U, 71U, 70U, 70U, 69U, 69U, 68U, 68U, 67U, 67U, 67U, 66U, 66U, 66U, 65U, 65U, 65U, 65U, 64U, 64U, 64U, 64U, 64U, 64U, 64U, 64U, 64U, 63U, 64U, 64U, 64U, 64U, 64U, 64U, 64U, 64U, 64U, 65U, 65U, 65U, 65U, 66U, 66U, 66U, 67U, 67U, 67U, 68U, 68U, 69U, 69U, 70U, 70U, 71U, 71U, 72U, 72U, 73U, 74U, 74U, 75U, 76U, 76U, 77U, 78U, 78U, 79U, 80U, 81U, 82U, 82U, 83U, 84U, 85U, 86U, 87U, 88U, 89U, 90U, 91U, 92U, 93U, 94U, 95U, 96U, 97U, 98U, 99U, 100U, 101U, 102U, 103U, 104U, 105U, 106U, 108U, 109U, 110U, 111U, 112U, 113U, 115U, 116U, 117U, 118U, 119U, 121U, 122U, 123U, 124U, 125U, 127U, 128U, 129U, 130U, 132U, 133U };
|
||||
// Setup DMAMUX (DAC_CH1_DMA as input)
|
||||
register_set(&DMAMUX1_Channel1->CCR, 67U, DMAMUX_CxCR_DMAREQ_ID_Msk);
|
||||
register_set(&DMA1_Stream1->PAR, (uint32_t)&(DAC1->DHR8R1), 0xFFFFFFFFU);
|
||||
// Setup DMA
|
||||
register_set(&DMA1_Stream1->M0AR, (uint32_t)fake_siren_lut, 0xFFFFFFFFU);
|
||||
register_set(&DMA1_Stream1->FCR, 0U, 0x00000083U);
|
||||
DMA1_Stream1->NDTR = sizeof(fake_siren_lut);
|
||||
DMA1_Stream1->CR = (0b11UL << DMA_SxCR_PL_Pos) | DMA_SxCR_MINC | DMA_SxCR_CIRC | (1U << DMA_SxCR_DIR_Pos);
|
||||
}
|
||||
|
||||
void fake_siren_codec_enable(bool enabled) {
|
||||
if (enabled) {
|
||||
bool success = true;
|
||||
success &= i2c_set_reg_bits(I2C5, CODEC_I2C_ADDR, 0x2B, (1U << 1)); // Left speaker mix from INA1
|
||||
success &= i2c_set_reg_bits(I2C5, CODEC_I2C_ADDR, 0x2C, (1U << 1)); // Right speaker mix from INA1
|
||||
success &= i2c_set_reg_mask(I2C5, CODEC_I2C_ADDR, 0x3D, 0x17, 0b11111); // Left speaker volume
|
||||
success &= i2c_set_reg_mask(I2C5, CODEC_I2C_ADDR, 0x3E, 0x17, 0b11111); // Right speaker volume
|
||||
success &= i2c_set_reg_mask(I2C5, CODEC_I2C_ADDR, 0x37, 0b101, 0b111); // INA gain
|
||||
success &= i2c_set_reg_bits(I2C5, CODEC_I2C_ADDR, 0x4C, (1U << 7)); // Enable INA
|
||||
success &= i2c_set_reg_bits(I2C5, CODEC_I2C_ADDR, 0x51, (1U << 7)); // Disable global shutdown
|
||||
if (!success) {
|
||||
print("Siren codec enable failed\n");
|
||||
fault_occurred(FAULT_SIREN_MALFUNCTION);
|
||||
}
|
||||
} else {
|
||||
// Disable INA input. Make sure to retry a few times if the I2C bus is busy.
|
||||
for (uint8_t i=0U; i<10U; i++) {
|
||||
if (i2c_clear_reg_bits(I2C5, CODEC_I2C_ADDR, 0x4C, (1U << 7))) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void fake_i2c_siren_init(void) {
|
||||
siren_dac_init();
|
||||
siren_dma_init();
|
||||
siren_tim7_init();
|
||||
// Enable the I2C to the codec
|
||||
i2c_init(I2C5);
|
||||
fake_siren_codec_enable(false);
|
||||
}
|
||||
|
||||
void fake_i2c_siren_set(bool enabled) {
|
||||
static bool initialized = false;
|
||||
static bool fake_siren_enabled = false;
|
||||
|
||||
if (!initialized) {
|
||||
fake_i2c_siren_init();
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
if (enabled != fake_siren_enabled) {
|
||||
fake_siren_codec_enable(enabled);
|
||||
}
|
||||
|
||||
if (enabled) {
|
||||
register_set_bits(&DMA1_Stream1->CR, DMA_SxCR_EN);
|
||||
} else {
|
||||
register_clear_bits(&DMA1_Stream1->CR, DMA_SxCR_EN);
|
||||
}
|
||||
fake_siren_enabled = enabled;
|
||||
}
|
||||
|
||||
void fake_siren_set(bool enabled) {
|
||||
static bool initialized = false;
|
||||
static bool fake_siren_enabled = false;
|
||||
|
||||
if (!initialized) {
|
||||
siren_tim7_init();
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
if (enabled != fake_siren_enabled) {
|
||||
if (enabled) {
|
||||
sound_stop_dac();
|
||||
siren_dac_init();
|
||||
siren_dma_init();
|
||||
current_board->set_amp_enabled(true);
|
||||
register_set_bits(&DMA1_Stream1->CR, DMA_SxCR_EN);
|
||||
} else {
|
||||
current_board->set_amp_enabled(false);
|
||||
// Stop modified 8-bit DAC and start normal 12-bit DAC
|
||||
sound_stop_dac();
|
||||
sound_init_dac();
|
||||
register_set_bits(&BDMA_Channel0->CCR, BDMA_CCR_EN);
|
||||
}
|
||||
fake_siren_enabled = enabled;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
#include "board/drivers/drivers.h"
|
||||
|
||||
struct fan_state_t fan_state;
|
||||
|
||||
static const uint8_t FAN_TICK_FREQ = 8U;
|
||||
|
||||
void fan_set_power(uint8_t percentage) {
|
||||
if (percentage > 0U) {
|
||||
fan_state.power = CLAMP(percentage, 20U, 100U);
|
||||
} else {
|
||||
fan_state.power = 0U;
|
||||
}
|
||||
}
|
||||
|
||||
void fan_init(void) {
|
||||
fan_state.cooldown_counter = current_board->fan_enable_cooldown_time * FAN_TICK_FREQ;
|
||||
llfan_init();
|
||||
}
|
||||
|
||||
// Call this at FAN_TICK_FREQ
|
||||
void fan_tick(void) {
|
||||
if (current_board->has_fan) {
|
||||
// Measure fan RPM
|
||||
uint16_t fan_rpm_fast = fan_state.tach_counter * (60U * FAN_TICK_FREQ / 4U); // 4 interrupts per rotation
|
||||
fan_state.tach_counter = 0U;
|
||||
fan_state.rpm = (fan_rpm_fast + (3U * fan_state.rpm)) / 4U;
|
||||
|
||||
#ifdef DEBUG_FAN
|
||||
puth(fan_state.target_rpm);
|
||||
print(" "); puth(fan_rpm_fast);
|
||||
print(" "); puth(fan_state.power);
|
||||
print("\n");
|
||||
#endif
|
||||
|
||||
// Cooldown counter to prevent noise on tachometer line.
|
||||
if (fan_state.power > 0U) {
|
||||
fan_state.cooldown_counter = current_board->fan_enable_cooldown_time * FAN_TICK_FREQ;
|
||||
} else {
|
||||
if (fan_state.cooldown_counter > 0U) {
|
||||
fan_state.cooldown_counter--;
|
||||
}
|
||||
}
|
||||
|
||||
// Set PWM and enable line
|
||||
pwm_set(TIM3, 3, fan_state.power);
|
||||
current_board->set_fan_enabled((fan_state.power > 0U) || (fan_state.cooldown_counter > 0U));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
#include "board/drivers/drivers.h"
|
||||
|
||||
FDCAN_GlobalTypeDef *cans[PANDA_CAN_CNT] = {FDCAN1, FDCAN2, FDCAN3};
|
||||
|
||||
static bool can_set_speed(uint8_t can_number) {
|
||||
bool ret = true;
|
||||
FDCAN_GlobalTypeDef *FDCANx = CANIF_FROM_CAN_NUM(can_number);
|
||||
uint8_t bus_number = BUS_NUM_FROM_CAN_NUM(can_number);
|
||||
|
||||
ret &= llcan_set_speed(
|
||||
FDCANx,
|
||||
bus_config[bus_number].can_speed,
|
||||
bus_config[bus_number].can_data_speed,
|
||||
bus_config[bus_number].canfd_non_iso,
|
||||
can_loopback,
|
||||
can_silent
|
||||
);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void can_clear_send(FDCAN_GlobalTypeDef *FDCANx, uint8_t can_number) {
|
||||
static uint32_t last_reset = 0U;
|
||||
uint32_t time = microsecond_timer_get();
|
||||
|
||||
// Resetting CAN core is a slow blocking operation, limit frequency
|
||||
if (get_ts_elapsed(time, last_reset) > 100000U) { // 10 Hz
|
||||
can_health[can_number].can_core_reset_cnt += 1U;
|
||||
can_health[can_number].total_tx_lost_cnt += (FDCAN_TX_FIFO_EL_CNT - (FDCANx->TXFQS & FDCAN_TXFQS_TFFL)); // TX FIFO msgs will be lost after reset
|
||||
llcan_clear_send(FDCANx);
|
||||
last_reset = time;
|
||||
}
|
||||
}
|
||||
|
||||
void update_can_health_pkt(uint8_t can_number, uint32_t ir_reg) {
|
||||
uint8_t can_irq_number[PANDA_CAN_CNT][2] = {
|
||||
{ FDCAN1_IT0_IRQn, FDCAN1_IT1_IRQn },
|
||||
{ FDCAN2_IT0_IRQn, FDCAN2_IT1_IRQn },
|
||||
{ FDCAN3_IT0_IRQn, FDCAN3_IT1_IRQn },
|
||||
};
|
||||
|
||||
FDCAN_GlobalTypeDef *FDCANx = CANIF_FROM_CAN_NUM(can_number);
|
||||
uint32_t psr_reg = FDCANx->PSR;
|
||||
uint32_t ecr_reg = FDCANx->ECR;
|
||||
|
||||
can_health[can_number].bus_off = ((psr_reg & FDCAN_PSR_BO) >> FDCAN_PSR_BO_Pos);
|
||||
can_health[can_number].bus_off_cnt += can_health[can_number].bus_off;
|
||||
can_health[can_number].error_warning = ((psr_reg & FDCAN_PSR_EW) >> FDCAN_PSR_EW_Pos);
|
||||
can_health[can_number].error_passive = ((psr_reg & FDCAN_PSR_EP) >> FDCAN_PSR_EP_Pos);
|
||||
|
||||
can_health[can_number].last_error = ((psr_reg & FDCAN_PSR_LEC) >> FDCAN_PSR_LEC_Pos);
|
||||
if ((can_health[can_number].last_error != 0U) && (can_health[can_number].last_error != 7U)) {
|
||||
can_health[can_number].last_stored_error = can_health[can_number].last_error;
|
||||
}
|
||||
|
||||
can_health[can_number].last_data_error = ((psr_reg & FDCAN_PSR_DLEC) >> FDCAN_PSR_DLEC_Pos);
|
||||
if ((can_health[can_number].last_data_error != 0U) && (can_health[can_number].last_data_error != 7U)) {
|
||||
can_health[can_number].last_data_stored_error = can_health[can_number].last_data_error;
|
||||
}
|
||||
|
||||
can_health[can_number].receive_error_cnt = ((ecr_reg & FDCAN_ECR_REC) >> FDCAN_ECR_REC_Pos);
|
||||
can_health[can_number].transmit_error_cnt = ((ecr_reg & FDCAN_ECR_TEC) >> FDCAN_ECR_TEC_Pos);
|
||||
|
||||
can_health[can_number].irq0_call_rate = interrupts[can_irq_number[can_number][0]].call_rate;
|
||||
can_health[can_number].irq1_call_rate = interrupts[can_irq_number[can_number][1]].call_rate;
|
||||
|
||||
if (ir_reg != 0U) {
|
||||
// Clear error interrupts
|
||||
FDCANx->IR |= (FDCAN_IR_PED | FDCAN_IR_PEA | FDCAN_IR_EP | FDCAN_IR_BO | FDCAN_IR_RF0L);
|
||||
can_health[can_number].total_error_cnt += 1U;
|
||||
// Check for RX FIFO overflow
|
||||
if ((ir_reg & (FDCAN_IR_RF0L)) != 0U) {
|
||||
can_health[can_number].total_rx_lost_cnt += 1U;
|
||||
}
|
||||
// Cases:
|
||||
// 1. while multiplexing between buses 1 and 3 we are getting ACK errors that overwhelm CAN core, by resetting it recovers faster
|
||||
// 2. H7 gets stuck in bus off recovery state indefinitely
|
||||
if ((((can_health[can_number].last_error == CAN_ACK_ERROR) || (can_health[can_number].last_data_error == CAN_ACK_ERROR)) && (can_health[can_number].transmit_error_cnt > 127U)) ||
|
||||
((ir_reg & FDCAN_IR_BO) != 0U)) {
|
||||
can_clear_send(FDCANx, can_number);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ***************************** CAN *****************************
|
||||
// FDFDCANx_IT1 IRQ Handler (TX)
|
||||
void process_can(uint8_t can_number) {
|
||||
if (can_number != 0xffU) {
|
||||
ENTER_CRITICAL();
|
||||
|
||||
FDCAN_GlobalTypeDef *FDCANx = CANIF_FROM_CAN_NUM(can_number);
|
||||
uint8_t bus_number = BUS_NUM_FROM_CAN_NUM(can_number);
|
||||
|
||||
FDCANx->IR |= FDCAN_IR_TFE; // Clear Tx FIFO Empty flag
|
||||
|
||||
if ((FDCANx->TXFQS & FDCAN_TXFQS_TFQF) == 0U) {
|
||||
CANPacket_t to_send;
|
||||
if (can_pop(can_queues[bus_number], &to_send)) {
|
||||
if (can_check_checksum(&to_send)) {
|
||||
can_health[can_number].total_tx_cnt += 1U;
|
||||
|
||||
uint32_t TxFIFOSA = FDCAN_START_ADDRESS + (can_number * FDCAN_OFFSET) + (FDCAN_RX_FIFO_0_EL_CNT * FDCAN_RX_FIFO_0_EL_SIZE);
|
||||
// get the index of the next TX FIFO element (0 to FDCAN_TX_FIFO_EL_CNT - 1)
|
||||
uint32_t tx_index = (FDCANx->TXFQS >> FDCAN_TXFQS_TFQPI_Pos) & 0x1FU;
|
||||
// only send if we have received a packet
|
||||
canfd_fifo *fifo;
|
||||
fifo = (canfd_fifo *)(TxFIFOSA + (tx_index * FDCAN_TX_FIFO_EL_SIZE));
|
||||
|
||||
fifo->header[0] = (to_send.extended << 30) | ((to_send.extended != 0U) ? (to_send.addr) : (to_send.addr << 18));
|
||||
|
||||
// If canfd_auto is set, outgoing packets will be automatically sent as CAN-FD if an incoming CAN-FD packet was seen
|
||||
bool fd = bus_config[can_number].canfd_auto ? bus_config[can_number].canfd_enabled : (bool)(to_send.fd > 0U);
|
||||
uint32_t canfd_enabled_header = fd ? (1UL << 21) : 0UL;
|
||||
|
||||
uint32_t brs_enabled_header = bus_config[can_number].brs_enabled ? (1UL << 20) : 0UL;
|
||||
fifo->header[1] = (to_send.data_len_code << 16) | canfd_enabled_header | brs_enabled_header;
|
||||
|
||||
uint8_t data_len_w = (dlc_to_len[to_send.data_len_code] / 4U);
|
||||
data_len_w += ((dlc_to_len[to_send.data_len_code] % 4U) > 0U) ? 1U : 0U;
|
||||
for (unsigned int i = 0; i < data_len_w; i++) {
|
||||
BYTE_ARRAY_TO_WORD(fifo->data_word[i], &to_send.data[i*4U]);
|
||||
}
|
||||
|
||||
FDCANx->TXBAR = (1UL << tx_index);
|
||||
|
||||
// Send back to USB
|
||||
CANPacket_t to_push;
|
||||
|
||||
to_push.fd = fd;
|
||||
to_push.returned = 1U;
|
||||
to_push.rejected = 0U;
|
||||
to_push.extended = to_send.extended;
|
||||
to_push.addr = to_send.addr;
|
||||
to_push.bus = bus_number;
|
||||
to_push.data_len_code = to_send.data_len_code;
|
||||
(void)memcpy(to_push.data, to_send.data, dlc_to_len[to_push.data_len_code]);
|
||||
can_set_checksum(&to_push);
|
||||
|
||||
rx_buffer_overflow += can_push(&can_rx_q, &to_push) ? 0U : 1U;
|
||||
} else {
|
||||
can_health[can_number].total_tx_checksum_error_cnt += 1U;
|
||||
}
|
||||
|
||||
refresh_can_tx_slots_available();
|
||||
}
|
||||
}
|
||||
EXIT_CRITICAL();
|
||||
}
|
||||
}
|
||||
|
||||
// FDFDCANx_IT0 IRQ Handler (RX and errors)
|
||||
// blink blue when we are receiving CAN messages
|
||||
void can_rx(uint8_t can_number) {
|
||||
FDCAN_GlobalTypeDef *FDCANx = CANIF_FROM_CAN_NUM(can_number);
|
||||
uint8_t bus_number = BUS_NUM_FROM_CAN_NUM(can_number);
|
||||
|
||||
uint32_t ir_reg = FDCANx->IR;
|
||||
|
||||
// Clear all new messages from Rx FIFO 0
|
||||
FDCANx->IR |= FDCAN_IR_RF0N;
|
||||
while ((FDCANx->RXF0S & FDCAN_RXF0S_F0FL) != 0U) {
|
||||
can_health[can_number].total_rx_cnt += 1U;
|
||||
// get the index of the next RX FIFO element (0 to FDCAN_RX_FIFO_0_EL_CNT - 1)
|
||||
uint32_t rx_fifo_idx = (uint8_t)((FDCANx->RXF0S >> FDCAN_RXF0S_F0GI_Pos) & 0x3FU);
|
||||
|
||||
// Recommended to offset get index by at least +1 if RX FIFO is in overwrite mode and full (datasheet)
|
||||
if ((FDCANx->RXF0S & FDCAN_RXF0S_F0F) == FDCAN_RXF0S_F0F) {
|
||||
rx_fifo_idx = ((rx_fifo_idx + 1U) >= FDCAN_RX_FIFO_0_EL_CNT) ? 0U : (rx_fifo_idx + 1U);
|
||||
can_health[can_number].total_rx_lost_cnt += 1U; // At least one message was lost
|
||||
}
|
||||
|
||||
uint32_t RxFIFO0SA = FDCAN_START_ADDRESS + (can_number * FDCAN_OFFSET);
|
||||
CANPacket_t to_push;
|
||||
canfd_fifo *fifo;
|
||||
|
||||
// getting address
|
||||
fifo = (canfd_fifo *)(RxFIFO0SA + (rx_fifo_idx * FDCAN_RX_FIFO_0_EL_SIZE));
|
||||
|
||||
bool canfd_frame = ((fifo->header[1] >> 21) & 0x1U);
|
||||
bool brs_frame = ((fifo->header[1] >> 20) & 0x1U);
|
||||
|
||||
to_push.fd = canfd_frame;
|
||||
to_push.returned = 0U;
|
||||
to_push.rejected = 0U;
|
||||
to_push.extended = (fifo->header[0] >> 30) & 0x1U;
|
||||
to_push.addr = ((to_push.extended != 0U) ? (fifo->header[0] & 0x1FFFFFFFU) : ((fifo->header[0] >> 18) & 0x7FFU));
|
||||
to_push.bus = bus_number;
|
||||
to_push.data_len_code = ((fifo->header[1] >> 16) & 0xFU);
|
||||
|
||||
uint8_t data_len_w = (dlc_to_len[to_push.data_len_code] / 4U);
|
||||
data_len_w += ((dlc_to_len[to_push.data_len_code] % 4U) > 0U) ? 1U : 0U;
|
||||
for (unsigned int i = 0; i < data_len_w; i++) {
|
||||
WORD_TO_BYTE_ARRAY(&to_push.data[i*4U], fifo->data_word[i]);
|
||||
}
|
||||
can_set_checksum(&to_push);
|
||||
|
||||
// forwarding (panda only)
|
||||
int bus_fwd_num = safety_fwd_hook(bus_number, to_push.addr);
|
||||
if (bus_fwd_num < 0) {
|
||||
bus_fwd_num = bus_config[can_number].forwarding_bus;
|
||||
}
|
||||
if (bus_fwd_num != -1) {
|
||||
CANPacket_t to_send;
|
||||
|
||||
to_send.fd = to_push.fd;
|
||||
to_send.returned = 0U;
|
||||
to_send.rejected = 0U;
|
||||
to_send.extended = to_push.extended;
|
||||
to_send.addr = to_push.addr;
|
||||
to_send.bus = to_push.bus;
|
||||
to_send.data_len_code = to_push.data_len_code;
|
||||
(void)memcpy(to_send.data, to_push.data, dlc_to_len[to_push.data_len_code]);
|
||||
can_set_checksum(&to_send);
|
||||
|
||||
can_send(&to_send, bus_fwd_num, true);
|
||||
can_health[can_number].total_fwd_cnt += 1U;
|
||||
}
|
||||
|
||||
#ifdef PANDA_BODY
|
||||
body_can_rx(&to_push);
|
||||
#endif
|
||||
|
||||
safety_rx_invalid += safety_rx_hook(&to_push) ? 0U : 1U;
|
||||
ignition_can_hook(&to_push);
|
||||
|
||||
led_set(LED_BLUE, true);
|
||||
rx_buffer_overflow += can_push(&can_rx_q, &to_push) ? 0U : 1U;
|
||||
|
||||
// Enable CAN FD and BRS if CAN FD message was received
|
||||
if (!(bus_config[can_number].canfd_enabled) && (canfd_frame)) {
|
||||
bus_config[can_number].canfd_enabled = true;
|
||||
}
|
||||
if (!(bus_config[can_number].brs_enabled) && (brs_frame)) {
|
||||
bus_config[can_number].brs_enabled = true;
|
||||
}
|
||||
|
||||
// update read index
|
||||
FDCANx->RXF0A = rx_fifo_idx;
|
||||
}
|
||||
|
||||
// Error handling
|
||||
if ((ir_reg & (FDCAN_IR_PED | FDCAN_IR_PEA | FDCAN_IR_EP | FDCAN_IR_BO | FDCAN_IR_RF0L)) != 0U) {
|
||||
update_can_health_pkt(can_number, ir_reg);
|
||||
}
|
||||
}
|
||||
|
||||
static void FDCAN1_IT0_IRQ_Handler(void) { can_rx(0); }
|
||||
static void FDCAN1_IT1_IRQ_Handler(void) { process_can(0); }
|
||||
|
||||
static void FDCAN2_IT0_IRQ_Handler(void) { can_rx(1); }
|
||||
static void FDCAN2_IT1_IRQ_Handler(void) { process_can(1); }
|
||||
|
||||
static void FDCAN3_IT0_IRQ_Handler(void) { can_rx(2); }
|
||||
static void FDCAN3_IT1_IRQ_Handler(void) { process_can(2); }
|
||||
|
||||
bool can_init(uint8_t can_number) {
|
||||
bool ret = false;
|
||||
|
||||
REGISTER_INTERRUPT(FDCAN1_IT0_IRQn, FDCAN1_IT0_IRQ_Handler, CAN_INTERRUPT_RATE, FAULT_INTERRUPT_RATE_CAN_1)
|
||||
REGISTER_INTERRUPT(FDCAN1_IT1_IRQn, FDCAN1_IT1_IRQ_Handler, CAN_INTERRUPT_RATE, FAULT_INTERRUPT_RATE_CAN_1)
|
||||
REGISTER_INTERRUPT(FDCAN2_IT0_IRQn, FDCAN2_IT0_IRQ_Handler, CAN_INTERRUPT_RATE, FAULT_INTERRUPT_RATE_CAN_2)
|
||||
REGISTER_INTERRUPT(FDCAN2_IT1_IRQn, FDCAN2_IT1_IRQ_Handler, CAN_INTERRUPT_RATE, FAULT_INTERRUPT_RATE_CAN_2)
|
||||
REGISTER_INTERRUPT(FDCAN3_IT0_IRQn, FDCAN3_IT0_IRQ_Handler, CAN_INTERRUPT_RATE, FAULT_INTERRUPT_RATE_CAN_3)
|
||||
REGISTER_INTERRUPT(FDCAN3_IT1_IRQn, FDCAN3_IT1_IRQ_Handler, CAN_INTERRUPT_RATE, FAULT_INTERRUPT_RATE_CAN_3)
|
||||
|
||||
if (can_number != 0xffU) {
|
||||
FDCAN_GlobalTypeDef *FDCANx = CANIF_FROM_CAN_NUM(can_number);
|
||||
ret &= can_set_speed(can_number);
|
||||
ret &= llcan_init(FDCANx);
|
||||
// in case there are queued up messages
|
||||
process_can(can_number);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
#define MODE_INPUT 0
|
||||
#define MODE_OUTPUT 1
|
||||
#define MODE_ALTERNATE 2
|
||||
#define MODE_ANALOG 3
|
||||
|
||||
#define PULL_NONE 0
|
||||
#define PULL_UP 1
|
||||
#define PULL_DOWN 2
|
||||
|
||||
#define OUTPUT_TYPE_PUSH_PULL 0U
|
||||
#define OUTPUT_TYPE_OPEN_DRAIN 1U
|
||||
|
||||
void set_gpio_mode(GPIO_TypeDef *GPIO, unsigned int pin, unsigned int mode) {
|
||||
ENTER_CRITICAL();
|
||||
uint32_t tmp = GPIO->MODER;
|
||||
tmp &= ~(3U << (pin * 2U));
|
||||
tmp |= (mode << (pin * 2U));
|
||||
register_set(&(GPIO->MODER), tmp, 0xFFFFFFFFU);
|
||||
EXIT_CRITICAL();
|
||||
}
|
||||
|
||||
void set_gpio_output(GPIO_TypeDef *GPIO, unsigned int pin, bool enabled) {
|
||||
ENTER_CRITICAL();
|
||||
if (enabled) {
|
||||
register_set_bits(&(GPIO->ODR), (1UL << pin));
|
||||
} else {
|
||||
register_clear_bits(&(GPIO->ODR), (1UL << pin));
|
||||
}
|
||||
set_gpio_mode(GPIO, pin, MODE_OUTPUT);
|
||||
EXIT_CRITICAL();
|
||||
}
|
||||
|
||||
void set_gpio_output_type(GPIO_TypeDef *GPIO, unsigned int pin, unsigned int output_type){
|
||||
ENTER_CRITICAL();
|
||||
if(output_type == OUTPUT_TYPE_OPEN_DRAIN) {
|
||||
register_set_bits(&(GPIO->OTYPER), (1UL << pin));
|
||||
} else {
|
||||
register_clear_bits(&(GPIO->OTYPER), (1U << pin));
|
||||
}
|
||||
EXIT_CRITICAL();
|
||||
}
|
||||
|
||||
void set_gpio_alternate(GPIO_TypeDef *GPIO, unsigned int pin, unsigned int mode) {
|
||||
ENTER_CRITICAL();
|
||||
uint32_t tmp = GPIO->AFR[pin >> 3U];
|
||||
tmp &= ~(0xFU << ((pin & 7U) * 4U));
|
||||
tmp |= mode << ((pin & 7U) * 4U);
|
||||
register_set(&(GPIO->AFR[pin >> 3]), tmp, 0xFFFFFFFFU);
|
||||
set_gpio_mode(GPIO, pin, MODE_ALTERNATE);
|
||||
EXIT_CRITICAL();
|
||||
}
|
||||
|
||||
void set_gpio_pullup(GPIO_TypeDef *GPIO, unsigned int pin, unsigned int mode) {
|
||||
ENTER_CRITICAL();
|
||||
uint32_t tmp = GPIO->PUPDR;
|
||||
tmp &= ~(3U << (pin * 2U));
|
||||
tmp |= (mode << (pin * 2U));
|
||||
register_set(&(GPIO->PUPDR), tmp, 0xFFFFFFFFU);
|
||||
EXIT_CRITICAL();
|
||||
}
|
||||
|
||||
int get_gpio_input(const GPIO_TypeDef *GPIO, unsigned int pin) {
|
||||
return (GPIO->IDR & (1UL << pin)) == (1UL << pin);
|
||||
}
|
||||
|
||||
#ifdef PANDA_JUNGLE
|
||||
typedef struct {
|
||||
GPIO_TypeDef * const bank;
|
||||
uint8_t pin;
|
||||
} gpio_t;
|
||||
|
||||
void gpio_set_all_output(gpio_t *pins, uint8_t num_pins, bool enabled) {
|
||||
for (uint8_t i = 0; i < num_pins; i++) {
|
||||
set_gpio_output(pins[i].bank, pins[i].pin, enabled);
|
||||
}
|
||||
}
|
||||
|
||||
void gpio_set_bitmask(gpio_t *pins, uint8_t num_pins, uint32_t bitmask) {
|
||||
for (uint8_t i = 0; i < num_pins; i++) {
|
||||
set_gpio_output(pins[i].bank, pins[i].pin, (bitmask >> i) & 1U);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Detection with internal pullup
|
||||
#define PULL_EFFECTIVE_DELAY 4096
|
||||
bool detect_with_pull(GPIO_TypeDef *GPIO, int pin, int mode) {
|
||||
set_gpio_mode(GPIO, pin, MODE_INPUT);
|
||||
set_gpio_pullup(GPIO, pin, mode);
|
||||
for (volatile int i=0; i<PULL_EFFECTIVE_DELAY; i++);
|
||||
bool ret = get_gpio_input(GPIO, pin);
|
||||
set_gpio_pullup(GPIO, pin, PULL_NONE);
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
#include "board/drivers/drivers.h"
|
||||
|
||||
struct harness_t harness;
|
||||
|
||||
// The ignition relay is only used for testing purposes
|
||||
void set_intercept_relay(bool intercept, bool ignition_relay) {
|
||||
bool drive_relay = intercept;
|
||||
if (harness.status == HARNESS_STATUS_NC) {
|
||||
// no harness, no relay to drive
|
||||
drive_relay = false;
|
||||
}
|
||||
|
||||
if (drive_relay || ignition_relay) {
|
||||
harness.relay_driven = true;
|
||||
}
|
||||
|
||||
// wait until we're not reading the analog voltages anymore
|
||||
while (harness.sbu_adc_lock) {}
|
||||
|
||||
if (harness.status == HARNESS_STATUS_NORMAL) {
|
||||
set_gpio_output(current_board->harness_config->GPIO_relay_SBU1, current_board->harness_config->pin_relay_SBU1, !ignition_relay);
|
||||
set_gpio_output(current_board->harness_config->GPIO_relay_SBU2, current_board->harness_config->pin_relay_SBU2, !drive_relay);
|
||||
} else {
|
||||
set_gpio_output(current_board->harness_config->GPIO_relay_SBU1, current_board->harness_config->pin_relay_SBU1, !drive_relay);
|
||||
set_gpio_output(current_board->harness_config->GPIO_relay_SBU2, current_board->harness_config->pin_relay_SBU2, !ignition_relay);
|
||||
}
|
||||
|
||||
if (!(drive_relay || ignition_relay)) {
|
||||
harness.relay_driven = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool harness_check_ignition(void) {
|
||||
bool ret = false;
|
||||
|
||||
// wait until we're not reading the analog voltages anymore
|
||||
while (harness.sbu_adc_lock) {}
|
||||
|
||||
switch(harness.status){
|
||||
case HARNESS_STATUS_NORMAL:
|
||||
ret = !get_gpio_input(current_board->harness_config->GPIO_SBU1, current_board->harness_config->pin_SBU1);
|
||||
break;
|
||||
case HARNESS_STATUS_FLIPPED:
|
||||
ret = !get_gpio_input(current_board->harness_config->GPIO_SBU2, current_board->harness_config->pin_SBU2);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static uint8_t harness_detect_orientation(void) {
|
||||
uint8_t ret = harness.status;
|
||||
|
||||
#ifndef BOOTSTUB
|
||||
// We can't detect orientation if the relay is being driven
|
||||
if (!harness.relay_driven) {
|
||||
harness.sbu_adc_lock = true;
|
||||
set_gpio_mode(current_board->harness_config->GPIO_SBU1, current_board->harness_config->pin_SBU1, MODE_ANALOG);
|
||||
set_gpio_mode(current_board->harness_config->GPIO_SBU2, current_board->harness_config->pin_SBU2, MODE_ANALOG);
|
||||
|
||||
harness.sbu1_voltage_mV = adc_get_mV(¤t_board->harness_config->adc_signal_SBU1);
|
||||
harness.sbu2_voltage_mV = adc_get_mV(¤t_board->harness_config->adc_signal_SBU2);
|
||||
uint16_t detection_threshold = current_board->avdd_mV / 2U;
|
||||
|
||||
// Detect connection and orientation
|
||||
if((harness.sbu1_voltage_mV < detection_threshold) || (harness.sbu2_voltage_mV < detection_threshold)){
|
||||
if (harness.sbu1_voltage_mV < harness.sbu2_voltage_mV) {
|
||||
// orientation flipped (PANDA_SBU1->HARNESS_SBU1(relay), PANDA_SBU2->HARNESS_SBU2(ign))
|
||||
ret = HARNESS_STATUS_FLIPPED;
|
||||
} else {
|
||||
// orientation normal (PANDA_SBU2->HARNESS_SBU1(relay), PANDA_SBU1->HARNESS_SBU2(ign))
|
||||
// (SBU1->SBU2 is the normal orientation connection per USB-C cable spec)
|
||||
ret = HARNESS_STATUS_NORMAL;
|
||||
}
|
||||
} else {
|
||||
ret = HARNESS_STATUS_NC;
|
||||
}
|
||||
|
||||
// Pins are not 5V tolerant in ADC mode
|
||||
set_gpio_mode(current_board->harness_config->GPIO_SBU1, current_board->harness_config->pin_SBU1, MODE_INPUT);
|
||||
set_gpio_mode(current_board->harness_config->GPIO_SBU2, current_board->harness_config->pin_SBU2, MODE_INPUT);
|
||||
harness.sbu_adc_lock = false;
|
||||
}
|
||||
#endif
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void harness_tick(void) {
|
||||
harness.status = harness_detect_orientation();
|
||||
}
|
||||
|
||||
void harness_init(void) {
|
||||
// init OBD_SBUx_RELAY
|
||||
set_gpio_output_type(current_board->harness_config->GPIO_relay_SBU1, current_board->harness_config->pin_relay_SBU1, OUTPUT_TYPE_OPEN_DRAIN);
|
||||
set_gpio_output_type(current_board->harness_config->GPIO_relay_SBU2, current_board->harness_config->pin_relay_SBU2, OUTPUT_TYPE_OPEN_DRAIN);
|
||||
set_gpio_output(current_board->harness_config->GPIO_relay_SBU1, current_board->harness_config->pin_relay_SBU1, 1);
|
||||
set_gpio_output(current_board->harness_config->GPIO_relay_SBU2, current_board->harness_config->pin_relay_SBU2, 1);
|
||||
|
||||
// detect initial orientation
|
||||
harness.status = harness_detect_orientation();
|
||||
|
||||
// keep buses connected by default
|
||||
set_intercept_relay(false, false);
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
#include "board/drivers/drivers.h"
|
||||
|
||||
void unused_interrupt_handler(void) {
|
||||
// Something is wrong if this handler is called!
|
||||
print("Unused interrupt handler called!\n");
|
||||
fault_occurred(FAULT_UNUSED_INTERRUPT_HANDLED);
|
||||
}
|
||||
|
||||
interrupt interrupts[NUM_INTERRUPTS];
|
||||
|
||||
static bool check_interrupt_rate = false;
|
||||
|
||||
static uint32_t idle_time = 0U;
|
||||
static uint32_t busy_time = 0U;
|
||||
float interrupt_load = 0.0f;
|
||||
|
||||
void handle_interrupt(IRQn_Type irq_type){
|
||||
static uint8_t interrupt_depth = 0U;
|
||||
static uint32_t last_time = 0U;
|
||||
ENTER_CRITICAL();
|
||||
if (interrupt_depth == 0U) {
|
||||
uint32_t time = microsecond_timer_get();
|
||||
idle_time += get_ts_elapsed(time, last_time);
|
||||
last_time = time;
|
||||
}
|
||||
interrupt_depth += 1U;
|
||||
EXIT_CRITICAL();
|
||||
|
||||
interrupts[irq_type].call_counter++;
|
||||
interrupts[irq_type].handler();
|
||||
|
||||
// Check that the interrupts don't fire too often
|
||||
if (check_interrupt_rate && (interrupts[irq_type].call_counter > interrupts[irq_type].max_call_rate)) {
|
||||
fault_occurred(interrupts[irq_type].call_rate_fault);
|
||||
}
|
||||
|
||||
ENTER_CRITICAL();
|
||||
interrupt_depth -= 1U;
|
||||
if (interrupt_depth == 0U) {
|
||||
uint32_t time = microsecond_timer_get();
|
||||
busy_time += get_ts_elapsed(time, last_time);
|
||||
last_time = time;
|
||||
}
|
||||
EXIT_CRITICAL();
|
||||
}
|
||||
|
||||
// Every second
|
||||
void interrupt_timer_handler(void) {
|
||||
if (INTERRUPT_TIMER->SR != 0U) {
|
||||
for (uint16_t i = 0U; i < NUM_INTERRUPTS; i++) {
|
||||
// Log IRQ call rate faults
|
||||
if (check_interrupt_rate && (interrupts[i].call_counter > interrupts[i].max_call_rate)) {
|
||||
print("Interrupt 0x"); puth(i); print(" fired too often (0x"); puth(interrupts[i].call_counter); print("/s)!\n");
|
||||
}
|
||||
|
||||
// Reset interrupt counters
|
||||
interrupts[i].call_rate = interrupts[i].call_counter;
|
||||
interrupts[i].call_counter = 0U;
|
||||
}
|
||||
|
||||
// Calculate interrupt load
|
||||
// The bootstub does not have the FPU enabled, so can't do float operations.
|
||||
#if !defined(BOOTSTUB)
|
||||
interrupt_load = ((busy_time + idle_time) > 0U) ? ((float) (((float) busy_time) / (busy_time + idle_time))) : 0.0f;
|
||||
#endif
|
||||
idle_time = 0U;
|
||||
busy_time = 0U;
|
||||
}
|
||||
INTERRUPT_TIMER->SR = 0;
|
||||
}
|
||||
|
||||
void init_interrupts(bool check_rate_limit){
|
||||
check_interrupt_rate = check_rate_limit;
|
||||
|
||||
for(uint16_t i=0U; i<NUM_INTERRUPTS; i++){
|
||||
interrupts[i].handler = unused_interrupt_handler;
|
||||
}
|
||||
|
||||
// Init interrupt timer for a 1s interval
|
||||
interrupt_timer_init();
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
|
||||
#define LED_RED 0U
|
||||
#define LED_GREEN 1U
|
||||
#define LED_BLUE 2U
|
||||
|
||||
#define LED_PWM_POWER 2U
|
||||
|
||||
void led_set(uint8_t color, bool enabled) {
|
||||
if (color < 3U) {
|
||||
if (current_board->led_pwm_channels[color] != 0U) {
|
||||
pwm_set(TIM3, current_board->led_pwm_channels[color], 100U - (enabled ? LED_PWM_POWER : 0U));
|
||||
} else {
|
||||
set_gpio_output(current_board->led_GPIO[color], current_board->led_pin[color], !enabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void led_init(void) {
|
||||
for (uint8_t i = 0U; i<3U; i++){
|
||||
set_gpio_pullup(current_board->led_GPIO[i], current_board->led_pin[i], PULL_NONE);
|
||||
set_gpio_output_type(current_board->led_GPIO[i], current_board->led_pin[i], OUTPUT_TYPE_OPEN_DRAIN);
|
||||
|
||||
if (current_board->led_pwm_channels[i] != 0U) {
|
||||
set_gpio_alternate(current_board->led_GPIO[i], current_board->led_pin[i], GPIO_AF2_TIM3);
|
||||
pwm_init(TIM3, current_board->led_pwm_channels[i]);
|
||||
} else {
|
||||
set_gpio_mode(current_board->led_GPIO[i], current_board->led_pin[i], MODE_OUTPUT);
|
||||
}
|
||||
|
||||
led_set(i, false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
#define PWM_COUNTER_OVERFLOW 4800U // To get ~25kHz
|
||||
|
||||
// TODO: Implement for 32-bit timers
|
||||
|
||||
void pwm_init(TIM_TypeDef *TIM, uint8_t channel){
|
||||
// Enable timer and auto-reload
|
||||
register_set(&(TIM->CR1), TIM_CR1_CEN | TIM_CR1_ARPE, 0x3FU);
|
||||
|
||||
// Set channel as PWM mode 1 and enable output
|
||||
switch(channel){
|
||||
case 1U:
|
||||
register_set_bits(&(TIM->CCMR1), (TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1PE));
|
||||
register_set_bits(&(TIM->CCER), TIM_CCER_CC1E);
|
||||
break;
|
||||
case 2U:
|
||||
register_set_bits(&(TIM->CCMR1), (TIM_CCMR1_OC2M_2 | TIM_CCMR1_OC2M_1 | TIM_CCMR1_OC2PE));
|
||||
register_set_bits(&(TIM->CCER), TIM_CCER_CC2E);
|
||||
break;
|
||||
case 3U:
|
||||
register_set_bits(&(TIM->CCMR2), (TIM_CCMR2_OC3M_2 | TIM_CCMR2_OC3M_1 | TIM_CCMR2_OC3PE));
|
||||
register_set_bits(&(TIM->CCER), TIM_CCER_CC3E);
|
||||
break;
|
||||
case 4U:
|
||||
register_set_bits(&(TIM->CCMR2), (TIM_CCMR2_OC4M_2 | TIM_CCMR2_OC4M_1 | TIM_CCMR2_OC4PE));
|
||||
register_set_bits(&(TIM->CCER), TIM_CCER_CC4E);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Set max counter value
|
||||
register_set(&(TIM->ARR), PWM_COUNTER_OVERFLOW, 0xFFFFU);
|
||||
|
||||
// Update registers and clear counter
|
||||
TIM->EGR |= TIM_EGR_UG;
|
||||
}
|
||||
|
||||
void pwm_set(TIM_TypeDef *TIM, uint8_t channel, uint8_t percentage){
|
||||
uint16_t comp_value = (((uint16_t) percentage * PWM_COUNTER_OVERFLOW) / 100U);
|
||||
switch(channel){
|
||||
case 1U:
|
||||
register_set(&(TIM->CCR1), comp_value, 0xFFFFU);
|
||||
break;
|
||||
case 2U:
|
||||
register_set(&(TIM->CCR2), comp_value, 0xFFFFU);
|
||||
break;
|
||||
case 3U:
|
||||
register_set(&(TIM->CCR3), comp_value, 0xFFFFU);
|
||||
break;
|
||||
case 4U:
|
||||
register_set(&(TIM->CCR4), comp_value, 0xFFFFU);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
#include "board/drivers/drivers.h"
|
||||
|
||||
typedef struct reg {
|
||||
volatile uint32_t *address;
|
||||
uint32_t value;
|
||||
uint32_t check_mask;
|
||||
bool logged_fault;
|
||||
} reg;
|
||||
|
||||
#define CHECK_COLLISION(hash, addr) (((uint32_t) register_map[hash].address != 0U) && (register_map[hash].address != (addr)))
|
||||
|
||||
static reg register_map[REGISTER_MAP_SIZE];
|
||||
|
||||
// Hash spread in first and second iterations seems to be reasonable.
|
||||
// See: tests/development/register_hashmap_spread.py
|
||||
// Also, check the collision warnings in the debug output, and minimize those.
|
||||
static uint16_t hash_addr(uint32_t input){
|
||||
return (((input >> 16U) ^ ((((input + 1U) & 0xFFFFU) * HASHING_PRIME) & 0xFFFFU)) & REGISTER_MAP_SIZE);
|
||||
}
|
||||
|
||||
// Do not put bits in the check mask that get changed by the hardware
|
||||
void register_set(volatile uint32_t *addr, uint32_t val, uint32_t mask){
|
||||
ENTER_CRITICAL()
|
||||
// Set bits in register that are also in the mask
|
||||
(*addr) = ((*addr) & (~mask)) | (val & mask);
|
||||
|
||||
// Add these values to the map
|
||||
uint16_t hash = hash_addr((uint32_t) addr);
|
||||
uint16_t tries = REGISTER_MAP_SIZE;
|
||||
while(CHECK_COLLISION(hash, addr) && (tries > 0U)) { hash = hash_addr((uint32_t) hash); tries--;}
|
||||
if (tries != 0U){
|
||||
register_map[hash].address = addr;
|
||||
register_map[hash].value = (register_map[hash].value & (~mask)) | (val & mask);
|
||||
register_map[hash].check_mask |= mask;
|
||||
} else {
|
||||
#ifdef DEBUG_FAULTS
|
||||
print("Hash collision: address 0x"); puth((uint32_t) addr); print("!\n");
|
||||
#endif
|
||||
}
|
||||
EXIT_CRITICAL()
|
||||
}
|
||||
|
||||
// Set individual bits. Also add them to the check_mask.
|
||||
// Do not use this to change bits that get reset by the hardware
|
||||
void register_set_bits(volatile uint32_t *addr, uint32_t val) {
|
||||
register_set(addr, val, val);
|
||||
}
|
||||
|
||||
// Clear individual bits. Also add them to the check_mask.
|
||||
// Do not use this to clear bits that get set by the hardware
|
||||
void register_clear_bits(volatile uint32_t *addr, uint32_t val) {
|
||||
register_set(addr, (~val), val);
|
||||
}
|
||||
|
||||
// To be called periodically
|
||||
void check_registers(void){
|
||||
for(uint16_t i=0U; i<REGISTER_MAP_SIZE; i++){
|
||||
if((uint32_t) register_map[i].address != 0U){
|
||||
ENTER_CRITICAL()
|
||||
if((*(register_map[i].address) & register_map[i].check_mask) != (register_map[i].value & register_map[i].check_mask)){
|
||||
if(!register_map[i].logged_fault){
|
||||
print("Register 0x"); puth((uint32_t) register_map[i].address); print(" divergent! Map: 0x"); puth(register_map[i].value); print(" Reg: 0x"); puth(*(register_map[i].address)); print("\n");
|
||||
register_map[i].logged_fault = true;
|
||||
}
|
||||
fault_occurred(FAULT_REGISTER_DIVERGENT);
|
||||
}
|
||||
EXIT_CRITICAL()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void init_registers(void) {
|
||||
for(uint16_t i=0U; i<REGISTER_MAP_SIZE; i++){
|
||||
register_map[i].address = (volatile uint32_t *) 0U;
|
||||
register_map[i].check_mask = 0U;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#include "board/drivers/drivers.h"
|
||||
|
||||
static simple_watchdog_state_t wd_state;
|
||||
|
||||
void simple_watchdog_kick(void) {
|
||||
uint32_t ts = microsecond_timer_get();
|
||||
|
||||
uint32_t et = get_ts_elapsed(ts, wd_state.last_ts);
|
||||
if (et > wd_state.threshold) {
|
||||
print("WD timeout 0x"); puth(et); print("\n");
|
||||
fault_occurred(wd_state.fault);
|
||||
}
|
||||
|
||||
wd_state.last_ts = ts;
|
||||
}
|
||||
|
||||
void simple_watchdog_init(uint32_t fault, uint32_t threshold) {
|
||||
wd_state.fault = fault;
|
||||
wd_state.threshold = threshold;
|
||||
wd_state.last_ts = microsecond_timer_get();
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
#pragma once
|
||||
|
||||
#include "board/drivers/drivers.h"
|
||||
|
||||
// H7 DMA2 located in D2 domain, so we need to use SRAM1/SRAM2
|
||||
#ifdef STM32H7
|
||||
__attribute__((section(".sram12"))) uint8_t spi_buf_rx[SPI_BUF_SIZE];
|
||||
__attribute__((section(".sram12"))) uint8_t spi_buf_tx[SPI_BUF_SIZE];
|
||||
#else
|
||||
uint8_t spi_buf_rx[SPI_BUF_SIZE];
|
||||
uint8_t spi_buf_tx[SPI_BUF_SIZE];
|
||||
#endif
|
||||
|
||||
#define SPI_CHECKSUM_START 0xABU
|
||||
#define SPI_SYNC_BYTE 0x5AU
|
||||
#define SPI_HACK 0x79U
|
||||
#define SPI_DACK 0x85U
|
||||
#define SPI_NACK 0x1FU
|
||||
|
||||
// SPI states
|
||||
enum {
|
||||
SPI_STATE_HEADER,
|
||||
SPI_STATE_HEADER_ACK,
|
||||
SPI_STATE_HEADER_NACK,
|
||||
SPI_STATE_DATA_RX,
|
||||
SPI_STATE_DATA_RX_ACK,
|
||||
SPI_STATE_DATA_TX
|
||||
};
|
||||
|
||||
uint16_t spi_error_count = 0;
|
||||
|
||||
#define SPI_HEADER_SIZE 7U
|
||||
|
||||
// low level SPI prototypes
|
||||
void llspi_init(void);
|
||||
void llspi_mosi_dma(uint8_t *addr, int len);
|
||||
void llspi_miso_dma(uint8_t *addr, int len);
|
||||
|
||||
static uint8_t spi_state = SPI_STATE_HEADER;
|
||||
static uint16_t spi_data_len_mosi;
|
||||
static bool spi_can_tx_ready = false;
|
||||
static const unsigned char version_text[] = "VERSION";
|
||||
|
||||
static uint16_t spi_version_packet(uint8_t *out) {
|
||||
// this protocol version request is a stable portion of
|
||||
// the panda's SPI protocol. its contents match that of the
|
||||
// panda USB descriptors and are sufficent to list/enumerate
|
||||
// a panda, determine panda type, and bootstub status.
|
||||
|
||||
// the response is:
|
||||
// VERSION + 2 byte data length + data + CRC8
|
||||
|
||||
// echo "VERSION"
|
||||
(void)memcpy(out, version_text, 7);
|
||||
|
||||
// write response
|
||||
uint16_t data_len = 0;
|
||||
uint16_t data_pos = 7U + 2U;
|
||||
|
||||
// write serial
|
||||
(void)memcpy(&out[data_pos], ((uint8_t *)UID_BASE), 12);
|
||||
data_len += 12U;
|
||||
|
||||
// HW type
|
||||
out[data_pos + data_len] = hw_type;
|
||||
data_len += 1U;
|
||||
|
||||
// bootstub
|
||||
out[data_pos + data_len] = USB_PID & 0xFFU;
|
||||
data_len += 1U;
|
||||
|
||||
// SPI protocol version
|
||||
out[data_pos + data_len] = 0x2;
|
||||
data_len += 1U;
|
||||
|
||||
// data length
|
||||
out[7] = data_len & 0xFFU;
|
||||
out[8] = (data_len >> 8) & 0xFFU;
|
||||
|
||||
// CRC8
|
||||
uint16_t resp_len = data_pos + data_len;
|
||||
out[resp_len] = crc_checksum(out, resp_len, 0xD5U);
|
||||
resp_len += 1U;
|
||||
|
||||
return resp_len;
|
||||
}
|
||||
|
||||
void spi_init(void) {
|
||||
// platform init
|
||||
llspi_init();
|
||||
|
||||
// Start the first packet!
|
||||
spi_state = SPI_STATE_HEADER;
|
||||
llspi_mosi_dma(spi_buf_rx, SPI_HEADER_SIZE);
|
||||
}
|
||||
|
||||
static bool validate_checksum(const uint8_t *data, uint16_t len) {
|
||||
// TODO: can speed this up by casting the bulk to uint32_t and xor-ing the bytes afterwards
|
||||
uint8_t checksum = SPI_CHECKSUM_START;
|
||||
for(uint16_t i = 0U; i < len; i++){
|
||||
checksum ^= data[i];
|
||||
}
|
||||
return checksum == 0U;
|
||||
}
|
||||
|
||||
void spi_rx_done(void) {
|
||||
uint16_t response_len = 0U;
|
||||
uint8_t next_rx_state = SPI_STATE_HEADER_NACK;
|
||||
bool checksum_valid = false;
|
||||
static uint8_t spi_endpoint;
|
||||
static uint16_t spi_data_len_miso;
|
||||
|
||||
// parse header
|
||||
spi_endpoint = spi_buf_rx[1];
|
||||
spi_data_len_mosi = (spi_buf_rx[3] << 8) | spi_buf_rx[2];
|
||||
spi_data_len_miso = (spi_buf_rx[5] << 8) | spi_buf_rx[4];
|
||||
|
||||
if (memcmp(spi_buf_rx, version_text, 7) == 0) {
|
||||
response_len = spi_version_packet(spi_buf_tx);
|
||||
next_rx_state = SPI_STATE_HEADER_NACK;;
|
||||
} else if (spi_state == SPI_STATE_HEADER) {
|
||||
checksum_valid = validate_checksum(spi_buf_rx, SPI_HEADER_SIZE);
|
||||
if ((spi_buf_rx[0] == SPI_SYNC_BYTE) && checksum_valid) {
|
||||
// response: ACK and start receiving data portion
|
||||
spi_buf_tx[0] = SPI_HACK;
|
||||
next_rx_state = SPI_STATE_HEADER_ACK;
|
||||
response_len = 1U;
|
||||
} else {
|
||||
// response: NACK and reset state machine
|
||||
#ifdef DEBUG_SPI
|
||||
print("- incorrect header sync or checksum "); hexdump(spi_buf_rx, SPI_HEADER_SIZE);
|
||||
#endif
|
||||
spi_buf_tx[0] = SPI_NACK;
|
||||
next_rx_state = SPI_STATE_HEADER_NACK;
|
||||
response_len = 1U;
|
||||
}
|
||||
} else if (spi_state == SPI_STATE_DATA_RX) {
|
||||
// We got everything! Based on the endpoint specified, call the appropriate handler
|
||||
bool response_ack = false;
|
||||
checksum_valid = validate_checksum(&(spi_buf_rx[SPI_HEADER_SIZE]), spi_data_len_mosi + 1U);
|
||||
if (checksum_valid) {
|
||||
if (spi_endpoint == 0U) {
|
||||
if (spi_data_len_mosi >= sizeof(ControlPacket_t)) {
|
||||
ControlPacket_t ctrl = {0};
|
||||
(void)memcpy((uint8_t*)&ctrl, &spi_buf_rx[SPI_HEADER_SIZE], sizeof(ControlPacket_t));
|
||||
response_len = comms_control_handler(&ctrl, &spi_buf_tx[3]);
|
||||
response_ack = true;
|
||||
} else {
|
||||
print("SPI: insufficient data for control handler\n");
|
||||
}
|
||||
} else if ((spi_endpoint == 1U) || (spi_endpoint == 0x81U)) {
|
||||
if (spi_data_len_mosi == 0U) {
|
||||
response_len = comms_can_read(&(spi_buf_tx[3]), spi_data_len_miso);
|
||||
response_ack = true;
|
||||
} else {
|
||||
print("SPI: did not expect data for can_read\n");
|
||||
}
|
||||
} else if (spi_endpoint == 2U) {
|
||||
comms_endpoint2_write(&spi_buf_rx[SPI_HEADER_SIZE], spi_data_len_mosi);
|
||||
response_ack = true;
|
||||
} else if (spi_endpoint == 3U) {
|
||||
if (spi_data_len_mosi > 0U) {
|
||||
if (spi_can_tx_ready) {
|
||||
spi_can_tx_ready = false;
|
||||
comms_can_write(&spi_buf_rx[SPI_HEADER_SIZE], spi_data_len_mosi);
|
||||
response_ack = true;
|
||||
} else {
|
||||
response_ack = false;
|
||||
print("SPI: CAN NACK\n");
|
||||
}
|
||||
} else {
|
||||
print("SPI: did expect data for can_write\n");
|
||||
}
|
||||
} else if (spi_endpoint == 0xABU) {
|
||||
// test endpoint: mimics panda -> device transfer
|
||||
response_len = spi_data_len_miso;
|
||||
response_ack = true;
|
||||
} else if (spi_endpoint == 0xACU) {
|
||||
// test endpoint: mimics device -> panda transfer (with NACK)
|
||||
response_ack = false;
|
||||
} else {
|
||||
print("SPI: unexpected endpoint"); puth(spi_endpoint); print("\n");
|
||||
}
|
||||
} else {
|
||||
// Checksum was incorrect
|
||||
response_ack = false;
|
||||
#ifdef DEBUG_SPI
|
||||
print("- incorrect data checksum ");
|
||||
puth4(spi_data_len_mosi);
|
||||
print("\n");
|
||||
hexdump(spi_buf_rx, SPI_HEADER_SIZE);
|
||||
hexdump(&(spi_buf_rx[SPI_HEADER_SIZE]), MIN(spi_data_len_mosi, 64));
|
||||
print("\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
if (!response_ack) {
|
||||
spi_buf_tx[0] = SPI_NACK;
|
||||
next_rx_state = SPI_STATE_HEADER_NACK;
|
||||
response_len = 1U;
|
||||
} else {
|
||||
// Setup response header
|
||||
spi_buf_tx[0] = SPI_DACK;
|
||||
spi_buf_tx[1] = response_len & 0xFFU;
|
||||
spi_buf_tx[2] = (response_len >> 8) & 0xFFU;
|
||||
|
||||
// Add checksum
|
||||
uint8_t checksum = SPI_CHECKSUM_START;
|
||||
for(uint16_t i = 0U; i < (response_len + 3U); i++) {
|
||||
checksum ^= spi_buf_tx[i];
|
||||
}
|
||||
spi_buf_tx[response_len + 3U] = checksum;
|
||||
response_len += 4U;
|
||||
|
||||
next_rx_state = SPI_STATE_DATA_TX;
|
||||
}
|
||||
} else {
|
||||
print("SPI: RX unexpected state: "); puth(spi_state); print("\n");
|
||||
}
|
||||
|
||||
// send out response
|
||||
if (response_len == 0U) {
|
||||
print("SPI: no response\n");
|
||||
spi_buf_tx[0] = SPI_NACK;
|
||||
spi_state = SPI_STATE_HEADER_NACK;
|
||||
response_len = 1U;
|
||||
}
|
||||
llspi_miso_dma(spi_buf_tx, response_len);
|
||||
|
||||
spi_state = next_rx_state;
|
||||
if (!checksum_valid) {
|
||||
spi_error_count += 1U;
|
||||
}
|
||||
}
|
||||
|
||||
void spi_tx_done(bool reset) {
|
||||
if ((spi_state == SPI_STATE_HEADER_NACK) || reset) {
|
||||
// Reset state
|
||||
spi_state = SPI_STATE_HEADER;
|
||||
llspi_mosi_dma(spi_buf_rx, SPI_HEADER_SIZE);
|
||||
} else if (spi_state == SPI_STATE_HEADER_ACK) {
|
||||
// ACK was sent, queue up the RX buf for the data + checksum
|
||||
spi_state = SPI_STATE_DATA_RX;
|
||||
llspi_mosi_dma(&spi_buf_rx[SPI_HEADER_SIZE], spi_data_len_mosi + 1U);
|
||||
} else if (spi_state == SPI_STATE_DATA_TX) {
|
||||
// Reset state
|
||||
spi_state = SPI_STATE_HEADER;
|
||||
llspi_mosi_dma(spi_buf_rx, SPI_HEADER_SIZE);
|
||||
} else {
|
||||
spi_state = SPI_STATE_HEADER;
|
||||
llspi_mosi_dma(spi_buf_rx, SPI_HEADER_SIZE);
|
||||
print("SPI: TX unexpected state: "); puth(spi_state); print("\n");
|
||||
}
|
||||
}
|
||||
|
||||
void can_tx_comms_resume_spi(void) {
|
||||
spi_can_tx_ready = true;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
static void timer_init(TIM_TypeDef *TIM, int psc) {
|
||||
register_set(&(TIM->PSC), (psc-1), 0xFFFFU);
|
||||
register_set(&(TIM->DIER), TIM_DIER_UIE, 0x5F5FU);
|
||||
register_set(&(TIM->CR1), TIM_CR1_CEN, 0x3FU);
|
||||
TIM->SR = 0;
|
||||
}
|
||||
|
||||
void microsecond_timer_init(void) {
|
||||
MICROSECOND_TIMER->PSC = (APB1_TIMER_FREQ - 1U);
|
||||
MICROSECOND_TIMER->CR1 = TIM_CR1_CEN;
|
||||
MICROSECOND_TIMER->EGR = TIM_EGR_UG;
|
||||
}
|
||||
|
||||
uint32_t microsecond_timer_get(void) {
|
||||
return MICROSECOND_TIMER->CNT;
|
||||
}
|
||||
|
||||
void interrupt_timer_init(void) {
|
||||
enable_interrupt_timer();
|
||||
REGISTER_INTERRUPT(INTERRUPT_TIMER_IRQ, interrupt_timer_handler, 2U, FAULT_INTERRUPT_RATE_INTERRUPTS)
|
||||
register_set(&(INTERRUPT_TIMER->PSC), ((uint16_t)(15.25*APB1_TIMER_FREQ)-1U), 0xFFFFU);
|
||||
register_set(&(INTERRUPT_TIMER->DIER), TIM_DIER_UIE, 0x5F5FU);
|
||||
register_set(&(INTERRUPT_TIMER->CR1), TIM_CR1_CEN, 0x3FU);
|
||||
INTERRUPT_TIMER->SR = 0;
|
||||
NVIC_EnableIRQ(INTERRUPT_TIMER_IRQ);
|
||||
}
|
||||
|
||||
void tick_timer_init(void) {
|
||||
timer_init(TICK_TIMER, (uint16_t)((15.25*APB2_TIMER_FREQ)/8U));
|
||||
NVIC_EnableIRQ(TICK_TIMER_IRQ);
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
#include "board/drivers/drivers.h"
|
||||
|
||||
// ***************************** Definitions *****************************
|
||||
|
||||
#define UART_BUFFER(x, size_rx, size_tx, uart_ptr, callback_ptr, overwrite_mode) \
|
||||
static uint8_t elems_rx_##x[size_rx]; \
|
||||
static uint8_t elems_tx_##x[size_tx]; \
|
||||
extern uart_ring uart_ring_##x; \
|
||||
uart_ring uart_ring_##x = { \
|
||||
.w_ptr_tx = 0, \
|
||||
.r_ptr_tx = 0, \
|
||||
.elems_tx = ((uint8_t *)&(elems_tx_##x)), \
|
||||
.tx_fifo_size = (size_tx), \
|
||||
.w_ptr_rx = 0, \
|
||||
.r_ptr_rx = 0, \
|
||||
.elems_rx = ((uint8_t *)&(elems_rx_##x)), \
|
||||
.rx_fifo_size = (size_rx), \
|
||||
.uart = (uart_ptr), \
|
||||
.callback = (callback_ptr), \
|
||||
.overwrite = (overwrite_mode) \
|
||||
};
|
||||
|
||||
// ******************************** UART buffers ********************************
|
||||
|
||||
// debug = USART2
|
||||
UART_BUFFER(debug, FIFO_SIZE_INT, FIFO_SIZE_INT, USART2, debug_ring_callback, true)
|
||||
|
||||
// SOM debug = UART7
|
||||
UART_BUFFER(som_debug, FIFO_SIZE_INT, FIFO_SIZE_INT, UART7, NULL, true)
|
||||
|
||||
uart_ring *get_ring_by_number(int a) {
|
||||
uart_ring *ring = NULL;
|
||||
switch(a) {
|
||||
case 0:
|
||||
ring = &uart_ring_debug;
|
||||
break;
|
||||
case 4:
|
||||
ring = &uart_ring_som_debug;
|
||||
break;
|
||||
default:
|
||||
ring = NULL;
|
||||
break;
|
||||
}
|
||||
return ring;
|
||||
}
|
||||
|
||||
// ************************* Low-level buffer functions *************************
|
||||
bool get_char(uart_ring *q, char *elem) {
|
||||
bool ret = false;
|
||||
|
||||
ENTER_CRITICAL();
|
||||
if (q->w_ptr_rx != q->r_ptr_rx) {
|
||||
if (elem != NULL) *elem = q->elems_rx[q->r_ptr_rx];
|
||||
q->r_ptr_rx = (q->r_ptr_rx + 1U) % q->rx_fifo_size;
|
||||
ret = true;
|
||||
}
|
||||
EXIT_CRITICAL();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool injectc(uart_ring *q, char elem) {
|
||||
int ret = false;
|
||||
uint16_t next_w_ptr;
|
||||
|
||||
ENTER_CRITICAL();
|
||||
next_w_ptr = (q->w_ptr_rx + 1U) % q->rx_fifo_size;
|
||||
|
||||
if ((next_w_ptr == q->r_ptr_rx) && q->overwrite) {
|
||||
// overwrite mode: drop oldest byte
|
||||
q->r_ptr_rx = (q->r_ptr_rx + 1U) % q->rx_fifo_size;
|
||||
}
|
||||
|
||||
if (next_w_ptr != q->r_ptr_rx) {
|
||||
q->elems_rx[q->w_ptr_rx] = elem;
|
||||
q->w_ptr_rx = next_w_ptr;
|
||||
ret = true;
|
||||
}
|
||||
EXIT_CRITICAL();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool put_char(uart_ring *q, char elem) {
|
||||
bool ret = false;
|
||||
uint16_t next_w_ptr;
|
||||
|
||||
ENTER_CRITICAL();
|
||||
next_w_ptr = (q->w_ptr_tx + 1U) % q->tx_fifo_size;
|
||||
|
||||
if ((next_w_ptr == q->r_ptr_tx) && q->overwrite) {
|
||||
// overwrite mode: drop oldest byte
|
||||
q->r_ptr_tx = (q->r_ptr_tx + 1U) % q->tx_fifo_size;
|
||||
}
|
||||
|
||||
if (next_w_ptr != q->r_ptr_tx) {
|
||||
q->elems_tx[q->w_ptr_tx] = elem;
|
||||
q->w_ptr_tx = next_w_ptr;
|
||||
ret = true;
|
||||
}
|
||||
EXIT_CRITICAL();
|
||||
|
||||
uart_tx_ring(q);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// ************************ High-level debug functions **********************
|
||||
void putch(const char a) {
|
||||
// misra-c2012-17.7: serial debug function, ok to ignore output
|
||||
(void)injectc(&uart_ring_debug, a);
|
||||
}
|
||||
|
||||
void print(const char *a) {
|
||||
for (const char *in = a; *in; in++) {
|
||||
if (*in == '\n') putch('\r');
|
||||
putch(*in);
|
||||
}
|
||||
}
|
||||
|
||||
void puthx(uint32_t i, uint8_t len) {
|
||||
const char c[] = "0123456789abcdef";
|
||||
for (int pos = ((int)len * 4) - 4; pos > -4; pos -= 4) {
|
||||
putch(c[(i >> (unsigned int)(pos)) & 0xFU]);
|
||||
}
|
||||
}
|
||||
|
||||
void puth(unsigned int i) {
|
||||
puthx(i, 8U);
|
||||
}
|
||||
|
||||
#if defined(DEBUG_SPI) || defined(BOOTSTUB) || defined(DEBUG)
|
||||
static void puth4(unsigned int i) {
|
||||
puthx(i, 4U);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(DEBUG_SPI) || defined(BOOTSTUB) || defined(DEBUG_USB) || defined(DEBUG_COMMS)
|
||||
static void hexdump(const void *a, int l) {
|
||||
if (a != NULL) {
|
||||
for (int i=0; i < l; i++) {
|
||||
if ((i != 0) && ((i & 0xf) == 0)) print("\n");
|
||||
puthx(((const unsigned char*)a)[i], 2U);
|
||||
print(" ");
|
||||
}
|
||||
}
|
||||
print("\n");
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,878 @@
|
||||
#include "board/drivers/drivers.h"
|
||||
|
||||
// IRQs: OTG_FS
|
||||
|
||||
typedef union {
|
||||
uint16_t w;
|
||||
struct BW {
|
||||
uint8_t msb;
|
||||
uint8_t lsb;
|
||||
}
|
||||
bw;
|
||||
} uint16_t_uint8_t;
|
||||
|
||||
typedef union _USB_Setup {
|
||||
uint32_t d8[2];
|
||||
struct _SetupPkt_Struc
|
||||
{
|
||||
uint8_t bmRequestType;
|
||||
uint8_t bRequest;
|
||||
uint16_t_uint8_t wValue;
|
||||
uint16_t_uint8_t wIndex;
|
||||
uint16_t_uint8_t wLength;
|
||||
} b;
|
||||
} USB_Setup_TypeDef;
|
||||
|
||||
// **** supporting defines ****
|
||||
#define USB_REQ_GET_STATUS 0x00
|
||||
#define USB_REQ_SET_ADDRESS 0x05
|
||||
#define USB_REQ_GET_DESCRIPTOR 0x06
|
||||
#define USB_REQ_SET_CONFIGURATION 0x09
|
||||
#define USB_REQ_SET_INTERFACE 0x0B
|
||||
|
||||
#define USB_DESC_TYPE_DEVICE 0x01
|
||||
#define USB_DESC_TYPE_CONFIGURATION 0x02
|
||||
#define USB_DESC_TYPE_STRING 0x03
|
||||
#define USB_DESC_TYPE_INTERFACE 0x04
|
||||
#define USB_DESC_TYPE_ENDPOINT 0x05
|
||||
#define USB_DESC_TYPE_DEVICE_QUALIFIER 0x06
|
||||
#define USB_DESC_TYPE_BINARY_OBJECT_STORE 0x0f
|
||||
|
||||
// offsets for configuration strings
|
||||
#define STRING_OFFSET_LANGID 0x00
|
||||
#define STRING_OFFSET_IMANUFACTURER 0x01
|
||||
#define STRING_OFFSET_IPRODUCT 0x02
|
||||
#define STRING_OFFSET_ISERIAL 0x03
|
||||
#define STRING_OFFSET_ICONFIGURATION 0x04
|
||||
|
||||
// WinUSB requests
|
||||
#define WINUSB_REQ_GET_COMPATID_DESCRIPTOR 0x04
|
||||
#define WINUSB_REQ_GET_EXT_PROPS_OS 0x05
|
||||
#define WINUSB_REQ_GET_DESCRIPTOR 0x07
|
||||
|
||||
#define STS_DATA_UPDT 2
|
||||
#define STS_SETUP_UPDT 6
|
||||
|
||||
// for the repeating interfaces
|
||||
#define DSCR_INTERFACE_LEN 9
|
||||
#define DSCR_ENDPOINT_LEN 7
|
||||
#define DSCR_CONFIG_LEN 9
|
||||
#define DSCR_DEVICE_LEN 18
|
||||
|
||||
// endpoint types
|
||||
#define ENDPOINT_TYPE_BULK 2
|
||||
#define ENDPOINT_TYPE_INT 3
|
||||
|
||||
// These are arbitrary values used in bRequest
|
||||
#define MS_VENDOR_CODE 0x20
|
||||
#define WEBUSB_VENDOR_CODE 0x30
|
||||
|
||||
// BOS constants
|
||||
#define BINARY_OBJECT_STORE_DESCRIPTOR_LENGTH 0x05
|
||||
#define BINARY_OBJECT_STORE_DESCRIPTOR 0x0F
|
||||
#define WINUSB_PLATFORM_DESCRIPTOR_LENGTH 0x9E
|
||||
|
||||
// Convert machine byte order to USB byte order
|
||||
#define TOUSBORDER(num)\
|
||||
((num) & 0xFFU), (((uint16_t)(num) >> 8) & 0xFFU)
|
||||
|
||||
// take in string length and return the first 2 bytes of a string descriptor
|
||||
#define STRING_DESCRIPTOR_HEADER(size)\
|
||||
(((((size) * 2) + 2) & 0xFF) | 0x0300)
|
||||
|
||||
#define ENDPOINT_RCV 0x80
|
||||
#define ENDPOINT_SND 0x00
|
||||
|
||||
static uint8_t response[USBPACKET_MAX_SIZE];
|
||||
|
||||
// current packet
|
||||
static USB_Setup_TypeDef setup;
|
||||
static uint8_t* ep0_txdata = NULL;
|
||||
static uint16_t ep0_txlen = 0;
|
||||
static bool outep3_processing = false;
|
||||
|
||||
// Store the current interface alt setting.
|
||||
static int current_int0_alt_setting = 0;
|
||||
|
||||
// packet read and write
|
||||
|
||||
static void *USB_ReadPacket(void *dest, uint16_t len) {
|
||||
uint32_t *dest_copy = (uint32_t *)dest;
|
||||
uint32_t count32b = ((uint32_t)len + 3U) / 4U;
|
||||
|
||||
for (uint32_t i = 0; i < count32b; i++) {
|
||||
*dest_copy = USBx_DFIFO(0U);
|
||||
dest_copy++;
|
||||
}
|
||||
return ((void *)dest_copy);
|
||||
}
|
||||
|
||||
static void USB_WritePacket(const void *src, uint16_t len, uint32_t ep) {
|
||||
#ifdef DEBUG_USB
|
||||
print("writing ");
|
||||
hexdump(src, len);
|
||||
#endif
|
||||
|
||||
uint32_t numpacket = ((uint32_t)len + (USBPACKET_MAX_SIZE - 1U)) / USBPACKET_MAX_SIZE;
|
||||
uint32_t count32b = 0;
|
||||
count32b = ((uint32_t)len + 3U) / 4U;
|
||||
|
||||
// TODO: revisit this
|
||||
USBx_INEP(ep)->DIEPTSIZ = ((numpacket << 19) & USB_OTG_DIEPTSIZ_PKTCNT) |
|
||||
(len & USB_OTG_DIEPTSIZ_XFRSIZ);
|
||||
USBx_INEP(ep)->DIEPCTL |= (USB_OTG_DIEPCTL_CNAK | USB_OTG_DIEPCTL_EPENA);
|
||||
|
||||
// load the FIFO
|
||||
if (src != NULL) {
|
||||
const uint32_t *src_copy = (const uint32_t *)src;
|
||||
for (uint32_t i = 0; i < count32b; i++) {
|
||||
USBx_DFIFO(ep) = *src_copy;
|
||||
src_copy++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IN EP 0 TX FIFO has a max size of 127 bytes (much smaller than the rest)
|
||||
// so use TX FIFO empty interrupt to send larger amounts of data
|
||||
static void USB_WritePacket_EP0(uint8_t *src, uint16_t len) {
|
||||
#ifdef DEBUG_USB
|
||||
print("writing ");
|
||||
hexdump(src, len);
|
||||
#endif
|
||||
|
||||
uint16_t wplen = MIN(len, 0x40);
|
||||
USB_WritePacket(src, wplen, 0);
|
||||
|
||||
if (wplen < len) {
|
||||
ep0_txdata = &src[wplen];
|
||||
ep0_txlen = len - wplen;
|
||||
USBx_DEVICE->DIEPEMPMSK |= 1;
|
||||
} else {
|
||||
USBx_OUTEP(0U)->DOEPCTL |= USB_OTG_DOEPCTL_CNAK;
|
||||
}
|
||||
}
|
||||
|
||||
static void usb_reset(void) {
|
||||
// unmask endpoint interrupts, so many sets
|
||||
USBx_DEVICE->DAINT = 0xFFFFFFFFU;
|
||||
USBx_DEVICE->DAINTMSK = 0xFFFFFFFFU;
|
||||
//USBx_DEVICE->DOEPMSK = (USB_OTG_DOEPMSK_STUPM | USB_OTG_DOEPMSK_XFRCM | USB_OTG_DOEPMSK_EPDM);
|
||||
//USBx_DEVICE->DIEPMSK = (USB_OTG_DIEPMSK_TOM | USB_OTG_DIEPMSK_XFRCM | USB_OTG_DIEPMSK_EPDM | USB_OTG_DIEPMSK_ITTXFEMSK);
|
||||
//USBx_DEVICE->DIEPMSK = (USB_OTG_DIEPMSK_TOM | USB_OTG_DIEPMSK_XFRCM | USB_OTG_DIEPMSK_EPDM);
|
||||
|
||||
// all interrupts for debugging
|
||||
USBx_DEVICE->DIEPMSK = 0xFFFFFFFFU;
|
||||
USBx_DEVICE->DOEPMSK = 0xFFFFFFFFU;
|
||||
|
||||
// clear interrupts
|
||||
USBx_INEP(0U)->DIEPINT = 0xFF;
|
||||
USBx_OUTEP(0U)->DOEPINT = 0xFF;
|
||||
|
||||
// unset the address
|
||||
USBx_DEVICE->DCFG &= ~USB_OTG_DCFG_DAD;
|
||||
|
||||
// set up USB FIFOs
|
||||
// RX start address is fixed to 0
|
||||
USBx->GRXFSIZ = 0x40;
|
||||
|
||||
// 0x100 to offset past GRXFSIZ
|
||||
USBx->DIEPTXF0_HNPTXFSIZ = (0x40UL << 16) | 0x40U;
|
||||
|
||||
// EP1, massive
|
||||
USBx->DIEPTXF[0] = (0x40UL << 16) | 0x80U;
|
||||
|
||||
// flush TX fifo
|
||||
USBx->GRSTCTL = USB_OTG_GRSTCTL_TXFFLSH | USB_OTG_GRSTCTL_TXFNUM_4;
|
||||
while ((USBx->GRSTCTL & USB_OTG_GRSTCTL_TXFFLSH) == USB_OTG_GRSTCTL_TXFFLSH);
|
||||
// flush RX FIFO
|
||||
USBx->GRSTCTL = USB_OTG_GRSTCTL_RXFFLSH;
|
||||
while ((USBx->GRSTCTL & USB_OTG_GRSTCTL_RXFFLSH) == USB_OTG_GRSTCTL_RXFFLSH);
|
||||
|
||||
// no global NAK
|
||||
USBx_DEVICE->DCTL |= USB_OTG_DCTL_CGINAK;
|
||||
|
||||
// ready to receive setup packets
|
||||
USBx_OUTEP(0U)->DOEPTSIZ = USB_OTG_DOEPTSIZ_STUPCNT | (USB_OTG_DOEPTSIZ_PKTCNT & (1UL << 19)) | (3U << 3);
|
||||
}
|
||||
|
||||
static char to_hex_char(uint8_t a) {
|
||||
char ret;
|
||||
if (a < 10U) {
|
||||
ret = '0' + a;
|
||||
} else {
|
||||
ret = 'a' + (a - 10U);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void usb_setup(void) {
|
||||
static uint8_t device_desc[] = {
|
||||
DSCR_DEVICE_LEN, USB_DESC_TYPE_DEVICE, //Length, Type
|
||||
0x10, 0x02, // bcdUSB max version of USB supported (2.1)
|
||||
0xFF, 0xFF, 0xFF, 0x40, // Class, Subclass, Protocol, Max Packet Size
|
||||
TOUSBORDER(USB_VID), // idVendor
|
||||
TOUSBORDER(USB_PID), // idProduct
|
||||
0x00, 0x00, // bcdDevice
|
||||
0x01, 0x02, // Manufacturer, Product
|
||||
0x03, 0x01 // Serial Number, Num Configurations
|
||||
};
|
||||
|
||||
static uint8_t device_qualifier[] = {
|
||||
0x0a, USB_DESC_TYPE_DEVICE_QUALIFIER, //Length, Type
|
||||
0x10, 0x02, // bcdUSB max version of USB supported (2.1)
|
||||
0xFF, 0xFF, 0xFF, 0x40, // bDeviceClass, bDeviceSubClass, bDeviceProtocol, bMaxPacketSize0
|
||||
0x01, 0x00 // bNumConfigurations, bReserved
|
||||
};
|
||||
|
||||
static uint8_t configuration_desc[] = {
|
||||
DSCR_CONFIG_LEN, USB_DESC_TYPE_CONFIGURATION, // Length, Type,
|
||||
TOUSBORDER(0x0045U), // Total Len (uint16)
|
||||
0x01, 0x01, STRING_OFFSET_ICONFIGURATION, // Num Interface, Config Value, Configuration
|
||||
0xc0, 0x32, // Attributes, Max Power
|
||||
// interface 0 ALT 0
|
||||
DSCR_INTERFACE_LEN, USB_DESC_TYPE_INTERFACE, // Length, Type
|
||||
0x00, 0x00, 0x03, // Index, Alt Index idx, Endpoint count
|
||||
0XFF, 0xFF, 0xFF, // Class, Subclass, Protocol
|
||||
0x00, // Interface
|
||||
// endpoint 1, read CAN
|
||||
DSCR_ENDPOINT_LEN, USB_DESC_TYPE_ENDPOINT, // Length, Type
|
||||
ENDPOINT_RCV | 1, ENDPOINT_TYPE_BULK, // Endpoint Num/Direction, Type
|
||||
TOUSBORDER(0x0040U), // Max Packet (0x0040)
|
||||
0x00, // Polling Interval (NA)
|
||||
// endpoint 2, send serial
|
||||
DSCR_ENDPOINT_LEN, USB_DESC_TYPE_ENDPOINT, // Length, Type
|
||||
ENDPOINT_SND | 2, ENDPOINT_TYPE_BULK, // Endpoint Num/Direction, Type
|
||||
TOUSBORDER(0x0040U), // Max Packet (0x0040)
|
||||
0x00, // Polling Interval
|
||||
// endpoint 3, send CAN
|
||||
DSCR_ENDPOINT_LEN, USB_DESC_TYPE_ENDPOINT, // Length, Type
|
||||
ENDPOINT_SND | 3, ENDPOINT_TYPE_BULK, // Endpoint Num/Direction, Type
|
||||
TOUSBORDER(0x0040U), // Max Packet (0x0040)
|
||||
0x00, // Polling Interval
|
||||
// interface 0 ALT 1
|
||||
DSCR_INTERFACE_LEN, USB_DESC_TYPE_INTERFACE, // Length, Type
|
||||
0x00, 0x01, 0x03, // Index, Alt Index idx, Endpoint count
|
||||
0XFF, 0xFF, 0xFF, // Class, Subclass, Protocol
|
||||
0x00, // Interface
|
||||
// endpoint 1, read CAN
|
||||
DSCR_ENDPOINT_LEN, USB_DESC_TYPE_ENDPOINT, // Length, Type
|
||||
ENDPOINT_RCV | 1, ENDPOINT_TYPE_INT, // Endpoint Num/Direction, Type
|
||||
TOUSBORDER(0x0040U), // Max Packet (0x0040)
|
||||
0x05, // Polling Interval (5 frames)
|
||||
// endpoint 2, send serial
|
||||
DSCR_ENDPOINT_LEN, USB_DESC_TYPE_ENDPOINT, // Length, Type
|
||||
ENDPOINT_SND | 2, ENDPOINT_TYPE_BULK, // Endpoint Num/Direction, Type
|
||||
TOUSBORDER(0x0040U), // Max Packet (0x0040)
|
||||
0x00, // Polling Interval
|
||||
// endpoint 3, send CAN
|
||||
DSCR_ENDPOINT_LEN, USB_DESC_TYPE_ENDPOINT, // Length, Type
|
||||
ENDPOINT_SND | 3, ENDPOINT_TYPE_BULK, // Endpoint Num/Direction, Type
|
||||
TOUSBORDER(0x0040U), // Max Packet (0x0040)
|
||||
0x00, // Polling Interval
|
||||
};
|
||||
|
||||
// STRING_DESCRIPTOR_HEADER is for uint16 string descriptors
|
||||
// it takes in a string length, which is bytes/2 because unicode
|
||||
static uint16_t string_language_desc[] = {
|
||||
STRING_DESCRIPTOR_HEADER(1),
|
||||
0x0409 // american english
|
||||
};
|
||||
|
||||
// these strings are all uint16's so that we don't need to spam ,0 after every character
|
||||
static uint16_t string_manufacturer_desc[] = {
|
||||
STRING_DESCRIPTOR_HEADER(8),
|
||||
'c', 'o', 'm', 'm', 'a', '.', 'a', 'i'
|
||||
};
|
||||
|
||||
static uint16_t string_product_desc[] = {
|
||||
STRING_DESCRIPTOR_HEADER(5),
|
||||
'p', 'a', 'n', 'd', 'a'
|
||||
};
|
||||
|
||||
// a string containing the default configuration index
|
||||
static uint16_t string_configuration_desc[] = {
|
||||
STRING_DESCRIPTOR_HEADER(2),
|
||||
'0', '1' // "01"
|
||||
};
|
||||
|
||||
// WCID (auto install WinUSB driver)
|
||||
// https://github.com/pbatard/libwdi/wiki/WCID-Devices
|
||||
// https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/winusb-installation#automatic-installation-of--winusb-without-an-inf-file
|
||||
// WinUSB 1.0 descriptors, this is mostly used by Windows XP
|
||||
static uint8_t string_238_desc[] = {
|
||||
0x12, USB_DESC_TYPE_STRING, // bLength, bDescriptorType
|
||||
'M',0, 'S',0, 'F',0, 'T',0, '1',0, '0',0, '0',0, // qwSignature (MSFT100)
|
||||
MS_VENDOR_CODE, 0x00 // bMS_VendorCode, bPad
|
||||
};
|
||||
|
||||
static uint8_t winusb_ext_compatid_os_desc[] = {
|
||||
0x28, 0x00, 0x00, 0x00, // dwLength
|
||||
0x00, 0x01, // bcdVersion
|
||||
0x04, 0x00, // wIndex
|
||||
0x01, // bCount
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Reserved
|
||||
0x00, // bFirstInterfaceNumber
|
||||
0x00, // Reserved
|
||||
'W', 'I', 'N', 'U', 'S', 'B', 0x00, 0x00, // compatible ID (WINUSB)
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // subcompatible ID (none)
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // Reserved
|
||||
};
|
||||
|
||||
static uint8_t winusb_ext_prop_os_desc[] = {
|
||||
0x8e, 0x00, 0x00, 0x00, // dwLength
|
||||
0x00, 0x01, // bcdVersion
|
||||
0x05, 0x00, // wIndex
|
||||
0x01, 0x00, // wCount
|
||||
// first property
|
||||
0x84, 0x00, 0x00, 0x00, // dwSize
|
||||
0x01, 0x00, 0x00, 0x00, // dwPropertyDataType
|
||||
0x28, 0x00, // wPropertyNameLength
|
||||
'D',0, 'e',0, 'v',0, 'i',0, 'c',0, 'e',0, 'I',0, 'n',0, 't',0, 'e',0, 'r',0, 'f',0, 'a',0, 'c',0, 'e',0, 'G',0, 'U',0, 'I',0, 'D',0, 0, 0, // bPropertyName (DeviceInterfaceGUID)
|
||||
0x4e, 0x00, 0x00, 0x00, // dwPropertyDataLength
|
||||
'{',0, 'c',0, 'c',0, 'e',0, '5',0, '2',0, '9',0, '1',0, 'c',0, '-',0, 'a',0, '6',0, '9',0, 'f',0, '-',0, '4',0 ,'9',0 ,'9',0 ,'5',0 ,'-',0, 'a',0, '4',0, 'c',0, '2',0, '-',0, '2',0, 'a',0, 'e',0, '5',0, '7',0, 'a',0, '5',0, '1',0, 'a',0, 'd',0, 'e',0, '9',0, '}',0, 0, 0, // bPropertyData ({CCE5291C-A69F-4995-A4C2-2AE57A51ADE9})
|
||||
};
|
||||
|
||||
/*
|
||||
Binary Object Store descriptor used to expose WebUSB (and more WinUSB) metadata
|
||||
comments are from the wicg spec
|
||||
References used:
|
||||
https://wicg.github.io/webusb/#webusb-platform-capability-descriptor
|
||||
https://github.com/sowbug/weblight/blob/192ad7a0e903542e2aa28c607d98254a12a6399d/firmware/webusb.c
|
||||
https://os.mbed.com/users/larsgk/code/USBDevice_WebUSB/file/1d8a6665d607/WebUSBDevice/
|
||||
*/
|
||||
static uint8_t binary_object_store_desc[] = {
|
||||
// BOS header
|
||||
BINARY_OBJECT_STORE_DESCRIPTOR_LENGTH, // bLength, this is only the length of the header
|
||||
BINARY_OBJECT_STORE_DESCRIPTOR, // bDescriptorType
|
||||
0x39, 0x00, // wTotalLength (LSB, MSB)
|
||||
0x02, // bNumDeviceCaps (WebUSB + WinUSB)
|
||||
|
||||
// -------------------------------------------------
|
||||
// WebUSB descriptor
|
||||
// header
|
||||
0x18, // bLength, Size of this descriptor. Must be set to 24.
|
||||
0x10, // bDescriptorType, DEVICE CAPABILITY descriptor
|
||||
0x05, // bDevCapabilityType, PLATFORM capability
|
||||
0x00, // bReserved, This field is reserved and shall be set to zero.
|
||||
|
||||
// PlatformCapabilityUUID, Must be set to {3408b638-09a9-47a0-8bfd-a0768815b665}.
|
||||
0x38, 0xB6, 0x08, 0x34,
|
||||
0xA9, 0x09, 0xA0, 0x47,
|
||||
0x8B, 0xFD, 0xA0, 0x76,
|
||||
0x88, 0x15, 0xB6, 0x65,
|
||||
// </PlatformCapabilityUUID>
|
||||
|
||||
0x00, 0x01, // bcdVersion, Protocol version supported. Must be set to 0x0100.
|
||||
WEBUSB_VENDOR_CODE, // bVendorCode, bRequest value used for issuing WebUSB requests.
|
||||
// there used to be a concept of "allowed origins", but it was removed from the spec
|
||||
// it was intended to be a security feature, but then the entire security model relies on domain ownership
|
||||
// https://github.com/WICG/webusb/issues/49
|
||||
// other implementations use various other indexed to leverate this no-longer-valid feature. we wont.
|
||||
// the spec says we *must* reply to index 0x03 with the url, so we'll hint that that's the right index
|
||||
0x03, // iLandingPage, URL descriptor index of the device’s landing page.
|
||||
|
||||
// -------------------------------------------------
|
||||
// WinUSB descriptor
|
||||
// header
|
||||
0x1C, // Descriptor size (28 bytes)
|
||||
0x10, // Descriptor type (Device Capability)
|
||||
0x05, // Capability type (Platform)
|
||||
0x00, // Reserved
|
||||
|
||||
// MS OS 2.0 Platform Capability ID (D8DD60DF-4589-4CC7-9CD2-659D9E648A9F)
|
||||
// Indicates the device supports the Microsoft OS 2.0 descriptor
|
||||
0xDF, 0x60, 0xDD, 0xD8,
|
||||
0x89, 0x45, 0xC7, 0x4C,
|
||||
0x9C, 0xD2, 0x65, 0x9D,
|
||||
0x9E, 0x64, 0x8A, 0x9F,
|
||||
|
||||
0x00, 0x00, 0x03, 0x06, // Windows version, currently set to 8.1 (0x06030000)
|
||||
|
||||
WINUSB_PLATFORM_DESCRIPTOR_LENGTH, 0x00, // MS OS 2.0 descriptor size (word)
|
||||
MS_VENDOR_CODE, 0x00 // vendor code, no alternate enumeration
|
||||
};
|
||||
|
||||
// WinUSB 2.0 descriptor. This is what modern systems use
|
||||
// https://github.com/sowbug/weblight/blob/192ad7a0e903542e2aa28c607d98254a12a6399d/firmware/webusb.c
|
||||
// http://janaxelson.com/files/ms_os_20_descriptors.c
|
||||
// https://books.google.com/books?id=pkefBgAAQBAJ&pg=PA353&lpg=PA353
|
||||
static uint8_t winusb_20_desc[WINUSB_PLATFORM_DESCRIPTOR_LENGTH] = {
|
||||
// Microsoft OS 2.0 descriptor set header (table 10)
|
||||
0x0A, 0x00, // Descriptor size (10 bytes)
|
||||
0x00, 0x00, // MS OS 2.0 descriptor set header
|
||||
|
||||
0x00, 0x00, 0x03, 0x06, // Windows version (8.1) (0x06030000)
|
||||
WINUSB_PLATFORM_DESCRIPTOR_LENGTH, 0x00, // Total size of MS OS 2.0 descriptor set
|
||||
|
||||
// Microsoft OS 2.0 compatible ID descriptor
|
||||
0x14, 0x00, // Descriptor size (20 bytes)
|
||||
0x03, 0x00, // MS OS 2.0 compatible ID descriptor
|
||||
'W', 'I', 'N', 'U', 'S', 'B', 0x00, 0x00, // compatible ID (WINUSB)
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Sub-compatible ID
|
||||
|
||||
// Registry property descriptor
|
||||
0x80, 0x00, // Descriptor size (130 bytes)
|
||||
0x04, 0x00, // Registry Property descriptor
|
||||
0x01, 0x00, // Strings are null-terminated Unicode
|
||||
0x28, 0x00, // Size of Property Name (40 bytes) "DeviceInterfaceGUID"
|
||||
|
||||
// bPropertyName (DeviceInterfaceGUID)
|
||||
'D', 0x00, 'e', 0x00, 'v', 0x00, 'i', 0x00, 'c', 0x00, 'e', 0x00, 'I', 0x00, 'n', 0x00,
|
||||
't', 0x00, 'e', 0x00, 'r', 0x00, 'f', 0x00, 'a', 0x00, 'c', 0x00, 'e', 0x00, 'G', 0x00,
|
||||
'U', 0x00, 'I', 0x00, 'D', 0x00, 0x00, 0x00,
|
||||
|
||||
0x4E, 0x00, // Size of Property Data (78 bytes)
|
||||
|
||||
// Vendor-defined property data: {CCE5291C-A69F-4995-A4C2-2AE57A51ADE9}
|
||||
'{', 0x00, 'c', 0x00, 'c', 0x00, 'e', 0x00, '5', 0x00, '2', 0x00, '9', 0x00, '1', 0x00, // 16
|
||||
'c', 0x00, '-', 0x00, 'a', 0x00, '6', 0x00, '9', 0x00, 'f', 0x00, '-', 0x00, '4', 0x00, // 32
|
||||
'9', 0x00, '9', 0x00, '5', 0x00, '-', 0x00, 'a', 0x00, '4', 0x00, 'c', 0x00, '2', 0x00, // 48
|
||||
'-', 0x00, '2', 0x00, 'a', 0x00, 'e', 0x00, '5', 0x00, '7', 0x00, 'a', 0x00, '5', 0x00, // 64
|
||||
'1', 0x00, 'a', 0x00, 'd', 0x00, 'e', 0x00, '9', 0x00, '}', 0x00, 0x00, 0x00 // 78 bytes
|
||||
};
|
||||
|
||||
int resp_len;
|
||||
ControlPacket_t control_req;
|
||||
|
||||
// setup packet is ready
|
||||
switch (setup.b.bRequest) {
|
||||
case USB_REQ_SET_CONFIGURATION:
|
||||
// enable other endpoints, has to be here?
|
||||
USBx_INEP(1U)->DIEPCTL = (0x40U & USB_OTG_DIEPCTL_MPSIZ) | (2UL << 18) | (1UL << 22) |
|
||||
USB_OTG_DIEPCTL_SD0PID_SEVNFRM | USB_OTG_DIEPCTL_USBAEP;
|
||||
USBx_INEP(1U)->DIEPINT = 0xFF;
|
||||
|
||||
USBx_OUTEP(2U)->DOEPTSIZ = (1UL << 19) | 0x40U;
|
||||
USBx_OUTEP(2U)->DOEPCTL = (0x40U & USB_OTG_DOEPCTL_MPSIZ) | (2UL << 18) |
|
||||
USB_OTG_DOEPCTL_SD0PID_SEVNFRM | USB_OTG_DOEPCTL_USBAEP;
|
||||
USBx_OUTEP(2U)->DOEPINT = 0xFF;
|
||||
|
||||
USBx_OUTEP(3U)->DOEPTSIZ = (32UL << 19) | 0x800U;
|
||||
USBx_OUTEP(3U)->DOEPCTL = (0x40U & USB_OTG_DOEPCTL_MPSIZ) | (2UL << 18) |
|
||||
USB_OTG_DOEPCTL_SD0PID_SEVNFRM | USB_OTG_DOEPCTL_USBAEP;
|
||||
USBx_OUTEP(3U)->DOEPINT = 0xFF;
|
||||
|
||||
// mark ready to receive
|
||||
USBx_OUTEP(2U)->DOEPCTL |= USB_OTG_DOEPCTL_EPENA | USB_OTG_DOEPCTL_CNAK;
|
||||
USBx_OUTEP(3U)->DOEPCTL |= USB_OTG_DOEPCTL_EPENA | USB_OTG_DOEPCTL_CNAK;
|
||||
|
||||
USB_WritePacket(0, 0, 0);
|
||||
USBx_OUTEP(0U)->DOEPCTL |= USB_OTG_DOEPCTL_CNAK;
|
||||
break;
|
||||
case USB_REQ_SET_ADDRESS:
|
||||
// set now?
|
||||
USBx_DEVICE->DCFG |= ((setup.b.wValue.w & 0x7fU) << 4);
|
||||
|
||||
#ifdef DEBUG_USB
|
||||
print(" set address\n");
|
||||
#endif
|
||||
|
||||
USB_WritePacket(0, 0, 0);
|
||||
USBx_OUTEP(0U)->DOEPCTL |= USB_OTG_DOEPCTL_CNAK;
|
||||
|
||||
break;
|
||||
case USB_REQ_GET_DESCRIPTOR:
|
||||
switch (setup.b.wValue.bw.lsb) {
|
||||
case USB_DESC_TYPE_DEVICE:
|
||||
//print(" writing device descriptor\n");
|
||||
|
||||
// set bcdDevice to hardware type
|
||||
device_desc[13] = hw_type;
|
||||
// setup transfer
|
||||
USB_WritePacket(device_desc, MIN(sizeof(device_desc), setup.b.wLength.w), 0);
|
||||
USBx_OUTEP(0U)->DOEPCTL |= USB_OTG_DOEPCTL_CNAK;
|
||||
|
||||
//print("D");
|
||||
break;
|
||||
case USB_DESC_TYPE_CONFIGURATION:
|
||||
USB_WritePacket(configuration_desc, MIN(sizeof(configuration_desc), setup.b.wLength.w), 0);
|
||||
USBx_OUTEP(0U)->DOEPCTL |= USB_OTG_DOEPCTL_CNAK;
|
||||
break;
|
||||
case USB_DESC_TYPE_DEVICE_QUALIFIER:
|
||||
USB_WritePacket(device_qualifier, MIN(sizeof(device_qualifier), setup.b.wLength.w), 0);
|
||||
USBx_OUTEP(0U)->DOEPCTL |= USB_OTG_DOEPCTL_CNAK;
|
||||
break;
|
||||
case USB_DESC_TYPE_STRING:
|
||||
switch (setup.b.wValue.bw.msb) {
|
||||
case STRING_OFFSET_LANGID:
|
||||
USB_WritePacket((uint8_t*)string_language_desc, MIN(sizeof(string_language_desc), setup.b.wLength.w), 0);
|
||||
break;
|
||||
case STRING_OFFSET_IMANUFACTURER:
|
||||
USB_WritePacket((uint8_t*)string_manufacturer_desc, MIN(sizeof(string_manufacturer_desc), setup.b.wLength.w), 0);
|
||||
break;
|
||||
case STRING_OFFSET_IPRODUCT:
|
||||
USB_WritePacket((uint8_t*)string_product_desc, MIN(sizeof(string_product_desc), setup.b.wLength.w), 0);
|
||||
break;
|
||||
case STRING_OFFSET_ISERIAL:
|
||||
response[0] = 0x02 + (12 * 4);
|
||||
response[1] = 0x03;
|
||||
|
||||
// 96 bits = 12 bytes
|
||||
for (int i = 0; i < 12; i++){
|
||||
uint8_t cc = ((uint8_t *)UID_BASE)[i];
|
||||
response[2 + (i * 4)] = to_hex_char((cc >> 4) & 0xFU);
|
||||
response[2 + (i * 4) + 1] = '\0';
|
||||
response[2 + (i * 4) + 2] = to_hex_char((cc >> 0) & 0xFU);
|
||||
response[2 + (i * 4) + 3] = '\0';
|
||||
}
|
||||
|
||||
USB_WritePacket(response, MIN(response[0], setup.b.wLength.w), 0);
|
||||
break;
|
||||
case STRING_OFFSET_ICONFIGURATION:
|
||||
USB_WritePacket((uint8_t*)string_configuration_desc, MIN(sizeof(string_configuration_desc), setup.b.wLength.w), 0);
|
||||
break;
|
||||
case 238:
|
||||
USB_WritePacket((uint8_t*)string_238_desc, MIN(sizeof(string_238_desc), setup.b.wLength.w), 0);
|
||||
break;
|
||||
default:
|
||||
// nothing
|
||||
USB_WritePacket(0, 0, 0);
|
||||
break;
|
||||
}
|
||||
USBx_OUTEP(0U)->DOEPCTL |= USB_OTG_DOEPCTL_CNAK;
|
||||
break;
|
||||
case USB_DESC_TYPE_BINARY_OBJECT_STORE:
|
||||
USB_WritePacket(binary_object_store_desc, MIN(sizeof(binary_object_store_desc), setup.b.wLength.w), 0);
|
||||
USBx_OUTEP(0U)->DOEPCTL |= USB_OTG_DOEPCTL_CNAK;
|
||||
break;
|
||||
default:
|
||||
// nothing here?
|
||||
USB_WritePacket(0, 0, 0);
|
||||
USBx_OUTEP(0U)->DOEPCTL |= USB_OTG_DOEPCTL_CNAK;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case USB_REQ_GET_STATUS:
|
||||
// empty response?
|
||||
response[0] = 0;
|
||||
response[1] = 0;
|
||||
USB_WritePacket((void*)&response, 2, 0);
|
||||
USBx_OUTEP(0U)->DOEPCTL |= USB_OTG_DOEPCTL_CNAK;
|
||||
break;
|
||||
case USB_REQ_SET_INTERFACE:
|
||||
// Store the alt setting number for IN EP behavior.
|
||||
current_int0_alt_setting = setup.b.wValue.w;
|
||||
USB_WritePacket(0, 0, 0);
|
||||
USBx_OUTEP(0U)->DOEPCTL |= USB_OTG_DOEPCTL_CNAK;
|
||||
break;
|
||||
case WEBUSB_VENDOR_CODE:
|
||||
// probably asking for allowed origins, which was removed from the spec
|
||||
USB_WritePacket(0, 0, 0);
|
||||
USBx_OUTEP(0U)->DOEPCTL |= USB_OTG_DOEPCTL_CNAK;
|
||||
break;
|
||||
case MS_VENDOR_CODE:
|
||||
switch (setup.b.wIndex.w) {
|
||||
// winusb 2.0 descriptor from BOS
|
||||
case WINUSB_REQ_GET_DESCRIPTOR:
|
||||
USB_WritePacket_EP0((uint8_t*)winusb_20_desc, MIN(sizeof(winusb_20_desc), setup.b.wLength.w));
|
||||
break;
|
||||
// Extended Compat ID OS Descriptor
|
||||
case WINUSB_REQ_GET_COMPATID_DESCRIPTOR:
|
||||
USB_WritePacket_EP0((uint8_t*)winusb_ext_compatid_os_desc, MIN(sizeof(winusb_ext_compatid_os_desc), setup.b.wLength.w));
|
||||
break;
|
||||
// Extended Properties OS Descriptor
|
||||
case WINUSB_REQ_GET_EXT_PROPS_OS:
|
||||
USB_WritePacket_EP0((uint8_t*)winusb_ext_prop_os_desc, MIN(sizeof(winusb_ext_prop_os_desc), setup.b.wLength.w));
|
||||
break;
|
||||
default:
|
||||
USB_WritePacket_EP0(0, 0);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
control_req.request = setup.b.bRequest;
|
||||
control_req.param1 = setup.b.wValue.w;
|
||||
control_req.param2 = setup.b.wIndex.w;
|
||||
control_req.length = setup.b.wLength.w;
|
||||
|
||||
resp_len = comms_control_handler(&control_req, response);
|
||||
USB_WritePacket(response, MIN(resp_len, setup.b.wLength.w), 0);
|
||||
USBx_OUTEP(0U)->DOEPCTL |= USB_OTG_DOEPCTL_CNAK;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ***************************** USB port *****************************
|
||||
|
||||
void usb_irqhandler(void) {
|
||||
//USBx->GINTMSK = 0;
|
||||
static uint8_t usbdata[0x100] __attribute__((aligned(4)));
|
||||
unsigned int gintsts = USBx->GINTSTS;
|
||||
unsigned int gotgint = USBx->GOTGINT;
|
||||
unsigned int daint = USBx_DEVICE->DAINT;
|
||||
|
||||
// gintsts SUSPEND? 04008428
|
||||
#ifdef DEBUG_USB
|
||||
puth(gintsts);
|
||||
print(" ");
|
||||
/*puth(USBx->GCCFG);
|
||||
print(" ");*/
|
||||
puth(gotgint);
|
||||
print(" ep ");
|
||||
puth(daint);
|
||||
print(" USB interrupt!\n");
|
||||
#endif
|
||||
|
||||
if ((gintsts & USB_OTG_GINTSTS_CIDSCHG) != 0U) {
|
||||
print("connector ID status change\n");
|
||||
}
|
||||
|
||||
if ((gintsts & USB_OTG_GINTSTS_USBRST) != 0U) {
|
||||
#ifdef DEBUG_USB
|
||||
print("USB reset\n");
|
||||
#endif
|
||||
usb_reset();
|
||||
}
|
||||
|
||||
if ((gintsts & USB_OTG_GINTSTS_ENUMDNE) != 0U) {
|
||||
#ifdef DEBUG_USB
|
||||
print("enumeration done\n");
|
||||
#endif
|
||||
// Full speed, ENUMSPD
|
||||
//puth(USBx_DEVICE->DSTS);
|
||||
}
|
||||
|
||||
if ((gintsts & USB_OTG_GINTSTS_OTGINT) != 0U) {
|
||||
#ifdef DEBUG_USB
|
||||
print("OTG int:");
|
||||
puth(USBx->GOTGINT);
|
||||
print("\n");
|
||||
#endif
|
||||
|
||||
// getting ADTOCHG
|
||||
//USBx->GOTGINT = USBx->GOTGINT;
|
||||
}
|
||||
|
||||
// RX FIFO first
|
||||
if ((gintsts & USB_OTG_GINTSTS_RXFLVL) != 0U) {
|
||||
// 1. Read the Receive status pop register
|
||||
volatile unsigned int rxst = USBx->GRXSTSP;
|
||||
int status = (rxst & USB_OTG_GRXSTSP_PKTSTS) >> 17;
|
||||
|
||||
#ifdef DEBUG_USB
|
||||
print(" RX FIFO:");
|
||||
puth(rxst);
|
||||
print(" status: ");
|
||||
puth(status);
|
||||
print(" len: ");
|
||||
puth((rxst & USB_OTG_GRXSTSP_BCNT) >> 4);
|
||||
print("\n");
|
||||
#endif
|
||||
|
||||
if (status == STS_DATA_UPDT) {
|
||||
int endpoint = (rxst & USB_OTG_GRXSTSP_EPNUM);
|
||||
int len = (rxst & USB_OTG_GRXSTSP_BCNT) >> 4;
|
||||
(void)USB_ReadPacket(&usbdata, len);
|
||||
#ifdef DEBUG_USB
|
||||
print(" data ");
|
||||
puth(len);
|
||||
print("\n");
|
||||
hexdump(&usbdata, len);
|
||||
#endif
|
||||
|
||||
if (endpoint == 2) {
|
||||
comms_endpoint2_write((uint8_t *) usbdata, len);
|
||||
}
|
||||
|
||||
if (endpoint == 3) {
|
||||
outep3_processing = true;
|
||||
comms_can_write(usbdata, len);
|
||||
}
|
||||
} else if (status == STS_SETUP_UPDT) {
|
||||
(void)USB_ReadPacket(&setup, 8);
|
||||
#ifdef DEBUG_USB
|
||||
print(" setup ");
|
||||
hexdump(&setup, 8);
|
||||
print("\n");
|
||||
#endif
|
||||
} else {
|
||||
// status is neither STS_DATA_UPDT or STS_SETUP_UPDT, skip
|
||||
}
|
||||
}
|
||||
|
||||
/*if (gintsts & USB_OTG_GINTSTS_HPRTINT) {
|
||||
// host
|
||||
print("HPRT:");
|
||||
puth(USBx_HOST_PORT->HPRT);
|
||||
print("\n");
|
||||
if (USBx_HOST_PORT->HPRT & USB_OTG_HPRT_PCDET) {
|
||||
USBx_HOST_PORT->HPRT |= USB_OTG_HPRT_PRST;
|
||||
USBx_HOST_PORT->HPRT |= USB_OTG_HPRT_PCDET;
|
||||
}
|
||||
|
||||
}*/
|
||||
|
||||
if ((gintsts & USB_OTG_GINTSTS_BOUTNAKEFF) || (gintsts & USB_OTG_GINTSTS_GINAKEFF)) {
|
||||
// no global NAK, why is this getting set?
|
||||
#ifdef DEBUG_USB
|
||||
print("GLOBAL NAK\n");
|
||||
#endif
|
||||
USBx_DEVICE->DCTL |= USB_OTG_DCTL_CGONAK | USB_OTG_DCTL_CGINAK;
|
||||
}
|
||||
|
||||
if ((gintsts & USB_OTG_GINTSTS_SRQINT) != 0U) {
|
||||
// we want to do "A-device host negotiation protocol" since we are the A-device
|
||||
/*print("start request\n");
|
||||
puth(USBx->GOTGCTL);
|
||||
print("\n");*/
|
||||
//USBx->GUSBCFG |= USB_OTG_GUSBCFG_FDMOD;
|
||||
//USBx_HOST_PORT->HPRT = USB_OTG_HPRT_PPWR | USB_OTG_HPRT_PENA;
|
||||
//USBx->GOTGCTL |= USB_OTG_GOTGCTL_SRQ;
|
||||
}
|
||||
|
||||
// out endpoint hit
|
||||
if ((gintsts & USB_OTG_GINTSTS_OEPINT) != 0U) {
|
||||
#ifdef DEBUG_USB
|
||||
print(" 0:");
|
||||
puth(USBx_OUTEP(0U)->DOEPINT);
|
||||
print(" 2:");
|
||||
puth(USBx_OUTEP(2U)->DOEPINT);
|
||||
print(" 3:");
|
||||
puth(USBx_OUTEP(3U)->DOEPINT);
|
||||
print(" ");
|
||||
puth(USBx_OUTEP(3U)->DOEPCTL);
|
||||
print(" 4:");
|
||||
puth(USBx_OUTEP(4)->DOEPINT);
|
||||
print(" OUT ENDPOINT\n");
|
||||
#endif
|
||||
|
||||
if ((USBx_OUTEP(2U)->DOEPINT & USB_OTG_DOEPINT_XFRC) != 0U) {
|
||||
#ifdef DEBUG_USB
|
||||
print(" OUT2 PACKET XFRC\n");
|
||||
#endif
|
||||
USBx_OUTEP(2U)->DOEPTSIZ = (1UL << 19) | 0x40U;
|
||||
USBx_OUTEP(2U)->DOEPCTL |= USB_OTG_DOEPCTL_EPENA | USB_OTG_DOEPCTL_CNAK;
|
||||
}
|
||||
|
||||
if ((USBx_OUTEP(3U)->DOEPINT & USB_OTG_DOEPINT_XFRC) != 0U) {
|
||||
#ifdef DEBUG_USB
|
||||
print(" OUT3 PACKET XFRC\n");
|
||||
#endif
|
||||
// NAK cleared by process_can (if tx buffers have room)
|
||||
outep3_processing = false;
|
||||
refresh_can_tx_slots_available();
|
||||
} else if ((USBx_OUTEP(3U)->DOEPINT & 0x2000U) != 0U) {
|
||||
#ifdef DEBUG_USB
|
||||
print(" OUT3 PACKET WTF\n");
|
||||
#endif
|
||||
// if NAK was set trigger this, unknown interrupt
|
||||
// TODO: why was this here? fires when TX buffers when we can't clear NAK
|
||||
// USBx_OUTEP(3U)->DOEPTSIZ = (1U << 19) | 0x40U;
|
||||
// USBx_OUTEP(3U)->DOEPCTL |= USB_OTG_DOEPCTL_CNAK;
|
||||
} else if ((USBx_OUTEP(3U)->DOEPINT) != 0U) {
|
||||
#ifdef DEBUG_USB
|
||||
print("OUTEP3 error ");
|
||||
puth(USBx_OUTEP(3U)->DOEPINT);
|
||||
print("\n");
|
||||
#endif
|
||||
} else {
|
||||
// USBx_OUTEP(3U)->DOEPINT is 0, ok to skip
|
||||
}
|
||||
|
||||
if ((USBx_OUTEP(0U)->DOEPINT & USB_OTG_DIEPINT_XFRC) != 0U) {
|
||||
// ready for next packet
|
||||
USBx_OUTEP(0U)->DOEPTSIZ = USB_OTG_DOEPTSIZ_STUPCNT | (USB_OTG_DOEPTSIZ_PKTCNT & (1UL << 19)) | (1U << 3);
|
||||
}
|
||||
|
||||
// respond to setup packets
|
||||
if ((USBx_OUTEP(0U)->DOEPINT & USB_OTG_DOEPINT_STUP) != 0U) {
|
||||
usb_setup();
|
||||
}
|
||||
|
||||
USBx_OUTEP(0U)->DOEPINT = USBx_OUTEP(0U)->DOEPINT;
|
||||
USBx_OUTEP(2U)->DOEPINT = USBx_OUTEP(2U)->DOEPINT;
|
||||
USBx_OUTEP(3U)->DOEPINT = USBx_OUTEP(3U)->DOEPINT;
|
||||
}
|
||||
|
||||
// interrupt endpoint hit (Page 1221)
|
||||
if ((gintsts & USB_OTG_GINTSTS_IEPINT) != 0U) {
|
||||
#ifdef DEBUG_USB
|
||||
print(" ");
|
||||
puth(USBx_INEP(0U)->DIEPINT);
|
||||
print(" ");
|
||||
puth(USBx_INEP(1U)->DIEPINT);
|
||||
print(" IN ENDPOINT\n");
|
||||
#endif
|
||||
|
||||
// Should likely check the EP of the IN request even if there is
|
||||
// only one IN endpoint.
|
||||
|
||||
// No need to set NAK in OTG_DIEPCTL0 when nothing to send,
|
||||
// Appears USB core automatically sets NAK. WritePacket clears it.
|
||||
|
||||
// Handle the two interface alternate settings. Setting 0 has EP1
|
||||
// as bulk. Setting 1 has EP1 as interrupt. The code to handle
|
||||
// these two EP variations are very similar and can be
|
||||
// restructured for smaller code footprint. Keeping split out for
|
||||
// now for clarity.
|
||||
|
||||
//TODO add default case. Should it NAK?
|
||||
switch (current_int0_alt_setting) {
|
||||
case 0: ////// Bulk config
|
||||
// *** IN token received when TxFIFO is empty
|
||||
if ((USBx_INEP(1U)->DIEPINT & USB_OTG_DIEPMSK_ITTXFEMSK) != 0U) {
|
||||
#ifdef DEBUG_USB
|
||||
print(" IN PACKET QUEUE\n");
|
||||
#endif
|
||||
// TODO: always assuming max len, can we get the length?
|
||||
USB_WritePacket((void *)response, comms_can_read(response, 0x40), 1);
|
||||
}
|
||||
break;
|
||||
|
||||
case 1: ////// Interrupt config
|
||||
// *** IN token received when TxFIFO is empty
|
||||
if ((USBx_INEP(1U)->DIEPINT & USB_OTG_DIEPMSK_ITTXFEMSK) != 0U) {
|
||||
#ifdef DEBUG_USB
|
||||
print(" IN PACKET QUEUE\n");
|
||||
#endif
|
||||
// TODO: always assuming max len, can we get the length?
|
||||
int len = comms_can_read(response, 0x40);
|
||||
if (len > 0) {
|
||||
USB_WritePacket((void *)response, len, 1);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
print("current_int0_alt_setting value invalid\n");
|
||||
break;
|
||||
}
|
||||
|
||||
if ((USBx_INEP(0U)->DIEPINT & USB_OTG_DIEPMSK_ITTXFEMSK) != 0U) {
|
||||
#ifdef DEBUG_USB
|
||||
print(" IN PACKET QUEUE\n");
|
||||
#endif
|
||||
|
||||
if ((ep0_txlen != 0U) && ((USBx_INEP(0U)->DTXFSTS & USB_OTG_DTXFSTS_INEPTFSAV) >= 0x40U)) {
|
||||
uint16_t len = MIN(ep0_txlen, 0x40);
|
||||
USB_WritePacket(ep0_txdata, len, 0);
|
||||
ep0_txdata = &ep0_txdata[len];
|
||||
ep0_txlen -= len;
|
||||
if (ep0_txlen == 0U) {
|
||||
ep0_txdata = NULL;
|
||||
USBx_DEVICE->DIEPEMPMSK &= ~1;
|
||||
USBx_OUTEP(0U)->DOEPCTL |= USB_OTG_DOEPCTL_CNAK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// clear interrupts
|
||||
USBx_INEP(0U)->DIEPINT = USBx_INEP(0U)->DIEPINT; // Why ep0?
|
||||
USBx_INEP(1U)->DIEPINT = USBx_INEP(1U)->DIEPINT;
|
||||
}
|
||||
|
||||
// clear all interrupts we handled
|
||||
USBx_DEVICE->DAINT = daint;
|
||||
USBx->GOTGINT = gotgint;
|
||||
USBx->GINTSTS = gintsts;
|
||||
|
||||
//USBx->GINTMSK = 0xFFFFFFFF & ~(USB_OTG_GINTMSK_NPTXFEM | USB_OTG_GINTMSK_PTXFEM | USB_OTG_GINTSTS_SOF | USB_OTG_GINTSTS_EOPF);
|
||||
}
|
||||
|
||||
void can_tx_comms_resume_usb(void) {
|
||||
ENTER_CRITICAL();
|
||||
if (!outep3_processing && (USBx_OUTEP(3U)->DOEPCTL & USB_OTG_DOEPCTL_NAKSTS) != 0U) {
|
||||
USBx_OUTEP(3U)->DOEPTSIZ = (32UL << 19) | 0x800U;
|
||||
USBx_OUTEP(3U)->DOEPCTL |= USB_OTG_DOEPCTL_EPENA | USB_OTG_DOEPCTL_CNAK;
|
||||
}
|
||||
EXIT_CRITICAL();
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
// Early bringup
|
||||
#define ENTER_BOOTLOADER_MAGIC 0xdeadbeefU
|
||||
#define ENTER_SOFTLOADER_MAGIC 0xdeadc0deU
|
||||
#define BOOT_NORMAL 0xdeadb111U
|
||||
|
||||
extern void *g_pfnVectors;
|
||||
extern uint32_t enter_bootloader_mode;
|
||||
|
||||
typedef void (*bootloader_fcn)(void);
|
||||
typedef bootloader_fcn *bootloader_fcn_ptr;
|
||||
|
||||
static void jump_to_bootloader(void) {
|
||||
// do enter bootloader
|
||||
enter_bootloader_mode = 0;
|
||||
|
||||
bootloader_fcn_ptr bootloader_ptr = (bootloader_fcn_ptr)BOOTLOADER_ADDRESS;
|
||||
bootloader_fcn bootloader = *bootloader_ptr;
|
||||
|
||||
// jump to bootloader
|
||||
enable_interrupts();
|
||||
bootloader();
|
||||
|
||||
// reset on exit
|
||||
enter_bootloader_mode = BOOT_NORMAL;
|
||||
NVIC_SystemReset();
|
||||
}
|
||||
|
||||
void early_initialization(void) {
|
||||
// Reset global critical depth
|
||||
disable_interrupts();
|
||||
global_critical_depth = 0;
|
||||
|
||||
// Init register and interrupt tables
|
||||
init_registers();
|
||||
|
||||
// after it's been in the bootloader, things are initted differently, so we reset
|
||||
if ((enter_bootloader_mode != BOOT_NORMAL) &&
|
||||
(enter_bootloader_mode != ENTER_BOOTLOADER_MAGIC) &&
|
||||
(enter_bootloader_mode != ENTER_SOFTLOADER_MAGIC)) {
|
||||
enter_bootloader_mode = BOOT_NORMAL;
|
||||
NVIC_SystemReset();
|
||||
}
|
||||
|
||||
// if wrong chip, reboot
|
||||
volatile unsigned int id = DBGMCU->IDCODE;
|
||||
if ((id & 0xFFFU) != MCU_IDCODE) {
|
||||
enter_bootloader_mode = ENTER_BOOTLOADER_MAGIC;
|
||||
}
|
||||
|
||||
// setup interrupt table
|
||||
SCB->VTOR = (uint32_t)&g_pfnVectors;
|
||||
|
||||
// early GPIOs float everything
|
||||
early_gpio_float();
|
||||
|
||||
detect_board_type();
|
||||
|
||||
if (enter_bootloader_mode == ENTER_BOOTLOADER_MAGIC) {
|
||||
led_init();
|
||||
current_board->init_bootloader();
|
||||
led_set(LED_GREEN, 1);
|
||||
jump_to_bootloader();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// minimal code to fake a panda for tests
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
#define ALLOW_DEBUG
|
||||
|
||||
#define ENTER_CRITICAL() 0
|
||||
#define EXIT_CRITICAL() 0
|
||||
|
||||
void print(const char *a) {
|
||||
printf("%s", a);
|
||||
}
|
||||
|
||||
void puth(unsigned int i) {
|
||||
printf("%u", i);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
uint32_t CNT;
|
||||
} TIM_TypeDef;
|
||||
|
||||
TIM_TypeDef timer;
|
||||
TIM_TypeDef *MICROSECOND_TIMER = &timer;
|
||||
uint32_t microsecond_timer_get(void);
|
||||
|
||||
uint32_t microsecond_timer_get(void) {
|
||||
return MICROSECOND_TIMER->CNT;
|
||||
}
|
||||
|
||||
typedef uint32_t GPIO_TypeDef;
|
||||
Executable
+27
@@ -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)
|
||||
@@ -0,0 +1,150 @@
|
||||
// from the linker script
|
||||
#define APP_START_ADDRESS 0x8020000U
|
||||
|
||||
// flasher state variables
|
||||
uint32_t *prog_ptr = NULL;
|
||||
bool unlocked = false;
|
||||
|
||||
int comms_control_handler(ControlPacket_t *req, uint8_t *resp) {
|
||||
int resp_len = 0;
|
||||
|
||||
// flasher machine
|
||||
memset(resp, 0, 4);
|
||||
memcpy(resp+4, "\xde\xad\xd0\x0d", 4);
|
||||
resp[0] = 0xff;
|
||||
resp[2] = req->request;
|
||||
resp[3] = ~req->request;
|
||||
*((uint32_t **)&resp[8]) = prog_ptr;
|
||||
resp_len = 0xc;
|
||||
|
||||
int sec;
|
||||
switch (req->request) {
|
||||
// **** 0xb0: flasher echo
|
||||
case 0xb0:
|
||||
resp[1] = 0xff;
|
||||
break;
|
||||
// **** 0xb1: unlock flash
|
||||
case 0xb1:
|
||||
if (flash_is_locked()) {
|
||||
flash_unlock();
|
||||
resp[1] = 0xff;
|
||||
}
|
||||
led_set(LED_GREEN, 1);
|
||||
unlocked = true;
|
||||
prog_ptr = (uint32_t *)APP_START_ADDRESS;
|
||||
break;
|
||||
// **** 0xb2: erase sector
|
||||
case 0xb2:
|
||||
sec = req->param1;
|
||||
if (flash_erase_sector(sec, unlocked)) {
|
||||
resp[1] = 0xff;
|
||||
}
|
||||
break;
|
||||
// **** 0xc1: get hardware type
|
||||
case 0xc1:
|
||||
resp[0] = hw_type;
|
||||
resp_len = 1;
|
||||
break;
|
||||
// **** 0xc3: fetch MCU UID
|
||||
case 0xc3:
|
||||
(void)memcpy(resp, ((uint8_t *)UID_BASE), 12);
|
||||
resp_len = 12;
|
||||
break;
|
||||
// **** 0xd0: fetch serial number
|
||||
case 0xd0:
|
||||
// addresses are OTP
|
||||
if (req->param1 == 1) {
|
||||
memcpy(resp, (void *)DEVICE_SERIAL_NUMBER_ADDRESS, 0x10);
|
||||
resp_len = 0x10;
|
||||
} else {
|
||||
get_provision_chunk(resp);
|
||||
resp_len = PROVISION_CHUNK_LEN;
|
||||
}
|
||||
break;
|
||||
// **** 0xd1: enter bootloader mode
|
||||
case 0xd1:
|
||||
// this allows reflashing of the bootstub
|
||||
switch (req->param1) {
|
||||
case 0:
|
||||
print("-> entering bootloader\n");
|
||||
enter_bootloader_mode = ENTER_BOOTLOADER_MAGIC;
|
||||
NVIC_SystemReset();
|
||||
break;
|
||||
case 1:
|
||||
print("-> entering softloader\n");
|
||||
enter_bootloader_mode = ENTER_SOFTLOADER_MAGIC;
|
||||
NVIC_SystemReset();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
// **** 0xd6: get version
|
||||
case 0xd6:
|
||||
COMPILE_TIME_ASSERT(sizeof(gitversion) <= USBPACKET_MAX_SIZE);
|
||||
memcpy(resp, gitversion, sizeof(gitversion));
|
||||
resp_len = sizeof(gitversion) - 1U;
|
||||
break;
|
||||
// **** 0xd8: reset ST
|
||||
case 0xd8:
|
||||
flush_write_buffer();
|
||||
NVIC_SystemReset();
|
||||
break;
|
||||
}
|
||||
return resp_len;
|
||||
}
|
||||
|
||||
void comms_can_write(const uint8_t *data, uint32_t len) {
|
||||
UNUSED(data);
|
||||
UNUSED(len);
|
||||
}
|
||||
|
||||
int comms_can_read(uint8_t *data, uint32_t max_len) {
|
||||
UNUSED(data);
|
||||
UNUSED(max_len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void refresh_can_tx_slots_available(void) {}
|
||||
|
||||
void comms_endpoint2_write(const uint8_t *data, uint32_t len) {
|
||||
led_set(LED_RED, 0);
|
||||
for (uint32_t i = 0; i < len/4; i++) {
|
||||
flash_write_word(prog_ptr, *(uint32_t*)(data+(i*4)));
|
||||
|
||||
//*(uint64_t*)(&spi_tx_buf[0x30+(i*4)]) = *prog_ptr;
|
||||
prog_ptr++;
|
||||
}
|
||||
led_set(LED_RED, 1);
|
||||
}
|
||||
|
||||
|
||||
void soft_flasher_start(void) {
|
||||
print("\n\n\n************************ FLASHER START ************************\n");
|
||||
|
||||
enter_bootloader_mode = 0;
|
||||
|
||||
flasher_peripherals_init();
|
||||
|
||||
gpio_usart2_init();
|
||||
gpio_usb_init();
|
||||
led_init();
|
||||
|
||||
// enable comms
|
||||
usb_init();
|
||||
if (current_board->has_spi) {
|
||||
gpio_spi_init();
|
||||
spi_init();
|
||||
}
|
||||
|
||||
// green LED on for flashing
|
||||
led_set(LED_GREEN, 1);
|
||||
|
||||
enable_interrupts();
|
||||
|
||||
for (;;) {
|
||||
// blink the green LED fast
|
||||
led_set(LED_GREEN, 0);
|
||||
delay(500000);
|
||||
led_set(LED_GREEN, 1);
|
||||
delay(500000);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
struct __attribute__((packed)) health_t {
|
||||
uint32_t uptime_pkt;
|
||||
uint32_t voltage_pkt;
|
||||
uint32_t current_pkt;
|
||||
uint32_t safety_tx_blocked_pkt;
|
||||
uint32_t safety_rx_invalid_pkt;
|
||||
uint32_t tx_buffer_overflow_pkt;
|
||||
uint32_t rx_buffer_overflow_pkt;
|
||||
uint32_t faults_pkt;
|
||||
uint8_t ignition_line_pkt;
|
||||
uint8_t ignition_can_pkt;
|
||||
uint8_t controls_allowed_pkt;
|
||||
uint8_t car_harness_status_pkt;
|
||||
uint8_t safety_mode_pkt;
|
||||
uint16_t safety_param_pkt;
|
||||
uint8_t fault_status_pkt;
|
||||
uint8_t power_save_enabled_pkt;
|
||||
uint8_t heartbeat_lost_pkt;
|
||||
uint16_t alternative_experience_pkt;
|
||||
float interrupt_load_pkt;
|
||||
uint8_t fan_power;
|
||||
uint8_t safety_rx_checks_invalid_pkt;
|
||||
uint16_t spi_error_count_pkt;
|
||||
uint16_t sbu1_voltage_mV;
|
||||
uint16_t sbu2_voltage_mV;
|
||||
uint8_t som_reset_triggered;
|
||||
uint16_t sound_output_level_pkt;
|
||||
};
|
||||
|
||||
typedef struct __attribute__((packed)) {
|
||||
uint8_t bus_off;
|
||||
uint32_t bus_off_cnt;
|
||||
uint8_t error_warning;
|
||||
uint8_t error_passive;
|
||||
uint8_t last_error; // real time LEC value
|
||||
uint8_t last_stored_error; // last LEC positive error code stored
|
||||
uint8_t last_data_error; // DLEC (for CANFD only)
|
||||
uint8_t last_data_stored_error; // last DLEC positive error code stored (for CANFD only)
|
||||
uint8_t receive_error_cnt; // Actual state of the receive error counter, values between 0 and 127. FDCAN_ECR.REC
|
||||
uint8_t transmit_error_cnt; // Actual state of the transmit error counter, values between 0 and 255. FDCAN_ECR.TEC
|
||||
uint32_t total_error_cnt; // How many times any error interrupt was invoked
|
||||
uint32_t total_tx_lost_cnt; // Tx event FIFO element lost
|
||||
uint32_t total_rx_lost_cnt; // Rx FIFO 0 message lost due to FIFO full condition
|
||||
uint32_t total_tx_cnt;
|
||||
uint32_t total_rx_cnt;
|
||||
uint32_t total_fwd_cnt; // Messages forwarded from one bus to another
|
||||
uint32_t total_tx_checksum_error_cnt;
|
||||
uint16_t can_speed;
|
||||
uint16_t can_data_speed;
|
||||
uint8_t canfd_enabled;
|
||||
uint8_t brs_enabled;
|
||||
uint8_t canfd_non_iso;
|
||||
uint32_t irq0_call_rate;
|
||||
uint32_t irq1_call_rate;
|
||||
uint32_t irq2_call_rate;
|
||||
uint32_t can_core_reset_cnt;
|
||||
} can_health_t;
|
||||
@@ -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.
|
||||
@@ -0,0 +1,151 @@
|
||||
# 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, compute_version_hash
|
||||
|
||||
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 = compute_version_hash(os.path.join(BASEDIR, "jungle_health.h"))
|
||||
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, McuType.H7.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 up_to_date(self, fn=None) -> bool:
|
||||
if fn is None:
|
||||
fn = os.path.join(FW_PATH, McuType.H7.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 *******************
|
||||
|
||||
def get_packets_versions(self):
|
||||
dat = self._handle.controlRead(PandaJungle.REQUEST_IN, 0xdd, 0, 0, 8)
|
||||
if dat and len(dat) == 8:
|
||||
return struct.unpack("<II", dat)
|
||||
return (0, 0)
|
||||
|
||||
# ******************* 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'')
|
||||
@@ -0,0 +1,60 @@
|
||||
// ******************** Prototypes ********************
|
||||
typedef void (*board_init)(void);
|
||||
typedef void (*board_board_tick)(void);
|
||||
typedef bool (*board_get_button)(void);
|
||||
typedef void (*board_init_bootloader)(void);
|
||||
typedef void (*board_set_panda_power)(bool enabled);
|
||||
typedef void (*board_set_panda_individual_power)(uint8_t port_num, bool enabled);
|
||||
typedef void (*board_set_ignition)(bool enabled);
|
||||
typedef void (*board_set_individual_ignition)(uint8_t bitmask);
|
||||
typedef void (*board_set_harness_orientation)(uint8_t orientation);
|
||||
typedef void (*board_set_can_mode)(uint8_t mode);
|
||||
typedef void (*board_enable_can_transceiver)(uint8_t transceiver, bool enabled);
|
||||
typedef void (*board_enable_header_pin)(uint8_t pin_num, bool enabled);
|
||||
typedef float (*board_get_channel_power)(uint8_t channel);
|
||||
typedef uint16_t (*board_get_sbu_mV)(uint8_t channel, uint8_t sbu);
|
||||
|
||||
struct board {
|
||||
GPIO_TypeDef * const led_GPIO[3];
|
||||
const uint8_t led_pin[3];
|
||||
const uint8_t led_pwm_channels[3]; // leave at 0 to disable PWM
|
||||
const uint16_t avdd_mV;
|
||||
board_init init;
|
||||
board_board_tick board_tick;
|
||||
board_get_button get_button;
|
||||
board_init_bootloader init_bootloader;
|
||||
board_set_panda_power set_panda_power;
|
||||
board_set_panda_individual_power set_panda_individual_power;
|
||||
board_set_ignition set_ignition;
|
||||
board_set_individual_ignition set_individual_ignition;
|
||||
board_set_harness_orientation set_harness_orientation;
|
||||
board_set_can_mode set_can_mode;
|
||||
board_enable_can_transceiver enable_can_transceiver;
|
||||
board_enable_header_pin enable_header_pin;
|
||||
board_get_channel_power get_channel_power;
|
||||
board_get_sbu_mV get_sbu_mV;
|
||||
|
||||
// TODO: shouldn't need these
|
||||
bool has_spi;
|
||||
};
|
||||
|
||||
// ******************* Definitions ********************
|
||||
#define HW_TYPE_UNKNOWN 0U
|
||||
#define HW_TYPE_V2 2U
|
||||
|
||||
// CAN modes
|
||||
#define CAN_MODE_NORMAL 0U
|
||||
#define CAN_MODE_OBD_CAN2 3U
|
||||
|
||||
// Harness states
|
||||
#define HARNESS_ORIENTATION_NONE 0U
|
||||
#define HARNESS_ORIENTATION_1 1U
|
||||
#define HARNESS_ORIENTATION_2 2U
|
||||
|
||||
#define SBU1 0U
|
||||
#define SBU2 1U
|
||||
|
||||
// ********************* Globals **********************
|
||||
uint8_t harness_orientation = HARNESS_ORIENTATION_NONE;
|
||||
uint8_t can_mode = CAN_MODE_NORMAL;
|
||||
uint8_t ignition = 0U;
|
||||
@@ -0,0 +1,308 @@
|
||||
// ///////////////////////// //
|
||||
// Jungle board v2 (STM32H7) //
|
||||
// ///////////////////////// //
|
||||
|
||||
#define ADC_CHANNEL(a, c) {.adc = (a), .channel = (c), .sample_time = SAMPLETIME_810_CYCLES, .oversampling = OVERSAMPLING_1}
|
||||
|
||||
gpio_t power_pins[] = {
|
||||
{.bank = GPIOA, .pin = 0},
|
||||
{.bank = GPIOA, .pin = 1},
|
||||
{.bank = GPIOF, .pin = 12},
|
||||
{.bank = GPIOA, .pin = 5},
|
||||
{.bank = GPIOC, .pin = 5},
|
||||
{.bank = GPIOB, .pin = 2},
|
||||
};
|
||||
|
||||
gpio_t sbu1_ignition_pins[] = {
|
||||
{.bank = GPIOD, .pin = 0},
|
||||
{.bank = GPIOD, .pin = 5},
|
||||
{.bank = GPIOD, .pin = 12},
|
||||
{.bank = GPIOD, .pin = 14},
|
||||
{.bank = GPIOE, .pin = 5},
|
||||
{.bank = GPIOE, .pin = 9},
|
||||
};
|
||||
|
||||
gpio_t sbu1_relay_pins[] = {
|
||||
{.bank = GPIOD, .pin = 1},
|
||||
{.bank = GPIOD, .pin = 6},
|
||||
{.bank = GPIOD, .pin = 11},
|
||||
{.bank = GPIOD, .pin = 15},
|
||||
{.bank = GPIOE, .pin = 6},
|
||||
{.bank = GPIOE, .pin = 10},
|
||||
};
|
||||
|
||||
gpio_t sbu2_ignition_pins[] = {
|
||||
{.bank = GPIOD, .pin = 3},
|
||||
{.bank = GPIOD, .pin = 8},
|
||||
{.bank = GPIOD, .pin = 9},
|
||||
{.bank = GPIOE, .pin = 0},
|
||||
{.bank = GPIOE, .pin = 7},
|
||||
{.bank = GPIOE, .pin = 11},
|
||||
};
|
||||
|
||||
gpio_t sbu2_relay_pins[] = {
|
||||
{.bank = GPIOD, .pin = 4},
|
||||
{.bank = GPIOD, .pin = 10},
|
||||
{.bank = GPIOD, .pin = 13},
|
||||
{.bank = GPIOE, .pin = 1},
|
||||
{.bank = GPIOE, .pin = 8},
|
||||
{.bank = GPIOE, .pin = 12},
|
||||
};
|
||||
|
||||
const adc_signal_t sbu1_channels[] = {
|
||||
ADC_CHANNEL(ADC3, 12),
|
||||
ADC_CHANNEL(ADC3, 2),
|
||||
ADC_CHANNEL(ADC3, 4),
|
||||
ADC_CHANNEL(ADC3, 6),
|
||||
ADC_CHANNEL(ADC3, 8),
|
||||
ADC_CHANNEL(ADC3, 10),
|
||||
};
|
||||
|
||||
const adc_signal_t sbu2_channels[] = {
|
||||
ADC_CHANNEL(ADC1, 13),
|
||||
ADC_CHANNEL(ADC3, 3),
|
||||
ADC_CHANNEL(ADC3, 5),
|
||||
ADC_CHANNEL(ADC3, 7),
|
||||
ADC_CHANNEL(ADC3, 9),
|
||||
ADC_CHANNEL(ADC3, 11),
|
||||
};
|
||||
|
||||
void board_v2_set_harness_orientation(uint8_t orientation) {
|
||||
switch (orientation) {
|
||||
case HARNESS_ORIENTATION_NONE:
|
||||
gpio_set_all_output(sbu1_ignition_pins, sizeof(sbu1_ignition_pins) / sizeof(gpio_t), false);
|
||||
gpio_set_all_output(sbu1_relay_pins, sizeof(sbu1_relay_pins) / sizeof(gpio_t), false);
|
||||
gpio_set_all_output(sbu2_ignition_pins, sizeof(sbu2_ignition_pins) / sizeof(gpio_t), false);
|
||||
gpio_set_all_output(sbu2_relay_pins, sizeof(sbu2_relay_pins) / sizeof(gpio_t), false);
|
||||
harness_orientation = orientation;
|
||||
break;
|
||||
case HARNESS_ORIENTATION_1:
|
||||
gpio_set_all_output(sbu1_ignition_pins, sizeof(sbu1_ignition_pins) / sizeof(gpio_t), false);
|
||||
gpio_set_all_output(sbu1_relay_pins, sizeof(sbu1_relay_pins) / sizeof(gpio_t), true);
|
||||
gpio_set_bitmask(sbu2_ignition_pins, sizeof(sbu2_ignition_pins) / sizeof(gpio_t), ignition);
|
||||
gpio_set_all_output(sbu2_relay_pins, sizeof(sbu2_relay_pins) / sizeof(gpio_t), false);
|
||||
harness_orientation = orientation;
|
||||
break;
|
||||
case HARNESS_ORIENTATION_2:
|
||||
gpio_set_bitmask(sbu1_ignition_pins, sizeof(sbu1_ignition_pins) / sizeof(gpio_t), ignition);
|
||||
gpio_set_all_output(sbu1_relay_pins, sizeof(sbu1_relay_pins) / sizeof(gpio_t), false);
|
||||
gpio_set_all_output(sbu2_ignition_pins, sizeof(sbu2_ignition_pins) / sizeof(gpio_t), false);
|
||||
gpio_set_all_output(sbu2_relay_pins, sizeof(sbu2_relay_pins) / sizeof(gpio_t), true);
|
||||
harness_orientation = orientation;
|
||||
break;
|
||||
default:
|
||||
print("Tried to set an unsupported harness orientation: "); puth(orientation); print("\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void board_v2_enable_can_transceiver(uint8_t transceiver, bool enabled) {
|
||||
switch (transceiver) {
|
||||
case 1U:
|
||||
set_gpio_output(GPIOG, 11, !enabled);
|
||||
break;
|
||||
case 2U:
|
||||
set_gpio_output(GPIOB, 3, !enabled);
|
||||
break;
|
||||
case 3U:
|
||||
set_gpio_output(GPIOD, 7, !enabled);
|
||||
break;
|
||||
case 4U:
|
||||
set_gpio_output(GPIOB, 4, !enabled);
|
||||
break;
|
||||
default:
|
||||
print("Invalid CAN transceiver ("); puth(transceiver); print("): enabling failed\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void board_v2_enable_header_pin(uint8_t pin_num, bool enabled) {
|
||||
if (pin_num < 8U) {
|
||||
set_gpio_output(GPIOG, pin_num, enabled);
|
||||
} else {
|
||||
print("Invalid pin number ("); puth(pin_num); print("): enabling failed\n");
|
||||
}
|
||||
}
|
||||
|
||||
void board_v2_set_can_mode(uint8_t mode) {
|
||||
board_v2_enable_can_transceiver(2U, false);
|
||||
board_v2_enable_can_transceiver(4U, false);
|
||||
switch (mode) {
|
||||
case CAN_MODE_NORMAL:
|
||||
// B12,B13: disable normal mode
|
||||
set_gpio_pullup(GPIOB, 12, PULL_NONE);
|
||||
set_gpio_mode(GPIOB, 12, MODE_ANALOG);
|
||||
|
||||
set_gpio_pullup(GPIOB, 13, PULL_NONE);
|
||||
set_gpio_mode(GPIOB, 13, MODE_ANALOG);
|
||||
|
||||
// B5,B6: FDCAN2 mode
|
||||
set_gpio_pullup(GPIOB, 5, PULL_NONE);
|
||||
set_gpio_alternate(GPIOB, 5, GPIO_AF9_FDCAN2);
|
||||
|
||||
set_gpio_pullup(GPIOB, 6, PULL_NONE);
|
||||
set_gpio_alternate(GPIOB, 6, GPIO_AF9_FDCAN2);
|
||||
can_mode = CAN_MODE_NORMAL;
|
||||
board_v2_enable_can_transceiver(2U, true);
|
||||
break;
|
||||
case CAN_MODE_OBD_CAN2:
|
||||
// B5,B6: disable normal mode
|
||||
set_gpio_pullup(GPIOB, 5, PULL_NONE);
|
||||
set_gpio_mode(GPIOB, 5, MODE_ANALOG);
|
||||
|
||||
set_gpio_pullup(GPIOB, 6, PULL_NONE);
|
||||
set_gpio_mode(GPIOB, 6, MODE_ANALOG);
|
||||
// B12,B13: FDCAN2 mode
|
||||
set_gpio_pullup(GPIOB, 12, PULL_NONE);
|
||||
set_gpio_alternate(GPIOB, 12, GPIO_AF9_FDCAN2);
|
||||
|
||||
set_gpio_pullup(GPIOB, 13, PULL_NONE);
|
||||
set_gpio_alternate(GPIOB, 13, GPIO_AF9_FDCAN2);
|
||||
can_mode = CAN_MODE_OBD_CAN2;
|
||||
board_v2_enable_can_transceiver(4U, true);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool panda_power = false;
|
||||
uint8_t panda_power_bitmask = 0U;
|
||||
void board_v2_set_panda_power(bool enable) {
|
||||
panda_power = enable;
|
||||
gpio_set_all_output(power_pins, sizeof(power_pins) / sizeof(gpio_t), enable);
|
||||
if (enable) {
|
||||
panda_power_bitmask = 0xFFU;
|
||||
} else {
|
||||
panda_power_bitmask = 0U;
|
||||
}
|
||||
}
|
||||
|
||||
void board_v2_set_panda_individual_power(uint8_t port_num, bool enable) {
|
||||
port_num -= 1U;
|
||||
if (port_num < 6U) {
|
||||
panda_power_bitmask &= ~(1U << port_num);
|
||||
panda_power_bitmask |= (enable ? 1U : 0U) << port_num;
|
||||
} else {
|
||||
print("Invalid port number ("); puth(port_num); print("): enabling failed\n");
|
||||
}
|
||||
gpio_set_bitmask(power_pins, sizeof(power_pins) / sizeof(gpio_t), (uint32_t)panda_power_bitmask);
|
||||
}
|
||||
|
||||
bool board_v2_get_button(void) {
|
||||
return get_gpio_input(GPIOG, 15);
|
||||
}
|
||||
|
||||
void board_v2_set_ignition(bool enabled) {
|
||||
ignition = enabled ? 0xFFU : 0U;
|
||||
board_v2_set_harness_orientation(harness_orientation);
|
||||
}
|
||||
|
||||
void board_v2_set_individual_ignition(uint8_t bitmask) {
|
||||
ignition = bitmask;
|
||||
board_v2_set_harness_orientation(harness_orientation);
|
||||
}
|
||||
|
||||
float board_v2_get_channel_power(uint8_t channel) {
|
||||
float ret = 0.0f;
|
||||
if ((channel >= 1U) && (channel <= 6U)) {
|
||||
uint16_t readout = adc_get_mV(&(const adc_signal_t) ADC_CHANNEL(ADC1, channel - 1U)); // these are mapped nicely in hardware
|
||||
ret = (((float) readout / 33e6) - 0.8e-6) / 52e-6 * 12.0f;
|
||||
} else {
|
||||
print("Invalid channel ("); puth(channel); print(")\n");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint16_t board_v2_get_sbu_mV(uint8_t channel, uint8_t sbu) {
|
||||
uint16_t ret = 0U;
|
||||
if ((channel >= 1U) && (channel <= 6U)) {
|
||||
switch(sbu){
|
||||
case SBU1:
|
||||
ret = adc_get_mV(&sbu1_channels[channel - 1U]);
|
||||
break;
|
||||
case SBU2:
|
||||
ret = adc_get_mV(&sbu2_channels[channel - 1U]);
|
||||
break;
|
||||
default:
|
||||
print("Invalid SBU ("); puth(sbu); print(")\n");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
print("Invalid channel ("); puth(channel); print(")\n");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void board_v2_init(void) {
|
||||
common_init_gpio();
|
||||
|
||||
// Normal CAN mode
|
||||
board_v2_set_can_mode(CAN_MODE_NORMAL);
|
||||
|
||||
// Enable CAN transceivers
|
||||
for(uint8_t i = 1; i <= 4; i++) {
|
||||
board_v2_enable_can_transceiver(i, true);
|
||||
}
|
||||
|
||||
// Set to no harness orientation
|
||||
board_v2_set_harness_orientation(HARNESS_ORIENTATION_NONE);
|
||||
|
||||
// Enable panda power by default
|
||||
board_v2_set_panda_power(true);
|
||||
|
||||
// Current monitor channels
|
||||
adc_init(ADC1);
|
||||
register_set_bits(&SYSCFG->PMCR, SYSCFG_PMCR_PA0SO | SYSCFG_PMCR_PA1SO); // open up analog switches for PA0_C and PA1_C
|
||||
set_gpio_mode(GPIOF, 11, MODE_ANALOG);
|
||||
set_gpio_mode(GPIOA, 6, MODE_ANALOG);
|
||||
set_gpio_mode(GPIOC, 4, MODE_ANALOG);
|
||||
set_gpio_mode(GPIOB, 1, MODE_ANALOG);
|
||||
|
||||
// SBU channels
|
||||
adc_init(ADC3);
|
||||
set_gpio_mode(GPIOC, 2, MODE_ANALOG);
|
||||
set_gpio_mode(GPIOC, 3, MODE_ANALOG);
|
||||
set_gpio_mode(GPIOF, 9, MODE_ANALOG);
|
||||
set_gpio_mode(GPIOF, 7, MODE_ANALOG);
|
||||
set_gpio_mode(GPIOF, 5, MODE_ANALOG);
|
||||
set_gpio_mode(GPIOF, 3, MODE_ANALOG);
|
||||
set_gpio_mode(GPIOF, 10, MODE_ANALOG);
|
||||
set_gpio_mode(GPIOF, 8, MODE_ANALOG);
|
||||
set_gpio_mode(GPIOF, 6, MODE_ANALOG);
|
||||
set_gpio_mode(GPIOF, 4, MODE_ANALOG);
|
||||
set_gpio_mode(GPIOC, 0, MODE_ANALOG);
|
||||
set_gpio_mode(GPIOC, 1, MODE_ANALOG);
|
||||
|
||||
// Header pins
|
||||
set_gpio_mode(GPIOG, 0, MODE_OUTPUT);
|
||||
set_gpio_mode(GPIOG, 1, MODE_OUTPUT);
|
||||
set_gpio_mode(GPIOG, 2, MODE_OUTPUT);
|
||||
set_gpio_mode(GPIOG, 3, MODE_OUTPUT);
|
||||
set_gpio_mode(GPIOG, 4, MODE_OUTPUT);
|
||||
set_gpio_mode(GPIOG, 5, MODE_OUTPUT);
|
||||
set_gpio_mode(GPIOG, 6, MODE_OUTPUT);
|
||||
set_gpio_mode(GPIOG, 7, MODE_OUTPUT);
|
||||
}
|
||||
|
||||
void board_v2_tick(void) {}
|
||||
|
||||
board board_v2 = {
|
||||
.avdd_mV = 3300U,
|
||||
.init = &board_v2_init,
|
||||
.init_bootloader = &board_v2_tick,
|
||||
.led_GPIO = {GPIOE, GPIOE, GPIOE},
|
||||
.led_pin = {4, 3, 2},
|
||||
.board_tick = &board_v2_tick,
|
||||
.get_button = &board_v2_get_button,
|
||||
.set_panda_power = &board_v2_set_panda_power,
|
||||
.set_panda_individual_power = &board_v2_set_panda_individual_power,
|
||||
.set_ignition = &board_v2_set_ignition,
|
||||
.set_individual_ignition = &board_v2_set_individual_ignition,
|
||||
.set_harness_orientation = &board_v2_set_harness_orientation,
|
||||
.set_can_mode = &board_v2_set_can_mode,
|
||||
.enable_can_transceiver = &board_v2_enable_can_transceiver,
|
||||
.enable_header_pin = &board_v2_enable_header_pin,
|
||||
.get_channel_power = &board_v2_get_channel_power,
|
||||
.get_sbu_mV = &board_v2_get_sbu_mV,
|
||||
};
|
||||
Executable
+27
@@ -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)
|
||||
@@ -0,0 +1,21 @@
|
||||
struct __attribute__((packed)) jungle_health_t {
|
||||
uint32_t uptime_pkt;
|
||||
float ch1_power;
|
||||
float ch2_power;
|
||||
float ch3_power;
|
||||
float ch4_power;
|
||||
float ch5_power;
|
||||
float ch6_power;
|
||||
uint16_t ch1_sbu1_mV;
|
||||
uint16_t ch1_sbu2_mV;
|
||||
uint16_t ch2_sbu1_mV;
|
||||
uint16_t ch2_sbu2_mV;
|
||||
uint16_t ch3_sbu1_mV;
|
||||
uint16_t ch3_sbu2_mV;
|
||||
uint16_t ch4_sbu1_mV;
|
||||
uint16_t ch4_sbu2_mV;
|
||||
uint16_t ch5_sbu1_mV;
|
||||
uint16_t ch5_sbu2_mV;
|
||||
uint16_t ch6_sbu1_mV;
|
||||
uint16_t ch6_sbu2_mV;
|
||||
};
|
||||
@@ -0,0 +1,202 @@
|
||||
// ********************* Includes *********************
|
||||
#include "board/config.h"
|
||||
|
||||
#include "opendbc/safety/safety.h"
|
||||
|
||||
#include "board/drivers/led.h"
|
||||
#include "board/drivers/pwm.h"
|
||||
#include "board/drivers/usb.h"
|
||||
|
||||
#include "board/early_init.h"
|
||||
#include "board/provision.h"
|
||||
|
||||
#include "board/health.h"
|
||||
#include "board/jungle/jungle_health.h"
|
||||
|
||||
#include "board/drivers/can_common.h"
|
||||
|
||||
#include "board/drivers/fdcan.h"
|
||||
|
||||
#include "board/obj/gitversion.h"
|
||||
|
||||
#include "board/can_comms.h"
|
||||
#include "board/jungle/main_comms.h"
|
||||
|
||||
|
||||
// ********************* Serial debugging *********************
|
||||
|
||||
void debug_ring_callback(uart_ring *ring) {
|
||||
char rcv;
|
||||
while (get_char(ring, &rcv)) {
|
||||
(void)injectc(ring, rcv);
|
||||
}
|
||||
}
|
||||
|
||||
// ***************************** main code *****************************
|
||||
|
||||
// cppcheck-suppress unusedFunction ; used in headers not included in cppcheck
|
||||
void __initialize_hardware_early(void) {
|
||||
early_initialization();
|
||||
}
|
||||
|
||||
void __attribute__ ((noinline)) enable_fpu(void) {
|
||||
// enable the FPU
|
||||
SCB->CPACR |= ((3UL << (10U * 2U)) | (3UL << (11U * 2U)));
|
||||
}
|
||||
|
||||
// called at 8Hz
|
||||
uint32_t loop_counter = 0U;
|
||||
uint16_t button_press_cnt = 0U;
|
||||
void tick_handler(void) {
|
||||
if (TICK_TIMER->SR != 0) {
|
||||
if (generated_can_traffic) {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if (can_health[i].transmit_error_cnt >= 128) {
|
||||
(void)llcan_init(CANIF_FROM_CAN_NUM(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// decimated to 1Hz
|
||||
if ((loop_counter % 8) == 0U) {
|
||||
#ifdef DEBUG
|
||||
print("** blink ");
|
||||
print("rx:"); puth4(can_rx_q.r_ptr); print("-"); puth4(can_rx_q.w_ptr); print(" ");
|
||||
print("tx1:"); puth4(can_tx1_q.r_ptr); print("-"); puth4(can_tx1_q.w_ptr); print(" ");
|
||||
print("tx2:"); puth4(can_tx2_q.r_ptr); print("-"); puth4(can_tx2_q.w_ptr); print(" ");
|
||||
print("tx3:"); puth4(can_tx3_q.r_ptr); print("-"); puth4(can_tx3_q.w_ptr); print("\n");
|
||||
#endif
|
||||
|
||||
current_board->board_tick();
|
||||
|
||||
// check registers
|
||||
check_registers();
|
||||
|
||||
// turn off the blue LED, turned on by CAN
|
||||
led_set(LED_BLUE, false);
|
||||
|
||||
// on to the next one
|
||||
uptime_cnt += 1U;
|
||||
}
|
||||
|
||||
// Check on button
|
||||
bool current_button_status = current_board->get_button();
|
||||
|
||||
if (current_button_status && button_press_cnt == 10) {
|
||||
current_board->set_panda_power(!panda_power);
|
||||
}
|
||||
|
||||
// toggle ignition on button press
|
||||
static bool prev_button_status = false;
|
||||
if (!current_button_status && prev_button_status && button_press_cnt < 10){
|
||||
current_board->set_ignition(!ignition);
|
||||
}
|
||||
prev_button_status = current_button_status;
|
||||
|
||||
button_press_cnt = current_button_status ? button_press_cnt + 1 : 0;
|
||||
|
||||
loop_counter++;
|
||||
}
|
||||
TICK_TIMER->SR = 0;
|
||||
}
|
||||
|
||||
|
||||
int main(void) {
|
||||
// Init interrupt table
|
||||
init_interrupts(true);
|
||||
|
||||
// shouldn't have interrupts here, but just in case
|
||||
disable_interrupts();
|
||||
|
||||
// init early devices
|
||||
clock_init();
|
||||
peripherals_init();
|
||||
detect_board_type();
|
||||
// red+green leds enabled until succesful USB init, as a debug indicator
|
||||
led_set(LED_RED, true);
|
||||
led_set(LED_GREEN, true);
|
||||
|
||||
// print hello
|
||||
print("\n\n\n************************ MAIN START ************************\n");
|
||||
|
||||
// check for non-supported board types
|
||||
assert_fatal(hw_type != HW_TYPE_UNKNOWN, "Unsupported board type\n");
|
||||
|
||||
print("Config:\n");
|
||||
print(" Board type: 0x"); puth(hw_type); print("\n");
|
||||
|
||||
// init board
|
||||
current_board->init();
|
||||
|
||||
// we have an FPU, let's use it!
|
||||
enable_fpu();
|
||||
|
||||
microsecond_timer_init();
|
||||
|
||||
// 8Hz timer
|
||||
REGISTER_INTERRUPT(TICK_TIMER_IRQ, tick_handler, 10U, FAULT_INTERRUPT_RATE_TICK)
|
||||
tick_timer_init();
|
||||
|
||||
#ifdef DEBUG
|
||||
print("DEBUG ENABLED\n");
|
||||
#endif
|
||||
// enable USB (right before interrupts or enum can fail!)
|
||||
usb_init();
|
||||
|
||||
led_set(LED_RED, false);
|
||||
led_set(LED_GREEN, false);
|
||||
|
||||
print("**** INTERRUPTS ON ****\n");
|
||||
enable_interrupts();
|
||||
|
||||
can_silent = false;
|
||||
set_safety_hooks(SAFETY_ALLOUTPUT, 0U);
|
||||
|
||||
can_init_all();
|
||||
current_board->set_harness_orientation(HARNESS_ORIENTATION_1);
|
||||
|
||||
// LED should keep on blinking all the time
|
||||
uint32_t cnt = 0;
|
||||
for (cnt=0;;cnt++) {
|
||||
if (generated_can_traffic) {
|
||||
// fill up all the queues
|
||||
can_ring *qs[] = {&can_tx1_q, &can_tx2_q, &can_tx3_q};
|
||||
for (int j = 0; j < 3; j++) {
|
||||
for (uint16_t n = 0U; n < can_slots_empty(qs[j]); n++) {
|
||||
uint16_t i = cnt % 100U;
|
||||
CANPacket_t to_send;
|
||||
to_send.returned = 0U;
|
||||
to_send.rejected = 0U;
|
||||
to_send.extended = 0U;
|
||||
to_send.addr = 0x200U + i;
|
||||
to_send.bus = i % 3U;
|
||||
to_send.data_len_code = i % 8U;
|
||||
(void)memcpy(to_send.data, "\xff\xff\xff\xff\xff\xff\xff\xff", dlc_to_len[to_send.data_len_code]);
|
||||
can_set_checksum(&to_send);
|
||||
|
||||
can_send(&to_send, to_send.bus, true);
|
||||
}
|
||||
}
|
||||
|
||||
delay(1000);
|
||||
continue;
|
||||
}
|
||||
|
||||
// useful for debugging, fade breaks = panda is overloaded
|
||||
for (uint32_t fade = 0U; fade < MAX_LED_FADE; fade += 1U) {
|
||||
led_set(LED_RED, true);
|
||||
delay(fade >> 4);
|
||||
led_set(LED_RED, false);
|
||||
delay((MAX_LED_FADE - fade) >> 4);
|
||||
}
|
||||
|
||||
for (uint32_t fade = MAX_LED_FADE; fade > 0U; fade -= 1U) {
|
||||
led_set(LED_RED, true);
|
||||
delay(fade >> 4);
|
||||
led_set(LED_RED, false);
|
||||
delay((MAX_LED_FADE - fade) >> 4);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
extern int _app_start[0xc000]; // Only first 3 sectors of size 0x4000 are used
|
||||
|
||||
bool generated_can_traffic = false;
|
||||
|
||||
int get_jungle_health_pkt(void *dat) {
|
||||
COMPILE_TIME_ASSERT(sizeof(struct jungle_health_t) <= USBPACKET_MAX_SIZE);
|
||||
struct jungle_health_t * health = (struct jungle_health_t*)dat;
|
||||
|
||||
health->uptime_pkt = uptime_cnt;
|
||||
health->ch1_power = current_board->get_channel_power(1U);
|
||||
health->ch2_power = current_board->get_channel_power(2U);
|
||||
health->ch3_power = current_board->get_channel_power(3U);
|
||||
health->ch4_power = current_board->get_channel_power(4U);
|
||||
health->ch5_power = current_board->get_channel_power(5U);
|
||||
health->ch6_power = current_board->get_channel_power(6U);
|
||||
|
||||
health->ch1_sbu1_mV = current_board->get_sbu_mV(1U, SBU1);
|
||||
health->ch1_sbu2_mV = current_board->get_sbu_mV(1U, SBU2);
|
||||
health->ch2_sbu1_mV = current_board->get_sbu_mV(2U, SBU1);
|
||||
health->ch2_sbu2_mV = current_board->get_sbu_mV(2U, SBU2);
|
||||
health->ch3_sbu1_mV = current_board->get_sbu_mV(3U, SBU1);
|
||||
health->ch3_sbu2_mV = current_board->get_sbu_mV(3U, SBU2);
|
||||
health->ch4_sbu1_mV = current_board->get_sbu_mV(4U, SBU1);
|
||||
health->ch4_sbu2_mV = current_board->get_sbu_mV(4U, SBU2);
|
||||
health->ch5_sbu1_mV = current_board->get_sbu_mV(5U, SBU1);
|
||||
health->ch5_sbu2_mV = current_board->get_sbu_mV(5U, SBU2);
|
||||
health->ch6_sbu1_mV = current_board->get_sbu_mV(6U, SBU1);
|
||||
health->ch6_sbu2_mV = current_board->get_sbu_mV(6U, SBU2);
|
||||
|
||||
return sizeof(*health);
|
||||
}
|
||||
|
||||
// send on serial, first byte to select the ring
|
||||
void comms_endpoint2_write(const uint8_t *data, uint32_t len) {
|
||||
UNUSED(data);
|
||||
UNUSED(len);
|
||||
}
|
||||
|
||||
int comms_control_handler(ControlPacket_t *req, uint8_t *resp) {
|
||||
unsigned int resp_len = 0;
|
||||
uint32_t time;
|
||||
|
||||
#ifdef DEBUG_COMMS
|
||||
print("raw control request: "); hexdump(req, sizeof(ControlPacket_t)); print("\n");
|
||||
print("- request "); puth(req->request); print("\n");
|
||||
print("- param1 "); puth(req->param1); print("\n");
|
||||
print("- param2 "); puth(req->param2); print("\n");
|
||||
#endif
|
||||
|
||||
switch (req->request) {
|
||||
// **** 0xa0: Set panda power.
|
||||
case 0xa0:
|
||||
current_board->set_panda_power((req->param1 == 1U));
|
||||
break;
|
||||
// **** 0xa1: Set harness orientation.
|
||||
case 0xa1:
|
||||
current_board->set_harness_orientation(req->param1);
|
||||
break;
|
||||
// **** 0xa2: Set ignition.
|
||||
case 0xa2:
|
||||
current_board->set_ignition((req->param1 == 1U));
|
||||
break;
|
||||
// **** 0xa3: Set panda power per channel by bitmask.
|
||||
case 0xa3:
|
||||
current_board->set_panda_individual_power(req->param1, (req->param2 > 0U));
|
||||
break;
|
||||
// **** 0xa4: Enable generated CAN traffic.
|
||||
case 0xa4:
|
||||
generated_can_traffic = (req->param1 > 0U);
|
||||
break;
|
||||
// **** 0xa8: get microsecond timer
|
||||
case 0xa8:
|
||||
time = microsecond_timer_get();
|
||||
resp[0] = (time & 0x000000FFU);
|
||||
resp[1] = ((time & 0x0000FF00U) >> 8U);
|
||||
resp[2] = ((time & 0x00FF0000U) >> 16U);
|
||||
resp[3] = ((time & 0xFF000000U) >> 24U);
|
||||
resp_len = 4U;
|
||||
break;
|
||||
// **** 0xc0: reset communications
|
||||
case 0xc0:
|
||||
comms_can_reset();
|
||||
break;
|
||||
// **** 0xc1: get hardware type
|
||||
case 0xc1:
|
||||
resp[0] = hw_type;
|
||||
resp_len = 1;
|
||||
break;
|
||||
// **** 0xc2: CAN health stats
|
||||
case 0xc2:
|
||||
COMPILE_TIME_ASSERT(sizeof(can_health_t) <= USBPACKET_MAX_SIZE);
|
||||
if (req->param1 < 3U) {
|
||||
update_can_health_pkt(req->param1, 0U);
|
||||
can_health[req->param1].can_speed = (bus_config[req->param1].can_speed / 10U);
|
||||
can_health[req->param1].can_data_speed = (bus_config[req->param1].can_data_speed / 10U);
|
||||
can_health[req->param1].canfd_enabled = bus_config[req->param1].canfd_enabled;
|
||||
can_health[req->param1].brs_enabled = bus_config[req->param1].brs_enabled;
|
||||
can_health[req->param1].canfd_non_iso = bus_config[req->param1].canfd_non_iso;
|
||||
resp_len = sizeof(can_health[req->param1]);
|
||||
(void)memcpy(resp, &can_health[req->param1], resp_len);
|
||||
}
|
||||
break;
|
||||
// **** 0xc3: fetch MCU UID
|
||||
case 0xc3:
|
||||
(void)memcpy(resp, ((uint8_t *)UID_BASE), 12);
|
||||
resp_len = 12;
|
||||
break;
|
||||
// **** 0xd0: fetch serial (aka the provisioned dongle ID)
|
||||
case 0xd0:
|
||||
// addresses are OTP
|
||||
if (req->param1 == 1U) {
|
||||
(void)memcpy(resp, (uint8_t *)DEVICE_SERIAL_NUMBER_ADDRESS, 0x10);
|
||||
resp_len = 0x10;
|
||||
} else {
|
||||
get_provision_chunk(resp);
|
||||
resp_len = PROVISION_CHUNK_LEN;
|
||||
}
|
||||
break;
|
||||
// **** 0xd1: enter bootloader mode
|
||||
case 0xd1:
|
||||
// this allows reflashing of the bootstub
|
||||
switch (req->param1) {
|
||||
case 0:
|
||||
// only allow bootloader entry on debug builds
|
||||
#ifdef ALLOW_DEBUG
|
||||
print("-> entering bootloader\n");
|
||||
enter_bootloader_mode = ENTER_BOOTLOADER_MAGIC;
|
||||
NVIC_SystemReset();
|
||||
#endif
|
||||
break;
|
||||
case 1:
|
||||
print("-> entering softloader\n");
|
||||
enter_bootloader_mode = ENTER_SOFTLOADER_MAGIC;
|
||||
NVIC_SystemReset();
|
||||
break;
|
||||
default:
|
||||
print("Bootloader mode invalid\n");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
// **** 0xd2: get health packet
|
||||
case 0xd2:
|
||||
resp_len = get_jungle_health_pkt(resp);
|
||||
break;
|
||||
// **** 0xd3: get first 64 bytes of signature
|
||||
case 0xd3:
|
||||
{
|
||||
resp_len = 64;
|
||||
char * code = (char*)_app_start;
|
||||
int code_len = _app_start[0];
|
||||
(void)memcpy(resp, &code[code_len], resp_len);
|
||||
}
|
||||
break;
|
||||
// **** 0xd4: get second 64 bytes of signature
|
||||
case 0xd4:
|
||||
{
|
||||
resp_len = 64;
|
||||
char * code = (char*)_app_start;
|
||||
int code_len = _app_start[0];
|
||||
(void)memcpy(resp, &code[code_len + 64], resp_len);
|
||||
}
|
||||
break;
|
||||
// **** 0xd6: get version
|
||||
case 0xd6:
|
||||
COMPILE_TIME_ASSERT(sizeof(gitversion) <= USBPACKET_MAX_SIZE);
|
||||
(void)memcpy(resp, gitversion, sizeof(gitversion));
|
||||
resp_len = sizeof(gitversion) - 1U;
|
||||
break;
|
||||
// **** 0xd8: reset ST
|
||||
case 0xd8:
|
||||
NVIC_SystemReset();
|
||||
break;
|
||||
// **** 0xdb: set OBD CAN multiplexing mode
|
||||
case 0xdb:
|
||||
if (req->param1 == 1U) {
|
||||
// Enable OBD CAN
|
||||
current_board->set_can_mode(CAN_MODE_OBD_CAN2);
|
||||
} else {
|
||||
// Disable OBD CAN
|
||||
current_board->set_can_mode(CAN_MODE_NORMAL);
|
||||
}
|
||||
break;
|
||||
// **** 0xdd: get healthpacket and CANPacket version hashes
|
||||
case 0xdd: {
|
||||
uint32_t versions[2] = {JUNGLE_HEALTH_PACKET_VERSION, CAN_PACKET_VERSION_HASH};
|
||||
(void)memcpy(resp, (uint8_t *)versions, sizeof(versions));
|
||||
resp_len = sizeof(versions);
|
||||
break;
|
||||
}
|
||||
// **** 0xde: set can bitrate
|
||||
case 0xde:
|
||||
if ((req->param1 < PANDA_CAN_CNT) && is_speed_valid(req->param2, speeds, sizeof(speeds)/sizeof(speeds[0]))) {
|
||||
bus_config[req->param1].can_speed = req->param2;
|
||||
bool ret = can_init(CAN_NUM_FROM_BUS_NUM(req->param1));
|
||||
UNUSED(ret);
|
||||
}
|
||||
break;
|
||||
// **** 0xe0: debug read
|
||||
case 0xe0:
|
||||
// read
|
||||
while ((resp_len < MIN(req->length, USBPACKET_MAX_SIZE)) && get_char(get_ring_by_number(0), (char*)&resp[resp_len])) {
|
||||
++resp_len;
|
||||
}
|
||||
break;
|
||||
// **** 0xe5: set CAN loopback (for testing)
|
||||
case 0xe5:
|
||||
can_loopback = (req->param1 > 0U);
|
||||
can_init_all();
|
||||
break;
|
||||
// **** 0xf1: Clear CAN ring buffer.
|
||||
case 0xf1:
|
||||
if (req->param1 == 0xFFFFU) {
|
||||
print("Clearing CAN Rx queue\n");
|
||||
can_clear(&can_rx_q);
|
||||
} else if (req->param1 < PANDA_CAN_CNT) {
|
||||
print("Clearing CAN Tx queue\n");
|
||||
can_clear(can_queues[req->param1]);
|
||||
} else {
|
||||
print("Clearing CAN CAN ring buffer failed: wrong bus number\n");
|
||||
}
|
||||
break;
|
||||
// **** 0xf4: Set CAN transceiver enable pin
|
||||
case 0xf4:
|
||||
current_board->enable_can_transceiver(req->param1, req->param2 > 0U);
|
||||
break;
|
||||
// **** 0xf5: Set CAN silent mode
|
||||
case 0xf5:
|
||||
can_silent = (req->param1 > 0U);
|
||||
can_init_all();
|
||||
break;
|
||||
// **** 0xf7: enable/disable header pin by number
|
||||
case 0xf7:
|
||||
current_board->enable_header_pin(req->param1, req->param2 > 0U);
|
||||
break;
|
||||
// **** 0xf9: set CAN FD data bitrate
|
||||
case 0xf9:
|
||||
if ((req->param1 < PANDA_CAN_CNT) &&
|
||||
is_speed_valid(req->param2, data_speeds, sizeof(data_speeds)/sizeof(data_speeds[0]))) {
|
||||
bus_config[req->param1].can_data_speed = req->param2;
|
||||
bus_config[req->param1].canfd_enabled = (req->param2 >= bus_config[req->param1].can_speed);
|
||||
bus_config[req->param1].brs_enabled = (req->param2 > bus_config[req->param1].can_speed);
|
||||
bool ret = can_init(CAN_NUM_FROM_BUS_NUM(req->param1));
|
||||
UNUSED(ret);
|
||||
}
|
||||
break;
|
||||
// **** 0xfc: set CAN FD non-ISO mode
|
||||
case 0xfc:
|
||||
if (req->param1 < PANDA_CAN_CNT) {
|
||||
bus_config[req->param1].canfd_non_iso = (req->param2 != 0U);
|
||||
bool ret = can_init(CAN_NUM_FROM_BUS_NUM(req->param1));
|
||||
UNUSED(ret);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
print("NO HANDLER ");
|
||||
puth(req->request);
|
||||
print("\n");
|
||||
break;
|
||||
}
|
||||
return resp_len;
|
||||
}
|
||||
Executable
+33
@@ -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)
|
||||
Executable
+29
@@ -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)
|
||||
Executable
+44
@@ -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()
|
||||
Executable
+38
@@ -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)
|
||||
+68
@@ -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")
|
||||
Executable
+9
@@ -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()}")
|
||||
|
||||
|
||||
Executable
+21
@@ -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
|
||||
|
||||
Executable
+141
@@ -0,0 +1,141 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import time
|
||||
import contextlib
|
||||
import random
|
||||
from termcolor import cprint
|
||||
|
||||
from opendbc.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")
|
||||
Executable
+21
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import random
|
||||
from opendbc.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)
|
||||
Executable
+18
@@ -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)
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
#include "board/jungle/boards/board_declarations.h"
|
||||
|
||||
#include "board/stm32h7/lladc.h"
|
||||
#include "board/jungle/boards/board_v2.h"
|
||||
|
||||
void detect_board_type(void) {
|
||||
hw_type = HW_TYPE_V2;
|
||||
current_board = &board_v2;
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
// **** libc ****
|
||||
|
||||
__attribute__((aligned(32), noinline)) void delay(uint32_t a) {
|
||||
// loop is 2.6x faster when 32-byte aligned (ART accelerator prefetches flash in 32-byte chunks)
|
||||
volatile uint32_t i;
|
||||
uint32_t n = a * 13U / 5U;
|
||||
for (i = 0; i < n; i++) {}
|
||||
}
|
||||
|
||||
void assert_fatal(bool condition, const char *msg) {
|
||||
if (!condition) {
|
||||
print("ASSERT FAILED\n");
|
||||
print(msg);
|
||||
while (1) {
|
||||
// hang
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// cppcheck-suppress misra-c2012-21.2
|
||||
void *memset(void *str, int c, unsigned int n) {
|
||||
uint8_t *s = str;
|
||||
for (unsigned int i = 0; i < n; i++) {
|
||||
*s = c;
|
||||
s++;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
#define UNALIGNED(X, Y) \
|
||||
(((uint32_t)(X) & (sizeof(uint32_t) - 1U)) | ((uint32_t)(Y) & (sizeof(uint32_t) - 1U)))
|
||||
|
||||
// cppcheck-suppress misra-c2012-21.2
|
||||
void *memcpy(void *dest, const void *src, unsigned int len) {
|
||||
unsigned int n = len;
|
||||
uint8_t *d8 = dest;
|
||||
const uint8_t *s8 = src;
|
||||
|
||||
if ((n >= 4U) && !UNALIGNED(s8, d8)) {
|
||||
uint32_t *d32 = (uint32_t *)d8; // cppcheck-suppress misra-c2012-11.3 ; already checked that it's properly aligned
|
||||
const uint32_t *s32 = (const uint32_t *)s8; // cppcheck-suppress misra-c2012-11.3 ; already checked that it's properly aligned
|
||||
|
||||
while(n >= 16U) {
|
||||
*d32 = *s32; d32++; s32++;
|
||||
*d32 = *s32; d32++; s32++;
|
||||
*d32 = *s32; d32++; s32++;
|
||||
*d32 = *s32; d32++; s32++;
|
||||
n -= 16U;
|
||||
}
|
||||
|
||||
while(n >= 4U) {
|
||||
*d32 = *s32; d32++; s32++;
|
||||
n -= 4U;
|
||||
}
|
||||
|
||||
d8 = (uint8_t *)d32;
|
||||
s8 = (const uint8_t *)s32;
|
||||
}
|
||||
while (n-- > 0U) {
|
||||
*d8 = *s8; d8++; s8++;
|
||||
}
|
||||
return dest;
|
||||
}
|
||||
|
||||
// cppcheck-suppress misra-c2012-21.2
|
||||
int memcmp(const void * ptr1, const void * ptr2, unsigned int num) {
|
||||
int ret = 0;
|
||||
const uint8_t *p1 = ptr1;
|
||||
const uint8_t *p2 = ptr2;
|
||||
for (unsigned int i = 0; i < num; i++) {
|
||||
if (*p1 != *p2) {
|
||||
ret = -1;
|
||||
break;
|
||||
}
|
||||
p1++;
|
||||
p2++;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,388 @@
|
||||
// ********************* Includes *********************
|
||||
#include "board/config.h"
|
||||
|
||||
#include "board/drivers/led.h"
|
||||
#include "board/drivers/pwm.h"
|
||||
#include "board/drivers/usb.h"
|
||||
#include "board/drivers/simple_watchdog.h"
|
||||
#include "board/drivers/bootkick.h"
|
||||
|
||||
#include "board/early_init.h"
|
||||
#include "board/provision.h"
|
||||
|
||||
#include "opendbc/safety/safety.h"
|
||||
|
||||
#include "board/health.h"
|
||||
|
||||
#include "board/drivers/can_common.h"
|
||||
|
||||
#include "board/drivers/fdcan.h"
|
||||
|
||||
#include "board/sys/power_saving.h"
|
||||
|
||||
#include "board/obj/gitversion.h"
|
||||
|
||||
#include "board/can_comms.h"
|
||||
#include "board/main_comms.h"
|
||||
|
||||
|
||||
// ********************* Serial debugging *********************
|
||||
|
||||
void debug_ring_callback(uart_ring *ring) {
|
||||
char rcv;
|
||||
while (get_char(ring, &rcv)) {
|
||||
(void)put_char(ring, rcv); // misra-c2012-17.7: cast to void is ok: debug function
|
||||
}
|
||||
}
|
||||
|
||||
// ****************************** safety mode ******************************
|
||||
|
||||
// this is the only way to leave silent mode
|
||||
void set_safety_mode(uint16_t mode, uint16_t param) {
|
||||
uint16_t mode_copy = mode;
|
||||
int err = set_safety_hooks(mode_copy, param);
|
||||
if (err == -1) {
|
||||
print("Error: safety set mode failed. Falling back to SILENT\n");
|
||||
mode_copy = SAFETY_SILENT;
|
||||
err = set_safety_hooks(mode_copy, 0U);
|
||||
// TERMINAL ERROR: we can't continue if SILENT safety mode isn't succesfully set
|
||||
assert_fatal(err == 0, "Error: Failed setting SILENT mode. Hanging\n");
|
||||
}
|
||||
safety_tx_blocked = 0;
|
||||
safety_rx_invalid = 0;
|
||||
|
||||
switch (mode_copy) {
|
||||
case SAFETY_SILENT:
|
||||
set_intercept_relay(false, false);
|
||||
current_board->set_can_mode(CAN_MODE_NORMAL);
|
||||
can_silent = true;
|
||||
break;
|
||||
case SAFETY_NOOUTPUT:
|
||||
set_intercept_relay(false, false);
|
||||
current_board->set_can_mode(CAN_MODE_NORMAL);
|
||||
can_silent = false;
|
||||
break;
|
||||
case SAFETY_ELM327:
|
||||
set_intercept_relay(false, false);
|
||||
heartbeat_counter = 0U;
|
||||
heartbeat_lost = false;
|
||||
|
||||
// Clear any pending messages in the can core (i.e. sending while comma power is unplugged)
|
||||
// TODO: rewrite using hardware queues rather than fifo to cancel specific messages
|
||||
can_clear_send(CANIF_FROM_CAN_NUM(1), 1);
|
||||
if (param == 0U) {
|
||||
current_board->set_can_mode(CAN_MODE_OBD_CAN2);
|
||||
} else {
|
||||
current_board->set_can_mode(CAN_MODE_NORMAL);
|
||||
}
|
||||
can_silent = false;
|
||||
break;
|
||||
default:
|
||||
set_intercept_relay(true, false);
|
||||
heartbeat_counter = 0U;
|
||||
heartbeat_lost = false;
|
||||
current_board->set_can_mode(CAN_MODE_NORMAL);
|
||||
can_silent = false;
|
||||
break;
|
||||
}
|
||||
can_init_all();
|
||||
}
|
||||
|
||||
bool is_car_safety_mode(uint16_t mode) {
|
||||
return (mode != SAFETY_SILENT) &&
|
||||
(mode != SAFETY_NOOUTPUT) &&
|
||||
(mode != SAFETY_ALLOUTPUT) &&
|
||||
(mode != SAFETY_ELM327);
|
||||
}
|
||||
|
||||
// ***************************** main code *****************************
|
||||
|
||||
// cppcheck-suppress unusedFunction ; used in headers not included in cppcheck
|
||||
// cppcheck-suppress misra-c2012-8.4
|
||||
void __initialize_hardware_early(void) {
|
||||
early_initialization();
|
||||
}
|
||||
|
||||
static void __attribute__ ((noinline)) enable_fpu(void) {
|
||||
// enable the FPU
|
||||
SCB->CPACR |= ((3UL << (10U * 2U)) | (3UL << (11U * 2U)));
|
||||
}
|
||||
|
||||
// go into SILENT when heartbeat isn't received for this amount of seconds.
|
||||
#define HEARTBEAT_IGNITION_CNT_ON 5U
|
||||
#define HEARTBEAT_IGNITION_CNT_OFF 2U
|
||||
|
||||
// called at 8Hz
|
||||
static void tick_handler(void) {
|
||||
static uint32_t siren_countdown = 0; // siren plays while countdown > 0
|
||||
static uint32_t controls_allowed_countdown = 0;
|
||||
static uint8_t prev_harness_status = HARNESS_STATUS_NC;
|
||||
static uint8_t loop_counter = 0U;
|
||||
static bool relay_malfunction_prev = false;
|
||||
|
||||
if (TICK_TIMER->SR != 0U) {
|
||||
|
||||
// siren
|
||||
current_board->set_siren((loop_counter & 1U) && (siren_enabled || (siren_countdown > 0U)));
|
||||
|
||||
// tick drivers at 8Hz
|
||||
fan_tick();
|
||||
harness_tick();
|
||||
simple_watchdog_kick();
|
||||
sound_tick();
|
||||
|
||||
if (relay_malfunction_prev != relay_malfunction) {
|
||||
if (relay_malfunction) {
|
||||
fault_occurred(FAULT_RELAY_MALFUNCTION);
|
||||
} else {
|
||||
fault_recovered(FAULT_RELAY_MALFUNCTION);
|
||||
}
|
||||
}
|
||||
relay_malfunction_prev = relay_malfunction;
|
||||
|
||||
// re-init everything that uses harness status
|
||||
if (harness.status != prev_harness_status) {
|
||||
prev_harness_status = harness.status;
|
||||
can_set_orientation(harness.status == HARNESS_STATUS_FLIPPED);
|
||||
|
||||
// re-init everything that uses harness status
|
||||
can_init_all();
|
||||
set_safety_mode(current_safety_mode, current_safety_param);
|
||||
set_power_save_state(power_save_enabled);
|
||||
}
|
||||
|
||||
// decimated to 1Hz
|
||||
if (loop_counter == 0U) {
|
||||
//puth(usart1_dma); print(" "); puth(DMA2_Stream5->M0AR); print(" "); puth(DMA2_Stream5->NDTR); print("\n");
|
||||
#ifdef DEBUG
|
||||
print("** blink ");
|
||||
print("rx:"); puth4(can_rx_q.r_ptr); print("-"); puth4(can_rx_q.w_ptr); print(" ");
|
||||
print("tx1:"); puth4(can_tx1_q.r_ptr); print("-"); puth4(can_tx1_q.w_ptr); print(" ");
|
||||
print("tx2:"); puth4(can_tx2_q.r_ptr); print("-"); puth4(can_tx2_q.w_ptr); print(" ");
|
||||
print("tx3:"); puth4(can_tx3_q.r_ptr); print("-"); puth4(can_tx3_q.w_ptr); print("\n");
|
||||
#endif
|
||||
|
||||
// set green LED to be controls allowed
|
||||
led_set(LED_GREEN, controls_allowed);
|
||||
|
||||
// turn off the blue LED, turned on by CAN
|
||||
// unless we are in power saving mode
|
||||
led_set(LED_BLUE, (uptime_cnt & 1U) && power_save_enabled);
|
||||
|
||||
const bool recent_heartbeat = heartbeat_counter == 0U;
|
||||
|
||||
// tick drivers at 1Hz
|
||||
bool started = harness_check_ignition() || ignition_can;
|
||||
bootkick_tick(started, recent_heartbeat);
|
||||
|
||||
// increase heartbeat counter and cap it at the uint32 limit
|
||||
if (heartbeat_counter < UINT32_MAX) {
|
||||
heartbeat_counter += 1U;
|
||||
}
|
||||
|
||||
// disabling heartbeat not allowed while in safety mode
|
||||
if (is_car_safety_mode(current_safety_mode)) {
|
||||
heartbeat_disabled = false;
|
||||
}
|
||||
|
||||
if (siren_countdown > 0U) {
|
||||
siren_countdown -= 1U;
|
||||
}
|
||||
|
||||
if (controls_allowed || heartbeat_engaged) {
|
||||
controls_allowed_countdown = 5U;
|
||||
} else if (controls_allowed_countdown > 0U) {
|
||||
controls_allowed_countdown -= 1U;
|
||||
} else {
|
||||
|
||||
}
|
||||
|
||||
// exit controls allowed if unused by openpilot for a few seconds
|
||||
if (controls_allowed && !heartbeat_engaged) {
|
||||
heartbeat_engaged_mismatches += 1U;
|
||||
if (heartbeat_engaged_mismatches >= 3U) {
|
||||
controls_allowed = false;
|
||||
}
|
||||
} else {
|
||||
heartbeat_engaged_mismatches = 0U;
|
||||
}
|
||||
|
||||
if (!heartbeat_disabled) {
|
||||
// if the heartbeat has been gone for a while, go to SILENT safety mode and enter power save
|
||||
if (heartbeat_counter >= (started ? HEARTBEAT_IGNITION_CNT_ON : HEARTBEAT_IGNITION_CNT_OFF)) {
|
||||
print("device hasn't sent a heartbeat for 0x");
|
||||
puth(heartbeat_counter);
|
||||
print(" seconds. Safety is set to SILENT mode.\n");
|
||||
|
||||
if (controls_allowed_countdown > 0U) {
|
||||
siren_countdown = 3U;
|
||||
controls_allowed_countdown = 0U;
|
||||
}
|
||||
|
||||
// set flag to indicate the heartbeat was lost
|
||||
if (is_car_safety_mode(current_safety_mode)) {
|
||||
heartbeat_lost = true;
|
||||
}
|
||||
|
||||
// clear heartbeat engaged state
|
||||
heartbeat_engaged = false;
|
||||
|
||||
if (current_safety_mode != SAFETY_SILENT) {
|
||||
set_safety_mode(SAFETY_SILENT, 0U);
|
||||
}
|
||||
|
||||
if (!power_save_enabled) {
|
||||
set_power_save_state(true);
|
||||
}
|
||||
|
||||
// Also disable IR when the heartbeat goes missing
|
||||
current_board->set_ir_power(0U);
|
||||
|
||||
// Run fan when device is up but not talking to us.
|
||||
// The bootloader enables the SOM GPIO on boot.
|
||||
fan_set_power(current_board->read_som_gpio() ? 30U : 0U);
|
||||
}
|
||||
}
|
||||
|
||||
// check registers
|
||||
check_registers();
|
||||
|
||||
// set ignition_can to false after 2s of no CAN seen
|
||||
if (ignition_can_cnt > 2U) {
|
||||
ignition_can = false;
|
||||
}
|
||||
|
||||
// on to the next one
|
||||
uptime_cnt += 1U;
|
||||
safety_mode_cnt += 1U;
|
||||
ignition_can_cnt += 1U;
|
||||
|
||||
// synchronous safety check
|
||||
safety_tick(¤t_safety_config);
|
||||
}
|
||||
|
||||
loop_counter++;
|
||||
loop_counter %= 8U;
|
||||
}
|
||||
TICK_TIMER->SR = 0;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
// Init interrupt table
|
||||
init_interrupts(true);
|
||||
|
||||
// shouldn't have interrupts here, but just in case
|
||||
disable_interrupts();
|
||||
|
||||
// init early devices
|
||||
clock_init();
|
||||
peripherals_init();
|
||||
detect_board_type();
|
||||
led_init();
|
||||
// red+green leds enabled until succesful USB/SPI init, as a debug indicator
|
||||
led_set(LED_RED, true);
|
||||
led_set(LED_GREEN, true);
|
||||
adc_init(ADC1);
|
||||
|
||||
// print hello
|
||||
print("\n\n\n************************ MAIN START ************************\n");
|
||||
|
||||
// check for non-supported board types
|
||||
assert_fatal(hw_type != HW_TYPE_UNKNOWN, "Unsupported board type");
|
||||
|
||||
print("Config:\n");
|
||||
print(" Board type: 0x"); puth(hw_type); print("\n");
|
||||
|
||||
// init board
|
||||
current_board->init();
|
||||
current_board->set_can_mode(CAN_MODE_NORMAL);
|
||||
harness_init();
|
||||
|
||||
// panda has an FPU, let's use it!
|
||||
enable_fpu();
|
||||
|
||||
microsecond_timer_init();
|
||||
|
||||
current_board->set_siren(false);
|
||||
if (current_board->has_fan) {
|
||||
fan_init();
|
||||
}
|
||||
|
||||
// init to SILENT and can silent
|
||||
set_safety_mode(SAFETY_SILENT, 0U);
|
||||
|
||||
// enable CAN TXs
|
||||
enable_can_transceivers(true);
|
||||
|
||||
// init watchdog for heartbeat loop, fed at 8Hz
|
||||
simple_watchdog_init(FAULT_HEARTBEAT_LOOP_WATCHDOG, (3U * 1000000U / 8U));
|
||||
|
||||
// 8Hz timer
|
||||
REGISTER_INTERRUPT(TICK_TIMER_IRQ, tick_handler, 10U, FAULT_INTERRUPT_RATE_TICK)
|
||||
tick_timer_init();
|
||||
|
||||
#ifdef DEBUG
|
||||
print("DEBUG ENABLED\n");
|
||||
#endif
|
||||
// enable USB (right before interrupts or enum can fail!)
|
||||
usb_init();
|
||||
|
||||
if (current_board->has_spi) {
|
||||
gpio_spi_init();
|
||||
spi_init();
|
||||
}
|
||||
|
||||
led_set(LED_RED, false);
|
||||
led_set(LED_GREEN, false);
|
||||
led_set(LED_BLUE, false);
|
||||
|
||||
print("**** INTERRUPTS ON ****\n");
|
||||
enable_interrupts();
|
||||
|
||||
// LED should keep on blinking all the time
|
||||
while (true) {
|
||||
#ifdef ALLOW_DEBUG
|
||||
if (stop_mode_requested) {
|
||||
enter_stop_mode();
|
||||
}
|
||||
#endif
|
||||
if (!power_save_enabled) {
|
||||
#ifdef DEBUG_FAULTS
|
||||
if (fault_status == FAULT_STATUS_NONE) {
|
||||
#endif
|
||||
// useful for debugging, fade breaks = panda is overloaded
|
||||
for (uint32_t fade = 0U; fade < MAX_LED_FADE; fade += 1U) {
|
||||
led_set(LED_RED, true);
|
||||
delay(fade >> 4);
|
||||
led_set(LED_RED, false);
|
||||
delay((MAX_LED_FADE - fade) >> 4);
|
||||
}
|
||||
|
||||
for (uint32_t fade = MAX_LED_FADE; fade > 0U; fade -= 1U) {
|
||||
led_set(LED_RED, true);
|
||||
delay(fade >> 4);
|
||||
led_set(LED_RED, false);
|
||||
delay((MAX_LED_FADE - fade) >> 4);
|
||||
}
|
||||
|
||||
#ifdef DEBUG_FAULTS
|
||||
} else {
|
||||
led_set(LED_RED, 1);
|
||||
delay(512000U);
|
||||
led_set(LED_RED, 0);
|
||||
delay(512000U);
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
if ((hw_type == HW_TYPE_CUATRO) && !current_board->read_som_gpio()) {
|
||||
assert_fatal(current_safety_mode == SAFETY_SILENT, "Error: Entering low power mode while not in SAFETY_SILENT. Hanging\n");
|
||||
enter_stop_mode(); // deep sleep, wakes on CAN or SBU activity
|
||||
assert_fatal(false, "Error: enter_stop_mode returned after system reset. Hanging\n");
|
||||
}
|
||||
__WFI();
|
||||
SCB->SCR &= ~SCB_SCR_SLEEPDEEP_Msk;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,339 @@
|
||||
extern int _app_start[0xc000]; // Only first 3 sectors of size 0x4000 are used
|
||||
|
||||
// Prototypes
|
||||
void set_safety_mode(uint16_t mode, uint16_t param);
|
||||
bool is_car_safety_mode(uint16_t mode);
|
||||
|
||||
static int get_health_pkt(void *dat) {
|
||||
COMPILE_TIME_ASSERT(sizeof(struct health_t) <= USBPACKET_MAX_SIZE);
|
||||
struct health_t * health = (struct health_t*)dat;
|
||||
|
||||
health->uptime_pkt = uptime_cnt;
|
||||
health->voltage_pkt = current_board->read_voltage_mV();
|
||||
health->current_pkt = current_board->read_current_mA();
|
||||
|
||||
health->ignition_line_pkt = (uint8_t)(harness_check_ignition());
|
||||
health->ignition_can_pkt = ignition_can;
|
||||
|
||||
health->controls_allowed_pkt = controls_allowed;
|
||||
health->safety_tx_blocked_pkt = safety_tx_blocked;
|
||||
health->safety_rx_invalid_pkt = safety_rx_invalid;
|
||||
health->tx_buffer_overflow_pkt = tx_buffer_overflow;
|
||||
health->rx_buffer_overflow_pkt = rx_buffer_overflow;
|
||||
health->car_harness_status_pkt = harness.status;
|
||||
health->safety_mode_pkt = (uint8_t)(current_safety_mode);
|
||||
health->safety_param_pkt = current_safety_param;
|
||||
health->alternative_experience_pkt = alternative_experience;
|
||||
health->power_save_enabled_pkt = power_save_enabled;
|
||||
health->heartbeat_lost_pkt = heartbeat_lost;
|
||||
health->safety_rx_checks_invalid_pkt = safety_rx_checks_invalid;
|
||||
|
||||
health->spi_error_count_pkt = spi_error_count;
|
||||
|
||||
health->fault_status_pkt = fault_status;
|
||||
health->faults_pkt = faults;
|
||||
|
||||
health->interrupt_load_pkt = interrupt_load;
|
||||
|
||||
health->fan_power = fan_state.power;
|
||||
|
||||
health->sbu1_voltage_mV = harness.sbu1_voltage_mV;
|
||||
health->sbu2_voltage_mV = harness.sbu2_voltage_mV;
|
||||
|
||||
health->som_reset_triggered = bootkick_reset_triggered;
|
||||
|
||||
health->sound_output_level_pkt = sound_output_level;
|
||||
|
||||
return sizeof(*health);
|
||||
}
|
||||
|
||||
// send on serial, first byte to select the ring
|
||||
void comms_endpoint2_write(const uint8_t *data, uint32_t len) {
|
||||
uart_ring *ur = get_ring_by_number(data[0]);
|
||||
if ((len != 0U) && (ur != NULL)) {
|
||||
if ((data[0] < 2U) || (data[0] >= 4U)) {
|
||||
for (uint32_t i = 1; i < len; i++) {
|
||||
while (!put_char(ur, data[i])) {
|
||||
// wait
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int comms_control_handler(ControlPacket_t *req, uint8_t *resp) {
|
||||
unsigned int resp_len = 0;
|
||||
uart_ring *ur = NULL;
|
||||
uint32_t time;
|
||||
|
||||
#ifdef DEBUG_COMMS
|
||||
print("raw control request: "); hexdump(req, sizeof(ControlPacket_t)); print("\n");
|
||||
print("- request "); puth(req->request); print("\n");
|
||||
print("- param1 "); puth(req->param1); print("\n");
|
||||
print("- param2 "); puth(req->param2); print("\n");
|
||||
#endif
|
||||
|
||||
switch (req->request) {
|
||||
// **** 0xa8: get microsecond timer
|
||||
case 0xa8:
|
||||
time = microsecond_timer_get();
|
||||
resp[0] = (time & 0x000000FFU);
|
||||
resp[1] = ((time & 0x0000FF00U) >> 8U);
|
||||
resp[2] = ((time & 0x00FF0000U) >> 16U);
|
||||
resp[3] = ((time & 0xFF000000U) >> 24U);
|
||||
resp_len = 4U;
|
||||
break;
|
||||
// **** 0xb0: set IR power
|
||||
case 0xb0:
|
||||
current_board->set_ir_power(req->param1);
|
||||
break;
|
||||
// **** 0xb1: set fan power
|
||||
case 0xb1:
|
||||
fan_set_power(req->param1);
|
||||
break;
|
||||
// **** 0xb2: get fan rpm
|
||||
case 0xb2:
|
||||
resp[0] = (fan_state.rpm & 0x00FFU);
|
||||
resp[1] = ((fan_state.rpm & 0xFF00U) >> 8U);
|
||||
resp_len = 2;
|
||||
break;
|
||||
// **** 0xb5: request deep sleep, wakes on CAN or SBU
|
||||
#ifdef ALLOW_DEBUG
|
||||
case 0xb5:
|
||||
set_safety_mode(SAFETY_SILENT, 0U);
|
||||
set_power_save_state(true);
|
||||
stop_mode_requested = true;
|
||||
break;
|
||||
#endif
|
||||
// **** 0xc0: reset communications state
|
||||
case 0xc0:
|
||||
comms_can_reset();
|
||||
break;
|
||||
// **** 0xc1: get hardware type
|
||||
case 0xc1:
|
||||
resp[0] = hw_type;
|
||||
resp_len = 1;
|
||||
break;
|
||||
// **** 0xc2: CAN health stats
|
||||
case 0xc2:
|
||||
COMPILE_TIME_ASSERT(sizeof(can_health_t) <= USBPACKET_MAX_SIZE);
|
||||
if (req->param1 < 3U) {
|
||||
update_can_health_pkt(req->param1, 0U);
|
||||
can_health[req->param1].can_speed = (bus_config[req->param1].can_speed / 10U);
|
||||
can_health[req->param1].can_data_speed = (bus_config[req->param1].can_data_speed / 10U);
|
||||
can_health[req->param1].canfd_enabled = bus_config[req->param1].canfd_enabled;
|
||||
can_health[req->param1].brs_enabled = bus_config[req->param1].brs_enabled;
|
||||
can_health[req->param1].canfd_non_iso = bus_config[req->param1].canfd_non_iso;
|
||||
resp_len = sizeof(can_health[req->param1]);
|
||||
(void)memcpy(resp, (uint8_t*)(&can_health[req->param1]), resp_len);
|
||||
}
|
||||
break;
|
||||
// **** 0xc3: fetch MCU UID
|
||||
case 0xc3:
|
||||
(void)memcpy(resp, ((uint8_t *)UID_BASE), 12);
|
||||
resp_len = 12;
|
||||
break;
|
||||
// **** 0xc4: get interrupt call rate
|
||||
case 0xc4:
|
||||
if (req->param1 < NUM_INTERRUPTS) {
|
||||
uint32_t load = interrupts[req->param1].call_rate;
|
||||
resp[0] = (load & 0x000000FFU);
|
||||
resp[1] = ((load & 0x0000FF00U) >> 8U);
|
||||
resp[2] = ((load & 0x00FF0000U) >> 16U);
|
||||
resp[3] = ((load & 0xFF000000U) >> 24U);
|
||||
resp_len = 4U;
|
||||
}
|
||||
break;
|
||||
// **** 0xc5: DEBUG: drive relay
|
||||
case 0xc5:
|
||||
set_intercept_relay((req->param1 & 0x1U), (req->param1 & 0x2U));
|
||||
break;
|
||||
// **** 0xc6: DEBUG: read SOM GPIO
|
||||
case 0xc6:
|
||||
resp[0] = current_board->read_som_gpio();
|
||||
resp_len = 1;
|
||||
break;
|
||||
// **** 0xd0: fetch serial (aka the provisioned dongle ID)
|
||||
case 0xd0:
|
||||
// addresses are OTP
|
||||
if (req->param1 == 1U) {
|
||||
(void)memcpy(resp, (uint8_t *)DEVICE_SERIAL_NUMBER_ADDRESS, 0x10);
|
||||
resp_len = 0x10;
|
||||
} else {
|
||||
get_provision_chunk(resp);
|
||||
resp_len = PROVISION_CHUNK_LEN;
|
||||
}
|
||||
break;
|
||||
// **** 0xd1: enter bootloader mode
|
||||
case 0xd1:
|
||||
// this allows reflashing of the bootstub
|
||||
switch (req->param1) {
|
||||
case 0:
|
||||
// only allow bootloader entry on debug builds
|
||||
#ifdef ALLOW_DEBUG
|
||||
print("-> entering bootloader\n");
|
||||
enter_bootloader_mode = ENTER_BOOTLOADER_MAGIC;
|
||||
NVIC_SystemReset();
|
||||
#endif
|
||||
break;
|
||||
case 1:
|
||||
print("-> entering softloader\n");
|
||||
enter_bootloader_mode = ENTER_SOFTLOADER_MAGIC;
|
||||
NVIC_SystemReset();
|
||||
break;
|
||||
default:
|
||||
print("Bootloader mode invalid\n");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
// **** 0xd2: get health packet
|
||||
case 0xd2:
|
||||
resp_len = get_health_pkt(resp);
|
||||
break;
|
||||
// **** 0xd3: get first 64 bytes of signature
|
||||
case 0xd3:
|
||||
{
|
||||
resp_len = 64;
|
||||
char * code = (char*)_app_start;
|
||||
int code_len = _app_start[0];
|
||||
(void)memcpy(resp, &code[code_len], resp_len);
|
||||
}
|
||||
break;
|
||||
// **** 0xd4: get second 64 bytes of signature
|
||||
case 0xd4:
|
||||
{
|
||||
resp_len = 64;
|
||||
char * code = (char*)_app_start;
|
||||
int code_len = _app_start[0];
|
||||
(void)memcpy(resp, &code[code_len + 64], resp_len);
|
||||
}
|
||||
break;
|
||||
// **** 0xd6: get version
|
||||
case 0xd6:
|
||||
COMPILE_TIME_ASSERT(sizeof(gitversion) <= USBPACKET_MAX_SIZE);
|
||||
(void)memcpy(resp, gitversion, sizeof(gitversion));
|
||||
resp_len = sizeof(gitversion) - 1U;
|
||||
break;
|
||||
// **** 0xd8: reset ST
|
||||
case 0xd8:
|
||||
NVIC_SystemReset();
|
||||
break;
|
||||
// **** 0xdb: set OBD CAN multiplexing mode
|
||||
case 0xdb:
|
||||
current_board->set_can_mode((req->param1 == 1U) ? CAN_MODE_OBD_CAN2 : CAN_MODE_NORMAL);
|
||||
break;
|
||||
// **** 0xdc: set safety mode
|
||||
case 0xdc:
|
||||
set_safety_mode(req->param1, (uint16_t)req->param2);
|
||||
break;
|
||||
// **** 0xdd: get health and CAN packet versions
|
||||
case 0xdd: {
|
||||
uint32_t versions[2] = {HEALTH_PACKET_VERSION, CAN_PACKET_VERSION_HASH};
|
||||
(void)memcpy(resp, (uint8_t *)versions, sizeof(versions));
|
||||
resp_len = sizeof(versions);
|
||||
break;
|
||||
}
|
||||
// **** 0xde: set can bitrate
|
||||
case 0xde:
|
||||
if ((req->param1 < PANDA_CAN_CNT) && is_speed_valid(req->param2, speeds, sizeof(speeds)/sizeof(speeds[0]))) {
|
||||
bus_config[req->param1].can_speed = req->param2;
|
||||
bool ret = can_init(CAN_NUM_FROM_BUS_NUM(req->param1));
|
||||
UNUSED(ret);
|
||||
}
|
||||
break;
|
||||
// **** 0xdf: set alternative experience
|
||||
case 0xdf:
|
||||
// you can only set this if you are in a non car safety mode
|
||||
if (!is_car_safety_mode(current_safety_mode)) {
|
||||
alternative_experience = req->param1;
|
||||
}
|
||||
break;
|
||||
// **** 0xe0: uart read
|
||||
case 0xe0:
|
||||
ur = get_ring_by_number(req->param1);
|
||||
if (!ur) {
|
||||
break;
|
||||
}
|
||||
|
||||
// read
|
||||
uint16_t req_length = MIN(req->length, USBPACKET_MAX_SIZE);
|
||||
while ((resp_len < req_length) &&
|
||||
get_char(ur, (char*)&resp[resp_len])) {
|
||||
++resp_len;
|
||||
}
|
||||
break;
|
||||
// **** 0xe5: set CAN loopback (for testing)
|
||||
case 0xe5:
|
||||
can_loopback = req->param1 > 0U;
|
||||
can_init_all();
|
||||
break;
|
||||
// **** 0xe6: set custom clock source period and pulse length
|
||||
case 0xe6:
|
||||
clock_source_set_timer_params(req->param1, req->param2);
|
||||
break;
|
||||
// **** 0xe7: set power save state
|
||||
case 0xe7:
|
||||
set_power_save_state(req->param1 != 0U);
|
||||
break;
|
||||
// **** 0xe8: set can-fd auto swithing mode
|
||||
case 0xe8:
|
||||
bus_config[req->param1].canfd_auto = req->param2 > 0U;
|
||||
break;
|
||||
// **** 0xf1: Clear CAN ring buffer.
|
||||
case 0xf1:
|
||||
if (req->param1 == 0xFFFFU) {
|
||||
print("Clearing CAN Rx queue\n");
|
||||
can_clear(&can_rx_q);
|
||||
} else if (req->param1 < PANDA_CAN_CNT) {
|
||||
print("Clearing CAN Tx queue\n");
|
||||
can_clear(can_queues[req->param1]);
|
||||
} else {
|
||||
print("Clearing CAN CAN ring buffer failed: wrong bus number\n");
|
||||
}
|
||||
break;
|
||||
// **** 0xf3: Heartbeat. Resets heartbeat counter.
|
||||
case 0xf3:
|
||||
{
|
||||
heartbeat_counter = 0U;
|
||||
heartbeat_lost = false;
|
||||
heartbeat_disabled = false;
|
||||
heartbeat_engaged = (req->param1 == 1U);
|
||||
break;
|
||||
}
|
||||
// **** 0xf6: set siren enabled
|
||||
case 0xf6:
|
||||
siren_enabled = (req->param1 != 0U);
|
||||
break;
|
||||
// **** 0xf8: disable heartbeat checks
|
||||
case 0xf8:
|
||||
if (!is_car_safety_mode(current_safety_mode)) {
|
||||
heartbeat_disabled = true;
|
||||
}
|
||||
break;
|
||||
// **** 0xf9: set CAN FD data bitrate
|
||||
case 0xf9:
|
||||
if ((req->param1 < PANDA_CAN_CNT) &&
|
||||
is_speed_valid(req->param2, data_speeds, sizeof(data_speeds)/sizeof(data_speeds[0]))) {
|
||||
bus_config[req->param1].can_data_speed = req->param2;
|
||||
bus_config[req->param1].canfd_enabled = (req->param2 >= bus_config[req->param1].can_speed);
|
||||
bus_config[req->param1].brs_enabled = (req->param2 > bus_config[req->param1].can_speed);
|
||||
bool ret = can_init(CAN_NUM_FROM_BUS_NUM(req->param1));
|
||||
UNUSED(ret);
|
||||
}
|
||||
break;
|
||||
// **** 0xfc: set CAN FD non-ISO mode
|
||||
case 0xfc:
|
||||
if (req->param1 < PANDA_CAN_CNT) {
|
||||
bus_config[req->param1].canfd_non_iso = (req->param2 != 0U);
|
||||
bool ret = can_init(CAN_NUM_FROM_BUS_NUM(req->param1));
|
||||
UNUSED(ret);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
print("NO HANDLER ");
|
||||
puth(req->request);
|
||||
print("\n");
|
||||
break;
|
||||
}
|
||||
return resp_len;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
// ******************** Prototypes ********************
|
||||
void print(const char *a);
|
||||
void puth(unsigned int i);
|
||||
typedef struct board board;
|
||||
typedef struct harness_configuration harness_configuration;
|
||||
void pwm_init(TIM_TypeDef *TIM, uint8_t channel);
|
||||
void pwm_set(TIM_TypeDef *TIM, uint8_t channel, uint8_t percentage);
|
||||
|
||||
// ********************* Globals **********************
|
||||
extern uint8_t hw_type;
|
||||
extern board *current_board;
|
||||
extern uint32_t uptime_cnt;
|
||||
|
||||
// heartbeat state
|
||||
extern uint32_t heartbeat_counter;
|
||||
extern bool heartbeat_lost;
|
||||
extern bool heartbeat_disabled;
|
||||
|
||||
// siren state
|
||||
extern bool siren_enabled;
|
||||
|
||||
// sound
|
||||
extern uint16_t sound_output_level;
|
||||
@@ -0,0 +1,14 @@
|
||||
#include "main_declarations.h"
|
||||
|
||||
// ********************* Globals **********************
|
||||
uint8_t hw_type = 0;
|
||||
board *current_board;
|
||||
uint32_t uptime_cnt = 0;
|
||||
|
||||
// heartbeat state
|
||||
uint32_t heartbeat_counter = 0;
|
||||
bool heartbeat_lost = false;
|
||||
bool heartbeat_disabled = false; // set over USB
|
||||
|
||||
// siren state
|
||||
bool siren_enabled = false;
|
||||
@@ -0,0 +1,13 @@
|
||||
// this is where we manage the dongle ID assigned during our
|
||||
// manufacturing. aside from this, there's a UID for the MCU
|
||||
|
||||
#define PROVISION_CHUNK_LEN 0x20
|
||||
|
||||
void get_provision_chunk(uint8_t *resp) {
|
||||
const unsigned char unprovisioned_text[] = "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff";
|
||||
|
||||
(void)memcpy(resp, (uint8_t *)PROVISION_CHUNK_ADDRESS, PROVISION_CHUNK_LEN);
|
||||
if (memcmp(resp, unprovisioned_text, 0x20) == 0) {
|
||||
(void)memcpy(resp, "unprovisioned\x00\x00\x00testing123\x00\x00\xa3\xa6\x99\xec", 0x20);
|
||||
}
|
||||
}
|
||||
Executable
+27
@@ -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)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user