C语言变量的地址

要研究指针,我们得先来深入理解内存地址这个概念。打个比方:整个内存就相当于一个拥有很多房间的大楼,每个房间都有房间号,比如从 101、102、103 一直到 NNN,我们可以说这些房间号就是房间的地址。相对应的内存中的每个单元也都有自己的编号,比如从0x00、0x01、0x02 一直到 0xNN,我们同样可以说这些编号就是内存单元的地址。房间里可以住人,对应的内存单元里就可以“住进”变量了:假如一位名字叫 A 的人住在 101 房间,我们可以说 A 的住址就是 101,或者 101 就是 A 的住址;对应的,假如一个名为 x 的变量住在编号为 0x00 的这个内存单元中,那么我们可以说变量 x 的内存地址就是 0x00,或者 0x00就是变量 x 的地址。

基本的内存单元是字节,英文单词为 Byte,我们所使用的 STC89C52 单片机共有 512 字节的 RAM,就是我们所谓的内存,但它分为内部 256 字节和外部 256 字节,我们仅以内部的 256 字节为例,很明显其地址的编号从 0 开始就是 0x00~0xFF。我们用 C 语言定义的各种变量就存在 0x00~0xFF 的地址范围内,而不同类型的变量会占用不同数量的内存单元,即字节,可以结合前面讲过的 C 语言变量类型深入理解。假如现在定义了

  • unsigned char a = 1;

  • unsigned char b = 2;

  • unsigned int c = 3;

  • unsigned long d = 4;




这样 4 个变量,我们把这 4 个变量分别放到内存中,就会是表 12-1 中所列的样子,我们先来大概了解一下他们的存储方式。


表12-1 变量存储方式
内存地址
存储的数据
……
……
0x07
d
0x06
d
0x05
d
0x04
d
0x03
c
0x02
c
0x01
b
0x00
a

变量 a、b 和 c 和 d 之间的变量类型不同,因此在内存中所占的存储单元也不一样,a 和b 都占一个字节,c 占了 2 个字节,而 d 占了 4 个字节。那么,a 的地址就是 0x00,b 的地址就是 0x01,c 的地址就是 0x02,d 的地址就是 0x04,它们的地址的表达方式可以写成:&a,&b,&c,&d。这样就代表了相应变量的地址,C 语言中变量前加一个&表示取这个变量的地址,&在这里就叫做“取址符”。

讲到这里,有一点延伸内容,大家可以了解下:比如变量 c 是 unsigned int 类型的,占了2 个字节,存储在了 0x02 和 0x03 这两个内存地址上,那么 0x02 是它的低字节还是高字节呢?

这个问题由所用的 C 编译器与单片机架构共同决定,单片机类型不同就有可能不同,大家知道这么回事即可。比如:在我们使用的 Keil+51 单片机的环境下,0x02 存的是高字节,0x03存的是低字节。这是编译底层实现上的细节问题,并不影响上层的应用,如下这两种情况在应用上丝毫不受这个细节的影响:强制类型转换——b = (unsigned char) c,那么 b 的值一定是 c 的低字节;取地址——&c,则得到的一定是 0x02,这都是 C 语言本身所决定的规则,不因单片机编译器的不同而有所改变。

实际生活中,我们要寻找一个人有两种方式,一种方式是通过它的名字来找人,还有第二种方式就是通过它的住宅地址来找人。我们在派出所的户籍管理系统的信息输入方框内,输入小明的家庭住址,系统会自动指向小明的相关信息,输入小刚的家庭住址,系统会自动指向小刚的相关信息。这个供我们输入地址的方框,在户籍管理系统叫做“地址输入框”。

那么,在 C 语言中,我们要访问一个变量,同样有两种方式:一种是通过变量名来访问,另一种自然就是通过变量的地址来访问了。在 C 语言中,地址就等同于指针,变量的地址就是变量的指针。我们要把地址送到上边那个所谓的“地址输入框”内,这个“地址输入框”既可以输入 x 的指针,又可以输入 y 的指针,所以相当于一个特殊的变量——保存指针的变量,因此称之为指针变量,简称为指针,而通常我们说的指针就是指指针变量。

