diff --git a/Jenkinsfile b/Jenkinsfile index 4c1f6ad8..d9b1c464 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -32,6 +32,7 @@ export GIT_BRANCH=${env.GIT_BRANCH} export GIT_COMMIT=${env.GIT_COMMIT} export PYTHONPATH=${env.TEST_DIR}/../ export PYTHONWARNINGS=error +export LOGLEVEL=debug ln -sf /data/openpilot/opendbc_repo/opendbc /data/opendbc # TODO: this is an agnos issue @@ -85,7 +86,7 @@ pipeline { steps { timeout(time: 20, unit: 'MINUTES') { script { - dockerImage = docker.build("${env.DOCKER_IMAGE_TAG}", "--build-arg CACHEBUST=${env.BUILD_NUMBER} .") + dockerImage = docker.build("${env.DOCKER_IMAGE_TAG}", "--build-arg CACHEBUST=${env.GIT_COMMIT} .") } } } @@ -109,7 +110,7 @@ pipeline { ["build", "scons -j4"], ["flash", "cd scripts/ && ./reflash_internal_panda.py"], ["flash jungle", "cd board/jungle && ./flash.py --all"], - ["test", "cd tests/hitl && HW_TYPES=10 pytest --durations=0 2*.py [5-9]*.py"], + ["test", "cd tests/hitl && pytest --durations=0 2*.py [5-9]*.py"], ]) } } @@ -121,7 +122,7 @@ pipeline { ["build", "scons -j4"], ["flash", "cd scripts/ && ./reflash_internal_panda.py"], ["flash jungle", "cd board/jungle && ./flash.py --all"], - ["test", "cd tests/hitl && HW_TYPES=9 pytest --durations=0 2*.py [5-9]*.py"], + ["test", "cd tests/hitl && pytest --durations=0 2*.py [5-9]*.py"], ]) } } diff --git a/board/boards/board_declarations.h b/board/boards/board_declarations.h index 9655fc35..ff5ca97a 100644 --- a/board/boards/board_declarations.h +++ b/board/boards/board_declarations.h @@ -29,10 +29,9 @@ struct board { const uint8_t led_pin[3]; const uint8_t led_pwm_channels[3]; // leave at 0 to disable PWM const bool has_spi; - const uint16_t fan_max_rpm; + const bool has_fan; const uint16_t avdd_mV; 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; diff --git a/board/boards/cuatro.h b/board/boards/cuatro.h index 8944922e..b6890f83 100644 --- a/board/boards/cuatro.h +++ b/board/boards/cuatro.h @@ -42,24 +42,6 @@ static void cuatro_set_bootkick(BootState state) { } static void cuatro_set_amp_enabled(bool enabled) { - // *** tmp, remove soon *** - static const uint8_t olds[][12] = { - {0x44, 0x00, 0x10, 0x00, 0x19, 0x51, 0x32, 0x34, 0x39, 0x37, 0x37, 0x30}, - {0x14, 0x00, 0x13, 0x00, 0x18, 0x51, 0x32, 0x34, 0x39, 0x37, 0x37, 0x30}, - {0x04, 0x00, 0x30, 0x00, 0x18, 0x51, 0x32, 0x34, 0x39, 0x37, 0x37, 0x30}, - {0x2f, 0x00, 0x14, 0x00, 0x18, 0x51, 0x32, 0x34, 0x39, 0x37, 0x37, 0x30}, - {0x1e, 0x00, 0x2f, 0x00, 0x18, 0x51, 0x32, 0x34, 0x39, 0x37, 0x37, 0x30}, - {0x26, 0x00, 0x15, 0x00, 0x19, 0x51, 0x32, 0x34, 0x39, 0x37, 0x37, 0x30}, - {0x35, 0x00, 0x32, 0x00, 0x18, 0x51, 0x32, 0x34, 0x39, 0x37, 0x37, 0x30}, - {0x37, 0x00, 0x2f, 0x00, 0x18, 0x51, 0x32, 0x34, 0x39, 0x37, 0x37, 0x30}, - }; - bool is_old = false; - for (uint8_t i = 0U; i < (sizeof(olds) / sizeof(olds[0])); i++) { - is_old |= (memcmp(olds[i], ((uint8_t *)UID_BASE), 12) == 0); - } - if (is_old) set_gpio_output(GPIOA, 5, enabled); - // *** tmp end *** - set_gpio_output(GPIOB, 0, enabled); } @@ -132,8 +114,7 @@ static harness_configuration cuatro_harness_config = { board board_cuatro = { .harness_config = &cuatro_harness_config, .has_spi = true, - .fan_max_rpm = 12500U, - .fan_max_pwm = 99U, // it can go up to 14k RPM, but 99% -> 100% is very non-linear + .has_fan = true, .avdd_mV = 1800U, .fan_enable_cooldown_time = 3U, .init = cuatro_init, diff --git a/board/boards/red.h b/board/boards/red.h index 608ccf64..761c2799 100644 --- a/board/boards/red.h +++ b/board/boards/red.h @@ -115,8 +115,7 @@ board board_red = { .set_bootkick = unused_set_bootkick, .harness_config = &red_harness_config, .has_spi = false, - .fan_max_rpm = 0U, - .fan_max_pwm = 100U, + .has_fan = false, .avdd_mV = 3300U, .fan_enable_cooldown_time = 0U, .init = red_init, diff --git a/board/boards/tres.h b/board/boards/tres.h index b9918dd7..73675a98 100644 --- a/board/boards/tres.h +++ b/board/boards/tres.h @@ -155,8 +155,7 @@ static harness_configuration tres_harness_config = { board board_tres = { .harness_config = &tres_harness_config, .has_spi = true, - .fan_max_rpm = 6600U, - .fan_max_pwm = 100U, + .has_fan = true, .avdd_mV = 1800U, .fan_enable_cooldown_time = 3U, .init = tres_init, diff --git a/board/can.h b/board/can.h index 9b120038..d6e360a0 100644 --- a/board/can.h +++ b/board/can.h @@ -1,7 +1,5 @@ #pragma once -#include "can_declarations.h" -static const uint8_t PANDA_CAN_CNT = 3U; -static const uint8_t PANDA_BUS_CNT = 3U; +#define PANDA_CAN_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}; +#include "opendbc/safety/can.h" diff --git a/board/can_declarations.h b/board/can_declarations.h deleted file mode 100644 index 96135e04..00000000 --- a/board/can_declarations.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -// bump this when changing the CAN packet -#define CAN_PACKET_VERSION 4 - -#define CANPACKET_HEAD_SIZE 6U - -#define CANPACKET_DATA_SIZE_MAX 64U - -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) diff --git a/board/drivers/can_common.h b/board/drivers/can_common.h index 23bc90f3..35c6702e 100644 --- a/board/drivers/can_common.h +++ b/board/drivers/can_common.h @@ -5,15 +5,13 @@ 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}}; +can_health_t can_health[PANDA_CAN_CNT] = {{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_silent = true; bool can_loopback = false; // ********************* instantiate queues ********************* @@ -39,7 +37,7 @@ 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}; +can_ring *can_queues[PANDA_CAN_CNT] = {&can_tx1_q, &can_tx2_q, &can_tx3_q}; // ********************* interrupt safe queue ********************* bool can_pop(can_ring *q, CANPacket_t *elem) { @@ -130,15 +128,15 @@ void can_clear(can_ring *q) { // Helpers // Panda: Bus 0=CAN1 Bus 1=CAN2 Bus 2=CAN3 -bus_config_t bus_config[BUS_CONFIG_ARRAY_SIZE] = { +bus_config_t bus_config[PANDA_CAN_CNT] = { { .bus_lookup = 0U, .can_num_lookup = 0U, .forwarding_bus = -1, .can_speed = 5000U, .can_data_speed = 20000U, .canfd_auto = false, .canfd_enabled = false, .brs_enabled = false, .canfd_non_iso = false }, { .bus_lookup = 1U, .can_num_lookup = 1U, .forwarding_bus = -1, .can_speed = 5000U, .can_data_speed = 20000U, .canfd_auto = false, .canfd_enabled = false, .brs_enabled = false, .canfd_non_iso = false }, { .bus_lookup = 2U, .can_num_lookup = 2U, .forwarding_bus = -1, .can_speed = 5000U, .can_data_speed = 20000U, .canfd_auto = false, .canfd_enabled = false, .brs_enabled = false, .canfd_non_iso = false }, - { .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++) { + bus_config[i].canfd_enabled = false; can_clear(can_queues[i]); (void)can_init(i); } @@ -158,20 +156,18 @@ void can_set_forwarding(uint8_t from, uint8_t to) { #endif void ignition_can_hook(CANPacket_t *msg) { - int bus = GET_BUS(msg); - if (bus == 0) { - int addr = GET_ADDR(msg); + if (msg->bus == 0U) { int len = GET_LEN(msg); // GM exception - if ((addr == 0x1F1) && (len == 8)) { + if ((msg->addr == 0x1F1U) && (len == 8)) { // SystemPowerMode (2=Run, 3=Crank Request) ignition_can = (msg->data[0] & 0x2U) != 0U; ignition_can_cnt = 0U; } // Rivian R1S/T GEN1 exception - if ((addr == 0x152) && (len == 8)) { + if ((msg->addr == 0x152U) && (len == 8)) { // 0x152 overlaps with Subaru pre-global which has this bit as the high beam int counter = msg->data[1] & 0xFU; // max is only 14 @@ -185,7 +181,7 @@ void ignition_can_hook(CANPacket_t *msg) { } // Tesla Model 3/Y exception - if ((addr == 0x221) && (len == 8)) { + if ((msg->addr == 0x221U) && (len == 8)) { // 0x221 overlaps with Rivian which has random data on byte 0 int counter = msg->data[6] >> 4; @@ -200,7 +196,7 @@ void ignition_can_hook(CANPacket_t *msg) { } // Mazda exception - if ((addr == 0x9E) && (len == 8)) { + if ((msg->addr == 0x9EU) && (len == 8)) { ignition_can = (msg->data[0] >> 5) == 0x6U; ignition_can_cnt = 0U; } @@ -234,7 +230,7 @@ bool can_check_checksum(CANPacket_t *packet) { 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) { + if (bus_number < PANDA_CAN_CNT) { // add CAN packet to send queue tx_buffer_overflow += can_push(can_queues[bus_number], to_push) ? 0U : 1U; process_can(CAN_NUM_FROM_BUS_NUM(bus_number)); diff --git a/board/drivers/can_common_declarations.h b/board/drivers/can_common_declarations.h index 57bf87d9..75580b7b 100644 --- a/board/drivers/can_common_declarations.h +++ b/board/drivers/can_common_declarations.h @@ -1,5 +1,7 @@ #pragma once +#include "board/can.h" + typedef struct { volatile uint32_t w_ptr; volatile uint32_t r_ptr; @@ -24,19 +26,13 @@ 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]; +extern can_health_t can_health[PANDA_CAN_CNT]; // 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_silent; extern bool can_loopback; // ******************* functions prototypes ********************* @@ -44,8 +40,7 @@ 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]; +extern can_ring *can_queues[PANDA_CAN_CNT]; // helpers #define WORD_TO_BYTE_ARRAY(dst8, src32) 0[dst8] = ((src32) & 0xFFU); 1[dst8] = (((src32) >> 8U) & 0xFFU); 2[dst8] = (((src32) >> 16U) & 0xFFU); 3[dst8] = (((src32) >> 24U) & 0xFFU) @@ -55,20 +50,7 @@ extern can_ring *can_queues[CAN_QUEUES_ARRAY_SIZE]; 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]; +extern bus_config_t bus_config[PANDA_CAN_CNT]; #define CANIF_FROM_CAN_NUM(num) (cans[num]) #define BUS_NUM_FROM_CAN_NUM(num) (bus_config[num].bus_lookup) diff --git a/board/drivers/fan.h b/board/drivers/fan.h index 1f1fb551..f1041ce8 100644 --- a/board/drivers/fan.h +++ b/board/drivers/fan.h @@ -5,10 +5,13 @@ struct fan_state_t fan_state; static const uint8_t FAN_TICK_FREQ = 8U; void fan_set_power(uint8_t percentage) { - fan_state.target_rpm = ((current_board->fan_max_rpm * CLAMP(percentage, 0U, 100U)) / 100U); + if (percentage > 0U) { + fan_state.power = CLAMP(percentage, 20U, 100U); + } else { + fan_state.power = 0U; + } } -void llfan_init(void); void fan_init(void) { fan_state.cooldown_counter = current_board->fan_enable_cooldown_time * FAN_TICK_FREQ; llfan_init(); @@ -16,9 +19,7 @@ void fan_init(void) { // Call this at FAN_TICK_FREQ void fan_tick(void) { - const float FAN_I = 6.5f; - - if (current_board->fan_max_rpm > 0U) { + if (current_board->has_fan) { // Measure fan RPM uint16_t fan_rpm_fast = fan_state.tach_counter * (60U * FAN_TICK_FREQ / 4U); // 4 interrupts per rotation fan_state.tach_counter = 0U; @@ -31,8 +32,8 @@ void fan_tick(void) { print("\n"); #endif - // Cooldown counter - if (fan_state.target_rpm > 0U) { + // Cooldown counter to prevent noise on tachometer line. + if (fan_state.power > 0U) { fan_state.cooldown_counter = current_board->fan_enable_cooldown_time * FAN_TICK_FREQ; } else { if (fan_state.cooldown_counter > 0U) { @@ -40,18 +41,8 @@ void fan_tick(void) { } } - // 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_state.target_rpm > 0U) || (fan_state.cooldown_counter > 0U)); + current_board->set_fan_enabled((fan_state.power > 0U) || (fan_state.cooldown_counter > 0U)); } } diff --git a/board/drivers/fan_declarations.h b/board/drivers/fan_declarations.h index f130bc43..3dd3e7cf 100644 --- a/board/drivers/fan_declarations.h +++ b/board/drivers/fan_declarations.h @@ -3,7 +3,6 @@ struct fan_state_t { uint16_t tach_counter; uint16_t rpm; - uint16_t target_rpm; uint8_t power; float error_integral; uint8_t cooldown_counter; diff --git a/board/drivers/fdcan.h b/board/drivers/fdcan.h index f3f8d997..2aa64e97 100644 --- a/board/drivers/fdcan.h +++ b/board/drivers/fdcan.h @@ -1,6 +1,6 @@ #include "fdcan_declarations.h" -FDCAN_GlobalTypeDef *cans[CANS_ARRAY_SIZE] = {FDCAN1, FDCAN2, FDCAN3}; +FDCAN_GlobalTypeDef *cans[PANDA_CAN_CNT] = {FDCAN1, FDCAN2, FDCAN3}; static bool can_set_speed(uint8_t can_number) { bool ret = true; @@ -13,7 +13,7 @@ static bool can_set_speed(uint8_t can_number) { bus_config[bus_number].can_data_speed, bus_config[bus_number].canfd_non_iso, can_loopback, - (unsigned int)(can_silent) & (1U << can_number) + can_silent ); return ret; } @@ -32,7 +32,7 @@ void can_clear_send(FDCAN_GlobalTypeDef *FDCANx, uint8_t can_number) { } void update_can_health_pkt(uint8_t can_number, uint32_t ir_reg) { - uint8_t can_irq_number[3][2] = { + uint8_t can_irq_number[PANDA_CAN_CNT][2] = { { FDCAN1_IT0_IRQn, FDCAN1_IT1_IRQn }, { FDCAN2_IT0_IRQn, FDCAN2_IT1_IRQn }, { FDCAN3_IT0_IRQn, FDCAN3_IT1_IRQn }, @@ -63,7 +63,6 @@ void update_can_health_pkt(uint8_t can_number, uint32_t ir_reg) { 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); @@ -158,17 +157,13 @@ void can_rx(uint8_t can_number) { // Clear all new messages from Rx FIFO 0 FDCANx->IR |= FDCAN_IR_RF0N; - while((FDCANx->RXF0S & FDCAN_RXF0S_F0FL) != 0U) { + 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) { + 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 } diff --git a/board/drivers/fdcan_declarations.h b/board/drivers/fdcan_declarations.h index ffddc2e0..c77d1e31 100644 --- a/board/drivers/fdcan_declarations.h +++ b/board/drivers/fdcan_declarations.h @@ -1,26 +1,19 @@ #pragma once -// IRQs: FDCAN1_IT0, FDCAN1_IT1 -// FDCAN2_IT0, FDCAN2_IT1 -// FDCAN3_IT0, FDCAN3_IT1 +#include "board/can.h" 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]; +extern FDCAN_GlobalTypeDef *cans[PANDA_CAN_CNT]; #define CAN_ACK_ERROR 3U void can_clear_send(FDCAN_GlobalTypeDef *FDCANx, uint8_t can_number); void update_can_health_pkt(uint8_t can_number, uint32_t ir_reg); -// ***************************** 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); diff --git a/board/drivers/led.h b/board/drivers/led.h index d927d8cf..7cea94e8 100644 --- a/board/drivers/led.h +++ b/board/drivers/led.h @@ -3,7 +3,7 @@ #define LED_GREEN 1U #define LED_BLUE 2U -#define LED_PWM_POWER 5U +#define LED_PWM_POWER 2U void led_set(uint8_t color, bool enabled) { if (color < 3U) { diff --git a/board/drivers/pwm.h b/board/drivers/pwm.h index b8ff9302..3d0d73ef 100644 --- a/board/drivers/pwm.h +++ b/board/drivers/pwm.h @@ -1,4 +1,4 @@ -#define PWM_COUNTER_OVERFLOW 2000U // To get ~50kHz +#define PWM_COUNTER_OVERFLOW 4800U // To get ~25kHz // TODO: Implement for 32-bit timers diff --git a/board/drivers/spi.h b/board/drivers/spi.h index e657aea6..d291fe06 100644 --- a/board/drivers/spi.h +++ b/board/drivers/spi.h @@ -3,10 +3,8 @@ #include "board/drivers/spi_declarations.h" #include "board/crc.h" -#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]; +uint8_t spi_buf_rx[SPI_BUF_SIZE]; +uint8_t spi_buf_tx[SPI_BUF_SIZE]; uint16_t spi_error_count = 0; @@ -202,7 +200,7 @@ void spi_rx_done(void) { llspi_miso_dma(spi_buf_tx, response_len); spi_state = next_rx_state; - if (!checksum_valid && (spi_error_count < UINT16_MAX)) { + if (!checksum_valid) { spi_error_count += 1U; } } diff --git a/board/drivers/spi_declarations.h b/board/drivers/spi_declarations.h index f719404d..23254f0e 100644 --- a/board/drivers/spi_declarations.h +++ b/board/drivers/spi_declarations.h @@ -8,7 +8,7 @@ // in a tight loop, plus some buffer #define SPI_IRQ_RATE 16000U -#define SPI_BUF_SIZE 2048U +#define SPI_BUF_SIZE 4096U // 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]; diff --git a/board/faults_declarations.h b/board/faults_declarations.h index eab9fcc0..981e2375 100644 --- a/board/faults_declarations.h +++ b/board/faults_declarations.h @@ -11,20 +11,10 @@ #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) diff --git a/board/jungle/__init__.py b/board/jungle/__init__.py index b2d63f5d..dfb7d98c 100644 --- a/board/jungle/__init__.py +++ b/board/jungle/__init__.py @@ -48,7 +48,7 @@ class PandaJungle(Panda): @classmethod def spi_connect(cls, serial, ignore_version=False): - return None, None, None, None, None + return None, None, None, None def flash(self, fn=None, code=None, reconnect=True): if not fn: diff --git a/board/jungle/main.c b/board/jungle/main.c index 5127b42e..7bac6d75 100644 --- a/board/jungle/main.c +++ b/board/jungle/main.c @@ -175,7 +175,7 @@ int main(void) { print("**** INTERRUPTS ON ****\n"); enable_interrupts(); - can_silent = ALL_CAN_LIVE; + can_silent = false; set_safety_hooks(SAFETY_ALLOUTPUT, 0U); can_init_all(); diff --git a/board/jungle/main_comms.h b/board/jungle/main_comms.h index b646381d..b19f799a 100644 --- a/board/jungle/main_comms.h +++ b/board/jungle/main_comms.h @@ -189,7 +189,7 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { break; // **** 0xde: set can bitrate case 0xde: - if ((req->param1 < PANDA_BUS_CNT) && is_speed_valid(req->param2, speeds, sizeof(speeds)/sizeof(speeds[0]))) { + if ((req->param1 < PANDA_CAN_CNT) && is_speed_valid(req->param2, speeds, sizeof(speeds)/sizeof(speeds[0]))) { bus_config[req->param1].can_speed = req->param2; bool ret = can_init(CAN_NUM_FROM_BUS_NUM(req->param1)); UNUSED(ret); @@ -212,7 +212,7 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { if (req->param1 == 0xFFFFU) { print("Clearing CAN Rx queue\n"); can_clear(&can_rx_q); - } else if (req->param1 < PANDA_BUS_CNT) { + } else if (req->param1 < PANDA_CAN_CNT) { print("Clearing CAN Tx queue\n"); can_clear(can_queues[req->param1]); } else { @@ -225,7 +225,7 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { break; // **** 0xf5: Set CAN silent mode case 0xf5: - can_silent = (req->param1 > 0U) ? ALL_CAN_SILENT : ALL_CAN_LIVE; + can_silent = (req->param1 > 0U); can_init_all(); break; // **** 0xf7: enable/disable header pin by number diff --git a/board/main.c b/board/main.c index af7cab4a..c3fdbc68 100644 --- a/board/main.c +++ b/board/main.c @@ -55,12 +55,12 @@ void set_safety_mode(uint16_t mode, uint16_t param) { case SAFETY_SILENT: set_intercept_relay(false, false); current_board->set_can_mode(CAN_MODE_NORMAL); - can_silent = ALL_CAN_SILENT; + can_silent = true; break; case SAFETY_NOOUTPUT: set_intercept_relay(false, false); current_board->set_can_mode(CAN_MODE_NORMAL); - can_silent = ALL_CAN_LIVE; + can_silent = false; break; case SAFETY_ELM327: set_intercept_relay(false, false); @@ -75,14 +75,14 @@ void set_safety_mode(uint16_t mode, uint16_t param) { } else { current_board->set_can_mode(CAN_MODE_NORMAL); } - can_silent = ALL_CAN_LIVE; + can_silent = false; break; default: set_intercept_relay(true, false); heartbeat_counter = 0U; heartbeat_lost = false; current_board->set_can_mode(CAN_MODE_NORMAL); - can_silent = ALL_CAN_LIVE; + can_silent = false; break; } can_init_all(); @@ -118,6 +118,7 @@ static void tick_handler(void) { static uint32_t controls_allowed_countdown = 0; static uint8_t prev_harness_status = HARNESS_STATUS_NC; static uint8_t loop_counter = 0U; + static bool relay_malfunction_prev = false; if (TICK_TIMER->SR != 0U) { @@ -130,6 +131,15 @@ static void tick_handler(void) { simple_watchdog_kick(); sound_tick(); + if (relay_malfunction_prev != relay_malfunction) { + if (relay_malfunction) { + fault_occurred(FAULT_RELAY_MALFUNCTION); + } else { + fault_recovered(FAULT_RELAY_MALFUNCTION); + } + } + relay_malfunction_prev = relay_malfunction; + // re-init everything that uses harness status if (harness.status != prev_harness_status) { prev_harness_status = harness.status; @@ -143,14 +153,7 @@ static void tick_handler(void) { // decimated to 1Hz if (loop_counter == 0U) { - can_live = pending_can_live; - //puth(usart1_dma); print(" "); puth(DMA2_Stream5->M0AR); print(" "); puth(DMA2_Stream5->NDTR); print("\n"); - - // reset this every 16th pass - if ((uptime_cnt & 0xFU) == 0U) { - pending_can_live = 0; - } #ifdef DEBUG print("** blink "); print("rx:"); puth4(can_rx_q.r_ptr); print("-"); puth4(can_rx_q.w_ptr); print(" "); @@ -303,7 +306,7 @@ int main(void) { microsecond_timer_init(); current_board->set_siren(false); - if (current_board->fan_max_rpm > 0U) { + if (current_board->has_fan) { fan_init(); } diff --git a/board/main_comms.h b/board/main_comms.h index 965398d4..e0fae6b9 100644 --- a/board/main_comms.h +++ b/board/main_comms.h @@ -210,13 +210,7 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { 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); - } + current_board->set_can_mode((req->param1 == 1U) ? CAN_MODE_OBD_CAN2 : CAN_MODE_NORMAL); break; // **** 0xdc: set safety mode case 0xdc: @@ -231,7 +225,7 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { break; // **** 0xde: set can bitrate case 0xde: - if ((req->param1 < PANDA_BUS_CNT) && is_speed_valid(req->param2, speeds, sizeof(speeds)/sizeof(speeds[0]))) { + if ((req->param1 < PANDA_CAN_CNT) && is_speed_valid(req->param2, speeds, sizeof(speeds)/sizeof(speeds[0]))) { bus_config[req->param1].can_speed = req->param2; bool ret = can_init(CAN_NUM_FROM_BUS_NUM(req->param1)); UNUSED(ret); @@ -282,7 +276,7 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { if (req->param1 == 0xFFFFU) { print("Clearing CAN Rx queue\n"); can_clear(&can_rx_q); - } else if (req->param1 < PANDA_BUS_CNT) { + } else if (req->param1 < PANDA_CAN_CNT) { print("Clearing CAN Tx queue\n"); can_clear(can_queues[req->param1]); } else { diff --git a/board/stm32h7/sound.h b/board/stm32h7/sound.h index 9c75e465..aeee2c71 100644 --- a/board/stm32h7/sound.h +++ b/board/stm32h7/sound.h @@ -104,33 +104,12 @@ void sound_init(void) { REGISTER_INTERRUPT(BDMA_Channel0_IRQn, BDMA_Channel0_IRQ_Handler, 128U, FAULT_INTERRUPT_RATE_SOUND_DMA) REGISTER_INTERRUPT(DMA1_Stream0_IRQn, DMA1_Stream0_IRQ_Handler, 128U, FAULT_INTERRUPT_RATE_SOUND_DMA) - // *** tmp, remove soon *** - static const uint8_t olds[][12] = { - {0x44, 0x00, 0x10, 0x00, 0x19, 0x51, 0x32, 0x34, 0x39, 0x37, 0x37, 0x30}, - {0x14, 0x00, 0x13, 0x00, 0x18, 0x51, 0x32, 0x34, 0x39, 0x37, 0x37, 0x30}, - {0x04, 0x00, 0x30, 0x00, 0x18, 0x51, 0x32, 0x34, 0x39, 0x37, 0x37, 0x30}, - {0x2f, 0x00, 0x14, 0x00, 0x18, 0x51, 0x32, 0x34, 0x39, 0x37, 0x37, 0x30}, - {0x1e, 0x00, 0x2f, 0x00, 0x18, 0x51, 0x32, 0x34, 0x39, 0x37, 0x37, 0x30}, - {0x26, 0x00, 0x15, 0x00, 0x19, 0x51, 0x32, 0x34, 0x39, 0x37, 0x37, 0x30}, - {0x35, 0x00, 0x32, 0x00, 0x18, 0x51, 0x32, 0x34, 0x39, 0x37, 0x37, 0x30}, - {0x37, 0x00, 0x2f, 0x00, 0x18, 0x51, 0x32, 0x34, 0x39, 0x37, 0x37, 0x30}, - }; - bool is_old = false; - for (uint8_t i = 0U; i < (sizeof(olds) / sizeof(olds[0])); i++) { - is_old |= (memcmp(olds[i], ((uint8_t *)UID_BASE), 12) == 0); - } - // *** tmp end *** - // Init DAC DAC1->DHR12R1 = (1UL << 11); - if (!is_old) DAC1->DHR12R2 = (1UL << 11); + DAC1->DHR12R2 = (1UL << 11); register_set(&DAC1->MCR, 0U, 0xFFFFFFFFU); register_set(&DAC1->CR, DAC_CR_TEN1 | (4U << DAC_CR_TSEL1_Pos) | DAC_CR_DMAEN1, 0xFFFFFFFFU); - if (is_old) { - register_set_bits(&DAC1->CR, DAC_CR_EN1); - } else { - register_set_bits(&DAC1->CR, DAC_CR_EN1 | DAC_CR_EN2); - } + register_set_bits(&DAC1->CR, DAC_CR_EN1 | DAC_CR_EN2); // Setup DMAMUX (DAC_CH1_DMA as input) register_set(&DMAMUX1_Channel1->CCR, 67U, DMAMUX_CxCR_DMAREQ_ID_Msk); diff --git a/pyproject.toml b/pyproject.toml index 1fa20287..6d425fc7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,6 +30,7 @@ dev = [ "mypy", "setuptools", "spidev; platform_system == 'Linux'", + "spidev2; platform_system == 'Linux'", ] [build-system] diff --git a/python/__init__.py b/python/__init__.py index d8c9de01..066dc3ef 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -23,7 +23,7 @@ __version__ = '0.0.10' CANPACKET_HEAD_SIZE = 0x6 DLC_TO_LEN = [0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 16, 20, 24, 32, 48, 64] LEN_TO_DLC = {length: dlc for (dlc, length) in enumerate(DLC_TO_LEN)} -PANDA_BUS_CNT = 3 +PANDA_CAN_CNT = 3 def calculate_checksum(data): @@ -32,16 +32,13 @@ def calculate_checksum(data): res ^= b return res -def pack_can_buffer(arr, fd=False): - snds = [b''] +def pack_can_buffer(arr, chunk=False, fd=False): + snds = [bytearray(), ] for address, dat, bus in arr: - assert len(dat) in LEN_TO_DLC - #logger.debug(" W 0x%x: 0x%s", address, dat.hex()) - extended = 1 if address >= 0x800 else 0 data_len_code = LEN_TO_DLC[len(dat)] header = bytearray(CANPACKET_HEAD_SIZE) - word_4b = address << 3 | extended << 2 + word_4b = (address << 3) | (extended << 2) header[0] = (data_len_code << 4) | (bus << 1) | int(fd) header[1] = word_4b & 0xFF header[2] = (word_4b >> 8) & 0xFF @@ -49,9 +46,10 @@ def pack_can_buffer(arr, fd=False): header[4] = (word_4b >> 24) & 0xFF header[5] = calculate_checksum(header[:5] + dat) - snds[-1] += header + dat - if len(snds[-1]) > 256: # Limit chunks to 256 bytes - snds.append(b'') + snds[-1].extend(header) + snds[-1].extend(dat) + if chunk and len(snds[-1]) > 256: + snds.append(bytearray()) return snds @@ -134,7 +132,7 @@ class Panda: MAX_FAN_RPMs = { HW_TYPE_TRES: 6600, - HW_TYPE_CUATRO: 12500, + HW_TYPE_CUATRO: 5000, } HARNESS_STATUS_NC = 0 @@ -199,9 +197,9 @@ class Panda: self._handle = None while self._handle is None: # try USB first, then SPI - self._context, self._handle, serial, self.bootstub, bcd = self.usb_connect(self._connect_serial, claim=claim, no_error=wait) + self._context, self._handle, serial, self.bootstub = self.usb_connect(self._connect_serial, claim=claim, no_error=wait) if self._handle is None: - self._context, self._handle, serial, self.bootstub, bcd = self.spi_connect(self._connect_serial) + self._context, self._handle, serial, self.bootstub = self.spi_connect(self._connect_serial) if not wait: break @@ -228,11 +226,11 @@ class Panda: self.can_reset_communications() # disable automatic CAN-FD switching - for bus in range(PANDA_BUS_CNT): + for bus in range(PANDA_CAN_CNT): self.set_canfd_auto(bus, False) # set CAN speed - for bus in range(PANDA_BUS_CNT): + for bus in range(PANDA_CAN_CNT): self.set_can_speed_kbps(bus, self._can_speed_kbps) @property @@ -241,49 +239,33 @@ class Panda: @classmethod def spi_connect(cls, serial, ignore_version=False): - # get UID to confirm slave is present and up - handle = None - spi_serial = None - bootstub = None - spi_version = None try: handle = PandaSpiHandle() - - # connect by protcol version - try: - dat = handle.get_protocol_version() - spi_serial = binascii.hexlify(dat[:12]).decode() - pid = dat[13] - if pid not in (0xcc, 0xee): - raise PandaSpiException("invalid bootstub status") - bootstub = pid == 0xee - spi_version = dat[14] - except PandaSpiException: - # fallback, we'll raise a protocol mismatch below - dat = handle.controlRead(Panda.REQUEST_IN, 0xc3, 0, 0, 12, timeout=100) - spi_serial = binascii.hexlify(dat).decode() - bootstub = Panda.flasher_present(handle) - spi_version = 0 + dat = handle.get_protocol_version() except PandaSpiException: - pass + return None, None, None, False - # no connection or wrong panda - if None in (spi_serial, bootstub) or (serial is not None and (spi_serial != serial)): - handle = None - spi_serial = None - bootstub = False + spi_serial = binascii.hexlify(dat[:12]).decode() + pid = dat[13] + if pid not in (0xcc, 0xee): + raise PandaProtocolMismatch(f"invalid bootstub status ({pid=}). reflash panda") + bootstub = pid == 0xee + spi_version = dat[14] + + # did we get the right panda? + if serial is not None and spi_serial != serial: + return None, None, None, False # ensure our protocol version matches the panda - if handle is not None and not ignore_version: - if spi_version != handle.PROTOCOL_VERSION: - err = f"panda protocol mismatch: expected {handle.PROTOCOL_VERSION}, got {spi_version}. reflash panda" - raise PandaProtocolMismatch(err) + if (not ignore_version) and spi_version != handle.PROTOCOL_VERSION: + raise PandaProtocolMismatch(f"panda protocol mismatch: expected {handle.PROTOCOL_VERSION}, got {spi_version}. reflash panda") - return None, handle, spi_serial, bootstub, None + # got a device and all good + return None, handle, spi_serial, bootstub @classmethod def usb_connect(cls, serial, claim=True, no_error=False): - handle, usb_serial, bootstub, bcd = None, None, None, None + handle, usb_serial, bootstub = None, None, None context = usb1.USBContext() context.open() try: @@ -309,11 +291,6 @@ class Panda: handle.claimInterface(0) # handle.setInterfaceAltSetting(0, 0) # Issue in USB stack - # bcdDevice wasn't always set to the hw type, ignore if it's the old constant - this_bcd = device.getbcdDevice() - if this_bcd is not None and this_bcd != 0x2300: - bcd = bytearray([this_bcd >> 8, ]) - break except Exception: logger.exception("USB connect error") @@ -324,7 +301,7 @@ class Panda: else: context.close() - return context, usb_handle, usb_serial, bootstub, bcd + return context, usb_handle, usb_serial, bootstub def is_connected_spi(self): return isinstance(self._handle, PandaSpiHandle) @@ -360,7 +337,7 @@ class Panda: @classmethod def spi_list(cls): - _, _, serial, _, _ = cls.spi_connect(None, ignore_version=True) + _, _, serial, _ = cls.spi_connect(None, ignore_version=True) if serial is not None: return [serial, ] return [] @@ -732,7 +709,7 @@ class Panda: @ensure_can_packet_version def can_send_many(self, arr, *, fd=False, timeout=CAN_SEND_TIMEOUT_MS): - snds = pack_can_buffer(arr, fd=fd) + snds = pack_can_buffer(arr, chunk=(not self.spi), fd=fd) for tx in snds: while len(tx) > 0: bs = self._handle.bulkWrite(3, tx, timeout=timeout) diff --git a/python/spi.py b/python/spi.py index cee8755e..8ee5aecb 100644 --- a/python/spi.py +++ b/python/spi.py @@ -1,5 +1,4 @@ import binascii -import ctypes import os import fcntl import math @@ -8,7 +7,6 @@ import struct import threading from contextlib import contextmanager from functools import reduce -from collections.abc import Callable from .base import BaseHandle, BaseSTBootloaderHandle, TIMEOUT from .constants import McuType, MCU_TYPE_BY_IDCODE, USBPACKET_MAX_SIZE @@ -18,6 +16,10 @@ try: import spidev except ImportError: spidev = None +try: + import spidev2 +except ImportError: + spidev2 = None # Constants SYNC = 0x5A @@ -29,7 +31,8 @@ CHECKSUM_START = 0xAB MIN_ACK_TIMEOUT_MS = 100 MAX_XFER_RETRY_COUNT = 5 -XFER_SIZE = 0x40*31 +SPI_BUF_SIZE = 4096 # from panda/board/drivers/spi.h +XFER_SIZE = SPI_BUF_SIZE - 0x40 # give some room for SPI protocol overhead DEV_PATH = "/dev/spidev0.0" @@ -70,18 +73,6 @@ class PandaSpiTransferFailed(PandaSpiException): pass -class PandaSpiTransfer(ctypes.Structure): - _fields_ = [ - ('rx_buf', ctypes.c_uint64), - ('tx_buf', ctypes.c_uint64), - ('tx_length', ctypes.c_uint32), - ('rx_length_max', ctypes.c_uint32), - ('timeout', ctypes.c_uint32), - ('endpoint', ctypes.c_uint8), - ('expect_disconnect', ctypes.c_uint8), - ] - - SPI_LOCK = threading.Lock() SPI_DEVICES = {} class SpiDevice: @@ -89,9 +80,7 @@ class SpiDevice: Provides locked, thread-safe access to a panda's SPI interface. """ - # 50MHz is the max of the 845. older rev comma three - # may not support the full 50MHz - MAX_SPEED = 50000000 + MAX_SPEED = 50000000 # max of the SDM845 def __init__(self, speed=MAX_SPEED): assert speed <= self.MAX_SPEED @@ -128,24 +117,12 @@ class PandaSpiHandle(BaseHandle): """ PROTOCOL_VERSION = 2 + HEADER = struct.Struct(" None: self.dev = SpiDevice() - - self._transfer_raw: Callable[[SpiDevice, int, bytes, int, int, bool], bytes] = self._transfer_spidev - - if "KERN" in os.environ: - self._transfer_raw = self._transfer_kernel_driver - - self.tx_buf = bytearray(1024) - self.rx_buf = bytearray(1024) - tx_buf_raw = ctypes.c_char.from_buffer(self.tx_buf) - rx_buf_raw = ctypes.c_char.from_buffer(self.rx_buf) - - self.ioctl_data = PandaSpiTransfer() - self.ioctl_data.tx_buf = ctypes.addressof(tx_buf_raw) - self.ioctl_data.rx_buf = ctypes.addressof(rx_buf_raw) - self.fileno = self.dev._spidev.fileno() + if spidev2 is not None and "SPI2" in os.environ: + self._spi2 = spidev2.SPIBus("/dev/spidev0.0", "w+b", bits_per_word=8, speed_hz=50_000_000) # helpers def _calc_checksum(self, data: bytes) -> int: @@ -160,10 +137,10 @@ class PandaSpiHandle(BaseHandle): start = time.monotonic() while (timeout == 0) or ((time.monotonic() - start) < timeout_s): dat = spi.xfer2([tx, ] * length) - if dat[0] == NACK: - raise PandaSpiNackResponse - elif dat[0] == ack_val: + if dat[0] == ack_val: return bytes(dat) + elif dat[0] == NACK: + raise PandaSpiNackResponse raise PandaSpiMissingAck @@ -171,7 +148,7 @@ class PandaSpiHandle(BaseHandle): max_rx_len = max(USBPACKET_MAX_SIZE, max_rx_len) logger.debug("- send header") - packet = struct.pack(" 0: dat += bytes(spi.readbytes(remaining)) - dat = dat[:3 + response_len + 1] if self._calc_checksum(dat) != 0: raise PandaSpiBadChecksum return dat[3:-1] - def _transfer_kernel_driver(self, spi, endpoint: int, data, timeout: int, max_rx_len: int = 1000, expect_disconnect: bool = False) -> bytes: - import spidev2 - self.tx_buf[:len(data)] = data - self.ioctl_data.endpoint = endpoint - self.ioctl_data.tx_length = len(data) - self.ioctl_data.rx_length_max = max_rx_len - self.ioctl_data.expect_disconnect = int(expect_disconnect) + def _transfer_spidev2(self, spi, endpoint: int, data, timeout: int, max_rx_len: int = USBPACKET_MAX_SIZE, expect_disconnect: bool = False) -> bytes: + max_rx_len = max(USBPACKET_MAX_SIZE, max_rx_len) - # TODO: use our own ioctl request - try: - ret = fcntl.ioctl(self.fileno, spidev2.SPI_IOC_RD_LSB_FIRST, self.ioctl_data) - except OSError as e: - raise PandaSpiException from e - if ret < 0: - raise PandaSpiException(f"ioctl returned {ret}") - return bytes(self.rx_buf[:ret]) + header = self.HEADER.pack(SYNC, endpoint, len(data), max_rx_len) + + header_ack = bytearray(1) + + # ACK + <2 bytes for response length> + data + checksum + data_rx = bytearray(3+max_rx_len+1) + + self._spi2.submitTransferList(spidev2.SPITransferList(( + # header + {'tx_buf': header + bytes([self._calc_checksum(header), ]), 'delay_usecs': 0, 'cs_change': True}, + {'rx_buf': header_ack, 'delay_usecs': 0, 'cs_change': True}, + + # send data + {'tx_buf': bytes([*data, self._calc_checksum(data)]), 'delay_usecs': 0, 'cs_change': True}, + {'rx_buf': data_rx, 'delay_usecs': 0, 'cs_change': True}, + ))) + + if header_ack[0] != HACK: + raise PandaSpiMissingAck + + if expect_disconnect: + logger.debug("- expecting disconnect, returning") + return b"" + else: + dat = bytes(data_rx) + if dat[0] != DACK: + if dat[0] == NACK: + raise PandaSpiNackResponse + + print("trying again") + dat = self._wait_for_ack(spi, DACK, timeout, 0x13, length=3 + max_rx_len) + + # get response length, then response + response_len = struct.unpack(" max_rx_len: + raise PandaSpiException(f"response length greater than max ({max_rx_len} {response_len})") + + dat = dat[:3 + response_len + 1] + if self._calc_checksum(dat) != 0: + raise PandaSpiBadChecksum + + return dat[3:-1] def _transfer(self, endpoint: int, data, timeout: int, max_rx_len: int = 1000, expect_disconnect: bool = False) -> bytes: logger.debug("starting transfer: endpoint=%d, max_rx_len=%d", endpoint, max_rx_len) @@ -236,11 +241,25 @@ class PandaSpiHandle(BaseHandle): logger.debug("\ntry #%d", n) with self.dev.acquire() as spi: try: - return self._transfer_raw(spi, endpoint, data, timeout, max_rx_len, expect_disconnect) + fn = self._transfer_spidev + #fn = self._transfer_spidev2 + return fn(spi, endpoint, data, timeout, max_rx_len, expect_disconnect) except PandaSpiException as e: exc = e logger.debug("SPI transfer failed, retrying", exc_info=True) + # ensure slave is in a consistent state and ready for the next transfer + # (e.g. slave TX buffer isn't stuck full) + nack_cnt = 0 + attempts = 5 + while (nack_cnt <= 3) and (attempts > 0): + attempts -= 1 + try: + self._wait_for_ack(spi, NACK, MIN_ACK_TIMEOUT_MS, 0x11, length=XFER_SIZE//2) + nack_cnt += 1 + except PandaSpiException: + nack_cnt = 0 + raise exc def get_protocol_version(self) -> bytes: @@ -290,8 +309,9 @@ class PandaSpiHandle(BaseHandle): return self._transfer(0, struct.pack(" int: + mv = memoryview(data) for x in range(math.ceil(len(data) / XFER_SIZE)): - self._transfer(endpoint, data[XFER_SIZE*x:XFER_SIZE*(x+1)], timeout) + self._transfer(endpoint, mv[XFER_SIZE*x:XFER_SIZE*(x+1)], timeout) return len(data) def bulkRead(self, endpoint: int, length: int, timeout: int = TIMEOUT) -> bytes: @@ -308,6 +328,9 @@ class STBootloaderSPIHandle(BaseSTBootloaderHandle): """ Implementation of the STM32 SPI bootloader protocol described in: https://www.st.com/resource/en/application_note/an4286-spi-protocol-used-in-the-stm32-bootloader-stmicroelectronics.pdf + + NOTE: the bootloader's state machine is fragile and immediately gets into a bad state when + sending any junk, e.g. when using the panda SPI protocol. """ SYNC = 0x5A diff --git a/scripts/benchmark.py b/scripts/benchmark.py index c2b0c85c..60633804 100755 --- a/scripts/benchmark.py +++ b/scripts/benchmark.py @@ -1,17 +1,32 @@ #!/usr/bin/env python3 +import io +import os import time +import pstats +import cProfile from contextlib import contextmanager from panda import Panda, PandaDFU from panda.tests.hitl.helpers import get_random_can_messages +PROFILE = "PROFILE" in os.environ + @contextmanager def print_time(desc): + if PROFILE: + pr = cProfile.Profile() + pr.enable() start = time.perf_counter() yield end = time.perf_counter() - print(f"{end - start:.2f}s - {desc}") + print(f"{end - start:.3f}s - {desc}") + if PROFILE: + pr.disable() + s = io.StringIO() + ps = pstats.Stats(pr, stream=s).sort_stats("cumtime") + ps.print_stats() + print(s.getvalue()) if __name__ == "__main__": diff --git a/setup.sh b/setup.sh index 41fb8ae2..042b4978 100755 --- a/setup.sh +++ b/setup.sh @@ -38,5 +38,5 @@ if ! command -v uv &>/dev/null; then fi export UV_PROJECT_ENVIRONMENT="$DIR/.venv" -uv sync --all-extras +uv sync --all-extras --upgrade source "$DIR/.venv/bin/activate" diff --git a/tests/hitl/5_spi.py b/tests/hitl/5_spi.py index 1861ffef..106a1c7d 100644 --- a/tests/hitl/5_spi.py +++ b/tests/hitl/5_spi.py @@ -3,31 +3,9 @@ import pytest import random from unittest.mock import patch -from panda import Panda, PandaDFU -from panda.python.spi import SpiDevice, PandaProtocolMismatch, PandaSpiNackResponse +from panda import Panda +from panda.python.spi import PandaProtocolMismatch, PandaSpiNackResponse -pytestmark = [ - pytest.mark.test_panda_types((Panda.HW_TYPE_TRES, )) -] - -@pytest.mark.skip("doesn't work, bootloader seems to ignore commands once it sees junk") -def test_dfu_with_spam(p): - dfu_serial = p.get_dfu_serial() - - # enter DFU - p.reset(enter_bootstub=True) - p.reset(enter_bootloader=True) - assert Panda.wait_for_dfu(dfu_serial, timeout=19), "failed to enter DFU" - - # send junk - d = SpiDevice() - for _ in range(9): - with d.acquire() as spi: - dat = [random.randint(-1, 255) for _ in range(random.randint(1, 100))] - spi.xfer(dat) - - # should still show up - assert dfu_serial in PandaDFU.list() class TestSpi: def _ping(self, mocker, panda): diff --git a/tests/hitl/7_internal.py b/tests/hitl/7_internal.py index 337f287a..47c24a47 100644 --- a/tests/hitl/7_internal.py +++ b/tests/hitl/7_internal.py @@ -4,18 +4,20 @@ import pytest from panda import Panda pytestmark = [ - pytest.mark.test_panda_types(Panda.INTERNAL_DEVICES) + pytest.mark.test_panda_types(Panda.INTERNAL_DEVICES), + pytest.mark.test_panda_types([Panda.HW_TYPE_TRES]) ] @pytest.mark.timeout(2*60) -def test_fan_controller(p): +def test_fan_curve(p): + # ensure fan curve is (roughly) linear + for power in (30, 50, 80, 100): p.set_fan_power(0) while p.get_fan_rpm() > 0: time.sleep(0.1) - # wait until fan spins up (and recovers if needed), - # then wait a bit more for the RPM to converge + # wait until fan spins up, then wait a bit more for the RPM to converge p.set_fan_power(power) for _ in range(20): time.sleep(1) @@ -24,7 +26,7 @@ def test_fan_controller(p): time.sleep(5) expected_rpm = Panda.MAX_FAN_RPMs[bytes(p.get_type())] * power / 100 - assert 0.9 * expected_rpm <= p.get_fan_rpm() <= 1.1 * expected_rpm + assert 0.75 * expected_rpm <= p.get_fan_rpm() <= 1.25 * expected_rpm def test_fan_cooldown(p): # if the fan cooldown doesn't work, we get high frequency noise on the tach line @@ -35,21 +37,3 @@ def test_fan_cooldown(p): for _ in range(5): assert p.get_fan_rpm() <= Panda.MAX_FAN_RPMs[bytes(p.get_type())] time.sleep(0.5) - -def test_fan_overshoot(p): - - # make sure it's stopped completely - p.set_fan_power(0) - while p.get_fan_rpm() > 0: - time.sleep(0.1) - - # set it to 30% power to mimic going onroad - p.set_fan_power(30) - max_rpm = 0 - for _ in range(50): - max_rpm = max(max_rpm, p.get_fan_rpm()) - time.sleep(0.1) - - # tolerate 10% overshoot - expected_rpm = Panda.MAX_FAN_RPMs[bytes(p.get_type())] * 30 / 100 - assert max_rpm <= 1.1 * expected_rpm, f"Fan overshoot: {(max_rpm / expected_rpm * 100) - 100:.1f}%" diff --git a/tests/libpanda/SConscript b/tests/libpanda/SConscript index fc680723..a5bdd7ce 100644 --- a/tests/libpanda/SConscript +++ b/tests/libpanda/SConscript @@ -21,7 +21,7 @@ env = Environment( '-Wfatal-errors', '-Wno-pointer-to-int-cast', ], - CPPPATH=[".", "../../board/", opendbc.INCLUDE_PATH], + CPPPATH=[".", "../../", "../../board/", opendbc.INCLUDE_PATH], ) if system == "Darwin": env.PrependENVPath('PATH', '/opt/homebrew/bin') diff --git a/tests/misra/test_mutation.py b/tests/misra/test_mutation.py index 0d53026a..762ab852 100755 --- a/tests/misra/test_mutation.py +++ b/tests/misra/test_mutation.py @@ -53,7 +53,7 @@ patterns = [ all_files = glob.glob('board/**', root_dir=ROOT, recursive=True) files = [f for f in all_files if f.endswith(('.c', '.h')) and not f.startswith(IGNORED_PATHS)] -assert len(files) > 70, all(d in files for d in ('board/main.c', 'board/stm32h7/llfdcan.h')) +assert len(files) > 50, all(d in files for d in ('board/main.c', 'board/stm32h7/llfdcan.h')) for p in patterns: mutations.append((random.choice(files), p, True)) diff --git a/tests/usbprotocol/test_comms.py b/tests/usbprotocol/test_comms.py index 5785db8b..446a1341 100755 --- a/tests/usbprotocol/test_comms.py +++ b/tests/usbprotocol/test_comms.py @@ -78,17 +78,17 @@ class TestPandaComms(unittest.TestCase): def test_comms_reset_tx(self): # store some test messages in the queue test_msg = (0x100, b"test", 0) - packed = pack_can_buffer([test_msg for _ in range(100)]) + packed = pack_can_buffer([test_msg for _ in range(100)], chunk=True) # write a small chunk such that we have some overflow TINY_CHUNK_SIZE = 6 - lpp.comms_can_write(packed[0][:TINY_CHUNK_SIZE], TINY_CHUNK_SIZE) + lpp.comms_can_write(bytes(packed[0][:TINY_CHUNK_SIZE]), TINY_CHUNK_SIZE) # reset the comms to clear the overflow buffer on the panda side lpp.comms_can_reset() # write a full valid chunk, which should now contain valid messages - lpp.comms_can_write(packed[1], len(packed[1])) + lpp.comms_can_write(bytes(packed[1]), len(packed[1])) # read the messages from the queue and make sure they're valid queue_msgs = [] @@ -114,7 +114,7 @@ class TestPandaComms(unittest.TestCase): for buf in packed: for i in range(0, len(buf), CHUNK_SIZE): chunk_len = min(CHUNK_SIZE, len(buf) - i) - lpp.comms_can_write(buf[i:i+chunk_len], chunk_len) + lpp.comms_can_write(bytes(buf[i:i+chunk_len]), chunk_len) # Check that they ended up in the right buffers queue_msgs = []