千家信息网

怎么使用Visual Studio Code + CMake + SDCC 进行C51开发尝试

发表于:2026-11-01 作者:千家信息网编辑
千家信息网最后更新 2026年11月01日,今天就跟大家聊聊有关怎么使用Visual Studio Code + CMake + SDCC 进行C51开发尝试,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇
千家信息网最后更新 2026年11月01日怎么使用Visual Studio Code + CMake + SDCC 进行C51开发尝试

今天就跟大家聊聊有关怎么使用Visual Studio Code + CMake + SDCC 进行C51开发尝试,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。

起因当然是Keil 太烂了,在尝试重新回到C51 的时候听说了可用于C51 的开源编译器SDCC,于是就开始尝试Keil 以外的开发方案。先提一下结果:失败了。部分失败了,虽然用SDCC 成功生成了可用的程序,但是致命的缺陷在于SDCC 没有Debug 体验可言。除此之外还有一些小问题,以下详细叙述,留作备份。

现有基于SDCC 的几个方案

1. Platform IO + Visual Studio Code

【应有图1】

这是我最先搜到并开始尝试的方案。Platform IO 相当于一个通用的嵌入式开发助手,主要功能是通用的"新建项目向导", 工具链管理和统一的调试工具等。

开始的体验非常 sweet,Platform IO 无缝集成于VS code 中,对STC C51 单片机有了实验性的支持,使用SDCC 编译器,用STCGAL 作为编程工具,统一的Debug 功能说实话有点儿不可思议,我还没尝试。创建项目的过程非常顺滑,由Visual Studio Code 支持的编辑体验当然也很不错,不过代码的智能补全和分析功能是由C/C++ 插件提供的,基于Clang,对C51 扩展的C语言语法没有支持,所以头文件里的寄存器定义全部报错,当然也没有相应的智能补全了,这个问题的解决方案见后文。

【应有图2】

但是就和所有企图大包大揽的东西一样,随后我就发现了Platform IO 细节上的不讲究。Platform IO 提供一个配置文件给用户,用于调整项目的一些设置,比如说引用外部的库文件、设置路径、添加编译选项之类的,问题是Platform IO 使用了类似Arduino 的库组织方式,它默认一个库就是个具有一定文件结构的包,其中包括了头文件和源代码,因此似乎没有必要再单独提供一个选项让你配置头文件包含目录,而我当时遇到的问题是智能提示找不到SDCC 的头文件,最简单的解决方案是在C/C++ 插件的项目配置里加一条路径就行了,但是这个配置文件是受Platform IO 管理的,每次Platform IO 更新状态的时候手动添加的内容就会被清理掉,所以必须去Platform IO的配置里加路径,然而它并没有提供单独配置头文件包含目录的功能……搜索一番后,唯一能做的似乎只有在添加编译选项的时候把目录加进去,编译器当然能找到它自己的头文件,但是我的智能提示依然不能work。

除此之外,Platform IO 的其他问题和这个类似,都是大包大揽的工具偶尔照顾不到的细节问题,比方说工具链的版本滞后,又没有什么简洁的方法直接升级;或者是在用STC 以外的C51 单片机的时候,如果要让Platform IO 兼容它的工作流,显然还得付出不少时间看它的文档,调配置。

然而既然都要花时间费劲看文档、调配置,为什么我不去用一个功能更强、更有用、更受欢迎的工具呢?说的就是CMake。

2. QtCreator + CMake + SDCC

【应有图3】

CMake 是时下流行的开源跨平台构建工具,主要用来管理C/C++ 项目的构建任务,这里有一个不错的CMake 基础教程,来自Clion,言简意赅。显然C51 项目也是C 项目,遵循传统的编译 - 链接流程,无非是目标平台是在单片机上裸机运行,需要交叉编译而已。QtCreator 则是目前来说开发Qt 程序体验最完整的IDE,毕竟是官方出品,用于开发非Qt 的普通C/C++ 项目时,相比VS, VS code 之类的其他IDE 环境的体验也是各有千秋,Qt 还是开发桌面GUI 程序或者说上位机程序的最优框架之一,如果能用QtCreator 一站式解决开发单片机和上位机程序的需求的话那就比较香了。

QtCreator 近期也集成了CMake 支持,CMake 已经是成熟的构建工具了,按说不应该出什么妖蛾子才对。结果折腾了挺长时间CMake 配置不成功,因为QtCreator 对CMake 有一些额外的、为了便于使用的配置,结果成也GUI 败也GUI,总之我也不确定究竟是什么地方配置错了,我只是希望不要再让我配置这些东西了。