地址输入框输入谁的地址,指向的就是这个人的信息,而给指针变量输入哪个普通变量的地址,它自然就指向了这个变量的内容,通常的说法就是指针指向了该变量。



C语言指针变量的声明

在 C 语言中,变量的地址往往都是编译系统自动分配的,对我们用户来说,我们是不知道某个变量的具体地址的。所以我们定义一个指针变量 p,把普通变量 a 的地址直接送给指针变量 p 就是 p = &a;这样的写法。

对于指针变量 p 的定义和初始化,一般有两种方式,这两种方式,初学者很容易混淆,因此这个地方没别的方法,就是死记硬背,记住即可。

方法 1:定义时直接进行初始化赋值。

  • unsigned char a;

  • unsigned char *p = &a;





方法 2:定义后再进行赋值。

  • unsigned char a;

  • unsigned char *p;

  • p = &a;





大家仔细看会看出来这两种写法的区别,它们都是正确的。我们在定义的指针变量前边加了个*,这个*p 就代表了这个 p 是个指针变量,不是个普通的变量,它是专门用来存放变量地址的。此外,我们定义*p 的时候,用了 unsigned char 来定义,这里表示的是这个指针指向的变量类型是 unsigned char 型的。

指针变量似乎比较好理解,大家也能很容易就听明白。但是为什么很多人弄不明白指针呢?因为在 C 语言中,有一些运算和定义,他们是有区别的,很多同学就是没弄明白它们的区别,指针就始终学不好。这里我要重点强调两个区别,只要把这两个区别弄明白了,起码指针变量这部分就不是问题了。这两个重点现在大家死记硬背,直接记住即可,靠理解有可能混淆概念。

第一个重要区别:指针变量 p 和普通变量 a 的区别。

我们定义一个变量 a,同时也可以给变量 a 赋值 a = 1,也可以赋值 a = 2。

我们定义一个指针变量 p,另外还定义了一个普通变量 a=1,普通变量 b=2,那么这个指针变量可以指向 a 的地址,也可以指向 b 的地址,可以写成 p = &a,也可以写成 p = &b,但就是不能写成 p = 1 或者 p = 2 或者 p = a,这三种表达方式都是错的。

因此这个地方,不要看到定义*p 的时候前边有个 unsigned char 型,就错误的赋值 p=1,这个只是说明 p 指向的变量是这个 unsigned char 类型的,而 p 本身,是指针变量,不可以给它赋值普通的值或者变量,后边我们会直接把指针变量称之为指针,大家要注意一下这个小细节。

前边这个区别似乎比较好理解,还有第二个重要区别,一定要记清楚。

第二个重要区别:定义指针变量*p 和取值运算*p 的区别。

“*”这个符号,在我们的 C 语言有三个用法,第一个用法很简单,乘法操作就是用这个符号,这里就不讲了。

第二个用法,是定义指针变量的时候用的,比如 unsigned char *p,这个地方使用“*”代表的意思是 p 是一个指针变量,而非普通的变量。

还有第三种用法,就是取值运算,和定义指针变量是完全两码事,比如:
纯文本
复制



  • unsigned char  a = 1;

  • unsigned char  b = 2;

  • unsigned char  *p;

  • p = &a;

  • b = *p;




这样两步运算完了之后,b 的值就成了 1 了。在这段代码中,&a 表示取 a 这个变量的地址,把这个地址送给 p 之后,再用*p 运算表示的是取指针变量 p 指向的地址的变量的值,又把这个值送给了 b,最终的结果相当于 b=a。同样是*p,放在定义的位置就是定义指针变量,放在执行代码中就是取值运算。

这两个重要区别,大家可以反复阅读三四遍,把这两个重要区别弄明白,指针的大门就顺利的踏进去一只脚了。至于详细的用法,我们后边用得多了就会慢慢熟悉起来了。


C语言指针的简单示例

