feat(protocol): 更新SPI Link协议至v2版本并优化CAN通信

- 将SPI OTA帧格式升级为SPI Link v2,采用新的帧格式:
  | SOF0 0xA5 | SOF1 0x5A | VER 0x02 | FLAGS | SESSION 4B LE |
  TX_SEQ | ACK_SEQ | CREDIT | TYPE | LEN 2B LE | RSV 2B | PAYLOAD |
  CRC32 4B LE |
- 新增V2_OVERHEAD=20字节开销,APP最大payload为236字节,BOOT为108字节
- 修改帧类型定义:ENTER_BOOT从0xB5合并至0xB0,新增CAN业务类型0xC0~C3
- 更新CAN协议文档,支持SPI Link v2-only模式,调整最大业务payload计算
- 扩展0xC1帧功能,支持双向通信和1Hz主动推送ID快照
- 添加0xC3完整ID快照帧类型,包含seq/rx_count等完整字段
- 更新协议文档中的错误码定义和反馈payload格式

refactor(main): 重构主程序入口并添加CAN反馈任务

- 在main.cpp中添加CAN反馈任务,更新启动流程注释
- 创建createCriticalTask辅助函数,统一关键任务创建逻辑
- 调整SPI初始化顺序,确保CAN反馈任务依赖正确
- 优化错误处理机制,关键任务失败时立即重启
- 将GD32版本探测移至ota_relay任务处理
- 更新版本号至0.3.48,调整编译时间和Git哈希

config: 更新SPI和BLE配置参数

