/* SPDX-License-Identifier: Apache-2.0 * XIAO STM32C5 + Round Display CAN FD logger/analyzer. */ #include #include #include #include #include #include #include #include #include #include #include #include #define LCD_WIDTH 240 #define LCD_HEIGHT 240 #define CAN_NOMINAL_BITRATE 500000U #define CAN_DATA_BITRATE 2000000U #define FRAME_BYTES 64U #define LOG_COUNT 48U #define LOG_TEXT_LEN 36U #define VISIBLE_LOG_LINES 8U #define UI_REFRESH_MS 180U #define UI_CENTER_X 120 #define UI_CENTER_Y 120 #define UI_OUTER_RADIUS 108 #define UI_INNER_RADIUS 107 #define UI_LOG_LEFT 51 #define UI_LOG_HEADER_Y 86 #define UI_LOG_FIRST_Y 99 #define UI_LOG_ROW_STEP 12 #define UI_BUTTON_Y 199 #define UI_BUTTON_H 19 #define UI_LAST_X 54 #define UI_LAST_W 50 #define UI_NEXT_X 136 #define UI_NEXT_W 50 #define UI_PAGE_X 106 #define UI_PAGE_W 28 #define UI_PICKER_X 91 #define UI_PICKER_W 58 #define UI_PICKER_Y 103 #define UI_PICKER_ROW_H 18 #define UI_PICKER_ROWS 5U #define TOUCH_I2C_ADDR 0x2eU #define ID_NORMAL 0x100U #define ID_LOSSY 0x101U #define ID_BAD_CRC 0x102U #define PACKET_MAGIC_0 0xCAU #define PACKET_MAGIC_1 0xFDU #define PACKET_VERSION 1U #define COLOR_BLACK 0x0000U #define COLOR_WHITE 0xffffU #define COLOR_GREEN 0x07e0U #define COLOR_RED 0xf800U #define COLOR_YELLOW 0xffe0U #define COLOR_CYAN 0x07ffU #define COLOR_BLUE 0x001fU #define COLOR_GRAY 0x8410U static const struct device *const display_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_display)); static const struct device *const can_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_canbus)); static const struct device *const touch_i2c_dev = DEVICE_DT_GET(DT_NODELABEL(i2c1)); static const struct gpio_dt_spec backlight = GPIO_DT_SPEC_GET(DT_NODELABEL(display_backlight), gpios); /* XIAO D7 maps to PA10 on the STM32C5 board definition. */ static const struct gpio_dt_spec touch_irq = { .port = DEVICE_DT_GET(DT_NODELABEL(gpioa)), .pin = 10, .dt_flags = GPIO_ACTIVE_LOW | GPIO_PULL_UP, }; /* The official Seeed CHSC6X firmware updater drives XIAO D0 as the shared * LCD/TP reset. D0 maps to PA0 on XIAO STM32C5. */ static const struct gpio_dt_spec touch_reset = { .port = DEVICE_DT_GET(DT_NODELABEL(gpioa)), .pin = 0, .dt_flags = 0, }; /* XIAO D4=SDA/PB7 and D5=SCL/PB6 are I2C1. Keep GPIO descriptors only for * non-invasive raw-level diagnostics while the pins remain in I2C AF mode. */ static const struct gpio_dt_spec touch_sda = { .port = DEVICE_DT_GET(DT_NODELABEL(gpiob)), .pin = 7, .dt_flags = GPIO_PULL_UP, }; static const struct gpio_dt_spec touch_scl = { .port = DEVICE_DT_GET(DT_NODELABEL(gpiob)), .pin = 6, .dt_flags = GPIO_PULL_UP, }; CAN_MSGQ_DEFINE(rx_msgq, 64); static uint16_t framebuffer[LCD_WIDTH * LCD_HEIGHT]; static const struct display_buffer_descriptor frame_desc = { .buf_size = sizeof(framebuffer), .width = LCD_WIDTH, .height = LCD_HEIGHT, .pitch = LCD_WIDTH, }; struct stream_state { bool seen; uint32_t last_sequence; }; struct log_line { char text[LOG_TEXT_LEN]; uint16_t color; }; static struct stream_state streams[3]; static struct log_line logs[LOG_COUNT]; static uint8_t log_head; static uint8_t log_count; static uint8_t scroll_offset; static uint32_t rx_total; static uint32_t app_valid; static uint32_t crc_failed; static uint32_t estimated_lost; static uint32_t raw_frames; static uint32_t out_of_order; static uint32_t touch_count; static int touch_x; static int touch_y; static bool touching; static uint32_t touch_read_failures; static bool touch_read_confirmed; static bool ui_force_refresh; static enum can_state bus_state = CAN_STATE_STOPPED; static struct can_bus_err_cnt bus_errors; enum nav_button { NAV_NONE, NAV_LAST, NAV_PAGE, NAV_NEXT, }; static enum nav_button touch_button; static enum nav_button active_button; static bool page_picker_open; static int8_t page_picker_hover = -1; static uint8_t page_picker_first_page = 1U; static uint8_t page_picker_item_count = 1U; static uint16_t rgb565_be(uint16_t color) { return (uint16_t)((color >> 8) | (color << 8)); } static void set_pixel(int x, int y, uint16_t color) { if ((unsigned int)x < LCD_WIDTH && (unsigned int)y < LCD_HEIGHT) { framebuffer[y * LCD_WIDTH + x] = rgb565_be(color); } } static void clear_frame(uint16_t color) { uint16_t value = rgb565_be(color); for (size_t i = 0; i < ARRAY_SIZE(framebuffer); ++i) { framebuffer[i] = value; } } static void fill_rect(int x, int y, int w, int h, uint16_t color) { for (int py = y; py < y + h; ++py) { for (int px = x; px < x + w; ++px) { set_pixel(px, py, color); } } } static void draw_circle(int cx, int cy, int radius, uint16_t color) { int x = radius, y = 0, error = 1 - radius; while (x >= y) { set_pixel(cx + x, cy + y, color); set_pixel(cx + y, cy + x, color); set_pixel(cx - y, cy + x, color); set_pixel(cx - x, cy + y, color); set_pixel(cx - x, cy - y, color); set_pixel(cx - y, cy - x, color); set_pixel(cx + y, cy - x, color); set_pixel(cx + x, cy - y, color); y++; if (error < 0) { error += 2 * y + 1; } else { x--; error += 2 * (y - x) + 1; } } } static void glyph5x7(char c, uint8_t g[5]) { static const uint8_t digits[10][5] = { {0x3e,0x51,0x49,0x45,0x3e},{0x00,0x42,0x7f,0x40,0x00}, {0x42,0x61,0x51,0x49,0x46},{0x21,0x41,0x45,0x4b,0x31}, {0x18,0x14,0x12,0x7f,0x10},{0x27,0x45,0x45,0x45,0x39}, {0x3c,0x4a,0x49,0x49,0x30},{0x01,0x71,0x09,0x05,0x03}, {0x36,0x49,0x49,0x49,0x36},{0x06,0x49,0x49,0x29,0x1e} }; static const uint8_t letters[26][5] = { {0x7e,0x11,0x11,0x11,0x7e},{0x7f,0x49,0x49,0x49,0x36}, {0x3e,0x41,0x41,0x41,0x22},{0x7f,0x41,0x41,0x22,0x1c}, {0x7f,0x49,0x49,0x49,0x41},{0x7f,0x09,0x09,0x09,0x01}, {0x3e,0x41,0x49,0x49,0x7a},{0x7f,0x08,0x08,0x08,0x7f}, {0x00,0x41,0x7f,0x41,0x00},{0x20,0x40,0x41,0x3f,0x01}, {0x7f,0x08,0x14,0x22,0x41},{0x7f,0x40,0x40,0x40,0x40}, {0x7f,0x02,0x0c,0x02,0x7f},{0x7f,0x04,0x08,0x10,0x7f}, {0x3e,0x41,0x41,0x41,0x3e},{0x7f,0x09,0x09,0x09,0x06}, {0x3e,0x41,0x51,0x21,0x5e},{0x7f,0x09,0x19,0x29,0x46}, {0x46,0x49,0x49,0x49,0x31},{0x01,0x01,0x7f,0x01,0x01}, {0x3f,0x40,0x40,0x40,0x3f},{0x1f,0x20,0x40,0x20,0x1f}, {0x3f,0x40,0x38,0x40,0x3f},{0x63,0x14,0x08,0x14,0x63}, {0x07,0x08,0x70,0x08,0x07},{0x61,0x51,0x49,0x45,0x43} }; memset(g, 0, 5); if (c >= '0' && c <= '9') { memcpy(g, digits[c - '0'], 5); return; } if (c >= 'a' && c <= 'z') { c -= ('a' - 'A'); } if (c >= 'A' && c <= 'Z') { memcpy(g, letters[c - 'A'], 5); return; } switch (c) { case '-': g[0]=g[1]=g[2]=g[3]=g[4]=0x08; break; case '.': g[2]=0x60; break; case ':': g[2]=0x36; break; case '/': g[0]=0x20;g[1]=0x10;g[2]=0x08;g[3]=0x04;g[4]=0x02;break; case '%': g[0]=0x23;g[1]=0x13;g[2]=0x08;g[3]=0x64;g[4]=0x62;break; case '+': g[1]=0x08;g[2]=0x1c;g[3]=0x08;break; case '[': g[1]=g[2]=g[3]=0x7f;g[4]=0x41;break; case ']': g[0]=0x41;g[1]=g[2]=g[3]=0x7f;break; case '?': g[0]=0x02;g[1]=0x01;g[2]=0x51;g[3]=0x09;g[4]=0x06;break; default: break; } } static void draw_char(int x, int y, char c, uint16_t color) { uint8_t g[5]; glyph5x7(c, g); for (int col = 0; col < 5; ++col) { for (int row = 0; row < 7; ++row) { if ((g[col] & BIT(row)) != 0U) { set_pixel(x + col, y + row, color); } } } } static void draw_text(int x, int y, const char *text, uint16_t color) { while (*text != '\0' && x < LCD_WIDTH - 5) { draw_char(x, y, *text++, color); x += 6; } } static void draw_text_centered(int y, const char *text, uint16_t color) { int width = (int)strlen(text) * 6 - 1; draw_text((LCD_WIDTH - width) / 2, y, text, color); } static void draw_nav_button(int x, int width, const char *label, bool active) { if (active) { fill_rect(x, UI_BUTTON_Y, width, UI_BUTTON_H, COLOR_CYAN); } int text_width = (int)strlen(label) * 6 - 1; draw_text(x + (width - text_width) / 2, UI_BUTTON_Y + 6, label, active ? COLOR_BLACK : COLOR_WHITE); } static uint8_t page_picker_first(void) { uint8_t total = log_count == 0U ? 1U : log_count; uint8_t current = scroll_offset + 1U; uint8_t first = current > 2U ? current - 2U : 1U; if (total > UI_PICKER_ROWS && first > total - UI_PICKER_ROWS + 1U) { first = total - UI_PICKER_ROWS + 1U; } return first; } static uint8_t page_picker_count(void) { uint8_t total = log_count == 0U ? 1U : log_count; return total < UI_PICKER_ROWS ? total : UI_PICKER_ROWS; } static int8_t page_picker_row_at(int x, int y) { if (x < UI_PICKER_X || x >= UI_PICKER_X + UI_PICKER_W || y < UI_PICKER_Y || y >= UI_PICKER_Y + UI_PICKER_ROW_H * UI_PICKER_ROWS) { return -1; } int8_t row = (int8_t)((y - UI_PICKER_Y) / UI_PICKER_ROW_H); uint8_t count = page_picker_open ? page_picker_item_count : page_picker_count(); return row < (int8_t)count ? row : -1; } static void draw_page_picker(void) { char label[12]; uint8_t first = page_picker_first_page; uint8_t count = page_picker_item_count; fill_rect(UI_PICKER_X - 1, UI_PICKER_Y - 1, UI_PICKER_W + 2, UI_PICKER_ROW_H * count + 2, COLOR_CYAN); fill_rect(UI_PICKER_X, UI_PICKER_Y, UI_PICKER_W, UI_PICKER_ROW_H * count, COLOR_BLACK); for (uint8_t row = 0; row < count; ++row) { uint8_t page = first + row; bool highlighted = page_picker_hover == (int8_t)row || (page_picker_hover < 0 && page == scroll_offset + 1U); if (highlighted) { fill_rect(UI_PICKER_X + 1, UI_PICKER_Y + row * UI_PICKER_ROW_H, UI_PICKER_W - 2, UI_PICKER_ROW_H, COLOR_CYAN); } snprintk(label, sizeof(label), "PAGE %u", page); int text_width = (int)strlen(label) * 6 - 1; draw_text(UI_PICKER_X + (UI_PICKER_W - text_width) / 2, UI_PICKER_Y + row * UI_PICKER_ROW_H + 6, label, highlighted ? COLOR_BLACK : COLOR_WHITE); } } static uint16_t crc16_ccitt(const uint8_t *data, size_t len) { uint16_t crc = 0xffffU; for (size_t i = 0; i < len; ++i) { crc ^= (uint16_t)data[i] << 8; for (int bit = 0; bit < 8; ++bit) { crc = (crc & 0x8000U) != 0U ? (uint16_t)((crc << 1) ^ 0x1021U) : (uint16_t)(crc << 1); } } return crc; } static uint32_t get_u32_le(const uint8_t *src) { return (uint32_t)src[0] | ((uint32_t)src[1] << 8) | ((uint32_t)src[2] << 16) | ((uint32_t)src[3] << 24); } static void add_log(uint16_t color, const char *format, ...) { va_list args; va_start(args, format); vsnprintk(logs[log_head].text, LOG_TEXT_LEN, format, args); va_end(args); logs[log_head].color = color; log_head = (log_head + 1U) % LOG_COUNT; if (log_count < LOG_COUNT) { log_count++; } else if (scroll_offset < LOG_COUNT - 1U) { scroll_offset++; } } static int stream_index(uint32_t id) { if (id == ID_NORMAL) return 0; if (id == ID_LOSSY) return 1; if (id == ID_BAD_CRC) return 2; return -1; } static void process_frame(const struct can_frame *frame) { uint8_t len = can_dlc_to_bytes(frame->dlc); uint32_t now = k_uptime_get_32() / 1000U; int stream = stream_index(frame->id); rx_total++; if (stream >= 0 && (frame->flags & CAN_FRAME_FDF) != 0U && len == FRAME_BYTES && frame->data[0] == PACKET_MAGIC_0 && frame->data[1] == PACKET_MAGIC_1 && frame->data[2] == PACKET_VERSION) { uint32_t sequence = get_u32_le(&frame->data[4]); uint16_t received_crc = (uint16_t)frame->data[62] | ((uint16_t)frame->data[63] << 8); uint16_t calculated_crc = crc16_ccitt(frame->data, 62U); if (streams[stream].seen) { if (sequence > streams[stream].last_sequence + 1U) { estimated_lost += sequence - streams[stream].last_sequence - 1U; } else if (sequence <= streams[stream].last_sequence) { out_of_order++; } } streams[stream].seen = true; streams[stream].last_sequence = sequence; if (received_crc == calculated_crc) { app_valid++; add_log(COLOR_GREEN, "%05u %03X F64 OK %05u", now % 100000U, frame->id, sequence % 100000U); } else { crc_failed++; add_log(COLOR_RED, "%05u %03X F64 CRC %05u", now % 100000U, frame->id, sequence % 100000U); } } else { raw_frames++; add_log(COLOR_YELLOW, "%05u %03X %c%02u RAW -----", now % 100000U, frame->id, (frame->flags & CAN_FRAME_FDF) != 0U ? 'F' : 'C', len); } } static const char *state_name(enum can_state state) { switch (state) { case CAN_STATE_ERROR_ACTIVE: return "ACTIVE"; case CAN_STATE_ERROR_WARNING: return "WARNING"; case CAN_STATE_ERROR_PASSIVE: return "PASSIVE"; case CAN_STATE_BUS_OFF: return "BUS-OFF"; case CAN_STATE_STOPPED: return "STOPPED"; default: return "UNKNOWN"; } } static int render_ui(void) { char line[40]; uint32_t expected = app_valid + crc_failed + estimated_lost; uint32_t loss_tenths = expected == 0U ? 0U : (estimated_lost * 1000U) / expected; clear_frame(COLOR_BLACK); draw_circle(UI_CENTER_X, UI_CENTER_Y, UI_OUTER_RADIUS, COLOR_BLUE); draw_circle(UI_CENTER_X, UI_CENTER_Y, UI_INNER_RADIUS, COLOR_CYAN); draw_text_centered(20, "CAN FD LOGGER", COLOR_CYAN); snprintk(line, sizeof(line), "RX %u OK %u", rx_total, app_valid); draw_text_centered(34, line, COLOR_WHITE); snprintk(line, sizeof(line), "LOST %u %u.%u%%", estimated_lost, loss_tenths / 10U, loss_tenths % 10U); draw_text_centered(46, line, estimated_lost == 0U ? COLOR_GREEN : COLOR_YELLOW); snprintk(line, sizeof(line), "CRC %u RAW %u OOO %u", crc_failed, raw_frames, out_of_order); draw_text_centered(58, line, crc_failed == 0U ? COLOR_GREEN : COLOR_RED); snprintk(line, sizeof(line), "%s TXE %u RXE %u", state_name(bus_state), bus_errors.tx_err_cnt, bus_errors.rx_err_cnt); draw_text_centered(70, line, bus_state == CAN_STATE_ERROR_ACTIVE ? COLOR_GREEN : COLOR_RED); fill_rect(UI_LOG_LEFT, 82, LCD_WIDTH - (UI_LOG_LEFT * 2), 1, COLOR_GRAY); draw_text(UI_LOG_LEFT, UI_LOG_HEADER_Y, "TIME ID LEN STA SEQ", COLOR_CYAN); for (uint8_t row = 0; row < VISIBLE_LOG_LINES; ++row) { uint8_t newest_offset = scroll_offset + row; if (newest_offset >= log_count) break; uint8_t index = (log_head + LOG_COUNT - 1U - newest_offset) % LOG_COUNT; draw_text(UI_LOG_LEFT, UI_LOG_FIRST_Y + row * UI_LOG_ROW_STEP, logs[index].text, logs[index].color); } draw_nav_button(UI_LAST_X, UI_LAST_W, "LAST", active_button == NAV_LAST); draw_nav_button(UI_NEXT_X, UI_NEXT_W, "NEXT", active_button == NAV_NEXT); snprintk(line, sizeof(line), "%u/%u", scroll_offset + 1U, log_count == 0U ? 1U : log_count); if (active_button == NAV_PAGE) { fill_rect(UI_PAGE_X, UI_BUTTON_Y, UI_PAGE_W, UI_BUTTON_H, COLOR_CYAN); } draw_text_centered(UI_BUTTON_Y + 6, line, active_button == NAV_PAGE ? COLOR_BLACK : COLOR_GRAY); if (page_picker_open) { draw_page_picker(); } return display_write(display_dev, 0, 0, &frame_desc, framebuffer); } static uint8_t max_scroll_offset(void) { return log_count > 0U ? log_count - 1U : 0U; } static enum nav_button nav_button_at(int x, int y) { if (y < 188) { return NAV_NONE; } if (x >= UI_PAGE_X && x < UI_PAGE_X + UI_PAGE_W) { return NAV_PAGE; } if (x >= 42 && x < 111) { return NAV_LAST; } if (x > 129 && x <= 198) { return NAV_NEXT; } return NAV_NONE; } static void touch_release(void) { uint8_t max_scroll = max_scroll_offset(); bool keep_picker = false; if (page_picker_open && page_picker_hover >= 0) { scroll_offset = page_picker_first_page + (uint8_t)page_picker_hover - 1U; page_picker_open = false; } else if (page_picker_open && touch_button == NAV_PAGE) { /* Tapping the center counter again leaves the picker open. */ keep_picker = true; } else if (page_picker_open) { page_picker_open = false; } else if (active_button == NAV_PAGE) { page_picker_first_page = page_picker_first(); page_picker_item_count = page_picker_count(); page_picker_open = true; keep_picker = true; } else if (active_button == NAV_LAST && scroll_offset > 0U) { scroll_offset--; } else if (active_button == NAV_NEXT && scroll_offset < max_scroll) { scroll_offset++; } printk("Touch %u button=%s page=%u/%u picker=%s\n", touch_count, active_button == NAV_LAST ? "LAST" : active_button == NAV_NEXT ? "NEXT" : touch_button == NAV_PAGE ? "PAGE" : "NONE", scroll_offset + 1U, log_count == 0U ? 1U : log_count, keep_picker ? "open" : "closed"); touch_button = NAV_NONE; active_button = NAV_NONE; page_picker_hover = -1; touching = false; ui_force_refresh = true; } static int touch_hardware_reset(void) { int ret = gpio_pin_configure_dt(&touch_reset, GPIO_OUTPUT_HIGH); if (ret < 0) { return ret; } /* Match Seeed's chsc6x_tp_reset(): low 20 ms, then high 30 ms. */ (void)gpio_pin_set_dt(&touch_reset, 0); k_msleep(20); (void)gpio_pin_set_dt(&touch_reset, 1); k_msleep(30); return 0; } static int touch_i2c_read_point(uint8_t point[5]) { /* A single read message generates START + 0x2e/R + five bytes + STOP, * matching Wire.requestFrom() in Seeed's CHSC6X driver. */ return i2c_read(touch_i2c_dev, point, 5, TOUCH_I2C_ADDR); } /* Seeed's CHSC6X protocol is a plain 5-byte read from address 0x2e while * TP_INT is low. Do not prepend a register byte. */ static void touch_poll(void) { uint8_t point[5]; int pressed = gpio_pin_get_dt(&touch_irq); if (pressed < 0) { return; } if (pressed == 0) { if (touching) { touch_release(); } return; } int ret = touch_i2c_read_point(point); if (ret < 0) { touch_read_failures++; if (touch_read_failures == 1U || (touch_read_failures % 100U) == 0U) { int recovery = (touch_read_failures % 100U) == 0U ? i2c_recover_bus(touch_i2c_dev) : 0; printk("CHSC6X HW-I2C failed: %d count=%u recover=%d SDA=%d SCL=%d INT=1\n", ret, touch_read_failures, recovery, gpio_pin_get_raw(touch_sda.port, touch_sda.pin), gpio_pin_get_raw(touch_scl.port, touch_scl.pin)); } return; } if (!touch_read_confirmed) { printk("CHSC6X HW-I2C OK: %02x %02x %02x %02x %02x\n", point[0], point[1], point[2], point[3], point[4]); touch_read_confirmed = true; } if (point[0] != 0x01U) { return; } touch_x = point[2]; touch_y = point[4]; if (!touching) { touching = true; touch_count++; page_picker_hover = page_picker_open ? page_picker_row_at(touch_x, touch_y) : -1; touch_button = nav_button_at(touch_x, touch_y); active_button = page_picker_open ? NAV_NONE : touch_button; ui_force_refresh = true; printk("Touch %u down: x=%d y=%d button=%s\n", touch_count, touch_x, touch_y, touch_button == NAV_LAST ? "LAST" : touch_button == NAV_NEXT ? "NEXT" : touch_button == NAV_PAGE ? "PAGE" : "NONE"); } else if (page_picker_open) { int8_t hovered = page_picker_row_at(touch_x, touch_y); if (hovered != page_picker_hover) { page_picker_hover = hovered; ui_force_refresh = true; } } else { enum nav_button hovered = nav_button_at(touch_x, touch_y); enum nav_button new_active = hovered == touch_button ? touch_button : NAV_NONE; if (new_active != active_button) { active_button = new_active; ui_force_refresh = true; } } } int main(void) { int ret; if (!device_is_ready(display_dev) || !device_is_ready(can_dev) || !device_is_ready(touch_i2c_dev) || !gpio_is_ready_dt(&backlight) || !gpio_is_ready_dt(&touch_irq) || !gpio_is_ready_dt(&touch_reset) || !gpio_is_ready_dt(&touch_sda) || !gpio_is_ready_dt(&touch_scl)) { printk("Display, CAN, backlight, or CHSC6X bus is not ready\n"); return -ENODEV; } ret = touch_hardware_reset(); if (ret != 0) { printk("Touch D0 hardware reset failed: %d\n", ret); return ret; } printk("Touch D0 hardware reset: low 20ms, high 30ms\n"); ret = gpio_pin_configure_dt(&backlight, GPIO_OUTPUT_ACTIVE); if (ret != 0) return ret; ret = display_set_pixel_format(display_dev, PIXEL_FORMAT_RGB_565); if (ret != 0) return ret; display_blanking_off(display_dev); /* Match Seeed's HardwareTest order: display first, then touch/Wire. */ k_msleep(100); ret = gpio_pin_configure_dt(&touch_irq, GPIO_INPUT); if (ret != 0) { printk("Touch IRQ configure failed: %d\n", ret); return ret; } ret = i2c_configure(touch_i2c_dev, I2C_MODE_CONTROLLER | I2C_SPEED_SET(I2C_SPEED_STANDARD)); if (ret != 0) { printk("Touch HW-I2C configure failed: %d\n", ret); return ret; } printk("Touch HW-I2C ready: 100k SDA=%d SCL=%d\n", gpio_pin_get_raw(touch_sda.port, touch_sda.pin), gpio_pin_get_raw(touch_scl.port, touch_scl.pin)); ret = can_set_mode(can_dev, CAN_MODE_FD); if (ret != 0) { printk("CAN FD mode failed: %d\n", ret); return ret; } ret = can_set_bitrate(can_dev, CAN_NOMINAL_BITRATE); if (ret != 0) { printk("CAN nominal bitrate failed: %d\n", ret); return ret; } ret = can_set_bitrate_data(can_dev, CAN_DATA_BITRATE); if (ret != 0) { printk("CAN data bitrate failed: %d\n", ret); return ret; } const struct can_filter rx_all = { .id = 0U, .mask = 0U, .flags = 0U }; ret = can_add_rx_filter_msgq(can_dev, &rx_msgq, &rx_all); if (ret < 0) { printk("CAN filter failed: %d\n", ret); return ret; } ret = can_start(can_dev); if (ret != 0) { printk("CAN start failed: %d\n", ret); return ret; } add_log(COLOR_CYAN, "LOGGER READY 500K/2M"); printk("CAN FD logger ready: nominal 500k, data 2M, CHSC6X HW-I2C\n"); printk("Touch bus: addr=0x%02x INT=%d (active-low)\n", TOUCH_I2C_ADDR, gpio_pin_get_dt(&touch_irq)); int64_t next_refresh = 0; while (true) { struct can_frame frame; touch_poll(); while (k_msgq_get(&rx_msgq, &frame, K_NO_WAIT) == 0) process_frame(&frame); (void)can_get_state(can_dev, &bus_state, &bus_errors); if (ui_force_refresh || k_uptime_get() >= next_refresh) { ret = render_ui(); if (ret != 0) printk("Display write failed: %d\n", ret); ui_force_refresh = false; next_refresh = k_uptime_get() + UI_REFRESH_MS; } k_msleep(10); } }