SPCE061A
这里不是教你如何使用 C 语言。而是怎样利用 C 语言来对凌阳的 unSP 内核的单片机进行编
程。
以下,我们将复习关于 C 的一些概念,如结构联合和类型定义等。
2.1 数据与运算
2.1.1 数据与数据类型
在表 2.1 中列出了 unSP GCC 认可的基本数据类型及其值域。读者应该特别注意:表中所列
的数据类型及其值域与一般机器使用的 GCC 数据类型之间有一些差别,比如 char 为 16 位
等等。此外,unSP GCC 的 float 与 double 均存储为 32 位浮点数,而 unSP 汇编器的 float 与
double 分别是 32 位与 64 位浮点数。
表 2.1 unSP GCC 的基本数据类型
数 据 类 型
数据长度(位数)
值 域
char
16
-32768~32767
short
16
-32768~32767
int
16
-32768~32767
long int
32
-2147483648~2147483647
unsigned char
16
0~65535
unsigned short
16
0~65535
unsigned int
16
0~65535
unsigned long int
32
0~4294967295
float
32
以 IEEE 格式表示的 32 位浮点数
double
32
以 IEEE 格式表示的 32 位浮点数
2.1.2 常量、变量、运算符与表达式
在程序运行过程中,其值不能改变的量称为常量。编程时可以用一个字符常量来代表一个看
不出有什么意义的数字,比如:
#define C_Fosc_49M 0x0080
这里定义了一个字符常量 C_Fosc_49M,它的值为 0x0080。在编程的时候,我们就可以用
C_Fosc_49M 来代替 0x0080。这样的好处是显而易见的,大家一眼就可以看出 Fosc 选择了
49MHz。
在程序运行过程中,其值可以改变的量称为变量。下面就有符号/无符号(signed/unsigned)问
题作一些说明。在编写程序时,如果使用 signed 和 unsigned 两种数据类型,就得使用这两
种格式类型的库函数,这将使占用的存储空间成倍增长。因此在编程时,如果只强调程序
北阳电子内部技术资料 5
第 2 章 C 语言的基本知识
的运算速度而又不进行负数运算时,最好采用 unsigned 格式。
unSP GCC 基本的算术运算符和 ANSI-C 是一样的,见表 2.2。
表 2.2 unSP GCC 基本的算术运算符
算逻操作符
作用
+、-、*、/、%
加、减、乘、除、求余运算
&&、||
逻辑与、或
&、|、^、<<、>>
按位与、或、异或、左移、右移
>,>=,<,<=,= =,!=
大于、不小于、小于、不大于、等于、不等于
=
赋值运算符
? :
条件运算符
,
逗号运算符
*、&
指针运算符
.
分量运算符
sizeof
求字节数运算符
[ ]
下标运算符
下面举一个用使用位操作运算扫描识别键盘的例子。
如图 2.1 所示为 SPCE061A 与一个 4×4 键盘接口的电路图。
图 2.1 电路直接扫描一个矩阵键盘。在这些按规则进行扫描的键盘矩阵上,每次只有一行电
平被拉低。在逐次扫描拉低这些行的同时,去读那些列的信息,在被拉低的行上被按下的按
键所对应的列的位值为 0,其他列的位值为 1。如图 2.1 所示,SPCE061A 的 A 口低八位作
为 4×4 键盘的接口,其中 IOA4~IOA7 作为行驱动线,IOA0~IOA3 作为列读入线。每隔
约 20ms 行驱动线被逐次拉低,以避免键盘的抖动干扰。接下来程序所要做的工作就是测试
输入的任何变化——新键的按下或旧键的释放。使用位操作运算,可以很容易地将这些变化
识别出来。如表 2.3 所示,该表说明了如何使用位操作来识别键盘的变化过程。
表 2.3 适合于把 IOA4~IOA7 设为反相输出口的情况。设为反相输出口的意思是,我们在软
件中送 1 到 IO 口,但 IO 口输出的是 0,两者正好是反相的。 VCC 第一行第二行第三行第
四 行 第 一 列 第 二 列 第 三 列 第 四 列
IOA4IOA5IOA6IOA7IOA0IOA1IOA2IOA3SPCE061A0123456789ABCDEF
图 2.1 键盘扫描原理图
表 2.3 使用位操作检测键值变化
IOA7 IOA6 IOA5 IOA4
IOA3 IOA2 IOA1 IOA0
原键值(old)
0 0 1 0
1 1 0 1 北阳电子内部技术资料 6
第 2 章 C 语言的基本知识
新键值(new)
0 1 0 0
1 1 1 0
中间变量(temp=old^ new)
0 1 1 0
0 0 1 1
新按键(temp&new)
0 1 0 0
0 0 0 1
释放键(temp&old)
0 0 1 0
0 0 1 0
从表 2.3 可以看出,A 口原读入值为 00101101B,由于行驱动线 IOA4~IOA7 为反相输出口,
按 1 为低电平,0 为高电平的规定,此时硬件电路的第二行 IOA5 为低电平。由于 IOA0~
IOA3 为列读入值,按 0 为低电平,1 为高电平的规定,则第二列的键被按下,其键值为 5。
根据同样的原理,对于新读入的值 01001110B,意味着,此时键值为 5 的键被释放,而第三
行第一列键值为 8 的键被按下。下面所进行的逻辑位操作也正好说明了这一点。在表 2.3 中,
IOA7~IOA4 的新值 0100B 和原值 0010B 不同说明扫描的行不同,而新按键栏和释放键栏
中 IOA7~IOA4 为 1 的是扫描的行。而对于读回列信息的 IOA3~IOA0,新按键栏为 0001B
说明第一列有新按键被按下。而释放键栏为 0010B 说明第二列有键被释放。因而第四位的
逻辑操作可以识别键的变化。
键扫描的程序如下:
*******************************************************************************
unsigned int old,new,push,rel,temp,row;
void key(void)
{
for(row=0x10;row<0x100;row<<1) //扫描
{
*P_IOA_Data=*P_IOA_Data&row;
new=(new<<4)| *P_IOA_Data&0x0f; //读回列信息
}
if((temp=new^old)>0) //获取按下和释放的键值信息
{
push=temp&new;
rel=temp&old;
old=new;
}
}
*******************************************************************************
在程序中使用了 for 循环,详细内容将在下一节讨论。for 循环的内容将循环 4 次。变量 row
的初值为 0x10,左移 4 次后,变为 0x0100。
读回列信息部分,4 位一组,每行左移 4 位,采用逻辑操作,很容易判别是否有键变化。
大家来看 if((temp=new^old)>0)这一行,这是一种“嵌入式赋值”,所以 if 测试中包含了对
temp 变量的赋值操作。请注意“=”和“==”的区别,“==”号将只对等式进行测试,
而不进行任何赋值操作。
2.2 流程控制语句
2.2.1 程序的基本结构及控制语句
归纳起来,C 语言有三种基本结构: 北阳电子内部技术资料 7
第 2 章 C 语言的基本知识
AB
1、 顺序结构
2、 选择结构
3、 循环结构
顺序结构流程图
图 2.2 顺序结构流程图
顺序结构是一种最基本,最简单的编程结构。在这种结构中,程序由低地址向高地址顺序执
行指令代码。如图 2.2 所示,程序先执行 A 操作,再执行 B 操作,两者是顺序执行的关系。
选择结构及其语句
在选择结构中程序首先对一个条件语句进行测试。当条件为真时,执行一个方向上的程序流
程,当条件为假时,执行另一个方向上的程序流程。如图 2.3 所示,T 代表一个条件,当 T
条件成立时,执行 A 操作,否则执行 B 操作。常见的选择语句有:if,else if,switch/case
语句。 ABT 为真 YN
图 2.3 选择结构流程图
C语言的 if 语句有三种基本形式。
第一种形式为基本形式
if(表达式) 语句;
其语义是:如果表达式的值为真,则执行其后的语句,否则不执行该语句。
第二种形式为 if-else 形式
if(表达式)
语句 1;
else
语句 2;
其语义是:如果表达式的值为真,则执行语句 1,否则执行语句 2 。
第三种形式为 if-else-if 形式
前二种形式的 if 语句一般都用于两个分支的情况。当有多个分支选择时,可采用 if-else-if
语句,其一般形式为:
if(表达式 1)
语句 1;
else if(表达式 2)
语句 2; 北阳电子内部技术资料 8
第 2 章 C 语言的基本知识
…
else if(表达式 n)
语句 n;
else
语句 n+1;
其语义是:依次判断表达式的值,当出现某个值为真时,则执行其对应的语句。然后跳到整
个 if 语句之外继续执行程序。如果所有的表达式均为假,则执行语句 n 。 然后继续执行后
续程序。
由若干条 if,else if 语句嵌套可构成串行多分支结构。在 if 语句的嵌套中,请注意 if 和 else
的对应关系。else 总是与它上面最近的一个 if 语句相对应,如果 if 和 else 的数目不同时,
可以用花括号将不对称的 if 括起来以确定它们之间的相应关系。
switch 语句的一般形式如下:
switch(表达式)
{
case 常量表达式 1:语句 1;break;
case 常量表达式 2:语句 2;break;
┇
case 常量表达式 n:语句 n;break;
default :语句 n+1;
}
说明:
每个 case 后的常量表达式只能是常量组成的表达式,当 switch 后的表达式的值与某一个常
量表达式的值一致时。程序就转到此 case 后的语句开始执行,然后遇 break 就退出 switch 语
句。如果没有一个常量表达式的值与 switch 后的值一致,就执行 default 后的语句。
各个 case 和 default 出现的次序不影响执行结果,一般情况下,尽量使用出现机率大的 case
放在前面。
循环结构及其语句
使用循环结构可以使分支流程重复地进行。
循环结构又分为“当”(while)型循环结构和“直到”(do-while)型循环结构两种。如图
2.4 所示。在“当”(while)型循环结构中,当判断条件 T 为真时,反复执行操作 A,当条
件为假时才停止循环。在“直到”(do-while)型循环结构中,先执行操作 A,再判断条件
T,若 T 为真,则再执行 A,如此反复,直到 T 为假为止。 AT 为真 YN“while”型 YN“do
while”型 AT 为真 北阳电子内部技术资料 9
第 2 章 C 语言的基本知识