今天就跟大家聊聊有关怎么使用Visual Studio Code + CMake + SDCC 进行C51开发尝试,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。
起因当然是Keil 太烂了,在尝试重新回到C51 的时候听说了可用于C51 的开源编译器SDCC,于是就开始尝试Keil 以外的开发方案。先提一下结果:失败了。部分失败了,虽然用SDCC 成功生成了可用的程序,但是致命的缺陷在于SDCC 没有Debug 体验可言。除此之外还有一些小问题,以下详细叙述,留作备份。
【应有图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。
【应有图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 好了。
【应有图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 文件夹之后会用于存放生成的文件。
首先安装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 $<TARGET_FILE:blink> " > 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 的代码。
C51 项目的CMake 配置是类似的,可以使用类似项目模板的插件,简化新建项目的工作。
#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 <mcs51/8051.h>
#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
#include "8051_inc_error_hide.h" //-->#include <mcs51/8051.h>
#include <stdint.h>
#define LED P0_0
void delay(uint16_t t) {
while(i--);
}
void main(void) {
while(1) {
LED = 0;
delay(20000);
LED = 1;
delay(20000);
}
}
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 $<TARGET_FILE:blink> " > blink.hex"
WORKING_DIRECTORY ./)
看完上述内容,你们对怎么使用Visual Studio Code + CMake + SDCC 进行C51开发尝试有进一步的了解吗?如果还想了解更多知识或者相关内容,请关注亿速云行业资讯频道,感谢大家的支持。
亿速云「云服务器」,即开即用、新一代英特尔至强铂金CPU、三副本存储NVMe SSD云盘,价格低至29元/月。点击查看>>
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。
原文链接:https://my.oschina.net/etberzin/blog/4561391