98

帖子

0

TA的资源

一粒金砂(高级)

61
 
sint27 发表于 2014-6-10 08:49
看了前面几节,写的很不错,对新手有很大帮助。
我的框架跟你的类似,不过我把计时全部放在定时中断处理中 ...

你在主程序里规定了每个时间分区的时间长度,这个结构表面上看起来工整漂亮,实际上这个结构很糟糕。在单片机裸奔的世界里,不要有“时间片”的概念,不要人为刻意模拟操作系统,记住,操作系统的调度方法在单片机的世界里,是负担和累赘,只有裸奔的速度才是最快的。主循环里应该时刻扫描各种相关标志位,这些标志位就相当于消息事件,哪些标识位满足要求就进去执行,执行完之后再退出来,就是这么简单。用switch语句可以把任何一个事件分解成多个小步骤,这个就是传说中万能的状态机思想。
此帖出自51单片机论坛
 

回复

1799

帖子

0

TA的资源

五彩晶圆(初级)

62
 
jianhong_wu 发表于 2014-6-10 12:56
你在主程序里规定了每个时间分区的时间长度,这个结构表面上看起来工整漂亮,实际上这个结构很糟糕。在单 ...

感谢你的回答,我会这么做是因为
1、涉及的大部分项目对所谓的信息扫描速度要求并不高,如果涉及到低功耗唤醒睡眠(对时间要求比较高)的情况,我也会用你所谓“裸奔”处理。
2、这样做的好处是利于各种延时计算,如按键扫描,LED闪烁,利用中断时间长度(1ms或者更低)的整数倍便于计算各种延时,而且条理清晰,代码维护简单。

我从事该行业不到2年,可能很多深层次的东西没有考虑清楚,我不知道单纯单片机裸奔速度快有什么特殊用途,否则像你介绍的那些章节大部分对运行速度没什么要求,或者你认为我那样做法会有什么样的隐患或缺点?望指明。

PS:我无法理解你所说的单片机最好不要考虑操作系统的思想,我以前熟悉的单片机上跑OSAL就是一种仿操作系统的做法,不知道你对此有什么看法
再次感谢你的回答!
此帖出自51单片机论坛
 
 
 

回复

450

帖子

43

TA的资源

一粒金砂(高级)

63
 
jianhong_wu 发表于 2014-6-10 12:42
我的看法给你恰恰相反。我认为结构体和宏定义用的越少,程序越简单清晰。除非有一些需要经常更改的参数, ...

如果你一直是一个人完成所有代码,确实差别不大
结构体和变量组合一样,比如那段开关的代码,几个开关的操作程序不是一模一样吗?
把那些开关相关的变量用结构体封装起来,以结构体为参数写个公用函数,以后增加开关就容易了,
不需要拷贝那个代码段,修改一组相关的变量名。
只要定义新的开关结构体,调用公用函数就行了,检查起来也方便
此帖出自51单片机论坛
 
个人签名一心一意,精益求精
 
 

回复

98

帖子

0

TA的资源

一粒金砂(高级)

64
 
sint27 发表于 2014-6-10 16:16
感谢你的回答,我会这么做是因为
1、涉及的大部分项目对所谓的信息扫描速度要求并不高,如果涉及到低功 ...

没有什么深层次的原因,只是凭我的直觉。我觉得很多人把单片机系统讲得太高深,太复杂了。时间片,任务切换调度 ,多线程,结构体,宏定义,大量使用指针等等,这些阅读起来很费劲。其实领悟到单片机的本质,很简单清晰的,不用玩那么多花俏。
此帖出自51单片机论坛
 
 
 

回复

98

帖子

0

TA的资源

一粒金砂(高级)

65
 
Laspide 发表于 2014-6-10 17:50
如果你一直是一个人完成所有代码,确实差别不大
结构体和变量组合一样,比如那段开关的代码,几个开关的 ...

(1)单片机的代码,我一直都是一个人独自完成。而且我也不赞成把单片机成分成两人以上分工编写。
(2)你说的没错,凡是程序代码相似的地方都可以封装成函数节省容量。但是我在这里为什么没有这么做?因为封装起来的代码可读性没有那么强,不方便初学者阅读学习。另外,我觉得编单片机程序,只要不会影响运行效率,容量不用刻意去追求。因为节省容量,就必然会使用大量的函数,for循环,数组等元素,这些元素用多了后续的可改性就没有那么强,维护或者扩展功能就没那么方便,
此帖出自51单片机论坛
 
 
 

回复

1634

帖子

0

TA的资源

裸片初长成(高级)

66
 
MSC1210单片机(51系)支持24bit的AD转换,需要分3次读取数值,读出后需要整理成long int型数据dat,则dat=dat2*65536*dat1*256+dat0,对于8位单片机,做long型数据的乘法并相加需要多少时间呢?

如果定义一个联合体,体内一个long型dat,一个char型4字节数组ch[4],读出AD转换的数据直接存进ch[0]、ch[1]、ch[2],则long型的AD转换结果dat不需要做32位的乘法计算了,直接就可以用了,极大的节约了CPU的开销。

要想迈得更高,还是需要尽量多的掌握些C语言知识和单片机知识。
此帖出自51单片机论坛
 
 
 

回复

98

帖子

0

TA的资源

一粒金砂(高级)

67
 
本帖最后由 jianhong_wu 于 2014-6-11 00:15 编辑
xu__changhua 发表于 2014-6-10 20:44
MSC1210单片机(51系)支持24bit的AD转换,需要分3次读取数值,读出后需要整理成long int型数据dat,则dat= ...

天啊!!!为什么要dat=dat2*65536*dat1*256+dat0?
你知道C语言结构体的本质是什么吗?结构体经过编译后就是以下我写的代码:
  1. unsigned long dat;
  2. unsigned char dat2,dat1,dat0;

  3. dat=0;
  4. dat=dat2;
  5. dat=dat<<8;
  6. dat=dat+dat1;
  7. dat=dat<<8;
  8. dat=dat+dat0;
复制代码
要想迈得更高,还是需要尽量多的了解最底层最基础的东西,要懂得看透事物的本质。


此帖出自51单片机论坛
 
 
 

回复

173

帖子

0

TA的资源

纯净的硅(初级)

68
 
jianhong_wu 发表于 2014-6-11 00:07
天啊!!!为什么要dat=dat2*65536*dat1*256+dat0?
你知道C语言结构体的本质是什么吗?结 ...

底层的东西是高手需要掌握的。
不使用C语言,(使用汇编)不一定不是高手。
不会汇编,永远成不了高手!汇编必须知道底层的东西。否则弄不了。
此帖出自51单片机论坛
 
 
 

回复

98

帖子

0

TA的资源

一粒金砂(高级)

69
 
ahshmj 发表于 2014-6-11 08:57
底层的东西是高手需要掌握的。
不使用C语言,(使用汇编)不一定不是高手。
不会汇编,永远成不了高手 ...

二进制代码,汇编,C语言都是属于底层的程序,都适合做驱动。

我觉得一般的初学者,汇编只要大概了解一下就可以了,没必要深入,除非以后工作中非要用汇编的。
C语言当然比汇编语言更具有通用性。
此帖出自51单片机论坛
 
 
 

回复

1634

帖子

0

TA的资源

裸片初长成(高级)

70
 
鼓励一下,请继续,看后文。
此帖出自51单片机论坛
 
 
 

回复

173

帖子

0

TA的资源

纯净的硅(初级)

71
 
jianhong_wu 发表于 2014-6-11 12:41
二进制代码,汇编,C语言都是属于底层的程序,都适合做驱动。

我觉得一般的初学者,汇编只要大概了解 ...

对于一般的玩玩,会不会汇编都无所谓。但要想成为高手,必须熟练掌握汇编,汇编会促进你对芯片的寄存器、存储器、I/O口的了解。这对软硬件系统一体设计来说至关重要。
通过c语言出来的代码,无论是从效率、速度、精确度等方面,都是无法和汇编相比的。

如果熟练掌握了汇编,再学习C语言,是很容易的。比如许多人说的指针问题,学过汇编的人就很容易理解,会很少或没有这方面的困惑。

可以说,没有汇编解决不了的问题(在芯片硬件设计的功能范围内),但确实有C语言难处理的问题。

还有就是C语言编程中遇到问题,如果掌握了汇编的人看看*.LIT文件中的汇编代码,可以找到问题所在。

二进制代码(机器码)仅仅是在没有电脑及相关软件的时候,用手工将指令代码转换为机器码,现在都是电脑代劳了。

没有人会用机器码编程序。

如果不是反汇编,也没有几个人会关心机器码的。


当然汇编,枯燥、难记,是一大障碍,但是不应再找其他的理由。


此帖出自51单片机论坛
 
 
 

回复

98

帖子

0

TA的资源

一粒金砂(高级)

72
 
ahshmj 发表于 2014-6-12 08:16
对于一般的玩玩,会不会汇编都无所谓。但要想成为高手,必须熟练掌握汇编,汇编会促进你对芯片的寄存器、 ...

