From d7120be9043d2cd8510022570c65e50831a4e38b Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 3 Aug 2023 23:55:13 -0700 Subject: [PATCH] panda jungle (#1547) * mv jungle * match pedal style * fix linter * fix fw path * fix paths * safety! --- .gitignore | 2 +- Dockerfile | 7 - SConscript | 3 + __init__.py | 4 + board/bootstub.c | 4 + board/config.h | 14 +- board/drivers/can_common.h | 14 +- board/drivers/fdcan.h | 3 + board/drivers/gpio.h | 11 + board/jungle/README.md | 26 ++ board/jungle/SConscript | 19 ++ board/jungle/__init__.py | 140 +++++++++ board/jungle/boards/board_declarations.h | 62 ++++ board/jungle/boards/board_v1.h | 168 +++++++++++ board/jungle/boards/board_v2.h | 278 ++++++++++++++++++ board/jungle/flash.py | 17 ++ board/jungle/jungle_health.h | 24 ++ board/jungle/main.c | 194 ++++++++++++ board/jungle/main_comms.h | 252 ++++++++++++++++ board/jungle/recover.py | 26 ++ board/jungle/scripts/can_health.py | 13 + board/jungle/scripts/can_printer.py | 35 +++ board/jungle/scripts/debug_console.py | 38 +++ board/jungle/scripts/echo_loopback_test.py | 68 +++++ board/jungle/scripts/get_version.py | 9 + board/jungle/scripts/health_test.py | 21 ++ board/jungle/scripts/loopback_test.py | 140 +++++++++ .../scripts/panda_identification_test.py | 45 +++ board/jungle/scripts/start.py | 18 ++ board/jungle/stm32fx/board.h | 7 + board/jungle/stm32h7/board.h | 9 + board/jungle/stm32h7/lladc.h | 54 ++++ board/stm32fx/peripherals.h | 2 +- board/stm32fx/stm32fx_config.h | 14 +- board/stm32h7/peripherals.h | 7 +- board/stm32h7/stm32h7_config.h | 2 +- python/__init__.py | 39 +-- requirements.txt | 1 + tests/bulk_write_test.py | 5 +- tests/canfd/test_canfd.py | 2 +- tests/hitl/2_health.py | 2 +- tests/hitl/conftest.py | 2 +- tests/message_drop_test.py | 2 +- tests/pedal/test_pedal.py | 6 +- tests/setup_device_ci.sh | 10 - 45 files changed, 1755 insertions(+), 64 deletions(-) create mode 100644 board/jungle/README.md create mode 100644 board/jungle/SConscript create mode 100644 board/jungle/__init__.py create mode 100644 board/jungle/boards/board_declarations.h create mode 100644 board/jungle/boards/board_v1.h create mode 100644 board/jungle/boards/board_v2.h create mode 100755 board/jungle/flash.py create mode 100644 board/jungle/jungle_health.h create mode 100644 board/jungle/main.c create mode 100644 board/jungle/main_comms.h create mode 100755 board/jungle/recover.py create mode 100755 board/jungle/scripts/can_health.py create mode 100755 board/jungle/scripts/can_printer.py create mode 100755 board/jungle/scripts/debug_console.py create mode 100755 board/jungle/scripts/echo_loopback_test.py create mode 100755 board/jungle/scripts/get_version.py create mode 100755 board/jungle/scripts/health_test.py create mode 100755 board/jungle/scripts/loopback_test.py create mode 100755 board/jungle/scripts/panda_identification_test.py create mode 100755 board/jungle/scripts/start.py create mode 100644 board/jungle/stm32fx/board.h create mode 100644 board/jungle/stm32h7/board.h create mode 100644 board/jungle/stm32h7/lladc.h diff --git a/.gitignore b/.gitignore index a3f6520b..1f96749b 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,7 @@ a.out .#* dist/ pandacan.egg-info/ -board/obj/ +obj/ examples/output.csv .DS_Store .vscode* diff --git a/Dockerfile b/Dockerfile index 7dc77ec7..6049cbee 100644 --- a/Dockerfile +++ b/Dockerfile @@ -80,13 +80,6 @@ RUN cd /tmp && \ rm -rf /tmp/openpilot/panda && \ rm -rf /tmp/tmppilot -RUN cd /tmp/openpilot && \ - git clone https://github.com/commaai/panda_jungle.git && \ - cd panda_jungle && \ - git fetch && \ - git checkout 3a791be1f1877a69cf45de16a670992380622297 && \ - rm -rf .git/ - RUN cd /tmp/openpilot && \ pip install --no-cache-dir -r opendbc/requirements.txt && \ pip install --no-cache-dir --upgrade aenum lru-dict pycurl tenacity atomicwrites serial smbus2 diff --git a/SConscript b/SConscript index 4454f0df..73414160 100644 --- a/SConscript +++ b/SConscript @@ -182,6 +182,9 @@ SConscript('board/SConscript') # pedal fw SConscript('board/pedal/SConscript') +# panda jungle fw +SConscript('board/jungle/SConscript') + # test files if GetOption('test'): SConscript('tests/libpanda/SConscript') diff --git a/__init__.py b/__init__.py index 25120f7f..e8e05c52 100644 --- a/__init__.py +++ b/__init__.py @@ -5,3 +5,7 @@ from .python.canhandle import CanHandle # noqa: F401 from .python import (Panda, PandaDFU, # noqa: F401 pack_can_buffer, unpack_can_buffer, calculate_checksum, unpack_log, DLC_TO_LEN, LEN_TO_DLC, ALTERNATIVE_EXPERIENCE, CANPACKET_HEAD_SIZE) + + +# panda jungle +from .board.jungle import PandaJungle, PandaJungleDFU # noqa: F401 diff --git a/board/bootstub.c b/board/bootstub.c index a2bf7dad..e2977e93 100644 --- a/board/bootstub.c +++ b/board/bootstub.c @@ -41,6 +41,10 @@ int main(void) { clock_init(); detect_board_type(); +#ifdef PANDA_JUNGLE + current_board->set_panda_power(true); +#endif + if (enter_bootloader_mode == ENTER_SOFTLOADER_MAGIC) { enter_bootloader_mode = 0; soft_flasher_start(); diff --git a/board/config.h b/board/config.h index 90316187..507d19e7 100644 --- a/board/config.h +++ b/board/config.h @@ -21,10 +21,18 @@ // USB definitions #define USB_VID 0xBBAAU -#ifdef BOOTSTUB - #define USB_PID 0xDDEEU +#ifdef PANDA_JUNGLE + #ifdef BOOTSTUB + #define USB_PID 0xDDEFU + #else + #define USB_PID 0xDDCFU + #endif #else - #define USB_PID 0xDDCCU + #ifdef BOOTSTUB + #define USB_PID 0xDDEEU + #else + #define USB_PID 0xDDCCU + #endif #endif // platform includes diff --git a/board/drivers/can_common.h b/board/drivers/can_common.h index be6b5b2a..4ad5b767 100644 --- a/board/drivers/can_common.h +++ b/board/drivers/can_common.h @@ -8,6 +8,7 @@ typedef struct { 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_enabled; @@ -157,14 +158,15 @@ void can_clear(can_ring *q) { // 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_lookup = 0U, .can_num_lookup = 0U, .can_speed = 5000U, .can_data_speed = 20000U, .canfd_enabled = false, .brs_enabled = false, .canfd_non_iso = false }, - { .bus_lookup = 1U, .can_num_lookup = 1U, .can_speed = 5000U, .can_data_speed = 20000U, .canfd_enabled = false, .brs_enabled = false, .canfd_non_iso = false }, - { .bus_lookup = 2U, .can_num_lookup = 2U, .can_speed = 5000U, .can_data_speed = 20000U, .canfd_enabled = false, .brs_enabled = false, .canfd_non_iso = false }, - { .bus_lookup = 0xFFU, .can_num_lookup = 0xFFU, .can_speed = 333U, .can_data_speed = 333U, .canfd_enabled = false, .brs_enabled = false, .canfd_non_iso = false }, + { .bus_lookup = 0U, .can_num_lookup = 0U, .forwarding_bus = -1, .can_speed = 5000U, .can_data_speed = 20000U, .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_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_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_enabled = false, .brs_enabled = false, .canfd_non_iso = false }, }; #define CANIF_FROM_CAN_NUM(num) (cans[num]) @@ -190,6 +192,10 @@ void can_flip_buses(uint8_t bus1, uint8_t bus2){ bus_config[bus2].can_num_lookup = bus1; } +void can_set_forwarding(uint8_t from, uint8_t to) { + bus_config[from].forwarding_bus = to; +} + void ignition_can_hook(CANPacket_t *to_push) { int bus = GET_BUS(to_push); int addr = GET_ADDR(to_push); diff --git a/board/drivers/fdcan.h b/board/drivers/fdcan.h index 7cab232d..ec3cc125 100644 --- a/board/drivers/fdcan.h +++ b/board/drivers/fdcan.h @@ -195,6 +195,9 @@ void can_rx(uint8_t can_number) { // 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; diff --git a/board/drivers/gpio.h b/board/drivers/gpio.h index 77af8765..88af9319 100644 --- a/board/drivers/gpio.h +++ b/board/drivers/gpio.h @@ -10,6 +10,11 @@ #define OUTPUT_TYPE_PUSH_PULL 0U #define OUTPUT_TYPE_OPEN_DRAIN 1U +typedef struct { + GPIO_TypeDef *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; @@ -63,6 +68,12 @@ int get_gpio_input(GPIO_TypeDef *GPIO, unsigned int pin) { return (GPIO->IDR & (1U << pin)) == (1U << pin); } +void gpio_set_all_output(const 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); + } +} + // Detection with internal pullup #define PULL_EFFECTIVE_DELAY 4096 bool detect_with_pull(GPIO_TypeDef *GPIO, int pin, int mode) { diff --git a/board/jungle/README.md b/board/jungle/README.md new file mode 100644 index 00000000..4c271cbf --- /dev/null +++ b/board/jungle/README.md @@ -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 < 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 + raise ValueError(f"unknown HW type: {hw_type}") + + # ******************* 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_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'') + + # ******************* 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) diff --git a/board/jungle/boards/board_declarations.h b/board/jungle/boards/board_declarations.h new file mode 100644 index 00000000..a02b906b --- /dev/null +++ b/board/jungle/boards/board_declarations.h @@ -0,0 +1,62 @@ +// ******************** Prototypes ******************** +typedef void (*board_init)(void); +typedef void (*board_set_led)(uint8_t color, bool enabled); +typedef void (*board_board_tick)(void); +typedef bool (*board_get_button)(void); +typedef void (*board_set_panda_power)(bool enabled); +typedef void (*board_set_ignition)(bool enabled); +typedef void (*board_set_harness_orientation)(uint8_t orientation); +typedef void (*board_set_can_mode)(uint8_t mode); +typedef void (*board_enable_can_transciever)(uint8_t transciever, 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 { + const char *board_type; + const bool has_canfd; + const uint16_t avdd_mV; + board_init init; + board_set_led set_led; + board_board_tick board_tick; + board_get_button get_button; + board_set_panda_power set_panda_power; + board_set_ignition set_ignition; + board_set_harness_orientation set_harness_orientation; + board_set_can_mode set_can_mode; + board_enable_can_transciever enable_can_transciever; + board_get_channel_power get_channel_power; + board_get_sbu_mV get_sbu_mV; + + // TODO: shouldn't need these + bool has_spi; + bool has_hw_gmlan; +}; + +// ******************* Definitions ******************** +#define HW_TYPE_UNKNOWN 0U +#define HW_TYPE_V1 1U +#define HW_TYPE_V2 2U + +// LED colors +#define LED_RED 0U +#define LED_GREEN 1U +#define LED_BLUE 2U + +// CAN modes +#define CAN_MODE_NORMAL 0U +#define CAN_MODE_GMLAN_CAN2 1U +#define CAN_MODE_GMLAN_CAN3 2U +#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; +bool ignition = false; diff --git a/board/jungle/boards/board_v1.h b/board/jungle/boards/board_v1.h new file mode 100644 index 00000000..5ad574fa --- /dev/null +++ b/board/jungle/boards/board_v1.h @@ -0,0 +1,168 @@ + +void board_v1_set_led(uint8_t color, bool enabled) { + switch (color){ + case LED_RED: + set_gpio_output(GPIOC, 9, !enabled); + break; + case LED_GREEN: + set_gpio_output(GPIOC, 7, !enabled); + break; + case LED_BLUE: + set_gpio_output(GPIOC, 6, !enabled); + break; + default: + break; + } +} + +void board_v1_set_can_mode(uint8_t mode) { + 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; + 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; + 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); + 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); + 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; + board_v1_set_harness_orientation(harness_orientation); +} + +void board_v1_enable_can_transciever(uint8_t transciever, bool enabled) { + switch (transciever){ + 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 transciever ("); puth(transciever); print("): enabling failed\n"); + break; + } +} + +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 transcievers + for(uint8_t i = 1; i <= 4; i++) { + board_v1_enable_can_transciever(i, true); + } + + // Disable LEDs + board_v1_set_led(LED_RED, false); + board_v1_set_led(LED_GREEN, false); + board_v1_set_led(LED_BLUE, false); + + // 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) {} + +const board board_v1 = { + .board_type = "V1", + .has_canfd = false, + .avdd_mV = 3300U, + .init = &board_v1_init, + .set_led = &board_v1_set_led, + .board_tick = &board_v1_tick, + .get_button = &board_v1_get_button, + .set_panda_power = &board_v1_set_panda_power, + .set_ignition = &board_v1_set_ignition, + .set_harness_orientation = &board_v1_set_harness_orientation, + .set_can_mode = &board_v1_set_can_mode, + .enable_can_transciever = &board_v1_enable_can_transciever, + .get_channel_power = &board_v1_get_channel_power, + .get_sbu_mV = &board_v1_get_sbu_mV, +}; \ No newline at end of file diff --git a/board/jungle/boards/board_v2.h b/board/jungle/boards/board_v2.h new file mode 100644 index 00000000..e4749673 --- /dev/null +++ b/board/jungle/boards/board_v2.h @@ -0,0 +1,278 @@ + +const 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}, +}; + +const 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}, +}; + +const 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}, +}; + +const 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}, +}; + +const gpio_t sbu2_relay_pins[] = { + {.bank = GPIOD, .pin = 4}, + {.bank = GPIOD, .pin = 10}, + {.bank = GPIOD, .pin = 13}, + {.bank = GPIOE, .pin = 1}, + {.bank = GPIOE, .pin = 8}, + {.bank = GPIOE, .pin = 12}, +}; + +const adc_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}, +}; + +const 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_led(uint8_t color, bool enabled) { + switch (color) { + case LED_RED: + set_gpio_output(GPIOE, 4, !enabled); + break; + case LED_GREEN: + set_gpio_output(GPIOE, 3, !enabled); + break; + case LED_BLUE: + set_gpio_output(GPIOE, 2, !enabled); + break; + default: + break; + } +} + +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_all_output(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_all_output(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_set_can_mode(uint8_t mode) { + 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; + 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; + break; + default: + break; + } +} + +bool panda_power = false; +void board_v2_set_panda_power(bool enable) { + panda_power = enable; + gpio_set_all_output(power_pins, sizeof(power_pins) / sizeof(gpio_t), enable); +} + +bool board_v2_get_button(void) { + return get_gpio_input(GPIOG, 15); +} + +void board_v2_set_ignition(bool enabled) { + ignition = enabled; + board_v2_set_harness_orientation(harness_orientation); +} + +void board_v2_enable_can_transciever(uint8_t transciever, bool enabled) { + switch (transciever){ + 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 transciever ("); puth(transciever); print("): enabling failed\n"); + break; + } +} + +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_v1_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(); + + // Disable LEDs + board_v2_set_led(LED_RED, false); + board_v2_set_led(LED_GREEN, false); + board_v2_set_led(LED_BLUE, false); + + // Normal CAN mode + board_v2_set_can_mode(CAN_MODE_NORMAL); + + // Enable CAN transcievers + for(uint8_t i = 1; i <= 4; i++) { + board_v2_enable_can_transciever(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); +} + +void board_v2_tick(void) {} + +const board board_v2 = { + .board_type = "V2", + .has_canfd = true, + .avdd_mV = 3300U, + .init = &board_v2_init, + .set_led = &board_v2_set_led, + .board_tick = &board_v2_tick, + .get_button = &board_v2_get_button, + .set_panda_power = &board_v2_set_panda_power, + .set_ignition = &board_v2_set_ignition, + .set_harness_orientation = &board_v2_set_harness_orientation, + .set_can_mode = &board_v2_set_can_mode, + .enable_can_transciever = &board_v2_enable_can_transciever, + .get_channel_power = &board_v2_get_channel_power, + .get_sbu_mV = &board_v1_get_sbu_mV, +}; \ No newline at end of file diff --git a/board/jungle/flash.py b/board/jungle/flash.py new file mode 100755 index 00000000..75a7f0c8 --- /dev/null +++ b/board/jungle/flash.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 +import os +import subprocess + +from panda import PandaJungle + +board_path = os.path.dirname(os.path.realpath(__file__)) + +if __name__ == "__main__": + subprocess.check_call(f"scons -C {board_path}/.. -u -j$(nproc) {board_path}", shell=True) + + serials = PandaJungle.list() + print(f"found {len(serials)} panda jungle(s) - {serials}") + for s in serials: + print("flashing", s) + with PandaJungle(serial=s) as p: + p.flash() diff --git a/board/jungle/jungle_health.h b/board/jungle/jungle_health.h new file mode 100644 index 00000000..931ed371 --- /dev/null +++ b/board/jungle/jungle_health.h @@ -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; +}; diff --git a/board/jungle/main.c b/board/jungle/main.c new file mode 100644 index 00000000..806fbd3f --- /dev/null +++ b/board/jungle/main.c @@ -0,0 +1,194 @@ +// ********************* Includes ********************* +#include "board/config.h" + +#include "board/safety.h" +#include "board/drivers/gmlan_alt.h" + +#include "board/drivers/pwm.h" +#include "board/drivers/usb.h" + +#include "board/early_init.h" +#include "board/provision.h" + +#include "board/health.h" +#include "jungle_health.h" + +#include "board/drivers/can_common.h" + +#ifdef STM32H7 + #include "board/drivers/fdcan.h" +#else + #include "board/drivers/bxcan.h" +#endif + +#include "board/obj/gitversion.h" + +#include "board/can_comms.h" +#include "main_comms.h" + + +// ********************* Serial debugging ********************* + +void debug_ring_callback(uart_ring *ring) { + char rcv; + while (getc(ring, &rcv)) { + (void)injectc(ring, rcv); + } +} + +// ***************************** main code ***************************** + +// cppcheck-suppress unusedFunction ; used in headers not included in cppcheck +void __initialize_hardware_early(void) { + early_initialization(); +} + +void __attribute__ ((noinline)) enable_fpu(void) { + // enable the FPU + SCB->CPACR |= ((3UL << (10U * 2U)) | (3UL << (11U * 2U))); +} + +// called at 8Hz +uint8_t loop_counter = 0U; +uint16_t button_press_cnt = 0U; +void tick_handler(void) { + if (TICK_TIMER->SR != 0) { + // tick drivers at 8Hz + usb_tick(); + + // decimated to 1Hz + if ((loop_counter % 8) == 0U) { + #ifdef DEBUG + print("** blink "); + print("rx:"); puth4(can_rx_q.r_ptr); print("-"); puth4(can_rx_q.w_ptr); print(" "); + print("tx1:"); puth4(can_tx1_q.r_ptr); print("-"); puth4(can_tx1_q.w_ptr); print(" "); + print("tx2:"); puth4(can_tx2_q.r_ptr); print("-"); puth4(can_tx2_q.w_ptr); print(" "); + print("tx3:"); puth4(can_tx3_q.r_ptr); print("-"); puth4(can_tx3_q.w_ptr); print("\n"); + #endif + + current_board->board_tick(); + + // check registers + check_registers(); + + // turn off the blue LED, turned on by CAN + current_board->set_led(LED_BLUE, false); + + // Blink and OBD CAN +#ifdef FINAL_PROVISIONING + current_board->set_can_mode(can_mode == CAN_MODE_NORMAL ? CAN_MODE_OBD_CAN2 : CAN_MODE_NORMAL); +#endif + + // on to the next one + uptime_cnt += 1U; + } + + current_board->set_led(LED_GREEN, green_led_enabled); + + // Check on button + bool current_button_status = current_board->get_button(); + + if (current_button_status && button_press_cnt == 10) { + current_board->set_panda_power(!panda_power); + } + +#ifdef FINAL_PROVISIONING + // ign on for 0.3s, off for 0.2s + const bool ign = (loop_counter % (3+2)) < 3; + if (ign != ignition) { + current_board->set_ignition(ign); + } +#else + static bool prev_button_status = false; + if (!current_button_status && prev_button_status && button_press_cnt < 10){ + current_board->set_ignition(!ignition); + } + prev_button_status = current_button_status; +#endif + + button_press_cnt = current_button_status ? button_press_cnt + 1 : 0; + + loop_counter++; + } + TICK_TIMER->SR = 0; +} + + +int main(void) { + // Init interrupt table + init_interrupts(true); + + // shouldn't have interrupts here, but just in case + disable_interrupts(); + + // init early devices + clock_init(); + peripherals_init(); + detect_board_type(); + + // print hello + print("\n\n\n************************ MAIN START ************************\n"); + + // check for non-supported board types + if (hw_type == HW_TYPE_UNKNOWN) { + print("Unsupported board type\n"); + while (1) { /* hang */ } + } + + print("Config:\n"); + print(" Board type: "); print(current_board->board_type); print("\n"); + + // init board + current_board->init(); + + // we have an FPU, let's use it! + enable_fpu(); + + microsecond_timer_init(); + + // 8Hz timer + REGISTER_INTERRUPT(TICK_TIMER_IRQ, tick_handler, 10U, FAULT_INTERRUPT_RATE_TICK) + tick_timer_init(); + +#ifdef DEBUG + print("DEBUG ENABLED\n"); +#endif + // enable USB (right before interrupts or enum can fail!) + usb_init(); + + print("**** INTERRUPTS ON ****\n"); + enable_interrupts(); + + can_silent = ALL_CAN_LIVE; + set_safety_hooks(SAFETY_ALLOUTPUT, 0U); + + can_init_all(); + current_board->set_harness_orientation(HARNESS_ORIENTATION_1); + +#ifdef FINAL_PROVISIONING + print("---- FINAL PROVISIONING BUILD ---- \n"); + can_set_forwarding(0, 2); + can_set_forwarding(1, 2); +#endif + + // LED should keep on blinking all the time + uint64_t cnt = 0; + for (cnt=0;;cnt++) { + // useful for debugging, fade breaks = panda is overloaded + for (uint32_t fade = 0U; fade < MAX_LED_FADE; fade += 1U) { + current_board->set_led(LED_RED, true); + delay(fade >> 4); + current_board->set_led(LED_RED, false); + delay((MAX_LED_FADE - fade) >> 4); + } + + for (uint32_t fade = MAX_LED_FADE; fade > 0U; fade -= 1U) { + current_board->set_led(LED_RED, true); + delay(fade >> 4); + current_board->set_led(LED_RED, false); + delay((MAX_LED_FADE - fade) >> 4); + } + } + + return 0; +} diff --git a/board/jungle/main_comms.h b/board/jungle/main_comms.h new file mode 100644 index 00000000..36ed6e79 --- /dev/null +++ b/board/jungle/main_comms.h @@ -0,0 +1,252 @@ +extern int _app_start[0xc000]; // Only first 3 sectors of size 0x4000 are used + +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(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; + // **** 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) { + 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)) && getc(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_transciever(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; + // **** 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; +} diff --git a/board/jungle/recover.py b/board/jungle/recover.py new file mode 100755 index 00000000..98afb067 --- /dev/null +++ b/board/jungle/recover.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +import os +import time +import subprocess + +from panda import PandaJungle, PandaJungleDFU + +board_path = os.path.dirname(os.path.realpath(__file__)) + +if __name__ == "__main__": + subprocess.check_call(f"scons -C {board_path}/.. -u -j$(nproc) {board_path}", shell=True) + + for s in PandaJungle.list(): + print("putting", s, "in DFU mode") + with PandaJungle(serial=s) as p: + 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() diff --git a/board/jungle/scripts/can_health.py b/board/jungle/scripts/can_health.py new file mode 100755 index 00000000..ff068b5b --- /dev/null +++ b/board/jungle/scripts/can_health.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 + +import time +from panda import PandaJungle + +if __name__ == "__main__": + jungle = PandaJungle() + + while True: + for bus in range(3): + print(bus, jungle.can_health(bus)) + print() + time.sleep(1) diff --git a/board/jungle/scripts/can_printer.py b/board/jungle/scripts/can_printer.py new file mode 100755 index 00000000..3e648063 --- /dev/null +++ b/board/jungle/scripts/can_printer.py @@ -0,0 +1,35 @@ +#!/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() + + start = sec_since_boot() + lp = sec_since_boot() + msgs = defaultdict(list) + canbus = int(os.getenv("CAN", "0")) + while True: + can_recv = p.can_recv() + for address, _, dat, src in can_recv: + if src == canbus: + msgs[address].append(dat) + + if sec_since_boot() - lp > 0.1: + dd = chr(27) + "[2J" + dd += "%5.2f\n" % (sec_since_boot() - start) + for k,v in sorted(zip(list(msgs.keys()), [binascii.hexlify(x[-1]) for x in list(msgs.values())])): + dd += "%s(%6d) %s\n" % ("%04X(%4d)" % (k,k),len(msgs[k]), v) + print(dd) + lp = sec_since_boot() + +if __name__ == "__main__": + can_printer() diff --git a/board/jungle/scripts/debug_console.py b/board/jungle/scripts/debug_console.py new file mode 100755 index 00000000..f8dd24ed --- /dev/null +++ b/board/jungle/scripts/debug_console.py @@ -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 = list([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) diff --git a/board/jungle/scripts/echo_loopback_test.py b/board/jungle/scripts/echo_loopback_test.py new file mode 100755 index 00000000..d68fa4f3 --- /dev/null +++ b/board/jungle/scripts/echo_loopback_test.py @@ -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") + assert False + +################################################################# +############################# 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") diff --git a/board/jungle/scripts/get_version.py b/board/jungle/scripts/get_version.py new file mode 100755 index 00000000..ad4a1c42 --- /dev/null +++ b/board/jungle/scripts/get_version.py @@ -0,0 +1,9 @@ +#!/usr/bin/env python3 +from panda import PandaJungle + +if __name__ == "__main__": + for p in PandaJungle.list(): + pp = PandaJungle(p) + print("%s: %s" % (pp.get_serial()[0], pp.get_version())) + + diff --git a/board/jungle/scripts/health_test.py b/board/jungle/scripts/health_test.py new file mode 100755 index 00000000..039f840e --- /dev/null +++ b/board/jungle/scripts/health_test.py @@ -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 + diff --git a/board/jungle/scripts/loopback_test.py b/board/jungle/scripts/loopback_test.py new file mode 100755 index 00000000..10caf42c --- /dev/null +++ b/board/jungle/scripts/loopback_test.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python3 +import os +import time +import contextlib +import random +from termcolor import cprint + +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][2] != string: + raise Exception("Received CAN message content or address does not match") + + # Check bus + if content[0][3] != 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(Panda.SAFETY_ELM327 if FOR_RELEASE_BUILDS else Panda.SAFETY_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(Panda.SAFETY_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 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") diff --git a/board/jungle/scripts/panda_identification_test.py b/board/jungle/scripts/panda_identification_test.py new file mode 100755 index 00000000..a61b0d60 --- /dev/null +++ b/board/jungle/scripts/panda_identification_test.py @@ -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") + + diff --git a/board/jungle/scripts/start.py b/board/jungle/scripts/start.py new file mode 100755 index 00000000..76afd14a --- /dev/null +++ b/board/jungle/scripts/start.py @@ -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) + + diff --git a/board/jungle/stm32fx/board.h b/board/jungle/stm32fx/board.h new file mode 100644 index 00000000..0adf7923 --- /dev/null +++ b/board/jungle/stm32fx/board.h @@ -0,0 +1,7 @@ +#include "boards/board_declarations.h" +#include "boards/board_v1.h" + +void detect_board_type(void) { + hw_type = HW_TYPE_V1; + current_board = &board_v1; +} diff --git a/board/jungle/stm32h7/board.h b/board/jungle/stm32h7/board.h new file mode 100644 index 00000000..4fe4fa46 --- /dev/null +++ b/board/jungle/stm32h7/board.h @@ -0,0 +1,9 @@ +#include "boards/board_declarations.h" + +#include "stm32h7/lladc.h" +#include "boards/board_v2.h" + +void detect_board_type(void) { + hw_type = HW_TYPE_V2; + current_board = &board_v2; +} diff --git a/board/jungle/stm32h7/lladc.h b/board/jungle/stm32h7/lladc.h new file mode 100644 index 00000000..06b742dd --- /dev/null +++ b/board/jungle/stm32h7/lladc.h @@ -0,0 +1,54 @@ + +typedef struct { + ADC_TypeDef *adc; + uint8_t channel; +} adc_channel_t; + +void adc_init(ADC_TypeDef *adc) { + adc->CR &= ~(ADC_CR_DEEPPWD); // Reset deep-power-down mode + adc->CR |= ADC_CR_ADVREGEN; // Enable ADC regulator + while(!(adc->ISR & ADC_ISR_LDORDY) && (adc != ADC3)); + + if (adc != ADC3) { + adc->CR &= ~(ADC_CR_ADCALDIF); // Choose single-ended calibration + adc->CR |= ADC_CR_ADCALLIN; // Lineriality calibration + } + adc->CR |= ADC_CR_ADCAL; // Start calibrtation + while((adc->CR & ADC_CR_ADCAL) != 0); + + adc->ISR |= ADC_ISR_ADRDY; + adc->CR |= ADC_CR_ADEN; + while(!(adc->ISR & ADC_ISR_ADRDY)); +} + +uint16_t adc_get_raw(ADC_TypeDef *adc, uint8_t channel) { + adc->SQR1 &= ~(ADC_SQR1_L); + adc->SQR1 = ((uint32_t) channel << 6U); + + if (channel < 10U) { + adc->SMPR1 = (0x7U << (channel * 3U)); + } else { + adc->SMPR2 = (0x7U << ((channel - 10U) * 3U)); + } + adc->PCSEL_RES0 = (0x1U << channel); + + adc->CR |= ADC_CR_ADSTART; + while (!(adc->ISR & ADC_ISR_EOC)); + + uint16_t res = adc->DR; + + while (!(adc->ISR & ADC_ISR_EOS)); + adc->ISR |= ADC_ISR_EOS; + + return res; +} + +uint16_t adc_get_mV(ADC_TypeDef *adc, uint8_t channel) { + uint16_t ret = 0; + if ((adc == ADC1) || (adc == ADC2)) { + ret = (adc_get_raw(adc, channel) * current_board->avdd_mV) / 65535U; + } else if (adc == ADC3) { + ret = (adc_get_raw(adc, channel) * current_board->avdd_mV) / 4095U; + } else {} + return ret; +} diff --git a/board/stm32fx/peripherals.h b/board/stm32fx/peripherals.h index 3eeebf0e..d910886d 100644 --- a/board/stm32fx/peripherals.h +++ b/board/stm32fx/peripherals.h @@ -68,7 +68,7 @@ void peripherals_init(void) { RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN; RCC->APB1ENR |= RCC_APB1ENR_USART2EN; RCC->APB1ENR |= RCC_APB1ENR_USART3EN; - #ifdef PANDA + #ifndef PEDAL RCC->APB1ENR |= RCC_APB1ENR_UART5EN; #endif RCC->APB1ENR |= RCC_APB1ENR_CAN1EN; diff --git a/board/stm32fx/stm32fx_config.h b/board/stm32fx/stm32fx_config.h index 1573426d..dd026e98 100644 --- a/board/stm32fx/stm32fx_config.h +++ b/board/stm32fx/stm32fx_config.h @@ -48,10 +48,10 @@ #include "comms_definitions.h" #ifndef BOOTSTUB - #ifdef PANDA - #include "main_declarations.h" - #else + #ifdef PEDAL #include "pedal/main_declarations.h" + #else + #include "main_declarations.h" #endif #else #include "bootstub_declarations.h" @@ -73,17 +73,17 @@ #include "drivers/watchdog.h" #include "stm32fx/llflash.h" -#if defined(PANDA) || defined(BOOTSTUB) +#if !defined(PEDAL) || defined(BOOTSTUB) #include "drivers/spi.h" #include "stm32fx/llspi.h" #endif -#if !defined(BOOTSTUB) && (defined(PANDA) || defined(PEDAL_USB)) +#if !defined(BOOTSTUB) && (!defined(PEDAL) || defined(PEDAL_USB)) #include "drivers/uart.h" #include "stm32fx/lluart.h" #endif -#if !defined(PEDAL_USB) && !defined(PEDAL) && !defined(BOOTSTUB) +#if defined(PANDA) && !defined(BOOTSTUB) #include "stm32fx/llexti.h" #endif @@ -91,7 +91,7 @@ #include "stm32fx/llbxcan.h" #endif -#if defined(PANDA) || defined(BOOTSTUB) || defined(PEDAL_USB) +#if !defined(PEDAL) || defined(PEDAL_USB) || defined(BOOTSTUB) #include "stm32fx/llusb.h" #endif diff --git a/board/stm32h7/peripherals.h b/board/stm32h7/peripherals.h index 7e3b2e5b..cf397644 100644 --- a/board/stm32h7/peripherals.h +++ b/board/stm32h7/peripherals.h @@ -125,10 +125,15 @@ void peripherals_init(void) { RCC->APB2ENR |= RCC_APB2ENR_TIM1EN; // clock source timer RCC->APB1HENR |= RCC_APB1HENR_FDCANEN; // FDCAN core enable - RCC->AHB1ENR |= RCC_AHB1ENR_ADC12EN; // Enable ADC clocks + RCC->AHB1ENR |= RCC_AHB1ENR_ADC12EN; // Enable ADC12 clocks RCC->APB4ENR |= RCC_APB4ENR_SYSCFGEN; +#ifdef PANDA_JUNGLE + RCC->AHB3ENR |= RCC_AHB3ENR_SDMMC1EN; // SDMMC + RCC->AHB4ENR |= RCC_AHB4ENR_ADC3EN; // Enable ADC3 clocks +#endif + // HS USB enable, also LP is needed for CSleep state(__WFI()) RCC->AHB1ENR |= RCC_AHB1ENR_USB1OTGHSEN; RCC->AHB1LPENR |= RCC_AHB1LPENR_USB1OTGHSLPEN; diff --git a/board/stm32h7/stm32h7_config.h b/board/stm32h7/stm32h7_config.h index 4e6f99bf..0cddad4f 100644 --- a/board/stm32h7/stm32h7_config.h +++ b/board/stm32h7/stm32h7_config.h @@ -75,7 +75,7 @@ separate IRQs for RX and TX. #include "drivers/watchdog.h" #include "stm32h7/llflash.h" -#if !defined(BOOTSTUB) && defined(PANDA) +#if !defined(BOOTSTUB) #include "drivers/uart.h" #include "stm32h7/lluart.h" #endif diff --git a/python/__init__.py b/python/__init__.py index 592a4bff..637a6b61 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -184,6 +184,7 @@ class Panda: GMLAN_CAN2 = 1 GMLAN_CAN3 = 2 + USB_PIDS = (0xddee, 0xddcc) REQUEST_IN = usb1.ENDPOINT_IN | usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE REQUEST_OUT = usb1.ENDPOINT_OUT | usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE @@ -204,9 +205,9 @@ class Panda: HEALTH_STRUCT = struct.Struct(" /sys/class/gpio/export || true