前边我们提到了,指针的意义往往在小程序里是体现不出来的,对于简单程序来说,有时候用了指针,反而可能比没用指针还麻烦,但是为了让大家巩固一下指针的用法,我还是写了个使用指针的流水灯程序,目的是让大家从简单程序开始了解指针,当程序复杂的时候不至于手足无措。

  • #include <reg52.h>

  • sbit ADDR0 = P1^0;

  • sbit ADDR1 = P1^1;

  • sbit ADDR2 = P1^2;

  • sbit ADDR3 = P1^3;

  • sbit ENLED = P1^4;

  • void ShiftLeft(unsigned char *p);


  • void main(){

  •     unsigned int i;

  •     unsigned char buf = 0x01;


  •     ENLED = 0; //使能选择独立 LED

  •     ADDR3 = 1;

  •     ADDR2 = 1;

  •     ADDR1 = 1;

  •     ADDR0 = 0;


  •     while (1){

  •         P0 = ~buf; //缓冲值取反送到 P0 口

  •         for (i=0; i<20000; i++); //延时

  •         ShiftLeft(&buf); //缓冲值左移一位

  •         if (buf == 0){ //如移位后为 0 则重赋初值

  •             buf = 0x01;

  •         }

  •     }

  • }

  • /* 将指针变量 p 指向的字节左移一位 */

  • void ShiftLeft(unsigned char *p){

  •     *p = *p << 1; //利用指针变量可以向函数外输出运算结果

  • }




这是一个使用指针实现流水灯的例子,纯粹是为了讲指针而写这样一段程序,程序中传递的是 buf 的地址,把这个地址直接传递给函数 ShiftLeft 的形参指针变量 p,也就是 p 指向了 buf。对比之前的函数调用,大家是否看明白,如果是普通变量传递,只能单向的,也就是说,主函数传递给子函数的值,子函数只能使用却不能改变。而现在我们传递的是指针,不仅仅子函数可以使用 buf 里边的值,而且还可以对 buf 里边的值进行修改。

此外再强调一句,只要是*p 前边带了变量类型如 unsigned char,就是表示定义了一个指针变量 p,而执行代码中的*p,是指 p 所指向的内容。

通过理论的学习和这样一个例程,我想大家对指针应该有概念了,至于它的灵活应用,需要我们在后边的程序中慢慢去体会,理论上就不再过多赘述了。



C语言指向数组元素的指针



指向数组元素的指针和运算法则
所谓指向数组元素的指针,其本质还是变量的指针。因为数组中的每个元素,其实都可以直接看成是一个变量,所以指向数组元素的指针,也就是变量的指针。