正如你所说汇编语言,枯燥、难记,是一大障碍,但是最让我不提倡深入学习汇编的不是这个原因。真正的原因是它不具有广泛的通用兼容性。比如51单片机跟PIC单片机的汇编语言在指令符号方面就有很大差异,汇编语言也不可以用来写电脑上位机软件。C语言就不一样,会了C语言,不管任何厂家的单片机都可以操作。会了C语言,未来职业扩展的空间就很大,还可以在VC,Java平台上写电脑软件。就像我们学外语,从通用性上考虑,你愿意学英语还是愿意学日语?
此帖出自51单片机论坛
 
 
 

回复

173

帖子

0

TA的资源

纯净的硅(初级)

73
 
本帖最后由 ahshmj 于 2014-6-13 20:26 编辑
jianhong_wu 发表于 2014-6-12 11:54
正如你所说汇编语言,枯燥、难记,是一大障碍,但是最让我不提倡深入学习汇编的不是这个原因。真正的原因 ...

我说的和你说的不一个意思。
我是说既要学习c语言,也要学习汇编。互不排斥。

不是说主张学习一点汇编,就不主张学习c语言。
此帖出自51单片机论坛
 
 
 

回复

98

帖子

0

TA的资源

一粒金砂(高级)

74
 
第五十一节:利用ADC0832采集电压信号,用连续N次一致性的方法进行滤波处理。

开场白:
连续判断N次一致性的滤波法,是为了避免末尾小数点的数据偶尔跳动。这种滤波方法的原理跟我在按键扫描中去抖动的原理是一模一样的,被我频繁地应用在大量的工控项目中。
这一节要教会大家一个知识点:连续判断N次一致性的滤波法。
具体原理:当某个采样变量发生变化时,有两种可能,一种可能是外界的一个瞬间干扰。另一种可能是变量确实发生变化。为了有效去除干扰,当发现变量有变化时,我会连续采集N次,如果连续N次都是一致的结果,我才认为不是干扰。如果中间只要出现一次不一致,我会马上把计数器清零,这一步是精华,很关键。

具体内容,请看源代码讲解。

(1)硬件平台.
基于朱兆祺51单片机学习板。

(2)实现功能:
本程序有2个局部显示。
第1个局部是第8,7,6,5位数码管,显示没有经过滤波处理的实际电压值。此时能观察到未经滤波的数据不太稳定,末尾小数点数据会有跳动的现象
第2个局部是第4,3,2,1位数码管,显示经过特定算法滤波后的实际电压值。此时能观察到经过滤波后的数据很稳定,没有跳动的现象。而且显示的电压值跟未经过滤波的电压值几乎是完全一致,不会出现上一节用区间滤波法所留下的0.02V误差问题。
系统保留3位小数点。手动调节可调电阻时,可以看到显示的数据在变化。

