传统上,51单片机一般用keil c集成环境来进行编译开发,然后直接烧录到芯片上去运行,这有几个缺点:

  • keil C是商业软件,很多人使用并不付钱,而是要么使用盗版(破解版),要么使用它的演化版(evolution version),前者存在法律风险,也有道德瑕疵(比如上课的老师带头用盗版,本身就不正,如何教人?),后者有功能上的限制;
  • 就算keil C买了正版,也有不尽如人意的地方,比如keil C对C99和C11的支持不好;
  • 直接烧录到芯片比较繁琐,对反复的测试不直观和方便。
    对上面的问题,一个比较好的方法是采用开源和免费的工具来实现编译和仿真。
    首先,我们可以采用开源免费的sdcc来进行编译,关于sdcc的介绍,可以看下面:

    SDCC is a retargettable, optimizing Standard C (ANSI C89, ISO C99, ISO C11) compiler suite that targets the Intel MCS51 based microprocessors (8031, 8032, 8051, 8052, etc.), Maxim (formerly Dallas) DS80C390 variants, Freescale (formerly Motorola) HC08 based (hc08, s08), Zilog Z80 based MCUs (z80, z180, gbz80, Rabbit 2000/3000, Rabbit 3000A, TLCS-90), Padauk (pdk14, pdk15) and STMicroelectronics STM8. Work is in progress on supporting the Padauk pdk13 target; Microchip PIC16 and PIC18 targets are unmaintained. SDCC can be retargeted for other microprocessors.
    SDCC was originally written by Sandeep Dutta and released under a GPL license.

    可见SDCC解决了我们两个大问题:开源免费,以及支持C99和C11(最新版还支持C2x)。

SDCC安装

关于SDCC的安装,在Ubuntu Linux(或者windows wsl) 下,可以用:

sudo apt install sdcc
来安装,如果是windows系统,可以从下面下载最新的版本:

https://sourceforge.net/projects/sdcc/files/

目前最新的版本是4.1。

下载安装完后,可以采用下面的命令来对编好的C程序文件进行编译:

SDCC程序编译

sdcc 源文件.c 编译选项 [-o 输出文件名]
这里编译选项比较多,可以下载手册进行参考:

http://sdcc.sourceforge.net/doc/sdccman.pdf

简单的使用,我们可以用下面的命令来编译满足C99标准(有少量的不满足)的8051程序:

sdcc 源文件.c --std-c99 -o 输出文件
除此之外,还可以设置:--std-sdcc99 来支持sdcc的扩展功能,或者--std-c11和--std-sdcc11以支持C11的标准(以及sdcc的扩展),和--std-c2x和--std-sdcc2x以支持C2x的草案。

sdcc对C语言的标准的支持是有少量的不完整的,参考下面的网页可以了解哪些没支持:
https://sourceforge.net/p/sdcc/wiki/Standard%20Compliance/

简单归纳一下:

ISO C90 / ANSI C89标准:

  • 不支持double、long double类型;
  • 不支持将 结构体(struct)和 联合体(union)作为函数的参数和返回值;
  • 不支持 K&R 风格的函数定义;
  • 对于51系列单片机,默认情况下函数是不可重入的(function are not reentrant),意味着默认情况下函数内的变量是static,并被放置在统一的数据空间(data-space),这主要是因为51系列的内存太小的缘故,如果需要变量存放在函数的栈(stack)内,需要单独进行函数的修饰说明,如下:
    unsigned char foo(char i) __reentrant
    {
    ...
    }

    或者在编译的时候加上--stack-auto 选项,或者如下加上预编译指令:

#pragma stackauto
ISO C99标准:
(上面C89的那些不支持的内容);
不支持复合字面量(Compound literals),即能够就地构造一个指定类型的无名对象,在只需要一次数组、结构体或联合体变量时使用,如下简单示例如何使用复合字面量:

int *p = (int[]){2, 4}; // 创建一个无名的 int[2] 类型静态存储数组
                        // 初始数组为值 {2, 4}
                        // 创建指向数组首元素的指针 p
const float *pc = (const float []){1e0, 1e1, 1e2}; // 只读复合字面量

int main(void)
{
    int n = 2, *p = &n;
    p = (int [2]){*p}; // 创建一个无名的 int[2] 类型自动存储数组
                       // 初始化首个元素为之前 *p 所持有的值
                       // 初始化第二个元素为零
                       // 将首元素的地址存储到 p

    struct point {double x,y;};
    void drawline1(struct point from, struct point to);
    void drawline2(struct point *from, struct point *to);
    drawline1((struct point){.x=1, .y=1},  // 创建二个块作用域的结构体
              (struct point){.x=3, .y=4}); // 然后调用 drawline1 ,以值传递
    drawline2(&(struct point){.x=1, .y=1},  // 创建二个块作用域的结构体
              &(struct point){.x=3, .y=4}); // 然后调用 drawline2 ,传递其地址
}

以上例程来源于:

复合字面量 - https://zh.cppreference.com/w/c/language/compound_literal
这真的很可惜,这是C99的很大一个优势,不过8051的sdcc本来就不支持结构体作为函数的参数,所以影响还不大;

不支持非常量长度数组(Variable-length arrays,VLA),即数组定义时其长度可以是一个变量,下面是简单示例VLA的用法:

{
   int n = 1;
label:;
   int a[n]; // 重分配 10 次,每次拥有不同大小
   printf("The array has %zu elements\n", sizeof a / sizeof *a);
   if (n++ < 10) goto label; // 离开作用域的 VLA 结束其生存期
}

例程来源:

https://zh.cppreference.com/w/c/language/array

数组声明 - cppreference.com
https://zh.cppreference.com/w/c/language/array
不支持非ASCII字符的宏名(Non-ASCII characters in macro names)

烧录到stc 8051芯片上的程序,可以用stcflash,这是python脚本,需要安装python环境。

https://github.com/laborer/stcflash
可以在上面地址下载。

程序仿真

对于8051的仿真,可以采用多种仿真器,其中,最直观简单的是edSim51,这是用java编写的,可以适用在不同的操作系统上,其自带了LED、按键、数码管、ADC、DAC、步进电机等模拟,非常适合教学和学习练习,可以在:

The 8051 Simulator for Teachers and Students
www.edsim51.com

下载最新的版本,注意需要安装java运行时来运行。其界面如下:
file

用上面的load按钮即可读入编译好的程序,run即可运行,注意在update freq.左边的刷新频率里选择至少100以上,否则运行速度太慢。

另外,sdcc编译好的程序是生成ihx格式的文件,不能直接读入(或者烧录),需要用下面的指令(sdcc自带)转换为hex格式:

packihx exam.ihx > exam.hex

然后读入exam.hex文件即可进行仿真。

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注