diff --git a/README.md b/README.md index 68fd2b9..6fe3128 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # FMD C Compiler -FMD C Compiler 是一个用于 FMD / FT61FC6X 系列 MCU 工程开发的 VS Code 插件。插件封装厂商 `c.exe` 编译器,并自动生成 VS Code 工程配置,让传统 MCU 工程可以在 VS Code 中编辑、编译、管理输出文件和配置 IntelliSense。 +FMD C Compiler 是一个用于 FMD 系列 MCU 工程开发的 VS Code 插件。插件封装厂商 `c.exe` 编译器,并自动生成 VS Code 工程配置,让传统 MCU 工程可以在 VS Code 中编辑、编译、管理输出文件和配置 IntelliSense。 ## 功能特性 @@ -35,25 +35,7 @@ xxx.C C:\Program Files (x86)\CCompiler\Compiler\data\bin\c.exe ``` -默认芯片: - -```text -FT61FC6X -``` - -## 安装方式 - -从 VSIX 安装: - -```bash -code --install-extension fmd-c-compiler-0.2.9.vsix -``` - -也可以在 VS Code 扩展面板中选择: - -```text -Install from VSIX... -``` +目标芯片会优先从 `.prj` 文件中的 `Device = ...` 字段自动识别。部分官方工程型号会映射为编译器芯片库中的实际型号,例如 `FT61E13X` 会按官方工具输出映射为 `FT61F13X` 后传给 `c.exe --chip=...`。 ## 快速开始 @@ -138,7 +120,7 @@ FMD: Regenerate VS Code Config ```json { "fmdCompiler.compilerPath": "C:\\Program Files (x86)\\CCompiler\\Compiler\\data\\bin\\c.exe", - "fmdCompiler.chip": "FT61FC6X", + "fmdCompiler.chip": "从 .prj 的 Device 自动识别,或手动指定", "fmdCompiler.projectFile": "C:\\path\\to\\project.prj", "fmdCompiler.outputDir": "build", "fmdCompiler.autoSaveBeforeBuild": true, @@ -159,7 +141,7 @@ FMD: Regenerate VS Code Config "C:/Program Files (x86)/CCompiler/Compiler/data/include" ], "defines": [ - "_FT61FC6X", + "_当前工程芯片型号", "__GCC8PRO__", "_CHIP_SELECT_H_" ], @@ -199,6 +181,8 @@ VS Code C/C++ IntelliSense 可能无法识别这些寄存器。插件会自动 工程目录\build\ ``` +为了兼容官方工具链,插件会先让 `c.exe` 按官方风格输出到工程根目录的小写文件名前缀,例如 `ft61e132a.hex`,编译成功后再复制为配置输出目录中的工程名文件,例如 `build\FT61E132A.hex`。 + 也可以设置为绝对路径,例如: ```json @@ -328,7 +312,21 @@ C/C++: Reset IntelliSense Database FMD: Regenerate VS Code Config ``` -### 4. 下载功能不能直接使用 +### 4. 编译提示芯片不在 gcc8.ini 中 + +如果输出类似: + +```text +chip "FT61E13X" not present in chipinfo file "...gcc8.ini" +``` + +说明插件已经从 `.prj` 的 `Device` 字段识别出了芯片,但当前安装的 CCompiler 芯片数据库不支持该型号。插件会尝试从历史 `.map` 的 `Machine type is ...` 和内置别名规则推导实际编译器芯片名,例如 `FT61E13X -> FT61F13X`。如果仍失败,请确认: + +- 是否安装了支持该芯片的新版本 CCompiler +- `.prj` 中的 `Device = ...` 是否写成了官方工具支持的芯片名 +- `fmdCompiler.compilerPath` 是否指向正确的 CCompiler 安装目录 + +### 5. 下载功能不能直接使用 需要先配置实际使用的外部烧录工具路径和参数: diff --git a/package-lock.json b/package-lock.json index b1f1f43..2c5a9bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "fmd-c-compiler", - "version": "0.2.10", + "version": "0.2.13", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "fmd-c-compiler", - "version": "0.2.10", + "version": "0.2.13", "devDependencies": { "@types/node": "^20.0.0", "@types/vscode": "^1.85.0", diff --git a/package.json b/package.json index fceaecb..9507b45 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "publisher": "kevinngmanfong", "displayName": "FMD C Compiler", "description": "FMD/FT61FC6X 系列 MCU 编译器支持(C.exe 工具链)", - "version": "0.2.10", + "version": "0.2.13", "license": "MIT", "icon": "resources/icon.png", "engines": { diff --git a/src/compiler.ts b/src/compiler.ts index e40a714..aae9e29 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -70,9 +70,13 @@ export class FmdCompiler { } const projectName = projectInfo?.projectName || path.basename(projectDir); + const projectChip = projectInfo?.device || cfg.chip; + const compilerChip = this.resolveCompilerChip(projectDir, projectName, projectChip, cfg.compilerPath); const outputDir = this.resolveOutputDir(projectDir, cfg.outputDir); fs.mkdirSync(outputDir, { recursive: true }); const artifacts = this.getOutputArtifacts(projectDir, projectName, outputDir); + const compilerOutputBaseName = projectName.toLowerCase(); + const compilerArtifacts = this.getOutputArtifacts(projectDir, compilerOutputBaseName, projectDir); this.building = true; this.diagnostics.clear(); @@ -84,14 +88,29 @@ export class FmdCompiler { this.outputChannel.appendLine(''); this.outputChannel.appendLine(`========== FMD 开始编译: ${projectName} ==========`); this.outputChannel.appendLine(`工程目录: ${projectDir}`); - this.outputChannel.appendLine(`芯片: ${cfg.chip}`); + this.outputChannel.appendLine(`工程芯片: ${projectChip}${projectInfo?.device ? '(来自 .prj Device)' : '(来自 VS Code 配置)'}`); + if (compilerChip !== projectChip) { + this.outputChannel.appendLine(`编译器芯片: ${compilerChip}(由工程芯片映射/历史 map 推导)`); + } if (projectInfo?.device && projectInfo.device !== cfg.chip) { - this.outputChannel.appendLine(`[警告] 配置芯片 ${cfg.chip} 与工程文件 Device ${projectInfo.device} 不一致,本次编译使用配置芯片 ${cfg.chip}`); + this.outputChannel.appendLine(`[提示] VS Code 配置芯片 ${cfg.chip} 与工程文件 Device ${projectInfo.device} 不一致,本次编译使用工程文件 Device ${projectInfo.device}`); } this.outputChannel.appendLine(new Date().toLocaleString()); this.outputChannel.appendLine(''); try { + const support = this.checkCompilerChipSupport(cfg.compilerPath, compilerChip); + if (!support.supported) { + const suggestions = this.findChipSuggestions(support.chips, projectChip); + this.outputChannel.appendLine(`[错误] 当前编译器芯片库不支持: ${compilerChip}(工程芯片: ${projectChip})`); + this.outputChannel.appendLine(`芯片库: ${support.chipInfoFile || '未找到 gcc8.ini'}`); + if (suggestions.length > 0) { + this.outputChannel.appendLine(`相近芯片: ${suggestions.join(', ')}`); + } + vscode.window.showErrorMessage(`FMD: 当前编译器不支持芯片 ${compilerChip}(工程 Device: ${projectChip}),请确认 CCompiler 版本或工程 Device 设置`); + return { success: false, exitCode: -1, artifacts }; + } + const cFiles = this.getSourceFiles(projectDir, projectInfo); if (cFiles.length === 0) { @@ -105,7 +124,7 @@ export class FmdCompiler { // 构建编译命令 // c.exe 是 XC8-like 驱动,可以接受多文件一次编译+链接 - const args = this.buildCompileArgs(cFiles, projectDir, outputDir, projectName, cfg); + const args = this.buildCompileArgs(cFiles, projectDir, compilerArtifacts.outputDir, compilerArtifacts.projectName, compilerChip, cfg); this.outputChannel.appendLine('编译命令:'); this.outputChannel.appendLine(` ${cfg.compilerPath} ${args.join(' ')}`); @@ -117,6 +136,7 @@ export class FmdCompiler { this.outputChannel.appendLine(''); this.outputChannel.appendLine('========== 编译成功 =========='); + this.copyCompilerArtifacts(compilerArtifacts, artifacts); this.logArtifact(artifacts.hexFile); this.logArtifact(artifacts.binFile); @@ -251,6 +271,95 @@ export class FmdCompiler { return path.isAbsolute(outputDir) ? outputDir : path.join(projectDir, outputDir); } + private copyCompilerArtifacts(from: FmdOutputArtifacts, to: FmdOutputArtifacts): void { + fs.mkdirSync(to.outputDir, { recursive: true }); + const pairs = [ + [from.hexFile, to.hexFile], + [from.binFile, to.binFile], + ]; + + for (const [source, target] of pairs) { + if (source === target || !fs.existsSync(source)) { + continue; + } + fs.copyFileSync(source, target); + this.outputChannel.appendLine(`复制输出: ${source} -> ${target}`); + } + } + + private resolveCompilerChip(projectDir: string, projectName: string, projectChip: string, compilerPath: string): string { + const historicalChip = this.readMachineTypeFromMap(projectDir, projectName); + if (historicalChip && this.checkCompilerChipSupport(compilerPath, historicalChip).supported) { + return historicalChip; + } + + if (this.checkCompilerChipSupport(compilerPath, projectChip).supported) { + return projectChip; + } + + const mappedChip = this.mapProjectChipToCompilerChip(projectChip); + if (mappedChip && this.checkCompilerChipSupport(compilerPath, mappedChip).supported) { + return mappedChip; + } + + return mappedChip || projectChip; + } + + private readMachineTypeFromMap(projectDir: string, projectName: string): string | undefined { + const candidates = [ + path.join(projectDir, projectName + '.map'), + path.join(projectDir, projectName.toLowerCase() + '.map'), + ]; + + for (const file of candidates) { + if (!fs.existsSync(file)) { + continue; + } + const text = fs.readFileSync(file, 'utf8'); + const m = /Machine\s+type\s+is\s+([A-Za-z0-9_]+)/i.exec(text); + if (m) { + return m[1].trim(); + } + } + + return undefined; + } + + private mapProjectChipToCompilerChip(projectChip: string): string | undefined { + // 官方 IDE 的 Device 名称可能是市场/工程型号,c.exe 使用的是芯片库型号。 + // 例如 .prj: FT61E13X,链接器实际 Machine type: FT61F13X。 + const known: Record = { + FT61E13X: 'FT61F13X', + }; + const upper = projectChip.toUpperCase(); + return known[upper] || upper.replace(/^FT61E/, 'FT61F'); + } + + private checkCompilerChipSupport(compilerPath: string, chip: string): { supported: boolean; chips: string[]; chipInfoFile?: string } { + const compilerBinDir = path.dirname(compilerPath); + const compilerDataDir = path.dirname(compilerBinDir); + const chipInfoFile = path.join(compilerDataDir, 'dat', 'gcc8.ini'); + if (!fs.existsSync(chipInfoFile)) { + return { supported: true, chips: [], chipInfoFile }; + } + + const text = fs.readFileSync(chipInfoFile, 'utf8'); + const chips = Array.from(text.matchAll(/^\[([^\]]+)\]/gm)).map(m => m[1].trim()); + return { + supported: chips.some(c => c.toUpperCase() === chip.toUpperCase()), + chips, + chipInfoFile, + }; + } + + private findChipSuggestions(chips: string[], chip: string): string[] { + const normalized = chip.toUpperCase(); + const prefix = normalized.slice(0, Math.max(4, normalized.length - 2)); + return chips + .filter(c => c.toUpperCase().startsWith(prefix) || normalized.startsWith(c.toUpperCase().slice(0, Math.max(4, c.length - 2)))) + .slice(0, 10); + } + /** * 构造编译参数 * 基于对 .map 文件的分析,c.exe 是 XC8-style 驱动器 @@ -261,6 +370,7 @@ export class FmdCompiler { projectDir: string, outputDir: string, projectName: string, + chip: string, cfg: ReturnType ): string[] { const compilerBinDir = path.dirname(cfg.compilerPath); @@ -268,7 +378,7 @@ export class FmdCompiler { const includeDir = path.join(compilerDataDir, 'include'); const args: string[] = [ - `--chip=${cfg.chip}`, + `--chip=${chip}`, `-I${includeDir}`, `-o${path.join(outputDir, projectName + '.hex')}`, ]; diff --git a/src/extension.ts b/src/extension.ts index 1f4cc83..faaae6b 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -287,7 +287,8 @@ function ensureCppProperties(): void { const propertiesFile = path.join(vscodeDir, 'c_cpp_properties.json'); const projectFile = projectManager.getProjectFile() || findSinglePrjFile(folder.uri.fsPath) || cfg.projectFile; const projectDir = projectFile ? path.dirname(projectFile) : folder.uri.fsPath; - const chip = (projectFile ? projectManager.getCurrentChip(projectFile) : undefined) || cfg.chip; + const projectChip = (projectFile ? projectManager.getCurrentChip(projectFile) : undefined) || cfg.chip; + const chip = resolveIntellisenseChip(projectDir, path.basename(projectDir), projectChip, compilerInclude); const intellisenseHeader = ensureFmdIntellisenseHeader(vscodeDir, compilerInclude, chip); const includePath = [ '${workspaceFolder}/**', @@ -357,6 +358,43 @@ function ensureCppProperties(): void { } } +function resolveIntellisenseChip(projectDir: string, projectName: string, projectChip: string, compilerInclude: string): string { + const historicalChip = readMachineTypeFromMap(projectDir, projectName); + if (historicalChip && findChipHeader(compilerInclude, historicalChip)) { + return historicalChip; + } + if (findChipHeader(compilerInclude, projectChip)) { + return projectChip; + } + const mappedChip = mapProjectChipToCompilerChip(projectChip); + return findChipHeader(compilerInclude, mappedChip) ? mappedChip : projectChip; +} + +function readMachineTypeFromMap(projectDir: string, projectName: string): string | undefined { + const candidates = [ + path.join(projectDir, projectName + '.map'), + path.join(projectDir, projectName.toLowerCase() + '.map'), + ]; + for (const file of candidates) { + if (!fs.existsSync(file)) { + continue; + } + const m = /Machine\s+type\s+is\s+([A-Za-z0-9_]+)/i.exec(fs.readFileSync(file, 'utf8')); + if (m) { + return m[1].trim(); + } + } + return undefined; +} + +function mapProjectChipToCompilerChip(projectChip: string): string { + const known: Record = { + FT61E13X: 'FT61F13X', + }; + const upper = projectChip.toUpperCase(); + return known[upper] || upper.replace(/^FT61E/, 'FT61F'); +} + function ensureFmdIntellisenseHeader(vscodeDir: string, compilerInclude: string, chip: string): string { fs.mkdirSync(vscodeDir, { recursive: true }); const target = path.join(vscodeDir, 'fmd_intellisense.h');