指向数组元素的指针不难,但很常用。我们用程序来解释会比较直观一些。

  • unsigned char number[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

  • unsigned char *p;




如果我们写 p = &number[0];那么指针 p 就指向了 number 的第 0 号元素,也就是把number[0]的地址赋值给了 p,同理,如果写 p = &number[1];p 就指向了数组 number 的第 1号元素。p = &number[x];其中 x 的取值范围是 0~9,就表示 p 指向了数组 number 的第 x 号元素。指针本身,也可以进行几种简单的运算,这几种运算对于数组元素的指针来说应用最多。

  • 比较运算。比较的前提是两个指针指向同种类型的对象,比如两个指针变量 p 和 q它们指向了具有同种数据类型的数组,那它们可以进行 <,>,>=,<=,==等关系运算。如果 p==q 为真的话,表示这两个指针指向的是同一个元素。

  • 指针和整数可以直接进行加减运算。比如还是上边我们那个指针 p 和数组 number,如果 p = &number[0],那么 p+1 就指向了 number[1],p+9 就指向了 number[9]。当然了,如果 p = &number[9],p-9 也就指向了 number[0]。

  • 两个指针变量在一定条件下可以进行减法运算。如 p = &number[0]; q = &number[9];那么 q-p 的结果就是 9。但是这个地方大家要特别注意,这个 9 代表的是元素的个数,而不是真正的地址差值。如果我们的 number 的变量类型是 unsigned int 型,占 2 个字节,q-p 的结果依然是 9,因为它代表的是数组元素的个数。



在数组元素指针这里还有一种情况,就是数组名字其实就代表了数组元素的首地址,也就是说:

  • p = &number[0];

  • p = number;




这两种表达方式是等价的,因此以下几种表达形式和内容需要大家格外注意一下。

根据指针的运算规则,p+x 代表的是 number[x]的地址,那么 number+x 代表的也是number[x]的地址。或者说,它们指向的都是 number 数组的第 x 号元素。

*(p+x)和*(number+x)都表示 number[x]。

指向数组元素的指针也可以表示成数组的形式,也就是说,允许指针变量带下标,即 p
和*(p+i)是等价的。但是为了避免混淆与规范起见,这里我们建议大家不要写成前者,而一律采用后者的写法。但如果看到别人那么写,也知道是怎么回事即可。

二维数组元素的指针和一维数组类似,需要介绍的内容不多。假如现在一个指针变量 p和一个二维数组 number[3][4],它的地址的表达方式也就是 p=&number[0][0],有一个地方要注意,既然数组名代表了数组元素的首地址,那么也就是说 p 和 number 都是指数组的首地址。对二维数组来说,number[0],number[1],number[2]都可以看成是一维数组的数组名字,所以 number[0]等价于 &number[0][0], number[1]等价于 &number[1][0], number[2]等价于&number[2][0]。加减运算和一维数组是类似的,不再详述。
指向数组元素指针的实例
在 C 语言里边,sizeof()可以用来获取括号内的对象所占用的内存字节数,虽然它写作函数的形式,但它并不是一个函数,而是 C 语言的一个关键字,sizeof()整体在程序代码中就相当于一个常量,也就是说这个获取操作是在程序编译的时候进行的,而不是在程序运行的时候进行。这是一个实际编程中很有用的关键字,灵活运用它可以为程序带来更好的可读性、易维护性和可移植性,在后续的例程学习中将会慢慢有所体会的。

sizeof()括号中可以是变量名,也可以是变量类型名,其结果是等效的。而其更大的用处是与数组名搭配使用,这样可以获取整个数组占用的字节数,就不用自己动手计算了,可以避免错误,而如果日后改变了数组的维数时,也不需要再到执行代码中逐个修改,便于程序的维护和移植。

下面我们提供了一个简单的串口演示例程,可以体验一下指针和 sizeof()的用法。例程首先接收上位机下发的命令,根据命令值分别把不同数组的数据回发给上位机,程序还用到了指针的自增运算,也就是+1 运算,大家可以认真考虑一下指针 ptrTxd 在串口发送的过程中的指向是如何变化的。在上位机串口调试助手中分别下发 1、2、3、4,就会得到不同的数组回发,注意这里都用十六进制发送和十六进制显示。

此外,这个程序还应用到一个小技巧,大家要学会使用。我们前边讲了串口发送中断标志位 TI 是硬件置位,软件清零的。通常来讲,我们想一次发送多个数据的时候,就需要把第一个字节写入 SBUF,然后再等待发送中断,在后续中断中再发送剩余的数据,这样我们的数据发送过程就被拆分到了两个地方——主循环内和中断服务函数内,无疑就使得程序结构变得零散了。这个时候,为了使程序结构尽量紧凑,在启动发送的时候,不是向 SBUF 中写入第一个待发的字节,而是直接让 TI=1,注意,这时候会马上进入串口中断,因为中断标志位置 1 了,但是串口线上并没有发送任何数据,于是,我们所有的数据发送都可以在中断中进行,而不用再分为两部分了。大家可以在程序中体会一下这个技巧的好处。
纯文本
复制



  • #include <reg52.h>

  • bit cmdArrived = 0; //命令到达标志,即接收到上位机下发的命令

  • unsigned char cmdIndex = 0; //命令索引,即与上位机约定好的数组编号

  • unsigned char cntTxd = 0; //串口发送计数器

  • unsigned char *ptrTxd; //串口发送指针


  • unsigned char array1[1] = {1};

  • unsigned char array2[2] = {1,2};

  • unsigned char array3[4] = {1,2,3,4};

  • unsigned char array4[8] = {1,2,3,4,5,6,7,8};


  • void ConfigUART(unsigned int baud);


  • void main(){

  •     EA = 1; //开总中断

  •     ConfigUART(9600); //配置波特率为 9600


  •     while (1){

  •         if (cmdArrived){

  •             cmdArrived = 0;

  •             switch (cmdIndex){

  •                 case 1:

  •                     ptrTxd = array1; //数组 1 的首地址赋值给发送指针

  •                     cntTxd = sizeof(array1); //数组 1 的长度赋值给发送计数器

  •                     TI = 1; //手动方式启动发送中断,处理数据发送

  •                     break;

  •                 case 2:

  •                     ptrTxd = array2;

  •                     cntTxd = sizeof(array2);

  •                     TI = 1;

  •                     break;

  •                 case 3:

  •                     ptrTxd = array3;

  •                     cntTxd = sizeof(array3);

  •                     TI = 1;

  •                     break;

  •                 case 4:

  •                     ptrTxd = array4;

  •                     cntTxd = sizeof(array4);

  •                     TI = 1;

  •                     break;

  •                 default:

  •                     break;

  •             }

  •         }

  •     }

  • }

  • /* 串口配置函数,baud-通信波特率 */

  • void ConfigUART(unsigned int baud){

  •     SCON = 0x50; //配置串口为模式 1

  •     TMOD &= 0x0F; //清零 T1 的控制位

  •     TMOD |= 0x20; //配置 T1 为模式 2

  •     TH1 = 256 - (11059200/12/32)/baud; //计算 T1 重载值

  •     TL1 = TH1; //初值等于重载值

  •     ET1 = 0; //禁止 T1 中断

  •     ES = 1; //使能串口中断

  •     TR1 = 1; //启动 T1

  • }

  • /* UART 中断服务函数 */

  • void InterruptUART() interrupt 4{

  •     if (RI){ //接收到字节

  •         RI = 0; //清零接收中断标志位

  •         cmdIndex = SBUF; //接收到的数据保存到命令索引中

  •         cmdArrived = 1;//设置命令到达标志

  •     }

  •     if (TI){ //字节发送完毕

  •         TI = 0; //清零发送中断标志位

  •         if (cntTxd > 0){ //有待发送数据时,继续发送后续字节

  •             SBUF = *ptrTxd; //发出指针指向的数据

  •             cntTxd--; //发送计数器递减

  •             ptrTxd++; //发送指针递增

  •         }

  •     }

  • }





C语言字符数组和字符指针



常量和符号常量
在程序运行过程中,其值不能被改变的量称之为常量。常量分为不同的类型,有整型常量如 1、2、3、100;浮点型常量 3.14、0.56、-4.8;字符型常量„a‟、„b‟、„0‟;字符串常量“a”、“abc”、“1234”、“1234abcd”等。

细心的同学会发现,整型和浮点型常量我们直接写的数字,而字符型常量用单引号来表示一个字符,用双引号来表示一个字符串,尤其大家要注意„a‟和“a”是不一样的,这个等会我们要详细介绍。

常量一般有两种表现形式:

  • 直接常量:直接以值的形式表示的常量称之为直接常量。上述举例这些都是直接常量,直接写出来了。

  • 符号常量:用标识符命名的常量称之为符号常量,就是为上面的直接常量再取一个名字。使用符号常量一是方便理解,提高程序可读性,更重要的是方便程序的后续维护,习惯上符号常量我们都用大写字母和下划线来命名。



比如,我们可以把 3.14 取名为 PI(即π)。再比如,我们上节课的串口程序,我们用的波特率是 9600,如果用符号常量来进行提前声明的话,那我们要修改成其它速率的话,就不用在程序中找 9600 修改了,直接修改声明处就可以了,两种方法举例说明。用 const 声明。比如我们在程序开始位置定义一个符号常量 BAUD。

定义形式是:
    const  类型  符号常量名字=常量值;

    const unsigned int BAUD = 9600;  /*注意结尾有个分号*/
我们就可以在程序中直接把 9600 改成 BAUD,这样我们如果要改波特率的话,直接在程序开头位置改一下这个值就可以了。用预处理命令#define 来完成,预处理命令我们先来认识#define。

定义形式是:
    #define  符号常量名  常量值

    #define  BAUD  9600  /*注意结尾没有分号*/
这样定义以后,只要在程序中出现 BAUD 的话,意思就是完全替代了后边的 9600 这个数字。

不知大家是否记得,我们之前定义数码管真值表的时候,用了一个 code 关键字。

  • unsigned char code LedChar[] = {  //数码管显示字符转换表

  •     0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,

  •     0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E

  • };




我们当时说加了 code 之后,这个真值表的数据只能被使用,不能被改变,如果我们直接写 LedChar[0] = 1;这样就错了。实际上 code 这个关键字是 51 单片机特有的,如果是其它类型的单片机我们只需要写成 const unsigned char LedChar[]={}就可以了,自动保存到 FLASH里,而 51 单片机只用 const 而不加 code 的话,这个数组会保存到 RAM 中,而不会保存到FLAHS 中,鉴于此,在 51 这个体系下,const 反倒变得不那么重要了,它的作用被 code 取代了,这里大家知道这么回事即可。

我们来对各种类型的常量做进一步说明。

整型常量和浮点型常量就没多少可说的了,之前我们应用的都很熟练了,整型直接写数字就是十进制如 128,前边 0x 开头的表示是十六进制 0x80,浮点型直接写带小数点的数据就可以了。

字符型常量是由一对单引号括起来的单个字符。它分为两种形式,一种是普通字符,一种是转义字符。

普通字符就是那些我们可以直接书写直接看到的有形的字符,比如阿拉伯数字 0~9,英文字符 A~z,以及标点符号等。它们都是 ASCII 码表中的字符,而它们在单片机中都占用一个字节的空间,其值就是对应的 ASCII 码值。比如„a‟的值是 97,„A‟的值是 65,„0‟的值是48,如果定义一个变量 unsigned char a = „a‟,那么变量 a 的值就是 97。

除了上述这些字符之外,还有一些特殊字符,它们一些是无形的,像回车符、换行符这些都是看不到的,还有一些像‟\”这类字符它们已经有特殊用途了,想象一下如果写 '''觉得编译器会怎么去解释呢。针对这些特殊符号,为了可以让它们正常进入到我们的程序代码中,C 语言就规定了转义字符,它是以反斜杠(\)开头的特定字符序列,让它们来表示这些特殊字符,比如我们用\n 来代表换行。我们用一个简单表格来说明一下常用的转义字符的意思,如表 12-2 所示。


表 12-2 常用转义字符及含义
字符形式
含义
\n
换行
\t
横向跳格(相当于 Tab)
\v
竖向跳格
\b
退格
\r
光标移到行首
\\
反斜杠字符„\‟
\‟
单引号字符
\”
双引号字符
\f
走纸换页
\0
空值

表格不需要大家记住,用到了,过来查就可以了。

字符串常量是用双引号括起来的字符序列,一般我们都称之字符串。如“a”、“1234”、“welcome to
www.kingst.org
”等都是字符串常量。字符串常量在内存中按顺序逐个存储字符串中的字符的 ASCII 码值,并且特别注意,最后还有一个字符„\0‟,„\0‟字符的 ASCII 码值是 0,它是字符串结束标志,在写字符串的时候,这个„\0‟是隐藏的,我们看不到,但是实际却是存在的。所以“a”就比„a‟多了一个 „\0‟,“a”的就占了 2 个字节,而 „a‟只占一个字节。

还有一 个地 方要注 意, 就是字 符串 中的空 格, 也是一 个字 符,比 如 “welcome to
www.kingst.org
”一共占了 26 个字节的空间。其中 21 个字母,2 个„.‟,2 个 „ ‟(空格字符)以及一个„\0‟。
字符和字符串数组实例
为了对比字符串、字符数组、常量数组的区别,我们写个了简单的演示程序,定义了 4个数组分别是:

  • unsigned char array1[] = "1-Hello!\r\n";

  • unsigned char array2[] = {'2', '-', 'H', 'e', 'l', 'l', 'o', '!', '\r', '\n'};

  • unsigned char array3[] = {51, 45, 72, 101, 108, 108, 111, 33, 13, 10};

  • unsigned char array4[] = "4-Hello!\r\n";





在串口调试助手下,发送十六进制的 1、2、3、4,使用字符形式显示的话,会分别往电脑上送这 4 个数组中对应的那个数组。我们只是在起始位置做了区分,其它均没有区别。大家可以比较一下效果。

此外还要说明一点,数组 1 和数组 4,数组 1 我们是发完整的字符串,而数组 4 我们仅仅发送数组中的字符,没有发结束符号。串口调试助手用字符形式显示是没有区别的,但是大家如果改用十六进制显示,大家会发现数组 1 比数组 4 多了一个字节„ \0 ‟的 ASCII 值 00。
纯文本
复制



  • #include <reg52.h>

  • bit cmdArrived = 0; //命令到达标志,即接收到上位机下发的命令

  • unsigned char cmdIndex = 0; //命令索引,即与上位机约定好的数组编号

  • unsigned char cntTxd = 0; //串口发送计数器

  • unsigned char *ptrTxd; //串口发送指针


  • unsigned char array1[] = "1-Hello!\r\n";

  • unsigned char array2[] = {'2', '-', 'H', 'e', 'l', 'l', 'o', '!', '\r', '\n'};

  • unsigned char array3[] = {51, 45, 72, 101, 108, 108, 111, 33, 13, 10};

  • unsigned char array4[] = "4-Hello!\r\n";


  • void ConfigUART(unsigned int baud);

  • void main(){

  •     EA = 1; //开总中断

  •     ConfigUART(9600); //配置波特率为 9600


  •     while (1){

  •         if (cmdArrived){

  •             cmdArrived = 0;

  •             switch (cmdIndex){

  •                 case 1:

  •                     ptrTxd = array1; //数组 1 的首地址赋值给发送指针

  •                     cntTxd = sizeof(array1); //数组 1 的长度赋值给发送计数器

  •                     TI = 1; //手动方式启动发送中断,处理数据发送

  •                     break;

  •                 case 2:

  •                     ptrTxd = array2;

  •                     cntTxd = sizeof(array2);

  •                     TI = 1;

  •                     break;

  •                 case 3:

  •                     ptrTxd = array3;

  •                     cntTxd = sizeof(array3);

  •                     TI = 1;

  •                     break;

  •                 case 4:

  •                     ptrTxd = array4;

  •                     cntTxd = sizeof(array4) - 1; //字符串实际长度为数组长度减 1

  •                     TI = 1;

  •                     break;

  •                 default:

  •                     break;

  •             }

  •         }

  •     }

  • }

  • /* 串口配置函数,baud-通信波特率 */

  • void ConfigUART(unsigned int baud){

  •     SCON = 0x50; //配置串口为模式 1

  •     TMOD &= 0x0F; //清零 T1 的控制位

  •     TMOD |= 0x20; //配置 T1 为模式 2

  •     TH1 = 256 - (11059200/12/32)/baud; //计算 T1 重载值

  •     TL1 = TH1; //初值等于重载值

  •     ET1 = 0; //禁止 T1 中断

  •     ES = 1; //使能串口中断

  •     TR1 = 1; //启动 T1

  • }

  • /* UART 中断服务函数 */

  • void InterruptUART() interrupt 4{

  •     if (RI){ //接收到字节

  •         RI = 0; //清零接收中断标志位

  •         cmdIndex = SBUF; //接收到的数据保存到命令索引中

  •         cmdArrived = 1; //设置命令到达标志

  •     }

  •     if (TI){ //字节发送完毕

  •         TI = 0; //清零发送中断标志位

  •         if (cntTxd > 0){ //有待发送数据时,继续发送后续字节

  •             SBUF = *ptrTxd; //发出指针指向的数据

  •             cntTxd--; //发送计数器递减

  •             ptrTxd++; //发送指针递增

  •         }

  •     }

  • }