Release 260111

This commit is contained in:
Comma Device
2026-01-11 18:23:29 +08:00
commit 3721ecbf8a
2601 changed files with 855070 additions and 0 deletions

View File

@@ -0,0 +1,20 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: check-ast
- id: check-yaml
- id: check-merge-conflict
- id: check-symlinks
- id: check-executables-have-shebangs
- id: check-shebang-scripts-are-executable
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.15.0
hooks:
- id: mypy
additional_dependencies: ['numpy', 'types-requests', 'types-atomicwrites',
'types-pycurl']
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.9.9
hooks:
- id: ruff

51
panda/Dockerfile Normal file
View File

@@ -0,0 +1,51 @@
FROM ubuntu:24.04
ENV PYTHONUNBUFFERED=1
ENV PYTHONPATH=/tmp/pythonpath
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y --no-install-recommends \
make \
g++ \
gcc-arm-none-eabi libnewlib-arm-none-eabi \
git \
libffi-dev \
libusb-1.0-0 \
python3 \
python3-dev \
python3-pip \
&& rm -rf /var/lib/apt/lists/* && \
apt clean && \
cd /usr/lib/gcc/arm-none-eabi/* && \
rm -rf arm/ && \
rm -rf thumb/nofp thumb/v6* thumb/v8* thumb/v7+fp thumb/v7-r+fp.sp && \
apt-get update && apt-get install -y clang-17 && \
ln -s $(which clang-17) /usr/bin/clang
RUN apt-get update && apt-get install -y curl && \
curl -1sLf 'https://dl.cloudsmith.io/public/mull-project/mull-stable/setup.deb.sh' | bash && \
apt-get update && apt-get install -y mull-17
ENV CPPCHECK_DIR=/tmp/cppcheck
COPY tests/misra/install.sh /tmp/
RUN /tmp/install.sh && rm -rf $CPPCHECK_DIR/.git/
ENV SKIP_CPPCHECK_INSTALL=1
COPY setup.py __init__.py $PYTHONPATH/panda/
COPY python/__init__.py $PYTHONPATH/panda/python/
RUN pip3 install --break-system-packages --no-cache-dir $PYTHONPATH/panda/[dev]
# TODO: this should be a "pip install" or not even in this repo at all
RUN git config --global --add safe.directory $PYTHONPATH/panda
ENV OPENDBC_REF="da0a5e3d2b3984b56ebf5e25d9769f5c77807e4d"
RUN cd /tmp/ && \
git clone --depth 1 https://github.com/commaai/opendbc opendbc_repo && \
cd opendbc_repo && git fetch origin $OPENDBC_REF && git checkout FETCH_HEAD && rm -rf .git/ && \
pip3 install --break-system-packages --no-cache-dir Cython numpy pycapnp && \
ln -s $PWD/opendbc $PYTHONPATH/opendbc && \
scons -j8 --minimal opendbc/
# for Jenkins
COPY README.md panda.tar.* /tmp/
RUN mkdir -p /tmp/pythonpath/panda && \
tar -xvf /tmp/panda.tar.gz -C /tmp/pythonpath/panda/ || true

140
panda/Jenkinsfile vendored Normal file
View File

@@ -0,0 +1,140 @@
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 \
--workdir /tmp/pythonpath/panda \
--net host \
${env.DOCKER_IMAGE_TAG} \
bash -c 'scons -j8 && ${cmd}'", \
label: step_label
}
}
def phone(String ip, String step_label, String cmd) {
withCredentials([file(credentialsId: 'id_rsa', variable: 'key_file')]) {
def ssh_cmd = """
ssh -tt -o StrictHostKeyChecking=no -i ${key_file} 'comma@${ip}' /usr/bin/bash <<'END'
set -e
source ~/.bash_profile
if [ -f /etc/profile ]; then
source /etc/profile
fi
export CI=1
export TEST_DIR=${env.TEST_DIR}
export SOURCE_DIR=${env.SOURCE_DIR}
export GIT_BRANCH=${env.GIT_BRANCH}
export GIT_COMMIT=${env.GIT_COMMIT}
export PYTHONPATH=${env.TEST_DIR}/../
export PYTHONWARNINGS=error
ln -sf /data/openpilot/opendbc_repo/opendbc /data/opendbc
cd ${env.TEST_DIR} || true
${cmd}
exit 0
END"""
sh script: ssh_cmd, label: step_label
}
}
def phone_steps(String device_type, steps) {
lock(resource: "", label: device_type, inversePrecedence: true, variable: 'device_ip', quantity: 1) {
timeout(time: 20, unit: 'MINUTES') {
phone(device_ip, "git checkout", readFile("tests/setup_device_ci.sh"),)
steps.each { item ->
phone(device_ip, item[0], item[1])
}
}
}
}
pipeline {
agent any
environment {
CI = "1"
PYTHONWARNINGS= "error"
DOCKER_IMAGE_TAG = "panda:build-${env.GIT_COMMIT}"
TEST_DIR = "/data/panda"
SOURCE_DIR = "/data/panda_source/"
}
options {
timeout(time: 3, unit: 'HOURS')
disableConcurrentBuilds(abortPrevious: env.BRANCH_NAME != 'master')
}
stages {
stage ('Acquire resource locks') {
options {
lock(resource: "pandas")
}
stages {
stage('Build Docker Image') {
steps {
timeout(time: 20, unit: 'MINUTES') {
script {
sh 'git archive -v -o panda.tar.gz --format=tar.gz HEAD'
dockerImage = docker.build("${env.DOCKER_IMAGE_TAG}")
}
}
}
}
stage('jungle tests') {
steps {
script {
retry (3) {
docker_run("reset hardware", 3, "python3 ./tests/hitl/reset_jungles.py")
}
}
}
}
stage('parallel tests') {
parallel {
stage('test tres') {
agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } }
steps {
phone_steps("panda-tres", [
["build", "scons -j4"],
["flash", "cd tests/ && ./reflash_internal_panda.py"],
["flash jungle", "cd board/jungle && ./flash.py --all"],
["test", "cd tests/hitl && HW_TYPES=9 pytest -n0 --durations=0 2*.py [5-9]*.py"],
])
}
}
stage('test dos') {
agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } }
steps {
phone_steps("panda-dos", [
["build", "scons -j4"],
["flash", "cd tests/ && ./reflash_internal_panda.py"],
["flash jungle", "cd board/jungle && ./flash.py --all"],
["test", "cd tests/hitl && HW_TYPES=6 pytest -n0 --durations=0 [2-9]*.py -k 'not test_send_recv'"],
])
}
}
stage('bootkick tests') {
steps {
script {
docker_run("test", 10, "pytest -n0 ./tests/som/test_bootkick.py")
}
}
}
}
}
}
}
}
}

7
panda/LICENSE Normal file
View File

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

106
panda/README.md Normal file
View File

@@ -0,0 +1,106 @@
# Welcome to panda
![panda tests](https://github.com/commaai/panda/workflows/tests/badge.svg)
![panda drivers](https://github.com/commaai/panda/workflows/drivers/badge.svg)
panda speaks CAN and CAN FD, and it runs on [STM32F413](https://www.st.com/resource/en/reference_manual/rm0430-stm32f413423-advanced-armbased-32bit-mcus-stmicroelectronics.pdf) and [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
├── drivers # Drivers (not needed for use with Python)
├── python # Python userspace library for interfacing with the panda
├── tests # Tests and helper programs for panda
```
## Safety Model
panda is compiled with safety firmware 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 relatively 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 USB and SPI
The above tests are themselves tested by:
* a [mutation test](tests/misra/test_mutation.py) on the MISRA coverage
In addition, we run the [ruff linter](https://github.com/astral-sh/ruff) and [mypy](https://mypy-lang.org/) on panda's Python library.
## Usage
Setup dependencies:
```bash
# Ubuntu
sudo apt-get install dfu-util gcc-arm-none-eabi python3-pip libffi-dev git clang-17
# macOS
brew install --cask gcc-arm-embedded
brew install python3 dfu-util gcc@13
```
Clone panda repository and install:
``` bash
git clone https://github.com/commaai/panda.git
cd panda
# install dependencies
pip install -e .[dev]
# install panda
python setup.py install
```
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.
## Software interface support
As a universal car interface, it should support every reasonable software interface.
- [Python library](https://github.com/commaai/panda/tree/master/python)
- [C++ library](https://github.com/commaai/openpilot/tree/master/selfdrive/pandad)
- [socketcan in kernel](https://github.com/commaai/panda/tree/master/drivers/linux) (alpha)
## Licensing
panda software is released under the MIT license unless otherwise specified.

11
panda/__init__.py Normal file
View File

@@ -0,0 +1,11 @@
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

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

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

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

130
panda/board/boards/black.h Normal file
View File

@@ -0,0 +1,130 @@
#pragma once
#include "board_declarations.h"
// /////////////////////////////// //
// Black Panda (STM32F4) + Harness //
// /////////////////////////////// //
static void black_enable_can_transceiver(uint8_t transceiver, bool enabled) {
switch (transceiver){
case 1U:
set_gpio_output(GPIOC, 1, !enabled);
break;
case 2U:
set_gpio_output(GPIOC, 13, !enabled);
break;
case 3U:
set_gpio_output(GPIOA, 0, !enabled);
break;
case 4U:
set_gpio_output(GPIOB, 10, !enabled);
break;
default:
print("Invalid CAN transceiver ("); puth(transceiver); print("): enabling failed\n");
break;
}
}
static void black_set_usb_load_switch(bool enabled) {
set_gpio_output(GPIOB, 1, !enabled);
}
static void black_set_can_mode(uint8_t mode) {
black_enable_can_transceiver(2U, false);
black_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 OBD mode
set_gpio_mode(GPIOB, 12, MODE_INPUT);
set_gpio_mode(GPIOB, 13, MODE_INPUT);
// B5,B6: normal CAN2 mode
set_gpio_alternate(GPIOB, 5, GPIO_AF9_CAN2);
set_gpio_alternate(GPIOB, 6, GPIO_AF9_CAN2);
black_enable_can_transceiver(2U, true);
} else {
// B5,B6: disable normal CAN2 mode
set_gpio_mode(GPIOB, 5, MODE_INPUT);
set_gpio_mode(GPIOB, 6, MODE_INPUT);
// B12,B13: OBD mode
set_gpio_alternate(GPIOB, 12, GPIO_AF9_CAN2);
set_gpio_alternate(GPIOB, 13, GPIO_AF9_CAN2);
black_enable_can_transceiver(4U, true);
}
break;
default:
print("Tried to set unsupported CAN mode: "); puth(mode); print("\n");
break;
}
}
static bool black_check_ignition(void){
// ignition is checked through harness
return harness_check_ignition();
}
static void black_init(void) {
common_init_gpio();
// A8,A15: normal CAN3 mode
set_gpio_alternate(GPIOA, 8, GPIO_AF11_CAN3);
set_gpio_alternate(GPIOA, 15, GPIO_AF11_CAN3);
// GPS OFF
set_gpio_output(GPIOC, 5, 0);
set_gpio_output(GPIOC, 12, 0);
// Turn on USB load switch.
black_set_usb_load_switch(true);
}
static void black_init_bootloader(void) {
// GPS OFF
set_gpio_output(GPIOC, 5, 0);
set_gpio_output(GPIOC, 12, 0);
}
static harness_configuration black_harness_config = {
.has_harness = true,
.GPIO_SBU1 = GPIOC,
.GPIO_SBU2 = GPIOC,
.GPIO_relay_SBU1 = GPIOC,
.GPIO_relay_SBU2 = GPIOC,
.pin_SBU1 = 0,
.pin_SBU2 = 3,
.pin_relay_SBU1 = 10,
.pin_relay_SBU2 = 11,
.adc_channel_SBU1 = 10,
.adc_channel_SBU2 = 13
};
board board_black = {
.set_bootkick = unused_set_bootkick,
.harness_config = &black_harness_config,
.has_spi = false,
.has_canfd = false,
.fan_max_rpm = 0U,
.fan_max_pwm = 100U,
.avdd_mV = 3300U,
.fan_stall_recovery = false,
.fan_enable_cooldown_time = 0U,
.init = black_init,
.init_bootloader = black_init_bootloader,
.enable_can_transceiver = black_enable_can_transceiver,
.led_GPIO = {GPIOC, GPIOC, GPIOC},
.led_pin = {9, 7, 6},
.set_can_mode = black_set_can_mode,
.check_ignition = black_check_ignition,
.read_voltage_mV = white_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
};

View File

@@ -0,0 +1,84 @@
#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 bool (*board_check_ignition)(void);
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 bool has_spi;
const bool has_canfd;
const uint16_t fan_max_rpm;
const uint16_t avdd_mV;
const bool fan_stall_recovery;
const uint8_t fan_enable_cooldown_time;
const uint8_t fan_max_pwm;
board_init init;
board_init_bootloader init_bootloader;
board_enable_can_transceiver enable_can_transceiver;
board_set_can_mode set_can_mode;
board_check_ignition check_ignition;
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_WHITE_PANDA 1U
#define HW_TYPE_GREY_PANDA 2U
#define HW_TYPE_BLACK_PANDA 3U
#define HW_TYPE_PEDAL 4U
#define HW_TYPE_UNO 5U
#define HW_TYPE_DOS 6U
#define HW_TYPE_RED_PANDA 7U
#define HW_TYPE_RED_PANDA_V2 8U
#define HW_TYPE_TRES 9U
#define HW_TYPE_CUATRO 10U
// USB power modes (from cereal.log.health)
#define USB_POWER_NONE 0U
#define USB_POWER_CLIENT 1U
#define USB_POWER_CDP 2U
#define USB_POWER_DCP 3U
// CAN modes
#define CAN_MODE_NORMAL 0U
#define CAN_MODE_OBD_CAN2 1U
extern struct board board_black;
extern struct board board_dos;
extern struct board board_uno;
extern struct board board_tres;
extern struct board board_grey;
extern struct board board_white;
extern struct board board_cuatro;
extern struct board board_red;

146
panda/board/boards/cuatro.h Normal file
View File

@@ -0,0 +1,146 @@
#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(8) * 11U;
}
static uint32_t cuatro_read_current_mA(void) {
return adc_get_mV(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);
// TODO: confirm we need this
//set_gpio_output(GPIOC, 12, state != BOOT_RESET);
}
static void cuatro_set_amp_enabled(bool enabled){
set_gpio_output(GPIOA, 5, 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, 12, OUTPUT_TYPE_OPEN_DRAIN); // VBAT_EN
// 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
// Initialize IR PWM and set to 0%
set_gpio_alternate(GPIOC, 9, GPIO_AF2_TIM3);
pwm_init(TIM3, 4);
tres_set_ir_power(0U);
// 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 = {
.has_harness = true,
.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_channel_SBU1 = 4, // ADC12_INP4
.adc_channel_SBU2 = 17 // ADC1_INP17
};
board board_cuatro = {
.harness_config = &cuatro_harness_config,
.has_spi = true,
.has_canfd = true,
.fan_max_rpm = 12500U,
.fan_max_pwm = 99U, // it can go up to 14k RPM, but 99% -> 100% is very non-linear
.avdd_mV = 1800U,
.fan_stall_recovery = false,
.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},
.set_can_mode = tres_set_can_mode,
.check_ignition = red_check_ignition,
.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 = unused_set_siren,
.set_bootkick = cuatro_set_bootkick,
.read_som_gpio = tres_read_som_gpio,
.set_amp_enabled = cuatro_set_amp_enabled
};

154
panda/board/boards/dos.h Normal file
View File

@@ -0,0 +1,154 @@
#pragma once
#include "board_declarations.h"
// /////////////////////// //
// Dos (STM32F4) + Harness //
// /////////////////////// //
static void dos_enable_can_transceiver(uint8_t transceiver, bool enabled) {
switch (transceiver){
case 1U:
set_gpio_output(GPIOC, 1, !enabled);
break;
case 2U:
set_gpio_output(GPIOC, 13, !enabled);
break;
case 3U:
set_gpio_output(GPIOA, 0, !enabled);
break;
case 4U:
set_gpio_output(GPIOB, 10, !enabled);
break;
default:
print("Invalid CAN transceiver ("); puth(transceiver); print("): enabling failed\n");
break;
}
}
static void dos_set_bootkick(BootState state) {
set_gpio_output(GPIOC, 4, state != BOOT_BOOTKICK);
}
static void dos_set_can_mode(uint8_t mode) {
dos_enable_can_transceiver(2U, false);
dos_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 OBD mode
set_gpio_mode(GPIOB, 12, MODE_INPUT);
set_gpio_mode(GPIOB, 13, MODE_INPUT);
// B5,B6: normal CAN2 mode
set_gpio_alternate(GPIOB, 5, GPIO_AF9_CAN2);
set_gpio_alternate(GPIOB, 6, GPIO_AF9_CAN2);
dos_enable_can_transceiver(2U, true);
} else {
// B5,B6: disable normal CAN2 mode
set_gpio_mode(GPIOB, 5, MODE_INPUT);
set_gpio_mode(GPIOB, 6, MODE_INPUT);
// B12,B13: OBD mode
set_gpio_alternate(GPIOB, 12, GPIO_AF9_CAN2);
set_gpio_alternate(GPIOB, 13, GPIO_AF9_CAN2);
dos_enable_can_transceiver(4U, true);
}
break;
default:
print("Tried to set unsupported CAN mode: "); puth(mode); print("\n");
break;
}
}
static bool dos_check_ignition(void){
// ignition is checked through harness
return harness_check_ignition();
}
static void dos_set_ir_power(uint8_t percentage){
pwm_set(TIM4, 2, percentage);
}
static void dos_set_fan_enabled(bool enabled){
set_gpio_output(GPIOA, 1, enabled);
}
//static void dos_set_siren(bool enabled){
// set_gpio_output(GPIOC, 12, enabled);
//}
static bool dos_read_som_gpio (void){
return (get_gpio_input(GPIOC, 2) != 0);
}
static void dos_init(void) {
common_init_gpio();
// A8,A15: normal CAN3 mode
set_gpio_alternate(GPIOA, 8, GPIO_AF11_CAN3);
set_gpio_alternate(GPIOA, 15, GPIO_AF11_CAN3);
// C8: FAN PWM aka TIM3_CH3
set_gpio_alternate(GPIOC, 8, GPIO_AF2_TIM3);
// C2: SOM GPIO used as input (fan control at boot)
set_gpio_mode(GPIOC, 2, MODE_INPUT);
set_gpio_pullup(GPIOC, 2, PULL_DOWN);
// Initialize IR PWM and set to 0%
set_gpio_alternate(GPIOB, 7, GPIO_AF2_TIM4);
pwm_init(TIM4, 2);
dos_set_ir_power(0U);
// Bootkick
dos_set_bootkick(true);
// Init clock source (camera strobe) using PWM
clock_source_init(false);
}
static harness_configuration dos_harness_config = {
.has_harness = true,
.GPIO_SBU1 = GPIOC,
.GPIO_SBU2 = GPIOC,
.GPIO_relay_SBU1 = GPIOC,
.GPIO_relay_SBU2 = GPIOC,
.pin_SBU1 = 0,
.pin_SBU2 = 3,
.pin_relay_SBU1 = 10,
.pin_relay_SBU2 = 11,
.adc_channel_SBU1 = 10,
.adc_channel_SBU2 = 13
};
board board_dos = {
.harness_config = &dos_harness_config,
#ifdef ENABLE_SPI
.has_spi = true,
#else
.has_spi = false,
#endif
.has_canfd = false,
.fan_max_rpm = 6500U,
.fan_max_pwm = 100U,
.avdd_mV = 3300U,
.fan_stall_recovery = true,
.fan_enable_cooldown_time = 3U,
.init = dos_init,
.init_bootloader = unused_init_bootloader,
.enable_can_transceiver = dos_enable_can_transceiver,
.led_GPIO = {GPIOC, GPIOC, GPIOC},
.led_pin = {9, 7, 6},
.set_can_mode = dos_set_can_mode,
.check_ignition = dos_check_ignition,
.read_voltage_mV = white_read_voltage_mV,
.read_current_mA = unused_read_current,
.set_fan_enabled = dos_set_fan_enabled,
.set_ir_power = dos_set_ir_power,
.set_siren = unused_set_siren,
.set_bootkick = dos_set_bootkick,
.read_som_gpio = dos_read_som_gpio,
.set_amp_enabled = unused_set_amp_enabled
};

35
panda/board/boards/grey.h Normal file
View File

@@ -0,0 +1,35 @@
#pragma once
#include "board_declarations.h"
// //////////////////// //
// Grey Panda (STM32F4) //
// //////////////////// //
// Most hardware functionality is similar to white panda
board board_grey = {
.set_bootkick = unused_set_bootkick,
.harness_config = &white_harness_config,
.has_spi = false,
.has_canfd = false,
.fan_max_rpm = 0U,
.fan_max_pwm = 100U,
.avdd_mV = 3300U,
.fan_stall_recovery = false,
.fan_enable_cooldown_time = 0U,
.init = white_grey_init,
.init_bootloader = white_grey_init_bootloader,
.enable_can_transceiver = white_enable_can_transceiver,
.led_GPIO = {GPIOC, GPIOC, GPIOC},
.led_pin = {9, 7, 6},
.set_can_mode = white_set_can_mode,
.check_ignition = white_check_ignition,
.read_voltage_mV = white_read_voltage_mV,
.read_current_mA = white_read_current_mA,
.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
};

144
panda/board/boards/red.h Normal file
View File

@@ -0,0 +1,144 @@
#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 bool red_check_ignition(void) {
// ignition is checked through harness
return harness_check_ignition();
}
static uint32_t red_read_voltage_mV(void){
return adc_get_mV(2) * 11U; // TODO: is this correct?
}
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 = {
.has_harness = true,
.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_channel_SBU1 = 4, //ADC12_INP4
.adc_channel_SBU2 = 17 //ADC1_INP17
};
board board_red = {
.set_bootkick = unused_set_bootkick,
.harness_config = &red_harness_config,
.has_spi = false,
.has_canfd = true,
.fan_max_rpm = 0U,
.fan_max_pwm = 100U,
.avdd_mV = 3300U,
.fan_stall_recovery = false,
.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,
.check_ignition = red_check_ignition,
.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
};

173
panda/board/boards/tres.h Normal file
View File

@@ -0,0 +1,173 @@
#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) {
switch (transceiver) {
case 1U:
set_gpio_output(GPIOG, 11, !enabled);
break;
case 2U:
set_gpio_output(GPIOB, 10, !enabled);
break;
case 3U:
set_gpio_output(GPIOD, 7, !enabled);
break;
case 4U:
set_gpio_output(GPIOB, 11, !enabled);
break;
default:
break;
}
}
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 = {
.has_harness = true,
.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_channel_SBU1 = 4, // ADC12_INP4
.adc_channel_SBU2 = 17 // ADC1_INP17
};
board board_tres = {
.harness_config = &tres_harness_config,
.has_spi = true,
.has_canfd = true,
.fan_max_rpm = 6600U,
.fan_max_pwm = 100U,
.avdd_mV = 1800U,
.fan_stall_recovery = false,
.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,
.check_ignition = red_check_ignition,
.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 = unused_set_siren,
.set_bootkick = tres_set_bootkick,
.read_som_gpio = tres_read_som_gpio,
.set_amp_enabled = unused_set_amp_enabled
};

166
panda/board/boards/uno.h Normal file
View File

@@ -0,0 +1,166 @@
#pragma once
#include "board_declarations.h"
// /////////////////////// //
// Uno (STM32F4) + Harness //
// /////////////////////// //
static void uno_enable_can_transceiver(uint8_t transceiver, bool enabled) {
switch (transceiver){
case 1U:
set_gpio_output(GPIOC, 1, !enabled);
break;
case 2U:
set_gpio_output(GPIOC, 13, !enabled);
break;
case 3U:
set_gpio_output(GPIOA, 0, !enabled);
break;
case 4U:
set_gpio_output(GPIOB, 10, !enabled);
break;
default:
print("Invalid CAN transceiver ("); puth(transceiver); print("): enabling failed\n");
break;
}
}
static void uno_set_bootkick(BootState state) {
if (state == BOOT_BOOTKICK) {
set_gpio_output(GPIOB, 14, false);
} else {
// We want the pin to be floating, not forced high!
set_gpio_mode(GPIOB, 14, MODE_INPUT);
}
}
static void uno_set_can_mode(uint8_t mode) {
uno_enable_can_transceiver(2U, false);
uno_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 OBD mode
set_gpio_mode(GPIOB, 12, MODE_INPUT);
set_gpio_mode(GPIOB, 13, MODE_INPUT);
// B5,B6: normal CAN2 mode
set_gpio_alternate(GPIOB, 5, GPIO_AF9_CAN2);
set_gpio_alternate(GPIOB, 6, GPIO_AF9_CAN2);
uno_enable_can_transceiver(2U, true);
} else {
// B5,B6: disable normal CAN2 mode
set_gpio_mode(GPIOB, 5, MODE_INPUT);
set_gpio_mode(GPIOB, 6, MODE_INPUT);
// B12,B13: OBD mode
set_gpio_alternate(GPIOB, 12, GPIO_AF9_CAN2);
set_gpio_alternate(GPIOB, 13, GPIO_AF9_CAN2);
uno_enable_can_transceiver(4U, true);
}
break;
default:
print("Tried to set unsupported CAN mode: "); puth(mode); print("\n");
break;
}
}
static bool uno_check_ignition(void){
// ignition is checked through harness
return harness_check_ignition();
}
static void uno_set_usb_switch(bool phone){
set_gpio_output(GPIOB, 3, phone);
}
static void uno_set_ir_power(uint8_t percentage){
pwm_set(TIM4, 2, percentage);
}
static void uno_set_fan_enabled(bool enabled){
set_gpio_output(GPIOA, 1, enabled);
}
static void uno_init(void) {
common_init_gpio();
// A8,A15: normal CAN3 mode
set_gpio_alternate(GPIOA, 8, GPIO_AF11_CAN3);
set_gpio_alternate(GPIOA, 15, GPIO_AF11_CAN3);
// GPS off
set_gpio_output(GPIOB, 1, 0);
set_gpio_output(GPIOC, 5, 0);
set_gpio_output(GPIOC, 12, 0);
// C8: FAN PWM aka TIM3_CH3
set_gpio_alternate(GPIOC, 8, GPIO_AF2_TIM3);
// Turn on phone regulator
set_gpio_output(GPIOB, 4, true);
// Initialize IR PWM and set to 0%
set_gpio_alternate(GPIOB, 7, GPIO_AF2_TIM4);
pwm_init(TIM4, 2);
uno_set_ir_power(0U);
// Switch to phone usb mode if harness connection is powered by less than 7V
if(white_read_voltage_mV() < 7000U){
uno_set_usb_switch(true);
} else {
uno_set_usb_switch(false);
}
// Bootkick phone
uno_set_bootkick(BOOT_BOOTKICK);
}
static void uno_init_bootloader(void) {
// GPS off
set_gpio_output(GPIOB, 1, 0);
set_gpio_output(GPIOC, 5, 0);
set_gpio_output(GPIOC, 12, 0);
}
static harness_configuration uno_harness_config = {
.has_harness = true,
.GPIO_SBU1 = GPIOC,
.GPIO_SBU2 = GPIOC,
.GPIO_relay_SBU1 = GPIOC,
.GPIO_relay_SBU2 = GPIOC,
.pin_SBU1 = 0,
.pin_SBU2 = 3,
.pin_relay_SBU1 = 10,
.pin_relay_SBU2 = 11,
.adc_channel_SBU1 = 10,
.adc_channel_SBU2 = 13
};
board board_uno = {
.harness_config = &uno_harness_config,
.has_spi = false,
.has_canfd = false,
.fan_max_rpm = 5100U,
.fan_max_pwm = 100U,
.avdd_mV = 3300U,
.fan_stall_recovery = false,
.fan_enable_cooldown_time = 0U,
.init = uno_init,
.init_bootloader = uno_init_bootloader,
.enable_can_transceiver = uno_enable_can_transceiver,
.led_GPIO = {GPIOC, GPIOC, GPIOC},
.led_pin = {9, 7, 6},
.set_can_mode = uno_set_can_mode,
.check_ignition = uno_check_ignition,
.read_voltage_mV = white_read_voltage_mV,
.read_current_mA = unused_read_current,
.set_fan_enabled = uno_set_fan_enabled,
.set_ir_power = uno_set_ir_power,
.set_siren = unused_set_siren,
.set_bootkick = uno_set_bootkick,
.read_som_gpio = unused_read_som_gpio,
.set_amp_enabled = unused_set_amp_enabled
};

View File

@@ -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);
}

179
panda/board/boards/white.h Normal file
View File

@@ -0,0 +1,179 @@
#pragma once
#include "board_declarations.h"
// ///////////////////// //
// White Panda (STM32F4) //
// ///////////////////// //
static void white_enable_can_transceiver(uint8_t transceiver, bool enabled) {
switch (transceiver){
case 1U:
set_gpio_output(GPIOC, 1, !enabled);
break;
case 2U:
set_gpio_output(GPIOC, 13, !enabled);
break;
case 3U:
set_gpio_output(GPIOA, 0, !enabled);
break;
default:
break;
}
}
static void white_set_usb_power_mode(uint8_t mode){
switch (mode) {
case USB_POWER_CLIENT:
// B2,A13: set client mode
set_gpio_output(GPIOB, 2, 0);
set_gpio_output(GPIOA, 13, 1);
break;
case USB_POWER_CDP:
// B2,A13: set CDP mode
set_gpio_output(GPIOB, 2, 1);
set_gpio_output(GPIOA, 13, 1);
break;
case USB_POWER_DCP:
// B2,A13: set DCP mode on the charger (breaks USB!)
set_gpio_output(GPIOB, 2, 0);
set_gpio_output(GPIOA, 13, 0);
break;
default:
print("Invalid usb power mode\n");
break;
}
}
static void white_set_can_mode(uint8_t mode){
if (mode == CAN_MODE_NORMAL) {
// B12,B13: disable GMLAN mode
set_gpio_mode(GPIOB, 12, MODE_INPUT);
set_gpio_mode(GPIOB, 13, MODE_INPUT);
// B3,B4: disable GMLAN mode
set_gpio_mode(GPIOB, 3, MODE_INPUT);
set_gpio_mode(GPIOB, 4, MODE_INPUT);
// B5,B6: normal CAN2 mode
set_gpio_alternate(GPIOB, 5, GPIO_AF9_CAN2);
set_gpio_alternate(GPIOB, 6, GPIO_AF9_CAN2);
// A8,A15: normal CAN3 mode
set_gpio_alternate(GPIOA, 8, GPIO_AF11_CAN3);
set_gpio_alternate(GPIOA, 15, GPIO_AF11_CAN3);
}
}
static uint32_t white_read_voltage_mV(void){
return adc_get_mV(12) * 11U;
}
static uint32_t white_read_current_mA(void){
// This isn't in mA, but we're keeping it for backwards compatibility
return adc_get_raw(13);
}
static bool white_check_ignition(void){
// ignition is on PA1
return !get_gpio_input(GPIOA, 1);
}
static void white_grey_init(void) {
common_init_gpio();
// C3: current sense
set_gpio_mode(GPIOC, 3, MODE_ANALOG);
// A1: started_alt
set_gpio_pullup(GPIOA, 1, PULL_UP);
// A2, A3: USART 2 for debugging
set_gpio_alternate(GPIOA, 2, GPIO_AF7_USART2);
set_gpio_alternate(GPIOA, 3, GPIO_AF7_USART2);
// A4, A5, A6, A7: SPI
set_gpio_alternate(GPIOA, 4, GPIO_AF5_SPI1);
set_gpio_alternate(GPIOA, 5, GPIO_AF5_SPI1);
set_gpio_alternate(GPIOA, 6, GPIO_AF5_SPI1);
set_gpio_alternate(GPIOA, 7, GPIO_AF5_SPI1);
// B12: GMLAN, ignition sense, pull up
set_gpio_pullup(GPIOB, 12, PULL_UP);
/* GMLAN mode pins:
M0(B15) M1(B14) mode
=======================
0 0 sleep
1 0 100kbit
0 1 high voltage wakeup
1 1 33kbit (normal)
*/
set_gpio_output(GPIOB, 14, 0);
set_gpio_output(GPIOB, 15, 0);
// B7: K-line enable
set_gpio_output(GPIOB, 7, 1);
// C12, D2: Setup K-line (UART5)
set_gpio_alternate(GPIOC, 12, GPIO_AF8_UART5);
set_gpio_alternate(GPIOD, 2, GPIO_AF8_UART5);
set_gpio_pullup(GPIOD, 2, PULL_UP);
// L-line enable
set_gpio_output(GPIOA, 14, 1);
// C10, C11: L-Line setup (USART3)
set_gpio_alternate(GPIOC, 10, GPIO_AF7_USART3);
set_gpio_alternate(GPIOC, 11, GPIO_AF7_USART3);
set_gpio_pullup(GPIOC, 11, PULL_UP);
// Init usb power mode
// Init in CDP mode only if panda is powered by 12V.
// Otherwise a PC would not be able to flash a standalone panda
if (white_read_voltage_mV() > 8000U) { // 8V threshold
white_set_usb_power_mode(USB_POWER_CDP);
} else {
white_set_usb_power_mode(USB_POWER_CLIENT);
}
// ESP/GPS off
set_gpio_output(GPIOC, 5, 0);
set_gpio_output(GPIOC, 14, 0);
}
static void white_grey_init_bootloader(void) {
// ESP/GPS off
set_gpio_output(GPIOC, 5, 0);
set_gpio_output(GPIOC, 14, 0);
}
static harness_configuration white_harness_config = {
.has_harness = false
};
board board_white = {
.set_bootkick = unused_set_bootkick,
.harness_config = &white_harness_config,
.has_spi = false,
.has_canfd = false,
.fan_max_rpm = 0U,
.fan_max_pwm = 100U,
.avdd_mV = 3300U,
.fan_stall_recovery = false,
.fan_enable_cooldown_time = 0U,
.init = white_grey_init,
.init_bootloader = white_grey_init_bootloader,
.enable_can_transceiver = white_enable_can_transceiver,
.led_GPIO = {GPIOC, GPIOC, GPIOC},
.led_pin = {9, 7, 6},
.set_can_mode = white_set_can_mode,
.check_ignition = white_check_ignition,
.read_voltage_mV = white_read_voltage_mV,
.read_current_mA = white_read_current_mA,
.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
};

