/* * can_bus.h — MCP2515 CAN bus wrapper for KL630 (J15 SPI connector) * * API mirrors bt_uart.h: both channels carry the same JSON payload. * BLE channel: bt_uart_send_json(json) * CAN channel: can_bus_send_json(json) * * The same JSON string {"class":"car","level":1} is transmitted on both * channels simultaneously from fire_json_async() in event_recorder.c. * * MCP2515 is classic CAN (max 8 bytes per frame). Long JSON strings are * split into sequential 8-byte frames automatically: * Frame N: bytes [N*8 .. N*8+7] of the JSON string (padded with 0x00) * The receiver reassembles by concatenating frames until a null byte. * If a CAN FD controller is fitted later, switch to single-frame send. */ #ifndef CAN_BUS_H #define CAN_BUS_H #include /* * can_bus_init — open MCP2515 via SPI and start writer thread. * spidev : e.g. "/dev/spidev1.0" * can_speed_kbps: 250 (typical) or 125, 500, 1000 * can_id : 11-bit standard frame ID for outbound frames * Returns 0 on success, -1 on error. */ int can_bus_init(const char *spidev, int can_speed_kbps, uint32_t can_id); /* * can_bus_send_json — non-blocking: enqueue a JSON string for CAN transmission. * Same signature as bt_uart_send_json(); call both from fire_json_async(). * Long strings are split into multiple 8-byte CAN frames automatically. */ void can_bus_send_json(const char *json); /* * can_ctrl_cmd_t — parameters for can_bus_send_control_cmd(). * * CAN ID 0x75 DLC=8 Intel byte order: * frame[0] = speed (bits 0-7, byte 0: throttle_limit_command) * frame[6] = (led_gpio & 1) | ((led_enable & 1) << 6) * bit 48 = led_gpio (byte 6 bit 0): LED GPIO 1=high 0=low * bit 54 = led_enable (byte 6 bit 6): LED 1=enable 0=disable * frame[1..5], frame[7] = 0x00 * * keepalive_interval_ms: >0 updates the keepalive period; 0 = no change. */ typedef struct { uint8_t speed; /* byte 0: throttle_limit_command, 0-255 */ uint8_t led_gpio; /* bit 48 (byte 6 bit 0): LED GPIO 1=high, 0=low */ uint8_t led_enable; /* bit 54 (byte 6 bit 6): 1=enable, 0=disable */ int keepalive_interval_ms; /* >0 updates interval, 0 = no change */ } can_ctrl_cmd_t; /* * can_bus_send_control_cmd — send one 8-byte CAN control frame immediately * and update the keepalive state to maintain the new values. */ void can_bus_send_control_cmd(const can_ctrl_cmd_t *ctrl); /* can_bus_close — drain queue, join writer thread, close SPI device. */ void can_bus_close(void); /* * can_ecu_status_t — parsed signals from ECU CAN ID 0x50 (throttle_status frame). * * Frame layout (Intel byte order): * frame[0..1] = throttle_status (bits 0-15, uint16) * frame[2..3] = throttle_pedal_volt (bits 16-31, uint16) * frame[4..5] = led_flash_timer (bits 32-47, uint16) * frame[6] bit 0 = can_lose_ecu (bit 48) * frame[6] bit 7 = throttle_limit_off (bit 55) * frame[7] = heart_beam (bits 56-63, uint8) * * can_lose_timeout: KL630 self-detected, set to 1 when no ECU frame for >100ms. */ typedef struct { uint16_t throttle_status; /* bits 0-15: current throttle value, 0-8191 */ uint16_t throttle_pedal_volt; /* bits 16-31: pedal voltage */ uint16_t led_flash_timer; /* bits 32-47: LED flash timer */ uint8_t can_lose_ecu; /* bit 48: ECU-reported CAN loss (0/1) */ uint8_t throttle_limit_off; /* bit 55: throttle limit off flag (0/1) */ uint8_t heart_beam; /* bits 56-63: heartbeat counter */ int can_lose_timeout; /* KL630 self-detected: 1 if ECU silent >100ms*/ } can_ecu_status_t; /* can_bus_get_ecu_status — thread-safe snapshot of latest ECU status. */ void can_bus_get_ecu_status(can_ecu_status_t *out); #endif /* CAN_BUS_H */