(3)源代码讲解如下:
  1. #include "REG52.H"

  2. #define const_N   8  //连续判断N次一致性滤波方法中,N的取值
  3. #define const_voice_short  40   //蜂鸣器短叫的持续时间

  4. void initial_myself(void);   
  5. void initial_peripheral(void);
  6. void delay_short(unsigned int uiDelayShort);
  7. void delay_long(unsigned int uiDelaylong);


  8. //驱动数码管的74HC595
  9. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);  
  10. void display_drive(void); //显示数码管字模的驱动函数
  11. void display_service(void); //显示的窗口菜单服务程序
  12. //驱动LED的74HC595
  13. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);

  14. void T0_time(void);  //定时中断函数

  15. void ad_sampling_service(void); //AD采样与处理的服务程序


  16. sbit led_dr=P3^5;  //LED灯
  17. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口



  18. sbit dig_hc595_sh_dr=P2^0;     //数码管的74HC595程序
  19. sbit dig_hc595_st_dr=P2^1;  
  20. sbit dig_hc595_ds_dr=P2^2;  
  21. sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
  22. sbit hc595_st_dr=P2^4;  
  23. sbit hc595_ds_dr=P2^5;  


  24. sbit adc0832_clk_dr     = P1^2;  // 定义adc0832的引脚
  25. sbit adc0832_cs_dr      = P1^0;
  26. sbit adc0832_data_sr_dr = P1^1;


  27. unsigned char ucDigShow8;  //第8位数码管要显示的内容
  28. unsigned char ucDigShow7;  //第7位数码管要显示的内容
  29. unsigned char ucDigShow6;  //第6位数码管要显示的内容
  30. unsigned char ucDigShow5;  //第5位数码管要显示的内容
  31. unsigned char ucDigShow4;  //第4位数码管要显示的内容
  32. unsigned char ucDigShow3;  //第3位数码管要显示的内容
  33. unsigned char ucDigShow2;  //第2位数码管要显示的内容
  34. unsigned char ucDigShow1;  //第1位数码管要显示的内容

  35. unsigned char ucDigDot8;  //数码管8的小数点是否显示的标志
  36. unsigned char ucDigDot7;  //数码管7的小数点是否显示的标志
  37. unsigned char ucDigDot6;  //数码管6的小数点是否显示的标志
  38. unsigned char ucDigDot5;  //数码管5的小数点是否显示的标志
  39. unsigned char ucDigDot4;  //数码管4的小数点是否显示的标志
  40. unsigned char ucDigDot3;  //数码管3的小数点是否显示的标志
  41. unsigned char ucDigDot2;  //数码管2的小数点是否显示的标志
  42. unsigned char ucDigDot1;  //数码管1的小数点是否显示的标志
  43. unsigned char ucDigShowTemp=0; //临时中间变量
  44. unsigned char ucDisplayDriveStep=1;  //动态扫描数码管的步骤变量


  45. unsigned char ucWd1Part1Update=1;  //在窗口1中,局部1的更新显示标志
  46. unsigned char ucWd1Part2Update=1; //在窗口1中,局部2的更新显示标志


  47. unsigned char ucTemp1=0;  //中间过渡变量
  48. unsigned char ucTemp2=0;  //中间过渡变量
  49. unsigned char ucTemp3=0;  //中间过渡变量
  50. unsigned char ucTemp4=0;  //中间过渡变量
  51. unsigned char ucTemp5=0;  //中间过渡变量
  52. unsigned char ucTemp6=0;  //中间过渡变量
  53. unsigned char ucTemp7=0;  //中间过渡变量
  54. unsigned char ucTemp8=0;  //中间过渡变量

  55. unsigned char ucAD=0;   //AD值
  56. unsigned char ucCheckAD=0; //用来做校验对比的AD值


  57. unsigned long ulTemp=0;  //参与换算的中间变量
  58. unsigned long ulTempFilterV=0; //参与换算的中间变量
  59. unsigned long ulBackupFilterV=5000;  //备份最新采样数据的中间变量
  60. unsigned char ucSamplingCnt=0; //记录连续N次采样的计数器

  61. unsigned long ulV=0; //未经滤波处理的实时电压值
  62. unsigned long ulFilterV=0; //经过滤波后的实时电压值


  63. //根据原理图得出的共阴数码管字模表
  64. code unsigned char dig_table[]=
  65. {
  66. 0x3f,  //0       序号0
  67. 0x06,  //1       序号1
  68. 0x5b,  //2       序号2
  69. 0x4f,  //3       序号3
  70. 0x66,  //4       序号4
  71. 0x6d,  //5       序号5
  72. 0x7d,  //6       序号6
  73. 0x07,  //7       序号7
  74. 0x7f,  //8       序号8
  75. 0x6f,  //9       序号9
  76. 0x00,  //无      序号10
  77. 0x40,  //-       序号11
  78. 0x73,  //P       序号12
  79. };
  80. void main()
  81.   {
  82.    initial_myself();  
  83.    delay_long(100);   
  84.    initial_peripheral();
  85.    while(1)  
  86.    {
  87.       ad_sampling_service(); //AD采样与处理的服务程序
  88.       display_service(); //显示的窗口菜单服务程序
  89.    }
  90. }

  91. void ad_sampling_service(void) //AD采样与处理的服务程序
  92. {
  93.     unsigned char i;

  94.     ucAD=0;   //AD值
  95.     ucCheckAD=0; //用来做校验对比的AD值


  96.     /* 片选信号置为低电平 */
  97.     adc0832_cs_dr = 0;

  98.         /* 第一个脉冲,开始位 */
  99.         adc0832_data_sr_dr = 1;
  100.         adc0832_clk_dr  = 0;
  101.     delay_short(1);
  102.         adc0832_clk_dr  = 1;

  103.         /* 第二个脉冲,选择通道 */
  104.         adc0832_data_sr_dr = 1;
  105.         adc0832_clk_dr  = 0;
  106.         adc0832_clk_dr  = 1;

  107.         /* 第三个脉冲,选择通道 */
  108.         adc0832_data_sr_dr = 0;
  109.         adc0832_clk_dr  = 0;
  110.         adc0832_clk_dr  = 1;

  111.     /* 数据线输出高电平 */
  112.         adc0832_data_sr_dr = 1;
  113.     delay_short(2);

  114.         /* 第一个下降沿 */
  115.         adc0832_clk_dr  = 1;
  116.         adc0832_clk_dr  = 0;
  117.     delay_short(1);


  118.         /* AD值开始送出 */
  119.         for (i = 0; i < 8; i++)
  120.         {
  121.         ucAD <<= 1;
  122.                 adc0832_clk_dr = 1;
  123.                 adc0832_clk_dr = 0;
  124.                 if (adc0832_data_sr_dr==1)
  125.                 {
  126.             ucAD |= 0x01;
  127.                 }
  128.         }

  129.         /* 用于校验的AD值开始送出 */
  130.         for (i = 0; i < 8; i++)
  131.         {
  132.         ucCheckAD >>= 1;
  133.                 if (adc0832_data_sr_dr==1)
  134.                 {
  135.            ucCheckAD |= 0x80;
  136.                 }
  137.                 adc0832_clk_dr = 1;
  138.                 adc0832_clk_dr = 0;
  139.         }
  140.         
  141.         /* 片选信号置为高电平 */
  142.         adc0832_cs_dr = 1;


  143.         if(ucCheckAD==ucAD)  //检验相等
  144.         {
  145.         
  146.             ulTemp=0;  //把char类型数据赋值给long类型数据之前,必须先清零
  147.             ulTemp=ucAD; //把char类型数据赋值给long类型数据,参与乘除法运算的数据,为了避免运算结果溢出,我都用long类型

  148. /* 注释一:
  149. * 因为保留3为小数点,这里的5000代表5.000V。ulTemp/255代表分辨率.
  150. * 有些书上说8位AD最高分辩可达到256级(0xff+1),我认为这种说法是错误的。
  151. * 8位AD最高分辩应该是255级(0xff),所以这里除以255,而不是256.
  152. */
  153.             ulTemp=5000*ulTemp/255;  //进行电压换算
  154.             ulV=ulTemp; //得到未经滤波处理的实时电压值
  155.             ucWd1Part1Update=1; //局部更新显示未经滤波处理的电压


  156. /* 注释二:
  157. * 以下连续判断N次一致性的滤波法,为了避免末尾小数点的数据偶尔跳动。
  158. * 这种滤波方法的原理跟我在按键扫描中的去抖动原理是一模一样的,被我频繁
  159. * 地应用在大量的工控项目中。
  160. * 具体原理:当某个采样变量发生变化时,有两种可能,一种可能是外界的一个瞬间干扰。
  161. * 另一种可能是变量确实发生变化。为了有效去除干扰,当发现变量有变化时,
  162. * 我会连续采集N次,如果连续N次都是一致的结果,我才认为不是干扰。如果中间
  163. * 只要出现一次不一致,我会马上把计数器清零,这一步是精华,很关键。
  164. *
  165. */
  166.                       if(ulTempFilterV!=ulTemp) //发现变量有变化
  167.                      {
  168.                         ucSamplingCnt++;    //计数器累加
  169.                               if(ucSamplingCnt>const_N)  //如果连续N次都是一致的,则认为不是干扰。确实有数据需要更新显示。这里的const_N取值是8
  170.                             {
  171.                                 ucSamplingCnt=0;

  172.                                 ulTempFilterV=ulTemp;   //及时保存更新了的数据,方便下一次有新数据对比做准备

  173.                     ulFilterV=ulTempFilterV; //得到经过滤波处理的实时电压值
  174.                     ucWd1Part2Update=1; //局部更新显示经过滤波处理的电压                         
  175.                             }
  176.                        }
  177.                     else
  178.                     {
  179.                          ucSamplingCnt=0;  //只要出现一次不一致,我会马上把计数器清零,这一步是精华,很关键。
  180.                     }



  181.         
  182.         }

  183. }

  184. void display_service(void) //显示的窗口菜单服务程序
  185. {

  186.                         if(ucWd1Part1Update==1)//未经滤波处理的实时电压更新显示
  187.                         {
  188.                            ucWd1Part1Update=0;

  189.                ucTemp8=ulV%10000/1000;  //显示电压值个位
  190.                ucTemp7=ulV%1000/100;    //显示电压值小数点后第1位
  191.                ucTemp6=ulV%100/10;      //显示电压值小数点后第2位
  192.                ucTemp5=ulV%10;          //显示电压值小数点后第3位


  193.                ucDigShow8=ucTemp8; //数码管显示实际内容
  194.                ucDigShow7=ucTemp7;
  195.                ucDigShow6=ucTemp6;
  196.                ucDigShow5=ucTemp5;
  197.                         }


  198.                         if(ucWd1Part2Update==1)//经过滤波处理后的实时电压更新显示
  199.                         {
  200.                              ucWd1Part2Update=0;

  201.                ucTemp4=ulFilterV%10000/1000;  //显示电压值个位
  202.                ucTemp3=ulFilterV%1000/100;    //显示电压值小数点后第1位
  203.                ucTemp2=ulFilterV%100/10;      //显示电压值小数点后第2位
  204.                ucTemp1=ulFilterV%10;          //显示电压值小数点后第3位


  205.                ucDigShow4=ucTemp4; //数码管显示实际内容
  206.                ucDigShow3=ucTemp3;
  207.                ucDigShow2=ucTemp2;
  208.                ucDigShow1=ucTemp1;
  209.                         }


  210. }



  211. void display_drive(void)  
  212. {
  213.    //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
  214.    switch(ucDisplayDriveStep)
  215.    {
  216.       case 1:  //显示第1位
  217.            ucDigShowTemp=dig_table[ucDigShow1];
  218.                    if(ucDigDot1==1)
  219.                    {
  220.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  221.                    }
  222.            dig_hc595_drive(ucDigShowTemp,0xfe);
  223.                break;
  224.       case 2:  //显示第2位
  225.            ucDigShowTemp=dig_table[ucDigShow2];
  226.                    if(ucDigDot2==1)
  227.                    {
  228.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  229.                    }
  230.            dig_hc595_drive(ucDigShowTemp,0xfd);
  231.                break;
  232.       case 3:  //显示第3位
  233.            ucDigShowTemp=dig_table[ucDigShow3];
  234.                    if(ucDigDot3==1)
  235.                    {
  236.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  237.                    }
  238.            dig_hc595_drive(ucDigShowTemp,0xfb);
  239.                break;
  240.       case 4:  //显示第4位
  241.            ucDigShowTemp=dig_table[ucDigShow4];
  242.                    if(ucDigDot4==1)
  243.                    {
  244.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  245.                    }
  246.            dig_hc595_drive(ucDigShowTemp,0xf7);
  247.                break;
  248.       case 5:  //显示第5位
  249.            ucDigShowTemp=dig_table[ucDigShow5];
  250.                    if(ucDigDot5==1)
  251.                    {
  252.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  253.                    }
  254.            dig_hc595_drive(ucDigShowTemp,0xef);
  255.                break;
  256.       case 6:  //显示第6位
  257.            ucDigShowTemp=dig_table[ucDigShow6];
  258.                    if(ucDigDot6==1)
  259.                    {
  260.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  261.                    }
  262.            dig_hc595_drive(ucDigShowTemp,0xdf);
  263.                break;
  264.       case 7:  //显示第7位
  265.            ucDigShowTemp=dig_table[ucDigShow7];
  266.                    if(ucDigDot7==1)
  267.                    {
  268.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  269.            }
  270.            dig_hc595_drive(ucDigShowTemp,0xbf);
  271.                break;
  272.       case 8:  //显示第8位
  273.            ucDigShowTemp=dig_table[ucDigShow8];
  274.                    if(ucDigDot8==1)
  275.                    {
  276.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  277.                    }
  278.            dig_hc595_drive(ucDigShowTemp,0x7f);
  279.                break;
  280.    }
  281.    ucDisplayDriveStep++;
  282.    if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
  283.    {
  284.      ucDisplayDriveStep=1;
  285.    }

  286. }

  287. //数码管的74HC595驱动函数
  288. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
  289. {
  290.    unsigned char i;
  291.    unsigned char ucTempData;
  292.    dig_hc595_sh_dr=0;
  293.    dig_hc595_st_dr=0;
  294.    ucTempData=ucDigStatusTemp16_09;  //先送高8位
  295.    for(i=0;i<8;i++)
  296.    {
  297.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  298.          else dig_hc595_ds_dr=0;
  299.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  300.          delay_short(1);
  301.          dig_hc595_sh_dr=1;
  302.          delay_short(1);
  303.          ucTempData=ucTempData<<1;
  304.    }
  305.    ucTempData=ucDigStatusTemp08_01;  //再先送低8位
  306.    for(i=0;i<8;i++)
  307.    {
  308.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  309.          else dig_hc595_ds_dr=0;
  310.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  311.          delay_short(1);
  312.          dig_hc595_sh_dr=1;
  313.          delay_short(1);
  314.          ucTempData=ucTempData<<1;
  315.    }
  316.    dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  317.    delay_short(1);
  318.    dig_hc595_st_dr=1;
  319.    delay_short(1);
  320.    dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
  321.    dig_hc595_st_dr=0;
  322.    dig_hc595_ds_dr=0;
  323. }

  324. //LED灯的74HC595驱动函数
  325. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
  326. {
  327.    unsigned char i;
  328.    unsigned char ucTempData;
  329.    hc595_sh_dr=0;
  330.    hc595_st_dr=0;
  331.    ucTempData=ucLedStatusTemp16_09;  //先送高8位
  332.    for(i=0;i<8;i++)
  333.    {
  334.          if(ucTempData>=0x80)hc595_ds_dr=1;
  335.          else hc595_ds_dr=0;
  336.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  337.          delay_short(1);
  338.          hc595_sh_dr=1;
  339.          delay_short(1);
  340.          ucTempData=ucTempData<<1;
  341.    }
  342.    ucTempData=ucLedStatusTemp08_01;  //再先送低8位
  343.    for(i=0;i<8;i++)
  344.    {
  345.          if(ucTempData>=0x80)hc595_ds_dr=1;
  346.          else hc595_ds_dr=0;
  347.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  348.          delay_short(1);
  349.          hc595_sh_dr=1;
  350.          delay_short(1);
  351.          ucTempData=ucTempData<<1;
  352.    }
  353.    hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  354.    delay_short(1);
  355.    hc595_st_dr=1;
  356.    delay_short(1);
  357.    hc595_sh_dr=0;    //拉低,抗干扰就增强
  358.    hc595_st_dr=0;
  359.    hc595_ds_dr=0;
  360. }


  361. void T0_time(void) interrupt 1   //定时中断
  362. {
  363.   TF0=0;  //清除中断标志
  364.   TR0=0; //关中断


  365.   display_drive();  //数码管字模的驱动函数

  366.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  367.   TL0=0x0b;
  368.   TR0=1;  //开中断
  369. }

  370. void delay_short(unsigned int uiDelayShort)
  371. {
  372.    unsigned int i;  
  373.    for(i=0;i<uiDelayShort;i++)
  374.    {
  375.      ;   //一个分号相当于执行一条空语句
  376.    }
  377. }

  378. void delay_long(unsigned int uiDelayLong)
  379. {
  380.    unsigned int i;
  381.    unsigned int j;
  382.    for(i=0;i<uiDelayLong;i++)
  383.    {
  384.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  385.           {
  386.              ; //一个分号相当于执行一条空语句
  387.           }
  388.    }
  389. }


  390. void initial_myself(void)  //第一区 初始化单片机
  391. {
  392.   led_dr=0;//LED灯默认关闭
  393.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
  394.   hc595_drive(0x00,0x00);  //关闭所有经过另外两个74HC595驱动的LED灯
  395.   TMOD=0x01;  //设置定时器0为工作方式1
  396.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  397.   TL0=0x0b;

  398. }
  399. void initial_peripheral(void) //第二区 初始化外围
  400. {

  401.    ucDigDot8=1;   //显示未经过滤波电压的小数点
  402.    ucDigDot7=0;  
  403.    ucDigDot6=0;
  404.    ucDigDot5=0;  
  405.    ucDigDot4=1;  //显示经过滤波后电压的小数点
  406.    ucDigDot3=0;  
  407.    ucDigDot2=0;
  408.    ucDigDot1=0;

  409.    EA=1;     //开总中断
  410.    ET0=1;    //允许定时中断
  411.    TR0=1;    //启动定时中断

  412. }
