怎么使用VisualStudioCode+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_0

void 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开发尝试有进一步的了解吗?如果还想了解更多知识或者相关内容,请关注创新互联行业资讯频道,感谢大家的支持。


文章标题:怎么使用VisualStudioCode+CMake+SDCC进行C51开发尝试
网页URL:http://myzitong.com/article/gccosd.html