于是最终,决定回归"原生"体验,去掉那些杂七杂八的"便利"、"统一化"功能,干脆的直接手写CMake 好了。

使用Visual Studio Code + CMake

【应有图4】

要支持C/C++ 开发需要先安装C/C++ 插件,巨硬官方出品,随后安装同样是巨硬出品的CMake tools 插件,或许需要重启一次VS code 让插件准备完毕。在此之前安装CMake 依赖的Ninjia 构建工具,添加到PATH。

接下来新建一个文件夹作为项目文件夹,用VS code 打开,然后点F1 打开命令面板,搜索CMake 相关的命令,运行CMake:配置 命令,用于准备好CMake 环境,如果是个完全空的新建文件夹的话运行配置命令后会又提示找不到CMakeLists.txt,点一下让它新建就好了,然后目标类型选择executable,这样它会自动准备一个CMakeLists.txt 文件在文件夹下面。弹出面板选择工具的时候选择"未指定",因为它给的列表里应该没有SDCC。

然后一个CMake 项目文件就算准备好了,其中的build 文件夹之后会用于存放生成的文件。

使用CMake + SDCC 编译

首先安装SDCC,添加到PATH。

需要编辑CMakeLists.txt 文件,允许使用SDCC 编译项目,启用智能提示。CMake 默认使用本地环境作为目标环境,要使用SDCC 交叉编译,需要添加以下两行:

set(CMAKE_SYSTEM_NAME Generic)set(CMAKE_C_COMPILER sdcc)

第一行指定交叉编译的目标平台,对于单片机裸机环境就是Generic,第二行指定SDCC 为C 编译器。至少CMake 3.18 安装后带有基本的SDCC 支持文件,所以用这两行开启SDCC 支持后基本上不需要再添加别的东西,可以算是真实省心。然后像一般的项目一样添加源文件,生成就好了。点击下部状态栏齿轮"生成"按钮或者执行CMake: 生成命令。

【应有图5】

SDCC 默认生成的文件是.ihx 格式,位于build 子文件夹内,要给STC 单片机编程的话可能需要转化成.hex 或.bin 格式的文件,SDCC 提供了packihx.exe 和makebin.exe 两个命令行工具用来做这件事,格式如下:

# 生成hex:packihx.exe blink.ihx > blink.hex# 或生成bin:makebin.exe blink.ihx > blink.bin

需要注意的是,这个地方对Windows 用户有个坑,如果用powershell 命令行执行命令的话,输出文件的编码存在问题,无法正常编程,至少是无法用STC-ISP 编程。这个问题我没有细究,可能是powershell 输出的文件编码使用了UTF-16 双字节编码,或者说是Unicode,而正常的hex 文件使用ASCII 编码,因此我看到的现象是powershell 输出的文件是正常文件的两倍大小,用文本编辑器,比如Notepad3 或者Notepad++ 打开错误的hex 文件,转换编码到UTF-8 就可以解决问题。

可以编辑CMakeLists.txt 实现让CMake 在编译后自动执行ihx 到hex 文件的转换工作,相关代码如下:

add_custom_command(TARGET blink                   POST_BUILD                   COMMAND packihx.exe ARGS $ " > blink.hex"                   WORKING_DIRECTORY ./)

这样自动生成的文件没有编码错误。

然后就可以使用STC-ISP 或者STCGAL 上传程序文件。这一步也可以通过编辑VS code的launch.json 实现集成。

文后会附上blink 实例程序和CMakeLists.txt 文件

解决智能提示兼容性问题

SDCC C51 扩展了不少非标准C 语言关键字,基于clang 的智能提示无法理解这些东西,于是使用这些关键字的时候都会报错,无法智能提示头文件中定义的寄存器。

一种解决思路是利用条件编译,区别智能提示运行环境和SDCC 实际编译环境,用空的define 取代这些关键字,寄存器也都用宏代替,然后在SDCC 实际编译时使用原来C51 语法的寄存器定义,举例来说:

#ifdef __SDCC        __sfr __at (0x80) P0;    //实际有效的寄存器定义#else        #define __sfr    //空的关键字宏,消除关键字不兼容        #define __at        #define P0 (*(char*)(0x80))    //用于兼容标准C 语法的寄存器符号,没有实际功能#endif//...        P0 = 0xf0;        P0 |= 0x02;//...