复制代码

总结陈词:
在单片机AD采样的系统中,我常用的滤波方法有求平均值法,区间法,连续判断N次一致性这三种方法。读者可以根据不同的系统特点选择对应的滤波方法,有一些要求高的系统还可以把三种滤波方法混合在一起用。关于AD采样的知识到本节已经讲完,下一节会讲什么新内容呢?欲知详情,请听下回分解-----return语句鲜为人知的用法。

(未完待续,下节更精彩,不要走开哦)
此帖出自51单片机论坛
 
 
 

回复

108

帖子

0

TA的资源

一粒金砂(中级)

75
 
做个记号,收藏起来。一定要看完。
此帖出自51单片机论坛
 
 
 

回复

98

帖子

0

TA的资源

一粒金砂(高级)

76
 
第五十二节:程序后续升级修改的利器,return语句鲜为人知的用法。

开场白:
return语句经常用在带参数返回的函数中,字面上理解就是返回的意思,因此很多单片机初学者很容易忽略了return语句还有中断强行退出的功能。利用这个强行退出的功能,在项目后续程序的升级修改上很方便,还可以有效减少if语句的嵌套层数,使程序阅读起来很简洁。这一节要教大家return语句三个鲜为人知的用法:
第一个鲜为人知的用法:在空函数里,可以插入很多个return语句,不仅仅是一个。
第二个鲜为人知的用法:return语句可以有效较少程序里条件判断语句的嵌套层数。
第三个鲜为人知的用法:return语句本身已经包含了类似break语句的功能,不管当前处于几层的内部循环嵌套,只要遇到return语句都可以强行退出全部循环,并且直接退出当前子程序,不执行当前子程序后面的任何语句,这个功能实在是太强大,太铁腕了。

具体内容,请看源代码讲解。

(1)硬件平台:
基于朱兆祺51单片机学习板。

(2)实现功能:
本程序实现的功能跟第三十九节是一摸一样的,唯一的差别就是在第三十九节的基础上,插入了几个return语句,用新的return语句替代原来的条件和循环判断语句。

波特率是:9600 。
通讯协议:EB 00 55  XX YY  
加无效填充字节后,上位机实际上应该发送:00  EB 00 55  XX YY
其中第1位00是无效填充字节,防止由于硬件原因丢失第一个字节。
其中第2,3,4位EB 00 55就是数据头
           后2位XX YY就是有效数据
任意时刻,单片机从电脑“串口调试助手”上位机收到的一串数据中,只要此数据中包含关键字EB 00 55 ,并且此关键字后面两个字节的数据XX YY 分别为01 02,那么蜂鸣器鸣叫一声表示接收的数据头和有效数据都是正确的。

也就是说,当在 串口助手往单片机发送十六进制数据串:  eb 00 55 01 02  时,会听到蜂鸣器”滴”的一声。

