找回密码
 立即注册
查看: 41|回复: 0

金水32051编译器8位8051单片机C351语言简介

[复制链接]
  • 打卡等级:常住居民II
  • 打卡总天数:87
  • 最近打卡:2026-05-11 01:12:23

164

主题

1330

回帖

5086

积分

荣誉版主

积分
5086
发表于 7 天前 | 显示全部楼层 |阅读模式
概述
金水32051编译器是笔者专门为88051单片机研制的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原生堆栈指针SP8位且不能访问XRAM,金水32051编译器抽象出一个“金水明32051 CPU模型”,引入了两个16位虚拟寄存器:
  - BP(基址指针)
  - VP(变量指针)
- 原生80518SP仅用于子程序调用返回和中断现场保存;而函数的动态变量全部存放在“16XRAM堆栈”中。
C51差异
- C51中函数“缺省不可重入”,需要递归或多任务调用时必须显式添加 `reentrant` 关键字,且会生成较大开销。
- C51小模式使用8051原生SP管理局部变量(限于内部idata),C351使用虚拟16BP/VP管理xdata堆栈。
四、  变量存储模型
1. 变量存储空间
- C35116XRAM(外部数据存储器)作为主要的变量存储区,最大支持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个参数(通过R7R5等),其余通过固定存储区(内部RAM)传递。
- C51没有“前两个参数专用寄存器”的明确规则,而是依赖特定寄存器(R7R6R5等)和存储类型。
六、  端口和SFR定义
1. 特殊功能寄存器(SFR)的性质
- 输入型SFR的值由外设决定,编译器不能对其进行优化(如删除看似无用的读操作)。
- SFR具有固定地址,类似于多任务中的共享变量或信号量。
2. C351中的SFR定义
- 使用与C51类似的关键字(如 `sfr``sbit`),但增加了一个重要特性:允许SFR地址小于0x80
- 地址小于0x80SFR可以作为辅助寄存器使用,提高数据处理速度(因为STC单片机的1T内核访问这些地址与通用寄存器速度相同)。
3. C351中的XSFR设备寄存器定义
- 随着STC单片机片上的设备增多,传统的128字节sfr空间已经不够用,只能扩展到XRAM
- C51使用下面宏语句来定义设备寄存器:
#define  T11L  (*(unsignedchar volatile xdata *)0xfe7b)
- C351使用类似sfr语句来定义设备寄存器: xsfr  设备寄存器名 = 16XRAM地址;
xsfr  T11L = 0xfe7b;
- C351也支持C51对设备寄存器的定义,只是在编译时将宏定义语句转换为xsfr语句。
4. 优化禁止
- C351编译器对SFR/XSFR的访问不会做任何假设(不缓存值、不重排顺序),确保输入读取的正确性。
C51差异
- C51标准中SFR地址仅限于0x80~0xFF(高128字节),小于0x80的地址被视为普通RAMC351扩展了SFR定义范围至整个64K XRAM中的特殊地址。
- C351明确要求编译器不对SFR进行优化,而C51在某些优化级别下可能产生副作用。
七、  数据类型
1. 标准数据类型
C51基本一致,但特别引入二进制数据类型
Fig_01_数据类型.jpg
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. 禁止的运算符和语句形式
Fig_02_禁止.jpg
5. 语句间无隐含关联
- C51中存在“潜规则”:移位操作后的进位位保留在PSWCY中,后续可直接使用。例如:
  // 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. 统一的启动流程
- 为了在88051内核上支持标准C运行环境(特别是16BP/VP虚拟寄存器和XRAM堆栈初始化),C351的启动代码(`CSTARTUP`必须是固定的
- 该启动代码完成:
  - XRAM清零(可选)。
  - 虚拟BPVP寄存器的初始化。
  - 堆栈指针设置。
  - 调用 `main` 函数。
2. 不支持用户自写BOOT程序
- C351不允许用户修改启动文件(如C51中的 `STARTUP.A51`),因为启动逻辑与C351的运行模型强耦合。
C51差异C51允许用户自定义启动代码(通过修改 `STARTUP.A51`),C351禁止。

2.5.12  其他语法差异速查表
Fig_03_对比.jpg

2.5.13  C351语言的运算、运算符与运算优先级(C351限制规范)
C351语言继承了标准C的基本运算符集合,但出于“无歧义、可验证”的设计原则,对运算符的使用方式和优先级依赖做出了严格限制。学习者应首先了解标准C运算符的种类,再掌握C351中的实际可用形式。
1.C351支持的运算符
C351仅支持下表所列的运算符。每个表达式中最多只能包含一个运算符(即一元运算或二元运算),复杂运算必须拆分为多个简单语句。
Fig_04_运算.jpg
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;       // 允许:将比较结果存入变量(结果为01
result = (a > b) + c; // 错误:禁止关系运算结果参与算术运算
```
4. 自增(`++`)和自减(`--`)的限制
- `++` `--` 只能作为独立的语句,不能嵌在其他表达式中。
- 不区分前置和后置(效果相同),但建议统一使用前置形式,以避免误解。
允许
i++;
++j;
```
禁止
k = (i++) + j;   // 错误
if (i-- > 0) ... // 错误
```

5. 学习要点总结
Fig_05_要求.jpg
C351C51/标准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系列)单片机程序。

杨为民写于202653


1 喜欢他/她就送朵鲜花吧,赠人玫瑰,手有余香!
回复

使用道具 举报 送花

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|深圳国芯人工智能有限公司 ( 粤ICP备2022108929号-2 )

GMT+8, 2026-5-11 10:32 , Processed in 0.113288 second(s), 45 queries .

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表