以上条件编译把代码区分到智能提示和实际编译两个环境,在实际编译时,SDCC 编译器会预定义__SDCC 符号,因此实际编译时使用实际有效的寄存器定义,而在智能提示环境,用空的宏取代所有关键字,消除关键字的不兼容,然后用一个宏定义寄存器,保证寄存器名智能提示依然可以使用。这里将P0 定义为一个对char* 指针解引用的左值表达式, 因为这样一来在语法上对P0 的赋值才是合法的,括号里的数值可以是任意不太大的整数,当然用P0 本来的值更合适。

实际使用时,创建一个特殊的头文件,其中的条件编译在实际编译时正常包含C51 头文件,将C51 头文件中的符号都转写成兼容的形式,供智能提示使用,见附1 的代码。

其他增强

  1. C51 项目的CMake 配置是类似的,可以使用类似项目模板的插件,简化新建项目的工作。

附1:8051_inc_error_hide.h

#ifndef 8051_INC_ERROR_HIDE_H#define 8051_INC_ERROR_HIDE_H/*        To solve the problem that vs / clang dose not        recognize those SDCC defined keywords.*/#ifdef __SDCC        #include #else        //keywords        #define __interrupt        #define __using        #define __code        #define __data        #define __near        #define __xdata        #define __far        #define __idata        #define __pdata        #define __code        #define __bit        #define __sfr        #define __sfr16        #define __sfr32        #define __sbit        #define __at        #define __critical        //#define __asm        //#define __endasm        //header - 8051.h registers        #define P0 (*(char*)((0x80)))        #define SP (*(char*)((0x81)))        #define DPL (*(char*)((0x82)))        #define DPH (*(char*)((0x83)))        #define PCON (*(char*)((0x87)))        #define TCON (*(char*)((0x88)))        #define TMOD (*(char*)((0x89)))        #define TL0 (*(char*)((0x8A)))        #define TL1 (*(char*)((0x8B)))        #define TH0 (*(char*)((0x8C)))        #define TH1 (*(char*)((0x8D)))        #define P1 (*(char*)((0x90)))        #define SCON (*(char*)((0x98)))        #define SBUF (*(char*)((0x99)))        #define P2 (*(char*)((0xA0)))        #define IE (*(char*)((0xA8)))        #define P3 (*(char*)((0xB0)))        #define IP (*(char*)((0xB8)))        #define PSW (*(char*)((0xD0)))        #define ACC (*(char*)((0xE0)))        #define B (*(char*)((0xF0)))        #define P0_0 (*(char*)((0x80)))        #define P0_1 (*(char*)((0x81)))        #define P0_2 (*(char*)((0x82)))        #define P0_3 (*(char*)((0x83)))        #define P0_4 (*(char*)((0x84)))        #define P0_5 (*(char*)((0x85)))        #define P0_6 (*(char*)((0x86)))        #define P0_7 (*(char*)((0x87)))        #define IT0 (*(char*)((0x88)))        #define IE0 (*(char*)((0x89)))        #define IT1 (*(char*)((0x8A)))        #define IE1 (*(char*)((0x8B)))        #define TR0 (*(char*)((0x8C)))        #define TF0 (*(char*)((0x8D)))        #define TR1 (*(char*)((0x8E)))        #define TF1 (*(char*)((0x8F)))        #define P1_0 (*(char*)((0x90)))        #define P1_1 (*(char*)((0x91)))        #define P1_2 (*(char*)((0x92)))        #define P1_3 (*(char*)((0x93)))        #define P1_4 (*(char*)((0x94)))        #define P1_5 (*(char*)((0x95)))        #define P1_6 (*(char*)((0x96)))        #define P1_7 (*(char*)((0x97)))        #define RI (*(char*)((0x98)))        #define TI (*(char*)((0x99)))        #define RB8 (*(char*)((0x9A)))        #define TB8 (*(char*)((0x9B)))        #define REN (*(char*)((0x9C)))        #define SM2 (*(char*)((0x9D)))        #define SM1 (*(char*)((0x9E)))        #define SM0 (*(char*)((0x9F)))        #define P2_0 (*(char*)((0xA0)))        #define P2_1 (*(char*)((0xA1)))        #define P2_2 (*(char*)((0xA2)))        #define P2_3 (*(char*)((0xA3)))        #define P2_4 (*(char*)((0xA4)))        #define P2_5 (*(char*)((0xA5)))        #define P2_6 (*(char*)((0xA6)))        #define P2_7 (*(char*)((0xA7)))        #define EX0 (*(char*)((0xA8)))        #define ET0 (*(char*)((0xA9)))        #define EX1 (*(char*)((0xAA)))        #define ET1 (*(char*)((0xAB)))        #define ES (*(char*)((0xAC)))        #define EA (*(char*)((0xAF)))        #define P3_0 (*(char*)((0xB0)))        #define P3_1 (*(char*)((0xB1)))        #define P3_2 (*(char*)((0xB2)))        #define P3_3 (*(char*)((0xB3)))        #define P3_4 (*(char*)((0xB4)))        #define P3_5 (*(char*)((0xB5)))        #define P3_6 (*(char*)((0xB6)))        #define P3_7 (*(char*)((0xB7)))        #define RXD (*(char*)((0xB0)))        #define TXD (*(char*)((0xB1)))        #define INT0 (*(char*)((0xB2)))        #define INT1 (*(char*)((0xB3)))        #define T0 (*(char*)((0xB4)))        #define T1 (*(char*)((0xB5)))        #define WR (*(char*)((0xB6)))        #define RD (*(char*)((0xB7)))        #define PX0 (*(char*)((0xB8)))        #define PT0 (*(char*)((0xB9)))        #define PX1 (*(char*)((0xBA)))        #define PT1 (*(char*)((0xBB)))        #define PS (*(char*)((0xBC)))        #define P (*(char*)((0xD0)))        #define F1 (*(char*)((0xD1)))        #define OV (*(char*)((0xD2)))        #define RS0 (*(char*)((0xD3)))        #define RS1 (*(char*)((0xD4)))        #define F0 (*(char*)((0xD5)))        #define AC (*(char*)((0xD6)))        #define CY (*(char*)((0xD7)))        /* BIT definitions for bits that are not directly accessible */        /* PCON bits */        #define IDL             0x01        #define PD              0x02        #define GF0             0x04        #define GF1             0x08        #define SMOD            0x80        /* TMOD bits */        #define T0_M0           0x01        #define T0_M1           0x02        #define T0_CT           0x04        #define T0_GATE         0x08        #define T1_M0           0x10        #define T1_M1           0x20        #define T1_CT           0x40        #define T1_GATE         0x80        #define T0_MASK         0x0F        #define T1_MASK         0xF0        /* Interrupt numbers: address = (number * 8) + 3 */        #define IE0_VECTOR      0       /* 0x03 external interrupt 0 */        #define TF0_VECTOR      1       /* 0x0b timer 0 */        #define IE1_VECTOR      2       /* 0x13 external interrupt 1 */        #define TF1_VECTOR      3       /* 0x1b timer 1 */        #define SI0_VECTOR      4       /* 0x23 serial port 0 */#endif#endif