(3)源代码讲解如下:
  1. #include "REG52.H"


  2. #define const_voice_short  40   //蜂鸣器短叫的持续时间
  3. #define const_rc_size  10  //接收串口中断数据的缓冲区数组大小

  4. #define const_receive_time  5  //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完,这个时间根据实际情况来调整大小

  5. void initial_myself(void);   
  6. void initial_peripheral(void);
  7. void delay_long(unsigned int uiDelaylong);



  8. void T0_time(void);  //定时中断函数
  9. void usart_receive(void); //串口接收中断函数
  10. void usart_service(void);  //串口服务程序,在main函数里

  11. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口

  12. unsigned int  uiSendCnt=0;     //用来识别串口是否接收完一串数据的计时器
  13. unsigned char ucSendLock=1;    //串口服务程序的自锁变量,每次接收完一串数据只处理一次
  14. unsigned int  uiRcregTotal=0;  //代表当前缓冲区已经接收了多少个数据
  15. unsigned char ucRcregBuf[const_rc_size]; //接收串口中断数据的缓冲区数组
  16. unsigned int  uiRcMoveIndex=0;  //用来解析数据协议的中间变量


  17. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器



  18. void main()
  19.   {
  20.    initial_myself();  
  21.    delay_long(100);   
  22.    initial_peripheral();
  23.    while(1)  
  24.    {
  25.        usart_service();  //串口服务程序
  26.    }

  27. }

  28. /* 注释一:
  29. * 以下函数说明了,在空函数里,可以插入很多个return语句。
  30. * 用return语句非常便于后续程序的升级修改。
  31. */
  32. void usart_service(void)  //串口服务程序,在main函数里
  33. {

  34.         

  35. //     if(uiSendCnt>=const_receive_time&&ucSendLock==1) //原来的语句,现在被两个return语句替代了
  36. //     {

  37.        if(uiSendCnt<const_receive_time)  //延时还没超过规定时间,直接退出本程序,不执行return后的任何语句。
  38.            {
  39.               return;  //强行退出本子程序,不执行以下任何语句
  40.            }

  41.            if(ucSendLock==0)  //不是最新一次接收到串口数据,直接退出本程序,不执行return后的任何语句。
  42.            {
  43.               return;  //强行退出本子程序,不执行以下任何语句
  44.            }
  45. /* 注释二:
  46. * 以上两条return语句就相当于原来的一条if(uiSendCnt>=const_receive_time&&ucSendLock==1)语句。
  47. * 用了return语句后,就明显减少了一个if嵌套。
  48. */


  49.             ucSendLock=0;    //处理一次就锁起来,不用每次都进来,除非有新接收的数据

  50.                     //下面的代码进入数据协议解析和数据处理的阶段

  51.             uiRcMoveIndex=0; //由于是判断数据头,所以下标移动变量从数组的0开始向最尾端移动


  52. //           while(uiRcregTotal>=5&&uiRcMoveIndex<=(uiRcregTotal-5)) //原来的语句,现在被两个return语句替代了
  53.             while(1) //死循环可以被以下return或者break语句中断,return本身已经包含了break语句功能。
  54.             {
  55.                if(uiRcregTotal<5)  //串口接收到的数据太少
  56.                            {
  57.                               uiRcregTotal=0;  //清空缓冲的下标,方便下次重新从0下标开始接受新数据
  58.                                   return;  //强行退出while(1)循环嵌套,直接退出本程序,不执行以下任何语句
  59.                            }

  60.                            if(uiRcMoveIndex>(uiRcregTotal-5)) //数组缓冲区的数据已经处理完
  61.                            {
  62.                               uiRcregTotal=0;  //清空缓冲的下标,方便下次重新从0下标开始接受新数据
  63.                                   return;  //强行退出while(1)循环嵌套,直接退出本程序,不执行以下任何语句
  64.                            }
  65. /* 注释三:
  66. * 以上两条return语句就相当于原来的一条while(uiRcregTotal>=5&&uiRcMoveIndex<=(uiRcregTotal-5))语句。
  67. * 以上两个return语句的用法,同时说明了return本身已经包含了break语句功能,不管当前处于几层的内部循环嵌套,
  68. * 都可以强行退出循环,并且直接退出本程序。
  69. */


  70.                if(ucRcregBuf[uiRcMoveIndex+0]==0xeb&&ucRcregBuf[uiRcMoveIndex+1]==0x00&&ucRcregBuf[uiRcMoveIndex+2]==0x55)  //数据头eb 00 55的判断
  71.                {
  72.                   if(ucRcregBuf[uiRcMoveIndex+3]==0x01&&ucRcregBuf[uiRcMoveIndex+4]==0x02)  //有效数据01 02的判断
  73.                   {
  74.                        uiVoiceCnt=const_voice_short; //蜂鸣器发出声音,说明数据头和有效数据都接收正确
  75.                   }
  76.                   break;   //退出while(1)循环
  77.                }
  78.                uiRcMoveIndex++; //因为是判断数据头,游标向着数组最尾端的方向移动
  79.            }
  80.                                          
  81.            uiRcregTotal=0;  //清空缓冲的下标,方便下次重新从0下标开始接受新数据
  82.   
  83. //     }
  84.                         
  85. }


  86. void T0_time(void) interrupt 1    //定时中断
  87. {
  88.   TF0=0;  //清除中断标志
  89.   TR0=0; //关中断


  90.   if(uiSendCnt<const_receive_time)   //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完
  91.   {
  92.           uiSendCnt++;    //表面上这个数据不断累加,但是在串口中断里,每接收一个字节它都会被清零,除非这个中间没有串口数据过来
  93.       ucSendLock=1;     //开自锁标志
  94.   }

  95.   if(uiVoiceCnt!=0)
  96.   {
  97.      uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  98.      beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。

  99.   }
  100.   else
  101.   {
  102.      ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
  103.      beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  104.   }


  105.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  106.   TL0=0x0b;
  107.   TR0=1;  //开中断
  108. }


  109. void usart_receive(void) interrupt 4                 //串口接收数据中断        
  110. {        

  111.    if(RI==1)  
  112.    {
  113.         RI = 0;

  114.             ++uiRcregTotal;
  115.         if(uiRcregTotal>const_rc_size)  //超过缓冲区
  116.         {
  117.            uiRcregTotal=const_rc_size;
  118.         }
  119.         ucRcregBuf[uiRcregTotal-1]=SBUF;   //将串口接收到的数据缓存到接收缓冲区里
  120.         uiSendCnt=0;  //及时喂狗,虽然main函数那边不断在累加,但是只要串口的数据还没发送完毕,那么它永远也长不大,因为每个中断都被清零。
  121.    
  122.    }
  123.    else  //我在其它单片机上都不用else这段代码的,可能在51单片机上多增加" TI = 0;"稳定性会更好吧。
  124.    {
  125.         TI = 0;
  126.    }
  127.                                                          
  128. }                                


  129. void delay_long(unsigned int uiDelayLong)
  130. {
  131.    unsigned int i;
  132.    unsigned int j;
  133.    for(i=0;i<uiDelayLong;i++)
  134.    {
  135.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  136.           {
  137.              ; //一个分号相当于执行一条空语句
  138.           }
  139.    }
  140. }


  141. void initial_myself(void)  //第一区 初始化单片机
  142. {

  143.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  144.   //配置定时器
  145.   TMOD=0x01;  //设置定时器0为工作方式1
  146.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  147.   TL0=0x0b;


  148.   //配置串口
  149.   SCON=0x50;
  150.   TMOD=0X21;
  151.   TH1=TL1=-(11059200L/12/32/9600);  //这段配置代码具体是什么意思,我也不太清楚,反正是跟串口波特率有关。
  152.   TR1=1;

  153. }

  154. void initial_peripheral(void) //第二区 初始化外围
  155. {

  156.    EA=1;     //开总中断
  157.    ES=1;     //允许串口中断
  158.    ET0=1;    //允许定时中断
  159.    TR0=1;    //启动定时中断

  160. }
复制代码

总结陈词:
我在第一节就告诉读者了,搞单片机开发如果不会C语言的指针也没关系,不会影响做项目。我本人平时做项目时,也很少用指针,只有在三种场合下我才会用指针,因为在这三种场合下,用了指针感觉程序阅读起来更加清爽了。所以,指针还是有它独到的好处,有哪三种好处?欲知详情,请听下回分解-----指针的第一大好处,让一个函数可以封装多个相当于return语句返回的参数。

(未完待续,下节更精彩,不要走开哦)
此帖出自51单片机论坛
 
 
 

回复

98

帖子

0

TA的资源

一粒金砂(高级)

77
 
第五十三节:指针的第一大好处,让一个函数可以封装多个相当于return语句返回的参数。

开场白:
当我们想把某种算法通过一个函数来实现的时候,如果不会指针,那么只有两种方法。
第1种:用不带参数返回的空函数。这是最原始的做法,也是我当年刚毕业就开始做项目的时候经常用的方法。它完全依靠全局变量作为函数的输入和输出口。我们要用到这个函数,就要把参与运算的变量直接赋给对应的输入全局变量,调用一次函数之后,再找到对应的输出变量,这些输出变量就是我们要的结果。这种方法的缺点是阅读不直观,封装性不强,没有面对用户的输入输出接口。
第2种:用return返回参数和带输入形参的函数,这种方法已经具备了完整的输入和输出性能,比第1种方法直观多了。但是这种方法有它的局限性,因为return只能返回一个变量,如果要用在返回多个输出结果的函数中,就无能为力了,这时候该怎么办?就必须用指针了,也就是我下面讲到的第3种方法。
这一节要教大家一个知识点:通过指针,让函数可以返回多个变量。

具体内容,请看源代码讲解。

(1)硬件平台:
基于朱兆祺51单片机学习板。

(2)实现功能:
通过电脑串口调试助手,往单片机发送EB 00 55 XX YY  指令,其中EB 00 55是数据头, XX是被除数,YY是除数。单片机收到指令后就会返回6个数据,最前面两个数据是第1种运算方式的商和余数,中间两个数据是第2种运算方式的商和余数,最后两个数据是第3种运算方式的商和余数。
比如电脑发送:EB 00 55 08 02
单片机就返回:04 00 04 00 04 00  (04是商,00是余数)

串口程序的接收部分请参考第39节。串口程序的发送部分请参考第42节。

波特率是:9600 。