View File

@@ -0,0 +1,18 @@
// ******************** Prototypes ********************
void print(const char *a){ UNUSED(a); }
void puth(uint8_t i){ UNUSED(i); }
void puth2(uint8_t i){ UNUSED(i); }
void puth4(uint8_t 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;
uart_ring uart_ring_som_debug;
void uart_init(uart_ring *q, int baud) { UNUSED(q); UNUSED(baud); }
// ********************* Globals **********************
uint8_t hw_type = 0;
board *current_board;

7
panda/board/can.h Normal file
View File

@@ -0,0 +1,7 @@
#pragma once
#include "can_declarations.h"
static const uint8_t PANDA_CAN_CNT = 3U;
static const uint8_t PANDA_BUS_CNT = 3U;
static const unsigned char dlc_to_len[] = {0U, 1U, 2U, 3U, 4U, 5U, 6U, 7U, 8U, 12U, 16U, 20U, 24U, 32U, 48U, 64U};

122
panda/board/can_comms.h Normal file
View File

@@ -0,0 +1,122 @@
/*
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.
* 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();
}
}

View File

@@ -0,0 +1,29 @@
#pragma once
// bump this when changing the CAN packet
#define CAN_PACKET_VERSION 4
#define CANPACKET_HEAD_SIZE 6U
#if !defined(STM32F4)
#define CANFD
#define CANPACKET_DATA_SIZE_MAX 64U
#else
#define CANPACKET_DATA_SIZE_MAX 8U
#endif
typedef struct {
unsigned char fd : 1;
unsigned char bus : 3;
unsigned char data_len_code : 4; // lookup length with dlc_to_len
unsigned char rejected : 1;
unsigned char returned : 1;
unsigned char extended : 1;
unsigned int addr : 29;
unsigned char checksum;
unsigned char data[CANPACKET_DATA_SIZE_MAX];
} __attribute__((packed, aligned(4))) CANPacket_t;
#define GET_BUS(msg) ((msg)->bus)
#define GET_LEN(msg) (dlc_to_len[(msg)->data_len_code])
#define GET_ADDR(msg) ((msg)->addr)

View File

@@ -0,0 +1,12 @@
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);

44
panda/board/config.h Normal file
View File

@@ -0,0 +1,44 @@
#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 "stm32h7/stm32h7_config.h"
#elif defined(STM32F4)
#include "stm32f4/stm32f4_config.h"
#else
// TODO: uncomment this, cppcheck complains
// building for tests
//#include "fake_stm.h"
#endif

19
panda/board/crc.h Normal file
View File

@@ -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;
}

16
panda/board/critical.h Normal file
View File

@@ -0,0 +1,16 @@
#include "critical_declarations.h"
// ********************* Critical section helpers *********************
uint8_t global_critical_depth = 0U;
static volatile bool interrupts_enabled = false;
void enable_interrupts(void) {
interrupts_enabled = true;
__enable_irq();
}
void disable_interrupts(void) {
interrupts_enabled = false;
__disable_irq();
}

View File

@@ -0,0 +1,17 @@
#pragma once
// ********************* Critical section helpers *********************
void enable_interrupts(void);
void disable_interrupts(void);
extern uint8_t global_critical_depth;
#define ENTER_CRITICAL() \
__disable_irq(); \
global_critical_depth += 1U;
#define EXIT_CRITICAL() \
global_critical_depth -= 1U; \
if ((global_critical_depth == 0U) && interrupts_enabled) { \
__enable_irq(); \
}

View File

@@ -0,0 +1,26 @@
# 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`.
To run, use `./debug_f4.sh (TODO)` or `./debug_h7.sh` depending on the panda.
## GDB
You need `gdb-multiarch`.
Once openocd is running, you can connect from gdb as follows:
```
$ gdb-multiarch
(gdb) target ext :3333
```
To reset and break, use `monitor reset halt`.

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

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

11
panda/board/dfu_util_f4.sh Executable file
View File

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

11
panda/board/dfu_util_h7.sh Executable file
View File

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

View File

@@ -0,0 +1,68 @@
#include "bootkick_declarations.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);
}

View File

@@ -0,0 +1,5 @@
#pragma once
extern bool bootkick_reset_triggered;
void bootkick_tick(bool ignition, bool recent_heartbeat);

222
panda/board/drivers/bxcan.h Normal file
View File

@@ -0,0 +1,222 @@
#include "bxcan_declarations.h"
// IRQs: CAN1_TX, CAN1_RX0, CAN1_SCE
// CAN2_TX, CAN2_RX0, CAN2_SCE
// CAN3_TX, CAN3_RX0, CAN3_SCE
CAN_TypeDef *cans[CAN_ARRAY_SIZE] = {CAN1, CAN2, CAN3};
uint8_t can_irq_number[CAN_IRQS_ARRAY_SIZE][CAN_IRQS_ARRAY_SIZE] = {
{ CAN1_TX_IRQn, CAN1_RX0_IRQn, CAN1_SCE_IRQn },
{ CAN2_TX_IRQn, CAN2_RX0_IRQn, CAN2_SCE_IRQn },
{ CAN3_TX_IRQn, CAN3_RX0_IRQn, CAN3_SCE_IRQn },
};
bool can_set_speed(uint8_t can_number) {
bool ret = true;
CAN_TypeDef *CANx = CANIF_FROM_CAN_NUM(can_number);
uint8_t bus_number = BUS_NUM_FROM_CAN_NUM(can_number);
ret &= llcan_set_speed(
CANx,
bus_config[bus_number].can_speed,
can_loopback,
(unsigned int)(can_silent) & (1U << can_number)
);
return ret;
}
void can_clear_send(CAN_TypeDef *CANx, uint8_t can_number) {
can_health[can_number].can_core_reset_cnt += 1U;
llcan_clear_send(CANx);
}
void update_can_health_pkt(uint8_t can_number, uint32_t ir_reg) {
CAN_TypeDef *CANx = CANIF_FROM_CAN_NUM(can_number);
uint32_t esr_reg = CANx->ESR;
can_health[can_number].bus_off = ((esr_reg & CAN_ESR_BOFF) >> CAN_ESR_BOFF_Pos);
can_health[can_number].bus_off_cnt += can_health[can_number].bus_off;
can_health[can_number].error_warning = ((esr_reg & CAN_ESR_EWGF) >> CAN_ESR_EWGF_Pos);
can_health[can_number].error_passive = ((esr_reg & CAN_ESR_EPVF) >> CAN_ESR_EPVF_Pos);
can_health[can_number].last_error = ((esr_reg & CAN_ESR_LEC) >> CAN_ESR_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].receive_error_cnt = ((esr_reg & CAN_ESR_REC) >> CAN_ESR_REC_Pos);
can_health[can_number].transmit_error_cnt = ((esr_reg & CAN_ESR_TEC) >> CAN_ESR_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;
can_health[can_number].irq2_call_rate = interrupts[can_irq_number[can_number][2]].call_rate;
if (ir_reg != 0U) {
can_health[can_number].total_error_cnt += 1U;
// RX message lost due to FIFO overrun
if ((CANx->RF0R & (CAN_RF0R_FOVR0)) != 0U) {
can_health[can_number].total_rx_lost_cnt += 1U;
CANx->RF0R &= ~(CAN_RF0R_FOVR0);
}
can_clear_send(CANx, can_number);
}
}
// ***************************** CAN *****************************
// CANx_SCE IRQ Handler
static void can_sce(uint8_t can_number) {
update_can_health_pkt(can_number, 1U);
}
// CANx_TX IRQ Handler
void process_can(uint8_t can_number) {
if (can_number != 0xffU) {
ENTER_CRITICAL();
CAN_TypeDef *CANx = CANIF_FROM_CAN_NUM(can_number);
uint8_t bus_number = BUS_NUM_FROM_CAN_NUM(can_number);
// check for empty mailbox
CANPacket_t to_send;
if ((CANx->TSR & (CAN_TSR_TERR0 | CAN_TSR_ALST0)) != 0U) { // last TX failed due to error arbitration lost
can_health[can_number].total_tx_lost_cnt += 1U;
CANx->TSR |= (CAN_TSR_TERR0 | CAN_TSR_ALST0);
}
if ((CANx->TSR & CAN_TSR_TME0) == CAN_TSR_TME0) {
// add successfully transmitted message to my fifo
if ((CANx->TSR & CAN_TSR_RQCP0) == CAN_TSR_RQCP0) {
if ((CANx->TSR & CAN_TSR_TXOK0) == CAN_TSR_TXOK0) {
CANPacket_t to_push;
to_push.fd = 0U;
to_push.returned = 1U;
to_push.rejected = 0U;
to_push.extended = (CANx->sTxMailBox[0].TIR >> 2) & 0x1U;
to_push.addr = (to_push.extended != 0U) ? (CANx->sTxMailBox[0].TIR >> 3) : (CANx->sTxMailBox[0].TIR >> 21);
to_push.data_len_code = CANx->sTxMailBox[0].TDTR & 0xFU;
to_push.bus = bus_number;
WORD_TO_BYTE_ARRAY(&to_push.data[0], CANx->sTxMailBox[0].TDLR);
WORD_TO_BYTE_ARRAY(&to_push.data[4], CANx->sTxMailBox[0].TDHR);
can_set_checksum(&to_push);
rx_buffer_overflow += can_push(&can_rx_q, &to_push) ? 0U : 1U;
}
// clear interrupt
// careful, this can also be cleared by requesting a transmission
CANx->TSR |= CAN_TSR_RQCP0;
}
if (can_pop(can_queues[bus_number], &to_send)) {
if (can_check_checksum(&to_send)) {
can_health[can_number].total_tx_cnt += 1U;
// only send if we have received a packet
CANx->sTxMailBox[0].TIR = ((to_send.extended != 0U) ? (to_send.addr << 3) : (to_send.addr << 21)) | (to_send.extended << 2);
CANx->sTxMailBox[0].TDTR = to_send.data_len_code;
BYTE_ARRAY_TO_WORD(CANx->sTxMailBox[0].TDLR, &to_send.data[0]);
BYTE_ARRAY_TO_WORD(CANx->sTxMailBox[0].TDHR, &to_send.data[4]);
// Send request TXRQ
CANx->sTxMailBox[0].TIR |= 0x1U;
} else {
can_health[can_number].total_tx_checksum_error_cnt += 1U;
}
refresh_can_tx_slots_available();
}
}
EXIT_CRITICAL();
}
}
// CANx_RX0 IRQ Handler
// blink blue when we are receiving CAN messages
void can_rx(uint8_t can_number) {
CAN_TypeDef *CANx = CANIF_FROM_CAN_NUM(can_number);
uint8_t bus_number = BUS_NUM_FROM_CAN_NUM(can_number);
while ((CANx->RF0R & CAN_RF0R_FMP0) != 0U) {
can_health[can_number].total_rx_cnt += 1U;
// can is live
pending_can_live = 1;
// add to my fifo
CANPacket_t to_push;
to_push.fd = 0U;
to_push.returned = 0U;
to_push.rejected = 0U;
to_push.extended = (CANx->sFIFOMailBox[0].RIR >> 2) & 0x1U;
to_push.addr = (to_push.extended != 0U) ? (CANx->sFIFOMailBox[0].RIR >> 3) : (CANx->sFIFOMailBox[0].RIR >> 21);
to_push.data_len_code = CANx->sFIFOMailBox[0].RDTR & 0xFU;
to_push.bus = bus_number;
WORD_TO_BYTE_ARRAY(&to_push.data[0], CANx->sFIFOMailBox[0].RDLR);
WORD_TO_BYTE_ARRAY(&to_push.data[4], CANx->sFIFOMailBox[0].RDHR);
can_set_checksum(&to_push);
// forwarding (panda only)
int bus_fwd_num = safety_fwd_hook(bus_number, to_push.addr);
if (bus_fwd_num != -1) {
CANPacket_t to_send;
to_send.fd = 0U;
to_send.returned = 0U;
to_send.rejected = 0U;
to_send.extended = to_push.extended; // TXRQ
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;
}
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;
// next
CANx->RF0R |= CAN_RF0R_RFOM0;
}
}
static void CAN1_TX_IRQ_Handler(void) { process_can(0); }
static void CAN1_RX0_IRQ_Handler(void) { can_rx(0); }
static void CAN1_SCE_IRQ_Handler(void) { can_sce(0); }
static void CAN2_TX_IRQ_Handler(void) { process_can(1); }
static void CAN2_RX0_IRQ_Handler(void) { can_rx(1); }
static void CAN2_SCE_IRQ_Handler(void) { can_sce(1); }
static void CAN3_TX_IRQ_Handler(void) { process_can(2); }
static void CAN3_RX0_IRQ_Handler(void) { can_rx(2); }
static void CAN3_SCE_IRQ_Handler(void) { can_sce(2); }
bool can_init(uint8_t can_number) {
bool ret = false;
REGISTER_INTERRUPT(CAN1_TX_IRQn, CAN1_TX_IRQ_Handler, CAN_INTERRUPT_RATE, FAULT_INTERRUPT_RATE_CAN_1)
REGISTER_INTERRUPT(CAN1_RX0_IRQn, CAN1_RX0_IRQ_Handler, CAN_INTERRUPT_RATE, FAULT_INTERRUPT_RATE_CAN_1)
REGISTER_INTERRUPT(CAN1_SCE_IRQn, CAN1_SCE_IRQ_Handler, CAN_INTERRUPT_RATE, FAULT_INTERRUPT_RATE_CAN_1)
REGISTER_INTERRUPT(CAN2_TX_IRQn, CAN2_TX_IRQ_Handler, CAN_INTERRUPT_RATE, FAULT_INTERRUPT_RATE_CAN_2)
REGISTER_INTERRUPT(CAN2_RX0_IRQn, CAN2_RX0_IRQ_Handler, CAN_INTERRUPT_RATE, FAULT_INTERRUPT_RATE_CAN_2)
REGISTER_INTERRUPT(CAN2_SCE_IRQn, CAN2_SCE_IRQ_Handler, CAN_INTERRUPT_RATE, FAULT_INTERRUPT_RATE_CAN_2)
REGISTER_INTERRUPT(CAN3_TX_IRQn, CAN3_TX_IRQ_Handler, CAN_INTERRUPT_RATE, FAULT_INTERRUPT_RATE_CAN_3)
REGISTER_INTERRUPT(CAN3_RX0_IRQn, CAN3_RX0_IRQ_Handler, CAN_INTERRUPT_RATE, FAULT_INTERRUPT_RATE_CAN_3)
REGISTER_INTERRUPT(CAN3_SCE_IRQn, CAN3_SCE_IRQ_Handler, CAN_INTERRUPT_RATE, FAULT_INTERRUPT_RATE_CAN_3)
if (can_number != 0xffU) {
CAN_TypeDef *CANx = CANIF_FROM_CAN_NUM(can_number);
ret &= can_set_speed(can_number);
ret &= llcan_init(CANx);
// in case there are queued up messages
process_can(can_number);
}
return ret;
}

View File

@@ -0,0 +1,22 @@
#pragma once
// IRQs: CAN1_TX, CAN1_RX0, CAN1_SCE
// CAN2_TX, CAN2_RX0, CAN2_SCE
// CAN3_TX, CAN3_RX0, CAN3_SCE
#define CAN_ARRAY_SIZE 3
#define CAN_IRQS_ARRAY_SIZE 3
extern CAN_TypeDef *cans[CAN_ARRAY_SIZE];
extern uint8_t can_irq_number[CAN_IRQS_ARRAY_SIZE][CAN_IRQS_ARRAY_SIZE];
bool can_set_speed(uint8_t can_number);
void can_clear_send(CAN_TypeDef *CANx, uint8_t can_number);
void update_can_health_pkt(uint8_t can_number, uint32_t ir_reg);
// ***************************** CAN *****************************
// CANx_TX IRQ Handler
void process_can(uint8_t can_number);
// CANx_RX0 IRQ Handler
// blink blue when we are receiving CAN messages
void can_rx(uint8_t can_number);
bool can_init(uint8_t can_number);

View File

@@ -0,0 +1,264 @@
#include "can_common_declarations.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[CAN_HEALTH_ARRAY_SIZE] = {{0}, {0}, {0}};
// Ignition detected from CAN meessages
bool ignition_can = false;
uint32_t ignition_can_cnt = 0U;
int can_live = 0;
int pending_can_live = 0;
int can_silent = ALL_CAN_SILENT;
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
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[CAN_QUEUES_ARRAY_SIZE] = {&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[BUS_CONFIG_ARRAY_SIZE] = {
{ .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 },
{ .bus_lookup = 0xFFU, .can_num_lookup = 0xFFU, .forwarding_bus = -1, .can_speed = 333U, .can_data_speed = 333U, .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++) {
if (!current_board->has_canfd) {
bus_config[i].can_data_speed = 0U;
}
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
void ignition_can_hook(CANPacket_t *to_push) {
int bus = GET_BUS(to_push);
if (bus == 0) {
int addr = GET_ADDR(to_push);
int len = GET_LEN(to_push);
// GM exception
if ((addr == 0x1F1) && (len == 8)) {
// SystemPowerMode (2=Run, 3=Crank Request)
ignition_can = (GET_BYTE(to_push, 0) & 0x2U) != 0U;
ignition_can_cnt = 0U;
}
// Rivian R1S/T GEN1 exception
if ((addr == 0x152) && (len == 8)) {
// 0x152 overlaps with Subaru pre-global which has this bit as the high beam
int counter = GET_BYTE(to_push, 1) & 0xFU; // max is only 14
static int prev_counter = -1;
if ((counter == ((prev_counter + 1) % 15)) && (prev_counter != -1)) {
// VDM_OutputSignals->VDM_EpasPowerMode
ignition_can = ((GET_BYTE(to_push, 7) >> 4U) & 0x3U) == 1U; // VDM_EpasPowerMode_Drive_On=1
ignition_can_cnt = 0U;
}
prev_counter = counter;
}
// Tesla Model 3/Y exception
if ((addr == 0x221) && (len == 8)) {
// 0x221 overlaps with Rivian which has random data on byte 0
int counter = GET_BYTE(to_push, 6) >> 4;
static int prev_counter = -1;
if ((counter == ((prev_counter + 1) % 16)) && (prev_counter != -1)) {
// VCFRONT_LVPowerState->VCFRONT_vehiclePowerState
int power_state = (GET_BYTE(to_push, 0) >> 5U) & 0x3U;
ignition_can = power_state == 0x3; // VEHICLE_POWER_STATE_DRIVE=3
ignition_can_cnt = 0U;
}
prev_counter = counter;
}
// Mazda exception
if ((addr == 0x9E) && (len == 8)) {
ignition_can = (GET_BYTE(to_push, 0) >> 5) == 0x6U;
ignition_can_cnt = 0U;
}
}
}
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_BUS_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;
}

View File

@@ -0,0 +1,88 @@
#pragma once
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;
#define CAN_HEALTH_ARRAY_SIZE 3
extern can_health_t can_health[CAN_HEALTH_ARRAY_SIZE];
// Ignition detected from CAN meessages
extern bool ignition_can;
extern uint32_t ignition_can_cnt;
#define ALL_CAN_SILENT 0xFF
#define ALL_CAN_LIVE 0
extern int can_live;
extern int pending_can_live;
extern int can_silent;
extern bool can_loopback;
// ******************* functions prototypes *********************
bool can_init(uint8_t can_number);
void process_can(uint8_t can_number);
// ********************* instantiate queues *********************
#define CAN_QUEUES_ARRAY_SIZE 3
extern can_ring *can_queues[CAN_QUEUES_ARRAY_SIZE];
// 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);
// 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
#define BUS_CONFIG_ARRAY_SIZE 4
extern bus_config_t bus_config[BUS_CONFIG_ARRAY_SIZE];
#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
void ignition_can_hook(CANPacket_t *to_push);
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);

View File

@@ -0,0 +1,39 @@
#include "clock_source_declarations.h"
void clock_source_set_period(uint8_t period) {
register_set(&(TIM1->ARR), ((period*10U) - 1U), 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, 0xFFFFU); // Enable compare 1
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 1 value
register_set(&(TIM1->CCR3), (CLOCK_SOURCE_PULSE_LEN_MS*10U), 0xFFFFU); // Compare 1 value
register_set_bits(&(TIM1->DIER), TIM_DIER_UIE | TIM_DIER_CC1IE); // Enable interrupts
register_set(&(TIM1->CR1), TIM_CR1_CEN, 0x3FU); // Enable timer
// 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_gpio_alternate(GPIOB, 15, 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), 0xFFFFU);
// Enable output
register_set(&(TIM1->BDTR), TIM_BDTR_MOE, 0xFFFFU);
// Enable complementary compares
register_set_bits(&(TIM1->CCER), TIM_CCER_CC2NE | TIM_CCER_CC3NE);
}

View File

@@ -0,0 +1,7 @@
#pragma once
#define CLOCK_SOURCE_PERIOD_MS 50U
#define CLOCK_SOURCE_PULSE_LEN_MS 2U
void clock_source_set_period(uint8_t period);
void clock_source_init(bool enable_channel1);

View File

@@ -0,0 +1,84 @@
#include "stm32h7/lli2c.h"
#define CODEC_I2C_ADDR 0x10
void fake_siren_init(void);
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;
}
}
}
}
void fake_siren_set(bool enabled) {
static bool initialized = false;
static bool fake_siren_enabled = false;
if (!initialized) {
fake_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_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 };
// Init DAC
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);
// Setup DMAMUX (DAC_CH1_DMA as input)
register_set(&DMAMUX1_Channel1->CCR, 67U, DMAMUX_CxCR_DMAREQ_ID_Msk);
// Setup DMA
register_set(&DMA1_Stream1->M0AR, (uint32_t) fake_siren_lut, 0xFFFFFFFFU);
register_set(&DMA1_Stream1->PAR, (uint32_t) &(DAC1->DHR8R1), 0xFFFFFFFFU);
DMA1_Stream1->NDTR = sizeof(fake_siren_lut);
register_set(&DMA1_Stream1->FCR, 0U, 0x00000083U);
DMA1_Stream1->CR = (0b11UL << DMA_SxCR_PL_Pos);
DMA1_Stream1->CR |= DMA_SxCR_MINC | DMA_SxCR_CIRC | (1U << DMA_SxCR_DIR_Pos);
// 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;
// Enable the I2C to the codec
i2c_init(I2C5);
fake_siren_codec_enable(false);
}

87
panda/board/drivers/fan.h Normal file
View File

@@ -0,0 +1,87 @@
#include "fan_declarations.h"
struct fan_state_t fan_state;
static const uint8_t FAN_TICK_FREQ = 8U;
static const uint8_t FAN_STALL_THRESHOLD_MIN = 3U;
void fan_set_power(uint8_t percentage) {
fan_state.target_rpm = ((current_board->fan_max_rpm * CLAMP(percentage, 0U, 100U)) / 100U);
}
void llfan_init(void);
void fan_init(void) {
fan_state.stall_threshold = FAN_STALL_THRESHOLD_MIN;
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) {
const float FAN_I = 6.5f;
const uint8_t FAN_STALL_THRESHOLD_MAX = 8U;
if (current_board->fan_max_rpm > 0U) {
// 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;
// Stall detection
bool fan_stalled = false;
if (current_board->fan_stall_recovery) {
if (fan_state.target_rpm > 0U) {
if (fan_rpm_fast == 0U) {
fan_state.stall_counter = MIN(fan_state.stall_counter + 1U, 254U);
} else {
fan_state.stall_counter = 0U;
}
if (fan_state.stall_counter > (fan_state.stall_threshold*FAN_TICK_FREQ)) {
fan_stalled = true;
fan_state.stall_counter = 0U;
fan_state.stall_threshold = CLAMP(fan_state.stall_threshold + 2U, FAN_STALL_THRESHOLD_MIN, FAN_STALL_THRESHOLD_MAX);
fan_state.total_stall_count += 1U;
// datasheet gives this range as the minimum startup duty
fan_state.error_integral = CLAMP(fan_state.error_integral, 20.0f, 45.0f);
}
} else {
fan_state.stall_counter = 0U;
fan_state.stall_threshold = FAN_STALL_THRESHOLD_MIN;
}
}
#ifdef DEBUG_FAN
puth(fan_state.target_rpm);
print(" "); puth(fan_rpm_fast);
print(" "); puth(fan_state.power);
print(" "); puth(fan_state.stall_counter);
print("\n");
#endif
// Cooldown counter
if (fan_state.target_rpm > 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--;
}
}
// Update controller
if (fan_state.target_rpm == 0U) {
fan_state.error_integral = 0.0f;
} else {
float error = (fan_state.target_rpm - fan_rpm_fast) / ((float) current_board->fan_max_rpm);
fan_state.error_integral += FAN_I * error;
}
fan_state.error_integral = CLAMP(fan_state.error_integral, 0U, current_board->fan_max_pwm);
fan_state.power = fan_state.error_integral;
// Set PWM and enable line
pwm_set(TIM3, 3, fan_state.power);
current_board->set_fan_enabled(!fan_stalled && ((fan_state.target_rpm > 0U) || (fan_state.cooldown_counter > 0U)));
}
}

View File

@@ -0,0 +1,20 @@
#pragma once
struct fan_state_t {
uint16_t tach_counter;
uint16_t rpm;
uint16_t target_rpm;
uint8_t power;
float error_integral;
uint8_t stall_counter;
uint8_t stall_threshold;
uint8_t total_stall_count;
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);

274
panda/board/drivers/fdcan.h Normal file
View File

@@ -0,0 +1,274 @@
#include "fdcan_declarations.h"
FDCAN_GlobalTypeDef *cans[CANS_ARRAY_SIZE] = {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,
(unsigned int)(can_silent) & (1U << can_number)
);
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[3][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;
// can is live
pending_can_live = 1;
// 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;
}
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;
}

View File

@@ -0,0 +1,28 @@
#pragma once
// IRQs: FDCAN1_IT0, FDCAN1_IT1
// FDCAN2_IT0, FDCAN2_IT1
// FDCAN3_IT0, FDCAN3_IT1
#define CANFD
typedef struct {
volatile uint32_t header[2];
volatile uint32_t data_word[CANPACKET_DATA_SIZE_MAX/4U];
} canfd_fifo;
#define CANS_ARRAY_SIZE 3
extern FDCAN_GlobalTypeDef *cans[CANS_ARRAY_SIZE];
#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);
// ***************************** CAN *****************************
// FDFDCANx_IT1 IRQ Handler (TX)
void process_can(uint8_t can_number);
// FDFDCANx_IT0 IRQ Handler (RX and errors)
// blink blue when we are receiving CAN messages
void can_rx(uint8_t can_number);
bool can_init(uint8_t can_number);

View File

@@ -0,0 +1,92 @@
#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
typedef struct {
GPIO_TypeDef * const bank;
uint8_t pin;
} gpio_t;
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);
}
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);
}
}
// 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;
}

View File

@@ -0,0 +1,108 @@
#include "harness_declarations.h"
struct harness_t harness;
// The ignition relay is only used for testing purposes
void set_intercept_relay(bool intercept, bool ignition_relay) {
if (current_board->harness_config->has_harness) {
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 && current_board->harness_config->has_harness) {
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(current_board->harness_config->adc_channel_SBU1);
harness.sbu2_voltage_mV = adc_get_mV(current_board->harness_config->adc_channel_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);
}

View File

@@ -0,0 +1,34 @@
#pragma once
#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 {
const bool has_harness;
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 uint8_t adc_channel_SBU1;
const uint8_t adc_channel_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);

View File

@@ -0,0 +1,81 @@
#include "interrupts_declarations.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();
}

View File

@@ -0,0 +1,31 @@
#pragma once
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);

20
panda/board/drivers/led.h Normal file
View File

@@ -0,0 +1,20 @@
#define LED_RED 0U
#define LED_GREEN 1U
#define LED_BLUE 2U
void led_set(uint8_t color, bool enabled) {
if (color < 3U) {
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_mode(current_board->led_GPIO[i], current_board->led_pin[i], MODE_OUTPUT);
set_gpio_output_type(current_board->led_GPIO[i], current_board->led_pin[i], OUTPUT_TYPE_OPEN_DRAIN);
led_set(i, false);
}
}

56
panda/board/drivers/pwm.h Normal file
View File

@@ -0,0 +1,56 @@
#define PWM_COUNTER_OVERFLOW 2000U // To get ~50kHz
// 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;
}
}

View File

@@ -0,0 +1,68 @@
#include "registers_declarations.h"
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;
}
}

View File

@@ -0,0 +1,25 @@
#pragma once
typedef struct reg {
volatile uint32_t *address;
uint32_t value;
uint32_t check_mask;
bool logged_fault;
} reg;
// 10 bit hash with 23 as a prime
#define REGISTER_MAP_SIZE 0x3FFU
#define HASHING_PRIME 23U
#define CHECK_COLLISION(hash, addr) (((uint32_t) register_map[hash].address != 0U) && (register_map[hash].address != (addr)))
// 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);

View File

@@ -0,0 +1,21 @@
#include "simple_watchdog_declarations.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();
}

View File

@@ -0,0 +1,10 @@
#pragma once
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);

241
panda/board/drivers/spi.h Normal file
View File

@@ -0,0 +1,241 @@
#pragma once
#include "spi_declarations.h"
#include "crc.h"
#ifdef STM32H7
#define SPI_BUF_SIZE 2048U
// H7 DMA2 located in D2 domain, so we need to use SRAM1/SRAM2
__attribute__((section(".sram12"))) uint8_t spi_buf_rx[SPI_BUF_SIZE];
__attribute__((section(".sram12"))) uint8_t spi_buf_tx[SPI_BUF_SIZE];
#else
#define SPI_BUF_SIZE 1024U
uint8_t spi_buf_rx[SPI_BUF_SIZE];
uint8_t spi_buf_tx[SPI_BUF_SIZE];
#endif
uint16_t spi_checksum_error_count = 0;
#if defined(ENABLE_SPI) || defined(BOOTSTUB)
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, send max response length
response_len = spi_data_len_miso;
response_ack = true;
} 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_checksum_error_count < UINT16_MAX)) {
spi_checksum_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;
}
#else
void can_tx_comms_resume_spi(void) {
return;
}
#endif

View File

@@ -0,0 +1,52 @@
#pragma once
#include "crc.h"
#define SPI_TIMEOUT_US 10000U
// got max rate from hitting a non-existent endpoint
// in a tight loop, plus some buffer
#define SPI_IRQ_RATE 16000U
#ifdef STM32H7
#define SPI_BUF_SIZE 2048U
// H7 DMA2 located in D2 domain, so we need to use SRAM1/SRAM2
__attribute__((section(".sram12"))) extern uint8_t spi_buf_rx[SPI_BUF_SIZE];
__attribute__((section(".sram12"))) extern uint8_t spi_buf_tx[SPI_BUF_SIZE];
#else
#define SPI_BUF_SIZE 1024U
extern uint8_t spi_buf_rx[SPI_BUF_SIZE];
extern 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
};
extern uint16_t spi_checksum_error_count;
#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);
void can_tx_comms_resume_spi(void);
#if defined(ENABLE_SPI) || defined(BOOTSTUB)
void spi_init(void);
void spi_rx_done(void);
void spi_tx_done(bool reset);
#endif

View File

@@ -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, 1, 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);
}

183
panda/board/drivers/uart.h Normal file
View File

@@ -0,0 +1,183 @@
#include "uart_declarations.h"
// IRQs: USART2, USART3, UART5
// ***************************** 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
#ifdef STM32H7
UART_BUFFER(som_debug, FIFO_SIZE_INT, FIFO_SIZE_INT, UART7, NULL, true)
#else
// UART7 is not available on F4
UART_BUFFER(som_debug, 1U, 1U, NULL, NULL, true)
#endif
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;
}
void clear_uart_buff(uart_ring *q) {
ENTER_CRITICAL();
q->w_ptr_tx = 0;
q->r_ptr_tx = 0;
q->w_ptr_rx = 0;
q->r_ptr_rx = 0;
EXIT_CRITICAL();
}
// ************************ 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 putui(uint32_t i) {
uint32_t i_copy = i;
char str[11];
uint8_t idx = 10;
str[idx] = '\0';
idx--;
do {
str[idx] = (i_copy % 10U) + 0x30U;
idx--;
i_copy /= 10;
} while (i_copy != 0U);
print(&str[idx + 1U]);
}
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);
}
void puth2(unsigned int i) {
puthx(i, 2U);
}
#if defined(ENABLE_SPI) || defined(BOOTSTUB) || defined(DEBUG)
void puth4(unsigned int i) {
puthx(i, 4U);
}
#endif
#if defined(ENABLE_SPI) || defined(BOOTSTUB) || defined(DEBUG_USB) || defined(DEBUG_COMMS)
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");
puth2(((const unsigned char*)a)[i]);
print(" ");
}
}
print("\n");
}
#endif

View File

@@ -0,0 +1,40 @@
#pragma once
// IRQs: USART2, USART3, UART5
// ***************************** 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);
void puth2(unsigned int i);
#if defined(ENABLE_SPI) || defined(BOOTSTUB) || defined(DEBUG)
void puth4(unsigned int i);
#endif
void hexdump(const void *a, int l);

798
panda/board/drivers/usb.h Normal file
View File

@@ -0,0 +1,798 @@
#include "usb_declarations.h"
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 devices 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);
// response pending if -1 was returned
if (resp_len != -1) {
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();
}

View File

@@ -0,0 +1,111 @@
#pragma once
// 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;
void usb_init(void);
void refresh_can_tx_slots_available(void);
// **** supporting defines ****
#define USB_REQ_GET_STATUS 0x00
#define USB_REQ_CLEAR_FEATURE 0x01
#define USB_REQ_SET_FEATURE 0x03
#define USB_REQ_SET_ADDRESS 0x05
#define USB_REQ_GET_DESCRIPTOR 0x06
#define USB_REQ_SET_DESCRIPTOR 0x07
#define USB_REQ_GET_CONFIGURATION 0x08
#define USB_REQ_SET_CONFIGURATION 0x09
#define USB_REQ_GET_INTERFACE 0x0A
#define USB_REQ_SET_INTERFACE 0x0B
#define USB_REQ_SYNCH_FRAME 0x0C
#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_OTHER_SPEED_CONFIGURATION 0x07
#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
#define STRING_OFFSET_IINTERFACE 0x05
// WebUSB requests
#define WEBUSB_REQ_GET_URL 0x02
// WebUSB types
#define WEBUSB_DESC_TYPE_URL 0x03
#define WEBUSB_URL_SCHEME_HTTPS 0x01
#define WEBUSB_URL_SCHEME_HTTP 0x00
// 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_GOUT_NAK 1
#define STS_DATA_UPDT 2
#define STS_XFER_COMP 3
#define STS_SETUP_COMP 4
#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_CONTROL 0
#define ENDPOINT_TYPE_ISO 1
#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
// ***************************** USB port *****************************
void can_tx_comms_resume_usb(void);

View File

@@ -0,0 +1,24 @@
typedef enum {
WATCHDOG_50_MS = (400U - 1U),
WATCHDOG_500_MS = 4000U,
} WatchdogTimeout;
static void watchdog_feed(void) {
IND_WDG->KR = 0xAAAAU;
}
void watchdog_init(WatchdogTimeout timeout) {
// enable watchdog
IND_WDG->KR = 0xCCCCU;
IND_WDG->KR = 0x5555U;
// 32KHz / 4 prescaler = 8000Hz
register_set(&(IND_WDG->PR), 0x0U, IWDG_PR_PR_Msk);
register_set(&(IND_WDG->RLR), timeout, IWDG_RLR_RL_Msk);
// wait for watchdog to be updated
while (IND_WDG->SR != 0U);
// start the countdown
watchdog_feed();
}

66
panda/board/early_init.h Normal file
View File

@@ -0,0 +1,66 @@
// 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();
#ifdef PANDA
current_board->init_bootloader();
#endif
led_set(LED_GREEN, 1);
jump_to_bootloader();
}
}

35
panda/board/fake_stm.h Normal file
View File

@@ -0,0 +1,35 @@
// minimal code to fake a panda for tests
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include "utils.h"
#define CANFD
#define ALLOW_DEBUG
#define PANDA
#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;

25
panda/board/faults.h Normal file
View File

@@ -0,0 +1,25 @@
#include "faults_declarations.h"
uint8_t fault_status = FAULT_STATUS_NONE;
uint32_t faults = 0U;
void fault_occurred(uint32_t fault) {
if ((faults & fault) == 0U) {
if ((PERMANENT_FAULTS & fault) != 0U) {
print("Permanent fault occurred: 0x"); puth(fault); print("\n");
fault_status = FAULT_STATUS_PERMANENT;
} else {
print("Temporary fault occurred: 0x"); puth(fault); print("\n");
fault_status = FAULT_STATUS_TEMPORARY;
}
}
faults |= fault;
}
void fault_recovered(uint32_t fault) {
if ((PERMANENT_FAULTS & fault) == 0U) {
faults &= ~fault;
} else {
print("Cannot recover from a permanent fault!\n");
}
}

View File

@@ -0,0 +1,44 @@
#pragma once
#define FAULT_STATUS_NONE 0U
#define FAULT_STATUS_TEMPORARY 1U
#define FAULT_STATUS_PERMANENT 2U
// Fault types, matches cereal.log.PandaState.FaultType
#define FAULT_RELAY_MALFUNCTION (1UL << 0)
#define FAULT_UNUSED_INTERRUPT_HANDLED (1UL << 1)
#define FAULT_INTERRUPT_RATE_CAN_1 (1UL << 2)
#define FAULT_INTERRUPT_RATE_CAN_2 (1UL << 3)
#define FAULT_INTERRUPT_RATE_CAN_3 (1UL << 4)
#define FAULT_INTERRUPT_RATE_TACH (1UL << 5)
#define FAULT_INTERRUPT_RATE_GMLAN (1UL << 6) // deprecated
#define FAULT_INTERRUPT_RATE_INTERRUPTS (1UL << 7)
#define FAULT_INTERRUPT_RATE_SPI_DMA (1UL << 8)
#define FAULT_INTERRUPT_RATE_SPI_CS (1UL << 9)
#define FAULT_INTERRUPT_RATE_UART_1 (1UL << 10)
#define FAULT_INTERRUPT_RATE_UART_2 (1UL << 11)
#define FAULT_INTERRUPT_RATE_UART_3 (1UL << 12)
#define FAULT_INTERRUPT_RATE_UART_5 (1UL << 13)
#define FAULT_INTERRUPT_RATE_UART_DMA (1UL << 14)
#define FAULT_INTERRUPT_RATE_USB (1UL << 15)
#define FAULT_INTERRUPT_RATE_TIM1 (1UL << 16)
#define FAULT_INTERRUPT_RATE_TIM3 (1UL << 17)
#define FAULT_REGISTER_DIVERGENT (1UL << 18)
#define FAULT_INTERRUPT_RATE_KLINE_INIT (1UL << 19)
#define FAULT_INTERRUPT_RATE_CLOCK_SOURCE (1UL << 20)
#define FAULT_INTERRUPT_RATE_TICK (1UL << 21)
#define FAULT_INTERRUPT_RATE_EXTI (1UL << 22)
#define FAULT_INTERRUPT_RATE_SPI (1UL << 23)
#define FAULT_INTERRUPT_RATE_UART_7 (1UL << 24)
#define FAULT_SIREN_MALFUNCTION (1UL << 25)
#define FAULT_HEARTBEAT_LOOP_WATCHDOG (1UL << 26)
#define FAULT_INTERRUPT_RATE_SOUND_DMA (1UL << 27)
// Permanent faults
#define PERMANENT_FAULTS 0U
extern uint8_t fault_status;
extern uint32_t faults;
void fault_occurred(uint32_t fault);
void fault_recovered(uint32_t fault);

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

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

158
panda/board/flasher.h Normal file
View File

@@ -0,0 +1,158 @@
// from the linker script
#ifdef STM32H7
#define APP_START_ADDRESS 0x8020000U
#elif defined(STM32F4)
#define APP_START_ADDRESS 0x8004000U
#endif
// flasher state variables
uint32_t *prog_ptr = NULL;
bool unlocked = false;
void spi_init(void);
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);
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 USB
usb_init();
// enable SPI
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);
}
}

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

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

61
panda/board/health.h Normal file
View File

@@ -0,0 +1,61 @@
// When changing these structs, python/__init__.py needs to be kept up to date!
#define HEALTH_PACKET_VERSION 16
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_checksum_error_count_pkt;
uint8_t fan_stall_count;
uint16_t sbu1_voltage_mV;
uint16_t sbu2_voltage_mV;
uint8_t som_reset_triggered;
};
#define CAN_HEALTH_PACKET_VERSION 5
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;

View File

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

View File

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

View File

@@ -0,0 +1,75 @@
// ******************** Prototypes ********************
typedef void (*board_init)(void);
typedef void (*board_board_tick)(void);
typedef bool (*board_get_button)(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 bool has_canfd;
const bool has_sbu_sense;
const uint16_t avdd_mV;
board_init init;
board_board_tick board_tick;
board_get_button get_button;
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_V1 1U
#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;
void unused_set_individual_ignition(uint8_t bitmask) {
UNUSED(bitmask);
}
void unused_board_enable_header_pin(uint8_t pin_num, bool enabled) {
UNUSED(pin_num);
UNUSED(enabled);
}
void unused_set_panda_individual_power(uint8_t port_num, bool enabled) {
UNUSED(port_num);
UNUSED(enabled);
}

View File

@@ -0,0 +1,158 @@
// ///////////////////////// //
// Jungle board v1 (STM32F4) //
// ///////////////////////// //
void board_v1_enable_can_transceiver(uint8_t transceiver, bool enabled) {
switch (transceiver) {
case 1U:
set_gpio_output(GPIOC, 1, !enabled);
break;
case 2U:
set_gpio_output(GPIOC, 13, !enabled);
break;
case 3U:
set_gpio_output(GPIOA, 0, !enabled);
break;
case 4U:
set_gpio_output(GPIOB, 10, !enabled);
break;
default:
print("Invalid CAN transceiver ("); puth(transceiver); print("): enabling failed\n");
break;
}
}
void board_v1_set_can_mode(uint8_t mode) {
board_v1_enable_can_transceiver(2U, false);
board_v1_enable_can_transceiver(4U, false);
switch (mode) {
case CAN_MODE_NORMAL:
print("Setting normal CAN mode\n");
// B12,B13: disable OBD mode
set_gpio_mode(GPIOB, 12, MODE_INPUT);
set_gpio_mode(GPIOB, 13, MODE_INPUT);
// B5,B6: normal CAN2 mode
set_gpio_alternate(GPIOB, 5, GPIO_AF9_CAN2);
set_gpio_alternate(GPIOB, 6, GPIO_AF9_CAN2);
can_mode = CAN_MODE_NORMAL;
board_v1_enable_can_transceiver(2U, true);
break;
case CAN_MODE_OBD_CAN2:
print("Setting OBD CAN mode\n");
// B5,B6: disable normal CAN2 mode
set_gpio_mode(GPIOB, 5, MODE_INPUT);
set_gpio_mode(GPIOB, 6, MODE_INPUT);
// B12,B13: OBD mode
set_gpio_alternate(GPIOB, 12, GPIO_AF9_CAN2);
set_gpio_alternate(GPIOB, 13, GPIO_AF9_CAN2);
can_mode = CAN_MODE_OBD_CAN2;
board_v1_enable_can_transceiver(4U, true);
break;
default:
print("Tried to set unsupported CAN mode: "); puth(mode); print("\n");
break;
}
}
void board_v1_set_harness_orientation(uint8_t orientation) {
switch (orientation) {
case HARNESS_ORIENTATION_NONE:
set_gpio_output(GPIOA, 2, false);
set_gpio_output(GPIOA, 3, false);
set_gpio_output(GPIOA, 4, false);
set_gpio_output(GPIOA, 5, false);
harness_orientation = orientation;
break;
case HARNESS_ORIENTATION_1:
set_gpio_output(GPIOA, 2, false);
set_gpio_output(GPIOA, 3, (ignition != 0U));
set_gpio_output(GPIOA, 4, true);
set_gpio_output(GPIOA, 5, false);
harness_orientation = orientation;
break;
case HARNESS_ORIENTATION_2:
set_gpio_output(GPIOA, 2, (ignition != 0U));
set_gpio_output(GPIOA, 3, false);
set_gpio_output(GPIOA, 4, false);
set_gpio_output(GPIOA, 5, true);
harness_orientation = orientation;
break;
default:
print("Tried to set an unsupported harness orientation: "); puth(orientation); print("\n");
break;
}
}
bool panda_power = false;
void board_v1_set_panda_power(bool enable) {
panda_power = enable;
set_gpio_output(GPIOB, 14, enable);
}
bool board_v1_get_button(void) {
return get_gpio_input(GPIOC, 8);
}
void board_v1_set_ignition(bool enabled) {
ignition = enabled ? 0xFFU : 0U;
board_v1_set_harness_orientation(harness_orientation);
}
float board_v1_get_channel_power(uint8_t channel) {
UNUSED(channel);
return 0.0f;
}
uint16_t board_v1_get_sbu_mV(uint8_t channel, uint8_t sbu) {
UNUSED(channel); UNUSED(sbu);
return 0U;
}
void board_v1_init(void) {
common_init_gpio();
// A8,A15: normal CAN3 mode
set_gpio_alternate(GPIOA, 8, GPIO_AF11_CAN3);
set_gpio_alternate(GPIOA, 15, GPIO_AF11_CAN3);
board_v1_set_can_mode(CAN_MODE_NORMAL);
// Enable CAN transceivers
for(uint8_t i = 1; i <= 4; i++) {
board_v1_enable_can_transceiver(i, true);
}
// Set normal CAN mode
board_v1_set_can_mode(CAN_MODE_NORMAL);
// Set to no harness orientation
board_v1_set_harness_orientation(HARNESS_ORIENTATION_NONE);
// Enable panda power by default
board_v1_set_panda_power(true);
}
void board_v1_tick(void) {}
board board_v1 = {
.has_canfd = false,
.has_sbu_sense = false,
.avdd_mV = 3300U,
.init = &board_v1_init,
.led_GPIO = {GPIOC, GPIOC, GPIOC},
.led_pin = {9, 7, 6},
.board_tick = &board_v1_tick,
.get_button = &board_v1_get_button,
.set_panda_power = &board_v1_set_panda_power,
.set_panda_individual_power = &unused_set_panda_individual_power,
.set_ignition = &board_v1_set_ignition,
.set_individual_ignition = &unused_set_individual_ignition,
.set_harness_orientation = &board_v1_set_harness_orientation,
.set_can_mode = &board_v1_set_can_mode,
.enable_can_transceiver = &board_v1_enable_can_transceiver,
.enable_header_pin = &unused_board_enable_header_pin,
.get_channel_power = &board_v1_get_channel_power,
.get_sbu_mV = &board_v1_get_sbu_mV,
};

View File

@@ -0,0 +1,308 @@
// ///////////////////////// //
// Jungle board v2 (STM32H7) //
// ///////////////////////// //
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},
};
adc_channel_t sbu1_channels[] = {
{.adc = ADC3, .channel = 12},
{.adc = ADC3, .channel = 2},
{.adc = ADC3, .channel = 4},
{.adc = ADC3, .channel = 6},
{.adc = ADC3, .channel = 8},
{.adc = ADC3, .channel = 10},
};
adc_channel_t sbu2_channels[] = {
{.adc = ADC1, .channel = 13},
{.adc = ADC3, .channel = 3},
{.adc = ADC3, .channel = 5},
{.adc = ADC3, .channel = 7},
{.adc = ADC3, .channel = 9},
{.adc = ADC3, .channel = 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(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].adc, sbu1_channels[channel - 1U].channel);
break;
case SBU2:
ret = adc_get_mV(sbu2_channels[channel - 1U].adc, sbu2_channels[channel - 1U].channel);
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 = {
.has_canfd = true,
.has_sbu_sense = true,
.avdd_mV = 3300U,
.init = &board_v2_init,
.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,
};

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

@@ -0,0 +1,27 @@
#!/usr/bin/env python3
import os
import subprocess
import argparse
from panda import PandaJungle
board_path = os.path.dirname(os.path.realpath(__file__))
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--all", action="store_true", help="Recover all panda jungle devices")
args = parser.parse_args()
subprocess.check_call(f"scons -C {board_path}/.. -u -j$(nproc) {board_path}", 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)

View File

@@ -0,0 +1,24 @@
// When changing these structs, python/__init__.py needs to be kept up to date!
#define JUNGLE_HEALTH_PACKET_VERSION 1
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;
};

View File

@@ -0,0 +1,267 @@
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 versions
case 0xdd:
resp[0] = JUNGLE_HEALTH_PACKET_VERSION;
resp[1] = CAN_PACKET_VERSION;
resp[2] = CAN_HEALTH_PACKET_VERSION;
resp_len = 3;
break;
// **** 0xde: set can bitrate
case 0xde:
if ((req->param1 < PANDA_BUS_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_BUS_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;
// **** 0xf2: Clear debug ring buffer.
case 0xf2:
print("Clearing debug queue.\n");
clear_uart_buff(get_ring_by_number(0));
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) ? ALL_CAN_SILENT : ALL_CAN_LIVE;
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) &&
current_board->has_canfd &&
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) && current_board->has_canfd) {
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;
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,141 @@
#!/usr/bin/env python3
import os
import time
import contextlib
import random
from termcolor import cprint
from 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")

View File

@@ -0,0 +1,45 @@
#!/usr/bin/env python3
import os
import time
import random
import contextlib
from panda import PandaJungle
from panda import Panda
PANDA_UNDER_TEST = Panda.HW_TYPE_UNO
panda_jungle = PandaJungle()
def silent_panda_connect():
with open(os.devnull, "w") as devnull:
with contextlib.redirect_stdout(devnull):
panda = Panda()
return panda
def reboot_panda(harness_orientation=PandaJungle.HARNESS_ORIENTATION_NONE, ignition=False):
print(f"Restarting panda with harness orientation: {harness_orientation} and ignition: {ignition}")
panda_jungle.set_panda_power(False)
panda_jungle.set_harness_orientation(harness_orientation)
panda_jungle.set_ignition(ignition)
time.sleep(2)
panda_jungle.set_panda_power(True)
time.sleep(2)
count = 0
if __name__ == "__main__":
while True:
ignition = random.randint(0, 1)
harness_orientation = random.randint(0, 2)
reboot_panda(harness_orientation, ignition)
p = silent_panda_connect()
assert p.get_type() == PANDA_UNDER_TEST
assert p.health()['car_harness_status'] == harness_orientation
if harness_orientation != PandaJungle.HARNESS_ORIENTATION_NONE:
assert p.health()['ignition_line'] == ignition
count += 1
print(f"Passed {count} loops")

View File

@@ -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)

View File

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

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