- 添加SPI主机相关配置参数,包括CS GPIO、READY GPIO等引脚定义
- 配置SPI缓冲区大小:APP为256字节,BOOT为128字节
- 更新BLE滑动窗口参数,ACK窗口设为32,数据队列深度为72
- 调整SPI工作队列大小至5120字节,增加任务堆栈监控说明
This commit is contained in:
小龙虾
2026-07-16 23:00:15 +08:00
parent 51c89b3065
commit 5db3f66b8e
57 changed files with 4728 additions and 2098 deletions
+67 -21
View File
@@ -7,30 +7,59 @@
* common/utils.js(escapeHtml/formatBytes)、
* signals.js(renderInlineSignalGrid/ensureMessageDefaults/renderSelectedMessage/updateEncodedPreview)。
*/
import { state, el, padHex, normalizeWhitespace, cssEscape } from './dbc.js';
import { escapeHtml as sanitizeHtml, formatBytes } from '../common/utils.js';
import { renderInlineSignalGrid, ensureMessageDefaults, renderSelectedMessage, updateEncodedPreview } from './signals.js';
import { state, el, padHex, normalizeWhitespace, cssEscape } from './dbc.js?v=20260717t';
import { escapeHtml as sanitizeHtml, formatBytes } from '../common/utils.js?v=20260717t';
import { renderInlineSignalGrid, ensureMessageDefaults, renderSelectedMessage, updateEncodedPreview } from './signals.js?v=20260717t';
import { scheduleCanPersist } from './persist.js?v=20260717t';
// ==================== DBC 文件信息 ====================
/** @description 更新 DBC 文件信息卡片(文件名/报文数/信号数/节点数/大小)。 */
export function updateDbcFileInfo(file, dbc) {
if (!el.dbcFileInfo) return;
if (!file || !dbc) {
el.dbcFileInfo.classList.remove('show');
el.dbcFileName.textContent = '-';
el.dbcMessageCount.textContent = '0';
el.dbcSignalCount.textContent = '0';
el.dbcNodeCount.textContent = '0';
el.dbcFileSize.textContent = '0 B';
/** @description 刷新多 DBC 摘要条与文件 chip 列表。 */
export function updateDbcFileInfo() {
var sources = state.dbcSources || [];
var has = sources.length > 0;
var dbc = state.dbc;
if (el.dbcFileInfo) {
el.dbcFileInfo.hidden = !has;
el.dbcFileInfo.classList.toggle('show', has);
}
/* 有文件时仍显示「添加」,文案改成添加;「全部清除」紧挨添加按钮 */
if (el.importDbcBtn) {
el.importDbcBtn.hidden = false;
el.importDbcBtn.textContent = has ? '添加 DBC' : '导入 DBC';
}
if (el.clearDbcBtn) el.clearDbcBtn.hidden = !has;
if (el.dbcDropZone) el.dbcDropZone.classList.toggle('has-dbc', has);
if (!has || !dbc) {
if (el.dbcFileCount) el.dbcFileCount.textContent = '0';
if (el.dbcMessageCount) el.dbcMessageCount.textContent = '0';
if (el.dbcSignalCount) el.dbcSignalCount.textContent = '0';
if (el.dbcNodeCount) el.dbcNodeCount.textContent = '0';
if (el.dbcFileSize) el.dbcFileSize.textContent = '0 B';
if (el.dbcFileChips) el.dbcFileChips.innerHTML = '';
return;
}
var signalCount = dbc.messages.reduce(function (sum, msg) { return sum + msg.signals.length; }, 0);
el.dbcFileInfo.classList.add('show');
el.dbcFileName.textContent = file.name || '-';
el.dbcMessageCount.textContent = String(dbc.messages.length);
el.dbcSignalCount.textContent = String(signalCount);
el.dbcNodeCount.textContent = String(dbc.nodeNames.length);
el.dbcFileSize.textContent = formatBytes(file.size || 0);
var totalSize = sources.reduce(function (sum, s) { return sum + (s.size || 0); }, 0);
if (el.dbcFileCount) el.dbcFileCount.textContent = String(sources.length);
if (el.dbcMessageCount) el.dbcMessageCount.textContent = String(dbc.messages.length);
if (el.dbcSignalCount) el.dbcSignalCount.textContent = String(signalCount);
if (el.dbcNodeCount) el.dbcNodeCount.textContent = String(dbc.nodeNames.length);
if (el.dbcFileSize) el.dbcFileSize.textContent = formatBytes(totalSize);
if (el.dbcFileChips) {
el.dbcFileChips.innerHTML = sources.map(function (s) {
var nMsg = (s.dbc && s.dbc.messages) ? s.dbc.messages.length : 0;
return '' +
'<span class="dbc-file-chip" data-source-id="' + sanitizeHtml(s.id) + '" title="' + sanitizeHtml(s.name) + ' · ' + nMsg + ' 报文">' +
'<span class="dbc-file-chip-name">' + sanitizeHtml(s.name) + '</span>' +
'<button type="button" class="dbc-file-chip-remove" data-remove-id="' + sanitizeHtml(s.id) + '" title="移除此文件">×</button>' +
'</span>';
}).join('');
}
}
// ==================== 报文过滤 ====================
@@ -109,6 +138,7 @@ export function renderNodeFilterList() {
else delete state.transmitterFilter[name];
recomputeTransmitterFilterEmpty();
filterMessages();
scheduleCanPersist();
});
}
}
@@ -143,9 +173,11 @@ export function setNodeFilterPanelOpen(open) {
renderNodeFilterList();
el.nodeFilterPanel.hidden = false;
el.nodeFilterBtn.setAttribute('aria-expanded', 'true');
if (el.nodeFilterWrap) el.nodeFilterWrap.classList.add('is-open');
} else {
el.nodeFilterPanel.hidden = true;
el.nodeFilterBtn.setAttribute('aria-expanded', 'false');
if (el.nodeFilterWrap) el.nodeFilterWrap.classList.remove('is-open');
}
}
@@ -173,12 +205,14 @@ export function bindNodeFilterEvents() {
recomputeTransmitterFilterEmpty();
renderNodeFilterList();
filterMessages();
scheduleCanPersist();
});
if (el.nodeFilterNoneBtn) el.nodeFilterNoneBtn.addEventListener('click', function () {
state.transmitterFilter = Object.create(null);
state.transmitterFilterEmpty = true;
renderNodeFilterList();
filterMessages();
scheduleCanPersist();
});
}
@@ -186,8 +220,13 @@ export function bindNodeFilterEvents() {
/** @description 渲染报文列表表格(表头 + 行 + 展开区)。 */
export function renderMessageList() {
if (!el.messageList) return;
if (!state.dbc) {
el.messageList.innerHTML = '<div class="status-empty">等待导入 DBC...</div>';
if (!state.dbc || !state.dbcSources.length) {
el.messageList.innerHTML =
'<div class="dbc-empty-drop" id="dbcEmptyDrop">' +
'<div class="upload-ico">📥</div>' +
'<h4>导入 DBC 开始编辑报文</h4>' +
'<p>支持一次选择或拖入 <b>多个</b> <code>.dbc</code> 文件</p>' +
'</div>';
updateBatchUi();
return;
}
@@ -210,7 +249,10 @@ export function renderMessageList() {
'<span class="message-col message-col-check"><input type="checkbox" class="message-check" data-key="' + sanitizeHtml(msg.key) + '"' + checked + '></span>' +
'<span class="message-col message-col-toggle"><button type="button" class="message-toggle' + (expanded ? ' expanded' : '') + '" data-key="' + sanitizeHtml(msg.key) + '" aria-label="展开/收起信号">' + caret + '</button></span>' +
'<span class="message-col message-col-index">' + (index + 1) + '</span>' +
'<span class="message-col message-col-name" title="' + sanitizeHtml(msg.name) + '">' + sanitizeHtml(msg.name) + '</span>' +
'<span class="message-col message-col-dbc" title="' + sanitizeHtml(msg.sourceName || '-') + '">' +
sanitizeHtml(msg.sourceName ? String(msg.sourceName).replace(/\.dbc$/i, '') : '-') + '</span>' +
'<span class="message-col message-col-name" title="' + sanitizeHtml(msg.name) + '">' +
sanitizeHtml(msg.name) + '</span>' +
'<span class="message-col message-col-id">' + sanitizeHtml(idHex) + '</span>' +
'<span class="message-col message-col-channel">CH' + sanitizeHtml(channelTxt) + '</span>' +
'<span class="message-col message-col-type">' + sanitizeHtml(msg.isExtended ? '扩展' : '标准') + '</span>' +
@@ -230,6 +272,7 @@ export function renderMessageList() {
'<span class="message-col message-col-check"></span>' +
'<span class="message-col message-col-toggle"></span>' +
'<span class="message-col message-col-index">#</span>' +
'<span class="message-col message-col-dbc">DBC名称</span>' +
'<span class="message-col message-col-name">报文名称</span>' +
'<span class="message-col message-col-id">标识符</span>' +
'<span class="message-col message-col-channel">通道</span>' +
@@ -260,6 +303,7 @@ export function renderMessageList() {
if (this.checked) state.checkedKeys[k] = true;
else delete state.checkedKeys[k];
updateBatchUi();
scheduleCanPersist();
});
}
var toggles = el.messageList.querySelectorAll('.message-toggle');
@@ -314,6 +358,7 @@ function toggleMessageExpand(key) {
toggle.classList.toggle('expanded', willExpand);
toggle.textContent = willExpand ? '\u25BE' : '\u25B8';
}
scheduleCanPersist();
}
// ==================== 批量 UI ====================
@@ -352,4 +397,5 @@ export function selectMessage(key) {
renderMessageList();
renderSelectedMessage();
updateEncodedPreview();
scheduleCanPersist();
}