(3)源代码讲解如下:
  1. #include "REG52.H"


  2. #define const_voice_short  40   //蜂鸣器短叫的持续时间
  3. #define const_rc_size  10  //接收串口中断数据的缓冲区数组大小

  4. #define const_receive_time  5  //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完,这个时间根据实际情况来调整大小

  5. void initial_myself(void);   
  6. void initial_peripheral(void);
  7. void delay_long(unsigned int uiDelaylong);
  8. void delay_short(unsigned int uiDelayShort);


  9. void T0_time(void);  //定时中断函数
  10. void usart_receive(void); //串口接收中断函数
  11. void usart_service(void);  //串口服务程序,在main函数里


  12. void eusart_send(unsigned char ucSendData);
  13. void chu_fa_yun_suan_1(void);//第1种方法 求商和余数
  14. unsigned char get_shang_2(unsigned char ucBeiChuShuTemp,unsigned char ucChuShuTemp); //第2种方法 求商
  15. unsigned char get_yu_2(unsigned char ucBeiChuShuTemp,unsigned char ucChuShuTemp); //第2种方法 求余数
  16. void chu_fa_yun_suan_3(unsigned char ucBeiChuShuTemp,unsigned char ucChuShuTemp,unsigned char *p_ucShangTemp,unsigned char *p_ucYuTemp);//第3种方法 求商和余数

  17. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口

  18. unsigned int  uiSendCnt=0;     //用来识别串口是否接收完一串数据的计时器
  19. unsigned char ucSendLock=1;    //串口服务程序的自锁变量,每次接收完一串数据只处理一次
  20. unsigned int  uiRcregTotal=0;  //代表当前缓冲区已经接收了多少个数据
  21. unsigned char ucRcregBuf[const_rc_size]; //接收串口中断数据的缓冲区数组
  22. unsigned int  uiRcMoveIndex=0;  //用来解析数据协议的中间变量


  23. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器


  24. unsigned char ucBeiChuShu_1=0;  //第1种方法中的被除数
  25. unsigned char ucChuShu_1=1;     //第1种方法中的除数
  26. unsigned char ucShang_1=0;      //第1种方法中的商
  27. unsigned char ucYu_1=0;         //第1种方法中的余数

  28. unsigned char ucBeiChuShu_2=0;  //第2种方法中的被除数
  29. unsigned char ucChuShu_2=1;     //第2种方法中的除数
  30. unsigned char ucShang_2=0;      //第2种方法中的商
  31. unsigned char ucYu_2=0;         //第2种方法中的余数

  32. unsigned char ucBeiChuShu_3=0;  //第3种方法中的被除数
  33. unsigned char ucChuShu_3=1;     //第3种方法中的除数
  34. unsigned char ucShang_3=0;      //第3种方法中的商
  35. unsigned char ucYu_3=0;         //第3种方法中的余数

  36. void main()
  37.   {
  38.    initial_myself();  
  39.    delay_long(100);   
  40.    initial_peripheral();
  41.    while(1)  
  42.    {
  43.        usart_service();  //串口服务程序
  44.    }

  45. }


  46. /* 注释一:
  47. * 第1种方法,用不带参数返回的空函数,这是最原始的做法,也是我当年刚毕业
  48. * 就开始做项目的时候经常用的方法。它完全依靠全局变量作为函数的输入和输出口。
  49. * 我们要用到这个函数,就要把参与运算的变量直接赋给对应的输入全局变量,
  50. * 调用一次函数之后,再找到对应的输出变量,这些输出变量就是我们要的结果。
  51. * 在本函数中,被除数ucBeiChuShu_1和除数ucChuShu_1就是输入全局变量,
  52. * 商ucShang_1和余数ucYu_1就是输出全局变量。这种方法的缺点是阅读不直观,
  53. * 封装性不强,没有面对用户的输入输出接口,
  54. */
  55. void chu_fa_yun_suan_1(void)//第1种方法 求商和余数
  56. {
  57.    if(ucChuShu_1==0) //如果除数为0,则商和余数都为0
  58.    {
  59.       ucShang_1=0;
  60.           ucYu_1=0;
  61.    }
  62.    else
  63.    {
  64.       ucShang_1=ucBeiChuShu_1/ucChuShu_1;  //求商
  65.       ucYu_1=ucBeiChuShu_1%ucChuShu_1;  //求余数
  66.    }

  67. }


  68. /* 注释二:
  69. * 第2种方法,用return返回参数和带输入形参的函数,这种方法已经具备了完整的输入和输出性能,
  70. * 比第1种方法直观多了。但是这种方法有它的局限性,因为return只能返回一个变量,
  71. * 如果要用在返回多个输出结果的函数中,就无能为力了。比如本程序,就不能同时输出
  72. * 商和余数,只能分两个函数来做。如果要在一个函数中同时输出商和余数,该怎么办?
  73. * 这个时候就必须用指针了,也就是我下面讲到的第3种方法。
  74. */
  75. unsigned char get_shang_2(unsigned char ucBeiChuShuTemp,unsigned char ucChuShuTemp) //第2种方法 求商
  76. {
  77.    unsigned char ucShangTemp;
  78.    if(ucChuShuTemp==0) //如果除数为0,则商为0
  79.    {
  80.       ucShangTemp=0;
  81.    }
  82.    else
  83.    {
  84.       ucShangTemp=ucBeiChuShuTemp/ucChuShuTemp;  //求商
  85.    }

  86.    return ucShangTemp; //返回运算后的结果 商
  87. }

  88. unsigned char get_yu_2(unsigned char ucBeiChuShuTemp,unsigned char ucChuShuTemp) //第2种方法 求余数
  89. {
  90.    unsigned char ucYuTemp;
  91.    if(ucChuShuTemp==0) //如果除数为0,则余数为0
  92.    {
  93.       ucYuTemp=0;
  94.    }
  95.    else
  96.    {
  97.       ucYuTemp=ucBeiChuShuTemp%ucChuShuTemp;   //求余数
  98.    }

  99.    return ucYuTemp; //返回运算后的结果 余数
  100. }

  101. /* 注释三:
  102. * 第3种方法,用带指针的函数,就可以顺心所欲,不受return的局限,想输出多少个
  103. * 运算结果都可以,赞一个!在本函数中,ucBeiChuShuTemp和ucChuShuTemp是输入变量,
  104. * 它们不是指针,所以不具备输出接口属性。*p_ucShangTemp和*p_ucYuTemp是输出变量,
  105. * 因为它们是指针,所以具备输出接口属性。
  106. */
  107. void chu_fa_yun_suan_3(unsigned char ucBeiChuShuTemp,unsigned char ucChuShuTemp,unsigned char *p_ucShangTemp,unsigned char *p_ucYuTemp)//第3种方法 求商和余数
  108. {
  109.    if(ucChuShuTemp==0) //如果除数为0,则商和余数都为0
  110.    {
  111.       *p_ucShangTemp=0;
  112.           *p_ucYuTemp=0;
  113.    }
  114.    else
  115.    {
  116.       *p_ucShangTemp=ucBeiChuShuTemp/ucChuShuTemp;  //求商
  117.       *p_ucYuTemp=ucBeiChuShuTemp%ucChuShuTemp;  //求余数
  118.    }

  119. }

  120. void usart_service(void)  //串口服务程序,在main函数里
  121. {

  122.         

  123.      if(uiSendCnt>=const_receive_time&&ucSendLock==1) //说明超过了一定的时间内,再也没有新数据从串口来
  124.      {

  125.             ucSendLock=0;    //处理一次就锁起来,不用每次都进来,除非有新接收的数据

  126.             //下面的代码进入数据协议解析和数据处理的阶段

  127.             uiRcMoveIndex=0; //由于是判断数据头,所以下标移动变量从数组的0开始向最尾端移动

  128.             while(uiRcregTotal>=5&&uiRcMoveIndex<=(uiRcregTotal-5))
  129.             {
  130.                if(ucRcregBuf[uiRcMoveIndex+0]==0xeb&&ucRcregBuf[uiRcMoveIndex+1]==0x00&&ucRcregBuf[uiRcMoveIndex+2]==0x55)  //数据头eb 00 55的判断
  131.                {

  132.                   //第1种运算方法,依靠全局变量
  133.                   ucBeiChuShu_1=ucRcregBuf[uiRcMoveIndex+3]; //被除数
  134.                   ucChuShu_1=ucRcregBuf[uiRcMoveIndex+4];  //除数
  135.                                   chu_fa_yun_suan_1(); //调用一次空函数就出结果了,结果保存在ucShang_1和ucYu_1全局变量中
  136.                                   eusart_send(ucShang_1); //把运算结果返回给上位机观察
  137.                                   eusart_send(ucYu_1);//把运算结果返回给上位机观察

  138.                   //第2种运算方法,依靠两个带return语句的返回函数
  139.                   ucBeiChuShu_2=ucRcregBuf[uiRcMoveIndex+3]; //被除数
  140.                   ucChuShu_2=ucRcregBuf[uiRcMoveIndex+4];  //除数
  141.                   ucShang_2=get_shang_2(ucBeiChuShu_2,ucChuShu_2); //第2种方法 求商
  142.                   ucYu_2=get_yu_2(ucBeiChuShu_2,ucChuShu_2); //第2种方法 求余数
  143.                                   eusart_send(ucShang_2); //把运算结果返回给上位机观察
  144.                                   eusart_send(ucYu_2);//把运算结果返回给上位机观察

  145.                   //第3种运算方法,依靠指针
  146.                   ucBeiChuShu_3=ucRcregBuf[uiRcMoveIndex+3]; //被除数
  147.                   ucChuShu_3=ucRcregBuf[uiRcMoveIndex+4];  //除数
  148. /* 注释四:
  149. * 注意,由于商和余数是指针形参,我们代入的变量必须带地址符号& 。比如&ucShang_3和&ucYu_3。
  150. * 因为我们是把变量的地址传递进去的。
  151. */
  152.                                   chu_fa_yun_suan_3(ucBeiChuShu_3,ucChuShu_3,&ucShang_3,&ucYu_3);//第3种方法 求商和余数
  153.                                   eusart_send(ucShang_3); //把运算结果返回给上位机观察
  154.                                   eusart_send(ucYu_3);//把运算结果返回给上位机观察


  155.                   break;   //退出循环
  156.                }
  157.                uiRcMoveIndex++; //因为是判断数据头,游标向着数组最尾端的方向移动
  158.            }
  159.                                          
  160.            uiRcregTotal=0;  //清空缓冲的下标,方便下次重新从0下标开始接受新数据
  161.   
  162.      }
  163.                         
  164. }

  165. void eusart_send(unsigned char ucSendData) //往上位机发送一个字节的函数
  166. {

  167.   ES = 0; //关串口中断
  168.   TI = 0; //清零串口发送完成中断请求标志
  169.   SBUF =ucSendData; //发送一个字节

  170.   delay_short(400);  //每个字节之间的延时,这里非常关键,也是最容易出错的地方。延时的大小请根据实际项目来调整

  171.   TI = 0; //清零串口发送完成中断请求标志
  172.   ES = 1; //允许串口中断

  173. }



  174. void T0_time(void) interrupt 1    //定时中断
  175. {
  176.   TF0=0;  //清除中断标志
  177.   TR0=0; //关中断


  178.   if(uiSendCnt<const_receive_time)   //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完
  179.   {
  180.           uiSendCnt++;    //表面上这个数据不断累加,但是在串口中断里,每接收一个字节它都会被清零,除非这个中间没有串口数据过来
  181.       ucSendLock=1;     //开自锁标志
  182.   }

  183.   if(uiVoiceCnt!=0)
  184.   {
  185.      uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  186.      beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。

  187.   }
  188.   else
  189.   {
  190.      ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
  191.      beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  192.   }


  193.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  194.   TL0=0x0b;
  195.   TR0=1;  //开中断
  196. }


  197. void usart_receive(void) interrupt 4                 //串口接收数据中断        
  198. {        

  199.    if(RI==1)  
  200.    {
  201.         RI = 0;

  202.             ++uiRcregTotal;
  203.         if(uiRcregTotal>const_rc_size)  //超过缓冲区
  204.         {
  205.            uiRcregTotal=const_rc_size;
  206.         }
  207.         ucRcregBuf[uiRcregTotal-1]=SBUF;   //将串口接收到的数据缓存到接收缓冲区里
  208.         uiSendCnt=0;  //及时喂狗,虽然main函数那边不断在累加,但是只要串口的数据还没发送完毕,那么它永远也长不大,因为每个中断都被清零。
  209.    
  210.    }
  211.    else  //发送中断,及时把发送中断标志位清零
  212.    {
  213.         TI = 0;
  214.    }
  215.                                                          
  216. }                                


  217. void delay_long(unsigned int uiDelayLong)
  218. {
  219.    unsigned int i;
  220.    unsigned int j;
  221.    for(i=0;i<uiDelayLong;i++)
  222.    {
  223.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  224.           {
  225.              ; //一个分号相当于执行一条空语句
  226.           }
  227.    }
  228. }

  229. void delay_short(unsigned int uiDelayShort)
  230. {
  231.    unsigned int i;  
  232.    for(i=0;i<uiDelayShort;i++)
  233.    {
  234.      ;   //一个分号相当于执行一条空语句
  235.    }
  236. }


  237. void initial_myself(void)  //第一区 初始化单片机
  238. {

  239.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  240.   //配置定时器
  241.   TMOD=0x01;  //设置定时器0为工作方式1
  242.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  243.   TL0=0x0b;


  244.   //配置串口
  245.   SCON=0x50;
  246.   TMOD=0X21;
  247.   TH1=TL1=-(11059200L/12/32/9600);  //这段配置代码具体是什么意思,我也不太清楚,反正是跟串口波特率有关。
  248.   TR1=1;

  249. }

  250. void initial_peripheral(void) //第二区 初始化外围
  251. {

  252.    EA=1;     //开总中断
  253.    ES=1;     //允许串口中断
  254.    ET0=1;    //允许定时中断
  255.    TR0=1;    //启动定时中断

  256. }
