IQ.Pilot Release Commit @ 661a2de

This commit is contained in:
IQ.Lvbs CI [bot]
2026-08-08 14:37:51 -05:00
parent a6c27ac169
commit a1ef7d6c80
211 changed files with 7332 additions and 2756 deletions

View File

@@ -238,13 +238,36 @@ void ignition_can_hook(CANPacket_t *msg) {
}
// Volkswagen MEB exception
// GE_Fahrstufe: 5=P, 6=R, 7=N, 8/9=D, 10=E, 13/14=T; 0/1/15 transitional/init/error.
// Both gear messages are latched independently and OR'd: cars broadcast one
// authoritative source (Gateway_73 on ALT_GEAR platforms), and a stale parked
// reading on the unused one must not veto the real one.
static bool vw_meb_getriebe_out_of_p = false;
static bool vw_meb_gateway_out_of_p = false;
// Getriebe_11->GE_Fahrstufe
if ((msg->addr == 0xADU) && (len == 8)) {
int fahrstufe = (msg->data[5] >> 2) & 0xFU;
vw_meb_getriebe_out_of_p = (fahrstufe >= 6) && (fahrstufe <= 14);
}
// Gateway_73->GE_Fahrstufe
if ((msg->addr == 0x3DCU) && (len == 8)) {
int fahrstufe = msg->data[5] & 0xFU;
vw_meb_gateway_out_of_p = (fahrstufe >= 6) && (fahrstufe <= 14);
}
if ((msg->addr == 0x3C0U) && (len == 4)) {
int counter = msg->data[1] & 0xFU;
static int prev_counter_vw_meb = -1;
if ((counter == ((prev_counter_vw_meb + 1) % 16)) && (prev_counter_vw_meb != -1)) {
// Klemmen_Status_01->ZAS_Kl_15
ignition_can = ((msg->data[2] >> 1) & 1U) != 0U;
// Klemmen_Status_01->ZAS_Kl_15, gated on gear out of P: the gateway broadcasts
// ZAS_Kl_15=1 with a live counter during network wake with the car off (unlock,
// app poll), flapping onroad while parked. No gear message is broadcast while
// parked-and-asleep, so gear-unseen means parked.
bool vw_meb_out_of_park = vw_meb_getriebe_out_of_p || vw_meb_gateway_out_of_p;
ignition_can = (((msg->data[2] >> 1) & 1U) != 0U) && vw_meb_out_of_park;
ignition_can_cnt = 0U;
}
prev_counter_vw_meb = counter;

View File

@@ -14,8 +14,10 @@ void set_intercept_relay(bool intercept, bool ignition_relay) {
harness.relay_driven = true;
}
// wait until we're not reading the analog voltages anymore
while (harness.sbu_adc_lock) {}
// The relay pins are separate from the SBU sense pins, so no need to wait for
// orientation sampling: it runs in thread context, sees relay_driven set, and
// discards its result. Spinning on sbu_adc_lock here would deadlock when called
// from interrupt context while thread-context sampling holds the lock.
if (harness.status == HARNESS_STATUS_NORMAL) {
set_gpio_output(current_board->harness_config->GPIO_relay_SBU1, current_board->harness_config->pin_relay_SBU1, !ignition_relay);
@@ -31,20 +33,25 @@ void set_intercept_relay(bool intercept, bool ignition_relay) {
}
bool harness_check_ignition(void) {
bool ret = false;
// The SBU pins are in analog mode while orientation sampling is in flight
// (thread context), so a digital read would return 0. Return the last good
// value instead of spinning: this is called from interrupt context, where
// waiting for preempted thread-context sampling to finish would deadlock.
bool ret = harness.ignition_line;
// wait until we're not reading the analog voltages anymore
while (harness.sbu_adc_lock) {}
switch(harness.status){
case HARNESS_STATUS_NORMAL:
ret = !get_gpio_input(current_board->harness_config->GPIO_SBU1, current_board->harness_config->pin_SBU1);
break;
case HARNESS_STATUS_FLIPPED:
ret = !get_gpio_input(current_board->harness_config->GPIO_SBU2, current_board->harness_config->pin_SBU2);
break;
default:
break;
if (!harness.sbu_adc_lock) {
switch(harness.status){
case HARNESS_STATUS_NORMAL:
ret = !get_gpio_input(current_board->harness_config->GPIO_SBU1, current_board->harness_config->pin_SBU1);
break;
case HARNESS_STATUS_FLIPPED:
ret = !get_gpio_input(current_board->harness_config->GPIO_SBU2, current_board->harness_config->pin_SBU2);
break;
default:
ret = false;
break;
}
harness.ignition_line = ret;
}
return ret;
}
@@ -81,6 +88,12 @@ static uint8_t harness_detect_orientation(void) {
set_gpio_mode(current_board->harness_config->GPIO_SBU1, current_board->harness_config->pin_SBU1, MODE_INPUT);
set_gpio_mode(current_board->harness_config->GPIO_SBU2, current_board->harness_config->pin_SBU2, MODE_INPUT);
harness.sbu_adc_lock = false;
// This runs in thread context and can be preempted by an interrupt driving
// the relay mid-sample, which changes the SBU line voltages; discard
if (harness.relay_driven) {
ret = harness.status;
}
}
#endif

View File

@@ -4,12 +4,15 @@
#define HARNESS_STATUS_NORMAL 1U
#define HARNESS_STATUS_FLIPPED 2U
// orientation sampling runs in thread context and shares this state with
// interrupt handlers, so the fields are volatile
struct harness_t {
uint8_t status;
uint16_t sbu1_voltage_mV;
uint16_t sbu2_voltage_mV;
bool relay_driven;
bool sbu_adc_lock;
volatile uint8_t status;
volatile uint16_t sbu1_voltage_mV;
volatile uint16_t sbu2_voltage_mV;
volatile bool ignition_line;
volatile bool relay_driven;
volatile bool sbu_adc_lock;
};
extern struct harness_t harness;

View File

@@ -74,6 +74,10 @@ void init_interrupts(bool check_rate_limit){
for(uint16_t i=0U; i<NUM_INTERRUPTS; i++){
interrupts[i].handler = unused_interrupt_handler;
// Default priority, lowered so the comms link can preempt everything else and
// re-arm its DMA (see IRQ_PRIORITY_COMMS). Shared state is guarded by
// ENTER_CRITICAL, which masks all interrupts regardless of priority.
NVIC_SetPriority((IRQn_Type)i, IRQ_PRIORITY_DEFAULT);
}
// Init interrupt timer for a 1s interval

View File

@@ -1,5 +1,12 @@
#pragma once
// The SPI slave must re-arm its RX DMA at every protocol turnaround before the
// master clocks the next phase. Without preemption that re-arm waits behind any
// in-flight handler (CAN RX under bus load), the master clocks into an unarmed
// peripheral, and the transfer fails its checksum -> NACK retry storms.
#define IRQ_PRIORITY_COMMS 0U
#define IRQ_PRIORITY_DEFAULT 2U
typedef struct interrupt {
IRQn_Type irq_type;
void (*handler)(void);

View File

@@ -117,6 +117,22 @@ static void __attribute__ ((noinline)) enable_fpu(void) {
#define HEARTBEAT_IGNITION_CNT_OFF 2U
// called at 8Hz
static volatile bool tick_sample_pending = false;
// All runtime ADC sampling runs here, in thread context. ADC conversions
// busy-wait for tens to hundreds of microseconds, and all interrupts share one
// NVIC priority: sampling in the 8Hz tick interrupt delayed the SPI slave's
// RX DMA re-arm long enough for the host to clock into an unarmed peripheral
// (checksum failures -> NACK retry storms -> multi-second CAN blackouts).
static void tick_sample_poll(void) {
if (tick_sample_pending) {
tick_sample_pending = false;
harness_tick();
voltage_mV = current_board->read_voltage_mV();
current_mA = current_board->read_current_mA();
}
}
static void tick_handler(void) {
static uint32_t siren_countdown = 0; // siren plays while countdown > 0
static uint32_t controls_allowed_countdown = 0;
@@ -131,7 +147,7 @@ static void tick_handler(void) {
// tick drivers at 8Hz
fan_tick();
harness_tick();
tick_sample_pending = true; // ADC sampling deferred to thread context (see tick_sample_poll)
simple_watchdog_kick();
sound_tick();
@@ -176,7 +192,11 @@ static void tick_handler(void) {
const bool recent_heartbeat = heartbeat_counter == 0U;
// tick drivers at 1Hz
bool started = harness_check_ignition() || ignition_can;
// MEB/MQBevo have no harness ignition line; the SBU pin can sit asserted on a
// sleeping car (held true for days on an ID.4, pinning the device onroad against
// a silent bus). CAN ignition (0x3C0 Klemmen_Status_01) is the only valid source
// on this branch.
bool started = ignition_can;
bootkick_tick(started, recent_heartbeat);
// increase heartbeat counter and cap it at the uint32 limit
@@ -304,6 +324,10 @@ int main(void) {
current_board->set_can_mode(CAN_MODE_NORMAL);
harness_init();
// seed the ADC caches before interrupts are live
voltage_mV = current_board->read_voltage_mV();
current_mA = current_board->read_current_mA();
// panda has an FPU, let's use it!
enable_fpu();
@@ -347,6 +371,7 @@ int main(void) {
// LED should keep on blinking all the time
while (true) {
tick_sample_poll();
if (power_save_status == POWER_SAVE_STATUS_DISABLED) {
#ifdef DEBUG_FAULTS
if (fault_status == FAULT_STATUS_NONE) {
@@ -357,6 +382,7 @@ int main(void) {
delay(fade >> 4);
led_set(LED_RED, false);
delay((MAX_LED_FADE - fade) >> 4);
tick_sample_poll();
}
for (uint32_t fade = MAX_LED_FADE; fade > 0U; fade -= 1U) {
@@ -364,6 +390,7 @@ int main(void) {
delay(fade >> 4);
led_set(LED_RED, false);
delay((MAX_LED_FADE - fade) >> 4);
tick_sample_poll();
}
#ifdef DEBUG_FAULTS

View File

@@ -9,10 +9,14 @@ static int get_health_pkt(void *dat) {
struct health_t * health = (struct health_t*)dat;
health->uptime_pkt = uptime_cnt;
health->voltage_pkt = current_board->read_voltage_mV();
health->current_pkt = current_board->read_current_mA();
// cached values sampled in thread context (see tick_sample_poll in main.c);
// reading the ADC here would busy-wait in interrupt context
health->voltage_pkt = voltage_mV;
health->current_pkt = current_mA;
health->ignition_line_pkt = (uint8_t)(harness_check_ignition());
// no ignition line on MEB/MQBevo (see started in main.c); pandad ORs line with
// CAN ignition, so reporting the floating SBU read would pin the device onroad
health->ignition_line_pkt = 0U;
health->ignition_can_pkt = ignition_can;
health->controls_allowed_pkt = controls_allowed;

View File

@@ -13,6 +13,10 @@ extern uint8_t hw_type;
extern board *current_board;
extern uint32_t uptime_cnt;
// ADC results, sampled in thread context (see tick_sample_poll in main.c)
extern uint32_t voltage_mV;
extern uint32_t current_mA;
// heartbeat state
extern uint32_t heartbeat_counter;
extern bool heartbeat_lost;

View File

@@ -5,6 +5,10 @@ uint8_t hw_type = 0;
board *current_board;
uint32_t uptime_cnt = 0;
// ADC results, sampled in thread context (see tick_sample_poll in main.c)
uint32_t voltage_mV = 0;
uint32_t current_mA = 0;
// heartbeat state
uint32_t heartbeat_counter = 0;
bool heartbeat_lost = false;

View File

@@ -101,6 +101,12 @@ void llspi_init(void) {
register_set(&(SPI4->CR1), SPI_CR1_SPE, 0xFFFFU);
register_set(&(SPI4->CR2), 0, 0xFFFFU);
// preempt other handlers so the RX DMA is re-armed before the master clocks
// the next phase of a transfer
NVIC_SetPriority(DMA2_Stream2_IRQn, IRQ_PRIORITY_COMMS);
NVIC_SetPriority(DMA2_Stream3_IRQn, IRQ_PRIORITY_COMMS);
NVIC_SetPriority(SPI4_IRQn, IRQ_PRIORITY_COMMS);
NVIC_EnableIRQ(DMA2_Stream2_IRQn);
NVIC_EnableIRQ(DMA2_Stream3_IRQn);
NVIC_EnableIRQ(SPI4_IRQn);