|
概述 金水32051编译器是笔者专门为8位8051单片机研制的C语言编译器,它支持C351语言和A351汇编语言,是参考大学生C语言计算机等级考试大纲和Keil的 C51规范专门设计的。 C351语言是基于标准C语法、针对8051内核单片机(特别是STC系列)设计的专业嵌入式开发语言,由金水32051编译器实现。C351语言与Keil C51语言同源但目标不同:C351强调“无歧义、高可靠性、多任务友好”,严格限制表达式的复杂性,确保源代码、汇编代码、机器码及CPU执行结果逐层一致。本章节为学习者系统介绍C351语言的主要规范,并明确指出其与C51的关键差异,以便已有C51基础的学习者快速迁移。 学习目标:理解C351的设计哲学(确定性>简洁性),掌握其核心语法限制,能够将C51代码改造为符合C351规范的代码。 一、 C351程序基本结构1. 主程序(后台任务) - C351程序从 `main` 函数开始执行,`main` 函数“没有输入参数,也没有返回值”(与C51相同)。 - `main` 函数内部通常是一个“无限循环”(`while(1)`),永不退出。这种在裸机上持续运行的任务称为“后台任务”。 - 示例: void main(void) { // 初始化代码 while(1) { // 主循环体 } } ``` 与规范的C51差异:无(两者规范一致)。 二、中断处理函数1. 标准中断函数 - 采用无参数、无返回值的函数定义,使用 `interrupt` 关键字指明中断号。 - 中断现场的保存与恢复由编译器自动完成,用户只需编写处理逻辑。 - 中断处理称为“前台任务”。带中断的单片机程序本质上是多任务程序,C351语言显式支持这一模型。 2. 中断号范围 - C351支持255个中断号,金水32051编译器实际支持63个中断号(对应大多数STC单片机的中断源)。 3. 快速中断函数 - C351提供 `fast_interrupt` 关键字,允许用户编写高速中断响应程序,自主决定保存哪些寄存器。 4. 寄存器组限制 - C351规定对于所有用户程序(包括中断函数)金水32051编译器只使用寄存器组0,其他3个寄存器组保留给用户的底层操作系统或RTOS使用。 - 因此,C51中的 `using` 关键字在C351中被忽略(编译器不产生切换寄存器组的代码)。 与C51差异: - C51中断函数可选用 `using` 指定寄存器组;C351不支持。 - C51中中断号最大31(典型值),C351扩展至63/255。 - C51无快速中断概念;C351提供快速中断扩展。 三、 函数重入模型1. 重入的必要性 - 当后台任务(main循环)和中断任务(ISR)调用同一个函数,或者函数需要实现递归算法时,该函数必须“可重入”。 - C351语言“缺省所有函数都是可重入的”(无需 `reentrant` 关键字),这符合多任务环境的要求。 2. 实现机制 - 标准C重入模型使用“帧基指针BP”指向堆栈中的当前函数帧,函数参数和局部变量通过相对BP的偏移访问。 - 由于8051原生堆栈指针SP为8位且不能访问XRAM,金水32051编译器抽象出一个“金水明32051 CPU模型”,引入了两个16位虚拟寄存器: - BP(基址指针) - VP(变量指针) - 原生8051的8位SP仅用于子程序调用返回和中断现场保存;而函数的动态变量全部存放在“16位XRAM堆栈”中。 与C51差异: - C51中函数“缺省不可重入”,需要递归或多任务调用时必须显式添加 `reentrant` 关键字,且会生成较大开销。 - C51小模式使用8051原生SP管理局部变量(限于内部idata),C351使用虚拟16位BP/VP管理xdata堆栈。 四、 变量存储模型1. 变量存储空间 - C351将16位XRAM(外部数据存储器)作为主要的变量存储区,最大支持64KB。 - 动态变量(局部变量、函数参数)存放在变量堆栈中,堆栈从XRAM_TOP顶向下增长(高地址→低地址)。 - 静态变量(全局变量、`static`局部变量)从底向上增长(低地址→高地址)。两者相向生长,最大程度利用空间。 2. 优势 - 允许在函数内部定义大型动态数据缓冲区(如大数组),函数结束时自动回收存储空间,特别适合具有大规模XRAM(如STC单片机)的应用。 3. XRAM最小要求 - C351要求XRAM至少256字节。绝大多数STC单片机满足此条件,包括STC89C52。 与C51差异: - C51变量可分布在 `data`、`idata`、`xdata`、`code` 等不同存储区,程序员需手动指定存储类型。C351统一使用XRAM作为变量空间,特殊情况也可以用 `data`/`code`指定变量存储区域。 - C51小模式局部变量默认在内部idata堆栈(可配置),C351全部在XRAM堆栈。 五、 函数访问接口(参数传递规范)1. 设计背景 - 8位单片机C语言主要面向控制而非数值计算。控制函数通常局部变量少、要求响应快。 - 为兼顾速度和通用性,C351采用混合参数传递规则。 2. 参数传递规则 - 前两个参数:通过寄存器传递(快速访问)。 - 其余参数:通过XRAM函数堆栈传递。 - 该规则与A351汇编语言紧密结合,可实现高效的函数调用。 与C51差异: - C51最多用寄存器传递3个参数(通过R7、R5等),其余通过固定存储区(内部RAM)传递。 - C51没有“前两个参数专用寄存器”的明确规则,而是依赖特定寄存器(R7、R6、R5等)和存储类型。 六、 端口和SFR定义1. 特殊功能寄存器(SFR)的性质 - 输入型SFR的值由外设决定,编译器不能对其进行优化(如删除看似无用的读操作)。 - SFR具有固定地址,类似于多任务中的共享变量或信号量。 2. C351中的SFR定义 - 使用与C51类似的关键字(如 `sfr`、`sbit`),但增加了一个重要特性:允许SFR地址小于0x80。 - 地址小于0x80的SFR可以作为辅助寄存器使用,提高数据处理速度(因为STC单片机的1T内核访问这些地址与通用寄存器速度相同)。 3. C351中的XSFR设备寄存器定义 - 随着STC单片机片上的设备增多,传统的128字节sfr空间已经不够用,只能扩展到XRAM。 - C51使用下面宏语句来定义设备寄存器: #define T11L (*(unsignedchar volatile xdata *)0xfe7b) - C351使用类似sfr语句来定义设备寄存器: xsfr 设备寄存器名 = 16位XRAM地址; xsfr T11L = 0xfe7b; - C351也支持C51对设备寄存器的定义,只是在编译时将宏定义语句转换为xsfr语句。 4. 优化禁止 - C351编译器对SFR/XSFR的访问不会做任何假设(不缓存值、不重排顺序),确保输入读取的正确性。 与C51差异: - C51标准中SFR地址仅限于0x80~0xFF(高128字节),小于0x80的地址被视为普通RAM。C351扩展了SFR定义范围至整个64K XRAM中的特殊地址。 - C351明确要求编译器不对SFR进行优化,而C51在某些优化级别下可能产生副作用。 七、 数据类型1. 标准数据类型 与C51基本一致,但特别引入二进制数据类型。 2. 新增:二进制数据类型 - 关键字为 `binary`,用来修饰“char”、“int”和“long”变量,这些变量相当于8位、16位和32位的CPU寄存器,主要用于与A351汇编语言程序,按用户要求进行处理。 - 该类型具有明确的位语义,便于二进制操作、位操作和位域定义。 与C51差异:C51没有独立的二进制数据类型。 2.5.9 表达式与语句规范(核心差异)C351语言最重要的特征是严格限制表达式的复杂性,彻底消除C语言中的未定义行为和依赖编译器的不确定性。 1. 典型案例:标准C中存在语义不确定的表达式: int i, n; i = 2; n =(i++) + (i++) + (i++); // 不同编译器结果不同 C351禁止此类表达式。 2. 表达式运算规则 - 仅支持加减乘除和求余数构成的算术表达式,且遵循“先乘除后加减”。 - 不支持C51中复杂的运算符优先级(如位运算、逻辑运算、移位、条件运算等混合)。 - 含有多种运算的表达式必须拆分为多个简单语句,每个语句最多包含一个二元运算。 - 示例(不支持的写法): return (((u16)ADC_RES<< 2) | (ADC_RESL & 3)); // C351中错误 X =(ADC_RES << 2) | (ADC_RESL & 3); // C351中错误 ``` - 正确写法(引入中间变量): X1 = ADC_RES << 2; X2 = ADC_RESL & 3; X1 = X1 | X2; // 或 X1 = X1 + X2; return X1; ``` 3. 表达式中禁止使用函数调用 - 有返回值的函数只能以独立赋值语句的形式调用: 变量 = 函数(); // 允许 if (函数()) ... // 不允许 a = b + 函数(); // 不允许 ```
4. 禁止的运算符和语句形式 5. 语句间无隐含关联 - C51中存在“潜规则”:移位操作后的进位位保留在PSW的CY中,后续可直接使用。例如: // C51代码 dat <<= 1; P_HC595_SER = CY; // 依赖上一条语句产生的CY标志 ``` - C351禁止这种依赖。每个语句的结果必须显式表达: // C351正确写法 P_HC595_SER = dat & 0x80; // 先取出最高位 dat <<= 1; // 再移位 ``` - 原则:前后C语句没有因果关系,寄存器和PSW的值不跨越语句保留。 6. 数组下标限制 - 数组元素的下标只能是常数或单变量(不允许表达式)。 arr[i+j] // C351中不允许 arr // 允许,i为单一变量 arr[5] // 允许 ``` 7. 函数返回值限制 -`return` 语句的返回值只能是常数或单个变量,不能是表达式。 与C51差异总结:C351大幅简化表达式系统,目标是“每个语句做一件事,且仅做一件事”。这是两者最显著的差异,也是C51代码移植到C351时改造工作量最大的部分。 2.5.10 库函数 1. 标准库支持 - 包含常用库:`<string.h>`、`<math.h>`、`<absacc.h>` 等。 - 不支持涉及内存动态分配的函数(如 `malloc`、`free`、`calloc`),因为C351使用动态变量堆栈模型。 2. 新增库 - STDLIBE:扩展的实用工具库。 - FMTIO:格式化输入输出辅助库。 3. 标准输入输出(stdio)处理 - C351的 `stdio.h` 库是一个可配置的驱动模块,用非中断的查询方式实现UART1串口1的输入输出。 - 不建议使用 `printf` 和 `scanf`,原因: - 内存需求不确定(可能占用大量XRAM)。 - 与多任务环境冲突(不可重入)。 - 推荐替代方案: - 使用 `sprintf` / `sscanf`(可重入,由用户自己定义和操作字符串缓冲区)。 - 建议自定义的串口输出函数(如 `UARTX_SendString`),然后与“sprintf”配合使用,实现不同串口的“printf”功能。 4. 库函数的重入性 - 除了 `printf` 和 `scanf`,其他所有库函数都是可重入的。 - 用户自定义函数缺省也是可重入的。 与C51差异: - C51提供的是非完整的 `stdio.h`,其中 `printf` 和 `scanf`是不可重入的,且需要用户自己通过写getchar。 - C51支持 `malloc` 等动态内存函数(需配置堆区)。 - C51没有专用的 `STDLIBE` 和 `FMTIO` 库。 2.5.11 启动过程与BOOT程序 1. 统一的启动流程 - 为了在8位8051内核上支持标准C运行环境(特别是16位BP/VP虚拟寄存器和XRAM堆栈初始化),C351的启动代码(`CSTARTUP`)必须是固定的。 - 该启动代码完成: - XRAM清零(可选)。 - 虚拟BP、VP寄存器的初始化。 - 堆栈指针设置。 - 调用 `main` 函数。 2. 不支持用户自写BOOT程序 - C351不允许用户修改启动文件(如C51中的 `STARTUP.A51`),因为启动逻辑与C351的运行模型强耦合。 与C51差异:C51允许用户自定义启动代码(通过修改 `STARTUP.A51`),C351禁止。
2.5.12 其他语法差异速查表
2.5.13 C351语言的运算、运算符与运算优先级(C351限制规范)C351语言继承了标准C的基本运算符集合,但出于“无歧义、可验证”的设计原则,对运算符的使用方式和优先级依赖做出了严格限制。学习者应首先了解标准C运算符的种类,再掌握C351中的实际可用形式。 1.C351支持的运算符 C351仅支持下表所列的运算符。每个表达式中最多只能包含一个运算符(即一元运算或二元运算),复杂运算必须拆分为多个简单语句。 2. C351支持的运算符优先级(非常有限) 由于C351禁止在同一个表达式中混合不同类型的运算符(算术、关系、位、逻辑等不得并存),因此实际需要考虑的优先级只有算术运算符内部的规则: - 先乘除、后加减(`*` `/` `%` 优先于 `+` `-`) - 同一优先级从左至右结合 除此之外,C351不依赖、也不建议记忆其他优先级(如移位高于关系、按位与高于按位或等)。涉及多种运算时,必须使用临时变量分步计算。 正确示例: // 算术混合(允许) X = 2 + 3 * 4; // 结果为14(先乘后加) // 多种运算必须拆分 b =(ADC_RES << 2) | (ADC_RESL & 3); // 错误!禁止 // 改为: temp1 = ADC_RES << 2; temp2 = ADC_RESL & 3; b = temp1 | temp2; ``` 3. 关系与逻辑运算的特殊处理 关系或逻辑表达式(如 `x> y`)可以作为独立条件出现在`if`、`while`、`for` 的括号中,但不能与其他值进行算术运算。例如: if (a > b && c == d) { ...} // 允许:条件内部可有限使用 && 和 || flag = a > b; // 允许:将比较结果存入变量(结果为0或1) result = (a > b) + c; // 错误:禁止关系运算结果参与算术运算 ``` 4. 自增(`++`)和自减(`--`)的限制 - `++` 和 `--` 只能作为独立的语句,不能嵌在其他表达式中。 - 不区分前置和后置(效果相同),但建议统一使用前置形式,以避免误解。 允许: i++; ++j; ``` 禁止: k = (i++) + j; // 错误 if (i-- > 0) ... // 错误 ```
5. 学习要点总结 C351与C51/标准C的重要差异: - C51可以使用任意复杂的表达式,依赖完整的运算符优先级表;C351强制“一行一个运算”。 - C51允许自增自减嵌入表达式;C351禁止。 - C51中关系运算结果可参与算术(如 `(a>b)+c`);C351不允许。 学习者应始终牢记:在C351中,代码的清晰性和确定性比代码的简洁性更重要。写出每个简单语句,让编译器生成与之严格对应的机器码,是编写高可靠单片机程序的基础。 2.5.14 学习建议与注意事项1. 先修基础:熟练掌握标准C语法(特别是函数、指针、数组),并了解8051的基本硬件结构(SFR、中断、XRAM)。 2. 从C51迁移时需重点改造: - 将所有复合表达式拆分为多行简单语句。 - 移除对CY进位标志等PSW状态的跨语句依赖。 - 删除 `using` 关键字。 - 将问号表达式改为 `if-else`。 - 用 `sprintf` 替换 `printf`。 3. 验证方法:使用金水32051编译器编译后,对照生成的A351汇编代码,确保每个C语句与汇编代码逐句对应。 4. 可靠性原则:C351的语法限制看似繁琐,但能从根本上杜绝“编译器不同结果不同”的问题,特别适合安全关键型单片机控制程序。 掌握以上规范,学习者即可编写符合C351语言标准的、高可靠性的8051(尤其是STC系列)单片机程序。
杨为民写于2026年5月3日
|