复制代码

总结陈词:
这节讲了指针的第一大好处,它的第二大好处是什么?欲知详情,请听下回分解-----指针的第二大好处,指针作为数组在函数内部的化身。

(未完待续,下节更精彩,不要走开哦)
此帖出自51单片机论坛
 
 
 

回复

108

帖子

0

TA的资源

一粒金砂(中级)

78
 
好帖,收藏了。
此帖出自51单片机论坛
 
 
 

回复

98

帖子

0

TA的资源

一粒金砂(高级)

79
 
第五十四节:指针的第二大好处,指针作为数组在函数中的输入接口。

开场白:
如果不会指针,当我们想把一个数组的数据传递进某个函数内部的时候,只能通过全局变量的方式,这种方法的缺点是阅读不直观,封装性不强,没有面对用户的输入接口。
针对以上问题,这一节要教大家一个知识点:通过指针,为函数增加一个数组输入接口。

具体内容,请看源代码讲解。

(1)硬件平台:
基于朱兆祺51单片机学习板。

(2)实现功能:
把5个随机数据按从大到小排序,用冒泡法来排序。
通过电脑串口调试助手,往单片机发送EB 00 55 08 06 09 05 07  指令,其中EB 00 55是数据头,08 06 09 05 07 是参与排序的5个随机原始数据。单片机收到指令后就会返回13个数据,最前面5个数据是第1种方法的排序结果,中间3个数据EE EE EE是第1种和第2种的分割线,为了方便观察,没实际意义。最后5个数据是第2种方法的排序结果.

比如电脑发送:EB 00 55 08 06 09 05 07
单片机就返回:09 08 07 06 05 EE EE EE 09 08 07 06 05

串口程序的接收部分请参考第39节。串口程序的发送部分请参考第42节。

波特率是:9600 。

