第11章 指针基础与串口实用程序(11.1 11.2)

内容分享10小时前发布 卢倪Ni
0 1 0

指针是C语言最核心的部分,而UART串口通信是单片机最常用的一种通信方式。因此这两部分内容,除了在第10章进行简单的介绍外,本章还需要进一步加深学习,用实用的例子来不断增强对于这两部分内容的理解和应用能力。

11.1 指向数组元素的指针

11.1.1 指向数组元素的指针和运算法则

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

指向数组元素的指针不难,但很常用。

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号元素。

指针本身,也可以进行几种简单的运算,这几种运算对于数组元素的指针来说应用最多。

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

2、指针和整数可以直接进行加减运算。列如还是前边讲的指针p和数组number,如果p = &number[0],那么p+1就指向了number[1],p+9就指向了number[9]。当然了,如果p = &number[9],p-9也就指向了number[0]。

3、两个指针变量在必定条件下可以进行减法运算。如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[i]和*(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]。加减运算和一维数组是类似的,不再详述。

11.1.2 指向数组元素指针的实例

在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++; //发送指针递增

}

}

}

采用逻辑分析仪将4次收发数据全部抓出来,直观的做一下对比,如图11-1所示。

第11章 指针基础与串口实用程序(11.1 11.2)

图11-1 逻辑分析仪抓取串口数据数据

11.2 字符数组和字符指针

11.2.1 常量和符号常量

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

整型和浮点型常量直接写的数字,字符型常量用单引号来表明一个字符,用双引号来表明一个字符串,尤其要注意‘a’和“a”是不一样的,后边会详细介绍。

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

直接常量:直接以值的形式表明的常量称之为直接常量。上述举例都是直接常量。

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

列如,可以把3.14取名为PI(即π)。再列如,前边的串口程序采用的波特率是9600,如果用符号常量来进行提前声明的话,那要修改成其它速率的话,就不用在程序中找9600修改了,直接修改声明处就可以了,两种方法举例说明。

1.用const声明。列如在程序开始位置定义一个符号常量BAUD。

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

如const unsigned int BAUD = 9600; /*注意结尾有个分号*/

就可以在程序中直接把9600全部采用BAUD替换,这样如果要改波特率的话,直接在程序开头位置改一下这个值就可以了。

2.用预处理命令#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中。

整型常量和浮点型常量比较简单,整型直接写数字,十进制如128,前边0x开头的表明是十六进制0x80,浮点型直接写带小数点的数据就可以了。

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

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

除了上述这些字符之外,还有一些特殊字符,它们一些是无形的,像回车符、换行符这些都是看不到的,还有一些像’’这类字符它们已经有特殊用途了。针对这些特殊符号,为了可以让它们正常进入到程序代码中,C语言就规定了转义字符,它是以反斜杠()开头的特定字符序列,让它们来表明这些特殊字符,列如用
来代表换行。用一个简单表格来说明一下常用的转义字符的意思,如表11-1所示。

表11-1 常用转义字符及含义

字符形式

含义

换行

横向跳格(相当于Tab)

v

竖向跳格

退格

光标移到行首

\

反斜杠字符‘’

单引号字符

双引号字符

f

走纸换页

空值

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

还有就是字符串中的空格,也是一个字符,列如“welcome to www.qdkingst.com”一共占了28个字节的空间。其中23个字母,2个‘.’,2个 ‘ ’(空格字符)以及一个‘’。

11.2.2 字符和字符串数组实例

定义4个数组,通过演示程序对比字符串、字符数组、常量数组的区别。

unsigned char array1[] = “1-Hello!
“;

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

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

unsigned char array4[] = “4-Hello!
“;

在串口调试助手下,发送十六进制的1、2、3、4,使用字符形式显示,分别往电脑上送这4个数组中对应的那个数组。程序只在起始位置做了区分,其它均没有区别。一方面通过串口调试助手观察,另外通过逻辑分析仪进行对比。

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

#include <reg52.h>

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

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

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

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

unsigned char array1[] = “1-Hello!
“;

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

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

unsigned char array4[] = “4-Hello!
“;

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++; //发送指针递增

}

}

}

采用逻辑分析仪将4次收发数据全部抓出来,其中串口助手下发用HEX模式,而接收用文本模式,也就是ASCII码格式显示,逻辑分析仪也是这样配置,如图11-2所示。

第11章 指针基础与串口实用程序(11.1 11.2)

图11-2 逻辑分析仪抓取串口字符和字符串信息

从图11-2可以看出,1比2、3、4多了一个结束符,其他内容4组数据是完全一致的。(CR就是
,LF就是

© 版权声明

相关文章

1 条评论

  • 头像
    废物点心 读者

    收藏了,感谢分享

    无记录
    回复