千家信息网

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

发表于:2025-01-24 作者:千家信息网编辑
千家信息网最后更新 2025年01月24日,今天就跟大家聊聊有关怎么使用Visual Studio Code + CMake + SDCC 进行C51开发尝试,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇
千家信息网最后更新 2025年01月24日怎么使用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开发尝试有进一步的了解吗?如果还想了解更多知识或者相关内容,请关注行业资讯频道,感谢大家的支持。

0