单片机学习笔记————指针的第一大好处,让一个函数可以封装多个相当于return语句返回的参数

proteus虚拟串口的实现:https://mp.csdn.net/console/editor/html/107251649

一、使用proteus绘制简单的电路图,用于后续仿真

单片机学习笔记————指针的第一大好处,让一个函数可以封装多个相当于return语句返回的参数_第1张图片

 

二、编写程序

  1 /********************************************************************************************************************
  2 ----    @Project:    Pointer
  3 ----    @File:    main.c
  4 ----    @Edit:    ZHQ
  5 ----    @Version:    V1.0
  6 ----    @CreationTime:    20200808
  7 ----    @ModifiedTime:    20200808
  8 ----    @Description:    
  9 ----    波特率是:9600 。
 10 ----    通讯协议:EB 00 55  XX YY  
 11 ----    通过电脑串口调试助手,往单片机发送EB 00 55 XX YY  指令,其中EB 00 55是数据头, XX是被除数,YY是除数。单片机收到指令后就会返回6个数据,最前面两个数据是第1种运算方式的商和余数,中间两个数据是第2种运算方式的商和余数,最后两个数据是第3种运算方式的商和余数。
 12 ----    比如电脑发送:EB 00 55 08 02
 13 ----    单片机就返回:04 00 04 00 04 00  (04是商,00是余数)
 14 ----    单片机:AT89C52
 15 ********************************************************************************************************************/
 16 #include "reg52.h"
 17 /*——————宏定义——————*/
 18 #define FOSC 11059200L
 19 #define BAUD 9600
 20 #define T1MS (65536-FOSC/12/500)   /*0.5ms timer calculation method in 12Tmode*/
 21 
 22 #define const_voice_short 19    /*蜂鸣器短叫的持续时间*/
 23 #define const_rc_size 10    /*接收串口中断数据的缓冲区数组大小*/
 24 
 25 #define const_receive_time 5    /*如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完,这个时间根据实际情况来调整大小*/
 26 
 27 /*——————变量函数定义及声明——————*/
 28 /*蜂鸣器的驱动IO口*/
 29 sbit BEEP = P2^7;
 30 /*LED*/
 31 sbit LED = P3^5;
 32 
 33 unsigned int uiSendCnt = 0;    /*用来识别串口是否接收完一串数据的计时器*/
 34 unsigned char ucSendLock = 1;    /*串口服务程序的自锁变量,每次接收完一串数据只处理一次*/
 35 unsigned int uiRcregTotal = 0;    /*代表当前缓冲区已经接收了多少个数据*/
 36 unsigned char ucRcregBuf[const_rc_size];    /*接收串口中断数据的缓冲区数组*/
 37 unsigned int uiRcMoveIndex = 0;    /*用来解析数据协议的中间变量*/
 38 
 39 unsigned int uiVoiceCnt = 0;    /*蜂鸣器鸣叫的持续时间计数器*/
 40 
 41 unsigned char ucBeiChuShu_1 = 0;    /* 第1种方法中的被除数 */
 42 unsigned char ucChuShu_1 = 1;    /* 第1种方法中的除数 */
 43 unsigned char ucShang_1 = 0;    /* 第1种方法中的商 */
 44 unsigned char ucYu_1 = 0;    /* 第1种方法中的余数 */
 45 
 46 unsigned char ucBeiChuShu_2 = 0;    /* 第2种方法中的被除数 */
 47 unsigned char ucChuShu_2 = 1;    /* 第2种方法中的除数 */
 48 unsigned char ucShang_2 = 0;    /* 第2种方法中的商 */
 49 unsigned char ucYu_2 = 0;    /* 第2种方法中的余数 */
 50 
 51 unsigned char ucBeiChuShu_3 = 0;    /* 第3种方法中的被除数 */
 52 unsigned char ucChuShu_3 = 1;    /* 第3种方法中的除数 */
 53 unsigned char ucShang_3 = 0;    /* 第3种方法中的商 */
 54 unsigned char ucYu_3 = 0;    /* 第3种方法中的余数 */
 55 
 56 /**
 57 * @brief  定时器0初始化函数
 58 * @param  无
 59 * @retval 初始化T0
 60 **/
 61 void Init_T0(void)
 62 {
 63     TMOD = 0x01;                    /*set timer0 as mode1 (16-bit)*/
 64     TL0 = T1MS;                     /*initial timer0 low byte*/
 65     TH0 = T1MS >> 8;                /*initial timer0 high byte*/
 66 }
 67 
 68 /**
 69 * @brief  串口初始化函数
 70 * @param  无
 71 * @retval 初始化T0
 72 **/
 73 void Init_USART(void)
 74 {
 75     SCON = 0x50;
 76     TMOD = 0x21;                    
 77     TH1=TL1=-(FOSC/12/32/BAUD);
 78 }
 79 
 80 /**
 81 * @brief  外围初始化函数
 82 * @param  无
 83 * @retval 初始化外围
 84 * 让数码管显示的内容转移到以下几个变量接口上,方便以后编写更上一层的窗口程序。
 85 * 只要更改以下对应变量的内容,就可以显示你想显示的数字。
 86 **/
 87 void Init_Peripheral(void)
 88 {
 89     ET0 = 1;/*允许定时中断*/
 90     TR0 = 1;/*启动定时中断*/
 91     TR1 = 1;
 92     ES = 1;    /*允许串口中断*/
 93     EA = 1;/*开总中断*/  
 94 }
 95 
 96 /**
 97 * @brief  初始化函数
 98 * @param  无
 99 * @retval 初始化单片机
100 **/
101 void Init(void)
102 {
103     LED  = 0;
104     Init_T0();
105     Init_USART();
106 }
107 /**
108 * @brief  延时函数
109 * @param  无
110 * @retval 无
111 **/
112 void Delay_Long(unsigned int uiDelayLong)
113 {
114    unsigned int i;
115    unsigned int j;
116    for(i=0;i)
117    {
118       for(j=0;j<500;j++)  /*内嵌循环的空指令数量*/
119           {
120              ; /*一个分号相当于执行一条空语句*/
121           }
122    }
123 }
124 /**
125 * @brief  延时函数
126 * @param  无
127 * @retval 无
128 **/
129 void Delay_Short(unsigned int uiDelayShort)
130 {
131   unsigned int i;
132   for(i=0;i)
133   {
134          ; /*一个分号相当于执行一条空语句*/
135   }
136 }
137 
138 /**
139 * @brief  串口发送函数
140 * @param  unsigned char ucSendData
141 * @retval 往上位机发送一个字节的函数
142 **/
143 void eusart_send(unsigned char ucSendData)
144 {
145     ES = 0;    /* 关串口中断 */
146     TI = 0;    /* 清零串口发送完成中断请求标志 */
147     SBUF = ucSendData;    /* 发送一个字节 */
148 
149     Delay_Short(400);    /* 每个字节之间的延时,这里非常关键,也是最容易出错的地方。延时的大小请根据实际项目来调整 */
150     
151     TI = 0;    /* 清零串口发送完成中断请求标志 */
152     ES = 1;    /* 允许串口中断 */
153 }
154 
155 /**
156 * @brief  第1种方法
157 * @param  无
158 * @retval 
159 *  第1种方法,用不带参数返回的空函数,这是最原始的做法,也是我当年刚毕业
160 * 就开始做项目的时候经常用的方法。它完全依靠全局变量作为函数的输入和输出口。
161 * 我们要用到这个函数,就要把参与运算的变量直接赋给对应的输入全局变量,
162 * 调用一次函数之后,再找到对应的输出变量,这些输出变量就是我们要的结果。
163 * 在本函数中,被除数ucBeiChuShu_1和除数ucChuShu_1就是输入全局变量,
164 * 商ucShang_1和余数ucYu_1就是输出全局变量。这种方法的缺点是阅读不直观,
165 * 封装性不强,没有面对用户的输入输出接口,
166 **/
167 void chu_fa_yun_suan_1(void)    /* 第1种方法 求商和余数 */
168 {
169     if(ucChuShu_1 == 0)    /* 如果除数为0,则商和余数都为0 */
170     {
171         ucShang_1 = 0;
172         ucYu_1 = 0;
173     }
174     else
175     {
176         ucShang_1 = (ucBeiChuShu_1) / (ucChuShu_1);    /* 求商 */
177         ucYu_1 = (ucBeiChuShu_1) % (ucChuShu_1);    /* 求余数 */
178     }
179 }
180 
181 /**
182 * @brief  第2种方法
183 * @param  无
184 * @retval 
185 * 第2种方法,用return返回参数和带输入形参的函数,这种方法已经具备了完整的输入和输出性能,
186 * 比第1种方法直观多了。但是这种方法有它的局限性,因为return只能返回一个变量,
187 * 如果要用在返回多个输出结果的函数中,就无能为力了。比如本程序,就不能同时输出
188 * 商和余数,只能分两个函数来做。如果要在一个函数中同时输出商和余数,该怎么办?
189 * 这个时候就必须用指针了,也就是我下面讲到的第3种方法。
190 **/
191 unsigned char get_shang_2(unsigned char ucBeiChuShuTemp, unsigned char ucChuShuTemp)
192 {
193     unsigned char ucShangTemp;
194     if(ucChuShuTemp == 0)    /* 如果除数为0,则商为0 */
195     {
196         ucShangTemp = 0;
197     }
198     else
199     {
200         ucShangTemp = (ucBeiChuShuTemp) / (ucChuShuTemp);
201     }
202     return ucShangTemp;    /* 返回运算后的结果 商 */
203 }
204 
205 unsigned char get_yu_2(unsigned char ucBeiChuShuTemp, unsigned char ucChuShuTemp)
206 {
207     unsigned char ucYuTemp;
208     if(ucChuShuTemp == 0)    /* 如果除数为0,则余数为0 */
209     {
210         ucYuTemp = 0;
211     }
212     else
213     {
214         ucYuTemp = (ucBeiChuShuTemp) % (ucChuShuTemp);
215     }
216     return ucYuTemp;    /* 返回运算后的结果 余数 */
217 }
218 
219 /**
220 * @brief  第3种方法
221 * @param  无
222 * @retval 
223 * 第3种方法,用带指针的函数,就可以顺心所欲,不受return的局限,想输出多少个
224 * 运算结果都可以,赞一个!在本函数中,ucBeiChuShuTemp和ucChuShuTemp是输入变量,
225 * 它们不是指针,所以不具备输出接口属性。*p_ucShangTemp和*p_ucYuTemp是输出变量,
226 * 因为它们是指针,所以具备输出接口属性。
227 **/
228 void chu_fa_yun_suan_3(unsigned char ucBeiChuShuTemp, unsigned char ucChuShuTemp, unsigned char *p_ucShangTemp, unsigned char *p_ucYuTemp)    
229 {
230     if(ucChuShuTemp == 0)    /* 如果除数为0,则商和余数都为0 */
231     {
232         *p_ucShangTemp = 0;
233         *p_ucYuTemp = 0;
234     }
235     else
236     {
237         *p_ucShangTemp = (ucBeiChuShuTemp) / (ucChuShuTemp);
238         *p_ucYuTemp = (ucBeiChuShuTemp) % (ucChuShuTemp);
239     }
240 }
241 
242 /**
243 * @brief  串口服务程序
244 * @param  无
245 * @retval 
246 * 以下函数说明了,在空函数里,可以插入很多个return语句。
247 * 用return语句非常便于后续程序的升级修改。
248 **/
249 void usart_service(void)
250 {
251     // /*如果超过了一定的时间内,再也没有新数据从串口来*/
252     // if(uiSendCnt >= const_receive_time && ucSendLock == 1)
253     // {
254     // 原来的语句,现在被两个return语句替代了
255     if(uiSendCnt < const_receive_time)    /* 延时还没超过规定时间,直接退出本程序,不执行return后的任何语句。 */
256     {
257         return;    /* 强行退出本子程序,不执行以下任何语句 */
258     }
259     if(ucSendLock == 0)    /* 不是最新一次接收到串口数据,直接退出本程序,不执行return后的任何语句。 */
260     {
261         return;    /* 强行退出本子程序,不执行以下任何语句 */
262     }
263 /*
264  * 以上两条return语句就相当于原来的一条if(uiSendCnt>=const_receive_time&&ucSendLock==1)语句。
265  * 用了return语句后,就明显减少了一个if嵌套。
266  */
267     ucSendLock = 0;    /*处理一次就锁起来,不用每次都进来,除非有新接收的数据*/
268     /*下面的代码进入数据协议解析和数据处理的阶段*/
269     uiRcMoveIndex = 0;    /*由于是判断数据头,所以下标移动变量从数组的0开始向最尾端移动*/
270     // /*
271     // * 判断数据头,进入循环解析数据协议必须满足两个条件:
272     // * 第一:最大接收缓冲数据必须大于一串数据的长度(这里是5。包括2个有效数据,3个数据头)
273     // * 第二:游标uiRcMoveIndex必须小于等于最大接收缓冲数据减去一串数据的长度(这里是5。包括2个有效数据,3个数据头)
274     // */
275     // while(uiRcregTotal >= 5 && uiRcMoveIndex <= (uiRcregTotal - 5))    
276     // {
277     // 原来的语句,现在被两个return语句替代了
278     while(1)    /* 死循环可以被以下return或者break语句中断,return本身已经包含了break语句功能。 */
279     {
280         if(uiRcregTotal < 5)    /* 串口接收到的数据太少 */
281         {
282             uiRcregTotal = 0;    /*清空缓冲的下标,方便下次重新从0下标开始接受新数据*/
283             return;    /* 强行退出while(1)循环嵌套,直接退出本程序,不执行以下任何语句 */
284         }
285         if(uiRcMoveIndex > (uiRcregTotal - 5))    /* 数组缓冲区的数据已经处理完 */
286         {
287             uiRcregTotal = 0;    /*清空缓冲的下标,方便下次重新从0下标开始接受新数据*/
288             return;    /* 强行退出while(1)循环嵌套,直接退出本程序,不执行以下任何语句 */
289         }
290 /* 
291  * 以上两条return语句就相当于原来的一条while(uiRcregTotal>=5&&uiRcMoveIndex<=(uiRcregTotal-5))语句。
292  * 以上两个return语句的用法,同时说明了return本身已经包含了break语句功能,不管当前处于几层的内部循环嵌套,
293  * 都可以强行退出循环,并且直接退出本程序。
294  */
295         if(ucRcregBuf[uiRcMoveIndex + 0] == 0xeb && ucRcregBuf[uiRcMoveIndex + 1] == 0x00 && ucRcregBuf[uiRcMoveIndex + 2] == 0x55)
296         {
297             /* 第1种运算方法,依靠全局变量 */
298             ucBeiChuShu_1 = ucRcregBuf[uiRcMoveIndex + 3];
299             ucChuShu_1 = ucRcregBuf[uiRcMoveIndex + 4];
300             chu_fa_yun_suan_1();
301             eusart_send(ucShang_1);
302             eusart_send(ucYu_1);
303 
304             /* 第2种运算方法,依靠两个带return语句的返回函数 */
305             ucBeiChuShu_2 = ucRcregBuf[uiRcMoveIndex + 3];
306             ucChuShu_2 = ucRcregBuf[uiRcMoveIndex + 4];
307             ucShang_2 = get_shang_2(ucBeiChuShu_2, ucChuShu_2);
308             ucYu_2 = get_yu_2(ucBeiChuShu_2, ucChuShu_2);
309             eusart_send(ucShang_2);
310             eusart_send(ucYu_2);            
311             
312             /* 第3种运算方法,依靠指针 */
313             ucBeiChuShu_3 = ucRcregBuf[uiRcMoveIndex + 3];
314             ucChuShu_3 = ucRcregBuf[uiRcMoveIndex + 4];        
315 /*
316 * 注意,由于商和余数是指针形参,我们代入的变量必须带地址符号& 。比如&ucShang_3和&ucYu_3。
317 * 因为我们是把变量的地址传递进去的。
318 */
319             chu_fa_yun_suan_3(ucBeiChuShu_3, ucChuShu_3, &ucShang_3, &ucYu_3);
320             eusart_send(ucShang_3);
321             eusart_send(ucYu_3);    
322 
323             break;    /*退出while(1)循环*/
324         }
325         uiRcMoveIndex ++;    /*因为是判断数据头,游标向着数组最尾端的方向移动*/        
326     }
327     // }
328     uiRcregTotal = 0;    /*清空缓冲的下标,方便下次重新从0下标开始接受新数据*/
329     // }
330 }
331 /**
332 * @brief  定时器0中断函数
333 * @param  无
334 * @retval 无
335 **/
336 void ISR_T0(void)    interrupt 1
337 {
338     TF0 = 0;  /*清除中断标志*/
339     TR0 = 0; /*关中断*/
340 
341     if(uiSendCnt < const_receive_time)    /*如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完*/
342     {
343         uiSendCnt ++;    /*表面上这个数据不断累加,但是在串口中断里,每接收一个字节它都会被清零,除非这个中间没有串口数据过来*/
344         ucSendLock = 1;    /*开自锁标志*/
345     }
346 
347     if(uiVoiceCnt != 0)
348     {
349         uiVoiceCnt --;
350         BEEP = 0;
351     }
352     else
353     {
354         ;
355         BEEP = 1;
356     }
357 
358     TL0 = T1MS;                     /*initial timer0 low byte*/
359     TH0 = T1MS >> 8;                /*initial timer0 high byte*/
360       TR0 = 1; /*开中断*/    
361 }
362 
363 /**
364 * @brief  串口接收数据中断
365 * @param  无
366 * @retval 无
367 **/
368 void usart_receive(void)    interrupt 4
369 {
370     if(RI == 1)
371     {
372         RI = 0;
373         ++ uiRcregTotal;
374         if(uiRcregTotal > const_rc_size)
375         {
376             uiRcregTotal = const_rc_size;
377         }
378         ucRcregBuf[uiRcregTotal - 1] = SBUF;    /*将串口接收到的数据缓存到接收缓冲区里*/
379         uiSendCnt = 0;    /*及时喂狗,虽然main函数那边不断在累加,但是只要串口的数据还没发送完毕,那么它永远也长不大,因为每个中断都被清零。*/
380     }
381     else
382     {
383         TI = 0;
384     }
385 }
386 
387 /*————————————主函数————————————*/
388 /**
389 * @brief  主函数
390 * @param  无
391 * @retval 实现LED灯闪烁
392 **/
393 void main()
394 {
395     /*单片机初始化*/
396     Init();
397     /*延时,延时时间一般是0.3秒到2秒之间,等待外围芯片和模块上电稳定*/
398     Delay_Long(100);
399     /*单片机外围初始化*/    
400     Init_Peripheral();
401     while(1)
402     {
403         usart_service();
404     }
405 }

 

三、仿真实现

单片机学习笔记————指针的第一大好处,让一个函数可以封装多个相当于return语句返回的参数_第2张图片

你可能感兴趣的:(单片机学习笔记————指针的第一大好处,让一个函数可以封装多个相当于return语句返回的参数)