(3)源代码讲解如下:
  1. #include "REG52.H"


  2. #define const_array_size  5  //参与排序的数组大小

  3. #define const_voice_short  40   //蜂鸣器短叫的持续时间
  4. #define const_rc_size  10  //接收串口中断数据的缓冲区数组大小

  5. #define const_receive_time  5  //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完,这个时间根据实际情况来调整大小

  6. void initial_myself(void);   
  7. void initial_peripheral(void);
  8. void delay_long(unsigned int uiDelaylong);
  9. void delay_short(unsigned int uiDelayShort);


  10. void T0_time(void);  //定时中断函数
  11. void usart_receive(void); //串口接收中断函数
  12. void usart_service(void);  //串口服务程序,在main函数里


  13. void eusart_send(unsigned char ucSendData);

  14. void big_to_small_sort_1(void);//第1种方法 把一个数组从大小小排序
  15. void big_to_small_sort_2(unsigned char *p_ucInputBuffer);//第2种方法 把一个数组从大小小排序

  16. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口

  17. unsigned int  uiSendCnt=0;     //用来识别串口是否接收完一串数据的计时器
  18. unsigned char ucSendLock=1;    //串口服务程序的自锁变量,每次接收完一串数据只处理一次
  19. unsigned int  uiRcregTotal=0;  //代表当前缓冲区已经接收了多少个数据
  20. unsigned char ucRcregBuf[const_rc_size]; //接收串口中断数据的缓冲区数组
  21. unsigned int  uiRcMoveIndex=0;  //用来解析数据协议的中间变量

  22. unsigned char ucUsartBuffer[const_array_size];  //从串口接收到的需要排序的原始数据
  23. unsigned char ucGlobalBuffer_1[const_array_size]; //第1种方法,参与具体排序算法的全局变量数组
  24. unsigned char ucGlobalBuffer_2[const_array_size]; //第2种方法,参与具体排序算法的全局变量数组
  25. void main()
  26.   {
  27.    initial_myself();  
  28.    delay_long(100);   
  29.    initial_peripheral();
  30.    while(1)  
  31.    {
  32.        usart_service();  //串口服务程序
  33.    }

  34. }


  35. /* 注释一:
  36. * 第1种方法,用不带输入输出接口的空函数,这是最原始的做法,它完全依靠
  37. * 全局变量作为函数的输入和输出口。我们要用到这个函数,就要把参与运算
  38. * 的变量直接赋给对应的输入全局变量,调用一次函数之后,再找到对应的
  39. * 输出全局变量,这些输出全局变量就是我们要的结果。
  40. * 在本函数中,ucGlobalBuffer_1[const_array_size]既是输入全局变量,也是输出全局变量,
  41. * 这种方法的缺点是阅读不直观,封装性不强,没有面对用户的输入输出接口,
  42. */
  43. void big_to_small_sort_1(void)//第1种方法 把一个数组从大小小排序
  44. {
  45.    unsigned char i;
  46.    unsigned char k;
  47.    unsigned char ucTemp; //在两两交换数据的过程中,用于临时存放交换的某个变量

  48. /* 注释二:
  49. * 以下就是著名的 冒泡法排序。这个方法几乎所有的C语言大学教材都讲过了。大家在百度上可以直接
  50. * 搜索到它的工作原理和详细的讲解步骤,我就不再详细讲解了。
  51. */
  52.    for(i=0;i<(const_array_size-1);i++)  //冒泡的次数是(const_array_size-1)次
  53.    {
  54.       for(k=0;k<(const_array_size-1-i);k++) //每次冒泡的过程中,需要两两比较的次数是(const_array_size-1-i)
  55.           {
  56.              if(ucGlobalBuffer_1[const_array_size-1-k]>ucGlobalBuffer_1[const_array_size-1-1-k])  //后一个与前一个数据两两比较
  57.                  {
  58.                      ucTemp=ucGlobalBuffer_1[const_array_size-1-1-k];     //通过一个中间变量实现两个数据交换
  59.              ucGlobalBuffer_1[const_array_size-1-1-k]=ucGlobalBuffer_1[const_array_size-1-k];
  60.              ucGlobalBuffer_1[const_array_size-1-k]=ucTemp;
  61.                  }
  62.           
  63.           }
  64.    }

  65. }

  66. /* 注释三:
  67. * 第2种方法,为了改进第1种方法的用户体验,用指针为函数增加一个输入接口。
  68. * 为什么要用指针?因为C语言的函数中,数组不能直接用来做函数的形参,只能用指针作为数组的形参。
  69. * 比如,你不能这样写一个函数void big_to_small_sort_2(unsigned char a[5]),否则编译就会出错不通过。
  70. * 在本函数中,*p_ucInputBuffer指针就是输入接口,而输出接口仍然是全局变量数组ucGlobalBuffer_2。
  71. * 这种方法由于为函数多增加了一个数组输入接口,已经比第1种方法更加直观了。
  72. */
  73. void big_to_small_sort_2(unsigned char *p_ucInputBuffer)//第2种方法 把一个数组从大小小排序
  74. {
  75.    unsigned char i;
  76.    unsigned char k;
  77.    unsigned char ucTemp; //在两两交换数据的过程中,用于临时存放交换的某个变量


  78.    for(i=0;i<const_array_size;i++)  
  79.    {
  80.       ucGlobalBuffer_2[i]=p_ucInputBuffer[i];  //参与排序算法之前,先把输入接口的数据全部搬移到全局变量数组中。
  81.    }


  82.    //以下就是著名的 冒泡法排序。详细讲解请找百度。
  83.    for(i=0;i<(const_array_size-1);i++)  //冒泡的次数是(const_array_size-1)次
  84.    {
  85.       for(k=0;k<(const_array_size-1-i);k++) //每次冒泡的过程中,需要两两比较的次数是(const_array_size-1-i)
  86.           {
  87.              if(ucGlobalBuffer_2[const_array_size-1-k]>ucGlobalBuffer_2[const_array_size-1-1-k])  //后一个与前一个数据两两比较
  88.                  {
  89.                      ucTemp=ucGlobalBuffer_2[const_array_size-1-1-k];     //通过一个中间变量实现两个数据交换
  90.              ucGlobalBuffer_2[const_array_size-1-1-k]=ucGlobalBuffer_2[const_array_size-1-k];
  91.              ucGlobalBuffer_2[const_array_size-1-k]=ucTemp;
  92.                  }
  93.           
  94.           }
  95.    }

  96. }



  97. void usart_service(void)  //串口服务程序,在main函数里
  98. {

  99.      unsigned char i=0;   

  100.      if(uiSendCnt>=const_receive_time&&ucSendLock==1) //说明超过了一定的时间内,再也没有新数据从串口来
  101.      {

  102.             ucSendLock=0;    //处理一次就锁起来,不用每次都进来,除非有新接收的数据

  103.             //下面的代码进入数据协议解析和数据处理的阶段

  104.             uiRcMoveIndex=0; //由于是判断数据头,所以下标移动变量从数组的0开始向最尾端移动

  105.             while(uiRcregTotal>=5&&uiRcMoveIndex<=(uiRcregTotal-5))
  106.             {
  107.                if(ucRcregBuf[uiRcMoveIndex+0]==0xeb&&ucRcregBuf[uiRcMoveIndex+1]==0x00&&ucRcregBuf[uiRcMoveIndex+2]==0x55)  //数据头eb 00 55的判断
  108.                {


  109.                                   for(i=0;i<const_array_size;i++)
  110.                                   {
  111.                      ucUsartBuffer[i]=ucRcregBuf[uiRcMoveIndex+3+i]; //从串口接收到的需要被排序的原始数据
  112.                                   }


  113.                   //第1种运算方法,依靠全局变量
  114.                                   for(i=0;i<const_array_size;i++)
  115.                                   {
  116.                                      ucGlobalBuffer_1[i]=ucUsartBuffer[i];  //把需要被排列的数据放进输入全局变量数组
  117.                                   }
  118.                   big_to_small_sort_1(); //调用一次空函数就出结果了,结果还是保存在ucGlobalBuffer_1全局变量数组中
  119.                   for(i=0;i<const_array_size;i++)
  120.                                   {
  121.                                     eusart_send(ucGlobalBuffer_1[i]);  ////把用第1种方法排序后的结果返回给上位机观察
  122.                                   }


  123.                                   eusart_send(0xee);  //为了方便上位机观察,多发送3个字节ee ee ee作为第1种方法与第2种方法的分割线
  124.                                   eusart_send(0xee);
  125.                                   eusart_send(0xee);

  126.                   //第2种运算方法,依靠指针为函数增加一个数组的输入接口
  127.                                   //通过指针输入接口,直接把ucUsartBuffer数组的首地址传址进去,排序后输出的结果还是保存在ucGlobalBuffer_2全局变量数组中
  128.                   big_to_small_sort_2(ucUsartBuffer);
  129.                   for(i=0;i<const_array_size;i++)
  130.                                   {
  131.                                     eusart_send(ucGlobalBuffer_2[i]);  //把用第2种方法排序后的结果返回给上位机观察
  132.                                   }





  133.                   break;   //退出循环
  134.                }
  135.                uiRcMoveIndex++; //因为是判断数据头,游标向着数组最尾端的方向移动
  136.            }
  137.                                          
  138.            uiRcregTotal=0;  //清空缓冲的下标,方便下次重新从0下标开始接受新数据
  139.   
  140.      }
  141.                         
  142. }

  143. void eusart_send(unsigned char ucSendData) //往上位机发送一个字节的函数
  144. {

  145.   ES = 0; //关串口中断
  146.   TI = 0; //清零串口发送完成中断请求标志
  147.   SBUF =ucSendData; //发送一个字节

  148.   delay_short(400);  //每个字节之间的延时,这里非常关键,也是最容易出错的地方。延时的大小请根据实际项目来调整

  149.   TI = 0; //清零串口发送完成中断请求标志
  150.   ES = 1; //允许串口中断

  151. }



  152. void T0_time(void) interrupt 1    //定时中断
  153. {
  154.   TF0=0;  //清除中断标志
  155.   TR0=0; //关中断


  156.   if(uiSendCnt<const_receive_time)   //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完
  157.   {
  158.           uiSendCnt++;    //表面上这个数据不断累加,但是在串口中断里,每接收一个字节它都会被清零,除非这个中间没有串口数据过来
  159.       ucSendLock=1;     //开自锁标志
  160.   }



  161.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  162.   TL0=0x0b;
  163.   TR0=1;  //开中断
  164. }


  165. void usart_receive(void) interrupt 4                 //串口接收数据中断        
  166. {        

  167.    if(RI==1)  
  168.    {
  169.         RI = 0;

  170.             ++uiRcregTotal;
  171.         if(uiRcregTotal>const_rc_size)  //超过缓冲区
  172.         {
  173.            uiRcregTotal=const_rc_size;
  174.         }
  175.         ucRcregBuf[uiRcregTotal-1]=SBUF;   //将串口接收到的数据缓存到接收缓冲区里
  176.         uiSendCnt=0;  //及时喂狗,虽然main函数那边不断在累加,但是只要串口的数据还没发送完毕,那么它永远也长不大,因为每个中断都被清零。
  177.    
  178.    }
  179.    else  //发送中断,及时把发送中断标志位清零
  180.    {
  181.         TI = 0;
  182.    }
  183.                                                          
  184. }                                


  185. void delay_long(unsigned int uiDelayLong)
  186. {
  187.    unsigned int i;
  188.    unsigned int j;
  189.    for(i=0;i<uiDelayLong;i++)
  190.    {
  191.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  192.           {
  193.              ; //一个分号相当于执行一条空语句
  194.           }
  195.    }
  196. }

  197. void delay_short(unsigned int uiDelayShort)
  198. {
  199.    unsigned int i;  
  200.    for(i=0;i<uiDelayShort;i++)
  201.    {
  202.      ;   //一个分号相当于执行一条空语句
  203.    }
  204. }


  205. void initial_myself(void)  //第一区 初始化单片机
  206. {

  207.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  208.   //配置定时器
  209.   TMOD=0x01;  //设置定时器0为工作方式1
  210.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  211.   TL0=0x0b;


  212.   //配置串口
  213.   SCON=0x50;
  214.   TMOD=0X21;
  215.   TH1=TL1=-(11059200L/12/32/9600);  //这段配置代码具体是什么意思,我也不太清楚,反正是跟串口波特率有关。
  216.   TR1=1;

  217. }

  218. void initial_peripheral(void) //第二区 初始化外围
  219. {

  220.    EA=1;     //开总中断
  221.    ES=1;     //允许串口中断
  222.    ET0=1;    //允许定时中断
  223.    TR0=1;    //启动定时中断

  224. }
复制代码

总结陈词:
第2种方法通过指针,为函数增加了一个数组输入接口,已经比第1种纯粹用全局变量的方法直观多了,但是还有一个小小的遗憾,因为它的输出排序结果仍然要靠全局变量。为了让函数更加完美,我们能不能为函数再增加一个输出接口?当然可以。欲知详情,请听下回分解-----指针的第三大好处,指针作为数组在函数中的输出接口。

(未完待续,下节更精彩,不要走开哦)
此帖出自51单片机论坛
 
 
 

回复

3

帖子

0

TA的资源

禁止发言

80
 
可能我看得不是太仔细,LZ你第一节说误区的时候,我觉得这不是误区,而是你自己的一些想法。
这不能称为误区,因为你写的是教程,不能这样引导初学者进入一个你认为是误区的学习方向!!
寄存器暂且不讨论了,因为到后面你调试项目的时候肯定会涉及到,因为谁也不能担保自己的程序一写好就能够按预计的跑。入栈,出栈顺序什么的,有没有用就按个人的想法。
汇编浪费时间,这保留,因为我自己也只是知道最基本的,在反编译的时候看一眼。但是不能认为学习他是浪费时间的。
C语言的各种不用学。。你自己到后面肯定也有用到,为什么要特地把他设为一个误区呢?
此帖出自51单片机论坛
 
 
 

回复
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/8 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表