毕设做网站太简单,太平洋手机,提升学历方式,搜索引擎优化方法案例开始之前推荐一个电路学习软件#xff0c;这个软件笔者也刚接触。名字是Circuit有在线版本和不在线版本#xff0c;这是笔者在B站看视频翻到的。
Paul Falstadhttps://www.falstad.com/这是地址。
离线版本在网站内点这个进去 根据你的系统下载你需要的版本红线的是windows…开始之前推荐一个电路学习软件这个软件笔者也刚接触。名字是Circuit有在线版本和不在线版本这是笔者在B站看视频翻到的。
Paul Falstadhttps://www.falstad.com/这是地址。
离线版本在网站内点这个进去 根据你的系统下载你需要的版本红线的是windows版本。它的使用说明都是英文如果是英文苦手可以使用QQ浏览器自带网页翻译或者使用翻译工具百度或者搜狗它有个文件翻译功能可以翻译整个文件。虽然有些翻译的不正确总比没有的好。 这期浅述下C语言指针与串口通信的一些功能。在学C之前笔者就听过C指针的恶名这次写程序的时候也确实狠狠的体验了一把。笔者的C也是初学目前是51特攻。除了之前博文使用的语句外笔者对C的其他用语句没有过多涉及或者说基本没有涉及。因此为了写这个程序笔者又在B站大学突击了一下最终是完成了。程序是简单的但是完成这个程序花了笔者不少时间。
言归正转
变量的地址 要研究指针得先来深入理解内存地址这个概念。打个比方整个内存就相当于一个拥有很多房间的大楼每个房间都有房间号比如从101、102、103一直到NNN。房间号就是房间的地址相对应的内存中的每个单元也都有自己的编号比如从0x000x010x02一直到0xNN同样可以说这些编号就是内存单元的地址。房间里可以住人对应的的内存单元里就可以“住进”变量了。 假如1位名叫A先生的顾客住进101的房间就可以说101就是A先生的住址。同样一个名为x的变量住在编号为0x00的内存单元中就可以说0x00是变量x的地址。
基本的内存单元是字节英文单词为Byte。通俗的讲是1个地址单元只能存储8位数据就好比一间客房只能住1个人而这个房间号或者地址名可以是8位、16位的或者32位的这个一般受限于地址总线51单片机的地址总线是16根因此它的取值范围是0x0000-0xFFFF因此它的寻址空间最大是64KB51单片机的内存RAM的地址范围是0x00-0xFF也就是它只需8根地址总线寻址且可拓展的外部RAM最大空间为64KB
看图不同类型元素在地址中存储 假设要存储char型的变量 a和b因为a和b只有1个字节的大小因此它只需要一个存储单元就能存下数据。 定义1个unsigned int型的变量c 0x0A0B它的大小有两个字节因此需要两个存储单元。那么变量C的低字节序内容0B是存储在内存空间中的低地址还是高地址即如图是0x02还0x03 这个问题是由所用C编译器与单片机构架共同决定的单片机类型不同就有可能不同。Keil51单片机的环境下0x02存的是高字节内容0x03存的是低字节内容0B。同理long型的d储存需要4个存储单元。这种存储方式在C语言中叫大端字节序。与之相反常用的VS编程软件储存方式是小端字节序。
标准的Big-Endian和Little-Endian的定义如下 a) Little-Endian 小端 就是低位字节排放在内存的低地址端高位字节排放在内存的高地址端。 b) Big-Endian 大端 就是高位字节排放在内存的低地址端低位字节排放在内存的高地址端。 实际生活中要寻找1个人有两种方式一种是通过它的名字来找人还有第二种方式就是通过它的住址来找人。在派出所的户籍管理系统中输入小明的家庭住址系统就会自动指向小明的相关信息。那么在C语言中要访问一个变量同样有两种方式一种是通过变量名来访问另一种自然就是通过变量的地址来访问。 在C语言地址就等同于指针变量的地址就是变量的指针。举个例子 unsigned char a 0;//定义了变量a
unsigned char* ptr a; //定义1个char型的指针变量这个指针变量ptr的值是变量a的地址
虽然这个ptr指针指向的是变量a的地址作为全局变量a程序开始运行后 编译系统会自动分配变量的地址对于变量a来说这个地址是固定但是对于指向变量的指针变量地址ptr它是可以被操作的即左移或者右移。那么它移动一次的距离由指针类型决定的。比如上述的char*就是代表指针移动一次跨过一个存储单元int*就代表指针移动一次跨过两个储存单元。注这只在51单片机中是这样的,电脑端VS软件int型是4个字节的。 定义一个int型的变量如上图c0x0A0B它的存储单元有两个。那么它的指针变量是指向哪个存储单元它指向的一定是该变量的低地址这是由C语言本身所决定的规则即上图的0x02变量c低地址0x02中存放是高字节内容0x0A。这和你是哪种指针类型没有关系它不会因为你是int型的指针就指向两个地址注这里仅只描述地址的位置它操作1次的范围还是两个存储空间它执行的内容是地址本身的内容和地址1后指向的内容上图long型的变量d它的ulong*p指针指向的也是低地址0x04。
举个例子变量B 0x12345678 一个long型的变量
如果我们定义1个char*的指针变量ptr。
即 unsigned char * ptr B; 我们用printf打印这个 *ptr指向的内容在小端序的主机中它的值是78H如果用51单片机它是大端序它的值是12H.
如果我们定义1个int*的指针变量ptr
即 unsigned int * ptr B 我们用printf打印这个 *ptr指向的内容在小端序的主机中它的值是5678H如果用51单片机它是大端序它的值是1234H.
如果我们定义1个long*的指针变量ptr
即 unsigned int * ptr B 我们用printf打印这个 *ptr指向的内容在小端序的主机中它的值是12345678H如果用51单片机它是大端序它的值是12345678H.
如果我们把变量B0x12345678直接赋值给SBUF缓冲寄存器不做其他的操作即只传输1次。问这个操作在串口助手STC-ISP接收缓冲区最后显示的值是什么后续笔者会给出完整的测试程序。
void main()
{unsigned long B 0x12345678;unsigned char* p B;unsigned char* ptr B;unsigned long* ptr1 B;SBUF 0x12345678; SBUF B; SBUF *P;SBUF *ptr;SBUF *ptr1;
}void interruptUART() interrupt 4{if(RI){RI 0;}if(TI){TI 0;}}从题目上看着好像是个信息传递的问题即对于一个多字节的的变量来说它在传输的过程中是先传递低字节的内容呢还是高字节的内容呢或者是先传输低地址的内容还是高地址的内容呢如果你涉及过着方面的内容肯定知道一些相关的内容可能在考虑什么网络传输字节序和主机传输字节序什么什么得。
先说结果
SBUF 0x12345678; 它最后在接收缓冲区的值是 78H
SBUF B;它最后在接收缓冲区的值是 78H
从上面两个结果上来看它好像是先传输了低字节即高地址的内容。
char型指针 SBUF *P; 它最后在接收缓冲区的值是 12H int型指针 SBUF *ptr; 它最后在接收缓冲区的值是 34H long型指针 SBUF *ptr1;它最后在接收缓冲区的值是 78H
可以看到用三种不同的指针它发送的结果都不一样。笔者陈述下这个问题可能不是非常正确只是笔者目前的理解。
1对于串口软件STC-ISP,它的工作原则是我先接收到什么数据我就先显示什么数据并且显示的数据在接收缓冲区里从左往右依次排序。即左边的数据接收到的时间是早于右边的数据:它显示的最小单元是1个字节即8为数据。
2SBUF它只是1个8位缓冲寄存器因此对于51单片机如果你1次要传输多字节的内容它仅会传输8位数据后面的数据它就中止传输了抛掉了。而且传输的是该数据的低8位有点像强制类型转换。如果说它是1个16位的缓冲寄存器对于变量BSBUF B他的结果就会是5678H。
3对于char型指针它的操作权限只有一个字节鉴于是大端序因此该指针操作一次的内容是12H刚好SBUF是8位寄存器可以完全的显示出来。 对于int型指针它的操作权限是两个字节鉴于是大端序因此该指针操作一次的内容是1234H应用陈述2的结论它结果就34H。 对于longt型指针它的操作权限是4个字节鉴于是大端序因此该指针操作一次的内容是12345678H应用陈述2的结论它结果就78H。 因此对于51单片机我们在传输的时候要控制每次传输的内容是1个字节不要超出了。而且无论它是用什么协议的程序是我们自己设计作为程序员的我们自己知道先发送的是哪些内容。
指向数组元素的指针 所谓指向数组元素的指针其本质还是变量的指针因为数组中的每个元素其实都可以直接看成是1个变量 所以指向数组元素的指针也就是变量的指针。
定义1个数组元素
unsigned char number[10] {0,1,2,3,4,5,6,7,8,9}; unsigned char *p number[0];它也可以写成
unsigned char* p number; 数组元素名其实就代表了数组元素的首地址。整个数组元素是在内存空间中连续存储的那么首元素是存在高位第地址还是低位地址呢对于数组、字符串来说首元素就相当于高字节。 数组、字符串或者文本它们的首元素都是存在低位地址的无论是大端序还是小端序它和单纯的变量存储是不一样的的。这个是笔者暴论可能也存在不一样的PC主机VS软件是小端序但它的首地址也是放在低位的因为大端序是符合我们这种自然人阅读习惯的
1定义1个Int型变量L0x1134;
定义1个int的指针指向这个变量unsigned int*p l;
然后再定义1个char型的变量K 最后把指针P指向的内容存入K中 k *p问K的值是多少
结果 K 0x34;这其实是强制类型转换了把int的变量存入char型的变量地址它只存入低字节的内容和大小端序没什么关系。
2还是这个变量把这指针变量改成char型unsigned char*p l; 问现在K的值是多少
结果K 0x11char型指针指向的是变量L的低位地址低位地址存储的是0x11加上char型指针的权限因此结果是0x11。
3还是这个变量指针变量依然是char型现在把K定义为1个Int型的数问现在K的值是多少
结果K0x0011;11H存放在高位地址因为51单片机是大端序。
那么定义1个数组unsigned char array4[8] {0x1,0x23,0x123,0x1234,0x12345,6,7,8};首先可以看到很多数组元素已经不符合数组定义的类型了。看下代码
#include reg52.hbit cmdArrived 0; //命令到达标志即接收到上位机下发的命令
unsigned char cmdIndex 0;
unsigned char *ptrTxd;
unsigned char cntTxd 0;unsigned char array1[1] {1};
unsigned char array2[2] {1,2};
unsigned char array3[4] {0x11,0x21,0x31,0x41};
unsigned char array4[8] {0x1,0x23,0x123,0x1234,0x12345,6,7,8};
unsigned int l 0x1134;
unsigned char*p l;
unsigned int k ;
void ConfigUART(unsigned int baud);
void main()
{EA 1; //开总中断k *p;ConfigUART(9600); //配置波特率为9600while (1){if(cmdArrived){cmdArrived 0;switch(cmdIndex){case 1:ptrTxd array1;cntTxd sizeof(array1);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; } }}
}void ConfigUART(unsigned int baud)
{SCON 0x50; //配置串口为模式1TMOD 0x0F; //清零T1的控制位TMOD | 0x20; //配置T1为模式2TH1 256 - (11059200/12/32)/baud; //计算T1重载值TL1 TH1; //初值等于重载值ET1 0; //禁止T1中断ES 1; //使能串口中断TR1 1; //启动T1
}void InterruptUART() interrupt 4
{if (RI) //接收到字节{RI 0; //清零接收中断标志位cmdIndex SBUF;cmdArrived 1; }if (TI) //字节发送完毕{TI 0; //清零发送中断标志位if(cntTxd 0){SBUF *ptrTxd;cntTxd--;ptrTxd;}}
}
这个代码的功能就是自动计算数组元素的长度并把数组元素1个个发送到串口助手的接收区显示出来。操作方式就是在单字符串发送区用16进制发送数字2对函数来说就是使能了case 2
因此显示出来的数据是数组2的元素。sizeof() 是一个判断数据类型或者表达式长度的运算符。 问如果发送数字4显示的数组元素是什么
原数组 unsigned char array4[8] {0x1,0x23,0x123,0x1234,0x12345,6,7,8};结果是 不知道这个结果是否符合读者你们的计算结果。首先呢这是char类型的数组如果它存入的数据所占的内存空间大于char的内存空间它会先发生强制类型转换对于这个数组来说它就只存8位数多余的就会被抛掉然后再传输数据因此这个强制类型转换后的结果是
unsigned char array4[8] {0x1,0x23,0x23,0x34,0x45,6,7,8}这就是串口助手显示出来的数据。
问如果把数组的类型改成int型即 unsigned int array4[8] {0x1,0x23,0x123,0x1234,0x12345,6,7,8}那么最后显示出来的结果是什么
首先它会报警提示指向不同对象的指针不过不影响工作。 结果是这个我们分析下为什么会是这个结果。首先数组类型从char变成了int型代表着它的存储空间扩大了1倍因此sizeof()函数计数的结果翻倍了即cntTxd的值是原先的2倍。所以它发送的值变成了16个又因为是char型的指针因此它操作1次的内容是1个字节操作1次越过的地址也是1个因此它的结果就是把数组里的元素按照地址从低到高全部发出来了因为数组所占的地址刚好是16个。并且对于long型的0x12345它显示的数是23H45H这个转换结果也符合之前的陈述。
问如果把ptrTxd指针改成int型数组也是Int型它显示的结果会是什么 不知道这个结果是不是符合你的推论显然前8个的显示结果和第一次的显示结果是一样的。我们分析一下因为该数组还是int型的因此数组求出的cntTxd值依然是16是之前两倍.int型的指针1次操作的内容是两个字节且跨过的地址也是两个前面例子已经展示过SBUF它是个8位的寄存器因此它只传输了低字节的内容。而操作8次就到达了数组的边界因而后面8次的内容就都是数组之外连续的高位地址存储的内容。这就越界了指针也变成了野指针。从这里你就可以看到指针是非常的自由同样的用好它你就需要注意每个细节。如果我们不越界计数这里除以2它就不会越界了即对char类型的元素sizeof()函数求出的值刚好是元素个数对与其他类型的元素的值的应用应该要配合指针的类型来确定。
字符数组和字符指针 在程序运行过程中其值不能被改变的量称之为常量。常量分为不同类型有整型常量如1、2、3浮点型常量3.14、0.56、-4.8字符型常量‘a’‘b’‘1’用单引号括起来字符串常量abcd,1234,abc123,用单引号括起来的表示1个字符用双引号括起来的是1个字符串。
字符串常量是常量不能被修改的因此如果你要对字符串处理应该开辟字符数组。 字符串常量在内存中按顺序逐个储存字符串中字符的ASCII码值并且特别注意最后1个字符‘\0’,它是字符串的结束标志它是系统自动添的。也就是说abcd这个字符串有5个字符但是对于字符数组char arr[] {a,c,c,d};系统是不会自动添加‘\0’的。在字符串中空格也是字符因此要注意。 笔者前面的博文有一个关于电子密码锁的功能密码是2024 键码区别长短按键是1010。如果想要通过串口通信交互改掉密码必然面对一些问题如何识别输入的数据是用于修改密码的。对此程序内要有明确的规定。
因此使能这个功能的格式是 先输入指令码 再输入数据码 因此必然要对指令码进行区别这个指令码是干什么的呢正常状态下程序是锁定密码修改的为此必须给他一个指令告诉它打开修改密码的功能。这个指令可以是数字字母字符串只要和程序约定好都行。单个数字字母这些虽然都可以但是不太符合自然人使用习惯而且容易误触。因此使用字符串会是一个比较好的方式。 比如我就设定1个字符串setkey作为密码锁开启的指令那么怎么在串口通信中实现这个功能呢:
1:需要开辟一个内存空间用于存储6个字节因此可以开辟1个char的数组arrbuf[6]这个可以用于存放通过串口通信传输过来的数据。2:程序怎么辨别接收的字符串已经发送完毕了C语言在操作字符串的时候它会自动加字符串结束码\0,很多库函数在操作字符串的时候都是以这个字符来判断的。但是串口通信助手STC-ISP软件,在“单字符串发送区”向单片机发送字符串的时候它是不会自动加‘\0’,因此需要手动添加字符串结束码本案添加的是。即设定的开启指令是setkey!当然你也可以不添加直接以字符串最后的字符y作为结束码判断但是这个代码扩展性就低了也不怎么规范。3很多字符串的库函数工作方式都是通过判断字符串结束码‘\0’来实现功能的因此一开始开辟的存储空间需要扩大最少7个多开辟几个也是没问题的。本案采用的strcmp(),字符串比较函数。这样就能确定输入的字符串是不是预设的指令了。这就完成了指令部分4字符串怎么变成整数如“2024”字符串变成整数2024本案采用的还是库函数atoi()来实现当然这个也是要加入结束码。如此就是实现了密码修改的最初要求数据的正确的获得。获得这些值后应该是可以通过修改下程序功能实现串口通信修改密码的。 看代码
# includereg52.h
# includestring.h
# includestdlib.hbit cmdArrived 0;
unsigned char cmdindex 0; //命令索引即与上位机约定好的数组编号
unsigned char cntTxd 0; //串口发送计数数组字节个数
unsigned char *ptrTxd 0; //串口发送指针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;
unsigned char ErroyRpt[] Error!Please re-enter\r\n;
pdata unsigned char CorrectRpt[] Correct!Please enter a 4-digit password;
unsigned char WordSet[] setkey;
unsigned char* wd_set WordSet;pdata unsigned char Word[7] {2,0,2,4,\0,s,b};
pdata unsigned long RxdByte 0x12345678;
pdata unsigned int* y RxdByte;
unsigned char *ptrRxd Word; //串口接收指针
bit Rxd_Fin_Mark 0; //接收接收位标志
unsigned int vaule 0;//atoi(Word);
unsigned char* p vaule;
unsigned char x 0;
code unsigned char* k vaule;//备份指针地址用于指针初始化
pdata unsigned long l 0x010203;
unsigned char* s l;void ConfigUART(unsigned int baud); void main(){EA 1;ConfigUART(9600);//strcpy(array2,2-Hello!);while(1){if(strcmp(WordSet,Word) 0 Rxd_Fin_Mark 1){Rxd_Fin_Mark 0;ptrRxd Word[0];cmdArrived 1;cmdindex 8;}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 ;TI 1;break;case 5:// ptrTxd ErroyRpt;//cntTxd sizeof(ErroyRpt) ;SBUF *y;// TI 1;break;case 6:ptrTxd Word;cntTxd sizeof(Word);TI 1;break;case 7:vaule atoi(Word); cntTxd sizeof(vaule); ptrTxd k;//指针初始化 TI 1;break;case 8:ptrTxd CorrectRpt;cntTxd sizeof(CorrectRpt) ;TI 1; break;case 9:Rxd_Fin_Mark 1;default:break;}}}} /*串口配置函数 buad为通信波特率 */void ConfigUART(unsigned int baud){SCON 0x50;TMOD 0x0F;//定时器控制寄存器T1区域清零T0区域不动TMOD |0x20;//工作在模式2TH1 256 - (11059200/12/32)/baud;TL1 TH1;ET1 0; //禁止使能T1中断ES 1; //使能串口中断TR1 1;}/*UART中断服务函数 */void interruptUART() interrupt 4{static unsigned char i 0; //定义数组指针在数组的索引if(RI){ if(SBUF 0 SBUF9) //输入0-9输出相应的语句{cmdindex SBUF; //把数据赋值给命令索引cmdArrived 1; //使能命令语句后后续的数据传输就停止使能}if(SBUF 32) //如果输入的是空格键则指针回到数组的首地址{i 0; //初始化ptrRxd Word;}else //不是上述的输入都存入word数组数组最大储存6个字节的数据{if(cmdArrived 0){//if((SBUF 0 SBUF 9 ) | SBUF !)if(SBUF !){*ptrRxd \0; //把字符串结束位符号发给相应的数组地址ptrRxd Word;//初始化i 0; Rxd_Fin_Mark 1; }else{* ptrRxd SBUF; //把数据存入指针指向的地址区域ptrRxd; //指针数加1i; //指针索引加1 }if(i 7) //这里不能用ptrRxd 6判断因为这是数组地址它的值不是从0开始的{ //这里i的值等于Word数组的长度数组内存扩充了注意更改。i 0;ptrRxd Word;//初始化}} }RI 0;}if(TI){if(cntTxd 0) //发送的数据判断{SBUF *ptrTxd; //把相应地址的数据发送cntTxd--; //每发送一个数据计数减1ptrTxd;//每发送1个数据相应的地址移动1位 }TI 0; }}
写的有点乱不过这本身就是个测试程序有些变量可能无用了是之前笔者测试别的用的。如果有兴趣可以拷贝过去自己测试一下这个程序使用的时候有几个注意点。
1程序烧录完成后必须要关机再开机测试程序才正常因为在烧录过程中就会向串口写入一堆其他的数据因此要想正确使用必须关机再开机就正常了。
2
1输入16进制的1-9 根据你想要的结果用16进制显示或者字符显示。
2要想输入字符串就用字符格式发送。
3字符串结尾要加上。
4如果在使用过程中找不到指针位置可以输入字符‘’或者字符‘空格’来使指针初始化位置
主意输入空格的结果只是让光标移了一位但是确实是输入了空格字符然后点发送字符/数据按钮指针就初始化执向数组的首元素了。
5:这个函数的指令长度不一定要7位的它是不固定的。你预设设置为set这也是可以实现的因为库函数在使用的时候遇到‘\0’就结束后后面是什么字符它不管的。所以这就很方便扩展移植。
然后向分享1个关于kei1memery窗口的相关情况。如果有小伙伴知道这个原因能否留言告诉我一下。keil5memery内容查找_哔哩哔哩_bilibili
测试程序操作示意
测试程序操作_哔哩哔哩_bilibili
对于这次程序笔者总结了一点心德
1在使用指针的时候要注意是否要初始化要时刻注意指针的边界问题以及类型问题。
2对于串口通信发送端在发送数据前要指针初始化。
3对于串口通信接收端在接收完字符串数据指针要初始化。
4字符串结束码是个好标志。