前言
当前,在单片机开发领域,Keil是广泛被采用的开发工具。然而,其编辑器功能相较于现代的VScode显得相当陈旧和不足。过去,我也曾试验过使用platformIO,其核心理念是对SDCC和stcgal进行封装,从而提供一个更加集成的开发环境。
现在,我们将基于SDCC和stcgal来搭建一个全新的单片机开发环境。我们将现有代码从Keil迁移到SDCC,确保原有的Keil代码能够在新环境中顺畅运行。
本文使用的示例项目地址:uart_temp
正文
一、安装SDCC和stcgal
下载链接
- SDCC - Small Device C Compiler (sourceforge.net)
- grigorig/stcgal: Open Source STC MCU ISP flash tool (github.com)
SDCC的特点:
-
开源免费、支持常见MCU
-
一次只能编译一个文件,多个文件需要编写Makefile
-
对c99标准支持的比Keil C51更好,更像一个标准的C语言编译器
-
SDCC代码生成的体积会比较大,原因如下:
在GCC编译器中,
-ffunction-sections
和-fdata-sections
参数是用来将每个函数和数据段分别放在单独的section中。这样做的好处是在链接阶段,未被引用的函数和数据可以被识别并删除,从而减小最终的执行文件大小。而-Wl,--gc-sections
参数则是告诉链接器去删除未使用的函数和数据段。然而,SDCC(Small Device C Compiler)并不支持这些GCC的特性。这可能导致SDCC编译的单片机程序的最终执行文件比使用GCC编译的更大,因为即使某些代码没有被调用,它也会被包含在执行文件中。为了解决这个问题,有些SDCC库的开发者选择在每个源文件中只放置一个函数,尽管这样做会让代码看起来更加分散和不易读。
stcgal的特点:
- 开源免费,支持 STC MCU Ltd. 8051 系列单片机
- 支持命令行操作,stc-isp可以用于烧录单片机,但是软件是闭源的。不支持命令行操作
二、从Keil迁移到SDCC
Keil C51 | SDCC | ||
---|---|---|---|
头文件 | reg51.h/reg52.h | 8051.h/8052.h | |
端口 | P2^0 | P2_0 | |
端口定义 | sbit LED1=P2^0; | #define LED1 P2_0 | 替换的时候注意删除后面的分号; |
中断声明 | void uart() interrupt 4 using 0 | void uart(void) __interrupt (4) __using (0) | |
特殊类型 | sbit,sfr,bit,code | __sbit,__sfr,__bit,__code | Keil中的很多特殊类型前面加上__就是sdcc中对应的类型。 |
_nop()_ | Keil C51专有 | #define _nop_() __asm NOP __endasm | |
intrins.h | Keil C51专有, SDCC需自己创建 | 自行创建 |
intrins.h文件内容图下,请自行创建
#ifndef _INTRINS_H_
#define _INTRINS_H_
/* warning: __push __pop 使用堆栈临时保存 sfr 数据,必需成对使用!
__push(x);
... // 保护代码块
__pop(x); //缺少无该语句编译不会出错,但运行错误!
*/
#define __push(x) __asm push _##x __endasm /* void _push_ (unsigned char _sfr); */
#define __pop(x) __asm pop _##x __endasm /* void _pop_ (unsigned char _sfr); */
#define _push_ __push /*兼容 keil c51*/
#define _pop_ __pop /*兼容 keil c51*/
/* 安全使用保护宏:
pushSfr(x);
... // 受保护代码块
popSfr(x); // 缺少无该语句编译出错,确保生成正确代码。
*/
#define pushSfr(x) do{\
__push(x)
#define popSfr(x) __pop(x);\
}while(0)
#endif //_INTRINS_H_
常见编译问题:
- 待补充
三、编写Makefile
示例项目地址:uart_temp
项目结构如下:
D:\PROJECT\UART_TEMP
├─include
├─obj
└─src
对应的Makefile如下:
# set CC TOOL
CC := sdcc
PACKIHX := packihx
CFLAGS := -DUSE_FLOATS=1
# set DIR
INCDIR = include
SRCDIR = src
OBJDIR = obj
TARGET = obj/main.ihx
INC := ./include
SRC := $(wildcard $(SRCDIR)/*.c)
# OBJ := $(SRC:%.c=%.rel)
OBJ := $(patsubst src/%.c, obj/%.rel, $(SRC))
.PHONY: all clean
all: uart_temp.hex
clean:
-del /q obj\*
$(OBJ):obj/%.rel:src/%.c
-$(CC) $(CFLAGS) -I $(INC) -c $^ -o $@
$(TARGET): $(OBJ)
-$(CC) $^ -o $@
uart_temp.hex: $(TARGET)
-$(PACKIHX) $(TARGET) > uart_temp.hex
flash:
-stcgal -P stc89 -p COM3 .\uart_temp.hex
#led.bin:led.hex
# objcopy -I ihex -O binary led.hex led.bin
#led.hex:main.ihx
# packihx main.ihx > led.hex
# led.bin:main.ihx
# makebin main.ihx led.bin
需要注意的是:
- sdcc编译生成
.rel
不生成.o
- sdcc链接得到
.ihx
,需要使用packihx转换成.hex
- sdcc一次只能编译一个文件
四、编译、烧录
编译命令,编译得到.\uart_temp.hex
make clean all
烧录前,确认单片机连接的串口号,并将Makefile中的COM3为单片机使用的串口号
烧录命令
make flash