附2:blink.c

#include "8051_inc_error_hide.h"  //-->#include #include #define LED P0_0void delay(uint16_t t) {        while(i--);}void main(void) {        while(1) {                LED = 0;                delay(20000);                LED = 1;                delay(20000);        }}

附3:CMakeLists.txt

cmake_minimum_required(VERSION 3.5)project(blink LANGUAGES C)set(CMAKE_SYSTEM_NAME Generic)set(CMAKE_C_COMPILER sdcc)add_executable(blink blink.c 8051_inc_error_hide.h)add_custom_command(TARGET blink                   POST_BUILD                   COMMAND packihx.exe ARGS $ " > blink.hex"                   WORKING_DIRECTORY ./)

看完上述内容,你们对怎么使用Visual Studio Code + CMake + SDCC 进行C51开发尝试有进一步的了解吗?如果还想了解更多知识或者相关内容,请关注行业资讯频道,感谢大家的支持。

文件 编译 项目 配置 智能 提示 工具 实际 问题 开发 寄存器 环境 命令 支持 生成 关键 关键字 功能 程序 尝试 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 南京欣网互联网络科技 互联网科技产业 互联网属科技 互联网最新的科技 智网互联网科技 宜安科技 互联网金融 百单互联网科技有限公司 深圳齐家互联网科技 电子科技大学互联网 互联网金融监管科技 济南华为互联网科技产业总部基地 须眉科技是互联网公司吗 互联网软件和科技 中国航天科技大学国际工业互联网 千方科技属于互联网企业吗 天津腾达互联网科技有限公司 工业互联网等科技实现智能化赋能 互联网科技实习生岗位 科技互联网行业投资研究 互联网科技大佬云集 互联网科技有限公司图片 互联网金融和金融科技 太原新华互联网科技学校怎么样 来此购互联网科技有限公司 华夏恒生互联网科技013171 互联网公司黑科技创始人 中国互联网公司排名锤子科技 互联网公司高科技产品 有人说阿里系是互联网科技企业 兰石爱特工业互联网科技分公司
0